"""一次性探测:微信 ClawBot 灰度是否覆盖某个微信号。 只做两件事(不碰 zcbot 主体、不落库): 1. GET get_bot_qrcode 拿二维码 -> 存 qr.png 并自动打开 2. 轮询 get_qrcode_status 等扫码确认 -> 报告 status 判读: - 接口连不通 / 非 200 -> 本机到 ilinkai 网络不通,换网或在有网机器跑 - 出码成功、手机扫得动确认 -> 该微信号在灰度内,ClawBot 可用 - 出码成功、扫了报"不支持" -> 版本不够或未灰度到该号 ASCII-only 输出(Windows GBK 控制台)。 """ from __future__ import annotations import base64 import os import random import sys import time import webbrowser import httpx BASE = "https://ilinkai.weixin.qq.com" QR_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "clawbot_qr.png") def _uin_header() -> str: # X-WECHAT-UIN: base64(String(randomUint32())) return base64.b64encode(str(random.randint(0, 2**32 - 1)).encode()).decode() def _headers() -> dict: return { "Content-Type": "application/json", "AuthorizationType": "ilink_bot_token", "X-WECHAT-UIN": _uin_header(), } def _save_qr(img_content: str, qrcode_id: str) -> bool: """实测:qrcode_img_content 是微信深链(https://liteapp.weixin.qq.com/q/...), 需把该 URL **编码成二维码** 让微信扫,而非当图片下载。 兜底:若哪天返回的是真图片字节(data-uri / base64 PNG)则直接存。 """ try: if not img_content: print(f"[hint] no img content; encode this id manually: {qrcode_id}") return False # 情况 A:真图片字节 if img_content.startswith("data:image"): data = base64.b64decode(img_content.split(",", 1)[1]) with open(QR_PATH, "wb") as f: f.write(data) print(f"[ok] QR (image) saved -> {QR_PATH}") return True # 情况 B(实测):深链 / 任意字符串 -> 自己渲染成二维码 import segno print(f"[info] encoding deep-link into QR: {img_content}") segno.make(img_content, error="m").save(QR_PATH, scale=8, border=3) print(f"[ok] QR (rendered from deep-link) saved -> {QR_PATH}") return True except Exception as e: print(f"[warn] could not build QR: {type(e).__name__}: {e}") print(f"[hint] deep-link to scan manually: {img_content}") return False def main() -> int: print("[step1] GET get_bot_qrcode ...") try: with httpx.Client(timeout=20) as c: r = c.get( f"{BASE}/ilink/bot/get_bot_qrcode", params={"bot_type": "3"}, headers=_headers(), ) except Exception as e: print(f"[FAIL] network error to {BASE}: {type(e).__name__}: {e}") print("[judge] host cannot reach ilinkai.weixin.qq.com -> try another network.") return 2 print(f"[http] status={r.status_code}") body_preview = r.text[:600] print(f"[body] {body_preview}") if r.status_code != 200: print("[judge] non-200 from get_bot_qrcode -> endpoint/params may be wrong or blocked.") return 3 try: data = r.json() except Exception: print("[FAIL] response not JSON; see body above.") return 3 qrcode_id = data.get("qrcode") or data.get("qrcode_id") or "" img = data.get("qrcode_img_content") or data.get("qrcode_img") or "" if not qrcode_id: print("[FAIL] no 'qrcode' field in response; field names differ -> inspect body above.") return 3 if _save_qr(img, qrcode_id): try: webbrowser.open("file://" + QR_PATH.replace("\\", "/")) except Exception: pass print("[action] QR opened. Scan it with your phone WeChat NOW.") else: print("[action] QR image unavailable; cannot open. See hint above.") poll_secs = int(sys.argv[1]) if len(sys.argv) > 1 else 100 print(f"[step2] polling get_qrcode_status (up to ~{poll_secs}s; Ctrl-C to stop)...") deadline = time.time() + poll_secs last = "" with httpx.Client(timeout=40) as c: while time.time() < deadline: try: r = c.get( f"{BASE}/ilink/bot/get_qrcode_status", params={"qrcode": qrcode_id}, headers=_headers(), ) st = "" try: st = (r.json() or {}).get("status", "") except Exception: st = f"(non-json http {r.status_code})" if st != last: print(f"[poll] status={st!r}") last = st if st == "confirmed": j = r.json() tok = j.get("bot_token", "") base_url = j.get("baseurl") or j.get("base_url") or "" masked = (tok[:6] + "..." + tok[-4:]) if len(tok) > 12 else "(short)" print("[SUCCESS] scan confirmed -> this WeChat account IS in the ClawBot rollout.") print(f"[SUCCESS] bot_token={masked} baseurl={base_url}") print("[note] token masked on purpose; it is a per-user credential.") return 0 except Exception as e: print(f"[poll] error: {type(e).__name__}: {e}") time.sleep(2) print("[timeout] no confirmation within window. Either not scanned in time, or") print("[timeout] your WeChat lacks the ClawBot entry (version <8.0.70 or not gray-rolled).") return 1 if __name__ == "__main__": sys.exit(main())