youtube-maze/lib/explore.mjs

88 lines
2.5 KiB
JavaScript

import util from 'util';
import * as api from './api.mjs';
import { WATCH_BASE, PromiseQueue, escapeQuotes } from './util.mjs';
import { retryable } from '../lib/retry.mjs';
const GRAPH_NODE = ' "%s" [label="%s", URL="%s", fontcolor=blue]';
const GRAPH_LINK = ' "%s" -> "%s"';
const retryPlayerConfig = retryable(api.getPlayerConfig);
/**
* Explore the video graph starting from the given root.
*
* @async
* @param string videoId Source video identifier.
* @return Array.<Object, Object> Nodes and set of neighbors of each node.
*/
export const exploreVideos = async videoId =>
{
// Store metadata about each visited video
const videosNodes = Object.create(null);
videosNodes[videoId] = {};
// List of videos linked from each video either through a card or an
// endscreen item
const nextVideos = Object.create(null);
nextVideos[videoId] = new Set();
// Pending video requests
const queue = new PromiseQueue();
queue.add(retryPlayerConfig(videoId));
while (!queue.empty())
{
const config = await queue.next();
const meta = api.getVideoMeta(config);
videosNodes[meta.videoId] = meta;
// Add links between this video and the linked ones
api.getEndScreenVideos(config)
.forEach(nextId => nextVideos[meta.videoId].add(nextId));
api.getCardVideos(config)
.forEach(nextId => nextVideos[meta.videoId].add(nextId));
for (let nextId of nextVideos[meta.videoId])
{
if (!(nextId in videosNodes))
{
videosNodes[nextId] = {};
nextVideos[nextId] = new Set();
queue.add(retryPlayerConfig(nextId));
}
}
}
return [videosNodes, nextVideos];
};
/**
* Convert a video graph to the GraphViz format.
*
* @param Object videosNodes Videos of the graph.
* @param Object nextVideos For each video, list of next videos.
* @return string GraphViz representation of the graph.
*/
export const toGraphviz = (videosNodes, nextVideos) =>
{
// Convert videosNodes
const nodesStr = Object.entries(videosNodes).map(([id, {title}]) =>
{
const url = util.format(WATCH_BASE, id);
return util.format(GRAPH_NODE, id, escapeQuotes(title), url);
}).join('\n');
// Convert edges
const nextStr = Object.entries(nextVideos).map(([id, neighbors]) =>
Array.from(neighbors)
.map(neighbor => util.format(GRAPH_LINK, id, neighbor))
.join('\n')
).join('\n');
return `digraph "" {
${nodesStr}
${nextStr}}`;
};