""" Agent 结构化 MoviePilot API 操作与固定路由注册表。 """ from dataclasses import dataclass from enum import Enum from typing import Optional from app.agent.policy.contracts import ( ActionEffect, ConfirmationMode, PrincipalRole, RecoveryMode, ResultSensitivity, ) class ApiIdentityScope(str, Enum): """ 区分公共资料、绑定用户数据和受保护的实例管理身份。 """ # 无需绑定用户的公共资料查询 PUBLIC = "public" # 使用可信入口绑定的用户身份 BOUND_USER = "bound_user" # 使用经过授权的实例管理身份 ADMIN = "admin" @dataclass(frozen=True) class ApiOperationSpec: """ 描述一个可由 Agent API 网关调用的稳定业务操作。 :param operation_id: 稳定业务操作标识,与固定路由表对应 :param effect: 该操作的实际副作用类别 :param required_role: 执行操作所需的最低授权角色 :param confirmation: 授权通过后需要的确认方式 :param recovery: 该操作提供的恢复或结果核对机制 :param result_sensitivity: 操作结果的敏感等级 :param identity_scope: API 应使用的身份范围,默认绑定当前用户 """ operation_id: str effect: ActionEffect required_role: PrincipalRole confirmation: ConfirmationMode recovery: RecoveryMode result_sensitivity: ResultSensitivity identity_scope: ApiIdentityScope = ApiIdentityScope.BOUND_USER def __post_init__(self) -> None: """ 公共身份只接受无需确认、无私密结果的普通用户级安全查询。 """ if self.identity_scope is ApiIdentityScope.PUBLIC and ( self.effect is not ActionEffect.SAFE_READ or self.required_role is not PrincipalRole.USER or self.confirmation is not ConfirmationMode.NONE or self.result_sensitivity is not ResultSensitivity.NORMAL ): raise ValueError("公共 API 身份不能用于敏感读取、管理员操作或写操作") @dataclass(frozen=True) class ApiOperationRoute: """ 描述一个固定 API 路由,禁止调用方注入任意 URL 或 HTTP 方法。 :param method: 宿主登记的固定 HTTP 方法 :param path: 宿主登记的 API 路径模板,只允许替换声明的路径参数 """ method: str path: str _ADMIN = PrincipalRole.SYSTEM_ADMIN _CONFIRM = ConfirmationMode.REQUIRED _IDEMPOTENT = RecoveryMode.IDEMPOTENT _DELETE_RECOVERABLE = RecoveryMode.RECOVERABLE_DELETE def _spec( operation_id: str, *, effect: ActionEffect = ActionEffect.SAFE_READ, required_role: PrincipalRole = PrincipalRole.USER, confirmation: ConfirmationMode = ConfirmationMode.NONE, recovery: RecoveryMode = RecoveryMode.NONE, result_sensitivity: ResultSensitivity = ResultSensitivity.NORMAL, identity_scope: Optional[ApiIdentityScope] = None, ) -> ApiOperationSpec: """ 构造不可变 API 操作描述。 """ return ApiOperationSpec( operation_id=operation_id, effect=effect, required_role=required_role, confirmation=confirmation, recovery=recovery, result_sensitivity=result_sensitivity, identity_scope=identity_scope or ( ApiIdentityScope.ADMIN if required_role is _ADMIN else ApiIdentityScope.BOUND_USER ), ) def _public_read(operation_id: str) -> ApiOperationSpec: """ 仅为已审计、不读取本地用户数据的公共资料声明无绑定查询。 """ return _spec(operation_id, identity_scope=ApiIdentityScope.PUBLIC) def _write( operation_id: str, *, effect: ActionEffect = ActionEffect.REVERSIBLE_WRITE, recovery: RecoveryMode = _IDEMPOTENT, sensitivity: ResultSensitivity = ResultSensitivity.NORMAL, ) -> ApiOperationSpec: """ 构造管理员确认写操作。 """ return _spec( operation_id, effect=effect, required_role=_ADMIN, confirmation=_CONFIRM, recovery=recovery, result_sensitivity=sensitivity, ) def _admin_read( operation_id: str, *, sensitivity: ResultSensitivity = ResultSensitivity.NORMAL, ) -> ApiOperationSpec: """ 构造管理员读取操作。 """ return _spec(operation_id, required_role=_ADMIN, result_sensitivity=sensitivity) def _user_write( operation_id: str, *, effect: ActionEffect = ActionEffect.REVERSIBLE_WRITE, recovery: RecoveryMode = _IDEMPOTENT, sensitivity: ResultSensitivity = ResultSensitivity.NORMAL, ) -> ApiOperationSpec: """ 构造保留普通用户 API 权限的确认写操作。 """ return _spec( operation_id, effect=effect, confirmation=_CONFIRM, recovery=recovery, result_sensitivity=sensitivity, ) API_FIRST_BATCH_OPERATION_SPECS: tuple[ApiOperationSpec, ...] = ( _public_read("media.search"), _public_read("media.person.search"), _public_read("media.person.credits"), _spec("media.recognize"), _spec("media.scrape", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM), _public_read("media.episode_schedule"), _public_read("media.detail"), # 订阅归属由 API 当前用户决定,渠道管理员也必须使用绑定用户身份。 _user_write("subscription.add"), _user_write("subscription.update"), _user_write("subscription.search", effect=ActionEffect.EXTERNAL_SIDE_EFFECT), _spec("subscription.list"), _public_read("subscription.shares"), _public_read("subscription.popular"), _spec("subscription.history"), _user_write("subscription.delete", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=_DELETE_RECOVERABLE), _spec("download.add", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, confirmation=_CONFIRM, recovery=_IDEMPOTENT), _spec( "download.artist_collection", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, confirmation=_CONFIRM, recovery=_IDEMPOTENT, ), _spec("download.tasks.active"), _spec("download.clients"), _spec("download.paths"), _spec("download.history.list"), _write("download.history.delete", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=_DELETE_RECOVERABLE), _write("transfer.history.delete", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=_DELETE_RECOVERABLE), _write("site.update"), _spec("site.list"), _admin_read("site.userdata", sensitivity=ResultSensitivity.PRIVATE), _spec("site.test"), _write("site.cookie.update", sensitivity=ResultSensitivity.PRIVATE), _public_read("recommendation.list"), _spec("library.exists"), _spec("library.latest"), _admin_read("storage.settings"), _spec("storage.list", result_sensitivity=ResultSensitivity.PRIVATE), _spec("transfer.history", result_sensitivity=ResultSensitivity.PRIVATE), _write("transfer.history.register", sensitivity=ResultSensitivity.PRIVATE), _spec( "transfer.file", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM, recovery=RecoveryMode.RECONCILE, ), _admin_read("scheduler.list"), _spec( "scheduler.run", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM, recovery=RecoveryMode.RECONCILE, ), _spec("workflow.list"), _spec( "workflow.run", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM, recovery=RecoveryMode.RECONCILE, ), _admin_read("plugin.installed"), _admin_read("plugin.market"), _admin_read("plugin.capabilities"), _admin_read("plugin.config.get", sensitivity=ResultSensitivity.PRIVATE), _write("plugin.config.update", sensitivity=ResultSensitivity.PRIVATE), _admin_read("plugin.source.options"), _spec( "plugin.source.install", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM, recovery=RecoveryMode.RECONCILE, ), _spec( "plugin.source.change", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM, recovery=RecoveryMode.RECONCILE, ), _spec( "plugin.reload", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM, recovery=RecoveryMode.RECONCILE, ), _spec( "plugin.install", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM, recovery=RecoveryMode.RECONCILE, ), _write("plugin.uninstall", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=_DELETE_RECOVERABLE), _admin_read("slash.list"), _admin_read("config.identifiers.get"), _write("config.identifiers.update"), ) API_PARITY_OPERATION_SPECS: tuple[ApiOperationSpec, ...] = ( _spec("search.torrents"), _spec("search.results"), _spec("filter.builtin"), _spec("filter.custom"), _spec("filter.groups"), _write("filter.custom.add", recovery=RecoveryMode.TRANSACTION), _write("filter.custom.update", recovery=RecoveryMode.TRANSACTION), _write("filter.custom.delete", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=RecoveryMode.TRANSACTION), _write("filter.group.add", recovery=RecoveryMode.TRANSACTION), _write("filter.group.update", recovery=RecoveryMode.TRANSACTION), _write("filter.group.delete", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=RecoveryMode.TRANSACTION), _admin_read("plugin.data", sensitivity=ResultSensitivity.PRIVATE), _admin_read("config.system.list", sensitivity=ResultSensitivity.PRIVATE), _admin_read("config.system.describe", sensitivity=ResultSensitivity.PRIVATE), _admin_read("config.system.get", sensitivity=ResultSensitivity.PRIVATE), _write("config.system.update", recovery=RecoveryMode.TRANSACTION, sensitivity=ResultSensitivity.PRIVATE), _spec( "slash.run", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM, recovery=RecoveryMode.RECONCILE, ), ) API_MUSIC_OPERATION_SPECS: tuple[ApiOperationSpec, ...] = ( _public_read("music.recognize"), _public_read("music.explore"), _public_read("music.album.get"), _public_read("music.album.related"), _public_read("music.artist.get"), _public_read("music.artist.albums"), _public_read("music.artist.related"), _admin_read("music.cache.get", sensitivity=ResultSensitivity.PRIVATE), _write( "music.cache.delete", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=RecoveryMode.NONE, ), _write( "music.cache.clear", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=RecoveryMode.NONE, ), ) API_SYSTEM_OPERATION_SPECS: tuple[ApiOperationSpec, ...] = ( _admin_read("system.versions"), _admin_read("system.update.status"), _spec( "system.update.check", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, recovery=_IDEMPOTENT, ), _spec( "system.update.download", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM, recovery=RecoveryMode.RECONCILE, ), _spec( "system.restart", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM, recovery=RecoveryMode.RECONCILE, ), _spec( "system.update.install", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM, recovery=RecoveryMode.RECONCILE, ), _spec( "system.upgrade.dev", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM, recovery=RecoveryMode.RECONCILE, ), ) API_EXTENDED_OPERATION_SPECS: tuple[ApiOperationSpec, ...] = ( _admin_read("dashboard.media.statistics", sensitivity=ResultSensitivity.PRIVATE), _admin_read("dashboard.storage", sensitivity=ResultSensitivity.PRIVATE), _admin_read("dashboard.processes", sensitivity=ResultSensitivity.PRIVATE), _admin_read("dashboard.system", sensitivity=ResultSensitivity.PRIVATE), _admin_read("dashboard.downloader", sensitivity=ResultSensitivity.PRIVATE), _admin_read("scheduler.progress"), _admin_read("dashboard.transfer.statistics"), _admin_read("dashboard.cpu"), _admin_read("dashboard.memory"), _admin_read("dashboard.network"), _spec("media.sources"), _spec("media.recognize_file"), _admin_read("media.cache.get", sensitivity=ResultSensitivity.PRIVATE), _write("media.cache.delete", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=RecoveryMode.NONE), _write("media.cache.clear", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=RecoveryMode.NONE), _spec("media.classification.fields"), _spec("media.classification.policy.get"), _admin_read("media.classification.policy.validate"), _spec("media.classification.policy.preview"), _admin_read("media.classification.policy.impact", sensitivity=ResultSensitivity.PRIVATE), _admin_read("media.classification.policy.history", sensitivity=ResultSensitivity.PRIVATE), _write("media.classification.policy.update", recovery=RecoveryMode.TRANSACTION), _write("media.classification.policy.rollback", recovery=RecoveryMode.TRANSACTION), _public_read("media.episode_groups"), _public_read("media.episode_group.seasons"), _public_read("media.seasons"), _spec("search.title", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, recovery=_IDEMPOTENT), _spec("search.recommend", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, recovery=_IDEMPOTENT), _spec("subtitle.search.title", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, recovery=_IDEMPOTENT), _spec("subtitle.search.media", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, recovery=_IDEMPOTENT), _write("site.add", recovery=RecoveryMode.TRANSACTION, sensitivity=ResultSensitivity.PRIVATE), _write( "site.delete", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=_DELETE_RECOVERABLE, ), _spec("site.auth.options"), _spec( "site.authenticate", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM, recovery=RecoveryMode.RECONCILE, result_sensitivity=ResultSensitivity.PRIVATE, ), _spec( "site.cookiecloud.sync", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM, recovery=RecoveryMode.RECONCILE, ), _write("site.cookie.set", sensitivity=ResultSensitivity.PRIVATE), _write( "site.reset", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=RecoveryMode.RECONCILE, ), _write("site.priorities.update", recovery=RecoveryMode.TRANSACTION), _spec( "site.userdata.refresh", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM, recovery=_IDEMPOTENT, result_sensitivity=ResultSensitivity.PRIVATE, ), _admin_read("site.userdata.latest", sensitivity=ResultSensitivity.PRIVATE), _spec("site.category"), _spec( "site.resource", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, recovery=_IDEMPOTENT, result_sensitivity=ResultSensitivity.PRIVATE, ), _admin_read("site.searchable", sensitivity=ResultSensitivity.PRIVATE), _admin_read("site.rss", sensitivity=ResultSensitivity.PRIVATE), _spec("site.statistics", result_sensitivity=ResultSensitivity.PRIVATE), _spec("site.statistic", result_sensitivity=ResultSensitivity.PRIVATE), _admin_read("site.mapping", sensitivity=ResultSensitivity.PRIVATE), _admin_read("site.supporting"), _spec("subscription.get"), _spec("subscription.find"), _user_write( "subscription.delete_by_media", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=_DELETE_RECOVERABLE, ), _user_write("subscription.status.update"), _user_write("subscription.reset"), _user_write("subscription.search_all", effect=ActionEffect.EXTERNAL_SIDE_EFFECT), _spec( "subscription.refresh", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM, recovery=RecoveryMode.RECONCILE, ), _spec( "subscription.metadata.refresh", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM, recovery=RecoveryMode.RECONCILE, ), _user_write( "subscription.history.delete", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=_DELETE_RECOVERABLE, ), _spec("subscription.user.list"), _spec("subscription.files", result_sensitivity=ResultSensitivity.PRIVATE), _spec("subscription.execution.list", result_sensitivity=ResultSensitivity.PRIVATE), _spec("subscription.execution.get", result_sensitivity=ResultSensitivity.PRIVATE), _user_write( "subscription.execution.cancel", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, recovery=RecoveryMode.RECONCILE, sensitivity=ResultSensitivity.PRIVATE, ), _user_write("subscription.share", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, recovery=RecoveryMode.RECONCILE), _write( "subscription.share.delete", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, recovery=RecoveryMode.RECONCILE, ), _user_write("subscription.fork", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, recovery=RecoveryMode.RECONCILE), _admin_read("subscription.follow.list"), _write("subscription.follow.add"), _write("subscription.follow.delete"), _public_read("subscription.share.statistics"), _spec( "storage.manage", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM, recovery=RecoveryMode.RECONCILE, result_sensitivity=ResultSensitivity.PRIVATE, ), _write("storage.mkdir", recovery=RecoveryMode.TRANSACTION), _write("storage.rename", recovery=RecoveryMode.RECONCILE), _write( "storage.delete", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=RecoveryMode.MANUAL_ONLY, ), _spec("transfer.queue", result_sensitivity=ResultSensitivity.PRIVATE), _write( "transfer.queue.delete", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=RecoveryMode.RECONCILE, ), _admin_read("transfer.name"), _admin_read("transfer.target_path", sensitivity=ResultSensitivity.PRIVATE), _admin_read("transfer.manual_history", sensitivity=ResultSensitivity.PRIVATE), _admin_read("transfer.episode_format.recommend", sensitivity=ResultSensitivity.PRIVATE), _admin_read("transfer.manual_reviews", sensitivity=ResultSensitivity.PRIVATE), _admin_read("transfer.manual_review", sensitivity=ResultSensitivity.PRIVATE), _write("transfer.manual_review.resolve", recovery=RecoveryMode.RECONCILE), _spec( "transfer.history.redo", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM, recovery=RecoveryMode.RECONCILE, ), _spec( "transfer.history.redo_batch", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM, recovery=RecoveryMode.RECONCILE, ), _spec( "transfer.history.replan", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM, recovery=RecoveryMode.RECONCILE, ), _write( "transfer.history.clear", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=RecoveryMode.MANUAL_ONLY, ), _write("workflow.create", recovery=RecoveryMode.TRANSACTION), _admin_read("workflow.get", sensitivity=ResultSensitivity.PRIVATE), _write("workflow.update", recovery=RecoveryMode.TRANSACTION), _write("workflow.delete", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=_DELETE_RECOVERABLE), _admin_read("workflow.actions"), _admin_read("workflow.event_types"), _admin_read("workflow.plugin.actions"), _write("workflow.start"), _write("workflow.pause"), _write("workflow.reset", recovery=RecoveryMode.TRANSACTION), _spec("workflow.shares", required_role=_ADMIN), _spec( "workflow.share", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM, recovery=RecoveryMode.RECONCILE, ), _spec( "workflow.share.delete", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM, recovery=RecoveryMode.RECONCILE, ), _spec( "workflow.fork", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM, recovery=RecoveryMode.RECONCILE, ), _admin_read("torrent.cache.get", sensitivity=ResultSensitivity.PRIVATE), _write("torrent.cache.delete", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=RecoveryMode.NONE), _write("torrent.cache.clear", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=RecoveryMode.NONE), _spec( "torrent.cache.refresh", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM, recovery=RecoveryMode.RECONCILE, ), _write("torrent.cache.reidentify", recovery=RecoveryMode.RECONCILE), _admin_read("database.backups.list", sensitivity=ResultSensitivity.PRIVATE), _spec( "database.backups.create", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM, recovery=RecoveryMode.RECONCILE, result_sensitivity=ResultSensitivity.PRIVATE, ), _admin_read("database.backups.verify", sensitivity=ResultSensitivity.PRIVATE), _write( "database.backups.delete", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=RecoveryMode.MANUAL_ONLY, ), _spec("filter.test", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, recovery=_IDEMPOTENT), _spec("system.network.targets"), _spec("system.network.test", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, recovery=_IDEMPOTENT), _spec("system.module.list"), _spec("system.module.catalog"), _admin_read("system.module.settings"), _spec("system.module.test", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, recovery=_IDEMPOTENT), _spec( "plugin.market.sync_wiki", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM, recovery=RecoveryMode.RECONCILE, ), _admin_read("plugin.runtime.status", sensitivity=ResultSensitivity.PRIVATE), _admin_read("plugin.history"), _admin_read("plugin.releases"), _admin_read("plugin.ratings", sensitivity=ResultSensitivity.PRIVATE), _admin_read("plugin.rating", sensitivity=ResultSensitivity.PRIVATE), _write("plugin.rating.submit", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, recovery=_IDEMPOTENT, sensitivity=ResultSensitivity.PRIVATE), _public_read("plugin.statistics"), _write( "plugin.reset", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=RecoveryMode.MANUAL_ONLY, ), _admin_read("plugin.clone.restorable", sensitivity=ResultSensitivity.PRIVATE), _write("plugin.clone", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, recovery=RecoveryMode.RECONCILE), _spec("config.user.get", result_sensitivity=ResultSensitivity.PRIVATE), _spec("config.public.get"), _spec("system.usage.statistics", result_sensitivity=ResultSensitivity.PRIVATE), _admin_read("plugin.folders.get", sensitivity=ResultSensitivity.PRIVATE), _write("plugin.folders.update", recovery=RecoveryMode.TRANSACTION), _write("plugin.folder.create", recovery=RecoveryMode.TRANSACTION), _write("plugin.folder.update", recovery=RecoveryMode.TRANSACTION), _write("plugin.folder.delete", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=_DELETE_RECOVERABLE), _write("plugin.folder.plugins.update", recovery=RecoveryMode.TRANSACTION), _write("plugin.folder.plugin.assign", recovery=RecoveryMode.TRANSACTION), _write("plugin.folder.plugin.remove", recovery=RecoveryMode.TRANSACTION), _admin_read("plugin.loglevel.get", sensitivity=ResultSensitivity.PRIVATE), _write("plugin.loglevel.set"), _write("plugin.loglevel.clear"), _write("plugin.default_target.set"), _write("plugin.default_target.clear"), _write("plugin.instance.set_enabled"), # 彻底清理按勾选范围真删用户数据且不可回滚,与重置同档:要确认、且只能人工补救 _write( "plugin.instance.purge", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=RecoveryMode.MANUAL_ONLY, ), ) API_OPERATION_SPECS: tuple[ApiOperationSpec, ...] = ( *API_FIRST_BATCH_OPERATION_SPECS, *API_PARITY_OPERATION_SPECS, *API_MUSIC_OPERATION_SPECS, *API_SYSTEM_OPERATION_SPECS, *API_EXTENDED_OPERATION_SPECS, ) API_OPERATION_BY_ID = {spec.operation_id: spec for spec in API_OPERATION_SPECS} # 身份范围是唯一事实源;不能按 safe_read 批量放行实例、文件或用户数据。 API_UNBOUND_READ_OPERATIONS = frozenset( spec.operation_id for spec in API_OPERATION_SPECS if spec.identity_scope is ApiIdentityScope.PUBLIC ) # 所有路径均由宿主注册,调用方只能传路径参数、查询参数和 JSON body。 API_OPERATION_ROUTES: dict[str, ApiOperationRoute] = { "media.search": ApiOperationRoute("GET", "/api/v1/media/search"), "media.person.search": ApiOperationRoute("GET", "/api/v1/media/search"), "media.person.credits": ApiOperationRoute("GET", "/api/v1/{source}/person/credits/{person_id}"), "media.recognize": ApiOperationRoute("GET", "/api/v1/media/recognize"), "media.scrape": ApiOperationRoute("POST", "/api/v1/media/scrape/{storage}"), "media.episode_schedule": ApiOperationRoute("GET", "/api/v1/tmdb/{tmdbid}/{season}"), "media.detail": ApiOperationRoute("GET", "/api/v1/media/{media_id}"), "music.recognize": ApiOperationRoute("POST", "/api/v1/music/recognize"), "music.explore": ApiOperationRoute("GET", "/api/v1/music/explore"), "music.album.get": ApiOperationRoute("GET", "/api/v1/music/album/{album_id}"), "music.album.related": ApiOperationRoute("GET", "/api/v1/music/album/{album_id}/related"), "music.artist.get": ApiOperationRoute("GET", "/api/v1/music/artist/{artist_id}"), "music.artist.albums": ApiOperationRoute("GET", "/api/v1/music/artist/{artist_id}/albums"), "music.artist.related": ApiOperationRoute("GET", "/api/v1/music/artist/{artist_id}/related"), "music.cache.get": ApiOperationRoute("GET", "/api/v1/music/cache"), "music.cache.delete": ApiOperationRoute("DELETE", "/api/v1/music/cache/{cache_key}"), "music.cache.clear": ApiOperationRoute("DELETE", "/api/v1/music/cache"), "subscription.add": ApiOperationRoute("POST", "/api/v1/subscribe/"), "subscription.update": ApiOperationRoute("PUT", "/api/v1/subscribe/"), "subscription.search": ApiOperationRoute("POST", "/api/v1/subscribe/search/{subscribe_id}"), "subscription.list": ApiOperationRoute("GET", "/api/v1/subscribe/"), "subscription.shares": ApiOperationRoute("GET", "/api/v1/subscribe/shares"), "subscription.popular": ApiOperationRoute("GET", "/api/v1/subscribe/popular"), "subscription.history": ApiOperationRoute("GET", "/api/v1/subscribe/history/{mtype}"), "subscription.delete": ApiOperationRoute("DELETE", "/api/v1/subscribe/{subscribe_id}"), "download.add": ApiOperationRoute("POST", "/api/v1/download/add"), "download.artist_collection": ApiOperationRoute("POST", "/api/v1/download/artist-collection"), "download.tasks.active": ApiOperationRoute("GET", "/api/v1/download/"), "download.clients": ApiOperationRoute("GET", "/api/v1/download/clients"), "download.paths": ApiOperationRoute("GET", "/api/v1/download/paths"), "download.history.list": ApiOperationRoute("GET", "/api/v1/history/download"), "download.history.delete": ApiOperationRoute("DELETE", "/api/v1/history/download"), "transfer.history.delete": ApiOperationRoute("DELETE", "/api/v1/history/transfer"), "site.list": ApiOperationRoute("GET", "/api/v1/site/agent"), "site.update": ApiOperationRoute("PUT", "/api/v1/site/"), "site.userdata": ApiOperationRoute("GET", "/api/v1/site/userdata/{site_id}"), "site.test": ApiOperationRoute("GET", "/api/v1/site/test/{site_id}"), "site.cookie.update": ApiOperationRoute("POST", "/api/v1/site/cookie/{site_id}"), "recommendation.list": ApiOperationRoute("GET", "/api/v1/recommend/agent"), "library.exists": ApiOperationRoute("GET", "/api/v1/mediaserver/exists"), "library.latest": ApiOperationRoute("GET", "/api/v1/mediaserver/latest"), "storage.settings": ApiOperationRoute("GET", "/api/v1/storage/directories"), "storage.list": ApiOperationRoute("POST", "/api/v1/storage/agent/list"), "transfer.history": ApiOperationRoute("GET", "/api/v1/history/transfer"), "transfer.history.register": ApiOperationRoute("POST", "/api/v1/history/transfer"), "transfer.file": ApiOperationRoute("POST", "/api/v1/transfer/manual"), "scheduler.list": ApiOperationRoute("GET", "/api/v1/dashboard/schedule"), "scheduler.run": ApiOperationRoute("GET", "/api/v1/system/runscheduler"), "workflow.list": ApiOperationRoute("GET", "/api/v1/workflow/agent"), "workflow.run": ApiOperationRoute("POST", "/api/v1/workflow/{workflow_id}/run"), "plugin.installed": ApiOperationRoute("GET", "/api/v1/plugin/"), "plugin.market": ApiOperationRoute("GET", "/api/v1/plugin/"), "plugin.capabilities": ApiOperationRoute("GET", "/api/v1/plugin/runtime/capabilities"), "plugin.config.get": ApiOperationRoute("GET", "/api/v1/plugin/form/{plugin_id}"), "plugin.config.update": ApiOperationRoute("PUT", "/api/v1/plugin/{plugin_id}"), "plugin.source.options": ApiOperationRoute("GET", "/api/v1/plugin/source/{plugin_id}"), "plugin.source.install": ApiOperationRoute("POST", "/api/v1/plugin/source/{plugin_id}/install"), "plugin.source.change": ApiOperationRoute("POST", "/api/v1/plugin/source/{plugin_id}"), "plugin.reload": ApiOperationRoute("POST", "/api/v1/plugin/reload/{plugin_id}"), "plugin.install": ApiOperationRoute("GET", "/api/v1/plugin/install/{plugin_id}"), "plugin.uninstall": ApiOperationRoute("DELETE", "/api/v1/plugin/{plugin_id}"), "slash.list": ApiOperationRoute("GET", "/api/v1/message/agent/commands"), "config.identifiers.get": ApiOperationRoute("GET", "/api/v1/system/identifiers"), "config.identifiers.update": ApiOperationRoute("POST", "/api/v1/system/identifiers"), "search.torrents": ApiOperationRoute("GET", "/api/v1/search/media/{media_id}"), "search.results": ApiOperationRoute("GET", "/api/v1/search/last/context"), "filter.builtin": ApiOperationRoute("GET", "/api/v1/rule/builtin"), "filter.custom": ApiOperationRoute("GET", "/api/v1/rule/custom"), "filter.groups": ApiOperationRoute("GET", "/api/v1/rule/groups"), "filter.custom.add": ApiOperationRoute("POST", "/api/v1/rule/custom"), "filter.custom.update": ApiOperationRoute("PUT", "/api/v1/rule/custom/{rule_id}"), "filter.custom.delete": ApiOperationRoute("DELETE", "/api/v1/rule/custom/{rule_id}"), "filter.group.add": ApiOperationRoute("POST", "/api/v1/rule/groups"), "filter.group.update": ApiOperationRoute("PUT", "/api/v1/rule/groups/{name}"), "filter.group.delete": ApiOperationRoute("DELETE", "/api/v1/rule/groups/{name}"), "plugin.data": ApiOperationRoute("GET", "/api/v1/plugin/runtime/{plugin_id}/data"), "config.system.list": ApiOperationRoute("GET", "/api/v1/system/settings/catalog"), "config.system.describe": ApiOperationRoute( "GET", "/api/v1/system/settings/describe/{setting_key}" ), "config.system.get": ApiOperationRoute("GET", "/api/v1/system/settings"), "config.system.update": ApiOperationRoute("POST", "/api/v1/system/settings"), "slash.run": ApiOperationRoute("POST", "/api/v1/message/agent/commands/run"), "system.versions": ApiOperationRoute("GET", "/api/v1/system/versions"), "system.restart": ApiOperationRoute("GET", "/api/v1/system/restart"), "system.update.status": ApiOperationRoute("GET", "/api/v1/system/update/status"), "system.update.check": ApiOperationRoute("POST", "/api/v1/system/update/check"), "system.update.download": ApiOperationRoute("POST", "/api/v1/system/update/download"), "system.update.install": ApiOperationRoute("POST", "/api/v1/system/update/install"), "system.upgrade.dev": ApiOperationRoute("POST", "/api/v1/system/upgrade"), "dashboard.media.statistics": ApiOperationRoute("GET", "/api/v1/dashboard/statistic"), "dashboard.storage": ApiOperationRoute("GET", "/api/v1/dashboard/storage"), "dashboard.processes": ApiOperationRoute("GET", "/api/v1/dashboard/processes"), "dashboard.system": ApiOperationRoute("GET", "/api/v1/dashboard/system"), "dashboard.downloader": ApiOperationRoute("GET", "/api/v1/dashboard/downloader"), "scheduler.progress": ApiOperationRoute("GET", "/api/v1/dashboard/schedule/{job_id}/progress"), "dashboard.transfer.statistics": ApiOperationRoute("GET", "/api/v1/dashboard/transfer"), "dashboard.cpu": ApiOperationRoute("GET", "/api/v1/dashboard/cpu"), "dashboard.memory": ApiOperationRoute("GET", "/api/v1/dashboard/memory"), "dashboard.network": ApiOperationRoute("GET", "/api/v1/dashboard/network"), "media.sources": ApiOperationRoute("GET", "/api/v1/media/source"), "media.recognize_file": ApiOperationRoute("GET", "/api/v1/media/recognize_file"), "media.cache.get": ApiOperationRoute("GET", "/api/v1/tmdb/cache"), "media.cache.delete": ApiOperationRoute("DELETE", "/api/v1/tmdb/cache/{cache_key}"), "media.cache.clear": ApiOperationRoute("DELETE", "/api/v1/tmdb/cache"), "media.classification.fields": ApiOperationRoute("GET", "/api/v1/media/classification/fields"), "media.classification.policy.get": ApiOperationRoute("GET", "/api/v1/media/classification/policy"), "media.classification.policy.update": ApiOperationRoute("PUT", "/api/v1/media/classification/policy"), "media.classification.policy.validate": ApiOperationRoute("POST", "/api/v1/media/classification/validate"), "media.classification.policy.preview": ApiOperationRoute("POST", "/api/v1/media/classification/preview"), "media.classification.policy.impact": ApiOperationRoute("POST", "/api/v1/media/classification/impact"), "media.classification.policy.history": ApiOperationRoute("GET", "/api/v1/media/classification/history"), "media.classification.policy.rollback": ApiOperationRoute( "POST", "/api/v1/media/classification/rollback/{revision}" ), "media.episode_groups": ApiOperationRoute("GET", "/api/v1/media/groups/{tmdbid}"), "media.episode_group.seasons": ApiOperationRoute("GET", "/api/v1/media/group/seasons/{episode_group}"), "media.seasons": ApiOperationRoute("GET", "/api/v1/media/seasons"), "search.title": ApiOperationRoute("GET", "/api/v1/search/title"), "search.recommend": ApiOperationRoute("POST", "/api/v1/search/recommend"), "subtitle.search.title": ApiOperationRoute("GET", "/api/v1/search/subtitle/title"), "subtitle.search.media": ApiOperationRoute("GET", "/api/v1/search/subtitle/media/{media_id}"), "site.add": ApiOperationRoute("POST", "/api/v1/site/"), "site.delete": ApiOperationRoute("DELETE", "/api/v1/site/{site_id}"), "site.auth.options": ApiOperationRoute("GET", "/api/v1/site/auth"), "site.authenticate": ApiOperationRoute("POST", "/api/v1/site/auth"), "site.cookiecloud.sync": ApiOperationRoute("POST", "/api/v1/site/cookiecloud"), "site.cookie.set": ApiOperationRoute("POST", "/api/v1/site/cookie/{site_id}/set"), "site.reset": ApiOperationRoute("POST", "/api/v1/site/reset"), "site.priorities.update": ApiOperationRoute("POST", "/api/v1/site/priorities"), "site.userdata.refresh": ApiOperationRoute("POST", "/api/v1/site/userdata/{site_id}"), "site.userdata.latest": ApiOperationRoute("GET", "/api/v1/site/userdata/latest"), "site.category": ApiOperationRoute("GET", "/api/v1/site/category/{site_id}"), "site.resource": ApiOperationRoute("GET", "/api/v1/site/resource/{site_id}"), "site.searchable": ApiOperationRoute("GET", "/api/v1/site/media/{media_type}"), "site.rss": ApiOperationRoute("GET", "/api/v1/site/rss"), "site.statistics": ApiOperationRoute("GET", "/api/v1/site/statistic"), "site.statistic": ApiOperationRoute("GET", "/api/v1/site/statistic/{site_url}"), "site.mapping": ApiOperationRoute("GET", "/api/v1/site/mapping"), "site.supporting": ApiOperationRoute("GET", "/api/v1/site/supporting"), "subscription.get": ApiOperationRoute("GET", "/api/v1/subscribe/{subscribe_id}"), "subscription.find": ApiOperationRoute("GET", "/api/v1/subscribe/media/{media_id}"), "subscription.delete_by_media": ApiOperationRoute("DELETE", "/api/v1/subscribe/media/{media_id}"), "subscription.status.update": ApiOperationRoute("PUT", "/api/v1/subscribe/status/{subid}"), "subscription.reset": ApiOperationRoute("POST", "/api/v1/subscribe/reset/{subid}"), "subscription.search_all": ApiOperationRoute("POST", "/api/v1/subscribe/search"), "subscription.refresh": ApiOperationRoute("POST", "/api/v1/subscribe/refresh"), "subscription.metadata.refresh": ApiOperationRoute("POST", "/api/v1/subscribe/check"), "subscription.history.delete": ApiOperationRoute("DELETE", "/api/v1/subscribe/history/{history_id}"), "subscription.user.list": ApiOperationRoute("GET", "/api/v1/subscribe/user/{username}"), "subscription.files": ApiOperationRoute("GET", "/api/v1/subscribe/files/{subscribe_id}"), "subscription.execution.list": ApiOperationRoute("GET", "/api/v1/subscribe/execution/batches"), "subscription.execution.get": ApiOperationRoute( "GET", "/api/v1/subscribe/execution/batches/{batch_id}" ), "subscription.execution.cancel": ApiOperationRoute( "PUT", "/api/v1/subscribe/execution/batches/{batch_id}/cancel" ), "subscription.share": ApiOperationRoute("POST", "/api/v1/subscribe/share"), "subscription.share.delete": ApiOperationRoute("DELETE", "/api/v1/subscribe/share/{share_id}"), "subscription.fork": ApiOperationRoute("POST", "/api/v1/subscribe/fork"), "subscription.follow.list": ApiOperationRoute("GET", "/api/v1/subscribe/follow"), "subscription.follow.add": ApiOperationRoute("POST", "/api/v1/subscribe/follow"), "subscription.follow.delete": ApiOperationRoute("DELETE", "/api/v1/subscribe/follow"), "subscription.share.statistics": ApiOperationRoute("GET", "/api/v1/subscribe/share/statistics"), "storage.manage": ApiOperationRoute("POST", "/api/v1/storage/manage"), "storage.mkdir": ApiOperationRoute("POST", "/api/v1/storage/mkdir"), "storage.rename": ApiOperationRoute("POST", "/api/v1/storage/rename"), "storage.delete": ApiOperationRoute("POST", "/api/v1/storage/delete"), "transfer.queue": ApiOperationRoute("GET", "/api/v1/transfer/queue"), "transfer.queue.delete": ApiOperationRoute("DELETE", "/api/v1/transfer/queue"), "transfer.name": ApiOperationRoute("GET", "/api/v1/transfer/name"), "transfer.target_path": ApiOperationRoute("POST", "/api/v1/transfer/manual/target-path"), "transfer.manual_history": ApiOperationRoute("POST", "/api/v1/transfer/manual/history"), "transfer.episode_format.recommend": ApiOperationRoute("POST", "/api/v1/transfer/episode-format/recommend"), "transfer.manual_reviews": ApiOperationRoute("GET", "/api/v1/transfer/tasks/manual-reviews"), "transfer.manual_review": ApiOperationRoute("GET", "/api/v1/transfer/tasks/{task_id}/manual-review"), "transfer.manual_review.resolve": ApiOperationRoute("POST", "/api/v1/transfer/tasks/{task_id}/manual-review"), "transfer.history.redo": ApiOperationRoute("POST", "/api/v1/history/transfer/{history_id}/ai-redo"), "transfer.history.redo_batch": ApiOperationRoute("POST", "/api/v1/history/transfer/ai-redo"), "transfer.history.replan": ApiOperationRoute("POST", "/api/v1/history/transfer/{history_id}/replan"), "transfer.history.clear": ApiOperationRoute("DELETE", "/api/v1/history/transfer/all"), "workflow.create": ApiOperationRoute("POST", "/api/v1/workflow/"), "workflow.get": ApiOperationRoute("GET", "/api/v1/workflow/{workflow_id}"), "workflow.update": ApiOperationRoute("PUT", "/api/v1/workflow/{workflow_id}"), "workflow.delete": ApiOperationRoute("DELETE", "/api/v1/workflow/{workflow_id}"), "workflow.actions": ApiOperationRoute("GET", "/api/v1/workflow/actions"), "workflow.event_types": ApiOperationRoute("GET", "/api/v1/workflow/event_types"), "workflow.plugin.actions": ApiOperationRoute("GET", "/api/v1/workflow/plugin/actions"), "workflow.start": ApiOperationRoute("POST", "/api/v1/workflow/{workflow_id}/start"), "workflow.pause": ApiOperationRoute("POST", "/api/v1/workflow/{workflow_id}/pause"), "workflow.reset": ApiOperationRoute("POST", "/api/v1/workflow/{workflow_id}/reset"), "workflow.shares": ApiOperationRoute("GET", "/api/v1/workflow/shares"), "workflow.share": ApiOperationRoute("POST", "/api/v1/workflow/share"), "workflow.share.delete": ApiOperationRoute("DELETE", "/api/v1/workflow/share/{share_id}"), "workflow.fork": ApiOperationRoute("POST", "/api/v1/workflow/fork"), "torrent.cache.get": ApiOperationRoute("GET", "/api/v1/torrent/cache"), "torrent.cache.delete": ApiOperationRoute("DELETE", "/api/v1/torrent/cache/{domain}/{torrent_hash}"), "torrent.cache.clear": ApiOperationRoute("DELETE", "/api/v1/torrent/cache"), "torrent.cache.refresh": ApiOperationRoute("POST", "/api/v1/torrent/cache/refresh"), "torrent.cache.reidentify": ApiOperationRoute("POST", "/api/v1/torrent/cache/reidentify/{domain}/{torrent_hash}"), "database.backups.list": ApiOperationRoute("GET", "/api/v1/system/database/backups"), "database.backups.create": ApiOperationRoute("POST", "/api/v1/system/database/backups"), "database.backups.verify": ApiOperationRoute("POST", "/api/v1/system/database/backups/{name}/verify"), "database.backups.delete": ApiOperationRoute("DELETE", "/api/v1/system/database/backups/{name}"), "filter.test": ApiOperationRoute("GET", "/api/v1/system/ruletest"), "system.network.targets": ApiOperationRoute("GET", "/api/v1/system/nettest/targets"), "system.network.test": ApiOperationRoute("GET", "/api/v1/system/nettest"), "system.module.list": ApiOperationRoute("GET", "/api/v1/system/modulelist"), "system.module.catalog": ApiOperationRoute("GET", "/api/v1/system/module-catalog"), "system.module.settings": ApiOperationRoute("GET", "/api/v1/system/module-settings"), "system.module.test": ApiOperationRoute("GET", "/api/v1/system/moduletest/{moduleid}"), "plugin.market.sync_wiki": ApiOperationRoute("POST", "/api/v1/system/setting/PLUGIN_MARKET/sync-wiki"), "plugin.runtime.status": ApiOperationRoute("GET", "/api/v1/plugin/runtime"), "plugin.history": ApiOperationRoute("GET", "/api/v1/plugin/history/{plugin_id}"), "plugin.releases": ApiOperationRoute("GET", "/api/v1/plugin/releases/{plugin_id}"), "plugin.ratings": ApiOperationRoute("GET", "/api/v1/plugin/rating"), "plugin.rating": ApiOperationRoute("GET", "/api/v1/plugin/rating/{plugin_id}"), "plugin.rating.submit": ApiOperationRoute("POST", "/api/v1/plugin/rating/{plugin_id}"), "plugin.statistics": ApiOperationRoute("GET", "/api/v1/plugin/statistic"), "plugin.reset": ApiOperationRoute("GET", "/api/v1/plugin/reset/{plugin_id}"), "plugin.clone": ApiOperationRoute("POST", "/api/v1/plugin/clone/{plugin_id}"), "plugin.clone.restorable": ApiOperationRoute( "GET", "/api/v1/plugin/clone/{plugin_id}/restorable" ), "config.user.get": ApiOperationRoute("GET", "/api/v1/system/global/user"), "config.public.get": ApiOperationRoute("GET", "/api/v1/system/setting/public/{key}"), "system.usage.statistics": ApiOperationRoute("GET", "/api/v1/system/usage/statistic"), "plugin.folders.get": ApiOperationRoute("GET", "/api/v1/plugin/folders"), "plugin.folders.update": ApiOperationRoute("POST", "/api/v1/plugin/folders"), "plugin.folder.create": ApiOperationRoute("POST", "/api/v1/plugin/folders/{folder_name}"), "plugin.folder.update": ApiOperationRoute("PATCH", "/api/v1/plugin/folders/{folder_name}"), "plugin.folder.delete": ApiOperationRoute("DELETE", "/api/v1/plugin/folders/{folder_name}"), "plugin.folder.plugins.update": ApiOperationRoute("PUT", "/api/v1/plugin/folders/{folder_name}/plugins"), "plugin.folder.plugin.assign": ApiOperationRoute( "PUT", "/api/v1/plugin/folders/{folder_name}/plugins/{plugin_id}", ), "plugin.folder.plugin.remove": ApiOperationRoute( "DELETE", "/api/v1/plugin/folders/{folder_name}/plugins/{plugin_id}", ), "plugin.loglevel.get": ApiOperationRoute("GET", "/api/v1/plugin/loglevel/{plugin_id}"), "plugin.loglevel.set": ApiOperationRoute( "PUT", "/api/v1/plugin/loglevel/{plugin_id}/{instance_id}" ), "plugin.loglevel.clear": ApiOperationRoute( "DELETE", "/api/v1/plugin/loglevel/{plugin_id}/{instance_id}" ), "plugin.default_target.set": ApiOperationRoute( "PUT", "/api/v1/plugin/instances/{plugin_id}/{instance_id}/default_target" ), "plugin.default_target.clear": ApiOperationRoute( "DELETE", "/api/v1/plugin/instances/{plugin_id}/{instance_id}/default_target" ), "plugin.instance.set_enabled": ApiOperationRoute( "POST", "/api/v1/plugin/instance/{instance_id}/enabled" ), "plugin.instance.purge": ApiOperationRoute( "POST", "/api/v1/plugin/instance/{instance_id}/purge" ), } def resolve_api_operation(operation_id: str) -> Optional[ApiOperationSpec]: """ 按稳定 operation ID 查找 API 操作。 """ return API_OPERATION_BY_ID.get(operation_id) def resolve_api_route(operation_id: str) -> Optional[ApiOperationRoute]: """ 按稳定 operation ID 查找固定 API 路由。 """ return API_OPERATION_ROUTES.get(operation_id) def list_api_operation_ids() -> tuple[str, ...]: """ 返回按注册顺序排列的 API 操作 ID。 """ return tuple(spec.operation_id for spec in API_OPERATION_SPECS) __all__ = [ "API_UNBOUND_READ_OPERATIONS", "API_FIRST_BATCH_OPERATION_SPECS", "API_EXTENDED_OPERATION_SPECS", "API_MUSIC_OPERATION_SPECS", "API_OPERATION_BY_ID", "API_OPERATION_ROUTES", "API_OPERATION_SPECS", "API_PARITY_OPERATION_SPECS", "API_SYSTEM_OPERATION_SPECS", "ApiIdentityScope", "ApiOperationRoute", "ApiOperationSpec", "list_api_operation_ids", "resolve_api_operation", "resolve_api_route", ]