diff --git a/README.md b/README.md index fa2338d0b..00bcb93df 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ The following sites are currently supported: - SoundCloud - media.ccc.de - PeerTube (no P2P) +- Bandcamp ## License diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/ServiceList.java b/extractor/src/main/java/org/schabi/newpipe/extractor/ServiceList.java index 83d8522f5..5bfc5692f 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/ServiceList.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/ServiceList.java @@ -1,5 +1,6 @@ package org.schabi.newpipe.extractor; +import org.schabi.newpipe.extractor.services.bandcamp.BandcampService; import org.schabi.newpipe.extractor.services.media_ccc.MediaCCCService; import org.schabi.newpipe.extractor.services.peertube.PeertubeService; import org.schabi.newpipe.extractor.services.soundcloud.SoundcloudService; @@ -39,6 +40,7 @@ public final class ServiceList { public static final SoundcloudService SoundCloud; public static final MediaCCCService MediaCCC; public static final PeertubeService PeerTube; + public static final BandcampService Bandcamp; /** * When creating a new service, put this service in the end of this list, @@ -49,7 +51,8 @@ public final class ServiceList { YouTube = new YoutubeService(0), SoundCloud = new SoundcloudService(1), MediaCCC = new MediaCCCService(2), - PeerTube = new PeertubeService(3) + PeerTube = new PeertubeService(3), + Bandcamp = new BandcampService(4) )); /** diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/linkhandler/ListLinkHandlerFactory.java b/extractor/src/main/java/org/schabi/newpipe/extractor/linkhandler/ListLinkHandlerFactory.java index 86991c4df..11b94c7a5 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/linkhandler/ListLinkHandlerFactory.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/linkhandler/ListLinkHandlerFactory.java @@ -4,6 +4,7 @@ import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.utils.Utils; import java.util.ArrayList; +import java.util.Collections; import java.util.List; public abstract class ListLinkHandlerFactory extends LinkHandlerFactory { @@ -13,7 +14,7 @@ public abstract class ListLinkHandlerFactory extends LinkHandlerFactory { /////////////////////////////////// public List getContentFilter(String url) throws ParsingException { - return new ArrayList<>(0); + return Collections.emptyList(); } public String getSortFilter(String url) throws ParsingException { diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampService.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampService.java new file mode 100644 index 000000000..145693285 --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampService.java @@ -0,0 +1,127 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp; + +import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.channel.ChannelExtractor; +import org.schabi.newpipe.extractor.comments.CommentsExtractor; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.kiosk.KioskList; +import org.schabi.newpipe.extractor.linkhandler.*; +import org.schabi.newpipe.extractor.playlist.PlaylistExtractor; +import org.schabi.newpipe.extractor.search.SearchExtractor; +import org.schabi.newpipe.extractor.services.bandcamp.extractors.*; +import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.*; +import org.schabi.newpipe.extractor.stream.StreamExtractor; +import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor; +import org.schabi.newpipe.extractor.suggestion.SuggestionExtractor; + +import java.util.Collections; + +import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.AUDIO; +import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_URL; +import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampFeaturedExtractor.FEATURED_API_URL; +import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampFeaturedExtractor.KIOSK_FEATURED; +import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampRadioExtractor.KIOSK_RADIO; +import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampRadioExtractor.RADIO_API_URL; + +public class BandcampService extends StreamingService { + + public BandcampService(final int id) { + super(id, "Bandcamp", Collections.singletonList(AUDIO)); + } + + @Override + public String getBaseUrl() { + return BASE_URL; + } + + @Override + public LinkHandlerFactory getStreamLHFactory() { + return new BandcampStreamLinkHandlerFactory(); + } + + @Override + public ListLinkHandlerFactory getChannelLHFactory() { + return new BandcampChannelLinkHandlerFactory(); + } + + @Override + public ListLinkHandlerFactory getPlaylistLHFactory() { + return new BandcampPlaylistLinkHandlerFactory(); + } + + @Override + public SearchQueryHandlerFactory getSearchQHFactory() { + return new BandcampSearchQueryHandlerFactory(); + } + + @Override + public ListLinkHandlerFactory getCommentsLHFactory() { + return null; + } + + @Override + public SearchExtractor getSearchExtractor(final SearchQueryHandler queryHandler) { + return new BandcampSearchExtractor(this, queryHandler); + } + + @Override + public SuggestionExtractor getSuggestionExtractor() { + return new BandcampSuggestionExtractor(this); + } + + @Override + public SubscriptionExtractor getSubscriptionExtractor() { + return null; + } + + @Override + public KioskList getKioskList() throws ExtractionException { + + KioskList kioskList = new KioskList(this); + + try { + kioskList.addKioskEntry((streamingService, url, kioskId) -> + new BandcampFeaturedExtractor( + BandcampService.this, + new BandcampFeaturedLinkHandlerFactory().fromUrl(FEATURED_API_URL), kioskId), + new BandcampFeaturedLinkHandlerFactory(), KIOSK_FEATURED); + + kioskList.addKioskEntry((streamingService, url, kioskId) -> + new BandcampRadioExtractor(BandcampService.this, + new BandcampFeaturedLinkHandlerFactory().fromUrl(RADIO_API_URL), kioskId), + new BandcampFeaturedLinkHandlerFactory(), KIOSK_RADIO); + + kioskList.setDefaultKiosk(KIOSK_FEATURED); + + } catch (final Exception e) { + throw new ExtractionException(e); + } + + return kioskList; + } + + @Override + public ChannelExtractor getChannelExtractor(final ListLinkHandler linkHandler) { + return new BandcampChannelExtractor(this, linkHandler); + } + + @Override + public PlaylistExtractor getPlaylistExtractor(final ListLinkHandler linkHandler) { + return new BandcampPlaylistExtractor(this, linkHandler); + } + + @Override + public StreamExtractor getStreamExtractor(final LinkHandler linkHandler) { + if (BandcampExtractorHelper.isRadioUrl(linkHandler.getUrl())) + return new BandcampRadioStreamExtractor(this, linkHandler); + else + return new BandcampStreamExtractor(this, linkHandler); + } + + @Override + public CommentsExtractor getCommentsExtractor(ListLinkHandler linkHandler) { + return null; + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampChannelExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampChannelExtractor.java new file mode 100644 index 000000000..ef2835d26 --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampChannelExtractor.java @@ -0,0 +1,137 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp.extractors; + +import com.grack.nanojson.JsonArray; +import com.grack.nanojson.JsonObject; +import org.jsoup.Jsoup; +import org.schabi.newpipe.extractor.Page; +import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.channel.ChannelExtractor; +import org.schabi.newpipe.extractor.downloader.Downloader; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; +import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler; +import org.schabi.newpipe.extractor.services.bandcamp.extractors.streaminfoitem.BandcampDiscographStreamInfoItemExtractor; +import org.schabi.newpipe.extractor.stream.StreamInfoItem; +import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector; + +import javax.annotation.Nonnull; +import java.io.IOException; + +public class BandcampChannelExtractor extends ChannelExtractor { + + private JsonObject channelInfo; + + public BandcampChannelExtractor(final StreamingService service, final ListLinkHandler linkHandler) { + super(service, linkHandler); + } + + @Override + public String getAvatarUrl() { + if (channelInfo.getLong("bio_image_id") == 0) return ""; + + return BandcampExtractorHelper.getImageUrl(channelInfo.getLong("bio_image_id"), false); + } + + @Override + public String getBannerUrl() throws ParsingException { + /* + * Why does the mobile endpoint not contain the header?? Or at least not the same one? + * Anyway we're back to querying websites + */ + try { + final String html = getDownloader() + .get(channelInfo.getString("bandcamp_url").replace("http://", "https://")) + .responseBody(); + + return Jsoup.parse(html) + .getElementById("customHeader") + .getElementsByTag("img") + .first() + .attr("src"); + + } catch (final IOException | ReCaptchaException e) { + throw new ParsingException("Could not download artist web site", e); + } catch (final NullPointerException e) { + // No banner available + return ""; + } + } + + /** + * bandcamp stopped providing RSS feeds when appending /feed to any URL + * because too few people used it. + */ + @Override + public String getFeedUrl() { + return null; + } + + @Override + public long getSubscriberCount() { + return -1; + } + + @Override + public String getDescription() { + return channelInfo.getString("bio"); + } + + @Override + public String getParentChannelName() { + return null; + } + + @Override + public String getParentChannelUrl() { + return null; + } + + @Override + public String getParentChannelAvatarUrl() { + return null; + } + + @Override + public boolean isVerified() throws ParsingException { + return false; + } + + @Nonnull + @Override + public InfoItemsPage getInitialPage() throws ParsingException { + + final StreamInfoItemsCollector collector = new StreamInfoItemsCollector(getServiceId()); + + final JsonArray discography = channelInfo.getArray("discography"); + + for (int i = 0; i < discography.size(); i++) { + // I define discograph as an item that can appear in a discography + final JsonObject discograph = discography.getObject(i); + + if (!discograph.getString("item_type").equals("track")) continue; + + collector.commit(new BandcampDiscographStreamInfoItemExtractor(discograph, getUrl())); + } + + return new InfoItemsPage<>(collector, null); + } + + @Override + public InfoItemsPage getPage(Page page) { + return null; + } + + @Override + public void onFetchPage(@Nonnull Downloader downloader) throws IOException, ExtractionException { + channelInfo = BandcampExtractorHelper.getArtistDetails(getId()); + } + + @Nonnull + @Override + public String getName() { + return channelInfo.getString("name"); + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampChannelInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampChannelInfoItemExtractor.java new file mode 100644 index 000000000..d25c82656 --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampChannelInfoItemExtractor.java @@ -0,0 +1,58 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp.extractors; + +import org.jsoup.nodes.Element; +import org.schabi.newpipe.extractor.channel.ChannelInfoItemExtractor; +import org.schabi.newpipe.extractor.exceptions.ParsingException; + +public class BandcampChannelInfoItemExtractor implements ChannelInfoItemExtractor { + + private final Element resultInfo, searchResult; + + public BandcampChannelInfoItemExtractor(final Element searchResult) { + this.searchResult = searchResult; + resultInfo = searchResult.getElementsByClass("result-info").first(); + } + + @Override + public String getName() throws ParsingException { + return resultInfo.getElementsByClass("heading").text(); + } + + @Override + public String getUrl() throws ParsingException { + return resultInfo.getElementsByClass("itemurl").text(); + } + + @Override + public String getThumbnailUrl() throws ParsingException { + final Element img = searchResult.getElementsByClass("art").first() + .getElementsByTag("img").first(); + if (img != null) { + return img.attr("src"); + } else { + return null; + } + } + + @Override + public String getDescription() { + return resultInfo.getElementsByClass("subhead").text(); + } + + @Override + public long getSubscriberCount() { + return -1; + } + + @Override + public long getStreamCount() { + return -1; + } + + @Override + public boolean isVerified() throws ParsingException { + return false; + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampExtractorHelper.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampExtractorHelper.java new file mode 100644 index 000000000..547a0356e --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampExtractorHelper.java @@ -0,0 +1,128 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp.extractors; + +import com.grack.nanojson.JsonObject; +import com.grack.nanojson.JsonParser; +import com.grack.nanojson.JsonParserException; +import com.grack.nanojson.JsonWriter; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; +import org.schabi.newpipe.extractor.localization.DateWrapper; +import org.schabi.newpipe.extractor.utils.Utils; + +import java.io.IOException; +import java.time.DateTimeException; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +public class BandcampExtractorHelper { + + public static final String BASE_URL = "https://bandcamp.com"; + public static final String BASE_API_URL = BASE_URL + "/api"; + + /** + * Translate all these parameters together to the URL of the corresponding album or track + * using the mobile API + */ + public static String getStreamUrlFromIds(final long bandId, final long itemId, final String itemType) + throws ParsingException { + + try { + final String jsonString = NewPipe.getDownloader().get( + BASE_API_URL + "/mobile/22/tralbum_details?band_id=" + bandId + + "&tralbum_id=" + itemId + "&tralbum_type=" + itemType.charAt(0)) + .responseBody(); + + return JsonParser.object().from(jsonString) + .getString("bandcamp_url").replace("http://", "https://"); + + } catch (final JsonParserException | ReCaptchaException | IOException e) { + throw new ParsingException("Ids could not be translated to URL", e); + } + + } + + /** + * Fetch artist details from mobile endpoint. + * + * More technical info. + */ + public static JsonObject getArtistDetails(String id) throws ParsingException { + try { + return + JsonParser.object().from( + NewPipe.getDownloader().post( + BASE_API_URL + "/mobile/22/band_details", + null, + JsonWriter.string() + .object() + .value("band_id", id) + .end() + .done() + .getBytes() + ).responseBody() + ); + } catch (final IOException | ReCaptchaException | JsonParserException e) { + throw new ParsingException("Could not download band details", e); + } + } + + /** + * @param id The image ID + * @param album Whether this is the cover of an album + * @return URL of image with this ID in size 10 which is 1200x1200 (we could also choose size 0 + * but we don't want something as large as 3460x3460 here) + */ + public static String getImageUrl(final long id, final boolean album) { + return "https://f4.bcbits.com/img/" + (album ? 'a' : "") + id + "_10.jpg"; + } + + /** + * @return true if the given URL looks like it comes from a bandcamp custom domain + * or if it comes from bandcamp.com itself + */ + public static boolean isSupportedDomain(final String url) throws ParsingException { + + // Accept all bandcamp.com URLs + if (url.toLowerCase().matches("https?://.+\\.bandcamp\\.com(/.*)?")) return true; + + try { + // Accept all other URLs if they contain a tag that says they are generated by bandcamp + return Jsoup.parse( + NewPipe.getDownloader().get(url).responseBody() + ) + .getElementsByAttributeValue("name", "generator") + .attr("content").equals("Bandcamp"); + } catch (IOException | ReCaptchaException e) { + throw new ParsingException("Could not determine whether URL is custom domain " + + "(not available? network error?)"); + } + } + + /** + * Whether the URL points to a radio kiosk. + * @param url the URL to check + * @return true if the URL matches https://bandcamp.com/?show=SHOW_ID + */ + public static boolean isRadioUrl(final String url) { + return url.toLowerCase().matches("https?://bandcamp\\.com/\\?show=\\d+"); + } + + static DateWrapper parseDate(final String textDate) throws ParsingException { + try { + final ZonedDateTime zonedDateTime = ZonedDateTime.parse( + textDate, DateTimeFormatter.ofPattern("dd MMM yyyy HH:mm:ss zzz", Locale.ENGLISH)); + return new DateWrapper(zonedDateTime.toOffsetDateTime(), false); + } catch (final DateTimeException e) { + throw new ParsingException("Could not parse date '" + textDate + "'", e); + } + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampFeaturedExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampFeaturedExtractor.java new file mode 100644 index 000000000..93b1b0297 --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampFeaturedExtractor.java @@ -0,0 +1,84 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp.extractors; + +import com.grack.nanojson.JsonArray; +import com.grack.nanojson.JsonObject; +import com.grack.nanojson.JsonParser; +import com.grack.nanojson.JsonParserException; +import org.schabi.newpipe.extractor.Page; +import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.downloader.Downloader; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.kiosk.KioskExtractor; +import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler; +import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; +import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemsCollector; + +import javax.annotation.Nonnull; +import java.io.IOException; + +import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_API_URL; + +public class BandcampFeaturedExtractor extends KioskExtractor { + + public static final String KIOSK_FEATURED = "Featured"; + public static final String FEATURED_API_URL = BASE_API_URL + "/mobile/24/bootstrap_data"; + + private JsonObject json; + + public BandcampFeaturedExtractor(final StreamingService streamingService, final ListLinkHandler listLinkHandler, + final String kioskId) { + super(streamingService, listLinkHandler, kioskId); + } + + @Override + public void onFetchPage(@Nonnull Downloader downloader) throws IOException, ExtractionException { + try { + json = JsonParser.object().from( + getDownloader().post( + FEATURED_API_URL, null, "{\"platform\":\"\",\"version\":0}".getBytes() + ).responseBody() + ); + } catch (final JsonParserException e) { + throw new ParsingException("Could not parse Bandcamp featured API response", e); + } + } + + @Nonnull + @Override + public String getName() throws ParsingException { + return KIOSK_FEATURED; + } + + @Nonnull + @Override + public InfoItemsPage getInitialPage() throws IOException, ExtractionException { + + final PlaylistInfoItemsCollector c = new PlaylistInfoItemsCollector(getServiceId()); + + final JsonArray featuredStories = json.getObject("feed_content") + .getObject("stories") + .getArray("featured"); + + for (int i = 0; i < featuredStories.size(); i++) { + final JsonObject featuredStory = featuredStories.getObject(i); + + if (featuredStory.isNull("album_title")) { + // Is not an album, ignore + continue; + } + + c.commit(new BandcampPlaylistInfoItemFeaturedExtractor(featuredStory)); + } + + return new InfoItemsPage<>(c, null); + + } + + @Override + public InfoItemsPage getPage(Page page) { + return null; + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampPlaylistExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampPlaylistExtractor.java new file mode 100644 index 000000000..8cc21a6e7 --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampPlaylistExtractor.java @@ -0,0 +1,165 @@ +package org.schabi.newpipe.extractor.services.bandcamp.extractors; + +import com.grack.nanojson.JsonArray; +import com.grack.nanojson.JsonObject; +import com.grack.nanojson.JsonParserException; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.schabi.newpipe.extractor.Page; +import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.downloader.Downloader; +import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler; +import org.schabi.newpipe.extractor.playlist.PlaylistExtractor; +import org.schabi.newpipe.extractor.services.bandcamp.extractors.streaminfoitem.BandcampPlaylistStreamInfoItemExtractor; +import org.schabi.newpipe.extractor.stream.StreamInfoItem; +import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector; + +import javax.annotation.Nonnull; +import java.io.IOException; + +import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImageUrl; +import static org.schabi.newpipe.extractor.utils.JsonUtils.getJsonData; +import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampStreamExtractor.getAlbumInfoJson; + +public class BandcampPlaylistExtractor extends PlaylistExtractor { + + /** + * An arbitrarily chosen number above which cover arts won't be fetched individually for each track; + * instead, it will be assumed that every track has the same cover art as the album, which is not + * always the case. + */ + private static final int MAXIMUM_INDIVIDUAL_COVER_ARTS = 10; + + private Document document; + private JsonObject albumJson; + private JsonArray trackInfo; + private String name; + + public BandcampPlaylistExtractor(final StreamingService service, final ListLinkHandler linkHandler) { + super(service, linkHandler); + } + + @Override + public void onFetchPage(@Nonnull final Downloader downloader) throws IOException, ExtractionException { + final String html = downloader.get(getLinkHandler().getUrl()).responseBody(); + document = Jsoup.parse(html); + albumJson = getAlbumInfoJson(html); + trackInfo = albumJson.getArray("trackinfo"); + + try { + name = getJsonData(html, "data-embed").getString("album_title"); + } catch (final JsonParserException e) { + throw new ParsingException("Faulty JSON; page likely does not contain album data", e); + } catch (final ArrayIndexOutOfBoundsException e) { + throw new ParsingException("JSON does not exist", e); + } + + + + if (trackInfo.size() <= 0) { + // Albums without trackInfo need to be purchased before they can be played + throw new ContentNotAvailableException("Album needs to be purchased"); + } + } + + @Override + public String getThumbnailUrl() throws ParsingException { + if (albumJson.isNull("art_id")) { + return ""; + } else { + return getImageUrl(albumJson.getLong("art_id"), true); + } + } + + @Override + public String getBannerUrl() { + return ""; + } + + @Override + public String getUploaderUrl() throws ParsingException { + final String[] parts = getUrl().split("/"); + // https: (/) (/) * .bandcamp.com (/) and leave out the rest + return "https://" + parts[2] + "/"; + } + + @Override + public String getUploaderName() { + return albumJson.getString("artist"); + } + + @Override + public String getUploaderAvatarUrl() { + try { + return document.getElementsByClass("band-photo").first().attr("src"); + } catch (NullPointerException e) { + return ""; + } + } + + @Override + public boolean isUploaderVerified() throws ParsingException { + return false; + } + + @Override + public long getStreamCount() { + return trackInfo.size(); + } + + @Nonnull + @Override + public String getSubChannelName() { + return ""; + } + + @Nonnull + @Override + public String getSubChannelUrl() { + return ""; + } + + @Nonnull + @Override + public String getSubChannelAvatarUrl() { + return ""; + } + + @Nonnull + @Override + public InfoItemsPage getInitialPage() throws ExtractionException { + + final StreamInfoItemsCollector collector = new StreamInfoItemsCollector(getServiceId()); + + for (int i = 0; i < trackInfo.size(); i++) { + JsonObject track = trackInfo.getObject(i); + + if (trackInfo.size() < MAXIMUM_INDIVIDUAL_COVER_ARTS) { + // Load cover art of every track individually + collector.commit(new BandcampPlaylistStreamInfoItemExtractor( + track, getUploaderUrl(), getService())); + } else { + // Pretend every track has the same cover art as the album + collector.commit(new BandcampPlaylistStreamInfoItemExtractor( + track, getUploaderUrl(), getThumbnailUrl())); + } + + } + + return new InfoItemsPage<>(collector, null); + } + + @Override + public InfoItemsPage getPage(Page page) { + return null; + } + + @Nonnull + @Override + public String getName() throws ParsingException { + return name; + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampPlaylistInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampPlaylistInfoItemExtractor.java new file mode 100644 index 000000000..f6b393bfb --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampPlaylistInfoItemExtractor.java @@ -0,0 +1,46 @@ +package org.schabi.newpipe.extractor.services.bandcamp.extractors; + +import org.jsoup.nodes.Element; +import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor; + +import javax.annotation.Nonnull; + +public class BandcampPlaylistInfoItemExtractor implements PlaylistInfoItemExtractor { + private final Element searchResult, resultInfo; + + public BandcampPlaylistInfoItemExtractor(@Nonnull Element searchResult) { + this.searchResult = searchResult; + resultInfo = searchResult.getElementsByClass("result-info").first(); + } + + @Override + public String getUploaderName() { + return resultInfo.getElementsByClass("subhead").text() + .split(" by")[0]; + } + + @Override + public long getStreamCount() { + final String length = resultInfo.getElementsByClass("length").text(); + return Integer.parseInt(length.split(" track")[0]); + } + + @Override + public String getName() { + return resultInfo.getElementsByClass("heading").text(); + } + + @Override + public String getUrl() { + return resultInfo.getElementsByClass("itemurl").text(); + } + + @Override + public String getThumbnailUrl() { + final Element img = searchResult.getElementsByClass("art").first() + .getElementsByTag("img").first(); + if (img != null) { + return img.attr("src"); + } else return null; + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampPlaylistInfoItemFeaturedExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampPlaylistInfoItemFeaturedExtractor.java new file mode 100644 index 000000000..65872aab3 --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampPlaylistInfoItemFeaturedExtractor.java @@ -0,0 +1,40 @@ +package org.schabi.newpipe.extractor.services.bandcamp.extractors; + +import com.grack.nanojson.JsonObject; +import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor; + +import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImageUrl; + +public class BandcampPlaylistInfoItemFeaturedExtractor implements PlaylistInfoItemExtractor { + + private final JsonObject featuredStory; + + public BandcampPlaylistInfoItemFeaturedExtractor(final JsonObject featuredStory) { + this.featuredStory = featuredStory; + } + + @Override + public String getUploaderName() { + return featuredStory.getString("band_name"); + } + + @Override + public long getStreamCount() { + return featuredStory.getInt("num_streamable_tracks"); + } + + @Override + public String getName() { + return featuredStory.getString("album_title"); + } + + @Override + public String getUrl() { + return featuredStory.getString("item_url").replaceAll("http://", "https://"); + } + + @Override + public String getThumbnailUrl() { + return featuredStory.has("art_id") ? getImageUrl(featuredStory.getLong("art_id"), true) : ""; + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampRadioExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampRadioExtractor.java new file mode 100644 index 000000000..da062ab04 --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampRadioExtractor.java @@ -0,0 +1,71 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp.extractors; + +import com.grack.nanojson.JsonArray; +import com.grack.nanojson.JsonObject; +import com.grack.nanojson.JsonParser; +import com.grack.nanojson.JsonParserException; +import org.schabi.newpipe.extractor.Page; +import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.downloader.Downloader; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.kiosk.KioskExtractor; +import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler; +import org.schabi.newpipe.extractor.stream.StreamInfoItem; +import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector; + +import javax.annotation.Nonnull; +import java.io.IOException; + +import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_API_URL; + +public class BandcampRadioExtractor extends KioskExtractor { + + public static final String KIOSK_RADIO = "Radio"; + public static final String RADIO_API_URL = BASE_API_URL + "/bcweekly/1/list"; + + private JsonObject json = null; + + public BandcampRadioExtractor(final StreamingService streamingService, final ListLinkHandler linkHandler, + final String kioskId) { + super(streamingService, linkHandler, kioskId); + } + + @Override + public void onFetchPage(@Nonnull final Downloader downloader) throws IOException, ExtractionException { + try { + json = JsonParser.object().from( + getDownloader().get(RADIO_API_URL).responseBody()); + } catch (final JsonParserException e) { + throw new ExtractionException("Could not parse Bandcamp Radio API response", e); + } + } + + @Nonnull + @Override + public String getName() throws ParsingException { + return KIOSK_RADIO; + } + + @Nonnull + @Override + public InfoItemsPage getInitialPage() { + final StreamInfoItemsCollector collector = new StreamInfoItemsCollector(getServiceId()); + + final JsonArray radioShows = json.getArray("results"); + + for (int i = 0; i < radioShows.size(); i++) { + final JsonObject radioShow = radioShows.getObject(i); + collector.commit(new BandcampRadioInfoItemExtractor(radioShow)); + } + + return new InfoItemsPage<>(collector, null); + } + + @Override + public InfoItemsPage getPage(final Page page) { + return null; + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampRadioInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampRadioInfoItemExtractor.java new file mode 100644 index 000000000..2331f258c --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampRadioInfoItemExtractor.java @@ -0,0 +1,91 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp.extractors; + +import com.grack.nanojson.JsonObject; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.localization.DateWrapper; +import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor; +import org.schabi.newpipe.extractor.stream.StreamType; + +import javax.annotation.Nullable; + +import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_URL; +import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImageUrl; + +public class BandcampRadioInfoItemExtractor implements StreamInfoItemExtractor { + + private final JsonObject show; + + public BandcampRadioInfoItemExtractor(final JsonObject radioShow) { + show = radioShow; + } + + @Override + public long getDuration() { + /* Duration is only present in the more detailed information that has to be queried separately. + * Because the servers would probably not like over 300 queries every time someone opens the kiosk, + * we're just providing 0 here. + */ + //return query(show.getInt("id")).getLong("audio_duration"); + return 0; + } + + @Nullable + @Override + public String getTextualUploadDate() { + return show.getString("date"); + } + + @Nullable + @Override + public DateWrapper getUploadDate() throws ParsingException { + return BandcampExtractorHelper.parseDate(getTextualUploadDate()); + } + + @Override + public String getName() throws ParsingException { + return show.getString("subtitle"); + } + + @Override + public String getUrl() { + return BASE_URL + "/?show=" + show.getInt("id"); + } + + @Override + public String getThumbnailUrl() { + return getImageUrl(show.getLong("image_id"), false); + } + + @Override + public StreamType getStreamType() { + return StreamType.AUDIO_STREAM; + } + + @Override + public long getViewCount() { + return -1; + } + + @Override + public String getUploaderName() { + // JSON does not contain uploader name + return ""; + } + + @Override + public String getUploaderUrl() { + return ""; + } + + @Override + public boolean isUploaderVerified() throws ParsingException { + return false; + } + + @Override + public boolean isAd() { + return false; + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampRadioStreamExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampRadioStreamExtractor.java new file mode 100644 index 000000000..7d2543c8a --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampRadioStreamExtractor.java @@ -0,0 +1,143 @@ +package org.schabi.newpipe.extractor.services.bandcamp.extractors; + +import com.grack.nanojson.JsonObject; +import com.grack.nanojson.JsonParser; +import com.grack.nanojson.JsonParserException; +import org.jsoup.Jsoup; +import org.schabi.newpipe.extractor.MediaFormat; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.downloader.Downloader; +import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; +import org.schabi.newpipe.extractor.linkhandler.LinkHandler; +import org.schabi.newpipe.extractor.stream.AudioStream; +import org.schabi.newpipe.extractor.stream.Description; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.*; + +public class BandcampRadioStreamExtractor extends BandcampStreamExtractor { + + private JsonObject showInfo; + + public BandcampRadioStreamExtractor(final StreamingService service, final LinkHandler linkHandler) { + super(service, linkHandler); + } + + static JsonObject query(final int id) throws ParsingException { + try { + return JsonParser.object().from( + NewPipe.getDownloader().get(BASE_API_URL + "/bcweekly/1/get?id=" + id).responseBody() + ); + } catch (final IOException | ReCaptchaException | JsonParserException e) { + throw new ParsingException("could not get show data", e); + } + } + + @Override + public void onFetchPage(@Nonnull final Downloader downloader) throws IOException, ExtractionException { + showInfo = query(Integer.parseInt(getId())); + } + + @Nonnull + @Override + public String getName() throws ParsingException { + return showInfo.getString("subtitle"); // "audio_title" is a boring title + } + + @Nonnull + @Override + public String getUploaderUrl() throws ContentNotSupportedException { + throw new ContentNotSupportedException("Fan pages are not supported"); + } + + @Nonnull + @Override + public String getUrl() throws ParsingException { + return getLinkHandler().getUrl(); + } + + @Nonnull + @Override + public String getUploaderName() { + return Jsoup.parse(showInfo.getString("image_caption")) + .getElementsByTag("a").first().text(); + } + + @Nullable + @Override + public String getTextualUploadDate() { + return showInfo.getString("published_date"); + } + + @Nonnull + @Override + public String getThumbnailUrl() throws ParsingException { + return getImageUrl(showInfo.getLong("show_image_id"), false); + } + + @Nonnull + @Override + public String getUploaderAvatarUrl() { + return BASE_URL + "/img/buttons/bandcamp-button-circle-whitecolor-512.png"; + } + + @Nonnull + @Override + public Description getDescription() { + return new Description(showInfo.getString("desc"), Description.PLAIN_TEXT); + } + + @Override + public long getLength() { + return showInfo.getLong("audio_duration"); + } + + @Override + public List getAudioStreams() { + final ArrayList list = new ArrayList<>(); + final JsonObject streams = showInfo.getObject("audio_stream"); + + if (streams.has("opus-lo")) { + list.add(new AudioStream( + streams.getString("opus-lo"), + MediaFormat.OPUS, 100 + )); + } + if (streams.has("mp3-128")) { + list.add(new AudioStream( + streams.getString("mp3-128"), + MediaFormat.MP3, 128 + )); + } + + return list; + } + + @Nonnull + @Override + public String getLicence() { + return ""; + } + + @Nonnull + @Override + public String getCategory() { + return ""; + } + + @Nonnull + @Override + public List getTags() { + return Collections.emptyList(); + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampSearchExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampSearchExtractor.java new file mode 100644 index 000000000..6888d6932 --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampSearchExtractor.java @@ -0,0 +1,127 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp.extractors; + +import edu.umd.cs.findbugs.annotations.NonNull; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; +import org.schabi.newpipe.extractor.InfoItem; +import org.schabi.newpipe.extractor.MetaInfo; +import org.schabi.newpipe.extractor.Page; +import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.downloader.Downloader; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandler; +import org.schabi.newpipe.extractor.search.InfoItemsSearchCollector; +import org.schabi.newpipe.extractor.search.SearchExtractor; +import org.schabi.newpipe.extractor.services.bandcamp.extractors.streaminfoitem.BandcampSearchStreamInfoItemExtractor; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +public class BandcampSearchExtractor extends SearchExtractor { + + public BandcampSearchExtractor(StreamingService service, SearchQueryHandler linkHandler) { + super(service, linkHandler); + } + + @NonNull + @Override + public String getSearchSuggestion() { + return ""; + } + + @Override + public boolean isCorrectedSearch() { + return false; + } + + @Nonnull + @Override + public List getMetaInfo() throws ParsingException { + return Collections.emptyList(); + } + + public InfoItemsPage getPage(final Page page) throws IOException, ExtractionException { + // okay apparently this is where we DOWNLOAD the page and then COMMIT its ENTRIES to an INFOITEMPAGE + final String html = getDownloader().get(page.getUrl()).responseBody(); + + final InfoItemsSearchCollector collector = new InfoItemsSearchCollector(getServiceId()); + + + final Document d = Jsoup.parse(html); + + final Elements searchResultsElements = d.getElementsByClass("searchresult"); + + for (final Element searchResult : searchResultsElements) { + + final String type = searchResult.getElementsByClass("result-info").first() + .getElementsByClass("itemtype").first().text(); + + switch (type) { + default: + continue; + case "FAN": + // don't display fan results + break; + + case "ARTIST": + collector.commit(new BandcampChannelInfoItemExtractor(searchResult)); + break; + + case "ALBUM": + collector.commit(new BandcampPlaylistInfoItemExtractor(searchResult)); + break; + + case "TRACK": + collector.commit(new BandcampSearchStreamInfoItemExtractor(searchResult, null)); + break; + } + + } + + // Count pages + final Elements pageLists = d.getElementsByClass("pagelist"); + if (pageLists.size() == 0) + return new InfoItemsPage<>(collector, null); + + final Elements pages = pageLists.first().getElementsByTag("li"); + + // Find current page + int currentPage = -1; + for (int i = 0; i < pages.size(); i++) { + final Element pageElement = pages.get(i); + if (pageElement.getElementsByTag("span").size() > 0) { + currentPage = i + 1; + break; + } + } + + // Search results appear to be capped at six pages + assert pages.size() < 10; + + String nextUrl = null; + if (currentPage < pages.size()) { + nextUrl = page.getUrl().substring(0, page.getUrl().length() - 1) + (currentPage + 1); + } + + return new InfoItemsPage<>(collector, new Page(nextUrl)); + + } + + @Nonnull + @Override + public InfoItemsPage getInitialPage() throws IOException, ExtractionException { + return getPage(new Page(getUrl())); + } + + @Override + public void onFetchPage(@Nonnull final Downloader downloader) throws IOException, ExtractionException { + + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampStreamExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampStreamExtractor.java new file mode 100644 index 000000000..344663525 --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampStreamExtractor.java @@ -0,0 +1,343 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp.extractors; + +import com.grack.nanojson.JsonObject; +import com.grack.nanojson.JsonParserException; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; +import org.schabi.newpipe.extractor.MediaFormat; +import org.schabi.newpipe.extractor.MetaInfo; +import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.downloader.Downloader; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.linkhandler.LinkHandler; +import org.schabi.newpipe.extractor.localization.DateWrapper; +import org.schabi.newpipe.extractor.stream.*; +import org.schabi.newpipe.extractor.utils.JsonUtils; +import org.schabi.newpipe.extractor.utils.Utils; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImageUrl; + +public class BandcampStreamExtractor extends StreamExtractor { + + private JsonObject albumJson; + private JsonObject current; + private Document document; + + public BandcampStreamExtractor(final StreamingService service, final LinkHandler linkHandler) { + super(service, linkHandler); + } + + + @Override + public void onFetchPage(@Nonnull final Downloader downloader) throws IOException, ExtractionException { + final String html = downloader.get(getLinkHandler().getUrl()).responseBody(); + document = Jsoup.parse(html); + albumJson = getAlbumInfoJson(html); + current = albumJson.getObject("current"); + + if (albumJson.getArray("trackinfo").size() > 1) { + // In this case, we are actually viewing an album page! + throw new ExtractionException("Page is actually an album, not a track"); + } + } + + /** + * Get the JSON that contains album's metadata from page + * + * @param html Website + * @return Album metadata JSON + * @throws ParsingException In case of a faulty website + */ + public static JsonObject getAlbumInfoJson(final String html) throws ParsingException { + try { + return JsonUtils.getJsonData(html, "data-tralbum"); + } catch (final JsonParserException e) { + throw new ParsingException("Faulty JSON; page likely does not contain album data", e); + } catch (final ArrayIndexOutOfBoundsException e) { + throw new ParsingException("JSON does not exist", e); + } + } + + @Nonnull + @Override + public String getName() throws ParsingException { + return current.getString("title"); + } + + @Nonnull + @Override + public String getUploaderUrl() throws ParsingException { + final String[] parts = getUrl().split("/"); + // https: (/) (/) * .bandcamp.com (/) and leave out the rest + return "https://" + parts[2] + "/"; + } + + @Nonnull + @Override + public String getUrl() throws ParsingException { + return albumJson.getString("url").replace("http://", "https://"); + } + + @Nonnull + @Override + public String getUploaderName() { + return albumJson.getString("artist"); + } + + @Override + public boolean isUploaderVerified() throws ParsingException { + return false; + } + + @Nullable + @Override + public String getTextualUploadDate() { + return current.getString("publish_date"); + } + + @Nullable + @Override + public DateWrapper getUploadDate() throws ParsingException { + return BandcampExtractorHelper.parseDate(getTextualUploadDate()); + } + + @Nonnull + @Override + public String getThumbnailUrl() throws ParsingException { + if (albumJson.isNull("art_id")) return ""; + else return getImageUrl(albumJson.getLong("art_id"), true); + } + + @Nonnull + @Override + public String getUploaderAvatarUrl() { + try { + return document.getElementsByClass("band-photo").first().attr("src"); + } catch (final NullPointerException e) { + return ""; + } + } + + @Nonnull + @Override + public String getSubChannelUrl() { + return ""; + } + + @Nonnull + @Override + public String getSubChannelName() { + return ""; + } + + @Nonnull + @Override + public String getSubChannelAvatarUrl() { + return ""; + } + + @Nonnull + @Override + public Description getDescription() { + final String s = Utils.nonEmptyAndNullJoin( + "\n\n", + new String[]{ + current.getString("about"), + current.getString("lyrics"), + current.getString("credits") + } + ); + return new Description(s, Description.PLAIN_TEXT); + } + + @Override + public int getAgeLimit() { + return NO_AGE_LIMIT; + } + + @Override + public long getLength() { + return 0; + } + + @Override + public long getTimeStamp() { + return 0; + } + + @Override + public long getViewCount() { + return -1; + } + + @Override + public long getLikeCount() { + return -1; + } + + @Override + public long getDislikeCount() { + return -1; + } + + @Nonnull + @Override + public String getDashMpdUrl() { + return ""; + } + + @Nonnull + @Override + public String getHlsUrl() { + return ""; + } + + @Override + public List getAudioStreams() { + final List audioStreams = new ArrayList<>(); + + audioStreams.add(new AudioStream( + albumJson.getArray("trackinfo").getObject(0) + .getObject("file").getString("mp3-128"), + MediaFormat.MP3, 128 + )); + return audioStreams; + } + + @Override + public List getVideoStreams() { + return Collections.emptyList(); + } + + @Override + public List getVideoOnlyStreams() { + return Collections.emptyList(); + } + + @Nonnull + @Override + public List getSubtitlesDefault() { + return Collections.emptyList(); + } + + @Nonnull + @Override + public List getSubtitles(MediaFormat format) { + return Collections.emptyList(); + } + + @Override + public StreamType getStreamType() { + return StreamType.AUDIO_STREAM; + } + + @Override + public StreamInfoItemsCollector getRelatedStreams() { + return null; + } + + @Override + public String getErrorMessage() { + return null; + } + + @Nonnull + @Override + public String getHost() { + return ""; + } + + @Nonnull + @Override + public String getPrivacy() { + return ""; + } + + @Nonnull + @Override + public String getCategory() { + // Get first tag from html, which is the artist's Genre + return document + .getElementsByClass("tralbum-tags").first() + .getElementsByClass("tag").first().text(); + } + + @Nonnull + @Override + public String getLicence() { + + int license = current.getInt("license_type"); + + // Tests resulted in this mapping of ints to licence: https://cloud.disroot.org/s/ZTWBxbQ9fKRmRWJ/preview + + switch (license) { + case 1: + return "All rights reserved ©"; + case 2: + return "CC BY-NC-ND 3.0"; + case 3: + return "CC BY-NC-SA 3.0"; + case 4: + return "CC BY-NC 3.0"; + case 5: + return "CC BY-ND 3.0"; + case 6: + return "CC BY 3.0"; + case 8: + return "CC BY-SA 3.0"; + default: + return "Unknown"; + } + } + + @Nullable + @Override + public Locale getLanguageInfo() { + return null; + } + + @Nonnull + @Override + public List getTags() { + final Elements tagElements = document.getElementsByAttributeValue("itemprop", "keywords"); + + final List tags = new ArrayList<>(); + + for (final Element e : tagElements) { + tags.add(e.text()); + } + + return tags; + } + + @Nonnull + @Override + public String getSupportInfo() { + return ""; + } + + @Nonnull + @Override + public List getStreamSegments() throws ParsingException { + return Collections.emptyList(); + } + + @Nonnull + @Override + public List getMetaInfo() throws ParsingException { + return Collections.emptyList(); + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampSuggestionExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampSuggestionExtractor.java new file mode 100644 index 000000000..46283345b --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampSuggestionExtractor.java @@ -0,0 +1,56 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp.extractors; + +import com.grack.nanojson.JsonArray; +import com.grack.nanojson.JsonObject; +import com.grack.nanojson.JsonParser; +import com.grack.nanojson.JsonParserException; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.downloader.Downloader; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.suggestion.SuggestionExtractor; + +import java.io.IOException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_API_URL; + +public class BandcampSuggestionExtractor extends SuggestionExtractor { + + private static final String AUTOCOMPLETE_URL = BASE_API_URL + "/fuzzysearch/1/autocomplete?q="; + public BandcampSuggestionExtractor(final StreamingService service) { + super(service); + } + + @Override + public List suggestionList(final String query) throws IOException, ExtractionException { + final Downloader downloader = NewPipe.getDownloader(); + + try { + final JsonObject fuzzyResults = JsonParser.object().from( + downloader.get(AUTOCOMPLETE_URL + URLEncoder.encode(query, "UTF-8")).responseBody() + ); + + final JsonArray jsonArray = fuzzyResults.getObject("auto") + .getArray("results"); + + final List suggestions = new ArrayList<>(); + + for (final Object fuzzyResult : jsonArray) { + final String res = ((JsonObject) fuzzyResult).getString("name"); + + if (!suggestions.contains(res)) suggestions.add(res); + } + + return suggestions; + } catch (final JsonParserException e) { + return Collections.emptyList(); + } + + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/streaminfoitem/BandcampDiscographStreamInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/streaminfoitem/BandcampDiscographStreamInfoItemExtractor.java new file mode 100644 index 000000000..15b044981 --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/streaminfoitem/BandcampDiscographStreamInfoItemExtractor.java @@ -0,0 +1,46 @@ +package org.schabi.newpipe.extractor.services.bandcamp.extractors.streaminfoitem; + +import com.grack.nanojson.JsonObject; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper; + +public class BandcampDiscographStreamInfoItemExtractor extends BandcampStreamInfoItemExtractor { + + private final JsonObject discograph; + public BandcampDiscographStreamInfoItemExtractor(final JsonObject discograph, final String uploaderUrl) { + super(uploaderUrl); + + this.discograph = discograph; + } + + @Override + public String getUploaderName() { + return discograph.getString("band_name"); + } + + @Override + public String getName() { + return discograph.getString("title"); + } + + @Override + public String getUrl() throws ParsingException { + return BandcampExtractorHelper.getStreamUrlFromIds( + discograph.getLong("band_id"), + discograph.getLong("item_id"), + discograph.getString("item_type") + ); + } + + @Override + public String getThumbnailUrl() throws ParsingException { + return BandcampExtractorHelper.getImageUrl( + discograph.getLong("art_id"), true + ); + } + + @Override + public long getDuration() { + return -1; + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/streaminfoitem/BandcampPlaylistStreamInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/streaminfoitem/BandcampPlaylistStreamInfoItemExtractor.java new file mode 100644 index 000000000..e90a12aa1 --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/streaminfoitem/BandcampPlaylistStreamInfoItemExtractor.java @@ -0,0 +1,74 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp.extractors.streaminfoitem; + +import com.grack.nanojson.JsonObject; +import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.stream.StreamExtractor; + +import java.io.IOException; + + +public class BandcampPlaylistStreamInfoItemExtractor extends BandcampStreamInfoItemExtractor { + + private final JsonObject track; + private String substituteCoverUrl; + private final StreamingService service; + + public BandcampPlaylistStreamInfoItemExtractor(final JsonObject track, final String uploaderUrl, + final StreamingService service) { + super(uploaderUrl); + this.track = track; + this.service = service; + } + + public BandcampPlaylistStreamInfoItemExtractor(final JsonObject track, final String uploaderUrl, + final String substituteCoverUrl) { + this(track, uploaderUrl, (StreamingService) null); + this.substituteCoverUrl = substituteCoverUrl; + } + + @Override + public String getName() { + return track.getString("title"); + } + + @Override + public String getUrl() { + return getUploaderUrl() + track.getString("title_link"); + } + + @Override + public long getDuration() { + return track.getLong("duration"); + } + + @Override + public String getUploaderName() { + /* Tracks can have an individual artist name, but it is not included in the + * given JSON. + */ + return ""; + } + + /** + * Each track can have its own cover art. Therefore, unless a substitute is provided, + * the thumbnail is extracted using a stream extractor. + */ + @Override + public String getThumbnailUrl() throws ParsingException { + if (substituteCoverUrl != null) { + return substituteCoverUrl; + } else { + try { + final StreamExtractor extractor = service.getStreamExtractor(getUrl()); + extractor.fetchPage(); + return extractor.getThumbnailUrl(); + } catch (final ExtractionException | IOException e) { + throw new ParsingException("could not download cover art location", e); + } + } + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/streaminfoitem/BandcampSearchStreamInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/streaminfoitem/BandcampSearchStreamInfoItemExtractor.java new file mode 100644 index 000000000..adeea0519 --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/streaminfoitem/BandcampSearchStreamInfoItemExtractor.java @@ -0,0 +1,52 @@ +package org.schabi.newpipe.extractor.services.bandcamp.extractors.streaminfoitem; + +import org.jsoup.nodes.Element; +import org.schabi.newpipe.extractor.exceptions.ParsingException; + +public class BandcampSearchStreamInfoItemExtractor extends BandcampStreamInfoItemExtractor { + + private final Element resultInfo, searchResult; + + public BandcampSearchStreamInfoItemExtractor(final Element searchResult, final String uploaderUrl) { + super(uploaderUrl); + this.searchResult = searchResult; + resultInfo = searchResult.getElementsByClass("result-info").first(); + } + + @Override + public String getUploaderName() { + final String subhead = resultInfo.getElementsByClass("subhead").text(); + final String[] splitBy = subhead.split("by "); + if (splitBy.length > 1) { + return splitBy[1]; + } else { + return splitBy[0]; + } + } + + @Override + public String getName() throws ParsingException { + return resultInfo.getElementsByClass("heading").text(); + } + + @Override + public String getUrl() throws ParsingException { + return resultInfo.getElementsByClass("itemurl").text(); + } + + @Override + public String getThumbnailUrl() throws ParsingException { + final Element img = searchResult.getElementsByClass("art").first() + .getElementsByTag("img").first(); + if (img != null) { + return img.attr("src"); + } else { + return null; + } + } + + @Override + public long getDuration() { + return -1; + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/streaminfoitem/BandcampStreamInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/streaminfoitem/BandcampStreamInfoItemExtractor.java new file mode 100644 index 000000000..5f792fdef --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/streaminfoitem/BandcampStreamInfoItemExtractor.java @@ -0,0 +1,57 @@ +package org.schabi.newpipe.extractor.services.bandcamp.extractors.streaminfoitem; + +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.localization.DateWrapper; +import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor; +import org.schabi.newpipe.extractor.stream.StreamType; + +import javax.annotation.Nullable; + +/** + * Implements methods that return a constant value for better readability in + * subclasses. + */ +public abstract class BandcampStreamInfoItemExtractor implements StreamInfoItemExtractor { + private final String uploaderUrl; + + public BandcampStreamInfoItemExtractor(final String uploaderUrl) { + this.uploaderUrl = uploaderUrl; + } + + @Override + public StreamType getStreamType() { + return StreamType.AUDIO_STREAM; + } + + @Override + public long getViewCount() { + return -1; + } + + @Override + public String getUploaderUrl() { + return uploaderUrl; + } + + @Nullable + @Override + public String getTextualUploadDate() { + return null; + } + + @Nullable + @Override + public DateWrapper getUploadDate() { + return null; + } + + @Override + public boolean isUploaderVerified() throws ParsingException { + return false; + } + + @Override + public boolean isAd() { + return false; + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/linkHandler/BandcampChannelLinkHandlerFactory.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/linkHandler/BandcampChannelLinkHandlerFactory.java new file mode 100644 index 000000000..727aec404 --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/linkHandler/BandcampChannelLinkHandlerFactory.java @@ -0,0 +1,82 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp.linkHandler; + +import com.grack.nanojson.JsonObject; +import com.grack.nanojson.JsonParserException; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; +import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory; +import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper; +import org.schabi.newpipe.extractor.utils.JsonUtils; + +import java.io.IOException; +import java.util.List; + +/** + * Artist do have IDs that are useful + */ +public class BandcampChannelLinkHandlerFactory extends ListLinkHandlerFactory { + + + @Override + public String getId(final String url) throws ParsingException { + try { + final String response = NewPipe.getDownloader().get(url).responseBody(); + + // Use band data embedded in website to extract ID + final JsonObject bandData = JsonUtils.getJsonData(response, "data-band"); + + return String.valueOf(bandData.getLong("id")); + + } catch (final IOException | ReCaptchaException | ArrayIndexOutOfBoundsException | JsonParserException e) { + throw new ParsingException("Download failed", e); + } + } + + /** + * Uses the mobile endpoint as a "translator" from id to url + */ + @Override + public String getUrl(final String id, final List contentFilter, final String sortFilter) + throws ParsingException { + try { + return BandcampExtractorHelper.getArtistDetails(id) + .getString("bandcamp_url") + .replace("http://", "https://"); + } catch (final NullPointerException e) { + throw new ParsingException("JSON does not contain URL (invalid id?) or is otherwise invalid", e); + } + + } + + /** + * Accepts only pages that lead to the root of an artist profile. Supports external pages. + */ + @Override + public boolean onAcceptUrl(final String url) throws ParsingException { + + // https: | | artist.bandcamp.com | releases + // 0 1 2 3 + String[] splitUrl = url.split("/"); + + // URL is too short + if (splitUrl.length < 3) return false; + + // Must have "releases" as segment after url or none at all + if (splitUrl.length > 3 && !splitUrl[3].equals("releases")) { + + return false; + + } else { + if (splitUrl[2].equals("daily.bandcamp.com")) { + // Refuse links to daily.bandcamp.com as that is not an artist + return false; + } + + // Test whether domain is supported + return BandcampExtractorHelper.isSupportedDomain(url); + } + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/linkHandler/BandcampFeaturedLinkHandlerFactory.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/linkHandler/BandcampFeaturedLinkHandlerFactory.java new file mode 100644 index 000000000..ea6254979 --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/linkHandler/BandcampFeaturedLinkHandlerFactory.java @@ -0,0 +1,46 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp.linkHandler; + +import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory; +import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper; +import org.schabi.newpipe.extractor.utils.Utils; + +import java.util.List; + +import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampFeaturedExtractor.FEATURED_API_URL; +import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampFeaturedExtractor.KIOSK_FEATURED; +import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampRadioExtractor.KIOSK_RADIO; +import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampRadioExtractor.RADIO_API_URL; + +public class BandcampFeaturedLinkHandlerFactory extends ListLinkHandlerFactory { + + @Override + public String getUrl(final String id, final List contentFilter, final String sortFilter) { + if (id.equals(KIOSK_FEATURED)) { + return FEATURED_API_URL; // doesn't have a website + } else if (id.equals(KIOSK_RADIO)) { + return RADIO_API_URL; // doesn't have its own website + } else { + return null; + } + } + + @Override + public String getId(String url) { + url = Utils.replaceHttpWithHttps(url); + if (BandcampExtractorHelper.isRadioUrl(url) || url.equals(RADIO_API_URL)) { + return KIOSK_RADIO; + } else if (url.equals(FEATURED_API_URL)) { + return KIOSK_FEATURED; + } else { + return null; + } + } + + @Override + public boolean onAcceptUrl(String url) { + url = Utils.replaceHttpWithHttps(url); + return url.equals(FEATURED_API_URL) || (url.equals(RADIO_API_URL) || BandcampExtractorHelper.isRadioUrl(url)); + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/linkHandler/BandcampPlaylistLinkHandlerFactory.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/linkHandler/BandcampPlaylistLinkHandlerFactory.java new file mode 100644 index 000000000..b0fd2d50a --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/linkHandler/BandcampPlaylistLinkHandlerFactory.java @@ -0,0 +1,38 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp.linkHandler; + +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory; +import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper; + +import java.util.List; + +/** + * Just as with streams, the album ids are essentially useless for us. + */ +public class BandcampPlaylistLinkHandlerFactory extends ListLinkHandlerFactory { + @Override + public String getId(final String url) throws ParsingException { + return getUrl(url); + } + + @Override + public String getUrl(final String url, final List contentFilter, final String sortFilter) + throws ParsingException { + return url; + } + + /** + * Accepts all bandcamp URLs that contain /album/ behind their domain name. + */ + @Override + public boolean onAcceptUrl(final String url) throws ParsingException { + + // Exclude URLs which do not lead to an album + if (!url.toLowerCase().matches("https?://.+\\..+/album/.+")) return false; + + // Test whether domain is supported + return BandcampExtractorHelper.isSupportedDomain(url); + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/linkHandler/BandcampSearchQueryHandlerFactory.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/linkHandler/BandcampSearchQueryHandlerFactory.java new file mode 100644 index 000000000..9e295d5de --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/linkHandler/BandcampSearchQueryHandlerFactory.java @@ -0,0 +1,30 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp.linkHandler; + +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandlerFactory; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.List; + +import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_URL; + +public class BandcampSearchQueryHandlerFactory extends SearchQueryHandlerFactory { + + + @Override + public String getUrl(final String query, final List contentFilter, final String sortFilter) + throws ParsingException { + try { + + return BASE_URL + "/search?q=" + + URLEncoder.encode(query, "UTF-8") + + "&page=1"; + + } catch (final UnsupportedEncodingException e) { + throw new ParsingException("query \"" + query + "\" could not be encoded", e); + } + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/linkHandler/BandcampStreamLinkHandlerFactory.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/linkHandler/BandcampStreamLinkHandlerFactory.java new file mode 100644 index 000000000..96b9f9d8e --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/linkHandler/BandcampStreamLinkHandlerFactory.java @@ -0,0 +1,61 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp.linkHandler; + +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.linkhandler.LinkHandlerFactory; +import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper; + +import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_URL; + +/** + *

