mirror of
https://github.com/yt-dlp/ejs.git
synced 2026-06-28 09:06:34 +00:00
Support all player variants
This commit is contained in:
168
src/nsig.ts
168
src/nsig.ts
@@ -1,97 +1,119 @@
|
||||
import {
|
||||
type ArrowFunctionExpression,
|
||||
type Node,
|
||||
type BlockStatement,
|
||||
} from "npm:@babel/types@7.28.2";
|
||||
import { matchesStructure } from "./utils.ts";
|
||||
import { type DeepPartial } from "./types.ts";
|
||||
|
||||
const identifier: DeepPartial<Node> = {
|
||||
type: "AssignmentExpression",
|
||||
operator: "=",
|
||||
left: {
|
||||
type: "Identifier",
|
||||
},
|
||||
right: {
|
||||
type: "FunctionExpression",
|
||||
params: [{}],
|
||||
body: {
|
||||
type: "BlockStatement",
|
||||
body: [
|
||||
{
|
||||
type: "ReturnStatement",
|
||||
// {
|
||||
argument:
|
||||
// or: [
|
||||
{
|
||||
type: "CallExpression",
|
||||
callee: {
|
||||
type: "MemberExpression",
|
||||
object: {
|
||||
type: "Identifier",
|
||||
// XXX: get switch function identifier, use here
|
||||
// name: "Lb",
|
||||
},
|
||||
property: {
|
||||
type: "MemberExpression",
|
||||
object: {
|
||||
type: "Identifier",
|
||||
// XXX: get global string store identifier, use here
|
||||
// name: "Y",
|
||||
},
|
||||
property: {
|
||||
type: "NumericLiteral",
|
||||
},
|
||||
},
|
||||
},
|
||||
arguments: [
|
||||
{ type: "ThisExpression" },
|
||||
{ type: "NumericLiteral" },
|
||||
{
|
||||
type: "Identifier",
|
||||
// XXX: get parameter name, use here
|
||||
// name: "Y",
|
||||
},
|
||||
],
|
||||
},
|
||||
// XXX: possible to be a direct call, ignore that for now
|
||||
// {
|
||||
// type: "CallExpression",
|
||||
// callee: {
|
||||
// type: "Identifier",
|
||||
// // XXX: get global string store identifier, use here
|
||||
// // name: "Y",
|
||||
// },
|
||||
// arguments: [
|
||||
// { type: "NumericLiteral" },
|
||||
// {
|
||||
// type: "Identifier",
|
||||
// // XXX: get parameter name, use here
|
||||
// // name: "Y",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
type: "VariableDeclaration",
|
||||
declarations: [
|
||||
{
|
||||
type: "VariableDeclarator",
|
||||
id: {
|
||||
type: "Identifier",
|
||||
},
|
||||
init: {
|
||||
type: "ArrayExpression",
|
||||
elements: [
|
||||
{
|
||||
type: "Identifier",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
kind: "var",
|
||||
};
|
||||
const catchBlockBody = [
|
||||
{
|
||||
"type": "ReturnStatement",
|
||||
"argument": {
|
||||
"type": "BinaryExpression",
|
||||
"operator": "+",
|
||||
"left": {
|
||||
"type": "MemberExpression",
|
||||
"object": {
|
||||
"type": "Identifier",
|
||||
},
|
||||
],
|
||||
"property": {
|
||||
"type": "NumericLiteral",
|
||||
},
|
||||
},
|
||||
"right": {
|
||||
"type": "Identifier",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
] as const;
|
||||
|
||||
export function extract(node: Node): ArrowFunctionExpression | null {
|
||||
if (!matchesStructure(node, identifier)) {
|
||||
// Fallback search for try { } catch { return X[12] + Y }
|
||||
let name: string | undefined | null = null;
|
||||
let block: BlockStatement | null = null;
|
||||
switch (node.type) {
|
||||
case "ExpressionStatement": {
|
||||
if (node.expression.type === "AssignmentExpression" &&
|
||||
node.expression.left.type === "Identifier" &&
|
||||
node.expression.right.type === "FunctionExpression" &&
|
||||
node.expression.right.params.length === 1) {
|
||||
name = node.expression.left.name;
|
||||
block = node.expression.right.body;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "FunctionDeclaration": {
|
||||
if (node.params.length === 1) {
|
||||
name = node.id?.name;
|
||||
block = node.body;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!block || !name) {
|
||||
return null;
|
||||
}
|
||||
const tryNode = block.body.at(-2);
|
||||
if (
|
||||
tryNode?.type !== "TryStatement" ||
|
||||
tryNode.handler?.type !== "CatchClause"
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const catchBody = tryNode.handler!.body.body;
|
||||
if (matchesStructure(catchBody, catchBlockBody)) {
|
||||
return makeSolverFuncFromName(name);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (node.type !== "AssignmentExpression" || node.left.type !== "Identifier") {
|
||||
|
||||
if (node.type !== "VariableDeclaration") {
|
||||
return null;
|
||||
}
|
||||
// TODO: verify identifiers here
|
||||
const declaration = node.declarations[0];
|
||||
if (
|
||||
declaration.type !== "VariableDeclarator" || !declaration.init ||
|
||||
declaration.init.type !== "ArrayExpression" ||
|
||||
declaration.init.elements.length !== 1
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const [firstElement] = declaration.init.elements;
|
||||
if (!firstElement || firstElement.type !== "Identifier") {
|
||||
return null;
|
||||
}
|
||||
return makeSolverFuncFromName(firstElement.name);
|
||||
}
|
||||
|
||||
function makeSolverFuncFromName(name: string): ArrowFunctionExpression {
|
||||
return {
|
||||
type: "ArrowFunctionExpression",
|
||||
params: [
|
||||
{
|
||||
type: "Identifier",
|
||||
name: "nsig",
|
||||
name: "x",
|
||||
},
|
||||
],
|
||||
async: false,
|
||||
@@ -100,12 +122,12 @@ export function extract(node: Node): ArrowFunctionExpression | null {
|
||||
type: "CallExpression",
|
||||
callee: {
|
||||
type: "Identifier",
|
||||
name: node.left.name,
|
||||
name: name,
|
||||
},
|
||||
arguments: [
|
||||
{
|
||||
type: "Identifier",
|
||||
name: "nsig",
|
||||
name: "x",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
177
src/sig.ts
177
src/sig.ts
@@ -1,95 +1,121 @@
|
||||
import {
|
||||
type ArrowFunctionExpression,
|
||||
type FunctionExpression,
|
||||
type Expression,
|
||||
type Node,
|
||||
ExpressionStatement,
|
||||
} from "npm:@babel/types@7.28.2";
|
||||
import { matchesStructure } from "./utils.ts";
|
||||
import { type DeepPartial } from "./types.ts";
|
||||
|
||||
const identifier: DeepPartial<Node> = {
|
||||
type: "AssignmentExpression",
|
||||
operator: "=",
|
||||
left: {
|
||||
type: "Identifier",
|
||||
},
|
||||
right: {
|
||||
type: "FunctionExpression",
|
||||
params: [{}, {}, {}],
|
||||
body: {
|
||||
type: "BlockStatement",
|
||||
body: [
|
||||
{ type: "ExpressionStatement" },
|
||||
{ type: "ExpressionStatement" },
|
||||
{ type: "ExpressionStatement" },
|
||||
{ type: "ExpressionStatement" },
|
||||
{
|
||||
type: "ExpressionStatement",
|
||||
expression: {
|
||||
type: "LogicalExpression",
|
||||
left: {
|
||||
type: "Identifier",
|
||||
// name: "M",
|
||||
},
|
||||
operator: "&&",
|
||||
right: {
|
||||
type: "SequenceExpression",
|
||||
expressions: [
|
||||
{
|
||||
type: "AssignmentExpression",
|
||||
operator: "=",
|
||||
left: {
|
||||
const logicalExpression: DeepPartial<ExpressionStatement> = {
|
||||
type: "ExpressionStatement",
|
||||
expression: {
|
||||
type: "LogicalExpression",
|
||||
left: {
|
||||
type: "Identifier",
|
||||
},
|
||||
operator: "&&",
|
||||
right: {
|
||||
type: "SequenceExpression",
|
||||
expressions: [
|
||||
{
|
||||
type: "AssignmentExpression",
|
||||
operator: "=",
|
||||
left: {
|
||||
type: "Identifier",
|
||||
},
|
||||
right: {
|
||||
type: "CallExpression",
|
||||
callee: {
|
||||
type: "Identifier",
|
||||
},
|
||||
right: {
|
||||
type: "CallExpression",
|
||||
callee: {
|
||||
type: "Identifier",
|
||||
},
|
||||
arguments: [
|
||||
{ type: "NumericLiteral" },
|
||||
{
|
||||
type: "CallExpression",
|
||||
callee: {
|
||||
type: "Identifier",
|
||||
name: "decodeURIComponent",
|
||||
arguments: {
|
||||
or: [
|
||||
[
|
||||
{ type: "NumericLiteral" },
|
||||
{
|
||||
type: "CallExpression",
|
||||
callee: {
|
||||
type: "Identifier",
|
||||
name: "decodeURIComponent",
|
||||
},
|
||||
arguments: [{ type: "Identifier" }],
|
||||
},
|
||||
arguments: [{ type: "Identifier" }],
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
type: "CallExpression",
|
||||
callee: {
|
||||
type: "Identifier",
|
||||
name: "decodeURIComponent",
|
||||
},
|
||||
arguments: [{ type: "Identifier" }],
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "CallExpression",
|
||||
},
|
||||
],
|
||||
extra: {
|
||||
parenthesized: true,
|
||||
},
|
||||
{
|
||||
type: "CallExpression",
|
||||
},
|
||||
],
|
||||
extra: {
|
||||
parenthesized: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{ type: "ReturnStatement" },
|
||||
],
|
||||
}
|
||||
|
||||
const identifier = {
|
||||
or: [{
|
||||
type: "ExpressionStatement",
|
||||
expression: {
|
||||
type: "AssignmentExpression",
|
||||
operator: "=",
|
||||
left: {
|
||||
type: "Identifier",
|
||||
},
|
||||
right: {
|
||||
type: "FunctionExpression",
|
||||
params: [{}, {}, {}],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}, {
|
||||
type: "FunctionDeclaration",
|
||||
params: [{}, {}, {}],
|
||||
}],
|
||||
} as const;
|
||||
|
||||
export function extract(node: Node): ArrowFunctionExpression | null {
|
||||
if (!matchesStructure(node, identifier)) {
|
||||
|
||||
if (!matchesStructure(node, identifier as unknown as DeepPartial<Node>)) {
|
||||
return null;
|
||||
}
|
||||
const block = (node.type === "ExpressionStatement" &&
|
||||
node.expression.type === "AssignmentExpression" &&
|
||||
node.expression.right.type === "FunctionExpression")
|
||||
? node.expression.right.body
|
||||
: node.type === "FunctionDeclaration"
|
||||
? node.body
|
||||
: null;
|
||||
const relevantExpression = block?.body.at(-2);
|
||||
if (!matchesStructure(relevantExpression!, logicalExpression)) {
|
||||
return null;
|
||||
}
|
||||
// shut the type checker up
|
||||
if (
|
||||
node.type !== "AssignmentExpression" ||
|
||||
node.right.type !== "FunctionExpression" ||
|
||||
node.right.body.body[4].type !== "ExpressionStatement" ||
|
||||
node.right.body.body[4].expression.type !== "LogicalExpression" ||
|
||||
node.right.body.body[4].expression.right.type !== "SequenceExpression" ||
|
||||
node.right.body.body[4].expression.right.expressions[0].type !==
|
||||
relevantExpression?.type !== "ExpressionStatement" ||
|
||||
relevantExpression.expression.type !==
|
||||
"LogicalExpression" ||
|
||||
relevantExpression.expression.right.type !==
|
||||
"SequenceExpression" ||
|
||||
relevantExpression.expression.right.expressions[0].type !==
|
||||
"AssignmentExpression"
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const call = node.right.body.body[4].expression.right.expressions[0].right;
|
||||
const call = relevantExpression.expression.right.expressions[0].right;
|
||||
if (call.type !== "CallExpression" || call.callee.type !== "Identifier") {
|
||||
return null;
|
||||
}
|
||||
@@ -110,13 +136,20 @@ export function extract(node: Node): ArrowFunctionExpression | null {
|
||||
type: "Identifier",
|
||||
name: call.callee.name,
|
||||
},
|
||||
arguments: [
|
||||
call.arguments[0],
|
||||
{
|
||||
type: "Identifier",
|
||||
name: "sig",
|
||||
},
|
||||
],
|
||||
arguments: call.arguments.length === 1
|
||||
? [
|
||||
{
|
||||
type: "Identifier",
|
||||
name: "sig",
|
||||
},
|
||||
]
|
||||
: [
|
||||
call.arguments[0],
|
||||
{
|
||||
type: "Identifier",
|
||||
name: "sig",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,48 +19,71 @@ export function preprocessPlayer(data: string): string {
|
||||
attachComment: false,
|
||||
});
|
||||
const body = ast.program.body;
|
||||
if (body.length !== 2 || body[1].type !== "ExpressionStatement") {
|
||||
|
||||
const block = (() => {
|
||||
switch (body.length) {
|
||||
case 1: {
|
||||
const func = body[0];
|
||||
if (
|
||||
func?.type === "ExpressionStatement" &&
|
||||
func.expression.type === "CallExpression" &&
|
||||
func.expression.callee.type === "MemberExpression" &&
|
||||
func.expression.callee.object.type === "FunctionExpression"
|
||||
) {
|
||||
return func.expression.callee.object.body;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
const func = body[1];
|
||||
if (
|
||||
func?.type === "ExpressionStatement" &&
|
||||
func.expression.type === "CallExpression" &&
|
||||
func.expression.callee.type === "FunctionExpression"
|
||||
) {
|
||||
const block = func.expression.callee.body;
|
||||
// Skip `var window = this;`
|
||||
block.body.splice(0, 1);
|
||||
return block;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
throw "unexpected structure";
|
||||
}
|
||||
const func = body[1];
|
||||
if (
|
||||
func.expression.type !== "CallExpression" ||
|
||||
func.expression.callee.type !== "FunctionExpression"
|
||||
) {
|
||||
throw "unexpected structure";
|
||||
}
|
||||
})();
|
||||
|
||||
const found = {
|
||||
nsig: [] as ArrowFunctionExpression[],
|
||||
sig: [] as ArrowFunctionExpression[],
|
||||
};
|
||||
const plainExpressions = func.expression.callee.body.body.filter(
|
||||
(node, idx) => {
|
||||
if (idx === 0) {
|
||||
// Ignore `var window = this;`
|
||||
return false;
|
||||
}
|
||||
if (node.type === "ExpressionStatement") {
|
||||
if (node.expression.type === "AssignmentExpression") {
|
||||
const nsig = extractNsig(node.expression);
|
||||
if (nsig) {
|
||||
found.nsig.push(nsig);
|
||||
}
|
||||
const sig = extractSig(node.expression);
|
||||
if (sig) {
|
||||
found.sig.push(sig);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return node.expression.type === "StringLiteral";
|
||||
}
|
||||
return true;
|
||||
const plainExpressions = block.body.filter((node) => {
|
||||
const nsig = extractNsig(node);
|
||||
if (nsig) {
|
||||
found.nsig.push(nsig);
|
||||
}
|
||||
);
|
||||
func.expression.callee.body.body = plainExpressions;
|
||||
const sig = extractSig(node);
|
||||
if (sig) {
|
||||
found.sig.push(sig);
|
||||
}
|
||||
if (node.type === "ExpressionStatement") {
|
||||
if (node.expression.type === "AssignmentExpression") {
|
||||
return true;
|
||||
}
|
||||
return node.expression.type === "StringLiteral";
|
||||
}
|
||||
return true;
|
||||
});
|
||||
block.body = plainExpressions;
|
||||
|
||||
for (const [name, options] of Object.entries(found)) {
|
||||
if (options.length !== 1) {
|
||||
continue;
|
||||
// TODO: this is cringe fix plz
|
||||
const unique = new Set(options.map((x) => JSON.stringify(x)));
|
||||
if (unique.size !== 1) {
|
||||
const message = `found ${unique.size} ${name} function possibilities`;
|
||||
throw message +
|
||||
(unique.size
|
||||
? `: ${options.map((x) => generate(x)["code"]).join(", ")}`
|
||||
: "");
|
||||
}
|
||||
plainExpressions.push({
|
||||
type: "ExpressionStatement",
|
||||
|
||||
12
src/types.ts
12
src/types.ts
@@ -1,4 +1,8 @@
|
||||
export type DeepPartial<T> = T extends object ? {
|
||||
[P in keyof T]?: DeepPartial<T[P]>;
|
||||
}
|
||||
: T;
|
||||
export type DeepPartial<T> = T extends object ? Or<
|
||||
{
|
||||
[P in keyof T]?: DeepPartial<T[P]>;
|
||||
}
|
||||
>
|
||||
: Or<T>;
|
||||
|
||||
type Or<T> = T | { or: T[] };
|
||||
|
||||
@@ -5,7 +5,7 @@ import { type DeepPartial } from "./types.ts";
|
||||
export function matchesStructure<T extends Node>(
|
||||
obj: Node | Node[],
|
||||
structure: DeepPartial<T> | readonly DeepPartial<T>[],
|
||||
): obj is T {
|
||||
): boolean {
|
||||
if (Array.isArray(structure)) {
|
||||
if (!Array.isArray(obj)) {
|
||||
return false;
|
||||
@@ -20,10 +20,8 @@ export function matchesStructure<T extends Node>(
|
||||
return !structure;
|
||||
}
|
||||
if ("or" in structure) {
|
||||
// Allow `{ or: [a, b] }` so we can handle some special cases
|
||||
return (structure.or! as DeepPartial<Node>[]).some((node) =>
|
||||
matchesStructure(obj, node)
|
||||
);
|
||||
// Handle `{ or: [a, b] }`
|
||||
return structure.or.some((node) => matchesStructure(obj, node));
|
||||
}
|
||||
for (const [key, value] of Object.entries(structure)) {
|
||||
if (!matchesStructure(obj[key as keyof typeof obj], value)) {
|
||||
|
||||
@@ -11,7 +11,6 @@ export const tests: {
|
||||
}[] = [
|
||||
{
|
||||
player: "3d3ba064",
|
||||
variants: ["tce"],
|
||||
nsig: [
|
||||
{ input: "ZdZIqFPQK-Ty8wId", expected: "qmtUsIz04xxiNW" },
|
||||
{ input: "4GMrWHyKI5cEvhDO", expected: "N9gmEX7YhKTSmw" },
|
||||
@@ -27,7 +26,6 @@ export const tests: {
|
||||
},
|
||||
{
|
||||
player: "5ec65609",
|
||||
variants: ["tce"],
|
||||
nsig: [{ input: "0eRGgQWJGfT5rFHFj", expected: "4SvMpDQH-vBJCw" }],
|
||||
sig: [
|
||||
{
|
||||
@@ -40,7 +38,6 @@ export const tests: {
|
||||
},
|
||||
{
|
||||
player: "6742b2b9",
|
||||
variants: ["tce"],
|
||||
nsig: [
|
||||
{ input: "_HPB-7GFg1VTkn9u", expected: "qUAsPryAO_ByYg" },
|
||||
{ input: "K1t_fcB6phzuq2SF", expected: "Y7PcOt3VE62mog" },
|
||||
@@ -54,6 +51,22 @@ export const tests: {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
player: "23ccdd25",
|
||||
nsig: [
|
||||
// Synthetic test
|
||||
{ input: "0eRGgQWJGfT5rFHFj", expected: "orSsTqUaUO-j" },
|
||||
],
|
||||
sig: [
|
||||
// Synthetic test
|
||||
{
|
||||
input:
|
||||
"MMGZJMUucirzS_SnrSPYsc85CJNnTUi6GgR5NKn-znQEICACojE8MHS6S7uYq4TGjQX_D4aPk99hNU6wbTvorvVVMgIARwsSdQfJAA",
|
||||
expected:
|
||||
"ZJMUucirzS_SnrSPYsc85CJNnTUi6GgR5NKn-znQEICACojE8MHS6S7uYq4TGjQX_D4aPk99hAU6wbTvorvVVMgIARwsSdQfJAN",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const players = new Map([
|
||||
|
||||
Reference in New Issue
Block a user