侧边栏壁纸
  • 累计撰写 27 篇文章
  • 累计创建 50 个标签
  • 累计收到 9 条评论

目 录CONTENT

文章目录

OSR 上手指南:用一套系统把 PT 下载、网盘同步、STRM 生成和刮削重命名串起来

Jack
2026-08-10 / 0 评论 / 0 点赞 / 156 阅读 / 0 字
温馨提示:
本文最后更新于2026-09-27,若内容或图片失效,请留言反馈。 部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

OSR 上手指南:用一套系统把 PT 下载、网盘同步、STRM 生成和刮削重命名串起来

本文基于 OSR 仓库代码与官方 Wiki 整理,覆盖从 Docker 部署到 PT 订阅全链路自动化的完整流程。
2026-09 更新:补上 Plex 接入、订阅一键诊断、字幕体检、过滤规则历史回放、观看状态同步、电影系列/人物订阅、TG 订阅指令、通知快捷操作、MCP 服务端与 AI 功能、STRM 增量扫描、配置备份与恢复、历史日志检索。
项目地址:github.com/907739769/OSR | Wiki:项目 Wiki

一、OSR 是什么

OSR 的全称是 OpenList STRM Relay,是一套围绕 OpenList(AList 分支) 构建的影视 STRM 管理系统。

如果你的观影链路是这样的:

PT 站点 → qBittorrent 下载到本地 → 上传/同步到网盘 → 生成 STRM → Emby/Jellyfin 播放

那么中间那几步「同步」「生成 STRM」「按 TMDb 规范重命名」「下完通知我一声」,基本都是靠脚本、cron 和人肉巡检拼起来的。OSR 做的事情就是把这条链路做成一个有界面、有记录、有通知、可重试的系统:

  • STRM 生成:递归扫描网盘目录,为视频文件生成对应的 .strm 流媒体文件;
  • 文件夹同步:本地 ↔ 网盘、网盘 ↔ 网盘的增量/全量同步,带复制任务监控与重启兜底;
  • TMDb 刮削与重命名:按元数据识别电影/剧集,模板化重命名,并生成 NFO、海报等刮削产物;
  • 一致性检查:双向孤儿扫描,找出「记录在库里、文件没了」和「文件在库里、记录没了」的残骸;
  • PT 订阅:接入 Torznab 索引器与下载器,RSS 自动追剧、缺集补搜、洗版、H&R 追踪、自动删种、转移做种;
  • 追剧日历与缺集体检:按播出日期铺开每一集,并把「播出多日仍未入库」的集连同「为什么还缺」一起列出来;单条订阅还能一键诊断,逐集看最近一轮搜索卡在哪条规则上;
  • 第三方回调:开放 API 接收 qBittorrent 等下载完成通知,自动触发同步 → 复制 → STRM 全流程;
  • 多端通知与控制:Telegram Bot、企业微信自建应用、通用 Webhook、Bark、Gotify 五条通知渠道,由一张路由表决定「哪种通知发到哪些渠道、发给谁」,前两者还能反向下指令、在通知上直接点「重试」「补搜」;
  • MCP 与 AI:内置 MCP 服务端,Claude Desktop、Cursor 这类本地 AI 助理凭令牌就能查订阅、看追剧进度、触发任务;另有自然语言建过滤规则、种子标题 AI 兜底解析和每周周报;
  • 配置备份与恢复:各项配置连同 PT 订阅导出成一个 JSON,换机器或重装时逐项预览后合并回来。

技术栈上是 Java 25(Spring Boot 4.0.6,开了 Preview 特性用虚拟线程)+ Vue 3 + Vuetify 3 + MyBatis-Plus + MySQL 8.0,前端带 PWA,移动端可以直接「添加到主屏幕」当 App 用。


二、安装部署

2.1 前置要求

项 要求
Docker 20.10+
Docker Compose v2
内存 至少 2GB 可用
磁盘 按 STRM/日志/上传量准备,建议 10GB 起
依赖服务 一个可访问的 OpenList/AList 实例(必需)

2.2 准备部署目录

最简单的方式是直接克隆仓库:

git clone https://github.com/907739769/OSR.git

如果只想部署、不想要源码,也可以只下载 docker-compose.yml、.env.example、nginx.conf 三个文件到一个自建目录里。

2.3 配置环境变量

复制 .env.example 为 .env,逐项改掉里面的占位值:

cp .env.example .env
# MySQL 配置
MYSQL_ROOT_PASSWORD=你的强密码
MYSQL_DATABASE=osr

# 应用专用数据库账号(非 root,仅拥有该库权限,MySQL 镜像会自动创建)
DB_USERNAME=osr_app
DB_PASSWORD=你的强密码

# JWT 密钥,至少 32 字符,建议 64 位随机串
JWT_SECRET=请替换为强随机字符串

# PT 下载器密码 / 索引器 apikey 的加密密钥,至少 32 字符
PT_CREDENTIAL_SECRET=请替换为强随机字符串

# 时区
TZ=Asia/Shanghai

# 前后端分域部署时才需要,生产环境由 Nginx 同源代理 /api,通常留空
# CORS_ALLOWED_ORIGINS=http://your-domain:80

几个容易踩的点:

  • MYSQL_ROOT_PASSWORD 和 DB_PASSWORD 是两个独立的东西,后端连库用的是 DB_USERNAME/DB_PASSWORD 这个最小权限账号,不复用 root;
  • PT_CREDENTIAL_SECRET 不配也能跑,但会退化成内置兜底密钥并打警告日志——它只能保证下载器密码、索引器 apikey 不明文落库,不具备真正的机密性,生产环境务必显式配置;
  • JWT_SECRET 改了之后所有人都要重新登录,这是预期行为。

生成随机串可以直接用:

openssl rand -base64 48

2.4 使用预构建镜像(推荐)

仓库里的 docker-compose.yml 默认是本地构建(build:),需要先 mvn package 才能用。日常部署建议改成官方镜像,把 backend、frontend 两个服务的 build: 段换成 image::

  backend:
    image: jacksaoding/osr-backend:latest
    # build:
    #   context: .
    #   dockerfile: Dockerfile.backend

  frontend:
    image: jacksaoding/osr-frontend:latest
    # build:
    #   context: .
    #   dockerfile: Dockerfile.frontend

2.5 启动

docker compose pull
docker compose up -d

首次启动时 MySQL 要初始化,后端会等健康检查通过后再连库并自动执行建表/迁移脚本,耐心等一两分钟。

2.6 验证

docker compose ps
docker compose logs -f backend

docker compose ps 里三个容器都是 running、restarts 为 0 才算正常。backend 还带健康检查(/api/health,匿名接口,只探数据库),STATUS 要显示 (healthy)。

前端的 depends_on 用的是 condition: service_healthy,所以后端起不来时前端整个不会启动。 这是有意的——比「Nginx 起来了却一路 502」更容易定位。首次启动要跑完全部迁移脚本,healthcheck 的 start_period 给了 180 秒,这段时间显示 starting 属于正常。

想单独确认后端就绪:

curl http://你的IP:6895/api/health

排查启动失败的关键:Java 异常写在容器内的 /data/logs/sys-error.log,不在 docker stdout(stdout 只有启动 banner)。容器反复重启时,先让它崩溃后停住再看日志:

docker update --restart=no osr-backend && docker restart osr-backend

然后把日志捞出来看:

docker cp osr-backend:/data/logs ./tmp

2.7 端口与数据卷

端口 服务 用途
80 frontend (Nginx) Web 界面 +/api 反代 + WebSocket 代理
6895 backend 后端 API
3306 mysql 数据库
卷 说明
/data 宿主机挂载,存放 upload/、logs/、strm/
mysql-data 数据库数据

