fix(Douyin): add abogus (#1451)

This commit is contained in:
XuanRan_Nyan authored and GitHub committed 2025-09-10 15:42:18 +00:00
1 parent 23c40ec5ae
commit 685872e70e
3 files changed
+948 -27

No files matched your search

+877
View File
@@ -0,0 +1,877 @@
# path: f2/utils/abogus.py
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@Description:abogus.py
@Date :2024/06/16 11:21:14
@Author :JohnserfSeed
@version :0.0.3
@License :Apache License 2.0
@Github :https://github.com/johnserf-seed
@Mail :support@f2.wiki
-------------------------------------------------
Change Log :
2024/06/16 17:27:47 - Create ABogus algorithm & black style
2024/06/16 17:27:47 - Limit custom ua late open source full version
2024/07/08 21:50:12 - Open the full version of the custom UA
2025/03/05 155:55:42 - Perf Post Method Generate
-------------------------------------------------
"""
import time
import random
from gmssl import sm3, func
from typing import Union, Callable, List, Dict
class StringProcessor:
"""
StringProcessor 类用于计算ABogus算法中所需的字符串处理方法。
包括字符串和 ASCII 码之间的转换、无符号右移运算等。
类方法:
to_ord_str(s: str) -> str:
将字符串转换为 ASCII 码字符串。
to_ord_array(s: str) -> List[int]:
将字符串转换为 ASCII 码列表。
to_char_str(s: List[int]) -> str:
将 ASCII 码列表转换为字符串。
to_char_array(s: str) -> List[int]:
将字符串转换为 ASCII 码列表。
js_shift_right(val: int, n: int) -> int:
实现 JavaScript 中的无符号右移运算。
generate_random_bytes(length: int = 3) -> str:
生成一组伪随机字节字符串,用于混淆数据。
使用示例:
```python
# 将字符串转换为 ASCII 码字符串
ord_str = StringProcessor.to_ord_str("Hello, World!")
print(ord_str)
# 将字符串转换为 ASCII 码列表
ord_array = StringProcessor.to_ord_array("Hello, World!")
print(ord_array)
# 将 ASCII 码列表转换为字符串
char_str = StringProcessor.to_char_str(ord_array)
print(char_str)
# 将字符串转换为 ASCII 码列表
char_array = StringProcessor.to_char_array("Hello, World!")
print(char_array)
# 实现 JavaScript 中的无符号右移运算
shifted_val = StringProcessor.js_shift_right(10, 2)
print(shifted_val)
# 生成一组伪随机字节字符串
random_bytes = StringProcessor.generate_random_bytes(3)
print(random_bytes)
```
"""
@staticmethod
def to_ord_str(s: str) -> str:
"""
将字符串转换为 ASCII 码字符串 (Convert a string to an ASCII code string).
Args:
s (str): 输入字符串 (Input string).
Returns:
str: 转换后的 ASCII 码字符串 (Converted ASCII code string).
"""
return "".join([chr(i) for i in s])
@staticmethod
def to_ord_array(s: str) -> List[int]:
"""
将字符串转换为 ASCII 码列表 (Convert a string to a list of ASCII codes).
Args:
s (str): 输入字符串 (Input string).
Returns:
List[int]: 转换后的 ASCII 码列表 (Converted list of ASCII codes).
"""
return [ord(char) for char in s]
@staticmethod
def to_char_str(s: List[int]) -> str:
"""
将 ASCII 码列表转换为字符串 (Convert a list of ASCII codes to a string).
Args:
s (str): ASCII 码列表 (List of ASCII codes).
Returns:
str: 转换后的字符串 (Converted string).
"""
return "".join([chr(i) for i in s])
@staticmethod
def to_char_array(s: str) -> List[int]:
"""
将字符串转换为 ASCII 码列表 (Convert a string to a list of ASCII codes).
Args:
s (str): 输入字符串 (Input string).
Returns:
List[int]: 转换后的 ASCII 码列表 (Converted list of ASCII codes).
"""
return [ord(char) for char in s]
@staticmethod
def js_shift_right(val: int, n: int) -> int:
"""
实现 JavaScript 中的无符号右移运算 (Implement the unsigned right shift operation in JavaScript).
Args:
val (int): 输入值 (Input value).
n (int): 右移位数 (Number of bits to shift right).
Returns:
int: 右移后的值 (Value after right shift).
"""
return (val % 0x100000000) >> n
@staticmethod
def generate_random_bytes(length: int = 3) -> str:
"""
生成一组伪随机字节字符串,用于混淆数据 (Generate a pseudo-random byte string to obfuscate the data).
Args:
length (int): 生成的字节序列长度 (Length of the byte sequence to generate).
Returns:
str: 生成的伪随机字节字符串 (Generated pseudo-random byte string).
"""
def generate_byte_sequence() -> List[str]:
_rd = int(random.random() * 10000)
return [
chr(((_rd & 255) & 170) | 1),
chr(((_rd & 255) & 85) | 2),
chr((StringProcessor.js_shift_right(_rd, 8) & 170) | 5),
chr((StringProcessor.js_shift_right(_rd, 8) & 85) | 40),
]
result = []
for _ in range(length):
result.extend(generate_byte_sequence())
return "".join(result)
class CryptoUtility:
"""
CryptoUtility 类用于提供加密和编码的工具方法,包括 SM3 哈希、添加盐值、Base64 编码和 RC4 加密等。
类属性:
salt (str): 加密盐值 (Encryption salt).
base64_alphabet (List[str]): 自定义 Base64 字符表 (Custom Base64 alphabet).
类方法:
sm3_to_array(input_data: Union[str, List[int]]) -> List[int]:
计算请求体的 SM3 哈希值,并将结果转换为整数数组。
add_salt(param: str) -> str:
为字符串参数添加盐值。
process_param(param: Union[str, List[int]], add_salt: bool) -> Union[str, List[int]]:
处理输入参数,根据需要添加盐值。
params_to_array(param: Union[str, List[int]], add_salt: bool = True) -> List[int]:
获取输入参数的哈希数组。
transform_bytes(bytes_list: List[int]) -> str:
对输入的字节列表进行加密/解密操作,返回处理后的字符串。
base64_encode(input_string: str, selected_alphabet: int = 0) -> str:
使用自定义字符表对输入字符串进行 Base64 编码。
abogus_encode(abogus_bytes_str: str, selected_alphabet: int) -> str:
对输入的字节字符串进行自定义 Base64 编码,并添加位移和填充。
rc4_encrypt(key: bytes, plaintext: str) -> bytes:
使用 RC4 算法加密数据。
使用示例:
```python
# 计算请求体的 SM3 哈希值
sm3_hash = CryptoUtility.sm3_to_array("Hello, World!")
print(sm3_hash)
# 为字符串参数添加盐值
salted_param = CryptoUtility.add_salt("Hello, World!")
print(salted_param)
# 获取输入参数的哈希数组
hash_array = CryptoUtility.params_to_array("Hello, World!")
print(hash_array)
# 对输入的字节列表进行加密/解密操作
encrypted_str = CryptoUtility.transform_bytes([72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33])
print(encrypted_str)
# 使用自定义字符表对输入字符串进行 Base64 编码
base64_str = CryptoUtility.base64_encode("Hello, World!")
print(base64_str)
# 对输入的字节字符串进行自定义 Base64 编码,并添加位移和填充
abogus_str = CryptoUtility.abogus_encode("Hello, World!", 0)
print(abogus_str)
# 使用 RC4 算法加密数据
key = b"key"
plaintext = "Hello, World!"
ciphertext = CryptoUtility.rc4_encrypt(key, plaintext)
print(ciphertext)
```
"""
def __init__(self, salt: str, custom_base64_alphabet: List[str]):
"""
初始化 CryptoUtility 类
Initialize the CryptoUtility class.
Args:
salt (str): 加密盐值 (Encryption salt).
custom_base64_alphabet (List[str]): 自定义 Base64 字符表 (Custom Base64 alphabet).
"""
self.salt = salt
self.base64_alphabet = custom_base64_alphabet
# fmt: off
self.big_array = [
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
]
# fmt: on
@staticmethod
def sm3_to_array(input_data: Union[str, List[int]]) -> List[int]:
"""
计算请求体的 SM3 哈希值,并将结果转换为整数数组 (Calculate the SM3 hash value of the request body and convert the result to an array of integers).
Args:
input_data (Union[str, List[int]]): 输入数据 (Input data).
Returns:
List[int]: 哈希值的整数数组 (Array of integers representing the hash value).
"""
# 如果输入是字符串,则将其编码为字节数组
if isinstance(input_data, str):
input_data_bytes = input_data.encode("utf-8")
else:
input_data_bytes = bytes(input_data) # 将 List[int] 转换为字节数组
# 将字节数组转换为适合 sm3.sm3_hash 函数处理的列表格式
hex_result = sm3.sm3_hash(func.bytes_to_list(input_data_bytes))
# 将十六进制字符串结果转换为十进制整数列表
return [int(hex_result[i : i + 2], 16) for i in range(0, len(hex_result), 2)]
def add_salt(self, param: str) -> str:
"""
为字符串参数添加盐值 (Add salt to the string parameter).
Args:
param (str): 输入字符串 (Input string).
Returns:
str: 添加盐值后的字符串 (String with added salt).
"""
return param + self.salt
def process_param(
self, param: Union[str, List[int]], add_salt: bool
) -> Union[str, List[int]]:
"""
处理输入参数,根据需要添加盐值 (Process input parameter and add salt if needed).
Args:
param (Union[str, List[int]]): 输入参数 (Input parameter).
add_salt (bool): 是否添加盐值 (Whether to add salt).
Returns:
Union[str, List[int]]: 处理后的参数 (Processed parameter).
"""
if isinstance(param, str) and add_salt:
param = self.add_salt(param)
return param
def params_to_array(
self, param: Union[str, List[int]], add_salt: bool = True
) -> List[int]:
"""
获取输入参数的哈希数组 (Get the hash array of the input parameter).
Args:
param (Union[str, List[int]]): 输入参数 (Input parameter).
add_salt (bool): 是否添加盐值 (Whether to add salt).
Returns:
List[int]: 哈希数组 (Hash array).
"""
processed_param = self.process_param(param, add_salt)
return self.sm3_to_array(processed_param)
def transform_bytes(self, bytes_list: List[int]) -> str:
"""
对输入的字节列表进行加密/解密操作,返回处理后的字符串 (Encrypt/decrypt the input byte list and return the processed string).
Args:
bytes_list (List[int]): 输入的字节列表 (Input byte list).
Returns:
str: 处理后的字符串 (Processed string).
"""
# 将字节列表转换为字符字符串
bytes_str = StringProcessor.to_char_str(bytes_list)
result_str = []
index_b = self.big_array[1]
initial_value = 0
for index, char in enumerate(bytes_str):
if index == 0:
initial_value = self.big_array[index_b]
sum_initial = index_b + initial_value
self.big_array[1] = initial_value
self.big_array[index_b] = index_b
else:
sum_initial = initial_value + value_e
char_value = ord(char)
sum_initial %= len(self.big_array)
value_f = self.big_array[sum_initial]
encrypted_char = char_value ^ value_f
result_str.append(chr(encrypted_char))
# 交换数组元素
value_e = self.big_array[(index + 2) % len(self.big_array)]
sum_initial = (index_b + value_e) % len(self.big_array)
initial_value = self.big_array[sum_initial]
self.big_array[sum_initial] = self.big_array[
(index + 2) % len(self.big_array)
]
self.big_array[(index + 2) % len(self.big_array)] = initial_value
index_b = sum_initial
return "".join(result_str)
def base64_encode(self, input_string: str, selected_alphabet: int = 0) -> str:
"""
使用自定义字符表对输入字符串进行 Base64 编码 (Encode the input string using a custom Base64 alphabet).
Args:
input_string (str): 输入字符串 (Input string).
selected_alphabet (int): 选择的自定义 Base64 字符表索引 (Selected custom Base64 alphabet index).
Returns:
str: 编码后的字符串 (Encoded string).
"""
# 将输入字符串转换为ASCII码的二进制形式
binary_string = "".join(["{:08b}".format(ord(char)) for char in input_string])
# 补全二进制字符串使其长度为6的倍数
padding_length = (6 - len(binary_string) % 6) % 6
binary_string += "0" * padding_length
# 将二进制字符串分割为6位一组
base64_indices = [
int(binary_string[i : i + 6], 2) for i in range(0, len(binary_string), 6)
]
# 根据自定义字符表生成输出字符串
output_string = "".join(
[self.base64_alphabet[selected_alphabet][index] for index in base64_indices]
)
# 添加等号填充,使符合 Base64 编码规范
output_string += "=" * (padding_length // 2)
return output_string
def abogus_encode(self, abogus_bytes_str: str, selected_alphabet: int) -> str:
"""
对输入的字节字符串进行自定义 Base64 编码,并添加位移和填充 (Encode the input byte string using a custom Base64 alphabet, and add shifts and padding).
Args:
abogus_bytes_str (str): 输入的字节字符串 (Input byte string).
selected_alphabet (int): 选择的自定义 Base64 字符表索引 (Selected custom Base64 alphabet index).
Returns:
str: 编码后的字符串 (Encoded string).
"""
abogus = []
for i in range(0, len(abogus_bytes_str), 3):
if i + 2 < len(abogus_bytes_str):
n = (
(ord(abogus_bytes_str[i]) << 16)
| (ord(abogus_bytes_str[i + 1]) << 8)
| ord(abogus_bytes_str[i + 2])
)
elif i + 1 < len(abogus_bytes_str):
n = (ord(abogus_bytes_str[i]) << 16) | (
ord(abogus_bytes_str[i + 1]) << 8
)
else:
n = ord(abogus_bytes_str[i]) << 16
for j, k in zip(range(18, -1, -6), (0xFC0000, 0x03F000, 0x0FC0, 0x3F)):
if j == 6 and i + 1 >= len(abogus_bytes_str):
break
if j == 0 and i + 2 >= len(abogus_bytes_str):
break
abogus.append(self.base64_alphabet[selected_alphabet][(n & k) >> j])
abogus.append("=" * ((4 - len(abogus) % 4) % 4))
return "".join(abogus)
@staticmethod
def rc4_encrypt(key: bytes, plaintext: str) -> bytes:
"""
使用 RC4 算法加密数据 (Encrypt data using the RC4 algorithm).
Args:
key (bytes): 加密密钥 (Encryption key).
plaintext (str): 明文数据 (Plaintext data).
Returns:
bytes: 加密后的数据 (Encrypted data).
"""
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
i = j = 0
ciphertext = []
for char in plaintext:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
K = S[(S[i] + S[j]) % 256]
ciphertext.append(ord(char) ^ K)
return bytes(ciphertext)
class BrowserFingerprintGenerator:
"""
BrowserFingerprintGenerator 用于生成模拟的浏览器指纹信息,用于在不同浏览器环境中进行测试和数据采集。
类属性:
browsers (Dict[str, Callable[[], str]]): 浏览器类型和生成浏览器指纹的映射关系。
方法:
generate_fingerprint(browser_type="Edge"):
根据指定的浏览器类型生成浏览器指纹。
generate_chrome_fingerprint():
生成 Chrome 浏览器指纹。
generate_firefox_fingerprint():
生成 Firefox 浏览器指纹。
generate_safari_fingerprint():
生成 Safari 浏览器指纹。
generate_edge_fingerprint():
生成 Edge 浏览器指纹。
_generate_fingerprint(platform="Win32"):
根据给定的参数生成浏览器指纹字符串。
使用示例:
```python
chrome_fp = BrowserFingerprintGenerator.generate_fingerprint("Chrome")
print(chrome_fp)
```
"""
@classmethod
def generate_fingerprint(cls, browser_type: str = "Edge") -> str:
"""
根据指定的浏览器类型生成浏览器指纹。 (Generate a browser fingerprint based on the specified browser type.)
Args:
browser_type (str): 浏览器类型 (Browser type).
Returns:
str: 生成的浏览器指纹字符串 (Generated browser fingerprint string).
"""
cls.browsers: Dict[str, Callable[[], str]] = {
"Chrome": cls.generate_chrome_fingerprint,
"Firefox": cls.generate_firefox_fingerprint,
"Safari": cls.generate_safari_fingerprint,
"Edge": cls.generate_edge_fingerprint,
}
return cls.browsers.get(browser_type, cls.generate_chrome_fingerprint)()
@classmethod
def generate_chrome_fingerprint(cls) -> str:
return cls._generate_fingerprint(platform="Win32")
@classmethod
def generate_firefox_fingerprint(cls) -> str:
return cls._generate_fingerprint(platform="Win32")
@classmethod
def generate_safari_fingerprint(cls) -> str:
return cls._generate_fingerprint(platform="MacIntel")
@classmethod
def generate_edge_fingerprint(cls) -> str:
return cls._generate_fingerprint(platform="Win32")
@staticmethod
def _generate_fingerprint(platform: str) -> str:
"""
根据给定的参数生成浏览器指纹字符串。 (Generate a browser fingerprint string based on the given parameters.)
Args:
platform (str): 操作系统平台 (Operating system platform).
Returns:
str: 生成的浏览器指纹字符串 (Generated browser fingerprint string).
"""
inner_width = random.randint(1024, 1920)
inner_height = random.randint(768, 1080)
outer_width = inner_width + random.randint(24, 32)
outer_height = inner_height + random.randint(75, 90)
screen_x = 0
screen_y = random.choice([0, 30])
size_width = random.randint(1024, 1920)
size_height = random.randint(768, 1080)
avail_width = random.randint(1280, 1920)
avail_height = random.randint(800, 1080)
fingerprint = (
f"{inner_width}|{inner_height}|{outer_width}|{outer_height}|"
f"{screen_x}|{screen_y}|0|0|{size_width}|{size_height}|"
f"{avail_width}|{avail_height}|{inner_width}|{inner_height}|24|24|{platform}"
)
return fingerprint
class ABogus:
"""
ABogus 类用于生成 ABogus 参数。
类属性:
array1 (List[int]): 加密请求体 (Encrypted request body).
array2 (List[int]): 加密请求头 (Encrypted request header).
array3 (List[int]): 加密 UA (Encrypted User-Agent).
aid (int): AID 值 (AID value).
pageId (int): 页面 ID (Page ID).
salt (str): 加密盐值 (Encryption salt).
options (List[int]): 请求选项 (Request options).
ua_key (bytes): UA 加密密钥 (UA encryption key).
character (str): 自定义 Base64 字符表 (Custom Base64 alphabet).
character2 (str): 自定义 Base64 字符表 (Custom Base64 alphabet).
character_list (List[str]): 自定义 Base64 字符表列表 (List of custom Base64 alphabets).
crypto_utility (CryptoUtility): 加密工具类 (Encryption utility).
user_agent (str): 自定义 UA (Custom User-Agent).
browser_fp (str): 浏览器指纹 (Browser fingerprint).
sort_index (List[int]): 排序索引 (Sort index).
sort_index_2 (List[int]): 排序索引 (Sort index).
说明:
options 参数用于指定请求的类型,GET 请求使用 [0, 1, 8],POST 请求使用 [0, 1, 14]。14兼容8,POST同样可以编码params,故写死。
(The options parameter is used to specify the type of request. GET requests use [0, 1, 8], and POST requests use [0, 1, 14]. 14 is compatible with 8, and POST can also encode params, so it is hardcoded.)
方法:
encode_data(data: str, alphabet_index: int = 0) -> str:
使用指定的字符表对数据进行 Base64 编码 (Encode the data using the specified Base64 alphabet).
generate_abogus(params: str, request: str = "") -> str:
生成 ABogus 参数 (Generate the ABogus parameter).
使用示例:
```python
# 生成 ABogus 参数,置空使用默认 UA 和浏览器指纹
abogus = ABogus(user_agent="xxx", fp="xxx")
abogus_param = abogus.generate_abogus("device_platform=webapp&aid=6383&channel=channel_pc_web&aweme_id=7380308675841297704……省略……")
print(abogus_param[1])
```
"""
def __init__(
self,
fp: str = "",
user_agent: str = "",
options: List[int] = [0, 1, 14],
):
self.aid = 6383
self.pageId = 0 # 1.0.1.19 -> 6241
self.salt = "cus" # 1.0.1.19 -> 加密盐 # dhzx
self.boe = False
self.ddrt = 8.5
self.ic = 8.5
self.paths = [
"^/webcast/",
"^/aweme/v1/",
"^/aweme/v2/",
"/v1/message/send",
"^/live/",
"^/captcha/",
"^/ecom/",
]
self.array1 = [] # 加密请求体
self.array2 = [] # 加密请求头 为空
self.array3 = [] # 加密UA
self.options = options # GET [0, 1, 8] POST [0, 1, 14]
self.ua_key = b"\x00\x01\x0E" # ua加密key
self.character = (
"Dkdpgh2ZmsQB80/MfvV36XI1R45-WUAlEixNLwoqYTOPuzKFjJnry79HbGcaStCe"
)
self.character2 = (
"ckdp1h4ZKsUB80/Mfvw36XIgR25+WQAlEi7NLboqYTOPuzmFjJnryx9HVGDaStCe"
)
self.character_list = [self.character, self.character2] # 自定义base64字符表
self.crypto_utility = CryptoUtility(
self.salt, self.character_list
) # 加密工具类
self.user_agent = (
user_agent
if user_agent is not None and user_agent != ""
else "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"
) # 自定义ua,为空则设置一个默认ua
self.browser_fp = (
fp
if fp is not None and fp != ""
else BrowserFingerprintGenerator.generate_fingerprint("Edge")
) # 自定义浏览器指纹,为空则生成Edge指纹
# fmt: off
self.sort_index = [
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
]
self.sort_index_2 = [
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
]
# fmt: on
def encode_data(self, data: str, alphabet_index: int = 0) -> str:
"""
使用指定的字符表对数据进行 Base64 编码 (Encode the data using the specified Base64 alphabet).
Args:
data (str): 输入数据 (Input data).
alphabet_index (int): 自定义字符表索引 (Custom alphabet index).
Returns:
str: 编码后的数据 (Encoded data).
"""
return self.crypto_utility.abogus_encode(data, alphabet_index)
def generate_abogus(self, params: str, body: str = "") -> tuple:
"""
生成 abogus 参数 (Generate the ABogus parameter).
Args:
params (str): 请求参数 (Request parameters).
body (str): 请求体,GET接口则为空 (Request body, empty for GET interfaces).
Returns:
tuple: params 生成的 abogus 参数 和 ua (ABogus parameter generated by params and ua).
"""
ab_dir = {
8: 3, # 固定
15: {
"aid": self.aid,
"pageId": self.pageId,
"boe": self.boe,
"ddrt": self.ddrt,
"paths": self.paths,
"track": {"mode": 0, "delay": 300, "paths": []},
"dump": True,
"rpU": "",
},
18: 44,
19: [1, 0, 1, 0, 1],
66: 0, # 固定
69: 0, # 固定
70: 0, # 固定
71: 0, # 固定
}
# 开始加密时间
start_encryption = int(time.time() * 1000)
# params参数加盐加密
array1 = self.crypto_utility.params_to_array(
self.crypto_utility.params_to_array(params)
)
array2 = self.crypto_utility.params_to_array(
self.crypto_utility.params_to_array(body)
)
array3 = self.crypto_utility.params_to_array(
self.crypto_utility.base64_encode(
StringProcessor.to_ord_str(
self.crypto_utility.rc4_encrypt(self.ua_key, self.user_agent)
),
1,
),
add_salt=False,
)
# 结束加密时间
end_encryption = int(time.time() * 1000)
# 插入加密开始时间
ab_dir[20] = (start_encryption >> 24) & 255
ab_dir[21] = (start_encryption >> 16) & 255
ab_dir[22] = (start_encryption >> 8) & 255
ab_dir[23] = start_encryption & 255
ab_dir[24] = int(start_encryption / 256 / 256 / 256 / 256) >> 0
ab_dir[25] = int(start_encryption / 256 / 256 / 256 / 256 / 256) >> 0
# 插入请求头配置
ab_dir[26] = (self.options[0] >> 24) & 255
ab_dir[27] = (self.options[0] >> 16) & 255
ab_dir[28] = (self.options[0] >> 8) & 255
ab_dir[29] = self.options[0] & 255
# 插入请求方法
ab_dir[30] = int(self.options[1] / 256) & 255
ab_dir[31] = (self.options[1] % 256) & 255
ab_dir[32] = (self.options[1] >> 24) & 255
ab_dir[33] = (self.options[1] >> 16) & 255
# 插入请求头加密
ab_dir[34] = (self.options[2] >> 24) & 255
ab_dir[35] = (self.options[2] >> 16) & 255
ab_dir[36] = (self.options[2] >> 8) & 255
ab_dir[37] = self.options[2] & 255
# 插入请求体加密
ab_dir[38] = array1[21]
ab_dir[39] = array1[22]
# 插入body加密
ab_dir[40] = array2[21]
ab_dir[41] = array2[22]
# 插入ua加密
ab_dir[42] = array3[23]
ab_dir[43] = array3[24]
# 插入加密结束时间
ab_dir[44] = (end_encryption >> 24) & 255
ab_dir[45] = (end_encryption >> 16) & 255
ab_dir[46] = (end_encryption >> 8) & 255
ab_dir[47] = end_encryption & 255
ab_dir[48] = ab_dir[8]
ab_dir[49] = int(end_encryption / 256 / 256 / 256 / 256) >> 0
ab_dir[50] = int(end_encryption / 256 / 256 / 256 / 256 / 256) >> 0
# 插入固定值
ab_dir[51] = (self.pageId >> 24) & 255
ab_dir[52] = (self.pageId >> 16) & 255
ab_dir[53] = (self.pageId >> 8) & 255
ab_dir[54] = self.pageId & 255
ab_dir[55] = self.pageId
ab_dir[56] = self.aid
ab_dir[57] = self.aid & 255
ab_dir[58] = (self.aid >> 8) & 255
ab_dir[59] = (self.aid >> 16) & 255
ab_dir[60] = (self.aid >> 24) & 255
# 插入浏览器指纹
ab_dir[64] = len(self.browser_fp)
ab_dir[65] = len(self.browser_fp)
# 获取 ab_dir 中 sort_index 的值
sorted_values = [ab_dir.get(i, 0) for i in self.sort_index]
# 将浏览器指纹转换为 ASCII 码列表
edge_fp_array = StringProcessor.to_char_array(self.browser_fp)
# 将浏览器指纹长度的低 8 位作为异或值
ab_xor = (len(self.browser_fp) & 255) >> 8 & 255
# 进行异或计算
for index in range(len(self.sort_index_2) - 1):
if index == 0:
ab_xor = ab_dir.get(self.sort_index_2[index], 0)
ab_xor ^= ab_dir.get(self.sort_index_2[index + 1], 0)
sorted_values.extend(edge_fp_array)
sorted_values.append(ab_xor)
abogus_bytes_str = (
StringProcessor.generate_random_bytes()
+ self.crypto_utility.transform_bytes(sorted_values)
)
abogus = self.crypto_utility.abogus_encode(abogus_bytes_str, 0)
params = "%s&a_bogus=%s" % (params, abogus)
return (params, abogus, self.user_agent, body)
if __name__ == "__main__":
# 24/06/16 晚点开源自定义ua
# 24/07/08 支持自定义ua和浏览器指纹
# 24/11/15 完成1.0.1.19版本abogus算法,择日开源
# 25/03/05 修复POST请求参数加密错误,修补环境
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0"
chrome_fp = BrowserFingerprintGenerator.generate_fingerprint("Edge")
abogus = ABogus(user_agent=user_agent, fp=chrome_fp)
# GET
url = "https://www.douyin.com/aweme/v1/web/aweme/detail/?"
params = "device_platform=webapp&aid=6383&channel=channel_pc_web&sec_user_id=MS4wLjABAAAArDVBosPJF3eIWVEFp0szuJ-e1V_-rK0ieJeWwpE77E8&max_cursor=0&locate_query=false&show_live_replay_strategy=1&need_time_list=1&time_list_query=0&whale_cut_token=&cut_version=1&count=18&publish_video_strategy_type=2&from_user_page=1&update_version_code=170400&pc_client_type=1&pc_libra_divert=Windows&support_h265=1&support_dash=0&version_code=290100&version_name=29.1.0&cookie_enabled=true&screen_width=1920&screen_height=1080&browser_language=zh-CN&browser_platform=Win32&browser_name=Edge&browser_version=131.0.0.0&browser_online=true&engine_name=Blink&engine_version=131.0.0.0&os_name=Windows&os_version=10&cpu_core_num=12&device_memory=8&platform=PC&downlink=10&effective_type=4g&round_trip_time=50"
body = ""
print(url + abogus.generate_abogus(params=params, body=body)[0])
# POST
url = "https://www.douyin.com/aweme/v2/web/aweme/stats/?"
params = "device_platform=webapp&aid=6383&channel=channel_pc_web&pc_client_type=1&pc_libra_divert=Windows&update_version_code=170400&support_h265=1&support_dash=0&version_code=170400&version_name=17.4.0&cookie_enabled=true&screen_width=1920&screen_height=1080&browser_language=zh-CN&browser_platform=Win32&browser_name=Edge&browser_version=131.0.0.0&browser_online=true&engine_name=Blink&engine_version=131.0.0.0&os_name=Windows&os_version=10&cpu_core_num=12&device_memory=8&platform=PC&downlink=10&effective_type=4g&round_trip_time=50"
body = "aweme_type=0&item_id=7467485482314763572&play_delta=1&source=0"
print(url + abogus.generate_abogus(params=params, body=body)[0])
# # 测试生成100个abogus参数 和 100个指纹所需时间
# start = time.time()
# for _ in range(100):
# abogus.generate_abogus(params=params, body=body)
# end = time.time()
# print("生成100个abogus参数和指纹所需时间:", end - start) # 生成100个abogus参数和指纹所需时间: 2.203000783920288
# start = time.time()
# for _ in range(100):
# BrowserFingerprintGenerator.generate_fingerprint("Chrome")
# end = time.time()
# print("生成100个指纹所需时间:", end - start) # 生成100个指纹所需时间: 0.00400090217590332
+70 -27
View File
@@ -4,13 +4,14 @@ from urllib.parse import unquote, urlparse, parse_qs, urlencode, urlunparse
import requests
import random
from biliup.common.util import client
from . import logger, match1, random_user_agent, json_loads
from biliup.config import config
from biliup.Danmaku import DanmakuClient
from ..common.util import client
from ..config import config
from ..Danmaku import DanmakuClient
from ..common.tools import NamedLock
from ..common.abogus import ABogus
from ..engine.decorators import Plugin
from ..engine.download import DownloadBase
from . import logger, match1, random_user_agent, json_loads, test_jsengine
@Plugin.download(regexp=r'https?://(?:(?:www|m|live|v)\.)?douyin\.com')
@@ -30,9 +31,17 @@ class Douyin(DownloadBase):
self.fake_headers['user-agent'] = DouyinUtils.DOUYIN_USER_AGENT
self.fake_headers['referer'] = "https://live.douyin.com/"
self.fake_headers['cookie'] = config.get('user', {}).get('douyin_cookie', '')
if self.fake_headers['cookie'] != "" and not self.fake_headers['cookie'].endswith(';'):
self.fake_headers['cookie'] += ";"
if "ttwid" not in self.fake_headers['cookie']:
self.fake_headers['cookie'] = f'ttwid={DouyinUtils.get_ttwid()};{self.fake_headers["cookie"]}'
self.fake_headers['cookie'] += f'ttwid={DouyinUtils.get_ttwid()};'
if 'odin_ttid=' not in self.fake_headers['cookie']:
self.fake_headers['cookie'] += f"odin_ttid={DouyinUtils.generate_odin_ttid()};"
if '__ac_nonce=' not in self.fake_headers['cookie']:
self.fake_headers['cookie'] += f"__ac_nonce={DouyinUtils.generate_nonce()};"
if "v.douyin" in self.url:
try:
@@ -115,6 +124,9 @@ class Douyin(DownloadBase):
if is_check:
return True
else:
# 清理上一次获取的直播流
self.raw_stream_url = ""
try:
pull_data = room_info['stream_url']['live_core_sdk_data']['pull_data']
@@ -126,7 +138,7 @@ class Douyin(DownloadBase):
logger.debug(f"{self.plugin_msg}: room_info {room_info}")
return False
# 抖音FLV真原画
# 抖音FLV真原画
if (
self.douyin_true_origin # 开启真原画
and
@@ -186,28 +198,24 @@ class Douyin(DownloadBase):
def danmaku_init(self):
if self.douyin_danmaku:
content = {
'web_rid': self.__web_rid,
'sec_uid': self.__sec_uid,
'room_id': self.__room_id,
}
try:
import jsengine
try:
jsengine.jsengine()
self.danmaku = DanmakuClient(self.url, self.gen_download_filename(), content)
except jsengine.exceptions.RuntimeError as e:
extra_msg = "如需录制抖音弹幕,"
logger.error(f"\n{e}\n{extra_msg}请至少安装一个 Javascript 解释器,如 pip install quickjs")
except:
pass
if (js_runable := test_jsengine()):
content = {
'web_rid': self.__web_rid,
'sec_uid': self.__sec_uid,
'room_id': self.__room_id,
}
self.danmaku = DanmakuClient(self.url, self.gen_download_filename(), content)
else:
logger.error(f"如需录制抖音弹幕,请至少安装一个 Javascript 解释器。如 pip install quickjs")
async def get_web_room_info(self, web_rid: str) -> dict:
query = {
'app_name': 'douyin_web',
# 'enter_from': random.choice(['link_share', 'web_live']),
'enter_from': 'web_live',
'live_id': '1',
'web_rid': web_rid,
# 2025-08-01 服务端暂不校验以下参数的值,只校验参数存在
'enter_from': random.choice(['link_share', 'web_live']),
'a_bogus': '0',
'is_need_double_stream': "false"
}
target_url = DouyinUtils.build_request_url(f"https://live.douyin.com/webcast/room/web/enter/", query)
logger.debug(f"{self.plugin_msg}: get_web_room_info {target_url}")
@@ -217,6 +225,9 @@ class Douyin(DownloadBase):
return web_info
async def get_h5_room_info(self, sec_user_id: str, room_id: str) -> dict:
'''
Mobile web 的 API 信息,海外可能不允许使用
'''
if not sec_user_id:
raise ValueError("sec_user_id is None")
query = {
@@ -227,8 +238,13 @@ class Douyin(DownloadBase):
'room_id': room_id if room_id else 2, # 必要但不校验
'sec_user_id': sec_user_id
}
info = await client.get("https://webcast.amemv.com/webcast/room/reflow/info/",
params=query, headers=self.fake_headers)
abogus = ABogus(user_agent=DouyinUtils.DOUYIN_USER_AGENT)
query_str, _, _, _ = abogus.generate_abogus(params=urlencode(query, doseq=True), body="")
# target_url = DouyinUtils.build_request_url(f"https://live.douyin.com/webcast/room/web/enter/", query)
info = await client.get(
f"https://webcast.amemv.com/webcast/room/reflow/info/?{query_str}",
headers=self.fake_headers
)
info = json_loads(info.text)
logger.debug(f"{self.plugin_msg}: get_h5_room_info {info}")
return info
@@ -243,6 +259,8 @@ class DouyinUtils:
DOUYIN_HTTP_HEADERS = {
'user-agent': DOUYIN_USER_AGENT
}
CHARSET = "abcdef0123456789"
LONG_CHATSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-"
@staticmethod
def get_ttwid() -> Optional[str]:
@@ -252,23 +270,48 @@ class DouyinUtils:
DouyinUtils._douyin_ttwid = page.cookies.get("ttwid")
return DouyinUtils._douyin_ttwid
@staticmethod
def generate_ms_token() -> str:
'''生成随机 msToken'''
return ''.join(random.choice(DouyinUtils.LONG_CHATSET) for _ in range(184))
@staticmethod
def generate_nonce() -> str:
"""生成 21 位随机十六进制小写 nonce"""
return ''.join(random.choice(DouyinUtils.CHARSET) for _ in range(21))
@staticmethod
def generate_odin_ttid() -> str:
"""生成 160 位随机十六进制小写 odin_ttid"""
return ''.join(random.choice(DouyinUtils.CHARSET) for _ in range(160))
@staticmethod
def build_request_url(url: str, query: Optional[dict] = None) -> str:
# NOTE: 不能在类级别初始化,否则非首次生成的 abogus 有问题,原因未知
abogus = ABogus(user_agent=DouyinUtils.DOUYIN_USER_AGENT)
parsed_url = urlparse(url)
existing_params = query or parse_qs(parsed_url.query)
existing_params['aid'] = ['6383']
existing_params['compress'] = ['gzip']
existing_params['device_platform'] = ['web']
existing_params['browser_language'] = ['zh-CN']
existing_params['browser_platform'] = ['Win32']
existing_params['browser_name'] = [DouyinUtils.DOUYIN_USER_AGENT.split('/')[0]]
existing_params['browser_version'] = [DouyinUtils.DOUYIN_USER_AGENT.split(existing_params['browser_name'][0])[-1][1:]]
if 'msToken' not in existing_params:
existing_params['msToken'] = [DouyinUtils.generate_ms_token()]
new_query_string = urlencode(existing_params, doseq=True)
signed_query_string, _, _, _ = abogus.generate_abogus(params=new_query_string, body="")
new_url = urlunparse((
parsed_url.scheme,
parsed_url.netloc,
parsed_url.path,
parsed_url.params,
new_query_string,
signed_query_string,
parsed_url.fragment
))
return new_url
+1
View File
@@ -41,6 +41,7 @@ dependencies = [
"m3u8 >= 6.0.0",
"truststore >= 0.10.1; python_version >= '3.10'",
"async-lru >= 2.0.5",
"gmssl >= 3.2.2"
]
dynamic = ["version"]