返回 douyin-downloader
xbogus.py
根目录 / utils / xbogus.py
1 # ==============================================================================
2 # Copyright (C) 2021 Evil0ctal
3 #
4 # This file is part of the Douyin_TikTok_Download_API project.
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17 # ==============================================================================
18
19 import base64
20 import hashlib
21 import time
22 from typing import List, Optional, Tuple, Union
23
24
25 class XBogus:
26 def __init__(self, user_agent: Optional[str] = None) -> None:
27 # fmt: off
28 self._array = [
29 None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None,
30 None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None,
31 None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None,
32 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, None, None, None, None, None, None, None, None, None, None, None,
33 None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None,
34 None, None, None, None, None, None, None, None, None, None, None, None, 10, 11, 12, 13, 14, 15
35 ]
36 self._character = "Dkdpgh4ZKsQB80/Mfvw36XI1R25-WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe="
37 # fmt: on
38 self._ua_key = b"\x00\x01\x0c"
39 self._user_agent = (
40 user_agent
41 if user_agent
42 else (
43 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
44 "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
45 )
46 )
47
48 @property
49 def user_agent(self) -> str:
50 return self._user_agent
51
52 def _md5_str_to_array(self, md5_str: str) -> List[int]:
53 if isinstance(md5_str, str) and len(md5_str) > 32:
54 return [ord(char) for char in md5_str]
55
56 array: List[int] = []
57 idx = 0
58 while idx < len(md5_str):
59 array.append((self._array[ord(md5_str[idx])] << 4) | self._array[ord(md5_str[idx + 1])])
60 idx += 2
61 return array
62
63 def _md5(self, input_data: Union[str, List[int]]) -> str:
64 if isinstance(input_data, str):
65 data = self._md5_str_to_array(input_data)
66 else:
67 data = input_data
68 md5_hash = hashlib.md5()
69 md5_hash.update(bytes(data))
70 return md5_hash.hexdigest()
71
72 def _md5_encrypt(self, url_path: str) -> List[int]:
73 hashed = self._md5(self._md5_str_to_array(self._md5(url_path)))
74 return self._md5_str_to_array(hashed)
75
76 def _encoding_conversion(
77 self,
78 a,
79 b,
80 c,
81 e,
82 d,
83 t,
84 f,
85 r,
86 n,
87 o,
88 i,
89 _,
90 x,
91 u,
92 s,
93 l, # noqa: E741 — single-letter params mirror the upstream JS xbogus impl
94 v,
95 h,
96 p,
97 ) -> str:
98 payload = [a]
99 payload.append(int(i))
100 payload.extend([b, _, c, x, e, u, d, s, t, l, f, v, r, h, n, p, o])
101 return bytes(payload).decode("ISO-8859-1")
102
103 def _encoding_conversion2(self, a: int, b: int, c: str) -> str:
104 return chr(a) + chr(b) + c
105
106 @staticmethod
107 def _rc4_encrypt(key: bytes, data: bytes) -> bytearray:
108 s = list(range(256))
109 j = 0
110 encrypted = bytearray()
111
112 for i in range(256):
113 j = (j + s[i] + key[i % len(key)]) % 256
114 s[i], s[j] = s[j], s[i]
115
116 i = j = 0
117 for byte in data:
118 i = (i + 1) % 256
119 j = (j + s[i]) % 256
120 s[i], s[j] = s[j], s[i]
121 encrypted.append(byte ^ s[(s[i] + s[j]) % 256])
122
123 return encrypted
124
125 def _calculation(self, a1: int, a2: int, a3: int) -> str:
126 x3 = ((a1 & 255) << 16) | ((a2 & 255) << 8) | (a3 & 255)
127 return (
128 self._character[(x3 & 16515072) >> 18]
129 + self._character[(x3 & 258048) >> 12]
130 + self._character[(x3 & 4032) >> 6]
131 + self._character[x3 & 63]
132 )
133
134 def build(self, url: str) -> Tuple[str, str, str]:
135 ua_md5_array = self._md5_str_to_array(
136 self._md5(
137 base64.b64encode(
138 self._rc4_encrypt(self._ua_key, self._user_agent.encode("ISO-8859-1"))
139 ).decode("ISO-8859-1")
140 )
141 )
142
143 empty_md5_array = self._md5_str_to_array(
144 self._md5(self._md5_str_to_array("d41d8cd98f00b204e9800998ecf8427e"))
145 )
146 url_md5_array = self._md5_encrypt(url)
147
148 timer = int(time.time())
149 ct = 536919696
150
151 new_array = [
152 64,
153 0.00390625,
154 1,
155 12,
156 url_md5_array[14],
157 url_md5_array[15],
158 empty_md5_array[14],
159 empty_md5_array[15],
160 ua_md5_array[14],
161 ua_md5_array[15],
162 timer >> 24 & 255,
163 timer >> 16 & 255,
164 timer >> 8 & 255,
165 timer & 255,
166 ct >> 24 & 255,
167 ct >> 16 & 255,
168 ct >> 8 & 255,
169 ct & 255,
170 ]
171
172 xor_result = new_array[0]
173 for value in new_array[1:]:
174 if isinstance(value, float):
175 value = int(value)
176 xor_result ^= value
177 new_array.append(xor_result)
178
179 array3: list[int] = []
180 array4: list[int] = []
181 idx = 0
182 while idx < len(new_array):
183 value = new_array[idx]
184 array3.append(value)
185 if idx + 1 < len(new_array):
186 array4.append(new_array[idx + 1])
187 idx += 2
188
189 merged = array3 + array4
190
191 garbled = self._encoding_conversion2(
192 2,
193 255,
194 self._rc4_encrypt(
195 "ÿ".encode("ISO-8859-1"),
196 self._encoding_conversion(*merged).encode("ISO-8859-1"),
197 ).decode("ISO-8859-1"),
198 )
199
200 xb = ""
201 idx = 0
202 while idx < len(garbled):
203 xb += self._calculation(
204 ord(garbled[idx]),
205 ord(garbled[idx + 1]),
206 ord(garbled[idx + 2]),
207 )
208 idx += 3
209
210 signed_url = f"{url}&X-Bogus={xb}"
211 return signed_url, xb, self._user_agent
212
213
214 def generate_x_bogus(url: str, user_agent: Optional[str] = None) -> Tuple[str, str, str]:
215 signer = XBogus(user_agent=user_agent)
216 return signer.build(url)
217
217 lines PYTHON