voidAlex

pt-claw

"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."

voidAlex 3 Updated 5d ago

Resources

5
GitHub

Install

npx skillscat add voidalex/pt-claw

Install via the SkillsCat registry.

SKILL.md

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 页 → 元数据优先

参考文档

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

公开磁链文件选择(推荐流程)

  1. --list-files:添加种子(暂停)→ 等元数据 → 列出所有文件(名称/大小/类型)→ 返回 JSON
  2. Agent 展示文件列表给用户确认要下哪些
  3. --select-files <HASH> --keep=0,3,5:跳过未选文件 → 恢复下载

--max-video 自动模式(兜底):添加种子暂停 → 等元数据 → 三级策略选正片:

  1. 文件名含番号(如 MIMK-267)→ 取最大的匹配文件
  2. 排除含广告关键词的文件(ad/sample/preview/广告/宣传/预告等)→ 取最大的
  3. 兜底取最大视频文件

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 list

qb_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 --check

javbus_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 --clear

qb_public_cleanup.pyqb_monitor.py --deletept_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.pypt_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_KEY

API 详细文档见 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 容量有限,偏好配置数据量大,全部走文件)。

询问清单:

  1. PT 站点:有哪些站?目前支持 8 站(M-Team、PTTime、BTSchool、CarPT、HDFans、1PTBar、SoulVoice、织梦),不在列表中的需要新增适配。
  2. 下载器:类型、地址、端口、用户名、密码(目前支持 qBittorrent)。
  3. 下载路径偏好:读取 qBittorrent 分类 API,展示所有分类和路径,让用户确认内容类型映射(电影/电视剧/成人等 → 分类 → 路径),写入 user-preferences.md
  4. 清晰度偏好:2160p / 1080p / 720p / 无偏好。
  5. 编码格式偏好:HEVC / AVC / AV1 / 无偏好。
  6. 其他偏好(可选):原盘 vs 压制、字幕、音轨、制作组。
  7. 通知偏好:下载完成后通知到哪里?
  8. 代理配置:代理地址、哪些站需要代理。
  9. Jellyfin 集成(可选):是否有 Jellyfin 服务器?几个?分别什么用途(影视/成人/音乐)?地址和 API Key?→ 没配置也能正常下载,只是无法自动去重。API key 写入 secrets.env,跨会话不丢失。
  10. javbus-api(可选):是否已部署 javbus-api(docker compose 模板在 templates/docker-compose.javbus-api.yml)?→ 已部署则在 secrets.env 设置 JAVBUS_API_URL=http://localhost:8922。未部署也能裸爬 JavBus,只是磁链无结构化数据、无封面预览。
  11. PT 刷流(可选):是否需要自动刷流保号?→ 需要指定:站点、搜索条件(关键词/免费/HR排除)、大小范围、做种人数、qB 专用目录、做种天数上限、死种清理阈值、每次新增上限。配置写入 <skill-dir>/pt_boost.json
  12. 成人内容下载偏好(可选):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.json

2. 创建定时任务(必须,不等用户追问):

任务 频率 作用
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-448SONE-833 JAV 成人 → PTTime adults.php + M-Team API adult → 做种不足→ JavBus(首选) → Sukebei
演员/导演名 影视/成人 先查元数据源获取完整作品列表,再逐部搜 PT
电影/剧集名 影视 → 全 8 站常规搜索
片库统计/演员排行 JF查询 → JF fields=People 分页计数,见 references/jf-actor-count.md

⚠️ 元数据优先原则:搜索演员/导演时,不要直接在 PT 站搜索。PT 站的演员标签经常不完整或误标(中文名和日文名搜出完全不同结果、合集中的客串被误标为主演等)。

元数据获取流程

  1. 成人演员 → 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))))"
  2. 主流演员/导演 → TMDB / IMDb 获取作品列表(后续适配)

  3. 拿到完整片单后:跟 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 读取偏好(清晰度、编码),按以下优先级排序:

  1. 匹配用户偏好(清晰度 + 编码)— 不符合的排后面
  2. 做种人数最多 — 下载更快
  3. 体积较小 — 省空间

