2020-11-12 21:19:45 +00:00
package me.kavin.piped.utils ;
2021-10-01 17:53:54 +00:00
import static me.kavin.piped.consts.Constants.YOUTUBE_SERVICE ;
2020-11-12 21:19:45 +00:00
import java.io.IOException ;
2021-07-22 09:43:27 +00:00
import java.io.InputStream ;
2020-11-12 21:19:45 +00:00
import java.net.MalformedURLException ;
import java.net.URI ;
2021-09-05 19:19:54 +00:00
import java.net.URISyntaxException ;
2020-11-12 21:19:45 +00:00
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 ;
2021-07-22 09:43:27 +00:00
import java.net.http.HttpResponse ;
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-08-28 08:06:09 +00:00
import java.util.Arrays ;
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 ;
2021-08-08 11:17:58 +00:00
import java.util.concurrent.ExecutionException ;
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 ;
2021-09-05 19:19:54 +00:00
import org.apache.commons.codec.digest.DigestUtils ;
2021-07-22 09:43:27 +00:00
import org.apache.commons.io.IOUtils ;
2021-09-05 18:44:24 +00:00
import org.apache.commons.lang3.StringUtils ;
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-10-01 17:53:54 +00:00
import org.schabi.newpipe.extractor.stream.StreamType ;
2021-07-16 22:40:46 +00:00
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder ;
2021-10-03 08:02:01 +00:00
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder ;
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 ;
2021-07-22 20:04:21 +00:00
import me.kavin.piped.utils.obj.SubscriptionChannel ;
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 ;
import me.kavin.piped.utils.obj.search.SearchPlaylist ;
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 ;
2021-09-05 19:19:54 +00:00
import me.kavin.piped.utils.resp.CompromisedPasswordResponse ;
2021-07-16 22:40:46 +00:00
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 ;
} ) ;
2021-08-08 11:17:58 +00:00
CompletableFuture < String > futureLbryId = CompletableFuture . supplyAsync ( ( ) - > {
try {
return getLBRYId ( videoId ) ;
} catch ( Exception e ) {
2021-10-06 15:37:30 +00:00
ExceptionHandler . handle ( e ) ;
2021-08-08 11:17:58 +00:00
}
return null ;
} ) ;
2021-02-24 09:52:29 +00:00
CompletableFuture < String > futureLBRY = CompletableFuture . supplyAsync ( ( ) - > {
try {
2021-08-08 11:17:58 +00:00
return getLBRYStreamURL ( futureLbryId ) ;
2021-02-24 09:52:29 +00:00
} catch ( Exception e ) {
2021-10-06 15:37:30 +00:00
ExceptionHandler . handle ( e ) ;
2021-02-24 09:52:29 +00:00
}
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-10-12 10:44:21 +00:00
String lbryURL = null ;
try {
lbryURL = futureLBRY . get ( 3 , TimeUnit . SECONDS ) ;
} catch ( Exception e ) {
// ignored
}
2021-02-24 09:52:29 +00:00
if ( lbryURL ! = null )
videoStreams . add ( new PipedStream ( lbryURL , " MP4 " , " LBRY " , " video/mp4 " , false ) ) ;
2021-10-01 17:53:54 +00:00
boolean livestream = info . getStreamType ( ) = = StreamType . LIVE_STREAM ;
2021-02-24 09:52:29 +00:00
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-09-09 19:25:16 +00:00
info . getRelatedItems ( ) . forEach ( o - > relatedStreams . add ( collectRelatedStream ( o ) ) ) ;
2021-02-24 09:52:29 +00:00
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 ( ) ,
2021-09-05 18:44:24 +00:00
info . getTextualUploadDate ( ) , info . getUploaderName ( ) , substringYouTube ( info . getUploaderUrl ( ) ) ,
2021-02-24 09:52:29 +00:00
rewriteURL ( info . getUploaderAvatarUrl ( ) ) , rewriteURL ( info . getThumbnailUrl ( ) ) , info . getDuration ( ) ,
2021-09-03 16:31:26 +00:00
info . getViewCount ( ) , info . getLikeCount ( ) , info . getDislikeCount ( ) , info . isUploaderVerified ( ) ,
2021-10-01 17:53:54 +00:00
audioStreams , videoStreams , relatedStreams , subtitles , livestream , rewriteURL ( info . getHlsUrl ( ) ) ,
rewriteURL ( info . getDashMpdUrl ( ) ) , futureLbryId . get ( ) ) ;
2021-02-24 09:52:29 +00:00
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-09-09 19:25:16 +00:00
info . getRelatedItems ( ) . forEach ( o - > relatedStreams . add ( collectRelatedStream ( o ) ) ) ;
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 ) {
2021-10-31 17:48:34 +00:00
if ( channel . isVerified ( ) ! = info . isVerified ( )
| | ! channel . getUploaderAvatar ( ) . equals ( info . getAvatarUrl ( ) ) ) {
2021-08-17 17:29:15 +00:00
channel . setVerified ( info . isVerified ( ) ) ;
2021-10-31 17:48:34 +00:00
channel . setUploaderAvatar ( info . getAvatarUrl ( ) ) ;
2021-08-17 17:29:15 +00:00
if ( ! s . getTransaction ( ) . isActive ( ) )
s . getTransaction ( ) . begin ( ) ;
s . update ( channel ) ;
s . getTransaction ( ) . commit ( ) ;
}
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 ) )
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-08-17 20:07:49 +00:00
rewriteURL ( info . getBannerUrl ( ) ) , info . getDescription ( ) , info . getSubscriberCount ( ) , info . isVerified ( ) ,
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-10-01 17:53:54 +00:00
InfoItemsPage < StreamInfoItem > info = ChannelInfo . getMoreItems ( 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-09-09 19:25:16 +00:00
info . getItems ( ) . forEach ( o - > relatedStreams . add ( collectRelatedStream ( o ) ) ) ;
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-10-01 17:53:54 +00:00
KioskList kioskList = YOUTUBE_SERVICE . getKioskList ( ) ;
2021-07-04 17:53:33 +00:00
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-09-09 19:25:16 +00:00
info . getRelatedItems ( ) . forEach ( o - > relatedStreams . add ( collectRelatedStream ( o ) ) ) ;
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-09-09 19:25:16 +00:00
info . getRelatedItems ( ) . forEach ( o - > relatedStreams . add ( collectRelatedStream ( o ) ) ) ;
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 ( ) ,
2021-09-05 18:44:24 +00:00
substringYouTube ( info . getUploaderUrl ( ) ) , 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-10-01 17:53:54 +00:00
InfoItemsPage < StreamInfoItem > info = PlaylistInfo . getMoreItems ( 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-09-09 19:25:16 +00:00
info . getItems ( ) . forEach ( o - > relatedStreams . add ( collectRelatedStream ( o ) ) ) ;
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-10-01 17:53:54 +00:00
return Constants . mapper . writeValueAsBytes ( YOUTUBE_SERVICE . getSuggestionExtractor ( ) . suggestionList ( query ) ) ;
2020-11-25 05:26:25 +00:00
}
2021-10-25 22:55:29 +00:00
public static final byte [ ] opensearchSuggestionsResponse ( String query )
throws JsonProcessingException , IOException , ExtractionException {
return Constants . mapper . writeValueAsBytes (
Arrays . asList ( query , YOUTUBE_SERVICE . getSuggestionExtractor ( ) . suggestionList ( query ) ) ) ;
}
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-10-01 17:53:54 +00:00
final SearchInfo info = SearchInfo . getInfo ( YOUTUBE_SERVICE ,
YOUTUBE_SERVICE . getSearchQHFactory ( ) . fromQuery ( q , Collections . singletonList ( filter ) , null ) ) ;
2021-02-24 09:52:29 +00:00
2021-09-03 19:32:51 +00:00
ObjectArrayList < Object > items = new ObjectArrayList < > ( ) ;
2021-02-24 09:52:29 +00:00
info . getRelatedItems ( ) . forEach ( item - > {
switch ( item . getInfoType ( ) ) {
case STREAM :
2021-09-09 19:25:16 +00:00
items . add ( collectRelatedStream ( item ) ) ;
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 ( ) ) ,
2021-09-05 18:44:24 +00:00
substringYouTube ( item . getUrl ( ) ) , channel . getDescription ( ) , channel . getSubscriberCount ( ) ,
2021-05-28 12:08:32 +00:00
channel . getStreamCount ( ) , channel . isVerified ( ) ) ) ;
break ;
case PLAYLIST :
PlaylistInfoItem playlist = ( PlaylistInfoItem ) item ;
items . add ( new SearchPlaylist ( item . getName ( ) , rewriteURL ( item . getThumbnailUrl ( ) ) ,
2021-09-05 18:44:24 +00:00
substringYouTube ( item . getUrl ( ) ) , playlist . getUploaderName ( ) , playlist . getStreamCount ( ) ) ) ;
2021-02-24 09:52:29 +00:00
break ;
default :
break ;
}
} ) ;
Page nextpage = info . getNextPage ( ) ;
2021-10-09 23:24:29 +00:00
return Constants . mapper . writeValueAsBytes ( new SearchResults ( items ,
Constants . mapper . writeValueAsString ( nextpage ) , info . getSearchSuggestion ( ) , info . isCorrectedSearch ( ) ) ) ;
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-10-01 17:53:54 +00:00
InfoItemsPage < InfoItem > pages = SearchInfo . getMoreItems ( YOUTUBE_SERVICE ,
YOUTUBE_SERVICE . getSearchQHFactory ( ) . fromQuery ( q , Collections . singletonList ( filter ) , null ) , prevpage ) ;
2021-02-24 09:52:29 +00:00
2021-09-03 19:32:51 +00:00
ObjectArrayList < Object > items = new ObjectArrayList < > ( ) ;
2021-02-24 09:52:29 +00:00
pages . getItems ( ) . forEach ( item - > {
switch ( item . getInfoType ( ) ) {
case STREAM :
2021-09-09 19:25:16 +00:00
items . add ( collectRelatedStream ( item ) ) ;
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 ( ) ) ,
2021-09-05 18:44:24 +00:00
substringYouTube ( item . getUrl ( ) ) , channel . getDescription ( ) , channel . getSubscriberCount ( ) ,
2021-05-28 12:08:32 +00:00
channel . getStreamCount ( ) , channel . isVerified ( ) ) ) ;
break ;
case PLAYLIST :
PlaylistInfoItem playlist = ( PlaylistInfoItem ) item ;
items . add ( new SearchPlaylist ( item . getName ( ) , rewriteURL ( item . getThumbnailUrl ( ) ) ,
2021-09-05 18:44:24 +00:00
substringYouTube ( item . getUrl ( ) ) , 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 - > {
2021-09-22 14:18:24 +00:00
try {
2021-11-01 19:58:44 +00:00
String repliespage = null ;
if ( comment . getReplies ( ) ! = null )
repliespage = Constants . mapper . writeValueAsString ( comment . getReplies ( ) ) ;
2021-09-22 14:18:24 +00:00
comments . add ( new Comment ( comment . getUploaderName ( ) , rewriteURL ( comment . getUploaderAvatarUrl ( ) ) ,
comment . getCommentId ( ) , comment . getCommentText ( ) , comment . getTextualUploadDate ( ) ,
2021-11-01 19:58:44 +00:00
substringYouTube ( comment . getUploaderUrl ( ) ) , repliespage , comment . getLikeCount ( ) ,
2021-09-22 14:18:24 +00:00
comment . isHeartedByUploader ( ) , comment . isPinned ( ) , comment . isUploaderVerified ( ) ) ) ;
} catch ( JsonProcessingException e ) {
ExceptionHandler . handle ( e ) ;
}
2021-04-03 15:08:32 +00:00
} ) ;
String nextpage = null ;
2021-07-29 19:21:37 +00:00
if ( info . hasNextPage ( ) ) {
Page page = info . getNextPage ( ) ;
nextpage = Constants . mapper . writeValueAsString ( page ) ;
}
2021-04-03 15:08:32 +00:00
2021-07-31 10:39:06 +00:00
CommentsPage commentsItem = new CommentsPage ( comments , nextpage , info . isCommentsDisabled ( ) ) ;
2021-04-03 15:08:32 +00:00
return Constants . mapper . writeValueAsBytes ( commentsItem ) ;
}
2021-07-29 19:21:37 +00:00
public static final byte [ ] commentsPageResponse ( String videoId , String prevpageStr ) throws Exception {
Page prevpage = Constants . mapper . readValue ( prevpageStr , Page . class ) ;
2021-04-03 15:08:32 +00:00
CommentsInfo init = commentsCache . get ( videoId ) ;
2021-07-29 19:21:37 +00:00
InfoItemsPage < CommentsInfoItem > info = CommentsInfo . getMoreItems ( init , prevpage ) ;
2021-04-03 15:08:32 +00:00
List < Comment > comments = new ObjectArrayList < > ( ) ;
info . getItems ( ) . forEach ( comment - > {
2021-09-22 14:18:24 +00:00
try {
2021-11-01 19:58:44 +00:00
String repliespage = null ;
if ( comment . getReplies ( ) ! = null )
repliespage = Constants . mapper . writeValueAsString ( comment . getReplies ( ) ) ;
2021-09-22 14:18:24 +00:00
comments . add ( new Comment ( comment . getUploaderName ( ) , rewriteURL ( comment . getUploaderAvatarUrl ( ) ) ,
comment . getCommentId ( ) , comment . getCommentText ( ) , comment . getTextualUploadDate ( ) ,
2021-11-01 19:58:44 +00:00
substringYouTube ( comment . getUploaderUrl ( ) ) , repliespage , comment . getLikeCount ( ) ,
2021-09-22 14:18:24 +00:00
comment . isHeartedByUploader ( ) , comment . isPinned ( ) , comment . isUploaderVerified ( ) ) ) ;
} catch ( JsonProcessingException e ) {
ExceptionHandler . handle ( e ) ;
}
2021-04-03 15:08:32 +00:00
} ) ;
String nextpage = null ;
2021-07-29 19:21:37 +00:00
if ( info . hasNextPage ( ) ) {
Page page = info . getNextPage ( ) ;
nextpage = Constants . mapper . writeValueAsString ( page ) ;
}
2021-04-03 15:08:32 +00:00
2021-07-31 10:39:06 +00:00
CommentsPage commentsItem = new CommentsPage ( comments , nextpage , init . isCommentsDisabled ( ) ) ;
2021-04-03 15:08:32 +00:00
return Constants . mapper . writeValueAsBytes ( commentsItem ) ;
}
2021-07-16 22:40:46 +00:00
private static final Argon2PasswordEncoder argon2PasswordEncoder = new Argon2PasswordEncoder ( ) ;
2021-09-05 19:19:54 +00:00
public static final byte [ ] registerResponse ( String user , String pass ) throws IOException , NoSuchAlgorithmException ,
InvalidKeySpecException , InterruptedException , URISyntaxException {
2021-07-16 22:40:46 +00:00
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 ( ) ) ;
}
2021-10-04 18:57:56 +00:00
if ( Constants . COMPROMISED_PASSWORD_CHECK ) {
2021-09-05 19:19:54 +00:00
String sha1Hash = DigestUtils . sha1Hex ( pass ) . toUpperCase ( ) ;
String prefix = sha1Hash . substring ( 0 , 5 ) ;
String suffix = sha1Hash . substring ( 5 ) ;
String [ ] entries = RequestUtils
. sendGet ( " https://api.pwnedpasswords.com/range/ " + prefix , " github.com/TeamPiped/Piped-Backend " )
. split ( " \ n " ) ;
for ( String entry : entries )
if ( StringUtils . substringBefore ( entry , " : " ) . equals ( suffix ) )
return Constants . mapper . writeValueAsBytes ( new CompromisedPasswordResponse ( ) ) ;
}
2021-07-16 22:40:46 +00:00
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 ( ) ) ) ;
}
2021-10-03 08:02:01 +00:00
private static final BCryptPasswordEncoder bcryptPasswordEncoder = new BCryptPasswordEncoder ( ) ;
2021-07-16 22:40:46 +00:00
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 ( ) ;
2021-10-03 08:02:01 +00:00
if ( dbuser ! = null ) {
2021-10-03 08:55:13 +00:00
String hash = dbuser . getPassword ( ) ;
if ( hash . startsWith ( " $argon2 " ) ) {
if ( argon2PasswordEncoder . matches ( pass , hash ) ) {
s . close ( ) ;
return Constants . mapper . writeValueAsBytes ( new LoginResponse ( dbuser . getSessionId ( ) ) ) ;
}
} else if ( bcryptPasswordEncoder . matches ( pass , hash ) ) {
2021-10-03 08:02:01 +00:00
s . close ( ) ;
return Constants . mapper . writeValueAsBytes ( new LoginResponse ( dbuser . getSessionId ( ) ) ) ;
}
2021-07-16 22:40:46 +00:00
}
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 ( ) ,
2021-08-17 17:29:15 +00:00
info . getAvatarUrl ( ) , info . isVerified ( ) ) ;
2021-07-16 22:40:46 +00:00
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 ) {
2021-09-09 18:49:46 +00:00
ExceptionHandler . handle ( e ) ;
2021-07-21 12:37:43 +00:00
}
} ) ;
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 ( ) ;
2021-09-21 21:35:12 +00:00
User user = DatabaseHelper . getUserFromSession ( s , session ) ;
2021-07-16 22:40:46 +00:00
if ( user ! = null ) {
2021-09-21 21:35:12 +00:00
@SuppressWarnings ( " unchecked " )
List < Object [ ] > queryResults = s . createNativeQuery (
" select Video.*, Channel.* from videos as Video left join channels as Channel on Video.uploader_id = Channel.uploader_id inner join users_subscribed on users_subscribed.channel = Channel.uploader_id where users_subscribed.subscriber = :user " )
. setParameter ( " user " , user . getId ( ) ) . addEntity ( " Video " , Video . class )
. addEntity ( " Channel " , me . kavin . piped . utils . obj . db . Channel . class ) . getResultList ( ) ;
2021-07-16 22:40:46 +00:00
List < FeedItem > feedItems = new ObjectArrayList < > ( ) ;
2021-09-21 21:35:12 +00:00
queryResults . forEach ( obj - > {
Video video = ( Video ) obj [ 0 ] ;
me . kavin . piped . utils . obj . db . Channel channel = ( me . kavin . piped . utils . obj . db . Channel ) obj [ 1 ] ;
2021-01-04 05:47:28 +00:00
2021-09-21 21:35:12 +00:00
feedItems . add ( new FeedItem ( " /watch?v= " + video . getId ( ) , video . getTitle ( ) ,
rewriteURL ( video . getThumbnail ( ) ) , " /channel/ " + channel . getUploaderId ( ) , channel . getUploader ( ) ,
rewriteURL ( channel . getUploaderAvatar ( ) ) , video . getViews ( ) , video . getDuration ( ) ,
video . getUploaded ( ) , channel . isVerified ( ) ) ) ;
2021-07-16 22:40:46 +00:00
2021-09-21 21:35:12 +00:00
} ) ;
2021-07-16 22:40:46 +00:00
2021-09-21 21:35:12 +00:00
Collections . sort ( feedItems , ( a , b ) - > ( int ) ( b . uploaded - a . uploaded ) ) ;
2021-07-16 22:40:46 +00:00
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 ( ) ;
2021-09-21 21:35:12 +00:00
User user = DatabaseHelper . getUserFromSession ( s , session ) ;
2021-07-19 19:44:18 +00:00
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 ( ) ) ) ;
2021-09-09 08:05:19 +00:00
feed . setUri ( Constants . FRONTEND_URL + " /feed " ) ;
2021-07-19 19:44:18 +00:00
if ( user . getSubscribed ( ) ! = null & & ! user . getSubscribed ( ) . isEmpty ( ) ) {
2021-09-21 21:35:12 +00:00
@SuppressWarnings ( " unchecked " )
List < Object [ ] > queryResults = s . createNativeQuery (
" select Video.*, Channel.* from videos as Video left join channels as Channel on Video.uploader_id = Channel.uploader_id inner join users_subscribed on users_subscribed.channel = Channel.uploader_id where users_subscribed.subscriber = :user " )
. setParameter ( " user " , user . getId ( ) ) . addEntity ( " Video " , Video . class )
. addEntity ( " Channel " , me . kavin . piped . utils . obj . db . Channel . class ) . getResultList ( ) ;
2021-07-19 19:44:18 +00:00
2021-09-21 21:35:12 +00:00
Collections . sort ( queryResults ,
( a , b ) - > ( int ) ( ( ( Video ) b [ 0 ] ) . getUploaded ( ) - ( ( Video ) a [ 0 ] ) . getUploaded ( ) ) ) ;
2021-07-19 19:44:18 +00:00
final List < SyndEntry > entries = new ObjectArrayList < > ( ) ;
2021-09-21 21:35:12 +00:00
for ( Object [ ] result : queryResults ) {
Video video = ( Video ) result [ 0 ] ;
me . kavin . piped . utils . obj . db . Channel channel = ( me . kavin . piped . utils . obj . db . Channel ) result [ 1 ] ;
2021-07-19 19:44:18 +00:00
SyndEntry entry = new SyndEntryImpl ( ) ;
SyndPerson person = new SyndPersonImpl ( ) ;
2021-09-21 21:35:12 +00:00
person . setName ( channel . getUploader ( ) ) ;
person . setUri ( Constants . FRONTEND_URL + " /channel/ " + channel . getUploaderId ( ) ) ;
2021-07-19 19:44:18 +00:00
entry . setAuthors ( Collections . singletonList ( person ) ) ;
2021-09-09 08:05:19 +00:00
entry . setUri ( Constants . FRONTEND_URL + " /watch?v= " + video . getId ( ) ) ;
2021-07-19 19:44:18 +00:00
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-08-28 07:56:50 +00:00
public static final byte [ ] importResponse ( String session , String [ ] channelIds , boolean override )
2021-07-16 22:40:46 +00:00
throws IOException , NoSuchAlgorithmException , InvalidKeySpecException {
Session s = DatabaseSessionFactory . createSession ( ) ;
User user = DatabaseHelper . getUserFromSessionWithSubscribed ( s , session ) ;
if ( user ! = null ) {
Multithreading . runAsync ( ( ) - > {
2021-08-28 07:56:50 +00:00
if ( override )
user . setSubscribed ( Arrays . asList ( channelIds ) ) ;
else
for ( String channelId : channelIds )
if ( ! user . getSubscribed ( ) . contains ( channelId ) )
user . getSubscribed ( ) . add ( channelId ) ;
2021-07-16 22:40:46 +00:00
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 ( ) ,
2021-08-17 17:29:15 +00:00
info . getAvatarUrl ( ) , info . isVerified ( ) ) ;
2021-07-16 22:40:46 +00:00
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-09-09 18:49:46 +00:00
ExceptionHandler . handle ( e ) ;
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 ) )
handleNewVideo ( item . getUrl ( ) , time , channel , sess ) ;
}
if ( ! sess . getTransaction ( ) . isActive ( ) )
sess . getTransaction ( ) . begin ( ) ;
sess . getTransaction ( ) . commit ( ) ;
}
sess . close ( ) ;
} catch ( Exception e ) {
2021-09-09 18:49:46 +00:00
ExceptionHandler . handle ( e ) ;
2021-07-16 22:40:46 +00:00
}
} ) ;
}
return Constants . mapper . writeValueAsBytes ( new AcceptedResponse ( ) ) ;
}
s . close ( ) ;
return Constants . mapper . writeValueAsBytes ( new AuthenticationFailureResponse ( ) ) ;
2021-01-04 05:47:28 +00:00
}
2021-07-22 20:04:21 +00:00
public static final byte [ ] subscriptionsResponse ( String session )
throws IOException , NoSuchAlgorithmException , InvalidKeySpecException {
Session s = DatabaseSessionFactory . createSession ( ) ;
User user = DatabaseHelper . getUserFromSessionWithSubscribed ( s , session ) ;
if ( user ! = null ) {
List < SubscriptionChannel > subscriptionItems = new ObjectArrayList < > ( ) ;
if ( user . getSubscribed ( ) ! = null & & ! user . getSubscribed ( ) . isEmpty ( ) ) {
List < me . kavin . piped . utils . obj . db . Channel > channels = DatabaseHelper . getChannelFromIds ( s ,
user . getSubscribed ( ) ) ;
channels . forEach ( channel - > {
subscriptionItems . add ( new SubscriptionChannel ( " /channel/ " + channel . getUploaderId ( ) ,
channel . getUploader ( ) , rewriteURL ( channel . getUploaderAvatar ( ) ) , channel . isVerified ( ) ) ) ;
} ) ;
Collections . sort ( subscriptionItems , ( a , b ) - > ( a . name . compareTo ( b . name ) ) ) ;
}
s . close ( ) ;
return Constants . mapper . writeValueAsBytes ( subscriptionItems ) ;
}
s . close ( ) ;
return Constants . mapper . writeValueAsBytes ( new AuthenticationFailureResponse ( ) ) ;
}
2021-08-08 11:17:58 +00:00
private static final String getLBRYId ( String videoId ) throws IOException , InterruptedException {
return new JSONObject ( Constants . h2client . send ( HttpRequest
2021-02-24 09:52:29 +00:00
. 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 ) ;
2021-08-08 11:17:58 +00:00
}
private static final String getLBRYStreamURL ( CompletableFuture < String > futureLbryId )
throws IOException , InterruptedException , ExecutionException {
2021-10-12 10:44:21 +00:00
String lbryId = " " ;
try {
lbryId = futureLbryId . get ( 2 , TimeUnit . SECONDS ) ;
} catch ( Exception e ) {
// ignored
}
2021-02-24 09:52:29 +00:00
if ( ! lbryId . isEmpty ( ) )
2021-10-06 15:37:30 +00:00
return 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 ( " id " , System . currentTimeMillis ( ) )
. 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 ) {
2021-09-09 18:49:46 +00:00
ExceptionHandler . handle ( e ) ;
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 ) {
2021-09-09 18:49:46 +00:00
ExceptionHandler . handle ( e ) ;
2021-07-16 22:40:46 +00:00
}
} ) ;
}
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 ) ) {
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 ) ) ) ) ;
2021-07-22 09:43:27 +00:00
HttpResponse < InputStream > resp = Constants . h2client . send ( builder . build ( ) , BodyHandlers . ofInputStream ( ) ) ;
2021-07-16 22:40:46 +00:00
2021-07-22 09:59:59 +00:00
if ( resp . statusCode ( ) = = 202 ) {
2021-07-22 09:43:27 +00:00
if ( pubsub = = null )
pubsub = new PubSub ( channelId , System . currentTimeMillis ( ) ) ;
else
pubsub . setSubbedAt ( System . currentTimeMillis ( ) ) ;
2021-07-16 22:40:46 +00:00
2021-07-22 09:43:27 +00:00
s . saveOrUpdate ( pubsub ) ;
2021-07-16 22:40:46 +00:00
2021-07-22 09:43:27 +00:00
if ( ! s . getTransaction ( ) . isActive ( ) )
s . getTransaction ( ) . begin ( ) ;
s . getTransaction ( ) . commit ( ) ;
} else
System . out . println (
" Failed to subscribe: " + resp . statusCode ( ) + " \ n " + IOUtils . toString ( resp . body ( ) , " UTF-8 " ) ) ;
2021-07-16 22:40:46 +00:00
}
}
2021-09-05 18:44:24 +00:00
private static final String substringYouTube ( String s ) {
return s = = null | | s . isEmpty ( ) ? null : StringUtils . substringAfter ( s , " youtube.com " ) ;
2021-06-14 19:44:23 +00:00
}
2021-09-09 19:25:16 +00:00
private static StreamItem collectRelatedStream ( Object o ) {
StreamInfoItem item = ( StreamInfoItem ) o ;
return new StreamItem ( substringYouTube ( item . getUrl ( ) ) , item . getName ( ) , rewriteURL ( item . getThumbnailUrl ( ) ) ,
item . getUploaderName ( ) , substringYouTube ( item . getUploaderUrl ( ) ) ,
rewriteURL ( item . getUploaderAvatarUrl ( ) ) , item . getTextualUploadDate ( ) , item . getDuration ( ) ,
item . getViewCount ( ) , item . isUploaderVerified ( ) ) ;
}
2020-11-12 21:19:45 +00:00
private static String rewriteURL ( final String old ) {
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 ) {
2021-09-09 18:49:46 +00:00
ExceptionHandler . handle ( e ) ;
2021-02-24 09:52:29 +00:00
}
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-11-04 17:45:33 +00:00
if ( path . contains ( " = " ) ) {
path = StringUtils . substringBefore ( path , " = " ) + " = "
+ StringUtils . substringAfter ( 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
}
}