From 210c5234b049afccc3a7d8d62a80ed200eac01fb Mon Sep 17 00:00:00 2001 From: Luan Date: Wed, 31 Jul 2024 07:49:47 -0300 Subject: [PATCH 1/2] refactor(Player): Generate and parse player script's `AST` 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). - `findFunction` expects the functions to be inside a self-invoking function. --- package-lock.json | 14 ++++---- package.json | 2 +- src/core/Player.ts | 14 +++++--- src/utils/Utils.ts | 81 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index f2a6e8e64..684d0c4da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" ], diff --git a/package.json b/package.json index 77ce78f9e..b5973e074 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ }, "license": "MIT", "dependencies": { - "jintr": "^2.0.0", + "jintr": "^2.1.1", "tslib": "^2.5.0", "undici": "^5.19.1" }, diff --git a/src/core/Player.ts b/src/core/Player.ts index 6016d6144..51304a586 100644 --- a/src/core/Player.ts +++ b/src/core/Player.ts @@ -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'; @@ -223,13 +223,19 @@ 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.'); + Log.error(TAG, 'Failed to extract nsig decipher algorithm using regex. Trying another method..'); + + const nsig_function = findFunction(data, { includes: 'enhanced_except' }); + + if (nsig_function) { + return `${nsig_function.result} ${nsig_function.name}(nsig);`; + } + return; } - return `function descramble_nsig(a) { let ${match[0]} descramble_nsig(nsig)`; + return `function descrambleNsig(a) { let ${match[0]} descrambleNsig(nsig)`; } get url(): string { diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index 02c86d30d..9b296333d 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -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; + 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); + } + } + } } \ No newline at end of file From 998095ac9db198da21beb73621fe0c22fd048bff Mon Sep 17 00:00:00 2001 From: Luan Date: Wed, 31 Jul 2024 17:49:09 -0300 Subject: [PATCH 2/2] refactor(Player): Remove regexp --- src/core/Player.ts | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/core/Player.ts b/src/core/Player.ts index 51304a586..dc6f4cf64 100644 --- a/src/core/Player.ts +++ b/src/core/Player.ts @@ -221,21 +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); - - if (!match) { - Log.error(TAG, 'Failed to extract nsig decipher algorithm using regex. Trying another method..'); - - const nsig_function = findFunction(data, { includes: 'enhanced_except' }); - - if (nsig_function) { - return `${nsig_function.result} ${nsig_function.name}(nsig);`; - } - - return; + const nsig_function = findFunction(data, { includes: 'enhanced_except' }); + if (nsig_function) { + return `${nsig_function.result} ${nsig_function.name}(nsig);`; } - - return `function descrambleNsig(a) { let ${match[0]} descrambleNsig(nsig)`; } get url(): string {