同一资源多个版本先按规则去重再展示,把最好的版本放最前面。

📋 多作品逐个介绍:如果搜索返回同一演员/导演/系列的多部不同作品,必须逐一介绍,每部列出:

  • 🎬 番号 + 标题
  • 📅 年份
  • 👤 主演(如果不是被搜的那位)
  • 📝 大致内容/简介(从标题或描述中提取,一句话概括)
  • 💾 大小 + 做种数
  • 🏷️ 标签(4K/HEVC 等)

格式示例:

搜到 某演员 6 部作品

# 番号 年份 简介 大小 做种
1 SONE-689 ? 3.4G ✅有
2 FNS-116 ? 10.4G ❌0
...

让用户看完后自己选要下哪些,不要替用户做决定,也不要一次性全下。

⚠️ 成人内容下载规则(具体阈值和优先级从 user-preferences.md 读取,以下为默认示例):

  • PT 站阈值:从偏好文件读取(如「做种 ≥ N 才从 PT 下载」),低于阈值触发公开源回退
  • 优选顺序:从偏好文件读取(如「做种数 > 去码 > 字幕 > 画质」)
  • 去码关键词uncensored 破解 無碼 無修正 Reducing Mosaic 破坏版 破壊版 RM leak 无码
  • 字幕关键词中文字幕 中文 字幕 -C -ch subtitle
  • 清晰度偏好:从偏好文件读取(如「4K/1080p 都接受」)

🎬 同名多版本歧义消除(电影 vs 动画 vs 剧集)

中文标题常同时存在真人电影、动画剧集、漫画改编等多个版本(如「镖人」有 2023 动画剧集和真人电影)。搜索结果中出现不同媒体类型时,必须分组展示并明确询问用户要哪个

搜到「镖人」有两类结果:

  • 🎬 真人电影(2026 上映,暂无资源)
  • 📺 动画剧集 S01(2023,5 个版本,做种 59-73)

你要哪个?

不要替用户做选择,更不要看到动画就默默跳过——用户可能还没上映的电影也想先关注。

做种不足时的处理:PT 做种低于阈值 → 自动回退公开源,不需要问用户。

⚠️ PT 做种不足(<5),已从公开源搜索。找到以下版本:

  1. 同站其他版本(降分辨率/编码,但有种)
  2. 成人番号 → Sukebei Nyaa 公共源?page=rss&q=<番号>,走代理)
  3. 等 PT 站做种恢复

列出所有可行选项,让用户自己选。

⚠️ 你偏好的 4K HEVC 版本做种数为 0,可能下不动。有几个替代方案:

  1. FNS-095 [4K HEVC] 10.4G · 做种 0 — 你偏好的格式,但可能没速度
  2. FNS-095 [1080p HEVC] 3.2G · 做种 15 — 降分辨率但编码一致,速度有保障
  3. 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"}
  ]
}

去重检查顺序(三步验证,缺一不可):

  1. 第一步:查下载历史pt_downloaded.json 里有 → 无条件跳过下载
    • 说明曾经推送过,无论用户后来删没删,都是已知的,不重复下载
    • 这是最快的检查(本地 JSON,无 API 调用),必须先于 JF 检查
  2. 第二步:查 Jellyfin → 仅有 JF 未配置时才跳过
  3. 第三步:比时间戳 → 防止误判当下正在下载的内容
# 第一步:查下载历史(先于一切)
# 方式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.mdsecrets.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

  1. 删除过期种子:做种超过 max_seed_days 天的 → 删除种子+文件
  2. 清理死种:下载中超过 2 天没进度的 → 删除
  3. 新增刷流种
    • 计算剩余槽位(max_torrents - 当前数)
    • 从配置的站点搜索免费种子(mteam 走 API)
    • 按条件过滤:Freeleech > 大小范围 > 做种人数
    • 加入 qBittorrent,分类 boost_category,路径 boost_save_path
  4. 输出状态:当前种子数/上限、总上传/下载、分享率

