xoichim 1.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xoichim-1.0.1/PKG-INFO +1224 -0
- xoichim-1.0.1/README.md +1196 -0
- xoichim-1.0.1/pyproject.toml +62 -0
- xoichim-1.0.1/scr/xoichim/__init__.py +94 -0
- xoichim-1.0.1/scr/xoichim/cli.py +61 -0
- xoichim-1.0.1/scr/xoichim/core.py +2073 -0
- xoichim-1.0.1/scr/xoichim/gweb_wasm_signer.js +97 -0
- xoichim-1.0.1/scr/xoichim/index-5ee0c009.js +141 -0
- xoichim-1.0.1/scr/xoichim.egg-info/PKG-INFO +1224 -0
- xoichim-1.0.1/scr/xoichim.egg-info/SOURCES.txt +12 -0
- xoichim-1.0.1/scr/xoichim.egg-info/dependency_links.txt +1 -0
- xoichim-1.0.1/scr/xoichim.egg-info/requires.txt +9 -0
- xoichim-1.0.1/scr/xoichim.egg-info/top_level.txt +1 -0
- xoichim-1.0.1/setup.cfg +4 -0
xoichim-1.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,1224 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xoichim
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Trần Đình Hoàng
|
|
5
|
+
Author: Hoang
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://app.hdxmax.vn/
|
|
8
|
+
Project-URL: YouTube, https://www.youtube.com/@chachanhgiatay02
|
|
9
|
+
Keywords: gecmax,python,library
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Requires-Python: >=3.8
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: cryptography>=41
|
|
22
|
+
Provides-Extra: curl
|
|
23
|
+
Requires-Dist: curl_cffi>=0.9.0; extra == "curl"
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: build; extra == "dev"
|
|
26
|
+
Requires-Dist: twine; extra == "dev"
|
|
27
|
+
Requires-Dist: curl_cffi>=0.9.0; extra == "dev"
|
|
28
|
+
|
|
29
|
+
# golike-gauth
|
|
30
|
+
|
|
31
|
+
[](https://pypi.org/project/golike-gauth/)
|
|
32
|
+
[](https://pypi.org/project/golike-gauth/)
|
|
33
|
+
[](LICENSE)
|
|
34
|
+
|
|
35
|
+
Client auth for the **Golike gateway API** (2026): JWT + device headers, TikTok `sig` minting, optional legacy `g-auth`.
|
|
36
|
+
|
|
37
|
+
**repup lại:** https://www.youtube.com/@chachanhgiatay02
|
|
38
|
+
**Version:** `1.1.3
|
|
39
|
+
## Changelog
|
|
40
|
+
|
|
41
|
+
### 1.1.3 — Golike 3.0 path map - Tran Dinh Hoang
|
|
42
|
+
- Facebook `complete-jobs-2026` → complete_job
|
|
43
|
+
- Traffic / review-jobs sig map
|
|
44
|
+
- Generic publishers/<plt>/jobs (bluesky, lazada, pinterest, ...)
|
|
45
|
+
- Crypto/captcha không đổi
|
|
46
|
+
|
|
47
|
+
### 0.1.14 — Facebook complete-jobs-2026 + multi-platform sig
|
|
48
|
+
|
|
49
|
+
- Map `complete-jobs-2026` → `('facebook', 'complete_job')`
|
|
50
|
+
- Map `get-jobs-2026` → `('facebook', 'get_job')`
|
|
51
|
+
- Sig tự động cho mọi `publishers/<plt>/jobs` và `complete-jobs`
|
|
52
|
+
(TikTok, Instagram, Threads, Twitter, YouTube, Facebook, …)
|
|
53
|
+
- `auto_solve_captcha` giữ nguyên
|
|
54
|
+
|
|
55
|
+
### 0.1.13 — Sig + captcha cho mọi platform job
|
|
56
|
+
|
|
57
|
+
- Mọi endpoint lấy job publisher giờ đều cần header `sig` (và có thể bắt captcha):
|
|
58
|
+
- TikTok, Facebook, Instagram, Threads, Twitter/X, YouTube, …
|
|
59
|
+
- `resolve_sig_act(path)` map tự động theo pattern:
|
|
60
|
+
|
|
61
|
+
| Path | plt | act |
|
|
62
|
+
|------|-----|-----|
|
|
63
|
+
| `/publishers/<plt>/jobs` | `<plt>` | `get_job` |
|
|
64
|
+
| `/publishers/<plt>/complete-jobs` | `<plt>` | `complete_job` |
|
|
65
|
+
| `/publishers/get-jobs-2026` | `facebook` | `get_job` |
|
|
66
|
+
| `*/skip-jobs` | — | không mint sig |
|
|
67
|
+
|
|
68
|
+
- `enable_sig=True` + `captcha_solver=auto_solve_captcha` →
|
|
69
|
+
`auth.get(...)` tự mint sig, tự giải captcha, không cần gọi
|
|
70
|
+
`mint_security_token` thủ công.
|
|
71
|
+
- `resolve_tiktok_sig_act` giữ nguyên (alias tương thích ngược).
|
|
72
|
+
- Không breaking change với code chỉ dùng TikTok.
|
|
73
|
+
|
|
74
|
+
#### Cách dùng
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from xoichim import GolikeAuth, auto_solve_captcha
|
|
78
|
+
|
|
79
|
+
auth = GolikeAuth.from_token(
|
|
80
|
+
"eyJ...",
|
|
81
|
+
enable_sig=True,
|
|
82
|
+
fetch_session=True,
|
|
83
|
+
captcha_solver=auto_solve_captcha,
|
|
84
|
+
captcha_max_attempts=3,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Instagram
|
|
88
|
+
auth.get("/advertising/publishers/instagram/jobs",
|
|
89
|
+
params={"instagram_account_id": "978936", "data": "null"})
|
|
90
|
+
|
|
91
|
+
# Threads
|
|
92
|
+
auth.get("/advertising/publishers/threads/jobs")
|
|
93
|
+
|
|
94
|
+
# Twitter / X
|
|
95
|
+
auth.get("/advertising/publishers/twitter/jobs",
|
|
96
|
+
params={"account_id": "132488"})
|
|
97
|
+
|
|
98
|
+
# YouTube
|
|
99
|
+
auth.get("/advertising/publishers/youtube/jobs",
|
|
100
|
+
params={"account_id": "61420"})
|
|
101
|
+
|
|
102
|
+
# Facebook
|
|
103
|
+
auth.get("/advertising/publishers/get-jobs-2026",
|
|
104
|
+
params={"fb_id": "61582501696566", "server": "sv2"})
|
|
105
|
+
|
|
106
|
+
# TikTok
|
|
107
|
+
auth.get("/advertising/publishers/tiktok/jobs",
|
|
108
|
+
params={"account_id": "708615", "data": "null"})
|
|
109
|
+
|
|
110
|
+
### 0.1.12 — Bundle `26.08.01.1` / `gweb-v2` + interactive challenge
|
|
111
|
+
print(r.status_code, r.json())
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
#### Kiểm tra map path
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from xoichim import resolve_sig_act
|
|
118
|
+
|
|
119
|
+
print(resolve_sig_act("/advertising/publishers/tiktok/jobs"))
|
|
120
|
+
# → ('tiktok', 'get_job')
|
|
121
|
+
|
|
122
|
+
print(resolve_sig_act("/advertising/publishers/get-jobs-2026"))
|
|
123
|
+
# → ('facebook', 'get_job')
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
#### Lưu ý quan trọng
|
|
127
|
+
|
|
128
|
+
1. **Phải truyền `captcha_solver`** nếu không muốn bị
|
|
129
|
+
`SecurityCaptchaRequired`:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
auth = GolikeAuth.from_token(
|
|
133
|
+
TOKEN,
|
|
134
|
+
enable_sig=True,
|
|
135
|
+
captcha_solver=auto_solve_captcha, # bắt buộc nếu hay bị captcha
|
|
136
|
+
)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
2. Path gateway **không** có prefix `/api` thừa:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
# Đúng
|
|
143
|
+
auth.get("/advertising/publishers/get-jobs-2026", params={...})
|
|
144
|
+
|
|
145
|
+
# Sai → 404 (base URL đã có /api)
|
|
146
|
+
auth.get("/api/advertising/publishers/get-jobs-2026", params={...})
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
3. `sig` **không reuse** được lâu / sang query khác.
|
|
150
|
+
Mỗi lần `auth.get(...)` thư viện mint `sig` mới gắn đúng path + query.
|
|
151
|
+
|
|
152
|
+
4. `enable_sig=False` (mặc định) → không mint sig cho bất kỳ platform nào
|
|
153
|
+
(giữ hành vi an toàn cho API không cần sig).
|
|
154
|
+
|
|
155
|
+
#### Breaking changes
|
|
156
|
+
|
|
157
|
+
Không có. API công khai cũ vẫn hoạt động:
|
|
158
|
+
|
|
159
|
+
- `resolve_tiktok_sig_act(path)` → vẫn trả `act` string hoặc `None`
|
|
160
|
+
- `GolikeAuth.from_token(..., enable_sig=True)` → TikTok như trước
|
|
161
|
+
- `mint_security_token(..., plt=..., act=...)` → vẫn nhận tham số thủ công
|
|
162
|
+
auth = GolikeAuth.from_token(
|
|
163
|
+
"eyJ...",
|
|
164
|
+
enable_sig=True,
|
|
165
|
+
fetch_session=True,
|
|
166
|
+
captcha_solver=auto_solve_captcha, # tự động giải
|
|
167
|
+
captcha_max_attempts=3,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
r = auth.get(
|
|
171
|
+
"/advertising/publishers/tiktok/jobs",
|
|
172
|
+
params={"account_id": "708615", "data": "null"},
|
|
173
|
+
)
|
|
174
|
+
print(r.status_code, r.json())
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Hoặc giải captcha riêng:
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
from gecmax import solve_security_captcha, auto_solve_captcha
|
|
181
|
+
|
|
182
|
+
ok = solve_security_captcha(
|
|
183
|
+
solver=auto_solve_captcha,
|
|
184
|
+
signing_key=auth.signing_key,
|
|
185
|
+
device_id=auth.device_id,
|
|
186
|
+
user_id=auth.user_id,
|
|
187
|
+
token=auth.token,
|
|
188
|
+
max_attempts=3,
|
|
189
|
+
)
|
|
190
|
+
print("captcha solved:", ok)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
#### Thuật toán path (tóm tắt)
|
|
194
|
+
|
|
195
|
+
1. Đoạn thẳng từ `start` → **tâm** `waypoint` (~45 điểm).
|
|
196
|
+
2. Đoạn thẳng từ tâm waypoint → **tâm** `target` (~50 điểm).
|
|
197
|
+
3. Giữ yên tại tâm target (5 điểm ổn định).
|
|
198
|
+
4. Timestamp tăng dần (`elapsed_ms`).
|
|
199
|
+
|
|
200
|
+
Vì mọi điểm đều đi qua tâm vòng tròn, tỷ lệ chấp nhận hình học thường
|
|
201
|
+
đạt **90–98%** khi server chỉ check geometry. Nên giữ
|
|
202
|
+
`captcha_max_attempts=3` để xử lý các lần fail hiếm.
|
|
203
|
+
|
|
204
|
+
#### Lưu ý
|
|
205
|
+
|
|
206
|
+
- `auto_solve_captcha` là **tùy chọn**. Vẫn có thể truyền callback UI
|
|
207
|
+
người dùng (như 0.1.11) nếu server bắt đầu chấm điểm giống người.
|
|
208
|
+
- Path job TikTok phải là dạng relative của gateway
|
|
209
|
+
(`/advertising/publishers/tiktok/jobs`), **không** có `/api` ở đầu
|
|
210
|
+
(base URL đã kết thúc bằng `/api`).
|
|
211
|
+
- `resolve_tiktok_sig_act` giờ cũng nhận diện
|
|
212
|
+
`publishers/tiktok/jobs` ngoài dạng cũ `/tiktok/jobs`.
|
|
213
|
+
|
|
214
|
+
- `/api/v1/security/session` sends `g-scheme: gweb-v2`.
|
|
215
|
+
- The pure-Python signer now matches the bundle's WASM `sign_v2` output.
|
|
216
|
+
- A session response may still report `schemeVersion: "gweb-v1"`; that field is
|
|
217
|
+
metadata and does not make bundle `26.08.01.1` downgrade its selected signer.
|
|
218
|
+
- If `/api/v1/security/token` returns `action/reason: "captcha"`, the client can
|
|
219
|
+
fetch `/security/captcha/challenge`, submit human pointer samples to
|
|
220
|
+
`/security/captcha/verify`, then retry the original token mint with the same
|
|
221
|
+
signing key.
|
|
222
|
+
- Session re-handshake remains separate: it happens only for a retryable mint
|
|
223
|
+
failure, not after a successful CAPTCHA.
|
|
224
|
+
|
|
225
|
+
The library does not automate or bypass the drag challenge. Supply a callback
|
|
226
|
+
that presents `challenge["geometry"]` to a user and returns the collected
|
|
227
|
+
`[elapsed_ms, x, y]` samples:
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
from xoichim import GolikeAuth
|
|
231
|
+
|
|
232
|
+
def collect_drag_points(challenge, message):
|
|
233
|
+
# Render the 300x200 logical geometry in your UI and collect real input.
|
|
234
|
+
# Return None if the user cancels.
|
|
235
|
+
return my_ui.collect_pointer_path(challenge["geometry"], message)
|
|
236
|
+
|
|
237
|
+
auth = GolikeAuth.from_token(
|
|
238
|
+
"eyJ...",
|
|
239
|
+
enable_sig=True,
|
|
240
|
+
captcha_solver=collect_drag_points,
|
|
241
|
+
)
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### 0.1.10 — Bundle `26.07.27.4` / `gweb-v1` TikTok signer
|
|
245
|
+
|
|
246
|
+
This supersedes the older sectoken scheme documented below:
|
|
247
|
+
|
|
248
|
+
- `/api/v1/security/session` now sends `g-scheme: gweb-v1`.
|
|
249
|
+
- `/api/v1/security/token` uses the bundle's `gweb-v1` signer (ported from
|
|
250
|
+
the embedded WASM to pure Python).
|
|
251
|
+
- The client keeps the optional gateway `firebase_id` key separate from the
|
|
252
|
+
security-session key used to mint TikTok `sig`.
|
|
253
|
+
- If token minting returns no token, the client refreshes the security session
|
|
254
|
+
and retries once, matching the web app.
|
|
255
|
+
- `mint_security_token(..., telemetry={...})` and
|
|
256
|
+
`request(..., sig_telemetry={...})` can include the bundle's optional `tel`
|
|
257
|
+
object.
|
|
258
|
+
|
|
259
|
+
To run the source checkout rather than an older wheel in `site-packages`:
|
|
260
|
+
|
|
261
|
+
```powershell
|
|
262
|
+
python -m pip install -e ".[curl]"
|
|
263
|
+
$env:GOLIKE_TOKEN="eyJ..."
|
|
264
|
+
python .\examples\z.py
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### 0.1.9 — `enable_sig` default `False`
|
|
268
|
+
|
|
269
|
+
`sig` chỉ dùng cho TikTok jobs/complete. Mặc định **tắt** để không ảnh hưởng API job nền tảng khác (Facebook, Instagram, …).
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
# Default — no sig on any request
|
|
273
|
+
auth = GolikeAuth.from_token("eyJ...")
|
|
274
|
+
|
|
275
|
+
# Facebook / multi jobs — no sig
|
|
276
|
+
auth.get("/advertising/publishers/get-jobs-2026", params={...})
|
|
277
|
+
|
|
278
|
+
# TikTok — must enable explicitly
|
|
279
|
+
auth = GolikeAuth.from_token("eyJ...", enable_sig=True)
|
|
280
|
+
auth.get(
|
|
281
|
+
"/advertising/publishers/tiktok/jobs",
|
|
282
|
+
params={"account_id": "711964", "data": "null"},
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
# Or per-request override
|
|
286
|
+
auth.get(".../tiktok/jobs", params={...}, with_sig=True)
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
| `enable_sig` | Behavior |
|
|
290
|
+
|---|---|
|
|
291
|
+
| `False` (default) | Never mint/send `sig` |
|
|
292
|
+
| `True` | Mint `sig` only for paths matching `/tiktok/jobs` or `/tiktok/complete-jobs` |
|
|
293
|
+
|
|
294
|
+
### 0.1.8 — TikTok `sig` encryption overhaul (JS bundle 24/7)
|
|
295
|
+
|
|
296
|
+
> Historical reference only. The sectoken `v3.1` algorithm in this section is
|
|
297
|
+
> not accepted by the current bundle; use the `gweb-v2` flow in 0.1.11 above.
|
|
298
|
+
|
|
299
|
+
App no longer puts a locally encrypted blob directly into `sig`.
|
|
300
|
+
TikTok jobs use a **two-step security API** on `api.golike.net`, with a **new AES-GCM scheme** separate from gateway `g-auth`.
|
|
301
|
+
|
|
302
|
+
#### Request flow
|
|
303
|
+
|
|
304
|
+
```text
|
|
305
|
+
JWT
|
|
306
|
+
│
|
|
307
|
+
├─① POST https://api.golike.net/api/v1/security/session
|
|
308
|
+
│ body: {}
|
|
309
|
+
│ → { signing_key, exp, epoch, schemeVersion: "v3.2" }
|
|
310
|
+
│
|
|
311
|
+
├─② POST https://api.golike.net/api/v1/security/token
|
|
312
|
+
│ body: { plt, act, req: { method, path, query, body } }
|
|
313
|
+
│ headers: Authorization, g-auth (sectoken scheme), g-device-id
|
|
314
|
+
│ → { token } ← this value is the gateway header `sig`
|
|
315
|
+
│
|
|
316
|
+
└─③ GET https://gateway.golike.net/api/advertising/publishers/tiktok/jobs?...
|
|
317
|
+
headers: Authorization, g-device-id, g-username, t, sig
|
|
318
|
+
(no g-auth on the gateway TikTok call)
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Missing `sig` on TikTok jobs → HTTP **403**
|
|
322
|
+
`Vui lòng tải lại trang để cập nhật phiên bản mới nhất.`
|
|
323
|
+
|
|
324
|
+
#### Two crypto schemes
|
|
325
|
+
|
|
326
|
+
| | Gateway `g-auth` (legacy / optional) | Sectoken mint (`security/token`) |
|
|
327
|
+
|---|---|---|
|
|
328
|
+
| Used for | Optional gateway binding | Build `g-auth` **only** for step ② |
|
|
329
|
+
| HKDF salt | `glk-gauth-v3-2026q3` | `glk-sectoken-v31-2026q3` |
|
|
330
|
+
| HKDF info | `aes-gcm-key` | `aes-gcm-key-v31` |
|
|
331
|
+
| Digest `r` | `sha256(t:deviceId:bodyHash:salt)[:16]` | `sha256(salt:t:userId:bodyHash)[:16]` |
|
|
332
|
+
| Extra field | — | `r2 = sha256hex(deviceId\|METHOD\|path\|salt)[16:40]` |
|
|
333
|
+
| Output header | `g-auth` | server returns `token` → client sends as **`sig`** |
|
|
334
|
+
|
|
335
|
+
Cipher for both: **AES-256-GCM**, output `base64url(iv12 ‖ ciphertext+tag)`.
|
|
336
|
+
|
|
337
|
+
Sectoken payload example (step ② `g-auth` plaintext):
|
|
338
|
+
|
|
339
|
+
```json
|
|
340
|
+
{
|
|
341
|
+
"t": 1784884100123,
|
|
342
|
+
"x": "<nonce b64url>",
|
|
343
|
+
"d": "<device-uuid>",
|
|
344
|
+
"u": 639111,
|
|
345
|
+
"n": "POST",
|
|
346
|
+
"k": "/api/v1/security/token",
|
|
347
|
+
"q": "<sha256 hex of mint body>",
|
|
348
|
+
"r": "<16 hex chars>",
|
|
349
|
+
"r2": "<24 hex chars>"
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Mint body (`req.path` is gateway pathname, including `/api/...`):
|
|
354
|
+
|
|
355
|
+
```json
|
|
356
|
+
{
|
|
357
|
+
"plt": "tiktok",
|
|
358
|
+
"act": "get_job",
|
|
359
|
+
"req": {
|
|
360
|
+
"method": "GET",
|
|
361
|
+
"path": "/api/advertising/publishers/tiktok/jobs",
|
|
362
|
+
"query": "account_id=711964&data=null",
|
|
363
|
+
"body": ""
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
`act` mapping:
|
|
369
|
+
|
|
370
|
+
| Gateway path contains | `act` |
|
|
371
|
+
|---|---|
|
|
372
|
+
| `/tiktok/jobs` (not skip) | `get_job` |
|
|
373
|
+
| `/tiktok/complete-jobs` | `complete_job` |
|
|
374
|
+
|
|
375
|
+
#### Library API (0.1.8)
|
|
376
|
+
|
|
377
|
+
```python
|
|
378
|
+
from xoichim import GolikeAuth
|
|
379
|
+
|
|
380
|
+
auth = GolikeAuth.from_token("eyJ...", enable_sig=True) # TikTok needs sig
|
|
381
|
+
# 1) GET gateway /users/me
|
|
382
|
+
# 2) POST security/session → signing_key
|
|
383
|
+
# 3) on TikTok get/post: mint security/token → header sig
|
|
384
|
+
|
|
385
|
+
r = auth.get(
|
|
386
|
+
"/advertising/publishers/tiktok/jobs",
|
|
387
|
+
params={"account_id": "711964", "data": "null"},
|
|
388
|
+
)
|
|
389
|
+
print(r.status_code, r.json())
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
Public helpers:
|
|
393
|
+
|
|
394
|
+
| Function | Role |
|
|
395
|
+
|---|---|
|
|
396
|
+
| `fetch_security_session(token)` | step ① |
|
|
397
|
+
| `mint_security_token(...)` / `generate_sig(...)` | step ② |
|
|
398
|
+
| `generate_sectoken_g_auth(...)` | local AES for step ② `g-auth` |
|
|
399
|
+
| `generate_g_auth(...)` | legacy gateway scheme |
|
|
400
|
+
| `auth.refresh_signing_key()` | refresh before `exp` |
|
|
401
|
+
|
|
402
|
+
Constants: `SALT`, `SECTOKEN_SALT`, `SECURITY_API`, `APP_VERSION` (`26.07.24.1`).
|
|
403
|
+
|
|
404
|
+
#### Breaking notes vs 0.1.6 / early 0.1.7
|
|
405
|
+
|
|
406
|
+
- Do **not** put gateway-style `generate_g_auth(...)` into header `sig` — server rejects it.
|
|
407
|
+
- Do **not** expect `firebase_id` from `/users/me`.
|
|
408
|
+
- `signing_key` comes from **`security/session`**, not profile.
|
|
409
|
+
- `sig` is a **server-minted** token from `security/token`, not a pure local encrypt of the jobs request.
|
|
410
|
+
|
|
411
|
+
### 0.1.7
|
|
412
|
+
|
|
413
|
+
- Auto `POST /security/session` in `from_token` (`fetch_session=True`).
|
|
414
|
+
- `fetch_security_session` / `refresh_signing_key`.
|
|
415
|
+
|
|
416
|
+
### 0.1.6
|
|
417
|
+
|
|
418
|
+
- Default **no** gateway `g-auth` on normal requests.
|
|
419
|
+
- Bearer + `g-device-id` + `g-username` + `t`.
|
|
420
|
+
- `/users/me` without `firebase_id`.
|
|
421
|
+
|
|
422
|
+
### 0.1.5
|
|
423
|
+
|
|
424
|
+
- Gỡ helper theo platform (`get_instagram_job`, `get_tiktok_job`, …).
|
|
425
|
+
- Auth dùng chung: `from_token` + `get` / `post` / `put` / `delete` / `request` / `headers`.
|
|
426
|
+
|
|
427
|
+
### 0.1.4
|
|
428
|
+
|
|
429
|
+
- `enable_sig` trên `GolikeAuth` / `from_token` (TikTok `sig`).
|
|
430
|
+
|
|
431
|
+
### 0.1.3
|
|
432
|
+
|
|
433
|
+
- Header `sig` TikTok; gộp query vào path khi ký.
|
|
434
|
+
|
|
435
|
+
### 0.1.2
|
|
436
|
+
|
|
437
|
+
- `from_token(token)` bootstrap `/users/me` + `firebase_id` (API cũ).
|
|
438
|
+
|
|
439
|
+
### 0.1.1 / 0.1.0
|
|
440
|
+
|
|
441
|
+
- HKDF + AES-GCM `g-auth`, `g-device-id`, header `t`.
|
|
442
|
+
|
|
443
|
+
### Nâng cấp
|
|
444
|
+
|
|
445
|
+
```bash
|
|
446
|
+
pip install -U "gecmax[curl]"
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
**Breaking (0.1.6):** mặc định **không** sinh `g-auth`. Code cũ dựa vào `auth.signing_key` / `auth.g_auth()` bắt buộc → truyền `enable_gauth=True` + `signing_key`, hoặc bỏ hẳn (API mới không cần).
|
|
450
|
+
|
|
451
|
+
**Breaking (0.1.5):** `get_tiktok_job` / `get_instagram_job` → dùng `auth.get` / `auth.post`.
|
|
452
|
+
|
|
453
|
+
## Install
|
|
454
|
+
|
|
455
|
+
```bash
|
|
456
|
+
pip install -U gecmax
|
|
457
|
+
|
|
458
|
+
# HTTP helper (curl_cffi) — khuyến nghị
|
|
459
|
+
pip install -U "gecmax[curl]"
|
|
460
|
+
|
|
461
|
+
# từ GitHub
|
|
462
|
+
pip install -U git+https://github.com/deno4908/golike-gauth.git
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
`curl_cffi` là dependency tùy chọn cho HTTP helper. Cài extra `[curl]` để bảo đảm phiên bản hỗ trợ fingerprint Safari iOS được dùng đồng nhất; `curl_cffi>=0.9.0` cũng tương thích với Python 3.8.
|
|
466
|
+
|
|
467
|
+
## Quick start
|
|
468
|
+
|
|
469
|
+
### Chỉ cần token (API 2026 — khuyến dùng)
|
|
470
|
+
|
|
471
|
+
```python
|
|
472
|
+
from xoichim import GolikeAuth
|
|
473
|
+
|
|
474
|
+
auth = GolikeAuth.from_token("eyJ...") # chỉ token
|
|
475
|
+
|
|
476
|
+
print(auth.user_id) # JWT sub
|
|
477
|
+
print(auth.username) # /users/me
|
|
478
|
+
print(auth.device_id) # UUID tự sinh
|
|
479
|
+
print(bool(auth.signing_key)) # security/session gweb key
|
|
480
|
+
print(bool(auth.gateway_signing_key)) # optional firebase_id gateway key
|
|
481
|
+
print(auth.profile) # raw /users/me
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
#### Flow `from_token` làm gì?
|
|
485
|
+
|
|
486
|
+
| Bước | Nguồn | Kết quả |
|
|
487
|
+
|---|---|---|
|
|
488
|
+
| 1 | Decode JWT | `user_id` = `sub` |
|
|
489
|
+
| 2 | `GET /users/me` (UA mobile) | `username`, profile, optional `firebase_id` gateway key |
|
|
490
|
+
| 3 | `POST /api/v1/security/session` | `signing_key` riêng cho `gweb-v2` |
|
|
491
|
+
| 4 | `device_id` | UUID v4 (tự tạo) |
|
|
492
|
+
| 5 | Headers | Base headers; TikTok adds `sig` and conditionally gateway `g-auth` when `firebase_id` exists |
|
|
493
|
+
|
|
494
|
+
```python
|
|
495
|
+
# Mặc định: tự handshake security/session
|
|
496
|
+
auth = GolikeAuth.from_token("eyJ...")
|
|
497
|
+
|
|
498
|
+
# Ép gateway g-auth với key riêng (hiếm)
|
|
499
|
+
auth = GolikeAuth.from_token(
|
|
500
|
+
"eyJ...",
|
|
501
|
+
signing_key="cxbbf6td1EXc...",
|
|
502
|
+
enable_gauth=True,
|
|
503
|
+
verify=True,
|
|
504
|
+
)
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
#### Lấy token từ đâu?
|
|
508
|
+
|
|
509
|
+
Trên https://app.golike.net (đã login, F12 → Network):
|
|
510
|
+
|
|
511
|
+
- Request bất kỳ → header `Authorization: Bearer eyJ...`
|
|
512
|
+
- Hoặc Application / Local Storage / vuex (field `token`)
|
|
513
|
+
|
|
514
|
+
#### Ví dụ: lấy job Facebook chỉ với token
|
|
515
|
+
|
|
516
|
+
```python
|
|
517
|
+
from xoichim import GolikeAuth
|
|
518
|
+
|
|
519
|
+
auth = GolikeAuth.from_token("eyJ...")
|
|
520
|
+
|
|
521
|
+
# list account FB tren Golike
|
|
522
|
+
accs = auth.get("/fb-account", params={"limit": 200}).json().get("data") or []
|
|
523
|
+
fb_id = accs[0]["fb_id"]
|
|
524
|
+
|
|
525
|
+
r = auth.get(
|
|
526
|
+
"/advertising/publishers/get-jobs-2026",
|
|
527
|
+
params={"fb_id": fb_id, "server": "sv2", "high_job": 1, "low_job": 1},
|
|
528
|
+
)
|
|
529
|
+
print(r.status_code, r.json())
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
Script mẫu trong workspace: `test_fb_jobs.py` (chỉ hỏi token).
|
|
533
|
+
|
|
534
|
+
### Thủ công (5 trường)
|
|
535
|
+
|
|
536
|
+
```python
|
|
537
|
+
from xoichim import GolikeAuth
|
|
538
|
+
|
|
539
|
+
auth = GolikeAuth(
|
|
540
|
+
token="eyJ...", # JWT Bearer
|
|
541
|
+
signing_key="...", # security/session key cho TikTok sig
|
|
542
|
+
gateway_signing_key="...", # optional firebase_id cho gateway g-auth
|
|
543
|
+
user_id=123456, # JWT sub
|
|
544
|
+
username="your_username",
|
|
545
|
+
device_id="32484704-8a4e-4909-9d42-866773b321d6", # nên giữ cố định
|
|
546
|
+
)
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
Lib dùng được với **mọi** path gateway. Chỉ cần đúng **method + path + body/query** như browser.
|
|
550
|
+
|
|
551
|
+
### Cách dùng chung (khuyến nghị)
|
|
552
|
+
|
|
553
|
+
```python
|
|
554
|
+
# GET — không body, params = query string
|
|
555
|
+
r = auth.get("/path/to/api", params={"key": "value"})
|
|
556
|
+
print(r.status_code, r.json())
|
|
557
|
+
|
|
558
|
+
# POST — có JSON body (g-auth ký đúng body compact, không space)
|
|
559
|
+
r = auth.post("/path/to/api", json={"a": 1, "b": "x"})
|
|
560
|
+
print(r.status_code, r.json())
|
|
561
|
+
|
|
562
|
+
# method bất kỳ
|
|
563
|
+
r = auth.request("PUT", "/path/to/api", json={...})
|
|
564
|
+
r = auth.request("DELETE", "/path/to/api")
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
### Ví dụ thật theo platform
|
|
568
|
+
|
|
569
|
+
#### Instagram — lấy job (GET)
|
|
570
|
+
|
|
571
|
+
```python
|
|
572
|
+
r = auth.get(
|
|
573
|
+
"/advertising/publishers/instagram/jobs",
|
|
574
|
+
params={
|
|
575
|
+
"instagram_account_id": "966624",
|
|
576
|
+
"data": "null",
|
|
577
|
+
},
|
|
578
|
+
)
|
|
579
|
+
# hoặc helper:
|
|
580
|
+
r = auth.get_instagram_job("966624")
|
|
581
|
+
print(r.json())
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
#### Instagram — skip job (POST, không phải GET!)
|
|
585
|
+
|
|
586
|
+
```python
|
|
587
|
+
r = auth.post(
|
|
588
|
+
"/advertising/publishers/instagram/skip-jobs",
|
|
589
|
+
json={
|
|
590
|
+
"ads_id": 620978,
|
|
591
|
+
"object_id": "6155111723",
|
|
592
|
+
"account_id": 966624,
|
|
593
|
+
"type": "follow", # follow | like | comment | ...
|
|
594
|
+
},
|
|
595
|
+
)
|
|
596
|
+
# hoặc helper:
|
|
597
|
+
r = auth.skip_instagram_job(
|
|
598
|
+
ads_id=620978,
|
|
599
|
+
object_id="6155111723",
|
|
600
|
+
account_id=966624,
|
|
601
|
+
type="follow",
|
|
602
|
+
)
|
|
603
|
+
print(r.json())
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
#### Instagram — complete job (POST)
|
|
607
|
+
|
|
608
|
+
```python
|
|
609
|
+
r = auth.post(
|
|
610
|
+
"/advertising/publishers/instagram/complete-jobs",
|
|
611
|
+
json={
|
|
612
|
+
"instagram_users_advertising_id": 620978,
|
|
613
|
+
"instagram_account_id": 966624,
|
|
614
|
+
"async": True,
|
|
615
|
+
"data": None,
|
|
616
|
+
},
|
|
617
|
+
)
|
|
618
|
+
# hoặc helper:
|
|
619
|
+
r = auth.complete_instagram_job(
|
|
620
|
+
instagram_users_advertising_id=620978,
|
|
621
|
+
instagram_account_id=966624,
|
|
622
|
+
)
|
|
623
|
+
print(r.json())
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
#### Twitter / X — lấy job (GET)
|
|
627
|
+
|
|
628
|
+
Tương đương curl:
|
|
629
|
+
|
|
630
|
+
`GET /api/advertising/publishers/twitter/jobs?account_id=97445`
|
|
631
|
+
|
|
632
|
+
```python
|
|
633
|
+
r = auth.get(
|
|
634
|
+
"/advertising/publishers/twitter/jobs",
|
|
635
|
+
params={"account_id": "97445"},
|
|
636
|
+
)
|
|
637
|
+
print(r.status_code, r.json())
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
#### TikTok / Facebook / … (cùng pattern)
|
|
641
|
+
|
|
642
|
+
```python
|
|
643
|
+
# GET jobs (query tùy platform — copy từ Network tab browser)
|
|
644
|
+
r = auth.get(
|
|
645
|
+
"/advertising/publishers/tiktok/jobs",
|
|
646
|
+
params={"account_id": "123"}, # hoặc param khác tùy API
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
# POST skip / complete — luôn dùng auth.post(..., json={...})
|
|
650
|
+
r = auth.post(
|
|
651
|
+
"/advertising/publishers/tiktok/skip-jobs",
|
|
652
|
+
json={...}, # body copy từ Network tab
|
|
653
|
+
)
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
#### Users / endpoint khác
|
|
657
|
+
|
|
658
|
+
```python
|
|
659
|
+
r = auth.get("/users/me")
|
|
660
|
+
r = auth.post("/some/path", json={"foo": "bar"})
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
### Sai thường gặp
|
|
664
|
+
|
|
665
|
+
```python
|
|
666
|
+
# ❌ SAI: auth.post() đã gọi API, không phải headers
|
|
667
|
+
# ❌ SAI: skip-jobs dùng GET → 405
|
|
668
|
+
from curl_cffi import requests as curl_requests
|
|
669
|
+
|
|
670
|
+
response = curl_requests.get(
|
|
671
|
+
"https://gateway.golike.net/api/advertising/publishers/instagram/skip-jobs",
|
|
672
|
+
headers=auth.post("/advertising/publishers/instagram/skip-jobs", json={...}),
|
|
673
|
+
)
|
|
674
|
+
|
|
675
|
+
# ✅ ĐÚNG
|
|
676
|
+
response = auth.post(
|
|
677
|
+
"/advertising/publishers/instagram/skip-jobs",
|
|
678
|
+
json={
|
|
679
|
+
"ads_id": 620978,
|
|
680
|
+
"object_id": "6155111723",
|
|
681
|
+
"account_id": 966624,
|
|
682
|
+
"type": "follow",
|
|
683
|
+
},
|
|
684
|
+
)
|
|
685
|
+
print(response.json())
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
Nếu tự gửi HTTP request bằng `curl_cffi`, **method + body bytes** phải khớp lúc ký:
|
|
689
|
+
|
|
690
|
+
```python
|
|
691
|
+
import json
|
|
692
|
+
from curl_cffi import requests as curl_requests
|
|
693
|
+
|
|
694
|
+
body = {
|
|
695
|
+
"ads_id": 620978,
|
|
696
|
+
"object_id": "6155111723",
|
|
697
|
+
"account_id": 966624,
|
|
698
|
+
"type": "follow",
|
|
699
|
+
}
|
|
700
|
+
raw = json.dumps(body, separators=(",", ":"), ensure_ascii=False) # không space
|
|
701
|
+
headers = auth.headers(
|
|
702
|
+
"POST",
|
|
703
|
+
"/advertising/publishers/instagram/skip-jobs",
|
|
704
|
+
body=raw,
|
|
705
|
+
)
|
|
706
|
+
response = curl_requests.post(
|
|
707
|
+
"https://gateway.golike.net/api/advertising/publishers/instagram/skip-jobs",
|
|
708
|
+
headers=headers,
|
|
709
|
+
data=raw.encode("utf-8"),
|
|
710
|
+
)
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
### Headers only / low-level
|
|
714
|
+
|
|
715
|
+
```python
|
|
716
|
+
from gecmax import generate_g_auth, generate_device_id, decode_g_auth
|
|
717
|
+
|
|
718
|
+
# headers: method/path/body phải trùng request thật
|
|
719
|
+
headers = auth.headers(
|
|
720
|
+
"GET",
|
|
721
|
+
"/advertising/publishers/twitter/jobs",
|
|
722
|
+
body="", # GET không body
|
|
723
|
+
)
|
|
724
|
+
|
|
725
|
+
g_auth = generate_g_auth(
|
|
726
|
+
method="GET",
|
|
727
|
+
path="/advertising/publishers/twitter/jobs",
|
|
728
|
+
body="",
|
|
729
|
+
signing_key="...",
|
|
730
|
+
device_id=generate_device_id(),
|
|
731
|
+
user_id=123456,
|
|
732
|
+
)
|
|
733
|
+
print(decode_g_auth(g_auth, "..."))
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
### Method cheatsheet
|
|
737
|
+
|
|
738
|
+
| API | Method | Params / Body |
|
|
739
|
+
|---|---|---|
|
|
740
|
+
| `/advertising/publishers/instagram/jobs` | **GET** | query: `instagram_account_id`, `data` |
|
|
741
|
+
| `/advertising/publishers/instagram/skip-jobs` | **POST** | JSON: `{ads_id, object_id, account_id, type}` |
|
|
742
|
+
| `/advertising/publishers/instagram/complete-jobs` | **POST** | JSON: `{instagram_users_advertising_id, instagram_account_id, async, data, ...}` |
|
|
743
|
+
| `/advertising/publishers/twitter/jobs` | **GET** | query: `account_id` |
|
|
744
|
+
| `/users/me` | **GET** | — |
|
|
745
|
+
| path khác | copy từ browser Network | **đúng method + body như browser** |
|
|
746
|
+
|
|
747
|
+
Quy tắc:
|
|
748
|
+
|
|
749
|
+
1. **GET** → `auth.get(path, params=...)` — body ký = `""`
|
|
750
|
+
2. **POST** → `auth.post(path, json=...)` — body ký = JSON compact
|
|
751
|
+
3. **405** = sai method (vd. GET `skip-jobs`)
|
|
752
|
+
4. **g-auth** tạo mới **mỗi request**, khớp method + path + body
|
|
753
|
+
5. Path truyền vào **không** cần `/api` prefix (`auth` tự thêm base `.../api`)
|
|
754
|
+
|
|
755
|
+
## Where to get `signing_key`
|
|
756
|
+
|
|
757
|
+
On https://app.golike.net (logged in), DevTools console:
|
|
758
|
+
|
|
759
|
+
```js
|
|
760
|
+
document.querySelector('#app').__vue__.$store.state.signing_key
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
> This value may differ from `data.firebase_id` in `/users/me`. Always use the store key the browser actually signs with.
|
|
764
|
+
|
|
765
|
+
## CLI
|
|
766
|
+
|
|
767
|
+
```bash
|
|
768
|
+
golike-gauth \
|
|
769
|
+
--token eyJ... \
|
|
770
|
+
--signing-key ... \
|
|
771
|
+
--user-id 123456 \
|
|
772
|
+
--username your_username \
|
|
773
|
+
--call --ig-account-id YOUR_IG_ACCOUNT_ID
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
## Notes
|
|
777
|
+
|
|
778
|
+
- `g-auth` must be **regenerated for every request** (binds method + path + body hash + timestamp).
|
|
779
|
+
- GET requests sign body as empty string `""`.
|
|
780
|
+
- Path signed is pathname only, e.g. `/api/advertising/publishers/instagram/jobs`.
|
|
781
|
+
|
|
782
|
+
## Development
|
|
783
|
+
|
|
784
|
+
```bash
|
|
785
|
+
git clone https://github.com/deno4908/golike-gauth.git
|
|
786
|
+
cd golike-gauth
|
|
787
|
+
python -m pip install -e ".[dev,curl]"
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
### Build & publish (PyPI)
|
|
791
|
+
|
|
792
|
+
```bash
|
|
793
|
+
python -m pip install -U build twine
|
|
794
|
+
python -m build
|
|
795
|
+
python -m twine upload dist/*
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
## Contributing
|
|
799
|
+
|
|
800
|
+
Issues and PRs: https://github.com/deno4908/golike-gauth/issues
|
|
801
|
+
|
|
802
|
+
## License
|
|
803
|
+
|
|
804
|
+
[MIT](LICENSE) — see [LICENSE](https://github.com/deno4908/golike-gauth/blob/main/LICENSE)
|
|
805
|
+
|
|
806
|
+
---
|
|
807
|
+
|
|
808
|
+
# Phân tích mã hóa / giải mã `g-auth` (Golike Gateway)
|
|
809
|
+
|
|
810
|
+
Tài liệu này mô tả cách app web Golike tạo header **`g-auth`** cho mỗi request tới `gateway.golike.net`, và cách server (hoặc client debug) giải mã token đó.
|
|
811
|
+
|
|
812
|
+
Nguồn reverse: bundle `index-68ef440b.js` (các hàm `H_`, `q_`, `Mg`, `M_`, `j_`, `Jd`, `aA`, `bu`).
|
|
813
|
+
|
|
814
|
+
---
|
|
815
|
+
|
|
816
|
+
## 1. Vai trò của `g-auth`
|
|
817
|
+
|
|
818
|
+
`g-auth` là **request-binding token**: mỗi request HTTP mang một token mới, gắn với:
|
|
819
|
+
|
|
820
|
+
| Trường payload | Ý nghĩa |
|
|
821
|
+
|---|---|
|
|
822
|
+
| `n` | HTTP method (`GET` / `POST` / …) |
|
|
823
|
+
| `k` | Pathname API (không có query) |
|
|
824
|
+
| `q` | SHA-256 hex của body |
|
|
825
|
+
| `d` | `g-device-id` (UUID) |
|
|
826
|
+
| `u` | `user_id` |
|
|
827
|
+
| `t` | timestamp client (ms) |
|
|
828
|
+
| `x` | nonce ngẫu nhiên |
|
|
829
|
+
| `r` | digest ngắn chống giả mạo thô |
|
|
830
|
+
|
|
831
|
+
Token được **mã hóa AES-GCM** bằng khóa dẫn xuất từ `signing_key` của user. Server giải mã → kiểm tra method/path/body/device/user/time → cho phép request.
|
|
832
|
+
|
|
833
|
+
> `g-auth` **không tái sử dụng** được giữa các request (path/body/time khác nhau, và có nonce).
|
|
834
|
+
|
|
835
|
+
---
|
|
836
|
+
|
|
837
|
+
## 2. Các header liên quan
|
|
838
|
+
|
|
839
|
+
Ngoài JWT `Authorization: Bearer …`, client còn gửi:
|
|
840
|
+
|
|
841
|
+
| Header | Nguồn / công thức |
|
|
842
|
+
|---|---|
|
|
843
|
+
| `g-auth` | AES-GCM token (mục 4–5) |
|
|
844
|
+
| `g-device-id` | UUID v4, lưu `localStorage.device_id` |
|
|
845
|
+
| `g-username` | username user |
|
|
846
|
+
| `g-version` | version app, ví dụ `26.07.10.2` |
|
|
847
|
+
| `g-client` | client id app |
|
|
848
|
+
| `t` | `btoa(btoa(btoa(unix_seconds)))` — timestamp 3 lớp Base64 |
|
|
849
|
+
|
|
850
|
+
---
|
|
851
|
+
|
|
852
|
+
## 3. `signing_key` — nguyên liệu khóa
|
|
853
|
+
|
|
854
|
+
### 3.1. Lấy key ở đâu?
|
|
855
|
+
|
|
856
|
+
Trong Vuex store:
|
|
857
|
+
|
|
858
|
+
```js
|
|
859
|
+
$store.state.signing_key
|
|
860
|
+
// DevTools:
|
|
861
|
+
document.querySelector('#app').__vue__.$store.state.signing_key
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
> **Lưu ý thực tế:** giá trị store **có thể khác** `data.firebase_id` trả về từ `GET /users/me`.
|
|
865
|
+
> Luôn dùng đúng key mà browser đang ký (store / console), không chỉ dựa vào field API.
|
|
866
|
+
|
|
867
|
+
Key là **32 bytes**, thường encode **Base64** (có `+`, `/`, `=`), ví dụ:
|
|
868
|
+
|
|
869
|
+
```text
|
|
870
|
+
cxbbf6td1EXcoEWlnk0eVmJwG1NJYhiqPxNcUXG+cBc=
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
Client cũng chấp nhận hex 64 ký tự.
|
|
874
|
+
|
|
875
|
+
### 3.2. Parse key (`k_`)
|
|
876
|
+
|
|
877
|
+
```
|
|
878
|
+
nếu chuỗi là hex hợp lệ và decode ra 32 bytes → dùng
|
|
879
|
+
else decode Base64 / Base64URL → 32 bytes → dùng
|
|
880
|
+
else lỗi: signing key must decode to 32 bytes
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
---
|
|
884
|
+
|
|
885
|
+
## 4. Dẫn xuất khóa AES (HKDF)
|
|
886
|
+
|
|
887
|
+
Trước khi AES-GCM, raw key 32 bytes được đưa qua **HKDF-SHA256**:
|
|
888
|
+
|
|
889
|
+
| Tham số | Giá trị |
|
|
890
|
+
|---|---|
|
|
891
|
+
| IKM | 32 bytes từ `signing_key` |
|
|
892
|
+
| Hash | SHA-256 |
|
|
893
|
+
| Salt | `glk-gauth-v3-2026q3` |
|
|
894
|
+
| Info | `aes-gcm-key` |
|
|
895
|
+
| Output length | 32 bytes |
|
|
896
|
+
|
|
897
|
+
```
|
|
898
|
+
AES_KEY = HKDF-SHA256(
|
|
899
|
+
ikm = decode(signing_key),
|
|
900
|
+
salt = "glk-gauth-v3-2026q3",
|
|
901
|
+
info = "aes-gcm-key",
|
|
902
|
+
len = 32
|
|
903
|
+
)
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
Hằng số trong JS:
|
|
907
|
+
|
|
908
|
+
```js
|
|
909
|
+
B_ = "glk-gauth"
|
|
910
|
+
E_ = "v3-2026" // sn(247)
|
|
911
|
+
S_ = "q3"
|
|
912
|
+
Sg = B_ + "-" + E_ + S_ // => "glk-gauth-v3-2026q3"
|
|
913
|
+
I_ = "aes-gcm-key"
|
|
914
|
+
```
|
|
915
|
+
|
|
916
|
+
`AES_KEY` import vào WebCrypto / OpenSSL dưới dạng **AES-256-GCM**.
|
|
917
|
+
|
|
918
|
+
---
|
|
919
|
+
|
|
920
|
+
## 5. Tạo payload trước khi mã hóa
|
|
921
|
+
|
|
922
|
+
### 5.1. Chuẩn hóa path (`Pg` + `yu`)
|
|
923
|
+
|
|
924
|
+
- Bỏ query (`?...`) và hash (`#...`)
|
|
925
|
+
- Ghép với base `https://gateway.golike.net/api`
|
|
926
|
+
- Chỉ lấy **pathname**
|
|
927
|
+
|
|
928
|
+
Ví dụ:
|
|
929
|
+
|
|
930
|
+
```text
|
|
931
|
+
request URL:
|
|
932
|
+
/advertising/publishers/instagram/jobs?instagram_account_id=966624&data=null
|
|
933
|
+
|
|
934
|
+
signed path k:
|
|
935
|
+
/api/advertising/publishers/instagram/jobs
|
|
936
|
+
```
|
|
937
|
+
|
|
938
|
+
### 5.2. Hash body (`j_`)
|
|
939
|
+
|
|
940
|
+
```text
|
|
941
|
+
body_str =
|
|
942
|
+
null/undefined → ""
|
|
943
|
+
string → chính nó
|
|
944
|
+
object → JSON.stringify(obj) // compact, không space
|
|
945
|
+
// JS: JSON.stringify → {"a":1}
|
|
946
|
+
|
|
947
|
+
q = SHA256_hex(body_str)
|
|
948
|
+
```
|
|
949
|
+
|
|
950
|
+
**GET / HEAD / DELETE** trong app: body ký = chuỗi rỗng `""`
|
|
951
|
+
|
|
952
|
+
```text
|
|
953
|
+
q = SHA256("")
|
|
954
|
+
= e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
### 5.3. Digest `r` (`M_`)
|
|
958
|
+
|
|
959
|
+
```text
|
|
960
|
+
r = SHA256_hex( f"{t}:{device_id}:{q}:{salt}" )[0:16]
|
|
961
|
+
```
|
|
962
|
+
|
|
963
|
+
với `salt = glk-gauth-v3-2026q3`, `t` là timestamp **milliseconds**.
|
|
964
|
+
|
|
965
|
+
### 5.4. Object payload đầy đủ
|
|
966
|
+
|
|
967
|
+
Thứ tự key (quan trọng vì `JSON.stringify` giữ insertion order):
|
|
968
|
+
|
|
969
|
+
```json
|
|
970
|
+
{
|
|
971
|
+
"t": 1783944428398,
|
|
972
|
+
"x": "z7Yv5E5ClYSc0gyyjllQ2w",
|
|
973
|
+
"d": "32484704-8a4e-4909-9d42-866773b321d6",
|
|
974
|
+
"u": 639111,
|
|
975
|
+
"n": "GET",
|
|
976
|
+
"k": "/api/advertising/publishers/instagram/jobs",
|
|
977
|
+
"q": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
978
|
+
"r": "286c4f5bd68ec1c4"
|
|
979
|
+
}
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
| Field | Mô tả |
|
|
983
|
+
|---|---|
|
|
984
|
+
| `t` | `Date.now()` (ms) |
|
|
985
|
+
| `x` | 16 bytes random → Base64URL (no padding) |
|
|
986
|
+
| `d` | device UUID (`g-device-id`) |
|
|
987
|
+
| `u` | user id (number) |
|
|
988
|
+
| `n` | method upper-case |
|
|
989
|
+
| `k` | pathname đã chuẩn hóa |
|
|
990
|
+
| `q` | body hash |
|
|
991
|
+
| `r` | digest 16 hex chars |
|
|
992
|
+
|
|
993
|
+
Chuỗi plaintext:
|
|
994
|
+
|
|
995
|
+
```text
|
|
996
|
+
plaintext = JSON.stringify(payload) // separators mặc định JS: không space
|
|
997
|
+
→ UTF-8 bytes
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
---
|
|
1001
|
+
|
|
1002
|
+
## 6. Mã hóa (encrypt) — `H_`
|
|
1003
|
+
|
|
1004
|
+
```
|
|
1005
|
+
IV = 12 bytes random (crypto.getRandomValues)
|
|
1006
|
+
CT = AES-256-GCM.Encrypt(
|
|
1007
|
+
key = AES_KEY,
|
|
1008
|
+
iv = IV,
|
|
1009
|
+
pt = plaintext_utf8
|
|
1010
|
+
)
|
|
1011
|
+
// CT = ciphertext || 16-byte auth tag (WebCrypto / cryptography)
|
|
1012
|
+
|
|
1013
|
+
token_bytes = IV || CT
|
|
1014
|
+
g-auth = Base64URL(token_bytes) // bỏ padding '='
|
|
1015
|
+
```
|
|
1016
|
+
|
|
1017
|
+
Sơ đồ:
|
|
1018
|
+
|
|
1019
|
+
```
|
|
1020
|
+
signing_key (b64/hex)
|
|
1021
|
+
│
|
|
1022
|
+
▼ decode 32B
|
|
1023
|
+
IKM
|
|
1024
|
+
│
|
|
1025
|
+
▼ HKDF-SHA256(salt, info)
|
|
1026
|
+
AES_KEY (32B)
|
|
1027
|
+
│
|
|
1028
|
+
│ ┌─ method, path, body, device, user, now ─┐
|
|
1029
|
+
│ ▼ │
|
|
1030
|
+
│ JSON payload {t,x,d,u,n,k,q,r} │
|
|
1031
|
+
│ ▼ UTF-8 │
|
|
1032
|
+
│ plaintext ──────────┐ │
|
|
1033
|
+
│ │ │
|
|
1034
|
+
▼ ▼ │
|
|
1035
|
+
AES-256-GCM(IV=12B) ←── encrypt │
|
|
1036
|
+
│ │
|
|
1037
|
+
▼ │
|
|
1038
|
+
IV || ciphertext+tag │
|
|
1039
|
+
│ │
|
|
1040
|
+
▼ Base64URL │
|
|
1041
|
+
header g-auth ───────────────────────────────────┘
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
---
|
|
1045
|
+
|
|
1046
|
+
## 7. Giải mã (decrypt) — `q_` / server / debug
|
|
1047
|
+
|
|
1048
|
+
```
|
|
1049
|
+
raw = Base64URL_decode(g-auth)
|
|
1050
|
+
IV = raw[0:12]
|
|
1051
|
+
CT = raw[12:] // ciphertext + tag
|
|
1052
|
+
|
|
1053
|
+
plaintext = AES-256-GCM.Decrypt(AES_KEY, IV, CT)
|
|
1054
|
+
payload = JSON.parse(plaintext)
|
|
1055
|
+
```
|
|
1056
|
+
|
|
1057
|
+
Sau khi giải mã, server thường kiểm tra:
|
|
1058
|
+
|
|
1059
|
+
1. **Decrypt OK** (key + IV + tag đúng)
|
|
1060
|
+
- Fail → `decrypt_fail_check_signing_key_or_iv_or_tag_or_r_field`
|
|
1061
|
+
2. **`n`** khớp HTTP method
|
|
1062
|
+
3. **`k`** khớp pathname canonical (vd. `/api/...`)
|
|
1063
|
+
4. **`q`** khớp SHA256 body thực tế nhận được
|
|
1064
|
+
5. **`d` / `u`** khớp session / JWT
|
|
1065
|
+
6. **`t`** không lệch quá xa server time (`ts_drift_ms`)
|
|
1066
|
+
7. **`r`** khớp lại công thức digest
|
|
1067
|
+
|
|
1068
|
+
Endpoint debug hữu ích:
|
|
1069
|
+
|
|
1070
|
+
```http
|
|
1071
|
+
POST /api/security/echo
|
|
1072
|
+
```
|
|
1073
|
+
|
|
1074
|
+
Response có block `gauth` mô tả lỗi decode / match.
|
|
1075
|
+
|
|
1076
|
+
---
|
|
1077
|
+
|
|
1078
|
+
## 8. Ví dụ end-to-end (GET jobs)
|
|
1079
|
+
|
|
1080
|
+
### Request
|
|
1081
|
+
|
|
1082
|
+
```http
|
|
1083
|
+
GET /api/advertising/publishers/instagram/jobs?instagram_account_id=966624&data=null
|
|
1084
|
+
Authorization: Bearer <jwt>
|
|
1085
|
+
g-device-id: 32484704-8a4e-4909-9d42-866773b321d6
|
|
1086
|
+
g-username: vinhhacker
|
|
1087
|
+
g-auth: Zmo3WFa5lp2X...
|
|
1088
|
+
t: VFZSak5FMTZhekJPUkZGNVQwRTlQUT09
|
|
1089
|
+
```
|
|
1090
|
+
|
|
1091
|
+
### Payload đã giải mã (thực tế từ browser)
|
|
1092
|
+
|
|
1093
|
+
```json
|
|
1094
|
+
{
|
|
1095
|
+
"t": 1783944428398,
|
|
1096
|
+
"x": "z7Yv5E5ClYSc0gyyjllQ2w",
|
|
1097
|
+
"d": "32484704-8a4e-4909-9d42-866773b321d6",
|
|
1098
|
+
"u": 639111,
|
|
1099
|
+
"n": "GET",
|
|
1100
|
+
"k": "/api/advertising/publishers/instagram/jobs",
|
|
1101
|
+
"q": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
1102
|
+
"r": "286c4f5bd68ec1c4"
|
|
1103
|
+
}
|
|
1104
|
+
```
|
|
1105
|
+
|
|
1106
|
+
### Tính lại `q` và `r`
|
|
1107
|
+
|
|
1108
|
+
```text
|
|
1109
|
+
q = SHA256("")
|
|
1110
|
+
= e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
|
1111
|
+
|
|
1112
|
+
r = SHA256(
|
|
1113
|
+
"1783944428398"
|
|
1114
|
+
+ ":" + "32484704-8a4e-4909-9d42-866773b321d6"
|
|
1115
|
+
+ ":" + q
|
|
1116
|
+
+ ":" + "glk-gauth-v3-2026q3"
|
|
1117
|
+
)[:16]
|
|
1118
|
+
= 286c4f5bd68ec1c4
|
|
1119
|
+
```
|
|
1120
|
+
|
|
1121
|
+
---
|
|
1122
|
+
|
|
1123
|
+
## 9. Header `t` (không nằm trong AES)
|
|
1124
|
+
|
|
1125
|
+
```js
|
|
1126
|
+
t = btoa(btoa(btoa(String(unix_seconds))))
|
|
1127
|
+
```
|
|
1128
|
+
|
|
1129
|
+
Ví dụ:
|
|
1130
|
+
|
|
1131
|
+
```text
|
|
1132
|
+
unix = 1783944424
|
|
1133
|
+
→ btoa × 3 → "VFZSak5FMTZhekJPUkZGNVQwRTlQUT09"
|
|
1134
|
+
```
|
|
1135
|
+
|
|
1136
|
+
Đây là timestamp phụ (giây), tách biệt field `t` (ms) bên trong `g-auth`.
|
|
1137
|
+
|
|
1138
|
+
---
|
|
1139
|
+
|
|
1140
|
+
## 10. Pseudocode Python
|
|
1141
|
+
|
|
1142
|
+
```python
|
|
1143
|
+
import base64, hashlib, json, secrets, time
|
|
1144
|
+
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
1145
|
+
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
|
1146
|
+
from cryptography.hazmat.primitives import hashes
|
|
1147
|
+
|
|
1148
|
+
SALT = "glk-gauth-v3-2026q3"
|
|
1149
|
+
INFO = "aes-gcm-key"
|
|
1150
|
+
|
|
1151
|
+
def b64url(data: bytes) -> str:
|
|
1152
|
+
return base64.urlsafe_b64encode(data).decode().rstrip("=")
|
|
1153
|
+
|
|
1154
|
+
def sha256_hex(s: str) -> str:
|
|
1155
|
+
return hashlib.sha256(s.encode()).hexdigest()
|
|
1156
|
+
|
|
1157
|
+
def derive_key(signing_key_b64: str) -> bytes:
|
|
1158
|
+
ikm = base64.b64decode(signing_key_b64)
|
|
1159
|
+
return HKDF(
|
|
1160
|
+
algorithm=hashes.SHA256(),
|
|
1161
|
+
length=32,
|
|
1162
|
+
salt=SALT.encode(),
|
|
1163
|
+
info=INFO.encode(),
|
|
1164
|
+
).derive(ikm)
|
|
1165
|
+
|
|
1166
|
+
def encrypt_g_auth(method, path, body, signing_key, device_id, user_id) -> str:
|
|
1167
|
+
key = derive_key(signing_key)
|
|
1168
|
+
iv = secrets.token_bytes(12)
|
|
1169
|
+
t = int(time.time() * 1000)
|
|
1170
|
+
q = sha256_hex(body if body is not None else "")
|
|
1171
|
+
r = sha256_hex(f"{t}:{device_id}:{q}:{SALT}")[:16]
|
|
1172
|
+
payload = {
|
|
1173
|
+
"t": t,
|
|
1174
|
+
"x": b64url(secrets.token_bytes(16)),
|
|
1175
|
+
"d": device_id,
|
|
1176
|
+
"u": user_id,
|
|
1177
|
+
"n": method.upper(),
|
|
1178
|
+
"k": path,
|
|
1179
|
+
"q": q,
|
|
1180
|
+
"r": r,
|
|
1181
|
+
}
|
|
1182
|
+
pt = json.dumps(payload, separators=(",", ":")).encode()
|
|
1183
|
+
ct = AESGCM(key).encrypt(iv, pt, None) # ct + tag
|
|
1184
|
+
return b64url(iv + ct)
|
|
1185
|
+
|
|
1186
|
+
def decrypt_g_auth(token: str, signing_key: str) -> dict:
|
|
1187
|
+
key = derive_key(signing_key)
|
|
1188
|
+
pad = "=" * (-len(token) % 4)
|
|
1189
|
+
raw = base64.urlsafe_b64decode(token.replace("-", "+").replace("_", "/") + pad)
|
|
1190
|
+
iv, ct = raw[:12], raw[12:]
|
|
1191
|
+
pt = AESGCM(key).decrypt(iv, ct, None)
|
|
1192
|
+
return json.loads(pt)
|
|
1193
|
+
```
|
|
1194
|
+
|
|
1195
|
+
---
|
|
1196
|
+
|
|
1197
|
+
## 11. Lỗi thường gặp
|
|
1198
|
+
|
|
1199
|
+
| Triệu chứng | Nguyên nhân |
|
|
1200
|
+
|---|---|
|
|
1201
|
+
| `decrypt_fail_...` | Sai `signing_key`, hỏng token, hoặc không đúng thuật toán HKDF/AES |
|
|
1202
|
+
| `AUTH_MISSING` | Không gửi `g-auth` (và/hoặc header bắt buộc khác) |
|
|
1203
|
+
| `403` “cập nhật phiên bản…” | Token thiếu/sai trên endpoint jobs; server từ chối client “cũ/không ký” |
|
|
1204
|
+
| 200 ở browser, 403 ở script | Reuse `g-auth` cũ; hoặc path/body hash khác (space trong JSON, thiếu `/api`, …) |
|
|
1205
|
+
| Decode local OK nhưng server fail | Key local ≠ key server đang expect; lấy lại từ `store.state.signing_key` |
|
|
1206
|
+
|
|
1207
|
+
---
|
|
1208
|
+
|
|
1209
|
+
## 12. Checklist implement đúng
|
|
1210
|
+
|
|
1211
|
+
1. `signing_key` = **store browser**, decode ra đúng 32 bytes
|
|
1212
|
+
2. HKDF salt = `glk-gauth-v3-2026q3`, info = `aes-gcm-key`
|
|
1213
|
+
3. Path ký = pathname có prefix `/api/...`, **không** query
|
|
1214
|
+
4. GET body ký = `""`
|
|
1215
|
+
5. POST body ký = **cùng bytes** body gửi đi (`JSON` compact nếu stringify)
|
|
1216
|
+
6. Payload JSON **không space**, đúng thứ tự key `t,x,d,u,n,k,q,r`
|
|
1217
|
+
7. AES-GCM IV 12 bytes, output `Base64URL(IV || CT||TAG)`
|
|
1218
|
+
8. **Mỗi request** tạo `g-auth` mới
|
|
1219
|
+
|
|
1220
|
+
---
|
|
1221
|
+
|
|
1222
|
+
## 13. Tóm tắt một dòng
|
|
1223
|
+
|
|
1224
|
+
> **`g-auth` = Base64URL( IV₁₂ ‖ AES-256-GCM<sub>HKDF(signing_key)</sub>( JSON{method, path, bodyHash, device, user, time, nonce, digest} ) )**
|