Skip to content

Instantly share code, notes, and snippets.

@dlh3
Last active October 22, 2023 23:02
Show Gist options
  • Save dlh3/de843ea6346290d4056af2dda70c596b to your computer and use it in GitHub Desktop.
Save dlh3/de843ea6346290d4056af2dda70c596b to your computer and use it in GitHub Desktop.
A YouTube end-card spider to discover all the videos CGP Grey created for his RPS interactive video
https://gist.githubusercontent.com/dlh3/de843ea6346290d4056af2dda70c596b/raw/4f9c5b81ff423a341862c90f9945622efe7f0a6f/Game%2520Tree
// copy and run treeify first (if you want the tree view)
// https://github.com/notatestuser/treeify/blob/master/treeify.js
// 1. open https://www.youtube.com/
// 2. run this in the browser's console
videos = {};
played = [];
async function discoverVideos(id) {
// skip videos we've already encountered
if (played.includes(id)) return;
else played.push(id);
videos[id] = await fetch('/watch?v=' + id)
.then(resp => resp.text())
.then(body => body.match(/"shortDescription":"([^"]*)"/)[1]
.split(/\\n/)
.filter(part => part.includes('/watch'))
.map(async card => {
const [text, cardId] = card.split("https://www.youtube.com/watch?v=");
console.log(text, cardId);
await discoverVideos(cardId);
return [text.replace(': ', ''), cardId];
}))
.then(Promise.all.bind(Promise))
.then(cards => cards.reduce((obj, [label, id]) => (obj[label] = id, obj), {}))
.catch(console.error);
}
function buildGameTree(id) {
return {
[id]: __buildGameTree(id)
};
}
let rootVideoId = played[0];
function __buildGameTree(id) {
return Object.entries(videos[id])
.filter(([cardLabel, cardVideoId]) => cardVideoId !== rootVideoId)
.map(([cardLabel, cardVideoId]) => [cardLabel, cardVideoId, __buildGameTree(cardVideoId)])
.reduce((obj, [cardLabel, cardVideoId, tree]) => (obj[`${cardLabel}: ${cardVideoId}`] = tree, obj), {});
}
await discoverVideos('PmWQmZXYd74');
let gameTree = buildGameTree(played[0]);
console.log(window.treeify && treeify.asTree(gameTree, false) || 'treeify not available'), gameTree;
// no longer works, instead refer to RPS-youtube-descriptions-spider.js
// 1. open https://www.youtube.com/embed/PmWQmZXYd74
// 2. run this in the browser's console
var videos = {};
var errors = [];
var played = [];
var queued = [];
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
height: '390',
width: '640',
videoId: 'PmWQmZXYd74',
playerVars: {
'autoplay': 1,
'playsinline': 1
},
events: {
'onError': onError,
'onStateChange': onPlayerStateChange
}
});
}
function playNext() {
if (!queued.length) {
setTimeout(playNext, 1000);
return;
}
var nextVideo = queued.shift();
console.log(`Loading video ${nextVideo}`);
player.loadVideoById(nextVideo);
}
function onError(event) {
errors.push({
videoId: event.target.getVideoData().video_id,
...event
});
console.error(event);
playNext();
}
function onPlayerStateChange(event) {
if (event.data == YT.PlayerState.PLAYING) {
player.pauseVideo();
var id = player.getVideoData().video_id;
var cards = Array.from(player.g.contentDocument.querySelectorAll('.ytp-ce-covering-overlay'))
.map(card => card.href.replace(/.+=/, ''))
.filter(card => !card.startsWith('PL'));
played.push(id);
if (cards) {
videos[id] = {
id: id,
cards: cards,
depth: [0],
rank: [0],
loadedBy: [],
...videos[id]
};
var uniqueCards = 0;
var currentCard = 0;
cards.forEach(card => {
if (videos[card]) {
console.debug(`${card} has already been discovered, loaded by ${videos[card].loadedBy}`);
} else {
uniqueCards++;
console.debug(`${card} queuing`);
queued.push(card);
videos[card] = {
depth: [],
rank: [],
loadedBy: []
};
}
// I started to implement depth/rank to aid in building a visualization, incomplete
videos[card].depth.push(videos[id].depth[0] + 1);
videos[card].rank.push(getCardRank(cards.length, currentCard++));
videos[card].loadedBy.push(id);
});
console.log(`Video ${id} had ${cards.length} cards (${uniqueCards} new)`);
console.log(`${played.length} videos played, ${queued.length} queued`);
playNext();
}
}
}
function getCardRank(total, current) {
if (total === 1 && current === 0) return 0;
if (total === 2 && current === 0) return -1;
if (total === 2 && current === 1) return 1;
if (total === 3 && current === 0) return -1;
if (total === 3 && current === 1) return 0;
if (total === 3 && current === 2) return 1;
}
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment