Skip to content

About

115 网盘自动转存系统:定时转存分享链接、离线下载、文件管理,并联动 QMediaSync 自动刮削生成 strm。A port of bdsavepro to 115 cloud drive.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

115SavePro

115 网盘自动转存系统 · 离线下载 · QMediaSync 联动

CI Build and Push Docker Image Image Size License: AGPL v3

把 xinyuLo/bdsavepro(百度网盘版)的思路搬到 115 网盘上:
定时把 115 分享链接里的文件转存到自己的目录,转存到新文件后自动通知 QMediaSync 刮削生成 strm 并整理分类;
同时把 115 的离线下载能力(磁力 / ed2k / HTTP)也接了进来。

快速开始(一行命令)

mkdir -p config log && docker compose up -d

访问 http://你的NAS IP:5000,默认账号 admin / 密码 zxcvbnm。 更新:docker compose pull && docker compose up -d

  • 后端:Flask + APScheduler + SQLite
  • 网盘 SDK:p115client(ChenyangGao,MIT),
    requirements.txt 里锁定到实测通过的版本 0.0.9.6.5.1,保证构建可复现
  • 前端:零构建单页(一个 HTML,不需要 npm / Node 构建)
  • 登录 115:扫码登录(也可手动粘贴 Cookie)
  • 需要 Python ≥ 3.12(p115client 硬性要求)

目录

  1. 功能一览
  2. 快速开始
  3. 使用指南
  4. QMediaSync 联动怎么配
  5. 数据与备份 / 面板内备份恢复
  6. 常见问题
  7. 与上游项目的差异
  8. 发布与更新(维护者)
  9. 更新日志
  10. License

功能一览

模块 能力
账号 扫码登录(多设备身份可选)、手动粘贴 Cookie、多账号切换、Cookie 有效性校验、空间用量显示
转存任务 多任务、启用/停用、单任务 cron 或跟随全局、正则过滤、正则替换
转存范围 可只转存分享里的指定文件夹(逐层下钻 + 跨层级多选),不选则整条转存
去重 按文件名 / 文件名+大小 / SHA1(115 秒传 SHA1 一致,改名也能拦住);支持独立的「对比路径」
排除清单 按文件名排除,命中即跳过
离线下载 提交磁力 / ed2k / HTTP / FTP / 种子链接到 115 云端;列出进度;删除 / 重试 / 清空
文件管理 浏览目录、新建文件夹、重命名、移动、复制、删除、搜索、生成分享链接
转存日志 每次执行留档(SQLite),含配置快照、分级日志、去重跳过清单、实际新增清单,每任务保留最近 10 次
通知 Bark / PushPlus / 钉钉 / 飞书 / 企业微信(机器人+应用)/ Telegram / Server酱 / ntfy / Gotify / PushDeer / SMTP / 自定义 Webhook
空间告警 定时巡检,超过阈值推送
QMediaSync 联动 手动绑定「转存任务 / 离线下载」↔「QMS 刮削目录」,有新文件时自动触发,支持手动触发与触发日志
备份与恢复 面板内导出 / 导入备份,支持服务器本地多份留存、按内容选择性恢复、恢复前自动存档

快速开始

方式一:docker compose(推荐)

镜像已发布,匿名可拉取,不需要登录:

mkdir -p config log
# 把 docker-compose.yml 放到当前目录,直接启动
docker compose up -d

访问 http://你的NAS IP:5000,默认账号 admin / 密码 zxcvbnm(登录后请立刻在「系统设置」改掉)。

方式二:docker-compose.yml 完整内容

没有现成文件的话,新建 docker-compose.yml,把下面这段完整粘进去:

services:
  save115:
    image: ghcr.io/xiaoming677/115savepro:latest
    container_name: 115savepro
    restart: unless-stopped
    ports:
      - "5000:5000"
    volumes:
      - ./config:/app/config
      - ./log:/app/log
    environment:
      - TZ=Asia/Shanghai

然后启动:

mkdir -p config log
docker compose up -d

两个最容易踩的坑

  1. 缩进必须用空格,YAML 不认 Tab。上面每一层是 2 个空格。 用 Tab 缩进会报 top-level object must be a mapping。
  2. 服务名是 save115 而不是 115savepro —— 以数字开头的键会被部分 compose 实现和网页版编辑器误解析。容器名仍然是 115savepro,使用上无感知。

想改用 Docker Hub 的镜像(国内通常更快),把 image: 那行替换成:

    image: xiaoming677/115savepro:latest

想让 QMS 同机访问更省事,把注释打开:

    network_mode: host

方式三:不用 compose,直接 docker run

mkdir -p config log
docker run -d --name 115savepro --restart unless-stopped \
  -p 5000:5000 \
  -v "$PWD/config:/app/config" \
  -v "$PWD/log:/app/log" \
  -e TZ=Asia/Shanghai \
  ghcr.io/xiaoming677/115savepro:latest

-v 的写法是 宿主机路径:容器内路径,中间的 : 是分隔符。 $PWD 是当前目录,所以要先 cd 到数据目录所在位置再执行。 Windows CMD 用 %cd%,PowerShell 用 ${PWD}。

方式四:本地构建镜像(改过代码才需要)

mkdir -p config log
docker build -t 115savepro:latest .
docker run -d --name 115savepro --restart unless-stopped \
  -p 5000:5000 \
  -v "$PWD/config:/app/config" \
  -v "$PWD/log:/app/log" \
  -e TZ=Asia/Shanghai \
  115savepro:latest

方式五:本机直接运行(开发调试)

pip install -r requirements.txt
python web_app.py          # 默认 0.0.0.0:5000,可用 PORT / HOST 环境变量覆盖

需要 Python ≥ 3.12(p115client 的硬性要求,Docker 镜像用的是 python:3.12-slim)。

可用的镜像地址

来源 地址 需要登录
GHCR(默认,零配置) ghcr.io/xiaoming677/115savepro:latest 否,公开可拉
Docker Hub xiaoming677/115savepro:latest 否(国内拉取通常更快)
本地构建 115savepro:latest 否

更新到最新版

在 docker-compose.yml 所在目录执行:

docker compose pull && docker compose up -d

以后更新就重复这一条命令。 就这样,不用改任何配置。

面板内一键更新(v1.4.0+,最省事)

