Merge pull request #1113 from RockChinQ/feat/component-manifest

feat: component discovering engine
This commit is contained in:
Junyan Qin (Chin)
2025-02-23 22:16:38 +08:00
committed by GitHub
35 changed files with 813 additions and 93 deletions
+1
View File
@@ -29,6 +29,7 @@ qcapi
claude.json claude.json
bard.json bard.json
/*yaml /*yaml
!components.yaml
!/docker-compose.yaml !/docker-compose.yaml
data/labels/instance_id.json data/labels/instance_id.json
.DS_Store .DS_Store
+15
View File
@@ -0,0 +1,15 @@
apiVersion: v1
kind: Blueprint
metadata:
name: builtin-components
label:
en_US: Builtin Components
zh_CN: 内置组件
spec:
components:
ComponentTemplate:
fromFiles:
- pkg/platform/adapter.yaml
MessagePlatformAdapter:
fromDirs:
- path: pkg/platform/sources/
+3
View File
@@ -25,6 +25,7 @@ from ..utils import version as version_mgr, proxy as proxy_mgr, announce as anno
from ..persistence import mgr as persistencemgr from ..persistence import mgr as persistencemgr
from ..api.http.controller import main as http_controller from ..api.http.controller import main as http_controller
from ..api.http.service import user as user_service from ..api.http.service import user as user_service
from ..discover import engine as discover_engine
from ..utils import logcache, ip from ..utils import logcache, ip
from . import taskmgr from . import taskmgr
from . import entities as core_entities from . import entities as core_entities
@@ -38,6 +39,8 @@ class Application:
# asyncio_tasks: list[asyncio.Task] = [] # asyncio_tasks: list[asyncio.Task] = []
task_mgr: taskmgr.AsyncTaskManager = None task_mgr: taskmgr.AsyncTaskManager = None
discover: discover_engine.ComponentDiscoveryEngine = None
platform_mgr: im_mgr.PlatformManager = None platform_mgr: im_mgr.PlatformManager = None
cmd_mgr: cmdmgr.CommandManager = None cmd_mgr: cmdmgr.CommandManager = None
+1 -1
View File
@@ -57,7 +57,7 @@ class Query(pydantic.BaseModel):
message_chain: platform_message.MessageChain message_chain: platform_message.MessageChain
"""消息链,platform收到的原始消息链""" """消息链,platform收到的原始消息链"""
adapter: msadapter.MessageSourceAdapter adapter: msadapter.MessagePlatformAdapter
"""消息平台适配器对象,单个app中可能启用了多个消息平台适配器,此对象表明发起此query的适配器""" """消息平台适配器对象,单个app中可能启用了多个消息平台适配器,此对象表明发起此query的适配器"""
session: typing.Optional[Session] = None session: typing.Optional[Session] = None
+7
View File
@@ -18,6 +18,7 @@ from ...platform import manager as im_mgr
from ...persistence import mgr as persistencemgr from ...persistence import mgr as persistencemgr
from ...api.http.controller import main as http_controller from ...api.http.controller import main as http_controller
from ...api.http.service import user as user_service from ...api.http.service import user as user_service
from ...discover import engine as discover_engine
from ...utils import logcache from ...utils import logcache
from .. import taskmgr from .. import taskmgr
@@ -32,6 +33,12 @@ class BuildAppStage(stage.BootingStage):
""" """
ap.task_mgr = taskmgr.AsyncTaskManager(ap) ap.task_mgr = taskmgr.AsyncTaskManager(ap)
discover = discover_engine.ComponentDiscoveryEngine(ap)
discover.discover_blueprint(
"components.yaml"
)
ap.discover = discover
proxy_mgr = proxy.ProxyManager(ap) proxy_mgr = proxy.ProxyManager(ap)
await proxy_mgr.initialize() await proxy_mgr.initialize()
ap.proxy_mgr = proxy_mgr ap.proxy_mgr = proxy_mgr
View File
+197
View File
@@ -0,0 +1,197 @@
from __future__ import annotations
import typing
import importlib
import os
import inspect
import yaml
import pydantic
from ..core import app
class I18nString(pydantic.BaseModel):
"""国际化字符串"""
en_US: str
"""英文"""
zh_CN: typing.Optional[str] = None
"""中文"""
ja_JP: typing.Optional[str] = None
"""日文"""
class Metadata(pydantic.BaseModel):
"""元数据"""
name: str
"""名称"""
label: I18nString
"""标签"""
description: typing.Optional[I18nString] = None
"""描述"""
icon: typing.Optional[str] = None
"""图标"""
class PythonExecution(pydantic.BaseModel):
"""Python执行"""
path: str
"""路径"""
attr: str
"""属性"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
if self.path.startswith('./'):
self.path = self.path[2:]
class Execution(pydantic.BaseModel):
"""执行"""
python: PythonExecution
"""Python执行"""
class Component(pydantic.BaseModel):
"""组件清单"""
owner: str
"""组件所属"""
manifest: typing.Dict[str, typing.Any]
"""组件清单内容"""
rel_path: str
"""组件清单相对main.py的路径"""
_metadata: Metadata
"""组件元数据"""
_spec: typing.Dict[str, typing.Any]
"""组件规格"""
_execution: Execution
"""组件执行"""
def __init__(self, owner: str, manifest: typing.Dict[str, typing.Any], rel_path: str):
super().__init__(
owner=owner,
manifest=manifest,
rel_path=rel_path
)
self._metadata = Metadata(**manifest['metadata'])
self._spec = manifest['spec']
self._execution = Execution(**manifest['execution']) if 'execution' in manifest else None
@property
def kind(self) -> str:
"""组件类型"""
return self.manifest['kind']
@property
def metadata(self) -> Metadata:
"""组件元数据"""
return self._metadata
@property
def spec(self) -> typing.Dict[str, typing.Any]:
"""组件规格"""
return self._spec
@property
def execution(self) -> Execution:
"""组件执行"""
return self._execution
def get_python_component_class(self) -> typing.Type[typing.Any]:
"""获取Python组件类"""
parent_path = os.path.dirname(self.rel_path)
module_path = os.path.join(parent_path, self.execution.python.path)
if module_path.endswith('.py'):
module_path = module_path[:-3]
module_path = module_path.replace('/', '.').replace('\\', '.')
module = importlib.import_module(module_path)
return getattr(module, self.execution.python.attr)
class ComponentDiscoveryEngine:
"""组件发现引擎"""
ap: app.Application
"""应用实例"""
components: typing.Dict[str, typing.List[Component]] = {}
"""组件列表"""
def __init__(self, ap: app.Application):
self.ap = ap
def load_component_manifest(self, path: str, owner: str = 'builtin', no_save: bool = False) -> Component:
"""加载组件清单"""
with open(path, 'r') as f:
manifest = yaml.safe_load(f)
comp = Component(
owner=owner,
manifest=manifest,
rel_path=path
)
if not no_save:
if comp.kind not in self.components:
self.components[comp.kind] = []
self.components[comp.kind].append(comp)
return comp
def load_component_manifests_in_dir(self, path: str, owner: str = 'builtin', no_save: bool = False) -> typing.List[Component]:
"""加载目录中的组件清单"""
components: typing.List[Component] = []
for file in os.listdir(path):
if file.endswith('.yaml') or file.endswith('.yml'):
components.append(self.load_component_manifest(os.path.join(path, file), owner, no_save))
return components
def load_blueprint_comp_group(self, group: dict, owner: str = 'builtin', no_save: bool = False) -> typing.List[Component]:
"""加载蓝图组件组"""
components: typing.List[Component] = []
if 'fromFiles' in group:
for file in group['fromFiles']:
components.append(self.load_component_manifest(file, owner, no_save))
if 'fromDirs' in group:
for dir in group['fromDirs']:
path = dir['path']
# depth = dir['depth']
components.extend(self.load_component_manifests_in_dir(path, owner, no_save))
return components
def discover_blueprint(self, blueprint_manifest_path: str, owner: str = 'builtin'):
"""发现蓝图"""
blueprint_manifest = self.load_component_manifest(blueprint_manifest_path, owner, no_save=True)
assert blueprint_manifest.kind == 'Blueprint', '`Kind` must be `Blueprint`'
components: typing.Dict[str, typing.List[Component]] = {}
# load ComponentTemplate first
if 'ComponentTemplate' in blueprint_manifest.spec['components']:
components['ComponentTemplate'] = self.load_blueprint_comp_group(blueprint_manifest.spec['components']['ComponentTemplate'], owner)
for name, component in blueprint_manifest.spec['components'].items():
if name == 'ComponentTemplate':
continue
components[name] = self.load_blueprint_comp_group(component, owner)
return blueprint_manifest, components
def get_components_by_kind(self, kind: str) -> typing.List[Component]:
"""获取指定类型的组件"""
if kind not in self.components:
raise ValueError(f'No components found for kind: {kind}')
return self.components[kind]
+1 -1
View File
@@ -33,7 +33,7 @@ class QueryPool:
sender_id: typing.Union[int, str], sender_id: typing.Union[int, str],
message_event: platform_events.MessageEvent, message_event: platform_events.MessageEvent,
message_chain: platform_message.MessageChain, message_chain: platform_message.MessageChain,
adapter: msadapter.MessageSourceAdapter adapter: msadapter.MessagePlatformAdapter
) -> entities.Query: ) -> entities.Query:
async with self.condition: async with self.condition:
query = entities.Query( query = entities.Query(
+3 -23
View File
@@ -10,27 +10,7 @@ from .types import message as platform_message
from .types import events as platform_events from .types import events as platform_events
preregistered_adapters: list[typing.Type[MessageSourceAdapter]] = [] class MessagePlatformAdapter(metaclass=abc.ABCMeta):
def adapter_class(
name: str
):
"""消息平台适配器类装饰器
Args:
name (str): 适配器名称
Returns:
typing.Callable[[typing.Type[MessageSourceAdapter]], typing.Type[MessageSourceAdapter]]: 装饰器
"""
def decorator(cls: typing.Type[MessageSourceAdapter]) -> typing.Type[MessageSourceAdapter]:
cls.name = name
preregistered_adapters.append(cls)
return cls
return decorator
class MessageSourceAdapter(metaclass=abc.ABCMeta):
"""消息平台适配器基类""" """消息平台适配器基类"""
name: str name: str
@@ -89,7 +69,7 @@ class MessageSourceAdapter(metaclass=abc.ABCMeta):
def register_listener( def register_listener(
self, self,
event_type: typing.Type[platform_message.Event], event_type: typing.Type[platform_message.Event],
callback: typing.Callable[[platform_message.Event, MessageSourceAdapter], None] callback: typing.Callable[[platform_message.Event, MessagePlatformAdapter], None]
): ):
"""注册事件监听器 """注册事件监听器
@@ -102,7 +82,7 @@ class MessageSourceAdapter(metaclass=abc.ABCMeta):
def unregister_listener( def unregister_listener(
self, self,
event_type: typing.Type[platform_message.Event], event_type: typing.Type[platform_message.Event],
callback: typing.Callable[[platform_message.Event, MessageSourceAdapter], None] callback: typing.Callable[[platform_message.Event, MessagePlatformAdapter], None]
): ):
"""注销事件监听器 """注销事件监听器
+14
View File
@@ -0,0 +1,14 @@
apiVersion: v1
kind: ComponentTemplate
metadata:
name: MessagePlatformAdapter
label:
en_US: Message Platform Adapter
zh_CN: 消息平台适配器模板类
spec:
type:
- python
execution:
python:
path: ./adapter.py
attr: MessagePlatformAdapter
+19 -11
View File
@@ -18,6 +18,8 @@ from .types import message as platform_message
from .types import events as platform_events from .types import events as platform_events
from .types import entities as platform_entities from .types import entities as platform_entities
from ..discover import engine
# 处理 3.4 移除了 YiriMirai 之后,插件的兼容性问题 # 处理 3.4 移除了 YiriMirai 之后,插件的兼容性问题
from . import types as mirai from . import types as mirai
sys.modules['mirai'] = mirai sys.modules['mirai'] = mirai
@@ -27,7 +29,9 @@ sys.modules['mirai'] = mirai
class PlatformManager: class PlatformManager:
# adapter: msadapter.MessageSourceAdapter = None # adapter: msadapter.MessageSourceAdapter = None
adapters: list[msadapter.MessageSourceAdapter] = [] adapters: list[msadapter.MessagePlatformAdapter] = []
message_platform_adapter_components: list[engine.Component] = []
# modern # modern
ap: app.Application = None ap: app.Application = None
@@ -39,9 +43,13 @@ class PlatformManager:
async def initialize(self): async def initialize(self):
from .sources import nakuru, aiocqhttp, qqbotpy, qqofficial, wecom, lark, discord, gewechat, officialaccount, telegram, dingtalk components = self.ap.discover.get_components_by_kind('MessagePlatformAdapter')
async def on_friend_message(event: platform_events.FriendMessage, adapter: msadapter.MessageSourceAdapter): self.message_platform_adapter_components = components
# from .sources import nakuru, aiocqhttp, qqbotpy, qqofficial, wecom, lark, discord, gewechat, officialaccount, telegram, dingtalk
async def on_friend_message(event: platform_events.FriendMessage, adapter: msadapter.MessagePlatformAdapter):
await self.ap.query_pool.add_query( await self.ap.query_pool.add_query(
launcher_type=core_entities.LauncherTypes.PERSON, launcher_type=core_entities.LauncherTypes.PERSON,
@@ -52,7 +60,7 @@ class PlatformManager:
adapter=adapter adapter=adapter
) )
async def on_group_message(event: platform_events.GroupMessage, adapter: msadapter.MessageSourceAdapter): async def on_group_message(event: platform_events.GroupMessage, adapter: msadapter.MessagePlatformAdapter):
await self.ap.query_pool.add_query( await self.ap.query_pool.add_query(
launcher_type=core_entities.LauncherTypes.GROUP, launcher_type=core_entities.LauncherTypes.GROUP,
@@ -76,10 +84,10 @@ class PlatformManager:
found = False found = False
for adapter in msadapter.preregistered_adapters: for adapter in self.message_platform_adapter_components:
if adapter.name == adapter_name: if adapter.metadata.name == adapter_name:
found = True found = True
adapter_cls = adapter adapter_cls = adapter.get_python_component_class()
adapter_inst = adapter_cls( adapter_inst = adapter_cls(
cfg_copy, cfg_copy,
@@ -102,7 +110,7 @@ class PlatformManager:
if len(self.adapters) == 0: if len(self.adapters) == 0:
self.ap.logger.warning('未运行平台适配器,请根据文档配置并启用平台适配器。') self.ap.logger.warning('未运行平台适配器,请根据文档配置并启用平台适配器。')
async def write_back_config(self, adapter_inst: msadapter.MessageSourceAdapter, config: dict): async def write_back_config(self, adapter_inst: msadapter.MessagePlatformAdapter, config: dict):
index = -2 index = -2
for i, adapter in enumerate(self.adapters): for i, adapter in enumerate(self.adapters):
@@ -131,7 +139,7 @@ class PlatformManager:
self.ap.platform_cfg.data['platform-adapters'][real_index] = new_cfg self.ap.platform_cfg.data['platform-adapters'][real_index] = new_cfg
await self.ap.platform_cfg.dump_config() await self.ap.platform_cfg.dump_config()
async def send(self, event: platform_events.MessageEvent, msg: platform_message.MessageChain, adapter: msadapter.MessageSourceAdapter): async def send(self, event: platform_events.MessageEvent, msg: platform_message.MessageChain, adapter: msadapter.MessagePlatformAdapter):
if self.ap.platform_cfg.data['at-sender'] and isinstance(event, platform_events.GroupMessage): if self.ap.platform_cfg.data['at-sender'] and isinstance(event, platform_events.GroupMessage):
@@ -152,7 +160,7 @@ class PlatformManager:
try: try:
tasks = [] tasks = []
for adapter in self.adapters: for adapter in self.adapters:
async def exception_wrapper(adapter: msadapter.MessageSourceAdapter): async def exception_wrapper(adapter: msadapter.MessagePlatformAdapter):
try: try:
await adapter.run_async() await adapter.run_async()
except Exception as e: except Exception as e:
@@ -167,7 +175,7 @@ class PlatformManager:
self.ap.task_mgr.create_task( self.ap.task_mgr.create_task(
task, task,
kind="platform-adapter", kind="platform-adapter",
name=f"platform-adapter-{adapter.name}", name=f"platform-adapter-{adapter.__class__.__name__}",
scopes=[core_entities.LifecycleControlScope.APPLICATION, core_entities.LifecycleControlScope.PLATFORM], scopes=[core_entities.LifecycleControlScope.APPLICATION, core_entities.LifecycleControlScope.PLATFORM],
) )
+3 -4
View File
@@ -210,8 +210,7 @@ class AiocqhttpEventConverter(adapter.EventConverter):
) )
@adapter.adapter_class("aiocqhttp") class AiocqhttpAdapter(adapter.MessagePlatformAdapter):
class AiocqhttpAdapter(adapter.MessageSourceAdapter):
bot: aiocqhttp.CQHttp bot: aiocqhttp.CQHttp
@@ -273,7 +272,7 @@ class AiocqhttpAdapter(adapter.MessageSourceAdapter):
def register_listener( def register_listener(
self, self,
event_type: typing.Type[platform_events.Event], event_type: typing.Type[platform_events.Event],
callback: typing.Callable[[platform_events.Event, adapter.MessageSourceAdapter], None], callback: typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None],
): ):
async def on_message(event: aiocqhttp.Event): async def on_message(event: aiocqhttp.Event):
self.bot_account_id = event.self_id self.bot_account_id = event.self_id
@@ -290,7 +289,7 @@ class AiocqhttpAdapter(adapter.MessageSourceAdapter):
def unregister_listener( def unregister_listener(
self, self,
event_type: typing.Type[platform_events.Event], event_type: typing.Type[platform_events.Event],
callback: typing.Callable[[platform_events.Event, adapter.MessageSourceAdapter], None], callback: typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None],
): ):
return super().unregister_listener(event_type, callback) return super().unregister_listener(event_type, callback)
+37
View File
@@ -0,0 +1,37 @@
apiVersion: v1
kind: MessagePlatformAdapter
metadata:
name: aiocqhttp
label:
en_US: OneBot v11 Adapter
zh_CN: OneBot v11 适配器
description:
en_US: OneBot v11 Adapter
zh_CN: OneBot v11 适配器
spec:
config:
- name: host
label:
en_US: Host
zh_CN: 主机
type: string
required: true
default: 0.0.0.0
- name: port
label:
en_US: Port
zh_CN: 端口
type: int
required: true
default: 2280
- name: access-token
label:
en_US: Access Token
zh_CN: 访问令牌
type: string
required: false
default: ""
execution:
python:
path: ./aiocqhttp.py
attr: AiocqhttpAdapter
+5 -5
View File
@@ -3,7 +3,7 @@ import traceback
import typing import typing
from libs.dingtalk_api.dingtalkevent import DingTalkEvent from libs.dingtalk_api.dingtalkevent import DingTalkEvent
from pkg.platform.types import message as platform_message from pkg.platform.types import message as platform_message
from pkg.platform.adapter import MessageSourceAdapter from pkg.platform.adapter import MessagePlatformAdapter
from pkg.platform.types import events as platform_events, message as platform_message from pkg.platform.types import events as platform_events, message as platform_message
from pkg.core import app from pkg.core import app
from .. import adapter from .. import adapter
@@ -96,8 +96,8 @@ class DingTalkEventConverter(adapter.EventConverter):
source_platform_object=event source_platform_object=event
) )
@adapter.adapter_class("dingtalk")
class DingTalkAdapter(adapter.MessageSourceAdapter): class DingTalkAdapter(adapter.MessagePlatformAdapter):
bot: DingTalkClient bot: DingTalkClient
ap: app.Application ap: app.Application
bot_account_id: str bot_account_id: str
@@ -149,7 +149,7 @@ class DingTalkAdapter(adapter.MessageSourceAdapter):
self, self,
event_type: typing.Type[platform_events.Event], event_type: typing.Type[platform_events.Event],
callback: typing.Callable[ callback: typing.Callable[
[platform_events.Event, adapter.MessageSourceAdapter], None [platform_events.Event, adapter.MessagePlatformAdapter], None
], ],
): ):
async def on_message(event: DingTalkEvent): async def on_message(event: DingTalkEvent):
@@ -176,7 +176,7 @@ class DingTalkAdapter(adapter.MessageSourceAdapter):
async def unregister_listener( async def unregister_listener(
self, self,
event_type: type, event_type: type,
callback: typing.Callable[[platform_events.Event, MessageSourceAdapter], None], callback: typing.Callable[[platform_events.Event, MessagePlatformAdapter], None],
): ):
return super().unregister_listener(event_type, callback) return super().unregister_listener(event_type, callback)
+44
View File
@@ -0,0 +1,44 @@
apiVersion: v1
kind: MessagePlatformAdapter
metadata:
name: dingtalk
label:
en_US: DingTalk
zh_CN: 钉钉
description:
en_US: DingTalk Adapter
zh_CN: 钉钉适配器
spec:
config:
- name: client_id
label:
en_US: Client ID
zh_CN: 客户端ID
type: string
required: true
default: ""
- name: client_secret
label:
en_US: Client Secret
zh_CN: 客户端密钥
type: string
required: true
default: ""
- name: robot_code
label:
en_US: Robot Code
zh_CN: 机器人代码
type: string
required: true
default: ""
- name: robot_name
label:
en_US: Robot Name
zh_CN: 机器人名称
type: string
required: true
default: ""
execution:
python:
path: ./dingtalk.py
attr: DingTalkAdapter
+5 -5
View File
@@ -168,8 +168,8 @@ class DiscordEventConverter(adapter.EventConverter):
source_platform_object=event, source_platform_object=event,
) )
@adapter.adapter_class("discord")
class DiscordMessageSourceAdapter(adapter.MessageSourceAdapter): class DiscordAdapter(adapter.MessagePlatformAdapter):
bot: discord.Client bot: discord.Client
@@ -184,7 +184,7 @@ class DiscordMessageSourceAdapter(adapter.MessageSourceAdapter):
listeners: typing.Dict[ listeners: typing.Dict[
typing.Type[platform_events.Event], typing.Type[platform_events.Event],
typing.Callable[[platform_events.Event, adapter.MessageSourceAdapter], None], typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None],
] = {} ] = {}
def __init__(self, config: dict, ap: app.Application): def __init__(self, config: dict, ap: app.Application):
@@ -249,14 +249,14 @@ class DiscordMessageSourceAdapter(adapter.MessageSourceAdapter):
def register_listener( def register_listener(
self, self,
event_type: typing.Type[platform_events.Event], event_type: typing.Type[platform_events.Event],
callback: typing.Callable[[platform_events.Event, adapter.MessageSourceAdapter], None], callback: typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None],
): ):
self.listeners[event_type] = callback self.listeners[event_type] = callback
def unregister_listener( def unregister_listener(
self, self,
event_type: typing.Type[platform_events.Event], event_type: typing.Type[platform_events.Event],
callback: typing.Callable[[platform_events.Event, adapter.MessageSourceAdapter], None], callback: typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None],
): ):
self.listeners.pop(event_type) self.listeners.pop(event_type)
+30
View File
@@ -0,0 +1,30 @@
apiVersion: v1
kind: MessagePlatformAdapter
metadata:
name: discord
label:
en_US: Discord
zh_CN: Discord
description:
en_US: Discord Adapter
zh_CN: Discord 适配器
spec:
config:
- name: client_id
label:
en_US: Client ID
zh_CN: 客户端ID
type: string
required: true
default: ""
- name: token
label:
en_US: Token
zh_CN: 令牌
type: string
required: true
default: ""
execution:
python:
path: ./discord.py
attr: DiscordAdapter
+4 -5
View File
@@ -133,8 +133,7 @@ class GewechatEventConverter(adapter.EventConverter):
) )
@adapter.adapter_class("gewechat") class GeWeChatAdapter(adapter.MessagePlatformAdapter):
class GewechatMessageSourceAdapter(adapter.MessageSourceAdapter):
bot: gewechat_client.GewechatClient bot: gewechat_client.GewechatClient
quart_app: quart.Quart quart_app: quart.Quart
@@ -150,7 +149,7 @@ class GewechatMessageSourceAdapter(adapter.MessageSourceAdapter):
listeners: typing.Dict[ listeners: typing.Dict[
typing.Type[platform_events.Event], typing.Type[platform_events.Event],
typing.Callable[[platform_events.Event, adapter.MessageSourceAdapter], None], typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None],
] = {} ] = {}
def __init__(self, config: dict, ap: app.Application): def __init__(self, config: dict, ap: app.Application):
@@ -222,14 +221,14 @@ class GewechatMessageSourceAdapter(adapter.MessageSourceAdapter):
def register_listener( def register_listener(
self, self,
event_type: typing.Type[platform_events.Event], event_type: typing.Type[platform_events.Event],
callback: typing.Callable[[platform_events.Event, adapter.MessageSourceAdapter], None] callback: typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None]
): ):
self.listeners[event_type] = callback self.listeners[event_type] = callback
def unregister_listener( def unregister_listener(
self, self,
event_type: typing.Type[platform_events.Event], event_type: typing.Type[platform_events.Event],
callback: typing.Callable[[platform_events.Event, adapter.MessageSourceAdapter], None] callback: typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None]
): ):
pass pass
+51
View File
@@ -0,0 +1,51 @@
apiVersion: v1
kind: MessagePlatformAdapter
metadata:
name: gewechat
label:
en_US: GeWeChat
zh_CN: GeWeChat(个人微信)
description:
en_US: GeWeChat Adapter
zh_CN: GeWeChat 适配器
spec:
config:
- name: gewechat_url
label:
en_US: GeWeChat URL
zh_CN: GeWeChat URL
type: string
required: true
default: ""
- name: port
label:
en_US: Port
zh_CN: 端口
type: int
required: true
default: 2286
- name: callback_url
label:
en_US: Callback URL
zh_CN: 回调URL
type: string
required: true
default: ""
- name: app_id
label:
en_US: App ID
zh_CN: 应用ID
type: string
required: true
default: ""
- name: token
label:
en_US: Token
zh_CN: 令牌
type: string
required: true
default: ""
execution:
python:
path: ./gewechat.py
attr: GeWeChatAdapter
+4 -5
View File
@@ -298,8 +298,7 @@ class LarkEventConverter(adapter.EventConverter):
) )
@adapter.adapter_class("lark") class LarkAdapter(adapter.MessagePlatformAdapter):
class LarkMessageSourceAdapter(adapter.MessageSourceAdapter):
bot: lark_oapi.ws.Client bot: lark_oapi.ws.Client
api_client: lark_oapi.Client api_client: lark_oapi.Client
@@ -312,7 +311,7 @@ class LarkMessageSourceAdapter(adapter.MessageSourceAdapter):
listeners: typing.Dict[ listeners: typing.Dict[
typing.Type[platform_events.Event], typing.Type[platform_events.Event],
typing.Callable[[platform_events.Event, adapter.MessageSourceAdapter], None], typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None],
] = {} ] = {}
config: dict config: dict
@@ -450,7 +449,7 @@ class LarkMessageSourceAdapter(adapter.MessageSourceAdapter):
self, self,
event_type: typing.Type[platform_events.Event], event_type: typing.Type[platform_events.Event],
callback: typing.Callable[ callback: typing.Callable[
[platform_events.Event, adapter.MessageSourceAdapter], None [platform_events.Event, adapter.MessagePlatformAdapter], None
], ],
): ):
self.listeners[event_type] = callback self.listeners[event_type] = callback
@@ -459,7 +458,7 @@ class LarkMessageSourceAdapter(adapter.MessageSourceAdapter):
self, self,
event_type: typing.Type[platform_events.Event], event_type: typing.Type[platform_events.Event],
callback: typing.Callable[ callback: typing.Callable[
[platform_events.Event, adapter.MessageSourceAdapter], None [platform_events.Event, adapter.MessagePlatformAdapter], None
], ],
): ):
self.listeners.pop(event_type) self.listeners.pop(event_type)
+58
View File
@@ -0,0 +1,58 @@
apiVersion: v1
kind: MessagePlatformAdapter
metadata:
name: lark
label:
en_US: Lark
zh_CN: 飞书
description:
en_US: Lark Adapter
zh_CN: 飞书适配器
spec:
config:
- name: app_id
label:
en_US: App ID
zh_CN: 应用ID
type: string
required: true
default: ""
- name: app_secret
label:
en_US: App Secret
zh_CN: 应用密钥
type: string
required: true
default: ""
- name: bot_name
label:
en_US: Bot Name
zh_CN: 机器人名称
type: string
required: true
default: ""
- name: enable-webhook
label:
en_US: Enable Webhook Mode
zh_CN: 启用Webhook模式
type: boolean
required: true
default: false
- name: port
label:
en_US: Webhook Port
zh_CN: Webhook端口
type: int
required: true
default: 2285
- name: encrypt-key
label:
en_US: Encrypt Key
zh_CN: 加密密钥
type: string
required: true
default: ""
execution:
python:
path: ./lark.py
attr: LarkAdapter
+3 -4
View File
@@ -158,8 +158,7 @@ class NakuruProjectEventConverter(adapter_model.EventConverter):
raise Exception("未支持转换的事件类型: " + str(event)) raise Exception("未支持转换的事件类型: " + str(event))
@adapter_model.adapter_class("nakuru") class NakuruAdapter(adapter_model.MessagePlatformAdapter):
class NakuruProjectAdapter(adapter_model.MessageSourceAdapter):
"""nakuru-project适配器""" """nakuru-project适配器"""
bot: nakuru.CQHTTP bot: nakuru.CQHTTP
bot_account_id: int bot_account_id: int
@@ -256,7 +255,7 @@ class NakuruProjectAdapter(adapter_model.MessageSourceAdapter):
def register_listener( def register_listener(
self, self,
event_type: typing.Type[platform_events.Event], event_type: typing.Type[platform_events.Event],
callback: typing.Callable[[platform_events.Event, adapter_model.MessageSourceAdapter], None] callback: typing.Callable[[platform_events.Event, adapter_model.MessagePlatformAdapter], None]
): ):
try: try:
@@ -284,7 +283,7 @@ class NakuruProjectAdapter(adapter_model.MessageSourceAdapter):
def unregister_listener( def unregister_listener(
self, self,
event_type: typing.Type[platform_events.Event], event_type: typing.Type[platform_events.Event],
callback: typing.Callable[[platform_events.Event, adapter_model.MessageSourceAdapter], None] callback: typing.Callable[[platform_events.Event, adapter_model.MessagePlatformAdapter], None]
): ):
nakuru_event_name = self.event_converter.yiri2target(event_type).__name__ nakuru_event_name = self.event_converter.yiri2target(event_type).__name__
+44
View File
@@ -0,0 +1,44 @@
apiVersion: v1
kind: MessagePlatformAdapter
metadata:
name: nakuru
label:
en_US: Nakuru
zh_CN: Nakuru
description:
en_US: Nakuru Adapter
zh_CN: Nakuru 适配器(go-cqhttp)
spec:
config:
- name: host
label:
en_US: Host
zh_CN: 主机
type: string
required: true
default: "127.0.0.1"
- name: http_port
label:
en_US: HTTP Port
zh_CN: HTTP端口
type: int
required: true
default: 5700
- name: ws_port
label:
en_US: WebSocket Port
zh_CN: WebSocket端口
type: int
required: true
default: 8080
- name: token
label:
en_US: Token
zh_CN: 令牌
type: string
required: true
default: ""
execution:
python:
path: ./nakuru.py
attr: NakuruAdapter
+5 -6
View File
@@ -5,13 +5,13 @@ import traceback
import time import time
import datetime import datetime
from pkg.core import app from pkg.core import app
from pkg.platform.adapter import MessageSourceAdapter from pkg.platform.adapter import MessagePlatformAdapter
from pkg.platform.types import events as platform_events, message as platform_message from pkg.platform.types import events as platform_events, message as platform_message
import aiocqhttp import aiocqhttp
import aiohttp import aiohttp
from libs.official_account_api.oaevent import OAEvent from libs.official_account_api.oaevent import OAEvent
from pkg.platform.adapter import MessageSourceAdapter from pkg.platform.adapter import MessagePlatformAdapter
from pkg.platform.types import events as platform_events, message as platform_message from pkg.platform.types import events as platform_events, message as platform_message
from libs.official_account_api.api import OAClient from libs.official_account_api.api import OAClient
from pkg.core import app from pkg.core import app
@@ -68,8 +68,7 @@ class OAEventConverter(adapter.EventConverter):
else: else:
return None return None
@adapter.adapter_class("officialaccount") class OfficialAccountAdapter(adapter.MessagePlatformAdapter):
class OfficialAccountAdapter(adapter.MessageSourceAdapter):
bot : OAClient bot : OAClient
ap : app.Application ap : app.Application
@@ -116,7 +115,7 @@ class OfficialAccountAdapter(adapter.MessageSourceAdapter):
pass pass
def register_listener(self, event_type: type, callback: typing.Callable[[platform_events.Event, MessageSourceAdapter], None]): def register_listener(self, event_type: type, callback: typing.Callable[[platform_events.Event, MessagePlatformAdapter], None]):
async def on_message(event: OAEvent): async def on_message(event: OAEvent):
self.bot_account_id = event.receiver_id self.bot_account_id = event.receiver_id
try: try:
@@ -148,7 +147,7 @@ class OfficialAccountAdapter(adapter.MessageSourceAdapter):
async def unregister_listener( async def unregister_listener(
self, self,
event_type: type, event_type: type,
callback: typing.Callable[[platform_events.Event, MessageSourceAdapter], None], callback: typing.Callable[[platform_events.Event, MessagePlatformAdapter], None],
): ):
return super().unregister_listener(event_type, callback) return super().unregister_listener(event_type, callback)
+58
View File
@@ -0,0 +1,58 @@
apiVersion: v1
kind: MessagePlatformAdapter
metadata:
name: officialaccount
label:
en_US: Official Account
zh_CN: 微信公众号
description:
en_US: Official Account Adapter
zh_CN: 微信公众号适配器
spec:
config:
- name: token
label:
en_US: Token
zh_CN: 令牌
type: string
required: true
default: ""
- name: EncodingAESKey
label:
en_US: EncodingAESKey
zh_CN: 消息加解密密钥
type: string
required: true
default: ""
- name: AppID
label:
en_US: App ID
zh_CN: 应用ID
type: string
required: true
default: ""
- name: AppSecret
label:
en_US: App Secret
zh_CN: 应用密钥
type: string
required: true
default: ""
- name: host
label:
en_US: Host
zh_CN: 监听主机
type: string
required: true
default: 0.0.0.0
- name: port
label:
en_US: Port
zh_CN: 监听端口
type: int
required: true
default: 2287
execution:
python:
path: ./officialaccount.py
attr: OfficialAccountAdapter
+3 -4
View File
@@ -360,8 +360,7 @@ class OfficialEventConverter(adapter_model.EventConverter):
) )
@adapter_model.adapter_class("qq-botpy") class OfficialAdapter(adapter_model.MessagePlatformAdapter):
class OfficialAdapter(adapter_model.MessageSourceAdapter):
"""QQ 官方消息适配器""" """QQ 官方消息适配器"""
bot: botpy.Client = None bot: botpy.Client = None
@@ -535,7 +534,7 @@ class OfficialAdapter(adapter_model.MessageSourceAdapter):
self, self,
event_type: typing.Type[platform_events.Event], event_type: typing.Type[platform_events.Event],
callback: typing.Callable[ callback: typing.Callable[
[platform_events.Event, adapter_model.MessageSourceAdapter], None [platform_events.Event, adapter_model.MessagePlatformAdapter], None
], ],
): ):
@@ -561,7 +560,7 @@ class OfficialAdapter(adapter_model.MessageSourceAdapter):
self, self,
event_type: typing.Type[platform_events.Event], event_type: typing.Type[platform_events.Event],
callback: typing.Callable[ callback: typing.Callable[
[platform_events.Event, adapter_model.MessageSourceAdapter], None [platform_events.Event, adapter_model.MessagePlatformAdapter], None
], ],
): ):
delattr(self.bot, event_handler_mapping[event_type]) delattr(self.bot, event_handler_mapping[event_type])
+37
View File
@@ -0,0 +1,37 @@
apiVersion: v1
kind: MessagePlatformAdapter
metadata:
name: qq-botpy
label:
en_US: QQBotPy
zh_CN: QQBotPy
description:
en_US: QQ Official API (WebSocket)
zh_CN: QQ 官方 API (WebSocket)
spec:
config:
- name: appid
label:
en_US: App ID
zh_CN: 应用ID
type: string
required: true
default: ""
- name: secret
label:
en_US: Secret
zh_CN: 密钥
type: string
required: true
default: ""
- name: intents
label:
en_US: Intents
zh_CN: 权限
type: array[string]
required: true
default: []
execution:
python:
path: ./qqbotpy.py
attr: OfficialAdapter
+4 -7
View File
@@ -6,7 +6,7 @@ import time
import datetime import datetime
import aiocqhttp import aiocqhttp
import aiohttp import aiohttp
from pkg.platform.adapter import MessageSourceAdapter from pkg.platform.adapter import MessagePlatformAdapter
from pkg.platform.types import events as platform_events, message as platform_message from pkg.platform.types import events as platform_events, message as platform_message
from pkg.core import app from pkg.core import app
from .. import adapter from .. import adapter
@@ -137,10 +137,7 @@ class QQOfficialEventConverter(adapter.EventConverter):
) )
class QQOfficialAdapter(adapter.MessagePlatformAdapter):
@adapter.adapter_class("qqofficial")
class QQOfficialAdapter(adapter.MessageSourceAdapter):
bot:QQOfficialClient bot:QQOfficialClient
ap:app.Application ap:app.Application
config:dict config:dict
@@ -213,7 +210,7 @@ class QQOfficialAdapter(adapter.MessageSourceAdapter):
self, self,
event_type: typing.Type[platform_events.Event], event_type: typing.Type[platform_events.Event],
callback: typing.Callable[ callback: typing.Callable[
[platform_events.Event, adapter.MessageSourceAdapter], None [platform_events.Event, adapter.MessagePlatformAdapter], None
], ],
): ):
async def on_message(event:QQOfficialEvent): async def on_message(event:QQOfficialEvent):
@@ -250,7 +247,7 @@ class QQOfficialAdapter(adapter.MessageSourceAdapter):
def unregister_listener( def unregister_listener(
self, self,
event_type: type, event_type: type,
callback: typing.Callable[[platform_events.Event, MessageSourceAdapter], None], callback: typing.Callable[[platform_events.Event, MessagePlatformAdapter], None],
): ):
return super().unregister_listener(event_type, callback) return super().unregister_listener(event_type, callback)
+44
View File
@@ -0,0 +1,44 @@
apiVersion: v1
kind: MessagePlatformAdapter
metadata:
name: qqofficial
label:
en_US: QQ Official API
zh_CN: QQ 官方 API
description:
en_US: QQ Official API (Webhook)
zh_CN: QQ 官方 API (Webhook)
spec:
config:
- name: appid
label:
en_US: App ID
zh_CN: 应用ID
type: string
required: true
default: ""
- name: secret
label:
en_US: Secret
zh_CN: 密钥
type: string
required: true
default: ""
- name: port
label:
en_US: Port
zh_CN: 监听端口
type: int
required: true
default: 2284
- name: token
label:
en_US: Token
zh_CN: 令牌
type: string
required: true
default: ""
execution:
python:
path: ./qqofficial.py
attr: QQOfficialAdapter
+5 -5
View File
@@ -147,8 +147,8 @@ class TelegramEventConverter(adapter.EventConverter):
source_platform_object=event source_platform_object=event
) )
@adapter.adapter_class("telegram")
class TelegramMessageSourceAdapter(adapter.MessageSourceAdapter): class TelegramAdapter(adapter.MessagePlatformAdapter):
bot: telegram.Bot bot: telegram.Bot
application: telegram.ext.Application application: telegram.ext.Application
@@ -163,7 +163,7 @@ class TelegramMessageSourceAdapter(adapter.MessageSourceAdapter):
listeners: typing.Dict[ listeners: typing.Dict[
typing.Type[platform_events.Event], typing.Type[platform_events.Event],
typing.Callable[[platform_events.Event, adapter.MessageSourceAdapter], None], typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None],
] = {} ] = {}
def __init__(self, config: dict, ap: app.Application): def __init__(self, config: dict, ap: app.Application):
@@ -218,14 +218,14 @@ class TelegramMessageSourceAdapter(adapter.MessageSourceAdapter):
def register_listener( def register_listener(
self, self,
event_type: typing.Type[platform_events.Event], event_type: typing.Type[platform_events.Event],
callback: typing.Callable[[platform_events.Event, adapter.MessageSourceAdapter], None], callback: typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None],
): ):
self.listeners[event_type] = callback self.listeners[event_type] = callback
def unregister_listener( def unregister_listener(
self, self,
event_type: typing.Type[platform_events.Event], event_type: typing.Type[platform_events.Event],
callback: typing.Callable[[platform_events.Event, adapter.MessageSourceAdapter], None], callback: typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None],
): ):
self.listeners.pop(event_type) self.listeners.pop(event_type)
+23
View File
@@ -0,0 +1,23 @@
apiVersion: v1
kind: MessagePlatformAdapter
metadata:
name: telegram
label:
en_US: Telegram
zh_CN: 电报
description:
en_US: Telegram Adapter
zh_CN: 电报适配器
spec:
config:
- name: token
label:
en_US: Token
zh_CN: 令牌
type: string
required: true
default: ""
execution:
python:
path: ./telegram.py
attr: TelegramAdapter
+4 -5
View File
@@ -8,7 +8,7 @@ import datetime
import aiocqhttp import aiocqhttp
import aiohttp import aiohttp
from libs.wecom_api.api import WecomClient from libs.wecom_api.api import WecomClient
from pkg.platform.adapter import MessageSourceAdapter from pkg.platform.adapter import MessagePlatformAdapter
from pkg.platform.types import events as platform_events, message as platform_message from pkg.platform.types import events as platform_events, message as platform_message
from libs.wecom_api.wecomevent import WecomEvent from libs.wecom_api.wecomevent import WecomEvent
from pkg.core import app from pkg.core import app
@@ -144,8 +144,7 @@ class WecomEventConverter:
) )
@adapter.adapter_class("wecom") class WecomAdapter(adapter.MessagePlatformAdapter):
class WecomeAdapter(adapter.MessageSourceAdapter):
bot: WecomClient bot: WecomClient
ap: app.Application ap: app.Application
@@ -207,7 +206,7 @@ class WecomeAdapter(adapter.MessageSourceAdapter):
self, self,
event_type: typing.Type[platform_events.Event], event_type: typing.Type[platform_events.Event],
callback: typing.Callable[ callback: typing.Callable[
[platform_events.Event, adapter.MessageSourceAdapter], None [platform_events.Event, adapter.MessagePlatformAdapter], None
], ],
): ):
async def on_message(event: WecomEvent): async def on_message(event: WecomEvent):
@@ -242,6 +241,6 @@ class WecomeAdapter(adapter.MessageSourceAdapter):
async def unregister_listener( async def unregister_listener(
self, self,
event_type: type, event_type: type,
callback: typing.Callable[[platform_events.Event, MessageSourceAdapter], None], callback: typing.Callable[[platform_events.Event, MessagePlatformAdapter], None],
): ):
return super().unregister_listener(event_type, callback) return super().unregister_listener(event_type, callback)
+65
View File
@@ -0,0 +1,65 @@
apiVersion: v1
kind: MessagePlatformAdapter
metadata:
name: wecom
label:
en_US: WeCom
zh_CN: 企业微信
description:
en_US: WeCom Adapter
zh_CN: 企业微信适配器
spec:
config:
- name: host
label:
en_US: Host
zh_CN: 监听主机
type: string
required: true
default: "0.0.0.0"
- name: port
label:
en_US: Port
zh_CN: 监听端口
type: int
required: true
default: 2290
- name: corpid
label:
en_US: Corpid
zh_CN: 企业ID
type: string
required: true
default: ""
- name: secret
label:
en_US: Secret
zh_CN: 密钥
type: string
required: true
default: ""
- name: token
label:
en_US: Token
zh_CN: 令牌
type: string
required: true
default: ""
- name: EncodingAESKey
label:
en_US: EncodingAESKey
zh_CN: 消息加解密密钥
type: string
required: true
default: ""
- name: contacts_secret
label:
en_US: Contacts Secret
zh_CN: 通讯录密钥
type: string
required: true
default: ""
execution:
python:
path: ./wecom.py
attr: WecomAdapter
+2 -2
View File
@@ -116,7 +116,7 @@ class APIHost:
# ========== 插件可调用的 API(主程序API ========== # ========== 插件可调用的 API(主程序API ==========
def get_platform_adapters(self) -> list[platform_adapter.MessageSourceAdapter]: def get_platform_adapters(self) -> list[platform_adapter.MessagePlatformAdapter]:
"""获取已启用的消息平台适配器列表 """获取已启用的消息平台适配器列表
Returns: Returns:
@@ -126,7 +126,7 @@ class APIHost:
async def send_active_message( async def send_active_message(
self, self,
adapter: platform_adapter.MessageSourceAdapter, adapter: platform_adapter.MessagePlatformAdapter,
target_type: str, target_type: str,
target_id: str, target_id: str,
message: platform_message.MessageChain, message: platform_message.MessageChain,
+14
View File
@@ -0,0 +1,14 @@
apiVersion: v1
kind: ComponentTemplate
metadata:
name: LLMAPIRequester
label:
en_US: LLM API Requester
zh_CN: LLM API 请求器
spec:
type:
- python
execution:
python:
path: ./requester.py
attr: LLMAPIRequester