fix(db,onebot): drop legacy link_preview column and add napcat stream upload#74
Conversation
修复 Issue #58:旧库残留的 link_preview NOT NULL 列导致 /sub 创建订阅失败。 新增 V2 迁移在启动时安全删除该列(幂等)。 同时把 OneBot 的 prefer_local_video 配置替换为 napcat_stream_mode (disabled/fallback/always):媒体已统一预下载,发送统一使用本地文件; 新增 NapCat 流式上传模块,支持发送前预上传或失败后流式重试,避免大文件 OOM。 主动推送场景通过 platform_manager 解析 bot 客户端,旧 prefer_local_video 配置在启动自愈时迁移为对应 napcat_stream_mode。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
for more information, see https://pre-commit.ci
…tings FFmpegSettings was renamed to MediaRuntimeSettings in the napcat_stream refactor but the __init__.py re-exports in config/ and models/ still referenced the old name, causing ImportError at runtime. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Sorry @FlanChanXwO, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
* fix(media): harden predownload and config handling (#62) * fix(media): harden predownload and config handling * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * docs: refresh help image and project docs (#66) * docs: refresh help image and project docs * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * docs: update pull request template * ci: remove unused knowledgebase script --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * fix(media): improve media detection and platform sending (#64) * fix(media): improve media detection and platform sending * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix(media): address platform review feedback * fix(media): address review feedback * refactor(media): add table rendering pipeline --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * feat(pages): add push retry and data self-healing (#65) * feat(pages): add push retry and data self-healing * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix(pages): address web api review feedback * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * refactor(pages): split dashboard modules --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * fix(pages): improve multi-select filters (#68) * fix(pages): improve multi-select filters * fix(pages): address filter review comments * fix(telegram): proxy telegraph page creation (#67) * fix(kb): reconcile route knowledge manifest (#69) * fix(kb): reconcile route knowledge manifest * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix(kb): address manifest review feedback --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * fix(db,onebot): drop legacy link_preview column and add napcat stream upload (#74) * fix(db): drop legacy link_preview column and add napcat stream upload 修复 Issue #58:旧库残留的 link_preview NOT NULL 列导致 /sub 创建订阅失败。 新增 V2 迁移在启动时安全删除该列(幂等)。 同时把 OneBot 的 prefer_local_video 配置替换为 napcat_stream_mode (disabled/fallback/always):媒体已统一预下载,发送统一使用本地文件; 新增 NapCat 流式上传模块,支持发送前预上传或失败后流式重试,避免大文件 OOM。 主动推送场景通过 platform_manager 解析 bot 客户端,旧 prefer_local_video 配置在启动自愈时迁移为对应 napcat_stream_mode。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix(config): replace stale FFmpegSettings export with MediaRuntimeSettings FFmpegSettings was renamed to MediaRuntimeSettings in the napcat_stream refactor but the __init__.py re-exports in config/ and models/ still referenced the old name, causing ImportError at runtime. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * feat: 内置 Noto Sans SC 字体并移除硬编码系统字体路径 (#71) * feat: bundle Noto Sans SC font and remove hardcoded system font paths Ship NotoSansSC-subset.otf (~14 MB) in assets/fonts/ so the table image renderer works out-of-the-box with CJK text on any OS. Remove the six hardcoded absolute system font paths from _iter_font_candidates() to eliminate platform-dependent font probing. - Add assets/fonts/NotoSansSC-subset.otf (Noto Sans SC Subset Variable OTF) - Add assets/fonts/OFL.txt (SIL Open Font License 1.1) - Remove hardcoded macOS/Linux system font paths from table_image_renderer.py - Update font type annotations to support FreeTypeFont | ImageFont union - Add tests for bundled font discovery and system path verification - Update docs/project/formatting.md to reflect bundled font Font search priority: env vars > assets/fonts/ > Pillow default fallback * feat: runtime font download instead of bundled CJK font Replace the 14 MB bundled NotoSansSC-subset.otf with runtime download from jsDelivr CDN. Font is downloaded at plugin startup, verified with SHA256 + size checks, and atomically written to persistent data dir. When no CJK font is available (download failed or offline), tables fall back to plain text (A | B | C) instead of using Pillow default font, preventing broken CJK rendering. - Add font_manager.py with async download, double-check lock, atomic write - Remove ImageFont.load_default() fallback from table_image_renderer - Add _warned_no_font + early return None when no font - Add get_runtime_font_dir() to font candidate priority list - Wire await ensure_table_font() in bootstrap startup flow - Add 8 font_manager tests + update table_renderer tests - Fix conftest.py namespace package conflict across feature branches - Update formatting.md docs Closes #71 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * fix: 多群订阅同源查重 (#70) + 字体加载/配置/文档清理 (#75) * perf(rendering): 字体改为后台预取+按需加载,不再阻塞插件启动 启用表格转图时,字体下载在 bootstrap 中由同步 await 改为后台 asyncio.create_task 预取,首次表格渲染前再按需等待就绪(未配置下载的 环境直接回退纯文本,不发起网络请求)。同时补全缺失的表格转图总开关 EntryTextFormatter.configure_table_to_image,修复其原本不存在却被 bootstrap 调用导致的启动 AttributeError。 * refactor(interfaces): 移除无效的 GreedyStr 兼容层 astrbot_compat 命令处理器参数均带默认值(args: GreedyStr = ""),框架解析时只读默认值 不读注解,greedy 合并分支恒不触发——该注解功能上是惰性的,等价于 str。 故将注解统一改为 str,文件上传监听改用框架原生 filter.event_message_type(filter.EventMessageType.ALL),删除整个 astrbot_compat 兼容层并同步重写相关回归测试。 * refactor(config): telegraph_proxy 归位 Telegram 策略并接通 将 telegraph_proxy 从顶层 media 配置移入 telegram_strategy 模板,与 enable_telegraph/telegraph_token 同源。发送时由 TelegramSender 从策略 解析并传入 TelegraphClient(留空即直连,不再继承通用 HTTP 代理),修复 此前 _get_telegraph_proxy 定义却从未被调用的死配置问题,并移除相关死链路。 启动配置自愈新增迁移:旧 media.telegraph_proxy 会自动写入首个 telegram 策略模板项。 * docs: 整理 usage 文档与 README,精简 CHANGELOG 2.0.3 新增/完善 docs/usage 下的命令、配置、AI 工具、Plugin Pages 与兼容性文档; commands.md 补 sub_stop 的 rss_stop 别名并修正 rsshelp 别名,configuration.md 随 telegraph_proxy 归位 Telegram 策略同步调整。CHANGELOG 2.0.3 由 22 条精简为 面向用户的高层要点。一并清理已内置字体后多余的 assets/fonts/OFL.txt,并在 metadata.yaml 声明最低 AstrBot 版本 >=4.24.0。 * fix(messaging): 用 TYPE_CHECKING 块消除 __all__ 懒加载告警 messaging 包通过 __getattr__ 懒加载所有导出,导致 __all__ 中 31 个名字在 类型检查器看来「未定义」(reportUnsupportedDunderAll)。新增 if TYPE_CHECKING 块真实导入这些符号:类型检查时静态可见、告警清零,运行时该块不执行,原有 懒加载行为完全保留。 * fix(subscription): 查重按会话隔离,允许多群订阅同一源 (#70) 订阅查重原先用 (user_id, feed_id) 忽略 target_session,导致同一用户在不同 群聊订阅同一 Feed 被误判为「您已经订阅了此源」。将仓储方法 get_by_user_and_feed 收紧为 get_by_user_feed_session(user_id, feed_id, target_session),/sub 与导入两处查重均带上会话;同时避免多会话行存在时 scalar_one_or_none 误抛。新增回归测试覆盖「不同会话各自成功、相同会话仍拒绝」。 * docs(changelog): 2.0.3 恢复 Added/Changed/Removed/Fixed 分节结构 将此前压缩成扁平要点的 2.0.3 条目还原为 Keep a Changelog 分节格式,并把 「媒体缓存 GC / 完整性阈值 / 平台降级策略不再作为用户配置项暴露」从 Changed 提取为独立的 Removed 段。 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * docs(dev): 新增仓库体积上限与函数中文 docstring 规范 - maintenance.md 新增「仓库体积」章节:插件仓库总大小严格不得超过 16 MB, 大体积资源应按需下载而非纳入仓库。 - engineering-principles.md 代码组织新增:每个函数/方法必须有确切的中文 docstring,并与实现保持一致。 * fix: 缓存字体校验结果并防御订阅查重重复行 - font_manager: ensure_table_font_runtime 引入模块级缓存 _cached_verified_font + _verify_lock,首次校验后命中缓存,避免按 <table> 反复跑 15MB 全量 SHA256 阻塞事件循环;ensure_table_font 在校验/下载成功后同步更新缓存。 - subscription_repository_impl: get_by_user_feed_session 改用 limit(1) + scalars().first(),(user_id, feed_id, target_session) 无唯一约束时若存在 重复行不再抛 MultipleResultsFound。 - 补 font_manager 缓存回归测试与缓存重置 fixture。 --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(media): 实现媒体反代并修复下载崩溃,新增 GIF 大小限制压缩 反代(修复严重回归): - #74 给 base_sender 加了反代管线,把 image_relay_base_url/media_relay_base_url 传给 MediaDownloader.get_or_download(_prepared),但下载器从未接收这两个参数, 导致每次媒体下载 TypeError 被吞、download_failed=True,所有媒体静默发不出去。 - 在 get_or_download / get_or_download_prepared 加这两个参数并真正接通:新增 _build_relay_url(支持 https://wsrv.nl/ 与 https://wsrv.nl/?url= 两种形式)和 _select_relay_base(图片优先图片反代、非图片走通用反代),在抓取处"先反代再 回源"。缓存 key 与 original_url 始终保持原始 URL,m3u8 不走反代,两个反代为空 时行为与之前一致。 ffmpeg: - 新增 FFmpegTool.transcode_to_gif_under_limit,逐步降分辨率/帧率把视频转为不超过 指定字节数的 GIF;media_downloader 在生成 GIF 变体时调用。 测试: - 新增反代回归测试与 GIF 压缩测试;修正 test_base_sender_ffmpeg 中因 #74 起就过时 的预期 call dict(补 relay 键)。 * refactor(media): 按评审意见调整 relay 类型标注与 GIF 体积校验 - relay 辅助函数与下载器参数(_build_relay_url / _select_relay_base / image_relay_base_url / media_relay_base_url)类型标注改为 str | None, 如实反映内部 `or ""` 容忍 None 的用法。 - transcode_to_gif_under_limit 每次尝试只读取一次 output_path.stat().st_size (缓存快速路径与转码后校验各缓存一次),减少重复 syscall。
* fix(media): harden predownload and config handling (#62) * fix(media): harden predownload and config handling * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * docs: refresh help image and project docs (#66) * docs: refresh help image and project docs * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * docs: update pull request template * ci: remove unused knowledgebase script --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * fix(media): improve media detection and platform sending (#64) * fix(media): improve media detection and platform sending * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix(media): address platform review feedback * fix(media): address review feedback * refactor(media): add table rendering pipeline --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * feat(pages): add push retry and data self-healing (#65) * feat(pages): add push retry and data self-healing * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix(pages): address web api review feedback * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * refactor(pages): split dashboard modules --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * fix(pages): improve multi-select filters (#68) * fix(pages): improve multi-select filters * fix(pages): address filter review comments * fix(telegram): proxy telegraph page creation (#67) * fix(kb): reconcile route knowledge manifest (#69) * fix(kb): reconcile route knowledge manifest * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix(kb): address manifest review feedback --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * fix(db,onebot): drop legacy link_preview column and add napcat stream upload (#74) * fix(db): drop legacy link_preview column and add napcat stream upload 修复 Issue #58:旧库残留的 link_preview NOT NULL 列导致 /sub 创建订阅失败。 新增 V2 迁移在启动时安全删除该列(幂等)。 同时把 OneBot 的 prefer_local_video 配置替换为 napcat_stream_mode (disabled/fallback/always):媒体已统一预下载,发送统一使用本地文件; 新增 NapCat 流式上传模块,支持发送前预上传或失败后流式重试,避免大文件 OOM。 主动推送场景通过 platform_manager 解析 bot 客户端,旧 prefer_local_video 配置在启动自愈时迁移为对应 napcat_stream_mode。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix(config): replace stale FFmpegSettings export with MediaRuntimeSettings FFmpegSettings was renamed to MediaRuntimeSettings in the napcat_stream refactor but the __init__.py re-exports in config/ and models/ still referenced the old name, causing ImportError at runtime. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * feat: 内置 Noto Sans SC 字体并移除硬编码系统字体路径 (#71) * feat: bundle Noto Sans SC font and remove hardcoded system font paths Ship NotoSansSC-subset.otf (~14 MB) in assets/fonts/ so the table image renderer works out-of-the-box with CJK text on any OS. Remove the six hardcoded absolute system font paths from _iter_font_candidates() to eliminate platform-dependent font probing. - Add assets/fonts/NotoSansSC-subset.otf (Noto Sans SC Subset Variable OTF) - Add assets/fonts/OFL.txt (SIL Open Font License 1.1) - Remove hardcoded macOS/Linux system font paths from table_image_renderer.py - Update font type annotations to support FreeTypeFont | ImageFont union - Add tests for bundled font discovery and system path verification - Update docs/project/formatting.md to reflect bundled font Font search priority: env vars > assets/fonts/ > Pillow default fallback * feat: runtime font download instead of bundled CJK font Replace the 14 MB bundled NotoSansSC-subset.otf with runtime download from jsDelivr CDN. Font is downloaded at plugin startup, verified with SHA256 + size checks, and atomically written to persistent data dir. When no CJK font is available (download failed or offline), tables fall back to plain text (A | B | C) instead of using Pillow default font, preventing broken CJK rendering. - Add font_manager.py with async download, double-check lock, atomic write - Remove ImageFont.load_default() fallback from table_image_renderer - Add _warned_no_font + early return None when no font - Add get_runtime_font_dir() to font candidate priority list - Wire await ensure_table_font() in bootstrap startup flow - Add 8 font_manager tests + update table_renderer tests - Fix conftest.py namespace package conflict across feature branches - Update formatting.md docs Closes #71 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * fix: 多群订阅同源查重 (#70) + 字体加载/配置/文档清理 (#75) * perf(rendering): 字体改为后台预取+按需加载,不再阻塞插件启动 启用表格转图时,字体下载在 bootstrap 中由同步 await 改为后台 asyncio.create_task 预取,首次表格渲染前再按需等待就绪(未配置下载的 环境直接回退纯文本,不发起网络请求)。同时补全缺失的表格转图总开关 EntryTextFormatter.configure_table_to_image,修复其原本不存在却被 bootstrap 调用导致的启动 AttributeError。 * refactor(interfaces): 移除无效的 GreedyStr 兼容层 astrbot_compat 命令处理器参数均带默认值(args: GreedyStr = ""),框架解析时只读默认值 不读注解,greedy 合并分支恒不触发——该注解功能上是惰性的,等价于 str。 故将注解统一改为 str,文件上传监听改用框架原生 filter.event_message_type(filter.EventMessageType.ALL),删除整个 astrbot_compat 兼容层并同步重写相关回归测试。 * refactor(config): telegraph_proxy 归位 Telegram 策略并接通 将 telegraph_proxy 从顶层 media 配置移入 telegram_strategy 模板,与 enable_telegraph/telegraph_token 同源。发送时由 TelegramSender 从策略 解析并传入 TelegraphClient(留空即直连,不再继承通用 HTTP 代理),修复 此前 _get_telegraph_proxy 定义却从未被调用的死配置问题,并移除相关死链路。 启动配置自愈新增迁移:旧 media.telegraph_proxy 会自动写入首个 telegram 策略模板项。 * docs: 整理 usage 文档与 README,精简 CHANGELOG 2.0.3 新增/完善 docs/usage 下的命令、配置、AI 工具、Plugin Pages 与兼容性文档; commands.md 补 sub_stop 的 rss_stop 别名并修正 rsshelp 别名,configuration.md 随 telegraph_proxy 归位 Telegram 策略同步调整。CHANGELOG 2.0.3 由 22 条精简为 面向用户的高层要点。一并清理已内置字体后多余的 assets/fonts/OFL.txt,并在 metadata.yaml 声明最低 AstrBot 版本 >=4.24.0。 * fix(messaging): 用 TYPE_CHECKING 块消除 __all__ 懒加载告警 messaging 包通过 __getattr__ 懒加载所有导出,导致 __all__ 中 31 个名字在 类型检查器看来「未定义」(reportUnsupportedDunderAll)。新增 if TYPE_CHECKING 块真实导入这些符号:类型检查时静态可见、告警清零,运行时该块不执行,原有 懒加载行为完全保留。 * fix(subscription): 查重按会话隔离,允许多群订阅同一源 (#70) 订阅查重原先用 (user_id, feed_id) 忽略 target_session,导致同一用户在不同 群聊订阅同一 Feed 被误判为「您已经订阅了此源」。将仓储方法 get_by_user_and_feed 收紧为 get_by_user_feed_session(user_id, feed_id, target_session),/sub 与导入两处查重均带上会话;同时避免多会话行存在时 scalar_one_or_none 误抛。新增回归测试覆盖「不同会话各自成功、相同会话仍拒绝」。 * docs(changelog): 2.0.3 恢复 Added/Changed/Removed/Fixed 分节结构 将此前压缩成扁平要点的 2.0.3 条目还原为 Keep a Changelog 分节格式,并把 「媒体缓存 GC / 完整性阈值 / 平台降级策略不再作为用户配置项暴露」从 Changed 提取为独立的 Removed 段。 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * docs(dev): 新增仓库体积上限与函数中文 docstring 规范 - maintenance.md 新增「仓库体积」章节:插件仓库总大小严格不得超过 16 MB, 大体积资源应按需下载而非纳入仓库。 - engineering-principles.md 代码组织新增:每个函数/方法必须有确切的中文 docstring,并与实现保持一致。 * fix: 缓存字体校验结果并防御订阅查重重复行 - font_manager: ensure_table_font_runtime 引入模块级缓存 _cached_verified_font + _verify_lock,首次校验后命中缓存,避免按 <table> 反复跑 15MB 全量 SHA256 阻塞事件循环;ensure_table_font 在校验/下载成功后同步更新缓存。 - subscription_repository_impl: get_by_user_feed_session 改用 limit(1) + scalars().first(),(user_id, feed_id, target_session) 无唯一约束时若存在 重复行不再抛 MultipleResultsFound。 - 补 font_manager 缓存回归测试与缓存重置 fixture。 --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * fix(media): 实现媒体反代并修复下载崩溃,新增 GIF 大小限制压缩 (#77) * fix(media): 实现媒体反代并修复下载崩溃,新增 GIF 大小限制压缩 反代(修复严重回归): - #74 给 base_sender 加了反代管线,把 image_relay_base_url/media_relay_base_url 传给 MediaDownloader.get_or_download(_prepared),但下载器从未接收这两个参数, 导致每次媒体下载 TypeError 被吞、download_failed=True,所有媒体静默发不出去。 - 在 get_or_download / get_or_download_prepared 加这两个参数并真正接通:新增 _build_relay_url(支持 https://wsrv.nl/ 与 https://wsrv.nl/?url= 两种形式)和 _select_relay_base(图片优先图片反代、非图片走通用反代),在抓取处"先反代再 回源"。缓存 key 与 original_url 始终保持原始 URL,m3u8 不走反代,两个反代为空 时行为与之前一致。 ffmpeg: - 新增 FFmpegTool.transcode_to_gif_under_limit,逐步降分辨率/帧率把视频转为不超过 指定字节数的 GIF;media_downloader 在生成 GIF 变体时调用。 测试: - 新增反代回归测试与 GIF 压缩测试;修正 test_base_sender_ffmpeg 中因 #74 起就过时 的预期 call dict(补 relay 键)。 * refactor(media): 按评审意见调整 relay 类型标注与 GIF 体积校验 - relay 辅助函数与下载器参数(_build_relay_url / _select_relay_base / image_relay_base_url / media_relay_base_url)类型标注改为 str | None, 如实反映内部 `or ""` 容忍 None 的用法。 - transcode_to_gif_under_limit 每次尝试只读取一次 output_path.stat().st_size (缓存快速路径与转码后校验各缓存一次),减少重复 syscall。 * docs: 帮助图 slogan、README 命令一览、KB 命令别名,并补 2.0.3 反代变更 (#78) - 帮助图副标题改为 "Everything is RSSable",并重新生成 light/dark 帮助图。 - README 顶部加 Last Commit 徽章,新增「命令一览」段贴帮助图。 - docs/usage/commands.md 补全 RSSHub Routes 知识库命令的中文别名。 - CHANGELOG 2.0.3 补记媒体反代(image/media relay,先反代再回源、缓存键保持原始 URL)与无声视频转 GIF 体积上限压缩,刷新发版内容。 * feat: prepare 2.1.0 RSSHub plugin updates (#82) * refactor: use system ffmpeg for media processing * feat(feed): support standard JSON Feed * fix(feed): keep unacknowledged polling entries retryable * fix(dispatch): audit skipped entries and preserve original layout semantics * fix(media): make FFmpeg bundling explicit and improve GIF detection * refactor(llmtools): split agent tools by responsibility * feat(pages): add dashboard overview charts * chore(deps): split runtime and dev requirements * docs: add 2.1.0 changelog * test: remove unused command handler import binding * feat(2.1.0): FFmpeg/KB 镜像测速、JSON Feed 解析与 code review 加固 - FFmpeg `auto` 镜像默认并发测速候选(内置 mirror + direct GitHub + custom_url), winner 不是直连时保留 direct 兜底;按真实候选数算 max_attempts/total_budget, 避免 user 的 media_timeout 被砍到 60s。 - 共享 mirror_helper:knowledge 同步新增 speed_test 模式(schema 默认), 与 FFmpeg 下载共享同一套镜像前缀与测速逻辑。 - ffmpeg_bundler 安全/可用性加固:恢复 TLS 证书验证、_cleanup_stale_tmp_dirs 挪进 _download_lock 内消除 race、prefetch 后台任务用模块级 set 保留强引用、 zip 解包前拒绝绝对路径/`..`/Windows 驱动器前缀。 - route_source.speed_test 在 bootstrap 关键路径上加 try/except + GitHub fallback, 避免离线启动 / 测速异常阻塞插件加载。 - RouteKnowledgeSettings.source_mode 默认值在 dataclass / settings_builder / Pydantic Field 三处对齐到 `speed_test`。 - 测试:_build_dependencies 改 AsyncMock;conf_schema / heal / verify_checksum 断言对齐新默认与新 contract。 * Revert "feat(2.1.0): FFmpeg/KB 镜像测速、JSON Feed 解析与 code review 加固" This reverts commit b9e3800. * chore: 清理遗留测试失败与修复 main.py 初始化 (#85) * feat(2.1.0): FFmpeg/KB 镜像测速、JSON Feed 解析与 code review 加固 - FFmpeg `auto` 镜像默认并发测速候选(内置 mirror + direct GitHub + custom_url), winner 不是直连时保留 direct 兜底;按真实候选数算 max_attempts/total_budget, 避免 user 的 media_timeout 被砍到 60s。 - 共享 mirror_helper:knowledge 同步新增 speed_test 模式(schema 默认), 与 FFmpeg 下载共享同一套镜像前缀与测速逻辑。 - ffmpeg_bundler 安全/可用性加固:恢复 TLS 证书验证、_cleanup_stale_tmp_dirs 挪进 _download_lock 内消除 race、prefetch 后台任务用模块级 set 保留强引用、 zip 解包前拒绝绝对路径/`..`/Windows 驱动器前缀。 - route_source.speed_test 在 bootstrap 关键路径上加 try/except + GitHub fallback, 避免离线启动 / 测速异常阻塞插件加载。 - RouteKnowledgeSettings.source_mode 默认值在 dataclass / settings_builder / Pydantic Field 三处对齐到 `speed_test`。 - 测试:_build_dependencies 改 AsyncMock;conf_schema / heal / verify_checksum 断言对齐新默认与新 contract。 * refactor: address PR #84 review comments - mirror_helper: 把 ClientSession 抽到 speed_test_mirrors 函数级,所有 _probe 共用一个 session/connector,省连接和 SSL 握手开销。 - ffmpeg_bundler._cleanup_stale_tmp_dirs: 改用 shutil.rmtree() 不吞 OSError, 显式 except 后写 debug 日志,方便排查 FS 权限/锁问题。 - ffmpeg_bundler 的 auto 镜像分支: speed_test_mirrors 抛异常时退回直连 GitHub,与 route_source.speed_test 的 try/except 兜底保持一致。 * fix(config): align media.* persistence with schema; drop legacy migrations v2.1.0 起 `_conf_schema.json` 中 `media.*` 整组(gif_transcode、video_transcode、 relay、ffmpeg_* 等 11 字段)从未被 runtime 读到,settings.media.gif_transcode 永远是默认 False。根因是 Pydantic 层 (RsshubPluginConfig) 与 schema 错位: - from_astrbot_config 读 "media_config" 而 schema/JSON 用 "media" - MediaConfig 只定义 download_media_timeout,11 个真实字段散落在 FFmpegConfig - build_application_settings 经 _get_value(config, "media") 拿到 None - _get_value 对 None 源静默返回 default,bug 隐藏至今 修复 + 轻量重构: * Pydantic 模型对齐 schema 1:1 - MediaConfig 扩为 11 字段镜像 schema - 删除 FFmpegConfig(已合并入 MediaConfig) - RsshubPluginConfig.media_config → media;删除 ffmpeg 字段 - from_astrbot_config / to_astrbot_config 适配 * 删除所有 legacy 迁移路径(无现存用户使用) - 删除 apply_legacy_config_aliases 函数体 - schema_healer 不再调用 legacy alias,仅做 schema 投影 * settings_builder 简化 - 删除 _merge_legacy_ffmpeg_media - 新增 _propagate_fields 反射 helper - MediaSettings 构造改 spread + 显式 clamp/strip - HttpSettings.media_timeout 去掉无用 fallback 链 * 运行时 DTO 改名 + 去默认值 - MediaRuntimeSettings → MediaPlatformLimits(语义:平台软上限常量) - ApplicationSettings.media_config → media_platform_limits - MediaSettings 字段全部要求构造时必传 * bootstrap 启动日志 - _configure_message_senders 末尾打印关键 behavior flags 便于排障"配置没生效" * 测试 - 新增 tests/unit/infrastructure/test_config_round_trip.py: 用真实 runtime JSON 锁死 round-trip,封死本 bug 复发 - 更新 4 处现有 fixture 适配重命名与必填字段 验证:settings.media.gif_transcode 由永远 False 修复为正确反映 JSON 真值 * chore(tests): fix caching import shadowing (4 tests) Issue B: Python resolves 'caching' to the caching.py submodule instead of the function from __getattr__. Added eager from-import of the function so it shadows the submodule in the package namespace. Test: pytest tests/unit/utils/test_caching.py -v → 15 passed * chore(tests): skip route-knowledge-generator test when CI script absent Issue C: KB generator script lives in external repo (FlanChanXwO/rsshub-routes-knowledgebase). Add @pytest.mark.skipif so the test is skipped locally and only runs when the script is vendored. Test: pytest tests/unit/test_route_knowledge_generator.py -v → 1 skipped * chore(tests): remove dead Extension/PluginManager exports from _EXPORT_MAP Issue A1: Removed 4 _EXPORT_MAP entries pointing to symbols that no longer exist in messaging module (Extension, PluginManager, get_plugin_manager, on_event). These classes/helpers were removed from the source in a prior cleanup. Test: pytest tests/unit/test_compat_exports.py -v → 2 passed * chore(tests): remove dead TestExtension and TestPluginManager test classes Issue A2: Extension and PluginManager classes were removed from the source long ago. Deleted their corresponding test classes but kept TestEventBus (5 tests remain, all passing). Test: pytest tests/unit/infrastructure/test_event_system.py -v → 5 passed * chore(tests): register main.py in conftest and fix test mocks Issue A3: Two-part fix: 1. Added main.py module registration to conftest (with try/except around exec_module, as main.py may import astrbot symbols not yet mocked). Also added PLUGIN_DIR to the package's submodule_search_locations so Python can resolve 'astrbot_plugin_rsshub.main' through the normal import mechanism after tests delete/re-import it. 2. Fixed two test FakeFilter classes that were missing EventMessageType attribute (added by a newer version of main.py). Also mocked ApplicationSettings in test_rsshelp_returns_image_chain since it now requires MediaSettings with 11 required fields. Test: pytest tests/unit/test_command_handlers_regression.py -v → 13 passed * fix(main): defer ApplicationSettings instantiation to initialize() commit fd186d1 移除了 MediaSettings 的默认值,要求 11 个字段必传。 main.py:38 在 __init__ 中直接 ApplicationSettings() 无参构造作为临时 占位,触发 TypeError。 真正的 settings 由 initialize() 中的 create_plugin_runtime() → build_application_settings() 正确构造并在 line 57 赋值。 修复:__init__ 中先设为 None,延迟到 initialize() 再赋值。 无其他代码在 initialize() 前访问 _app_settings,改动安全。 验证:scripts/astrbot/reload-plugins.sh 6196 astrbot_plugin_rsshub 返回 {"status":"ok","message":"重载成功。"} * fix: address PR #85 code review findings 1. ApplicationSettings.media 改为必传字段,防御无参构造导致的 TypeError。 - 移除 default_factory=MediaSettings(会触发 11 个必传参数错误) - 将 media 字段移到 dataclass 最前面(dataclass 要求无默认值字段在前) - build_application_settings() 用 keyword args 构造,顺序变化无影响 2. test_config_round_trip.py 移除硬编码本地路径,改用相对路径查找 + skipif。 - _find_runtime_config() 从插件根向上查找 data/config/ 目录 - 找不到时 pytest.skip,CI 和其他环境不会失败 - 开发环境能自动定位到实际 runtime config 3. goal-1/plan.md typo 修正:"目票" → "目标" 验证:pytest tests/unit/ -v → 620 passed, 1 skipped * feat(2.1.0): 镜像测速、JSON Feed 解析与 code review 加固 (#84) * feat(2.1.0): FFmpeg/KB 镜像测速、JSON Feed 解析与 code review 加固 - FFmpeg `auto` 镜像默认并发测速候选(内置 mirror + direct GitHub + custom_url), winner 不是直连时保留 direct 兜底;按真实候选数算 max_attempts/total_budget, 避免 user 的 media_timeout 被砍到 60s。 - 共享 mirror_helper:knowledge 同步新增 speed_test 模式(schema 默认), 与 FFmpeg 下载共享同一套镜像前缀与测速逻辑。 - ffmpeg_bundler 安全/可用性加固:恢复 TLS 证书验证、_cleanup_stale_tmp_dirs 挪进 _download_lock 内消除 race、prefetch 后台任务用模块级 set 保留强引用、 zip 解包前拒绝绝对路径/`..`/Windows 驱动器前缀。 - route_source.speed_test 在 bootstrap 关键路径上加 try/except + GitHub fallback, 避免离线启动 / 测速异常阻塞插件加载。 - RouteKnowledgeSettings.source_mode 默认值在 dataclass / settings_builder / Pydantic Field 三处对齐到 `speed_test`。 - 测试:_build_dependencies 改 AsyncMock;conf_schema / heal / verify_checksum 断言对齐新默认与新 contract。 * refactor: address PR #84 review comments - mirror_helper: 把 ClientSession 抽到 speed_test_mirrors 函数级,所有 _probe 共用一个 session/connector,省连接和 SSL 握手开销。 - ffmpeg_bundler._cleanup_stale_tmp_dirs: 改用 shutil.rmtree() 不吞 OSError, 显式 except 后写 debug 日志,方便排查 FS 权限/锁问题。 - ffmpeg_bundler 的 auto 镜像分支: speed_test_mirrors 抛异常时退回直连 GitHub,与 route_source.speed_test 的 try/except 兜底保持一致。 * fix: improve overview success chart (#86) * fix: improve overview success chart * fix: clarify push success chart helpers * fix: remove redundant filter search buttons (#87) * docs: update v2.1.0 changelog --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(media): harden predownload and config handling (#62) * fix(media): harden predownload and config handling * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * docs: refresh help image and project docs (#66) * docs: refresh help image and project docs * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * docs: update pull request template * ci: remove unused knowledgebase script --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * fix(media): improve media detection and platform sending (#64) * fix(media): improve media detection and platform sending * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix(media): address platform review feedback * fix(media): address review feedback * refactor(media): add table rendering pipeline --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * feat(pages): add push retry and data self-healing (#65) * feat(pages): add push retry and data self-healing * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix(pages): address web api review feedback * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * refactor(pages): split dashboard modules --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * fix(pages): improve multi-select filters (#68) * fix(pages): improve multi-select filters * fix(pages): address filter review comments * fix(telegram): proxy telegraph page creation (#67) * fix(kb): reconcile route knowledge manifest (#69) * fix(kb): reconcile route knowledge manifest * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix(kb): address manifest review feedback --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * fix(db,onebot): drop legacy link_preview column and add napcat stream upload (#74) * fix(db): drop legacy link_preview column and add napcat stream upload 修复 Issue #58:旧库残留的 link_preview NOT NULL 列导致 /sub 创建订阅失败。 新增 V2 迁移在启动时安全删除该列(幂等)。 同时把 OneBot 的 prefer_local_video 配置替换为 napcat_stream_mode (disabled/fallback/always):媒体已统一预下载,发送统一使用本地文件; 新增 NapCat 流式上传模块,支持发送前预上传或失败后流式重试,避免大文件 OOM。 主动推送场景通过 platform_manager 解析 bot 客户端,旧 prefer_local_video 配置在启动自愈时迁移为对应 napcat_stream_mode。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix(config): replace stale FFmpegSettings export with MediaRuntimeSettings FFmpegSettings was renamed to MediaRuntimeSettings in the napcat_stream refactor but the __init__.py re-exports in config/ and models/ still referenced the old name, causing ImportError at runtime. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * feat: 内置 Noto Sans SC 字体并移除硬编码系统字体路径 (#71) * feat: bundle Noto Sans SC font and remove hardcoded system font paths Ship NotoSansSC-subset.otf (~14 MB) in assets/fonts/ so the table image renderer works out-of-the-box with CJK text on any OS. Remove the six hardcoded absolute system font paths from _iter_font_candidates() to eliminate platform-dependent font probing. - Add assets/fonts/NotoSansSC-subset.otf (Noto Sans SC Subset Variable OTF) - Add assets/fonts/OFL.txt (SIL Open Font License 1.1) - Remove hardcoded macOS/Linux system font paths from table_image_renderer.py - Update font type annotations to support FreeTypeFont | ImageFont union - Add tests for bundled font discovery and system path verification - Update docs/project/formatting.md to reflect bundled font Font search priority: env vars > assets/fonts/ > Pillow default fallback * feat: runtime font download instead of bundled CJK font Replace the 14 MB bundled NotoSansSC-subset.otf with runtime download from jsDelivr CDN. Font is downloaded at plugin startup, verified with SHA256 + size checks, and atomically written to persistent data dir. When no CJK font is available (download failed or offline), tables fall back to plain text (A | B | C) instead of using Pillow default font, preventing broken CJK rendering. - Add font_manager.py with async download, double-check lock, atomic write - Remove ImageFont.load_default() fallback from table_image_renderer - Add _warned_no_font + early return None when no font - Add get_runtime_font_dir() to font candidate priority list - Wire await ensure_table_font() in bootstrap startup flow - Add 8 font_manager tests + update table_renderer tests - Fix conftest.py namespace package conflict across feature branches - Update formatting.md docs Closes #71 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * fix: 多群订阅同源查重 (#70) + 字体加载/配置/文档清理 (#75) * perf(rendering): 字体改为后台预取+按需加载,不再阻塞插件启动 启用表格转图时,字体下载在 bootstrap 中由同步 await 改为后台 asyncio.create_task 预取,首次表格渲染前再按需等待就绪(未配置下载的 环境直接回退纯文本,不发起网络请求)。同时补全缺失的表格转图总开关 EntryTextFormatter.configure_table_to_image,修复其原本不存在却被 bootstrap 调用导致的启动 AttributeError。 * refactor(interfaces): 移除无效的 GreedyStr 兼容层 astrbot_compat 命令处理器参数均带默认值(args: GreedyStr = ""),框架解析时只读默认值 不读注解,greedy 合并分支恒不触发——该注解功能上是惰性的,等价于 str。 故将注解统一改为 str,文件上传监听改用框架原生 filter.event_message_type(filter.EventMessageType.ALL),删除整个 astrbot_compat 兼容层并同步重写相关回归测试。 * refactor(config): telegraph_proxy 归位 Telegram 策略并接通 将 telegraph_proxy 从顶层 media 配置移入 telegram_strategy 模板,与 enable_telegraph/telegraph_token 同源。发送时由 TelegramSender 从策略 解析并传入 TelegraphClient(留空即直连,不再继承通用 HTTP 代理),修复 此前 _get_telegraph_proxy 定义却从未被调用的死配置问题,并移除相关死链路。 启动配置自愈新增迁移:旧 media.telegraph_proxy 会自动写入首个 telegram 策略模板项。 * docs: 整理 usage 文档与 README,精简 CHANGELOG 2.0.3 新增/完善 docs/usage 下的命令、配置、AI 工具、Plugin Pages 与兼容性文档; commands.md 补 sub_stop 的 rss_stop 别名并修正 rsshelp 别名,configuration.md 随 telegraph_proxy 归位 Telegram 策略同步调整。CHANGELOG 2.0.3 由 22 条精简为 面向用户的高层要点。一并清理已内置字体后多余的 assets/fonts/OFL.txt,并在 metadata.yaml 声明最低 AstrBot 版本 >=4.24.0。 * fix(messaging): 用 TYPE_CHECKING 块消除 __all__ 懒加载告警 messaging 包通过 __getattr__ 懒加载所有导出,导致 __all__ 中 31 个名字在 类型检查器看来「未定义」(reportUnsupportedDunderAll)。新增 if TYPE_CHECKING 块真实导入这些符号:类型检查时静态可见、告警清零,运行时该块不执行,原有 懒加载行为完全保留。 * fix(subscription): 查重按会话隔离,允许多群订阅同一源 (#70) 订阅查重原先用 (user_id, feed_id) 忽略 target_session,导致同一用户在不同 群聊订阅同一 Feed 被误判为「您已经订阅了此源」。将仓储方法 get_by_user_and_feed 收紧为 get_by_user_feed_session(user_id, feed_id, target_session),/sub 与导入两处查重均带上会话;同时避免多会话行存在时 scalar_one_or_none 误抛。新增回归测试覆盖「不同会话各自成功、相同会话仍拒绝」。 * docs(changelog): 2.0.3 恢复 Added/Changed/Removed/Fixed 分节结构 将此前压缩成扁平要点的 2.0.3 条目还原为 Keep a Changelog 分节格式,并把 「媒体缓存 GC / 完整性阈值 / 平台降级策略不再作为用户配置项暴露」从 Changed 提取为独立的 Removed 段。 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * docs(dev): 新增仓库体积上限与函数中文 docstring 规范 - maintenance.md 新增「仓库体积」章节:插件仓库总大小严格不得超过 16 MB, 大体积资源应按需下载而非纳入仓库。 - engineering-principles.md 代码组织新增:每个函数/方法必须有确切的中文 docstring,并与实现保持一致。 * fix: 缓存字体校验结果并防御订阅查重重复行 - font_manager: ensure_table_font_runtime 引入模块级缓存 _cached_verified_font + _verify_lock,首次校验后命中缓存,避免按 <table> 反复跑 15MB 全量 SHA256 阻塞事件循环;ensure_table_font 在校验/下载成功后同步更新缓存。 - subscription_repository_impl: get_by_user_feed_session 改用 limit(1) + scalars().first(),(user_id, feed_id, target_session) 无唯一约束时若存在 重复行不再抛 MultipleResultsFound。 - 补 font_manager 缓存回归测试与缓存重置 fixture。 --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * fix(media): 实现媒体反代并修复下载崩溃,新增 GIF 大小限制压缩 (#77) * fix(media): 实现媒体反代并修复下载崩溃,新增 GIF 大小限制压缩 反代(修复严重回归): - #74 给 base_sender 加了反代管线,把 image_relay_base_url/media_relay_base_url 传给 MediaDownloader.get_or_download(_prepared),但下载器从未接收这两个参数, 导致每次媒体下载 TypeError 被吞、download_failed=True,所有媒体静默发不出去。 - 在 get_or_download / get_or_download_prepared 加这两个参数并真正接通:新增 _build_relay_url(支持 https://wsrv.nl/ 与 https://wsrv.nl/?url= 两种形式)和 _select_relay_base(图片优先图片反代、非图片走通用反代),在抓取处"先反代再 回源"。缓存 key 与 original_url 始终保持原始 URL,m3u8 不走反代,两个反代为空 时行为与之前一致。 ffmpeg: - 新增 FFmpegTool.transcode_to_gif_under_limit,逐步降分辨率/帧率把视频转为不超过 指定字节数的 GIF;media_downloader 在生成 GIF 变体时调用。 测试: - 新增反代回归测试与 GIF 压缩测试;修正 test_base_sender_ffmpeg 中因 #74 起就过时 的预期 call dict(补 relay 键)。 * refactor(media): 按评审意见调整 relay 类型标注与 GIF 体积校验 - relay 辅助函数与下载器参数(_build_relay_url / _select_relay_base / image_relay_base_url / media_relay_base_url)类型标注改为 str | None, 如实反映内部 `or ""` 容忍 None 的用法。 - transcode_to_gif_under_limit 每次尝试只读取一次 output_path.stat().st_size (缓存快速路径与转码后校验各缓存一次),减少重复 syscall。 * docs: 帮助图 slogan、README 命令一览、KB 命令别名,并补 2.0.3 反代变更 (#78) - 帮助图副标题改为 "Everything is RSSable",并重新生成 light/dark 帮助图。 - README 顶部加 Last Commit 徽章,新增「命令一览」段贴帮助图。 - docs/usage/commands.md 补全 RSSHub Routes 知识库命令的中文别名。 - CHANGELOG 2.0.3 补记媒体反代(image/media relay,先反代再回源、缓存键保持原始 URL)与无声视频转 GIF 体积上限压缩,刷新发版内容。 * feat: prepare 2.1.0 RSSHub plugin updates (#82) * refactor: use system ffmpeg for media processing * feat(feed): support standard JSON Feed * fix(feed): keep unacknowledged polling entries retryable * fix(dispatch): audit skipped entries and preserve original layout semantics * fix(media): make FFmpeg bundling explicit and improve GIF detection * refactor(llmtools): split agent tools by responsibility * feat(pages): add dashboard overview charts * chore(deps): split runtime and dev requirements * docs: add 2.1.0 changelog * test: remove unused command handler import binding * feat(2.1.0): FFmpeg/KB 镜像测速、JSON Feed 解析与 code review 加固 - FFmpeg `auto` 镜像默认并发测速候选(内置 mirror + direct GitHub + custom_url), winner 不是直连时保留 direct 兜底;按真实候选数算 max_attempts/total_budget, 避免 user 的 media_timeout 被砍到 60s。 - 共享 mirror_helper:knowledge 同步新增 speed_test 模式(schema 默认), 与 FFmpeg 下载共享同一套镜像前缀与测速逻辑。 - ffmpeg_bundler 安全/可用性加固:恢复 TLS 证书验证、_cleanup_stale_tmp_dirs 挪进 _download_lock 内消除 race、prefetch 后台任务用模块级 set 保留强引用、 zip 解包前拒绝绝对路径/`..`/Windows 驱动器前缀。 - route_source.speed_test 在 bootstrap 关键路径上加 try/except + GitHub fallback, 避免离线启动 / 测速异常阻塞插件加载。 - RouteKnowledgeSettings.source_mode 默认值在 dataclass / settings_builder / Pydantic Field 三处对齐到 `speed_test`。 - 测试:_build_dependencies 改 AsyncMock;conf_schema / heal / verify_checksum 断言对齐新默认与新 contract。 * Revert "feat(2.1.0): FFmpeg/KB 镜像测速、JSON Feed 解析与 code review 加固" This reverts commit b9e3800. * chore: 清理遗留测试失败与修复 main.py 初始化 (#85) * feat(2.1.0): FFmpeg/KB 镜像测速、JSON Feed 解析与 code review 加固 - FFmpeg `auto` 镜像默认并发测速候选(内置 mirror + direct GitHub + custom_url), winner 不是直连时保留 direct 兜底;按真实候选数算 max_attempts/total_budget, 避免 user 的 media_timeout 被砍到 60s。 - 共享 mirror_helper:knowledge 同步新增 speed_test 模式(schema 默认), 与 FFmpeg 下载共享同一套镜像前缀与测速逻辑。 - ffmpeg_bundler 安全/可用性加固:恢复 TLS 证书验证、_cleanup_stale_tmp_dirs 挪进 _download_lock 内消除 race、prefetch 后台任务用模块级 set 保留强引用、 zip 解包前拒绝绝对路径/`..`/Windows 驱动器前缀。 - route_source.speed_test 在 bootstrap 关键路径上加 try/except + GitHub fallback, 避免离线启动 / 测速异常阻塞插件加载。 - RouteKnowledgeSettings.source_mode 默认值在 dataclass / settings_builder / Pydantic Field 三处对齐到 `speed_test`。 - 测试:_build_dependencies 改 AsyncMock;conf_schema / heal / verify_checksum 断言对齐新默认与新 contract。 * refactor: address PR #84 review comments - mirror_helper: 把 ClientSession 抽到 speed_test_mirrors 函数级,所有 _probe 共用一个 session/connector,省连接和 SSL 握手开销。 - ffmpeg_bundler._cleanup_stale_tmp_dirs: 改用 shutil.rmtree() 不吞 OSError, 显式 except 后写 debug 日志,方便排查 FS 权限/锁问题。 - ffmpeg_bundler 的 auto 镜像分支: speed_test_mirrors 抛异常时退回直连 GitHub,与 route_source.speed_test 的 try/except 兜底保持一致。 * fix(config): align media.* persistence with schema; drop legacy migrations v2.1.0 起 `_conf_schema.json` 中 `media.*` 整组(gif_transcode、video_transcode、 relay、ffmpeg_* 等 11 字段)从未被 runtime 读到,settings.media.gif_transcode 永远是默认 False。根因是 Pydantic 层 (RsshubPluginConfig) 与 schema 错位: - from_astrbot_config 读 "media_config" 而 schema/JSON 用 "media" - MediaConfig 只定义 download_media_timeout,11 个真实字段散落在 FFmpegConfig - build_application_settings 经 _get_value(config, "media") 拿到 None - _get_value 对 None 源静默返回 default,bug 隐藏至今 修复 + 轻量重构: * Pydantic 模型对齐 schema 1:1 - MediaConfig 扩为 11 字段镜像 schema - 删除 FFmpegConfig(已合并入 MediaConfig) - RsshubPluginConfig.media_config → media;删除 ffmpeg 字段 - from_astrbot_config / to_astrbot_config 适配 * 删除所有 legacy 迁移路径(无现存用户使用) - 删除 apply_legacy_config_aliases 函数体 - schema_healer 不再调用 legacy alias,仅做 schema 投影 * settings_builder 简化 - 删除 _merge_legacy_ffmpeg_media - 新增 _propagate_fields 反射 helper - MediaSettings 构造改 spread + 显式 clamp/strip - HttpSettings.media_timeout 去掉无用 fallback 链 * 运行时 DTO 改名 + 去默认值 - MediaRuntimeSettings → MediaPlatformLimits(语义:平台软上限常量) - ApplicationSettings.media_config → media_platform_limits - MediaSettings 字段全部要求构造时必传 * bootstrap 启动日志 - _configure_message_senders 末尾打印关键 behavior flags 便于排障"配置没生效" * 测试 - 新增 tests/unit/infrastructure/test_config_round_trip.py: 用真实 runtime JSON 锁死 round-trip,封死本 bug 复发 - 更新 4 处现有 fixture 适配重命名与必填字段 验证:settings.media.gif_transcode 由永远 False 修复为正确反映 JSON 真值 * chore(tests): fix caching import shadowing (4 tests) Issue B: Python resolves 'caching' to the caching.py submodule instead of the function from __getattr__. Added eager from-import of the function so it shadows the submodule in the package namespace. Test: pytest tests/unit/utils/test_caching.py -v → 15 passed * chore(tests): skip route-knowledge-generator test when CI script absent Issue C: KB generator script lives in external repo (FlanChanXwO/rsshub-routes-knowledgebase). Add @pytest.mark.skipif so the test is skipped locally and only runs when the script is vendored. Test: pytest tests/unit/test_route_knowledge_generator.py -v → 1 skipped * chore(tests): remove dead Extension/PluginManager exports from _EXPORT_MAP Issue A1: Removed 4 _EXPORT_MAP entries pointing to symbols that no longer exist in messaging module (Extension, PluginManager, get_plugin_manager, on_event). These classes/helpers were removed from the source in a prior cleanup. Test: pytest tests/unit/test_compat_exports.py -v → 2 passed * chore(tests): remove dead TestExtension and TestPluginManager test classes Issue A2: Extension and PluginManager classes were removed from the source long ago. Deleted their corresponding test classes but kept TestEventBus (5 tests remain, all passing). Test: pytest tests/unit/infrastructure/test_event_system.py -v → 5 passed * chore(tests): register main.py in conftest and fix test mocks Issue A3: Two-part fix: 1. Added main.py module registration to conftest (with try/except around exec_module, as main.py may import astrbot symbols not yet mocked). Also added PLUGIN_DIR to the package's submodule_search_locations so Python can resolve 'astrbot_plugin_rsshub.main' through the normal import mechanism after tests delete/re-import it. 2. Fixed two test FakeFilter classes that were missing EventMessageType attribute (added by a newer version of main.py). Also mocked ApplicationSettings in test_rsshelp_returns_image_chain since it now requires MediaSettings with 11 required fields. Test: pytest tests/unit/test_command_handlers_regression.py -v → 13 passed * fix(main): defer ApplicationSettings instantiation to initialize() commit fd186d1 移除了 MediaSettings 的默认值,要求 11 个字段必传。 main.py:38 在 __init__ 中直接 ApplicationSettings() 无参构造作为临时 占位,触发 TypeError。 真正的 settings 由 initialize() 中的 create_plugin_runtime() → build_application_settings() 正确构造并在 line 57 赋值。 修复:__init__ 中先设为 None,延迟到 initialize() 再赋值。 无其他代码在 initialize() 前访问 _app_settings,改动安全。 验证:scripts/astrbot/reload-plugins.sh 6196 astrbot_plugin_rsshub 返回 {"status":"ok","message":"重载成功。"} * fix: address PR #85 code review findings 1. ApplicationSettings.media 改为必传字段,防御无参构造导致的 TypeError。 - 移除 default_factory=MediaSettings(会触发 11 个必传参数错误) - 将 media 字段移到 dataclass 最前面(dataclass 要求无默认值字段在前) - build_application_settings() 用 keyword args 构造,顺序变化无影响 2. test_config_round_trip.py 移除硬编码本地路径,改用相对路径查找 + skipif。 - _find_runtime_config() 从插件根向上查找 data/config/ 目录 - 找不到时 pytest.skip,CI 和其他环境不会失败 - 开发环境能自动定位到实际 runtime config 3. goal-1/plan.md typo 修正:"目票" → "目标" 验证:pytest tests/unit/ -v → 620 passed, 1 skipped * feat(2.1.0): 镜像测速、JSON Feed 解析与 code review 加固 (#84) * feat(2.1.0): FFmpeg/KB 镜像测速、JSON Feed 解析与 code review 加固 - FFmpeg `auto` 镜像默认并发测速候选(内置 mirror + direct GitHub + custom_url), winner 不是直连时保留 direct 兜底;按真实候选数算 max_attempts/total_budget, 避免 user 的 media_timeout 被砍到 60s。 - 共享 mirror_helper:knowledge 同步新增 speed_test 模式(schema 默认), 与 FFmpeg 下载共享同一套镜像前缀与测速逻辑。 - ffmpeg_bundler 安全/可用性加固:恢复 TLS 证书验证、_cleanup_stale_tmp_dirs 挪进 _download_lock 内消除 race、prefetch 后台任务用模块级 set 保留强引用、 zip 解包前拒绝绝对路径/`..`/Windows 驱动器前缀。 - route_source.speed_test 在 bootstrap 关键路径上加 try/except + GitHub fallback, 避免离线启动 / 测速异常阻塞插件加载。 - RouteKnowledgeSettings.source_mode 默认值在 dataclass / settings_builder / Pydantic Field 三处对齐到 `speed_test`。 - 测试:_build_dependencies 改 AsyncMock;conf_schema / heal / verify_checksum 断言对齐新默认与新 contract。 * refactor: address PR #84 review comments - mirror_helper: 把 ClientSession 抽到 speed_test_mirrors 函数级,所有 _probe 共用一个 session/connector,省连接和 SSL 握手开销。 - ffmpeg_bundler._cleanup_stale_tmp_dirs: 改用 shutil.rmtree() 不吞 OSError, 显式 except 后写 debug 日志,方便排查 FS 权限/锁问题。 - ffmpeg_bundler 的 auto 镜像分支: speed_test_mirrors 抛异常时退回直连 GitHub,与 route_source.speed_test 的 try/except 兜底保持一致。 * fix: improve overview success chart (#86) * fix: improve overview success chart * fix: clarify push success chart helpers * fix: remove redundant filter search buttons (#87) * docs: update v2.1.0 changelog * chore: remove local agent artifacts * fix(sender): harden QQ Official media fallback Merge QQ Official media fallback hardening and v2.1.1 release metadata into dev. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Motivation / 动机
Fixes #58 — 旧版本数据库中
rsshub_sub表残留的link_preview NOT NULL列导致/sub命令创建订阅时触发约束失败。同时将 OneBot 的
prefer_local_video配置替换为更合理的napcat_stream_mode(disabled/fallback/always),解决插件已统一预下载媒体却仍在prefer_local_video=false时传原始 URL(如 m3u8)给 OneBot 端、导致重复下载和兼容性问题的设计缺陷。Modifications / 改动点
Issue #58 修复 — V2 数据库迁移
V2_drop_link_preview.py迁移:启动时安全删除rsshub_sub.link_preview遗留列(幂等,新安装自动跳过)test_database_manager.py验证迁移发现和列不存在断言NapCat 流式上传 — 替换 prefer_local_video
napcat_stream.py:NapCat 分块流式上传模块,支持 SHA256 校验napcat_stream_mode三种模式:disabled:不使用流式上传fallback(默认):普通发送失败后流式重试always:发送前优先流式上传prefer_local_video配置及ONEBOT_PREFER_LOCAL_VIDEO_DEFAULT常量media_send_planner.py统一使用variant.path,不再根据prefer_local_video传原始 URLmedia_dispatch.py移除prefer_local_video参数base_sender.py重构:configure_behavior改为接收napcat_stream_mode,主动推送通过platform_manager解析 bot 客户端onebot_sender.py:实现三种 napcat stream 模式的发送逻辑,移除_prefer_local_video()配置迁移与导出修复
legacy_migration.py:onebot_prefer_local_video→napcat_stream_mode自动迁移(true→fallback, false→disabled)settings_builder.py:适配新配置结构,FFmpegSettings拆分为MediaRuntimeSettings+MediaSettings__init__.py/datamodels.py:修复FFmpegSettings过时导出为MediaRuntimeSettingsThis is NOT a breaking change. / 这不是一个破坏性变更。
Screenshots or Test Results / 运行截图或测试结果
Checklist / 检查清单
😊 If there are new features added in the PR, I have discussed them with the authors through issues/emails, etc.
/ 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。
👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
/ 我的更改经过了良好的测试,并已在上方提供了"验证步骤"和"运行截图"。
🤓 I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in
requirements.txt./ 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到
requirements.txt文件相应位置。😮 My changes do not introduce malicious code.
/ 我的更改没有引入恶意代码。