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:
Luan
2024-08-01 06:09:27 -03:00
committed by GitHub
parent d85fbc56cf
commit 3b3cf1b2aa
4 changed files with 93 additions and 17 deletions

14
package-lock.json generated
View File

@@ -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"
],

View File

@@ -103,7 +103,7 @@
},
"license": "MIT",
"dependencies": {
"jintr": "^2.0.0",
"jintr": "^2.1.1",
"tslib": "^2.5.0",
"undici": "^5.19.1"
},

View File

@@ -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 {

View File

@@ -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);
}
}
}
}