mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-10-31 20:34:48 +08:00
976 lines
46 KiB
Python
976 lines
46 KiB
Python
"""
|
|
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),
|
|
_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.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",
|
|
]
|