probe_sms_endpoint_v2.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. """Wider SMS endpoint probe — scan all 3 996box hosts × common paths.
  2. Found 1 endpoint so far: /auth/v3/sms/sendLoginCode (exists but requires auth)
  3. Looking for: the unauthenticated SMS-send endpoint that fires on first login.
  4. """
  5. import asyncio
  6. import sys
  7. from pathlib import Path
  8. _HERE = Path(__file__).resolve().parent
  9. sys.path.insert(0, str(_HERE.parent / "src"))
  10. from cq2_sdk.env_loader import load_env
  11. load_env(_HERE.parent / ".env")
  12. from cq2_sdk.login import Box996Login, ManualGeetestSolver
  13. HOSTS = [
  14. "https://api.jiyuhz.cn", # 密码登录用的 (api.jiyuhz.cn/auth/v3/login/safe/byPwd)
  15. "https://pc-api.996hezi.com", # 业务 API (getTodayOpen 等)
  16. "https://user-api.996hezi.com", # 用户 API
  17. ]
  18. # 各种 SMS 相关 endpoint 命名模式
  19. CANDIDATE_PATHS = [
  20. # api.jiyuhz.cn 风格 (auth/ 前缀)
  21. "/auth/v3/sms/sendLoginCode", # 已确认 200 but 需要 auth
  22. "/auth/v3/login/sms/send",
  23. "/auth/v3/login/sendSmsCode",
  24. "/auth/v3/login/safe/sendSms",
  25. "/auth/v3/login/sendSms",
  26. "/auth/v3/login/sendLoginCode",
  27. "/auth/v3/login/sendCode",
  28. "/auth/v3/login/sendSmsCode",
  29. # 业务 API 风格 (无 /auth/ 前缀)
  30. "/v1/sms/sendCode",
  31. "/v1/sms/sendLoginCode",
  32. "/v1/sms/send",
  33. # user-api 风格
  34. "/v1/user/sendSms",
  35. "/v1/user/sendLoginCode",
  36. # 其它
  37. "/sms/send",
  38. "/sms/sendCode",
  39. ]
  40. async def probe(box, host: str, path: str, phone: str = "00000000000") -> dict:
  41. """Returns dict with url, status, code, msg, is_real."""
  42. url = f"{host}{path}"
  43. try:
  44. r = await box._request("POST", host, path,
  45. json_body={"phone": phone, "channel_id": "",
  46. "game_id": "", "inviter_uid": "",
  47. "needConfirmLogin": True})
  48. # 200 response: 996box returned valid JSON
  49. data_code = r.get("code", 0) if isinstance(r, dict) else 0
  50. msg = r.get("msg", "") if isinstance(r, dict) else ""
  51. # 区分: "需要登录" vs 真正 SMS 发送响应
  52. is_real_sms = (
  53. data_code == 200 and r.get("data", {}).get("expire") is not None
  54. )
  55. return {"url": url, "http": 200, "code": data_code, "msg": msg[:60],
  56. "is_real_sms": is_real_sms}
  57. except Exception as e:
  58. from cq2_sdk.login.box_996 import Box996Error, CaptchaRequiredError
  59. msg = str(e)[:80]
  60. if "404" in msg:
  61. return {"url": url, "http": 404, "code": 0, "msg": "404", "is_real_sms": False}
  62. if isinstance(e, CaptchaRequiredError):
  63. return {"url": url, "http": 200, "code": 101000, "msg": "geetest",
  64. "is_real_sms": False}
  65. if isinstance(e, Box996Error):
  66. return {"url": url, "http": 200, "code": e.code, "msg": e.msg[:60],
  67. "is_real_sms": False}
  68. return {"url": url, "http": 0, "code": 0, "msg": msg, "is_real_sms": False}
  69. async def main() -> int:
  70. box = Box996Login(geetest=ManualGeetestSolver(), device_id="00:15:5d:0a:04:00")
  71. print(f"Probing {len(HOSTS)} hosts × {len(CANDIDATE_PATHS)} paths "
  72. f"= {len(HOSTS)*len(CANDIDATE_PATHS)} candidates\n")
  73. results = []
  74. for host in HOSTS:
  75. print(f"--- {host} ---")
  76. for path in CANDIDATE_PATHS:
  77. r = await probe(box, host, path)
  78. results.append(r)
  79. mark = ""
  80. if r["is_real_sms"]:
  81. mark = "[!!! REAL SMS !!!]"
  82. elif r["http"] == 200 and r["code"] in (200, 101000):
  83. mark = "[reachable]"
  84. elif r["http"] == 404:
  85. mark = "[404]"
  86. print(f" {mark:<16} {r['url']:<55} code={r['code']:>4} {r['msg']}")
  87. print()
  88. real = [r for r in results if r["is_real_sms"]]
  89. auth_req = [r for r in results if r["http"] == 200 and r["code"] not in (200, 101000)
  90. and "登录" in r["msg"]]
  91. if real:
  92. print(f"[!!! FOUND !!!] Real SMS endpoints: {len(real)}")
  93. for r in real:
  94. print(f" {r['url']}")
  95. elif auth_req:
  96. print(f"[*] {len(auth_req)} endpoint(s) require auth (rejected our fake phone):")
  97. for r in auth_req:
  98. print(f" {r['url']} code={r['code']} msg={r['msg']}")
  99. else:
  100. print("[!] No real SMS endpoint found. 996box may have a different flow now.")
  101. print(" → 建议你抓浏览器 Network 包: 打开 https://pc.hzwlan.com/ → 输手机号 → 点发验证码 → 看 F12 里的 URL")
  102. return 0
  103. if __name__ == "__main__":
  104. sys.exit(asyncio.run(main()))