**「系统设置 → 版本更新」**里可以直接检查并更新,不用碰 Docker 界面:

  • 检查更新 —— 零配置。容器通网即可用,自动对比 GitHub 上的最新版本与更新说明。
  • 一键更新 —— 需要额外挂载 docker.sock(见下)。点一下按钮就自动完成 「拉新镜像 → 重建容器」,页面会自动等待服务恢复并提示新版本号。

启用一键更新:在 docker-compose.yml 的 volumes: 下加一行,然后 docker compose up -d:

    volumes:
      - ./config:/app/config
      - ./log:/app/log
      - /var/run/docker.sock:/var/run/docker.sock    # ← 加这一行

⚠️ 安全提示:挂载 docker.sock 等于把宿主机 Docker 的控制权交给这个容器 (等价于 root 权限)。不需要一键更新就不要加 —— 检查更新、以及上面那条手动 更新命令都不受任何影响。

它是怎么做到的(值得一读,因为有点绕):容器没法自己重建自己 —— 一旦被 停止,进程就结束了,后续动作没人执行。所以点「立即更新」后,程序会通过 Docker Engine API 启动一个一次性助手容器,由它在你退出之后继续干活:

sleep 3                # 留出时间让页面收到响应
cd <compose 项目目录>
docker compose pull
docker compose up -d --force-recreate

之所以走 docker compose 而不用 API 手拼容器参数,是因为 compose 文件里有权威配置 —— 端口、挂载、环境变量、网络模式全部以它为准,不会因为重建而丢失或走样。

几个实现细节:

  • 助手镜像用 docker:cli(自带 compose 插件),执行完自动删除;想换镜像可以设环境变量 UPDATE_ASSISTANT_IMAGE
  • 项目目录自动识别:读容器标签 com.docker.compose.project.working_dir, 所以不管你是在飞牛界面粘贴 YAML 还是 SSH 手写,位置都能自动找到
  • 检查更新失败不影响使用:容器访问 GitHub 可能被网络限制,界面会提示去浏览器看 Releases
  • 更新过程有日志:写进 config/update.log,面板里点「上次更新日志」就能看

飞牛 fnOS(图形界面)用户看这里

飞牛自带的 Docker 没有「自动检测镜像更新」这个功能(社区已确认的缺陷)—— 界面上显示「已是最新」是假象:它只比对本地已有镜像,根本不会去远端查询。 所以必须手动触发一次拉取:

  1. 飞牛桌面 → Docker → 左侧 镜像
  2. 点「拉取」,输入 xiaoming677/115savepro:latest (先试这条;ghcr.io 在国内常拉不动)
  3. 拉取成功后,左侧切到 Compose → 找到本项目 → 点 停止
  4. 项目右侧 三个点 → 重构(部分版本叫「重新构建」或「更新镜像」)

第 2 步如果失败,改试 ghcr.io/xiaoming677/115savepro:latest —— 哪个能拉下来就用哪个。

也可以直接改 YAML 换源:Compose → 项目右侧 三个点 → 详情 → 把 image: 那行改成 xiaoming677/115savepro:latest → 保存 → 再点 三个点 → 重构。

嫌飞牛自带界面麻烦的话,可以装 Portainer:它的「Recreate」按钮会 自动比对远端有没有新版本,比自己点省事。

怎么确认更新生效了

三种方式,任选其一:

方式 操作
看页面 打开面板,左下角会显示当前版本号(如 v1.2.0)
看接口 浏览器访问 http://你的IP:5000/api/version,返回 {"version":"1.2.0", ...}
看容器 docker inspect 115savepro --format '{{index .Config.Labels "org.opencontainers.image.revision"}}'

常见问题

更新后版本没变?按这个顺序排查

第 1 步:确认容器当前到底跑的是哪个版本

# 容器镜像里带的版本标签(最准,不依赖页面显示)
docker inspect 115savepro --format '镜像={{.Config.Image}}  版本={{index .Config.Labels "org.opencontainers.image.version"}}'

# 本地已有哪些相关镜像
docker images | grep -i 115savepro

# 程序自报的版本
curl -s http://127.0.0.1:5000/api/version; echo

第 2 步:按结果对症处理

现象 原因 处理
本地镜像有新的,但容器标签还是旧的 镜像拉到了,容器没重建 docker compose up -d --force-recreate
本地镜像也是旧的,pull 说 up to date 镜像源连不上,或返回了缓存的旧摘要 见下面「拉不动镜像」
容器标签里没有 version 这一项 你跑的是自己 docker build 出来的镜像 见下面「本地构建的镜像」

拉不动镜像

ghcr.io 在国内经常连不上,典型表现是 docker compose pull 卡住、超时,或者静默告诉你 up to date 但其实没拉到新版本。先直接测试哪个源通:

docker pull xiaoming677/115savepro:latest          # Docker Hub
docker pull ghcr.io/xiaoming677/115savepro:latest   # GHCR

哪条成功就用哪个。 比如只有 Docker Hub 能拉,就把 docker-compose.yml 里那行改成:

image: xiaoming677/115savepro:latest

然后:

docker compose pull && docker compose up -d --force-recreate

两个都拉不动,就是 NAS 的 DNS / 网络问题:给 Docker 配国内镜像加速器,或检查这台机器上的代理(Clash 之类的分流规则有时会把 registry 也拦掉)。

本地构建的镜像

如果你改过 docker-compose.yml,把 image: 指向了本地名字(如 115savepro:latest),那 pull 对你完全没用 —— 它不会去远端找。要么改用已发布的镜像地址(推荐,以后更新只需一条命令),要么每次自己重新构建:

docker compose up -d --build

docker compose up -d 说容器没重建?

compose 只在镜像摘要变化时才重建。如果镜像确实更新了但仍没重建,用 --force-recreate。

我是用 docker build 自己构建的镜像,怎么办?

那 pull 对你没用,要重新构建:

docker compose up -d --build        # compose 项目里带 build: . 时
# 或
docker build -t 115savepro:latest . && docker compose up -d --force-recreate

建议直接改用已发布的镜像(把 image: 指向 ghcr.io/... 或 xiaoming677/...), 这样以后更新只要 pull 一条命令。

数据会不会丢?

不会。账号 Cookie、转存任务、历史、QMS 绑定全部在 ./config 挂载卷里, 升级镜像只换程序不碰数据。稳妥起见可以先备份:

tar czf 115savepro-config-$(date +%Y%m%d).tar.gz config/

旧镜像占空间怎么办?

