Files

2013 lines
77 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
字符串模块方法协议的可检查契约清单。
"""
from __future__ import annotations
import inspect
from collections.abc import Callable
from dataclasses import dataclass
from enum import StrEnum
from typing import Any, Protocol
from pydantic import BaseModel
class ModuleResultAggregation(StrEnum):
"""
描述多模块结果沿调用链的兼容聚合方式。
"""
# 沿用历史模块方法的结果聚合规则
LEGACY = "legacy"
# 采用首个非空结果并停止后续调用
FIRST_NON_EMPTY = "first_non_empty"
# 按调用顺序合并列表结果
ORDERED_LIST_MERGE = "ordered_list_merge"
# 按调用顺序合并映射结果
ORDERED_MAPPING_MERGE = "ordered_mapping_merge"
# 将上一结果作为下一调用的输入
PIPELINE_RELAY = "pipeline_relay"
# 向各实现方分发同一调用
FAN_OUT = "fan_out"
class ModuleResultShape(StrEnum):
"""
描述模块 provider 返回值的基础 Python 形状或模型对象。
MODEL 用于 Pydantic 分页等模型对象,MAPPING 仍只接受字典。
"""
# 接受任意结果形状
ANY = "any"
# 结果应为列表
LIST = "list"
# 结果应为字符串
STRING = "string"
# 结果应为键值映射
MAPPING = "mapping"
MODEL = "model"
# 结果应为布尔值
BOOLEAN = "boolean"
# 结果应为字节序列
BYTES = "bytes"
class ModuleExecutionMode(StrEnum):
"""
描述 provider 可以采用的执行形态。
"""
# 允许同步或异步实现,由调度器适配调用
SYNC_OR_ASYNC = "sync_or_async"
class ModuleErrorPolicy(StrEnum):
"""
描述单个 provider 失败后的兼容处理策略。
"""
# 隔离单个实现方的异常,按聚合规则处理其余实现
ISOLATE_PROVIDER = "isolate_provider"
class ModuleCapability(Protocol):
"""
宿主和新插件可用于声明动态能力的最小 Protocol。
"""
def __call__(self, *args: Any, **kwargs: Any) -> Any:
"""
执行模块能力并返回契约声明的结果。
:param args: 模块方法契约规定的位置参数
:param kwargs: 模块方法契约规定的关键字参数
:return: 该模块方法契约规定的业务结果;异步实现返回供调度器等待的对象
"""
@dataclass(frozen=True, slots=True)
class ModuleMethodContract:
"""
记录模块方法的输入、结果、执行与兼容错误协议。
:param family: 方法所属的业务能力族
:param aggregation: 多个提供方返回结果的聚合策略
:param version: 模块方法协议版本,默认从 1 开始
:param input_contract: 输入参数协议名称
:param result_contract: 返回值业务协议名称
:param result_shape: 用于运行时检查的返回值基础形状
:param required_parameters: 调用该能力必须提供的参数名
:param execution: 允许使用的同步或异步执行形态
:param timeout_policy: 执行预算的归属策略,默认沿用调用方预算
:param error_policy: 单个提供方失败时的隔离策略
:param public_to_plugins: 是否允许插件实现此模块能力
:param supports_sync: 是否支持同步调用入口
:param supports_async: 是否支持异步调用入口
:param plugin_short_circuit: 插件返回有效结果后是否短路宿主模块调用
:param optional_parameters: 兼容调用时可选择传入的参数名
"""
family: str
aggregation: ModuleResultAggregation = ModuleResultAggregation.LEGACY
version: int = 1
input_contract: str = "legacy_args"
result_contract: str = "Any"
result_shape: ModuleResultShape = ModuleResultShape.ANY
required_parameters: tuple[str, ...] = ()
execution: ModuleExecutionMode = ModuleExecutionMode.SYNC_OR_ASYNC
timeout_policy: str = "caller_budget"
error_policy: ModuleErrorPolicy = ModuleErrorPolicy.ISOLATE_PROVIDER
public_to_plugins: bool = True
supports_sync: bool = True
supports_async: bool = True
plugin_short_circuit: bool = True
optional_parameters: tuple[str, ...] = ()
_DEFAULT_CONTRACT = ModuleMethodContract(family="legacy")
# 首批登记高频能力族。方法名仍保持开放字符串,以兼容第三方插件自定义模块能力;
# 未命中项继续使用冻结的 legacy 规则,并由架构快照记录新增调用位置。
_METHOD_CONTRACTS = {
"recognize_media": ModuleMethodContract(
family="media-recognition",
input_contract="MediaRecognitionRequest",
result_contract="MediaInfo | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("meta", "mtype", "media_source", "media_id", "episode_group", "cache"),
),
"match_doubaninfo": ModuleMethodContract(
family="media-recognition",
input_contract="DoubanMatchRequest",
result_contract="dict[str, Any] | None",
result_shape=ModuleResultShape.MAPPING,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("name", "imdbid", "mtype", "year", "season", "raise_exception"),
),
"match_tmdbinfo": ModuleMethodContract(
family="media-recognition",
input_contract="TmdbMatchRequest",
result_contract="dict[str, Any] | None",
result_shape=ModuleResultShape.MAPPING,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("name", "mtype", "year", "season"),
),
"update_recognize_cache": ModuleMethodContract(
family="media-recognition",
input_contract="RecognitionCacheUpdateRequest",
result_contract="bool | None",
result_shape=ModuleResultShape.BOOLEAN,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("meta", "mediainfo"),
),
"search_medias": ModuleMethodContract(
family="media-recognition",
input_contract="MediaSearchRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("meta", "media_source"),
),
"get_media_auxiliary_info": ModuleMethodContract(
family="media-recognition",
input_contract="MediaAuxiliaryInfoRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("mediainfo", "media_source", "metainfo"),
plugin_short_circuit=False,
),
"get_media_classification_facts": ModuleMethodContract(
family="media-classification",
version=1,
input_contract="ClassificationEnrichmentRequest",
result_contract="ClassificationEnrichmentResponse | None",
result_shape=ModuleResultShape.MAPPING,
aggregation=ModuleResultAggregation.FAN_OUT,
required_parameters=("request",),
supports_async=False,
plugin_short_circuit=False,
),
"obtain_images": ModuleMethodContract(
family="media-recognition",
input_contract="MediaInfo",
result_contract="MediaInfo | None",
aggregation=ModuleResultAggregation.PIPELINE_RELAY,
required_parameters=("mediainfo",),
),
"mediaserver_items": ModuleMethodContract(
family="media-server",
input_contract="MediaServerItemsRequest",
result_contract="Iterable[MediaServerItem] | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("server", "library_id", "start_index", "limit"),
optional_parameters=("query",),
),
"mediaserver_iteminfo": ModuleMethodContract(
family="media-server",
input_contract="MediaServerItemRequest",
result_contract="MediaServerItem | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("server", "item_id"),
optional_parameters=("query",),
),
"mediaserver_play_url": ModuleMethodContract(
family="media-server",
input_contract="MediaServerPlayRequest",
result_contract="str | None",
result_shape=ModuleResultShape.STRING,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("server", "item_id"),
),
"mediaserver_tv_episodes": ModuleMethodContract(
family="media-server",
input_contract="MediaServerEpisodesRequest",
result_contract="list[MediaServerPlayItem]",
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("server", "item_id"),
optional_parameters=("query",),
),
"media_exists": ModuleMethodContract(
family="media-server",
input_contract="MediaExistsRequest",
result_contract="ExistMediaInfo | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("mediainfo", "itemid", "server"),
optional_parameters=("query",),
),
"media_statistic": ModuleMethodContract(
family="media-server",
input_contract="MediaStatisticRequest",
result_contract="list[Statistic]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("server",),
),
"mediaserver_image_cookies": ModuleMethodContract(
family="media-server",
input_contract="MediaServerImageCookiesRequest",
result_contract="str | dict | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("server", "image_url"),
),
"mediaserver_items_count": ModuleMethodContract(
family="media-server",
input_contract="MediaServerItemsCountRequest",
result_contract="int | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("server", "library_id"),
),
"mediaserver_latest": ModuleMethodContract(
family="media-server",
input_contract="MediaServerRecentRequest",
result_contract="list[MediaServerPlayItem]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("server", "count", "username"),
optional_parameters=("query",),
),
"mediaserver_latest_images": ModuleMethodContract(
family="media-server",
input_contract="MediaServerRecentImagesRequest",
result_contract="list[str]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("server", "count", "remote", "username"),
optional_parameters=("query",),
),
"mediaserver_librarys": ModuleMethodContract(
family="media-server",
input_contract="MediaServerLibrariesRequest",
result_contract="list[MediaServerLibrary]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("server", "username", "hidden"),
optional_parameters=("query",),
),
"mediaserver_playing": ModuleMethodContract(
family="media-server",
input_contract="MediaServerRecentRequest",
result_contract="list[MediaServerPlayItem]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("server", "count", "username"),
optional_parameters=("query",),
),
"mediaserver_season_episode_ids": ModuleMethodContract(
family="media-server",
input_contract="MediaServerSeasonEpisodesRequest",
result_contract="dict[int, str] | None",
result_shape=ModuleResultShape.MAPPING,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("server", "item_id", "season"),
optional_parameters=("query",),
),
"any_files": ModuleMethodContract(
family="storage",
input_contract="StorageAnyFilesRequest",
result_contract="bool | None",
result_shape=ModuleResultShape.BOOLEAN,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("fileitem", "extensions"),
),
"create_folder": ModuleMethodContract(
family="storage",
input_contract="StorageCreateFolderRequest",
result_contract="FileItem | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("fileitem", "name"),
),
"delete_file": ModuleMethodContract(
family="storage",
input_contract="StorageDeleteRequest",
result_contract="bool | None",
result_shape=ModuleResultShape.BOOLEAN,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("fileitem",),
),
"download_file": ModuleMethodContract(
family="storage",
input_contract="StorageDownloadRequest",
result_contract="FileItem | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("fileitem", "path"),
),
"upload_file": ModuleMethodContract(
family="storage",
input_contract="StorageUploadRequest",
result_contract="FileItem | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("fileitem", "path", "new_name"),
),
"link_file": ModuleMethodContract(
family="storage",
input_contract="StorageLinkRequest",
result_contract="bool | None",
result_shape=ModuleResultShape.BOOLEAN,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("fileitem", "target_file"),
public_to_plugins=False,
),
"list_files": ModuleMethodContract(
family="storage",
input_contract="StorageListRequest",
result_contract="list[FileItem]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("fileitem", "recursion"),
),
"media_files": ModuleMethodContract(
family="storage",
input_contract="MediaFilesRequest",
result_contract="list[FileItem]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("mediainfo",),
),
"get_file_item": ModuleMethodContract(
family="storage",
input_contract="StorageItemRequest",
result_contract="FileItem | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("storage", "path"),
),
"get_file_item_strict": ModuleMethodContract(
family="storage",
input_contract="StorageItemRequest",
result_contract="FileItemQueryResult | None",
result_shape=ModuleResultShape.MODEL,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("storage", "path"),
),
"get_folder": ModuleMethodContract(
family="storage",
input_contract="StorageFolderRequest",
result_contract="FileItem | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("storage", "path"),
),
"get_parent_item": ModuleMethodContract(
family="storage",
input_contract="StorageParentRequest",
result_contract="FileItem | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("fileitem",),
),
"rename_file": ModuleMethodContract(
family="storage",
input_contract="StorageRenameRequest",
result_contract="bool | FileItem",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("fileitem", "name"),
),
"storage_manage": ModuleMethodContract(
family="storage",
input_contract="StorageManageRequest",
result_contract="StorageProviderResult",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("storage", "action"),
),
"snapshot_storage": ModuleMethodContract(
family="storage",
input_contract="StorageSnapshotRequest",
result_contract="dict[str, dict] | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("storage", "path", "last_snapshot_time", "max_depth", "previous_snapshot"),
),
"plan_transfer": ModuleMethodContract(
family="storage",
input_contract="TransferPlanningInput",
result_contract="TransferPlanCheckpoint | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=(
"fileitem",
"meta",
"mediainfo",
"target_directory",
"target_storage",
"target_path",
"transfer_type",
"scrape",
"library_type_folder",
"library_category_folder",
"episodes_info",
"source_oper",
"preview",
"planning_input",
),
public_to_plugins=False,
),
"execute_transfer_plan": ModuleMethodContract(
family="storage",
input_contract="TransferPlanCheckpoint",
result_contract="TransferInfo | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=(
"checkpoint",
"meta",
"mediainfo",
"source_oper",
"target_oper",
),
public_to_plugins=False,
),
"transfer": ModuleMethodContract(
family="storage",
input_contract="TransferRequest",
result_contract="TransferInfo | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=(
"fileitem",
"meta",
"mediainfo",
"target_directory",
"target_storage",
"target_path",
"transfer_type",
"scrape",
"library_type_folder",
"library_category_folder",
"episodes_info",
"source_oper",
"target_oper",
"preview",
),
),
"clear_cache": ModuleMethodContract(
family="category",
input_contract="CacheClearRequest",
result_contract="None",
aggregation=ModuleResultAggregation.FAN_OUT,
plugin_short_circuit=False,
),
"get_search_page_size": ModuleMethodContract(
family="site",
input_contract="SiteSearchPageSizeRequest",
result_contract="int | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("site", "keyword"),
),
"restore_search_torrent": ModuleMethodContract(
family="site",
input_contract="SearchTorrentRestoreRequest",
result_contract="TorrentInfo | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("site", "record"),
public_to_plugins=False,
),
"refresh_userdata": ModuleMethodContract(
family="site",
input_contract="SiteUserDataRequest",
result_contract="SiteUserData | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("site",),
),
"site_subtitle_links": ModuleMethodContract(
family="site",
input_contract="SiteSubtitleLinksRequest",
result_contract="list[str]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("context",),
),
"metadata_img": ModuleMethodContract(
family="metadata",
input_contract="MetadataImageRequest",
result_contract="dict[str, str] | None",
result_shape=ModuleResultShape.MAPPING,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("mediainfo", "season", "episode"),
),
"metadata_nfo": ModuleMethodContract(
family="metadata",
input_contract="MetadataNfoRequest",
result_contract="str | None",
result_shape=ModuleResultShape.STRING,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("meta", "mediainfo", "season", "episode"),
),
"obtain_specific_image": ModuleMethodContract(
family="metadata",
input_contract="SpecificImageRequest",
result_contract="str | None",
result_shape=ModuleResultShape.STRING,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("mediaid", "mtype", "image_type", "image_prefix", "season", "episode"),
),
"recommend_name": ModuleMethodContract(
family="metadata",
input_contract="RecommendNameRequest",
result_contract="str | None",
result_shape=ModuleResultShape.STRING,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("meta", "mediainfo", "episodes_info"),
),
"user_authenticate": ModuleMethodContract(
family="authentication",
input_contract="UserAuthenticationRequest",
result_contract="AuthCredentials | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("credentials",),
),
"tvdb_info": ModuleMethodContract(
family="tvdb",
input_contract="TvdbInfoRequest",
result_contract="dict | None",
result_shape=ModuleResultShape.MAPPING,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("tvdbid",),
),
"tvdb_slug": ModuleMethodContract(
family="tvdb",
input_contract="TvdbInfoRequest",
result_contract="str | None",
result_shape=ModuleResultShape.STRING,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("tvdbid",),
),
"search_tvdb": ModuleMethodContract(
family="tvdb",
input_contract="TvdbSearchRequest",
result_contract="list[dict]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("title",),
),
"movie_hot": ModuleMethodContract(
family="media-discovery",
input_contract="MediaRankingRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("page", "count"),
),
"movie_showing": ModuleMethodContract(
family="media-discovery",
input_contract="MediaRankingRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("page", "count"),
),
"movie_top250": ModuleMethodContract(
family="media-discovery",
input_contract="MediaRankingRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("page", "count"),
),
"search_collections": ModuleMethodContract(
family="media-discovery",
input_contract="CollectionSearchRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("name", "media_source"),
),
"search_persons": ModuleMethodContract(
family="media-discovery",
input_contract="PersonSearchRequest",
result_contract="list[MediaPerson | MusicArtistInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("name", "media_source"),
),
"search_subtitles": ModuleMethodContract(
family="media-discovery",
input_contract="SubtitleSearchRequest",
result_contract="list[SubtitleInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("site", "keyword", "page"),
),
"search_torrents": ModuleMethodContract(
family="media-discovery",
input_contract="TorrentSearchRequest",
result_contract="list[TorrentInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("site", "keyword", "mtype", "page"),
),
"tv_animation": ModuleMethodContract(
family="media-discovery",
input_contract="MediaRankingRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("page", "count"),
),
"tv_hot": ModuleMethodContract(
family="media-discovery",
input_contract="MediaRankingRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("page", "count"),
),
"tv_weekly_chinese": ModuleMethodContract(
family="media-discovery",
input_contract="MediaRankingRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("page", "count"),
),
"tv_weekly_global": ModuleMethodContract(
family="media-discovery",
input_contract="MediaRankingRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("page", "count"),
),
"douban_discover": ModuleMethodContract(
family="douban",
input_contract="DoubanDiscoverRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("mtype", "sort", "tags", "page", "count"),
),
"douban_info": ModuleMethodContract(
family="douban",
input_contract="DoubanInfoRequest",
result_contract="dict | None",
result_shape=ModuleResultShape.MAPPING,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("doubanid", "mtype", "raise_exception"),
),
"douban_movie_credits": ModuleMethodContract(
family="douban",
input_contract="DoubanMediaRequest",
result_contract="list[MediaPerson]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("doubanid",),
),
"douban_movie_recommend": ModuleMethodContract(
family="douban",
input_contract="DoubanMediaRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("doubanid",),
),
"douban_person_credits": ModuleMethodContract(
family="douban",
input_contract="DoubanPersonCreditsRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("person_id", "page"),
),
"douban_person_detail": ModuleMethodContract(
family="douban",
input_contract="DoubanPersonRequest",
result_contract="MediaPerson | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("person_id",),
),
"douban_tv_credits": ModuleMethodContract(
family="douban",
input_contract="DoubanMediaRequest",
result_contract="list[MediaPerson]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("doubanid",),
),
"douban_tv_recommend": ModuleMethodContract(
family="douban",
input_contract="DoubanMediaRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("doubanid",),
),
"douban_movie_interests": ModuleMethodContract(
family="douban",
input_contract="DoubanCommentRequest",
result_contract="MediaCommentPage",
result_shape=ModuleResultShape.MODEL,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("doubanid", "start", "count"),
),
"douban_tv_interests": ModuleMethodContract(
family="douban",
input_contract="DoubanCommentRequest",
result_contract="MediaCommentPage",
result_shape=ModuleResultShape.MODEL,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("doubanid", "start", "count"),
),
"douban_movie_reviews": ModuleMethodContract(
family="douban",
input_contract="DoubanCommentRequest",
result_contract="MediaReviewPage",
result_shape=ModuleResultShape.MODEL,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("doubanid", "start", "count"),
),
"douban_tv_reviews": ModuleMethodContract(
family="douban",
input_contract="DoubanCommentRequest",
result_contract="MediaReviewPage",
result_shape=ModuleResultShape.MODEL,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("doubanid", "start", "count"),
),
"bangumi_calendar": ModuleMethodContract(
family="bangumi",
input_contract="BangumiCalendarRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
),
"bangumi_credits": ModuleMethodContract(
family="bangumi",
input_contract="BangumiMediaRequest",
result_contract="list[MediaPerson]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("bangumiid",),
),
"bangumi_discover": ModuleMethodContract(
family="bangumi",
input_contract="BangumiDiscoverArguments",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
),
"bangumi_info": ModuleMethodContract(
family="bangumi",
input_contract="BangumiMediaRequest",
result_contract="dict | None",
result_shape=ModuleResultShape.MAPPING,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("bangumiid",),
),
"bangumi_person_credits": ModuleMethodContract(
family="bangumi",
input_contract="BangumiPersonRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("person_id",),
),
"bangumi_person_detail": ModuleMethodContract(
family="bangumi",
input_contract="BangumiPersonRequest",
result_contract="MediaPerson | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("person_id",),
),
"bangumi_recommend": ModuleMethodContract(
family="bangumi",
input_contract="BangumiMediaRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("bangumiid",),
),
"anilist_credits": ModuleMethodContract(
family="anilist",
input_contract="AniListMediaPageRequest",
result_contract="list[MediaPerson]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("anilist_id", "page", "count"),
),
"anilist_discover": ModuleMethodContract(
family="anilist",
input_contract="AniListDiscoverArguments",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
),
"anilist_info": ModuleMethodContract(
family="anilist",
input_contract="AniListMediaRequest",
result_contract="dict | None",
result_shape=ModuleResultShape.MAPPING,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("anilist_id",),
),
"anilist_person_credits": ModuleMethodContract(
family="anilist",
input_contract="AniListPersonPageRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("person_id", "page", "count"),
),
"anilist_person_detail": ModuleMethodContract(
family="anilist",
input_contract="AniListPersonRequest",
result_contract="MediaPerson | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("person_id",),
),
"anilist_popular_this_season": ModuleMethodContract(
family="anilist",
input_contract="AniListPageRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("page", "count"),
),
"anilist_recommendations": ModuleMethodContract(
family="anilist",
input_contract="AniListMediaPageRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("anilist_id", "page", "count"),
),
"anilist_trending": ModuleMethodContract(
family="anilist",
input_contract="AniListPageRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("page", "count"),
),
"tmdb_collection": ModuleMethodContract(
family="tmdb",
input_contract="TmdbCollectionRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("collection_id",),
),
"tmdb_discover": ModuleMethodContract(
family="tmdb",
input_contract="TmdbDiscoverRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=(
"mtype",
"sort_by",
"with_genres",
"with_original_language",
"with_keywords",
"with_watch_providers",
"vote_average",
"vote_count",
"release_date",
"page",
),
),
"tmdb_episodes": ModuleMethodContract(
family="tmdb",
input_contract="TmdbEpisodesRequest",
result_contract="list[TmdbEpisode]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("tmdbid", "season", "episode_group"),
),
"tmdb_group_seasons": ModuleMethodContract(
family="tmdb",
input_contract="TmdbGroupRequest",
result_contract="list[TmdbSeason]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("group_id",),
),
"tmdb_info": ModuleMethodContract(
family="tmdb",
input_contract="TmdbInfoRequest",
result_contract="dict | None",
result_shape=ModuleResultShape.MAPPING,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("tmdbid", "mtype", "season"),
),
"tmdb_movie_credits": ModuleMethodContract(
family="tmdb",
input_contract="TmdbMediaPageRequest",
result_contract="list[MediaPerson]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("tmdbid", "page"),
),
"tmdb_movie_recommend": ModuleMethodContract(
family="tmdb",
input_contract="TmdbMediaRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("tmdbid",),
),
"tmdb_movie_similar": ModuleMethodContract(
family="tmdb",
input_contract="TmdbMediaRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("tmdbid",),
),
"tmdb_person_credits": ModuleMethodContract(
family="tmdb",
input_contract="TmdbPersonPageRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("person_id", "page"),
),
"tmdb_person_detail": ModuleMethodContract(
family="tmdb",
input_contract="TmdbPersonRequest",
result_contract="MediaPerson | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("person_id",),
),
"tmdb_seasons": ModuleMethodContract(
family="tmdb",
input_contract="TmdbMediaRequest",
result_contract="list[TmdbSeason]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("tmdbid",),
),
"tmdb_trending": ModuleMethodContract(
family="tmdb",
input_contract="TmdbTrendingRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("page",),
),
"tmdb_tv_credits": ModuleMethodContract(
family="tmdb",
input_contract="TmdbMediaPageRequest",
result_contract="list[MediaPerson]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("tmdbid", "page"),
),
"tmdb_tv_recommend": ModuleMethodContract(
family="tmdb",
input_contract="TmdbMediaRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("tmdbid",),
),
"tmdb_tv_similar": ModuleMethodContract(
family="tmdb",
input_contract="TmdbMediaRequest",
result_contract="list[MediaInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("tmdbid",),
),
"identify_music_by_fingerprint": ModuleMethodContract(
family="music",
input_contract="MusicFingerprintRequest",
result_contract="str | None",
result_shape=ModuleResultShape.STRING,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("path",),
),
"match_music_album": ModuleMethodContract(
family="music",
input_contract="MusicAlbumMatchRequest",
result_contract="MusicAlbumInfo | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("meta", "tracks", "limit"),
),
"music_album": ModuleMethodContract(
family="music",
input_contract="MusicIdentityRequest",
result_contract="MusicAlbumInfo | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("media_source", "media_id"),
),
"music_album_related": ModuleMethodContract(
family="music",
input_contract="MusicRelatedRequest",
result_contract="list[MusicInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("media_source", "media_id", "count"),
),
"music_artist": ModuleMethodContract(
family="music",
input_contract="MusicIdentityRequest",
result_contract="MusicArtistInfo | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("media_source", "media_id"),
),
"music_artist_albums": ModuleMethodContract(
family="music",
input_contract="MusicArtistAlbumsRequest",
result_contract="list[MusicInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("media_source", "media_id", "page", "count", "album_type"),
),
"music_artist_related": ModuleMethodContract(
family="music",
input_contract="MusicRelatedRequest",
result_contract="list[MusicArtistInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("media_source", "media_id", "count"),
),
"music_cache_delete": ModuleMethodContract(
family="music",
input_contract="MusicCacheDeleteRequest",
result_contract="dict | None",
result_shape=ModuleResultShape.MAPPING,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("cache_key",),
),
"music_cache_items": ModuleMethodContract(
family="music",
input_contract="MusicCacheReadRequest",
result_contract="list[dict]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
),
"music_cache_clear": ModuleMethodContract(
family="music",
input_contract="MusicCacheClearRequest",
result_contract="None",
aggregation=ModuleResultAggregation.FAN_OUT,
plugin_short_circuit=False,
),
"music_chart": ModuleMethodContract(
family="music",
input_contract="MusicChartRequest",
result_contract="list[MusicInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("range_name", "offset", "count", "entity"),
),
"music_discover": ModuleMethodContract(
family="music",
input_contract="MusicDiscoverRequest",
result_contract="list[MusicInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("media_source", "page", "count", "entity", "mode", "tags", "sort"),
),
"music_fresh_releases": ModuleMethodContract(
family="music",
input_contract="MusicFreshReleasesRequest",
result_contract="list[MusicInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("days", "sort", "past", "future", "offset", "count"),
),
"music_lyrics": ModuleMethodContract(
family="music",
input_contract="MusicLyricsRequest",
result_contract="MusicLyrics | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("music",),
),
"music_lyrics_candidates": ModuleMethodContract(
family="music",
input_contract="MusicLyricsRequest",
result_contract="list[MusicLyrics]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("music",),
plugin_short_circuit=False,
),
"search_music": ModuleMethodContract(
family="music",
input_contract="MusicSearchRequest",
result_contract="list[MusicInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("meta", "limit", "media_source"),
),
"channel_manage": ModuleMethodContract(
family="messaging",
input_contract="ChannelManageRequest",
result_contract="dict[str, Any] | None",
result_shape=ModuleResultShape.MAPPING,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("channel", "action"),
),
"delete_message": ModuleMethodContract(
family="messaging",
input_contract="MessageDeleteRequest",
result_contract="bool | None",
result_shape=ModuleResultShape.BOOLEAN,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("channel", "source", "message_id", "chat_id"),
),
"edit_message": ModuleMethodContract(
family="messaging",
input_contract="MessageEditRequest",
result_contract="bool | None",
result_shape=ModuleResultShape.BOOLEAN,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("channel", "source", "message_id", "chat_id", "text", "title", "buttons", "metadata"),
),
"mark_message_processing_started": ModuleMethodContract(
family="messaging",
input_contract="MessageProcessingStartRequest",
result_contract="dict[str, Any] | None",
result_shape=ModuleResultShape.MAPPING,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("channel", "source", "userid", "message_id", "chat_id", "text"),
),
"mark_message_processing_finished": ModuleMethodContract(
family="messaging",
input_contract="MessageProcessingFinishRequest",
result_contract="bool | None",
result_shape=ModuleResultShape.BOOLEAN,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("channel", "source", "userid", "message_id", "chat_id", "status"),
),
"message_parser": ModuleMethodContract(
family="messaging",
input_contract="MessageParseRequest",
result_contract="IncomingMessage | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("source", "body", "form", "args"),
),
"send_direct_message": ModuleMethodContract(
family="messaging",
input_contract="DirectMessageSendRequest",
result_contract="MessageResponse | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("message",),
),
"send_message": ModuleMethodContract(
family="messaging",
input_contract="MessageSendRequest",
result_contract="Message | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
),
"finalize_message": ModuleMethodContract(
family="messaging",
input_contract="MessageFinalizeRequest",
result_contract="Message | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("response",),
),
"register_commands": ModuleMethodContract(
family="messaging",
input_contract="CommandRegistrationRequest",
result_contract="None",
aggregation=ModuleResultAggregation.FAN_OUT,
required_parameters=("commands",),
plugin_short_circuit=False,
),
"scheduler_job": ModuleMethodContract(
family="scheduling",
input_contract="SchedulerJobRequest",
result_contract="None",
aggregation=ModuleResultAggregation.FAN_OUT,
plugin_short_circuit=False,
),
"webhook_parser": ModuleMethodContract(
family="integration",
input_contract="WebhookRequest",
result_contract="WebhookEventInfo | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("body", "form", "args"),
),
"download_discord_file_bytes": ModuleMethodContract(
family="messaging",
input_contract="MessageFileDownloadRequest",
result_contract="bytes | None",
result_shape=ModuleResultShape.BYTES,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("file_ref", "source"),
),
"download_feishu_file_bytes": ModuleMethodContract(
family="messaging",
input_contract="MessageFileDownloadRequest",
result_contract="bytes | None",
result_shape=ModuleResultShape.BYTES,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("file_ref", "source"),
),
"download_feishu_image_to_data_url": ModuleMethodContract(
family="messaging",
input_contract="MessageImageDownloadRequest",
result_contract="str | None",
result_shape=ModuleResultShape.STRING,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("image_ref", "source"),
),
"download_qq_file_bytes": ModuleMethodContract(
family="messaging",
input_contract="MessageFileDownloadRequest",
result_contract="bytes | None",
result_shape=ModuleResultShape.BYTES,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("file_ref", "source"),
),
"download_slack_file_bytes": ModuleMethodContract(
family="messaging",
input_contract="MessageFileDownloadRequest",
result_contract="bytes | None",
result_shape=ModuleResultShape.BYTES,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("file_ref", "source"),
),
"download_slack_file_to_data_url": ModuleMethodContract(
family="messaging",
input_contract="MessageFileDownloadRequest",
result_contract="str | None",
result_shape=ModuleResultShape.STRING,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("file_url", "source"),
),
"download_synologychat_file_bytes": ModuleMethodContract(
family="messaging",
input_contract="MessageFileDownloadRequest",
result_contract="bytes | None",
result_shape=ModuleResultShape.BYTES,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("file_ref", "source"),
),
"download_telegram_file_bytes": ModuleMethodContract(
family="messaging",
input_contract="MessageFileDownloadRequest",
result_contract="bytes | None",
result_shape=ModuleResultShape.BYTES,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("file_id", "source"),
),
"download_telegram_file_to_base64": ModuleMethodContract(
family="messaging",
input_contract="MessageFileDownloadRequest",
result_contract="str | None",
result_shape=ModuleResultShape.STRING,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("file_id", "source"),
),
"download_vocechat_file_bytes": ModuleMethodContract(
family="messaging",
input_contract="MessageFileDownloadRequest",
result_contract="bytes | None",
result_shape=ModuleResultShape.BYTES,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("file_ref", "source"),
),
"download_vocechat_image_to_data_url": ModuleMethodContract(
family="messaging",
input_contract="MessageImageDownloadRequest",
result_contract="str | None",
result_shape=ModuleResultShape.STRING,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("image_ref", "source"),
),
"download_wechat_image_to_data_url": ModuleMethodContract(
family="messaging",
input_contract="MessageImageDownloadRequest",
result_contract="str | None",
result_shape=ModuleResultShape.STRING,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("image_ref", "source"),
),
"download_wechat_media_bytes": ModuleMethodContract(
family="messaging",
input_contract="MessageMediaDownloadRequest",
result_contract="bytes | None",
result_shape=ModuleResultShape.BYTES,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("media_ref", "source"),
),
"downloader_info": ModuleMethodContract(
family="downloader",
input_contract="DownloaderInfoRequest",
result_contract="list[DownloaderInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("downloader",),
),
"list_torrents": ModuleMethodContract(
family="downloader",
input_contract="TorrentListRequest",
result_contract="list[DownloaderTorrent]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("status", "hashs", "downloader", "include_all_tags"),
),
"filter_torrents": ModuleMethodContract(
family="downloader",
input_contract="TorrentFilterRequest",
result_contract="list[TorrentInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("rule_groups", "torrent_list", "mediainfo"),
),
"refresh_torrents": ModuleMethodContract(
family="downloader",
input_contract="TorrentRefreshRequest",
result_contract="list[TorrentInfo]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
required_parameters=("site", "keyword", "cat", "page", "mtype"),
),
"torrent_files": ModuleMethodContract(
family="downloader",
input_contract="TorrentFilesRequest",
result_contract="list[DownloaderFile]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("tid", "downloader"),
),
"get_torrent_trackers": ModuleMethodContract(
family="downloader",
input_contract="TorrentTrackersRequest",
result_contract="dict[str, list[str]] | None",
result_shape=ModuleResultShape.MAPPING,
aggregation=ModuleResultAggregation.ORDERED_MAPPING_MERGE,
required_parameters=("hash_string", "downloader"),
),
"download": ModuleMethodContract(
family="downloader",
input_contract="DownloadTaskRequest",
result_contract="DownloadTaskResult | None",
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("content", "download_dir", "cookie", "episodes", "category", "label", "downloader"),
optional_parameters=("custom_words",),
),
"download_added": ModuleMethodContract(
family="downloader",
input_contract="DownloadAddedHook",
result_contract="None",
aggregation=ModuleResultAggregation.FAN_OUT,
required_parameters=("context", "torrent_content", "download_dir"),
plugin_short_circuit=False,
),
"remove_torrents": ModuleMethodContract(
family="downloader",
input_contract="TorrentRemoveRequest",
result_contract="bool | None",
result_shape=ModuleResultShape.BOOLEAN,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("hashs", "delete_file", "downloader"),
),
"set_torrents_tag": ModuleMethodContract(
family="downloader",
input_contract="TorrentTagRequest",
result_contract="bool | None",
result_shape=ModuleResultShape.BOOLEAN,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("hashs", "tags", "downloader"),
),
"start_torrents": ModuleMethodContract(
family="downloader",
input_contract="TorrentControlRequest",
result_contract="bool | None",
result_shape=ModuleResultShape.BOOLEAN,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("hashs", "downloader"),
),
"stop_torrents": ModuleMethodContract(
family="downloader",
input_contract="TorrentControlRequest",
result_contract="bool | None",
result_shape=ModuleResultShape.BOOLEAN,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("hashs", "downloader"),
),
"update_torrent": ModuleMethodContract(
family="downloader",
input_contract="TorrentUpdateRequest",
result_contract="dict[str, bool] | None",
result_shape=ModuleResultShape.MAPPING,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=(
"hash_string",
"downloader",
"download_limit",
"upload_limit",
"tracker_list",
"save_path",
"category",
"ratio_limit",
"seeding_time_limit",
),
),
"transfer_completed": ModuleMethodContract(
family="downloader",
input_contract="TransferCompletedHook",
result_contract="None",
aggregation=ModuleResultAggregation.FAN_OUT,
required_parameters=("hashs", "downloader"),
plugin_short_circuit=False,
),
"tmdb_cache_items": ModuleMethodContract(
family="tmdb",
input_contract="TmdbCacheListRequest",
result_contract="list[dict[str, Any]]",
result_shape=ModuleResultShape.LIST,
aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE,
),
"tmdb_cache_delete": ModuleMethodContract(
family="tmdb",
input_contract="TmdbCacheDeleteRequest",
result_contract="dict[str, Any] | None",
result_shape=ModuleResultShape.MAPPING,
aggregation=ModuleResultAggregation.FIRST_NON_EMPTY,
required_parameters=("cache_key",),
),
"tmdb_cache_clear": ModuleMethodContract(
family="tmdb",
input_contract="TmdbCacheClearRequest",
result_contract="None",
aggregation=ModuleResultAggregation.FAN_OUT,
plugin_short_circuit=False,
),
}
# 同一能力的同步/异步入口共享不可变契约对象,避免参数和聚合语义各自漂移。
_METHOD_CONTRACTS.update(
{
"async_recognize_media": _METHOD_CONTRACTS["recognize_media"],
"async_match_doubaninfo": _METHOD_CONTRACTS["match_doubaninfo"],
"async_match_tmdbinfo": _METHOD_CONTRACTS["match_tmdbinfo"],
"async_update_recognize_cache": _METHOD_CONTRACTS["update_recognize_cache"],
"async_search_medias": _METHOD_CONTRACTS["search_medias"],
"async_get_media_auxiliary_info": _METHOD_CONTRACTS["get_media_auxiliary_info"],
"async_obtain_images": _METHOD_CONTRACTS["obtain_images"],
"async_movie_hot": _METHOD_CONTRACTS["movie_hot"],
"async_movie_showing": _METHOD_CONTRACTS["movie_showing"],
"async_movie_top250": _METHOD_CONTRACTS["movie_top250"],
"async_search_collections": _METHOD_CONTRACTS["search_collections"],
"async_search_persons": _METHOD_CONTRACTS["search_persons"],
"async_search_subtitles": _METHOD_CONTRACTS["search_subtitles"],
"async_search_torrents": _METHOD_CONTRACTS["search_torrents"],
"async_tv_animation": _METHOD_CONTRACTS["tv_animation"],
"async_tv_hot": _METHOD_CONTRACTS["tv_hot"],
"async_tv_weekly_chinese": _METHOD_CONTRACTS["tv_weekly_chinese"],
"async_tv_weekly_global": _METHOD_CONTRACTS["tv_weekly_global"],
"async_douban_discover": _METHOD_CONTRACTS["douban_discover"],
"async_douban_info": _METHOD_CONTRACTS["douban_info"],
"async_douban_movie_credits": _METHOD_CONTRACTS["douban_movie_credits"],
"async_douban_movie_interests": _METHOD_CONTRACTS["douban_movie_interests"],
"async_douban_movie_recommend": _METHOD_CONTRACTS["douban_movie_recommend"],
"async_douban_movie_reviews": _METHOD_CONTRACTS["douban_movie_reviews"],
"async_douban_person_credits": _METHOD_CONTRACTS["douban_person_credits"],
"async_douban_person_detail": _METHOD_CONTRACTS["douban_person_detail"],
"async_douban_tv_credits": _METHOD_CONTRACTS["douban_tv_credits"],
"async_douban_tv_interests": _METHOD_CONTRACTS["douban_tv_interests"],
"async_douban_tv_recommend": _METHOD_CONTRACTS["douban_tv_recommend"],
"async_douban_tv_reviews": _METHOD_CONTRACTS["douban_tv_reviews"],
"async_bangumi_calendar": _METHOD_CONTRACTS["bangumi_calendar"],
"async_bangumi_credits": _METHOD_CONTRACTS["bangumi_credits"],
"async_bangumi_discover": _METHOD_CONTRACTS["bangumi_discover"],
"async_bangumi_info": _METHOD_CONTRACTS["bangumi_info"],
"async_bangumi_person_credits": _METHOD_CONTRACTS["bangumi_person_credits"],
"async_bangumi_person_detail": _METHOD_CONTRACTS["bangumi_person_detail"],
"async_bangumi_recommend": _METHOD_CONTRACTS["bangumi_recommend"],
"async_anilist_credits": _METHOD_CONTRACTS["anilist_credits"],
"async_anilist_discover": _METHOD_CONTRACTS["anilist_discover"],
"async_anilist_info": _METHOD_CONTRACTS["anilist_info"],
"async_anilist_person_credits": _METHOD_CONTRACTS["anilist_person_credits"],
"async_anilist_person_detail": _METHOD_CONTRACTS["anilist_person_detail"],
"async_anilist_popular_this_season": _METHOD_CONTRACTS["anilist_popular_this_season"],
"async_anilist_recommendations": _METHOD_CONTRACTS["anilist_recommendations"],
"async_anilist_trending": _METHOD_CONTRACTS["anilist_trending"],
"async_tmdb_collection": _METHOD_CONTRACTS["tmdb_collection"],
"async_tmdb_discover": _METHOD_CONTRACTS["tmdb_discover"],
"async_tmdb_episodes": _METHOD_CONTRACTS["tmdb_episodes"],
"async_tmdb_group_seasons": _METHOD_CONTRACTS["tmdb_group_seasons"],
"async_tmdb_info": _METHOD_CONTRACTS["tmdb_info"],
"async_tmdb_movie_credits": _METHOD_CONTRACTS["tmdb_movie_credits"],
"async_tmdb_movie_recommend": _METHOD_CONTRACTS["tmdb_movie_recommend"],
"async_tmdb_movie_similar": _METHOD_CONTRACTS["tmdb_movie_similar"],
"async_tmdb_person_credits": _METHOD_CONTRACTS["tmdb_person_credits"],
"async_tmdb_person_detail": _METHOD_CONTRACTS["tmdb_person_detail"],
"async_tmdb_seasons": _METHOD_CONTRACTS["tmdb_seasons"],
"async_tmdb_trending": _METHOD_CONTRACTS["tmdb_trending"],
"async_tmdb_tv_credits": _METHOD_CONTRACTS["tmdb_tv_credits"],
"async_tmdb_tv_recommend": _METHOD_CONTRACTS["tmdb_tv_recommend"],
"async_tmdb_tv_similar": _METHOD_CONTRACTS["tmdb_tv_similar"],
"async_identify_music_by_fingerprint": _METHOD_CONTRACTS["identify_music_by_fingerprint"],
"async_match_music_album": _METHOD_CONTRACTS["match_music_album"],
"async_refresh_torrents": _METHOD_CONTRACTS["refresh_torrents"],
}
)
_PREFIX_CONTRACTS = (
("async_tmdb_", ModuleMethodContract(family="tmdb")),
("tmdb_", ModuleMethodContract(family="tmdb")),
("async_douban_", ModuleMethodContract(family="douban")),
("douban_", ModuleMethodContract(family="douban")),
("async_bangumi_", ModuleMethodContract(family="bangumi")),
("bangumi_", ModuleMethodContract(family="bangumi")),
("async_anilist_", ModuleMethodContract(family="anilist")),
("anilist_", ModuleMethodContract(family="anilist")),
("tvdb_", ModuleMethodContract(family="tvdb")),
("music_", ModuleMethodContract(family="music")),
("torrent_", ModuleMethodContract(family="downloader")),
)
# 宿主静态扫描到的全部字符串能力名。第三方插件仍可声明未在这里出现的自定义方法,
# 自定义方法继续走开放的 legacy contract;宿主新增调用则必须先进入本清单。
_OBSERVED_HOST_METHODS = (
"anilist_credits",
"anilist_discover",
"anilist_info",
"anilist_person_credits",
"anilist_person_detail",
"anilist_popular_this_season",
"anilist_recommendations",
"anilist_trending",
"any_files",
"async_anilist_credits",
"async_anilist_discover",
"async_anilist_info",
"async_anilist_person_credits",
"async_anilist_person_detail",
"async_anilist_popular_this_season",
"async_anilist_recommendations",
"async_anilist_trending",
"async_bangumi_calendar",
"async_bangumi_credits",
"async_bangumi_discover",
"async_bangumi_info",
"async_bangumi_person_credits",
"async_bangumi_person_detail",
"async_bangumi_recommend",
"async_douban_discover",
"async_douban_info",
"async_douban_movie_credits",
"async_douban_movie_interests",
"async_douban_movie_recommend",
"async_douban_movie_reviews",
"async_douban_person_credits",
"async_douban_person_detail",
"async_douban_tv_credits",
"async_douban_tv_interests",
"async_douban_tv_recommend",
"async_douban_tv_reviews",
"async_identify_music_by_fingerprint",
"async_match_doubaninfo",
"async_match_music_album",
"async_match_tmdbinfo",
"async_movie_hot",
"async_movie_showing",
"async_movie_top250",
"async_obtain_images",
"async_recognize_media",
"async_refresh_torrents",
"async_search_collections",
"async_search_medias",
"async_search_persons",
"async_search_subtitles",
"async_search_torrents",
"async_tmdb_collection",
"async_tmdb_discover",
"async_tmdb_episodes",
"async_tmdb_group_seasons",
"async_tmdb_info",
"async_tmdb_movie_credits",
"async_tmdb_movie_recommend",
"async_tmdb_movie_similar",
"async_tmdb_person_credits",
"async_tmdb_person_detail",
"async_tmdb_seasons",
"async_tmdb_trending",
"async_tmdb_tv_credits",
"async_tmdb_tv_recommend",
"async_tmdb_tv_similar",
"async_tv_animation",
"async_tv_hot",
"async_tv_weekly_chinese",
"async_tv_weekly_global",
"async_update_recognize_cache",
"bangumi_calendar",
"bangumi_credits",
"bangumi_discover",
"bangumi_info",
"bangumi_person_credits",
"bangumi_person_detail",
"bangumi_recommend",
"channel_manage",
"clear_cache",
"create_folder",
"delete_file",
"delete_message",
"douban_discover",
"douban_info",
"douban_movie_credits",
"douban_movie_interests",
"douban_movie_recommend",
"douban_movie_reviews",
"douban_person_credits",
"douban_person_detail",
"douban_tv_credits",
"douban_tv_interests",
"douban_tv_recommend",
"douban_tv_reviews",
"download",
"download_added",
"download_discord_file_bytes",
"download_feishu_file_bytes",
"download_feishu_image_to_data_url",
"download_file",
"download_qq_file_bytes",
"download_slack_file_bytes",
"download_slack_file_to_data_url",
"download_synologychat_file_bytes",
"download_telegram_file_bytes",
"download_telegram_file_to_base64",
"download_vocechat_file_bytes",
"download_vocechat_image_to_data_url",
"download_wechat_image_to_data_url",
"download_wechat_media_bytes",
"downloader_info",
"edit_message",
"filter_torrents",
"finalize_message",
"get_file_item",
"get_folder",
"get_parent_item",
"get_search_page_size",
"get_torrent_trackers",
"identify_music_by_fingerprint",
"list_files",
"list_torrents",
"mark_message_processing_finished",
"mark_message_processing_started",
"match_doubaninfo",
"match_music_album",
"match_tmdbinfo",
"media_exists",
"media_files",
"media_statistic",
"mediaserver_image_cookies",
"mediaserver_iteminfo",
"mediaserver_items",
"mediaserver_items_count",
"mediaserver_latest",
"mediaserver_latest_images",
"mediaserver_librarys",
"mediaserver_play_url",
"mediaserver_playing",
"mediaserver_season_episode_ids",
"mediaserver_tv_episodes",
"message_parser",
"metadata_img",
"metadata_nfo",
"movie_hot",
"movie_showing",
"movie_top250",
"music_album",
"music_album_related",
"music_artist",
"music_artist_albums",
"music_artist_related",
"music_cache_clear",
"music_cache_delete",
"music_cache_items",
"music_chart",
"music_discover",
"music_fresh_releases",
"music_lyrics",
"obtain_images",
"obtain_specific_image",
"recognize_media",
"recommend_name",
"refresh_torrents",
"refresh_userdata",
"register_commands",
"remove_torrents",
"rename_file",
"scheduler_job",
"search_collections",
"search_medias",
"search_music",
"search_persons",
"search_subtitles",
"search_torrents",
"search_tvdb",
"send_direct_message",
"set_torrents_tag",
"site_subtitle_links",
"snapshot_storage",
"start_torrents",
"stop_torrents",
"storage_manage",
"tmdb_cache_clear",
"tmdb_cache_delete",
"tmdb_cache_items",
"tmdb_collection",
"tmdb_discover",
"tmdb_episodes",
"tmdb_group_seasons",
"tmdb_info",
"tmdb_movie_credits",
"tmdb_movie_recommend",
"tmdb_movie_similar",
"tmdb_person_credits",
"tmdb_person_detail",
"tmdb_seasons",
"tmdb_trending",
"tmdb_tv_credits",
"tmdb_tv_recommend",
"tmdb_tv_similar",
"torrent_files",
"transfer",
"transfer_completed",
"tv_animation",
"tv_hot",
"tv_weekly_chinese",
"tv_weekly_global",
"tvdb_info",
"tvdb_slug",
"update_recognize_cache",
"update_torrent",
"upload_file",
"user_authenticate",
"webhook_parser",
)
_FAMILY_IO_CONTRACTS = {
"anilist": ("AniListKeywordArguments", "AniListProviderResult"),
"authentication": ("AuthenticationKeywordArguments", "AuthenticationResult"),
"bangumi": ("BangumiKeywordArguments", "BangumiProviderResult"),
"category": ("CategoryKeywordArguments", "CategoryProviderResult"),
"douban": ("DoubanKeywordArguments", "DoubanProviderResult"),
"downloader": ("DownloaderKeywordArguments", "DownloaderProviderResult"),
"integration": ("IntegrationKeywordArguments", "IntegrationProviderResult"),
"media-discovery": ("MediaDiscoveryKeywordArguments", "MediaDiscoveryProviderResult"),
"media-recognition": ("MediaRecognitionKeywordArguments", "MediaRecognitionProviderResult"),
"media-server": ("MediaServerKeywordArguments", "MediaServerProviderResult"),
"messaging": ("MessagingKeywordArguments", "MessagingProviderResult"),
"metadata": ("MetadataKeywordArguments", "MetadataProviderResult"),
"music": ("MusicKeywordArguments", "MusicProviderResult"),
"site": ("SiteKeywordArguments", "SiteProviderResult"),
"storage": ("StorageKeywordArguments", "StorageProviderResult"),
"tmdb": ("TmdbKeywordArguments", "TmdbProviderResult"),
"tvdb": ("TvdbKeywordArguments", "TvdbProviderResult"),
}
def _infer_observed_family(method: str) -> str:
"""
按稳定能力前缀把已观察宿主方法归入可审计的输入/结果族。
"""
for prefix, contract in _PREFIX_CONTRACTS:
if method.startswith(prefix):
return contract.family
if method.startswith(("mediaserver_", "media_exists", "media_statistic")):
return "media-server"
if method.startswith(
(
"download",
"torrent_",
"list_torrents",
"refresh_torrents",
"remove_torrents",
"start_torrents",
"stop_torrents",
"set_torrents_tag",
"update_torrent",
"get_torrent_trackers",
"downloader_info",
"filter_torrents",
"transfer_completed",
)
):
return "downloader"
if method.startswith(
(
"channel_",
"delete_message",
"edit_message",
"finalize_message",
"mark_message_",
"message_parser",
"register_commands",
"send_direct_message",
"send_message",
)
):
return "messaging"
if method.startswith(
(
"any_files",
"create_folder",
"delete_file",
"get_file_item",
"get_folder",
"get_parent_item",
"list_files",
"media_files",
"rename_file",
"snapshot_storage",
"storage_manage",
"transfer",
"upload_file",
)
):
return "storage"
if method.startswith(
(
"metadata_",
"obtain_specific_image",
"recommend_name",
)
):
return "metadata"
if method.startswith(
(
"async_identify_music",
"async_match_music",
"identify_music",
"match_music",
"search_music",
)
):
return "music"
if method.startswith(
(
"async_get_media_auxiliary_info",
"async_match_",
"async_obtain_images",
"async_recognize_media",
"async_update_recognize_cache",
"match_",
"obtain_images",
"get_media_auxiliary_info",
"recognize_media",
"update_recognize_cache",
)
):
return "media-recognition"
if method.startswith(
(
"async_movie_",
"async_search_",
"async_tv_",
"movie_",
"search_collections",
"search_medias",
"search_persons",
"search_subtitles",
"search_torrents",
"tv_",
)
):
return "media-discovery"
if method == "clear_cache":
return "category"
if method in {"get_search_page_size", "refresh_userdata", "site_subtitle_links"}:
return "site"
if method == "user_authenticate":
return "authentication"
return "integration"
def _register_observed_host_contracts() -> None:
"""
为全部宿主字符串调用登记完整 V2 字段,保留未知插件方法的 legacy fallback。
"""
for method in _OBSERVED_HOST_METHODS:
if method in _METHOD_CONTRACTS:
continue
family = _infer_observed_family(method)
input_contract, result_contract = _FAMILY_IO_CONTRACTS[family]
_METHOD_CONTRACTS[method] = ModuleMethodContract(
family=family,
input_contract=input_contract,
result_contract=result_contract,
)
_register_observed_host_contracts()
def get_module_method_contract(method: str) -> ModuleMethodContract:
"""
返回方法的显式能力族契约,未知方法保持既有 legacy 协议。
"""
if contract := _METHOD_CONTRACTS.get(method):
return contract
for prefix, contract in _PREFIX_CONTRACTS:
if method.startswith(prefix):
return contract
return _DEFAULT_CONTRACT
def is_explicit_module_method(method: str) -> bool:
"""
判断方法是否已进入首批显式能力族清单。
"""
return get_module_method_contract(method) is not _DEFAULT_CONTRACT
def diagnose_module_callable(method: str, callback: Callable[..., Any]) -> tuple[str, ...]:
"""
诊断显式能力的基础签名;兼容阶段只返回问题,不拒绝 provider。
"""
contract = get_module_method_contract(method)
if contract is _DEFAULT_CONTRACT:
return ()
try:
parameters = inspect.signature(callback).parameters
except (TypeError, ValueError):
return ("signature-unavailable",)
missing = tuple(
name
for name in contract.required_parameters
if name not in parameters
and not any(parameter.kind is inspect.Parameter.VAR_KEYWORD for parameter in parameters.values())
)
return tuple(f"missing-parameter:{name}" for name in missing)
def compatible_module_kwargs(
method: str, callback: Callable[..., Any], kwargs: dict[str, Any],
) -> dict[str, Any]:
"""
仅移除显式登记的新可选参数,保留旧 provider 签名和其它参数错误。
接受该参数或 **kwargs 的 provider 收到完整输入;无法检查签名时不推断兼容性。
"""
optional = get_module_method_contract(method).optional_parameters
if not optional or not any(name in kwargs for name in optional):
return kwargs
try:
parameters = inspect.signature(callback).parameters
except (TypeError, ValueError):
return kwargs
if any(parameter.kind is inspect.Parameter.VAR_KEYWORD for parameter in parameters.values()):
return kwargs
return {name: value for name, value in kwargs.items() if name not in optional or name in parameters}
def diagnose_module_result(method: str, result: Any) -> tuple[str, ...]:
"""
诊断显式模块结果的基础形状,兼容阶段只告警而不改写返回值。
"""
shape = get_module_method_contract(method).result_shape
if shape is ModuleResultShape.ANY or result is None:
return ()
matches = {
ModuleResultShape.LIST: isinstance(result, list),
ModuleResultShape.STRING: isinstance(result, str),
ModuleResultShape.MAPPING: isinstance(result, dict),
ModuleResultShape.MODEL: isinstance(result, BaseModel),
ModuleResultShape.BOOLEAN: isinstance(result, bool),
ModuleResultShape.BYTES: isinstance(result, bytes),
}
if matches.get(shape, True):
return ()
return (f"unexpected-result:{shape.value}:{type(result).__name__}",)
def list_explicit_module_contracts() -> dict[str, ModuleMethodContract]:
"""
返回显式方法清单的副本,供架构基线和 SDK 文档使用。
"""
return dict(_METHOD_CONTRACTS)