Remove fyydlin and connect directly (#8327)

### Description

Remove fyydlin and connect directly. JitPack is not reliable. Because
the fyydlin library is so simple, we don't really need it and can just
connect directly to fyyd. This makes it possible to remove one library
hosted on JitPack. Also taking the chance to make `PodcastSearchResult`
independent from actual providers, so it becomes more of an
interface/model class.

### Checklist
<!-- 
To help us keep the issue tracker clean and work as efficient as
possible,
  please make sure that you have done all of the following.
You can tick the boxes below by placing an x inside the brackets like
this: [x]
-->
- [x] I have read the contribution guidelines:
https://github.com/AntennaPod/AntennaPod/blob/develop/CONTRIBUTING.md#submit-a-pull-request
- [x] I have performed a self-review of my code, going through my
changes line by line and carefully considering why this line change is
necessary
- [x] I have run the automated code checks using `./gradlew checkstyle
spotbugsPlayDebug spotbugsDebug :app:lintPlayDebug`
- [x] My code follows the style guidelines of the AntennaPod project:
https://antennapod.org/contribute/develop/app/code-style
- [x] I have mentioned the corresponding issue and the relevant keyword
(e.g., "Closes: #xy") in the description (see
https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue)
- [x] If it is a core feature, I have added automated tests
This commit is contained in:
Hans-Peter Lehmann
2026-03-08 17:13:03 +01:00
committed by GitHub
parent 95d3f37d25
commit 57e8ce6fbb
9 changed files with 87 additions and 95 deletions

1
.gitignore vendored
View File

@@ -29,3 +29,4 @@ gradle.properties
*.p12
.vscode
.cursor
*.hprof

View File

@@ -34,8 +34,6 @@
# RxJava
-keep class io.reactivex.Single
#### Proguard rules for fyyd client
-keep class de.mfietz.fyydlin.** { *; }
# Retrofit 2.0
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }

View File