端口冲突就改 docker-compose.yml 里的 ports 映射,比如把 "80:80" 改成 "8080:80"。

2.8 首次登录

浏览器打开 http://你的IP:80:

  • 用户名:admin
  • 密码:openliststrm666

登录后第一件事:点右上角头像改掉默认密码。


三、基础配置

3.1 配置 OpenList 连接

进入 系统管理 → 参数管理,这里是 OSR 所有全局开关的所在地。必填的只有两项:

参数键 名称 说明
openlist.server.url OpenList 访问地址 如 http://192.168.1.10:5244
openlist.server.token OpenList API token 在 OpenList 后台「其他」页面获取

强烈建议顺手配上的:

参数键 名称 说明
openlist.api.apikey OSR 接口 Apikey 第三方回调(qB 等)的鉴权凭证,不配则回调接口不可用
openlist.tmdb.apikey TMDb API Key 重命名/刮削/PT 订阅都依赖它

可选的 AI 兜底(文件名正则和 TMDb 都识别不出来时才调用):

参数键 默认值 说明
openlist.openai.endpoint 官方地址 支持任意 OpenAI 兼容端点
openlist.openai.apikey 空
openlist.openai.model gpt-5-mini

注意:Telegram Bot 相关参数改完需要重启后端才生效;TMDb / OpenAI 参数在每次重命名时实时读取,改完立即生效。

3.2 值得了解的性能与行为开关

这几个参数默认值就很合理,但了解它们能帮你在大库场景下调优:

参数键 默认 作用
openlist.strm.outputdir /data/strm STRM 文件生成根目录
openlist.strm.encode 0 STRM 内路径是否 URL 编码
openlist.strm.downloadsub 0 生成 STRM 时是否顺带下载字幕
openlist.copy.minfilesize 10 复制的最小文件大小(MB),过滤掉样片、说明文件
openlist.copy.strm 1 复制完成后是否自动生成 STRM
openlist.api.refresh 1 源目录同步列举时强制刷新网盘,保证增量正确性
openlist.api.traversal.refresh 0 目标目录遍历时是否强制刷新。默认走缓存,对网络盘快非常多
openlist.api.traversal.concurrency 10 目录遍历并发度,范围 1–64。大目录树调高能显著提速
openlist.copy.monitor.maxminutes 600 复制任务监控最长时长,超时标记异常,避免无限期挂着
openlist.local.allowedroots /data 本地目录浏览接口的根目录白名单,防止管理端枚举整个宿主机文件系统
openlist.tmdb.metadata.language zh-CN TMDb 元数据语言
openlist.tmdb.image.language zh TMDb 图片语言偏好
openlist.tmdb.image.size original 图片尺寸,可改 w780/w500 省带宽和存储

登录安全相关(登录失败同时按「账号」和「来源 IP」两个桶计数,任一超阈值即临时锁定):

参数键 默认 作用
sys.login.maxRetryCount 5 账号桶阈值,0 = 关闭
sys.login.ipMaxRetryCount 30 IP 桶阈值,用来拦换着用户名喷的扫描器。刻意放宽,避免同一出口 IP 下的正常用户被连坐;0 = 关闭
sys.login.lockMinutes 10 锁定时长,同时也是失败次数的统计窗口;0 = 关闭全部登录锁定

参数设置页按配置键前缀分组(openlist.strm.*、openlist.notify.*、sys.login.* …)。加同前缀的配置会自动落进对应分组,全新前缀落进「其他」。

3.3 配置路径映射

进入 OpenListStrm → 路径管理,把「本地路径」和「网盘路径」建立对应关系。核心要求是两侧的目录树结构必须一致,OSR 才能把一侧的相对路径原样映射到另一侧。

举个例子:

本地:/download/pt/电影/华语电影/功夫/功夫.mp4
网盘:/115网盘/影视/pt/电影/华语电影/功夫/功夫.mp4
       └────┬────┘ └──────────┬──────────────┘
       映射前缀差异        结构必须完全相同

这一步配错,后面同步任务和回调自动化都会「跑成功但文件放错地方」,值得多花两分钟核对。


四、核心功能怎么用

4.1 同步任务

OpenListStrm → 同步任务配置 → 新建:

  1. 填任务名;
  2. 选源路径与目标路径(网盘 ↔ 网盘、网盘 ↔ 本地都支持);
  3. 设置任务启用状态;
  4. 保存后可以手动执行,也可以交给定时任务。

执行结果在 同步任务记录 页查看。这个页面支持:

  • 单条 / 批量重新处理失败记录,顶部还有一个「重试全部失败」(和 TG 的 /retry 走同一套逻辑);
  • 单个 / 多个网盘文件删除;
  • 顶部状态统计条,点一下就按状态筛选;每条记录显示文件大小与失败原因,PC 端点开详情抽屉能看到完整路径和 OpenList 任务 ID。

批量操作超过 20 条会转后台执行,页面会如实提示并在几秒后自动刷新;有处理中的记录时页面每 10 秒静默刷新一次,全部结束后停。

4.2 STRM 任务

OpenListStrm → STRM 任务配置 → 新建:

  1. 填任务名;
  2. 配置扫描路径、输出目录、文件过滤规则;
  3. 需要时展开「本任务覆盖」,单独设置输出根目录、是否下载字幕、最小文件体积——只有填了的项才覆盖,其余继续用参数管理里的全局值;
  4. 保存后手动执行或等定时任务。

结果在 STRM 生成记录 页查看,同样支持单条/批量重新处理与网盘文件删除,视频和字幕分开显示、可按类型筛选。

增量扫描:网盘目录大了以后,每天全量列一遍目录是 STRM 任务最慢的部分。在任务弹窗里打开「增量扫描」后,定时执行会跳过「修改时间没变、生成设置没变、上次里面的文件都处理完了」的叶子目录。几点要知道:

  • 只跳叶子目录(只有文件、没有子目录的那层),上层目录每轮照常列——目录的修改时间不会向上传递,按整棵子树跳的话会漏掉已有季里新增的一集;
  • 每隔 openlist.strm.fullscan.days(默认 7 天)自动全量扫一次兜底,顺带清理已不存在的目录快照;
  • 只有定时执行走增量,页面手动执行、复制完成自动触发、TG 按路径生成仍是全量,想强制刷一遍直接手动点就行。

任务级覆盖是按输出路径反查任务生效的,而不是靠调用方传任务进来:无论这次生成是页面点的、定时任务跑的、复制完成自动触发的,还是 Telegram 里 /strm <路径> 手打的,只要路径落在某个任务的目录下就走该任务的覆盖值。否则同一个目录会因为「谁触发的」而输出到两个不同的根目录、长出两棵 STRM 树,一致性检查还会把其中一棵报成孤儿。

URL 编码开关和视频/字幕扩展名刻意不可按任务覆盖:前者有三个解码侧消费者要从 .strm 内容反解回网盘路径,后者是全站字典,分库配置只会制造解不开的历史数据。

4.3 重命名任务

前置条件:参数管理里配好 openlist.tmdb.apikey;想要 AI 兜底的话再配上 OpenAI 三项。

OpenListStrm → 重命名任务配置 → 新建,指定源目录和目标目录,保存后执行。

识别链路是:本地正则抽取 → TMDb 增强 → AI 补充(仅在前两步都识别不出时)→ 模板渲染。识别结果、原文件名到目标文件名的映射,都能在重命名明细页逐条查看——新文件名下方会列出识别到的类型、季集、分辨率、编码、来源、发布组和 TMDb 链接,识别错了一眼就能看出错在哪一项。

