forked from Lesmiscore/bookish-octo-barnacle
-
Notifications
You must be signed in to change notification settings - Fork 1
/
decrypt.js
103 lines (95 loc) · 2.92 KB
/
decrypt.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
const axios = require("axios");
const ivm = require("isolated-vm")
const nParamFuncBody = [/[FUNCNAME]=(function\([a-zA-Z0-9$]+\)\{.+?return [a-zA-Z0-9$]+\.join\(['"]{2}\)\});?/ms];
class NDecryptError extends Error {
constructor(step, msg) {
super(msg);
this.step = step;
}
}
// https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_Expressions
function escapeRegExp(string) {
return string.replace(/[.*+?^=!:${}()|[\]\/\\]/g, '\\$&');
}
async function decryptNParam(playerJs, nValue) {
let fName, fIdx;
const nMatch = /\.get\("n"\)\)&&\(([a-zA-Z0-9$]+)=([a-zA-Z0-9$]+)(?:\[(\d+)\])?\(\1\)/.exec(playerJs);
if (nMatch) {
fName = nMatch[2];
fIdx = Number(nMatch[3]);
}
if (!fName) {
throw new NDecryptError("finding_fname", "Failed to find function name");
}
if (!isNaN(fIdx)) {
const lMatch = new RegExp(`var ${escapeRegExp(fName)}\\s*=\\s*\\[(.+?)\\];`).exec(playerJs);
if (!lMatch) {
throw new NDecryptError("complex_fname", `Failed to search variable name ${fName}`);
}
fName = lMatch[1].split(",")[fIdx];
}
let funcBody;
for (const re of nParamFuncBody) {
const matches = new RegExp(re.source.replace(/\[FUNCNAME\]/g, fName), re.flags).exec(playerJs);
if (matches) {
funcBody = matches[1];
break;
}
}
if (!funcBody) {
throw new NDecryptError("finding_funcbody", "Failed to find function body");
}
// build payload and evaluate it
const isolate = new ivm.Isolate();
const [context, script] = await Promise.all([
isolate.createContext(),
isolate.compileScript(`
(${funcBody})(${JSON.stringify(nValue)})
`)
])
try {
return [await script.run(context), `(${funcBody})(NNN)`];
} catch (e) {
e.step = "eval_n";
throw e;
}
}
module.exports = async (req, resp) => {
const { player, n, funcbody } = req.query;
const provideFuncBody = ["1", "true", "yes"].includes(funcbody);
let playerUrl = player;
if (!playerUrl.startsWith("https://") && !playerUrl.startsWith("http://")) {
// setting "player" parameter to js url is always recommended
playerUrl = `https://www.youtube.com/s/player/${playerUrl}/player_ias.vflset/en_US/base.js`;
}
let playerResponse;
try {
playerResponse = await axios(playerUrl, {
responseType: "text",
});
} catch (e) {
return resp.status(503).send({
status: "error",
step: "downloading_js",
data: e,
});
}
try {
const [decryptedN, body] = await decryptNParam(playerResponse.data, n);
// resp.setHeader("Cache-Control", "stale-while-revalidate=86400");
return resp.send({
status: "ok",
data: decryptedN,
body: provideFuncBody ? body : undefined,
param: provideFuncBody ? "NNN" : undefined,
});
} catch (e) {
console.error(e);
return resp.status(503).send({
status: "error",
step: e.step || "decrypting",
data: e,
});
}
};
module.exports.decryptNParam = decryptNParam;