-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Hide shorts and livestreams setting #5609
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 5 commits
0bce731
b8e7202
1fd5bd4
93724f8
44e700c
7b48da6
ea8e3b7
073396c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -2,6 +2,14 @@ | |||||
|
|
||||||
| -- DROP TABLE public.channel_videos; | ||||||
|
|
||||||
| CREATE TYPE public.video_type AS ENUM | ||||||
| ( | ||||||
| 'Video', | ||||||
| 'Short', | ||||||
| 'Livestream', | ||||||
| 'Scheduled' | ||||||
| ); | ||||||
|
|
||||||
| CREATE TABLE IF NOT EXISTS public.channel_videos | ||||||
| ( | ||||||
| id text NOT NULL, | ||||||
|
|
@@ -14,6 +22,7 @@ CREATE TABLE IF NOT EXISTS public.channel_videos | |||||
| live_now boolean, | ||||||
| premiere_timestamp timestamp with time zone, | ||||||
| views bigint, | ||||||
| video_type video_type, | ||||||
|
||||||
| video_type video_type, | |
| video_type video_type NOT NULL DEFAULT 'Video', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discuss
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -21,6 +21,14 @@ struct ChannelVideo | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| property live_now : Bool = false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| property premiere_timestamp : Time? = nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| property views : Int64? = nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @[DB::Field(converter: ChannelVideo::VideoTypeConverter)] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| property video_type : VideoType = VideoType::Video | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| module VideoTypeConverter | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def self.from_rs(rs) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return VideoType.parse(String.new(rs.read(Slice(UInt8)))) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def to_json(locale, json : JSON::Builder) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| json.object do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -200,6 +208,8 @@ def fetch_channel(ucid, pull_all_videos : Bool) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| LOGGER.trace("fetch_channel: #{ucid} : Extracting videos from channel RSS feed") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rss.xpath_nodes("//default:feed/default:entry", namespaces).each do |entry| | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| video_id = entry.xpath_node("yt:videoId", namespaces).not_nil!.content | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| database_video = Invidious::Database::ChannelVideos.select([video_id]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title = entry.xpath_node("default:title", namespaces).not_nil!.content | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| published = Time.parse_rfc3339( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -216,30 +226,46 @@ def fetch_channel(ucid, pull_all_videos : Bool) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| .xpath_node("media:group/media:community/media:statistics", namespaces) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .try &.["views"]?.try &.to_i64? || 0_i64 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| channel_video = videos | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .select(SearchVideo) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .select(&.id.== video_id)[0]? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| length_seconds = channel_video.try &.length_seconds | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| length_seconds ||= 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| live_now = channel_video.try &.badges.live_now? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| live_now ||= false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| premiere_timestamp = channel_video.try &.premiere_timestamp | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # If there is no update for the video, only update the views | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if database_video.size > 0 && updated == database_video[0].updated | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| video = database_video[0] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| video.views = views | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Harm133 marked this conversation as resolved.
Outdated
Harm133 marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| channel_video = videos | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .select(SearchVideo) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .select(&.id.== video_id)[0]? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Not a video, either a short of a livestream | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Harm133 marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Fetch invididual for info | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Harm133 marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if channel_video.nil? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| short_or_live = fetch_video(video_id, "") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| video_type = short_or_live.video_type | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| length_seconds = short_or_live.length_seconds | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| live_now = short_or_live.live_now | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| premiere_timestamp = short_or_live.premiere_timestamp | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Fetch invididual for info | |
| if channel_video.nil? | |
| short_or_live = fetch_video(video_id, "") | |
| video_type = short_or_live.video_type | |
| length_seconds = short_or_live.length_seconds | |
| live_now = short_or_live.live_now | |
| premiere_timestamp = short_or_live.premiere_timestamp | |
| # Fetch individual info | |
| premiere_timestamp = nil | |
| if channel_video.nil? | |
| begin | |
| short_or_live = fetch_video(video_id, "") | |
| video_type = short_or_live.video_type | |
| length_seconds = short_or_live.length_seconds | |
| live_now = short_or_live.live_now | |
| premiere_timestamp = short_or_live.premiere_timestamp | |
| rescue ex : NotFoundException | InfoException | |
| LOGGER.warn("fetch_channel: #{ucid} : video #{video_id} : failed to fetch video details (#{ex.class.name}): #{ex.message}") | |
| # Fall back to treating this as a regular video with minimal information | |
| video_type = VideoType::Video | |
| rescue ex | |
| LOGGER.error("fetch_channel: #{ucid} : video #{video_id} : unexpected error while fetching video details: #{ex}") | |
| # Fall back to treating this as a regular video with minimal information | |
| video_type = VideoType::Video | |
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The video would not show in the the RSS feed.
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When processing regular videos (the else branch starting at line 246), the premiere_timestamp variable is not set. This means it will be uninitialized when used at line 264, which could lead to a compilation error or unexpected behavior. The premiere_timestamp should be set to nil or retrieved from channel_video.premiere_timestamp in the else branch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unsure how default nullability works in crystal.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,6 +29,17 @@ def get_subscription_feed(user, max_results = 40, page = 1) | |
| notifications = Invidious::Database::Users.select_notifications(user) | ||
| view_name = "subscriptions_#{sha256(user.email)}" | ||
|
|
||
| types_to_fetch = [VideoType::Video, VideoType::Short, VideoType::Livestream, VideoType::Scheduled] | ||
| if user.preferences.hide_shorts | ||
| types_to_fetch.delete(VideoType::Short) | ||
| end | ||
|
Harm133 marked this conversation as resolved.
|
||
| if user.preferences.hide_livestreams | ||
| [VideoType::Livestream, VideoType::Scheduled].each { |v| types_to_fetch.delete(v) } | ||
| end | ||
|
|
||
| types_to_fetch = types_to_fetch.map { |type| "'#{type}'" }.join(", ") | ||
|
||
| LOGGER.trace("Types to fetch: #{types_to_fetch}") | ||
|
Harm133 marked this conversation as resolved.
|
||
|
|
||
| if user.preferences.notifications_only && !notifications.empty? | ||
| # Only show notifications | ||
| notifications = Invidious::Database::ChannelVideos.select(notifications) | ||
|
|
@@ -58,11 +69,10 @@ def get_subscription_feed(user, max_results = 40, page = 1) | |
| else | ||
| values = "VALUES #{user.watched.map { |id| %(('#{id}')) }.join(",")}" | ||
| end | ||
| videos = PG_DB.query_all("SELECT DISTINCT ON (ucid) * FROM #{view_name} WHERE NOT id = ANY (#{values}) ORDER BY ucid, published DESC", as: ChannelVideo) | ||
| videos = PG_DB.query_all("SELECT DISTINCT ON (ucid) * FROM #{view_name} WHERE NOT id = ANY (#{values}) AND video_type IN (#{types_to_fetch}) ORDER BY ucid, published DESC", as: ChannelVideo) | ||
| else | ||
| # Show latest video from each channel | ||
|
|
||
| videos = PG_DB.query_all("SELECT DISTINCT ON (ucid) * FROM #{view_name} ORDER BY ucid, published DESC", as: ChannelVideo) | ||
| videos = PG_DB.query_all("SELECT DISTINCT ON (ucid) * FROM #{view_name} WHERE video_type IN (#{types_to_fetch}) ORDER BY ucid, published DESC", as: ChannelVideo) | ||
|
||
| end | ||
|
|
||
| videos.sort_by!(&.published).reverse! | ||
|
|
@@ -75,11 +85,10 @@ def get_subscription_feed(user, max_results = 40, page = 1) | |
| else | ||
| values = "VALUES #{user.watched.map { |id| %(('#{id}')) }.join(",")}" | ||
| end | ||
| videos = PG_DB.query_all("SELECT * FROM #{view_name} WHERE NOT id = ANY (#{values}) ORDER BY published DESC LIMIT $1 OFFSET $2", limit, offset, as: ChannelVideo) | ||
| videos = PG_DB.query_all("SELECT * FROM #{view_name} WHERE NOT id = ANY (#{values}) AND video_type IN (#{types_to_fetch}) ORDER BY published DESC LIMIT $1 OFFSET $2", limit, offset, as: ChannelVideo) | ||
|
||
| else | ||
| # Sort subscriptions as normal | ||
|
|
||
| videos = PG_DB.query_all("SELECT * FROM #{view_name} ORDER BY published DESC LIMIT $1 OFFSET $2", limit, offset, as: ChannelVideo) | ||
| videos = PG_DB.query_all("SELECT * FROM #{view_name} WHERE video_type IN (#{types_to_fetch}) ORDER BY published DESC LIMIT $1 OFFSET $2", limit, offset, as: ChannelVideo) | ||
|
Harm133 marked this conversation as resolved.
Outdated
|
||
| end | ||
| end | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| enum VideoType | ||
| Video | ||
| Short | ||
| Livestream | ||
| Scheduled | ||
| end | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR adds a new enum type and column to the database schema, but does not provide a migration script for existing databases. Users upgrading to this version will need to manually run this SQL to add the enum type and column. A migration script should be added to config/migrate-scripts/ (following the pattern of existing migration scripts like migrate-db-1eca969.sh) that:
This is critical for production deployments where the database already contains data.