mirror of
https://github.com/iv-org/invidious.git
synced 2026-09-08 10:02:46 -05:00
Add playlist-backed Trending/Popular feeds
New config lists trending_playlists / popular_playlists. When set, the feed serves the merged content of those local PUBLIC playlists instead of the stock feed: - Swap at the data layer (fetch_trending, popular_videos), so the HTML views and /api/v1/trending|popular emit the same items and API clients (Yattee, Materialious) need no changes. - Playlists merge in config order, each in its own order, duplicates dropped. Missing or non-public entries are skipped and logged, never fatal. - One local Postgres query per playlist — no YouTube call. - PlaylistVideo converts to SearchVideo with neutral defaults for metadata a playlist row lacks (views, description, thumbnails). - Trending category tabs and region are ignored while set. With a single trending playlist, 'View as playlist' links to it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
31fe847126
commit
c644d73def
@ -391,6 +391,24 @@ https_only: false
|
|||||||
# - 192.168.1.101
|
# - 192.168.1.101
|
||||||
# logout_url: "https://auth.example.com/logout"
|
# logout_url: "https://auth.example.com/logout"
|
||||||
|
|
||||||
|
##
|
||||||
|
## Playlist-backed feeds (ArikTube extension).
|
||||||
|
##
|
||||||
|
## When set, the Trending and/or Popular feed serves the content of these
|
||||||
|
## local PUBLIC playlists instead of the stock feed. Playlists merge in
|
||||||
|
## the listed order, each in its own order, duplicates dropped. The swap
|
||||||
|
## is at the data layer, so /api/v1/trending and /api/v1/popular serve
|
||||||
|
## the same items — API clients need no changes. Missing or non-public
|
||||||
|
## playlists are skipped and logged. Trending category tabs and region
|
||||||
|
## are ignored while a list is set.
|
||||||
|
##
|
||||||
|
## Default: [] (stock feeds)
|
||||||
|
##
|
||||||
|
#trending_playlists:
|
||||||
|
# - IVPLxxxxxxxxxxxxxxxxxxxx
|
||||||
|
#popular_playlists:
|
||||||
|
# - IVPLxxxxxxxxxxxxxxxxxxxx
|
||||||
|
|
||||||
##
|
##
|
||||||
## Enable/Disable the captcha challenge on the login page.
|
## Enable/Disable the captcha challenge on the login page.
|
||||||
##
|
##
|
||||||
|
|||||||
@ -193,6 +193,11 @@ Invidious::Jobs.register Invidious::Jobs::InstanceListRefreshJob.new
|
|||||||
Invidious::Jobs.start_all
|
Invidious::Jobs.start_all
|
||||||
|
|
||||||
def popular_videos
|
def popular_videos
|
||||||
|
# ArikTube: playlist-backed Popular
|
||||||
|
unless CONFIG.popular_playlists.empty?
|
||||||
|
return Invidious::PlaylistFeeds.feed_videos(CONFIG.popular_playlists)
|
||||||
|
end
|
||||||
|
|
||||||
Invidious::Jobs::PullPopularVideosJob::POPULAR_VIDEOS.get
|
Invidious::Jobs::PullPopularVideosJob::POPULAR_VIDEOS.get
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -147,6 +147,11 @@ class Config
|
|||||||
# Subscribe to channels using PubSubHubbub (requires domain, hmac_key)
|
# Subscribe to channels using PubSubHubbub (requires domain, hmac_key)
|
||||||
property use_pubsub_feeds : Bool | Int32 = false
|
property use_pubsub_feeds : Bool | Int32 = false
|
||||||
property popular_enabled : Bool = true
|
property popular_enabled : Bool = true
|
||||||
|
# Playlist-backed feeds (ArikTube extension): when set, the Trending or
|
||||||
|
# Popular feed serves these local PUBLIC playlists (merged in order,
|
||||||
|
# duplicates dropped) instead of the stock feed content.
|
||||||
|
property trending_playlists : Array(String) = [] of String
|
||||||
|
property popular_playlists : Array(String) = [] of String
|
||||||
property captcha_enabled : Bool = true
|
property captcha_enabled : Bool = true
|
||||||
property login_enabled : Bool = true
|
property login_enabled : Bool = true
|
||||||
property registration_enabled : Bool = true
|
property registration_enabled : Bool = true
|
||||||
|
|||||||
58
src/invidious/playlist_feeds.cr
Normal file
58
src/invidious/playlist_feeds.cr
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# Playlist-backed Trending/Popular feeds (ArikTube extension).
|
||||||
|
#
|
||||||
|
# On a personal instance "Trending" and "Popular" mean "what this server's
|
||||||
|
# owner pushes". When `trending_playlists` / `popular_playlists` name local
|
||||||
|
# public playlists, those feeds serve the merged playlist content instead.
|
||||||
|
# The swap happens at the data layer (fetch_trending / popular_videos), so
|
||||||
|
# the HTML views and /api/v1/trending|popular emit the same items and every
|
||||||
|
# API client shows them without client-side support.
|
||||||
|
module Invidious::PlaylistFeeds
|
||||||
|
extend self
|
||||||
|
|
||||||
|
# Merged videos of the configured playlists: config order, each playlist
|
||||||
|
# in its own order, duplicates dropped. Missing or non-public playlists
|
||||||
|
# are skipped with a log line so one bad entry cannot break the feed.
|
||||||
|
def feed_videos(plids : Array(String)) : Array(SearchVideo)
|
||||||
|
videos = [] of SearchVideo
|
||||||
|
seen = Set(String).new
|
||||||
|
|
||||||
|
plids.each do |plid|
|
||||||
|
playlist = Invidious::Database::Playlists.select(id: plid)
|
||||||
|
if playlist.nil?
|
||||||
|
LOGGER.warn("PlaylistFeeds: playlist #{plid} does not exist, skipping")
|
||||||
|
next
|
||||||
|
end
|
||||||
|
if playlist.privacy != PlaylistPrivacy::Public
|
||||||
|
LOGGER.warn("PlaylistFeeds: playlist #{plid} is not public, skipping")
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
Invidious::Database::PlaylistVideos.select(plid, playlist.index, 0, limit: 200).each do |video|
|
||||||
|
next if seen.includes?(video.id)
|
||||||
|
seen << video.id
|
||||||
|
videos << as_search_video(video)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
videos
|
||||||
|
end
|
||||||
|
|
||||||
|
# PlaylistVideo lacks views/description/thumbnails metadata; neutral
|
||||||
|
# defaults keep the SearchVideo JSON shape the feed endpoints promise.
|
||||||
|
private def as_search_video(video : PlaylistVideo) : SearchVideo
|
||||||
|
SearchVideo.new({
|
||||||
|
title: video.title,
|
||||||
|
id: video.id,
|
||||||
|
author: video.author,
|
||||||
|
ucid: video.ucid,
|
||||||
|
published: video.published,
|
||||||
|
views: 0_i64,
|
||||||
|
description_html: "",
|
||||||
|
length_seconds: video.length_seconds,
|
||||||
|
premiere_timestamp: nil,
|
||||||
|
author_verified: false,
|
||||||
|
author_thumbnail: nil,
|
||||||
|
badges: video.live_now ? VideoBadges::LiveNow : VideoBadges::None,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -1,4 +1,12 @@
|
|||||||
def fetch_trending(trending_type, region, locale)
|
def fetch_trending(trending_type, region, locale)
|
||||||
|
# ArikTube: playlist-backed Trending. Category tabs and region are
|
||||||
|
# ignored on purpose — the owner's list is the feed.
|
||||||
|
unless CONFIG.trending_playlists.empty?
|
||||||
|
plids = CONFIG.trending_playlists
|
||||||
|
plid = plids.size == 1 ? plids[0] : nil
|
||||||
|
return {Invidious::PlaylistFeeds.feed_videos(plids), plid}
|
||||||
|
end
|
||||||
|
|
||||||
region ||= "US"
|
region ||= "US"
|
||||||
region = region.upcase
|
region = region.upcase
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user