2020-11-12 21:19:45 +00:00
|
|
|
package me.kavin.piped.utils;
|
|
|
|
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.net.MalformedURLException;
|
|
|
|
import java.net.URI;
|
|
|
|
import java.net.URL;
|
|
|
|
import java.net.http.HttpRequest;
|
|
|
|
import java.net.http.HttpRequest.BodyPublishers;
|
2021-07-16 22:40:46 +00:00
|
|
|
import java.net.http.HttpRequest.Builder;
|
2020-11-12 21:19:45 +00:00
|
|
|
import java.net.http.HttpResponse.BodyHandlers;
|
2021-07-04 18:57:18 +00:00
|
|
|
import java.nio.charset.StandardCharsets;
|
2021-07-16 22:40:46 +00:00
|
|
|
import java.security.NoSuchAlgorithmException;
|
|
|
|
import java.security.spec.InvalidKeySpecException;
|
2021-06-14 19:44:23 +00:00
|
|
|
import java.util.Collections;
|
2021-07-19 19:44:18 +00:00
|
|
|
import java.util.Date;
|
2020-11-12 21:19:45 +00:00
|
|
|
import java.util.List;
|
2021-07-16 22:40:46 +00:00
|
|
|
import java.util.Map;
|
2020-11-17 05:34:50 +00:00
|
|
|
import java.util.concurrent.CompletableFuture;
|
2020-11-12 21:19:45 +00:00
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
2021-07-16 22:40:46 +00:00
|
|
|
import javax.persistence.criteria.CriteriaBuilder;
|
|
|
|
import javax.persistence.criteria.CriteriaQuery;
|
|
|
|
import javax.persistence.criteria.Root;
|
|
|
|
|
2020-11-17 05:34:50 +00:00
|
|
|
import org.apache.commons.lang3.exception.ExceptionUtils;
|
2021-07-16 22:40:46 +00:00
|
|
|
import org.hibernate.Session;
|
2020-11-12 21:19:45 +00:00
|
|
|
import org.json.JSONObject;
|
2020-12-14 07:11:42 +00:00
|
|
|
import org.schabi.newpipe.extractor.InfoItem;
|
2020-11-25 05:26:25 +00:00
|
|
|
import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage;
|
|
|
|
import org.schabi.newpipe.extractor.Page;
|
2020-11-12 21:19:45 +00:00
|
|
|
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
2021-05-28 12:08:32 +00:00
|
|
|
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
2020-11-12 21:19:45 +00:00
|
|
|
import org.schabi.newpipe.extractor.comments.CommentsInfo;
|
2021-04-03 15:08:32 +00:00
|
|
|
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
2020-11-12 21:19:45 +00:00
|
|
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
|
|
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
2021-07-04 17:53:33 +00:00
|
|
|
import org.schabi.newpipe.extractor.kiosk.KioskExtractor;
|
2020-11-12 21:19:45 +00:00
|
|
|
import org.schabi.newpipe.extractor.kiosk.KioskInfo;
|
2021-07-04 17:53:33 +00:00
|
|
|
import org.schabi.newpipe.extractor.kiosk.KioskList;
|
|
|
|
import org.schabi.newpipe.extractor.localization.ContentCountry;
|
2021-01-04 05:47:28 +00:00
|
|
|
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
2021-05-28 12:08:32 +00:00
|
|
|
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
|
2020-12-09 13:30:42 +00:00
|
|
|
import org.schabi.newpipe.extractor.search.SearchInfo;
|
2020-11-12 21:19:45 +00:00
|
|
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
|
|
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
2021-07-16 22:40:46 +00:00
|
|
|
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
|
2020-11-12 21:19:45 +00:00
|
|
|
|
2020-11-25 05:26:25 +00:00
|
|
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
2020-11-12 21:19:45 +00:00
|
|
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
|
|
|
import com.github.benmanes.caffeine.cache.LoadingCache;
|
2021-07-04 18:57:18 +00:00
|
|
|
import com.rometools.rome.feed.synd.SyndEntry;
|
|
|
|
import com.rometools.rome.feed.synd.SyndEntryImpl;
|
|
|
|
import com.rometools.rome.feed.synd.SyndFeed;
|
|
|
|
import com.rometools.rome.feed.synd.SyndFeedImpl;
|
2021-07-19 19:44:18 +00:00
|
|
|
import com.rometools.rome.feed.synd.SyndPerson;
|
|
|
|
import com.rometools.rome.feed.synd.SyndPersonImpl;
|
2021-07-04 18:57:18 +00:00
|
|
|
import com.rometools.rome.io.FeedException;
|
|
|
|
import com.rometools.rome.io.SyndFeedOutput;
|
2020-11-12 21:19:45 +00:00
|
|
|
|
2021-07-16 22:40:46 +00:00
|
|
|
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
2020-11-12 21:19:45 +00:00
|
|
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
|
|
|
import me.kavin.piped.consts.Constants;
|
2021-02-24 09:52:29 +00:00
|
|
|
import me.kavin.piped.ipfs.IPFS;
|
2020-11-12 21:19:45 +00:00
|
|
|
import me.kavin.piped.utils.obj.Channel;
|
2021-02-24 09:52:29 +00:00
|
|
|
import me.kavin.piped.utils.obj.ChapterSegment;
|
2021-04-03 15:08:32 +00:00
|
|
|
import me.kavin.piped.utils.obj.Comment;
|
|
|
|
import me.kavin.piped.utils.obj.CommentsPage;
|
2021-07-16 22:40:46 +00:00
|
|
|
import me.kavin.piped.utils.obj.FeedItem;
|
2020-11-25 05:26:25 +00:00
|
|
|
import me.kavin.piped.utils.obj.PipedStream;
|
2021-01-04 05:47:28 +00:00
|
|
|
import me.kavin.piped.utils.obj.Playlist;
|
2020-12-14 07:11:42 +00:00
|
|
|
import me.kavin.piped.utils.obj.SearchResults;
|
2020-11-12 21:19:45 +00:00
|
|
|
import me.kavin.piped.utils.obj.StreamItem;
|
|
|
|
import me.kavin.piped.utils.obj.Streams;
|
2021-01-04 05:47:28 +00:00
|
|
|
import me.kavin.piped.utils.obj.StreamsPage;
|
2020-11-12 21:19:45 +00:00
|
|
|
import me.kavin.piped.utils.obj.Subtitle;
|
2021-07-16 22:40:46 +00:00
|
|
|
import me.kavin.piped.utils.obj.db.PubSub;
|
|
|
|
import me.kavin.piped.utils.obj.db.User;
|
|
|
|
import me.kavin.piped.utils.obj.db.Video;
|
2021-05-28 12:08:32 +00:00
|
|
|
import me.kavin.piped.utils.obj.search.SearchChannel;
|
2020-12-09 13:30:42 +00:00
|
|
|
import me.kavin.piped.utils.obj.search.SearchItem;
|
2021-05-28 12:08:32 +00:00
|
|
|
import me.kavin.piped.utils.obj.search.SearchPlaylist;
|
2020-12-09 13:30:42 +00:00
|
|
|
import me.kavin.piped.utils.obj.search.SearchStream;
|
2021-07-16 22:40:46 +00:00
|
|
|
import me.kavin.piped.utils.resp.AcceptedResponse;
|
|
|
|
import me.kavin.piped.utils.resp.AlreadyRegisteredResponse;
|
|
|
|
import me.kavin.piped.utils.resp.AuthenticationFailureResponse;
|
|
|
|
import me.kavin.piped.utils.resp.IncorrectCredentialsResponse;
|
2021-07-20 21:03:44 +00:00
|
|
|
import me.kavin.piped.utils.resp.InvalidRequestResponse;
|
2021-07-16 22:40:46 +00:00
|
|
|
import me.kavin.piped.utils.resp.LoginResponse;
|
|
|
|
import me.kavin.piped.utils.resp.SubscribeStatusResponse;
|
2020-11-12 21:19:45 +00:00
|
|
|
|
|
|
|
public class ResponseHelper {
|
|
|
|
|
|
|
|
public static final LoadingCache<String, CommentsInfo> commentsCache = Caffeine.newBuilder()
|
2021-02-24 09:52:29 +00:00
|
|
|
.expireAfterWrite(1, TimeUnit.HOURS).maximumSize(1000)
|
|
|
|
.build(key -> CommentsInfo.getInfo("https://www.youtube.com/watch?v=" + key));
|
2020-11-12 21:19:45 +00:00
|
|
|
|
2021-01-12 08:15:09 +00:00
|
|
|
public static final byte[] streamsResponse(String videoId) throws Exception {
|
2020-11-17 05:34:50 +00:00
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
CompletableFuture<StreamInfo> futureStream = CompletableFuture.supplyAsync(() -> {
|
|
|
|
try {
|
|
|
|
return StreamInfo.getInfo("https://www.youtube.com/watch?v=" + videoId);
|
|
|
|
} catch (Exception e) {
|
|
|
|
ExceptionUtils.rethrow(e);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
});
|
|
|
|
|
|
|
|
CompletableFuture<String> futureLBRY = CompletableFuture.supplyAsync(() -> {
|
|
|
|
try {
|
|
|
|
return getLBRYStreamURL(videoId);
|
|
|
|
} catch (Exception e) {
|
|
|
|
ExceptionUtils.rethrow(e);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
});
|
|
|
|
|
|
|
|
final List<Subtitle> subtitles = new ObjectArrayList<>();
|
|
|
|
|
2021-05-28 15:17:25 +00:00
|
|
|
final StreamInfo info = futureStream.get();
|
2021-02-24 09:52:29 +00:00
|
|
|
|
|
|
|
// System.out.println(Constants.mapper.writeValueAsString(info.getStreamSegments()));
|
2021-04-21 14:22:58 +00:00
|
|
|
info.getSubtitles()
|
|
|
|
.forEach(subtitle -> subtitles.add(new Subtitle(rewriteURL(subtitle.getUrl()),
|
|
|
|
subtitle.getFormat().getMimeType(), subtitle.getDisplayLanguageName(),
|
|
|
|
subtitle.getLanguageTag(), subtitle.isAutoGenerated())));
|
2021-02-24 09:52:29 +00:00
|
|
|
|
|
|
|
final List<PipedStream> videoStreams = new ObjectArrayList<>();
|
|
|
|
final List<PipedStream> audioStreams = new ObjectArrayList<>();
|
|
|
|
|
2021-05-28 15:17:25 +00:00
|
|
|
final String lbryURL = futureLBRY.get();
|
2021-02-24 09:52:29 +00:00
|
|
|
|
|
|
|
if (lbryURL != null)
|
|
|
|
videoStreams.add(new PipedStream(lbryURL, "MP4", "LBRY", "video/mp4", false));
|
|
|
|
|
|
|
|
final String hls;
|
|
|
|
boolean livestream = false;
|
|
|
|
|
|
|
|
if ((hls = info.getHlsUrl()) != null && !hls.isEmpty())
|
|
|
|
livestream = true;
|
|
|
|
|
|
|
|
if (!livestream) {
|
|
|
|
info.getVideoOnlyStreams().forEach(stream -> videoStreams.add(new PipedStream(rewriteURL(stream.getUrl()),
|
|
|
|
String.valueOf(stream.getFormat()), stream.getResolution(), stream.getFormat().getMimeType(), true,
|
|
|
|
stream.getBitrate(), stream.getInitStart(), stream.getInitEnd(), stream.getIndexStart(),
|
|
|
|
stream.getIndexEnd(), stream.getCodec(), stream.getWidth(), stream.getHeight(), 30)));
|
|
|
|
info.getVideoStreams()
|
|
|
|
.forEach(stream -> videoStreams
|
|
|
|
.add(new PipedStream(rewriteURL(stream.getUrl()), String.valueOf(stream.getFormat()),
|
|
|
|
stream.getResolution(), stream.getFormat().getMimeType(), false)));
|
|
|
|
|
|
|
|
info.getAudioStreams()
|
|
|
|
.forEach(stream -> audioStreams.add(new PipedStream(rewriteURL(stream.getUrl()),
|
|
|
|
String.valueOf(stream.getFormat()), stream.getAverageBitrate() + " kbps",
|
|
|
|
stream.getFormat().getMimeType(), false, stream.getBitrate(), stream.getInitStart(),
|
|
|
|
stream.getInitEnd(), stream.getIndexStart(), stream.getIndexEnd(), stream.getCodec())));
|
|
|
|
}
|
|
|
|
|
|
|
|
final List<StreamItem> relatedStreams = new ObjectArrayList<>();
|
|
|
|
|
2021-04-21 14:02:50 +00:00
|
|
|
info.getRelatedItems().forEach(o -> {
|
2021-02-24 09:52:29 +00:00
|
|
|
StreamInfoItem item = (StreamInfoItem) o;
|
|
|
|
relatedStreams.add(new StreamItem(item.getUrl().substring(23), item.getName(),
|
|
|
|
rewriteURL(item.getThumbnailUrl()), item.getUploaderName(), item.getUploaderUrl().substring(23),
|
|
|
|
item.getTextualUploadDate(), item.getDuration(), item.getViewCount()));
|
|
|
|
});
|
|
|
|
|
|
|
|
List<ChapterSegment> segments = new ObjectArrayList<>();
|
|
|
|
|
|
|
|
info.getStreamSegments().forEach(
|
|
|
|
segment -> segments.add(new ChapterSegment(segment.getTitle(), segment.getStartTimeSeconds())));
|
|
|
|
|
2021-07-18 11:14:31 +00:00
|
|
|
long time = info.getUploadDate() != null ? info.getUploadDate().offsetDateTime().toInstant().toEpochMilli()
|
|
|
|
: System.currentTimeMillis();
|
2021-07-16 22:40:46 +00:00
|
|
|
|
|
|
|
if (info.getUploadDate() != null && System.currentTimeMillis() - time < TimeUnit.DAYS.toMillis(10))
|
|
|
|
updateViews(info.getId(), info.getViewCount(), time, false);
|
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
final Streams streams = new Streams(info.getName(), info.getDescription().getContent(),
|
|
|
|
info.getTextualUploadDate(), info.getUploaderName(), info.getUploaderUrl().substring(23),
|
|
|
|
rewriteURL(info.getUploaderAvatarUrl()), rewriteURL(info.getThumbnailUrl()), info.getDuration(),
|
|
|
|
info.getViewCount(), info.getLikeCount(), info.getDislikeCount(), audioStreams, videoStreams,
|
|
|
|
relatedStreams, subtitles, livestream, hls);
|
|
|
|
|
|
|
|
return Constants.mapper.writeValueAsBytes(streams);
|
2020-11-12 21:19:45 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-05-28 18:40:11 +00:00
|
|
|
public static final byte[] channelResponse(String channelPath) throws Exception {
|
2020-11-12 21:19:45 +00:00
|
|
|
|
2021-05-28 18:40:11 +00:00
|
|
|
final ChannelInfo info = ChannelInfo.getInfo("https://youtube.com/" + channelPath);
|
2020-11-12 21:19:45 +00:00
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
final List<StreamItem> relatedStreams = new ObjectArrayList<>();
|
2020-11-12 21:19:45 +00:00
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
info.getRelatedItems().forEach(o -> {
|
|
|
|
StreamInfoItem item = o;
|
|
|
|
relatedStreams.add(new StreamItem(item.getUrl().substring(23), item.getName(),
|
|
|
|
rewriteURL(item.getThumbnailUrl()), item.getUploaderName(), item.getUploaderUrl().substring(23),
|
|
|
|
item.getTextualUploadDate(), item.getDuration(), item.getViewCount()));
|
|
|
|
});
|
2020-11-12 21:19:45 +00:00
|
|
|
|
2021-07-16 22:40:46 +00:00
|
|
|
Multithreading.runAsync(() -> {
|
|
|
|
Session s = DatabaseSessionFactory.createSession();
|
|
|
|
|
|
|
|
me.kavin.piped.utils.obj.db.Channel channel = DatabaseHelper.getChannelFromId(s, info.getId());
|
|
|
|
|
|
|
|
if (channel != null) {
|
|
|
|
for (StreamInfoItem item : info.getRelatedItems()) {
|
|
|
|
long time = item.getUploadDate() != null
|
|
|
|
? item.getUploadDate().offsetDateTime().toInstant().toEpochMilli()
|
|
|
|
: System.currentTimeMillis();
|
|
|
|
if (System.currentTimeMillis() - time < TimeUnit.DAYS.toMillis(10))
|
|
|
|
updateViews(item.getUrl().substring("https://www.youtube.com/watch?v=".length()),
|
|
|
|
item.getViewCount(), time, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
s.close();
|
|
|
|
});
|
|
|
|
|
2021-06-05 19:34:33 +00:00
|
|
|
String nextpage = null;
|
2021-03-29 13:59:10 +00:00
|
|
|
if (info.hasNextPage()) {
|
|
|
|
Page page = info.getNextPage();
|
2021-06-05 19:34:33 +00:00
|
|
|
nextpage = Constants.mapper.writeValueAsString(page);
|
2021-03-29 13:59:10 +00:00
|
|
|
}
|
2020-11-25 05:26:25 +00:00
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
final Channel channel = new Channel(info.getId(), info.getName(), rewriteURL(info.getAvatarUrl()),
|
2021-06-05 19:34:33 +00:00
|
|
|
rewriteURL(info.getBannerUrl()), info.getDescription(), nextpage, relatedStreams);
|
2020-11-12 21:19:45 +00:00
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
IPFS.publishData(channel);
|
|
|
|
|
|
|
|
return Constants.mapper.writeValueAsBytes(channel);
|
2020-11-12 21:19:45 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-06-05 19:34:33 +00:00
|
|
|
public static final byte[] channelPageResponse(String channelId, String prevpageStr)
|
2021-02-24 09:52:29 +00:00
|
|
|
throws IOException, ExtractionException, InterruptedException {
|
2020-11-25 05:26:25 +00:00
|
|
|
|
2021-06-05 19:34:33 +00:00
|
|
|
Page prevpage = Constants.mapper.readValue(prevpageStr, Page.class);
|
|
|
|
|
2021-03-30 09:22:36 +00:00
|
|
|
InfoItemsPage<StreamInfoItem> info = ChannelInfo.getMoreItems(Constants.YOUTUBE_SERVICE,
|
2021-06-05 19:34:33 +00:00
|
|
|
"https://youtube.com/channel/" + channelId, prevpage);
|
2020-11-25 05:26:25 +00:00
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
final List<StreamItem> relatedStreams = new ObjectArrayList<>();
|
2020-11-25 05:26:25 +00:00
|
|
|
|
2021-03-30 09:22:36 +00:00
|
|
|
info.getItems().forEach(o -> {
|
2021-02-24 09:52:29 +00:00
|
|
|
StreamInfoItem item = o;
|
|
|
|
relatedStreams.add(new StreamItem(item.getUrl().substring(23), item.getName(),
|
|
|
|
rewriteURL(item.getThumbnailUrl()), item.getUploaderName(), item.getUploaderUrl().substring(23),
|
|
|
|
item.getTextualUploadDate(), item.getDuration(), item.getViewCount()));
|
|
|
|
});
|
2020-11-25 05:26:25 +00:00
|
|
|
|
2021-06-05 19:34:33 +00:00
|
|
|
String nextpage = null;
|
2021-03-30 09:22:36 +00:00
|
|
|
if (info.hasNextPage()) {
|
|
|
|
Page page = info.getNextPage();
|
2021-06-05 19:34:33 +00:00
|
|
|
nextpage = Constants.mapper.writeValueAsString(page);
|
2021-03-30 09:22:36 +00:00
|
|
|
}
|
2020-11-25 05:26:25 +00:00
|
|
|
|
2021-06-05 19:34:33 +00:00
|
|
|
final StreamsPage streamspage = new StreamsPage(nextpage, relatedStreams);
|
2020-11-25 05:26:25 +00:00
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
return Constants.mapper.writeValueAsBytes(streamspage);
|
2020-11-25 05:26:25 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-07-04 17:53:33 +00:00
|
|
|
public static final byte[] trendingResponse(String region)
|
|
|
|
throws ParsingException, ExtractionException, IOException {
|
2020-11-12 21:19:45 +00:00
|
|
|
|
2021-07-20 21:03:44 +00:00
|
|
|
if (region == null)
|
|
|
|
return Constants.mapper.writeValueAsBytes(new InvalidRequestResponse());
|
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
final List<StreamItem> relatedStreams = new ObjectArrayList<>();
|
2020-11-12 21:19:45 +00:00
|
|
|
|
2021-07-04 17:53:33 +00:00
|
|
|
KioskList kioskList = Constants.YOUTUBE_SERVICE.getKioskList();
|
|
|
|
kioskList.forceContentCountry(new ContentCountry(region));
|
|
|
|
KioskExtractor<?> extractor = kioskList.getDefaultKioskExtractor();
|
|
|
|
extractor.fetchPage();
|
|
|
|
KioskInfo info = KioskInfo.getInfo(extractor);
|
2020-11-12 21:19:45 +00:00
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
info.getRelatedItems().forEach(o -> {
|
|
|
|
StreamInfoItem item = o;
|
|
|
|
relatedStreams.add(new StreamItem(item.getUrl().substring(23), item.getName(),
|
|
|
|
rewriteURL(item.getThumbnailUrl()), item.getUploaderName(), item.getUploaderUrl().substring(23),
|
|
|
|
item.getTextualUploadDate(), item.getDuration(), item.getViewCount()));
|
|
|
|
});
|
2020-11-12 21:19:45 +00:00
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
return Constants.mapper.writeValueAsBytes(relatedStreams);
|
2020-11-12 21:19:45 +00:00
|
|
|
}
|
|
|
|
|
2021-01-12 08:15:09 +00:00
|
|
|
public static final byte[] playlistResponse(String playlistId)
|
2021-02-24 09:52:29 +00:00
|
|
|
throws IOException, ExtractionException, InterruptedException {
|
2021-01-04 05:47:28 +00:00
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
final PlaylistInfo info = PlaylistInfo.getInfo("https://www.youtube.com/playlist?list=" + playlistId);
|
2021-01-04 05:47:28 +00:00
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
final List<StreamItem> relatedStreams = new ObjectArrayList<>();
|
2021-01-04 05:47:28 +00:00
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
info.getRelatedItems().forEach(o -> {
|
|
|
|
StreamInfoItem item = o;
|
|
|
|
relatedStreams.add(new StreamItem(item.getUrl().substring(23), item.getName(),
|
|
|
|
rewriteURL(item.getThumbnailUrl()), item.getUploaderName(), item.getUploaderUrl().substring(23),
|
|
|
|
item.getTextualUploadDate(), item.getDuration(), item.getViewCount()));
|
|
|
|
});
|
2021-01-04 05:47:28 +00:00
|
|
|
|
2021-06-05 19:34:33 +00:00
|
|
|
String nextpage = null;
|
2021-03-30 09:36:01 +00:00
|
|
|
if (info.hasNextPage()) {
|
|
|
|
Page page = info.getNextPage();
|
2021-06-05 19:34:33 +00:00
|
|
|
nextpage = Constants.mapper.writeValueAsString(page);
|
2021-03-30 09:36:01 +00:00
|
|
|
}
|
2021-01-04 05:47:28 +00:00
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
final Playlist playlist = new Playlist(info.getName(), rewriteURL(info.getThumbnailUrl()),
|
2021-06-05 19:34:33 +00:00
|
|
|
rewriteURL(info.getBannerUrl()), nextpage,
|
2021-06-01 18:52:48 +00:00
|
|
|
info.getUploaderName().isEmpty() ? null : info.getUploaderName(),
|
|
|
|
info.getUploaderUrl().isEmpty() ? null : info.getUploaderUrl().substring(23),
|
|
|
|
rewriteURL(info.getUploaderAvatarUrl()), (int) info.getStreamCount(), relatedStreams);
|
2021-01-04 05:47:28 +00:00
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
return Constants.mapper.writeValueAsBytes(playlist);
|
2021-01-04 05:47:28 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-06-05 19:34:33 +00:00
|
|
|
public static final byte[] playlistPageResponse(String playlistId, String prevpageStr)
|
2021-02-24 09:52:29 +00:00
|
|
|
throws IOException, ExtractionException, InterruptedException {
|
2021-01-04 05:47:28 +00:00
|
|
|
|
2021-06-05 19:34:33 +00:00
|
|
|
Page prevpage = Constants.mapper.readValue(prevpageStr, Page.class);
|
|
|
|
|
2021-03-30 09:36:01 +00:00
|
|
|
InfoItemsPage<StreamInfoItem> info = PlaylistInfo.getMoreItems(Constants.YOUTUBE_SERVICE,
|
2021-06-05 19:34:33 +00:00
|
|
|
"https://www.youtube.com/playlist?list=" + playlistId, prevpage);
|
2021-01-04 05:47:28 +00:00
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
final List<StreamItem> relatedStreams = new ObjectArrayList<>();
|
2021-01-04 05:47:28 +00:00
|
|
|
|
2021-03-30 09:36:01 +00:00
|
|
|
info.getItems().forEach(o -> {
|
2021-02-24 09:52:29 +00:00
|
|
|
StreamInfoItem item = o;
|
|
|
|
relatedStreams.add(new StreamItem(item.getUrl().substring(23), item.getName(),
|
|
|
|
rewriteURL(item.getThumbnailUrl()), item.getUploaderName(), item.getUploaderUrl().substring(23),
|
|
|
|
item.getTextualUploadDate(), item.getDuration(), item.getViewCount()));
|
|
|
|
});
|
2021-01-04 05:47:28 +00:00
|
|
|
|
2021-06-05 19:34:33 +00:00
|
|
|
String nextpage = null;
|
2021-03-30 09:36:01 +00:00
|
|
|
if (info.hasNextPage()) {
|
|
|
|
Page page = info.getNextPage();
|
2021-06-05 19:34:33 +00:00
|
|
|
nextpage = Constants.mapper.writeValueAsString(page);
|
2021-03-30 09:36:01 +00:00
|
|
|
}
|
2021-01-04 05:47:28 +00:00
|
|
|
|
2021-06-05 19:34:33 +00:00
|
|
|
final StreamsPage streamspage = new StreamsPage(nextpage, relatedStreams);
|
2021-01-04 05:47:28 +00:00
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
return Constants.mapper.writeValueAsBytes(streamspage);
|
2021-01-04 05:47:28 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-07-04 18:57:18 +00:00
|
|
|
public static final byte[] playlistRSSResponse(String playlistId)
|
|
|
|
throws IOException, ExtractionException, InterruptedException, FeedException {
|
|
|
|
|
|
|
|
final PlaylistInfo info = PlaylistInfo.getInfo("https://www.youtube.com/playlist?list=" + playlistId);
|
|
|
|
|
|
|
|
final List<SyndEntry> entries = new ObjectArrayList<>();
|
|
|
|
|
|
|
|
SyndFeed feed = new SyndFeedImpl();
|
|
|
|
feed.setFeedType("rss_2.0");
|
|
|
|
feed.setTitle(info.getName());
|
|
|
|
feed.setAuthor(info.getUploaderName());
|
|
|
|
feed.setLink(info.getUrl());
|
|
|
|
feed.setDescription(String.format("%s - Piped", info.getName()));
|
|
|
|
|
|
|
|
info.getRelatedItems().forEach(o -> {
|
|
|
|
StreamInfoItem item = o;
|
|
|
|
SyndEntry entry = new SyndEntryImpl();
|
|
|
|
entry.setAuthor(item.getUploaderName());
|
|
|
|
entry.setUri(item.getUrl());
|
|
|
|
entry.setTitle(item.getName());
|
|
|
|
entries.add(entry);
|
|
|
|
});
|
|
|
|
|
|
|
|
feed.setEntries(entries);
|
|
|
|
|
|
|
|
return new SyndFeedOutput().outputString(feed).getBytes(StandardCharsets.UTF_8);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-01-12 08:15:09 +00:00
|
|
|
public static final byte[] suggestionsResponse(String query)
|
2021-02-24 09:52:29 +00:00
|
|
|
throws JsonProcessingException, IOException, ExtractionException {
|
2020-11-25 05:26:25 +00:00
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
return Constants.mapper
|
|
|
|
.writeValueAsBytes(Constants.YOUTUBE_SERVICE.getSuggestionExtractor().suggestionList(query));
|
2020-11-25 05:26:25 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-06-14 19:44:23 +00:00
|
|
|
public static final byte[] searchResponse(String q, String filter)
|
|
|
|
throws IOException, ExtractionException, InterruptedException {
|
2020-12-09 13:30:42 +00:00
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
final SearchInfo info = SearchInfo.getInfo(Constants.YOUTUBE_SERVICE,
|
2021-06-14 19:44:23 +00:00
|
|
|
Constants.YOUTUBE_SERVICE.getSearchQHFactory().fromQuery(q, Collections.singletonList(filter), null));
|
2021-02-24 09:52:29 +00:00
|
|
|
|
|
|
|
ObjectArrayList<SearchItem> items = new ObjectArrayList<>();
|
|
|
|
|
|
|
|
info.getRelatedItems().forEach(item -> {
|
|
|
|
switch (item.getInfoType()) {
|
|
|
|
case STREAM:
|
|
|
|
StreamInfoItem stream = (StreamInfoItem) item;
|
|
|
|
items.add(new SearchStream(item.getName(), rewriteURL(item.getThumbnailUrl()),
|
2021-05-06 16:26:03 +00:00
|
|
|
item.getUrl().substring(23), stream.getTextualUploadDate(), stream.getUploaderName(),
|
2021-06-14 19:44:23 +00:00
|
|
|
optionalSubstring(stream.getUploaderUrl(), 23), stream.getViewCount(), stream.getDuration(),
|
2021-05-28 12:08:32 +00:00
|
|
|
stream.isUploaderVerified()));
|
2021-02-24 09:52:29 +00:00
|
|
|
break;
|
|
|
|
case CHANNEL:
|
2021-05-28 12:08:32 +00:00
|
|
|
ChannelInfoItem channel = (ChannelInfoItem) item;
|
|
|
|
items.add(new SearchChannel(item.getName(), rewriteURL(item.getThumbnailUrl()),
|
|
|
|
item.getUrl().substring(23), channel.getDescription(), channel.getSubscriberCount(),
|
|
|
|
channel.getStreamCount(), channel.isVerified()));
|
|
|
|
break;
|
|
|
|
case PLAYLIST:
|
|
|
|
PlaylistInfoItem playlist = (PlaylistInfoItem) item;
|
|
|
|
items.add(new SearchPlaylist(item.getName(), rewriteURL(item.getThumbnailUrl()),
|
|
|
|
item.getUrl().substring(23), playlist.getUploaderName(), playlist.getStreamCount()));
|
2021-02-24 09:52:29 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
Page nextpage = info.getNextPage();
|
|
|
|
|
2021-06-14 19:44:23 +00:00
|
|
|
return Constants.mapper
|
|
|
|
.writeValueAsBytes(new SearchResults(items, Constants.mapper.writeValueAsString(nextpage)));
|
2020-12-14 07:11:42 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-06-14 19:44:23 +00:00
|
|
|
public static final byte[] searchPageResponse(String q, String filter, String prevpageStr)
|
2021-02-24 09:52:29 +00:00
|
|
|
throws IOException, ExtractionException, InterruptedException {
|
|
|
|
|
2021-06-14 19:44:23 +00:00
|
|
|
Page prevpage = Constants.mapper.readValue(prevpageStr, Page.class);
|
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
InfoItemsPage<InfoItem> pages = SearchInfo.getMoreItems(Constants.YOUTUBE_SERVICE,
|
2021-06-14 19:44:23 +00:00
|
|
|
Constants.YOUTUBE_SERVICE.getSearchQHFactory().fromQuery(q, Collections.singletonList(filter), null),
|
|
|
|
prevpage);
|
2021-02-24 09:52:29 +00:00
|
|
|
|
|
|
|
ObjectArrayList<SearchItem> items = new ObjectArrayList<>();
|
|
|
|
|
|
|
|
pages.getItems().forEach(item -> {
|
|
|
|
switch (item.getInfoType()) {
|
|
|
|
case STREAM:
|
|
|
|
StreamInfoItem stream = (StreamInfoItem) item;
|
|
|
|
items.add(new SearchStream(item.getName(), rewriteURL(item.getThumbnailUrl()),
|
2021-05-06 16:26:03 +00:00
|
|
|
item.getUrl().substring(23), stream.getTextualUploadDate(), stream.getUploaderName(),
|
2021-06-14 19:48:06 +00:00
|
|
|
optionalSubstring(stream.getUploaderUrl(), 23), stream.getViewCount(), stream.getDuration(),
|
2021-05-28 12:08:32 +00:00
|
|
|
stream.isUploaderVerified()));
|
2021-02-24 09:52:29 +00:00
|
|
|
break;
|
|
|
|
case CHANNEL:
|
2021-05-28 12:08:32 +00:00
|
|
|
ChannelInfoItem channel = (ChannelInfoItem) item;
|
|
|
|
items.add(new SearchChannel(item.getName(), rewriteURL(item.getThumbnailUrl()),
|
|
|
|
item.getUrl().substring(23), channel.getDescription(), channel.getSubscriberCount(),
|
|
|
|
channel.getStreamCount(), channel.isVerified()));
|
|
|
|
break;
|
|
|
|
case PLAYLIST:
|
|
|
|
PlaylistInfoItem playlist = (PlaylistInfoItem) item;
|
|
|
|
items.add(new SearchPlaylist(item.getName(), rewriteURL(item.getThumbnailUrl()),
|
|
|
|
item.getUrl().substring(23), playlist.getUploaderName(), playlist.getStreamCount()));
|
2021-02-24 09:52:29 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
Page nextpage = pages.getNextPage();
|
|
|
|
|
2021-06-14 19:44:23 +00:00
|
|
|
return Constants.mapper
|
|
|
|
.writeValueAsBytes(new SearchResults(items, Constants.mapper.writeValueAsString(nextpage)));
|
2020-12-09 13:30:42 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-04-03 15:08:32 +00:00
|
|
|
public static final byte[] commentsResponse(String videoId) throws Exception {
|
|
|
|
|
|
|
|
CommentsInfo info = commentsCache.get(videoId);
|
|
|
|
|
|
|
|
List<Comment> comments = new ObjectArrayList<>();
|
|
|
|
|
|
|
|
info.getRelatedItems().forEach(comment -> {
|
|
|
|
comments.add(new Comment(comment.getUploaderName(), rewriteURL(comment.getUploaderAvatarUrl()),
|
|
|
|
comment.getCommentId(), comment.getCommentText(), comment.getTextualUploadDate(),
|
2021-05-03 15:36:53 +00:00
|
|
|
comment.getUploaderUrl().substring(19), comment.getLikeCount(), comment.isHeartedByUploader(),
|
2021-04-03 15:08:32 +00:00
|
|
|
comment.isPinned(), comment.isUploaderVerified()));
|
|
|
|
});
|
|
|
|
|
|
|
|
String nextpage = null;
|
|
|
|
|
|
|
|
if (info.getNextPage() != null)
|
|
|
|
nextpage = info.getNextPage().getUrl();
|
|
|
|
|
|
|
|
CommentsPage commentsItem = new CommentsPage(comments, nextpage);
|
|
|
|
|
|
|
|
return Constants.mapper.writeValueAsBytes(commentsItem);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public static final byte[] commentsPageResponse(String videoId, String url) throws Exception {
|
|
|
|
|
|
|
|
CommentsInfo init = commentsCache.get(videoId);
|
|
|
|
|
|
|
|
InfoItemsPage<CommentsInfoItem> info = CommentsInfo.getMoreItems(init, new Page(url));
|
|
|
|
|
|
|
|
List<Comment> comments = new ObjectArrayList<>();
|
|
|
|
|
|
|
|
info.getItems().forEach(comment -> {
|
|
|
|
comments.add(new Comment(comment.getUploaderName(), rewriteURL(comment.getUploaderAvatarUrl()),
|
|
|
|
comment.getCommentId(), comment.getCommentText(), comment.getTextualUploadDate(),
|
2021-05-03 15:36:53 +00:00
|
|
|
comment.getUploaderUrl().substring(19), comment.getLikeCount(), comment.isHeartedByUploader(),
|
2021-04-03 15:08:32 +00:00
|
|
|
comment.isPinned(), comment.isUploaderVerified()));
|
|
|
|
});
|
|
|
|
|
|
|
|
String nextpage = null;
|
|
|
|
|
|
|
|
if (info.getNextPage() != null)
|
|
|
|
nextpage = info.getNextPage().getUrl();
|
|
|
|
|
|
|
|
CommentsPage commentsItem = new CommentsPage(comments, nextpage);
|
|
|
|
|
|
|
|
return Constants.mapper.writeValueAsBytes(commentsItem);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-07-16 22:40:46 +00:00
|
|
|
private static final Argon2PasswordEncoder argon2PasswordEncoder = new Argon2PasswordEncoder();
|
|
|
|
|
|
|
|
public static final byte[] registerResponse(String user, String pass)
|
|
|
|
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
|
|
|
|
|
2021-07-20 21:03:44 +00:00
|
|
|
if (user == null || pass == null)
|
|
|
|
return Constants.mapper.writeValueAsBytes(new InvalidRequestResponse());
|
|
|
|
|
2021-07-16 22:40:46 +00:00
|
|
|
user = user.toLowerCase();
|
|
|
|
|
|
|
|
Session s = DatabaseSessionFactory.createSession();
|
|
|
|
CriteriaBuilder cb = s.getCriteriaBuilder();
|
|
|
|
CriteriaQuery<User> cr = cb.createQuery(User.class);
|
|
|
|
Root<User> root = cr.from(User.class);
|
|
|
|
cr.select(root).where(root.get("username").in(user));
|
|
|
|
boolean registered = s.createQuery(cr).uniqueResult() != null;
|
|
|
|
|
|
|
|
if (registered) {
|
|
|
|
s.close();
|
|
|
|
return Constants.mapper.writeValueAsBytes(new AlreadyRegisteredResponse());
|
|
|
|
}
|
|
|
|
|
|
|
|
User newuser = new User(user, argon2PasswordEncoder.encode(pass), Collections.emptyList());
|
|
|
|
|
|
|
|
s.save(newuser);
|
|
|
|
s.getTransaction().begin();
|
|
|
|
s.getTransaction().commit();
|
|
|
|
|
|
|
|
s.close();
|
|
|
|
|
|
|
|
return Constants.mapper.writeValueAsBytes(new LoginResponse(newuser.getSessionId()));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public static final byte[] loginResponse(String user, String pass)
|
|
|
|
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
|
|
|
|
|
2021-07-20 21:03:44 +00:00
|
|
|
if (user == null || pass == null)
|
|
|
|
return Constants.mapper.writeValueAsBytes(new InvalidRequestResponse());
|
|
|
|
|
2021-07-16 22:40:46 +00:00
|
|
|
user = user.toLowerCase();
|
|
|
|
|
|
|
|
Session s = DatabaseSessionFactory.createSession();
|
|
|
|
CriteriaBuilder cb = s.getCriteriaBuilder();
|
|
|
|
CriteriaQuery<User> cr = cb.createQuery(User.class);
|
|
|
|
Root<User> root = cr.from(User.class);
|
|
|
|
cr.select(root).where(root.get("username").in(user));
|
|
|
|
|
|
|
|
User dbuser = s.createQuery(cr).uniqueResult();
|
|
|
|
|
|
|
|
if (dbuser != null && argon2PasswordEncoder.matches(pass, dbuser.getPassword())) {
|
|
|
|
s.close();
|
|
|
|
return Constants.mapper.writeValueAsBytes(new LoginResponse(dbuser.getSessionId()));
|
|
|
|
}
|
|
|
|
|
|
|
|
s.close();
|
|
|
|
|
|
|
|
return Constants.mapper.writeValueAsBytes(new IncorrectCredentialsResponse());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public static final byte[] subscribeResponse(String session, String channelId)
|
|
|
|
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
|
|
|
|
|
|
|
|
Session s = DatabaseSessionFactory.createSession();
|
|
|
|
|
|
|
|
User user = DatabaseHelper.getUserFromSessionWithSubscribed(s, session);
|
|
|
|
|
|
|
|
if (user != null) {
|
|
|
|
if (!user.getSubscribed().contains(channelId)) {
|
|
|
|
|
|
|
|
s.getTransaction().begin();
|
|
|
|
s.createNativeQuery("insert into users_subscribed (subscriber, channel) values (?,?)")
|
|
|
|
.setParameter(1, user.getId()).setParameter(2, channelId).executeUpdate();
|
|
|
|
s.getTransaction().commit();
|
|
|
|
s.close();
|
|
|
|
|
|
|
|
Multithreading.runAsync(() -> {
|
|
|
|
Session sess = DatabaseSessionFactory.createSession();
|
|
|
|
me.kavin.piped.utils.obj.db.Channel channel = DatabaseHelper.getChannelFromId(sess, channelId);
|
|
|
|
|
|
|
|
if (channel == null) {
|
|
|
|
ChannelInfo info = null;
|
|
|
|
|
|
|
|
try {
|
|
|
|
info = ChannelInfo.getInfo("https://youtube.com/channel/" + channelId);
|
|
|
|
} catch (IOException | ExtractionException e) {
|
|
|
|
ExceptionUtils.rethrow(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
channel = new me.kavin.piped.utils.obj.db.Channel(channelId, info.getName(),
|
|
|
|
info.getAvatarUrl(), false);
|
|
|
|
sess.save(channel);
|
|
|
|
sess.beginTransaction().commit();
|
|
|
|
|
2021-07-21 12:37:43 +00:00
|
|
|
Multithreading.runAsync(() -> {
|
|
|
|
try {
|
|
|
|
Session sessSub = DatabaseSessionFactory.createSession();
|
|
|
|
subscribePubSub(channelId, sessSub);
|
|
|
|
sessSub.close();
|
|
|
|
} catch (Exception e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
});
|
2021-07-16 22:40:46 +00:00
|
|
|
|
|
|
|
for (StreamInfoItem item : info.getRelatedItems()) {
|
|
|
|
long time = item.getUploadDate() != null
|
|
|
|
? item.getUploadDate().offsetDateTime().toInstant().toEpochMilli()
|
|
|
|
: System.currentTimeMillis();
|
|
|
|
if ((System.currentTimeMillis() - time) < TimeUnit.DAYS.toMillis(10))
|
2021-07-17 14:02:57 +00:00
|
|
|
handleNewVideo(item.getUrl(), time, channel, sess);
|
2021-07-16 22:40:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sess.close();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return Constants.mapper.writeValueAsBytes(new AcceptedResponse());
|
|
|
|
}
|
|
|
|
|
|
|
|
s.close();
|
|
|
|
|
|
|
|
return Constants.mapper.writeValueAsBytes(new AuthenticationFailureResponse());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public static final byte[] unsubscribeResponse(String session, String channelId)
|
|
|
|
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
|
|
|
|
|
|
|
|
Session s = DatabaseSessionFactory.createSession();
|
|
|
|
|
|
|
|
User user = DatabaseHelper.getUserFromSession(s, session);
|
|
|
|
|
|
|
|
if (user != null) {
|
|
|
|
s.getTransaction().begin();
|
|
|
|
s.createNativeQuery("delete from users_subscribed where subscriber = :id and channel = :channel")
|
|
|
|
.setParameter("id", user.getId()).setParameter("channel", channelId).executeUpdate();
|
|
|
|
s.getTransaction().commit();
|
|
|
|
s.close();
|
|
|
|
return Constants.mapper.writeValueAsBytes(new AcceptedResponse());
|
|
|
|
}
|
|
|
|
|
|
|
|
s.close();
|
|
|
|
|
|
|
|
return Constants.mapper.writeValueAsBytes(new AuthenticationFailureResponse());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public static final byte[] isSubscribedResponse(String session, String channelId)
|
|
|
|
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
|
|
|
|
|
|
|
|
Session s = DatabaseSessionFactory.createSession();
|
|
|
|
|
|
|
|
User user = DatabaseHelper.getUserFromSessionWithSubscribed(s, session);
|
|
|
|
|
|
|
|
if (user != null) {
|
|
|
|
if (user.getSubscribed().contains(channelId)) {
|
|
|
|
s.close();
|
|
|
|
return Constants.mapper.writeValueAsBytes(new SubscribeStatusResponse(true));
|
|
|
|
}
|
|
|
|
s.close();
|
|
|
|
return Constants.mapper.writeValueAsBytes(new SubscribeStatusResponse(false));
|
|
|
|
}
|
|
|
|
|
|
|
|
s.close();
|
|
|
|
|
|
|
|
return Constants.mapper.writeValueAsBytes(new AuthenticationFailureResponse());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public static final byte[] feedResponse(String session)
|
|
|
|
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
|
|
|
|
|
|
|
|
Session s = DatabaseSessionFactory.createSession();
|
|
|
|
|
|
|
|
User user = DatabaseHelper.getUserFromSessionWithSubscribed(s, session);
|
|
|
|
|
|
|
|
if (user != null) {
|
|
|
|
|
|
|
|
List<FeedItem> feedItems = new ObjectArrayList<>();
|
|
|
|
|
|
|
|
if (user.getSubscribed() != null && !user.getSubscribed().isEmpty()) {
|
2021-01-04 05:47:28 +00:00
|
|
|
|
2021-07-16 22:40:46 +00:00
|
|
|
List<Video> videos = DatabaseHelper.getVideosFromChannelIds(s, user.getSubscribed());
|
|
|
|
|
|
|
|
videos.forEach(video -> {
|
2021-07-21 12:19:05 +00:00
|
|
|
feedItems.add(new FeedItem("/watch?v=" + video.getId(), video.getTitle(),
|
|
|
|
rewriteURL(video.getThumbnail()), "/channel/" + video.getChannel().getUploaderId(),
|
|
|
|
video.getChannel().getUploader(), rewriteURL(video.getChannel().getUploaderAvatar()),
|
|
|
|
video.getViews(), video.getDuration(), video.getUploaded(),
|
|
|
|
video.getChannel().isVerified()));
|
2021-07-16 22:40:46 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
Collections.sort(feedItems, (a, b) -> (int) (b.uploaded - a.uploaded));
|
|
|
|
}
|
|
|
|
|
|
|
|
s.close();
|
|
|
|
|
|
|
|
return Constants.mapper.writeValueAsBytes(feedItems);
|
|
|
|
}
|
|
|
|
|
|
|
|
s.close();
|
|
|
|
|
|
|
|
return Constants.mapper.writeValueAsBytes(new AuthenticationFailureResponse());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-07-19 19:44:18 +00:00
|
|
|
public static final byte[] feedResponseRSS(String session)
|
|
|
|
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, FeedException {
|
|
|
|
|
|
|
|
Session s = DatabaseSessionFactory.createSession();
|
|
|
|
|
|
|
|
User user = DatabaseHelper.getUserFromSessionWithSubscribed(s, session);
|
|
|
|
|
|
|
|
if (user != null) {
|
|
|
|
|
|
|
|
SyndFeed feed = new SyndFeedImpl();
|
|
|
|
feed.setFeedType("atom_1.0");
|
|
|
|
feed.setTitle("Piped - Feed");
|
|
|
|
feed.setDescription(String.format("Piped's RSS subscription feed for %s.", user.getUsername()));
|
|
|
|
feed.setUri("https://piped.kavin.rocks/feed");
|
|
|
|
|
|
|
|
if (user.getSubscribed() != null && !user.getSubscribed().isEmpty()) {
|
|
|
|
|
|
|
|
List<Video> videos = DatabaseHelper.getVideosFromChannelIds(s, user.getSubscribed());
|
|
|
|
|
|
|
|
Collections.sort(videos, (a, b) -> (int) (b.getUploaded() - a.getUploaded()));
|
|
|
|
|
|
|
|
final List<SyndEntry> entries = new ObjectArrayList<>();
|
|
|
|
|
|
|
|
for (Video video : videos) {
|
|
|
|
SyndEntry entry = new SyndEntryImpl();
|
|
|
|
|
|
|
|
SyndPerson person = new SyndPersonImpl();
|
|
|
|
person.setName(video.getChannel().getUploader());
|
|
|
|
person.setUri("https://piped.kavin.rocks/channel/" + video.getChannel().getUploaderId());
|
|
|
|
|
|
|
|
entry.setAuthors(Collections.singletonList(person));
|
|
|
|
|
|
|
|
entry.setUri("https://piped.kavin.rocks/watch?v=" + video.getId());
|
|
|
|
entry.setTitle(video.getTitle());
|
|
|
|
entry.setPublishedDate(new Date(video.getUploaded()));
|
|
|
|
entries.add(entry);
|
|
|
|
}
|
|
|
|
|
|
|
|
feed.setEntries(entries);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
s.close();
|
|
|
|
|
|
|
|
return new SyndFeedOutput().outputString(feed).getBytes(StandardCharsets.UTF_8);
|
|
|
|
}
|
|
|
|
|
|
|
|
s.close();
|
|
|
|
|
|
|
|
return Constants.mapper.writeValueAsBytes(new AuthenticationFailureResponse());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-07-16 22:40:46 +00:00
|
|
|
public static final byte[] importResponse(String session, String[] channelIds)
|
|
|
|
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
|
|
|
|
|
|
|
|
Session s = DatabaseSessionFactory.createSession();
|
|
|
|
|
|
|
|
User user = DatabaseHelper.getUserFromSessionWithSubscribed(s, session);
|
|
|
|
|
|
|
|
if (user != null) {
|
|
|
|
|
|
|
|
Multithreading.runAsync(() -> {
|
|
|
|
for (String channelId : channelIds)
|
|
|
|
if (!user.getSubscribed().contains(channelId))
|
|
|
|
user.getSubscribed().add(channelId);
|
|
|
|
|
|
|
|
if (channelIds.length > 0) {
|
|
|
|
s.update(user);
|
|
|
|
s.beginTransaction().commit();
|
|
|
|
}
|
|
|
|
|
|
|
|
s.close();
|
|
|
|
});
|
|
|
|
|
|
|
|
for (String channelId : channelIds) {
|
|
|
|
|
|
|
|
Multithreading.runAsyncLimited(() -> {
|
|
|
|
try {
|
|
|
|
|
|
|
|
Session sess = DatabaseSessionFactory.createSession();
|
|
|
|
|
|
|
|
me.kavin.piped.utils.obj.db.Channel channel = DatabaseHelper.getChannelFromId(sess, channelId);
|
|
|
|
|
|
|
|
if (channel == null) {
|
|
|
|
ChannelInfo info = null;
|
|
|
|
|
|
|
|
try {
|
|
|
|
info = ChannelInfo.getInfo("https://youtube.com/channel/" + channelId);
|
|
|
|
} catch (Exception e) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
channel = new me.kavin.piped.utils.obj.db.Channel(channelId, info.getName(),
|
|
|
|
info.getAvatarUrl(), false);
|
|
|
|
sess.save(channel);
|
|
|
|
|
|
|
|
Multithreading.runAsync(() -> {
|
|
|
|
try {
|
|
|
|
Session sessSub = DatabaseSessionFactory.createSession();
|
|
|
|
subscribePubSub(channelId, sessSub);
|
|
|
|
sessSub.close();
|
2021-07-21 12:37:43 +00:00
|
|
|
} catch (Exception e) {
|
2021-07-16 22:40:46 +00:00
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
for (StreamInfoItem item : info.getRelatedItems()) {
|
|
|
|
long time = item.getUploadDate() != null
|
|
|
|
? item.getUploadDate().offsetDateTime().toInstant().toEpochMilli()
|
|
|
|
: System.currentTimeMillis();
|
|
|
|
if ((System.currentTimeMillis() - time) < TimeUnit.DAYS.toMillis(10))
|
|
|
|
handleNewVideo(item.getUrl(), time, channel, sess);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!sess.getTransaction().isActive())
|
|
|
|
sess.getTransaction().begin();
|
|
|
|
sess.getTransaction().commit();
|
|
|
|
}
|
|
|
|
|
|
|
|
sess.close();
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return Constants.mapper.writeValueAsBytes(new AcceptedResponse());
|
|
|
|
}
|
|
|
|
|
|
|
|
s.close();
|
|
|
|
|
|
|
|
return Constants.mapper.writeValueAsBytes(new AuthenticationFailureResponse());
|
2021-01-04 05:47:28 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-11-12 21:19:45 +00:00
|
|
|
private static final String getLBRYStreamURL(String videoId) throws IOException, InterruptedException {
|
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
String lbryId = new JSONObject(Constants.h2client.send(HttpRequest
|
|
|
|
.newBuilder(URI.create("https://api.lbry.com/yt/resolve?video_ids=" + URLUtils.silentEncode(videoId)))
|
|
|
|
.setHeader("User-Agent", Constants.USER_AGENT).build(), BodyHandlers.ofString()).body())
|
|
|
|
.getJSONObject("data").getJSONObject("videos").optString(videoId);
|
|
|
|
|
|
|
|
if (!lbryId.isEmpty())
|
2021-04-03 15:08:32 +00:00
|
|
|
new JSONObject(Constants.h2client.send(HttpRequest
|
|
|
|
.newBuilder(URI.create("https://api.lbry.tv/api/v1/proxy?m=get"))
|
|
|
|
.POST(BodyPublishers.ofString(
|
|
|
|
String.valueOf(new JSONObject().put("jsonrpc", "2.0").put("method", "get").put("params",
|
|
|
|
new JSONObject().put("uri", "lbry://" + lbryId).put("save_file", true)))))
|
|
|
|
.build(), BodyHandlers.ofString()).body()).getJSONObject("result").getString("streaming_url");
|
2021-02-24 09:52:29 +00:00
|
|
|
|
|
|
|
return null;
|
2020-11-12 21:19:45 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-07-16 22:40:46 +00:00
|
|
|
public static void handleNewVideo(String url, long time, me.kavin.piped.utils.obj.db.Channel channel, Session s) {
|
|
|
|
try {
|
|
|
|
handleNewVideo(StreamInfo.getInfo(url), time, channel, s);
|
2021-07-17 14:02:57 +00:00
|
|
|
} catch (Exception e) {
|
|
|
|
e.printStackTrace();
|
2021-07-16 22:40:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void handleNewVideo(StreamInfo info, long time, me.kavin.piped.utils.obj.db.Channel channel,
|
|
|
|
Session s) {
|
|
|
|
|
|
|
|
if (channel == null)
|
|
|
|
channel = DatabaseHelper.getChannelFromId(s,
|
|
|
|
info.getUploaderUrl().substring("https://www.youtube.com/channel/".length()));
|
|
|
|
|
|
|
|
long infoTime = info.getUploadDate() != null ? info.getUploadDate().offsetDateTime().toInstant().toEpochMilli()
|
|
|
|
: System.currentTimeMillis();
|
|
|
|
|
|
|
|
Video video = null;
|
|
|
|
|
|
|
|
if (channel != null && (video = DatabaseHelper.getVideoFromId(s, info.getId())) == null
|
|
|
|
&& (System.currentTimeMillis() - infoTime) < TimeUnit.DAYS.toMillis(10)) {
|
|
|
|
|
|
|
|
video = new Video(info.getId(), info.getName(), info.getViewCount(), info.getDuration(),
|
|
|
|
Math.max(infoTime, time), info.getThumbnailUrl(), channel);
|
|
|
|
|
|
|
|
s.save(video);
|
|
|
|
|
|
|
|
if (!s.getTransaction().isActive())
|
|
|
|
s.getTransaction().begin();
|
|
|
|
s.getTransaction().commit();
|
|
|
|
} else if (video != null) {
|
|
|
|
video.setViews(info.getViewCount());
|
|
|
|
|
|
|
|
s.update(video);
|
|
|
|
|
2021-07-20 20:57:02 +00:00
|
|
|
if (!s.getTransaction().isActive())
|
|
|
|
s.getTransaction().begin();
|
2021-07-16 22:40:46 +00:00
|
|
|
s.getTransaction().commit();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void updateViews(String id, long views, long time, boolean addIfNonExistent) {
|
|
|
|
Multithreading.runAsync(() -> {
|
|
|
|
try {
|
|
|
|
Session s = DatabaseSessionFactory.createSession();
|
|
|
|
|
|
|
|
Video video = DatabaseHelper.getVideoFromId(s, id);
|
|
|
|
|
|
|
|
if (video != null) {
|
|
|
|
video.setViews(views);
|
|
|
|
s.update(video);
|
|
|
|
s.beginTransaction().commit();
|
|
|
|
} else if (addIfNonExistent)
|
|
|
|
handleNewVideo("https://www.youtube.com/watch?v=" + id, time, null, s);
|
|
|
|
|
|
|
|
s.close();
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void subscribePubSub(String channelId, Session s) throws IOException, InterruptedException {
|
|
|
|
|
|
|
|
PubSub pubsub = DatabaseHelper.getPubSubFromId(s, channelId);
|
|
|
|
|
|
|
|
if (pubsub == null || System.currentTimeMillis() - pubsub.getSubbedAt() > TimeUnit.DAYS.toMillis(4)) {
|
|
|
|
System.out.println(String.format("PubSub: Subscribing to %s", channelId));
|
|
|
|
|
|
|
|
String callback = Constants.PUBLIC_URL + "/webhooks/pubsub";
|
|
|
|
String topic = "https://www.youtube.com/xml/feeds/videos.xml?channel_id=" + channelId;
|
|
|
|
|
|
|
|
Builder builder = HttpRequest.newBuilder(URI.create("https://pubsubhubbub.appspot.com/subscribe"));
|
|
|
|
|
|
|
|
Map<String, String> formParams = new Object2ObjectOpenHashMap<>();
|
|
|
|
StringBuilder formBody = new StringBuilder();
|
|
|
|
|
|
|
|
builder.header("content-type", "application/x-www-form-urlencoded");
|
|
|
|
|
|
|
|
formParams.put("hub.callback", callback);
|
|
|
|
formParams.put("hub.topic", topic);
|
|
|
|
formParams.put("hub.verify", "async");
|
|
|
|
formParams.put("hub.mode", "subscribe");
|
|
|
|
formParams.put("hub.lease_seconds", "432000");
|
|
|
|
|
|
|
|
formParams.forEach((name, value) -> {
|
|
|
|
formBody.append(name + "=" + URLUtils.silentEncode(value) + "&");
|
|
|
|
});
|
|
|
|
|
|
|
|
builder.method("POST",
|
|
|
|
BodyPublishers.ofString(String.valueOf(formBody.substring(0, formBody.length() - 1))));
|
|
|
|
|
|
|
|
Constants.h2client.send(builder.build(), BodyHandlers.ofInputStream());
|
|
|
|
|
|
|
|
if (pubsub == null)
|
|
|
|
pubsub = new PubSub(channelId, System.currentTimeMillis());
|
|
|
|
else
|
|
|
|
pubsub.setSubbedAt(System.currentTimeMillis());
|
|
|
|
|
|
|
|
s.saveOrUpdate(pubsub);
|
|
|
|
|
|
|
|
if (!s.getTransaction().isActive())
|
|
|
|
s.getTransaction().begin();
|
|
|
|
s.getTransaction().commit();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-06-14 19:44:23 +00:00
|
|
|
private static final String optionalSubstring(String s, int index) {
|
|
|
|
return s == null || s.isEmpty() ? null : s.substring(index);
|
|
|
|
}
|
|
|
|
|
2020-11-12 21:19:45 +00:00
|
|
|
private static String rewriteURL(final String old) {
|
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
if (Constants.debug)
|
|
|
|
return old;
|
2020-11-12 21:19:45 +00:00
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
if (old == null || old.isEmpty())
|
|
|
|
return null;
|
2020-12-09 13:29:12 +00:00
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
URL url = null;
|
2020-11-12 21:19:45 +00:00
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
try {
|
|
|
|
url = new URL(old);
|
|
|
|
} catch (MalformedURLException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
2020-11-12 21:19:45 +00:00
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
final String host = url.getHost();
|
2020-11-12 21:19:45 +00:00
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
String query = url.getQuery();
|
2020-11-12 21:19:45 +00:00
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
boolean hasQuery = query != null;
|
2020-11-12 21:19:45 +00:00
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
String path = url.getPath();
|
2020-11-12 21:19:45 +00:00
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
path = path.replace("-rj", "-rw");
|
2020-11-12 21:19:45 +00:00
|
|
|
|
2021-02-24 09:52:29 +00:00
|
|
|
return Constants.PROXY_PART + path + (hasQuery ? "?" + query + "&host=" : "?host=")
|
|
|
|
+ URLUtils.silentEncode(host);
|
2020-11-12 21:19:45 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
}
|