const fetch = require("node-fetch") const { Track } = require("./track") const { Playlist } = require("./playlist") const ytdl = require('ytdl-core') /** * Centralize all youtube requests within a single interface. * It allows searching infos (tracks, playlists, metadata) over youtube. */ class Youtube { constructor() {} async #get_json_from_url(url) { const req = await fetch(url, {'headers': {'Cookie': 'CONSENT=YES+cb.20210622-13-p0.fr+FX+073'}}) const body = await req.text() return JSON.parse(body.match(/ytInitialData = (.*?);<\/script>/)[1]) } async #get_json_from_route(route) { return await this.#get_json_from_url('https://www.youtube.com/' + route) } /** * @param {string} search The search pattern that will be sent to Youtube * @return {Track[]} An array of objects representing the search results. source is set to "youtube" */ async search_track(search) { const json = await this.#get_json_from_route('results?search_query=' + encodeURIComponent(search)) const results = json.contents.twoColumnSearchResultsRenderer.primaryContents.sectionListRenderer.contents[0].itemSectionRenderer.contents return results .filter(e => e.videoRenderer) .map(e => { return new Track({ url: 'https://www.youtube.com/watch?v=' + e.videoRenderer.videoId, title: e.videoRenderer.title.runs[0].text, length: e.videoRenderer.lengthText?.simpleText || null, source: 'youtube', }) }) } /** * Retrieve all tracks of the playlist on youtube * @param {string} playlist_query an url or id of the youtube playlist. It will recognize the list=xxx pattern. * @returns {Playlist} */ async get_playlist(playlist_query) { const playlist_id = playlist_query.match(/list=([\w-]+)/)?.[1] || playlist_query const json = await this.#get_json_from_route('playlist?list=' + encodeURIComponent(playlist_id)) // console.log(JSON.stringify(json, null, 2)) const results = json.contents.twoColumnBrowseResultsRenderer.tabs[0].tabRenderer.content.sectionListRenderer.contents[0].itemSectionRenderer.contents[0].playlistVideoListRenderer.contents const tracks = results .filter(video => video?.playlistVideoRenderer?.lengthText) .map((video) => new Track({ title: video.playlistVideoRenderer.title.runs[0].text, url: 'https://www.youtube.com/watch?v=' + video.playlistVideoRenderer.videoId, length: video.playlistVideoRenderer.lengthText?.simpleText || null, source: 'youtube', })) return new Playlist({ tracks }) } /** * Retrieve the metadata of a YouTube video * @param {string} url * @returns {Track} */ async get_metadata(url) { const video_infos = (await ytdl.getBasicInfo(url)).videoDetails return new Track({ url: video_infos.video_url, title: video_infos.title, length: parseInt(video_infos.lengthSeconds), source: 'youtube' }) } } const youtube_instance = new Youtube() module.exports = { Youtube, youtube_instance, }