From 4344d398d9aa1d6ced40ee58229cf86f7aa3938f Mon Sep 17 00:00:00 2001 From: zergzorg Date: Thu, 21 May 2026 14:33:24 +0300 Subject: [PATCH] Show message when YouTube comments are disabled --- assets/js/comments.js | 17 ++++++++++++++- assets/js/watch.js | 5 +++++ locales/en-US.json | 1 + .../videos/regular_videos_extract_spec.cr | 21 +++++++++++++++++++ src/invidious/routes/watch.cr | 8 +++++-- src/invidious/videos.cr | 6 +++++- src/invidious/videos/parser.cr | 19 ++++++++++++++++- src/invidious/views/watch.ecr | 10 ++++++++- 8 files changed, 81 insertions(+), 6 deletions(-) diff --git a/assets/js/comments.js b/assets/js/comments.js index 35ffa96e..211acf6f 100644 --- a/assets/js/comments.js +++ b/assets/js/comments.js @@ -52,7 +52,22 @@ function show_youtube_replies(event) { target.setAttribute('data-sub-text', sub_text); } +function show_youtube_comments_disabled_message() { + var comments = document.getElementById('comments'); + comments.innerHTML = ' \ +
\ +

{message}

\ +
'.supplant({ + message: video_data.youtube_comments_disabled_text + }); +} + function get_youtube_comments() { + if (!video_data.comments_enabled) { + show_youtube_comments_disabled_message(); + return; + } + var comments = document.getElementById('comments'); var fallback = comments.innerHTML; @@ -171,4 +186,4 @@ function get_youtube_replies(target, load_more, load_replies) { body.innerHTML = fallback; } }); -} \ No newline at end of file +} diff --git a/assets/js/watch.js b/assets/js/watch.js index ee9c29e8..ce3107ce 100644 --- a/assets/js/watch.js +++ b/assets/js/watch.js @@ -182,6 +182,11 @@ addEventListener('load', function (e) { if (video_data.plid) get_playlist(video_data.plid); + var first_comments_source = video_data.params.comments[0] || video_data.params.comments[1]; + if (!video_data.comments_enabled && first_comments_source === 'youtube') { + return; + } + if (video_data.params.comments[0] === 'youtube') { get_youtube_comments(); } else if (video_data.params.comments[0] === 'reddit') { diff --git a/locales/en-US.json b/locales/en-US.json index 5b2ef8d0..f6e8f041 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -220,6 +220,7 @@ "View Reddit comments": "View Reddit comments", "Hide replies": "Hide replies", "Show replies": "Show replies", + "youtube_comments_disabled_text": "Comments are turned off on this video.", "Incorrect password": "Incorrect password", "Wrong answer": "Wrong answer", "Erroneous CAPTCHA": "Erroneous CAPTCHA", diff --git a/spec/invidious/videos/regular_videos_extract_spec.cr b/spec/invidious/videos/regular_videos_extract_spec.cr index b82a08ee..75b4136e 100644 --- a/spec/invidious/videos/regular_videos_extract_spec.cr +++ b/spec/invidious/videos/regular_videos_extract_spec.cr @@ -40,6 +40,7 @@ Spectator.describe "parse_video_info" do expect(info["keywords"].as_a).to be_empty expect(info["allowRatings"].as_bool).to be_true + expect(info["commentsEnabled"].as_bool).to be_true expect(info["isFamilyFriendly"].as_bool).to be_true expect(info["isListed"].as_bool).to be_true expect(info["isUpcoming"].as_bool).to be_false @@ -125,6 +126,7 @@ Spectator.describe "parse_video_info" do ).in_any_order expect(info["allowRatings"].as_bool).to be_true + expect(info["commentsEnabled"].as_bool).to be_true expect(info["isFamilyFriendly"].as_bool).to be_true expect(info["isListed"].as_bool).to be_true expect(info["isUpcoming"].as_bool).to be_false @@ -163,4 +165,23 @@ Spectator.describe "parse_video_info" do expect(info["authorVerified"].as_bool).to be_false expect(info["subCountText"].as_s).to eq("3.11K") end + + it "detects disabled YouTube comments" do + _player = load_mock("video/regular_no-description.player") + _next = load_mock("video/regular_no-description.next") + + raw_data = _player.merge!(_next) + comments_section = raw_data.dig( + "contents", "twoColumnWatchNextResults", + "results", "results", "contents" + ).as_a[3]["itemSectionRenderer"].as_h + + comments_section["contents"] = JSON.parse(%([ + {"messageRenderer":{"text":{"simpleText":"Comments are turned off."}}} + ])) + + info = parse_video_info("iuevw6218F0", raw_data) + + expect(info["commentsEnabled"].as_bool).to be_false + end end diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr index b829b0f5..b19c331a 100644 --- a/src/invidious/routes/watch.cr +++ b/src/invidious/routes/watch.cr @@ -84,7 +84,9 @@ module Invidious::Routes::Watch source = preferences.comments[1] end - if source == "youtube" + if source == "youtube" && !video.comments_enabled? + comment_html = "" + elsif source == "youtube" begin comment_html = JSON.parse(Comments.fetch_youtube(id, nil, "html", locale, preferences.thin_mode, region))["contentHtml"] rescue ex @@ -110,7 +112,9 @@ module Invidious::Routes::Watch end end else - comment_html = JSON.parse(Comments.fetch_youtube(id, nil, "html", locale, preferences.thin_mode, region))["contentHtml"] + if video.comments_enabled? + comment_html = JSON.parse(Comments.fetch_youtube(id, nil, "html", locale, preferences.thin_mode, region))["contentHtml"] + end end comment_html ||= "" diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr index 0446922f..bb139edd 100644 --- a/src/invidious/videos.cr +++ b/src/invidious/videos.cr @@ -15,7 +15,7 @@ struct Video # NOTE: don't forget to bump this number if any change is made to # the `params` structure in videos/parser.cr!!! # - SCHEMA_VERSION = 3 + SCHEMA_VERSION = 4 property id : String @@ -192,6 +192,10 @@ struct Video } end + def comments_enabled? : Bool + info["commentsEnabled"]?.try &.as_bool || true + end + # Macros defining getters/setters for various types of data private macro getset_string(name) diff --git a/src/invidious/videos/parser.cr b/src/invidious/videos/parser.cr index d9107d5a..4c5ad9ff 100644 --- a/src/invidious/videos/parser.cr +++ b/src/invidious/videos/parser.cr @@ -52,6 +52,18 @@ def parse_related_video(related : JSON::Any) : Hash(String, JSON::Any)? } end +private def detect_comments_enabled(primary_results : JSON::Any?) : Bool + primary_results.try &.as_a.each do |section| + item_section = section["itemSectionRenderer"]? + next if !item_section + next if item_section["sectionIdentifier"]?.try &.as_s != "comment-item-section" + + return false if item_section["contents"]?.try &.as_a.any?(&.["messageRenderer"]?) + end + + return true +end + def extract_video_info(video_id : String) # Fetch data from the player endpoint player_response = YoutubeAPI.player(video_id: video_id) @@ -160,7 +172,9 @@ def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any # Primary results are not available on Music videos # See: https://github.com/iv-org/invidious/pull/3238#issuecomment-1207193725 - if primary_results = main_results.dig?("results", "results", "contents") + primary_results = main_results.dig?("results", "results", "contents") + + if primary_results video_primary_renderer = primary_results .as_a.find(&.["videoPrimaryInfoRenderer"]?) .try &.["videoPrimaryInfoRenderer"] @@ -231,6 +245,8 @@ def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any keywords = video_details["keywords"]? .try &.as_a.map &.as_s || [] of String + comments_enabled = detect_comments_enabled(primary_results) + # Related videos LOGGER.debug("extract_video_info: parsing related videos...") @@ -418,6 +434,7 @@ def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any "isFamilyFriendly" => JSON::Any.new(family_friendly || false), "isListed" => JSON::Any.new(is_listed || false), "isUpcoming" => JSON::Any.new(is_upcoming || false), + "commentsEnabled" => JSON::Any.new(comments_enabled), "keywords" => JSON::Any.new(keywords.map { |v| JSON::Any.new(v) }), "isPostLiveDvr" => JSON::Any.new(post_live_dvr), # Related videos diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr index 796c0c91..ea2024f8 100644 --- a/src/invidious/views/watch.ecr +++ b/src/invidious/views/watch.ecr @@ -57,12 +57,14 @@ we're going to need to do it here in order to allow for translations. "reddit_comments_text" => HTML.escape(I18n.translate(locale, "View Reddit comments")), "reddit_permalink_text" => HTML.escape(I18n.translate(locale, "View more comments on Reddit")), "comments_text" => HTML.escape(I18n.translate(locale, "View `x` comments", "{commentCount}")), + "youtube_comments_disabled_text" => HTML.escape(I18n.translate(locale, "youtube_comments_disabled_text")), "hide_replies_text" => HTML.escape(I18n.translate(locale, "Hide replies")), "show_replies_text" => HTML.escape(I18n.translate(locale, "Show replies")), "params" => params, "preferences" => preferences, "premiere_timestamp" => video.premiere_timestamp.try &.to_unix, "vr" => video.vr?, + "comments_enabled" => video.comments_enabled?, "projection_type" => video.projection_type, "local_disabled" => CONFIG.disabled?("local"), "support_reddit" => true, @@ -288,8 +290,14 @@ we're going to need to do it here in order to allow for translations.
<% end %> + <% first_comments_source = params.comments[0]? || "" %> + <% first_comments_source = params.comments[1]? || "" if first_comments_source.empty? %>
- <% if nojs %> + <% if !video.comments_enabled? && first_comments_source == "youtube" %> +
+

<%= I18n.translate(locale, "youtube_comments_disabled_text") %>

+
+ <% elsif nojs %> <%= comment_html %> <% else %>