refactor: rewrite OAuth and Requester (#90)

* chore: update type declarations

* dev: refactor oauth & requester

* chore: tidy things up

* chore: remove unneeded check
This commit is contained in:
LuanRT
2022-07-04 16:34:02 -03:00
committed by GitHub
parent d7d6a4e019
commit 84d5edb6f0
21 changed files with 394 additions and 479 deletions

View File

@@ -20,18 +20,16 @@ module.exports = {
GRANT_TYPE: 'http://oauth.net/grant_type/device/1.0',
MODEL_NAME: 'ytlr::',
HEADERS: {
headers: {
'accept': '*/*',
'origin': 'https://www.youtube.com',
'user-agent': 'Mozilla/5.0 (ChromiumStylePlatform) Cobalt/Version',
'content-type': 'application/json',
'referer': 'https://www.youtube.com/tv',
'accept-language': 'en-US'
}
'accept': '*/*',
'origin': 'https://www.youtube.com',
'user-agent': 'Mozilla/5.0 (ChromiumStylePlatform) Cobalt/Version',
'content-type': 'application/json',
'referer': 'https://www.youtube.com/tv',
'accept-language': 'en-US'
},
REGEX: {
AUTH_SCRIPT: /<script id="base-js" src="(.*?)" nonce=".*?"><\/script>/,
CLIENT_IDENTITY: /.+?={};var .+?={clientId:"(?<id>.+?)",.+?:"(?<secret>.+?)"},/
CLIENT_IDENTITY: /.+?={};var .+?={clientId:"(?<client_id>.+?)",.+?:"(?<client_secret>.+?)"},/
}
},
CLIENTS: {

View File

@@ -1,105 +1,126 @@
'use strict';
const Axios = require('axios');
const Utils = require('./Utils');
const Constants = require('./Constants');
const Utils = require('./Utils');
/** @namespace */
class Request {
/**
* @param {import('../Innertube')} session
*/
constructor(session) {
this.session = session;
#instance;
#session;
this.instance = Axios.create({
...session.axios.defaults,
baseURL: Constants.URLS.API.PRODUCTION + session.version,
headers: Constants.INNERTUBE_HEADERS_BASE,
params: { key: session.key, prettyPrint: false },
constructor(config) {
this.config = config;
/** @type {Axios.AxiosInstance} */
this.#instance = Axios.create({
proxy: config.proxy,
httpAgent: config.http_agent,
httpsAgent: config.https_agent,
params: { prettyPrint: false },
headers: {
'accept': '*/*',
'accept-encoding': 'gzip, deflate',
'content-type': 'application/json',
'user-agent': Utils.getRandomUserAgent('desktop').userAgent
},
validateStatus: () => true,
timeout: 15000
});
this.#setupInterceptor();
return this.instance;
this.#setupRequestInterceptor();
this.#setupResponseInterceptor();
}
#setupInterceptor() {
this.instance.interceptors.request.use((config) => {
const is_json_payload = typeof config.data == 'object';
#setupRequestInterceptor() {
this.#instance.interceptors.request.use(async (config) => {
if (this.#session) {
const innertube_url = `${Constants.URLS.API.PRODUCTION}${this.#session.version}`;
config.headers['user-agent'] = Utils.getRandomUserAgent('desktop').userAgent;
config.headers['accept-language'] = `en-${this.session.config.gl || 'US'}`;
config.headers['x-goog-visitor-id'] = this.session.context.client.visitorData || '';
config.headers['x-youtube-client-version'] = this.session.context.client.clientVersion;
config.baseURL = config.baseURL || innertube_url;
if (is_json_payload) {
config.data = {
context: JSON.parse(JSON.stringify(this.session.context)), // Deep copies the context object
...config.data
};
config.headers['accept-language'] = `en-${this.#session.config.gl || 'US'}`;
config.headers['x-goog-visitor-id'] = this.#session.context.client.visitorData || '';
config.headers['x-youtube-client-version'] = this.#session.context.client.clientVersion;
config.headers['x-origin'] = new URL(config.baseURL).origin;
config.headers['origin'] = new URL(config.baseURL).origin;
this.#adjustContext(config.data.context, config.data.client);
config.params.key = this.#session.key;
config.headers['x-youtube-client-version'] = config.data.context.client.clientVersion;
config.headers['x-origin'] = config.data.context.client.originalUrl;
config.headers['origin'] = config.data.context.client.originalUrl;
const is_innertube_req = config.baseURL == innertube_url;
delete config.data.client;
// Copy the context into the payload.
if (is_innertube_req && typeof config.data === 'object') {
config.data = {
context: JSON.parse(JSON.stringify(this.#session.context)), // Deep copies the context object
...config.data
};
this.#adjustContext(config.data.context, config.data.client);
config.headers['x-youtube-client-version'] = config.data.context.client.clientVersion;
delete config.data.client;
}
// Check if authentication is possible.
if (this.#session.logged_in && is_innertube_req) {
const oauth = this.#session.oauth;
if (oauth.validateCredentials()) {
// Check if the access token is valid to avoid authorization errors.
await oauth.checkAccessTokenValidity();
config.headers.authorization = `Bearer ${oauth.credentials.access_token}`;
// Remove API key as it is not required when using oauth.
delete config.params.key;
}
if (this.config.cookie) {
const papisid = Utils.getStringBetweenStrings(this.config.cookie, 'PAPISID=', ';');
config.headers.authorization = Utils.generateSidAuth(papisid);
config.headers.cookie = this.config.cookie;
}
}
}
if (this.session.logged_in) {
const cookie = this.session.config.cookie;
const token = cookie &&
this.session.auth_apisid ||
this.session.access_token;
config.headers.cookie = cookie || '';
config.headers.authorization = cookie ? token : `Bearer ${token}`;
!cookie && (delete config.params.key);
if (this.config.debug) {
const url = `${(config.baseURL ? `${config.baseURL}` : '')}${config.url}`;
console.info('\n', `[${config.method.toUpperCase()}] > ${url}`, '\n', config?.data || 'N/A', '\n');
}
this.session.config.debug &&
console.info('\n', `[${config.method.toUpperCase()}]`, '>', config.baseURL + config.url, '\n', config?.data, '\n');
return config;
}, (error) => {
throw new Utils.InnertubeError(error.message, error);
});
}
/**
* Standardizes the API response and catches all errors.
*/
this.instance.interceptors.response.use((res) => {
const response = {
success: res.status === 200,
status_code: res.status,
data: res.data
};
#setupResponseInterceptor() {
this.#instance.interceptors.response.use(
(res) => {
const response = {
success: res.status === 200,
status_code: res.status,
data: res.data
};
if (res.status !== 200)
throw new Utils.InnertubeError(`Request to ${res.config.url} failed with status code ${res.status} ${res.statusText}`, response);
if (res.status !== 200)
throw new Utils.InnertubeError(`Request to ${res.config.url} failed with status code ${res.status}`, response);
return response;
});
return response;
});
this.instance.interceptors.response.use(undefined, (error) => {
if (error.info) return Promise.reject(error);
throw new Utils.InnertubeError('Could not complete this operation', error.message);
});
this.#instance.interceptors.response.use(undefined,
(error) => {
if (error.info) return Promise.reject(error);
throw new Utils.InnertubeError('Could not complete this operation', error.message);
});
}
/**
* Adjusts the context according to the given client.
*
* Adjusts the context object according to the given client.
* @param {object} ctx
* @param {string} client
* @todo refactor this?
* @returns {void}
*/
#adjustContext(ctx, client) {
switch (client) {
@@ -116,6 +137,18 @@ class Request {
break;
}
}
setSession(session) {
this.#session = session;
}
/**
* Returns the axios instance.
* @returns {Axios.AxiosInstance}
*/
get instance() {
return this.#instance;
}
}
module.exports = Request;

View File

@@ -22,6 +22,7 @@ class DownloadError extends InnertubeError {}
class MissingParamError extends InnertubeError {}
class UnavailableContentError extends InnertubeError {}
class NoStreamingDataError extends InnertubeError {}
class OAuthError extends InnertubeError {}
/**
* Utility to help access deep properties of an object.
@@ -274,7 +275,7 @@ function refineNTokenData(data) {
.replace(/""/g, '').replace(/length]\)}"/g, 'length])}');
}
const errors = { InnertubeError, UnavailableContentError, ParsingError, DownloadError, MissingParamError, NoStreamingDataError };
const errors = { InnertubeError, UnavailableContentError, ParsingError, DownloadError, MissingParamError, NoStreamingDataError, OAuthError };
const functions = {
findNode, observe, getRandomUserAgent, generateSidAuth, generateRandomString, getStringBetweenStrings,