111 Commits

Author SHA1 Message Date
Estrella Pan
4b739bcfc3 Merge remote-tracking branch 'origin/4.0-dev' into feat/notifier-plugins 2026-10-21 10:46:28 +02:00
Estrella Pan
81cbc549b0 fix: 审查修复——连载番剧只超出集数不再每周误报、仍不匹配时更新原因、未完结剧集 TMDB 缓存 24 小时过期、无法滚动时不判触底、路径未变的完成通知按本轮实际策略发送、season_raw 只存季的写法、已归档番剧不列入待检查、复制令牌按真实结果提示
- offset_scanner: 建议偏移全为 0 且剧集未完结时不标记不通知;已标记仍不匹配时更新原因与建议偏移(不再通知)
- tmdb_parser: 除 Ended/Canceled 外的状态都按 24 小时过期
- renamer: 路径未变时的通知改由本轮解析出的策略是否为选定策略决定,不再读 NO_RENAME 设置
- tokenizer: season_raw 只记 S03 / Season 2;搜索端保留正则处理旧数据并补上 EPs 写法 (#1138)
- bangumi: get_needs_review 排除已归档番剧
- webui: isAtBottom 移入 scroll-spy 并要求容器可滚动;搜索确认弹窗复制启用 legacy;新增 copyText,令牌复制失败时报错
- 删除只断言 SQL 语句形态的测试
2026-10-21 09:16:01 +02:00
Estrella Pan
f1dd439fbe feat(notification): Bark 支持铃声、分组、中断级别、图标与点击地址;插件权限新增「运行外部命令」
- Bark 渠道新增可选字段 sound、group、level、icon,并复用 url 作为点击打开的地址;
  只发送已设置的项,旧配置的请求体不变;配置了 icon 时优先于海报地址(#792)
- 测试接口 /notification/test-config 透传新字段;通知设置表单加上对应输入框
  与 level 下拉框,并更新三语言的通知配置文档
- 插件权限标签新增 subprocess(运行外部命令),仅用于展示
2026-10-21 09:09:43 +02:00
Estrella Pan
6e6461febe fix(webui): 剩余 carbon 图标改为 Lucide、设置页导航高亮与滚动位置对齐(参考 #1119)、HTTP 下复制令牌回退到兼容方式 2026-10-21 08:47:35 +02:00
Estrella Pan
09f43ef78e perf(webui): 只打包 Vue 运行时、按需加载 zh-CN 语言包与布局弹窗,待审核光晕改为 opacity 动画 2026-10-20 17:30:20 +02:00
Estrella Pan
5d8a02787a chore(webui): ESLint 10 + antfu flat 配置取代 Prettier,husky 升级到 9 2026-10-20 11:12:07 +02:00
Estrella Pan
13dc621ff7 chore(deps): 前端运行时升级到 vue-router 5 / pinia 4 / vueuse 15 / vue-i18n 11 / axios 1.20 2026-10-20 11:06:50 +02:00
Estrella Pan
2f1fa317d1 chore(deps): 前端构建与测试工具链升级到 Vite 8 / Vitest 5 / TS 6 / Storybook 10
- vite 8、@vitejs/plugin-vue 6、vitest 5、happy-dom 21、@vue/test-utils 2.5、
  typescript ~6.0.3、vue-tsc 3.3、unocss 66.11、unplugin-auto-import 21、
  unplugin-vue-components 32、sass-embedded 1.105、playwright 1.64.1、storybook 10.6
- vite.config:去掉 defineModel 开关;Sass 改用 @use;esbuild.drop 换成
  rolldown 的 dropConsole;__dirname 换成 import.meta.dirname;两处 alias
  改为 resolve.tsconfigPaths
- 删除 plugin-vue-jsx:三个 SFC 的 JSX 改写为 h()
- 删除 preset-attributify:模板里的属性式工具类全部改回 class
  (含 space-y-*、text-*、line、i-carbon-* 等,不止 5 处 flex="~")
- 删除 vite-plugin-pwa:改为静态 manifest.webmanifest + 自毁 sw.js(4.1 删除)
- tsconfig:moduleResolution Bundler、去掉 baseUrl 与 JSX 配置,
  app 的 types 只留 vitest/globals
- Storybook 10:addon-links + addon-docs,stories 改从 @storybook/vue3-vite 导入类型
- @intlify/unplugin-vue-i18n 0.11 -> 11.2.7(Vite 8 下旧版产物被 vite-json 再次解析而失败);
  新版只产出 AST 文案,故打开 __INTLIFY_JIT_COMPILATION__,hasKey 识别 AST 叶子
2026-10-20 11:00:31 +02:00
Estrella Pan
02e0c3ea63 chore(deps): CI 运行时升级到 Node 24 / pnpm 12,并移除未用依赖
- Node 24、pnpm 12.14.0、pnpm/action-setup@v6,@types/node ^24
- .npmrc 的 public-hoist-pattern 迁入 pnpm-workspace.yaml,新增 allowBuilds
- docs 同步迁移 pnpm 12
- 删除 @vueuse/components、radash、generate-pwa-assets 脚本、失效的 coverage 配置、app AutoImport 的 vitest 预设与 Components dirs
2026-10-20 10:41:07 +02:00
Estrella Pan
87e23eea2d fix(plugins): 处理 Codex 对插件市场的审查意见
- 安装请求带上用户确认的版本,目录已换版本时后端拒绝
- 目录刷新换了条目时关闭确认栏,重新确认权限
- 是否有更新由后端按 PEP 440 判断(update_available)
- 插件改动后的同步失败不再算作操作失败
- KeepAlive 再次进入时重新加载失败,下次进入会再试
- 手机详情页的返回只在上一条历史是列表时后退
2026-10-09 15:39:17 +02:00
Estrella Pan
c67a63267f fix(webui): 处理市场页与内置插件文案的审查意见
- 设置页与市场页通过 pluginSetVersion 在再次进入时互相同步;刷新逻辑合并为 refreshPluginState
- 安装成功后本地更新 installed_version,不再重新下载目录
- 选中的插件变化时关闭确认栏,避免装错插件
- 本机版本比目录新时不提示更新
- 内置插件文案只用于 source 为 builtin 的插件
- README:链接中的行内代码、段落中的“2024.”、分隔线、标题中的“C#”
2026-10-09 15:34:31 +02:00
Estrella Pan
7ace0581e0 fix(webui): 市场页再次进入时同步安装状态;手机底栏用短标签 2026-10-09 15:27:01 +02:00
Estrella Pan
56ac06db2a feat(webui): 插件市场页面
- 新增 /market 与 /market/:id:列表 + 详情双栏,手机上点行进入整屏详情,
  主操作固定在底部;侧栏与手机导航加入口
- 按扩展点归类的分类标签、安装状态筛选与搜索;签名目录获取成功后缓存,
  目录不可达时显示上次的目录并禁用安装/更新
- 安装/更新前的确认步骤:来源与审查、权限声明、前端、同进程警告;
  更新显示版本与权限差异,权限变多须勾选确认
- README 用安全的 Markdown 子集渲染(先转义 HTML,链接只允许 http(s))
- 设置页的插件目录区换成「打开插件市场」入口
- 后端 CatalogEntry 补上 sdk 与 path 字段
2026-10-09 13:34:38 +02:00
Estrella Pan
5fc7f749d7 Merge remote-tracking branch 'origin/4.0-dev' into feat/4.0-plugin-market 2026-10-09 13:00:21 +02:00
Estrella Pan
5e15c5cf17 fix(webui): 4.0 界面审查修复
按 4.0 UI 审查确认的问题修复(设计规范、状态与流程、无障碍与手机端、文案):
- 插件卡片:加载失败可重试、未保存标记、开关与保存分开忙碌状态、停用或卸载
  仍在使用的插件前确认、扩展点与权限显示为译名、tags 字段改用 ab-tags-input
- 插件表单:必填与范围校验、删除非空行前确认、清空数字恢复默认值、稳定的行 key
- 插件页面与挂载点:加载骨架、失败原因与重试;手机底栏可进入插件页面
- 下载器:按类型显示字段、切换类型重置选项、id 规则提示、类型名称
- 通知:测试结果按列表 / 对话框分开、插件渠道提示、rename_skipped 显示方法名
- 规则编辑与添加 RSS:高级区改用 ab-field / ab-select,补充可访问名称
2026-10-09 10:23:16 +02:00
Estrella Pan
eb1598c21e feat(plugins): 插件市场索引与签名发布流水线
- plugins/registry/<id>.toml 指向作者仓库的固定 commit(repo、commit、path)
- scripts/plugin_registry.py:check 在 PR 上校验改动的条目、运行作者测试、
  从 web-src/ 构建前端并写审查摘要;build 在合并后增量打包(commit 未变的
  条目复用已发布的 zip)
- plugin-registry.yml:check / build 不读 secret,sign 只用 PLUGIN_SIGNING_KEY
  签名,不运行作者代码;发布到 release plugins 并删除下架的资源
- 插件目录改用独立的插件公钥 ab_plugin_pubkey.pem,与在线更新密钥分开
- catalog 条目新增 authors、permissions、has_web、repo、commit、readme
- 6 个示例插件上架;上架文档(中 / 英 / 日)、PR 模板与 CODEOWNERS
2026-10-09 10:13:30 +02:00
Estrella Pan
56fa95f51f refactor(4.0): 删除前端 experimental_openai 时代遗留的 OpenAIModel / OpenAIType 类型
P0 已删除后端的 experimental_openai 配置段,这两个类型没有任何引用。
2026-10-08 17:21:35 +02:00
Estrella Pan
c05c2b1727 fix(4.0): 种子批量操作按实例汇总结果、按实例区分选择,下载器页按任一已配置实例判断
- perInstance 对所有实例并发执行,任一实例异常或返回 status: false 都按失败处理并提示,不被其它实例的成功掩盖;无论成败都刷新列表
- 选择键改为 downloader_id:hash,同一 hash 存在于多个实例时只操作选中的那一个
- 下载器页「未配置」判断改为没有任一已配置实例;插件下载器无 host 字段时视为已配置
2026-10-08 17:06:53 +02:00
Estrella Pan
ee5704eef0 feat(4.0): 设置页新增签名插件目录的浏览、安装、更新与卸载
- 插件卡片下方新增「插件目录」区:点击后才请求 GET /plugins/catalog(silent),失败时在区内显示后端原因并可刷新
- 条目按已装版本显示安装 / 更新 / 已安装;安装成功后刷新卡片、Provider 下拉与前端挂载点
- 卸载只对来源为 catalog 的插件开放,经危险确认框;与后端卸载前提为同一条件,LLM 插件不受影响
- 卡片标出已签名 / 未签名
- 修复前端缺少 catalog 来源类型与 source_catalog 文案
- 同步签名与分发文档(中 / 英 / 日)、设计文档第 0 节与 CHANGELOG
2026-10-08 15:38:52 +02:00
Estrella Pan
91e01f716f feat(4.0-P6): WebUI 插件挂载点、AbHost 宿主实现与示例插件「手动选种」
- PluginSlot:导入插件模块并在 Shadow DOM 中挂载 custom element,host / context 先于插入设置;加载失败、元素未定义或运行时报错只让该插件的挂载点显示「插件组件加载失败」
- AbHost:api 限定在 /api/v1/plugins/<id>/ 与只读 GET 的宿主 API;i18n、主题、toast;events.on 订阅 SSE 新增的 bus 帧(后端把总线事件转发给前端)
- 五个挂载点:设置分区、番剧详情标签、卡片操作、侧边栏页面 /plugins/:id、首页小组件
- 未启用的内置 / 已允许的插件也能取得配置 schema,首次启用前即可填写配置
- Schema 表单支持对象数组(hardlink 的 path_map)
- hardlink 插件自带 settings.section 元素「链接已有文件」,调用补链路由并显示计数
- examples/plugins/manual-pick:详情页标签示例,含插件路由与事件订阅
- 设计文档第 0 节记录调整,第 10 节标记 P6 已完成
2026-10-08 13:50:46 +02:00
Estrella Pan
bf1c8db08c feat(4.0-P6): 前端插件后端与 plugin-ui 工作区包
- 清单 [[plugin.ui]]:五个挂载点、ab-plugin- 元素名与 web/ 入口校验
- GET /plugins/ui 列出已启用插件的挂载点;GET /plugins/{id}/web/{path} 提供静态资源(防路径穿越、JS MIME、需登录)
- SPA 文档加 CSP script-src 'self',内联深色模式脚本移到 theme-init.js
- webui/packages/plugin-ui:AbHost 类型、--ab-* 主题变量、Vite 库模式模板
- 设计文档第 0 节记录第一部分的调整
2026-10-08 13:31:03 +02:00
Estrella Pan
d0e357a603 feat(4.0-P2.5): WebUI 管理多个下载器实例,规则与订阅可选下载器,种子列表标注实例
- 下载器设置列出所有实例:添加、编辑、删除、设为默认,类型下拉框含插件下载器
- 规则编辑与添加订阅新增下载器选择(留空跟随默认实例,只有一个实例时不显示)
- 下载器页与种子列表在多实例时标出种子所在实例,批量暂停 / 恢复 / 删除按实例分别请求
- 插件卡片保存后只刷新 plugins.enabled / options,不再覆盖未保存的实例与 slots 修改
- 添加订阅时保存 downloader_id
2026-10-08 12:37:06 +02:00
Estrella Pan
e442d35c76 feat(4.0-P2.5): 下载器改为 plugins.instances 实例,slots 选择 Provider,新增 3.3 配置迁移器
- 运行时模型移除 downloader 配置节:下载器是 plugins.instances 中的实例(id / point / provider / options),plugins.slots.downloader 指向默认实例;Config.downloader 改为只读视图
- 重命名方式、文件分类、版本冲突策略按 plugins.slots 选择,未登记时退回 none / default / hold;冲突策略改为 core 的 hold / replace 两个 Provider,ConflictRequest 删除 configured,ab_sdk 升至 0.5.0
- module/update/v4.py:迁移 downloader、rename_method(normal 改为 none)、revision_conflict_policy;先备份为 config.json.v3.bak,失败时恢复备份并拒绝启动;在 Settings 读写配置前运行
- module.update 包初始化不再 import 子模块,避免 module.conf 引用迁移器时循环 import
- AB_DOWNLOADER_* 等环境变量与设置向导写入默认实例
- 数据库迁移 v26:bangumi / movie / rssitem / torrent 增加 downloader_id,存量行为 default
- WebUI:下载器设置编辑默认实例,重命名方式与版本冲突策略绑定 plugins.slots
2026-10-08 12:07:53 +02:00
Estrella Pan
a0ebfff0e1 feat(4.0-P4): 内置插件 rename 提供 pn / advance / template 重命名方式
- pn / advance 从 core 移入内置插件 rename(默认启用),输出与 3.x 逐字节一致;
  宿主只保留 none,插件停用时按 none 处理。
- template:Jinja2 沙箱模板(StrictUndefined + pad 过滤器)渲染文件名主体,
  字幕自动追加语言与扩展名。保存配置时编译并试渲染示例,不合法返回 422;
  运行时渲染失败、结果为空或含路径分隔符时抛出 RenameSkipped,文件保留原名并
  通知,绝不退回 pn。
- 设置页重命名方式下拉框并入插件提供的方式;通知中心支持 rename_skipped。
- 测试环境默认登记内置 rename 插件,与生产默认启用一致。
2026-10-08 10:37:58 +02:00
Estrella Pan
58f3b40599 Merge branch 'refactor/4.0-p2-extension-points' into refactor/4.0-p3-ingest
# Conflicts:
#	backend/src/module/plugin/host.py
#	webui/src/hooks/usePluginProviders.ts
2026-10-08 09:00:59 +02:00
Estrella Pan
0c5563f8c8 fix(4.0-P2): 修复评审发现的插件隔离与掩码问题
- 插件清单拒绝保留 id "core",避免插件与宿主内置 Provider 共享归属
- 新增 host.provider_impls():搜索站点与 LLM 扩展适配器逐个调用工厂,出错的跳过并记录
- 插件代码未加载(无 schema)时,掩码/还原把全部配置项按秘密处理
- 定时任务桥接:ScheduledTask 字段读取纳入异常保护并计入熔断;enabled 每轮读取,异常计入熔断
- 通知渠道先按原样匹配 type,再按小写兼容,支持含大写的插件渠道 id
- WebUI:插件启停/保存后刷新插件 Provider 候选列表
2026-10-08 08:55:34 +02:00
Claude
9b7ef00c2e feat(4.0-P3): ingest 流水线扩展点与内置种子过滤插件
- 钩子:torrent.filter(排除过滤之后,fail-open)、title.parsed(准入判定前)、
  torrent.adding(save_path / category / tags,ab:<id> 标签强制保留)、
  http.request(GET 请求头)
- Provider:metadata_provider,mikan / tmdb 以 core 登记,按 RSSItem.parser 选择,
  行为与 3.x 一致;插件元数据源走超时与熔断
- SDK 0.3.0:新增 ab_sdk.ingest(冻结快照 TorrentInfo / BangumiInfo / AddRequest /
  HttpRequest / Metadata)
- host.get_runner()/hook_runner():AppContext 构建时设置 runner,无钩子时零开销
- 内置插件 ingest-filters:全局包含过滤,默认启用、留空不生效
- WebUI:添加订阅的解析器下拉框并入插件元数据源
- 文档:插件开发指南新增各扩展点;设计文档 P3 调整

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoBUPFw7Ty1epuPESdELs1
2026-10-08 06:31:23 +00:00
Claude
332fb451c4 feat(4.0-P2): 下载器/通知/LLM/搜索/定时任务扩展点与插件管理界面
- ab_sdk 0.2:新增 points、downloader(自 module/downloader/base.py 迁入)、
  llm(自 providers/base.py 迁入)、notify、search、tasks 契约与
  secret_field();原路径保留重新导出
- module/plugin/host:进程级扩展注册表,内置 qB/aria2/mock 与各通知渠道以
  core 身份登记;DownloadClient、NotificationManager、LLM 注册表、搜索站点
  均改为按 id 从注册表解析
- 插件定时任务随插件加载/重载/停用同步进调度器,失败计入熔断
- 插件变更后重建通知渠道、清理 LLM 解析器缓存;下载器客户端缓存键包含
  Provider 登记项,插件重载后自动换新
- /api/v1/plugins:总览(schema + 掩码配置)、启停、配置(按模型校验)、
  allow_unsigned、Provider 列表;/config 读写同样掩码/还原插件秘密字段
- WebUI:设置页「插件」卡片与 JSON Schema 表单;下载器与通知下拉框并入插件
  Provider;插件保存后刷新 config store 的 plugins 段,避免全局保存覆盖
- 修复:/search/provider 读取导入时的站点快照,新站点需重启才出现
- 文档:开发者指南 → 插件开发

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoBUPFw7Ty1epuPESdELs1
2026-10-08 05:59:07 +00:00
Claude
94da8fa741 feat(4.0-P1): 插件运行时与 SDK 骨架
- ab_sdk:Plugin[ConfigT] 基类、@hook / @provider / @subscribe、只读的
  PluginContext 协议、事件基类,以及插件单测辅助 ab_sdk.testing;SDK 不依赖
  module.*(有边界测试)
- module/plugin:plugin.toml 清单校验(含 sdk 版本范围),内置 / 本地目录 /
  pip entry point 三种来源的发现与加载(ab_plugin_<id> 包名、支持相对导入、
  vendor 纯 Python 依赖、拒绝原生扩展)
- 扩展点注册表;HookRunner 负责 filter / transform 钩子的优先级与 hook_order
  排序、超时、fail-open / fail-closed,以及按插件熔断
- EventBus:每个订阅者独立的有序队列,失败与超时隔离,计入熔断
- PluginManager:失败时回滚注册;配置变更只重载受影响的插件;失败插件在其
  配置变化后才重试;熔断后自动禁用并发布 PluginDisabled
- 插件私有 KV 存储(plugin_kv 表,迁移 v25)、plugins 配置段、
  GET /api/v1/plugins
- 插件随进程 lifespan 启停,reload_settings 时重新应用插件配置

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoBUPFw7Ty1epuPESdELs1
2026-10-08 04:37:57 +00:00
Claude
8ccacb6060 refactor(4.0-P0): 清理死代码与 3.x 兼容层
- 4.0 只支持从 3.3.x 升级:version_check 改为完整 semver 比较,数据低于 3.3
  或残留 2.x data.json 时抛 UnsupportedUpgradeError 中止启动;删除 3.0→3.2
  跨版本迁移、data.json 迁移和 Database.migrate()
- 删除 3.2 兼容的 GET 控制端点与 GET /auth/refresh_token
- 删除旧 JWT 与内存会话实现,密码哈希移到 security/password.py
- 删除 experimental_openai 配置节、通知单 provider 旧字段、3.1 配置字段改名迁移
  与 DEFAULT_SETTINGS;normal 重命名方式迁移为 none
- 删除约 70 处无调用方的函数、方法、模型及未使用的 qB API 封装
- CI 新增 vulture 死代码检查(配置见 pyproject.toml 与 vulture_whitelist.py)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoBUPFw7Ty1epuPESdELs1
2026-10-08 04:19:53 +00:00
Estrella Pan
9d5b1c3998 fix(renamer): safely replace higher revisions (#1078) 2026-07-13 01:36:17 +02:00
Estrella Pan
e8bcece42f feat(parser): add selectable preview engine 2026-07-12 21:07:33 +02:00
Estrella Pan
81cfa41c26 fix: address authentication review findings 2026-07-10 11:56:42 +02:00
Estrella Pan
42ccb7611b feat: persist multi-user authentication 2026-07-10 00:52:39 +02:00
Estrella Pan
618464a450 feat(config): user-configurable TMDB API key + Network settings panel (#975, #1042)
Backend: add network.tmdb_api_key (empty = fall back to the built-in
shared key, read live like tmdb_base_url); the config API masks it
automatically.

WebUI: the network section existed backend-only since #1042 — expose
it as a Network settings panel (TMDB API URL, TMDB API key,
Bangumi API URL) with types, initConfig defaults, i18n, and mocks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pz1EmcTPzjgEpkGQWzb8AQ
2026-07-07 18:05:53 +02:00
Estrella Pan
1b928ee326 feat(rss): setting to disable orphan-torrent tracking
New bangumi_manage.track_orphans toggle (default on = current
behavior). When off, refresh_rss no longer persists unmatched torrents
(bangumi_id NULL orphan records): they are re-matched in memory each
tick (cheap, no network — the analyser dedups via bangumi rules, not
torrent records), and a rule added later can retroactively pick up
episodes still in the feed, which persisted orphans used to block
forever. Season-collect flows still persist their NULL records for
feed dedup and are not gated.

WebUI: "Track Unmatched Torrents" switch in Settings -> Manage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pz1EmcTPzjgEpkGQWzb8AQ
2026-07-07 12:15:06 +02:00
Estrella Pan
d91686486b refactor(webui): rework torrent-management UI to Soft Ink ab-* layer
Post-rebase rework addressing review findings on #1051 (the branch
predated the component-system migration):

- Replace retired ab-popup confirm with the useConfirm() promise hook
- Replace NButton and bare <button> with ab-button / ab-icon-button;
  badge pills with ab-tag / ab-badge marks; empty state with ab-empty;
  drop hardcoded colors for theme variables
- Make the per-bangumi page's route param reactive with a NaN guard,
  and remount the list on param change
- Add a "View torrents" entry point in the rule edit dialog
- Reload the torrent list in finally so partial batch-delete failures
  don't leave stale rows
- Align the orphan card title across locales; regenerate dts files

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pz1EmcTPzjgEpkGQWzb8AQ
2026-07-07 10:58:58 +02:00
Estrella Pan
679bd2621c feat: manage bangumi torrents and orphan torrents (port of #1020)
Port zhushanwen321's torrent-management feature (#1020) from the 3.2 sync
architecture to the 3.3 async design.

Backend:
- TorrentDatabase: search_orphans / count_orphans / delete_orphans / delete_obj
- bangumi API: list/delete endpoints for per-bangumi torrents and orphan
  torrents (bangumi_id IS NULL); literal /torrents/orphans routes registered
  before /{bangumi_id}/torrents

WebUI:
- Others card on the bangumi list with orphan count badge
- Torrent list pages (per-bangumi and orphans) with multi-select,
  batch delete, and clear-all confirm dialog

The PR's mikan_parser and rss/analyser fixes are not ported: 3.3-dev
already handles failed poster downloads and missing elements.

Co-authored-by: zhushanwen321 <zhushanwen321@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FAxVyRwrY7z5NotTtgnwVs
2026-07-07 10:48:07 +02:00
Estrella Pan
378acc90cb feat(webui): NexusPHP PT-site preset for search providers
Add PT (private-tracker) support via the existing RSS-based search
pipeline. The Add Search Provider dialog gains a "PT Site (NexusPHP)"
mode that builds a torrentrss.php search template from base URL +
passkey + optional category ID (e.g. Audiences.me anime = 402), so
search -> subscribe -> auto-download -> rename works end-to-end on
NexusPHP trackers with no backend changes.

- webui/src/utils/nexusphp.ts: URL-template builder (vitest-covered)
- config-search-provider.vue: segmented custom/NexusPHP add dialog
  with live URL preview; en + zh-CN strings
- backend test_nexusphp_rss.py: regression tests proving NexusPHP
  torrentrss output (enclosure and linktype=dl variants) parses
  through the generic rss_parser path

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JhTduCYBGeHETU7dSWoh37
2026-07-07 10:19:26 +02:00
Estrella Pan
e309301dd6 refactor(webui): complete ab-* migration, retire legacy components
Migrate the remaining surfaces: all seven ab-popup call sites to
ab-modal (footer slots, sm sizing), every ab-label to ab-field, all
raw ab-input-shortcut inputs to the ab-input component (textareas get
local Soft Ink styles), rss mobile list to ab-list with loading
skeleton, and de-tint the hand-rolled warning callouts (hairline +
ink, per the no-tinted-boxes rule). Wire aria-live into pull-refresh.

Delete dead code: ab-popup, ab-adaptive-modal, ab-label, ab-data-list,
ab-add, ab-button-multi, ab-image, ab-rule, ab-swipe-container (+
orphaned stories); drop the ab-input/input-error UnoCSS shortcuts.
Add stories for the full universal set and document the final
22-component catalog in DESIGN.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FAxVyRwrY7z5NotTtgnwVs
2026-07-06 11:38:59 +02:00
Estrella Pan
6e5cb2ca07 feat(webui): Soft Ink universal component set
Overlays: ab-modal (adaptive dialog/sheet on headlessui, focus trap,
footer slot), promise-based useConfirm + host, ab-tooltip. Forms:
ab-field (label/description/error/aria wiring), ab-input component
(affixes, clearable, password reveal), ab-select/ab-switch wrappers,
ab-segmented underline tabs; ab-setting rebuilt on them with new
description/error/required props. Display: ab-tag v2 (semantic marks,
ink text, no truncation), ab-status v2 (4 states + label/detail),
ab-badge, ab-progress (scaleX fill), ab-container footer slot,
accessible animated ab-fold-panel. Feedback: ab-alert (hairline, bold
lead word), inverted-ink toast theme, ab-empty, ab-skeleton, ab-list
(selection, loading, density), ab-toolbar. All with vitest coverage
and Storybook stories; bottom sheet gains footer slot and drops
hard-coded overlay/z-index.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FAxVyRwrY7z5NotTtgnwVs
2026-07-06 11:15:16 +02:00
Estrella Pan
e6d39e5624 feat: in-app notification center and LLM provider plugin framework
Two 3.3 platform features sharing core infra (notification events, DB
migrations/wiring, config model, API registration), committed together
because they cannot be split at file granularity without broken
intermediate builds.

Notification center:
- New `inboxmessage` table (migration 16) + repo with coalescing
  (unread rows with the same dedup_key increment a count; `once` kinds
  like update_available notify per-version) and 500-row pruning.
- SystemEvent contract (kind/severity/dedup_key/payload) with new
  DownloaderUnavailable / UpdateAvailable / UpdateApplied events;
  NotificationManager.send_event persists to the inbox first, then
  broadcasts to external providers only when notification.enable.
- SSE `notification` frames driven by an in-process revision counter;
  REST endpoints for list/unread-count/read/read-all/delete/clear.
- Wire-ups: qBittorrent auth now reports last_auth_error
  (credentials|banned|unreachable) and stops retrying rejected
  credentials to avoid a WebUI IP ban; the startup wait loop aborts
  early on credential errors and emits a notification; a 24h
  update-check tick surfaces new versions; apply/rollback emit results.
- WebUI bell + unread badge + dropdown inbox (severity, coalesce count,
  relative time, deep links), SSE-driven with a 30s poll fallback.

LLM provider plugin framework + subscription auth:
- LLMParser refactored to a thin facade over a provider adapter
  contract + registry; the three built-ins (openai/anthropic/gemini)
  move to adapters with zero behaviour change (existing tests unchanged).
- base_url presets for zai/minimax/deepseek/kimi (mainland + overseas)
  reusing the OpenAI-compatible adapter, with a json_object structured-
  output fallback for providers without json_schema.
- Downloadable signed plugins: catalog + installer clone the update
  system's ed25519 verify / zip-slip-guarded extract pipeline (unsigned
  refused, catalog signed against downgrade); versioned-module-name
  loader hot-loads without restart.
- Subscription auth: `llmcredential` table (migration 17) stores OAuth
  tokens server-side only (never sent to the frontend); device-flow
  auth runs a server-side polling task keyed by an opaque handle so the
  device_code stays off the wire; AuthExpiredError trips the breaker and
  notifies. Two grey-area device-flow plugins (github-copilot,
  codex-chatgpt) under plugins/, flagged at-your-own-risk.
- WebUI: grouped provider select (built-in/preset/subscription/
  downloadable), install with risk confirmation, device-flow connect
  dialog, connected-account chip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FAxVyRwrY7z5NotTtgnwVs
2026-07-06 09:43:45 +02:00
Estrella Pan
6f72cfe1df perf(startup): defer LLM SDK imports and gate the volume chown
Two things dominated container start:

- The LLM SDKs (anthropic + openai + google-genai) imported at module load
  via parser.analyser/__init__ → llm.py, costing ~0.7s on every boot even
  though the LLM parser is off by default. Move those three heavy imports
  into the provider branch of __init__ and each _parse_* method, so they
  load only when an LLMParser is actually built. Measured: importing
  parser.analyser drops 782ms → 203ms; none of the SDKs are in sys.modules
  after the startup import chain. httpx/httpx_socks stay module-level (cheap,
  and the proxy tests patch them by module path).

- entrypoint.sh ran `chown -R /app/data /app/config` unconditionally every
  start — O(files), which a large poster cache on a NAS turns into the
  dominant startup cost. Walk the tree only when ownership needs fixing
  (first run, changed PUID/PGID, or a wrong-owned volume root), tracked by a
  marker; the legacy bangumi.json migration chowns its one file explicitly so
  the gate can't miss it. /home/ab (image-internal, tiny) is still chowned
  every time.

Also regenerates the auto-imports dts for onResponseError (exported for the
401 test in the previous commit).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FAxVyRwrY7z5NotTtgnwVs
2026-07-04 19:16:49 +02:00
Estrella Pan
620b4f5ecc feat(webui): config/log redesign, log dedup, 3.3 UI alignment, LLM model list
Config page redesigned to a section-rail layout (search + per-section
dirty dots, single column), a save bar that names the restart and
double-confirms save/discard, and a navigate-away guard. Log page
redesigned with a health strip (+ jump-to-first-problem), a unified
toolbar (level chips with counts + text search), severity-only row
colour (fixes the dark-mode 3.53:1 contrast failure), an extracted
module column, and overflow-wrap word handling.

Log deduplication: fix the queue log handler using Python's default
formatter (source of the "INFO::module.x:" duplicate prefix on every
line), strip 245 hand-written [Tag] prefixes, drop double-logged
offset-scan/calendar completion lines, demote "Config loaded" to DEBUG.

3.3 feature/UI alignment: aria2 selectable in Config + wizard (with an
aria2 JSON-RPC connection test), preferred group/resolution and
episode_type surfaced in the rule editor (migrations v10/v12), update
auto_check switch with immediate persistence (no restart).

LLM: model input is now a searchable/free-text select that fetches the
provider's model list on demand (new POST /api/v1/config/llm/models);
timeout/cache/concurrency/circuit-breaker tuning fields; default model
gpt-4o-mini -> gpt-5-mini (discontinued).

Accessibility: real <label for> on config controls, aria-labels on
switches/selects, aria-pressed on log chips, 44px touch targets.
Unify delete confirms to NPopconfirm; password inputs; copy fixes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FAxVyRwrY7z5NotTtgnwVs
2026-07-04 07:42:10 +02:00
Estrella Pan
b34fa62403 feat(update): in-app auto-update via volume-backed overlay
Adds one-click update from the settings page so routine updates no
longer require pulling a new Docker image.

- CI attaches update-bundle-<version>.zip (+ .sha256) to each release:
  backend module tree, built webui, and a manifest; the image bakes
  /app/IMAGE_VERSION as the baseline
- boot_overlay.py (shipped in the image, outside /app/module so it can
  never overwrite its own deciding logic) runs from entrypoint before
  the app starts: if a persisted overlay in /app/config/updates is
  newer than the image it tree-replaces /app/module and /app/dist and
  runs `uv sync` when the lockfile changed; if the image is newer it
  clears the stale overlay. Applied via the config volume, so updates
  survive container recreate; a newer pulled image always wins
- GET/POST /api/v1/update/{check,apply,rollback} (auth-gated, repo
  pinned to EstrellaXD/Auto_Bangumi): check queries GitHub releases
  with a stable/beta channel; apply downloads, verifies sha256, stages,
  promotes current->backup, then exits the process so Docker restarts
  into the overlay; rollback swaps the backup back. Progress streams
  over the existing SSE channel
- Settings card (Log page) shows current/latest, release notes, channel
  toggle, update + rollback, with a restart:unless-stopped requirement
  note; compose examples updated

Requires restart: unless-stopped for self-restart (documented; no
Docker-socket fallback). 1043 backend tests, mypy 0, ruff clean; 135
frontend tests, vue-tsc clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTnuEgR78cBk32bLNKar3z
2026-07-03 09:22:34 +02:00
Estrella Pan
93d3f1a947 feat(parser): multi-provider LLM parser with selectable fallback/primary mode
Generalizes the OpenAI-only title parser into a provider-dispatching
LLMParser (openai-compatible / Anthropic / Gemini) with a parse-mode
switch:

- fallback (default): regex parser runs first, LLM only rescues titles
  the regex can't handle — minimal LLM spend
- primary: LLM parses every title first, regex is the safety net so no
  title is dropped on LLM failure

The openai provider takes a base_url, covering any OpenAI-compatible
endpoint (DeepSeek, Ollama, LM Studio, OpenRouter, OneAPI). Anthropic
uses structured JSON output with refusal handling; Gemini uses
response_mime_type=application/json.

Config auto-migrates the legacy experimental_openai section into a new
llm section on load (migrated users get mode=primary, preserving their
original LLM-first behavior); the old section is kept for downgrade
safety and llm.api_key is redacted by the config API. Settings page
swaps the OpenAI panel for an LLM panel with provider + mode selects.

1018 backend tests, mypy 0, ruff clean; 131 frontend tests, vue-tsc clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTnuEgR78cBk32bLNKar3z
2026-07-03 08:51:01 +02:00
Estrella Pan
3baf1c72b3 refactor(webui): split oversized components, adopt naive-ui primitives, add SSE
Extract useBangumiRuleForm + shared preview/tag/rss-link/filter/offset
components out of ab-add-rss.vue and ab-edit-rule.vue, and split
calendar.vue into calendar-board/calendar-day-column/calendar-card/
calendar-mobile-list/calendar-rule-list-popup. Replace the bespoke
ab-button/ab-select/ab-switch/ab-checkbox primitives with themed
NButton/NSelect/NSwitch (naive-ui) across the app and delete the now-
unused wrappers. Add a backend SSE endpoint (GET /api/v1/events/stream)
streaming status/downloader/log ticks, and a shared useEventStream
composable that the status/downloader/log polling loops prefer over
their interval fetch, falling back to polling when SSE is unavailable
or the tab is hidden.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTnuEgR78cBk32bLNKar3z
2026-07-02 19:48:46 +02:00
Estrella Pan
b57d3c49ae feat(security): add security config UI and improve auth/MCP security
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 17:19:14 +01:00
Estrella Pan
a0bf878b7e feat(calendar): add drag-and-drop to assign unknown bangumi to weekdays
Allow users to drag bangumi cards from the "Unknown" section into weekday
columns in the calendar view. Manual assignments are locked so calendar
refresh from Bangumi.tv doesn't overwrite them. A reset button lets users
unlock and send cards back to Unknown.

Backend:
- Add weekday_locked field to Bangumi model (migration v9)
- Add PATCH /api/v1/bangumi/{id}/weekday endpoint
- Skip locked items in refresh_calendar()

Frontend:
- Add vuedraggable for smooth drag-and-drop
- Pin indicator and unpin button on manually-assigned cards
- Drop zone highlighting during drag
- i18n strings for drag/pin/unpin

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 14:21:00 +01:00
Estrella Pan
5f604e94fd perf(logging): optimize logging with rotation, async I/O, and lazy formatting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 14:10:56 +01:00