Files
Auto_Bangumi/docs/en/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
5.1 KiB
Markdown

# Events
An event is a frozen dataclass. The class variable `kind` is the event name. The host and the plugins share one event bus.
```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("Subscription %s failed: %s", event.rss_name, event.error)
```
- Subscribers run asynchronously in their own queue, in publish order. A failure or a timeout does not affect the publisher, and it counts toward the plugin breaker. The default timeout is 30 seconds. For long operations such as copying large files, use `@subscribe(kind, timeout=600)`.
- `"*"` subscribes to all events.
- Publish with `self.ctx.bus.publish(event)`. The `kind` of a plugin event must start with `<plugin id>.`. This prevents a clash with host events.
- A frontend component subscribes to the same bus with `host.events.on(kind, callback)`. The callback gets the event fields (see [Frontend slots](/en/dev/plugins/frontend-slots)).
## System events
The events that AB sends to the notification center are all subclasses of `ab_sdk.events.SystemEvent`. They have `severity`, `payload()`, `describe()` (default Chinese title and body) and `i18n()` (the frontend i18n key and parameters). AB first writes them to the notification center, then publishes them on the event bus, then pushes them to external channels. The "notification" switch affects only the external push. Plugins always receive the events.
| kind | Event class | When AB publishes it |
| --- | --- | --- |
| `rss_failure` | `RssFailureEvent` | An RSS subscription changes from normal to a connection failure |
| `download_failure` | `DownloadFailureEvent` | A torrent still fails to add after retries |
| `offset_review` | `OffsetReviewEvent` | The season or episode offset of a series needs a manual check |
| `downloader_unavailable` | `DownloaderUnavailableEvent` | The downloader is unreachable, the credentials are wrong or the IP is banned; once per instance when it changes from available to unavailable |
| `update_available` | `UpdateAvailableEvent` | A new version is found |
| `update_applied` / `update_failed` | `UpdateAppliedEvent` | An online update succeeds / fails |
| `llm_auth_failure` | `LLMAuthFailureEvent` | The credentials of a subscription-type LLM provider are no longer valid |
| `llm_plugin_install_failed` | `LLMPluginInstallFailedEvent` | The installation of an LLM plugin fails |
| `rename_conflict` | `RenameConflictEvent` | A media file rename meets a target path conflict |
| `rename_skipped` | `RenameSkippedEvent` | The rename strategy cannot name a file; the file keeps its name |
| `plugin.loaded` | `PluginLoaded` | A plugin loads successfully |
| `plugin.disabled` | `PluginDisabled` | A plugin fails to load or the breaker trips |
The `kind` of a system event keeps the 3.x value (the notification center stores and translates by it). So it has no `.` prefix.
## Organize events
| kind | Event class | Fields | When AB publishes it |
| --- | --- | --- | --- |
| `file.renamed` | `FileRenamed` | `bangumi_id`, `old_path`, `new_path`, `file_kind`, `downloader_id` | A file is renamed (including a rename after a version replacement) |
| `torrent.organized` | `TorrentOrganized` | `torrent_hash`, `bangumi_id`, `files` (a tuple of `OrganizedFile(path, kind)`), `downloader_id` | A torrent is organized. AB also publishes it when the rename strategy is `none`; `files` then holds the original paths. AB does not publish it while the selected rename strategy is not registered (its plugin is disabled or tripped) |
- Paths are absolute paths **as the downloader sees them**, joined with `/` (a `\` from a Windows downloader is also changed to `/`). If AB and the downloader see different directories (for example, they run in different containers), the subscriber must map the paths.
- `bangumi_id` comes from the `ab:<id>` tag of the torrent. An old torrent can have `None`. `downloader_id` is the id of the downloader instance that holds the torrent. Different instances can have different path views.
- `torrent.organized` has **at-least-once** delivery. AB publishes it again after each restart for the organized torrents in the downloader, also for torrents with the "renamed" tag. Subscribers must be idempotent.
- These two events go to the event bus only. They do not go to the notification center.
## Custom events
Inherit `Event` for a normal event. Inherit `SystemEvent` for a notifiable event. After you publish it, it takes the same path as a host event: the notification center (the frontend shows the title and body of `describe()` if it has no translation), the event bus and the external channels. Events with the same `dedup_key()` merge into one entry in the notification center.
```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 ("Sync failed", self.reason)
```
The built-in `hardlink` plugin sends its `hardlink.failed` event in this way.