youtube: workaround, don't list topic/playlist items as channels - frontends - … | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
commit b608dae128234bedd5ed09fe89c22c0dd5ef0b28 | |
parent 6242ec658d5610990c6b19944473fa7277493ac1 | |
Author: Hiltjo Posthuma <[email protected]> | |
Date: Sun, 1 Jun 2025 13:37:34 +0200 | |
youtube: workaround, don't list topic/playlist items as channels | |
Listing channel videos based on this topic or playlist would serve different | |
JSON which we don't handle, so it would serve an empty page. | |
For now just reset the channelid if the channeltitle ends with " - Topic". | |
Separate the function for checking the renderer name. This might make it easier | |
to adapt to support playlists in the future. The JSON output for | |
playlists/topics has a "shelfRenderer". | |
Diffstat: | |
M youtube/youtube.c | 40 +++++++++++++++++++++++++----… | |
1 file changed, 32 insertions(+), 8 deletions(-) | |
--- | |
diff --git a/youtube/youtube.c b/youtube/youtube.c | |
@@ -145,6 +145,12 @@ extractjson_video(const char *s, const char **start, const… | |
return 0; | |
} | |
+static int | |
+isrenderername(const char *name) | |
+{ | |
+ return !strcmp(name, "videoRenderer"); | |
+} | |
+ | |
static void | |
processnode_search(struct json_node *nodes, size_t depth, const char *value, s… | |
void *pp) | |
@@ -159,7 +165,7 @@ processnode_search(struct json_node *nodes, size_t depth, c… | |
(items|contents)[].videoRenderer objects */ | |
if (depth >= 3 && | |
nodes[depth - 1].type == JSON_TYPE_OBJECT && | |
- !strcmp(nodes[depth - 1].name, "videoRenderer")) { | |
+ isrenderername(nodes[depth - 1].name)) { | |
r->nitems++; | |
return; | |
} | |
@@ -170,7 +176,7 @@ processnode_search(struct json_node *nodes, size_t depth, c… | |
if (depth >= 4 && | |
nodes[depth - 1].type == JSON_TYPE_STRING && | |
- !strcmp(nodes[depth - 2].name, "videoRenderer") && | |
+ isrenderername(nodes[depth - 2].name) && | |
!strcmp(nodes[depth - 1].name, "videoId")) { | |
strlcpy(item->id, value, sizeof(item->id)); | |
} | |
@@ -181,7 +187,7 @@ processnode_search(struct json_node *nodes, size_t depth, c… | |
nodes[depth - 3].type == JSON_TYPE_ARRAY && | |
nodes[depth - 2].type == JSON_TYPE_OBJECT && | |
nodes[depth - 1].type == JSON_TYPE_STRING && | |
- !strcmp(nodes[depth - 5].name, "videoRenderer") && | |
+ isrenderername(nodes[depth - 5].name) && | |
!strcmp(nodes[depth - 4].name, "title") && | |
!strcmp(nodes[depth - 3].name, "runs") && | |
!strcmp(nodes[depth - 1].name, "text") && | |
@@ -198,7 +204,7 @@ processnode_search(struct json_node *nodes, size_t depth, c… | |
nodes[depth - 3].type == JSON_TYPE_ARRAY && | |
nodes[depth - 2].type == JSON_TYPE_OBJECT && | |
nodes[depth - 1].type == JSON_TYPE_STRING && | |
- !strcmp(nodes[depth - 7].name, "videoRenderer") && | |
+ isrenderername(nodes[depth - 7].name) && | |
!strcmp(nodes[depth - 6].name, "detailedMetadataSnippets") && | |
!strcmp(nodes[depth - 4].name, "snippetText") && | |
!strcmp(nodes[depth - 3].name, "runs") && | |
@@ -213,7 +219,7 @@ processnode_search(struct json_node *nodes, size_t depth, c… | |
nodes[depth - 3].type == JSON_TYPE_ARRAY && | |
nodes[depth - 2].type == JSON_TYPE_OBJECT && | |
nodes[depth - 1].type == JSON_TYPE_STRING && | |
- !strcmp(nodes[depth - 5].name, "videoRenderer") && | |
+ isrenderername(nodes[depth - 5].name) && | |
!strcmp(nodes[depth - 4].name, "descriptionSnippet") && | |
!strcmp(nodes[depth - 3].name, "runs") && | |
!strcmp(nodes[depth - 1].name, "text")) { | |
@@ -225,7 +231,7 @@ processnode_search(struct json_node *nodes, size_t depth, c… | |
nodes[depth - 3].type == JSON_TYPE_OBJECT && | |
nodes[depth - 2].type == JSON_TYPE_OBJECT && | |
nodes[depth - 1].type == JSON_TYPE_STRING && | |
- !strcmp(nodes[depth - 3].name, "videoRenderer") && | |
+ isrenderername(nodes[depth - 3].name) && | |
!strcmp(nodes[depth - 1].name, "simpleText")) { | |
if (!strcmp(nodes[depth - 2].name, "viewCountText") && | |
!item->viewcount[0]) { | |
@@ -248,7 +254,7 @@ processnode_search(struct json_node *nodes, size_t depth, c… | |
nodes[depth - 3].type == JSON_TYPE_OBJECT && | |
nodes[depth - 2].type == JSON_TYPE_OBJECT && | |
nodes[depth - 1].type == JSON_TYPE_STRING && | |
- !strcmp(nodes[depth - 7].name, "videoRenderer") && | |
+ isrenderername(nodes[depth - 7].name) && | |
!strcmp(nodes[depth - 6].name, "longBylineText") && | |
!strcmp(nodes[depth - 5].name, "runs") && | |
!strcmp(nodes[depth - 3].name, "navigationEndpoint") && | |
@@ -265,7 +271,7 @@ processnode_search(struct json_node *nodes, size_t depth, c… | |
nodes[depth - 3].type == JSON_TYPE_ARRAY && | |
nodes[depth - 2].type == JSON_TYPE_OBJECT && | |
nodes[depth - 1].type == JSON_TYPE_STRING && | |
- !strcmp(nodes[depth - 5].name, "videoRenderer") && | |
+ isrenderername(nodes[depth - 5].name) && | |
!strcmp(nodes[depth - 4].name, "longBylineText") && | |
!strcmp(nodes[depth - 3].name, "runs")) { | |
if (!strcmp(nodes[depth - 1].name, "text") && | |
@@ -279,7 +285,9 @@ static struct search_response * | |
parse_search_response(const char *data) | |
{ | |
struct search_response *r; | |
+ struct item *item; | |
const char *s, *start, *end; | |
+ size_t i, len; | |
int ret; | |
if (!(s = strstr(data, "\r\n\r\n"))) | |
@@ -300,6 +308,22 @@ parse_search_response(const char *data) | |
free(r); | |
return NULL; | |
} | |
+ | |
+ /* workaround: sometimes playlists or topics are listed as channels, f… | |
+ these topic/playlist links away because they won't work for channel… | |
+ JSON response would have to be parsed in a different way than chann… | |
+ for (i = 0; i < r->nitems; i++) { | |
+ item = &(r->items[i]); | |
+ len = strlen(item->channeltitle); | |
+ | |
+ if (len > sizeof(" - Topic") && | |
+ !strcmp(item->channeltitle + len - sizeof(" - Topic") + 1,… | |
+ /* reset information that doesn't work for topics */ | |
+ item->channelid[0] = '\0'; | |
+ item->viewcount[0] = '\0'; | |
+ } | |
+ } | |
+ | |
return r; | |
} | |