mirror of
https://github.com/LuanRT/YouTube.js.git
synced 2026-06-19 04:21:35 +00:00
refactor(Player): Generate and parse player script's AST (#713)
Notes: - The Syntax Tree is generated by Jinter (which is built on top of `Acorn`). - While doing this may be slightly slower than using a regular exp, it is much more reliable (plus we already cache the player functions anyway).
This commit is contained in:
14
package-lock.json
generated
14
package-lock.json
generated
@@ -12,7 +12,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jintr": "^2.0.0",
|
||||
"jintr": "^2.1.1",
|
||||
"tslib": "^2.5.0",
|
||||
"undici": "^5.19.1"
|
||||
},
|
||||
@@ -2178,9 +2178,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.11.3",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
|
||||
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
|
||||
"version": "8.12.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
|
||||
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -5829,9 +5829,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jintr": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jintr/-/jintr-2.0.0.tgz",
|
||||
"integrity": "sha512-RiVlevxttZ4eHEYB2dXKXDXluzHfRuw0DJQGsYuKCc5IvZj5/GbOakeqVX+Bar/G9kTty9xDJREcxukurkmYLA==",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jintr/-/jintr-2.1.1.tgz",
|
||||
"integrity": "sha512-89cwX4ouogeDGOBsEVsVYsnWWvWjchmwXBB4kiBhmjOKw19FiOKhNhMhpxhTlK2ctl7DS+d/ethfmuBpzoNNgA==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/LuanRT"
|
||||
],
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jintr": "^2.0.0",
|
||||
"jintr": "^2.1.1",
|
||||
"tslib": "^2.5.0",
|
||||
"undici": "^5.19.1"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Log, LZW, Constants } from '../utils/index.js';
|
||||
import { Platform, getRandomUserAgent, getStringBetweenStrings, PlayerError } from '../utils/Utils.js';
|
||||
import { Platform, getRandomUserAgent, getStringBetweenStrings, findFunction, PlayerError } from '../utils/Utils.js';
|
||||
import type { ICache, FetchFunction } from '../types/index.js';
|
||||
|
||||
const TAG = 'Player';
|
||||
@@ -221,15 +221,10 @@ export default class Player {
|
||||
}
|
||||
|
||||
static extractNSigSourceCode(data: string): string | undefined {
|
||||
const match = data.match(/b=(?:a\.split\(|String\.prototype\.split\.call\(a,)\n?(?:""|\("",""\))\).*?\}return (?:b\.join\(|Array\.prototype\.join\.call\(b,)\n?(?:""|\("",""\))\)\}/s);
|
||||
|
||||
// Don't throw an error here.
|
||||
if (!match) {
|
||||
Log.warn(TAG, 'Failed to extract nsig decipher algorithm.');
|
||||
return;
|
||||
const nsig_function = findFunction(data, { includes: 'enhanced_except' });
|
||||
if (nsig_function) {
|
||||
return `${nsig_function.result} ${nsig_function.name}(nsig);`;
|
||||
}
|
||||
|
||||
return `function descramble_nsig(a) { let ${match[0]} descramble_nsig(nsig)`;
|
||||
}
|
||||
|
||||
get url(): string {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Memo } from '../parser/helpers.js';
|
||||
import { Text } from '../parser/misc.js';
|
||||
import Log from './Log.js';
|
||||
import userAgents from './user-agents.js';
|
||||
import { Jinter } from 'jintr';
|
||||
|
||||
import type { EmojiRun, TextRun } from '../parser/misc.js';
|
||||
import type { FetchFunction } from '../types/PlatformShim.js';
|
||||
@@ -245,4 +246,84 @@ export function getCookie(cookies: string, name: string, matchWholeName = false)
|
||||
const regex = matchWholeName ? `(^|\\s?)\\b${name}\\b=([^;]+)` : `(^|s?)${name}=([^;]+)`;
|
||||
const match = cookies.match(new RegExp(regex));
|
||||
return match ? match[2] : undefined;
|
||||
}
|
||||
|
||||
export type FindFunctionArgs = {
|
||||
/**
|
||||
* The name of the function.
|
||||
*/
|
||||
name?: string;
|
||||
|
||||
/**
|
||||
* A string that must be included in the function's code for it to be considered.
|
||||
*/
|
||||
includes?: string;
|
||||
|
||||
/**
|
||||
* A regular expression that the function's code must match.
|
||||
*/
|
||||
regexp?: RegExp;
|
||||
};
|
||||
|
||||
export type FindFunctionResult = {
|
||||
start: number;
|
||||
end: number;
|
||||
name: string;
|
||||
node: Record<string, any>;
|
||||
result: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds a function in a source string based on the provided search criteria.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const source = '(function() {var foo, bar; foo = function() { console.log("foo"); }; bar = function() { console.log("bar"); }; })();';
|
||||
* const result = findFunction(source, { name: 'bar' });
|
||||
* console.log(result);
|
||||
* // Output: { start: 69, end: 110, name: 'bar', node: { ... }, result: 'bar = function() { console.log("bar"); };' }
|
||||
* ```
|
||||
*/
|
||||
export function findFunction(source: string, args: FindFunctionArgs): FindFunctionResult | undefined {
|
||||
const { name, includes, regexp } = args;
|
||||
|
||||
const node = Jinter.parseScript(source);
|
||||
const stack = [ node ];
|
||||
|
||||
for (let i = 0; i < stack.length; i++) {
|
||||
const current = stack[i];
|
||||
|
||||
if (
|
||||
current.type === 'ExpressionStatement' && (
|
||||
current.expression.type === 'AssignmentExpression' &&
|
||||
current.expression.left.type === 'Identifier' &&
|
||||
current.expression.right.type === 'FunctionExpression'
|
||||
)
|
||||
) {
|
||||
const code = source.substring(current.start, current.end);
|
||||
|
||||
if (
|
||||
(name && current.expression.left.name === name) ||
|
||||
(includes && code.indexOf(includes) > -1) ||
|
||||
(regexp && regexp.test(code))
|
||||
) {
|
||||
return {
|
||||
start: current.start,
|
||||
end: current.end,
|
||||
name: current.expression.left.name,
|
||||
node: current,
|
||||
result: code
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
for (const key in current) {
|
||||
const child = current[key];
|
||||
if (Array.isArray(child)) {
|
||||
stack.push(...child);
|
||||
} else if (typeof child === 'object' && child !== null) {
|
||||
stack.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user