youtube-maze/lib/explore.mjs

86 lines
2.5 KiB
JavaScript

import util from 'util';
import * as api from './api.mjs';
import { WATCH_BASE, escapeQuotes } from './util.mjs';
const GRAPH_NODE = ' "%s" [label="%s", URL="%s", fontcolor=blue]';
const GRAPH_LINK = ' "%s" -> "%s"';
/**
* Explore the video graph starting from the given root.
*
* @async
* @param string videoId Source video identifier.
* @param function onUpdate Callback run every time a new video is explored.
* @return Array.<Object, Object> Nodes and set of neighbors of each node.
*/
export const exploreVideos = async (videoId, onUpdate) =>
{
// Store metadata about each visited video
const videosNodes = Object.create(null);
// List of videos linked from each video either through a card or an
// endscreen item
const nextVideos = Object.create(null);
// Videos that still need to be explored
const queue = [videoId];
while (queue.length > 0)
{
const currentId = queue.shift();
if (!(currentId in videosNodes))
{
const config = await api.getPlayerConfig(currentId);
const meta = api.getVideoMeta(config);
videosNodes[currentId] = meta;
nextVideos[currentId] = new Set();
// 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])
{
queue.push(nextId);
}
}
onUpdate(Object.keys(videosNodes).length, queue.length);
}
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}}`;
};