Angiin

feat: 登录时把二维码图片直接发到对话窗口

- 登录时不再只给文件路径,二维码图片会直接显示在对话里,扫一下就能登录
- 服务器无界面环境下同样支持扫码登录,扫对话里的图片即可
- 无界面服务器也可选择手机验证码登录,两种方式都支持
- 有桌面的设备会同时弹出浏览器窗口,浏览器和对话里的二维码都能扫
- 扫码后自动等待登录完成,不再需要反复查询登录状态
- 二维码图片自带白色边框,更容易被手机识别
... ... @@ -111,21 +111,25 @@ def cmd_check_login(args: argparse.Namespace) -> None:
if logged_in:
_output({"logged_in": True}, exit_code=0)
else:
import platform
from chrome_launcher import has_display
system = platform.system()
if has_display():
# 所有有界面环境(macOS/Windows/Linux 桌面):二维码显示在对话窗口
_output({
"logged_in": False,
"login_method": "qrcode",
"hint": "请运行 login,Chrome 窗口会弹出二维码,扫码后自动完成登录",
"hint": "请运行 get-qrcode 获取二维码,扫码后运行 wait-login 等待登录结果",
}, exit_code=1)
else:
# 无界面环境:二维码(扫对话窗口中的图片)和手机验证码均可
# 无界面服务器:二维码或手机验证码均可
_output({
"logged_in": False,
"login_method": "both",
"hint": (
"方式A: get-qrcode(二维码将显示在对话窗口,扫码即可);"
"方式B: send-code --phone <手机号>(手机验证码)"
"方式A: get-qrcode + wait-login(二维码显示在对话窗口);"
"方式B: send-code --phone <手机号> + verify-code(手机验证码)"
),
}, exit_code=1)
finally:
... ... @@ -214,7 +218,8 @@ def cmd_get_qrcode(args: argparse.Namespace) -> None:
从登录弹窗的二维码 img 元素读取图片(data URL 或网络 URL),
保存为本地 PNG 文件后立即退出。Chrome tab 保持打开,QR 会话继续有效。
调用方收到 qrcode_path 后用 Read 工具将图片显示在对话窗口,再轮询 check-login。
调用方收到 qrcode_data_url 后直接内嵌到对话窗口显示;同时浏览器窗口(GUI 环境)
也会显示二维码,用户可选择扫任意一个。
"""
from xhs.login import fetch_qrcode, save_qrcode_to_file
... ... @@ -238,6 +243,27 @@ def cmd_get_qrcode(args: argparse.Namespace) -> None:
})
def cmd_wait_login(args: argparse.Namespace) -> None:
"""等待扫码登录完成(配合 get-qrcode 使用)。
连接已有 Chrome tab,内部轮询直到登录成功或超时,替代 Skill 层的多次 check-login 轮询。
"""
from xhs.login import wait_for_login
browser, page = _connect_existing(args)
try:
success = wait_for_login(page, timeout=args.timeout)
_output(
{
"logged_in": success,
"message": "登录成功" if success else "等待超时,请重新运行 get-qrcode 获取新二维码",
},
exit_code=0 if success else 2,
)
finally:
browser.close()
def cmd_send_code(args: argparse.Namespace) -> None:
"""分步登录第一步:填写手机号并发送验证码,保持页面不关闭。"""
from chrome_launcher import has_display, restart_chrome
... ... @@ -727,6 +753,11 @@ def build_parser() -> argparse.ArgumentParser:
sub = subparsers.add_parser("get-qrcode", help="获取登录二维码截图并立即返回(非阻塞)")
sub.set_defaults(func=cmd_get_qrcode)
# wait-login(配合 get-qrcode,阻塞等待登录完成)
sub = subparsers.add_parser("wait-login", help="等待扫码登录完成(配合 get-qrcode 使用)")
sub.add_argument("--timeout", type=float, default=120.0, help="等待超时秒数 (default: 120)")
sub.set_defaults(func=cmd_wait_login)
# phone-login(单命令交互式)
sub = subparsers.add_parser("phone-login", help="手机号+验证码登录(交互式,适合本地终端)")
sub.add_argument("--phone", required=True, help="手机号(不含国家码,如 13800138000)")
... ...
... ... @@ -33,23 +33,12 @@ python scripts/cli.py check-login
输出解读:
- `"logged_in": true` → 已登录,可执行后续操作。
- `"logged_in": false` + `"login_method": "qrcode"` → 有界面环境,直接走二维码登录。
- `"logged_in": false` + `"login_method": "both"` → 无界面服务器,**询问用户选择方式 A(二维码)或方式 B(手机验证码)**
- `"logged_in": false` + `"login_method": "qrcode"` → 有界面环境,走方式 A(二维码)。
- `"logged_in": false` + `"login_method": "both"` → 无界面服务器,**询问用户选方式 A(二维码)或方式 B(手机验证码)**
### 第二步:根据 login_method 选择登录方式
### 第二步:根据输出选择登录方式
#### 方式 A1:二维码登录 — 有界面(GUI)设备
```bash
python scripts/cli.py login
```
- Chrome **有窗口**弹出,二维码直接显示在浏览器窗口中。
- 告知用户用小红书 App 扫屏幕上的二维码。
- 命令阻塞等待(最多 120 秒),扫码成功后输出 `{"logged_in": true}`
- 无需在对话窗口显示图片。
#### 方式 A2:二维码登录 — 无界面(headless)服务器
#### 方式 A:二维码登录(所有平台通用)
**第一步** — 获取二维码(非阻塞,立即返回):
... ... @@ -57,7 +46,7 @@ python scripts/cli.py login
python scripts/cli.py get-qrcode
```
- headless Chrome 加载登录页,从 `img` 元素读取二维码图片(相当于右键另存为)。
- Chrome 正常启动,从登录弹窗 `img` 元素读取二维码(相当于右键另存为)。
- 命令立即退出,Chrome tab 保持打开(QR 会话继续有效)。
- 输出:`{"qrcode_path": "...", "qrcode_data_url": "data:image/png;base64,...", "message": "..."}`
... ... @@ -67,16 +56,16 @@ python scripts/cli.py get-qrcode
![小红书登录二维码]({qrcode_data_url})
```
图片本身已含 16px 白色边框,内嵌在对话窗口,用户用小红书 App 扫对话里的二维码即可
图片含 16px 白色边框,内嵌在对话窗口,用户用小红书 App 扫对话里的二维码
**第三步**轮询登录状态(每 10 秒一次,最多 12 次):
**第三步**等待登录完成(**单次调用,无需轮询**):
```bash
python scripts/cli.py check-login
python scripts/cli.py wait-login
```
- `"logged_in": true` 则完成。
- 2 分钟内未登录,提示用户重新执行第一步。
- 连接已有 Chrome tab,内部阻塞等待(最多 120 秒)。
- 输出 `{"logged_in": true}` 则完成;超时则提示用户重新运行 `get-qrcode`
#### 方式 B:手机验证码登录(无界面服务器,分两步)
... ...