UNPKG

yt-stream

Version:

Create easily readable streams from YouTube video url's

169 lines (159 loc) 8.14 kB
const { Cookie, CookieJar, canonicalDomain } = require('tough-cookie'); const { HttpCookieAgent, HttpsCookieAgent } = require('http-cookie-agent/http'); const fs = require('fs'); const path = require('path'); function toDate(cookie){ if(typeof cookie.expirationDate === 'string'){ if(cookie.expirationDate.toLowerCase() === 'infinity') return 'Infinity'; return new Date(cookie.expirationDate); } else if(typeof cookie.expires === 'string'){ if(cookie.expires.toLowerCase() === 'infinity') return 'Infinity'; return new Date(cookie.expires); } else if(typeof cookie.expirationDate === 'number'){ return new Date(cookie.expirationDate * 1000); } else if(typeof cookie.expires === 'number'){ return new Date(cookie.expires * 1000); } else return 'Infinity'; } function addCookiesToJar(cookies, jar){ for(const cookie of cookies){ if(cookie instanceof Cookie){ if(cookie.domain !== 'youtube.com' && cookie.domain !== '.youtube.com') continue; jar.setCookieSync(cookie, 'https://www.youtube.com'); } else if(typeof cookie === 'object' && !Array.isArray(cookie) && cookie !== null){ if(typeof cookie.key !== 'string' && typeof cookie.name !== 'string') throw new Error(`Invalid cookie. A cookie must have a key or name.`); if(typeof cookie.domain !== 'string') throw new Error(`Invalid cookie. A cookie must have a domain.`); if(cookie.domain !== 'youtube.com' && cookie.domain !== '.youtube.com') continue; jar.setCookieSync(new Cookie({ key: cookie.key ?? cookie.name, value: typeof cookie.value === 'string' ? cookie.value : "", domain: canonicalDomain(cookie.domain), httpOnly: typeof cookie.httpOnly === 'boolean' ? cookie.httpOnly : false, hostOnly: typeof cookie.hostOnly === 'boolean' ? cookie.hostOnly : false, secure: typeof cookie.secure === 'boolean' ? cookie.secure : false, path: typeof cookie.path === 'string' ? cookie.path : '/', expires: toDate(cookie), sameSite: ['lax', 'samesite'].indexOf((cookie.sameSite ?? "").toLowerCase()) >= 0 ? cookie.sameSite : 'None' }), 'https://www.youtube.com'); } else throw new Error(`Invalid cookie. Cookie must be an instance of Cookie class or an object.`); } return jar; } class YTStreamAgent { constructor(cookies, options){ if(typeof options !== 'object' || Array.isArray(options) || options === null) options = {timeout: 5000, keepAlive: true, keepAliveMsecs: (Math.round(Math.random() * 3) + 5)}; if(!(options.cookies?.jar instanceof CookieJar)){ options.cookies = {}; options.cookies.jar = new CookieJar(); } this.jar = options.cookies.jar; this._options = options; if(typeof cookies === 'string') this.syncFile(cookies); else if(!Array.isArray(cookies)) cookies = []; if(Array.isArray(cookies)){ if(!!!cookies.filter(c => c.name === 'SOCS').length){ const templates = [{ key: 'SOCS', value: 'CAI', sameSite: 'lax', hostOnly: false, secure: true, path: '/', httpOnly: false, domain: 'youtube.com' }, { key: 'CONSENT', value: 'PENDING+'+(Math.round(Math.random() * 400) + 1).toString(), sameSite: 'None', hostOnly: false, secure: true, path: '/', httpOnly: false, domain: 'youtube.com', expires: 'Infinity' }]; templates.forEach(t => options.cookies.jar.setCookieSync(new Cookie(t), 'https://www.youtube.com')); } options.cookies.jar = addCookiesToJar(cookies, options.cookies.jar); } this.agents = { https: new HttpsCookieAgent(options), http: new HttpCookieAgent(options) }; this.localAddress = options.localAddress; this._cookies = this.jar.getCookiesSync('https://www.youtube.com') ?? []; if(typeof cookies !== 'string') this.syncedFile = ''; } addCookies(cookies){ if(!Array.isArray(cookies)) cookies = []; if(!(this.jar instanceof CookieJar)) throw new Error(`Jar property is not an instance of CookieJar`); this.jar = addCookiesToJar(cookies, this.jar); this._options.cookies.jar = this.jar; if(this.syncedFile.length > 0){ fs.writeFileSync(this.syncedFile, JSON.stringify(this.jar.getCookiesSync('https://www.youtube.com'), null, 2)); } } removeCookies(force){ if(force){ if(Object.keys(this._options).indexOf('cookies') >= 0) delete this._options.cookies; this.jar = new CookieJar(); let options = {..._this._options, cookies: {jar: this.jar}}; this.agents = { https: new HttpsCookieAgent(options), http: new HttpCookieAgent(options) }; } else { if(!(this._options.cookies?.jar instanceof CookieJar)){ this._options.cookies = {}; this._options.cookies.jar = new CookieJar(); } if(!!!this._cookies.filter(c => c.name === 'SOCS').length){ const templates = [{ key: 'SOCS', value: 'CAI', sameSite: 'lax', hostOnly: false, secure: true, path: '/', httpOnly: false, domain: 'youtube.com' }, { key: 'CONSENT', value: 'PENDING+'+(Math.round(Math.random() * 400) + 1).toString(), sameSite: 'None', hostOnly: false, secure: true, path: '/', httpOnly: false, domain: 'youtube.com', expires: 'Infinity' }]; templates.forEach(t => this._options.cookies.jar.setCookieSync(new Cookie(t), 'https://www.youtube.com')); } this._options.cookies.jar = addCookiesToJar(this._cookies, this._options.cookies.jar); this.jar = this._options.cookies.jar; this.agents = { https: new HttpsCookieAgent(this._options), http: new HttpCookieAgent(this._options) }; } } syncFile(filePath){ if(typeof filePath !== 'string') throw new Error(`Expected the file path to be a type of string, received ${typeof filePath}`); if(path.extname(filePath) !== ".json") throw new Error(`File expected to have .json extension name, received ${path.extname(filePath)}`); if(!path.isAbsolute(filePath)) filePath = path.join(process.cwd(), filePath); if(!fs.existsSync(filePath)) throw new Error(`Couldn't find a file with the path '${filePath}'. Make sure that the file exists and the path is either absolute or relative to the root of the process`); let cookies = fs.readFileSync(filePath); try{ cookies = JSON.parse(cookies); } catch { throw new Error(`Cookies from imported file is not a valid json object`); } if(!Array.isArray(cookies)) throw new Error(`Imported cookies expected to be an array, received type of ${typeof cookies}, but no array`); this.syncedFile = filePath; this.jar = addCookiesToJar(cookies, this.jar); this._options.cookies.jar = this.jar; } } module.exports = { YTStreamAgent };