From eddc9acc1d92b1326ef269d3743400f62f027fee Mon Sep 17 00:00:00 2001 From: Miguel Ángel Moreno Date: Tue, 3 Dec 2024 11:15:42 +0100 Subject: feat(backend): add channel tabs support and adapt NPE changes --- src/backend/tubo/api.clj | 208 ++++++++++++++++++++++++++----------------- src/backend/tubo/handler.clj | 4 + src/backend/tubo/routes.clj | 5 +- 3 files changed, 134 insertions(+), 83 deletions(-) diff --git a/src/backend/tubo/api.clj b/src/backend/tubo/api.clj index d0d33e2..8fc3aa0 100644 --- a/src/backend/tubo/api.clj +++ b/src/backend/tubo/api.clj @@ -4,13 +4,20 @@ [ring.util.codec :refer [url-decode]]) (:import org.schabi.newpipe.extractor.channel.ChannelInfo + org.schabi.newpipe.extractor.channel.tabs.ChannelTabInfo + org.schabi.newpipe.extractor.channel.tabs.ChannelTabs org.schabi.newpipe.extractor.comments.CommentsInfo org.schabi.newpipe.extractor.kiosk.KioskInfo org.schabi.newpipe.extractor.playlist.PlaylistInfo org.schabi.newpipe.extractor.search.SearchInfo org.schabi.newpipe.extractor.stream.StreamInfo org.schabi.newpipe.extractor.NewPipe - org.schabi.newpipe.extractor.Page)) + org.schabi.newpipe.extractor.Page + org.schabi.newpipe.extractor.linkhandler.ReadyChannelTabListLinkHandler)) + +(defn non-negative + [val] + (when-not (= val -1) val)) (defn get-stream-item [stream] @@ -18,15 +25,14 @@ :service-id (.getServiceId stream) :url (.getUrl stream) :name (.getName stream) - :thumbnail-url (.getThumbnailUrl stream) + :thumbnails (j/from-java (.getThumbnails stream)) :uploader-name (.getUploaderName stream) :uploader-url (.getUploaderUrl stream) - :uploader-avatar (.getUploaderAvatarUrl stream) + :uploader-avatars (j/from-java (.getUploaderAvatars stream)) :upload-date (.getTextualUploadDate stream) :short-description (.getShortDescription stream) :duration (.getDuration stream) - :view-count (when-not (= (.getViewCount stream) -1) - (.getViewCount stream)) + :view-count (non-negative (.getViewCount stream)) :uploaded (when (.getUploadDate stream) (.. stream (getUploadDate) @@ -41,12 +47,10 @@ :service-id (.getServiceId channel) :url (.getUrl channel) :name (.getName channel) - :thumbnail-url (.getThumbnailUrl channel) + :thumbnails (j/from-java (.getThumbnails channel)) :description (.getDescription channel) - :subscriber-count (when-not (= (.getSubscriberCount channel) -1) - (.getSubscriberCount channel)) - :stream-count (when-not (= (.getStreamCount channel) -1) - (.getStreamCount channel)) + :subscriber-count (non-negative (.getSubscriberCount channel)) + :stream-count (non-negative (.getStreamCount channel)) :verified? (.isVerified channel)}) (defn get-playlist-item @@ -55,10 +59,9 @@ :service-id (.getServiceId playlist) :url (.getUrl playlist) :name (.getName playlist) - :thumbnail-url (.getThumbnailUrl playlist) + :thumbnails (j/from-java (.getThumbnails playlist)) :uploader-name (.getUploaderName playlist) - :stream-count (when-not (= (.getStreamCount playlist) -1) - (.getStreamCount playlist))}) + :stream-count (non-negative (.getStreamCount playlist))}) (defn get-items [items] @@ -68,82 +71,126 @@ "PLAYLIST" (get-playlist-item %)) items)) -(defn get-common-info - [info] - {:name (.getName info) - :service-id (.getServiceId info) - :related-streams (get-items (.getRelatedItems info))}) - (defn get-channel ([url] - (let [info (ChannelInfo/getInfo (url-decode url))] - (merge (get-common-info info) - {:id (.getId info) - :verified? (.isVerified info) - :banner (.getBannerUrl info) - :description (.getDescription info) - :avatar (.getAvatarUrl info) - :subscriber-count (when-not (= (.getSubscriberCount info) -1) - (.getSubscriberCount info)) - :donation-links (.getDonationLinks info) - :next-page (j/from-java (.getNextPage info))}))) + (let [info (ChannelInfo/getInfo (url-decode url)) + service (NewPipe/getServiceByUrl (url-decode url)) + preloaded-videos-tab (->> (.getTabs info) + (filter #(instance? + ReadyChannelTabListLinkHandler + %)) + (filter #(some #{ChannelTabs/VIDEOS} + (.getContentFilters %))) + first) + tab-info (ChannelTabInfo/getInfo + service + (or preloaded-videos-tab + (first (.getTabs info))))] + {:name (.getName info) + :service-id (.getServiceId info) + :id (.getId info) + :tabs (j/from-java (.getTabs info)) + :verified? (.isVerified info) + :banners (j/from-java (.getBanners info)) + :description (.getDescription info) + :avatars (j/from-java (.getAvatars info)) + :subscriber-count (non-negative (.getSubscriberCount info)) + :donation-links (.getDonationLinks info) + :next-page (j/from-java (.getNextPage tab-info)) + :related-streams (when tab-info + (get-items (.getRelatedItems tab-info)))})) ([url page-url] - (let [service (NewPipe/getServiceByUrl (url-decode url)) - info (ChannelInfo/getMoreItems service - (url-decode url) - (Page. (url-decode page-url)))] + (let [channel-info (ChannelInfo/getInfo (url-decode url)) + service (NewPipe/getServiceByUrl (url-decode url)) + preloaded-videos-tab (->> (.getTabs channel-info) + (filter #(instance? + ReadyChannelTabListLinkHandler + %)) + (filter #(some #{ChannelTabs/VIDEOS} + (.getContentFilters %))) + first) + tab-info (ChannelTabInfo/getMoreItems service + preloaded-videos-tab + (Page. (url-decode + page-url)))] + {:related-streams (get-items (.getRelatedItems tab-info)) + :next-page (j/from-java (.getNextPage tab-info))}))) + +(defn get-channel-tab + ([url tabId] + (let [service (NewPipe/getServiceByUrl (url-decode url)) + channel-info (ChannelInfo/getInfo (url-decode url)) + tab (if (= tabId "default") + (first (.getTabs channel-info)) + (->> (.getTabs channel-info) + (filter #(some #{tabId} (.getContentFilters %))) + first)) + info (ChannelTabInfo/getInfo service tab)] + {:related-streams (get-items (.getRelatedItems info)) + :next-page (j/from-java (.getNextPage info))})) + ([url tabId page-url] + (let [service (NewPipe/getServiceByUrl (url-decode url)) + channel-info (ChannelInfo/getInfo (url-decode url)) + tab (if (= tabId "default") + (first (.getTabs channel-info)) + (->> (.getTabs channel-info) + (filter #(some #{tabId} (.getContentFilters %))) + first)) + info (ChannelTabInfo/getMoreItems service + tab + (Page. (url-decode + page-url)))] {:related-streams (get-items (.getItems info)) :next-page (j/from-java (.getNextPage info))}))) (defn get-stream [url] (let [info (StreamInfo/getInfo (url-decode url))] - (merge (get-common-info info) - {:url (.getUrl info) - :thumbnail-url (.getThumbnailUrl info) - :description (.. info (getDescription) (getContent)) - :duration (.getDuration info) - :upload-date (.getTextualUploadDate info) - :uploader-name (.getUploaderName info) - :uploader-url (.getUploaderUrl info) - :uploader-avatar (.getUploaderAvatarUrl info) - :uploader-verified? (.isUploaderVerified info) - :tags (.getTags info) - :category (.getCategory info) - :view-count (when-not (= (.getViewCount info) -1) - (.getViewCount info)) - :like-count (when-not (= (.getLikeCount info) -1) - (.getLikeCount info)) - :dislike-count (when-not (= (.getDislikeCount info) -1) - (.getDislikeCount info)) - :subscriber-count (when-not (= (.getUploaderSubscriberCount info) - -1) - (.getUploaderSubscriberCount info)) - :audio-streams (j/from-java (.getAudioStreams info)) - :video-streams (j/from-java (.getVideoStreams info)) - :hls-url (.getHlsUrl info) - :dash-mpd-url (.getDashMpdUrl info) - :preview-frames (.getPreviewFrames info) - :stream-segments (.getStreamSegments info) - :support-info (.getSupportInfo info) - :short? (.isShortFormContent info) - :license (.getLicence info) - :subtitles (.getSubtitles info)}))) + {:name (.getName info) + :service-id (.getServiceId info) + :related-streams (get-items (.getRelatedItems info)) + :url (.getUrl info) + :thumbnails (j/from-java (.getThumbnails info)) + :description (.. info (getDescription) (getContent)) + :duration (.getDuration info) + :upload-date (.getTextualUploadDate info) + :uploader-name (.getUploaderName info) + :uploader-url (.getUploaderUrl info) + :uploader-avatars (j/from-java (.getUploaderAvatars info)) + :uploader-verified? (.isUploaderVerified info) + :tags (.getTags info) + :category (.getCategory info) + :view-count (non-negative (.getViewCount info)) + :like-count (non-negative (.getLikeCount info)) + :dislike-count (non-negative (.getDislikeCount info)) + :subscriber-count (non-negative (.getUploaderSubscriberCount info)) + :audio-streams (j/from-java (.getAudioStreams info)) + :video-streams (j/from-java (.getVideoStreams info)) + :hls-url (.getHlsUrl info) + :dash-mpd-url (.getDashMpdUrl info) + :preview-frames (j/from-java (.getPreviewFrames info)) + :stream-segments (.getStreamSegments info) + :support-info (.getSupportInfo info) + :short? (.isShortFormContent info) + :license (.getLicence info) + :subtitles (j/from-java (.getSubtitles info))})) (defn get-playlist ([url] (let [service (NewPipe/getServiceByUrl (url-decode url)) info (PlaylistInfo/getInfo service (url-decode url))] - (merge (get-common-info info) - {:id (.getId info) - :playlist-type (j/from-java (.getPlaylistType info)) - :thumbnail-url (.getThumbnailUrl info) - :banner-url (.getBannerUrl info) - :uploader-name (.getUploaderName info) - :uploader-url (.getUploaderUrl info) - :uploader-avatar (.getUploaderAvatarUrl info) - :stream-count (.getStreamCount info) - :next-page (j/from-java (.getNextPage info))}))) + {:name (.getName info) + :service-id (.getServiceId info) + :related-streams (get-items (.getRelatedItems info)) + :id (.getId info) + :playlist-type (j/from-java (.getPlaylistType info)) + :thumbnails (j/from-java (.getThumbnails info)) + :banners (j/from-java (.getBanners info)) + :uploader-name (.getUploaderName info) + :uploader-url (.getUploaderUrl info) + :uploader-avatars (j/from-java (.getUploaderAvatars info)) + :stream-count (.getStreamCount info) + :next-page (j/from-java (.getNextPage info))})) ([url page-url] (let [service (NewPipe/getServiceByUrl (url-decode url)) info @@ -158,16 +205,13 @@ :upload-date (.getTextualUploadDate item) :uploader-name (.getUploaderName item) :uploader-url (.getUploaderUrl item) - :uploader-avatar (.getUploaderAvatarUrl item) + :uploader-avatars (j/from-java (.getUploaderAvatars item)) :uploader-verified? (.isUploaderVerified item) - :like-count (when-not (= (.getLikeCount item) -1) - (.getLikeCount item)) - :reply-count (when-not (= (.getReplyCount item) -1) - (.getReplyCount item)) + :like-count (non-negative (.getLikeCount item)) + :reply-count (non-negative (.getReplyCount item)) :hearted-by-uploader? (.isHeartedByUploader item) :pinned? (.isPinned item) - :stream-position (when-not (= (.getStreamPosition item) -1) - (.getStreamPosition item)) + :stream-position (non-negative (.getStreamPosition item)) :replies (when (.getReplies item) (if extractor (let [comments-page (.getPage extractor diff --git a/src/backend/tubo/handler.clj b/src/backend/tubo/handler.clj index 96ce49c..bff4326 100644 --- a/src/backend/tubo/handler.clj +++ b/src/backend/tubo/handler.clj @@ -37,6 +37,10 @@ [{{:keys [url]} :path-params {:strs [nextPage]} :query-params}] (response (apply api/get-channel url (if nextPage [nextPage] [])))) +(defn channel-tabs + [{{:keys [url tab-id]} :path-params {:strs [nextPage]} :query-params}] + (response (apply api/get-channel-tab url tab-id (if nextPage [nextPage] [])))) + (defn playlist [{{:keys [url]} :path-params {:strs [nextPage]} :query-params}] (response (apply api/get-playlist url (if nextPage [nextPage] [])))) diff --git a/src/backend/tubo/routes.clj b/src/backend/tubo/routes.clj index 7a3a456..e8dd536 100644 --- a/src/backend/tubo/routes.clj +++ b/src/backend/tubo/routes.clj @@ -42,7 +42,10 @@ :parameters {:path {:service-id int? :kiosk-id string?}} :handler handler/kiosk}}]]]] ["/streams/:url" {:get handler/stream}] - ["/channels/:url" {:get handler/channel}] + ["/channels" + ["/:url" + ["" {:get handler/channel}] + ["/tabs/:tab-id" {:get handler/channel-tabs}]]] ["/playlists/:url" {:get handler/playlist}] ["/comments/:url" {:get handler/comments}]]] {:data {:middleware [rrc/coerce-request-middleware -- cgit v1.2.3