PrimeBot 启元 Q1 & T1 技能整活工作台
TOKEN EXTRACTOR · 通吃主流浏览器

全浏览器顺手牵 Token 神器源码精讲

自动从 Chrome / Edge / Brave / Chromium / Firefox 或 --token 提取登录凭证至 .local/ph_token.txt

😜 调皮课代表大白话划重点(30 秒读懂核心)
官方原版工具包最大的槽点就是「非逼着大家用 Firefox 浏览器登录,别的浏览器登了也白登」!我们对它进行了史诗级魔改:不仅保留了 Firefox 的 SQLite WAL 备份快照 + 纯标准库 Snappy 解压,还全新加入了 Chromium 内核(Chrome / Edge / Brave)LevelDB 快照扫描引擎(同时兼容 UTF-8、UTF-16LE 与 Snappy 块,并按 JWT 过期时间戳自动挑最新鲜的一条),甚至支持 F12 复制后用 --token 直接写入!

🔍 核心机关与修改指南

🌐 Chromium 全家桶引擎 Chrome / Edge / Brave

自动定位 Local Storage/leveldb,先拷贝到临时目录防浏览器锁文件,再从二进制流中精准捞出最新鲜的 skillhub_token。

🦊 Firefox 零锁库快照 SQLite Backup + Snappy

使用 SQLite 在线 backup() API 连还在 -wal 日志里的热乎登录态一起读出,再用纯标准库解开 Snappy 压缩。

🔒 0600 保险箱权限 绝不泄露明文

以 0o600 权限安全写入 .local/ph_token.txt,终端只打印字符数和来源,绝不把 JWT 明文印在屏幕上!

💻 完整源码与配置内容(共 354 行 · 支持行号定位与一键复制)

