diff --git a/运营中枢/scripts/smsonline_get_token.py b/运营中枢/scripts/smsonline_get_token.py index 9498518c..8991c45f 100644 --- a/运营中枢/scripts/smsonline_get_token.py +++ b/运营中枢/scripts/smsonline_get_token.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ -自动打开 premium.smsonline.cloud,用已保存的 Google 登录态拿到 Firebase idToken。 -拿到后立刻下单 SoulApp 中国号码,轮询收验证码,超时自动退号。 +SMSOnline Premium 取号收码脚本 + +- 第一次:只用它打开 premium.smsonline.cloud,手动完成登录,脚本自动帮你截获 Firebase idToken。 +- 之后:命令行传入 service/country,一键完成「取号 → 轮询收码 → 超时退号」。 """ +import argparse import json import sys import time -import re import urllib.request from playwright.sync_api import sync_playwright @@ -45,6 +47,7 @@ def api_patch(url, token): PROFILE_DIR = "/Users/karuo/.smsonline_browser_profile" + def get_token_from_browser(): """用 Playwright persistent context 打开 premium 站,拦截请求拿到最新 Bearer token。 首次需手动 Google 登录,之后 cookie 保存在 profile 里自动登录。""" @@ -70,7 +73,7 @@ def get_token_from_browser(): page.goto(PREMIUM_URL, wait_until="networkidle", timeout=60000) except Exception: pass - for i in range(15): + for _ in range(15): if captured_token["value"]: break time.sleep(2) @@ -79,13 +82,13 @@ def get_token_from_browser(): page.reload(wait_until="networkidle", timeout=30000) except Exception: pass - for i in range(15): + for _ in range(15): if captured_token["value"]: break time.sleep(2) if not captured_token["value"]: print("未自动拿到 token,请在浏览器中手动登录…最多等 120 秒") - for i in range(60): + for _ in range(60): if captured_token["value"]: break time.sleep(2) @@ -93,6 +96,22 @@ def get_token_from_browser(): return captured_token["value"] +def normalize_country(country_arg: str) -> str: + """把命令行里的缩写(cn/us/gb)转换成 premium 接口里的 country 名称。""" + if not country_arg: + return "China" + m = country_arg.strip().lower() + mapping = { + "cn": "China", + "china": "China", + "us": "United States", + "usa": "United States", + "gb": "United Kingdom", + "uk": "United Kingdom", + } + return mapping.get(m, country_arg) + + def buy_and_receive(token, service="mx", country="China", network="network02"): """下单→轮询→收码→超时退号。""" url = f"{API_BASE}/numbers/{USER_ID}/order" @@ -103,7 +122,7 @@ def buy_and_receive(token, service="mx", country="China", network="network02"): "carrier": "any", "network": network, "m": "", - "n": "" + "n": "", } print(f"正在下单 {service} ({country}) …") result = api_put(url, token, body) @@ -117,6 +136,7 @@ def buy_and_receive(token, service="mx", country="China", network="network02"): print(f"号码:{number} 订单:{order_id}") print(f"轮询中(最多 {TIMEOUT_SEC} 秒)…") start = time.time() + sms = None while time.time() - start < TIMEOUT_SEC: time.sleep(POLL_INTERVAL) elapsed = int(time.time() - start) @@ -129,40 +149,86 @@ def buy_and_receive(token, service="mx", country="China", network="network02"): print(f"\n收到验证码!({elapsed}s)") print(f" 号码:{number}") print(f" 验证码:{sms}") - return number, sms + break print(f" 等待中 {elapsed}s… status={status}", flush=True) except Exception as e: print(f" 轮询出错 {elapsed}s: {e}", flush=True) - # 超时退号 - print(f"\n超时 {TIMEOUT_SEC}s 未收到验证码,正在取消退费…") - try: - refund_url = f"{API_BASE}/numbers/{USER_ID}/order/{network}/{order_id}/refund" - api_patch(refund_url, token) - print(" 已取消退费。") - except Exception as e: - print(f" 退费失败:{e}") - return number, None + if not sms: + # 超时退号 + print(f"\n超时 {TIMEOUT_SEC}s 未收到验证码,正在取消退费…") + try: + refund_url = f"{API_BASE}/numbers/{USER_ID}/order/{network}/{order_id}/refund" + api_patch(refund_url, token) + print(" 已取消退费。") + except Exception as e: + print(f" 退费失败:{e}") + return number, None + return number, sms def main(): + parser = argparse.ArgumentParser(description="SMSOnline Premium 自动取号收码") + parser.add_argument( + "--init-login", + action="store_true", + help="仅用于第一次:打开浏览器并完成登录,不下单号码", + ) + parser.add_argument( + "--service", + default="mx", + help="目标服务编码,例如 soul 对应的服务码(默认 mx)", + ) + parser.add_argument( + "--country", + default="cn", + help="国家代码或名称,例如 cn/us/gb(默认 cn=China)", + ) + parser.add_argument( + "--network", + default="network02", + help="网络编码,默认 network02(通常保持默认即可)", + ) + args = parser.parse_args() + print("=" * 50) print("SMSOnline Premium 自动取号收码") print("=" * 50) - # 1. 拿 token + + # 1. 拿 token(必须先有浏览器里已登录的 session) token = get_token_from_browser() if not token: print("ERROR: 未能获取 token,退出。") sys.exit(1) print(f"Token 获取成功(长度 {len(token)})") + + # 仅用于初始化登录:拿到 token 就结束 + if args.init_login: + print("初始化登录完成,后续可直接使用 --service/--country 下单收码。") + return + # 2. 下单 + 收码 - number, code = buy_and_receive(token) - if code: - print(f"\n{'='*50}") - print(f"号码:{number}") - print(f"验证码:{code}") - print(f"{'='*50}") + country_name = normalize_country(args.country) + number, code = buy_and_receive( + token, service=args.service, country=country_name, network=args.network + ) + + print("\n" + "=" * 50) + if number: + # 标准化输出,方便 Skill 与其他脚本复用 + if code: + print(f"号码:{number}") + print(f"验证码:{code}") + print(f"NUMBER: {number}") + print(f"SMS: {code}") + print(f"{number} | {code}") + else: + print(f"未收到验证码,号码 {number} 已退费或取消。") + print(f"NUMBER: {number}") + print("SMS: (无)") + print(f"{number} | (无)") else: - print(f"\n未收到验证码,号码 {number} 已退费。") + print("未能成功下单号码。") + print("=" * 50) if __name__ == "__main__": diff --git a/运营中枢/工作台/gitea_push_log.md b/运营中枢/工作台/gitea_push_log.md index 509d7693..af879047 100644 --- a/运营中枢/工作台/gitea_push_log.md +++ b/运营中枢/工作台/gitea_push_log.md @@ -343,3 +343,4 @@ | 2026-03-13 21:17:43 | 🔄 卡若AI 同步 2026-03-13 21:17 | 更新:运营中枢工作台 | 排除 >20MB: 11 个 | | 2026-03-13 21:26:54 | 🔄 卡若AI 同步 2026-03-13 21:26 | 更新:运营中枢工作台 | 排除 >20MB: 11 个 | | 2026-03-13 21:28:20 | 🔄 卡若AI 同步 2026-03-13 21:28 | 更新:运营中枢工作台 | 排除 >20MB: 11 个 | +| 2026-03-13 21:44:32 | 🔄 卡若AI 同步 2026-03-13 21:44 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 11 个 | diff --git a/运营中枢/工作台/skills_export.json b/运营中枢/工作台/skills_export.json index 7d22d7e5..c463d156 100644 --- a/运营中枢/工作台/skills_export.json +++ b/运营中枢/工作台/skills_export.json @@ -5,7 +5,8 @@ "member": "金仓", "group": "金", "triggers": "NAS、群晖、Docker", - "oneLiner": "NAS 部署、容器、存储池管理" + "oneLiner": "NAS 部署、容器、存储池管理", + "updated": "2026-03-13T14:05:36.285Z" }, { "id": "G02", @@ -125,7 +126,8 @@ "member": "金盾", "group": "金", "triggers": "远程部署、装Clash", - "oneLiner": "远程服务器环境配置" + "oneLiner": "远程服务器环境配置", + "updated": "2026-03-13T14:05:28.444Z" }, { "id": "G17", diff --git a/运营中枢/工作台/代码管理.md b/运营中枢/工作台/代码管理.md index 983c448c..f8677d59 100644 --- a/运营中枢/工作台/代码管理.md +++ b/运营中枢/工作台/代码管理.md @@ -346,3 +346,4 @@ | 2026-03-13 21:17:43 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-13 21:17 | 更新:运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-03-13 21:26:54 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-13 21:26 | 更新:运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-03-13 21:28:20 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-13 21:28 | 更新:运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | +| 2026-03-13 21:44:32 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-13 21:44 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |