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