commit
021da75f24
|
@ -44,6 +44,7 @@ The following sites are currently supported:
|
||||||
- SoundCloud
|
- SoundCloud
|
||||||
- media.ccc.de
|
- media.ccc.de
|
||||||
- PeerTube (no P2P)
|
- PeerTube (no P2P)
|
||||||
|
- Bandcamp
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.schabi.newpipe.extractor;
|
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.media_ccc.MediaCCCService;
|
||||||
import org.schabi.newpipe.extractor.services.peertube.PeertubeService;
|
import org.schabi.newpipe.extractor.services.peertube.PeertubeService;
|
||||||
import org.schabi.newpipe.extractor.services.soundcloud.SoundcloudService;
|
import org.schabi.newpipe.extractor.services.soundcloud.SoundcloudService;
|
||||||
|
@ -39,6 +40,7 @@ public final class ServiceList {
|
||||||
public static final SoundcloudService SoundCloud;
|
public static final SoundcloudService SoundCloud;
|
||||||
public static final MediaCCCService MediaCCC;
|
public static final MediaCCCService MediaCCC;
|
||||||
public static final PeertubeService PeerTube;
|
public static final PeertubeService PeerTube;
|
||||||
|
public static final BandcampService Bandcamp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When creating a new service, put this service in the end of this list,
|
* 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),
|
YouTube = new YoutubeService(0),
|
||||||
SoundCloud = new SoundcloudService(1),
|
SoundCloud = new SoundcloudService(1),
|
||||||
MediaCCC = new MediaCCCService(2),
|
MediaCCC = new MediaCCCService(2),
|
||||||
PeerTube = new PeertubeService(3)
|
PeerTube = new PeertubeService(3),
|
||||||
|
Bandcamp = new BandcampService(4)
|
||||||
));
|
));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -4,6 +4,7 @@ import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.utils.Utils;
|
import org.schabi.newpipe.extractor.utils.Utils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public abstract class ListLinkHandlerFactory extends LinkHandlerFactory {
|
public abstract class ListLinkHandlerFactory extends LinkHandlerFactory {
|
||||||
|
@ -13,7 +14,7 @@ public abstract class ListLinkHandlerFactory extends LinkHandlerFactory {
|
||||||
///////////////////////////////////
|
///////////////////////////////////
|
||||||
|
|
||||||
public List<String> getContentFilter(String url) throws ParsingException {
|
public List<String> getContentFilter(String url) throws ParsingException {
|
||||||
return new ArrayList<>(0);
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSortFilter(String url) throws ParsingException {
|
public String getSortFilter(String url) throws ParsingException {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) : "";
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,8 @@ import com.grack.nanojson.JsonObject;
|
||||||
import com.grack.nanojson.JsonParser;
|
import com.grack.nanojson.JsonParser;
|
||||||
import com.grack.nanojson.JsonParserException;
|
import com.grack.nanojson.JsonParserException;
|
||||||
|
|
||||||
|
import org.jsoup.Jsoup;
|
||||||
|
import org.jsoup.nodes.Document;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -118,4 +120,35 @@ public class JsonUtils {
|
||||||
throw new ParsingException("Could not parse JSON", e);
|
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="{"name":"Mycenae","country":"Greece"}">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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -271,4 +271,12 @@ public class Utils {
|
||||||
return join(delimiter, list);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ public class UtilsTest {
|
||||||
@Test
|
@Test
|
||||||
public void testJoin() {
|
public void testJoin() {
|
||||||
assertEquals("some,random,stuff", Utils.join(",", Arrays.asList("some", "random", "stuff")));
|
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
|
@Test
|
||||||
|
|
Loading…
Reference in New Issue