From 4a3937a9238985a5f2f83a21e7d6122c2efbc5c9 Mon Sep 17 00:00:00 2001 From: sherl Date: Thu, 18 Sep 2025 08:09:33 +0200 Subject: [PATCH] feat: livestream browsing adds the ability to browse channel's livestreams, just like with shorts, videos and playlists --- ythdd_inv_tl.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/ythdd_inv_tl.py b/ythdd_inv_tl.py index 26ee1c7..d90a5f4 100644 --- a/ythdd_inv_tl.py +++ b/ythdd_inv_tl.py @@ -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()