mirror of
https://github.com/iv-org/invidious.git
synced 2026-09-06 00:52:45 -05:00
fix: resolve channel of related videos published as a collaboration
Related videos published as a collaboration between several channels carry no "browseEndpoint" in their byline. Their byline opens a "Collaborators" dialog instead, which holds the channel of each collaborator, so `get_browse_id` returned an empty string and the recommendation was rendered without a link to the channel. Fall back to the first entry of that dialog, which is the channel the video was published on. Fixes #5722 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
821365cf71
commit
f708bda6e4
103
spec/invidious/videos/related_video_collaboration_spec.cr
Normal file
103
spec/invidious/videos/related_video_collaboration_spec.cr
Normal file
@ -0,0 +1,103 @@
|
||||
require "../../parsers_helper.cr"
|
||||
|
||||
Spectator.describe "parse_related_video" do
|
||||
it "extracts the channel of a video published as a collaboration" do
|
||||
# A video published as a collaboration between two channels. Its byline
|
||||
# carries no "browseEndpoint"; it opens a "Collaborators" dialog instead,
|
||||
# and the channel of each collaborator sits inside that dialog.
|
||||
#
|
||||
# See: https://github.com/iv-org/invidious/issues/5722
|
||||
related = JSON.parse(<<-JSON)
|
||||
{
|
||||
"videoId": "dbVdnL6HWOg",
|
||||
"title": { "simpleText": "Professor Jiang Debates Iran War, Trump And China Ties" },
|
||||
"lengthInSeconds": 2241,
|
||||
"shortViewCountText": { "simpleText": "1M views" },
|
||||
"publishedTimeText": { "simpleText": "3 months ago" },
|
||||
"shortBylineText": {
|
||||
"runs": [
|
||||
{
|
||||
"text": "Piers Morgan Uncensored and ProfSteveKeen",
|
||||
"navigationEndpoint": {
|
||||
"showDialogCommand": {
|
||||
"panelLoadingStrategy": {
|
||||
"inlineContent": {
|
||||
"dialogViewModel": {
|
||||
"customContent": {
|
||||
"listViewModel": {
|
||||
"listItems": [
|
||||
{
|
||||
"listItemViewModel": {
|
||||
"title": { "content": "Piers Morgan Uncensored" },
|
||||
"rendererContext": {
|
||||
"commandContext": {
|
||||
"onTap": {
|
||||
"innertubeCommand": {
|
||||
"browseEndpoint": { "browseId": "UCatt7TBjfBkiJWx8khav_Gg" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"listItemViewModel": {
|
||||
"title": { "content": "ProfSteveKeen" },
|
||||
"rendererContext": {
|
||||
"commandContext": {
|
||||
"onTap": {
|
||||
"innertubeCommand": {
|
||||
"browseEndpoint": { "browseId": "UCM1ubsbE-tG9ru61mc3zX8A" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
JSON
|
||||
|
||||
parsed = Invidious::Videos::Parser.parse_related_video(related).not_nil!
|
||||
|
||||
expect(parsed["ucid"].as_s).to eq("UCatt7TBjfBkiJWx8khav_Gg")
|
||||
expect(parsed["author"].as_s).to eq("Piers Morgan Uncensored and ProfSteveKeen")
|
||||
end
|
||||
|
||||
it "keeps extracting the channel of a single author video" do
|
||||
related = JSON.parse(<<-JSON)
|
||||
{
|
||||
"videoId": "_g4l7YkDQwA",
|
||||
"title": { "simpleText": "The Diary Of A CEO episode" },
|
||||
"lengthInSeconds": 1337,
|
||||
"shortViewCountText": { "simpleText": "2.1M views" },
|
||||
"publishedTimeText": { "simpleText": "1 month ago" },
|
||||
"shortBylineText": {
|
||||
"runs": [
|
||||
{
|
||||
"text": "The Diary Of A CEO",
|
||||
"navigationEndpoint": {
|
||||
"browseEndpoint": { "browseId": "UCGq-a57w-aPwyi3pW7XLiHw" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
JSON
|
||||
|
||||
parsed = Invidious::Videos::Parser.parse_related_video(related).not_nil!
|
||||
|
||||
expect(parsed["ucid"].as_s).to eq("UCGq-a57w-aPwyi3pW7XLiHw")
|
||||
expect(parsed["author"].as_s).to eq("The Diary Of A CEO")
|
||||
end
|
||||
end
|
||||
@ -28,6 +28,13 @@ module Invidious::Videos::Parser
|
||||
|
||||
ucid = channel_info.try { |ci| HelperExtractors.get_browse_id(ci) }
|
||||
|
||||
# Videos published as a collaboration have no "browseEndpoint" in their
|
||||
# byline, so the channel has to be read from the "Collaborators" dialog
|
||||
# that the byline opens instead. See `get_collaborator_browse_id`.
|
||||
if ucid.nil? || ucid.empty?
|
||||
ucid = channel_info.try { |ci| HelperExtractors.get_collaborator_browse_id(ci) }
|
||||
end
|
||||
|
||||
short_view_count = related.try do |r|
|
||||
HelperExtractors.get_short_view_count(r).to_s
|
||||
end
|
||||
|
||||
@ -1143,6 +1143,21 @@ module HelperExtractors
|
||||
def self.get_browse_id(container)
|
||||
return container.dig?("navigationEndpoint", "browseEndpoint", "browseId").try &.as_s || ""
|
||||
end
|
||||
|
||||
# Videos published as a collaboration have no "browseEndpoint" in their
|
||||
# byline. Their byline opens a "Collaborators" dialog instead, which holds
|
||||
# the channel of every collaborator. Retrieves the ID of the first entry,
|
||||
# which is the channel the video was published on.
|
||||
#
|
||||
# Returns an empty string when it's unable to do so
|
||||
def self.get_collaborator_browse_id(container)
|
||||
return container.dig?(
|
||||
"navigationEndpoint", "showDialogCommand", "panelLoadingStrategy",
|
||||
"inlineContent", "dialogViewModel", "customContent", "listViewModel",
|
||||
"listItems", 0, "listItemViewModel", "rendererContext", "commandContext",
|
||||
"onTap", "innertubeCommand", "browseEndpoint", "browseId"
|
||||
).try &.as_s || ""
|
||||
end
|
||||
end
|
||||
|
||||
# Parses an item from Youtube's JSON response into a more usable structure.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user