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 = ' \
+
'.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? %>
<%= I18n.translate(locale, "youtube_comments_disabled_text") %>
+