From 88c25e48f8f172505af45ea111079d13ab7849b2 Mon Sep 17 00:00:00 2001 From: wrvnnull Date: Sun, 16 Aug 2026 17:27:44 +0800 Subject: [PATCH] fix: populate ucid for videos with multiple authors (fixes #5722) For collaboration videos YouTube does not expose a single `channelId` in `videoDetails`, leaving `ucid` empty and making the channel link on the watch page disappear. Fall back to the owner/byline navigation endpoint (`videoOwnerRenderer.title.runs[0]`, then `longBylineText`/`shortBylineText`) which still carries the first channel's browseId. Closes iv-org/invidious#5722 --- src/invidious/videos/parser.cr | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/invidious/videos/parser.cr b/src/invidious/videos/parser.cr index 8367fcd7..0bc0f3ad 100644 --- a/src/invidious/videos/parser.cr +++ b/src/invidious/videos/parser.cr @@ -399,6 +399,39 @@ module Invidious::Videos::Parser author = video_details["author"]?.try &.as_s ucid = video_details["channelId"]?.try &.as_s + # For videos with multiple authors (collaborations) YouTube does not + # expose a single `channelId` in `videoDetails`, which left `ucid` empty + # and made the channel link on the watch page disappear. Fall back to the + # owner/byline navigation endpoint, which still carries the first + # channel's browseId. See iv-org/invidious#5722. + if ucid.nil? || ucid.empty? + # Prefer the owner renderer (first channel of a collaboration). + if owner = video_secondary_renderer.try &.dig?("owner", "videoOwnerRenderer") + owner_ucid = owner.dig?("title", "runs", 0, "navigationEndpoint", "browseEndpoint", "browseId").try &.as_s + ucid = owner_ucid if owner_ucid && !owner_ucid.empty? + end + + # Still missing? Walk every byline candidate and keep the first one that + # actually carries a usable browseId, instead of stopping at the first + # non-nil byline (whose first run may lack a navigation endpoint). + if ucid.nil? || ucid.empty? + byline_candidates = [ + video_details["longBylineText"]?, + video_details["shortBylineText"]?, + video_secondary_renderer.try &.dig?("longBylineText"), + video_secondary_renderer.try &.dig?("shortBylineText"), + ] + byline_candidates.each do |candidate| + next if candidate.nil? + candidate_ucid = candidate.dig?("runs", 0, "navigationEndpoint", "browseEndpoint", "browseId").try &.as_s + if candidate_ucid && !candidate_ucid.empty? + ucid = candidate_ucid + break + end + end + end + end + if author_info = video_secondary_renderer.try &.dig?("owner", "videoOwnerRenderer") author_thumbnail = author_info.dig?("thumbnail", "thumbnails", 0, "url") author_verified = has_verified_badge?(author_info["badges"]?)