@bililive-tools/douyin-recorder
Version:
@bililive-tools douyin recorder implemention
296 lines (295 loc) • 12.9 kB
JavaScript
// abogus.ts
// 这是对 Rust 版 ABogus 的 TypeScript 实现
// 代码来自:https://github.com/hua0512/rust-srec/blob/6444641014ea58628af9b0fa51b099620a01d0d0/crates/platforms/src/extractor/platforms/douyin/abogus.rs
// 依赖 sm3、rc4、random 等库
import { sm3 } from "sm-crypto"; // npm install sm-crypto
export class StringProcessor {
static toCharStr(bytes) {
return Array.from(bytes)
.map((b) => String.fromCharCode(b))
.join("");
}
static toCharArray(s) {
return Array.from(s).map((c) => c.charCodeAt(0));
}
static generateRandomBytes(length) {
const result = [];
for (let i = 0; i < length; i++) {
const rd = Math.floor(Math.random() * 10000);
result.push((rd & 255 & 170) | 1);
result.push((rd & 255 & 85) | 2);
result.push(((rd >> 8) & 170) | 5);
result.push(((rd >> 8) & 85) | 40);
}
return this.toCharStr(Uint8Array.from(result));
}
}
export class CryptoUtility {
salt;
base64Alphabet;
bigArray;
constructor(salt, customBase64Alphabet) {
this.salt = salt;
this.base64Alphabet = customBase64Alphabet.map((s) => Array.from(s));
this.bigArray = [
121, 243, 55, 234, 103, 36, 47, 228, 30, 231, 106, 6, 115, 95, 78, 101, 250, 207, 198, 50,
139, 227, 220, 105, 97, 143, 34, 28, 194, 215, 18, 100, 159, 160, 43, 8, 169, 217, 180, 120,
247, 45, 90, 11, 27, 197, 46, 3, 84, 72, 5, 68, 62, 56, 221, 75, 144, 79, 73, 161, 178, 81,
64, 187, 134, 117, 186, 118, 16, 241, 130, 71, 89, 147, 122, 129, 65, 40, 88, 150, 110, 219,
199, 255, 181, 254, 48, 4, 195, 248, 208, 32, 116, 167, 69, 201, 17, 124, 125, 104, 96, 83,
80, 127, 236, 108, 154, 126, 204, 15, 20, 135, 112, 158, 13, 1, 188, 164, 210, 237, 222, 98,
212, 77, 253, 42, 170, 202, 26, 22, 29, 182, 251, 10, 173, 152, 58, 138, 54, 141, 185, 33,
157, 31, 252, 132, 233, 235, 102, 196, 191, 223, 240, 148, 39, 123, 92, 82, 128, 109, 57, 24,
38, 113, 209, 245, 2, 119, 153, 229, 189, 214, 230, 174, 232, 63, 52, 205, 86, 140, 66, 175,
111, 171, 246, 133, 238, 193, 99, 60, 74, 91, 225, 51, 76, 37, 145, 211, 166, 151, 213, 206,
0, 200, 244, 176, 218, 44, 184, 172, 49, 216, 93, 168, 53, 21, 183, 41, 67, 85, 224, 155, 226,
242, 87, 177, 146, 70, 190, 12, 162, 19, 137, 114, 25, 165, 163, 192, 23, 59, 9, 94, 179, 107,
35, 7, 142, 131, 239, 203, 149, 136, 61, 249, 14, 156,
];
}
static sm3ToArray(input) {
const hash = sm3(input instanceof Uint8Array ? Buffer.from(input) : input);
return Buffer.from(hash, "hex").toJSON().data;
}
addSalt(param) {
return param + this.salt;
}
paramsToArray(param, addSalt) {
const processed = addSalt ? this.addSalt(param) : param;
return CryptoUtility.sm3ToArray(processed);
}
transformBytes(valuesList) {
const result = [];
let indexB = this.bigArray[1];
let initialValue = 0, valueE = 0;
const arrayLen = this.bigArray.length;
for (let index = 0; index < valuesList.length; index++) {
let sumInitial;
if (index === 0) {
initialValue = this.bigArray[indexB];
sumInitial = indexB + initialValue;
this.bigArray[1] = initialValue;
this.bigArray[indexB] = indexB;
}
else {
sumInitial = initialValue + valueE;
}
const sumInitialIdx = sumInitial % arrayLen;
const valueF = this.bigArray[sumInitialIdx];
result.push(valuesList[index] ^ valueF);
const nextIdx = (index + 2) % arrayLen;
valueE = this.bigArray[nextIdx];
const newSumInitialIdx = (indexB + valueE) % arrayLen;
initialValue = this.bigArray[newSumInitialIdx];
[this.bigArray[newSumInitialIdx], this.bigArray[nextIdx]] = [
this.bigArray[nextIdx],
this.bigArray[newSumInitialIdx],
];
indexB = newSumInitialIdx;
}
return result;
}
base64Encode(bytes, selectedAlphabet) {
const alphabet = this.base64Alphabet[selectedAlphabet];
let output = "";
for (let i = 0; i < bytes.length; i += 3) {
const b1 = bytes[i];
const b2 = bytes[i + 1] || 0;
const b3 = bytes[i + 2] || 0;
const combined = (b1 << 16) | (b2 << 8) | b3;
output += alphabet[(combined >> 18) & 63];
output += alphabet[(combined >> 12) & 63];
output += i + 1 < bytes.length ? alphabet[(combined >> 6) & 63] : "";
output += i + 2 < bytes.length ? alphabet[combined & 63] : "";
}
while (output.length % 4 !== 0)
output += "=";
return output;
}
abogusEncode(values, selectedAlphabet) {
const alphabet = this.base64Alphabet[selectedAlphabet];
let abogus = "";
for (let i = 0; i < values.length; i += 3) {
const v1 = values[i];
const v2 = values[i + 1] || 0;
const v3 = values[i + 2] || 0;
const n = (v1 << 16) | (v2 << 8) | v3;
abogus += alphabet[(n & 0xfc0000) >> 18];
abogus += alphabet[(n & 0x03f000) >> 12];
abogus += i + 1 < values.length ? alphabet[(n & 0x0fc0) >> 6] : "";
abogus += i + 2 < values.length ? alphabet[n & 0x3f] : "";
}
while (abogus.length % 4 !== 0)
abogus += "=";
return abogus;
}
static rc4Encrypt(key, plaintext) {
// 推荐用 npm 包 rc4 或自己实现
const S = Array.from({ length: 256 }, (_, i) => i);
let j = 0;
for (let i = 0; i < 256; i++) {
j = (j + S[i] + key[i % key.length]) & 0xff;
[S[i], S[j]] = [S[j], S[i]];
}
let i = 0;
j = 0;
const ptBytes = StringProcessor.toCharArray(plaintext);
const ct = [];
for (const charVal of ptBytes) {
i = (i + 1) & 0xff;
j = (j + S[i]) & 0xff;
[S[i], S[j]] = [S[j], S[i]];
const k = S[(S[i] + S[j]) & 0xff];
ct.push(charVal ^ k);
}
return Uint8Array.from(ct);
}
}
export class BrowserFingerprintGenerator {
static generateFingerprint(browserType) {
switch (browserType) {
case "Chrome":
case "Edge":
case "Firefox":
return this._generateFingerprint("Win32");
case "Safari":
return this._generateFingerprint("MacIntel");
default:
return this._generateFingerprint("Win32");
}
}
static _generateFingerprint(platform) {
const rand = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
const innerWidth = rand(1024, 1920);
const innerHeight = rand(768, 1080);
const outerWidth = innerWidth + rand(24, 32);
const outerHeight = innerHeight + rand(75, 90);
const screenX = 0;
const screenY = [0, 30][rand(0, 1)];
const sizeWidth = rand(1024, 1920);
const sizeHeight = rand(768, 1080);
const availWidth = rand(1280, 1920);
const availHeight = rand(800, 1080);
return `${innerWidth}|${innerHeight}|${outerWidth}|${outerHeight}|${screenX}|${screenY}|0|0|${sizeWidth}|${sizeHeight}|${availWidth}|${availHeight}|${innerWidth}|${innerHeight}|24|24|${platform}`;
}
}
export class ABogus {
cryptoUtility;
userAgent;
browserFp;
options;
pageId;
aid;
uaKey;
sortIndex;
sortIndex2;
constructor(fp, userAgent, options) {
const salt = "cus";
const character = "Dkdpgh2ZmsQB80/MfvV36XI1R45-WUAlEixNLwoqYTOPuzKFjJnry79HbGcaStCe";
const character2 = "ckdp1h4ZKsUB80/Mfvw36XIgR25+WQAlEi7NLboqYTOPuzmFjJnryx9HVGDaStCe";
const characterList = [character, character2];
this.cryptoUtility = new CryptoUtility(salt, characterList);
this.userAgent =
userAgent && userAgent.length > 0
? userAgent
: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0";
this.browserFp =
fp && fp.length > 0 ? fp : BrowserFingerprintGenerator.generateFingerprint("Edge");
this.options = options || [0, 1, 14];
this.pageId = 0;
this.aid = 6383;
this.uaKey = [0x00, 0x01, 0x0e];
this.sortIndex = [
18, 20, 52, 26, 30, 34, 58, 38, 40, 53, 42, 21, 27, 54, 55, 31, 35, 57, 39, 41, 43, 22, 28,
32, 60, 36, 23, 29, 33, 37, 44, 45, 59, 46, 47, 48, 49, 50, 24, 25, 65, 66, 70, 71,
];
this.sortIndex2 = [
18, 20, 26, 30, 34, 38, 40, 42, 21, 27, 31, 35, 39, 41, 43, 22, 28, 32, 36, 23, 29, 33, 37,
44, 45, 46, 47, 48, 49, 50, 24, 25, 52, 53, 54, 55, 57, 58, 59, 60, 65, 66, 70, 71,
];
}
generateAbogus(params, body) {
const abDir = {
8: 3,
18: 44,
66: 0,
69: 0,
70: 0,
71: 0,
};
const startEncryption = Date.now();
// Hash(Hash(params))
const paramsHash1 = this.cryptoUtility.paramsToArray(params, true);
// @ts-ignore
const array1 = CryptoUtility.sm3ToArray(paramsHash1);
// Hash(Hash(body))
const bodyHash1 = this.cryptoUtility.paramsToArray(body, true);
// @ts-ignore
const array2 = CryptoUtility.sm3ToArray(bodyHash1);
// Hash(Base64(RC4(user_agent)))
const rc4Ua = CryptoUtility.rc4Encrypt(this.uaKey, this.userAgent);
const uaB64 = this.cryptoUtility.base64Encode(rc4Ua, 1);
const array3 = this.cryptoUtility.paramsToArray(uaB64, false);
const endEncryption = Date.now();
// 动态填充 abDir
abDir[20] = (startEncryption >> 24) & 255;
abDir[21] = (startEncryption >> 16) & 255;
abDir[22] = (startEncryption >> 8) & 255;
abDir[23] = startEncryption & 255;
abDir[24] = Math.floor(startEncryption / 0x100000000);
abDir[25] = Math.floor(startEncryption / 0x10000000000);
abDir[26] = (this.options[0] >> 24) & 255;
abDir[27] = (this.options[0] >> 16) & 255;
abDir[28] = (this.options[0] >> 8) & 255;
abDir[29] = this.options[0] & 255;
abDir[30] = Math.floor(this.options[1] / 256) & 255;
abDir[31] = this.options[1] % 256;
abDir[32] = (this.options[1] >> 24) & 255;
abDir[33] = (this.options[1] >> 16) & 255;
abDir[34] = (this.options[2] >> 24) & 255;
abDir[35] = (this.options[2] >> 16) & 255;
abDir[36] = (this.options[2] >> 8) & 255;
abDir[37] = this.options[2] & 255;
abDir[38] = array1[21];
abDir[39] = array1[22];
abDir[40] = array2[21];
abDir[41] = array2[22];
abDir[42] = array3[23];
abDir[43] = array3[24];
abDir[44] = (endEncryption >> 24) & 255;
abDir[45] = (endEncryption >> 16) & 255;
abDir[46] = (endEncryption >> 8) & 255;
abDir[47] = endEncryption & 255;
abDir[48] = abDir[8];
abDir[49] = Math.floor(endEncryption / 0x100000000);
abDir[50] = Math.floor(endEncryption / 0x10000000000);
abDir[51] = (this.pageId >> 24) & 255;
abDir[52] = (this.pageId >> 16) & 255;
abDir[53] = (this.pageId >> 8) & 255;
abDir[54] = this.pageId & 255;
abDir[55] = this.pageId;
abDir[56] = this.aid;
abDir[57] = this.aid & 255;
abDir[58] = (this.aid >> 8) & 255;
abDir[59] = (this.aid >> 16) & 255;
abDir[60] = (this.aid >> 24) & 255;
abDir[64] = this.browserFp.length;
abDir[65] = this.browserFp.length;
const sortedValues = this.sortIndex.map((i) => abDir[i] || 0);
const fpArray = StringProcessor.toCharArray(this.browserFp);
let abXor = 0;
this.sortIndex2.forEach((key, idx) => {
const val = abDir[key] || 0;
abXor = idx === 0 ? val : abXor ^ val;
});
const allValues = [...sortedValues, ...fpArray, abXor];
const transformedValues = this.cryptoUtility.transformBytes(allValues);
const randomPrefix = StringProcessor.generateRandomBytes(3)
.split("")
.map((c) => c.charCodeAt(0));
const finalValues = [...randomPrefix, ...transformedValues];
const abogus = this.cryptoUtility.abogusEncode(finalValues, 0);
const finalParams = `${params}&a_bogus=${abogus}`;
return [finalParams, abogus, this.userAgent, body];
}
}