📄 共 354 行
1#!/usr/bin/env python3
2"""extract_token.py —— 从本机任意主流浏览器或环境变量/参数提取 PrimeHub 登录 token
3
4支持以下 4 种提取来源(不再局限于 Firefox!):
51. 【Chrome / Edge / Brave / Chromium】自动扫描 Local Storage/leveldb(复制快照防锁库,兼容 UTF-8 / UTF-16LE 与 Snappy 块)
62. 【Firefox(含 Windows MSIX / macOS / Linux)】自动扫描 storage/default/https+++primehub.primebot.com/ls/data.sqlite(含 WAL 快照与 Snappy 解压)
73. 【环境变量 PRIMEHUB_TOKEN】若设置了环境变量 PRIMEHUB_TOKEN,直接使用
84. 【命令行参数 --token】支持在任意浏览器按 F12 复制 skillhub_token 后通过 `--token "eyJ..."` 直接写入
9
10用法:
11 python3 extract_token.py [输出文件(默认 workspace/.local/ph_token.txt)]
12 python3 extract_token.py --browser auto|chrome|edge|firefox|brave|chromium [输出文件]
13 python3 extract_token.py --token "eyJhbG..." [输出文件]
14"""
15import argparse
16import base64
17import glob
18import json
19import os
20import re
21import shutil
22import sqlite3
23import sys
24import tempfile
25from pathlib import Path
26
27DEFAULT_TOKEN_FILE = os.environ.get(
28 "PRIMEHUB_TOKEN_FILE", str(Path(__file__).resolve().parents[2] / ".local/ph_token.txt")
29)
30
31JWT_ASCII_RE = re.compile(rb"eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}")
32
33
34def clean_token(raw: str) -> str:
35 """剥离首尾空白、外层单双引号及可能的 Bearer 前缀。"""
36 tok = raw.strip().strip('"').strip("'").strip()
37 if tok.lower().startswith("bearer "):
38 tok = tok[7:].strip()
39 return tok
40
41
42def jwt_timestamp_score(token: str) -> int:
43 """解析 JWT payload 中的 exp 或 iat 时间戳用于新旧排序;无法解析时返回 0。"""
44 parts = token.split(".")
45 if len(parts) != 3:
46 return 0
47 try:
48 payload_b64 = parts[1] + "=" * (-len(parts[1]) % 4)
49 payload = json.loads(base64.urlsafe_b64decode(payload_b64.encode("ascii")).decode("utf-8"))
50 if isinstance(payload, dict):
51 return int(payload.get("exp") or payload.get("iat") or 0)
52 except Exception:
53 pass
54 return 0
55
56
57def save_token_file(out_file: Path, token: str, source_label: str = "") -> None:
58 """以 0600 权限安全写入 token 文件,仅打印路径、字符数与来源,绝不打印 token 明文。"""
59 tok = clean_token(token)
60 out_file.parent.mkdir(parents=True, exist_ok=True)
61 descriptor = os.open(out_file, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
62 with os.fdopen(descriptor, "w", encoding="utf-8") as output:
63 output.write(tok)
64 suffix = (",来源: " + source_label) if source_label else ""
65 print("token → %s(%d 字符%s;有效期以服务端为准)" % (out_file, len(tok), suffix))
66
67
68# ==============================================================================
69# 来源 1:Firefox (SQLite + WAL + Snappy)
70# ==============================================================================
71
72def firefox_databases() -> list[Path]:
73 """支持 Windows、MSIX、macOS、Linux 及显式指定的 Firefox profile。"""
74 profile = os.environ.get("PRIMEHUB_FIREFOX_PROFILE")
75 roots = [Path(profile)] if profile else [
76 Path(os.environ.get("APPDATA", Path.home() / "AppData/Roaming"))
77 / "Mozilla/Firefox/Profiles",
78 Path.home() / "Library/Application Support/Firefox/Profiles",
79 Path.home() / ".mozilla/firefox",
80 Path.home() / "snap/firefox/common/.mozilla/firefox",
81 ]
82 if not profile and os.environ.get("LOCALAPPDATA"):
83 packages = Path(os.environ["LOCALAPPDATA"]) / "Packages"
84 roots.extend(packages.glob(
85 "Mozilla.MozillaFirefox_*/LocalCache/Roaming/Mozilla/Firefox/Profiles"))
86 suffix = "storage/default/https+++primehub.primebot.com/ls/data.sqlite"
87 databases = set()
88 for root in roots:
89 for pattern in (suffix, "*/" + suffix):
90 databases.update(Path(p) for p in glob.glob(glob.escape(str(root)) + "/" + pattern))
91 return sorted(databases, key=lambda p: p.stat().st_mtime, reverse=True)
92
93
94def read_storage(database: Path) -> dict:
95 """用 SQLite backup 读取一致快照,包含仍在 WAL 中的登录数据。"""
96 source = sqlite3.connect(database.resolve().as_uri() + "?mode=ro", uri=True)
97 snapshot = sqlite3.connect(":memory:")
98 try:
99 source.backup(snapshot)
100 return dict(snapshot.execute("SELECT key, value FROM data").fetchall())
101 finally:
102 snapshot.close()
103 source.close()
104
105
106def snappy_decompress(data: bytes) -> bytes:
107 """纯 stdlib snappy 解压:uvarint 声明长度 + literal/copy tag 流。"""
108 def uvarint(d, i):
109 v = s = 0
110 while True:
111 b = d[i]; i += 1
112 v |= (b & 0x7F) << s; s += 7
113 if not (b & 0x80):
114 break
115 return v, i
116
117 declared, i = uvarint(data, 0)
118 out = bytearray(); n = len(data)
119 while i < n:
120 tag = data[i]; i += 1
121 t = tag & 0x03
122 if t == 0: # literal
123 ln = tag >> 2
124 if ln < 60:
125 ln += 1
126 else:
127 extra = ln - 59
128 ln = int.from_bytes(data[i:i + extra], "little") + 1
129 i += extra
130 out += data[i:i + ln]; i += ln
131 else: # copy
132 if t == 1:
133 ln = ((tag >> 2) & 0x07) + 4
134 off = ((tag >> 5) << 8) | data[i]; i += 1
135 elif t == 2:
136 ln = (tag >> 2) + 1
137 off = int.from_bytes(data[i:i + 2], "little"); i += 2
138 else:
139 ln = (tag >> 2) + 1
140 off = int.from_bytes(data[i:i + 4], "little"); i += 4
141 for _ in range(ln):
142 out.append(out[-off])
143 assert len(out) == declared, (len(out), declared)
144 return bytes(out)
145
146
147def extract_from_firefox() -> str | None:
148 """从 Firefox 数据库列表提取 skillhub_token。"""
149 for db in firefox_databases():
150 try:
151 rows = read_storage(db)
152 if "skillhub_token" in rows:
153 return clean_token(snappy_decompress(rows["skillhub_token"]).decode("utf-8"))
154 except Exception:
155 continue
156 return None
157
158
159# ==============================================================================
160# 来源 2:Chrome / Edge / Brave / Chromium (LevelDB .log / .ldb)
161# ==============================================================================
162
163def chromium_user_data_roots(browser: str = "auto") -> list[tuple[str, Path]]:
164 """返回指定或全部 Chromium 内核浏览器的 User Data 或 Profile 根目录列表。"""
165 custom = os.environ.get("PRIMEHUB_CHROME_PROFILE") or os.environ.get("PRIMEHUB_BROWSER_PATH")
166 if custom:
167 return [("custom", Path(custom))]
168
169 local_app = Path(os.environ.get("LOCALAPPDATA", Path.home() / "AppData/Local"))
170 home = Path.home()
171 catalog = {
172 "chrome": [
173 local_app / "Google/Chrome/User Data",
174 home / "Library/Application Support/Google/Chrome",
175 home / ".config/google-chrome",
176 ],
177 "edge": [
178 local_app / "Microsoft/Edge/User Data",
179 home / "Library/Application Support/Microsoft Edge",
180 home / ".config/microsoft-edge",
181 ],
182 "brave": [
183 local_app / "BraveSoftware/Brave-Browser/User Data",
184 home / "Library/Application Support/BraveSoftware/Brave-Browser",
185 home / ".config/BraveSoftware/Brave-Browser",
186 ],
187 "chromium": [
188 local_app / "Chromium/User Data",
189 home / "Library/Application Support/Chromium",
190 home / ".config/chromium",
191 home / "snap/chromium/common/chromium",
192 ],
193 }
194 selected = catalog.keys() if browser == "auto" else [browser]
195 results = []
196 for name in selected:
197 for path in catalog.get(name, []):
198 results.append((name, path))
199 return results
200
201
202def chromium_leveldb_dirs(browser: str = "auto") -> list[tuple[str, Path]]:
203 """查找 Chromium 系浏览器所有 Profile 下的 Local Storage/leveldb 目录。"""
204 found = []
205 seen = set()
206 for name, root in chromium_user_data_roots(browser):
207 for pattern in ("Local Storage/leveldb", "Default/Local Storage/leveldb", "Profile */Local Storage/leveldb"):
208 for match in glob.glob(glob.escape(str(root)) + "/" + pattern):
209 p = Path(match)
210 if p.is_dir() and p not in seen:
211 seen.add(p)
212 found.append((name, p))
213 return found
214
215
216def _scan_bytes_for_skillhub_jwt(raw: bytes) -> list[str]:
217 """在字节流(支持 UTF-8/Latin1 与 UTF-16LE)中检索紧跟 skillhub_token 后的 JWT。"""
218 tokens = []
219 # 1. UTF-8 / Latin1 编码的 key + value
220 key_utf8 = b"skillhub_token"
221 pos = 0
222 while True:
223 idx = raw.find(key_utf8, pos)
224 if idx == -1:
225 break
226 window = raw[idx + len(key_utf8): idx + len(key_utf8) + 4096]
227 match = JWT_ASCII_RE.search(window)
228 if match and match.start() < 128:
229 tokens.append(match.group(0).decode("ascii"))
230 pos = idx + len(key_utf8)
231
232 # 2. UTF-16LE 编码的 key 或 value
233 key_utf16 = "skillhub_token".encode("utf-16le")
234 pos = 0
235 while True:
236 idx = raw.find(key_utf16, pos)
237 if idx == -1:
238 break
239 window = raw[idx + len(key_utf16): idx + len(key_utf16) + 8192]
240 # 压缩奇数位 0x00 还原 ASCII 视图
241 collapsed = window[0::2]
242 match = JWT_ASCII_RE.search(collapsed)
243 if match and match.start() < 128:
244 tokens.append(match.group(0).decode("ascii"))
245 pos = idx + len(key_utf16)
246 return tokens
247
248
249def read_chromium_leveldb_token(leveldb_dir: Path) -> str | None:
250 """拷贝 LevelDB 快照并提取最新的 skillhub_token。"""
251 candidates = []
252 with tempfile.TemporaryDirectory(prefix="primehub_ldb_") as tmp_dir:
253 tmp_path = Path(tmp_dir)
254 files = sorted(
255 [p for p in leveldb_dir.iterdir() if p.suffix in (".log", ".ldb", ".sst")],
256 key=lambda p: p.stat().st_mtime,
257 )
258 for src_file in files:
259 try:
260 dst_file = tmp_path / src_file.name
261 shutil.copy2(src_file, dst_file)
262 data = dst_file.read_bytes()
263 except Exception:
264 continue
265 mtime = int(src_file.stat().st_mtime)
266 for tok in _scan_bytes_for_skillhub_jwt(data):
267 candidates.append((jwt_timestamp_score(tok), mtime, tok))
268 # 若整个文件/块为纯 Snappy 流,也尝试解压后扫描
269 try:
270 decompressed = snappy_decompress(data)
271 for tok in _scan_bytes_for_skillhub_jwt(decompressed):
272 candidates.append((jwt_timestamp_score(tok), mtime, tok))
273 except Exception:
274 pass
275 if not candidates:
276 return None
277 # 按 (JWT过期时间/签发时间, 文件修改时间) 升序排,取最新一条
278 candidates.sort(key=lambda item: (item[0], item[1]))
279 return clean_token(candidates[-1][2])
280
281
282def extract_from_chromium(browser: str = "auto") -> tuple[str, str] | None:
283 """从 Chrome / Edge / Brave / Chromium 提取最新 token,返回 (browser_name, token)。"""
284 best = None # (jwt_score, dir_mtime, browser_name, token)
285 for name, ldb_dir in chromium_leveldb_dirs(browser):
286 tok = read_chromium_leveldb_token(ldb_dir)
287 if tok:
288 score = (jwt_timestamp_score(tok), int(ldb_dir.stat().st_mtime), name, tok)
289 if best is None or score[:2] >= best[:2]:
290 best = score
291 if best:
292 return best[2], best[3]
293 return None
294
295
296def main():
297 parser = argparse.ArgumentParser(
298 description="从 Firefox / Chrome / Edge / Brave / Chromium 或命令行提取 PrimeHub 登录 token"
299 )
300 parser.add_argument("out_file", nargs="?", default=DEFAULT_TOKEN_FILE, help="输出 token 文件路径")
301 parser.add_argument(
302 "--browser",
303 choices=("auto", "firefox", "chrome", "edge", "brave", "chromium"),
304 default="auto",
305 help="指定浏览器来源(默认 auto:自动扫描 Firefox 与 Chrome/Edge 等)",
306 )
307 parser.add_argument("--token", help="直接传入从浏览器 DevTools 复制的 skillhub_token 字符串")
308 args = parser.parse_args()
309 out_file = Path(args.out_file)
310
311 # 1. 优先支持显式 --token 参数或 PRIMEHUB_TOKEN 环境变量
312 direct_token = args.token or os.environ.get("PRIMEHUB_TOKEN")
313 if direct_token and clean_token(direct_token):
314 save_token_file(out_file, direct_token, "direct")
315 return
316
317 # 2. Firefox 提取路径
318 if args.browser in ("auto", "firefox"):
319 dbs = firefox_databases()
320 for db in dbs:
321 try:
322 rows = read_storage(db)
323 except Exception:
324 continue
325 if "skillhub_token" in rows:
326 tok = snappy_decompress(rows["skillhub_token"]).decode("utf-8").strip('"')
327 save_token_file(out_file, tok, "firefox")
328 return
329 if args.browser == "firefox":
330 if not dbs:
331 raise SystemExit("没找到 Firefox 的 primehub localStorage(先在 Firefox 登录一次 PrimeHub)")
332 print("没找到 skillhub_token——Firefox 登录态过期,重新登录一次", file=sys.stderr)
333 sys.exit(1)
334
335 # 3. Chrome / Edge / Brave / Chromium 提取路径
336 if args.browser in ("auto", "chrome", "edge", "brave", "chromium"):
337 chromium_hit = extract_from_chromium(args.browser)
338 if chromium_hit:
339 browser_name, tok = chromium_hit
340 save_token_file(out_file, tok, browser_name)
341 return
342
343 # 4. 全部未命中时给出清晰的可操作提示(保留 Firefox 关键字以兼容既有自动化断言)
344 if args.browser == "auto" and firefox_databases():
345 print("没找到 skillhub_token——Firefox / Chrome / Edge 登录态过期,请重新登录一次", file=sys.stderr)
346 sys.exit(1)
347 raise SystemExit(
348 "没找到 Firefox / Chrome / Edge 的 primehub localStorage"
349 "(请先在任意主流浏览器登录一次 https://primehub.primebot.com,或使用 --token 传入)"
350 )
351
352
353if __name__ == "__main__":
354 main()