Generate visual representations of the networks underlying video-gamebooks on YouTube
https://youtube-maze.delab.re/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
109 lines
3.2 KiB
109 lines
3.2 KiB
'use strict';
|
|
|
|
const util = require('util');
|
|
const request = require('request');
|
|
const cheerio = require('cheerio');
|
|
|
|
const YOUTUBE_BASE = 'https://www.youtube.com/%s';
|
|
const VIDEO_ID_REGEX = /watch\?v=([^&]*)/i;
|
|
const END_SCREEN_BASE = util.format(YOUTUBE_BASE, 'get_endscreen?v=%s');
|
|
const CARD_BASE = util.format(YOUTUBE_BASE, 'annotations_invideo?video_id=%s');
|
|
|
|
/**
|
|
* Récupère une liste des vidéos liées à la fin d’une autre vidéo.
|
|
*
|
|
* @param videoId Identifiant de la vidéo source.
|
|
* @return Liste des vidéos liées. Chaque vidéo est un objet contenant
|
|
* son identifiant (videoId) et son titre (title).
|
|
*/
|
|
const getEndScreenVideos = videoId =>
|
|
{
|
|
const url = util.format(END_SCREEN_BASE, videoId);
|
|
|
|
return new Promise((resolve, reject) =>
|
|
{
|
|
request(url, (err, res, body) =>
|
|
{
|
|
if (err)
|
|
{
|
|
reject(err);
|
|
return;
|
|
}
|
|
|
|
// Suppression des caractères initiaux inutiles si applicable
|
|
if (body.substr(0, 3) === ')]}')
|
|
{
|
|
body = body.substr(3);
|
|
}
|
|
|
|
// Interprétation du JSON
|
|
const data = JSON.parse(body);
|
|
|
|
// Aucun écran de fin
|
|
if (
|
|
typeof data !== 'object' || data === null ||
|
|
data.elements === undefined
|
|
)
|
|
{
|
|
resolve([]);
|
|
return;
|
|
}
|
|
|
|
// Filtrage des données pour extraire le titre et l’ID des vidéos
|
|
resolve(data.elements.map(elt => ({
|
|
videoId: elt.endscreenElementRenderer.endpoint
|
|
.watchEndpoint.videoId,
|
|
title: elt.endscreenElementRenderer.title.simpleText,
|
|
})));
|
|
});
|
|
});
|
|
};
|
|
|
|
exports.getEndScreenVideos = getEndScreenVideos;
|
|
|
|
/**
|
|
* Récupère une liste des vidéos liées en tant que carte d’une autre vidéo.
|
|
*
|
|
* @param videoId Identifiant de la vidéo source.
|
|
* @return Liste des vidéos liées. Chaque vidéo est un objet contenant
|
|
* son identifiant (videoId) et son titre (title).
|
|
*/
|
|
const getCardVideos = videoId =>
|
|
{
|
|
const url = util.format(CARD_BASE, videoId);
|
|
|
|
return new Promise((resolve, reject) =>
|
|
{
|
|
request(url, (err, res, body) =>
|
|
{
|
|
if (err)
|
|
{
|
|
reject(err);
|
|
return;
|
|
}
|
|
|
|
// Interprétation du XML externe et recherche des annotations
|
|
// de type carte
|
|
const nav = cheerio.load(body);
|
|
const cards = nav('annotation[type="card"][style="video"]');
|
|
const list = [];
|
|
|
|
cards.each((i, el) =>
|
|
{
|
|
// Interprétation du JSON de chaque carte, extraction
|
|
// du titre et de l’identifiant de la vidéo
|
|
const data = JSON.parse(nav(el).children('data').text());
|
|
const videoIdResults = VIDEO_ID_REGEX.exec(data.url);
|
|
|
|
list.push({
|
|
title: data.title,
|
|
videoId: videoIdResults ? videoIdResults[1] : null
|
|
});
|
|
});
|
|
|
|
resolve(list);
|
|
});
|
|
});
|
|
};
|
|
|
|
exports.getCardVideos = getCardVideos;
|
|
|