Metadata-Version: 2.4
Name: xianyu-goofish-cli
Version: 0.2.0
Summary: Minimal Goofish (Xianyu) CLI: QR login, search with filters, IM chats
Author-email: 肖佳权 <1259103745@qq.com>
License-Expression: Apache-2.0
Project-URL: Homepage, https://git.weisanju.fun/weisanju/xianyu-cli
Project-URL: Repository, https://git.weisanju.fun/weisanju/xianyu-cli
Project-URL: Issues, https://git.weisanju.fun/weisanju/xianyu-cli/issues
Keywords: xianyu,goofish,cli,second-hand,idle-fish
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Requires-Dist: patchright>=1.64
Requires-Dist: requests>=2.31
Requires-Dist: websockets>=13
Requires-Dist: pillow>=10
Requires-Dist: segno>=1.6
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: twine>=5; extra == "dev"
Dynamic: license-file

# xianyu-goofish-cli

[![ci](https://git.weisanju.fun/weisanju/xianyu-cli/actions/workflows/ci.yml/badge.svg)](https://git.weisanju.fun/weisanju/xianyu-cli/actions/workflows/ci.yml)

极简的闲鱼（Goofish）只读 CLI：**扫码登录 → 搜索商品 → 会话列表 → IM 实时流**。

许可证 Apache-2.0。仓库：<https://git.weisanju.fun/weisanju/xianyu-cli>。

实现上刻意避开了两件最麻烦的事：

1. **不自己复刻 mtop 签名/风控**：搜索时用 Patchright 打开真实 Chrome，监听页面自己发出的
   `mtop.taobao.idlemtopsearch.pc.search` 响应，直接解析 JSON。
2. **不逆向 IM 私有二进制**：闲鱼 WebSocket 实际是 LWP（JSON）协议，`wss://wss-goofish.dingtalk.com/`，
   本 CLI 只做注册、心跳、ack 和解码。

> 本项目是非官方实现，仅用于个人学习与低频只读场景。请遵守闲鱼用户协议；账号风控、封号等后果自负。

## 安装

```bash
pip install xianyu-goofish-cli
patchright install chrome     # 或直接使用系统 Chrome（channel="chrome"）
```

从源码：

```bash
git clone https://git.weisanju.fun/weisanju/xianyu-cli.git
cd xianyu-cli
python -m venv .venv
source .venv/bin/activate
pip install -e .
```

需要本机有 Chrome/Chromium。

Linux 下有头模式默认优先走 Xvfb（等价于内置 xvfb-run 思路），以减少图形环境依赖。
CLI 会对异常终端尺寸环境变量（如 `COLUMNS=131072`）做清洗，减少“屏幕尺寸不真实”类提示对登录流程的干扰。

从本版本起，CLI 全部通过内置 server 调用：
- 默认 server 地址：`http://127.0.0.1:17357`
- 若本地 server 不存在，CLI 会自动拉起并复用
- 启动时会检查本地 server `/health` 返回的 `version`，若与当前 CLI 版本不一致会自动重启本地 server
- 也可通过 `--server` / `XIANYU_SERVER` 指向远程 server

## 使用

```bash
# 扫码登录：默认有头 Chrome 打开登录页，终端会在拿到二维码后立即渲染，PNG 存在 ~/.xianyu-cli/qr.png
# 若已有有效登录态会直接复用并跳过扫码；Linux 默认优先启用 Xvfb 有头模式
xianyu login

# 显式启动 server（可用于远程部署）
xianyu serve --host 127.0.0.1 --port 17357

# 强制重新扫码（忽略已缓存登录态）
xianyu login --force

# 关闭终端二维码渲染
xianyu login --no-render-qr

# 调小终端二维码（例如 28；仅图片渲染 fallback 时生效）
xianyu login --qr-width 28

# 机器可读：返回登录结果 + 二维码路径（尽力附带 png_base64）
xianyu login --json

# 检查登录态
xianyu whoami

# 搜索（默认无头；若命中风控会自动重试一次 --no-headless）
xianyu search "iPad Pro" --limit 10
xianyu search "相机" --pages 2 --json

# 筛选：价格区间 / 包邮 / 排除关键词 / 有原价（划线价）/ 排序
xianyu search "iPad Pro" --price-min 1000 --price-max 3500 --shipping free
xianyu search "相机" --exclude 配件 --sort price_asc --limit 20
xianyu search "显卡" --has-original-price --sort price_desc

# 指向远程 server
xianyu --server http://10.0.0.8:17357 search "相机" --limit 10

# 会话列表（支持区分私聊/非私聊；可按 cid 过滤）
xianyu chats --limit 50
xianyu chats --private --limit 50
xianyu chats --non-private --limit 50
xianyu chats --cid 1234567890
xianyu chats --json

# 实时监听私信 60 秒（可只看某个 cid）
xianyu watch --seconds 60
xianyu watch --seconds 60 --cid 1234567890
xianyu watch --seconds 60 --json

# 检查更新（只报告）/ 执行升级（pip 安装到当前解释器环境）
xianyu update --check
xianyu update --yes

# 给 AI agent 的用法说明：终端打印 SKILL.md
xianyu skill
xianyu skill --install                    # 写入 ~/.xianyu-cli/skills/xianyu/SKILL.md
xianyu skill --install ~/.claude/skills/xianyu   # 写入指定目录的 SKILL.md
```

全局 `--json` 可用于所有命令。全局 `--server` 可切换到远程 server。

`login --json` 成功时会返回：

```json
{
  "logged_in": true,
  "cookies": 12,
  "qr": {
    "path": "/home/you/.xianyu-cli/qr.png",
    "png_base64": "iVBORw0KGgoAAA..."
  }
}
```

其中 `png_base64` 为尽力返回（读取失败时可能不存在）。
CLI 默认优先用 Python 库 `segno` 根据二维码原文重渲染终端码（更易扫）；若失败则回退 `qrencode`，最后回退到图片像素渲染。
`login --json` 时二维码渲染和提示信息会输出到 stderr，stdout 保持纯 JSON。

## 命令与退出码

| 命令 | 说明 |
|---|---|
| `login [--timeout N] [--no-headless] [--no-render-qr] [--qr-width N] [--force]` | 扫码登录，保存 cookie + storage_state；默认优先复用有效登录态并跳过扫码，渲染时优先用 `segno`；`--json` 时返回二维码信息 |
| `whoami` | 调用 `loginuser.get` 验证登录态 |
| `search <query> [--limit] [--pages] [--price-min] [--price-max] [--shipping any\|free\|no] [--exclude WORD] [--has-original-price] [--sort default\|price_asc\|price_desc] [--no-headless]` | 拦截搜索响应并解析；筛选在 server 端对已抓取结果进行（先筛选再截断到 `--limit`） |
| `chats [--limit] [--private|--non-private] [--cid]` | `session.sync` 拉会话列表，并可筛选私聊/非私聊或指定会话 CID |
| `watch [--seconds] [--cid]` | IM WebSocket 实时事件，可只监听指定会话 CID |
| `update [--check] [--yes]` | 读取 PyPI 上 `xianyu-goofish-cli` 的最新版本；默认只报告升级命令，`--yes` 才用当前 Python 执行 `pip install --upgrade` |
| `skill [--install [DIR]]` | 打印给 AI agent 的用法说明（SKILL.md）；`--install [DIR]` 写入 `DIR/SKILL.md`（默认 `~/.xianyu-cli/skills/xianyu/`） |
| `serve [--host] [--port]` | 启动 RPC server（CLI 默认会自动拉起本地 server） |

退出码：`0` 成功，`1` 普通错误，`2` 未登录/登录失效，`3` 风控拦截，`130` Ctrl-C。

## 数据位置

```
~/.xianyu-cli/
  cookies.json      # 登录 cookie（0600）
  profile/          # 扫码登录用的 Chrome profile
  storage_state.json
  qr.png            # 最近一次登录二维码
  device.json       # IM 设备 ID（按账号固定）
  im_token.json     # IM accessToken 缓存（30 分钟）
```

可用 `XIANYU_HOME` 改根目录。

## 发布（维护者）

PyPI 分发包名是 `xianyu-goofish-cli`（`xianyu-cli` 已被占用），导入包仍为 `xianyu_cli`，命令为 `xianyu`。

1. 仓库 Settings → Actions → Secrets 添加 `PYPI_API_TOKEN`（PyPI API token，scope 建议只限本项目）。
2. 改 `pyproject.toml` 里的 `version`，提交后打同名 tag：

```bash
git tag v0.1.0
git push origin v0.1.0
```

`.forgejo/workflows/release.yml` 会校验 tag 与版本一致、构建 sdist/wheel、跑 `twine check`，再上传 PyPI。
本地手动发布等价命令：

```bash
python -m pip install --upgrade build twine
python -m build
TWINE_USERNAME=__token__ TWINE_PASSWORD=pypi-... python -m twine upload dist/*
```

## 已知限制

- **只读**：v1 不提供发消息、发布、自动回复。
- `search` 依赖页面实际发出搜索请求；未登录/登录失效时页面会弹登录框，CLI 会报“未捕获到搜索响应”。
- 二维码 canvas 被中心 logo 跨域污染，不能 `toDataURL()`，本实现用元素截图。
- 会话列表来自 `session.sync`，可能与 App 端不完全一致。
- IM 协议随时可能变化；`watch` 目前只处理文本/图片/已读/新消息通知。
- 高频请求会触发 `RGV587`，CLI 会直接报错退出，不做自动重试风暴。

## 参考与致谢

协议常量与解析思路参考了以下 Apache-2.0 / MIT 项目：

- [fancyboi999/goofish-cli](https://github.com/fancyboi999/goofish-cli)（Apache-2.0）：浏览器拦截搜索响应、LWP 注册/ack、`session.sync` 字段。
- [cv-cat/XianYuApis](https://github.com/cv-cat/XianYuApis)（MIT）：IM WebSocket 与 mtop 常量。
- [superboyyy/xianyu_spider](https://github.com/superboyyy/xianyu_spider)：IM 解码思路。

本项目为独立实现，未复制上述项目的完整代码；详见 `NOTICE`。
