From f3f6937ffcc703f11173653dc250c7f5bac5d736 Mon Sep 17 00:00:00 2001 From: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com> Date: Wed, 25 Jun 2025 22:22:30 -0400 Subject: [PATCH 1/6] Fix community tab not loading --- src/invidious/channels/community.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/invidious/channels/community.cr b/src/invidious/channels/community.cr index 49ffd990..6a296009 100644 --- a/src/invidious/channels/community.cr +++ b/src/invidious/channels/community.cr @@ -3,8 +3,8 @@ private IMAGE_QUALITIES = {320, 560, 640, 1280, 2000} # TODO: Add "sort_by" def fetch_channel_community(ucid, cursor, locale, format, thin_mode) if cursor.nil? - # Egljb21tdW5pdHk%3D is the protobuf object to load "community" - initial_data = YoutubeAPI.browse(ucid, params: "Egljb21tdW5pdHk%3D") + # EgVwb3N0c_IGBAoCSgA%3D is the protobuf object to load "community" + initial_data = YoutubeAPI.browse(ucid, params: "EgVwb3N0c_IGBAoCSgA%3D") items = [] of JSON::Any extract_items(initial_data) do |item| From b9171d9dab7e6791376c4cc899ed3b6fa16e5f19 Mon Sep 17 00:00:00 2001 From: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com> Date: Wed, 25 Jun 2025 22:34:26 -0400 Subject: [PATCH 2/6] Update protobuf for individual community post --- src/invidious/channels/community.cr | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/invidious/channels/community.cr b/src/invidious/channels/community.cr index 6a296009..c0688416 100644 --- a/src/invidious/channels/community.cr +++ b/src/invidious/channels/community.cr @@ -3,7 +3,7 @@ private IMAGE_QUALITIES = {320, 560, 640, 1280, 2000} # TODO: Add "sort_by" def fetch_channel_community(ucid, cursor, locale, format, thin_mode) if cursor.nil? - # EgVwb3N0c_IGBAoCSgA%3D is the protobuf object to load "community" + # EgVwb3N0c_IGBAoCSgA%3D is the protobuf object to load "posts" initial_data = YoutubeAPI.browse(ucid, params: "EgVwb3N0c_IGBAoCSgA%3D") items = [] of JSON::Any @@ -26,21 +26,18 @@ end def fetch_channel_community_post(ucid, post_id, locale, format, thin_mode) object = { - "2:string" => "community", - "25:embedded" => { - "22:string" => post_id.to_s, - }, - "45:embedded" => { - "2:varint" => 1_i64, - "3:varint" => 1_i64, - }, + "56:embedded" => { + "2:string" => ucid, + "3:string" => post_id.to_s, + "11:string" => ucid, + } } params = object.try { |i| Protodec::Any.cast_json(i) } .try { |i| Protodec::Any.from_json(i) } .try { |i| Base64.urlsafe_encode(i) } .try { |i| URI.encode_www_form(i) } - initial_data = YoutubeAPI.browse(ucid, params: params) + initial_data = YoutubeAPI.browse("FEpost_detail", params: params) items = [] of JSON::Any extract_items(initial_data) do |item| From 4155f15bf73ede39434e7aa3878e295d5d203c04 Mon Sep 17 00:00:00 2001 From: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com> Date: Wed, 25 Jun 2025 23:33:28 -0400 Subject: [PATCH 3/6] update resolve_url api to better support new post endpoint --- src/invidious/routes/api/v1/misc.cr | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/invidious/routes/api/v1/misc.cr b/src/invidious/routes/api/v1/misc.cr index 4f5b58da..40f2a439 100644 --- a/src/invidious/routes/api/v1/misc.cr +++ b/src/invidious/routes/api/v1/misc.cr @@ -190,15 +190,30 @@ module Invidious::Routes::API::V1::Misc sub_endpoint = endpoint["watchEndpoint"]? || endpoint["browseEndpoint"]? || endpoint params = sub_endpoint.try &.dig?("params") + + if sub_endpoint["browseId"]?.try &.as_s == "FEpost_detail" + decoded_protobuf = params.try &.as_s.try { |i| URI.decode_www_form(i) } + .try { |i| Base64.decode(i) } + .try { |i| IO::Memory.new(i) } + .try { |i| Protodec::Any.parse(i) } + + ucid = decoded_protobuf.try(&.["56:0:embedded"]["2:0:string"].as_s) + post_id = decoded_protobuf.try(&.["56:0:embedded"]["3:1:string"].as_s) + else + ucid = sub_endpoint["browseId"]? if sub_endpoint["browseId"]? && sub_endpoint["browseId"]?.try &.as_s.starts_with? "UC" + post_id = nil + end rescue ex return error_json(500, ex) end JSON.build do |json| json.object do - json.field "ucid", sub_endpoint["browseId"].as_s if sub_endpoint["browseId"]? + json.field "browseId", sub_endpoint["browseId"].as_s if sub_endpoint["browseId"]? + json.field "ucid", ucid if ucid != nil json.field "videoId", sub_endpoint["videoId"].as_s if sub_endpoint["videoId"]? json.field "playlistId", sub_endpoint["playlistId"].as_s if sub_endpoint["playlistId"]? json.field "startTimeSeconds", sub_endpoint["startTimeSeconds"].as_i if sub_endpoint["startTimeSeconds"]? + json.field "postId", post_id if post_id != nil json.field "params", params.try &.as_s json.field "pageType", page_type end From 436f955e0f20c9e398e3587175f3071dcec153d4 Mon Sep 17 00:00:00 2001 From: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com> Date: Wed, 25 Jun 2025 23:34:30 -0400 Subject: [PATCH 4/6] update fetch_community_post_comments protobuf to match currently used protobuf, add sort_by option --- src/invidious/channels/community.cr | 9 +++++++++ src/invidious/comments/youtube.cr | 26 ++++++++++++++----------- src/invidious/routes/api/v1/channels.cr | 6 ++++-- src/invidious/routes/channels.cr | 2 +- 4 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/invidious/channels/community.cr b/src/invidious/channels/community.cr index c0688416..8927c81b 100644 --- a/src/invidious/channels/community.cr +++ b/src/invidious/channels/community.cr @@ -24,6 +24,15 @@ def fetch_channel_community(ucid, cursor, locale, format, thin_mode) return extract_channel_community(items, ucid: ucid, locale: locale, format: format, thin_mode: thin_mode) end +def decode_ucid_from_post_protobuf(params) + decoded_protobuf = params.try { |i| URI.decode_www_form(i) } + .try { |i| Base64.decode(i) } + .try { |i| IO::Memory.new(i) } + .try { |i| Protodec::Any.parse(i) } + + return decoded_protobuf.try(&.["56:0:embedded"]["2:0:string"].as_s) +end + def fetch_channel_community_post(ucid, post_id, locale, format, thin_mode) object = { "56:embedded" => { diff --git a/src/invidious/comments/youtube.cr b/src/invidious/comments/youtube.cr index 0716fcde..18403656 100644 --- a/src/invidious/comments/youtube.cr +++ b/src/invidious/comments/youtube.cr @@ -16,34 +16,38 @@ module Invidious::Comments return parse_youtube(id, response, format, locale, thin_mode, sort_by) end - def fetch_community_post_comments(ucid, post_id) + def fetch_community_post_comments(ucid, post_id, sort_by = "top") object = { - "2:string" => "community", - "25:embedded" => { - "22:string" => post_id, - }, - "45:embedded" => { - "2:varint" => 1_i64, - "3:varint" => 1_i64, - }, + "2:string" => "posts", "53:embedded" => { "4:embedded" => { "6:varint" => 0_i64, - "27:varint" => 1_i64, + "15:varint" => 2_i64, + "25:varint" => 0_i64, "29:string" => post_id, "30:string" => ucid, }, + "7:varint" => 0_i64, "8:string" => "comments-section", }, } + case sort_by + when "top" + object["53:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 0_i64 + when "new", "newest" + object["53:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 1_i64 + else # top + object["53:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 0_i64 + end + object_parsed = object.try { |i| Protodec::Any.cast_json(i) } .try { |i| Protodec::Any.from_json(i) } .try { |i| Base64.urlsafe_encode(i) } object2 = { "80226972:embedded" => { - "2:string" => ucid, + "2:string" => "FEcomment_post_detail_page_web_top_level", "3:string" => object_parsed, }, } diff --git a/src/invidious/routes/api/v1/channels.cr b/src/invidious/routes/api/v1/channels.cr index a940ee68..503b8c05 100644 --- a/src/invidious/routes/api/v1/channels.cr +++ b/src/invidious/routes/api/v1/channels.cr @@ -436,7 +436,7 @@ module Invidious::Routes::API::V1::Channels if ucid.nil? response = YoutubeAPI.resolve_url("https://www.youtube.com/post/#{id}") return error_json(400, "Invalid post ID") if response["error"]? - ucid = response.dig("endpoint", "browseEndpoint", "browseId").as_s + ucid = decode_ucid_from_post_protobuf(response.dig("endpoint", "browseEndpoint", "params").as_s) else ucid = ucid.to_s end @@ -460,13 +460,15 @@ module Invidious::Routes::API::V1::Channels format = env.params.query["format"]? format ||= "json" + sort_by = env.params.query["sort_by"]?.try &.downcase + sort_by ||= "top" continuation = env.params.query["continuation"]? case continuation when nil, "" ucid = env.params.query["ucid"] - comments = Comments.fetch_community_post_comments(ucid, id) + comments = Comments.fetch_community_post_comments(ucid, id, sort_by: sort_by) else comments = YoutubeAPI.browse(continuation: continuation) end diff --git a/src/invidious/routes/channels.cr b/src/invidious/routes/channels.cr index 508aa3e4..6d2b4465 100644 --- a/src/invidious/routes/channels.cr +++ b/src/invidious/routes/channels.cr @@ -284,7 +284,7 @@ module Invidious::Routes::Channels response = YoutubeAPI.resolve_url("https://www.youtube.com/post/#{id}") return error_template(400, "Invalid post ID") if response["error"]? - ucid = response.dig("endpoint", "browseEndpoint", "browseId").as_s + ucid = decode_ucid_from_post_protobuf(response.dig("endpoint", "browseEndpoint", "params").as_s) post_response = fetch_channel_community_post(ucid, id, locale, "json", thin_mode) end From f8febbe2b2fbc618e96cad027619b1acbc8509f4 Mon Sep 17 00:00:00 2001 From: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com> Date: Wed, 25 Jun 2025 23:53:07 -0400 Subject: [PATCH 5/6] format changes --- src/invidious/channels/community.cr | 12 ++++++------ src/invidious/routes/api/v1/misc.cr | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/invidious/channels/community.cr b/src/invidious/channels/community.cr index 8927c81b..43843b11 100644 --- a/src/invidious/channels/community.cr +++ b/src/invidious/channels/community.cr @@ -26,9 +26,9 @@ end def decode_ucid_from_post_protobuf(params) decoded_protobuf = params.try { |i| URI.decode_www_form(i) } - .try { |i| Base64.decode(i) } - .try { |i| IO::Memory.new(i) } - .try { |i| Protodec::Any.parse(i) } + .try { |i| Base64.decode(i) } + .try { |i| IO::Memory.new(i) } + .try { |i| Protodec::Any.parse(i) } return decoded_protobuf.try(&.["56:0:embedded"]["2:0:string"].as_s) end @@ -36,10 +36,10 @@ end def fetch_channel_community_post(ucid, post_id, locale, format, thin_mode) object = { "56:embedded" => { - "2:string" => ucid, - "3:string" => post_id.to_s, + "2:string" => ucid, + "3:string" => post_id.to_s, "11:string" => ucid, - } + }, } params = object.try { |i| Protodec::Any.cast_json(i) } .try { |i| Protodec::Any.from_json(i) } diff --git a/src/invidious/routes/api/v1/misc.cr b/src/invidious/routes/api/v1/misc.cr index 40f2a439..4ae877a8 100644 --- a/src/invidious/routes/api/v1/misc.cr +++ b/src/invidious/routes/api/v1/misc.cr @@ -197,8 +197,8 @@ module Invidious::Routes::API::V1::Misc .try { |i| IO::Memory.new(i) } .try { |i| Protodec::Any.parse(i) } - ucid = decoded_protobuf.try(&.["56:0:embedded"]["2:0:string"].as_s) - post_id = decoded_protobuf.try(&.["56:0:embedded"]["3:1:string"].as_s) + ucid = decoded_protobuf.try(&.["56:0:embedded"]["2:0:string"].as_s) + post_id = decoded_protobuf.try(&.["56:0:embedded"]["3:1:string"].as_s) else ucid = sub_endpoint["browseId"]? if sub_endpoint["browseId"]? && sub_endpoint["browseId"]?.try &.as_s.starts_with? "UC" post_id = nil From 7b8bc8f68a580eb736877415c86cb337b8be3d15 Mon Sep 17 00:00:00 2001 From: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com> Date: Thu, 26 Jun 2025 15:34:45 -0400 Subject: [PATCH 6/6] make `sort_by` code more legible --- src/invidious/comments/youtube.cr | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/invidious/comments/youtube.cr b/src/invidious/comments/youtube.cr index 18403656..ca0bdc2b 100644 --- a/src/invidious/comments/youtube.cr +++ b/src/invidious/comments/youtube.cr @@ -34,13 +34,15 @@ module Invidious::Comments case sort_by when "top" - object["53:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 0_i64 + sort_by_val = 0_i64 when "new", "newest" - object["53:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 1_i64 + sort_by_val = 1_i64 else # top - object["53:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 0_i64 + sort_by_val = 0_i64 end + object["53:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = sort_by_val + object_parsed = object.try { |i| Protodec::Any.cast_json(i) } .try { |i| Protodec::Any.from_json(i) } .try { |i| Base64.urlsafe_encode(i) }