chore: import private baseline from gitea state
This commit is contained in:
244
plugin-templates/MaiBot-Napcat-Adapter/plugin.py
Normal file
244
plugin-templates/MaiBot-Napcat-Adapter/plugin.py
Normal file
@@ -0,0 +1,244 @@
|
||||
"""内置 NapCat 适配器插件。
|
||||
|
||||
当前实现承担完整的 QQ / NapCat 消息网关职责:
|
||||
1. 作为客户端连接 NapCat / OneBot v11 WebSocket 服务。
|
||||
2. 将入站消息、通知事件与元事件转换为 Host 侧结构。
|
||||
3. 将 Host 出站消息转换为 OneBot 动作并发送。
|
||||
4. 通过公开 API 暴露 QQ 平台专属查询与管理动作。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, ClassVar, Dict, Mapping, Optional, cast
|
||||
|
||||
from maibot_sdk import MaiBotPlugin, MessageGateway, PluginConfigBase
|
||||
|
||||
from .apis import (
|
||||
NapCatAccountApiMixin,
|
||||
NapCatFileApiMixin,
|
||||
NapCatGroupApiMixin,
|
||||
NapCatMessageApiMixin,
|
||||
NapCatSystemApiMixin,
|
||||
)
|
||||
from .config import NapCatPluginSettings
|
||||
from .constants import NAPCAT_GATEWAY_NAME
|
||||
from .runtime import NapCatEventRouter, NapCatRuntimeBuilder, NapCatRuntimeBundle
|
||||
from .services import NapCatActionService, NapCatQueryService
|
||||
|
||||
|
||||
class NapCatAdapterPlugin(
|
||||
NapCatAccountApiMixin,
|
||||
NapCatFileApiMixin,
|
||||
NapCatGroupApiMixin,
|
||||
NapCatMessageApiMixin,
|
||||
NapCatSystemApiMixin,
|
||||
MaiBotPlugin,
|
||||
):
|
||||
"""NapCat 消息网关与 QQ 能力插件。"""
|
||||
|
||||
config_model: ClassVar[type[PluginConfigBase] | None] = NapCatPluginSettings
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""初始化 NapCat 适配器插件实例。"""
|
||||
super().__init__()
|
||||
self._action_service: Optional[NapCatActionService] = None
|
||||
self._query_service: Optional[NapCatQueryService] = None
|
||||
self._event_router: Optional[NapCatEventRouter] = None
|
||||
self._runtime_bundle: Optional[NapCatRuntimeBundle] = None
|
||||
|
||||
async def on_load(self) -> None:
|
||||
"""在插件加载时根据配置决定是否启动连接。"""
|
||||
await self._restart_connection_if_needed()
|
||||
|
||||
async def on_unload(self) -> None:
|
||||
"""在插件卸载时关闭连接。"""
|
||||
await self._stop_connection()
|
||||
|
||||
async def on_config_update(self, scope: str, config_data: Dict[str, Any], version: str) -> None:
|
||||
"""在配置更新后重载连接状态。
|
||||
|
||||
Args:
|
||||
scope: 配置变更范围。
|
||||
config_data: 最新的配置数据。
|
||||
version: 配置版本号。
|
||||
"""
|
||||
if scope != "self":
|
||||
return
|
||||
|
||||
self.set_plugin_config(config_data)
|
||||
if version:
|
||||
self.ctx.logger.debug(f"NapCat 适配器收到配置更新通知: {version}")
|
||||
await self._restart_connection_if_needed()
|
||||
|
||||
@MessageGateway(
|
||||
name=NAPCAT_GATEWAY_NAME,
|
||||
route_type="duplex",
|
||||
platform="qq",
|
||||
protocol="napcat",
|
||||
description="NapCat 正向 WebSocket 双工消息网关",
|
||||
)
|
||||
async def handle_napcat_gateway(
|
||||
self,
|
||||
message: Dict[str, Any],
|
||||
route: Optional[Dict[str, Any]] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
**kwargs: Any,
|
||||
) -> Dict[str, Any]:
|
||||
"""处理 Host 出站消息并发送到 NapCat。
|
||||
|
||||
Args:
|
||||
message: Host 侧标准 ``MessageDict``。
|
||||
route: Platform IO 生成的路由信息。
|
||||
metadata: Platform IO 附带的投递元数据。
|
||||
**kwargs: 预留扩展参数。
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: 标准化后的发送结果。
|
||||
"""
|
||||
del metadata
|
||||
del kwargs
|
||||
|
||||
runtime_bundle = self._require_runtime_bundle()
|
||||
try:
|
||||
action_name, params = runtime_bundle.outbound_codec.build_outbound_action(message, route or {})
|
||||
response = await runtime_bundle.transport.call_action(action_name, params)
|
||||
except Exception as exc:
|
||||
return {"success": False, "error": str(exc)}
|
||||
|
||||
if str(response.get("status", "")).lower() != "ok":
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(response.get("wording") or response.get("message") or "NapCat send failed"),
|
||||
"metadata": {"retcode": response.get("retcode")},
|
||||
}
|
||||
|
||||
response_data = response.get("data", {})
|
||||
internal_message_id = str(message.get("message_id") or "").strip()
|
||||
external_message_id = ""
|
||||
if isinstance(response_data, Mapping):
|
||||
external_message_id = str(response_data.get("message_id") or "")
|
||||
|
||||
adapter_callbacks = []
|
||||
if internal_message_id and external_message_id and internal_message_id != external_message_id:
|
||||
adapter_callbacks.append(
|
||||
{
|
||||
"name": "message_id_echo",
|
||||
"payload": {
|
||||
"content": {
|
||||
"type": "echo",
|
||||
"echo": internal_message_id,
|
||||
"actual_id": external_message_id,
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"external_message_id": external_message_id or None,
|
||||
"metadata": {
|
||||
"action": action_name,
|
||||
"adapter_callbacks": adapter_callbacks,
|
||||
},
|
||||
}
|
||||
|
||||
def _ensure_runtime_components(self) -> None:
|
||||
"""确保运行时依赖对象已经完成初始化。"""
|
||||
if self._event_router is None:
|
||||
self._event_router = NapCatEventRouter(
|
||||
gateway_capability=self.ctx.gateway,
|
||||
logger=self.ctx.logger,
|
||||
gateway_name=NAPCAT_GATEWAY_NAME,
|
||||
load_settings=self._load_settings,
|
||||
)
|
||||
|
||||
if self._runtime_bundle is None:
|
||||
runtime_builder = NapCatRuntimeBuilder(
|
||||
gateway_capability=self.ctx.gateway,
|
||||
logger=self.ctx.logger,
|
||||
gateway_name=NAPCAT_GATEWAY_NAME,
|
||||
)
|
||||
self._runtime_bundle = runtime_builder.build(
|
||||
on_connection_opened=self._event_router.bootstrap_adapter_runtime_state,
|
||||
on_connection_closed=self._event_router.handle_transport_disconnected,
|
||||
on_payload=self._event_router.handle_transport_payload,
|
||||
on_natural_lift=self._event_router.emit_natural_lift_notice,
|
||||
on_heartbeat_timeout=self._event_router.handle_heartbeat_timeout,
|
||||
)
|
||||
self._event_router.bind_runtime(self._runtime_bundle)
|
||||
self._bind_runtime_aliases(self._runtime_bundle)
|
||||
|
||||
def _bind_runtime_aliases(self, runtime_bundle: NapCatRuntimeBundle) -> None:
|
||||
"""同步运行时组件到插件级别的快捷引用。
|
||||
|
||||
Args:
|
||||
runtime_bundle: 已初始化的运行时组件集合。
|
||||
"""
|
||||
self._action_service = runtime_bundle.action_service
|
||||
self._query_service = runtime_bundle.query_service
|
||||
|
||||
def _load_settings(self) -> NapCatPluginSettings:
|
||||
"""返回当前生效的插件配置。
|
||||
|
||||
Returns:
|
||||
NapCatPluginSettings: 当前生效的插件配置。
|
||||
"""
|
||||
return cast(NapCatPluginSettings, self.config)
|
||||
|
||||
async def _restart_connection_if_needed(self) -> None:
|
||||
"""根据当前配置重启连接循环。"""
|
||||
self._ensure_runtime_components()
|
||||
runtime_bundle = self._require_runtime_bundle()
|
||||
settings = self._load_settings()
|
||||
|
||||
await self._stop_connection()
|
||||
if not settings.should_connect():
|
||||
self.ctx.logger.info("NapCat 适配器保持空闲状态,因为插件或配置未启用")
|
||||
return
|
||||
if not settings.validate_runtime_config(self.ctx.logger):
|
||||
return
|
||||
if not runtime_bundle.transport.is_available():
|
||||
self.ctx.logger.error("NapCat 适配器依赖 aiohttp,但当前环境未安装该依赖")
|
||||
return
|
||||
|
||||
if not settings.chat.enable_chat_list_filter:
|
||||
self.ctx.logger.info(
|
||||
"NapCat 聊天名单过滤已关闭:将忽略 group_list 与 private_list,仅保留 ban_user_id 和官方机器人屏蔽规则"
|
||||
)
|
||||
|
||||
runtime_bundle.transport.configure(settings.napcat_server)
|
||||
await runtime_bundle.transport.start()
|
||||
|
||||
async def _stop_connection(self) -> None:
|
||||
"""停止当前连接并清理运行时缓存。"""
|
||||
runtime_bundle = self._runtime_bundle
|
||||
if runtime_bundle is None:
|
||||
return
|
||||
|
||||
await runtime_bundle.transport.stop()
|
||||
if self._event_router is not None:
|
||||
self._event_router.reset_caches()
|
||||
|
||||
def _require_runtime_bundle(self) -> NapCatRuntimeBundle:
|
||||
"""返回当前已初始化的运行时组件集合。
|
||||
|
||||
Returns:
|
||||
NapCatRuntimeBundle: 当前运行时组件集合。
|
||||
|
||||
Raises:
|
||||
RuntimeError: 当运行时尚未初始化时抛出。
|
||||
"""
|
||||
self._ensure_runtime_components()
|
||||
runtime_bundle = self._runtime_bundle
|
||||
if runtime_bundle is None:
|
||||
raise RuntimeError("NapCat 运行时尚未初始化")
|
||||
return runtime_bundle
|
||||
|
||||
|
||||
def create_plugin() -> NapCatAdapterPlugin:
|
||||
"""创建插件实例。
|
||||
|
||||
Returns:
|
||||
NapCatAdapterPlugin: NapCat 内置适配器插件实例。
|
||||
"""
|
||||
return NapCatAdapterPlugin()
|
||||
Reference in New Issue
Block a user