"Use when the user wants to search/download torrents from PT sites, manage qBittorrent, or set up a media download stack. 8 sites supported: PTTime, M-Team (API), BTSchool, CarPT, HDFans, 1PTBar, SoulVoice, 织梦. Search via cookie or REST API, push to qBittorrent, monitor completion."
Resources
5Install
npx skillscat add voidalex/pt-claw Install via the SkillsCat registry.
PT Downloader — 对话式资源搜索与下载
Overview
通过对话搜索 8 个 PT 站资源、推送到 qBittorrent 下载、完成后通知。纯脚本实现,无 Prowlarr/Jackett 依赖。
核心链路:
用户说"搜XXX" → 识别内容类型 → 路由到对应搜索 → 展示结果+元数据 → 排序 → 🛑用户确认 → 推送 qBittorrent → 打站点标签 → 记录下载历史(pt_downloaded.json) → 下载完成 → 通知⚠️ 确认闸门(全局规则):搜到资源后禁止直接下载。必须先展示以下完整信息并等用户确认:
- 来源站点、资源大小、做种数、版本标签(4K/HEVC/字幕/去码)
- 目标下载路径(分类 + save_path)
- 影片元数据:发行日期、导演、主演、剧情简介
- 只有用户明确说「下」「下载」「all」等确认词后才推送到 qBittorrent
- 此规则适用于所有场景:手动搜索、演员追剧、定时订阅,无一例外
8 站支持:PTTime · M-Team(馒头) · BTSchool · CarPT · HDFans · 1PTBar · SoulVoice · 织梦
When to Use
以下任意关键词或场景触发本技能加载:
触发词
| 分类 | 关键词 |
|---|---|
| 搜索/下载 | 搜、搜索、下载、下、找个资源、有没有、求片 |
| qBittorrent | qb、qB、qbit、下载进度、下载状态、做种、种子、删种、暂停、恢复、死种 |
| PT 站点 | pt、PT、PT站、馒头、mteam、pttime、btschool、carpt、hdfans、1ptba、soulvoice、zmpt、织梦 |
| 番号/成人 | 番号、车牌、jav、JAV、成人、sukebei、javbus、nyaa |
| 定时任务 | 定时任务、cron、追剧、刷流、下载通知 |
| 关注/收藏 | 关注、取消关注、关注列表、wishlist |
| Jellyfin | jf、JF、jellyfin、片库、去重、已存在 |
场景速查
| 用户说 | 路由 |
|---|---|
| 「搜流浪地球2」「找个4K沙丘」 | → Step 1 识别类型 → Step 2 全站搜索 |
| 「下SSIS-448」「搜SONE-833」 | → 番号 → 成人区搜索 + 公开源回退 |
| 「查下载进度」「qb怎么样了」 | → 加载 skill → qb_monitor.py |
| 「查下载器完整状态」「qb完整状态」 | → 写 Python 脚本调 qB API 全量查询(下载/做种/校验/分类/标签/死种) |
| 「删掉那个死种」「暂停xxx」 | → qB API 操作 |
| 「关注诺兰」「收藏SSIS-xxx」 | → 写入 pt_wishlist.json |
| 「列出定时任务」「暂停刷流」 | → cronjob(action='list') 等 |
| 「恢复」「qb种子丢了」「误删」 | → references/qb-disaster-recovery.md |
| 「葵司全部作品」「三上悠亚片单」 | → JavBus star 页 → 元数据优先 |
参考文档
- references/mteam-api.md — 馒头 API 详细对接记录(端点、认证、genDlToken)
- references/nexusphp-parser-notes.md — 两种 HTML 解析模式(rowfollow 分离式 vs 经典表格)
- references/adult-section-search.md — PTTime/M-Team 成人区搜索参数和适配方案
- references/qb-push-two-step.md — qB 推送两步法:下载.torrent→multipart上传(URL推送会静默失败)
- references/qbittorrent-setup.md — qBittorrent 连接配置
- references/diagnostic-proxy-cookie.md — Cookie 过期 vs 代理被封诊断流程
- references/public-magnet-detection.md — 公开磁链判定规则(tag-only,禁止 tracker URL)
- references/qb-disaster-recovery.md — qB 种子误删后从磁盘恢复流程
- references/nas-volume-mapping.md — NAS NFS 挂载与 qB 容器路径映射表
- references/cron-progress-check-pattern.md — Cron 下载进度检查执行模式(hash tracker + tirith 绕行)
- references/cron-adult-chase.md — Cron 成人追剧高效模式:跳过 PT 搜索,javbus-api→Sukebei 直通链路
- references/jf-actor-count.md — JF 演员片库统计:
fields=People分页计数,「谁最多」查询 - references/orphan-media-scan.md — 扫描磁盘孤儿媒体:不在 qB 中的大文件/文件夹排名,用于手动恢复种子
- references/privacy-audit-checklist.md — 隐私审计检查清单:API Key/IP/路径/用户ID 扫描正则 + Git历史清理
Scripts
所有脚本位于 scripts/ 目录,配置文件路径见脚注¹。
pt_search.py — 多站搜索(8 站,含馒头 API)
# 单站
python3 scripts/pt_search.py "流浪地球2" --site pttime --limit 5
# 全站(需代理的站自动走 PT_PROXY,无需手动包装)
python3 scripts/pt_search.py "流浪地球2" --limit 5
# PTTime 成人区(按番号/关键词)
python3 scripts/pt_search.py "SONE-833" --site pttime --adult --limit 10
# PTTime 成人区(按演员名)
python3 scripts/pt_search.py "" --site pttime --adult --actor "浅野心" --limit 10接入方式:
- M-Team: REST API(POST,
x-api-key认证,从MTEAM_API_KEY环境变量读取)— 搜索 ✅,下载 ✅(genDlToken) - 其他 7 站: Cookie 直连(从
PT_COOKIE_<SITE>环境变量读取) - 织梦 zmpt.cc: 需走代理(直连超时)
qb_add.py — 添加到 qBittorrent(含站点标签 + 文件选择)
# 公开磁链 — 列出文件供用户选择(推荐)
python3 scripts/qb_add.py "magnet:?xt=urn:btih:ABCDEF..." --tags sukebei --list-files
# 用户确认后,选择要下载的文件
python3 scripts/qb_add.py --select-files <HASH> --keep=0,3,5
# 公开磁链 — 自动选最大视频文件(批量场景兜底)
python3 scripts/qb_add.py "magnet:?xt=urn:btih:ABCDEF..." --tags sukebei --max-video
# PT 种子不需要文件选择(PT 站一般只有正片)
python3 scripts/qb_add.py "https://pt.example.com/download.php?id=123" --category "电影" --tags pttime
# stdin JSON 模式(支持 max_video 字段)
echo '{"magnet": "...", "category": "<分类名>", "save_path": "<路径>", "tags": ["sukebei"], "max_video": true}' | python3 scripts/qb_add.py --stdin公开磁链文件选择(推荐流程):
--list-files:添加种子(暂停)→ 等元数据 → 列出所有文件(名称/大小/类型)→ 返回 JSON- Agent 展示文件列表给用户确认要下哪些
--select-files <HASH> --keep=0,3,5:跳过未选文件 → 恢复下载
--max-video 自动模式(兜底):添加种子暂停 → 等元数据 → 三级策略选正片:
- 文件名含番号(如
MIMK-267)→ 取最大的匹配文件 - 排除含广告关键词的文件(ad/sample/preview/广告/宣传/预告等)→ 取最大的
- 兜底取最大视频文件
stdin JSON 传 "code": "MIMK-267" 可辅助精确匹配。字幕文件(.srt/.ass)同名校验后保留。PT 站种子不需要此参数(PT 资源干净)。
download_history.py — 下载历史追踪
# 添加记录
python3 scripts/download_history.py add --code MIMK-267 --title "xxx" --source sukebei
# 检查是否已下载过
python3 scripts/download_history.py check --code MIMK-267
# → {"exists": true, "code": "MIMK-267"}
# 批量过滤:从 stdin 读 code 列表,只输出未下载过的
echo -e "MIMK-267\nNEW-001" | python3 scripts/download_history.py filter
# → NEW-001
# 列出全部历史
python3 scripts/download_history.py listqb_monitor.py — qBittorrent 全功能查询
# 默认:24h内完成的下载
python3 scripts/qb_monitor.py
# 完整状态报告(全状态、速度、死种、近期完成)
python3 scripts/qb_monitor.py --full
# 按番号过滤
python3 scripts/qb_monitor.py --full --codes "ROYD-318,JUFE-622"
# 按标签过滤
python3 scripts/qb_monitor.py --tags sukebei,javbus
# 按状态过滤
python3 scripts/qb_monitor.py --states downloading,stalledDL
# 删除匹配的种子(保留文件)——先查后删
python3 scripts/qb_monitor.py --delete --codes "ROYD-318" --check # 只预览不删
python3 scripts/qb_monitor.py --delete --codes "ROYD-318" # 确认后删除
python3 scripts/qb_monitor.py --delete --tags sukebei --states stalledDL --checkjavbus_star.py — 演员片单交叉对比
# 搜索演员(支持中/日文名),交叉对比 JF + 下载历史
python3 scripts/javbus_star.py "彩月七緒"
# 只显示缺失的最新 N 部
python3 scripts/javbus_star.py "浅野こころ" --top 10
# 用 star ID 直接查询
python3 scripts/javbus_star.py --star-id 11wm输出:已有(JF或已下载)和缺失的影片列表,按日期排序。
qb_backup.py / qb_restore.py — 删种备份与恢复
# 查看最近50条备份
python3 scripts/qb_backup.py --list
# 查看最近备份(格式化)
python3 scripts/qb_restore.py --list
# 恢复指定种子(按hash前缀)
python3 scripts/qb_restore.py --restore <hash_prefix>
# 恢复最近一次删除的种子
python3 scripts/qb_restore.py --last
# 批量恢复(按删除原因)
python3 scripts/qb_restore.py --restore-all --reason public_cleanup
python3 scripts/qb_restore.py --restore-all --reason manual_delete
python3 scripts/qb_restore.py --restore-all --reason boost_aged
# 清除备份记录
python3 scripts/qb_backup.py --clearqb_public_cleanup.py、qb_monitor.py --delete、pt_ratio_boost.py 删除种子前自动调用 backup_from_torrents(),将 hash/名称/路径/标签/分类 加上 .torrent 文件 备份到 torrent_backups/,元数据写入 pt_deleted_backup.json(保留最近 500 条)。
恢复链路:qb_restore.py 从备份恢复种子到 qBittorrent。支持按 hash 单个恢复、最近恢复、按原因批量恢复。恢复时重新上传 .torrent 文件并应用原始标签/分类/路径。无 .torrent 文件的条目会打印搜索关键词供手动恢复。
确认机制:三个删种脚本均支持 --check 参数,只查不删,展示将被删除的种子清单,等用户确认后再执行实际删除。用法:
python3 scripts/qb_public_cleanup.py --check # 公开磁链:预览待删除清单
python3 scripts/qb_monitor.py --delete --tags javbus --check # qb_monitor:预览匹配项
python3 scripts/pt_ratio_boost.py cleanup --check # 刷流清理:预览过期种子jf_query.py — Jellyfin 查询
# 演员排名
python3 scripts/jf_query.py --list stars --top 20
# 搜索
python3 scripts/jf_query.py --search "浅野"
# 检查番号
python3 scripts/jf_query.py --check "ROYD-318"javbus_magnet.py — JavBus 磁链获取
# 通过 javbus-api(推荐,结构化数据)
python3 scripts/javbus_magnet.py SNOS-151 --api http://localhost:8922
# 裸爬模式(无 javbus-api 时)
python3 scripts/javbus_magnet.py SNOS-151 --scrape输出 JSON 数组,每条含 title、magnet、size、isHD、hasSubtitle、shareDate。内置广告过滤和垃圾磁链排除。
sukebei_search.py — Sukebei Nyaa RSS 搜索
python3 scripts/sukebei_search.py SNOS-151 # 搜索
python3 scripts/sukebei_search.py SNOS-151 --limit 5 # 限制条数通过 RSS 搜索 Sukebei Nyaa,返回 JSON(seeders/leechers/size/magnet/title)。内置广告去重和评分排序。
mteam_api.py — M-Team API 客户端
pt_search.py 和 pt_ratio_boost.py 的内部依赖,也可独立使用:
# 搜索
python3 scripts/mteam_api.py search "关键词" --key $MTEAM_API_KEY --limit 25
# 获取签名下载 URL
python3 scripts/mteam_api.py download <torrent_id> --key $MTEAM_API_KEYAPI 详细文档见 references/mteam-api.md。
connectivity_check.py — 全服务连接测试
python3 scripts/connectivity_check.py # 测试所有服务
python3 scripts/connectivity_check.py --quick # 跳过 PT 站(仅测 qB/JF/M-Team/代理)
python3 scripts/connectivity_check.py --site btschool # 只测某个站
python3 scripts/connectivity_check.py --json # JSON 格式输出实际发起 HTTP 请求测试每个外部服务:qBittorrent 登录、M-Team API、两个 Jellyfin 实例、javbus-api Docker、PT_PROXY 代理、8 个 PT 站 Cookie 有效性。env_check.sh 只检查变量是否存在,此脚本验证连接和认证是否真正可用。
Agent Workflow
Step 0:初次使用 — 主动询问用户配置
当用户第一次使用 PT 下载功能时(没有本地 user-preferences.md 文件),必须主动询问以下信息:
配置模板:templates/user-preferences.example.md 包含所有配置维度和占位符。首次配置时读取此模板作为参考,逐项向用户确认,用真实值填充后写入本地 user-preferences.md(不入 Git)。敏感值(API key、密码、IP)写入 secrets.env。不写入 memory(memory 容量有限,偏好配置数据量大,全部走文件)。
询问清单:
- PT 站点:有哪些站?目前支持 8 站(M-Team、PTTime、BTSchool、CarPT、HDFans、1PTBar、SoulVoice、织梦),不在列表中的需要新增适配。
- 下载器:类型、地址、端口、用户名、密码(目前支持 qBittorrent)。
- 下载路径偏好:读取 qBittorrent 分类 API,展示所有分类和路径,让用户确认内容类型映射(电影/电视剧/成人等 → 分类 → 路径),写入
user-preferences.md。 - 清晰度偏好:2160p / 1080p / 720p / 无偏好。
- 编码格式偏好:HEVC / AVC / AV1 / 无偏好。
- 其他偏好(可选):原盘 vs 压制、字幕、音轨、制作组。
- 通知偏好:下载完成后通知到哪里?
- 代理配置:代理地址、哪些站需要代理。
- Jellyfin 集成(可选):是否有 Jellyfin 服务器?几个?分别什么用途(影视/成人/音乐)?地址和 API Key?→ 没配置也能正常下载,只是无法自动去重。API key 写入
secrets.env,跨会话不丢失。 - javbus-api(可选):是否已部署 javbus-api(
docker compose模板在templates/docker-compose.javbus-api.yml)?→ 已部署则在secrets.env设置JAVBUS_API_URL=http://localhost:8922。未部署也能裸爬 JavBus,只是磁链无结构化数据、无封面预览。 - PT 刷流(可选):是否需要自动刷流保号?→ 需要指定:站点、搜索条件(关键词/免费/HR排除)、大小范围、做种人数、qB 专用目录、做种天数上限、死种清理阈值、每次新增上限。配置写入
<skill-dir>/pt_boost.json。 - 成人内容下载偏好(可选):PT 做种阈值(低于N则走公开源)、版本优选顺序(做种数/去码/字幕/画质)、清晰度范围(4K/1080p/720p)。
所有回答写入 user-preferences.md(本地文件,不入 Git)。敏感值同时写入 secrets.env。不使用 memory 存储偏好(容量有限)。
配置完成后自动初始化
用户回答完以上问题后,不要等用户说,直接自动执行以下初始化:
1. 创建数据文件:
# 下载历史(不存在则创建空文件)
test -f pt_downloaded.json || echo '{"description":"下载历史 — 防止用户手动删除后定时任务重复下载","items":[]}' > pt_downloaded.json
# 完成通知跟踪(不存在则创建)
test -f pt_completed_last.txt || touch pt_completed_last.txt
# 关注列表(不存在则创建空模板)
test -f pt_wishlist.json || echo '{"movies":[],"actors":[],"fanhao":[]}' > pt_wishlist.json2. 创建定时任务(必须,不等用户追问):
| 任务 | 频率 | 作用 |
|---|---|---|
| PT下载进度检查 | 每 15 分钟 | 完成通知 + 死种告警,没事件时不发消息 |
| PT自动追剧 | 每天 10:00 | 搜索+去重+展示,等用户确认后才下载 |
| 公开磁链状态检查 | 每 30 分钟 | 自动删除已完成公开种、标记死种 |
| PT站点Cookie保活 | 每天 06:00 | 访问各站首页刷新 session,防止 cookie 过期 |
# 下载进度检查(静默模式:没事件不通知)
cronjob(action='create',
name="PT下载进度检查",
schedule="every 15m",
repeat="forever",
prompt="""加载 pt-claw skill,检查 qBittorrent 下载进度。skill 目录下已有 secrets.env(凭据)、user-preferences.md(偏好)、pt_completed_last.txt(上次检查时间戳)。
只做三件事:
1. 有下载完成的(progress=100%)→ 通知「✅ xxx 下载完成」,更新 pt_completed_last.txt
- 公开磁链(sukebei/javbus 标签)完成后自动调 qb_public_cleanup.py 停止做种保留文件
2. 超7天0%死种 → 通知「💀 xxx 已死种N天,回复「删」清理」
- ⚠️ 不要自动删除,等用户确认
3. 都没有 → 整个回复只有三个字符:[SILENT]
脚本内部通过 `_load_env_file()` 自动读取 secrets.env,无需手动 source。""",
skills=["pt-claw"],
deliver="origin",
workdir="<skill-dir>",
)
# 自动追剧(只搜索展示,不自动下载)
cronjob(action='create',
name="PT自动追剧",
schedule="0 10 * * *",
prompt="""加载 pt-claw skill,自动追剧检查(只搜索展示,不自动下载)。skill 目录下已有 secrets.env、user-preferences.md、pt_wishlist.json、pt_downloaded.json。
1. 读 pt_wishlist.json + pt_downloaded.json
2. 搜资源 → 三重去重(历史>JF>时间戳)
3. 检查 exclude_prefixes 排除封禁厂牌
4. 展示结果:每部列出站点、大小、做种数、下载路径、元数据(日期/导演/主演/简介)
5. **绝对不推送下载**——等用户说「下」确认
6. 无新资源则「今日无新资源」
脚本内部通过 `_load_env_file()` 自动读取 secrets.env,无需手动 source。""",
skills=["pt-claw"],
deliver="origin",
workdir="<skill-dir>",
)
# 公开磁链清理(只报告不自动删)
cronjob(action='create',
name="公开磁链状态检查",
schedule="every 30m",
repeat="forever",
prompt="""加载 pt-claw skill。运行 `python3 scripts/qb_public_cleanup.py --check` 检查公开磁链(TAG ONLY:sukebei/javbus,禁止 tracker URL 匹配)。
- 无事 → [SILENT]
- 有事 → 列出待清理清单,结尾「回复「清了」确认删除」
⚠️ 绝对不能自动删种,必须等用户确认""",
skills=["pt-claw"],
deliver="origin",
workdir="<skill-dir>",
)
# Cookie 保活(静默)
cronjob(action='create',
name="PT站点Cookie保活",
schedule="0 6 * * *",
prompt="""加载 pt-claw skill。运行 `python3 scripts/connectivity_check.py --keepalive`,只报告失败的站点。全部成功则 [SILENT]。
脚本内部通过 `_load_env_file()` 自动读取 secrets.env,无需手动 source。""",
skills=["pt-claw"],
deliver="origin",
workdir="<skill-dir>",
)⚠️ 定时任务创建后告知用户:「已创建 4 个定时任务——下载进度(15m)、自动追剧(10:00)、公开种检查(30m)、Cookie保活(每天06:00)。随时可以说『暂停XX任务』来停止。」
Step 1:识别内容类型,路由搜索
根据搜索关键词判断内容类型:
| 关键词特征 | 内容类型 | 搜索路由 |
|---|---|---|
番号模式(如 SSIS-448、SONE-833) |
JAV 成人 | → PTTime adults.php + M-Team API adult → 做种不足→ JavBus(首选) → Sukebei |
| 演员/导演名 | 影视/成人 | → 先查元数据源获取完整作品列表,再逐部搜 PT |
| 电影/剧集名 | 影视 | → 全 8 站常规搜索 |
| 片库统计/演员排行 | JF查询 | → JF fields=People 分页计数,见 references/jf-actor-count.md |
⚠️ 元数据优先原则:搜索演员/导演时,不要直接在 PT 站搜索。PT 站的演员标签经常不完整或误标(中文名和日文名搜出完全不同结果、合集中的客串被误标为主演等)。
元数据获取流程:
成人演员 → JavBus 获取片单:
# 从任意一部作品的详情页找到 star ID curl -s 'https://www.javbus.com/<CODE>' -x <proxy> | grep -oP 'star/[a-z0-9]+' # 访问 star 页面获取完整片单(30-100 部) curl -s 'https://www.javbus.com/star/<ID>' -x <proxy> | \ python3 -c "import sys,re; codes=re.findall(r'javbus\.com/([A-Z0-9]+-\d+)', sys.stdin.read()); print('\n'.join(sorted(set(codes))))"主流演员/导演 → TMDB / IMDb 获取作品列表(后续适配)
拿到完整片单后:跟 Jellyfin 交叉对比 → 找出缺失的 → 逐部去 PT/Sukebei 搜
番号判断正则:^[A-Z]{2,6}[-_ ]?\d{2,5}$(不区分大小写)
| 国籍 | 搜索用名 | 示例 |
|---|---|---|
| 日本 | 日文原名 | 搜索时用日文原名而非中文译名 |
| 欧美 | 英文原名 | Christopher Nolan(不是「诺兰」) |
| 韩国 | 韩文原名 | 박찬욱(不是「朴赞郁」) |
| 中国 | 中文原名 | 张艺谋 |
PT 站的演员/导演标签通常是原语言,用中文译名可能搜不到或结果不全。先用原名搜,搜不到再尝试译名。
Step 2:执行搜索
常规搜索(影视/其他):
# 默认直连(非必要不加代理)
python3 scripts/pt_search.py "关键词" --limit 10
# 搜索(脚本根据 SITES 字典自动决定是否走代理)
python3 scripts/pt_search.py "关键词" --limit 10成人区搜索 — 仅 PTTime 和 M-Team 有成人区,不走全站搜索:
- PTTime 成人区:页面
adults.php,搜索参数searchstr(不是search!)# 按番号/关键词 python3 scripts/pt_search.py "SONE-833" --site pttime --adult --limit 10 # 按演员名 python3 scripts/pt_search.py "" --site pttime --adult --actor "浅野心" --limit 10 - M-Team 成人区:API
/torrent/search可能需加mode参数(API 限速 1000次/24h,超限返回 403)
Step 3:展示结果 & 排序
从 user-preferences.md 读取偏好(清晰度、编码),按以下优先级排序:
- 匹配用户偏好(清晰度 + 编码)— 不符合的排后面
- 做种人数最多 — 下载更快
- 体积较小 — 省空间
同一资源多个版本先按规则去重再展示,把最好的版本放最前面。
📋 多作品逐个介绍:如果搜索返回同一演员/导演/系列的多部不同作品,必须逐一介绍,每部列出:
- 🎬 番号 + 标题
- 📅 年份
- 👤 主演(如果不是被搜的那位)
- 📝 大致内容/简介(从标题或描述中提取,一句话概括)
- 💾 大小 + 做种数
- 🏷️ 标签(4K/HEVC 等)
格式示例:
搜到 某演员 6 部作品:
# 番号 年份 简介 大小 做种 1 SONE-689 ? — 3.4G ✅有 2 FNS-116 ? — 10.4G ❌0 ...
让用户看完后自己选要下哪些,不要替用户做决定,也不要一次性全下。
⚠️ 成人内容下载规则(具体阈值和优先级从 user-preferences.md 读取,以下为默认示例):
- PT 站阈值:从偏好文件读取(如「做种 ≥ N 才从 PT 下载」),低于阈值触发公开源回退
- 优选顺序:从偏好文件读取(如「做种数 > 去码 > 字幕 > 画质」)
- 去码关键词:
uncensored破解無碼無修正Reducing Mosaic破坏版破壊版RMleak无码 - 字幕关键词:
中文字幕中文字幕-C-chsubtitle - 清晰度偏好:从偏好文件读取(如「4K/1080p 都接受」)
🎬 同名多版本歧义消除(电影 vs 动画 vs 剧集):
中文标题常同时存在真人电影、动画剧集、漫画改编等多个版本(如「镖人」有 2023 动画剧集和真人电影)。搜索结果中出现不同媒体类型时,必须分组展示并明确询问用户要哪个:
搜到「镖人」有两类结果:
- 🎬 真人电影(2026 上映,暂无资源)
- 📺 动画剧集 S01(2023,5 个版本,做种 59-73)
你要哪个?
不要替用户做选择,更不要看到动画就默默跳过——用户可能还没上映的电影也想先关注。
做种不足时的处理:PT 做种低于阈值 → 自动回退公开源,不需要问用户。
⚠️ PT 做种不足(<5),已从公开源搜索。找到以下版本:
- 同站其他版本(降分辨率/编码,但有种)
- 成人番号 → Sukebei Nyaa 公共源(
?page=rss&q=<番号>,走代理)- 等 PT 站做种恢复
列出所有可行选项,让用户自己选。
⚠️ 你偏好的 4K HEVC 版本做种数为 0,可能下不动。有几个替代方案:
- FNS-095 [4K HEVC] 10.4G · 做种 0 — 你偏好的格式,但可能没速度
- FNS-095 [1080p HEVC] 3.2G · 做种 15 — 降分辨率但编码一致,速度有保障
- FNS-095 [4K AVC] 15.6G · 做种 8 — 编码降级但分辨率不变
让用户自己选,不要替用户做决定。同时列出偏好版和替代版,标注各自的取舍(分辨率/编码/体积/速度)。
Step 4:🛑 用户确认闸门(必须!禁止跳过!)
搜到资源后绝不能直接下载。 必须将结果汇总展示给用户,等待确认。
展示格式(每部资源必须包含以下全部字段):
🎬 番号/片名:XXX
📅 发行日期:YYYY-MM-DD
👤 主演:XXX、XXX
🎬 导演:XXX(如有)
📝 简介:一句话剧情概括
📡 来源站点:PTTime / M-Team / Sukebei
💾 大小:X.X GB
📊 做种数:N
🏷️ 版本标签:4K / HEVC / 字幕 / 去码
📂 下载路径:/media/xxx/xxx(分类:XXX)用户确认后才执行下载。 用户说「下」「下载」「all」「全下」「下第N个」等确认词 → 进入 Step 5 推送。
对于定时追剧(cron),同样遵守此规则:cron 只做搜索+去重+展示,把结果报告给用户,由用户确认后再下载。cron 不自动推送任何种子。
Step 5:确认路径 & 添加到 qBittorrent
⚠️ 仅在用户明确确认后执行此步骤!
⚠️ 推送前必须查 Jellyfin 去重(如果已配置)! 避免下已经有的资源。
前置验证 — 确保 API key 确实在 secrets.env 中:
Agent 跨会话后无法保留运行时状态,API key 必须落在 secrets.env 文件里才能持久。每次做去重前先跑:
bash scripts/env_check.sh # 检查环境变量是否齐全
python3 scripts/connectivity_check.py # 实际连接测试(登录/API/站点可达性)如果 Jellyfin key 缺失,直接用 clarify() 向用户索要(不要猜测、不要跳过、不要尝试其他来源),拿到后写入 secrets.env 再继续。
🛡️ 下载历史防重复(最高优先级,先于 Jellyfin 检查!)
⚠️ 致命场景:用户下载了某部片 → 看了不喜欢 → 手动从 Jellyfin/qB 删除 → 定时任务发现「片库缺失」→ 又给下回来了。下载历史记录就是防这个的。
每条推送成功的下载,必须记录到 pt_downloaded.json:
{
"items": [
{"code": "MIMK-267", "title": "カラミざかり同窓会編", "added_at": "2026-05-28T12:00:00", "source": "sukebei", "status": "downloaded"}
]
}去重检查顺序(三步验证,缺一不可):
- 第一步:查下载历史 →
pt_downloaded.json里有 → 无条件跳过下载- 说明曾经推送过,无论用户后来删没删,都是已知的,不重复下载
- 这是最快的检查(本地 JSON,无 API 调用),必须先于 JF 检查
- 第二步:查 Jellyfin → 仅有 JF 未配置时才跳过
- 第三步:比时间戳 → 防止误判当下正在下载的内容
# 第一步:查下载历史(先于一切)
# 方式1:单条检查
python3 scripts/download_history.py check --code <番号>
# → {"exists": true} → 跳过下载
# 方式2:批量过滤(推荐,配合缺失列表使用)
echo -e "MIMK-267\nMIDA-574\nNEW-001" | python3 scripts/download_history.py filter
# → NEW-001 (只输出真正需要下载的)| 检查结果 | 判断 | 动作 |
|---|---|---|
在 pt_downloaded.json 中 |
曾经下载过 — 用户可能已删 | 🚫 跳过,不重复下载 |
| 不在历史 + JF 有 + 时间戳正常 | 片库已有 | 跳过 |
| 不在历史 + JF 无 | 真正缺失 | ✅ 下载 |
Jellyfin 去重检查(仅当以上验证通过):
# 通用模板 — 地址和 key 从 secrets.env 读取
curl -s "http://<jf_host>:8096/Items?searchTerm=<keyword>&includeItemTypes=Movie,Series&recursive=true" \
-H "X-MediaBrowser-Token: <api_key>" | python3 -c "
import sys,json; d=json.load(sys.stdin)
print(f'已存在 {d[\"TotalRecordCount\"]} 条')
"
# 番号成人内容 → 查偏好文件配置的成人 Jellyfin
curl -s "http://<jf_adult_host>:8096/Items?searchTerm=<fanhao>&recursive=true" \
-H "X-MediaBrowser-Token: <api_key>" | python3 -c "
import sys,json; d=json.load(sys.stdin)
print(f'片库已存在 {d[\"TotalRecordCount\"]} 条')
"判断逻辑(两步验证):
第一步:搜 Jellyfin 看有没有
TotalRecordCount == 0→ 没有,继续下载流程 ✅TotalRecordCount > 0→ 片库有同名资源,进入第二步
第二步:比时间戳(关键!)
不能只看 TotalRecordCount > 0 就判重复——用户可能先下载了,Jellyfin 才自动扫描入库的。必须对比时间线:
# 获取 Jellyfin 入库时间
jf_date=$(curl -s "http://<host>:8096/Items?searchTerm=<keyword>&recursive=true" \
-H "X-MediaBrowser-Token: <key>" | python3 -c "
import sys,json; items=json.load(sys.stdin)['Items']
if items: print(items[0].get('DateCreated','')[:19])
")
# 获取 qBittorrent 添加时间
qb_added=$(curl -b <cookie> "http://<qb_host>:<port>/api/v2/torrents/info" | python3 -c "
import sys,json
for t in json.load(sys.stdin):
if '<keyword>' in t['name']:
from datetime import datetime
print(datetime.fromtimestamp(t['added_on']).isoformat())
")
# 比较:JF 入库 < qB 添加 → 真重复;JF 入库 > qB 添加 → 下载后入库的正常流程| 时间线 | 判断 | 动作 |
|---|---|---|
JF DateCreated < qB added_on |
真重复 — 下载前片库已有 | 停止下载,「你已经有《XXX》了」 |
JF DateCreated > qB added_on |
非重复 — 下载后 JF 扫入的 | 跳过,继续搜其他缺失的 |
| 无法获取时间戳(API key 丢失等) | 不确定 — 让步放行 | ⚠️ 告知用户「JF 有此片但无法确认时间线,保留不删」 |
- Jellyfin 未配置 → 跳过所有去重检查,直接下载
qBittorrent 分类映射:
初始化时一次性读取 qB 分类 API(GET /api/v2/torrents/categories),展示给用户确认内容类型映射(电影/电视剧/纪录片/成人等),写入 user-preferences.md 的分类映射段。
日常使用时直接读偏好文件,不走 API。仅在以下场景更新映射:
- Agent 发现种子所属分类不在偏好文件中
- 用户在 qB 中新建/修改了分类
- 用户主动要求重新配置
路径选择逻辑:
- 偏好文件有匹配的分类路径 → 直接用
- 无匹配 → 询问用户选择分类并更新偏好文件
推送下载 — 必须使用两步法,禁止只用 URL 推送!
⚠️ PT 站的 download.php?id=X 需要 PT Cookie 才能访问,qBittorrent 没有这个 Cookie,直接用 URL 推送会静默失败(qB 返回 Ok. 但实际没有添加任何任务)。必须先把 .torrent 文件下载到本地,再通过 multipart form 上传。
🏷️ 站点标签(必须!):每次添加下载任务时,必须打上来源站点标签,用于后续追踪、比率管理和问题排查。
🎬 公开磁链文件选择:Sukebei、JavBus 等公开源的种子常附带广告图片、URL 文件、垃圾 txt。推送时使用 --list-files 列出文件 → 展示给用户确认 → --select-files 选择下载。批量场景可用 --max-video 自动选最大视频文件。PT 站种子不需要文件选择(PT 资源干净)。
| 来源 | 标签 | 说明 |
|---|---|---|
| M-Team (馒头) | mteam |
API 下载 |
| PTTime | pttime |
Cookie 下载 |
| BTSchool | btschool |
Cookie 下载 |
| CarPT | carpt |
Cookie 下载 |
| HDFans | hdfans |
Cookie 下载 |
| 1PTBar | 1ptba |
Cookie 下载 |
| SoulVoice | soulvoice |
Cookie 下载 |
| 织梦 (zmpt.cc) | zmpt |
Cookie 下载 |
| Sukebei Nyaa | sukebei |
公开磁链 |
| JavBus | javbus |
公开磁链 |
标签通过 qBittorrent 的 /api/v2/torrents/addTags API 添加,qb_add.py 自动处理。curl 手动推送时也必须补打标签:
# 手动推送后补标签
HASH=$(curl -s -b /tmp/qb_cookies.txt "<QB_HOST>/api/v2/torrents/info?sort=added_on&reverse=true&limit=5" | python3 -c "import sys,json;[print(t['hash']) for t in json.load(sys.stdin) if '<关键词>' in t['name']]")
curl -s -b /tmp/qb_cookies.txt -X POST "<QB_HOST>/api/v2/torrents/addTags" --data-urlencode "hashes=$HASH" --data-urlencode "tags=sukebei"# Step 1: 用 PT Cookie 下载 .torrent 文件到本地
import urllib.request
req = urllib.request.Request(
"https://<pt_site>/download.php?id=<id>",
headers={"Cookie": <pt_cookie>, "User-Agent": "Mozilla/5.0"}
)
with urllib.request.urlopen(req, timeout=30) as resp:
torrent_data = resp.read()
# 验证是 torrent 文件:data[:2] == b'd8'
with open('/tmp/torrent.torrent', 'wb') as f:
f.write(torrent_data)
# Step 2: 登录 qB 后通过 multipart 上传本地文件
cj = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))
login_data = f"username={quote(qb_user)}&password={quote(qb_pass)}".encode()
opener.open(f"{qb_url}/api/v2/auth/login", data=login_data)
# 用正确的分类名(从 qB categories API 读取实际名称,如"电视剧"不是"tv")
import binascii
boundary = '----QB' + binascii.hexlify(os.urandom(8)).decode()
with open('/tmp/torrent.torrent', 'rb') as f:
file_bytes = f.read()
body = (
f'--{boundary}\r\n'
f'Content-Disposition: form-data; name="torrents"; filename="download.torrent"\r\n'
f'Content-Type: application/x-bittorrent\r\n\r\n'
).encode() + file_bytes + f'\r\n--{boundary}--\r\n'.encode()
add_req = urllib.request.Request(
f"{qb_url}/api/v2/torrents/add?category={quote('电视剧')}", # 必须用实际分类名!
data=body,
headers={'Content-Type': f'multipart/form-data; boundary={boundary}'}
)
opener.open(add_req)
# Step 3: 验证(必做!)
# qB 返回 "Ok." 不代表成功 — 必须回查 API 确认种子真的出现在列表中
with opener.open(f"{qb_url}/api/v2/torrents/info?sort=added_on&reverse=true&limit=5") as resp:
recent = json.loads(resp.read())
found = any('<keyword>' in t.get('name','') for t in recent)
if not found:
# 可能 category 名不匹配或路径错误,排查并修复
...
# Step 4: 记录下载历史(必做!)
# 防止用户手动删除后被定时任务重复下载
import subprocess
subprocess.run([
"python3", "scripts/download_history.py", "add",
"--code", "<番号>",
"--title", "<标题>",
"--source", "<站点标签>"
], cwd=os.path.dirname(__file__))分类选择指南(先调 GET /api/v2/torrents/categories 读 qB 实际分类,再匹配):
⚠️ skill 中不硬编码分类名和路径。用户的分类信息存于
user-preferences.md和secrets.env,Agent 运行时从 API 动态读取后匹配。
常用分类映射示例(仅作参考,实际以 API 返回为准):
| 内容类型 | 典型分类名 | 说明 |
|---|---|---|
| 电影 | 电影 | 影视类 |
| 电视剧 | 电视剧 | 影视类 |
| 纪录片 | 纪录片 | 影视类 |
| 成人 | 用户自定义 | 从偏好文件读取默认路径 |
路径错误修复:如果推送到错误路径,用 setCategory + setLocation API 修正:
POST /api/v2/torrents/setCategory {hashes, category}
POST /api/v2/torrents/setLocation {hashes, location}告知用户已添加,附详情页链接和预计大小。
Step 6:后台定时任务(自动创建)
定时任务在 Step 0 的「配置完成后自动初始化」中已自动创建,这里仅作参考说明。
三个常驻任务:
| 任务 | 频率 | 触发条件 | 通知内容 |
|---|---|---|---|
| 下载进度检查 | 每 15 分钟 | 有下载完成 | ✅ xxx 下载完成 |
| 超 7 天 0% 死种 | 💀 xxx 死种 N 天 | ||
| 无事件 | 不通知(静默) | ||
| 自动追剧 | 每天 10:00 | 有新资源 | 展示资源详情等用户确认(不自动下载) |
| 无新资源 | 今日无新资源 | ||
| 公开种检查 | 每 30 分钟 | 有待清理项 | 列出可清理/死种清单,等确认后删 |
| 无事件 | 不通知 |
管理命令:
- 暂停:「暂停 PT下载进度检查」
- 恢复:「恢复 PT下载进度检查」
- 查看:「列出定时任务」
PT 刷流 — 自动保种赚上传(可选)
⚠️ 非必选项。未配置则跳过。用户在 Step 0 被询问是否需要。
刷流是 PT 站常见的保号策略:自动下载免费(Freeleech)大包种子做种赚上传量,达到天数后自动删除换新种。
配置
用户在 Step 0 被询问刷流偏好,生成 <skill-dir>/pt_boost.json(模板:templates/pt_boost.example.json):
{
"enabled": false,
"global": {
"boost_category": "boost",
"boost_save_path": "<dedicated_download_path>",
"max_seed_days": 7,
"max_torrents": 20,
"dead_seed_hours": 48,
"delete_files": true
},
"sites": {
"mteam": {
"search_keyword": "",
"freeleech_only": true,
"exclude_hr": true,
"sort_by": "seeders",
"sort_order": "desc",
"min_size_gb": 5,
"max_size_gb": 200,
"min_seeders": 5,
"max_results": 50
}
},
"schedule": "0 8 * * *",
"per_run_add_limit": 5
}qBittorrent 凭据统一从 secrets.env 读取(QBITTORRENT_URL/USER/PASS),不在此文件中重复存储。
| 字段 | 说明 |
|---|---|
global.boost_category |
qBittorrent 分类名,专用于刷流 |
global.boost_save_path |
刷流专用下载目录(独立于正常下载) |
global.max_seed_days |
做种多少天后自动删除 |
global.max_torrents |
同时做种上限 |
global.dead_seed_hours |
死种(stalledDL)超过此时长后自动清理(与 max_seed_days 独立) |
global.delete_files |
删除种子时是否同时删除文件 |
sites.<site>.search_keyword |
搜索关键词,空字符串表示拉最新种子列表 |
sites.<site>.freeleech_only |
是否仅下载免费种子 |
sites.<site>.exclude_hr |
是否排除 HR 种子 |
sites.<site>.min_size_gb / max_size_gb |
种子大小范围 |
sites.<site>.min_seeders |
最少做种人数 |
sites.<site>.max_results |
每次搜索取前 N 个结果 |
per_run_add_limit |
每次运行最多新增多少个种子 |
每日定时任务
📌 此任务仅在用户启用 PT 刷流时创建,不属于 Step 0 的三项核心任务。
固化在 cron,一条命令完成「清理→辅种检查→新增」全流程:
cronjob(action='create',
name="PT刷流",
schedule="0 8 * * *", # 每天早上8点
prompt="运行 python3 scripts/pt_ratio_boost.py run,中文总结今日刷流情况后通知。",
skills=["pt-claw"],
deliver="origin"
)每日执行逻辑(pt_ratio_boost.py run)
- 删除过期种子:做种超过
max_seed_days天的 → 删除种子+文件 - 清理死种:下载中超过 2 天没进度的 → 删除
- 新增刷流种:
- 计算剩余槽位(
max_torrents- 当前数) - 从配置的站点搜索免费种子(mteam 走 API)
- 按条件过滤:Freeleech > 大小范围 > 做种人数
- 加入 qBittorrent,分类
boost_category,路径boost_save_path
- 计算剩余槽位(
- 输出状态:当前种子数/上限、总上传/下载、分享率
脚本
- scripts/pt_ratio_boost.py — 刷流主脚本,三个子命令:
python3 scripts/pt_ratio_boost.py run— 完整刷流一次python3 scripts/pt_ratio_boost.py cleanup— 仅清理过期python3 scripts/pt_ratio_boost.py status— 查看刷流状态
Supported PT Sites
| 站点 | 接入 | 成人区 | 备注 |
|---|---|---|---|
| M-Team (馒头) | REST API | /browse/adult(web),API 待验证 mode 参数 |
POST,x-api-key header |
| PTTime | Cookie | adults.php?searchstr= |
data= attribute 变体 |
| BTSchool, CarPT, HDFans, 1PTBar, SoulVoice, 织梦 | Cookie | 无 | Classic NexusPHP |
M-Team API 要点
- API Host:
https://api.m-team.cc/api - Auth:
x-api-keyheader - Method: 仅 POST
- Quirk:
code是字符串"0",不是整数 - Search:
POST /torrent/search,body{"keyword": "...", "page": 1, "size": 25} - Download:
POST /torrent/genDlToken?id=<torrent_id>(query 参数,返回签名下载 URL) - 限速: 搜索 1000次/24h,超限返回 403
Parser 结构差异
- PTTime 变体:
<tr data=ID>标题行 + stats cells 在</tr>之后、下一个<tr之前 - Classic 变体: 双行结构无
data=属性,通过下载链接定位<tr>边界 - PTTime 成人区 (
adults.php): 与 PTTime 主页相同的 HTML 结构,解析器通用
新增 PT 站适配流程
当用户添加不在已适配列表中的 PT 站时,按以下步骤逐一适配,每步完成后向用户确认再继续。
Step A:信息收集
向用户获取以下信息:
- 站点 URL(如
https://hdtime.org) - 站点是否需代理:直接 curl 首页,连接超时/被墙 → 在 SITES 字典中设
"needs_proxy": true - 是否有成人区:问用户「这站有成人区吗?」→ 有则
new_site.has_adult = true - 认证方式:
- 优先问用户有没有 API Key(站内 控制面板 → API Token)
- 没有 API → 用 Cookie。让用户从浏览器导出或提供 Cookie 字符串
Cookie 存入 secrets.env:PT_COOKIE_<SITE>(站点名大写,如 PT_COOKIE_HDTIME)
Step B:平台识别 — 判断站点框架
用 curl 抓取搜索页 HTML,对照下表识别平台类型:
| 特征 | 平台 | 搜索端点 | 解析模式 |
|---|---|---|---|
<table class="torrents"> 内嵌 <table class="torrentname">,含 details.php?id= |
NexusPHP | torrents.php?search=<kw> |
复用经典解析器 |
<tr data="..." 属性,种子行带 data= |
NexusPHP 变体 | torrents.php?search=<kw> |
复用 PTTime 解析器 |
/torrents.php 返回 JSON |
NexusPHP(新版) | torrents.php?search=<kw> |
直接 json.loads() |
API 端点 /api/torrents 或 /api/v1 |
UNIT3D | API POST | 比照 M-Team API 模式 |
页面含 torrent-search class,卡片式布局 |
Gazelle / Luminance | torrents.php?searchstr=<kw> |
需手写解析器 |
| 完全陌生的 DOM 结构 | 自定义 | 逐个分析 | 手写解析器 |
识别命令:
# 抓搜索页并保存
curl -b "c_secure_uid=...; c_secure_pass=..." \
-x $PT_PROXY \
"https://<site>/torrents.php?search=test" -o /tmp/<site>_search.html
# 快速判断框架
grep -oP 'torrents|torrentname|data-row|torrent-search|api/v1' /tmp/<site>_search.html | sort -uStep C:解析器适配
Case 1:复用已有解析器(NexusPHP 系列)
如果 grep 命中了 torrentname 或 torrents class:
经典 NexusPHP → 复用 BTSchool/CarPT 的解析逻辑(
references/nexusphp-parser-notes.md的「Classic 变体」)- 检查点:
details.php?id=链接格式、download.php?id=格式、搜索页搜索参数名(search/searchstr)
- 检查点:
NexusPHP
data=变体 → 复用 PTTime 解析逻辑(references/nexusphp-parser-notes.md的「PTTime 变体」)- 检查点:
<tr data=ID>是否存在、stats cells 是否在</tr>之后
- 检查点:
无需重写代码,只需在 pt_search.py 中注册新站点:
# 在 SITES 字典中添加
SITES["new_site"] = {
"name": "新站名",
"url": "https://<site>",
"parser": "nexusphp_classic", # 或 "nexusphp_pttime"
"search_path": "torrents.php", # 搜索页路径
"search_param": "search", # 搜索参数名
"needs_proxy": True, # 是否需要代理
}Case 2:UNIT3D API
如果站点提供 REST API(类似 M-Team):
- 找 API 文档(通常
https://<site>/api或 Swagger) - 确认认证方式(Bearer token /
x-api-key) - 对照
references/mteam-api.md的模式适配
Case 3:完全自定义解析
新 DOM 结构 → 写新解析函数:
先定位种子列表容器:找到包含所有种子行的最外层元素
# 在 HTML 中找种子标题出现的位置 grep -n "<title>" /tmp/<site>_search.html | head -3 # 向上追溯找容器元素提取单条种子信息,至少拿到:
- 标题(
<a>标签内的文本) - 详情页链接(
details.php?id=xxx) - 下载链接(
download.php?id=xxx或dl.php?id=xxx) - 大小(通常在
<td>中,含GB/MB) - 做种数/下载数/完成数
- 免费/优惠标签(Freeleech / 50% / 2x 等)
- 标题(
写入解析函数
parse_<site>(html),返回与已有解析器相同格式的 dict 列表添加到
pt_search.py的 SITES 字典和路由逻辑
Step D:验证
每步适配完成后立即验证:
# 1. 验证搜索返回结果
python3 scripts/pt_search.py "test" --site <new_site> --limit 3
# 2. 验证下载链接可访问
curl -I -b "<cookie>" -x $PT_PROXY "<download_url>" | head-3
# 应返回 Content-Type: application/x-bittorrent 或 Content-Disposition: attachment
# 3. 验证推送到 qBittorrent
curl -b <qb_cookie> -X POST '<qb_url>/api/v2/torrents/add' \
--data-urlencode 'urls=<download_url>'
# 检查 qB 是否成功添加Step E:补充适配记录
适配完成后:
- 更新
references/nexusphp-parser-notes.md— 如果是新解析模式的变体,记录关键差异 - 更新 skill 的 Supported PT Sites 表格 — 添加新站点行
- 更新
user-preferences.md— 补充新站点的代理需求配置 - 更新
templates/secrets.env.example— 添加新站点的 Cookie/API key 环境变量 - 告知用户:适配完成,附验证结果
适配检查清单
| # | 检查项 | 状态 |
|---|---|---|
| 1 | Cookie / API Key 已写入 secrets.env |
☐ |
| 2 | 平台类型已识别 | ☐ |
| 3 | 搜索返回结果非空 | ☐ |
| 4 | 标题/大小/做种数/下载链接解析正确 | ☐ |
| 5 | 免费标签识别正确(如有) | ☐ |
| 6 | 脱敏/SSH 内容过滤(如有) | ☐ |
| 7 | 成人区搜索(如有) | ☐ |
| 8 | 代理配置正确 | ☐ |
| 9 | 下载链接可用(application/x-bittorrent) |
☐ |
| 10 | qBittorrent 推送成功 | ☐ |
| 11 | pt_search.py SITES 字典已注册 |
☐ |
| 12 | Skill 文档站点表格已更新 | ☐ |
| 13 | 模板文件已更新(secrets.env.example + user-preferences.md) | ☐ |
Jellyfin 集成 — 片库感知 & 自动追剧(可选)
⚠️ 非必选项。未配置 Jellyfin 时,跳过去重检查,PT 下载功能完全不受影响。用户在 Step 0 被询问是否配置。
概述
如果用户配置了 Jellyfin,通过 API 读片库实现去重和自动追剧。
备选下载源(成人内容)
当 PT 站做种不足(<3)时,自动回退到公开源。三个公开源互为补充:
javbus-api — 影片元数据 API(可选)
⚠️ 非必选项。不部署也能通过裸爬方式获取磁链、封面和预览图。部署后数据更结构化,磁链可排序,封面预览一键获取。用户在 Step 0 被询问是否部署。
| 源 | 类型 | 接入方式 | 做种数 | 封面 | 预览图 | 备注 |
|---|---|---|---|---|---|---|
| javbus-api | REST API | Docker 自部署 (localhost:8922) | ❌ 无 | ✅ | ✅ | 首选,磁链多+有去码/AI版 |
| Sukebei Nyaa | RSS 磁链 | ?page=rss&q=<番号> |
✅ 有 | ❌ | ❌ | 备选,有种数可判断活性 |
| JavBus 裸爬 | HTML+Ajax | 两步(详情页+Ajax) | ❌ 无 | ✅ | ✅ | javbus-api 不可用时的备选 |
优先级:JavBus(javbus-api,磁链多+去码/AI版)→ Sukebei Nyaa(有种数)→ JavBus 裸爬(最后手段)
Sukebei Nyaa(首选)
直接用脚本:python3 scripts/sukebei_search.py <CODE> --limit 10,返回 JSON(seeders/leechers/size/magnet/score)。不要手动 HTML 解析——脚本已处理 RSS、去重、广告过滤、评分排序。
<item>
<title>SNOS-151 [自提征用]...</title>
<link>https://sukebei.nyaa.si/download/4581362.torrent</link>
<nyaa:seeders>1</nyaa:seeders>
<nyaa:leechers>10</nyaa:leechers>
<nyaa:infoHash>285382bc8b54d3c28c71d9a3158bb955b0cde017</nyaa:infoHash>
<nyaa:size>6.3 GiB</nyaa:size>
</item>公开源搜索链路(成人内容,PT做种不足时触发):
- PT 站搜到番号 → 做种低于阈值则标记为「低种」,触发回退
- 第一轮 — JavBus(javbus-api):
GET /api/magnets/{番号}?gid=X&uc=Y,获取结构化磁链(含大小/HD/字幕/去码标记),优先挑去码(uncensored/破解)/AI增强版本 - 第二轮 — Sukebei Nyaa(有种数可判断活性):
python3 scripts/sukebei_search.py <番号> --limit 5 - 两源结果合并,按用户偏好排序(去码 > AI增强 > 做种数 > 字幕 > 画质)
- 通过
magnet:?xt=urn:btih:<hash>推 qBittorrent,标签javbus或sukebei
JavBus(备选,量大无种数 + 封面/预览图)
JavBus 不提供做种数,但磁链数量是 Sukebei 的 2-3 倍,还能获取封面和内容预览截图。适合 Sukebei 也找不到种时碰运气。
参考项目:ovnrain/javbus-api — TypeScript,封装了完整 JavBus 接口(影片详情/磁链/搜索/演员),可作为自部署 API 或参考其解析逻辑。
方式一:自部署 javbus-api(推荐,功能最全)
使用 skill 目录下的 docker compose 模板部署:
# 复制模板到部署目录,替换代理地址为 PT_PROXY 的值
cp templates/docker-compose.javbus-api.yml ~/javbus-api/docker-compose.yml
# 编辑 ~/javbus-api/docker-compose.yml,将 <PT_PROXY> 替换为实际代理地址(同 secrets.env 中 PT_PROXY 的值)
docker compose -f ~/javbus-api/docker-compose.yml up -d部署后在 secrets.env 中设置 JAVBUS_API_URL=http://localhost:8922。
部署后可用 REST API:
| 端点 | 功能 | 返回 |
|---|---|---|
GET /api/movies/{番号} |
影片详情 | 封面图、预览截图(≤20张)、演员、导演、标签、时长、gid、uc |
GET /api/magnets/{番号}?gid=X&uc=Y |
磁链列表 | 结构化:hash、大小(bytes)、HD/字幕标记、日期,可按大小/日期排序 |
GET /api/movies/search?keyword=xxx |
关键词搜索 | 番号+标题+封面+标签列表 |
GET /api/stars/{starId} |
演员详情 | 演员名、作品列表、头像 |
影片详情返回示例(关键字段):
{
"id": "SSIS-406",
"title": "SSIS-406 才色兼備な女上司が...",
"img": "https://www.javbus.com/pics/cover/8xnc_b.jpg", // 封面大图
"date": "2022-05-20",
"videoLength": 120,
"director": {"id": "hh", "name": "五右衛門"},
"stars": [{"id": "2xi", "name": "葵つかさ"}],
"genres": [{"id": "e", "name": "巨乳"}],
"samples": [ // 内容预览截图(大图+缩略图)
{"src": "https://pics.dmm.co.jp/.../ssis00406jp-1.jpg",
"thumbnail": "https://www.javbus.com/pics/sample/8xnc_1.jpg"}
],
"gid": "50217160940",
"uc": "0"
}磁链返回示例(结构化,可直接排序筛选):
[{
"id": "17508BF5C17CBDF7C77E12DAAD1BDAB325116585",
"link": "magnet:?xt=urn:btih:17508...&dn=SSNI-730-C",
"isHD": true,
"title": "SSNI-730-C",
"size": "6.57GB",
"numberSize": 7054483783,
"shareDate": "2021-03-14",
"hasSubtitle": true
}]方式二:裸爬 JavBus(无需部署,轻量)
# Step 1: 获取影片详情(含封面 + 预览图 + gid)
curl -s "https://www.javbus.com/<CODE>" -x <proxy> | python3 -c "
import sys,re,json
t=sys.stdin.read()
# 封面
cover=re.search(r'class=\"bigImage\"[^>]*href=\"([^\"]+)\"', t)
# gid
gid=re.search(r'var gid = (\d+)', t)
# uc
uc=re.search(r'var uc = (\d+)', t)
# 预览图(sample images)
samples=re.findall(r'https://pics\.dmm\.co\.jp[^\"]+\.jpg', t)
info={'cover':cover.group(1) if cover else None,
'gid':gid.group(1) if gid else None,
'uc':uc.group(1) if uc else None,
'samples':samples[:10]}
print(json.dumps(info,ensure_ascii=False))
"
# Step 2: 用 gid+uc 调 Ajax 拿磁链列表(同上)
curl -s "https://www.javbus.com/ajax/uncledatoolsbyajax.php?gid=<GID>&lang=zh&img=...&uc=<UC>" \
-x <proxy> -H "Referer: https://www.javbus.com/<CODE>"
# Step 3: 提取磁链 + 去重(同一 hash 会出现多次)
python3 -c "
import sys,re,urllib.parse
seen=set()
for m in re.finditer(r'magnet:\?xt=urn:btih:([a-f0-9A-F]{40})(?:&dn=([^&\042\047]+))?', sys.stdin.read()):
ih=m.group(1).upper()
if ih not in seen:
seen.add(ih)
dn=urllib.parse.unquote(m.group(2) or '')
print(f'magnet:?xt=urn:btih:{ih}&dn={dn}')
"JavBus vs javbus-api 对比:
| 能力 | 裸爬 | javbus-api |
|---|---|---|
| 磁链获取 | ✅ | ✅ |
| 结构化磁链(大小/HD/字幕) | ❌ 需额外解析 | ✅ 开箱即用 |
| 封面预览 | ✅ | ✅ |
| 内容预览截图 | ✅ 可获取 | ✅ 开箱 ≤20张 |
| 按大小/日期排序 | ❌ | ✅ |
| 演员作品列表 | ❌ 需额外页面 | ✅ /api/stars/{id} |
| 部署成本 | 无 | Docker 一行 |
公开磁链筛选规则(添加前必过):
Sukebei/Nyaa 等公开站混杂大量广告、合集、预告片,添加前必须逐条筛选:
🚫 必须排除的:
- 标题含广告词:
加群QQ微信tg广告推广福利免费导航 - 合集/大包:
合集大合集まとめpackcollection全作品,只下单部 - 预告/宣传:
预告宣传片sampletrailer预览 - 标题是纯 hash 或乱码的
- 文件数量异常多(>5 个视频文件)的
✅ 优先选:
- 带明确标签的:
FHDCHD4K中文字幕H265HEVC - 有清晰番号 + 标题的
- 做种数 ≥ 1(0 种的除非是唯一版本,否则跳过)
流程:搜到 N 条 → 逐条过筛 → 只保留正经片源 → 展示给用户选 → 再添加
公开磁链管理规则(区别于 PT 私有种子,Sukebei + JavBus 都是公开源):
| 规则 | PT 私有种子 | 公开磁链(Sukebei/JavBus) |
|---|---|---|
| 做种 | 必须保种,否则封号 | ❌ 下载完立即删种,保留文件 |
| 上传 | 正常做种上传 | 🚫 禁止上传,避免占带宽 |
| 完成处理 | 持续做种 | 自动停止+删除任务 |
为什么公开磁链不做种:公开 tracker 没有分享率要求,做种纯浪费上行带宽。PT 站有考核必须保种。
公开磁链生命周期监控:
下载完成后自动处理:
# 下载完成 → 立即暂停并删除任务(保留文件)
curl -b <cookie> -X POST '<qb_url>/api/v2/torrents/delete' \
--data-urlencode 'hashes=<hash>' \
--data-urlencode 'deleteFiles=false'定期检查 cron(每 30 分钟):
cronjob(action='create',
name="公开磁链状态检查",
schedule="every 30m",
repeat=-1,
prompt="""运行 `python3 scripts/qb_public_cleanup.py --check` 检查公开磁链(TAG ONLY:sukebei/javbus)。
- 无事 → [SILENT]
- 有事 → 列出待清理清单(已完成可删 + 死种),结尾「回复「清了」确认删除」
⚠️ 绝对不能自动删种,必须等用户确认""",
skills=["pt-claw"],
deliver="origin"
)# 搜索 Sukebei Nyaa
curl -s "https://sukebei.nyaa.si/?page=rss&q=SNOS-151" \
-x $HTTP_PROXY
# 从 RSS 提取 magnet
# magnet:?xt=urn:btih:<infoHash>&dn=<title>Jellyfin 连接
⚠️ Jellyfin 未配置 → 跳过此节,不影响 PT 下载。
认证方式:Header X-MediaBrowser-Token: <api_key>(存 secrets.env,从环境变量读取)
核心端点:
| 端点 | 用途 |
|---|---|
GET /Library/VirtualFolders |
列出所有媒体库 |
GET /Items?searchTerm=xxx |
搜索片库 |
GET /Items?parentId=xxx&recursive=true |
浏览指定媒体库 |
GET /Persons/{name} |
查演员信息 |
GET /System/Info |
服务信息 |
配置:Jellyfin 地址和 API key 必须存入 secrets.env(如 JELLYFIN1_API_KEY=xxx、JELLYFIN1_URL=<JF_ADULT_HOST>),skill 脚本从环境变量读取。偏好文件 user-preferences.md 记录实例数量和用途映射。
关注列表
存储文件:pt_wishlist.json
{
"movies": [
{"title": "流浪地球3", "year": 2027, "quality": "4K", "codec": "HEVC"},
{"title": "碟中谍9", "year": null, "quality": null, "codec": null}
],
"actors": [
{"name": "诺兰", "type": "director"},
{"name": "汤姆·克鲁斯", "type": "actor"},
{"name": "浅野こころ", "type": "adult_actress", "exclude_prefixes": ["FNS"]}
],
"fanhao": [
{"code": "SSIS-448", "actress": null}
]
}操作:
- 「关注《流浪地球3》」→ 加到 movies 列表
- 「关注诺兰的电影」→ 加到 actors(type=director)
- 「取消关注 SSIS-448」→ 从 fanhao 移除
- 「我的关注列表」→ 展示当前所有关注
每日自动追剧 Cron Job
📌 此任务在 Step 0「配置完成后自动初始化」中已自动创建。以下为详细参考。
⚠️ Cron 遵守确认闸门规则:只搜索+去重+展示,不自动推送下载。
创建 cron 每天运行一次:
cronjob(action='create',
name="PT自动追剧",
schedule="0 10 * * *", # 每天上午10点
prompt="""自动追剧检查(只搜索展示,不自动下载):
1. 读取 pt_wishlist.json 关注列表
2. **第一步:读 pt_downloaded.json 下载历史**,构建已下载集合
3. 对每个关注项搜索资源(影视→PT全站,成人→javbus-api片单+Sukebei)
4. **逐条三重去重**:
- 🛡️ 先查下载历史:在 pt_downloaded.json 中 → 无条件跳过
- 🎬 再查 Jellyfin:已存在则跳过
- ⏱️ 最后比时间戳:JF 入库 < qB 添加 → 真重复,跳过
5. 检查演员的 exclude_prefixes(如有),跳过排除厂牌的作品
6. **展示结果给用户**(每部列出:站点、大小、做种数、路径、元数据)
7. **绝对不推送下载**——等用户确认。用户说「下」后才执行推送
8. 无新资源则报告「今日无新资源」""",
skills=["pt-claw"],
deliver="origin"
)Cron 执行流程:
- 读取 wishlist → 下载历史 → JF 去重 → 搜索 → 筛选 → 展示结果
- 展示格式同 Step 4 确认闸门的格式(站点、大小、做种、路径、元数据)
- 结尾加「回复「下」或「下第N个」确认下载」
- 用户确认后,在同一次对话中逐部推送到 qBittorrent
重复检测逻辑(逐条/逐集检查)
推送前必须逐条查 Jellyfin(如果已配置),不能批量跳过。 同一部剧的不同集要分别检查:
# 剧集 — 查影视 Jellyfin 是否已有该季该集
curl -s "http://<jf_host>:8096/Items?searchTerm=<剧名>&includeItemTypes=Series&recursive=true" \
-H "X-MediaBrowser-Token: <api_key>" | python3 -c "
import sys,json; items=json.load(sys.stdin)['Items']
...
"
# 电影 — 查影视 Jellyfin
curl -s "http://<jf_host>:8096/Items?searchTerm=<片名>&includeItemTypes=Movie&recursive=true" \
-H "X-MediaBrowser-Token: <api_key>" | python3 -c "
import sys,json; d=json.load(sys.stdin)
print(f'已存在 {d[\"TotalRecordCount\"]} 部')
"
# 番号 — 查成人 Jellyfin
curl -s "http://<jf_adult_host>:8096/Items?searchTerm=<番号>&recursive=true" \
-H "X-MediaBrowser-Token: <api_key>" | python3 -c "
import sys,json; d=json.load(sys.stdin)
print(f'片库已存在 {d[\"TotalRecordCount\"]} 部')
"匹配策略(Jellyfin 未配置则全部跳过):
- 🎬 电影:按标题+年份匹配,
TotalRecordCount > 0即跳过 - 📺 剧集:查到该剧后,获取已有 Season/Episode 列表,只下载缺失的集
- 🔞 番号:成人 Jellyfin 按番号精确匹配
- 👤 主流演员/导演:影视 Jellyfin 查该人作品
- 👤 成人演员:成人 Jellyfin 查该人作品。关注时需和用户确认是主流还是成人演员,不可猜错
常用命令速查
# 查看 Jellyfin 片库
curl -s "http://<host>:8096/Library/VirtualFolders" -H "X-MediaBrowser-Token: <key>"
# 搜索是否存在某部电影
curl -s "http://<host>:8096/Items?searchTerm=流浪地球&includeItemTypes=Movie" \
-H "X-MediaBrowser-Token: <key>" | python3 -c "import sys,json;d=json.load(sys.stdin);print(d['TotalRecordCount'])"
# 查看演员作品
curl -s "http://<host>:8096/Persons?searchTerm=诺兰" -H "X-MediaBrowser-Token: <key>"数据文件迁移(从 v2.4 → v2.5)
v2.5 将数据文件从 ~/.hermes/pt_* 迁移到 skill 目录下,同时新增 secrets.env 和 user-preferences.md 替代原来的 .env + memory 混合存储。
迁移检查清单:
# 1. 确认数据文件在 skill 目录
ls <skill-dir>/pt_wishlist.json <skill-dir>/pt_downloaded.json <skill-dir>/pt_completed_last.txt
# 2. 确认 secrets.env 包含所有凭据
grep -c '=' <skill-dir>/secrets.env # 应 >= 15 行
# 3. 确认 user-preferences.md 存在
test -f <skill-dir>/user-preferences.md && echo "OK"
# 4. 清理旧 ~/.hermes/ 残留(可选,避免混淆)
rm -f ~/.hermes/pt_wishlist.json ~/.hermes/pt_downloaded.json ~/.hermes/pt_completed_last.txt ~/.hermes/pt_boost.jsonCron 任务迁移要点:
- 添加
workdir参数指向 skill 目录 - 提示词中去掉
~/.hermes/pt_*绝对路径,改用相对路径 - 每个 cron 任务不需要
source secrets.env——脚本内部有_load_env_file()自动处理,bash source 会因 Cookie 值中的=误解析 repeat=-1改为repeat="forever"
Common Pitfalls
🔴 致命级
1. 代理用 PT_PROXY,禁止用 HTTP_PROXY:脚本从 PT_PROXY 读取代理并按站点 needs_proxy 标记自动应用。HTTP_PROXY 设在 secrets.env 会导致 Agent 自身 API 走代理,挂了直接失联。
2. 公开磁链只看标签不看 tracker(520 种误删事故):PT 种常有公共 tracker,唯一可靠判断是 qB 标签(sukebei/javbus)。qb_public_cleanup.py 四道防线:占比>20%中止、单次≤50、--check 先查后删、删除前自动备份 .torrent + 元数据。
3. 下载历史防重复:推送成功立即写 pt_downloaded.json。去重第一优先——历史中有 = 无条件跳过。
4. [SILENT] 不能和内容混用:含 [SILENT] 整条静默。无事件只回三字符,有事件发摘要,互斥。
5. 确认闸门——下载和删种都经用户确认:
- 下载:搜到资源禁止直接推送。先展示站点/大小/做种/路径/日期/导演/主演/简介,等「下」才推送。Cron 只搜索不下载。
- 删种:禁止直接调 qB delete API。三个删种脚本必须先用
--check查询+展示待删清单,等用户说「清了」「删」后才执行实际删除。删除前自动备份.torrent+ 元数据。pt_ratio_boost.py run模式下--check仅预览不删除。 - 恢复:
qb_restore.py从备份数据+.torrent文件恢复种子到 qBittorrent,支持单个/批量/最近恢复。
🟠 严重级
6. M-Team API:①限速 403(1000次/24h)②下线 405 ③DNS 302。②③跳过馒头。code 是字符串 "0"。genDlToken 限频返空 bytes。
7. qB URL 推送静默失败:PT 站 download.php 需 Cookie,qB 没有。两步法:Cookie 下 .torrent → multipart 上传,推送后 API 验证。
8. 推送后 setCategory+setLocation:multipart 不认 ?category=,立即修正分类路径。先用 API 读分类列表,中文名("电视剧"非"tv")。
9. JF 已有 ≠ 重复:比 DateCreated vs qB added_on 时间戳。JF 晚于 qB = 正常入库。
10. API key 必须写 secrets.env:不依赖 memory。用 printf >> 追加(write_file 替换敏感值)。
11. PT 恢复种子结构不匹配:本地 .torrent → snatchlist 原始种 → 搜索下载。导入后验证命中已有文件。
12. wishlist 厂牌排除同步:用户说「XXX 厂牌不要」→ 立即更新 exclude_prefixes(如 ["FNS"])。
13. 同名多版本分组展示:电影/动画/剧集分别列出,让用户选。
🟡 注意级
14. 成人搜索链路:PTTime 用 adults.php?searchstr=;M-Team 成人区。成人番号 PT 少收录 → 优先 javbus-api + Sukebei。
15. 演员走元数据不搜 PT:javbus-api 分页 /api/movies/search?keyword=&page=N(/api/stars/{id} 无作品列表)。JF 逐条查(不支持批量)。
16. 日本演员用日文汉字:「七緒」能搜,「七绪」0 结果。先用番号反查获取原名。
17. JF/javbus-api 中文 URL 编码:--data-urlencode 或 urllib.parse.quote()。
18. 公开磁链:JavBus > Sukebei(磁链多/去码/AI)、--list-files→用户确认→--select-files、--max-video 兜底、下完删种保文件、卡死换不同 hash。
19. 三重去重:下载历史 → JF 搜索 → JF DateCreated vs qB added_on。
20. 搜老剧多关键词:中文通用标题 + 季别名 + 英文名+季号。
21. Cookie 403 ≠ 过期:先去代理重试,再判过期。
22. PTTime Cloudflare 拦截:browser_navigate 抓或等冷却。
🔧 脚本纪律
23. 禁止 source secrets.env:Cookie 值含 =(如 sl-session=...==),bash source 会把 = 后的等号部分当命令执行导致错误。脚本内部通过 _load_env_file() 逐行读取 secrets.env,安全处理含等号的值。Cron 提示词中不要写 source secrets.env &&,直接让脚本自行加载。
24. 禁止 /tmp/*.py 临时脚本:日常用 qb_monitor/jf_query/javbus_star/qb_add。新场景事后固化到永久脚本。
25. 内网用 Python 脚本不裸 curl:tirith 拦截 curl→私有 IP。脚本内部 urllib.request 绕过。
26. write_file 替换敏感值:写 secrets.env 用 printf >>。
27. 全量隐私审计(每次推送前自查):API Key、内网 IP、路径、用户 ID 绝不允许出现在 skill 文件中。发现硬编码凭据 → 立即替换为环境变量引用或占位符,并 rotate 对应 Token。审计步骤见 references/privacy-audit-checklist.md。
环境变量清单(secrets.env)
所有敏感配置集中管理,skill 脚本从环境变量读取。
PT 站点
| 变量 | 说明 |
|---|---|
PT_COOKIE_PTTIME |
PTTime Cookie |
PT_COOKIE_BTSCHOOL |
BTSchool Cookie |
PT_COOKIE_CARPT |
CarPT Cookie |
PT_COOKIE_HDFANS |
HDFans Cookie |
PT_COOKIE_1PTBA |
1PTBar Cookie |
PT_COOKIE_SOULVOICE |
SoulVoice Cookie |
PT_COOKIE_ZMPT |
织梦 (zmpt.cc) Cookie |
MTEAM_API_KEY |
M-Team API Key(x-api-key header) |
下载器
| 变量 | 说明 |
|---|---|
QBITTORRENT_URL |
qBittorrent 地址(含端口,如 <QB_HOST>) |
QBITTORRENT_USER |
qBittorrent 用户名 |
QBITTORRENT_PASS |
qBittorrent 密码 |
Jellyfin(可选,未配置不影响下载)
| 变量 | 说明 |
|---|---|
JELLYFIN1_URL |
JF1 地址(如 <JF_ADULT_HOST>) |
JELLYFIN1_API_KEY |
JF1 API Key |
JELLYFIN2_URL |
JF2 地址(如 <JF_TV_HOST>) |
JELLYFIN2_API_KEY |
JF2 API Key |
网络
⚠️ 致命警告:不要在
secrets.env中设置HTTP_PROXY/HTTPS_PROXY!用PT_PROXY代替。脚本按站点needs_proxy标记自动应用代理,不会影响 Agent 自身的 API 调用。
| 变量 | 说明 |
|---|---|
PT_PROXY |
PT 搜索代理地址(如 <PROXY_HOST>),脚本按站点 needs_proxy 标记自动应用 |
javbus-api(可选)
| 变量 | 说明 |
|---|---|
JAVBUS_API_URL |
javbus-api 地址(如 http://localhost:8922),Docker 自部署 |
用户偏好(存入 user-preferences.md,不硬编码)
- 清晰度偏好(4K/1080p/720p)
- 编码偏好(HEVC/AVC/AV1)
- 下载路径分类映射
- 成人内容 PT 做种阈值 & 版本优选顺序
- 关注列表 (
pt_wishlist.json)