Added podcastindex.org search (#4435)

This commit is contained in:
Edwin J P 2020-09-29 15:46:15 +08:00 committed by GitHub
parent e31a833361
commit 61ed58074e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 155 additions and 0 deletions

View File

@ -30,6 +30,8 @@ android {
} catch (Exception ignore) {
}
buildConfigField "String", "COMMIT_HASH", ('"' + commit + '"')
buildConfigField "String", "PODCASTINDEX_API_KEY", '"JRJPPWC6ZA7DKKTSU2R3"'
buildConfigField "String", "PODCASTINDEX_API_SECRET", '"7$$67JtrfkSYtAncGBEaJp$v$Y9$ZJUzYVy8GuBm"'
javaCompileOptions {
annotationProcessorOptions {

View File

@ -0,0 +1,126 @@
package de.danoeh.antennapod.discovery;
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.security.MessageDigest;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import de.danoeh.antennapod.BuildConfig;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
import io.reactivex.Single;
import io.reactivex.SingleOnSubscribe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class PodcastIndexPodcastSearcher implements PodcastSearcher {
private static final String PODCASTINDEX_API_URL = "https://api.podcastindex.org/api/1.0/search/byterm?q=%s";
public PodcastIndexPodcastSearcher() {
}
@Override
public Single<List<PodcastSearchResult>> search(String query) {
return Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) subscriber -> {
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
calendar.clear();
Date now = new Date();
calendar.setTime(now);
long secondsSinceEpoch = calendar.getTimeInMillis() / 1000L;
String apiHeaderTime = String.valueOf(secondsSinceEpoch);
String data4Hash = BuildConfig.PODCASTINDEX_API_KEY + BuildConfig.PODCASTINDEX_API_SECRET + apiHeaderTime;
String hashString = sha1(data4Hash);
String encodedQuery;
try {
encodedQuery = URLEncoder.encode(query, "UTF-8");
} catch (UnsupportedEncodingException e) {
// this won't ever be thrown
encodedQuery = query;
}
String formattedUrl = String.format(PODCASTINDEX_API_URL, encodedQuery);
OkHttpClient client = AntennapodHttpClient.getHttpClient();
Request.Builder httpReq = new Request.Builder()
.addHeader("X-Auth-Date", apiHeaderTime)
.addHeader("X-Auth-Key", BuildConfig.PODCASTINDEX_API_KEY)
.addHeader("Authorization", hashString)
.addHeader("User-Agent", ClientConfig.USER_AGENT)
.url(formattedUrl);
List<PodcastSearchResult> podcasts = new ArrayList<>();
try {
Response response = client.newCall(httpReq.build()).execute();
if (response.isSuccessful()) {
String resultString = response.body().string();
JSONObject result = new JSONObject(resultString);
JSONArray j = result.getJSONArray("feeds");
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);
}
}
} else {
subscriber.onError(new IOException(response.toString()));
}
} catch (IOException | JSONException e) {
subscriber.onError(e);
}
subscriber.onSuccess(podcasts);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
@Override
public Single<String> lookupUrl(String url) {
return Single.just(url);
}
@Override
public boolean urlNeedsLookup(String url) {
return false;
}
@Override
public String getName() {
return "Podcastindex.org";
}
private static String sha1(String clearString) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
messageDigest.update(clearString.getBytes("UTF-8"));
return toHex(messageDigest.digest());
} catch (Exception ignored) {
ignored.printStackTrace();
return null;
}
}
private static String toHex(byte[] bytes) {
StringBuilder buffer = new StringBuilder();
for (byte b : bytes) {
buffer.append(String.format(Locale.getDefault(), "%02x", b));
}
return buffer.toString();
}
}

View File

@ -103,4 +103,12 @@ public class PodcastSearchResult {
searchHit.getUrl(),
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

@ -19,6 +19,7 @@ public class PodcastSearcherRegistry {
searchProviders.add(new SearcherInfo(new ItunesPodcastSearcher(), 1.f));
searchProviders.add(new SearcherInfo(new FyydPodcastSearcher(), 1.f));
searchProviders.add(new SearcherInfo(new GpodnetPodcastSearcher(), 0.0f));
searchProviders.add(new SearcherInfo(new PodcastIndexPodcastSearcher(), 0.0f));
}
return searchProviders;
}

View File

@ -26,6 +26,7 @@ import de.danoeh.antennapod.activity.OpmlImportActivity;
import de.danoeh.antennapod.discovery.CombinedSearcher;
import de.danoeh.antennapod.discovery.FyydPodcastSearcher;
import de.danoeh.antennapod.discovery.ItunesPodcastSearcher;
import de.danoeh.antennapod.discovery.PodcastIndexPodcastSearcher;
import de.danoeh.antennapod.fragment.gpodnet.GpodnetMainFragment;
/**
@ -55,6 +56,8 @@ public class AddFeedFragment extends Fragment {
-> activity.loadChildFragment(OnlineSearchFragment.newInstance(FyydPodcastSearcher.class)));
root.findViewById(R.id.btn_search_gpodder).setOnClickListener(v
-> activity.loadChildFragment(new GpodnetMainFragment()));
root.findViewById(R.id.btn_search_podcastindex).setOnClickListener(v
-> activity.loadChildFragment(OnlineSearchFragment.newInstance(PodcastIndexPodcastSearcher.class)));
combinedFeedSearchBox = root.findViewById(R.id.combinedFeedSearchBox);
combinedFeedSearchBox.setOnEditorActionListener((v, actionId, event) -> {

View File

@ -142,6 +142,20 @@
android:clickable="true"
android:text="@string/browse_gpoddernet_label"/>
<TextView
android:id="@+id/btn_search_podcastindex"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawablePadding="8dp"
app:drawableStartCompat="?attr/action_search"
app:drawableLeftCompat="?attr/action_search"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:background="?android:attr/selectableItemBackground"
android:textColor="?android:attr/textColorPrimary"
android:clickable="true"
android:text="@string/search_podcastindex_label"/>
<TextView
android:id="@+id/btn_opml_import"
android:layout_width="match_parent"

View File

@ -714,6 +714,7 @@
<!-- Add podcast fragment -->
<string name="search_podcast_hint">Search podcast…</string>
<string name="search_itunes_label">Search iTunes</string>
<string name="search_podcastindex_label">Search Podcastindex.org</string>
<string name="search_fyyd_label">Search fyyd</string>
<string name="advanced">Advanced</string>
<string name="add_podcast_by_url">Add Podcast by RSS address</string>