Skip to content

fix: 多群订阅同源查重 (#70) + 字体加载/配置/文档清理#75

Merged
FlanChanXwO merged 10 commits into
devfrom
fix/sub-dedup-and-cleanups
May 31, 2026
Merged

fix: 多群订阅同源查重 (#70) + 字体加载/配置/文档清理#75
FlanChanXwO merged 10 commits into
devfrom
fix/sub-dedup-and-cleanups

Conversation

@FlanChanXwO
Copy link
Copy Markdown
Owner

@FlanChanXwO FlanChanXwO commented May 31, 2026

本次合并修复了 issue #70(同一用户无法在不同群聊订阅同一 RSS 源),并附带若干启动性能优化、死配置接通与代码/文档清理。

Modifications / 改动点

核心改动按功能拆分为多个提交:

  • fix(subscription) — issue Bug: /sub功能异常 #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 误抛。

    • src/domain/repositories/subscription_repository.pysrc/infrastructure/persistence/subscription_repository_impl.pysrc/application/commands/subscribe_feed_cmd.pysrc/application/commands/import_subscriptions_cmd.py
  • perf(rendering):字体下载从 bootstrap 同步 await 改为后台预取 + 首次渲染按需等待(仅在 table_to_image 开启时预取),不再阻塞插件加载;顺带修复 EntryTextFormatter.configure_table_to_image 不存在却被调用导致的启动 AttributeError

  • refactor(interfaces):移除无效的 GreedyStr 兼容层 astrbot_compat(带默认值时框架只读默认值、greedy 分支恒不触发,注解等价于 str),上传监听改用框架原生 filter.event_message_type

  • refactor(config)telegraph_proxy 从顶层 media 归位到 telegram_strategy 模板并真正接通(此前 _get_telegraph_proxy 定义却从未被调用),留空即直连不继承通用代理;启动配置自愈新增旧键迁移。

  • fix(messaging)messaging/__init__.pyTYPE_CHECKING 块消除 31 个 __all__ 懒加载告警(运行时懒加载行为不变)。

  • docs:整理 docs/usage/ 命令/配置文档,CHANGELOG 2.0.3 恢复 Added/Changed/Removed/Fixed 分节结构。

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

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

# 受影响订阅/导入测试(含 issue #70 回归用例)
$ pytest tests/unit/application/test_commands.py tests/unit/application/test_import_export.py -q
31 passed

# 官方测试脚本
$ python tests/run_tests.py --category unit         # Results: 17 passed, 0 failed
$ python tests/run_tests.py --category integration  # Results: 4 passed, 0 failed

# 代码风格
$ uv run ruff format <plugin>   # all files unchanged
$ uv run ruff check  <plugin>   # All checks passed!

新增回归测试 test_subscribe_same_feed_allowed_in_different_sessions:验证同一用户同一源在不同会话各自订阅成功、相同会话仍按查重拒绝。

说明:全量套件中另有若干 media/ffmpeg 与 event_system 的失败,已用干净 HEAD worktree 逐一比对确认均为预先存在、与本 PR 改动模块无关。

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. / 我确保没有引入新依赖库。
  • 😮 My changes do not introduce malicious code. / 我的更改没有引入恶意代码。

Summary by CodeRabbit

发布说明

  • 文档

    • 新增详细使用文档,包括命令、配置、管理界面、AI工具、兼容性说明
    • 更新文档导航结构,改进用户快速查阅体验
  • 新增功能

    • 支持同一用户在不同会话中对同一订阅源创建独立订阅
    • 优化表格转图片的字体加载策略,减少启动阻塞
  • 配置调整

    • Telegraph 代理配置从媒体设置迁移至 Telegram 平台策略
    • 更新 AstrBot 最低版本要求至 4.24.0
  • 改进

    • 完善命令参数处理机制
    • 增强配置迁移逻辑以支持旧版本升级

启用表格转图时,字体下载在 bootstrap 中由同步 await 改为后台
asyncio.create_task 预取,首次表格渲染前再按需等待就绪(未配置下载的
环境直接回退纯文本,不发起网络请求)。同时补全缺失的表格转图总开关
EntryTextFormatter.configure_table_to_image,修复其原本不存在却被
bootstrap 调用导致的启动 AttributeError。
命令处理器参数均带默认值(args: GreedyStr = ""),框架解析时只读默认值
不读注解,greedy 合并分支恒不触发——该注解功能上是惰性的,等价于 str。
故将注解统一改为 str,文件上传监听改用框架原生
filter.event_message_type(filter.EventMessageType.ALL),删除整个
astrbot_compat 兼容层并同步重写相关回归测试。
将 telegraph_proxy 从顶层 media 配置移入 telegram_strategy 模板,与
enable_telegraph/telegraph_token 同源。发送时由 TelegramSender 从策略
解析并传入 TelegraphClient(留空即直连,不再继承通用 HTTP 代理),修复
此前 _get_telegraph_proxy 定义却从未被调用的死配置问题,并移除相关死链路。
启动配置自愈新增迁移:旧 media.telegraph_proxy 会自动写入首个 telegram
策略模板项。
新增/完善 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。
messaging 包通过 __getattr__ 懒加载所有导出,导致 __all__ 中 31 个名字在
类型检查器看来「未定义」(reportUnsupportedDunderAll)。新增 if TYPE_CHECKING
块真实导入这些符号:类型检查时静态可见、告警清零,运行时该块不执行,原有
懒加载行为完全保留。
订阅查重原先用 (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 误抛。新增回归测试覆盖「不同会话各自成功、相同会话仍拒绝」。
将此前压缩成扁平要点的 2.0.3 条目还原为 Keep a Changelog 分节格式,并把
「媒体缓存 GC / 完整性阈值 / 平台降级策略不再作为用户配置项暴露」从 Changed
提取为独立的 Removed 段。
Copilot AI review requested due to automatic review settings May 31, 2026 22:10
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

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
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2026

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: 308adb32-4055-4f7a-8a63-028389a80b60

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:

  • 🔍 Trigger review

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 release Release metadata or changelog changes labels May 31, 2026
@FlanChanXwO
Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

Copilot AI left a comment

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.

Copy link
Copy Markdown

Copilot AI left a comment

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/infrastructure/persistence/subscription_repository_impl.py (1)

44-61: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

为避免重复数据导致 MultipleResultsFound:给 (user_id, feed_id, target_session) 加唯一约束或改为取第一条防御
get_by_user_feed_session() 里用 result.scalar_one_or_none();但 src/infrastructure/persistence/models.pySubORM 未声明该三列唯一约束/唯一索引,src/infrastructure/persistence/migrations/V1_init.py 创建 rsshub_sub 也没有 UNIQUE/CREATE UNIQUE/unique index,因此一旦历史数据或并发写入产生重复行仍可能再次触发 MultipleResultsFound(与 #70 同源)。建议:要么在数据库层补上 (user_id, feed_id, target_session) 唯一约束(并清理历史重复);要么在查询上加 limit(1) / 改用 scalars().first(),确保重复数据也不会抛异常。 [src/infrastructure/persistence/subscription_repository_impl.py 44-61]

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/infrastructure/persistence/subscription_repository_impl.py` around lines
44 - 61, The get_by_user_feed_session method uses result.scalar_one_or_none()
but SubORM lacks a DB-level unique constraint on (user_id, feed_id,
target_session), so duplicate rows can raise MultipleResultsFound; fix by either
adding a unique constraint/index for those three columns in the ORM model
(SubORM) and the migration (V1_init.py) and clean duplicates, or make the query
defensive by applying a limit and using scalars().first() (i.e., change the
select in get_by_user_feed_session to include .limit(1) or call
result.scalars().first() instead of scalar_one_or_none) to avoid exceptions if
duplicates exist.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/infrastructure/rendering/font_manager.py`:
- Around line 76-87: ensure_table_font_runtime currently re-runs expensive
synchronous font verification (_verify_font) on each call, which can block the
event loop when invoked repeatedly (e.g., per-table in html_parser); add a
module-level cache (e.g. _cached_verified_font: Path | None) and an asyncio.Lock
to make checking/setting atomic, then update ensure_table_font_runtime to first
return the cached path if present, otherwise run _verify_font once, store the
verified path in _cached_verified_font, and return it; also ensure any
install/download path (ensure_table_font) clears or updates
_cached_verified_font after a successful download so subsequent calls use the
cache; reference functions: ensure_table_font_runtime, _verify_font,
ensure_table_font, get_runtime_font_path.

---

Outside diff comments:
In `@src/infrastructure/persistence/subscription_repository_impl.py`:
- Around line 44-61: The get_by_user_feed_session method uses
result.scalar_one_or_none() but SubORM lacks a DB-level unique constraint on
(user_id, feed_id, target_session), so duplicate rows can raise
MultipleResultsFound; fix by either adding a unique constraint/index for those
three columns in the ORM model (SubORM) and the migration (V1_init.py) and clean
duplicates, or make the query defensive by applying a limit and using
scalars().first() (i.e., change the select in get_by_user_feed_session to
include .limit(1) or call result.scalars().first() instead of
scalar_one_or_none) to avoid exceptions if duplicates exist.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 638f6822-b6ce-4ebb-931a-e846db74828a

📥 Commits

Reviewing files that changed from the base of the PR and between 6bac180 and 3466226.

📒 Files selected for processing (35)
  • CHANGELOG.md
  • README.md
  • _conf_schema.json
  • assets/fonts/OFL.txt
  • bootstrap.py
  • docs/README.md
  • docs/usage/README.md
  • docs/usage/ai-tools.md
  • docs/usage/commands.md
  • docs/usage/compatibility.md
  • docs/usage/configuration.md
  • docs/usage/plugin-pages.md
  • main.py
  • metadata.yaml
  • src/application/commands/import_subscriptions_cmd.py
  • src/application/commands/subscribe_feed_cmd.py
  • src/application/services/html_parser.py
  • src/domain/repositories/subscription_repository.py
  • src/infrastructure/config/legacy_migration.py
  • src/infrastructure/config/models/runtime_settings.py
  • src/infrastructure/config/models/sender_strategy_models.py
  • src/infrastructure/config/settings_builder.py
  • src/infrastructure/messaging/__init__.py
  • src/infrastructure/messaging/senders/base_sender.py
  • src/infrastructure/messaging/senders/telegram_sender.py
  • src/infrastructure/persistence/subscription_repository_impl.py
  • src/infrastructure/pipeline/entry_formatter.py
  • src/infrastructure/rendering/font_manager.py
  • src/interfaces/astrbot_compat.py
  • tests/unit/application/test_commands.py
  • tests/unit/application/test_import_export.py
  • tests/unit/application/test_settings.py
  • tests/unit/infrastructure/test_platform_sequence_senders.py
  • tests/unit/test_command_handlers_regression.py
  • tests/unit/test_conf_schema.py
💤 Files with no reviewable changes (3)
  • assets/fonts/OFL.txt
  • src/interfaces/astrbot_compat.py
  • src/infrastructure/messaging/senders/base_sender.py

Comment thread src/infrastructure/rendering/font_manager.py Outdated
- maintenance.md 新增「仓库体积」章节:插件仓库总大小严格不得超过 16 MB,
  大体积资源应按需下载而非纳入仓库。
- engineering-principles.md 代码组织新增:每个函数/方法必须有确切的中文
  docstring,并与实现保持一致。
- 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。
@FlanChanXwO FlanChanXwO merged commit 89fb3e2 into dev May 31, 2026
3 of 4 checks passed
@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>
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 release Release metadata or changelog changes

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants