Metadata-Version: 2.4
Name: ChatSale
Version: 0.1.0
Summary: Personal resale listing and buyer-message automation for Goofish.
Author-email: ChatArch <rex@chatarch.org>
License-Expression: GPL-3.0-only
Project-URL: Homepage, https://github.com/ChatArch/ChatSale
Project-URL: Repository, https://github.com/ChatArch/ChatSale
Keywords: chatsale,chatarch,cli
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: LICENSES/pyxianyu-MIT.txt
License-File: THIRD_PARTY_NOTICES.md
Requires-Dist: click>=8.0
Requires-Dist: chatstyle<0.3.0,>=0.2.0
Requires-Dist: chatenv<0.3.0,>=0.2.11
Requires-Dist: httpx<1,>=0.27
Requires-Dist: pyxianyu<2,>=1.0.0
Requires-Dist: qrcode[pil]<9,>=7.4
Requires-Dist: requests<3,>=2.31
Requires-Dist: websockets<16,>=15
Requires-Dist: Pillow<13,>=10
Provides-Extra: dev
Requires-Dist: build; extra == "dev"
Requires-Dist: pytest; extra == "dev"
Requires-Dist: twine; extra == "dev"
Dynamic: license-file

<div align="center">

[English](README.en.md) | [简体中文](README.md)

</div>

# ChatSale 0.1.0

ChatSale 是面向个人闲置实物转售的命令行工具。0.1.0 默认通过本人操作的官方网页和本地普通 Chrome 完成扫码登录；登录态始终留在专用浏览器 Profile 中。工具可以读取本人商品、准备并确认发布/编辑/上下架操作，发现活跃买家会话，并对未读买家消息输出有界、去重的提醒。

> 平台风险：本项目依赖平台网页实际使用的非公开网页能力，而非平台承诺的个人卖家公开 API。页面、接口、风控及权限可能随时变化。请只操作本人账号、本人当前有权处分的实物，并自行遵守平台规则和交易责任。

## 0.1.0 范围与验证状态

已实现的代码能力：

- 每个命名 Profile 使用专用本地 Chrome Profile；浏览器描述符位于 `~/.chatarch/chatsale/browser/`，权限为私有。它只记录本次受控浏览器的本地连接元数据，不保存或导出 Cookie。
- `login` 打开官方页面，确认 `passport.goofish.com` 框架中确有二维码后，才写入实际页面截图并输出 `QR_READY` 路径；页面会一直保留到扫码超时或登录完成。
- `status` 在同一官方页面上检查本人 `/personal` 公开锚点，而不是根据某些本地 Cookie 名称宣称已登录。
- 商品读取、预取、发布、编辑、下架和上架通过页面内的 `window.lib.mtop.request` 白名单调用；图片以浏览器自己的 `fetch`/`FormData` 上传。
- 本人商品列表来自 `/personal` 的真实 DOM 完成态；页面明确显示“暂无商品”时才返回空列表，不会从无关 ID 猜测用户 ID。
- `messages conversations` 使用 `mtop.taobao.idlemessage.pc.session.sync` v3.0 发现活跃会话。`messages watch` 未指定 `--conversation` 时只对有未读数的会话输出 `buyer_reminder`，并用 SQLite 台账持久去重。

主管已在真实官方页面上完成只读验收：本地 headed Chrome 的本人 Profile 可以登录，个人页存在本人公开锚点，`/personal` 当前显示“暂无商品”，已购订单页和消息入口可渲染，且页面内 `window.lib.mtop.request` 与用户页导航请求可用。本仓库的测试只使用假 CDP 和临时运行目录；本次实现**没有**再次连接真实账号或平台。

尚未在真实平台写入验收，因而不能宣称已经成功发布、编辑、上/下架或上传图片。写操作仍要求 `--yes`，并且任何回执不完整的发布都会明确标为 `unknown` 或 `published_unverified`，绝不自动重放。

本版明确不包含付款、退款、订单履约、购买记录到现货的推断、外部平台购买、数字商品、多店铺 ERP、自动议价、自动客服、后台常驻服务或绕过验证码/人脸验证。

## 安装与命令树

```bash
python -m pip install -e .
chatsale --version
chatsale --tree
chatsale --tree-brief
```

完整签名来自实际 Click 注册树，见 [docs/cli.md](docs/cli.md)。`--tree` 显示签名，`--tree-brief` 保留命令和说明但省略签名。

## 官方页面扫码登录

首次登录需要本机已安装的普通 Chrome；ChatSale 不下载浏览器。可用 `CHATSALE_CHROME_BIN` 指向已有二进制文件。macOS 默认通过普通 Chrome 的后台新实例启动，随后按实际监听端口和私有启动标记确认所有权；不会信任短命的启动器 PID。

```bash
chatsale login personal --qrcode ./chatsale-login.png --timeout 180
```

命令先重新检查 Profile 的实际网页登录态；如果已登录，会输出安全的 `already_authenticated` 状态，而不会生成另一个二维码。否则，在确认官方 passport 框架和二维码元素后，立刻输出一条不含秘密的记录：

