Files
Auto_Bangumi/docs/ja/dev/plugins/concepts.md
Estrella Pan 3258fcca4b docs(4.0-P7): 插件开发文档拆分为总览加每个扩展点一页,补齐中英日三语
- docs/dev/plugins.md 改为总览与导航,旧内容拆入 docs/dev/plugins/
- 新增核心概念、配置表单、事件、前端挂载点、命令行、签名与分发、内置插件、示例页
- 每个扩展点一页,侧边栏由页面表生成
- 改正旧文档中下载器配置、hardlink path_map 与补链按钮的过时描述
2026-10-08 14:51:00 +02:00

7.8 KiB
Raw Permalink Blame History

基本概念

ディレクトリ構成とマニフェスト

config/plugins/local/my-plugin/     # ディレクトリ名はマニフェストの id と同じにします
├── plugin.toml
├── my_plugin/                      # パッケージ(または単一の my_plugin.py)。相対 import が使えます
│   └── __init__.py
├── web/                            # 任意:フロントエンドコンポーネント(「フロントエンドスロット」を参照)
└── vendor/                         # 任意:純 Python の依存(.so / .pyd / .dylib / .dll は禁止)
# plugin.toml
[plugin]
id = "my-plugin"              # 小文字・数字・ハイフン。core と local は予約済み
name = "My plugin"
version = "0.1.0"
sdk = ">=0.5,<1"              # 対応する ab_sdk のバージョン範囲
entry = "my_plugin:MyPlugin"  # モジュール:Plugin サブクラス(プラグインディレクトリからの相対)
description = "一文の説明"
authors = ["me"]
permissions = ["network"]     # ユーザーへの表示のみ。強制はされません
extension_points = ["notifier"]  # 任意:署名付きカタログの表示にだけ使います

プラグインは AB と同じプロセスで動作し、すべての権限を持ちます。permissions はユーザーへの宣言であり、サンドボックスではありません。ab-plugin validate はマニフェスト、SDK のバージョン範囲、エントリモジュール、フロントエンドのエントリファイルを検証します。

Plugin と設定

  • Plugin[設定モデル] を継承し、config_model を設定します。AB はモデルでユーザー設定を検証します。プラグインは self.config で型付きのインスタンスを受け取ります。WebUI で不正な値を保存しようとすると拒否されます(HTTP 422)。
  • async setup() は読み込み後に一度、async teardown() はアンロード前に一度呼ばれます。設定が変わると、teardown の後でプラグインを作り直し、setup を呼びます。setup() が例外を送出すると、プラグインはエラー状態になります。
  • フォームについては 設定フォーム を参照してください。

self.ctx は、プラグインから見えるホストの機能のすべてです。

属性 説明
plugin_id プラグイン id
config 検証済みの設定モデルのインスタンス(config_model がなければ None)
log plugin.<id> のプレフィックス付き logger
kv プラグイン専用の永続キーバリューストア(get / set / delete)。値は JSON にできるものに限ります
data_dir config/plugin-data/<id>/。初回アクセス時に作成されます
bus イベントバス:publish(event)、subscribe(kind, handler)

プラグインでは ab_sdk だけを import してください。module.* は import しないでください。ホストの内部実装であり、いつでも変更される可能性があります。

3 種類の拡張宣言

デコレーター 用途
@provider(point, id=...) 拡張ポイントが定める実装を返すファクトリメソッド。id はユーザーが選ぶときの名前です
@hook(point, priority=..., timeout=...) ホストが宣言した filter / transform 拡張ポイントに接続します。同じ拡張ポイントでは priority の昇順、同順位ならプラグイン id の順に実行します。ユーザーは plugins.hook_order で順序を指定できます
@subscribe(kind, timeout=...) イベントを購読します。"*" は全イベントです。timeout で 1 イベントの処理時間の上限(既定 30 秒)を変更できます

デコレーターは目印を付けるだけです。拡張ポイント名が間違っていると、プラグインの読み込みが失敗し、プラグイン一覧に理由が表示されます。黙って無視されることはありません。

  • filter フックは Verdict(または bool)を返します。1 つでも拒否すると、そこで打ち切ります。
  • transform フックは前のフックの結果を受け取り、新しい値を返します。None は変更なしです。フックが受け取るのは凍結されたスナップショットです。変更したコピーは dataclasses.replace で作って返します。

分離とサーキットブレーカー

  • フック、イベントハンドラー、定期タスク、ルートハンドラーにはタイムアウトがあります。
  • プラグインの例外はホストに影響しません。同じプラグインが 5 回連続で失敗すると、AB は自動的に無効にし、プラグイン一覧に理由を表示します。そのプラグインの設定を変更すると、再度読み込みを試みます。
  • プラグインの戻り値は、ブレーカーの保護下で検証されます。型が違えば失敗として扱い、ホストの既定の動作に戻します。
  • 例外:RenameSkipped は「入力または設定に問題がある」ことを表し、ブレーカーには数えません。

テスト

ab_sdk.testing を使うと、AB を起動せずにプラグインを作成して動かせます。

from ab_sdk.testing import create_plugin

def test_filter(tmp_path):
    plugin, ctx = create_plugin(MyPlugin, {"key": "k"}, data_dir=tmp_path)
    ...
    assert ctx.bus.published == []

create_plugin はホストと同じ規則で設定を検証し、プラグインを作成します(setup は呼びません)。ctx.bus はプラグインが発行したイベントを記録します。await ctx.bus.deliver(event) で、イベントをプラグイン自身の購読者に同期的に配信できます。

Provider 系の拡張ポイントには、SDK がコントラクトテストスイートを用意しています。スイートを継承して create() を実装すると、pytest がテストケースを収集します。

スイート 検査対象
DownloaderContract ダウンローダークライアント。behavioral = True でログインやトレント追加などの動作も検査します
RenameStrategyContract リネーム方式:トレント内の相対パスを返す、拡張子を保つ、結果が決定的
NotifierContract 通知チャンネル:成功時は True、バックエンドが拒否したときは例外ではなく False
SearchSiteContract 検索サイト:URL に %s が 1 つ、パーサーは mikan または tmdb
from ab_sdk.testing import RenameStrategyContract, create_plugin

class TestMyStrategy(RenameStrategyContract):
    def create(self):
        plugin, _ = create_plugin(MyPlugin)
        return plugin.strategy()

テストケースは同期関数です(内部で asyncio.run を使います)。pytest-asyncio は不要です。

pip パッケージとして公開する

パッケージの pyproject.toml に entry point を宣言し、トップレベルのパッケージ内に plugin.toml を置きます。

[project.entry-points."autobangumi.plugins"]
my-plugin = "my_plugin:MyPlugin"

pip でインストールしたプラグインも未署名です。「未署名プラグインを許可」をオンにする必要があります。Docker イメージには pip でインストールできません。その場合はローカルディレクトリを使い、依存を vendor/ に入れてください。署名付きプラグインは 署名と配布 を参照してください。