docker image prune -f            # 清理悬空镜像(安全)
# 想更彻底地清掉未被使用的镜像:
docker image prune -a -f

-a 会删掉所有没有被容器使用的镜像,执行前确认没有别的镜像在用。

飞牛 NAS 上要注意的

  • 如果你已经用 CloudDrive2 把 115 挂载到本机,QMediaSync 的刮削目录要指向挂载后的本地路径,
    而 115SavePro 里的「保存路径」填的是 115 网盘里的路径(例如 /我的影视/剧集)。两者是同一个位置的两个视角。
  • 如果 QMediaSync 也在这台 NAS 的 Docker 里,把 docker-compose.yml 里的 network_mode: host 打开,
    然后在 QMS 连接设置里填 127.0.0.1 + QMS 端口(默认 12333)即可,最省事。

使用指南

第 1 步:登录 115

左侧「账号管理」→「扫码登录 115」:

  1. 选登录设备(不确定就保持默认「115生活_支付宝小程序」)
  2. 页面上会出现二维码,用 115 手机 App 扫码
  3. 手机上点「确认登录」,页面会提示登录成功
  4. 点「保存账号」

扫码轮询会把状态实时显示出来:等待扫码 → 已扫码待确认 → 登录成功。
也支持「手动粘贴 Cookie」:浏览器登录 115 网页版 → F12 → 应用/存储 → Cookie,复制 UID、CID、SEID、KID 四项。

第 2 步:创建转存任务

「转存任务」→「新建任务」:

字段 说明
分享链接 必填。支持 https://115.com/s/xxxxx?password=abcd 这种带提取码的完整链接
提取码 链接里没带时才需要填
任务名称 留空会自动抓取分享标题
保存路径 必填。115 网盘里的目录,不存在会自动创建。可点「选择」用目录选择器
对比路径 可选。去重时跟这个目录比;留空就与保存路径比
去重模式 按文件名 / 文件名+大小 / SHA1
定时规则 cron 表达式,留空则跟随「系统设置」里的全局定时
文件过滤正则 只转存文件名匹配的项,例如 .*\.(mp4|mkv)$
排除文件清单 每行一个文件名,命中即跳过
指定转存文件夹 不选 = 整条分享全部转存;选了就只转存选中的项

目录选择器怎么用

点「选择」打开后:

  • 打开时会自动定位到输入框里已填的路径,不用从根目录一层层点
  • 点一行 = 进入下一层(行尾有「进入 ›」提示),面包屑可以点回上层
  • 到目标目录后点「选择当前目录」才会把路径填回去
  • 想放一个还不存在的目录?点「新建目录」,建完会直接进去

也可以完全不用选择器,直接在输入框里手打路径(如 /我的影视/剧集/2026), 转存时不存在会自动创建。

cron 示例

表达式 含义
*/30 * * * * 每 30 分钟
0 */6 * * * 每 6 小时
0 10 * * * 每天 10:00
0 8,12,20 * * * 每天 8 点、12 点、20 点

创建后点任务行里的「执行」可以立刻跑一次,会弹出实时日志。

第 3 步:离线下载(可选)

「离线下载」→「新建下载」,把磁力 / ed2k / HTTP 链接每行一个贴进去,选个保存目录后提交。
115 会在云端下载,下载完成后落到你的网盘目录里。

系统默认每 5 分钟巡检一次已完成的离线任务,完成时会触发绑定到「离线下载」的 QMS 刮削,并推送一条通知。
巡检频率在「系统设置 → 离线下载巡检」里改。

第 4 步:文件管理

「文件管理」里可以像网盘客户端一样浏览目录,勾选后批量移动 / 复制 / 删除 / 生成分享链接。


QMediaSync 联动怎么配

思路和上游项目一致:手动把「转存任务」或「离线下载」绑定到「QMS 的某个刮削目录」,
之后只有绑定了的刮削任务会被触发,避免整库空跑。

1. 在 QMS 侧准备 API Key

登录 QMediaSync → 系统设置 → API 密钥 → 创建,记下 qms_ 开头的那串字符。

2. 在本项目填连接信息

「连接 QMediaSync」:

字段 说明
地址 / 端口 QMS 的地址。默认端口 12333。填实际能访问到的地址(同机 host 网络就填 127.0.0.1,跨容器填 NAS 内网 IP)
鉴权方式 API Key(推荐)或 账号密码
启用 QMS 联动 总开关
转存到新文件后自动触发 关掉就只保留手动触发
触发前延时 挂载盘刷新有延迟时,可以设几秒再触发

点「测试连接」确认通,再点「保存配置」。点「查看刮削任务」可以看 QMS 里现有的刮削目录。

3. 创建连接(绑定关系)

点「创建连接」:

  • 绑定对象:
    • 某个转存任务 → 该任务转存到新文件时触发
    • 离线下载完成 → 任意离线任务下载完成时触发
  • ① 先生成 strm(可选) → 选一个 QMS 的同步路径(见下一节)
  • ② 刮削任务 → 从 QMS 拉到的刮削目录列表里选

连接列表里可以:触发(手动跑一次)、编辑、日志(最近 30 条)、停用/启用、删除。
页面顶部还有「立即触发全部」。

⭐ 先生成 strm,再刮削(v1.5.0+)

为什么需要这一步:QMS 里「生成 strm」和「刮削」是两个独立动作 —— 前者把 115 上的视频映射成本地 .strm 文件,后者才去 TMDB 抓元数据。 顺序不能反:没有 strm,就刮不到东西。 如果转存完直接触发刮削, 新转存的文件很可能因为 strm 还没生成而被整个漏掉。

所以每条连接可以额外绑定一个同步路径,把流程变成两步:

转存到新文件 → ① 触发生成 strm → 等待 → ② 触发刮削

等待方式(推荐「轮询」):

方式 说明
轮询等待(推荐) 每隔几秒查一次同步路径的 is_running,等它真正跑完再刮削。最可靠
固定延迟 简单等 N 秒再刮削。QMS 规模小、strm 生成快时够用
不等待 触发完 strm 立即刮削(老行为,可能刮漏)

「轮询」最长等待默认 900 秒。超时不算失败 —— 会照常继续刮削, 因为已经生成的那部分 strm 依然值得刮,触发日志里会写明。

不绑定同步路径时行为与以前完全一致(直接刮削),老配置不用改。

