mirror of
https://github.com/LuanRT/YouTube.js.git
synced 2026-07-03 09:35:05 +00:00
refactor: rewrite Comments Section logic (#88)
* feat: add core comments section classes * chore: update type declarations * chore: fix linter warnings * style: fix linter * chore: update tests * chore(tests): fix typo * chore(tests): fix typo x2 * fix(tests): `getReplies()` method is only present in `CommentThread` and not `Comment` * chore(tests): fix comment id path * chore(tests): remove outdated code * chore(tests): fix results path * chore: enforce code style * chore: update type declarations * docs: add examples and documentation * chore(docs): fix paths * chore(docs): fix more paths * chore(docs): fix `Comments.js` path * chore(docs): fix typo * chore(docs): mention example file * chore(examples): fix imports * chore(examples): fix typo
This commit is contained in:
@@ -15,7 +15,7 @@ const NTokenTransformOperation = exports.NTokenTransformOperation = {
|
||||
BASE64_DIA: 9,
|
||||
TRANSLATE_1: 10,
|
||||
TRANSLATE_2: 11
|
||||
}
|
||||
};
|
||||
|
||||
const NTokenTransformOpType = exports.NTokenTransformOpType = {
|
||||
FUNC: 0,
|
||||
@@ -54,7 +54,7 @@ class NTokenTransforms {
|
||||
token_chars.push(loc[index] = characters[(characters.indexOf(char) - characters.indexOf(token_chars[index]) + 64) % characters.length]);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
static translate2(arr, token, characters) {
|
||||
let chars_length = characters.length;
|
||||
const token_chars = token.split('');
|
||||
@@ -62,7 +62,7 @@ class NTokenTransforms {
|
||||
token_chars.push(loc[index] = characters[(characters.indexOf(char) - characters.indexOf(token_chars[index]) + index + chars_length--) % characters.length]);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the requested base64 dialect, currently this is only used by 'translate2'.
|
||||
*
|
||||
@@ -73,7 +73,7 @@ class NTokenTransforms {
|
||||
const characters = is_reverse_base64 ? BASE64_DIALECT.REVERSE : BASE64_DIALECT.NORMAL;
|
||||
return characters;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Swaps the first element with the one at the given index.
|
||||
*
|
||||
@@ -87,7 +87,7 @@ class NTokenTransforms {
|
||||
arr[0] = arr[index];
|
||||
arr[index] = old_elem;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Rotates elements of the array.
|
||||
*
|
||||
@@ -99,7 +99,7 @@ class NTokenTransforms {
|
||||
index = (index % arr.length + arr.length) % arr.length;
|
||||
arr.splice(-index).reverse().forEach((el) => arr.unshift(el));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deletes one element at the given index.
|
||||
*
|
||||
@@ -111,14 +111,14 @@ class NTokenTransforms {
|
||||
index = (index % arr.length + arr.length) % arr.length;
|
||||
arr.splice(index, 1);
|
||||
}
|
||||
|
||||
|
||||
static reverse(arr) {
|
||||
arr.reverse();
|
||||
}
|
||||
|
||||
|
||||
static push(arr, item) {
|
||||
if (Array.isArray(arr?.[0]))
|
||||
arr.push([NTokenTransformOpType.LITERAL, item]);
|
||||
arr.push([ NTokenTransformOpType.LITERAL, item ]);
|
||||
else
|
||||
arr.push(item);
|
||||
}
|
||||
@@ -126,18 +126,18 @@ class NTokenTransforms {
|
||||
|
||||
exports.NTokenTransforms = NTokenTransforms;
|
||||
|
||||
const TRANSFORM_FUNCTIONS = [{
|
||||
const TRANSFORM_FUNCTIONS = [ {
|
||||
[NTokenTransformOperation.PUSH]: NTokenTransforms.push,
|
||||
[NTokenTransformOperation.SPLICE]: NTokenTransforms.splice,
|
||||
[NTokenTransformOperation.SWAP0_1]: NTokenTransforms.swap0,
|
||||
[NTokenTransformOperation.SWAP0_2]: NTokenTransforms.swap0,
|
||||
[NTokenTransformOperation.ROTATE_1]: NTokenTransforms.rotate,
|
||||
[NTokenTransformOperation.ROTATE_2]: NTokenTransforms.rotate,
|
||||
[NTokenTransformOperation.ROTATE_2]: NTokenTransforms.rotate,
|
||||
[NTokenTransformOperation.REVERSE_1]: NTokenTransforms.reverse,
|
||||
[NTokenTransformOperation.REVERSE_2]: NTokenTransforms.reverse,
|
||||
[NTokenTransformOperation.BASE64_DIA]: () => NTokenTransforms.getBase64Dia(false),
|
||||
[NTokenTransformOperation.TRANSLATE_1]: (...args) => NTokenTransforms.translate1.apply(null, [...args, false]),
|
||||
[NTokenTransformOperation.TRANSLATE_2]: NTokenTransforms.translate2,
|
||||
[NTokenTransformOperation.TRANSLATE_1]: (...args) => NTokenTransforms.translate1.apply(null, [ ...args, false ]),
|
||||
[NTokenTransformOperation.TRANSLATE_2]: NTokenTransforms.translate2
|
||||
}, {
|
||||
[NTokenTransformOperation.PUSH]: NTokenTransforms.push,
|
||||
[NTokenTransformOperation.SPLICE]: NTokenTransforms.splice,
|
||||
@@ -148,50 +148,50 @@ const TRANSFORM_FUNCTIONS = [{
|
||||
[NTokenTransformOperation.REVERSE_1]: NTokenTransforms.reverse,
|
||||
[NTokenTransformOperation.REVERSE_2]: NTokenTransforms.reverse,
|
||||
[NTokenTransformOperation.BASE64_DIA]: () => NTokenTransforms.getBase64Dia(true),
|
||||
[NTokenTransformOperation.TRANSLATE_1]: (...args) => NTokenTransforms.translate1.apply(null, [...args, true]),
|
||||
[NTokenTransformOperation.TRANSLATE_2]: NTokenTransforms.translate2,
|
||||
}];
|
||||
[NTokenTransformOperation.TRANSLATE_1]: (...args) => NTokenTransforms.translate1.apply(null, [ ...args, true ]),
|
||||
[NTokenTransformOperation.TRANSLATE_2]: NTokenTransforms.translate2
|
||||
} ];
|
||||
|
||||
class NToken {
|
||||
constructor(transformer) {
|
||||
this.transformer = transformer;
|
||||
}
|
||||
|
||||
|
||||
static fromSourceCode(raw) {
|
||||
const transformation_data = NToken.getTransformationData(raw);
|
||||
|
||||
|
||||
const transformations = transformation_data.map((el) => {
|
||||
if (el != null && typeof el != 'number') {
|
||||
const is_reverse_base64 = el.includes('case 65:');
|
||||
let opcode = OP_LOOKUP[NToken.getFunc(el)?.[0]];
|
||||
const opcode = OP_LOOKUP[NToken.getFunc(el)?.[0]];
|
||||
if (opcode) {
|
||||
el = [
|
||||
NTokenTransformOpType.FUNC,
|
||||
opcode, 0 + is_reverse_base64
|
||||
];
|
||||
} else if (el == 'b') {
|
||||
el = [NTokenTransformOpType.N_ARR];
|
||||
el = [ NTokenTransformOpType.N_ARR ];
|
||||
} else {
|
||||
el = [NTokenTransformOpType.LITERAL, el ];
|
||||
el = [ NTokenTransformOpType.LITERAL, el ];
|
||||
}
|
||||
} else if (el != null) {
|
||||
el = [NTokenTransformOpType.LITERAL, el ];
|
||||
el = [ NTokenTransformOpType.LITERAL, el ];
|
||||
}
|
||||
|
||||
|
||||
return el;
|
||||
});
|
||||
|
||||
|
||||
// Fills all placeholders with the transformations array
|
||||
const placeholder_indexes = [...raw.matchAll(NTOKEN_REGEX.PLACEHOLDERS)].map((item) => parseInt(item[1]));
|
||||
placeholder_indexes.forEach((i) => transformations[i] = [NTokenTransformOpType.REF]);
|
||||
|
||||
const placeholder_indexes = [ ...raw.matchAll(NTOKEN_REGEX.PLACEHOLDERS) ].map((item) => parseInt(item[1]));
|
||||
placeholder_indexes.forEach((i) => transformations[i] = [ NTokenTransformOpType.REF ]);
|
||||
|
||||
// Parses and emulates calls to the functions of the transformations array
|
||||
const function_calls = [...(raw.replace(/\n/g, '').match(/try\{(.*?)\}catch/s)[1])
|
||||
.matchAll(NTOKEN_REGEX.CALLS)].map((params) => [
|
||||
parseInt(params[1]),
|
||||
params[2].split(',').map((param) => parseInt(param.match(/c\[(.*?)\]/)?.[1]))
|
||||
]);
|
||||
|
||||
const function_calls = [ ...(raw.replace(/\n/g, '').match(/try\{(.*?)\}catch/s)[1])
|
||||
.matchAll(NTOKEN_REGEX.CALLS) ].map((params) => [
|
||||
parseInt(params[1]),
|
||||
params[2].split(',').map((param) => parseInt(param.match(/c\[(.*?)\]/)?.[1]))
|
||||
]);
|
||||
|
||||
return new NToken([ transformations, function_calls ]);
|
||||
}
|
||||
|
||||
@@ -207,26 +207,26 @@ class NToken {
|
||||
return transformer[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
transform(n) {
|
||||
let n_token = n.split('');
|
||||
|
||||
const n_token = n.split('');
|
||||
|
||||
// We must copy since we will modify the array
|
||||
const transformer = this.getTransformerClone();
|
||||
|
||||
|
||||
try {
|
||||
transformer[1].forEach(([index, param_index]) => {
|
||||
transformer[1].forEach(([ index, param_index ]) => {
|
||||
const base64_dia = (param_index[2] && this.evaluate(transformer[0][param_index[2]], n_token, transformer)());
|
||||
this.evaluate(transformer[0][index], n_token, transformer)(
|
||||
param_index[0] !== undefined &&
|
||||
this.evaluate(transformer[0][param_index[0]], n_token, transformer),
|
||||
this.evaluate(transformer[0][param_index[0]], n_token, transformer),
|
||||
param_index[1] !== undefined &&
|
||||
this.evaluate(transformer[0][param_index[1]], n_token, transformer),
|
||||
base64_dia
|
||||
);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(new Error('Could not transform n-token, download may be throttled.\nOriginal Token:'+ n + 'Error:\n' + err));
|
||||
console.error(new Error(`Could not transform n-token, download may be throttled.\nOriginal Token:${n}Error:\n${err}`));
|
||||
return n;
|
||||
}
|
||||
|
||||
@@ -235,8 +235,8 @@ class NToken {
|
||||
|
||||
getTransformerClone() {
|
||||
return [
|
||||
[...this.transformer[0]],
|
||||
[...this.transformer[1]]
|
||||
[ ...this.transformer[0] ],
|
||||
[ ...this.transformer[1] ]
|
||||
];
|
||||
}
|
||||
|
||||
@@ -249,10 +249,10 @@ class NToken {
|
||||
// (8 bit N_ARG and REF) 2 bit op - 6 bit nonce
|
||||
// (40 bit LITERAL) 2 bit op - 6 bit nonce - 32 bit value
|
||||
// NTokenCall will be 8 bit for the index, 8 bit for the number of parameters, and 8 bit for each parameter
|
||||
// we've got a 3 * 32 bit header to store the library version and the size of the two arrays
|
||||
// We've got a 3 * 32 bit header to store the library version and the size of the two arrays
|
||||
|
||||
let size = 4 * 3;
|
||||
|
||||
|
||||
for (const instruction of this.transformer[0]) {
|
||||
switch (instruction[0]) {
|
||||
case NTokenTransformOpType.FUNC:
|
||||
@@ -297,14 +297,14 @@ class NToken {
|
||||
view.setUint8(offset, instruction[1]);
|
||||
offset += 1;
|
||||
}
|
||||
break;
|
||||
break;
|
||||
case NTokenTransformOpType.N_ARR:
|
||||
case NTokenTransformOpType.REF: {
|
||||
const opcode = (instruction[0] << 6);
|
||||
view.setUint8(offset, opcode);
|
||||
offset += 1;
|
||||
}
|
||||
break;
|
||||
break;
|
||||
case NTokenTransformOpType.LITERAL: {
|
||||
const type = typeof instruction[1] === 'string' ? 1 : 0;
|
||||
const opcode = (instruction[0] << 6) | type;
|
||||
@@ -325,7 +325,7 @@ class NToken {
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,7 +351,7 @@ class NToken {
|
||||
|
||||
const version = view.getUint32(offset, true);
|
||||
offset += 4;
|
||||
|
||||
|
||||
if (version !== NToken.LIBRARY_VERSION)
|
||||
throw new TypeError('Invalid library version');
|
||||
|
||||
@@ -366,26 +366,26 @@ class NToken {
|
||||
for (let i = 0; i < transformations_length; i++) {
|
||||
const opcode = view.getUint8(offset++);
|
||||
const op = opcode >> 6;
|
||||
|
||||
|
||||
switch (op) {
|
||||
case NTokenTransformOpType.FUNC: {
|
||||
const is_reverse_base64 = opcode & 0b00000001;
|
||||
const operation = view.getUint8(offset++);
|
||||
transformations[i] = [op, operation, is_reverse_base64];
|
||||
transformations[i] = [ op, operation, is_reverse_base64 ];
|
||||
}
|
||||
break;
|
||||
break;
|
||||
case NTokenTransformOpType.N_ARR:
|
||||
case NTokenTransformOpType.REF:
|
||||
transformations[i] = [op];
|
||||
transformations[i] = [ op ];
|
||||
break;
|
||||
case NTokenTransformOpType.LITERAL: {
|
||||
const type = opcode & 0b00000001;
|
||||
|
||||
|
||||
if (type === 0) {
|
||||
const literal = view.getInt32(offset, true);
|
||||
offset += 4;
|
||||
|
||||
transformations[i] = [op, literal];
|
||||
|
||||
transformations[i] = [ op, literal ];
|
||||
} else {
|
||||
const length = view.getUint32(offset, true);
|
||||
offset += 4;
|
||||
@@ -395,11 +395,11 @@ class NToken {
|
||||
for (let i = 0; i < length; i++) {
|
||||
literal[i] = view.getUint8(offset++);
|
||||
}
|
||||
|
||||
transformations[i] = [op, new TextDecoder().decode(literal)];
|
||||
|
||||
transformations[i] = [ op, new TextDecoder().decode(literal) ];
|
||||
}
|
||||
}
|
||||
break;
|
||||
break;
|
||||
default:
|
||||
throw new Error('Invalid opcode');
|
||||
}
|
||||
@@ -410,17 +410,17 @@ class NToken {
|
||||
for (let i = 0; i < function_calls_length; i++) {
|
||||
const index = view.getUint8(offset++);
|
||||
const num_params = view.getUint8(offset++);
|
||||
|
||||
|
||||
const params = new Array(num_params);
|
||||
|
||||
|
||||
for (let j = 0; j < num_params; j++) {
|
||||
params[j] = view.getUint8(offset++);
|
||||
}
|
||||
|
||||
function_calls[i] = [index, params];
|
||||
function_calls[i] = [ index, params ];
|
||||
}
|
||||
|
||||
return new NToken([transformations, function_calls]);
|
||||
return new NToken([ transformations, function_calls ]);
|
||||
}
|
||||
|
||||
static get LIBRARY_VERSION() {
|
||||
|
||||
@@ -12,41 +12,41 @@ class Signature {
|
||||
constructor(action_sequence) {
|
||||
this.action_sequence = action_sequence;
|
||||
}
|
||||
|
||||
|
||||
static fromSourceCode(sig_decipher_sc) {
|
||||
let actions;
|
||||
|
||||
|
||||
const action_sequence = [];
|
||||
const functions = Signature.getFunctions(sig_decipher_sc);
|
||||
|
||||
|
||||
while ((actions = SIG_REGEX.ACTIONS.exec(sig_decipher_sc)) !== null) {
|
||||
const action = actions.groups;
|
||||
if (!action) continue;
|
||||
|
||||
|
||||
switch (action.name) {
|
||||
case functions[0]:
|
||||
action_sequence.push([SignatureOperation.REVERSE, 0]);
|
||||
action_sequence.push([ SignatureOperation.REVERSE, 0 ]);
|
||||
break;
|
||||
case functions[1]:
|
||||
action_sequence.push([SignatureOperation.SPLICE, parseInt(action.param)]);
|
||||
action_sequence.push([ SignatureOperation.SPLICE, parseInt(action.param) ]);
|
||||
break;
|
||||
case functions[2]:
|
||||
action_sequence.push([SignatureOperation.SWAP, parseInt(action.param)]);
|
||||
action_sequence.push([ SignatureOperation.SWAP, parseInt(action.param) ]);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return new Signature(action_sequence);
|
||||
}
|
||||
|
||||
|
||||
decipher(url) {
|
||||
const args = new URLSearchParams(url);
|
||||
const signature = args.get('s')?.split('');
|
||||
|
||||
|
||||
if (!signature)
|
||||
throw new TypeError('Invalid signature');
|
||||
|
||||
|
||||
for (const action of this.action_sequence) {
|
||||
switch (action[0]) {
|
||||
case SignatureOperation.REVERSE:
|
||||
@@ -58,7 +58,7 @@ class Signature {
|
||||
case SignatureOperation.SWAP:
|
||||
{
|
||||
const index = action[1];
|
||||
let orig_arr = signature[0];
|
||||
const orig_arr = signature[0];
|
||||
signature[0] = signature[index % signature.length];
|
||||
signature[index % signature.length] = orig_arr;
|
||||
}
|
||||
@@ -67,53 +67,53 @@ class Signature {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return signature.join('');
|
||||
}
|
||||
|
||||
|
||||
toJSON() {
|
||||
return [...this.action_sequence];
|
||||
return [ ...this.action_sequence ];
|
||||
}
|
||||
|
||||
|
||||
toArrayBuffer() {
|
||||
// Array buffer encoding assumes that the index of the action is a short (16 bit unsigned)
|
||||
const buffer = new ArrayBuffer(4 + 4 + this.action_sequence.length * (1 + 2));
|
||||
const view = new DataView(buffer);
|
||||
|
||||
|
||||
let offset = 0;
|
||||
|
||||
|
||||
view.setUint32(offset, Signature.LIBRARY_VERSION, true);
|
||||
offset += 4;
|
||||
|
||||
|
||||
view.setUint32(offset, this.action_sequence.length, true);
|
||||
offset += 4;
|
||||
|
||||
|
||||
for (let i = 0; i < this.action_sequence.length; i++) {
|
||||
view.setUint8(offset, this.action_sequence[i][0]);
|
||||
offset += 1;
|
||||
|
||||
|
||||
view.setUint16(offset, this.action_sequence[i][1], true);
|
||||
offset += 2;
|
||||
}
|
||||
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
static fromArrayBuffer(buffer) {
|
||||
const view = new DataView(buffer);
|
||||
let offset = 0;
|
||||
|
||||
|
||||
const version = view.getUint32(offset, true);
|
||||
offset += 4;
|
||||
|
||||
|
||||
if (version !== Signature.LIBRARY_VERSION)
|
||||
throw new TypeError('Invalid library version');
|
||||
|
||||
|
||||
const action_sequence_length = view.getUint32(offset, true);
|
||||
offset += 4;
|
||||
|
||||
|
||||
const action_sequence = new Array(action_sequence_length);
|
||||
|
||||
|
||||
for (let i = 0; i < action_sequence_length; i++) {
|
||||
action_sequence[i] = [
|
||||
view.getUint8(offset),
|
||||
@@ -121,21 +121,21 @@ class Signature {
|
||||
];
|
||||
offset += 3;
|
||||
}
|
||||
|
||||
|
||||
return new Signature(action_sequence);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extracts the functions used to modify the signature
|
||||
* and returns them in the correct order.
|
||||
*
|
||||
*
|
||||
* @param {string} sc
|
||||
* @returns {Array.<string>}
|
||||
*/
|
||||
static getFunctions(sc) {
|
||||
let func;
|
||||
let functions = [];
|
||||
|
||||
const functions = [];
|
||||
|
||||
while ((func = SIG_REGEX.FUNCTIONS.exec(sc)) !== null) {
|
||||
if (func[0].includes('reverse')) {
|
||||
functions[0] = func[1];
|
||||
@@ -145,13 +145,13 @@ class Signature {
|
||||
functions[2] = func[1];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return functions;
|
||||
}
|
||||
|
||||
|
||||
static get LIBRARY_VERSION() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
exports.default = Signature;
|
||||
Reference in New Issue
Block a user