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
This commit is contained in:
wrvnnull 2026-08-16 17:27:44 +08:00
parent d10f2a4802
commit 88c25e48f8

View File

@ -399,6 +399,39 @@ module Invidious::Videos::Parser
author = video_details["author"]?.try &.as_s author = video_details["author"]?.try &.as_s
ucid = video_details["channelId"]?.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") if author_info = video_secondary_renderer.try &.dig?("owner", "videoOwnerRenderer")
author_thumbnail = author_info.dig?("thumbnail", "thumbnails", 0, "url") author_thumbnail = author_info.dig?("thumbnail", "thumbnails", 0, "url")
author_verified = has_verified_badge?(author_info["badges"]?) author_verified = has_verified_badge?(author_info["badges"]?)