同步路径在哪找:QMS 的「同步」页面里建的那些。本项目页面上的 「查看同步路径」按钮可以直接列出(含 115 远程路径 → 本地 strm 路径), 以及当前是否正在运行。

触发日志会记录完整流程,例如:

生成 strm:已加入同步队列 → 等待 strm 完成:strm 同步已完成(用时 42 秒) → 刮削:已开始

触发规则(什么时候会触发)

情况 是否触发
转存到新文件 + 有绑定 ✅
转存了但全是已存在(去重跳过) ❌ 不触发,避免空跑
任务没有绑定任何连接 ❌
总开关或「自动触发」关掉 ❌
该条连接被停用 ❌
离线任务完成 + 绑定了「离线下载完成」 ✅(去重,同一个任务只触发一次)

QMS 配置和绑定关系存在 config/history.db(SQLite)里,不会被转存过程中的 config.json 回写覆盖。


数据与备份

数据都在哪

路径 内容 是否需要备份
config/config.json 账号 Cookie、转存任务、通知配置 ✅ 必须
config/history.db 转存历史、QMS 配置与绑定、离线完成去重记录 ✅ 必须
config/backups/ 面板生成的备份文件 可以不用
config/config.template.json 配置模板 不用
log/ 运行日志(按天切割,保留 14 天) 可选

手工备份就是打包 config/ 目录,换机器时把 config/ 放回去即可。

面板内备份与恢复(v1.7.0+)

更省事的做法:「系统设置 → 备份与恢复」。

导出能选包含哪些内容:

选项 说明
包含账号 Cookie 默认开。关掉后导出的文件不含登录凭据,可以放心发给别人帮忙排查问题(恢复后需重新登录 115)
包含后台密码 默认开
包含历史记录 默认开

两个按钮:

  • 导出并下载 —— 浏览器直接下载一个 JSON 文件
  • 备份到服务器 —— 存在 config/backups/ 下,最多保留 20 份,自动清理最旧的

⚠️ 默认导出的备份里含有 115 账号登录凭据,和密码一样重要,别随手发人。 config/backups/ 已在 .gitignore / .dockerignore 里排除,不会被提交或打进镜像。

恢复

两种入口:

  • 从文件恢复 —— 选择之前下载的备份文件
  • 在下方列表里点「恢复」 —— 直接用服务器上的某一份

点完之后会先预览:显示备份时间、程序版本、里面有几个账号 / 任务 / QMS 连接,然后让你选:

① 恢复方式

方式 行为
覆盖(默认) 用备份内容替换现有配置,结果和备份时完全一致
合并 账号 / 转存任务 / QMS 连接按 ID 合并,当前多出来的部分保留

② 恢复内容(可多选,按需来)

账号(含 Cookie) / 转存任务 / QMS 联动配置 / 通知渠道 / 系统设置 / 后台登录账号密码 / 历史记录

比如只想把通知渠道搬过来,就只勾「通知渠道」—— 账号、任务都不动。

恢复是安全的

  1. 点下「确认恢复」前会先把当前状态自动存一份到 config/backups/(来源标为「恢复前自动存档」), 拿错文件可以原样退回来
  2. 备份文件必须通过 app / kind / format 三个字段校验,误传别的 JSON 会被直接拒绝
  3. 来自更高版本的备份会被明确拦下(提示先升级程序),而不是半懂不懂地写进去
  4. 恢复完会列出每一项都做了什么(恢复了几条、跳过了什么、为什么跳过)

恢复后需要留意的

  • cookie 已失效的账号:恢复的 Cookie 是备份那一刻的,如果期间在别处登录过、或 Cookie 过期, 在「账号管理」里点「校验」重新确认,必要时重新登录
  • 定时任务:恢复后会自动重载 cron,不用重启容器
  • QMS 连接:如果换了机器、QMS 地址变了,恢复后记得改一下地址再测连接

常见问题

Q:扫码后一直停在「等待扫码」?
大概率是容器访问不了 qrcodeapi.115.com。检查容器的 DNS / 代理设置(注意:如果这台机器上跑了 Clash 之类的代理,容器内的 115 接口也走代理,可能反而更慢或失败)。

Q:扫码时手机响了一声,但之后提示「参数错误」?

「参数错误」意味着二维码被识别了,但换取登录凭据的设备身份被拒。 v1.3.0 起系统会自动依次尝试多个设备身份(支付宝小程序 / 微信小程序 / 安卓端 / iOS / Windows 端 / 网页端…),第一个成功的就采用 —— 不需要你手动换设备重扫。

如果全部失败,点弹窗里的「复制诊断」:诊断里会列出每个设备分别报什么错, 一眼就能区分是设备不匹配、还是 115 风控。

另外注意:web 不在 115 官方的扫码设备清单内,且最容易触发「IP登录异常」 (次日零点才解禁),所以 v1.3.0 已把它降到列表最后,默认改用支付宝小程序。

Q:目录选择器 / 文件管理里看不到目录、进不去子目录?

先确认面板左下角的版本号 ≥ v1.7.3(旧版本有这个 bug,见更新日志)。

如果已经是最新版还是不对:

  1. 在显示「该目录下没有子目录」的地方点「点这里诊断」, 会拉出 115 原样返回的数据,点「复制全部」发出来即可
  2. 也可以直接访问 http://你的IP:5000/api/dir/debug?cid=0 (把 cid 换成你进不去的那个目录 id)

诊断内容里有三个关键信息:每一次请求实际发的参数、115 返回的 state/count/message、以及 raw_sample(原始数据的前 3 条)。 有了 raw_sample 就能看出目录条目到底带哪些字段,一次定位根因。

Q:扫码登录一直失败,怎么办?

按这个顺序排查:

  1. 点「接口自检」 —— 现在检查 4 项:取 token / 二维码 / 状态轮询 / 时间同步。
    • 四项全绿 → 代码和网络都没问题,问题在 115 侧(风控或账号状态)
    • 时间项标红 → 系统时间偏差超过 5 分钟会导致凭证签名校验失败, 去飞牛「系统设置 → 时间」校准或配置 NTP
  2. 确认手机上点了「确认登录」 —— 只扫码不确认拿不到凭据,这是最常见的漏操作
  3. 点「复制诊断」发出来 —— 含每步原始响应 + 每个设备的尝试结果
  4. 换网络 / 关掉代理再试 —— 115 对 IP 有风控,报「老乡验证失败」 (errno=40101017)或「IP登录异常」时尤其如此
  5. 兜底:用「手动粘贴 Cookie」 —— 浏览器登录 115 网页版 → F12 → Network → 点任意一个请求 → 复制请求头里的整段 Cookie:(含 UID/CID/SEID/KID), 整段粘进去即可,程序会自动识别。转存功能只需要 Cookie,完全不依赖扫码。

