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. 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 metadata = await api.getMetadata(currentId); const info = api.getVideoInfo(metadata); videosNodes[currentId] = info; nextVideos[currentId] = new Set(); // Add links between this video and the linked ones api.getEndScreenVideos(metadata) .forEach(nextId => nextVideos[info.videoId].add(nextId)); api.getCardVideos(metadata) .forEach(nextId => nextVideos[info.videoId].add(nextId)); for (let nextId of nextVideos[info.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}}`; };