youtube: workaround, don't list topic/playlist items as channels - frontends - front-ends for some sites (experiment)
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
(DIR) commit b608dae128234bedd5ed09fe89c22c0dd5ef0b28
(DIR) parent 6242ec658d5610990c6b19944473fa7277493ac1
(HTM) Author: Hiltjo Posthuma <hiltjo@codemadness.org>
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(-)
---
(DIR) diff --git a/youtube/youtube.c b/youtube/youtube.c
@@ -145,6 +145,12 @@ extractjson_video(const char *s, const char **start, const char **end)
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, size_t valuelen,
void *pp)
@@ -159,7 +165,7 @@ processnode_search(struct json_node *nodes, size_t depth, const char *value, siz
(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, const char *value, siz
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, const char *value, siz
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, const char *value, siz
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, const char *value, siz
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, const char *value, siz
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, const char *value, siz
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, const char *value, siz
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, filter
+ these topic/playlist links away because they won't work for channel videos. The
+ JSON response would have to be parsed in a different way than channels. */
+ 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, " - Topic")) {
+ /* reset information that doesn't work for topics */
+ item->channelid[0] = '\0';
+ item->viewcount[0] = '\0';
+ }
+ }
+
return r;
}