Q:执行任务报「登录状态失效」?
115 的 Cookie 会过期。去「账号管理」点「校验」确认,失效了就重新扫码登录。

Q:转存明明有文件,却提示「没有新文件需要转存」?
这是去重生效了。去重是拿分享里的顶层项和保存路径下的直接子项比名字。
如果你希望在新目录里再存一份,把「对比路径」改成一个空目录,或改个保存路径。

Q:115 转存有没有次数/风控限制?
有。115 对分享接收有频率限制,短时间大量接收会失败。
建议把定时间隔拉开一些(比如 30 分钟以上),不要多个任务同分钟同时跑。

Q:任务跑失败了想看细节?
任务行点「日志」看实时输出;跑完的记录在「转存日志」里,点「查看」能看到当时的配置快照、分级日志、去重跳过清单、实际新增清单。

Q:QMS 连接测试失败?

  1. 确认地址端口能 ping 通 / telnet 通;
  2. 确认 API Key 是在 QMS「系统设置 → API 密钥」里创建的;
  3. 同机 Docker 场景,填 127.0.0.1 往往不行(容器内的 127.0.0.1 是自己),要用 NAS 内网 IP 或改用 host 网络。

Q:为什么 QMS 触发了但没生成 strm?
本项目只负责通知 QMS 去跑某个刮削任务,strm 的生成规则、源目录、媒体类型都在 QMS 那边配置。
要确认 QMS 里那个刮削任务的「源目录」指向的是能读到 115 文件的路径(挂载盘)。

Q:改密码?
「系统设置 → 登录密码」。默认 admin / zxcvbnm 务必改掉。


与上游项目的差异

项 bdsavepro(上游) 115SavePro(本项目)
网盘 百度网盘(baidupcs-py,BDUSS/STOKEN) 115 网盘(p115client,UID/CID/SEID/KID)
登录 只能手填 Cookie 扫码登录为主,Cookie 为辅
去重键 MD5 SHA1(115 秒传机制,改名也能去重)/ 文件名+大小
转存范围 逐层下钻多选 逐层下钻多选 + 跨层级混选
离线下载 无 有(磁力 / ed2k / HTTP / 种子)
文件管理 无 有(浏览 / 重命名 / 移动 / 复制 / 删除 / 分享)
前端 Vue 3 + Element Plus(需 Node 构建) 零构建单页 HTML,镜像更小、部署更简单
QMS 联动 手动绑定任务 ↔ 刮削目录 手动绑定任务/离线下载 ↔ 刮削目录 + 离线完成自动触发
通知渠道 25+ 13 个主流渠道(含自定义 Webhook)

技术要点:为什么能直接复用上游的调度层

上游项目里「调度 / 通知 / 历史 / QMS 客户端」这几层是不依赖网盘类型的,
它们只认一个约定:存储层暴露 transfer_share(task),返回 {success, message, file_count, new_items, ...}。
所以移植时只需要把存储层整个换掉(本项目 storage_115.py),上层几乎原样沿用即可。


发布与更新(维护者看这里)

自动构建是怎么工作的

仓库里带两个 GitHub Actions(.github/workflows/):

工作流 触发 作用
docker-publish.yml 推送到 main / 推 v* tag / 手动 构建 Docker 镜像,推到 Docker Hub 和 GHCR
ci.yml 每次 push / PR Python 语法 + 前端 JS 语法检查 + 防敏感文件误提交

镜像标签规则:

触发方式 产生的标签
推送到 main latest、sha-xxxxxx
推 v1.2.0 这个 tag 1.2.0、1.2、latest

镜像推到哪

目标 是否需要配置 说明
GHCR
ghcr.io/<owner>/115savepro
不需要 用仓库自带的 GITHUB_TOKEN,开箱即用。首次发布后包默认是私有的,想公开给别人拉需要手动改包可见性(见下)
Docker Hub
<用户名>/115savepro
需要两个 Secret 见下方「配置 Docker Hub」

工作流会先探测 Secrets:没配就自动只推 GHCR,不会报错(会在日志里给出提示)。

把 GHCR 包设为公开(可选)

如果你的仓库是公开的,但别人拉不到镜像,多半是因为 GHCR 包默认私有:

  1. 打开 https://github.com/users/xiaoming677/packages/container/115savepro/settings
  2. 页面底部 Danger Zone → Change visibility → 选 Public → 输入包名确认

自己用(docker login ghcr.io 或本机已登录)不需要这步。

配置 Docker Hub(可选,只做一次)

只需要 1 个 Secret + 1 个 Variable(用户名不是机密,所以放 Variable 更合适):

① 在 Docker Hub 建 Access Token Docker Hub → 右上角头像 → Account settings → Personal access tokens → New Access Token, 权限选 Read & Write,建好立刻复制(只显示一次)。

② 在 GitHub 仓库里配置

仓库 → Settings → Secrets and variables → Actions:

标签页 类型 名称 值
Variables New repository variable DOCKERHUB_USERNAME 你的 Docker Hub 用户名
Secrets New repository secret DOCKERHUB_TOKEN 上一步复制的 Token

⚠️ 两个容易踩的坑:

  1. 别建到 Environments 标签页里 —— 环境级 Secrets 只有工作流显式声明 environment: 才读得到,本工作流没声明。要用 Actions 标签页。
  2. 名称必须一字不差(全大写、下划线),值才填实际内容。

③ 重新触发构建 Actions → Build and Push Docker Image → Run workflow。 成功后推送目标会变成 Docker Hub + GHCR,日志里 登录 Docker Hub 会从 skipped 变成 success。

用命令行配置(更不容易出错)

# 用户名(值直接用参数给,不会填错输入框)
gh secret set DOCKERHUB_USERNAME -R <owner>/<repo>        # 或 gh variable set,两者都支持

# Token(交互式隐藏输入,不会留在命令历史里)
gh secret set DOCKERHUB_TOKEN -R <owner>/<repo>

# 核对
gh secret list -R <owner>/<repo>
gh variable list -R <owner>/<repo>

排查:构建成功但没推 Docker Hub

说明工作流没读到配置。逐项检查:

