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

5.1 KiB

Events

An event is a frozen dataclass. The class variable kind is the event name. The host and the plugins share one event bus.

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).

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.

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.