Files
Auto_Bangumi/docs/dev/plugins/events.md
Estrella Pan 045bdbd407 fix(4.0): 已打 ab:renamed 的种子在本进程未发布过时补发 torrent.organized
标签在发布之前打上,之后的轮次不再检查该种子:订阅者处理中被取消
(重启、插件重载)的事件永远不会再投递,硬链接漏掉这一集。现在每个
进程按当前文件名为已打标签的种子补发一次,丢失的事件在下次启动时
重新投递,投递恢复为至少一次;没有事件总线时不查询文件。
test_renamed_tag_skips_before_file_and_offset_queries 改为在已发布的
稳态下断言。文档与 TorrentOrganized 说明同步更新。
2026-10-08 16:42:42 +02:00

72 lines
4.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 事件
事件是冻结的 dataclass,类变量 `kind` 是事件名。宿主和插件共用一条事件总线。
```python
from ab_sdk import subscribe
from ab_sdk.events import RssFailureEvent
@subscribe("rss_failure")
async def on_rss_failure(self, event: RssFailureEvent):
self.ctx.log.warning("订阅 %s 失败:%s", event.rss_name, event.error)
```
- 订阅者在独立队列中按发布顺序异步执行。失败或超时不影响发布方,并计入插件熔断。默认超时 30 秒;复制大文件等耗时操作用 `@subscribe(kind, timeout=600)`。
- `"*"` 订阅全部事件。
- 用 `self.ctx.bus.publish(event)` 发布。插件事件的 `kind` 必须以 `<插件 id>.` 开头,避免与宿主事件冲突。
- 前端组件用 `host.events.on(kind, callback)` 订阅同一条总线,回调收到事件的字段(见 [前端挂载点](/dev/plugins/frontend-slots))。
## 系统事件
AB 发出的、会进入通知中心的事件都是 `ab_sdk.events.SystemEvent` 的子类,带有 `severity`、`payload()`、`describe()`(默认中文标题与正文)和 `i18n()`(前端 i18n key 与参数)。它们先写入通知中心,再发布到事件总线,最后推送到外部通知渠道。关闭「通知」开关只影响外部推送,不影响插件收到事件。
| kind | 事件类 | 何时发布 |
| --- | --- | --- |
| `rss_failure` | `RssFailureEvent` | RSS 订阅从正常变为连接异常 |
| `download_failure` | `DownloadFailureEvent` | 种子重试后仍添加失败 |
| `offset_review` | `OffsetReviewEvent` | 番剧的季度 / 集数偏移需要人工确认 |
| `downloader_unavailable` | `DownloaderUnavailableEvent` | 下载器连不上、凭据错误或 IP 被封;每个实例从可用变为不可用时发布一次 |
| `update_available` | `UpdateAvailableEvent` | 检查到新版本 |
| `update_applied` / `update_failed` | `UpdateAppliedEvent` | 在线更新成功 / 失败 |
| `llm_auth_failure` | `LLMAuthFailureEvent` | 订阅类 LLM 提供商凭据失效 |
| `llm_plugin_install_failed` | `LLMPluginInstallFailedEvent` | LLM 插件安装失败 |
| `rename_conflict` | `RenameConflictEvent` | 媒体文件重命名遇到目标路径冲突 |
| `rename_skipped` | `RenameSkippedEvent` | 重命名方式无法为文件给出名字,文件保留原名 |
| `plugin.loaded` | `PluginLoaded` | 插件加载成功 |
| `plugin.disabled` | `PluginDisabled` | 插件加载失败或被熔断 |
系统事件的 `kind` 沿用 3.x 的取值(通知中心按它存储和翻译),因此不带 `.` 前缀。
## 整理事件
| kind | 事件类 | 字段 | 何时发布 |
| --- | --- | --- | --- |
| `file.renamed` | `FileRenamed` | `bangumi_id`、`old_path`、`new_path`、`file_kind`、`downloader_id` | 一个文件被实际重命名(含版本替换后的改名) |
| `torrent.organized` | `TorrentOrganized` | `torrent_hash`、`bangumi_id`、`files`(`OrganizedFile(path, kind)` 元组)、`downloader_id` | 一个种子整理完成;重命名方式为 `none` 时同样发布,`files` 为原路径。选中的重命名方式未登记(插件停用或被熔断)时不发布 |
- 路径是**下载器视角**的绝对路径,以 `/` 拼接(Windows 下载器的 `\` 也统一为 `/`)。AB 与下载器看到的目录不同(如分别运行在不同容器)时,订阅者要自己做路径映射。
- `bangumi_id` 来自种子的 `ab:<id>` 标签,旧种子可能为 `None`。`downloader_id` 是种子所在下载器实例的 id;多个实例的路径视角可能不同。
- `torrent.organized` 的投递是**至少一次**:下载器中已整理的种子(包括已打「已重命名」标签的)在每次 AB 重启后会再发布一次。订阅者必须幂等。
- 这两个事件只发布到事件总线,不进入通知中心。
## 自定义事件
继承 `Event` 定义一般事件。继承 `SystemEvent` 定义可通知事件:发布后它与宿主事件走同一条路径,即写入通知中心(前端没有对应翻译时显示 `describe()` 的标题与正文)、发布到事件总线、推送到外部渠道。`dedup_key()` 相同的事件在通知中心合并为一条。
```python
from dataclasses import dataclass
from typing import ClassVar
from ab_sdk.events import SystemEvent
@dataclass(frozen=True, slots=True)
class SyncFailed(SystemEvent):
kind: ClassVar[str] = "my-plugin.sync_failed"
severity: ClassVar[str] = "warning"
reason: str
def describe(self) -> tuple[str, str]:
return ("同步失败", self.reason)
```
内置插件 `hardlink` 的 `hardlink.failed` 就是这样发出的。