Tracks don't have standalone ids, they are always in combination with the band id. + * That's why id = url.

+ * + *

Radio (bandcamp weekly) shows do have ids.

+ */ +public class BandcampStreamLinkHandlerFactory extends LinkHandlerFactory { + + + /** + * @see BandcampStreamLinkHandlerFactory + */ + @Override + public String getId(final String url) throws ParsingException { + if (BandcampExtractorHelper.isRadioUrl(url)) { + return url.split("bandcamp.com/\\?show=")[1]; + } else { + return getUrl(url); + } + } + + /** + * Clean up url + * @see BandcampStreamLinkHandlerFactory + */ + @Override + public String getUrl(final String input) { + if (input.matches("\\d+")) { + return BASE_URL + "/?show=" + input; + } else { + return input; + } + } + + /** + * Accepts URLs that point to a bandcamp radio show or that are a bandcamp + * domain and point to a track. + */ + @Override + public boolean onAcceptUrl(final String url) throws ParsingException { + + // Accept Bandcamp radio + if (BandcampExtractorHelper.isRadioUrl(url)) return true; + + // Don't accept URLs that don't point to a track + if (!url.toLowerCase().matches("https?://.+\\..+/track/.+")) return false; + + // Test whether domain is supported + return BandcampExtractorHelper.isSupportedDomain(url); + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/utils/JsonUtils.java b/extractor/src/main/java/org/schabi/newpipe/extractor/utils/JsonUtils.java index d3d3d1f54..e9eed4126 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/utils/JsonUtils.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/utils/JsonUtils.java @@ -5,6 +5,8 @@ import com.grack.nanojson.JsonObject; import com.grack.nanojson.JsonParser; import com.grack.nanojson.JsonParserException; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; import org.schabi.newpipe.extractor.exceptions.ParsingException; import java.util.ArrayList; @@ -118,4 +120,35 @@ public class JsonUtils { throw new ParsingException("Could not parse JSON", e); } } + + /** + *

Get an attribute of a web page as JSON + * + *

Originally a part of bandcampDirect.

+ *

Example HTML:

+ *
+     * {@code
+     * 

This is Sparta!

+ * } + *
+ *

Calling this function to get the attribute data-town returns the JsonObject for

+ *
+     * {@code
+     *   {
+     *     "name": "Mycenae",
+     *     "country": "Greece"
+     *   }
+     * }
+     * 
+ * @param html The HTML where the JSON we're looking for is stored inside a + * variable inside some JavaScript block + * @param variable Name of the variable + * @return The JsonObject stored in the variable with this name + */ + public static JsonObject getJsonData(final String html, final String variable) + throws JsonParserException, ArrayIndexOutOfBoundsException { + final Document document = Jsoup.parse(html); + final String json = document.getElementsByAttribute(variable).attr(variable); + return JsonParser.object().from(json); + } } diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/utils/Utils.java b/extractor/src/main/java/org/schabi/newpipe/extractor/utils/Utils.java index 721bc96c3..82ff58263 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/utils/Utils.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/utils/Utils.java @@ -271,4 +271,12 @@ public class Utils { return join(delimiter, list); } + /** + * Concatenate all non-null, non-empty and strings which are not equal to "null". + */ + public static String nonEmptyAndNullJoin(final CharSequence delimiter, final String[] elements) { + final List list = new java.util.ArrayList<>(Arrays.asList(elements)); + list.removeIf(s -> isNullOrEmpty(s) || s.equals("null")); + return join(delimiter, list); + } } diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampChannelExtractorTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampChannelExtractorTest.java new file mode 100644 index 000000000..78b392558 --- /dev/null +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampChannelExtractorTest.java @@ -0,0 +1,99 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.schabi.newpipe.downloader.DownloaderTestImpl; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.channel.ChannelExtractor; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.services.BaseChannelExtractorTest; + +import java.io.IOException; + +import static org.junit.Assert.*; +import static org.schabi.newpipe.extractor.ServiceList.Bandcamp; + +public class BandcampChannelExtractorTest implements BaseChannelExtractorTest { + + private static ChannelExtractor extractor; + + @BeforeClass + public static void setUp() throws Exception { + NewPipe.init(DownloaderTestImpl.getInstance()); + extractor = Bandcamp.getChannelExtractor("https://toupie.bandcamp.com/releases"); + extractor.fetchPage(); + } + + @Test + public void testLength() throws ExtractionException, IOException { + assertTrue(extractor.getInitialPage().getItems().size() >= 0); + } + + @Override + @Test + public void testDescription() throws Exception { + assertEquals("making music:)", extractor.getDescription()); + } + + @Override + public void testAvatarUrl() throws Exception { + assertTrue("unexpected avatar URL", extractor.getAvatarUrl().contains("://f4.bcbits.com/")); + } + + @Override + public void testBannerUrl() throws Exception { + assertTrue("unexpected banner URL", extractor.getBannerUrl().contains("://f4.bcbits.com/")); + } + + @Override + public void testFeedUrl() throws Exception { + assertNull(extractor.getFeedUrl()); + } + + @Override + public void testSubscriberCount() throws Exception { + assertEquals(-1, extractor.getSubscriberCount()); + } + + @Override + public void testVerified() throws Exception { + assertFalse(extractor.isVerified()); + } + + @Override + public void testRelatedItems() throws Exception { + // not implemented + } + + @Override + public void testMoreRelatedItems() throws Exception { + // not implemented + } + + @Override + public void testServiceId() { + assertEquals(Bandcamp.getServiceId(), extractor.getServiceId()); + } + + @Override + public void testName() throws Exception { + assertEquals("toupie", extractor.getName()); + } + + @Override + public void testId() throws Exception { + assertEquals("https://toupie.bandcamp.com/", extractor.getId()); + } + + @Override + public void testUrl() throws Exception { + assertEquals("https://toupie.bandcamp.com", extractor.getUrl()); + } + + @Override + public void testOriginalUrl() throws Exception { + assertEquals("https://toupie.bandcamp.com", extractor.getUrl()); + } +} diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampChannelLinkHandlerFactoryTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampChannelLinkHandlerFactoryTest.java new file mode 100644 index 000000000..aa7a93d8a --- /dev/null +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampChannelLinkHandlerFactoryTest.java @@ -0,0 +1,73 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.schabi.newpipe.downloader.DownloaderTestImpl; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampChannelLinkHandlerFactory; + +import static org.junit.Assert.*; + +/** + * Test for {@link BandcampChannelLinkHandlerFactory} + */ +public class BandcampChannelLinkHandlerFactoryTest { + private static BandcampChannelLinkHandlerFactory linkHandler; + + @BeforeClass + public static void setUp() { + linkHandler = new BandcampChannelLinkHandlerFactory(); + NewPipe.init(DownloaderTestImpl.getInstance()); + } + + @Test + public void testAcceptUrl() throws ParsingException { + // Bandcamp URLs + assertTrue(linkHandler.acceptUrl("http://zachbenson.bandcamp.com")); + assertTrue(linkHandler.acceptUrl("https://zachbenson.bandcamp.com/")); + assertTrue(linkHandler.acceptUrl("https://billwurtz.bandcamp.com/releases")); + + assertTrue(linkHandler.acceptUrl("http://zachbenson.bandcamp.com/")); + + assertFalse(linkHandler.acceptUrl("https://bandcamp.com")); + assertFalse(linkHandler.acceptUrl("https://zachbenson.bandcamp.com/track/kitchen")); + assertFalse(linkHandler.acceptUrl("https://daily.bandcamp.com/")); + assertFalse(linkHandler.acceptUrl("https://daily.bandcamp.com/best-of-2020/bandcamp-daily-staffers-on-their-favorite-albums-of-2020")); + + // External URLs + assertTrue(linkHandler.acceptUrl("http://interovgm.com/releases/")); + assertTrue(linkHandler.acceptUrl("https://interovgm.com/releases")); + + assertFalse(linkHandler.acceptUrl("https://example.com/releases")); + } + + @Test + public void testGetId() throws ParsingException { + assertEquals("1196681540", linkHandler.getId("https://macbenson.bandcamp.com/")); + assertEquals("1196681540", linkHandler.getId("http://macbenson.bandcamp.com/")); + assertEquals("1581461772", linkHandler.getId("https://interovgm.com/releases")); + assertEquals("3321800855", linkHandler.getId("https://infiniteammo.bandcamp.com/")); + assertEquals("3775652329", linkHandler.getId("https://npet.bandcamp.com/")); + } + + @Test + public void testGetUrl() throws ParsingException { + assertEquals("https://macbenson.bandcamp.com", linkHandler.getUrl("1196681540")); + assertEquals("https://interovgm.com", linkHandler.getUrl("1581461772")); + assertEquals("https://infiniteammo.bandcamp.com", linkHandler.getUrl("3321800855")); + } + + @Test(expected = ParsingException.class) + public void testGetUrlWithInvalidId() throws ParsingException { + linkHandler.getUrl("0"); + } + + @Test(expected = ParsingException.class) + public void testGetIdWithInvalidUrl() throws ParsingException { + linkHandler.getId("https://bandcamp.com"); + } + +} diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampFeaturedExtractorTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampFeaturedExtractorTest.java new file mode 100644 index 000000000..1be966dea --- /dev/null +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampFeaturedExtractorTest.java @@ -0,0 +1,83 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.schabi.newpipe.downloader.DownloaderTestImpl; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; +import org.schabi.newpipe.extractor.services.BaseListExtractorTest; +import org.schabi.newpipe.extractor.services.DefaultTests; +import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampFeaturedExtractor; + +import java.io.IOException; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.schabi.newpipe.extractor.ServiceList.Bandcamp; + +/** + * Tests for {@link BandcampFeaturedExtractor} + */ +public class BandcampFeaturedExtractorTest implements BaseListExtractorTest { + + private static BandcampFeaturedExtractor extractor; + + @BeforeClass + public static void setUp() throws ExtractionException, IOException { + NewPipe.init(DownloaderTestImpl.getInstance()); + extractor = (BandcampFeaturedExtractor) Bandcamp + .getKioskList().getDefaultKioskExtractor(); + extractor.fetchPage(); + } + + @Test + public void testFeaturedCount() throws ExtractionException, IOException { + final List list = extractor.getInitialPage().getItems(); + assertTrue(list.size() > 1); + } + + @Test + public void testHttps() throws ExtractionException, IOException { + final List list = extractor.getInitialPage().getItems(); + assertTrue(list.get(0).getUrl().contains("https://")); + } + + @Override + public void testRelatedItems() throws Exception { + DefaultTests.defaultTestRelatedItems(extractor); + } + + @Override + public void testMoreRelatedItems() throws Exception { + // more items not implemented + } + + @Override + public void testServiceId() { + assertEquals(Bandcamp.getServiceId(), extractor.getServiceId()); + } + + @Override + public void testName() throws Exception { + assertEquals("Featured", extractor.getName()); + } + + @Override + public void testId() { + assertEquals("", extractor.getId()); + } + + @Override + public void testUrl() throws Exception { + assertEquals("", extractor.getUrl()); + } + + @Override + public void testOriginalUrl() throws Exception { + assertEquals("", extractor.getOriginalUrl()); + } +} diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampFeaturedLinkHandlerFactoryTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampFeaturedLinkHandlerFactoryTest.java new file mode 100644 index 000000000..b337ded7d --- /dev/null +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampFeaturedLinkHandlerFactoryTest.java @@ -0,0 +1,52 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampFeaturedLinkHandlerFactory; + +import static org.junit.Assert.*; + +/** + * Tests for {@link BandcampFeaturedLinkHandlerFactory} + */ +public class BandcampFeaturedLinkHandlerFactoryTest { + + private static BandcampFeaturedLinkHandlerFactory linkHandler; + + @BeforeClass + public static void setUp() { + linkHandler = new BandcampFeaturedLinkHandlerFactory(); + } + + + @Test + public void testAcceptUrl() throws ParsingException { + assertTrue(linkHandler.acceptUrl("http://bandcamp.com/?show=1")); + assertTrue(linkHandler.acceptUrl("https://bandcamp.com/?show=1")); + assertTrue(linkHandler.acceptUrl("http://bandcamp.com/?show=2")); + assertTrue(linkHandler.acceptUrl("https://bandcamp.com/api/mobile/24/bootstrap_data")); + assertTrue(linkHandler.acceptUrl("https://bandcamp.com/api/bcweekly/1/list")); + + assertFalse(linkHandler.acceptUrl("https://bandcamp.com/?show=")); + assertFalse(linkHandler.acceptUrl("https://bandcamp.com/?show=a")); + assertFalse(linkHandler.acceptUrl("https://bandcamp.com/")); + } + + @Test + public void testGetUrl() throws ParsingException { + assertEquals("https://bandcamp.com/api/mobile/24/bootstrap_data", linkHandler.getUrl("Featured")); + assertEquals("https://bandcamp.com/api/bcweekly/1/list", linkHandler.getUrl("Radio")); + } + + @Test + public void testGetId() { + assertEquals("Featured", linkHandler.getId("http://bandcamp.com/api/mobile/24/bootstrap_data")); + assertEquals("Featured", linkHandler.getId("https://bandcamp.com/api/mobile/24/bootstrap_data")); + assertEquals("Radio", linkHandler.getId("http://bandcamp.com/?show=1")); + assertEquals("Radio", linkHandler.getId("https://bandcamp.com/api/bcweekly/1/list")); + } + +} diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampPlaylistExtractorTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampPlaylistExtractorTest.java new file mode 100644 index 000000000..31109951a --- /dev/null +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampPlaylistExtractorTest.java @@ -0,0 +1,185 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.schabi.newpipe.downloader.DownloaderTestImpl; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.playlist.PlaylistExtractor; +import org.schabi.newpipe.extractor.services.BasePlaylistExtractorTest; +import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampPlaylistExtractor; +import org.schabi.newpipe.extractor.stream.StreamInfoItem; + +import java.io.IOException; +import java.util.List; + +import static org.junit.Assert.*; +import static org.schabi.newpipe.extractor.ServiceList.Bandcamp; + +/** + * Tests for {@link BandcampPlaylistExtractor} + */ +public class BandcampPlaylistExtractorTest { + + @BeforeClass + public static void setUp() { + NewPipe.init(DownloaderTestImpl.getInstance()); + } + + /** + * Test whether playlists contain the correct amount of items + */ + @Test + public void testCount() throws ExtractionException, IOException { + final PlaylistExtractor extractor = Bandcamp.getPlaylistExtractor("https://macbenson.bandcamp.com/album/coming-of-age"); + extractor.fetchPage(); + + assertEquals(5, extractor.getStreamCount()); + } + + /** + * Tests whether different stream thumbnails (track covers) get loaded correctly + */ + @Test + public void testDifferentTrackCovers() throws ExtractionException, IOException { + final PlaylistExtractor extractor = Bandcamp.getPlaylistExtractor("https://zachbensonarchive.bandcamp.com/album/results-of-boredom"); + extractor.fetchPage(); + + final List l = extractor.getInitialPage().getItems(); + assertEquals(extractor.getThumbnailUrl(), l.get(0).getThumbnailUrl()); + assertNotEquals(extractor.getThumbnailUrl(), l.get(5).getThumbnailUrl()); + } + + /** + * Tests that no attempt to load every track's cover individually is made + */ + @Test(timeout = 10000L) + public void testDifferentTrackCoversDuration() throws ExtractionException, IOException { + final PlaylistExtractor extractor = Bandcamp.getPlaylistExtractor("https://infiniteammo.bandcamp.com/album/night-in-the-woods-vol-1-at-the-end-of-everything"); + extractor.fetchPage(); + + /* All tracks in this album have the same cover art, but I don't know any albums with more than 10 tracks + * that has at least one track with a cover art different from the rest. + */ + final List l = extractor.getInitialPage().getItems(); + assertEquals(extractor.getThumbnailUrl(), l.get(0).getThumbnailUrl()); + assertEquals(extractor.getThumbnailUrl(), l.get(5).getThumbnailUrl()); + } + + /** + * Test playlists with locked content + */ + @Test(expected = ContentNotAvailableException.class) + public void testLockedContent() throws ExtractionException, IOException { + final PlaylistExtractor extractor = Bandcamp.getPlaylistExtractor("https://billwurtz.bandcamp.com/album/high-enough"); + extractor.fetchPage(); + } + + /** + * Test playlist with just one track + */ + @Test + public void testSingleStreamPlaylist() throws ExtractionException, IOException { + final PlaylistExtractor extractor = Bandcamp.getPlaylistExtractor("https://zachjohnson1.bandcamp.com/album/endless"); + extractor.fetchPage(); + + assertEquals(1, extractor.getStreamCount()); + + } + + public static class ComingOfAge implements BasePlaylistExtractorTest { + + private static PlaylistExtractor extractor; + + @BeforeClass + public static void setUp() throws ExtractionException, IOException { + NewPipe.init(DownloaderTestImpl.getInstance()); + extractor = Bandcamp.getPlaylistExtractor("https://macbenson.bandcamp.com/album/coming-of-age"); + extractor.fetchPage(); + } + + @Test + public void testThumbnailUrl() throws ParsingException { + assertTrue(extractor.getThumbnailUrl().contains("f4.bcbits.com/img")); + } + + @Test + public void testBannerUrl() throws ParsingException { + assertEquals("", extractor.getBannerUrl()); + } + + @Test + public void testUploaderUrl() throws ParsingException { + assertTrue(extractor.getUploaderUrl().contains("macbenson.bandcamp.com")); + } + + @Test + public void testUploaderName() throws ParsingException { + assertEquals("mac benson", extractor.getUploaderName()); + } + + @Test + public void testUploaderAvatarUrl() throws ParsingException { + assertTrue(extractor.getUploaderAvatarUrl().contains("f4.bcbits.com/img")); + } + + @Test + public void testStreamCount() throws ParsingException { + assertEquals(5, extractor.getStreamCount()); + } + + @Override + public void testUploaderVerified() throws Exception { + assertFalse(extractor.isUploaderVerified()); + } + + @Test + public void testInitialPage() throws IOException, ExtractionException { + assertNotNull(extractor.getInitialPage().getItems().get(0)); + } + + @Test + public void testServiceId() { + assertEquals(Bandcamp.getServiceId(), extractor.getServiceId()); + } + + @Test + public void testName() throws ParsingException { + assertEquals("Coming of Age", extractor.getName()); + } + + @Test + public void testId() throws Exception { + assertEquals("https://macbenson.bandcamp.com/album/coming-of-age", extractor.getId()); + } + + @Test + public void testUrl() throws Exception { + assertEquals("https://macbenson.bandcamp.com/album/coming-of-age", extractor.getUrl()); + } + + @Test + public void testOriginalUrl() throws Exception { + assertEquals("https://macbenson.bandcamp.com/album/coming-of-age", extractor.getOriginalUrl()); + } + + @Test + public void testNextPageUrl() throws IOException, ExtractionException { + assertNull(extractor.getPage(extractor.getInitialPage().getNextPage())); + } + + @Test + public void testRelatedItems() throws Exception { + // DefaultTests.defaultTestRelatedItems(extractor); + // Would fail because BandcampPlaylistStreamInfoItemExtractor.getUploaderName() returns an empty String + } + + @Test + public void testMoreRelatedItems() throws Exception { + } + } +} diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampPlaylistLinkHandlerFactoryTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampPlaylistLinkHandlerFactoryTest.java new file mode 100644 index 000000000..e1b7949f8 --- /dev/null +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampPlaylistLinkHandlerFactoryTest.java @@ -0,0 +1,44 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.schabi.newpipe.downloader.DownloaderTestImpl; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampPlaylistLinkHandlerFactory; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Test for {@link BandcampPlaylistLinkHandlerFactory} + */ +public class BandcampPlaylistLinkHandlerFactoryTest { + + private static BandcampPlaylistLinkHandlerFactory linkHandler; + + @BeforeClass + public static void setUp() { + linkHandler = new BandcampPlaylistLinkHandlerFactory(); + NewPipe.init(DownloaderTestImpl.getInstance()); + } + + @Test + public void testAcceptUrl() throws ParsingException { + assertFalse(linkHandler.acceptUrl("http://interovgm.com/releases/")); + assertFalse(linkHandler.acceptUrl("https://interovgm.com/releases")); + assertFalse(linkHandler.acceptUrl("http://zachbenson.bandcamp.com")); + assertFalse(linkHandler.acceptUrl("https://bandcamp.com")); + assertFalse(linkHandler.acceptUrl("https://zachbenson.bandcamp.com/")); + assertFalse(linkHandler.acceptUrl("https://zachbenson.bandcamp.com/track/kitchen")); + assertFalse(linkHandler.acceptUrl("https://interovgm.com/track/title")); + assertFalse(linkHandler.acceptUrl("https://example.com/album/samplealbum")); + + assertTrue(linkHandler.acceptUrl("https://powertothequeerkids.bandcamp.com/album/power-to-the-queer-kids")); + assertTrue(linkHandler.acceptUrl("https://zachbenson.bandcamp.com/album/prom")); + assertTrue(linkHandler.acceptUrl("https://MACBENSON.BANDCAMP.COM/ALBUM/COMING-OF-AGE")); + } + +} diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampRadioExtractorTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampRadioExtractorTest.java new file mode 100644 index 000000000..70838bd7e --- /dev/null +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampRadioExtractorTest.java @@ -0,0 +1,78 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.schabi.newpipe.downloader.DownloaderTestImpl; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.services.BaseListExtractorTest; +import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampRadioExtractor; +import org.schabi.newpipe.extractor.stream.StreamInfoItem; + +import java.io.IOException; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.schabi.newpipe.extractor.ServiceList.Bandcamp; + +/** + * Tests for {@link BandcampRadioExtractor} + */ +public class BandcampRadioExtractorTest implements BaseListExtractorTest { + + private static BandcampRadioExtractor extractor; + + @BeforeClass + public static void setUp() throws ExtractionException, IOException { + NewPipe.init(DownloaderTestImpl.getInstance()); + extractor = (BandcampRadioExtractor) Bandcamp + .getKioskList() + .getExtractorById("Radio", null); + extractor.fetchPage(); + } + + @Test + public void testRadioCount() throws ExtractionException, IOException { + final List list = extractor.getInitialPage().getItems(); + assertTrue(list.size() > 300); + } + + @Test + public void testRelatedItems() throws Exception { + // DefaultTests.defaultTestRelatedItems(extractor); + // Would fail because BandcampRadioInfoItemExtractor.getUploaderName() returns an empty String + } + + @Test + public void testMoreRelatedItems() throws Exception { + // All items are on one page + } + + @Test + public void testServiceId() { + assertEquals(Bandcamp.getServiceId(), extractor.getServiceId()); + } + + @Test + public void testName() throws Exception { + assertEquals("Radio", extractor.getName()); + } + + @Test + public void testId() { + assertEquals("Radio", extractor.getId()); + } + + @Test + public void testUrl() throws Exception { + assertEquals("https://bandcamp.com/api/bcweekly/1/list", extractor.getUrl()); + } + + @Test + public void testOriginalUrl() throws Exception { + assertEquals("https://bandcamp.com/api/bcweekly/1/list", extractor.getOriginalUrl()); + } +} diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampRadioStreamExtractorTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampRadioStreamExtractorTest.java new file mode 100644 index 000000000..8773c5564 --- /dev/null +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampRadioStreamExtractorTest.java @@ -0,0 +1,107 @@ +package org.schabi.newpipe.extractor.services.bandcamp; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.schabi.newpipe.downloader.DownloaderTestImpl; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.services.DefaultStreamExtractorTest; +import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampRadioStreamExtractor; +import org.schabi.newpipe.extractor.stream.StreamExtractor; +import org.schabi.newpipe.extractor.stream.StreamType; + +import java.io.IOException; +import java.util.Calendar; +import java.util.Collections; +import java.util.List; +import java.util.TimeZone; + +import static org.junit.Assert.*; +import static org.schabi.newpipe.extractor.ServiceList.Bandcamp; + +public class BandcampRadioStreamExtractorTest extends DefaultStreamExtractorTest { + + private static StreamExtractor extractor; + + private static final String URL = "https://bandcamp.com/?show=230"; + + @BeforeClass + public static void setUp() throws IOException, ExtractionException { + NewPipe.init(DownloaderTestImpl.getInstance()); + extractor = Bandcamp.getStreamExtractor(URL); + extractor.fetchPage(); + } + + @Test + public void testGettingCorrectStreamExtractor() throws ExtractionException { + assertTrue(Bandcamp.getStreamExtractor("https://bandcamp.com/?show=3") instanceof BandcampRadioStreamExtractor); + assertFalse(Bandcamp.getStreamExtractor("https://zachbenson.bandcamp.com/track/deflated") + instanceof BandcampRadioStreamExtractor); + } + + @Override public StreamExtractor extractor() { return extractor; } + @Override public String expectedName() throws Exception { return "Sound Movements"; } + @Override public String expectedId() throws Exception { return "230"; } + @Override public String expectedUrlContains() throws Exception { return URL; } + @Override public String expectedOriginalUrlContains() throws Exception { return URL; } + @Override public boolean expectedHasVideoStreams() { return false; } + @Override public boolean expectedHasSubtitles() { return false; } + @Override public boolean expectedHasFrames() { return false; } + @Override public boolean expectedHasRelatedStreams() { return false; } + @Override public StreamType expectedStreamType() { return StreamType.AUDIO_STREAM; } + @Override public StreamingService expectedService() { return Bandcamp; } + @Override public String expectedUploaderName() { return "Andrew Jervis"; } + + @Test(expected = ContentNotSupportedException.class) + public void testGetUploaderUrl() throws ParsingException { + extractor.getUploaderUrl(); + } + + @Test(expected = ContentNotSupportedException.class) + @Override + public void testUploaderUrl() throws Exception { + super.testUploaderUrl(); + } + @Override public String expectedUploaderUrl() { return null; } + + @Override + public List expectedDescriptionContains() { + return Collections.singletonList("Featuring special guests Nick Hakim and Elbows, plus fresh cuts from Eddie Palmieri, KRS One, Ladi6, and Moonchild."); + } + + @Override public long expectedLength() { return 5619; } + @Override public long expectedViewCountAtLeast() { return -1; } + @Override public long expectedLikeCountAtLeast() { return -1; } + @Override public long expectedDislikeCountAtLeast() { return -1; } + + @Override public String expectedUploadDate() { return "16 May 2017 00:00:00 GMT"; } + @Override public String expectedTextualUploadDate() { return "16 May 2017 00:00:00 GMT"; } + @Test + public void testUploadDate() throws ParsingException { + final Calendar expectedCalendar = Calendar.getInstance(); + + // 16 May 2017 00:00:00 GMT + expectedCalendar.setTimeZone(TimeZone.getTimeZone("GMT")); + expectedCalendar.setTimeInMillis(0); + expectedCalendar.set(2017, Calendar.MAY, 16); + + assertEquals(expectedCalendar.getTimeInMillis(), extractor.getUploadDate().offsetDateTime().toInstant().toEpochMilli()); + } + + @Test + public void testGetThumbnailUrl() throws ParsingException { + assertTrue(extractor.getThumbnailUrl().contains("bcbits.com/img")); + } + + @Test + public void testGetUploaderAvatarUrl() throws ParsingException { + assertTrue(extractor.getUploaderAvatarUrl().contains("bandcamp-button")); + } + + @Test public void testGetAudioStreams() throws ExtractionException, IOException { + assertEquals(2, extractor.getAudioStreams().size()); + } +} diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampSearchExtractorTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampSearchExtractorTest.java new file mode 100644 index 000000000..124265168 --- /dev/null +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampSearchExtractorTest.java @@ -0,0 +1,125 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.schabi.newpipe.downloader.DownloaderTestImpl; +import org.schabi.newpipe.extractor.*; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; +import org.schabi.newpipe.extractor.search.SearchExtractor; +import org.schabi.newpipe.extractor.services.DefaultSearchExtractorTest; +import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampSearchExtractor; +import org.schabi.newpipe.extractor.stream.StreamInfoItem; + +import javax.annotation.Nullable; +import java.io.IOException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.schabi.newpipe.extractor.ServiceList.Bandcamp; + +/** + * Test for {@link BandcampSearchExtractor} + */ +public class BandcampSearchExtractorTest { + + @BeforeClass + public static void setUp() { + NewPipe.init(DownloaderTestImpl.getInstance()); + + } + + /** + * Tests whether searching bandcamp for "best friend's basement" returns + * the accordingly named song by Zach Benson + */ + @Test + public void testStreamSearch() throws ExtractionException, IOException { + final SearchExtractor extractor = Bandcamp.getSearchExtractor("best friend's basement"); + + final ListExtractor.InfoItemsPage page = extractor.getInitialPage(); + final StreamInfoItem bestFriendsBasement = (StreamInfoItem) page.getItems().get(0); + + // The track by Zach Benson should be the first result, no? + assertEquals("Best Friend's Basement", bestFriendsBasement.getName()); + assertEquals("Zach Benson", bestFriendsBasement.getUploaderName()); + assertTrue(bestFriendsBasement.getThumbnailUrl().endsWith(".jpg")); + assertTrue(bestFriendsBasement.getThumbnailUrl().contains("f4.bcbits.com/img/")); + assertEquals(InfoItem.InfoType.STREAM, bestFriendsBasement.getInfoType()); + } + + /** + * Tests whether searching bandcamp for "C418" returns the artist's profile + */ + @Test + public void testChannelSearch() throws ExtractionException, IOException { + final SearchExtractor extractor = Bandcamp.getSearchExtractor("C418"); + final InfoItem c418 = extractor.getInitialPage() + .getItems().get(0); + + // C418's artist profile should be the first result, no? + assertEquals("C418", c418.getName()); + assertTrue(c418.getThumbnailUrl().endsWith(".jpg")); + assertTrue(c418.getThumbnailUrl().contains("f4.bcbits.com/img/")); + assertEquals("https://c418.bandcamp.com", c418.getUrl()); + + } + + /** + * Tests whether searching bandcamp for "minecraft volume alpha" returns the corresponding album + */ + @Test + public void testAlbumSearch() throws ExtractionException, IOException { + final SearchExtractor extractor = Bandcamp.getSearchExtractor("minecraft volume alpha"); + InfoItem minecraft = extractor.getInitialPage() + .getItems().get(0); + + // Minecraft volume alpha should be the first result, no? + assertEquals("Minecraft - Volume Alpha", minecraft.getName()); + assertTrue(minecraft.getThumbnailUrl().endsWith(".jpg")); + assertTrue(minecraft.getThumbnailUrl().contains("f4.bcbits.com/img/")); + assertEquals("https://c418.bandcamp.com/album/minecraft-volume-alpha", minecraft.getUrl()); + + // Verify that playlist tracks counts get extracted correctly + assertEquals(24, ((PlaylistInfoItem) minecraft).getStreamCount()); + + } + + /** + * Tests searches with multiple pages + */ + @Test + public void testMultiplePages() throws ExtractionException, IOException { + // A query practically guaranteed to have the maximum amount of pages + final SearchExtractor extractor = Bandcamp.getSearchExtractor("e"); + + final Page page2 = extractor.getInitialPage().getNextPage(); + assertEquals("https://bandcamp.com/search?q=e&page=2", page2.getUrl()); + + final Page page3 = extractor.getPage(page2).getNextPage(); + assertEquals("https://bandcamp.com/search?q=e&page=3", page3.getUrl()); + } + + public static class DefaultTest extends DefaultSearchExtractorTest { + private static SearchExtractor extractor; + private static final String QUERY = "noise"; + + @BeforeClass + public static void setUp() throws Exception { + NewPipe.init(DownloaderTestImpl.getInstance()); + extractor = Bandcamp.getSearchExtractor(QUERY); + extractor.fetchPage(); + } + + @Override public SearchExtractor extractor() { return extractor; } + @Override public StreamingService expectedService() { return Bandcamp; } + @Override public String expectedName() { return QUERY; } + @Override public String expectedId() { return QUERY; } + @Override public String expectedUrlContains() { return "bandcamp.com/search?q=" + QUERY; } + @Override public String expectedOriginalUrlContains() { return "bandcamp.com/search?q=" + QUERY; } + @Override public String expectedSearchString() { return QUERY; } + @Nullable @Override public String expectedSearchSuggestion() { return null; } + } +} diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampSearchQueryHandlerFactoryTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampSearchQueryHandlerFactoryTest.java new file mode 100644 index 000000000..c39115247 --- /dev/null +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampSearchQueryHandlerFactoryTest.java @@ -0,0 +1,34 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.schabi.newpipe.downloader.DownloaderTestImpl; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampSearchQueryHandlerFactory; + +import static org.junit.Assert.assertEquals; +import static org.schabi.newpipe.extractor.ServiceList.Bandcamp; + +public class BandcampSearchQueryHandlerFactoryTest { + + static BandcampSearchQueryHandlerFactory searchQuery; + + @BeforeClass + public static void setUp() { + NewPipe.init(DownloaderTestImpl.getInstance()); + + searchQuery = (BandcampSearchQueryHandlerFactory) Bandcamp + .getSearchQHFactory(); + } + + @Test + public void testEncoding() throws ParsingException { + // Note: this isn't exactly as bandcamp does it (it wouldn't encode '!'), but both works + assertEquals("https://bandcamp.com/search?q=hello%21%22%C2%A7%24%25%26%2F%28%29%3D&page=1", searchQuery.getUrl("hello!\"§$%&/()=")); + // Note: bandcamp uses %20 instead of '+', but both works + assertEquals("https://bandcamp.com/search?q=search+query+with+spaces&page=1", searchQuery.getUrl("search query with spaces")); + } +} diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampStreamExtractorTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampStreamExtractorTest.java new file mode 100644 index 000000000..a080de674 --- /dev/null +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampStreamExtractorTest.java @@ -0,0 +1,167 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.schabi.newpipe.downloader.DownloaderTestImpl; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.services.DefaultStreamExtractorTest; +import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper; +import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampStreamExtractor; +import org.schabi.newpipe.extractor.stream.StreamExtractor; +import org.schabi.newpipe.extractor.stream.StreamType; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.schabi.newpipe.extractor.ServiceList.Bandcamp; + +/** + * Tests for {@link BandcampStreamExtractor} + */ +public class BandcampStreamExtractorTest extends DefaultStreamExtractorTest { + + private static BandcampStreamExtractor extractor; + + @BeforeClass + public static void setUp() throws ExtractionException, IOException { + NewPipe.init(DownloaderTestImpl.getInstance()); + + extractor = (BandcampStreamExtractor) Bandcamp + .getStreamExtractor("https://teaganbear.bandcamp.com/track/just-for-the-halibut-creative-commons-attribution"); + extractor.fetchPage(); + } + + @Override + public StreamExtractor extractor() { + return extractor; + } + + @Override + public StreamingService expectedService() { + return Bandcamp; + } + + @Override + public String expectedName() { + return "Just for the Halibut [Creative Commons: Attribution]"; + } + + @Override + public String expectedId() { + return "https://teaganbear.bandcamp.com/track/just-for-the-halibut-creative-commons-attribution"; + } + + @Override + public String expectedUrlContains() { + return "https://teaganbear.bandcamp.com/track/just-for-the-halibut-creative-commons-attribution"; + } + + @Override + public String expectedOriginalUrlContains() { + return "https://teaganbear.bandcamp.com/track/just-for-the-halibut-creative-commons-attribution"; + } + + @Override + public StreamType expectedStreamType() { + return StreamType.AUDIO_STREAM; + } + + @Override + public String expectedUploaderName() { + return "Teaganbear"; + } + + @Override + public String expectedUploaderUrl() { + return "https://teaganbear.bandcamp.com/"; + } + + @Override + public List expectedDescriptionContains() { + return Collections.singletonList("it's Creative Commons so feel free to use it in whatever"); + } + + @Override + public long expectedLength() { + return 0; + } + + @Override + public long expectedViewCountAtLeast() { + return Long.MIN_VALUE; + } + + @Override + public String expectedUploadDate() { + return "2019-03-10 23:00:42.000"; + } + + @Override + public String expectedTextualUploadDate() { + return "10 Mar 2019 23:00:42 GMT"; + } + + @Override + public long expectedLikeCountAtLeast() { + return Long.MIN_VALUE; + } + + @Override + public long expectedDislikeCountAtLeast() { + return Long.MIN_VALUE; + } + + @Override + public boolean expectedHasVideoStreams() { + return false; + } + + @Override + public boolean expectedHasRelatedStreams() { + return false; + } + + @Override + public boolean expectedHasSubtitles() { + return false; + } + + @Override + public boolean expectedHasFrames() { + return false; + } + + @Override + public String expectedLicence() { + return "CC BY 3.0"; + } + + @Override + public String expectedCategory() { + return "dance"; + } + + @Test + public void testArtistProfilePicture() throws Exception { + final String url = extractor().getUploaderAvatarUrl(); + assertTrue(url.contains("://f4.bcbits.com/img/") && url.endsWith(".jpg")); + } + + @Test + public void testTranslateIdsToUrl() throws ParsingException { + // To add tests: look at website's source, search for `band_id` and `item_id` + assertEquals( + "https://teaganbear.bandcamp.com/track/just-for-the-halibut-creative-commons-attribution", + BandcampExtractorHelper.getStreamUrlFromIds(3877364987L, 3486455278L, "track") + ); + } + +} diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampStreamLinkHandlerFactoryTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampStreamLinkHandlerFactoryTest.java new file mode 100644 index 000000000..bf99fa308 --- /dev/null +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampStreamLinkHandlerFactoryTest.java @@ -0,0 +1,53 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.schabi.newpipe.downloader.DownloaderTestImpl; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampStreamLinkHandlerFactory; + +import static org.junit.Assert.*; + +/** + * Test for {@link BandcampStreamLinkHandlerFactory} + */ +public class BandcampStreamLinkHandlerFactoryTest { + + private static BandcampStreamLinkHandlerFactory linkHandler; + + @BeforeClass + public static void setUp() { + linkHandler = new BandcampStreamLinkHandlerFactory(); + NewPipe.init(DownloaderTestImpl.getInstance()); + } + + @Test + public void testGetRadioUrl() { + assertEquals("https://bandcamp.com/?show=1", linkHandler.getUrl("1")); + } + + @Test + public void testGetRadioId() throws ParsingException { + assertEquals("2", linkHandler.getId("https://bandcamp.com/?show=2")); + } + + @Test + public void testAcceptUrl() throws ParsingException { + assertFalse(linkHandler.acceptUrl("http://interovgm.com/releases/")); + assertFalse(linkHandler.acceptUrl("https://interovgm.com/releases")); + assertFalse(linkHandler.acceptUrl("http://zachbenson.bandcamp.com")); + assertFalse(linkHandler.acceptUrl("https://bandcamp.com")); + assertFalse(linkHandler.acceptUrl("https://zachbenson.bandcamp.com/")); + assertFalse(linkHandler.acceptUrl("https://powertothequeerkids.bandcamp.com/album/power-to-the-queer-kids")); + assertFalse(linkHandler.acceptUrl("https://example.com/track/sampletrack")); + + assertTrue(linkHandler.acceptUrl("https://zachbenson.bandcamp.com/track/kitchen")); + assertTrue(linkHandler.acceptUrl("http://ZachBenson.Bandcamp.COM/Track/U-I-Tonite/")); + assertTrue(linkHandler.acceptUrl("https://interovgm.com/track/title")); + assertTrue(linkHandler.acceptUrl("http://bandcamP.com/?show=38")); + assertTrue(linkHandler.acceptUrl("https://goodgoodblood-tl.bandcamp.com/track/when-it-all-wakes-up")); + } +} diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampSuggestionExtractorTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampSuggestionExtractorTest.java new file mode 100644 index 000000000..95f7de3a9 --- /dev/null +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampSuggestionExtractorTest.java @@ -0,0 +1,40 @@ +// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.extractor.services.bandcamp; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.schabi.newpipe.downloader.DownloaderTestImpl; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampSuggestionExtractor; + +import java.io.IOException; +import java.util.List; + +import static org.junit.Assert.assertTrue; +import static org.schabi.newpipe.extractor.ServiceList.Bandcamp; + +/** + * Tests for {@link BandcampSuggestionExtractor} + */ +public class BandcampSuggestionExtractorTest { + + private static BandcampSuggestionExtractor extractor; + + @BeforeClass + public static void setUp() { + NewPipe.init(DownloaderTestImpl.getInstance()); + extractor = (BandcampSuggestionExtractor) Bandcamp.getSuggestionExtractor(); + } + + @Test + public void testSearchExample() throws IOException, ExtractionException { + final List c418 = extractor.suggestionList("c418"); + + assertTrue(c418.contains("C418")); + + // There should be five results, but we can't be sure of that forever + assertTrue(c418.size() > 2); + } +} diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/utils/UtilsTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/utils/UtilsTest.java index e4a65505b..a81270de0 100644 --- a/extractor/src/test/java/org/schabi/newpipe/extractor/utils/UtilsTest.java +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/utils/UtilsTest.java @@ -20,6 +20,7 @@ public class UtilsTest { @Test public void testJoin() { assertEquals("some,random,stuff", Utils.join(",", Arrays.asList("some", "random", "stuff"))); + assertEquals("some,random,not-null,stuff", Utils.nonEmptyAndNullJoin(",", new String[]{"some", "null", "random", "", "not-null", null, "stuff"})); } @Test