gh api repos/<owner>/<repo>/actions/secrets      --jq '.total_count'   # 仓库级 Secrets
gh api repos/<owner>/<repo>/actions/variables    --jq '.total_count'   # 仓库级 Variables
gh api repos/<owner>/<repo>/environments                               # 是不是建到环境里了

仓库级为 0 但环境里有内容 → 就是建错标签页了,见上面 ⚠️。

以后怎么发新版

# 改完代码
git add -A
git commit -m "feat: 新增 xxx 功能"
git push

# 要发正式版就打个 tag
git tag v1.1.0
git push origin v1.1.0
  • 只 git push → 更新 latest,NAS 上执行 docker compose pull && docker compose up -d 即完成升级
  • 打 tag → 额外产出带版本号的镜像,方便回滚到指定版本

用户数据都在 config/ 挂载卷里,升级镜像不会丢数据。


更新日志

v1.7.3

找到「进不去子目录」的真正根因了 —— 两个,其中一个是我上一版引入的。

靠 v1.7.2 加的自助诊断,拿到了 115 返回的原始数据,一眼就看出问题:

根因一:pc(提取码)目录也有

v1.7.2 我把「有 pc → 判为文件」当成了硬信号。但真实数据里目录长这样:

{"cid": "3378630208007515606", "pid": "0", "n": "04_影视",
 "pc": "fbd53em77w07qrw31h", "fc": 0}      ← 目录也有 pc,且没有 fid / sha

结果所有目录又被判成了文件,从列表里消失。这一版把 pc 从判据里彻底去掉, 改成:

顺序 条件 结论
1 有非空 sha / sha1 文件
2 有 fid 且没有 pid 文件
3 其余 目录

根因二(更关键):cid 是 19 位整数,超出 JS 的安全整数范围

115 的 cid 是 3378630208007515606 这样的 19 位数,而 JavaScript 的 Number.MAX_SAFE_INTEGER 只有 16 位(9007199254740991)。 JSON 把它当数字传给浏览器时,JSON.parse 会静默丢精度:

真实 cid : 3378630208007515606
JS 解析后: 3378630208007515648   ← 差了 42

于是点一个目录时,实际请求的是另一个 cid,返回空 —— 表现正是「进不去子目录」。 这也解释了为什么它从第一版就存在,而且跟目录判定无关。

修法:id 全链路改用字符串承载。后端 id / parent_id / cid 一律输出字符串, 前端所有 Number(cid) 转换全部去掉,需要整数时由后端转(Python 的 int 是精确的)。

这个精度问题同样影响文件操作:勾选文件后删除/移动/重命名时, 丢精度的 fid 可能操作到另一个文件。所以一并修掉了。

其他

  • only_dir 的兜底保留(nf=1 返回空则去掉 nf 重来并按 is_dir 过滤)
  • 自助诊断保留:目录列表为空时有「点这里诊断」,可拉出 115 原始响应
  • 回归测试改用真实响应样本固化,两个根因都有对应用例

升级后建议:如果之前建的任务用过「指定转存文件夹」,那些选中项的 id 可能是 早期丢精度的值,进编辑里重新选一次即可。

v1.7.2

继续加固目录列表:不再依赖单一参数,并加了自助诊断。

v1.7.1 修了「目录被误判成文件」和「type=0 应改 nf=1」,但如果 115 那边 对 nf 参数返回空列表,表现依然是「进不去子目录」。这一版把这种可能性也堵掉了:

  • only_dir 增加本地过滤兜底:先带 nf=1 请求;若返回空,去掉 nf 再请求一次, 然后自己按目录/文件过滤。「只看目录」不再依赖任何服务端参数
  • 目录判定改为多重信号(顺序:有 sha/sha1/pc → 文件;有 fid 且无 pid → 文件; 其余 → 目录)。这样即使某些接口形态下目录也带 fid,也能靠 pid 认出来
  • 新增自助诊断:目录列表为空时,界面会给出「点这里诊断」, 一键拉出 115 原样返回的 JSON(含每一次请求实际发的参数、state/count、 以及原始数据样本),可直接复制发给开发者定位
  • 新增 GET /api/dir/debug?cid=N,同上,便于脚本化排查

为什么加诊断:目录这类问题几乎都出在「115 返回的字段形态」上, 光看归一化后的结果猜不出根因。把原始响应拿出来,一次就能定位。

v1.7.1

修复「保存路径只能选到第一层目录,进不去子目录」。

根因有两个,都在目录列表这一层:

  1. 目录被误判成文件。 原来用 p115client 的 normalize_attr_simple 判目录, 它优先看 fc 字段(is_dir = int(fc) == 0)。而 fc 在不同接口里语义不一致 —— 只要它不是 0,目录就会被判成文件,紧接着取 info['fid'] 抛 KeyError, 被容错分支吞掉后统一变成「文件」,于是非空目录从列表里整体消失, 选择器里自然什么都进不去。 现在改为只认「有没有 fid」(115 网页接口的稳定规则:文件有 fid,目录没有), 与 p115client 自己 overview_attr() 的判定方式一致。
  2. 「只看目录」的参数用错了。 原来给 115 传的是 type=0,但那个参数的含义是 「文件类型=全部」,仍然属于按类型筛选 —— 而按类型筛选时目录还需要额外 stdir=1 才会带出来,很容易变成「一个目录都看不到」。 现在改用官方专用参数 nf=1(不要显示文件)。

同一处修复也让文件管理页受益:之前那里的非空目录也被当成文件显示。

顺带:

  • 目录选择器打开时自动定位到输入框里已填的路径,不再从根目录一层层点
  • 选择器里新增「新建目录」(建完直接进去)和「刷新」,行尾提示「进入 ›」, 底部固定显示当前路径
  • 进空目录不再报错,正常显示「该目录下没有子目录」
  • 分页 limit 夹到 115 的上限 1150;非法 cid 返回明确提示而不是 500

v1.7.0

