Skip to content

fix(db,onebot): drop legacy link_preview column and add napcat stream upload#74

Merged
FlanChanXwO merged 3 commits into
devfrom
fix/issue-58-and-napcat-stream
May 31, 2026
Merged

fix(db,onebot): drop legacy link_preview column and add napcat stream upload#74
FlanChanXwO merged 3 commits into
devfrom
fix/issue-58-and-napcat-stream

Conversation

@FlanChanXwO

Copy link
Copy Markdown
Owner

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 传原始 URL
  • media_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.pyonebot_prefer_local_videonapcat_stream_mode 自动迁移(true→fallback, false→disabled)

  • settings_builder.py:适配新配置结构,FFmpegSettings 拆分为 MediaRuntimeSettings + MediaSettings

  • __init__.py / datamodels.py:修复 FFmpegSettings 过时导出为 MediaRuntimeSettings

  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果

Results: 21 passed, 0 failed
All tests passed!
  • ruff check: All checks passed!
  • ruff format: 149 files already formatted

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.
    / 我的更改没有引入恶意代码。

FlanChanXwO and others added 3 commits May 31, 2026 11:44
修复 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>
…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>
Copilot AI review requested due to automatic review settings May 31, 2026 16:07

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @FlanChanXwO, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e560da5e-fb8c-4d1d-8af9-83a90da2ee60

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added area: docs Documentation changes area: backend Backend or core runtime changes area: tests Test changes labels May 31, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@FlanChanXwO FlanChanXwO merged commit cd0b1ea into dev May 31, 2026
4 of 6 checks passed
@FlanChanXwO FlanChanXwO deleted the fix/issue-58-and-napcat-stream branch May 31, 2026 16:38
@github-project-automation github-project-automation Bot moved this from Todo to Done in astrbot_plugin_rsshub May 31, 2026
This was referenced May 31, 2026
FlanChanXwO added a commit that referenced this pull request May 31, 2026
* 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>
FlanChanXwO added a commit that referenced this pull request Jun 1, 2026
* 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。
FlanChanXwO added a commit that referenced this pull request Jun 16, 2026
* 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>
FlanChanXwO added a commit that referenced this pull request Jun 18, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: backend Backend or core runtime changes area: docs Documentation changes area: tests Test changes

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants