6 Commits
0.4.0 ... 0.5.0

Author SHA1 Message Date
Simon Sawicki
5bc9811c7a Fix sig solving for tce and es6 player variants (#47) 2026-02-21 17:32:45 +01:00
toddynnn
1b648c34c1 Fix sig extraction in main variant of player 4e51e895 (#48) 2026-02-16 23:27:40 +01:00
Simon Sawicki
d13ca53401 Simplify package scripts 2026-02-08 21:58:03 +01:00
Simon Sawicki
a3095891a9 Use parallel testing in CI for deno 2026-02-08 21:58:03 +01:00
Simon Sawicki
c51d14fa61 Implement extract.ts helper for quick manual testing 2026-02-08 21:58:03 +01:00
Simon Sawicki
96c417f90a More robust solver extraction
- Do not hard fail if a single extraction fails
- Do not fail if multiple solutions exist but they are the same
2026-02-08 21:58:03 +01:00
9 changed files with 753 additions and 184 deletions

View File

@@ -380,9 +380,11 @@ jobs:
name: player-js
- name: Run Deno tests
run: |
deno test \
xargs -n 1 -P 10 deno test \
--no-prompt \
--allow-read=src/yt/solver/test/players/
--no-check \
--allow-read=src/yt/solver/test/players/ \
--filter <<<"$(printf -- '-%s-\n' main tcc tce es5 es6 tv tv_es6 phone es6_tcc es6_tce)"
bun_build:
name: Test Bun build

View File

@@ -3,8 +3,9 @@
"type": "module",
"scripts": {
"bundle": "rollup -c",
"fmt": "prettier --write \"src/**.ts\" \"package.json\" \"rollup.config.js\" \"run.ts\" \"eslint.config.js\"",
"fmt:check": "prettier --check \"src/**.ts\" \"package.json\" \"rollup.config.js\" \"run.ts\" \"eslint.config.js\"",
"prettier": "prettier",
"fmt": "prettier --write \"**/*.[jt]s\" \"package.json\"",
"fmt:check": "prettier --check \"**/*.[jt]s\" \"package.json\"",
"lint": "eslint src"
},
"dependencies": {

24
src/yt/solver/extract.ts Normal file
View File

@@ -0,0 +1,24 @@
import { parse } from "meriyah";
import { getIO } from "./test/io.ts";
import { downloadCached } from "./test/utils.ts";
import { argv } from "node:process";
import { getSolutions, modifyPlayer } from "./solvers.ts";
import { generate } from "astring";
const data = await (
argv.length > 3
? () => downloadCached(argv[2], argv[3])
: async () => {
const io = await getIO();
return await io.read(argv[2]);
}
)();
const program = parse(data);
const statements = modifyPlayer(program);
const solutionMap = getSolutions(statements);
for (const solutions of Object.values(solutionMap)) {
for (const solution of solutions) {
console.log(String.raw`${generate(solution)}`);
}
}

View File

@@ -2,33 +2,34 @@ import { type ESTree } from "meriyah";
import { matchesStructure } from "../../utils.ts";
import { type DeepPartial } from "../../types.ts";
const nsigExpression: DeepPartial<ESTree.Statement> = {
type: "VariableDeclaration",
kind: "var",
declarations: [
const nsig: DeepPartial<ESTree.CallExpression> = {
type: "CallExpression",
callee: {
or: [{ type: "Identifier" }, { type: "SequenceExpression" }],
},
arguments: [
{},
{
type: "VariableDeclarator",
init: {
type: "CallExpression",
callee: {
type: "Identifier",
},
arguments: [
{
type: "Literal",
},
{
type: "CallExpression",
callee: {
type: "Identifier",
name: "decodeURIComponent",
},
},
],
type: "CallExpression",
callee: {
type: "Identifier",
name: "decodeURIComponent",
},
arguments: [{}],
},
],
};
const nsigAssignment: DeepPartial<ESTree.AssignmentExpression> = {
type: "AssignmentExpression",
left: { type: "Identifier" },
operator: "=",
right: nsig,
};
const nsigDeclarator: DeepPartial<ESTree.VariableDeclarator> = {
type: "VariableDeclarator",
id: { type: "Identifier" },
init: nsig,
};
const logicalExpression: DeepPartial<ESTree.ExpressionStatement> = {
type: "ExpressionStatement",
@@ -53,6 +54,17 @@ const logicalExpression: DeepPartial<ESTree.ExpressionStatement> = {
},
arguments: {
or: [
[
{
type: "CallExpression",
callee: {
type: "Identifier",
name: "decodeURIComponent",
},
arguments: [{ type: "Identifier" }],
optional: false,
},
],
[
{ type: "Literal" },
{
@@ -66,6 +78,8 @@ const logicalExpression: DeepPartial<ESTree.ExpressionStatement> = {
},
],
[
{ type: "Literal" },
{ type: "Literal" },
{
type: "CallExpression",
callee: {
@@ -98,17 +112,15 @@ const identifier: DeepPartial<ESTree.Node> = {
type: "AssignmentExpression",
operator: "=",
left: {
type: "Identifier",
or: [{ type: "Identifier" }, { type: "MemberExpression" }],
},
right: {
type: "FunctionExpression",
params: [{}, {}, {}],
},
},
},
{
type: "FunctionDeclaration",
params: [{}, {}, {}],
},
{
type: "VariableDeclaration",
@@ -118,7 +130,6 @@ const identifier: DeepPartial<ESTree.Node> = {
type: "VariableDeclarator",
init: {
type: "FunctionExpression",
params: [{}, {}, {}],
},
},
],
@@ -130,103 +141,149 @@ const identifier: DeepPartial<ESTree.Node> = {
export function extract(
node: ESTree.Node,
): ESTree.ArrowFunctionExpression | null {
if (!matchesStructure(node, identifier)) {
return null;
}
let block: ESTree.BlockStatement | undefined | null;
if (
const blocks: ESTree.BlockStatement[] = [];
if (matchesStructure(node, identifier)) {
if (
node.type === "ExpressionStatement" &&
node.expression.type === "AssignmentExpression" &&
node.expression.right.type === "FunctionExpression" &&
node.expression.right.params.length >= 3
) {
blocks.push(node.expression.right.body!);
} else if (node.type === "VariableDeclaration") {
for (const decl of node.declarations) {
if (
decl.init?.type === "FunctionExpression" &&
decl.init.params.length >= 3
) {
blocks.push(decl.init.body!);
}
}
} else if (node.type === "FunctionDeclaration" && node.params.length >= 3) {
blocks.push(node.body!);
} else {
return null;
}
} else if (
node.type === "ExpressionStatement" &&
node.expression.type === "AssignmentExpression" &&
node.expression.right.type === "FunctionExpression"
node.expression.type === "SequenceExpression"
) {
block = node.expression.right.body;
} else if (node.type === "VariableDeclaration") {
for (const decl of node.declarations) {
for (const expr of node.expression.expressions) {
if (
decl.type === "VariableDeclarator" &&
decl.init?.type === "FunctionExpression" &&
decl.init?.params.length === 3
expr.type === "AssignmentExpression" &&
expr.right.type === "FunctionExpression" &&
expr.right.params.length === 3
) {
block = decl.init.body;
break;
blocks.push(expr.right.body as ESTree.BlockStatement);
}
}
} else if (node.type === "FunctionDeclaration") {
block = node.body;
} else {
return null;
}
const relevantExpression = block?.body.at(-2);
let call: ESTree.CallExpression | null = null;
if (matchesStructure(relevantExpression!, logicalExpression)) {
if (
relevantExpression?.type !== "ExpressionStatement" ||
relevantExpression.expression.type !== "LogicalExpression" ||
relevantExpression.expression.right.type !== "SequenceExpression" ||
relevantExpression.expression.right.expressions[0].type !==
"AssignmentExpression" ||
relevantExpression.expression.right.expressions[0].right.type !==
"CallExpression"
) {
return null;
}
call = relevantExpression.expression.right.expressions[0].right;
} else if (
relevantExpression?.type === "IfStatement" &&
relevantExpression.consequent.type === "BlockStatement"
) {
for (const n of relevantExpression.consequent.body) {
if (!matchesStructure(n, nsigExpression)) {
continue;
for (const block of blocks) {
let call: ESTree.CallExpression | null = null;
for (const stmt of block.body) {
if (matchesStructure(stmt, logicalExpression)) {
// legacy matching
if (
stmt.type === "ExpressionStatement" &&
stmt.expression.type === "LogicalExpression" &&
stmt.expression.right.type === "SequenceExpression" &&
stmt.expression.right.expressions[0].type ===
"AssignmentExpression" &&
stmt.expression.right.expressions[0].right.type === "CallExpression"
) {
call = stmt.expression.right.expressions[0].right;
}
} else if (stmt.type === "IfStatement") {
// if (...) { var a, b = (0, c)(1, decodeURIComponent(...))}
let consequent = stmt.consequent;
while (consequent.type === "LabeledStatement") {
consequent = consequent.body;
}
if (consequent.type !== "BlockStatement") {
continue;
}
for (const n of consequent.body) {
if (n.type !== "VariableDeclaration") {
continue;
}
for (const decl of n.declarations) {
if (
matchesStructure(decl, nsigDeclarator) &&
decl.init?.type === "CallExpression"
) {
call = decl.init;
break;
}
}
if (call) {
break;
}
}
} else if (stmt.type === "ExpressionStatement") {
// (...) && ((...), (c = (...)(decodeURIComponent(...))))
if (
stmt.expression.type !== "LogicalExpression" ||
stmt.expression.operator !== "&&" ||
stmt.expression.right.type !== "SequenceExpression"
) {
continue;
}
for (const expr of stmt.expression.right.expressions) {
if (matchesStructure(expr, nsigAssignment) && expr.type) {
if (
expr.type === "AssignmentExpression" &&
expr.right.type === "CallExpression"
) {
call = expr.right;
break;
}
}
}
}
if (
n.type !== "VariableDeclaration" ||
n.declarations[0].init?.type !== "CallExpression"
) {
continue;
if (call) {
break;
}
call = n.declarations[0].init;
break;
}
}
if (call === null) {
return null;
}
// TODO: verify identifiers here
return {
type: "ArrowFunctionExpression",
params: [
{
type: "Identifier",
name: "sig",
if (!call) {
continue;
}
// TODO: verify identifiers here
return {
type: "ArrowFunctionExpression",
params: [
{
type: "Identifier",
name: "sig",
},
],
body: {
type: "CallExpression",
callee: call.callee,
arguments: call.arguments.map((arg): ESTree.Expression => {
if (
arg.type === "CallExpression" &&
arg.callee.type === "Identifier" &&
arg.callee.name === "decodeURIComponent"
) {
return { type: "Identifier", name: "sig" };
}
return arg as unknown as ESTree.Expression;
}),
optional: false,
},
],
body: {
type: "CallExpression",
callee: {
type: "Identifier",
name: call.callee.name,
},
arguments:
call.arguments.length === 1
? [
{
type: "Identifier",
name: "sig",
},
]
: [
call.arguments[0],
{
type: "Identifier",
name: "sig",
},
],
optional: false,
},
async: false,
expression: false,
generator: false,
};
async: false,
expression: false,
generator: false,
};
}
return null;
}

View File

@@ -8,7 +8,7 @@ const io = await getIO();
for (const test of tests) {
for (const variant of test.variants ?? players.keys()) {
const path = getCachePath(test.player, variant);
await io.test(`${test.player} ${variant}`, async (assert, subtest) => {
await io.test(`-${test.player}-${variant}-`, async (assert, subtest) => {
const content = await io.read(path);
const solvers = getFromPrepared(preprocessPlayer(content));
for (const mode of ["n", "sig"] as const) {

View File

@@ -5,10 +5,41 @@ import { extract as extractN } from "./n.ts";
import { setupNodes } from "./setup.ts";
export function preprocessPlayer(data: string): string {
const ast = parse(data);
const body = ast.body;
const program = parse(data);
const plainStatements = modifyPlayer(program);
const solutions = getSolutions(plainStatements);
for (const [name, options] of Object.entries(solutions)) {
plainStatements.push({
type: "ExpressionStatement",
expression: {
type: "AssignmentExpression",
operator: "=",
left: {
type: "MemberExpression",
computed: false,
object: {
type: "Identifier",
name: "_result",
},
property: {
type: "Identifier",
name: name,
},
optional: false,
},
right: multiTry(options),
},
});
}
const block = (() => {
program.body.splice(0, 0, ...setupNodes);
return generate(program);
}
export function modifyPlayer(program: ESTree.Program) {
const body = program.body;
const block: ESTree.BlockStatement = (() => {
switch (body.length) {
case 1: {
const func = body[0];
@@ -40,19 +71,7 @@ export function preprocessPlayer(data: string): string {
throw "unexpected structure";
})();
const found = {
n: [] as ESTree.ArrowFunctionExpression[],
sig: [] as ESTree.ArrowFunctionExpression[],
};
const plainExpressions = block.body.filter((node: ESTree.Node) => {
const n = extractN(node);
if (n) {
found.n.push(n);
}
const sig = extractSig(node);
if (sig) {
found.sig.push(sig);
}
block.body = block.body.filter((node: ESTree.Statement) => {
if (node.type === "ExpressionStatement") {
if (node.expression.type === "AssignmentExpression") {
return true;
@@ -61,43 +80,28 @@ export function preprocessPlayer(data: string): string {
}
return true;
});
block.body = plainExpressions;
for (const [name, options] of Object.entries(found)) {
// 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)).join(", ")}` : "")
);
return block.body;
}
export function getSolutions(
statements: ESTree.Statement[],
): Record<string, ESTree.ArrowFunctionExpression[]> {
const found = {
n: [] as ESTree.ArrowFunctionExpression[],
sig: [] as ESTree.ArrowFunctionExpression[],
};
for (const statement of statements) {
const n = extractN(statement);
if (n) {
found.n.push(n);
}
const sig = extractSig(statement);
if (sig) {
found.sig.push(sig);
}
plainExpressions.push({
type: "ExpressionStatement",
expression: {
type: "AssignmentExpression",
operator: "=",
left: {
type: "MemberExpression",
computed: false,
object: {
type: "Identifier",
name: "_result",
},
property: {
type: "Identifier",
name: name,
},
},
right: options[0],
},
});
}
ast.body.splice(0, 0, ...setupNodes);
return generate(ast);
return found;
}
export function getFromPrepared(code: string): {
@@ -108,3 +112,293 @@ export function getFromPrepared(code: string): {
Function("_result", code)(resultObj);
return resultObj;
}
function multiTry(
generators: ESTree.ArrowFunctionExpression[],
): ESTree.ArrowFunctionExpression {
return {
type: "ArrowFunctionExpression",
params: [
{
type: "Identifier",
name: "_input",
},
],
body: {
type: "BlockStatement",
body: [
{
type: "VariableDeclaration",
kind: "const",
declarations: [
{
type: "VariableDeclarator",
id: {
type: "Identifier",
name: "_results",
},
init: {
type: "NewExpression",
callee: {
type: "Identifier",
name: "Set",
},
arguments: [],
},
},
],
},
{
type: "ForOfStatement",
left: {
type: "VariableDeclaration",
kind: "const",
declarations: [
{
type: "VariableDeclarator",
id: {
type: "Identifier",
name: "_generator",
},
init: null,
},
],
},
right: {
type: "ArrayExpression",
elements: generators,
},
body: {
type: "BlockStatement",
body: [
{
type: "TryStatement",
block: {
type: "BlockStatement",
body: [
{
type: "ExpressionStatement",
expression: {
type: "CallExpression",
callee: {
type: "MemberExpression",
object: {
type: "Identifier",
name: "_results",
},
computed: false,
property: {
type: "Identifier",
name: "add",
},
optional: false,
},
arguments: [
{
type: "CallExpression",
callee: {
type: "Identifier",
name: "_generator",
},
arguments: [
{
type: "Identifier",
name: "_input",
},
],
optional: false,
},
],
optional: false,
},
},
],
},
handler: {
type: "CatchClause",
param: null,
body: {
type: "BlockStatement",
body: [],
},
},
finalizer: null,
},
],
},
await: false,
},
{
type: "IfStatement",
test: {
type: "UnaryExpression",
operator: "!",
argument: {
type: "MemberExpression",
object: {
type: "Identifier",
name: "_results",
},
computed: false,
property: {
type: "Identifier",
name: "size",
},
optional: false,
},
prefix: true,
},
consequent: {
type: "BlockStatement",
body: [
{
type: "ThrowStatement",
argument: {
type: "TemplateLiteral",
expressions: [],
quasis: [
{
type: "TemplateElement",
value: {
cooked: "no solutions",
raw: "no solutions",
},
tail: true,
},
],
},
},
],
},
alternate: null,
},
{
type: "IfStatement",
test: {
type: "BinaryExpression",
left: {
type: "MemberExpression",
object: {
type: "Identifier",
name: "_results",
},
computed: false,
property: {
type: "Identifier",
name: "size",
},
optional: false,
},
right: {
type: "Literal",
value: 1,
},
operator: "!==",
},
consequent: {
type: "BlockStatement",
body: [
{
type: "ThrowStatement",
argument: {
type: "TemplateLiteral",
expressions: [
{
type: "CallExpression",
callee: {
type: "MemberExpression",
object: {
type: "Identifier",
name: "_results",
},
computed: false,
property: {
type: "Identifier",
name: "join",
},
optional: false,
},
arguments: [
{
type: "Literal",
value: ", ",
},
],
optional: false,
},
],
quasis: [
{
type: "TemplateElement",
value: {
cooked: "invalid solutions: ",
raw: "invalid solutions: ",
},
tail: false,
},
{
type: "TemplateElement",
value: {
cooked: "",
raw: "",
},
tail: true,
},
],
},
},
],
},
alternate: null,
},
{
type: "ReturnStatement",
argument: {
type: "MemberExpression",
object: {
type: "CallExpression",
callee: {
type: "MemberExpression",
object: {
type: "CallExpression",
callee: {
type: "MemberExpression",
object: {
type: "Identifier",
name: "_results",
},
computed: false,
property: {
type: "Identifier",
name: "values",
},
optional: false,
},
arguments: [],
optional: false,
},
computed: false,
property: {
type: "Identifier",
name: "next",
},
optional: false,
},
arguments: [],
optional: false,
},
computed: false,
property: {
type: "Identifier",
name: "value",
},
optional: false,
},
},
],
},
async: false,
expression: false,
generator: false,
};
}

View File

@@ -1,24 +1,9 @@
import { players, tests } from "./tests.ts";
import { getCachePath } from "./utils.ts";
import { getIO } from "./io.ts";
const io = await getIO();
import { downloadCached } from "./utils.ts";
for (const test of tests) {
const variants = test.variants ?? players.keys();
for (const variant of variants) {
const path = getCachePath(test.player, variant);
if (await io.exists(path)) {
continue;
}
const playerPath = players.get(variant);
const url = `https://www.youtube.com/s/player/${test.player}/${playerPath}`;
console.log("Requesting", url);
const response = await fetch(url);
if (!response.ok) {
console.error(`Failed to request ${variant} player for ${test.player}`);
continue;
}
await io.write(path, response);
await downloadCached(test.player, variant);
}
}

View File

@@ -300,6 +300,189 @@ export const tests: {
},
],
},
{
player: "4e51e895",
variants: ["main"],
n: [
// Synthetic test
{ input: "0eRGgQWJGfT5rFHFj", expected: "t5kO23_msekBur" },
],
sig: [
{
// Synthetic test
input:
"AL6p_8AwdY9yAhRzK8rYA_9n97Kizf7_9n97Kizf7_9n97Kizf7_9n97Kizf7_9n97Kizf7_9n97Kizf7",
expected:
"AwdY9yAhRzK8rYA_9n97Kizf7_9n97Kizf7_9n9pKizf7_9n97Kizf7_9n97Kizf7_9n97Kizf7",
},
],
},
{
// sig: tce: deep if: multiple matching but giving same solution
player: "42c5570b",
n: [
// Synthetic test
{ input: "ZdZIqFPQK-Ty8wId", expected: "CRoXjB-R-R" },
],
sig: [
// Synthetic test
{
input:
"gN7a-hudCuAuPH6fByOk1_GNXN0yNMHShjZXS2VOgsEItAJz0tipeavEOmNdYN-wUtcEqD3bCXjc0iyKfAyZxCBGgIARwsSdQfJ2CJtt",
expected:
"EN7a-hudCuAuPH6fByOk1_GNXN0yNMHShjZXS2VOgsEItAJz0tipeavcOmNdYN-wUtgEqD3bCXjc0iyKfAyZxCBGgIARwsSdQfJ2CJtt",
},
],
},
{
// sig: tce: deep if
player: "ed3f6ea5",
n: [
// Synthetic test
{ input: "ZdZIqFPQK-Ty8wId", expected: "CRoXjB-R-R" },
],
sig: [
// Synthetic test
{
input:
"gN7a-hudCuAuPH6fByOk1_GNXN0yNMHShjZXS2VOgsEItAJz0tipeavEOmNdYN-wUtcEqD3bCXjc0iyKfAyZxCBGgIARwsSdQfJ2CJtt",
expected:
"EN7a-hudCuAuPH6fByOk1_GNXN0yNMHShjZXS2VOgsEItAJz0tipeavcOmNdYN-wUtgEqD3bCXjc0iyKfAyZxCBGgIARwsSdQfJ2CJtt",
},
],
},
{
// sig: tce: deep if: another, similar structure using && instead of if
player: "d6afc319",
n: [
// Synthetic test
{ input: "ZdZIqFPQK-Ty8wId", expected: "5RA1UjcYMe33HCQ" },
],
sig: [
// Synthetic test
{
input:
"gN7a-hudCuAuPH6fByOk1_GNXN0yNMHShjZXS2VOgsEItAJz0tipeavEOmNdYN-wUtcEqD3bCXjc0iyKfAyZxCBGgIARwsSdQfJ2CJtt",
expected:
"7a-hudCuAuPH6fByOk1_GNXN0yNMHShjZXt2VOgsEItAJz0tipeavEOmNdYN-wUtcEqD3bCXjc0iyKfAyZxCBGgIARwsSdQfJ2CJtS",
},
],
},
{
// sig: tce: deep if
player: "8da75a6a",
n: [
// Synthetic test
{ input: "ZdZIqFPQK-Ty8wId", expected: "Q3JvBQziA7PvI" },
],
sig: [
// Synthetic test
{
input:
"gN7a-hudCuAuPH6fByOk1_GNXN0yNMHShjZXS2VOgsEItAJz0tipeavEOmNdYN-wUtcEqD3bCXjc0iyKfAyZxCBGgIARwsSdQfJ2CJtt",
expected:
"g7aNhudCuAuPH6fByOk1_GNXN0yNMHShjZXS2VOgsEItAJz0tipeavEOmNdYN-wUtcEqD3bCXjc0iyKfAyJxCBGgIARwsSdQfJ2CZ",
},
],
},
{
// sig: tce: call with 3 parameters
player: "54bd1de4",
n: [
// Synthetic test
{ input: "ZdZIqFPQK-Ty8wId", expected: "ka-slAQ31sijFN" },
],
sig: [
// Synthetic test
{
input:
"gN7a-hudCuAuPH6fByOk1_GNXN0yNMHShjZXS2VOgsEItAJz0tipeavEOmNdYN-wUtcEqD3bCXjc0iyKfAyZxCBGgIARwsSdQfJ2CJtt",
expected:
"gN7a-hudCuAuPH6fByOk1_GNXN0yNMHShjZXS2VOgsEItAJz0titeavEOmNdYN-wUtcEqD3bCXjc0iyKfAyZxCBGgIARwsSdQfJ2CJtp",
},
],
},
{
// sig: tce: call with 3 parameters
player: "f104ea90",
n: [
// Synthetic test
{ input: "ZdZIqFPQK-Ty8wId", expected: "n5DnuOYzgrSUbWp" },
],
sig: [
// Synthetic test
{
input:
"gN7a-hudCuAuPH6fByOk1_GNXN0yNMHShjZXS2VOgsEItAJz0tipeavEOmNdYN-wUtcEqD3bCXjc0iyKfAyZxCBGgIARwsSdQfJ2CJtt",
expected:
"fJC2JtQdSswRAIgGBCxZyAfKyi0cjXCb3DqEctUw-NYdNmOEZaepit0z7AtIEsgOV2SX-jhSHMNy0NXNG_1kOyBf6HPuAuCduhv",
},
],
},
{
// sig: tce: call with 3 parameters
player: "3510b6ff",
n: [
// Synthetic test
{ input: "ZdZIqFPQK-Ty8wId", expected: "n5DnuOYzgrSUbWp" },
],
sig: [
// Synthetic test
{
input:
"gN7a-hudCuAuPH6fByOk1_GNXN0yNMHShjZXS2VOgsEItAJz0tipeavEOmNdYN-wUtcEqD3bCXjc0iyKfAyZxCBGgIARwsSdQfJ2CJtt",
expected:
"fJC2JtQdSswRAIgGBCxZyAfKyi0cjXCb3DqEctUw-NYdNmOEZaepit0z7AtIEsgOV2SX-jhSHMNy0NXNG_1kOyBf6HPuAuCduhv",
},
],
},
{
// sig: tce: call with 3 parameters
player: "0675bd00",
n: [
// Synthetic test
{ input: "ZdZIqFPQK-Ty8wId", expected: "n5DnuOYzgrSUbWp" },
],
sig: [
// Synthetic test
{
input:
"gN7a-hudCuAuPH6fByOk1_GNXN0yNMHShjZXS2VOgsEItAJz0tipeavEOmNdYN-wUtcEqD3bCXjc0iyKfAyZxCBGgIARwsSdQfJ2CJtt",
expected:
"fJC2JtQdSswRAIgGBCxZyAfKyi0cjXCb3DqEctUw-NYdNmOEZaepit0z7AtIEsgOV2SX-jhSHMNy0NXNG_1kOyBf6HPuAuCduhv",
},
],
},
{
// sig: tce: call with 3 parameters
player: "e0528946",
n: [
// Synthetic test
{ input: "ZdZIqFPQK-Ty8wId", expected: "cGKEGBME8PGi7z" },
],
sig: [
// Synthetic test
{
input:
"gN7a-hudCuAuPH6fByOk1_GNXN0yNMHShjZXS2VOgsEItAJz0tipeavEOmNdYN-wUtcEqD3bCXjc0iyKfAyZxCBGgIARwsSdQfJ2CJtt",
expected:
"7a-hudCuAuPH6fByOk1_GNXN0yNMgShjZXS2VOgsEItAJz0tipeavEOmNdYN-wUtcEqD3bCXjc0iyKfAyZxCBGgIARwsSdQfJ2C",
},
],
},
{
// sig: es6: call with 3 parameters
player: "94667337",
n: [{ input: "BQoJvGBkC2nj1ZZLK-", expected: "ib1ShEOGoFXIIw" }],
sig: [
{
input:
"NJAJEij0EwRgIhAI0KExTgjfPk-MPM9MAdzyyPRt=BM8-XO5tm5hlMCSVpAiEAv7eP3CURqZNSPow8BXXAoazVoXgeMP7gH9BdylHCwgw=gwzz",
expected:
"AJEij0EwRgIhAI0KExTgjfPk-MPM9MNdzyyPRtzBM8-XO5tm5hlMCSVpAiEAv7eP3CURqZNSPow8BXXAoazVoXgeMP7gH9BdylHCwgw=",
},
],
},
];
export const players = new Map([
@@ -311,6 +494,8 @@ export const players = new Map([
["tv", "tv-player-ias.vflset/tv-player-ias.js"],
["tv_es6", "tv-player-es6.vflset/tv-player-es6.js"],
["phone", "player-plasma-ias-phone-en_US.vflset/base.js"],
["es6_tcc", "player_es6_tcc.vflset/en_US/base.js"],
["es6_tce", "player_es6_tce.vflset/en_US/base.js"],
] as const);
export type Variant = typeof players extends Map<infer T, unknown> ? T : never;

View File

@@ -1,5 +1,26 @@
import { type Variant } from "./tests.ts";
import { getIO } from "./io.ts";
import { players, type Variant } from "./tests.ts";
export function getCachePath(player: string, variant: Variant) {
return `src/yt/solver/test/players/${player}-${variant}`;
}
export async function downloadCached(player: string, variant: string) {
const io = await getIO();
const playerPath = players.get(variant as Variant);
if (!playerPath) {
throw `Invalid player variant: ${variant}`;
}
const path = getCachePath(player, variant as Variant);
if (!(await io.exists(path))) {
const url = `https://www.youtube.com/s/player/${player}/${playerPath}`;
console.log("Requesting", url);
const response = await fetch(url);
if (!response.ok) {
throw `Failed to request ${variant} player for ${player}`;
}
await io.write(path, response);
}
return await io.read(path);
}