mirror of
https://github.com/LuanRT/YouTube.js.git
synced 2026-06-19 04:21:35 +00:00
refactor: generate sessions manually
Session generation has been moved to `core/SessionBuilder.js`, which retrieves & generates all the required data to create a valid session. This should also decrease initialization time by over 600 milliseconds!
This commit is contained in:
@@ -7,9 +7,9 @@ const CancelToken = Axios.CancelToken;
|
||||
const EventEmitter = require('events');
|
||||
|
||||
const OAuth = require('./core/OAuth');
|
||||
const Player = require('./core/Player');
|
||||
const Actions = require('./core/Actions');
|
||||
const Livechat = require('./core/Livechat');
|
||||
const SessionBuilder = require('./core/SessionBuilder');
|
||||
|
||||
const Utils = require('./utils/Utils');
|
||||
const Request = require('./utils/Request');
|
||||
@@ -44,56 +44,41 @@ class Innertube {
|
||||
this.#retry_count = 0;
|
||||
return this.#init();
|
||||
}
|
||||
|
||||
|
||||
async #init() {
|
||||
const response = await Axios.get(Constants.URLS.YT_BASE, Constants.DEFAULT_HEADERS(this.config)).catch((error) => error);
|
||||
if (response instanceof Error) throw new Utils.InnertubeError('Could not retrieve Innertube session', { message: response.message, status_code: response.status || 0 });
|
||||
|
||||
const data = JSON.parse(`{${Utils.getStringBetweenStrings(response.data, 'ytcfg.set({', '});') || ''}}`);
|
||||
if (data.INNERTUBE_CONTEXT) {
|
||||
this.key = data.INNERTUBE_API_KEY;
|
||||
this.version = data.INNERTUBE_API_VERSION;
|
||||
this.context = data.INNERTUBE_CONTEXT;
|
||||
|
||||
this.player_url = data.PLAYER_JS_URL;
|
||||
this.logged_in = data.LOGGED_IN;
|
||||
this.sts = data.STS;
|
||||
|
||||
this.context.client.hl = 'en';
|
||||
this.context.client.gl = this.config.gl || 'US';
|
||||
|
||||
/**
|
||||
* @event Innertube#auth - fired when signing in to an account.
|
||||
* @event Innertube#update-credentials - fired when the access token is no longer valid.
|
||||
* @type {EventEmitter}
|
||||
*/
|
||||
this.ev = new EventEmitter();
|
||||
this.#oauth = new OAuth(this.ev);
|
||||
|
||||
this.#player = new Player(this);
|
||||
await this.#player.init();
|
||||
|
||||
if (this.logged_in && this.config.cookie) {
|
||||
this.auth_apisid = Utils.getStringBetweenStrings(this.config.cookie, 'PAPISID=', ';');
|
||||
this.auth_apisid = Utils.generateSidAuth(this.auth_apisid);
|
||||
}
|
||||
|
||||
this.request = new Request(this);
|
||||
this.actions = new Actions(this);
|
||||
|
||||
this.#initMethods();
|
||||
} else {
|
||||
this.#retry_count += 1;
|
||||
if (this.#retry_count >= 10)
|
||||
throw new Utils.ParsingError('No InnerTubeContext shell provided in ytconfig.', {
|
||||
data_snippet: response.data.slice(0, 300),
|
||||
status_code: response.status || 0
|
||||
});
|
||||
return this.#init();
|
||||
const session = await new SessionBuilder(this.config).build();
|
||||
|
||||
this.key = session.key;
|
||||
this.version = session.api_version;
|
||||
this.context = session.context;
|
||||
|
||||
this.logged_in = false;
|
||||
this.player_url = session.player.url;
|
||||
this.sts = session.player.sts;
|
||||
|
||||
this.#player = session.player;
|
||||
|
||||
/**
|
||||
* @fires Innertube#auth - fired when signing in to an account.
|
||||
* @fires Innertube#update-credentials - fired when the access token is no longer valid.
|
||||
* @type {EventEmitter}
|
||||
*/
|
||||
this.ev = new EventEmitter();
|
||||
this.#oauth = new OAuth(this.ev);
|
||||
|
||||
if (this.config.cookie) {
|
||||
this.auth_apisid = Utils.getStringBetweenStrings(this.config.cookie, 'PAPISID=', ';');
|
||||
this.auth_apisid = Utils.generateSidAuth(this.auth_apisid);
|
||||
}
|
||||
|
||||
this.request = new Request(this);
|
||||
this.actions = new Actions(this);
|
||||
|
||||
this.#initMethods();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
#initMethods() {
|
||||
this.account = {
|
||||
info: () => this.getAccountInfo(),
|
||||
@@ -705,7 +690,7 @@ class Innertube {
|
||||
format.url = format.url || format.signatureCipher || format.cipher;
|
||||
|
||||
if (format.signatureCipher || format.cipher) {
|
||||
format.url = new Signature(format.url, this.#player).decipher();
|
||||
format.url = new Signature(format.url, this.#player.signature_decipher).decipher();
|
||||
}
|
||||
|
||||
const url_components = new URL(format.url);
|
||||
@@ -713,7 +698,7 @@ class Innertube {
|
||||
url_components.searchParams.set('ratebypass', 'yes');
|
||||
|
||||
if (url_components.searchParams.get('n')) {
|
||||
url_components.searchParams.set('n', new NToken(this.#player.ntoken_sc, url_components.searchParams.get('n')).transform());
|
||||
url_components.searchParams.set('n', new NToken(this.#player.ntoken_decipher, url_components.searchParams.get('n')).transform());
|
||||
}
|
||||
|
||||
format.url = url_components.toString();
|
||||
@@ -796,7 +781,7 @@ class Innertube {
|
||||
let cancel;
|
||||
let cancelled = false;
|
||||
|
||||
const cpn = Utils.generateContentPlaybackNonce();
|
||||
const cpn = Utils.generateRandomString(16);
|
||||
|
||||
const stream = new Stream.PassThrough();
|
||||
this.actions.getVideoInfo(id, cpn).then(async (video_data) => {
|
||||
|
||||
@@ -197,7 +197,7 @@ class OAuth {
|
||||
const url_body = Constants.OAUTH.REGEX.AUTH_SCRIPT.exec(yttv_response.data)[1];
|
||||
const script_url = `${Constants.URLS.YT_BASE}/${url_body}`;
|
||||
|
||||
const response = await Axios.get(script_url, Constants.DEFAULT_HEADERS()).catch((error) => error);
|
||||
const response = await Axios.get(script_url).catch((error) => error);
|
||||
if (response instanceof Error) throw new Error(`Could not extract client identity: ${response.message}`);
|
||||
|
||||
const client_identity = response.data.replace(/\n/g, '').match(Constants.OAUTH.REGEX.CLIENT_IDENTITY);
|
||||
|
||||
119
lib/core/SessionBuilder.js
Normal file
119
lib/core/SessionBuilder.js
Normal file
@@ -0,0 +1,119 @@
|
||||
'use strict';
|
||||
|
||||
const Axios = require('axios');
|
||||
const Player = require('./Player');
|
||||
const Proto = require('../proto');
|
||||
const Utils = require('../utils/Utils');
|
||||
const Constants = require('../utils/Constants');
|
||||
const UserAgent = require('user-agents');
|
||||
|
||||
class SessionBuilder {
|
||||
#config;
|
||||
|
||||
#key;
|
||||
#client_name;
|
||||
#client_version;
|
||||
#api_version;
|
||||
#context;
|
||||
#player;
|
||||
|
||||
constructor(config) {
|
||||
this.#config = config;
|
||||
}
|
||||
|
||||
async build() {
|
||||
const data = await Promise.all([
|
||||
this.#getYtConfig(),
|
||||
this.#getPlayerId()
|
||||
]);
|
||||
|
||||
const ytcfg = data[0];
|
||||
|
||||
this.#key = ytcfg.INNERTUBE_API_KEY;
|
||||
this.#client_name = ytcfg.INNERTUBE_CLIENT_NAME;
|
||||
this.#client_version = ytcfg.INNERTUBE_CLIENT_VERSION;
|
||||
this.#api_version = ytcfg.INNERTUBE_API_VERSION;
|
||||
this.#player = await new Player(data[1]).init();
|
||||
|
||||
this.#context = this.#buildContext();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
#buildContext() {
|
||||
const user_agent = new UserAgent({ deviceCategory: 'desktop' });
|
||||
|
||||
const id = Utils.generateRandomString(11);
|
||||
const timestamp = new Date().getTime();
|
||||
|
||||
const visitor_data = Proto.encodeVisitorData(id, timestamp);
|
||||
|
||||
const context = {
|
||||
client: {
|
||||
hl: 'en',
|
||||
gl: this.#config.gl || 'US',
|
||||
deviceMake: user_agent.vendor,
|
||||
deviceModel: user_agent.platform,
|
||||
visitorData: visitor_data,
|
||||
userAgent: user_agent.toString(),
|
||||
clientName: this.#client_name,
|
||||
clientVersion: this.#client_version,
|
||||
originalUrl: Constants.URLS.YT_BASE
|
||||
},
|
||||
user: { lockedSafetyMode: false },
|
||||
request: { useSsl: true }
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
async #getYtConfig() {
|
||||
const response = await Axios.get(`${Constants.URLS.YT_BASE}/sw.js`).catch((err) => err);
|
||||
|
||||
if (response instanceof Error)
|
||||
throw new Utils.InnertubeError('Could not retrieve session data', {
|
||||
status_code: response?.response?.status || 0,
|
||||
message: response.message
|
||||
});
|
||||
|
||||
return JSON.parse(Utils.getStringBetweenStrings(response.data, 'ytcfg.set(', ')'));
|
||||
}
|
||||
|
||||
async #getPlayerId() {
|
||||
const response = await Axios.get(`${Constants.URLS.YT_BASE}/iframe_api`).catch((err) => err);
|
||||
|
||||
if (response instanceof Error)
|
||||
throw new Utils.InnertubeError('Could not retrieve js player id', {
|
||||
status_code: response?.response?.status || 0,
|
||||
message: response.message
|
||||
});
|
||||
|
||||
return Utils.getStringBetweenStrings(response.data, 'player\\/', '\\/');
|
||||
}
|
||||
|
||||
get key() {
|
||||
return this.#key;
|
||||
}
|
||||
|
||||
get context() {
|
||||
return this.#context;
|
||||
}
|
||||
|
||||
get api_version() {
|
||||
return this.#api_version;
|
||||
}
|
||||
|
||||
get client_version() {
|
||||
return this.#client_version;
|
||||
}
|
||||
|
||||
get client_name() {
|
||||
return this.#client_name;
|
||||
}
|
||||
|
||||
get player() {
|
||||
return this.#player;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SessionBuilder;
|
||||
@@ -77,4 +77,4 @@ class Request {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Request;
|
||||
module.exports = Request;
|
||||
@@ -86,11 +86,11 @@ function generateSidAuth(sid) {
|
||||
return ['SAPISIDHASH', [timestamp, gen_hash].join('_')].join(' ');
|
||||
}
|
||||
|
||||
function generateContentPlaybackNonce() {
|
||||
function generateRandomString(length) {
|
||||
const result = [];
|
||||
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
|
||||
|
||||
for (let i = 0; i < 16; i++) {
|
||||
for (let i = 0; i < length; i++) {
|
||||
result.push(alphabet.charAt(Math.floor(Math.random() * alphabet.length)));
|
||||
}
|
||||
|
||||
@@ -139,6 +139,6 @@ function refineNTokenData(data) {
|
||||
}
|
||||
|
||||
const errors = { UnavailableContentError, ParsingError, DownloadError, InnertubeError, MissingParamError, NoStreamingDataError };
|
||||
const functions = { findNode, getRandomUserAgent, generateSidAuth, generateContentPlaybackNonce, getStringBetweenStrings, camelToSnake, timeToSeconds, refineNTokenData };
|
||||
const functions = { findNode, getRandomUserAgent, generateSidAuth, generateRandomString, getStringBetweenStrings, camelToSnake, timeToSeconds, refineNTokenData };
|
||||
|
||||
module.exports = { ...functions, ...errors };
|
||||
Reference in New Issue
Block a user