@@ -26,8 +26,6 @@ dependencies {
annotationProcessor "androidx.annotation:annotation:$annotationVersion"
implementation 'com.github.mfietz:fyydlin:e1d25f347641fe5e26c4c2b9756d4e9001a398dd'
implementation "io.reactivex.rxjava3:rxandroid:$rxAndroidVersion"
implementation "io.reactivex.rxjava3:rxjava:$rxJavaVersion"
implementation "com.squareup.okhttp3:okhttp:$okhttpVersion"

View File

@@ -1,35 +1,68 @@
package de.danoeh.antennapod.net.discovery;
import de.danoeh.antennapod.net.common.AntennapodHttpClient;
import de.mfietz.fyydlin.FyydClient;
import de.mfietz.fyydlin.FyydResponse;
import de.mfietz.fyydlin.SearchHit;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.SingleOnSubscribe;
import io.reactivex.rxjava3.schedulers.Schedulers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
public class FyydPodcastSearcher implements PodcastSearcher {
private final FyydClient client = new FyydClient(AntennapodHttpClient.getHttpClient());
private static final String FYYD_API_URL = "https://api.fyyd.de/0.2/search/podcast?title=%s&count=10";
public Single<List<PodcastSearchResult>> search(String query) {
return Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) subscriber -> {
FyydResponse response = client.searchPodcasts(query, 10)
.subscribeOn(Schedulers.io())
.blockingGet();
ArrayList<PodcastSearchResult> searchResults = new ArrayList<>();
if (!response.getData().isEmpty()) {
for (SearchHit searchHit : response.getData()) {
PodcastSearchResult podcast = PodcastSearchResult.fromFyyd(searchHit);
searchResults.add(podcast);
}
String encodedQuery;
try {
encodedQuery = URLEncoder.encode(query, "UTF-8");
} catch (UnsupportedEncodingException e) {
encodedQuery = query;
}
String formattedUrl = String.format(FYYD_API_URL, encodedQuery);
OkHttpClient client = AntennapodHttpClient.getHttpClient();
Request.Builder httpReq = new Request.Builder().url(formattedUrl);
ArrayList<PodcastSearchResult> searchResults = new ArrayList<>();
try (Response response = client.newCall(httpReq.build()).execute()) {
if (!response.isSuccessful()) {
subscriber.onError(new IOException(response.toString()));
return;
}
if (response.body() == null) {
subscriber.onError(new IOException("Null response"));
return;
}
String resultString = response.body().string();
JSONObject result = new JSONObject(resultString);
JSONArray data = result.optJSONArray("data");
if (data == null) {
subscriber.onError(new IOException("Null response"));
return;
}
for (int i = 0; i < data.length(); i++) {
JSONObject podcastJson = data.getJSONObject(i);
String title = podcastJson.optString("title", "Unknown");
String imageUrl = podcastJson.optString("thumbImageURL", "");
String feedUrl = podcastJson.optString("xmlURL", "");
String author = podcastJson.optString("author", "Unknown");
searchResults.add(new PodcastSearchResult(title, imageUrl, feedUrl, author));
}
} catch (IOException | JSONException e) {
subscriber.onError(e);
return;
}
subscriber.onSuccess(searchResults);
})
.subscribeOn(Schedulers.io())

View File

@@ -54,10 +54,14 @@ public class ItunesPodcastSearcher implements PodcastSearcher {
for (int i = 0; i < j.length(); i++) {
JSONObject podcastJson = j.getJSONObject(i);
PodcastSearchResult podcast = PodcastSearchResult.fromItunes(podcastJson);
if (podcast.feedUrl != null) {
podcasts.add(podcast);
if (!podcastJson.has("feedUrl")) {
continue;
}
String title = podcastJson.optString("collectionName", "Unknown");
String imageUrl = podcastJson.optString("artworkUrl100", "");
String feedUrl = podcastJson.optString("feedUrl", "");
String author = podcastJson.optString("artistName", "Unknown");
podcasts.add(new PodcastSearchResult(title, imageUrl, feedUrl, author));
}
} else {
subscriber.onError(new IOException(response.toString()));

View File

@@ -108,10 +108,32 @@ public class ItunesTopListLoader {
List<PodcastSearchResult> results = new ArrayList<>();
for (int i = 0; i < entries.length(); i++) {
JSONObject json = entries.getJSONObject(i);
results.add(PodcastSearchResult.fromItunesToplist(json));
results.add(toSearchResult(json));
}
return results;
}
private static PodcastSearchResult toSearchResult(JSONObject json) throws JSONException {
String title = json.getJSONObject("im:name").getString("label");
String imageUrl = null;
JSONArray images = json.getJSONArray("im:image");
for (int i = 0; imageUrl == null && i < images.length(); i++) {
JSONObject image = images.getJSONObject(i);
String height = image.getJSONObject("attributes").getString("height");
if (Integer.parseInt(height) >= 100) {
imageUrl = image.getString("label");
}
}
String feedUrl = "https://itunes.apple.com/lookup?id="
+ json.getJSONObject("id").getJSONObject("attributes").getString("im:id");
String author = null;
try {
author = json.getJSONObject("im:artist").getString("label");
} catch (Exception e) {
// Some feeds have empty artist
}
return new PodcastSearchResult(title, imageUrl, feedUrl, author);
}
}

View File

@@ -54,10 +54,14 @@ public class PodcastIndexPodcastSearcher implements PodcastSearcher {
for (int i = 0; i < j.length(); i++) {
JSONObject podcastJson = j.getJSONObject(i);
PodcastSearchResult podcast = PodcastSearchResult.fromPodcastIndex(podcastJson);
if (podcast.feedUrl != null) {
podcasts.add(podcast);
if (!podcastJson.has("url")) {
continue;
}
String title = podcastJson.optString("title", "Unknown");
String imageUrl = podcastJson.optString("image", "");
String feedUrl = podcastJson.optString("url", "");
String author = podcastJson.optString("author", "Unknown");
podcasts.add(new PodcastSearchResult(title, imageUrl, feedUrl, author));
}
} else {
subscriber.onError(new IOException(response.toString()));

View File

@@ -1,10 +1,6 @@
package de.danoeh.antennapod.net.discovery;
import androidx.annotation.Nullable;
import de.mfietz.fyydlin.SearchHit;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class PodcastSearchResult {
@@ -31,7 +27,7 @@ public class PodcastSearchResult {
public final String author;
private PodcastSearchResult(String title, @Nullable String imageUrl, @Nullable String feedUrl, @Nullable String author) {
public PodcastSearchResult(String title, @Nullable String imageUrl, @Nullable String feedUrl, @Nullable String author) {
this.title = title;
this.imageUrl = imageUrl;
this.feedUrl = feedUrl;
@@ -41,62 +37,4 @@ public class PodcastSearchResult {
public static PodcastSearchResult dummy() {
return new PodcastSearchResult("", "", "", "");
}
/**
* Constructs a Podcast instance from a iTunes search result
*
* @param json object holding the podcast information
* @throws JSONException
*/
public static PodcastSearchResult fromItunes(JSONObject json) {
String title = json.optString("collectionName", "");
String imageUrl = json.optString("artworkUrl100", null);
String feedUrl = json.optString("feedUrl", null);
String author = json.optString("artistName", null);
return new PodcastSearchResult(title, imageUrl, feedUrl, author);
}
/**
* Constructs a Podcast instance from iTunes toplist entry
*
* @param json object holding the podcast information
* @throws JSONException
*/
public static PodcastSearchResult fromItunesToplist(JSONObject json) throws JSONException {
String title = json.getJSONObject("im:name").getString("label");
String imageUrl = null;
JSONArray images = json.getJSONArray("im:image");
for(int i=0; imageUrl == null && i < images.length(); i++) {
JSONObject image = images.getJSONObject(i);
String height = image.getJSONObject("attributes").getString("height");
if(Integer.parseInt(height) >= 100) {
imageUrl = image.getString("label");
}
}
String feedUrl = "https://itunes.apple.com/lookup?id=" +
json.getJSONObject("id").getJSONObject("attributes").getString("im:id");
String author = null;
try {
author = json.getJSONObject("im:artist").getString("label");
} catch (Exception e) {
// Some feeds have empty artist
}
return new PodcastSearchResult(title, imageUrl, feedUrl, author);
}
public static PodcastSearchResult fromFyyd(SearchHit searchHit) {
return new PodcastSearchResult(searchHit.getTitle(),
searchHit.getThumbImageURL(),
searchHit.getXmlUrl(),
searchHit.getAuthor());
}
public static PodcastSearchResult fromPodcastIndex(JSONObject json) {
String title = json.optString("title", "");
String imageUrl = json.optString("image", null);
String feedUrl = json.optString("url", null);
String author = json.optString("author", null);
return new PodcastSearchResult(title, imageUrl, feedUrl, author);
}
}

View File

@@ -42,12 +42,6 @@
website="https://github.com/google/ExoPlayer"
license="Apache 2.0"
licenseText="LICENSE_APACHE-2.0.txt" />
<library
name="fyydlin"
author="Martin Fietz"
website="https://github.com/mfietz/fyydlin"
license="Apache 2.0"
licenseText="LICENSE_APACHE-2.0.txt" />
<library
name="Glide"
author="bumptech"