'use strict'; const util = require('util'); const request = require('request'); const cheerio = require('cheerio'); const YOUTUBE_BASE = 'https://www.youtube.com/%s'; const WATCH_BASE = util.format(YOUTUBE_BASE, 'watch?v=%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'); const playerRegex = /ytplayer\.config = (\{.*?\});/; /** * Fetch the `ytplayer.config` object for a YouTube video. * * @async * @param videoId Identifier of the video to fetch. * @return The player configuration object. */ const getPlayerConfig = videoId => { const url = util.format(WATCH_BASE, videoId); return new Promise((resolve, reject) => { request(url, (err, res, body) => { if (err) { reject(err); return; } // Look for the definition of ytplayer.config and unserialize it // and the player_response subobject try { const playerConfig = JSON.parse(body.match(playerRegex)[1]); playerConfig.args.player_response = JSON.parse(playerConfig.args.player_response); resolve(playerConfig); } catch (err) { reject(err); } }); }); }; exports.getPlayerConfig = getPlayerConfig; /** * Get metadata about a YouTube video. * * @param playerConfig The `ytplayer.config` object corresponding to the source * YouTube video, as obtained from `getPlayerConfig`. * @return Object containing the video metadata. */ const getVideoMeta = playerConfig => ({ videoId: playerConfig.args.player_response.videoDetails.videoId, title: playerConfig.args.player_response.videoDetails.title, }); exports.getVideoMeta = getVideoMeta; /** * Find videos linked from the endscreen of a YouTube video. * * @param playerConfig The `ytplayer.config` object corresponding to the source * YouTube video, as obtained from `getPlayerConfig`. * @return List of identifiers of linked videos. */ const getEndScreenVideos = playerConfig => { const response = playerConfig.args.player_response; if (!('endscreen' in response)) { return []; } return response.endscreen.endscreenRenderer.elements .map(elt => elt.endscreenElementRenderer) .filter(rdr => 'watchEndpoint' in rdr.endpoint) .map(rdr => rdr.endpoint.watchEndpoint.videoId); }; exports.getEndScreenVideos = getEndScreenVideos; /** * Find videos linked from as cards from a YouTube video. * * @param playerConfig The `ytplayer.config` object corresponding to the source * YouTube video, as obtained from `getPlayerConfig`. * @return List of identifiers of linked videos. */ const getCardVideos = playerConfig => { const response = playerConfig.args.player_response; if (!('cards' in response)) { return []; } return response.cards.cardCollectionRenderer.cards .map(card => card.cardRenderer.content) .filter(content => 'videoInfoCardContentRenderer' in content) .map(content => content.videoInfoCardContentRenderer) .map(rdr => rdr.action.watchEndpoint.videoId); }; exports.getCardVideos = getCardVideos;