feat: 登录时把二维码图片直接发到对话窗口
- 登录时不再只给文件路径,二维码图片会直接显示在对话里,扫一下就能登录 - 服务器无界面环境下同样支持扫码登录,扫对话里的图片即可 - 无界面服务器也可选择手机验证码登录,两种方式都支持 - 有桌面的设备会同时弹出浏览器窗口,浏览器和对话里的二维码都能扫 - 扫码后自动等待登录完成,不再需要反复查询登录状态 - 二维码图片自带白色边框,更容易被手机识别
Showing
2 changed files
with
46 additions
and
26 deletions
| @@ -111,21 +111,25 @@ def cmd_check_login(args: argparse.Namespace) -> None: | @@ -111,21 +111,25 @@ def cmd_check_login(args: argparse.Namespace) -> None: | ||
| 111 | if logged_in: | 111 | if logged_in: |
| 112 | _output({"logged_in": True}, exit_code=0) | 112 | _output({"logged_in": True}, exit_code=0) |
| 113 | else: | 113 | else: |
| 114 | + import platform | ||
| 114 | from chrome_launcher import has_display | 115 | from chrome_launcher import has_display |
| 116 | + system = platform.system() | ||
| 117 | + | ||
| 115 | if has_display(): | 118 | if has_display(): |
| 119 | + # 所有有界面环境(macOS/Windows/Linux 桌面):二维码显示在对话窗口 | ||
| 116 | _output({ | 120 | _output({ |
| 117 | "logged_in": False, | 121 | "logged_in": False, |
| 118 | "login_method": "qrcode", | 122 | "login_method": "qrcode", |
| 119 | - "hint": "请运行 login,Chrome 窗口会弹出二维码,扫码后自动完成登录", | 123 | + "hint": "请运行 get-qrcode 获取二维码,扫码后运行 wait-login 等待登录结果", |
| 120 | }, exit_code=1) | 124 | }, exit_code=1) |
| 121 | else: | 125 | else: |
| 122 | - # 无界面环境:二维码(扫对话窗口中的图片)和手机验证码均可 | 126 | + # 无界面服务器:二维码或手机验证码均可 |
| 123 | _output({ | 127 | _output({ |
| 124 | "logged_in": False, | 128 | "logged_in": False, |
| 125 | "login_method": "both", | 129 | "login_method": "both", |
| 126 | "hint": ( | 130 | "hint": ( |
| 127 | - "方式A: get-qrcode(二维码将显示在对话窗口,扫码即可);" | ||
| 128 | - "方式B: send-code --phone <手机号>(手机验证码)" | 131 | + "方式A: get-qrcode + wait-login(二维码显示在对话窗口);" |
| 132 | + "方式B: send-code --phone <手机号> + verify-code(手机验证码)" | ||
| 129 | ), | 133 | ), |
| 130 | }, exit_code=1) | 134 | }, exit_code=1) |
| 131 | finally: | 135 | finally: |
| @@ -214,7 +218,8 @@ def cmd_get_qrcode(args: argparse.Namespace) -> None: | @@ -214,7 +218,8 @@ def cmd_get_qrcode(args: argparse.Namespace) -> None: | ||
| 214 | 218 | ||
| 215 | 从登录弹窗的二维码 img 元素读取图片(data URL 或网络 URL), | 219 | 从登录弹窗的二维码 img 元素读取图片(data URL 或网络 URL), |
| 216 | 保存为本地 PNG 文件后立即退出。Chrome tab 保持打开,QR 会话继续有效。 | 220 | 保存为本地 PNG 文件后立即退出。Chrome tab 保持打开,QR 会话继续有效。 |
| 217 | - 调用方收到 qrcode_path 后用 Read 工具将图片显示在对话窗口,再轮询 check-login。 | 221 | + 调用方收到 qrcode_data_url 后直接内嵌到对话窗口显示;同时浏览器窗口(GUI 环境) |
| 222 | + 也会显示二维码,用户可选择扫任意一个。 | ||
| 218 | """ | 223 | """ |
| 219 | from xhs.login import fetch_qrcode, save_qrcode_to_file | 224 | from xhs.login import fetch_qrcode, save_qrcode_to_file |
| 220 | 225 | ||
| @@ -238,6 +243,27 @@ def cmd_get_qrcode(args: argparse.Namespace) -> None: | @@ -238,6 +243,27 @@ def cmd_get_qrcode(args: argparse.Namespace) -> None: | ||
| 238 | }) | 243 | }) |
| 239 | 244 | ||
| 240 | 245 | ||
| 246 | +def cmd_wait_login(args: argparse.Namespace) -> None: | ||
| 247 | + """等待扫码登录完成(配合 get-qrcode 使用)。 | ||
| 248 | + | ||
| 249 | + 连接已有 Chrome tab,内部轮询直到登录成功或超时,替代 Skill 层的多次 check-login 轮询。 | ||
| 250 | + """ | ||
| 251 | + from xhs.login import wait_for_login | ||
| 252 | + | ||
| 253 | + browser, page = _connect_existing(args) | ||
| 254 | + try: | ||
| 255 | + success = wait_for_login(page, timeout=args.timeout) | ||
| 256 | + _output( | ||
| 257 | + { | ||
| 258 | + "logged_in": success, | ||
| 259 | + "message": "登录成功" if success else "等待超时,请重新运行 get-qrcode 获取新二维码", | ||
| 260 | + }, | ||
| 261 | + exit_code=0 if success else 2, | ||
| 262 | + ) | ||
| 263 | + finally: | ||
| 264 | + browser.close() | ||
| 265 | + | ||
| 266 | + | ||
| 241 | def cmd_send_code(args: argparse.Namespace) -> None: | 267 | def cmd_send_code(args: argparse.Namespace) -> None: |
| 242 | """分步登录第一步:填写手机号并发送验证码,保持页面不关闭。""" | 268 | """分步登录第一步:填写手机号并发送验证码,保持页面不关闭。""" |
| 243 | from chrome_launcher import has_display, restart_chrome | 269 | from chrome_launcher import has_display, restart_chrome |
| @@ -727,6 +753,11 @@ def build_parser() -> argparse.ArgumentParser: | @@ -727,6 +753,11 @@ def build_parser() -> argparse.ArgumentParser: | ||
| 727 | sub = subparsers.add_parser("get-qrcode", help="获取登录二维码截图并立即返回(非阻塞)") | 753 | sub = subparsers.add_parser("get-qrcode", help="获取登录二维码截图并立即返回(非阻塞)") |
| 728 | sub.set_defaults(func=cmd_get_qrcode) | 754 | sub.set_defaults(func=cmd_get_qrcode) |
| 729 | 755 | ||
| 756 | + # wait-login(配合 get-qrcode,阻塞等待登录完成) | ||
| 757 | + sub = subparsers.add_parser("wait-login", help="等待扫码登录完成(配合 get-qrcode 使用)") | ||
| 758 | + sub.add_argument("--timeout", type=float, default=120.0, help="等待超时秒数 (default: 120)") | ||
| 759 | + sub.set_defaults(func=cmd_wait_login) | ||
| 760 | + | ||
| 730 | # phone-login(单命令交互式) | 761 | # phone-login(单命令交互式) |
| 731 | sub = subparsers.add_parser("phone-login", help="手机号+验证码登录(交互式,适合本地终端)") | 762 | sub = subparsers.add_parser("phone-login", help="手机号+验证码登录(交互式,适合本地终端)") |
| 732 | sub.add_argument("--phone", required=True, help="手机号(不含国家码,如 13800138000)") | 763 | sub.add_argument("--phone", required=True, help="手机号(不含国家码,如 13800138000)") |
| @@ -33,23 +33,12 @@ python scripts/cli.py check-login | @@ -33,23 +33,12 @@ python scripts/cli.py check-login | ||
| 33 | 33 | ||
| 34 | 输出解读: | 34 | 输出解读: |
| 35 | - `"logged_in": true` → 已登录,可执行后续操作。 | 35 | - `"logged_in": true` → 已登录,可执行后续操作。 |
| 36 | -- `"logged_in": false` + `"login_method": "qrcode"` → 有界面环境,直接走二维码登录。 | ||
| 37 | -- `"logged_in": false` + `"login_method": "both"` → 无界面服务器,**询问用户选择方式 A(二维码)或方式 B(手机验证码)**。 | 36 | +- `"logged_in": false` + `"login_method": "qrcode"` → 有界面环境,走方式 A(二维码)。 |
| 37 | +- `"logged_in": false` + `"login_method": "both"` → 无界面服务器,**询问用户选方式 A(二维码)或方式 B(手机验证码)**。 | ||
| 38 | 38 | ||
| 39 | -### 第二步:根据 login_method 选择登录方式 | 39 | +### 第二步:根据输出选择登录方式 |
| 40 | 40 | ||
| 41 | -#### 方式 A1:二维码登录 — 有界面(GUI)设备 | ||
| 42 | - | ||
| 43 | -```bash | ||
| 44 | -python scripts/cli.py login | ||
| 45 | -``` | ||
| 46 | - | ||
| 47 | -- Chrome **有窗口**弹出,二维码直接显示在浏览器窗口中。 | ||
| 48 | -- 告知用户用小红书 App 扫屏幕上的二维码。 | ||
| 49 | -- 命令阻塞等待(最多 120 秒),扫码成功后输出 `{"logged_in": true}`。 | ||
| 50 | -- 无需在对话窗口显示图片。 | ||
| 51 | - | ||
| 52 | -#### 方式 A2:二维码登录 — 无界面(headless)服务器 | 41 | +#### 方式 A:二维码登录(所有平台通用) |
| 53 | 42 | ||
| 54 | **第一步** — 获取二维码(非阻塞,立即返回): | 43 | **第一步** — 获取二维码(非阻塞,立即返回): |
| 55 | 44 | ||
| @@ -57,7 +46,7 @@ python scripts/cli.py login | @@ -57,7 +46,7 @@ python scripts/cli.py login | ||
| 57 | python scripts/cli.py get-qrcode | 46 | python scripts/cli.py get-qrcode |
| 58 | ``` | 47 | ``` |
| 59 | 48 | ||
| 60 | -- headless Chrome 加载登录页,从 `img` 元素读取二维码图片(相当于右键另存为)。 | 49 | +- Chrome 正常启动,从登录弹窗 `img` 元素读取二维码(相当于右键另存为)。 |
| 61 | - 命令立即退出,Chrome tab 保持打开(QR 会话继续有效)。 | 50 | - 命令立即退出,Chrome tab 保持打开(QR 会话继续有效)。 |
| 62 | - 输出:`{"qrcode_path": "...", "qrcode_data_url": "data:image/png;base64,...", "message": "..."}` | 51 | - 输出:`{"qrcode_path": "...", "qrcode_data_url": "data:image/png;base64,...", "message": "..."}` |
| 63 | 52 | ||
| @@ -67,16 +56,16 @@ python scripts/cli.py get-qrcode | @@ -67,16 +56,16 @@ python scripts/cli.py get-qrcode | ||
| 67 |  | 56 |  |
| 68 | ``` | 57 | ``` |
| 69 | 58 | ||
| 70 | -图片本身已含 16px 白色边框,内嵌在对话窗口,用户用小红书 App 扫对话里的二维码即可。 | 59 | +图片含 16px 白色边框,内嵌在对话窗口,用户用小红书 App 扫对话里的二维码。 |
| 71 | 60 | ||
| 72 | -**第三步** — 轮询登录状态(每 10 秒一次,最多 12 次): | 61 | +**第三步** — 等待登录完成(**单次调用,无需轮询**): |
| 73 | 62 | ||
| 74 | ```bash | 63 | ```bash |
| 75 | -python scripts/cli.py check-login | 64 | +python scripts/cli.py wait-login |
| 76 | ``` | 65 | ``` |
| 77 | 66 | ||
| 78 | -- `"logged_in": true` 则完成。 | ||
| 79 | -- 2 分钟内未登录,提示用户重新执行第一步。 | 67 | +- 连接已有 Chrome tab,内部阻塞等待(最多 120 秒)。 |
| 68 | +- 输出 `{"logged_in": true}` 则完成;超时则提示用户重新运行 `get-qrcode`。 | ||
| 80 | 69 | ||
| 81 | #### 方式 B:手机验证码登录(无界面服务器,分两步) | 70 | #### 方式 B:手机验证码登录(无界面服务器,分两步) |
| 82 | 71 |
-
Please register or login to post a comment