Skip to content

Commit

Permalink
refactor(Player): Generate and parse player script's AST (#713)
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
LuanRT authored Aug 1, 2024
1 parent d85fbc5 commit 3b3cf1b
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 17 deletions.
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
},
"license": "MIT",
"dependencies": {
"jintr": "^2.0.0",
"jintr": "^2.1.1",
"tslib": "^2.5.0",
"undici": "^5.19.1"
},
Expand Down
13 changes: 4 additions & 9 deletions src/core/Player.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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 {
Expand Down
81 changes: 81 additions & 0 deletions src/utils/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
}
}
}
}

0 comments on commit 3b3cf1b

Please sign in to comment.