```json
{"event":"QR_READY","image_path":"./chatsale-login.png"}
```

用本人设备扫描该实际截图，并在平台页面完成确认。密码、短信验证码、验证码、人脸或其他人工验证不会由工具填写、绕过或公开。超时后，只有本次启动且重新验证过启动标记的浏览器才会通过其原始本地端点关闭；附着到的已有浏览器保持运行。

```bash
chatsale status personal
chatsale paths --profile personal
chatsale logout personal --yes
```

`logout --yes` 只在专用浏览器 Profile 内清除浏览器 Cookie，不会读取或打印任何 Cookie。`status`、`paths` 和所有 stdout 都不会输出 DevTools WebSocket、启动标记、认证查询串、手机号、验证码、二维码原文或会话材料。

历史的原生协议二维码模块仍保留为内部兼容代码，但不是默认 CLI 登录路径，也不应被当作可靠的浏览器登录后端。

## 商品操作

`items publish --dry-run` 会先精确校验价格到“分”，并解码验证本地 JPEG/PNG/GIF/WebP 图片；它不需要登录，不上传图片，也不调用平台。

```bash
# 只做本地校验，不写入平台
chatsale items publish --dry-run \
  --title "示例物品" --description "如实描述" --price 12.34 \
  --image ./item.png

# 真实写入：需要可验证的浏览器登录和显式确认
chatsale items publish --profile personal --yes \
  --title "示例物品" --description "如实描述" --price 12.34 \
  --image ./item.png

chatsale items list --profile personal
chatsale items get ITEM_ID --profile personal
chatsale items edit ITEM_ID --profile personal --price 10.00 --yes
chatsale items down ITEM_ID --profile personal --yes
chatsale items up ITEM_ID --profile personal --yes
```

浏览器模式只支持 `items list` 的首页；若官方个人页未提供可验证的商品完成态，命令会明确失败，而不会伪造空列表。价格超过两位小数会在任何预取、上传或写入之前失败。0.1.0 的真实写操作均按未完成业务验收处理：发布为 `unknown` / `published_unverified`，编辑、下架、上架分别为 `updated_unverified`、`downshelved_unverified`、`reshelved_unverified`。即使商品 ID 存在，也不把它当作源内容一致的证明；需要在官网核对，不自动重试。

## 买家会话与提醒

```bash
# 发现官方页面 session-sync 返回的活跃会话
chatsale messages conversations --profile personal

# 有界轮询所有发现到的未读买家提醒，并持久去重
chatsale messages watch --profile personal --cycles 3 --interval 20

# 只保留实际会话关联到指定本人商品 ID 的提醒
chatsale messages watch --profile personal --item ITEM_ID --cycles 3 --interval 20
```

`session.sync` 是活跃会话基线，不承诺等同于网页左栏的完整长期历史；输出会带 `source: "session_sync"`。提醒表示官方会话摘要中的未读状态，不把摘要冒充成单条已送达消息。

`messages watch --conversation ID` 仍保留给未来已验证的会话历史读取路径，但本版浏览器模式尚未验证官方前端的单会话历史 API。`messages list`、`messages history` 和 `messages send` 会明确报告该已知缺口，而不是返回成功形状的假数据。发送没有可验证的官方页面送达回执时绝不会被宣称成功。

去重账本位于 ChatArch 运行根目录的 `chatsale/message-ledger.sqlite3`。如果通过 ChatEnv 配置了敏感的 `CHATSALE_NOTIFICATION_WEBHOOK_URL`，每条新提醒还会以受验证 TLS 和有界超时发送到该 webhook；否则可直接消费 JSONL stdout。

## Python API 与安全边界

可复用的工作流仍可从 Python 导入：

```python
from chatsale import PublishRequest, SaleService, price_to_cents
from chatsale.browser_adapter import BrowserXianyuAdapter

adapter = BrowserXianyuAdapter.for_profile("personal")
try:
    result = SaleService(adapter).publish(
        PublishRequest(
            title="示例物品",
            description="如实描述",
            price="12.34",
            images=["./item.png"],
        )
    )
finally:
    adapter.close()
```

只应跨不可信边界传递 `PublishResult.to_public_dict()`、会话 DTO 和其他安全投影。不要把浏览器描述符、内部模板、上传 URL 或历史兼容模块的状态序列化到日志、模型、环境变量或 HTTP 客户端。ChatSale 不提供 Cookie 导入/导出命令，也不会把浏览器认证材料桥接给 HTTP SDK。

## 许可与归属

ChatSale 是 **GPL-3.0-only**。历史二维码模块改编自提供的 GPL-3.0-only `xianyu-qr` 实现；完整说明见 [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)。仓库仍保留 MIT 许可的 `pyxianyu` 1.x 归属和许可文本，但 0.1.0 默认 CLI 浏览器路径不把浏览器 Cookie 交给该 SDK。
