feat: livestream browsing

adds the ability to browse channel's livestreams, just like with
shorts, videos and playlists
This commit is contained in:
2025-09-18 08:09:33 +02:00
parent c3fae689e1
commit 4a3937a923

View File

@@ -619,6 +619,9 @@ def get_channel_tab(requested_tab, ucid, req, only_json: bool = False):
tabs = safeTraverse(wdata, ["contents", "twoColumnBrowseResultsRenderer", "tabs"], default=[])
for tab in tabs:
tab_name = safeTraverse(tab, ["tabRenderer", "title"], default="").lower()
# rewrite livestream tab for backwards compatibility with invidious (and clients like freetube)
if tab_name == "live":
tab_name = "streams"
if tab_name and tab_name == requested_tab:
result = safeTraverse(tab, ["tabRenderer", "content"], default=[])
break
@@ -628,8 +631,8 @@ def get_channel_tab(requested_tab, ucid, req, only_json: bool = False):
new_continuation = ""
response = {}
match requested_tab:
case "videos" | "shorts":
# videos/shorts have actually the same response schema,
case "videos" | "shorts" | "streams":
# videos/shorts/livestreams have actually the same response schema,
# only the renderers differ - but they are taken care of in ythdd_struct_parser.parseRenderers()
if ctoken is None:
@@ -646,10 +649,12 @@ def get_channel_tab(requested_tab, ucid, req, only_json: bool = False):
new_continuation = safeTraverse(inner_contents[-1], ["continuationItemRenderer", "continuationEndpoint", "continuationCommand", "token"], default="")
response = {
"videos": items,
"continuation": new_continuation
"videos": items
}
if new_continuation:
response["continuation"] = new_continuation
# cache response
if ythdd_globals.config['general']['cache']:
ythdd_globals.general_cache["channels"][unique_request_fingerprint] = response
@@ -672,10 +677,12 @@ def get_channel_tab(requested_tab, ucid, req, only_json: bool = False):
new_continuation = safeTraverse(inner_contents[-1], ["continuationItemRenderer", "continuationEndpoint", "continuationCommand", "token"], default="")
response = {
"playlists": items,
"continuation": new_continuation
"playlists": items
}
if new_continuation:
response["continuation"] = new_continuation
# cache response
if ythdd_globals.config['general']['cache']:
ythdd_globals.general_cache["channels"][unique_request_fingerprint] = response
@@ -764,8 +771,10 @@ def channels(data, req, only_json: bool = False):
if len(data) > 4:
match data[4]:
case "videos" | "shorts" | "playlists" | "podcasts":
return get_channel_tab(data[4], data[3], req)
case "videos" | "shorts" | "playlists" | "podcasts" | "streams":
return get_channel_tab( data[4], data[3], req)
case "live":
return get_channel_tab("streams", data[3], req)
case _:
return send(400, {"error": f"Bad request, unrecognized/unsupported tab \"{data[4]}\"."})
@@ -791,6 +800,9 @@ def channels(data, req, only_json: bool = False):
# collect tab names
tab_name = safeTraverse(tab, ["tabRenderer", "title"], default="").lower()
if tab_name:
# same as in get_channel_tab
if tab_name == "live":
tab_name = "streams"
tab_names.append(tab_name)
# and their params (used to retrieve data about them)
ythdd_globals.general_cache["continuations"]["channels"][author_ucid]["tabs"][tab_name] = dict()