分类规则和命名模板在重命名规则设置里改:模板预览同时给电影和剧集两份样例,「测试」页会给出目标目录,还能直接用编辑中还没保存的模板来试;规则表会标出被前面更宽的规则完全覆盖、永远命不中的规则——最常见的是一条什么条件都没填的规则被挪到了中间,它后面的全部失效。

有几个设计上的取舍值得知道:

  • 目标库里的主文件是 Files.copy 出来的副本,源文件(下载器的保种目录、网盘挂载点)OSR 一律不动——所以系统里根本没有「删除源文件」的入口,这是刻意的:删了它等于毁保种。
  • 「删产物」和「删记录」是两件不同的事,界面上也是分开的。只删数据库记录会导致三个后果:孤儿扫描再也发现不了那批文件、刮削共享元数据(tvshow.nfo 等)的兄弟计数算错、手动重跑任务时会把源文件当成没处理过再复制一份出来。前端确认框里会把这几条写清楚。

4.4 定时任务

系统监控 → 定时任务 里内置了三个作业:

任务 默认时间
重命名任务 每天 02:00
同步任务 每天 03:00
STRM 任务 每天 05:00

可以改 Cron 表达式、手动触发、停用。但不要改调用目标字符串。

另有一个默认暂停的「每周周报」(每周一 09:00):汇总最近 7 天的同步 / STRM / 重命名 / PT 下载情况,配了 OpenAI 时附一段点评,走通知路由里的「每周周报」类型发出。想用的话启用它,或先点「执行一次」看看效果。

「执行一次」是后台受理的:点完立即返回,不会再因为复制、STRM 这类分钟级任务撞上前端超时——以前的表现是界面报错、任务其实还在跑,再点一次就真的并发了两遍。不允许并发的任务在上一轮没结束时不再受理。列表上能看到下次/上次执行时间,执行日志也有了真实的开始时间和耗时。

4.5 一致性检查(孤儿扫描)

OpenListStrm → 重命名一致性检查。它是双向扫描:

  • 正向(记录 → 文件):数据库里有记录,目标位置的文件不见了;
  • 反向(文件 → 记录):目标库里有文件,却找不到对应记录,细分为 local_extra(多余文件)、metadata_only(只有元数据没有视频,Emby 里会显示成一个空剧集)、empty_dir(空目录)。

反向扫描带 mtime 基线:早于对应任务创建时间的文件一律不上报。判据是「任务建起来之前就躺在库里、之后又没被动过的东西不是它产出的」——没这道闸,一个混着历史文件的媒体库每轮会报出成千上万条无关项。

发现的项可以清理或忽略,忽略过的项后续轮次直接跳过,不会反复打扰。

4.6 实时日志与看板

  • 实时日志:WebSocket 推送,info / debug / error 三级,移动端自适应。可以暂停、按 traceId 过滤(点一下日志行里的 traceId 就只看这一条链路)、导出当前过滤结果;断线重连不再清屏或重复刷 500 行。
  • 检索历史:实时日志页切到「检索历史」,在全部日志文件(包括滚动出去的分片)里按关键字 / 正则 / 级别 / 起止时间检索。命中堆栈里的类名会把首行一起带出来,按 traceId 检索能带出整段堆栈。单次扫描有 512MB / 15 秒上限,触发时会写明截断原因。
  • 数据看板与首页待办:首页汇总各类任务的统计,另有一块「待办」——失败记录、下载器离线、媒体服务器连不上、失败下载待处理——点一下直达对应的记录。某一路没取到数时会提示「没取到」,而不是装作没有待办。

五、把 qBittorrent 接进来:下载完成自动上云 + 生成 STRM

这是 OSR 最实用的自动化之一:qB 下载完成 → 回调 OSR → 同步到网盘 → 生成 STRM,全程无人值守。

5.1 前置条件

  1. 参数管理里配好 openlist.api.apikey;
  2. qBittorrent 所在网络能访问到 OSR 的 6895 端口(或走前端 80 端口的 /api 反代);
  3. 本地目录、网盘目录、qB 下载目录三者的目录结构对得上(见 3.3)。

5.2 接口说明

POST /api/openliststrm/notify/notifyByDir
Header: X-API-KEY: <openlist.api.apikey 的值>
Content-Type: application/json

请求体四个字段全部必填:

{
  "srcDir": "/download/pt",
  "srcDst": "/115网盘/影视/pt",
  "qbDlRootPath": "/downloads",
  "qbDlFilePath": "/downloads/电影/华语电影/功夫/功夫.mp4"
}
字段 含义
srcDir 源目录(本地下载根目录在 OSR/OpenList 视角下的路径)
srcDst 目标目录(对应的网盘路径)
qbDlRootPath qB 的下载根目录
qbDlFilePath 本次下载完成的资源路径

OSR 拿到后会用 qbDlFilePath 去掉 qbDlRootPath 前缀得到相对路径,再判断是视频文件还是目录:视频走单文件同步,目录走批量同步。

5.3 qB 侧配置

在 qB 的 config 目录下建一个 notify.sh(按自己的路径改前三个变量):

#!/bin/sh
srcDir="/download/pt"
srcDst="/115网盘/影视/pt"
qbDlRootPath="/downloads"
apiKey="你在参数管理里配的 apikey"
osrUrl="http://osr-host:6895"

