Metadata-Version: 2.4
Name: douyin-hot-reply
Version: 0.2.0
Summary: Token-free Playwright preview and reply scripts for recommended Douyin comments
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: playwright<2,>=1.48

# douyin-hot-reply

跨平台、token-free 的 Python Playwright 包，只提供两个公开功能：

1. **Preview**：输入任意抖音视频链接，读取页面默认推荐序第一条非置顶评论，保存截图并输出 LLM-ready JSON。
2. **Reply**：消费 LLM 已决定的回复，发送到指定评论；未指定评论时发送到第一条非置顶推荐评论，保存截图并输出 JSON log。

> token-free 指两个脚本不导入、不调用任何 LLM、HTTP SDK 或 token API。Preview JSON 交给包外的大模型时，大模型本身仍会正常消耗 token。

## 推荐评论定义

`first-recommended-non-pinned`：页面保持默认推荐排序时，第一条非置顶评论。它不是点赞数最高评论。

## 安装

macOS / Linux：

```bash
python3 -m pip install douyin-hot-reply
python3 -m playwright install chromium
```

Windows：

```powershell
py -m pip install douyin-hot-reply
py -m playwright install chromium
```

本地 wheel：

```bash
python3 -m pip install dist/douyin_hot_reply-0.2.0-py3-none-any.whl
```

默认使用 Playwright Chromium；也可传 `--channel chrome` 使用本机 Google Chrome。登录态保存在 `~/.douyin-hot-reply/profiles/<profile>/`，Windows 会自动使用对应用户主目录。

每个 profile 首次运行时会绑定浏览器类型；后续即使不再传 `--channel` 也会复用该绑定。为避免 Cookie 数据损坏，同一个 profile 禁止在 Playwright Chromium 与 Google Chrome 之间切换；需要换浏览器时请换一个 profile 名称。

### 切换 Google Chrome profile

每个名称对应一套独立 Cookie 和登录账号。首次为各账号扫码一次，之后直接切换 `--profile`：

```bash
# 首次创建并登录两个专用 Chrome profile
douyin-hot-preview '视频链接' --profile account-a --channel chrome
douyin-hot-preview '视频链接' --profile account-b --channel chrome

# 后续可省略 --channel，自动复用绑定
douyin-hot-preview '视频链接' --profile account-a --headless
douyin-hot-preview '视频链接' --profile account-b --headless
```

这些是本包管理的专用 Chrome profile，不会直接修改个人 Chrome 的 `Default` / `Profile 1` 目录。

## 1. Preview

首次运行用有头浏览器扫码：

```bash
douyin-hot-preview '视频链接' --profile account
```

登录后可在后台运行；同一 profile、同一浏览器无需重复扫码：

```bash
douyin-hot-preview '视频链接' \
  --profile account \
  --headless \
  --output preview.json
```

Preview 同时写入：

```text
~/.douyin-hot-reply/artifacts/*-preview.png
~/.douyin-hot-reply/logs/*-preview.json
```

JSON 包含：

- 实际使用的 `browser.profile` / `browser.channel`；
- 视频 URL / ID；
- 推荐评论选择定义；
- 作者、评论正文、点赞数、页面可见原文；
- 可用于精确回复的 `target_text`；
- `llm_handoff.input`；
- LLM 必须返回的 `response_schema`。

## LLM 结构化输出

把 Preview JSON 中的 `llm_handoff` 交给任意大模型。大模型应返回：

```json
{
  "decision": "reply",
  "video_url": "https://www.douyin.com/video/123",
  "target_text": "需要精确匹配的原评论",
  "reply_text": "结合上下文生成的回复",
  "reason": "简短理由"
}
```

不适合回复时返回：

```json
{
  "decision": "skip",
  "video_url": "",
  "target_text": "",
  "reply_text": "",
  "reason": "不适合回复"
}
```

## 2. Reply

消费 LLM 计划并真实发送一次：

```bash
douyin-hot-reply \
  --plan reply-plan.json \
  --profile account \
  --headless \
  --yes \
  --output reply-result.json
```

也可直接传入回复。指定评论：

```bash
douyin-hot-reply '视频链接' '回复文本' \
  --target-text '必须匹配的原评论' \
  --profile account \
  --headless \
  --yes
```

未传 `--target-text` 时回复第一条非置顶推荐评论：

```bash
douyin-hot-reply '视频链接' '回复文本' --profile account --headless --yes
```

Reply 同时写入：

```text
~/.douyin-hot-reply/artifacts/*-reply.png
~/.douyin-hot-reply/logs/*-reply.json
```

安全约束：

- 必须显式传入 `--yes`；
- 指定 `target_text` 时，找不到原评论就不会发送；
- 无头模式遇到登录、短信或风控验证会停止；
- 发送按钮一旦点击，失败也不会自动重试；
- `decision=skip` 不会启动浏览器。

## Python API

```python
from douyin_hot_reply import preview_video, reply_to_comment

preview = preview_video("视频链接", profile="account", headless=True)
result = reply_to_comment(
    "视频链接",
    "LLM 已决定的回复",
    target_text=preview["comment"]["target_text"],
    profile="account",
    headless=True,
)
```

## Token-free 与测试验证

```bash
python3 -m pytest
```

测试会验证：

- 公共 API 只有 `preview_video` 和 `reply_to_comment`；
- 无 OpenAI、Anthropic、LiteLLM、HTTP SDK 或 token 凭据读取；
- 运行时依赖只有 `playwright`；
- Preview 评论能稳定结构化；
- Reply 缺少 `--yes` 或收到 `decision=skip` 时绝不启动发送流程。
