Merge pull request #232 from fynngodau/dev

Bandcamp support
This commit is contained in:
Tobi 2021-03-05 21:37:57 +01:00 committed by GitHub
commit 021da75f24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 3291 additions and 2 deletions

View File

@ -44,6 +44,7 @@ The following sites are currently supported:
- SoundCloud
- media.ccc.de
- PeerTube (no P2P)
- Bandcamp
## License

View File

@ -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)
));
/**

View File

@ -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<String> getContentFilter(String url) throws ParsingException {
return new ArrayList<>(0);
return Collections.emptyList();
}
public String getSortFilter(String url) throws ParsingException {

View File

@ -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;
}
}

View File

@ -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<StreamInfoItem> 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<StreamInfoItem> 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");
}
}

View File

@ -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;
}
}

View File

@ -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.
* <a href=https://notabug.org/fynngodau/bandcampDirect/wiki/rewindBandcamp+%E2%80%93+Fetching+artist+details>
* More technical info.</a>
*/
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 <code>true</code> if the given URL looks like it comes from a bandcamp custom domain
* or if it comes from <code>bandcamp.com</code> 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 <meta> 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 <code>https://bandcamp.com/?show=SHOW_ID</code>
*/
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);
}
}
}

View File

@ -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<PlaylistInfoItem> {
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<PlaylistInfoItem> 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<PlaylistInfoItem> getPage(Page page) {
return null;
}
}

View File

@ -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<StreamInfoItem> 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<StreamInfoItem> getPage(Page page) {
return null;
}
@Nonnull
@Override
public String getName() throws ParsingException {
return name;
}
}

View File

@ -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;
}
}

View File

@ -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) : "";
}
}

View File

@ -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<StreamInfoItem> {
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<StreamInfoItem> 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<StreamInfoItem> getPage(final Page page) {
return null;
}
}

View File

@ -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;
}
}

View File

@ -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<AudioStream> getAudioStreams() {
final ArrayList<AudioStream> 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<String> getTags() {
return Collections.emptyList();
}
}

View File

@ -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<MetaInfo> getMetaInfo() throws ParsingException {
return Collections.emptyList();
}
public InfoItemsPage<InfoItem> 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<InfoItem> getInitialPage() throws IOException, ExtractionException {
return getPage(new Page(getUrl()));
}
@Override
public void onFetchPage(@Nonnull final Downloader downloader) throws IOException, ExtractionException {
}
}

View File

@ -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<AudioStream> getAudioStreams() {
final List<AudioStream> 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<VideoStream> getVideoStreams() {
return Collections.emptyList();
}
@Override
public List<VideoStream> getVideoOnlyStreams() {
return Collections.emptyList();
}
@Nonnull
@Override
public List<SubtitlesStream> getSubtitlesDefault() {
return Collections.emptyList();
}
@Nonnull
@Override
public List<SubtitlesStream> 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<String> getTags() {
final Elements tagElements = document.getElementsByAttributeValue("itemprop", "keywords");
final List<String> tags = new ArrayList<>();
for (final Element e : tagElements) {
tags.add(e.text());
}
return tags;
}
@Nonnull
@Override
public String getSupportInfo() {
return "";
}
@Nonnull
@Override
public List<StreamSegment> getStreamSegments() throws ParsingException {
return Collections.emptyList();
}
@Nonnull
@Override
public List<MetaInfo> getMetaInfo() throws ParsingException {
return Collections.emptyList();
}
}

View File

@ -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<String> 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<String> 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();
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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<String> 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);
}
}
}

View File

@ -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<String> 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));
}
}

View File

@ -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<String> 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);
}
}

View File

@ -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<String> 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);
}
}
}

View File

@ -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;
/**
* <p>Tracks don't have standalone ids, they are always in combination with the band id.
* That's why id = url.</p>
*
* <p>Radio (bandcamp weekly) shows do have ids.</p>
*/
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);
}
}

View File

@ -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);
}
}
/**
* <p>Get an attribute of a web page as JSON
*
* <p>Originally a part of bandcampDirect.</p>
* <p>Example HTML:</p>
* <pre>
* {@code
* <p data-town="{&quot;name&quot;:&quot;Mycenae&quot;,&quot;country&quot;:&quot;Greece&quot;}">This is Sparta!</p>
* }
* </pre>
* <p>Calling this function to get the attribute <code>data-town</code> returns the JsonObject for</p>
* <pre>
* {@code
* {
* "name": "Mycenae",
* "country": "Greece"
* }
* }
* </pre>
* @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);
}
}

View File

@ -271,4 +271,12 @@ public class Utils {
return join(delimiter, list);
}
/**
* Concatenate all non-null, non-empty and strings which are not equal to <code>"null"</code>.
*/
public static String nonEmptyAndNullJoin(final CharSequence delimiter, final String[] elements) {
final List<String> list = new java.util.ArrayList<>(Arrays.asList(elements));
list.removeIf(s -> isNullOrEmpty(s) || s.equals("null"));
return join(delimiter, list);
}
}

View File

@ -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());
}
}

View File

@ -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");
}
}

View File

@ -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<PlaylistInfoItem> list = extractor.getInitialPage().getItems();
assertTrue(list.size() > 1);
}
@Test
public void testHttps() throws ExtractionException, IOException {
final List<PlaylistInfoItem> 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());
}
}

View File

@ -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"));
}
}

View File

@ -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<StreamInfoItem> 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<StreamInfoItem> 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 {
}
}
}

View File

@ -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"));
}
}

View File

@ -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<StreamInfoItem> 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());
}
}

View File

@ -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<String> 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());
}
}

View File

@ -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<InfoItem> 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; }
}
}

View File

@ -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"));
}
}

View File

@ -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<String> 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")
);
}
}

View File

@ -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"));
}
}

View File

@ -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<String> 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);
}
}

View File

@ -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