curl -s -X POST "${osrUrl}/api/openliststrm/notify/notifyByDir" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: ${apiKey}" \
  -d "{\"srcDir\":\"${srcDir}\",\"srcDst\":\"${srcDst}\",\"qbDlRootPath\":\"${qbDlRootPath}\",\"qbDlFilePath\":\"$2\"}"

然后在 qB 的「下载完成时运行外部程序」里填:

sh /config/notify.sh "%G" "%F"

六、PT 订阅:把追剧也自动化

这是 OSR 里最复杂、也最有意思的模块。它做的事情是:你订阅一部剧,剩下的搜索、筛选、推送下载、进度追踪、入库确认、缺集补搜、洗版、H&R 保种提醒,全部自动完成。

6.1 前置条件

组件 说明
Torznab 索引器 Jackett 或 Prowlarr,至少配好一个 PT 站
下载器 qBittorrent 或 Transmission,OSR 能访问到
媒体服务器 Emby、Jellyfin 或 Plex,必需;可以同时配多台
TMDb API Key 参数管理里的 openlist.tmdb.apikey

媒体服务器为什么是必需的:OSR 判断「这一集齐了」的依据不是下载完成,而是媒体服务器的库里真的能查到它。下完 ≠ 活干完——文件还要传上网盘、还要生成 STRM、Emby 还要扫到,中间任何一步断了这一集就不算数。

菜单在哪:PT 相关菜单按「我要干什么」分成四组,都直接挂在侧边栏根节点——PT 追剧(追剧日历 / 缺集体检(含字幕体检) / 订阅管理 / 热门自动订阅)、PT 下载(下载记录 / 统计仪表盘 / 转移做种)、PT 规则(过滤规则 / 洗版规则 / 黑名单)、PT 接入(索引器 / 下载器 / 媒体服务器)。分组标题已经带 PT,子菜单名不再重复写前缀。

6.2 配置索引器

PT 接入 → 索引器 → 新增:

  • 索引器名称、Torznab URL(从 Jackett/Prowlarr 复制)、API Key;
  • 分类代码(剧集 5000、电影 2000);
  • 轮询周期,默认 600 秒。

轮询周期不要设太短。系统内部对同一索引器有最小 2 秒间隔的限流,撞到站点 429 之后会进入 300 秒冷却——冷却期内的请求会走「背压」分支快速失败,这类失败既不计入失败次数也不清零,专门避免「限流 → 退避放大 → 拉取窗口从几分钟变成几小时」的正反馈。

系统还会做 RSS 覆盖度校验:记录上一轮的最大发布时间作为游标,判断本轮的窗口下沿是否覆盖得上。覆盖不全会告警提醒你缩短周期。(这里刻意用「时间位置」而不是「上一轮首条种子还在不在」当游标——后者在有置顶种子、种子被删、guid 带一次性 token 的站上会恒定误报。)

6.3 配置下载器

PT 接入 → 下载器 → 新增:

  • 类型(qBittorrent / Transmission)、地址、账号密码(支持 HTTPS);
  • 保存路径必须落在某个已启用的同步任务的源目录内——这是硬要求,否则下载完的文件不会流进「同步到网盘 → 生成 STRM」这条管道;
  • 标签,默认 osr-pt(OSR 靠它认领自己推的种子);
  • 最大并发下载数;
  • 角色:DOWNLOAD(订阅下载池)或 SEED_ONLY(只做种的保种机)。

角色这个字段值得单独说:如果你加了一台「接 IYUU 转移+辅种、开着自动删种」的保种机,不把它标成 SEED_ONLY 的话,订阅会把新剧集也往它上面推——而那台机器的清理规则是按保种设计的(做满 N 小时就删),正在补的剧集下上去会被当成保种种子清掉。

媒体服务器(PT 接入 → 媒体服务器):类型选 Emby / Jellyfin / Plex,填地址和 API Key(Plex 填 X-Plex-Token)。「测试连接」会直接告诉你真实原因——401 是 Key 填错、连不上是地址/端口/网络、返回了 HTML 多半是反代把请求转给了别的服务;成功时回显服务器名和版本,局域网里跑着两台 Emby 时这一句能省不少排查。

启用的媒体服务器全部参与入库判定,任一台查到就算入库。所以「电影在 Emby、剧集在 Jellyfin」或者「新旧两台并存迁移中」都能直接用,配置页上会显示每台最近的连通状态。Emby/Jellyfin 的用户 ID 可以在表单里直接拉取,填上之后才能同步观看状态(见 6.11)。

6.4 过滤规则(可选但强烈建议)

PT 规则 → 过滤规则:

  • 硬过滤:最小做种数、体积上下限(支持按集折算)、分辨率/片源/标签要求、标题关键词、描述关键词、是否必须带中文字幕、是否规避 H&R 站点;
  • 排序维度:默认 RESOLUTION,FREE,SEEDERS,SIZE,可调整优先级。

「描述排除词」是标题关键词之外单独的一维,因为有一类属性标题里根本不写。蓝光原盘最典型:国内站只在种子描述里标一句「原盘」,标题与压制版逐字同构,两者都解析成 source=BluRay,来源白名单分不开;体积上限虽然能挡住原盘,却会连体积区间重叠的 REMUX 一起切掉,而 REMUX 是 mkv、播放器本来吃得下。

两条相反的兜底是刻意的:标题为空一律淘汰,描述为空一律放行。标题是索引器必给的字段,描述不是——不少索引器压根不返回 <description>,按「判不出即淘汰」处理会把这些站点的候选整批清光。

改规则之前有三个工具可以先试:

  • 用一句话描述规则:比如「只要 1080p 以上的 WEB-DL,不要杜比视界,体积每集 5G 以内」,AI 生成一份草稿填进表单,逐字段校验,你确认后才保存(需要配 OpenAI);
  • 规则试算:贴一个种子标题(可以附上体积、做种数),看它在编辑中、还没保存的规则下会不会被淘汰、因为哪一条;
  • 历史回放:用最近 3 / 7 / 30 天真实搜到过的候选,比较「已保存的规则」和「编辑中的草稿」,列出会新通过和会新淘汰的种子、属于哪条订阅、因为什么——改完规则心里没底时最有用。回放只读,不发起任何搜索。

6.5 创建订阅

PT 追剧 → 订阅管理:

  1. 搜索剧名/片名(走 TMDb);
  2. 选中作品;剧集还要选季;
  3. 保存——保存那一刻会立即触发一次补充搜索,不用等下一轮 RSS。

订阅列表可以按「自动补搜开没开」「有没有已播出的缺集」筛选、按已播缺集数排序,也能批量开关自动补搜、批量立即补搜(串行执行、带进度、可以中途停)。手动搜索(卡片「更多 → 搜索补齐」)可以限定站点,候选列表能按关键词、站点、单集/区间包/整季筛选。

多用户场景下,订阅页和追剧日历顶部会出现归属筛选(我的 / 公共 / 其他用户),管理员视图会在别人的订阅卡片上标出归属人。

6.6 后台是怎么跑的

创建完订阅后,四个后台任务接管一切:

任务 周期 职责
RSS 轮询 60 秒 拉索引器最新种子,匹配订阅
自动补搜 30 分钟 按订阅粒度主动搜缺失集
下载追踪 30 秒 跟进度、选文件、排除非目标集
媒体库同步 10 分钟 去 Emby/Jellyfin 确认这一集真的入库了
播出日期同步 12 小时 从 TMDb 拉每集播出日期,追剧日历的数据来源
缺集体检提醒 每日 把播出多日仍未入库的集聚合成一条提醒
自动删种 60 分钟 按体积区间 + 做种时长分级删种(默认关)
转移做种 15 分钟 把做够时长的种子搬到另一台下载器(默认无规则)
观看状态同步 1 小时 从媒体服务器读每集看没看过,供订阅卡片展示与补搜排序
保种快照 1 小时 记录各下载器的累计上传量与保种体积,供保种看板用

单集状态机:

MISSING(缺失,等待匹配)
   ↓ 推送到下载器
IN_FLIGHT(在途)
   ↓ 下载完成 + 同步上云 + 媒体库确认
IN_LIBRARY(已入库,完成)
   ↓ 触发洗版
UPGRADING(下载更高画质版本中)

任意环节失败 3 次以上 → BLOCKED(熔断,需人工在下载记录页重试)

几个实现细节挺能体现这个模块的成熟度:

  • 多集包以暂停态推送:推送那一刻不知道包里有哪几集,先暂停,等下载器返回文件列表、选出目标集文件后再启动。单集/电影不暂停(没有选错的可能),磁力链也不暂停(暂停态下下载器不下元数据,会永远卡住)。
  • 包内一个目标集都没有时会中止,而不是照常下发排除指令——把全部文件设成不下载会得到一个 0 字节、永远挂着的僵尸任务,白占并发名额。
  • 只缺一两集时先试单集,季包降级成兜底。早期是季包无条件优先,结果「季包里恰好没有第 41 集、站上明明有单独的第 41 集,却永远只下季包」成了稳态。
  • 标题里没集号时会去种子 description 里找。有一类日更剧发布组把同一季每一集都发成标题逐字相同的种子,集号只写在描述里,不解析的话会被反复当成整季包白推。
  • OSR 默认从不删种,只有两个受控例外:从未下完也从未做种的废种,以及你逐个下载器显式开启的自动删种。

6.7 追剧日历与缺集体检

这两个页面回答的是两个不同的问题。

追剧日历(PT 追剧 → 追剧日历)按播出日期把已订阅剧集的每一集铺在格子里,一眼看出这周有哪几集、哪几集还是灰的。播出日期来自 TMDb,由后台任务每 12 小时同步(首轮兼做存量回填,升级上来的库不需要额外动作);查询本身是纯 SQL 范围查询,翻月不打 TMDb。两个刻意的取向:TMDb 撤掉日期时不清空已有值(撤档信息本身不可靠,清掉会让这一集从日历上凭空消失),并且只写日期确实变了的行(否则每 12 小时把整表刷一遍,看起来像天天都有变化)。

缺集体检(PT 追剧 → 缺集体检)回答的是「这一格为什么还是灰的」。它把播出超过 N 天(默认 3 天)仍未入库的集列出来,分成四档——分档不是严重程度,是处置方向:

分档 含义 该往哪儿查
逾期缺失 压根没下到 搜索链路:关键词、索引器、过滤规则
在途逾期 下了但没进库 下载/上传链路:同步任务、STRM 任务
已熔断 连续失败达阈值 需要人工介入,去下载记录页重置
无播出日期 连「逾期」都算不出来 未定档或 TMDb 未录入,通常只需等

每一集还带一条诊断,直接说清下一步:未开启自动补搜(可在本页一键开启)、补搜没搜到候选、候选被过滤规则淘汰、已熔断、已下好卡在上传或刮削(不需要重下)、正常在流程里。

这个页面的存在本身来自一个设计取舍:自动补搜默认是关的。每条开着的订阅每轮都要向每个索引器打满一整份检索计划,全量开启会让追完的老剧长期空转;代价是「订阅建完就没再管过、集一直缺着」这个最常见的场景在改造前一条提醒都没有——补搜落空通知只对开着开关的订阅发,追剧日历只按日期铺格子、不回答为什么。体检补的是这层可见性,而不是去改默认值。

几个实现上的取向:

  • 诊断挂在集上,不挂在订阅上。同一条订阅完全可能一部分集缺着、另一部分卡在上传,压成一个「主诊断」就得定一套武断的优先级,而那个优先级对用户没有意义。
  • 整个体检是一次纯 SQL 查询,不打任何外部请求,刷新页面不会变成一轮 TMDb/索引器调用。
  • 电影不参与体检。它没有播出日期、逾期天数恒为空,而且「上映后几周内没有资源」是正常状态不是故障,报出来只会把真正缺集的剧集淹掉。这只影响体检的可见性,电影的 RSS 匹配、自动补搜、手动搜索一律照常。
  • 通知只发「逾期缺失」一档(页面展示全部四档)。另外三档各自已经有人管:在途逾期由「入库卡住」负责、熔断在转 BLOCKED 那一刻通知过、无播出日期那档拿去打扰只会稀释信号。这条边界一旦模糊,同一集会从两三个地方各通知一次,用户很快会把整类通知关掉。
  • 按收件人聚合成一条,不是每条订阅发一条——首次启用时积压的订阅可能有几十条,逐条发比不提醒还糟。去重靠指纹:缺的集变了立刻再发,没变则按 pt.health.notify-repeat-days(默认 7 天)重提醒。只按「发过就不再发」是不行的——一部永远补不上的剧提醒一次之后就再无声息,而它最该被记住。

阈值默认 3 天是刻意的宽松:热门剧几小时内就有资源,但冷门剧、原盘小组、等字幕的片子拖一两天是常态,判早了会把一批正在正常走流程的集报成问题,而一个总在误报的看板用户看两次就不看了。

日历里未播出的缺集显示为「待播出」,点开某一集可以直接「查看诊断」或「搜这一集」;体检页也支持带 ?subId= 只看一条订阅。

订阅一键诊断补的是体检覆盖不到的部分:刚播出一两天的集,以及电影。订阅卡片「更多 → 一键诊断」会先列订阅级的前提问题(没开自动补搜、没有可用索引器、订阅不在订阅中、有单独的过滤覆盖),再逐集列出最近一轮搜索拿到了几个候选、各因为什么原因被淘汰了多少个,弹窗里可以就地立即补搜。它只读日志、不发起搜索。TG 里 /diagnose 编号、企微里 诊断 编号 得到的是同一份结果。

字幕体检是缺集体检页里的一张卡片,点「检查」才加载:它去媒体服务器上查每一集实际的字幕流和音轨,分成中文字幕、中文音轨、语言未知、没有中字、未解析媒体信息几类,华语作品直接跳过。直接查媒体服务器而不是从种子标题猜,是因为标题只能说明「没写」,说明不了「没有」。目前支持 Emby / Jellyfin;硬字幕(烧在画面里的)查不到,页面上会注明。

6.8 集号有三套,OSR 会自动对齐

这是长篇动画(航海王一类)最容易踩空的一处建模:

  • OSR 本地与 PT 种子用季内相对集号(One Piece S23E13);
  • TMDb 主数据对这类剧用绝对集号(第 23 季 = 1156..1181);
  • 媒体库按刮削结果组织,可能是上面任意一种(实测有用户的 Emby 把 1172 集全平铺在 Season 1、按绝对号编号)。

种子标题自己就印证了对应关系:One Piece S23E13 Episode 1168。OSR 把 TMDb 集号对齐后落库,日历取日期、对账取集号,因此三处都能正常工作:入库判定(本季本地集号 → 本季 TMDb 集号 → 整部剧任意季的 TMDb 集号,逐级兜底)、搜索与匹配(站上 S23E18 与 S01E1173 两种命名并存,两种都搜得到、都匹配得上)、包内文件识别(种子里的 One Piece S01E1174.mkv 会被归一化回本地集号,不会被误判成「这个包里没有目标集」而中止)。

这类 bug 的症状极其隐蔽:下载记录明明显示 COMPLETED,集状态却永远 MISSING,订阅进度一直卡着,日志里一切正常。

已知缺口:字幕组的 [Sakurato] One Piece - 1173 [2160p] 这类裸数字命名仍然匹配不上,卡点在标题解析而不是集号。

6.9 自动补搜的节奏

自动补搜(订阅编辑页里的开关,默认关闭)每 30 分钟检查一次,每条订阅按自己的间隔(默认 24 小时)实际执行。这个节奏上有四层修正,每层都对应一个具体故障:

  1. 未播出的集不参与。 一部刚播到第 3 集的 12 集新番,剩下 9 集恒为缺失,不做这层的话每轮都打满一整轮索引器请求、每轮落空,而用户收到的是「未找到可用资源,可检查关键词与索引器配置」——真实原因是还没播。省下的请求是次要的,避免这条误导才是主因。
  2. 候选订阅在 SQL 层就收窄(进行中 + 开关开着 + 确实有缺失集)。追完的老剧长期留在进行中是常态,拉全部再内存过滤等于每轮为它们各查一次集表。
  3. 到期时刻带一点按订阅 id 派生的抖动(0 ~ +20%,只向后偏)。否则首次启动时所有订阅同时到期,串行跑完之后又几乎同时到期——这个抱团是自我维持的,不会自己散开。
  4. 单轮有总耗时预算(默认 20 分钟,刻意小于 30 分钟的心跳)。超预算的订阅原样留到下一轮,下轮天然从断点接着走,不会饿死。刻意不用并发解决:限流器是全局的,多条订阅同时搜只会在限流器上排队,撞上等待上限后静默跳过。

还有两条关于「落空」的:按连续落空次数退避(24 → 48 → 96 小时,封顶一周,命中一次立刻回到基准)——片源确实不存在的老剧原本会永远每 24 小时打满一整轮请求,而这件事从现象上根本看不出来;落空通知会去重,除首次外只有淘汰原因的种类变了才再发一次,「压根没搜到候选」与「候选全被 freeOnly 淘汰」处置方向完全相反,这个翻转恰恰是最需要知道的一次变化,不会被去重吃掉。

6.10 转移做种

PT 下载 → 转移做种。把 qBittorrent 上已经做够时长的种子搬到 Transmission(或另一台下载器)继续做种,腾出主下载器的任务槽。这是 IYUU「转移」功能的自建实现。

⚠️ 只覆盖「转移」,不覆盖「辅种」。 辅种要拿 infohash 去其它站点找同一份资源,依赖 IYUU 服务端的站点索引与各站 passkey,OSR 没有也造不出这些数据。装 IYUU 是为了辅种的话,这个功能替代不了它。

一条规则就是一对「源下载器 → 目标下载器」加筛选条件:最短做种时长、体积区间、包含/排除标签、路径映射(两边挂载一致时留空)、目标端标签、单轮上限、校验超时、校验通过后是否删源端种子。规则之间没有优先级,各管一对下载器、各自独立执行。默认每 15 分钟扫一轮,页面上也能「预览」和「立即执行」。

它是个跨轮次的状态机:本轮「导出 → 暂停态加种 → 触发校验」,下一轮「读校验结果 → 启动 → 删源端种子」。中间态落库而不是放内存——校验要跑几分钟到几十分钟,放内存里的话进程一重启,目标端就留下一批暂停态的孤儿种子。

几条硬性安全边界:

  • 目标端一律以暂停态加入、校验到 100% 才启动。 直接以运行态加进去的话,保存路径一旦对不上(路径映射配错是本功能最常见的故障),下载器会把整个种子重新下载一遍。
  • 撤销目标端种子时永远不删文件。 那份文件正是源下载器在做种的数据,删了等于让源端种子立刻变成「文件丢失」、在它所属的站点上记一次 H&R——这是整个功能里唯一能造成真实数据损失的一步。
  • 顺序是「先启动目标端,再删源端」。 反过来的话中间那段窗口两边都不在做种,站点看到的是一个突然消失的种子。
  • H&R 考核中的种子不转移。 换下载器后做种时长要从零重新累计,更要紧的是站点的 H&R 限额是下发到原下载器上的,限额不跟着种子搬家。同理,还有集停在在途/洗版中的记录也不转移:种子下完不等于活干完,文件还要传网盘。
  • Transmission 不能作为转移来源——它的 RPC 里没有导出 .torrent 内容的接口(唯一沾边的字段给的是服务端本地路径)。前端的源下载器下拉直接把它滤掉。
  • 源端的文件选择会跟着搬。 一个「只下了其中几集」的季包在 qB 里照样显示 100%(进度是相对已选文件算的),而导出的 .torrent 不含文件优先级,原样加到目标端就是全选、校验必然到不了 100%,被判成「该路径下没有这份数据」而回滚。这在本项目里根本不是边缘情况——OSR 自己就会给季包排除非目标集。
  • 失败之后不会无限重试:6 小时冷却 + 累计 3 次后停止。失败不改变源端种子的状态,下一轮的判定条件与上一轮完全相同,没有闸门的话任何持续性故障都会变成「每轮转一次、每轮失败一次、每轮发一条通知」。配置改对之后,用记录弹窗里的「清除失败记录」解除——否则种子会因为历史失败次数永远不再被转移,比原来的刷屏更糟。

6.11 其它进阶功能

洗版(画质升级):在升级规则里开启,必须指定 cutoff 目标(分辨率/片源/标签至少填一项)——没有终止条件的话每一集都会永远搜下去把索引器配额烧干,所以三项全空时洗版不激活。判定维度只有分辨率、片源、标签、发布组四项,刻意不引入做种数/体积/免费状态:那些值随时间连续变化,会导致无限循环洗版。洗版不会自动删旧文件。

热门自动订阅:按榜单自动建订阅,可设最低评分、最低评分人数、排除类型、限定地区,单轮新增上限默认 5 部。数据源有四类:

  • TMDb 榜单:每日/每周趋势、热门、正在播出等;
  • RSSHub 豆瓣榜单:TMDb 的热门以欧美内容为主,华语和日韩的「热」在豆瓣上才看得出来。豆瓣条目没有 TMDb ID,OSR 按「归一化标题全等 + 年份差不超过 1 年」去 TMDb 补全——宁可漏也不订错,匹配不上的会记成「未匹配」写进执行日志,看得到、能手动补订;
  • 电影系列:填一个 TMDb 合集的 ID 或页面链接,订阅系列里的全部电影,新出的续集下一轮自然补进来;
  • 人物:填导演或演员的 TMDb ID / 链接,只订他担任导演、或演员表前 5 位、且近一年内上映或尚未上映的电影——要的是「他的新片出了告诉我」,不是把他全部作品都下下来。

你手动删掉的订阅,自动订阅不会再把它加回来。

种子黑名单:拉黑特定发布组或种子,后续不再匹配。

H&R 追踪:按索引器配置最短做种时长和分享率要求(或关系,达标任一即可)。H&R 是站点属性不是种子属性——Torznab 协议没有标准 H&R 字段,只能整站判定。OSR 只做告警,不会也无法自动补救。追踪是跨下载器的:种子被 IYUU 转移走了会去其它下载器的快照里找回来,而不是直接判成违规。

自动删种:按体积区间 + 做种时长分级删除(比如 >50GB 做满 3 天删、其余做满 7 天删)。五条护栏一条都没省:

  1. 总开关默认关,且要逐个下载器显式开启;
  2. 没有启用任何规则就一个都不删(空规则集 = 没有规则说该删它);
  3. H&R 考核中的记录一律保护;
  4. 还有集停在在途/洗版中的记录同样保护——种子下完不等于活干完,文件还要传网盘,大文件跨天是常态;
  5. 每轮有删除上限,超出的会记日志而不是静默截断。

删除以辅种组为最小单位(按内容路径分组),组内每个种子都达标才整组删,任一个不达标整组保留——删掉一个种子的文件会让共用这份文件的其它种子立刻变成「文件丢失」,那是在别的站上记 H&R。

观看状态同步:每小时从媒体服务器读一次每集看没看过(Emby/Jellyfin 需要在媒体服务器配置里填用户 ID,Plex 直接读),订阅卡片的进度后面会显示「已看 N 集」,悬停看最近观看时间。自动补搜每轮会先补最近在看的剧,单轮预算用完时被推到下一轮的是没人在看的老剧。多台服务器取并集,读不到的那台不参与,全都读不到时保留原值。

统计仪表盘:下载量趋势、索引器命中率、搜索淘汰原因分布、失败原因分布,统计卡、趋势点、失败扇形都可以点进去下钻到对应的下载记录。成功率按「完成 /(完成 + 没被接替的失败)」算——一集失败后又被别的种子补上了,就不该再拉低成功率。底部还有一块只对管理员可见的「保种与上传」:每日上传量、保种体积趋势、上传最多的种子与低效保种(做种满 7 天、分享率仍低于 0.2 的,最值得考虑腾地方),每日上传量按下载器累计计数器求差,删种不会让它变成负数。

下载记录:按订阅归属隔离——普通用户只看得到自己订阅的记录。可以按推送/完成/失败日期、失败原因、H&R 状态、索引器、下载器筛选;失败之后同一集已经有新推送的,会标「已由 #x 接替」、不再给重试。不打算处理的失败可以忽略,首页「失败下载待处理」就不再算它(统计里的失败数照算)。管理员可以按保留天数清理旧记录,只删终态、不在 H&R 考核中的。

6.12 PT 排障速查

现象 排查方向
一直不下载 先点订阅卡片的「一键诊断」,逐集看卡在哪条规则上;再看索引器状态和「搜索淘汰原因」看板
下载完了还显示在途 同步任务/STRM 任务有没有执行;媒体服务器配置页的连通状态;文件到底传上网盘没有
想放宽/收紧过滤规则又怕改坏 过滤规则页的「历史回放」,先看改完会多放过和多挡掉哪些种子
某集卡住不动 大概率已 BLOCKED,去下载记录页手动重试
反复失败 看「搜索淘汰原因」统计,通常是过滤规则卡太死
剧一直缺集,却收不到任何提醒 自动补搜默认关,落空通知只对开着开关的订阅发。去「缺集体检」页看诊断,可就地一键开启
长篇动画搜不到 / 下了却不算入库 集号有三套(见 6.8)。裸数字命名(- 1173)目前仍匹配不上
搜索结果比预期少一批 慢索引器跑不完整份计划被放弃了尾部步骤,日志里有 warn 说明是哪个索引器、放弃了几步
转移做种反复失败 多半是路径映射配错或源端是部分下载的季包。累计 3 次后会停止重试,改对配置后要「清除失败记录」解除

七、通知与远程控制

OSR 有五条通知渠道:Telegram Bot、企业微信自建应用、通用 Webhook、Bark、Gotify。各自在参数管理里配置,留空即不启用,可以同时开。

7.1 通知路由:发不发、发给谁

早期版本是每个渠道自己读一个 types 参数判断该不该发,渠道一多就没法统一配置,也没有「收件人」这一维。现在这件事收口到一张路由表(系统管理 → 通知路由):「发不发、发给谁」由路由表决定,渠道实现只管「怎么发」。

一行对应一组「通知类型 × 渠道」,可以分别设置是否发送与收件人范围,每个渠道都能发一条测试消息。通知类型有十种:

类型 说明
系统告警 索引器故障、复制超时等
订阅命中 匹配到候选并已推送下载器
下载完成
下载失败 带失败原因;熔断提示拼在同一条里,不再单发
媒体库入库 对账确认某集真的进库了
H&R 保种 达标可安全删种 / 达标前种子消失了
补搜落空 只对开着自动补搜的订阅发
入库卡住 文件已下好、迟迟没进媒体库
缺集逾期 有集播出多日仍未匹配到资源,由缺集体检发出
每周周报 由定时任务「每周周报」发出,默认暂停

通知带快捷操作:下载失败的通知附「重试 / 拉黑 / 进度」,补搜落空附「立即补搜 / 进度」,缺集逾期附每部剧的「补搜」(最多 4 个)。操作本质就是一条聊天指令:Telegram 上渲染成内联按钮,企业微信上是一行「可直接回复:补搜 12」的提示,其余渠道只收正文。点了之后按下载记录/订阅的归属校验权限,补搜和重试都在后台跑,完成后再补发一条结果。

三条容易做错的语义,都是刻意这么定的:

  1. 路由缺失按「发送」处理。 新增通知类型或新增渠道时路由行还没补上,宁可多发也不能静默丢——丢通知的故障用户根本发现不了。
  2. 「仅归属人」档在通知无归属时回退默认接收人,不是丢弃。 系统级告警(索引器故障、复制超时)本来就没有归属人,理解成丢弃会让这类告警凭空消失。
  3. 不支持分人的渠道一律退化为广播。 TG 只有一个 chat id、Webhook 只有一个 URL,配置页对这些渠道不展示收件人选项——给出一个不生效的开关比缺少这个功能更糟。

拆分已有通知类型时会配一条迁移,把新类型的路由行按拆分来源逐渠道复制一份。新增取值本身是安全的(缺失按「发送」处理),但拆分会让用户对旧类型的关闭设置落空——明确关掉「下载完成」的人升级后突然开始收到 H&R 提醒,他的设置没变、行为却变了。

还有两个文案上的约定值得一提:PT 通知的第一行统一是「哪部作品的哪一集」——命中、完成、失败、季包讲的本就是同一集,说法不一致时用户对不上号(原先命中说《三体》S01E05、完成和失败只给种子标题,而国内站的标题常带一长串站点前缀);以及片段缺失时整段不写,不写「未知」——一句「分辨率:未知」不帮用户做任何判断,只把真正有用的几段挤下去。

通知文案统一按 Telegram 的 HTML 模式编写,其余渠道会自动还原成纯文本(去标签 + 解 HTML 实体,顺序不能换),所以片名里的 & 不会在企微/Bark/Gotify 上显示成 &amp;——而 & 在 PT 片名和组名里相当常见。

7.2 Telegram Bot

参数管理里配 openlist.tg.token 和 openlist.tg.userid,重启后端生效。支持的指令:

指令 作用
/strm 执行 STRM 任务
/strmdir 生成指定路径的 STRM
/sync 执行同步任务
/syncdir 同步 OpenList 指定目录
/rename 执行重命名任务
/retry 重试所有失败任务
/checkorphan 执行重命名一致性检查

PT 订阅指令与企业微信共用同一套逻辑(纯中文指令如「我的订阅」同样可用):

指令 作用
/sub 剧名 / /submovie 片名 搜索并订阅剧集 / 电影,结果以内联按钮选片、选季
/subs 我的订阅
/downloading / /recent 正在下载的集 / 最近入库的集
/progress 编号 订阅进度,进度页上有补搜、暂停/恢复、刷新按钮
/search 编号 立即补搜缺集,后台执行,完成后回报结果与真实的落空原因
/diagnose 编号 一键诊断:缺的集为什么还没下到
/pause 编号 / /resume 编号 暂停 / 恢复订阅
/pthelp 订阅指令说明

选片、选季这类按钮带会话 id,点到上一轮搜索留下的旧按钮会提示已过期,不会订错片。

发送范围由通知路由决定(早期版本的 openlist.notify.tg.types 已被路由表取代)。TG 只有一个 chat id,因此它在路由里一律是广播。

7.3 企业微信自建应用

比 TG 更适合国内网络环境,而且是订阅助手式的多轮交互。需要配置的参数:

参数键 说明
openlist.wecom.corpid 企业 ID,留空则企微功能整体不启用
openlist.wecom.agentid 自建应用 AgentId
openlist.wecom.secret 自建应用 Secret
openlist.wecom.token 回调 Token,不配则只能发通知、收不到指令
openlist.wecom.aeskey 回调 EncodingAESKey(43 位)
openlist.wecom.touser 无归属通知的接收人,默认 @all
openlist.wecom.proxy API 代理地址,默认官方地址
openlist.wecom.autocreate 成员首次发指令时自动建号绑定,默认开启

💡 2022-06-20 之后创建的自建应用调用企微 API 必须登记「企业可信 IP」,家宽/动态 IP 登记不了。通行做法是自己反代 qyapi.weixin.qq.com,把中转地址填进 openlist.wecom.proxy。

支持的指令:

订阅 <剧名>       搜索剧集并订阅,如:订阅 三体
订阅电影 <片名>   搜索电影并订阅
我的订阅          查看自己的订阅列表
下载中            查看正在下载的集
最近入库          查看最近入库的集
进度 <编号>       查看某条订阅的进度
补搜 <编号>       立即搜索该订阅的全部缺集
诊断 <编号>       看看缺的集为什么还没下到
重试下载 <记录号> 重新搜索并下载一条失败的下载记录
拉黑种子 <记录号> 拉黑该下载记录对应的种子,以后不再推送
暂停 <编号>       暂停订阅
恢复 <编号>       恢复订阅
我的账号          查看绑定状态
取消              中断当前的多轮选择
帮助              显示本说明

搜索后直接回复序号即可选择。订阅是带归属的:某条订阅的动态只推给它的所有者,不会把 A 的下载进度推到 B 的企微上(历史遗留的无归属订阅退化为广播,所有人可见)。

7.4 通用 Webhook / Bark / Gotify

渠道 参数键 说明
Webhook openlist.notify.webhook.url OSR 会 POST 一个 {"text": "消息内容"} 过去
Bark openlist.notify.bark.url 形如 https://api.day.app/你的Key,自建服务填自己的域名
Gotify openlist.notify.gotify.url + openlist.notify.gotify.token 服务地址不含路径;两项都填了才会发送

三者都只有一个投递目标,因此在路由里一律是广播。发送范围同样由通知路由决定,早期的 openlist.notify.*.types 参数已被路由表取代。

新增一个渠道只要实现 INotifier 接口,配置页会自动多出一列——但渠道标识一旦发布就不能改,改了等于把用户已有的路由配置全丢。

7.5 MCP:让本地 AI 助理接管

OSR 内置了一个 MCP 服务端,端点是 http://你的地址/mcp。Claude Desktop、Cursor、Cherry Studio 这类支持 MCP 的 AI 助理连上之后,就能用自然语言问「这周有哪些剧更新了还没下到」「帮我订阅一下《xxx》第二季」「把 12 号订阅补搜一下」。

配置步骤:

  1. 系统管理 → MCP 令牌 里签发一个令牌,选好权限档。令牌明文只在签发时显示一次,库里只存哈希;
  2. 在 AI 助理里添加一个 Streamable HTTP 类型的 MCP 服务器,地址填 http://你的地址/mcp,请求头加 Authorization: Bearer osr_mcp_xxx。

一共 33 个工具,按「做错了要花多大代价收回来」分三档:read(查订阅、进度、日历、下载记录)、write(建订阅、触发任务、补搜、重试)、admin(删订阅、重置集状态、拉黑发布组)。权限档和 OSR 自身的权限是「与」的关系——普通用户的令牌勾上 admin,照样删不掉别人的订阅。有一批操作任何档都没有对应工具:删网盘文件、删种、改索引器/下载器/参数设置、用户与角色。

几个使用上要知道的:

  • 补搜这类要跑几十秒以上的工具会立即返回一个作业 ID,助理再用 get_job_status 轮询结果,不会因为客户端 60 秒超时而重复触发;
  • 用自带的 nginx.conf 部署时 /mcp 已经单独配好(关缓冲、放大读超时)。自己套了一层反代的话也要同样处理,否则症状是握手正常、工具列表也拿得到,只有跑得久一点的调用莫名断线。

7.6 AI 功能

在参数设置里配好 OpenAI 兼容接口(openlist.openai.*)之后,除了重命名的 AI 兜底识别,还有三处可用:

  • 自然语言建过滤规则(见 6.4),生成的是草稿,不确认不落库;
  • 种子标题 AI 兜底解析:openlist.openai.pt-title-fallback 设为 1 开启(默认关)。本地正则解析不出的标题在后台排队交给 AI,结果缓存,下次搜到同一个种子时生效,不会拖慢 RSS 和补搜;
  • 每周周报的点评(见 4.4)。

八、日常运维

8.1 备份

配置备份与恢复(系统管理 → 备份与恢复,仅 PC 端、仅管理员):把参数设置、通知路由、STRM/同步/重命名任务与分类规则、PT 下载器/索引器/媒体服务器、各类 PT 规则、黑名单、企微用户绑定和 PT 订阅导出成一个 JSON 文件。恢复时:

  • 先逐项预览会新增、会更新、会跳过什么,确认后才写入;
  • 只合并、不删除,按名称/路径对应;引用到的下载器、用户按名字换成本机的 id,找不到就跳过并提示;
  • 密码、API Key 这类敏感字段默认不导出,恢复时空值保留本机现有的值;
  • 订阅在后台逐条重建并按本机媒体库对账,不会触发补搜,免得几十条订阅一起撞站点限流。

备份文件由你自己下载保存,服务端不留副本——留在服务端的备份在「整台机器坏了」这个最需要它的场景里恰恰跟着一起没了。换机器、重装时它比整库 dump 更合适;想完整保留下载记录、执行日志等历史数据,还是用下面的数据库备份。

数据库:

docker exec osr-mysql mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" osr > osr-backup.sql

文件(STRM、日志、上传)直接备份宿主机的 /data 目录即可。

8.2 升级

docker compose pull && docker compose up -d

自建镜像的话:拉代码 → mvn clean package -DskipTests → 前端 cd osr-web && npm run build → docker compose up -d --build。

升级前先备份数据库。建表和迁移脚本由后端启动时自动执行,不需要手动导入。

跨多个版本升级时,后端要跑完全部迁移才会转为 healthy,而前端的 depends_on 用的是 condition: service_healthy——这段时间前端不启动是正常的,不是升级失败。healthcheck 的 start_period 给了 180 秒。

⚠️ 自建前端镜像有个坑:Dockerfile.frontend 不是多阶段构建,它只是把 osr-web/dist COPY 进 Nginx 镜像,不会自己跑 npm run build。改了前端代码不先手动构建的话,COPY 这层会命中 Docker 缓存,容器里跑的还是旧代码(构建日志里这行显示 CACHED 就是没生效的信号)。

只更新单个服务:

docker compose up -d --build --no-deps frontend

8.3 常见问题

服务起不来? 确认 Docker 正常、.env 已配置、MySQL 初始化完成(首次启动需要时间),然后看 /data/logs/sys-error.log。

改数据库密码? MYSQL_ROOT_PASSWORD 和 DB_PASSWORD 是独立的,改完要重建容器才生效。

改 JWT_SECRET? 至少 32 字节,改完重启后端,所有用户需重新登录。

前端访问不了 API? 依次检查后端容器是否运行、后端日志有无报错、端口映射是否正确、前后端分域时 CORS_ALLOWED_ORIGINS 有没有配。

定时任务不执行? 检查任务是否启用、Cron 表达式是否正确、日志里有无异常。

能配多个 OpenList 实例吗? 当前版本只支持单实例。

登录提示账号被锁定? 登录失败会同时按「账号」和「来源 IP」两个桶计数,任一超阈值就临时锁定(默认账号 5 次、IP 30 次、锁定 10 分钟,锁定时长同时也是失败次数的统计窗口)。等锁定时长过去自动解除,阈值在参数管理的 sys.login.* 三项里调,填 0 关闭。

后端一直 starting,前端根本没起来? 这是设计如此:前端的 depends_on 用的是 condition: service_healthy,后端没就绪前端就不启动,比「Nginx 起来了却一路 502」更容易定位。首次启动要跑完全部迁移,start_period 给了 180 秒;超过这个时间仍不健康,去看 /data/logs/sys-error.log。

新增了 SQL 迁移脚本却没执行? 这是开发向的坑:MysqlDdl.getSqlFiles() 是硬编码的文件名清单,不是目录扫描,新脚本必须手动追加到列表末尾。


九、写在最后

OSR 的定位很清楚:它不试图取代 Emby/Jellyfin,也不取代 OpenList,而是把「下载器 → 网盘 → STRM → 媒体库」这条链路上所有需要人肉盯着的环节接管过来,并且在每个环节留下可查询、可重试的记录。

如果你只是想要 STRM 生成,配好 OpenList 地址和 token,建一个 STRM 任务就能用;如果你想要全自动追剧,那就沿着「索引器 → 下载器 → 媒体服务器 → 过滤规则 → 订阅」的顺序一路配下来。两种用法都成立,中间的每一档也都成立。

评论区