新增「备份与恢复」,在面板里就能导出和还原全部设置。

  • 导出:可选是否包含「账号 Cookie / 后台密码 / 历史记录」三项; 支持「导出并下载」或「备份到服务器」(存 config/backups/,最多留 20 份)
  • 恢复:可从文件恢复,也可直接在列表里挑一份服务器备份恢复
  • 恢复方式二选一:覆盖(结果与备份时一致)/ 合并(账号、任务、QMS 连接按 ID 合并,现有内容保留)
  • 可按内容选择恢复:账号 / 转存任务 / QMS 联动 / 通知渠道 / 系统设置 / 后台登录密码 / 历史记录,可单独只恢复其中一项
  • 恢复前自动存档当前状态(来源标为「恢复前自动存档」),选错文件能退回
  • 安全校验:备份文件需通过 app / kind / format 校验,误传别的 JSON 会被拒绝; 来自更高版本的备份会明确拦下并提示先升级
  • 关闭「包含账号 Cookie」后导出的文件不含登录凭据,可以放心发给别人排查问题
  • config/backups/ 已加入 .gitignore 与 .dockerignore —— 备份含 Cookie,不会被提交或打进镜像

实现细节:备份 = config.json + SQLite 里的 QMS 配置与历史(task_history / qms_trigger_log / offline_log)。恢复走 storage.reload() + 重载定时任务, 不用重启容器。

v1.6.0

保存任务后自动执行,一并串起 QMS 全流程

以前建完任务还要手动点一次「执行」,现在保存即执行:

新建/编辑任务并保存 → 自动执行转存 → 转存到新文件 → 自动生成 strm → 等待 → 刮削

全程不用再手动点。前提是在「连接 QMediaSync」里为该任务绑好了连接 (想先出 strm 就再绑一个同步路径,见上文)。

  • 可在「系统设置 → 全局定时」里关掉这个默认行为;新建/编辑任务的对话框里 也有「保存后立即执行一次」勾选框,可对单个任务临时取消
  • 停用的任务不会自动跑;已在执行中的任务不会重复触发(有运行态检查)
  • 保存接口的返回增加 auto_started 字段,前端据此提示「已保存,并已开始执行」, 而不是笼统一句「已保存」
  • 默认开启;想回到旧行为:设置里关掉,或建任务时取消勾选

关于 QMS 自动触发:转存后触发 QMS 的能力一直都有,但要同时满足三个条件 —— ① 「启用 QMS 联动」开着;② 「转存到新文件后自动触发」开着; ③ 该任务在「连接 QMediaSync」里绑定了刮削目录(想要先出 strm 就再绑一个同步路径)。 任一条不满足都不会触发。 另外只有转存到新文件(去重后确实有新增)才触发, 全是已存在时会跳过以避免空跑。

v1.5.1

一键更新不可用时,明确告诉你原因

原来只有一句「未挂载 /var/run/docker.sock」,无法区分三种完全不同的情况, 用户根本不知道该往哪修。现在拆开说:

情况 真实原因 该怎么办
容器内没有该文件 挂载没生效 —— 改完 compose 没重建容器 三个点 →「重构」/ docker compose up -d --force-recreate
有文件但没权限 容器用户不在 docker 组 去掉 user:,或加 group_add
有权限但连不上 socket 在、daemon 异常 会打印 Docker 返回的状态码与内容

最容易被忽略的是第一条:只点「启动」不会重新挂载 volumes,必须重建容器。

另外:

  • 找不到 /var/run/docker.sock 时会在 /var/run、/run 扫一遍, 发现别的 docker.sock 就提示「路径可能写歪了」
  • 容器 ID 识别增加从 /proc/self/cgroup 解析的兜底(compose 自定义 hostname 时)
  • 「不可用」提示改为常驻显示(以前只在有新版本时才出现)
  • GET /api/update/env 新增 socket_exists / socket_found / running_as_root

v1.5.0

QMediaSync 联动升级:先生成 strm,等它落地后再刮削

QMS 里「生成 strm」和「刮削」是两个独立动作 —— 前者把 115 上的视频映射成本地 .strm,后者才去 TMDB 抓元数据。顺序不能反:没有 strm 就刮不到东西。 之前版本转存完直接触发刮削,新转存的文件很容易因为 strm 还没生成而被漏掉。

现在每条连接可以额外绑定一个「同步路径」,流程变成:

转存到新文件 → ① 触发生成 strm → 等待 → ② 触发刮削

等待策略三选一:

方式 说明
轮询(默认,推荐) 轮询同步路径的 is_running(QMS 语义:0 未运行 / 1 队列中 / 2 运行中),等它真正归零再刮削
固定延迟 等指定秒数再刮削
不等待 触发后立即刮削(等价旧行为)
  • 轮询最长等待默认 900 秒,超时不算失败 —— 会照常继续刮削(已生成的 strm 仍值得刮),并在触发日志里写明
  • 实现上有个坑要绕:刚触发的瞬间任务可能还没入队,立刻查 is_running 会看到 0, 被误判成"已完成"。所以先等一小会儿再开始轮询,并用「是否见过运行态」来区分 「秒完」和「还没开始」
  • 新增「查看同步路径」弹窗:可看 115 远程路径 → 本地 strm 路径的映射与运行状态
  • 连接支持编辑(改绑同步路径 / 换刮削任务 / 调等待策略)
  • 触发日志记录完整流程,例如: 生成 strm:已加入同步队列 → 等待 strm 完成:用时 42 秒 → 刮削:已开始
  • 完全向后兼容:不绑定同步路径时行为与以前一致,老配置不用改

新增接口:GET /api/qms/sync-paths、POST /api/qms/link/update。 用到的新 QMS API:GET /sync/path-list、POST /sync/path/start。

v1.4.0

新增「版本更新」:面板内检查 + 一键更新

「系统设置 → 版本更新」现在可以:

  • 检查更新(零配置)—— 对比 GitHub Releases 上的最新版,显示版本号、发布时间与更新说明
  • 一键更新(需额外挂载 docker.sock)—— 点一下就自动完成「拉新镜像 → 重建容器」, 页面自动等待服务恢复,不用再去飞牛 Docker 界面手动拉取 + 重构

实现要点:容器无法自己重建自己。 一旦被停止,进程就结束了,后续动作没人执行。 所以点「立即更新」后会启动一个一次性助手容器(docker:cli,自带 compose 插件), 由它在旧容器退出之后执行:

sleep 3 && cd <compose 项目目录> && docker compose pull && docker compose up -d --force-recreate

走 docker compose 而不是用 Docker API 手拼容器参数,是因为 compose 文件里有权威配置 —— 端口、挂载、环境变量、网络模式全部以它为准,重建后不会走样。项目目录自动读取容器标签 com.docker.compose.project.working_dir,飞牛界面粘贴 YAML 的场景也能正确定位。