脚本

  • 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-key header
  • 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:信息收集

向用户获取以下信息:

  1. 站点 URL(如 https://hdtime.org
  2. 站点是否需代理:直接 curl 首页,连接超时/被墙 → 在 SITES 字典中设 "needs_proxy": true
  3. 是否有成人区:问用户「这站有成人区吗?」→ 有则 new_site.has_adult = true
  4. 认证方式
    • 优先问用户有没有 API Key(站内 控制面板 → API Token)
    • 没有 API → 用 Cookie。让用户从浏览器导出或提供 Cookie 字符串

Cookie 存入 secrets.envPT_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 -u

Step C:解析器适配

Case 1:复用已有解析器(NexusPHP 系列)

如果 grep 命中了 torrentnametorrents class:

  1. 经典 NexusPHP → 复用 BTSchool/CarPT 的解析逻辑(references/nexusphp-parser-notes.md 的「Classic 变体」)

    • 检查点:details.php?id= 链接格式、download.php?id= 格式、搜索页搜索参数名(search / searchstr
  2. 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 结构 → 写新解析函数:

  1. 先定位种子列表容器:找到包含所有种子行的最外层元素

    # 在 HTML 中找种子标题出现的位置
    grep -n "<title>" /tmp/<site>_search.html | head -3
    # 向上追溯找容器元素
  2. 提取单条种子信息,至少拿到:

    • 标题(<a> 标签内的文本)
    • 详情页链接(details.php?id=xxx
    • 下载链接(download.php?id=xxxdl.php?id=xxx
    • 大小(通常在 <td> 中,含 GB/MB
    • 做种数/下载数/完成数
    • 免费/优惠标签(Freeleech / 50% / 2x 等)
  3. 写入解析函数 parse_<site>(html),返回与已有解析器相同格式的 dict 列表

  4. 添加到 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:补充适配记录

适配完成后:

  1. 更新 references/nexusphp-parser-notes.md — 如果是新解析模式的变体,记录关键差异
  2. 更新 skill 的 Supported PT Sites 表格 — 添加新站点行
  3. 更新 user-preferences.md — 补充新站点的代理需求配置
  4. 更新 templates/secrets.env.example — 添加新站点的 Cookie/API key 环境变量
  5. 告知用户:适配完成,附验证结果

适配检查清单

# 检查项 状态
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做种不足时触发):

  1. PT 站搜到番号 → 做种低于阈值则标记为「低种」,触发回退
  2. 第一轮 — JavBus(javbus-api)GET /api/magnets/{番号}?gid=X&uc=Y,获取结构化磁链(含大小/HD/字幕/去码标记),优先挑去码(uncensored/破解)/AI增强版本
  3. 第二轮 — Sukebei Nyaa(有种数可判断活性):python3 scripts/sukebei_search.py <番号> --limit 5
  4. 两源结果合并,按用户偏好排序(去码 > AI增强 > 做种数 > 字幕 > 画质)
  5. 通过 magnet:?xt=urn:btih:<hash> 推 qBittorrent,标签 javbussukebei

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 广告 推广 福利 免费 导航
  • 合集/大包:合集 大合集 まとめ pack collection 全作品,只下单部
  • 预告/宣传:预告 宣传片 sample trailer 预览
  • 标题是纯 hash 或乱码的
  • 文件数量异常多(>5 个视频文件)的

✅ 优先选

  • 带明确标签的:FHDC HD 4K 中文字幕 H265 HEVC
  • 有清晰番号 + 标题的
  • 做种数 ≥ 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=xxxJELLYFIN1_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 执行流程

  1. 读取 wishlist → 下载历史 → JF 去重 → 搜索 → 筛选 → 展示结果
  2. 展示格式同 Step 4 确认闸门的格式(站点、大小、做种、路径、元数据)
  3. 结尾加「回复「下」或「下第N个」确认下载」
  4. 用户确认后,在同一次对话中逐部推送到 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.envuser-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.json

Cron 任务迁移要点

  • 添加 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-urlencodeurllib.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.envprintf >>

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)