助手容器用 AutoRemove,执行完自动删除;更新过程写入 config/update.log, 可在面板里点「上次更新日志」查看。

不挂 docker.sock 也能用:检查更新照常工作,界面会直接给出手动更新命令。 (挂载 docker.sock 相当于把宿主机 Docker 的控制权交给该容器,UI 与文档里都标注了这一点。)

GET /api/update/env 会明确报告「缺什么、怎么补」,而不是笼统一句"不支持"。

v1.3.0

扫码登录:一次扫码自动轮试多个设备身份

  • 扫码确认后,系统会依次用 alipaymini / wechatmini / android / 115android / ios / ipad / os_windows / web 尝试换取 Cookie,第一个成功的即采用。 不再需要用户手动换设备重扫,也顺带产出「每个设备分别报什么错」的完整明细。
  • 明显是「还没确认 / 被风控」时提前退出,不浪费请求。
  • 每次尝试的结果都写进诊断日志。

默认登录设备改回 alipaymini

  • 依据 xiaoya-alist 修 115 扫码的 commit:115 的扫码设备清单里 windows/mac/linux/wechatmini/alipaymini 等是官方支持的,web 不在清单内, 且 p115client 文档警告 app="web" 最容易触发「IP登录异常」(次日零点才解禁)。 故把 web 降到列表最后。

修复诊断日志「只有 2 条」的 bug

  • 原因:等待扫码阶段 115 返回 data={},raw_status 为 None, 与初始值相同 → 第 3 步完全不写日志,错误也被「等待扫码」吞掉。 改用哨兵初值,并强制「接口报错时必记」。

自检新增第 4 项:时间同步检查

  • 对比容器时间与 115 服务器时间(HTTP Date 头)。偏差超过 5 分钟会导致 扫码凭证签名校验失败 —— 这是排查「参数错误」时最容易被忽略的一环。

v1.2.0

修复扫码登录「参数错误」—— 默认登录设备改为「网页端」

复盘两次失败发现共同点是 alipaymini(支付宝小程序)这个命名空间:

版本 取 token 用的 app 换取凭据用的 app 结果
v1.0.0 web alipaymini 参数错误
v1.1.0 alipaymini alipaymini 参数错误

而症状是「手机扫得出声音、但确认后失败」—— 这正是小程序端命名空间签发的二维码, 用 115 主 App 扫无法完成确认的表现。115 官方网页版用的是 web 命名空间, 而本项目的场景(电脑屏幕显示二维码 → 手机 App 扫码)与官网登录完全一致, 所以默认改为 web,并按其可用性重排了设备列表。

新增诊断能力(这次能定位问题靠的就是它)

  • 「扫码登录」弹窗里新增实时诊断日志:token / 二维码 / 状态轮询 / 换取凭据 四步的原始 115 响应全部记录并展示,含时间戳
  • 「复制诊断」按钮:一键复制全部诊断信息,直接发给开发者定位
  • 修复一个隐蔽 bug:状态轮询接口报错时被静默当成「还在等待扫码」, 现在会把 115 的 message + errno 明确显示出来
  • 「重新生成二维码」按钮,换设备后不用关闭弹窗重开(切换下拉框也会自动重新生成)

v1.1.1

  • 新增版本号显示:面板左下角显示当前版本,GET /api/version 也可查, docker inspect 能看到构建时间和 commit —— 更新后一眼确认有没有生效
  • 镜像补充 OCI 标准 LABEL(版本、源码地址、构建时间、commit)
  • README 补充「更新到最新版」完整说明:如何确认更新生效、常见问题、 数据备份、清理旧镜像

v1.1.0

修复 115 扫码登录「参数错误」

  • 修正扫码登录的登录设备(app)不一致问题:原先二维码是用 web 身份生成的, 但换取 Cookie 时用的是页面上选的设备(如 alipaymini),115 会把这种 不匹配视为参数错误而拒绝。现在 token / 二维码 / 换取凭据三步统一使用同一个 app。
  • 扫码失败时不再只给一句笼统提示,而是透出 115 返回的原始 message 与 errno, 并针对「参数错误」「IP登录异常」等情况给出对应建议。
  • 新增扫码登录自检(「扫码登录 115」弹窗里的「接口自检」按钮): 不需要真的扫码,直接验证 token / 二维码 / 状态轮询三个接口是否都通, 方便快速判断是代码问题还是 115 风控。
  • 手动粘贴 Cookie 的入口保留,作为扫码被风控拦截时的兜底方案。

说明:如果自检三步全通过、但扫码确认后仍拿不到凭据,通常是 115 对本机 IP 的 风控(表现为「老乡验证失败」errno=40101017 或「IP登录异常」)。 换网络/挂代理,或直接用「手动粘贴 Cookie」。另外建议把登录设备换成 115生活_支付宝小程序(默认值,比 网页端 更不容易触发风控)。

v1.0.0(首个版本)

  • 从 0 实现 115 网盘存储层(基于 p115client),支持扫码登录与 Cookie 导入、多账号切换
  • 转存任务:定时执行、启用/停用、正则过滤与替换、排除清单、指定转存文件夹(逐层下钻 + 跨层级多选)
  • 去重三档:文件名 / 文件名+大小 / SHA1(115 秒传机制下改了文件名也能拦住),支持独立的对比路径
  • 离线下载:磁力 / ed2k / HTTP / FTP / 种子提交到 115 云端,进度查看、删除、重试、清空
  • 文件管理:浏览、新建文件夹、重命名、移动、复制、删除、搜索、生成分享链接
  • QMediaSync 联动:手动绑定「转存任务 / 离线下载」↔「QMS 刮削目录」, 仅在转存到新文件时触发(避免空跑),离线下载完成自动触发,带触发日志
  • 转存历史落 SQLite(配置快照 + 分级日志 + 去重跳过清单 + 实际新增清单),每任务保留最近 10 次
  • 13 个通知渠道、空间用量告警、零构建单页前端(免 npm 构建)、Docker 化 + GitHub Actions 自动构建

License

本项目为二次开发作品,沿用上游的 AGPL-3.0(见 LICENSE 与 NOTICE)。 p115client 为 MIT。115 网盘接口为非官方公开接口,请合理使用,注意账号安全与风控。

About

115 网盘自动转存系统:定时转存分享链接、离线下载、文件管理,并联动 QMediaSync 自动刮削生成 strm。A port of bdsavepro to 115 cloud drive.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages