feat: switch message platform adapters to sdk

This commit is contained in:
Junyan Qin
2025-06-16 13:18:59 +08:00
parent 62b2884011
commit a177786063
51 changed files with 361 additions and 1763 deletions

View File

@@ -3,7 +3,7 @@ from quart import request
import httpx import httpx
from quart import Quart from quart import Quart
from typing import Callable, Dict, Any from typing import Callable, Dict, Any
from pkg.platform.types import events as platform_events import langbot_plugin.api.entities.builtin.platform.events as platform_events
from .qqofficialevent import QQOfficialEvent from .qqofficialevent import QQOfficialEvent
import json import json
import traceback import traceback

View File

@@ -4,7 +4,7 @@ from quart import Quart, jsonify, request
from slack_sdk.web.async_client import AsyncWebClient from slack_sdk.web.async_client import AsyncWebClient
from .slackevent import SlackEvent from .slackevent import SlackEvent
from typing import Callable from typing import Callable
from pkg.platform.types import events as platform_events import langbot_plugin.api.entities.builtin.platform.events as platform_events
class SlackClient: class SlackClient:

View File

@@ -8,7 +8,7 @@ from quart import Quart
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from typing import Callable, Dict, Any from typing import Callable, Dict, Any
from .wecomevent import WecomEvent from .wecomevent import WecomEvent
from pkg.platform.types import message as platform_message import langbot_plugin.api.entities.builtin.platform.message as platform_message
import aiofiles import aiofiles

View File

@@ -8,7 +8,7 @@ from quart import Quart
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from typing import Callable from typing import Callable
from .wecomcsevent import WecomCSEvent from .wecomcsevent import WecomCSEvent
from pkg.platform.types import message as platform_message import langbot_plugin.api.entities.builtin.platform.message as platform_message
import aiofiles import aiofiles

View File

@@ -6,7 +6,7 @@ import pydantic
import langbot_plugin.api.entities.builtin.provider.session as provider_session import langbot_plugin.api.entities.builtin.provider.session as provider_session
from . import errors from . import errors
from ..platform.types import message as platform_message import langbot_plugin.api.entities.builtin.platform.message as platform_message
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query

View File

@@ -5,7 +5,7 @@ from ...core import app
from .. import stage, entities from .. import stage, entities
from . import filter as filter_model, entities as filter_entities from . import filter as filter_model, entities as filter_entities
from langbot_plugin.api.entities.builtin.provider import message as provider_message from langbot_plugin.api.entities.builtin.provider import message as provider_message
from ...platform.types import message as platform_message import langbot_plugin.api.entities.builtin.platform.message as platform_message
from ...utils import importutil from ...utils import importutil
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query
from . import filters from . import filters

View File

@@ -4,9 +4,9 @@ import enum
import typing import typing
import pydantic import pydantic
from ..platform.types import message as platform_message
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query
import langbot_plugin.api.entities.builtin.platform.message as platform_message
class ResultType(enum.Enum): class ResultType(enum.Enum):

View File

@@ -5,7 +5,7 @@ import traceback
from . import strategy from . import strategy
from .. import stage, entities from .. import stage, entities
from ...platform.types import message as platform_message import langbot_plugin.api.entities.builtin.platform.message as platform_message
from ...utils import importutil from ...utils import importutil
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query
from . import strategies from . import strategies

View File

@@ -4,8 +4,8 @@ from __future__ import annotations
from .. import strategy as strategy_model from .. import strategy as strategy_model
from ....platform.types import message as platform_message
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query
import langbot_plugin.api.entities.builtin.platform.message as platform_message
ForwardMessageDiaplay = platform_message.ForwardMessageDiaplay ForwardMessageDiaplay = platform_message.ForwardMessageDiaplay
Forward = platform_message.Forward Forward = platform_message.Forward

View File

@@ -8,10 +8,10 @@ import re
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
import functools import functools
from ....platform.types import message as platform_message
from .. import strategy as strategy_model from .. import strategy as strategy_model
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query
import langbot_plugin.api.entities.builtin.platform.message as platform_message
@strategy_model.strategy_class('image') @strategy_model.strategy_class('image')

View File

@@ -4,7 +4,8 @@ import typing
from ...core import app from ...core import app
from ...platform.types import message as platform_message
import langbot_plugin.api.entities.builtin.platform.message as platform_message
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query

View File

@@ -9,7 +9,8 @@ from ..core import app
from . import entities as pipeline_entities from . import entities as pipeline_entities
from ..entity.persistence import pipeline as persistence_pipeline from ..entity.persistence import pipeline as persistence_pipeline
from . import stage from . import stage
from ..platform.types import message as platform_message, events as platform_events import langbot_plugin.api.entities.builtin.platform.message as platform_message
import langbot_plugin.api.entities.builtin.platform.events as platform_events
from ..plugin import events from ..plugin import events
from ..utils import importutil from ..utils import importutil

View File

@@ -3,11 +3,11 @@ from __future__ import annotations
import asyncio import asyncio
import typing import typing
from ..platform import adapter as msadapter import langbot_plugin.api.entities.builtin.platform.message as platform_message
from ..platform.types import message as platform_message import langbot_plugin.api.entities.builtin.platform.events as platform_events
from ..platform.types import events as platform_events
import langbot_plugin.api.entities.builtin.provider.session as provider_session import langbot_plugin.api.entities.builtin.provider.session as provider_session
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query
import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
class QueryPool: class QueryPool:
@@ -35,7 +35,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.MessagePlatformAdapter, adapter: abstract_platform_adapter.AbstractMessagePlatformAdapter,
pipeline_uuid: typing.Optional[str] = None, pipeline_uuid: typing.Optional[str] = None,
) -> pipeline_query.Query: ) -> pipeline_query.Query:
async with self.condition: async with self.condition:

View File

@@ -5,7 +5,7 @@ import datetime
from .. import stage, entities from .. import stage, entities
from langbot_plugin.api.entities.builtin.provider import message as provider_message from langbot_plugin.api.entities.builtin.provider import message as provider_message
from ...plugin import events from ...plugin import events
from ...platform.types import message as platform_message import langbot_plugin.api.entities.builtin.platform.message as platform_message
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query

View File

@@ -9,7 +9,7 @@ from ... import entities
from ....provider import runner as runner_module from ....provider import runner as runner_module
from ....plugin import events from ....plugin import events
from ....platform.types import message as platform_message import langbot_plugin.api.entities.builtin.platform.message as platform_message
from ....utils import importutil from ....utils import importutil
from ....provider import runners from ....provider import runners
import langbot_plugin.api.entities.builtin.provider.session as provider_session import langbot_plugin.api.entities.builtin.provider.session as provider_session

View File

@@ -4,9 +4,9 @@ import typing
from .. import handler from .. import handler
from ... import entities from ... import entities
from langbot_plugin.api.entities.builtin.provider import message as provider_message
from ....plugin import events from ....plugin import events
from ....platform.types import message as platform_message import langbot_plugin.api.entities.builtin.provider.message as provider_message
import langbot_plugin.api.entities.builtin.platform.message as platform_message
import langbot_plugin.api.entities.builtin.provider.session as provider_session import langbot_plugin.api.entities.builtin.provider.session as provider_session
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query

View File

@@ -4,8 +4,8 @@ import random
import asyncio import asyncio
from ...platform.types import events as platform_events import langbot_plugin.api.entities.builtin.platform.events as platform_events
from ...platform.types import message as platform_message import langbot_plugin.api.entities.builtin.platform.message as platform_message
from .. import stage, entities from .. import stage, entities
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query

View File

@@ -1,6 +1,6 @@
import pydantic import pydantic
from ...platform.types import message as platform_message import langbot_plugin.api.entities.builtin.platform.message as platform_message
class RuleJudgeResult(pydantic.BaseModel): class RuleJudgeResult(pydantic.BaseModel):

View File

@@ -5,7 +5,7 @@ import typing
from ...core import app from ...core import app
from . import entities from . import entities
from ...platform.types import message as platform_message import langbot_plugin.api.entities.builtin.platform.message as platform_message
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
from .. import rule as rule_model from .. import rule as rule_model
from .. import entities from .. import entities
from ....platform.types import message as platform_message import langbot_plugin.api.entities.builtin.platform.message as platform_message
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query

View File

@@ -1,6 +1,6 @@
from .. import rule as rule_model from .. import rule as rule_model
from .. import entities from .. import entities
from ....platform.types import message as platform_message import langbot_plugin.api.entities.builtin.platform.message as platform_message
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query

View File

@@ -3,7 +3,7 @@ import random
from .. import rule as rule_model from .. import rule as rule_model
from .. import entities from .. import entities
from ....platform.types import message as platform_message import langbot_plugin.api.entities.builtin.platform.message as platform_message
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query

View File

@@ -3,7 +3,7 @@ import re
from .. import rule as rule_model from .. import rule as rule_model
from .. import entities from .. import entities
from ....platform.types import message as platform_message import langbot_plugin.api.entities.builtin.platform.message as platform_message
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query

View File

@@ -5,7 +5,7 @@ import typing
from .. import entities from .. import entities
from .. import stage from .. import stage
from ...plugin import events from ...plugin import events
from ...platform.types import message as platform_message import langbot_plugin.api.entities.builtin.platform.message as platform_message
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query

View File

@@ -1,156 +0,0 @@
from __future__ import annotations
# MessageSource的适配器
import typing
import abc
import pydantic
from .types import message as platform_message
from .types import events as platform_events
from .logger import EventLogger
class MessagePlatformAdapter(pydantic.BaseModel, metaclass=abc.ABCMeta):
"""消息平台适配器基类"""
name: str
bot_account_id: int
"""机器人账号ID需要在初始化时设置"""
config: dict
logger: EventLogger = pydantic.Field(exclude=True)
def __init__(self, config: dict, logger: EventLogger):
"""初始化适配器
Args:
config (dict): 对应的配置
ap (app.Application): 应用上下文
"""
self.config = config
self.logger = logger
async def send_message(self, target_type: str, target_id: str, message: platform_message.MessageChain):
"""主动发送消息
Args:
target_type (str): 目标类型,`person`或`group`
target_id (str): 目标ID
message (platform.types.MessageChain): 消息链
"""
raise NotImplementedError
async def reply_message(
self,
message_source: platform_events.MessageEvent,
message: platform_message.MessageChain,
quote_origin: bool = False,
):
"""回复消息
Args:
message_source (platform.types.MessageEvent): 消息源事件
message (platform.types.MessageChain): 消息链
quote_origin (bool, optional): 是否引用原消息. Defaults to False.
"""
raise NotImplementedError
async def is_muted(self, group_id: int) -> bool:
"""获取账号是否在指定群被禁言"""
raise NotImplementedError
def register_listener(
self,
event_type: typing.Type[platform_message.Event],
callback: typing.Callable[[platform_message.Event, MessagePlatformAdapter], None],
):
"""注册事件监听器
Args:
event_type (typing.Type[platform.types.Event]): 事件类型
callback (typing.Callable[[platform.types.Event], None]): 回调函数,接收一个参数,为事件
"""
raise NotImplementedError
def unregister_listener(
self,
event_type: typing.Type[platform_message.Event],
callback: typing.Callable[[platform_message.Event, MessagePlatformAdapter], None],
):
"""注销事件监听器
Args:
event_type (typing.Type[platform.types.Event]): 事件类型
callback (typing.Callable[[platform.types.Event], None]): 回调函数,接收一个参数,为事件
"""
raise NotImplementedError
async def run_async(self):
"""异步运行"""
raise NotImplementedError
async def kill(self) -> bool:
"""关闭适配器
Returns:
bool: 是否成功关闭热重载时若此函数返回False则不会重载MessageSource底层
"""
raise NotImplementedError
class MessageConverter:
"""消息链转换器基类"""
@staticmethod
def yiri2target(message_chain: platform_message.MessageChain):
"""将源平台消息链转换为目标平台消息链
Args:
message_chain (platform.types.MessageChain): 源平台消息链
Returns:
typing.Any: 目标平台消息链
"""
raise NotImplementedError
@staticmethod
def target2yiri(message_chain: typing.Any) -> platform_message.MessageChain:
"""将目标平台消息链转换为源平台消息链
Args:
message_chain (typing.Any): 目标平台消息链
Returns:
platform.types.MessageChain: 源平台消息链
"""
raise NotImplementedError
class EventConverter:
"""事件转换器基类"""
@staticmethod
def yiri2target(event: typing.Type[platform_message.Event]):
"""将源平台事件转换为目标平台事件
Args:
event (typing.Type[platform.types.Event]): 源平台事件
Returns:
typing.Any: 目标平台事件
"""
raise NotImplementedError
@staticmethod
def target2yiri(event: typing.Any) -> platform_message.Event:
"""将目标平台事件的调用参数转换为源平台的事件参数对象
Args:
event (typing.Any): 目标平台事件
Returns:
typing.Type[platform.types.Event]: 源平台事件
"""
raise NotImplementedError

View File

@@ -1,14 +0,0 @@
apiVersion: v1
kind: ComponentTemplate
metadata:
name: MessagePlatformAdapter
label:
en_US: Message Platform Adapter
zh_Hans: 消息平台适配器模板类
spec:
type:
- python
execution:
python:
path: ./adapter.py
attr: MessagePlatformAdapter

View File

@@ -1,15 +1,10 @@
from __future__ import annotations from __future__ import annotations
import sys
import asyncio import asyncio
import traceback import traceback
import sqlalchemy import sqlalchemy
# FriendMessage, Image, MessageChain, Plain
from . import adapter as msadapter
from ..core import app, entities as core_entities, taskmgr from ..core import app, entities as core_entities, taskmgr
from .types import events as platform_events, message as platform_message
from ..discover import engine from ..discover import engine
@@ -20,11 +15,9 @@ from ..entity.errors import platform as platform_errors
from .logger import EventLogger from .logger import EventLogger
import langbot_plugin.api.entities.builtin.provider.session as provider_session import langbot_plugin.api.entities.builtin.provider.session as provider_session
import langbot_plugin.api.entities.builtin.platform.events as platform_events
# 处理 3.4 移除了 YiriMirai 之后,插件的兼容性问题 import langbot_plugin.api.entities.builtin.platform.message as platform_message
from . import types as mirai import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
sys.modules['mirai'] = mirai
class RuntimeBot: class RuntimeBot:
@@ -36,7 +29,7 @@ class RuntimeBot:
enable: bool enable: bool
adapter: msadapter.MessagePlatformAdapter adapter: abstract_platform_adapter.AbstractMessagePlatformAdapter
task_wrapper: taskmgr.TaskWrapper task_wrapper: taskmgr.TaskWrapper
@@ -48,7 +41,7 @@ class RuntimeBot:
self, self,
ap: app.Application, ap: app.Application,
bot_entity: persistence_bot.Bot, bot_entity: persistence_bot.Bot,
adapter: msadapter.MessagePlatformAdapter, adapter: abstract_platform_adapter.AbstractMessagePlatformAdapter,
logger: EventLogger, logger: EventLogger,
): ):
self.ap = ap self.ap = ap
@@ -61,7 +54,7 @@ class RuntimeBot:
async def initialize(self): async def initialize(self):
async def on_friend_message( async def on_friend_message(
event: platform_events.FriendMessage, event: platform_events.FriendMessage,
adapter: msadapter.MessagePlatformAdapter, adapter: abstract_platform_adapter.AbstractMessagePlatformAdapter,
): ):
image_components = [ image_components = [
component for component in event.message_chain if isinstance(component, platform_message.Image) component for component in event.message_chain if isinstance(component, platform_message.Image)
@@ -86,7 +79,7 @@ class RuntimeBot:
async def on_group_message( async def on_group_message(
event: platform_events.GroupMessage, event: platform_events.GroupMessage,
adapter: msadapter.MessagePlatformAdapter, adapter: abstract_platform_adapter.AbstractMessagePlatformAdapter,
): ):
image_components = [ image_components = [
component for component in event.message_chain if isinstance(component, platform_message.Image) component for component in event.message_chain if isinstance(component, platform_message.Image)
@@ -153,7 +146,7 @@ class PlatformManager:
adapter_components: list[engine.Component] adapter_components: list[engine.Component]
adapter_dict: dict[str, type[msadapter.MessagePlatformAdapter]] adapter_dict: dict[str, type[abstract_platform_adapter.AbstractMessagePlatformAdapter]]
def __init__(self, ap: app.Application = None): def __init__(self, ap: app.Application = None):
self.ap = ap self.ap = ap
@@ -163,7 +156,7 @@ class PlatformManager:
async def initialize(self): async def initialize(self):
self.adapter_components = self.ap.discover.get_components_by_kind('MessagePlatformAdapter') self.adapter_components = self.ap.discover.get_components_by_kind('MessagePlatformAdapter')
adapter_dict: dict[str, type[msadapter.MessagePlatformAdapter]] = {} adapter_dict: dict[str, type[abstract_platform_adapter.AbstractMessagePlatformAdapter]] = {}
for component in self.adapter_components: for component in self.adapter_components:
adapter_dict[component.metadata.name] = component.get_python_component_class() adapter_dict[component.metadata.name] = component.get_python_component_class()
self.adapter_dict = adapter_dict self.adapter_dict = adapter_dict
@@ -175,6 +168,7 @@ class PlatformManager:
webchat_adapter_inst = webchat_adapter_class( webchat_adapter_inst = webchat_adapter_class(
{}, {},
webchat_logger, webchat_logger,
ap=self.ap,
) )
webchat_adapter_inst.ap = self.ap webchat_adapter_inst.ap = self.ap
@@ -195,7 +189,7 @@ class PlatformManager:
await self.load_bots_from_db() await self.load_bots_from_db()
def get_running_adapters(self) -> list[msadapter.MessagePlatformAdapter]: def get_running_adapters(self) -> list[abstract_platform_adapter.AbstractMessagePlatformAdapter]:
return [bot.adapter for bot in self.bots if bot.enable] return [bot.adapter for bot in self.bots if bot.enable]
async def load_bots_from_db(self): async def load_bots_from_db(self):
@@ -275,43 +269,6 @@ class PlatformManager:
return component return component
return None return None
async def write_back_config(
self,
adapter_name: str,
adapter_inst: msadapter.MessagePlatformAdapter,
config: dict,
):
# index = -2
# for i, adapter in enumerate(self.adapters):
# if adapter == adapter_inst:
# index = i
# break
# if index == -2:
# raise Exception('平台适配器未找到')
# # 只修改启用的适配器
# real_index = -1
# for i, adapter in enumerate(self.ap.platform_cfg.data['platform-adapters']):
# if adapter['enable']:
# index -= 1
# if index == -1:
# real_index = i
# break
# new_cfg = {
# 'adapter': adapter_name,
# 'enable': True,
# **config
# }
# self.ap.platform_cfg.data['platform-adapters'][real_index] = new_cfg
# await self.ap.platform_cfg.dump_config()
# TODO implement this
pass
async def run(self): async def run(self):
# This method will only be called when the application launching # This method will only be called when the application launching
await self.webchat_proxy_bot.run() await self.webchat_proxy_bot.run()

View File

@@ -9,7 +9,8 @@ import traceback
import uuid import uuid
from ..core import app from ..core import app
from .types import message as platform_message import langbot_plugin.api.entities.builtin.platform.message as platform_message
import langbot_plugin.api.definition.abstract.platform.event_logger as abstract_platform_event_logger
class EventLogLevel(enum.Enum): class EventLogLevel(enum.Enum):
@@ -55,7 +56,7 @@ MAX_LOG_COUNT = 200
DELETE_COUNT_PER_TIME = 50 DELETE_COUNT_PER_TIME = 50
class EventLogger: class EventLogger(abstract_platform_event_logger.AbstractEventLogger):
"""used for logging bot events""" """used for logging bot events"""
ap: app.Application ap: app.Application

View File

@@ -5,16 +5,17 @@ import traceback
import datetime import datetime
import aiocqhttp import aiocqhttp
import pydantic
from .. import adapter import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
from ..types import message as platform_message import langbot_plugin.api.entities.builtin.platform.message as platform_message
from ..types import events as platform_events import langbot_plugin.api.entities.builtin.platform.events as platform_events
from ..types import entities as platform_entities import langbot_plugin.api.entities.builtin.platform.entities as platform_entities
from ...utils import image from ...utils import image
from ..logger import EventLogger import langbot_plugin.api.definition.abstract.platform.event_logger as abstract_platform_logger
class AiocqhttpMessageConverter(adapter.MessageConverter): class AiocqhttpMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
@staticmethod @staticmethod
async def yiri2target( async def yiri2target(
message_chain: platform_message.MessageChain, message_chain: platform_message.MessageChain,
@@ -69,7 +70,6 @@ class AiocqhttpMessageConverter(adapter.MessageConverter):
elif msg.face_type=='dice': elif msg.face_type=='dice':
msg_list.append(aiocqhttp.MessageSegment.dice()) msg_list.append(aiocqhttp.MessageSegment.dice())
else: else:
msg_list.append(aiocqhttp.MessageSegment.text(str(msg))) msg_list.append(aiocqhttp.MessageSegment.text(str(msg)))
@@ -190,6 +190,7 @@ class AiocqhttpMessageConverter(adapter.MessageConverter):
file_data = await bot.get_file(file_id=file_id) file_data = await bot.get_file(file_id=file_id)
file_name = file_data.get('file_name') file_name = file_data.get('file_name')
file_path = file_data.get('file') file_path = file_data.get('file')
_ = file_path
file_url = file_data.get('file_url') file_url = file_data.get('file_url')
file_size = file_data.get('file_size') file_size = file_data.get('file_size')
yiri_msg_list.append(platform_message.File(id=file_id, name=file_name,url=file_url,size=file_size)) yiri_msg_list.append(platform_message.File(id=file_id, name=file_name,url=file_url,size=file_size))
@@ -211,7 +212,7 @@ class AiocqhttpMessageConverter(adapter.MessageConverter):
return chain return chain
class AiocqhttpEventConverter(adapter.EventConverter): class AiocqhttpEventConverter(abstract_platform_adapter.AbstractEventConverter):
@staticmethod @staticmethod
async def yiri2target(event: platform_events.MessageEvent, bot_account_id: int): async def yiri2target(event: platform_events.MessageEvent, bot_account_id: int):
return event.source_platform_object return event.source_platform_object
@@ -262,21 +263,19 @@ class AiocqhttpEventConverter(adapter.EventConverter):
) )
class AiocqhttpAdapter(adapter.MessagePlatformAdapter): class AiocqhttpAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
bot: aiocqhttp.CQHttp bot: aiocqhttp.CQHttp = pydantic.Field(exclude=True, default_factory=aiocqhttp.CQHttp)
bot_account_id: int
message_converter: AiocqhttpMessageConverter = AiocqhttpMessageConverter() message_converter: AiocqhttpMessageConverter = AiocqhttpMessageConverter()
event_converter: AiocqhttpEventConverter = AiocqhttpEventConverter() event_converter: AiocqhttpEventConverter = AiocqhttpEventConverter()
config: dict
on_websocket_connection_event_cache: typing.List[typing.Callable[[aiocqhttp.Event], None]] = [] on_websocket_connection_event_cache: typing.List[typing.Callable[[aiocqhttp.Event], None]] = []
def __init__(self, config: dict, logger: EventLogger): def __init__(self, config: dict, logger: abstract_platform_logger.AbstractEventLogger):
self.config = config super().__init__(
self.logger = logger config=config,
logger=logger,
)
async def shutdown_trigger_placeholder(): async def shutdown_trigger_placeholder():
while True: while True:
@@ -296,7 +295,6 @@ class AiocqhttpAdapter(adapter.MessagePlatformAdapter):
aiocq_msg = (await AiocqhttpMessageConverter.yiri2target(message))[0] aiocq_msg = (await AiocqhttpMessageConverter.yiri2target(message))[0]
if target_type == 'group': if target_type == 'group':
await self.bot.send_group_msg(group_id=int(target_id), message=aiocq_msg) await self.bot.send_group_msg(group_id=int(target_id), message=aiocq_msg)
elif target_type == 'person': elif target_type == 'person':
await self.bot.send_private_msg(user_id=int(target_id), message=aiocq_msg) await self.bot.send_private_msg(user_id=int(target_id), message=aiocq_msg)
@@ -320,7 +318,9 @@ class AiocqhttpAdapter(adapter.MessagePlatformAdapter):
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.MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], 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
@@ -351,7 +351,9 @@ class AiocqhttpAdapter(adapter.MessagePlatformAdapter):
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.MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None
],
): ):
return super().unregister_listener(event_type, callback) return super().unregister_listener(event_type, callback)

View File

@@ -1,17 +1,16 @@
import traceback 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 import langbot_plugin.api.entities.builtin.platform.message as platform_message
from pkg.platform.adapter import MessagePlatformAdapter import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
from .. import adapter import langbot_plugin.api.entities.builtin.platform.events as platform_events
from ..types import events as platform_events import langbot_plugin.api.entities.builtin.platform.entities as platform_entities
from ..types import entities as platform_entities
from libs.dingtalk_api.api import DingTalkClient from libs.dingtalk_api.api import DingTalkClient
import datetime import datetime
from ..logger import EventLogger from ..logger import EventLogger
class DingTalkMessageConverter(adapter.MessageConverter): class DingTalkMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
@staticmethod @staticmethod
async def yiri2target(message_chain: platform_message.MessageChain): async def yiri2target(message_chain: platform_message.MessageChain):
content = '' content = ''
@@ -47,7 +46,7 @@ class DingTalkMessageConverter(adapter.MessageConverter):
return chain return chain
class DingTalkEventConverter(adapter.EventConverter): class DingTalkEventConverter(abstract_platform_adapter.AbstractEventConverter):
@staticmethod @staticmethod
async def yiri2target(event: platform_events.MessageEvent): async def yiri2target(event: platform_events.MessageEvent):
return event.source_platform_object return event.source_platform_object
@@ -91,7 +90,7 @@ class DingTalkEventConverter(adapter.EventConverter):
) )
class DingTalkAdapter(adapter.MessagePlatformAdapter): class DingTalkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
bot: DingTalkClient bot: DingTalkClient
bot_account_id: str bot_account_id: str
message_converter: DingTalkMessageConverter = DingTalkMessageConverter() message_converter: DingTalkMessageConverter = DingTalkMessageConverter()
@@ -137,7 +136,9 @@ class DingTalkAdapter(adapter.MessagePlatformAdapter):
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.MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None
],
): ):
async def on_message(event: DingTalkEvent): async def on_message(event: DingTalkEvent):
try: try:
@@ -171,6 +172,8 @@ class DingTalkAdapter(adapter.MessagePlatformAdapter):
async def unregister_listener( async def unregister_listener(
self, self,
event_type: type, event_type: type,
callback: typing.Callable[[platform_events.Event, MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None
],
): ):
return super().unregister_listener(event_type, callback) return super().unregister_listener(event_type, callback)

View File

@@ -10,15 +10,16 @@ import os
import datetime import datetime
import aiohttp import aiohttp
import pydantic
from .. import adapter import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
from ..types import message as platform_message import langbot_plugin.api.entities.builtin.platform.message as platform_message
from ..types import events as platform_events import langbot_plugin.api.entities.builtin.platform.events as platform_events
from ..types import entities as platform_entities import langbot_plugin.api.entities.builtin.platform.entities as platform_entities
from ..logger import EventLogger import langbot_plugin.api.definition.abstract.platform.event_logger as abstract_platform_logger
class DiscordMessageConverter(adapter.MessageConverter): class DiscordMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
@staticmethod @staticmethod
async def yiri2target( async def yiri2target(
message_chain: platform_message.MessageChain, message_chain: platform_message.MessageChain,
@@ -111,7 +112,7 @@ class DiscordMessageConverter(adapter.MessageConverter):
return platform_message.MessageChain(element_list) return platform_message.MessageChain(element_list)
class DiscordEventConverter(adapter.EventConverter): class DiscordEventConverter(abstract_platform_adapter.AbstractEventConverter):
@staticmethod @staticmethod
async def yiri2target(event: platform_events.Event) -> discord.Message: async def yiri2target(event: platform_events.Event) -> discord.Message:
pass pass
@@ -153,26 +154,21 @@ class DiscordEventConverter(adapter.EventConverter):
) )
class DiscordAdapter(adapter.MessagePlatformAdapter): class DiscordAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
bot: discord.Client bot: discord.Client = pydantic.Field(exclude=True)
bot_account_id: str # 用于在流水线中识别at是否是本bot直接以bot_name作为标识
config: dict
message_converter: DiscordMessageConverter = DiscordMessageConverter() message_converter: DiscordMessageConverter = DiscordMessageConverter()
event_converter: DiscordEventConverter = DiscordEventConverter() event_converter: DiscordEventConverter = DiscordEventConverter()
listeners: typing.Dict[ listeners: typing.Dict[
typing.Type[platform_events.Event], typing.Type[platform_events.Event],
typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None], typing.Callable[[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None],
] = {} ] = {}
def __init__(self, config: dict, logger: EventLogger): def __init__(self, config: dict, logger: abstract_platform_logger.AbstractEventLogger):
self.config = config bot_account_id = config['client_id']
self.logger = logger
self.bot_account_id = self.config['client_id'] listeners = {}
adapter_self = self adapter_self = self
@@ -192,7 +188,15 @@ class DiscordAdapter(adapter.MessagePlatformAdapter):
if os.getenv('http_proxy'): if os.getenv('http_proxy'):
args['proxy'] = os.getenv('http_proxy') args['proxy'] = os.getenv('http_proxy')
self.bot = MyClient(intents=intents, **args) bot = MyClient(intents=intents, **args)
super().__init__(
config=config,
logger=logger,
bot_account_id=bot_account_id,
listeners=listeners,
bot=bot,
)
async def send_message(self, target_type: str, target_id: str, message: platform_message.MessageChain): async def send_message(self, target_type: str, target_id: str, message: platform_message.MessageChain):
pass pass
@@ -227,14 +231,18 @@ class DiscordAdapter(adapter.MessagePlatformAdapter):
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.MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], 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.MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None
],
): ):
self.listeners.pop(event_type) self.listeners.pop(event_type)

View File

@@ -17,12 +17,13 @@ import aiohttp
import lark_oapi.ws.exception import lark_oapi.ws.exception
import quart import quart
from lark_oapi.api.im.v1 import * from lark_oapi.api.im.v1 import *
import pydantic
from .. import adapter import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
from ..types import message as platform_message import langbot_plugin.api.entities.builtin.platform.message as platform_message
from ..types import events as platform_events import langbot_plugin.api.entities.builtin.platform.events as platform_events
from ..types import entities as platform_entities import langbot_plugin.api.entities.builtin.platform.entities as platform_entities
from ..logger import EventLogger import langbot_plugin.api.definition.abstract.platform.event_logger as abstract_platform_logger
class AESCipher(object): class AESCipher(object):
@@ -51,7 +52,7 @@ class AESCipher(object):
return self.decrypt(enc).decode('utf8') return self.decrypt(enc).decode('utf8')
class LarkMessageConverter(adapter.MessageConverter): class LarkMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
@staticmethod @staticmethod
async def yiri2target( async def yiri2target(
message_chain: platform_message.MessageChain, api_client: lark_oapi.Client message_chain: platform_message.MessageChain, api_client: lark_oapi.Client
@@ -275,7 +276,7 @@ class LarkMessageConverter(adapter.MessageConverter):
return platform_message.MessageChain(lb_msg_list) return platform_message.MessageChain(lb_msg_list)
class LarkEventConverter(adapter.EventConverter): class LarkEventConverter(abstract_platform_adapter.AbstractEventConverter):
@staticmethod @staticmethod
async def yiri2target( async def yiri2target(
event: platform_events.MessageEvent, event: platform_events.MessageEvent,
@@ -319,31 +320,24 @@ class LarkEventConverter(adapter.EventConverter):
) )
class LarkAdapter(adapter.MessagePlatformAdapter): class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
bot: lark_oapi.ws.Client bot: lark_oapi.ws.Client = pydantic.Field(exclude=True)
api_client: lark_oapi.Client api_client: lark_oapi.Client = pydantic.Field(exclude=True)
bot_account_id: str # 用于在流水线中识别at是否是本bot直接以bot_name作为标识
lark_tenant_key: str # 飞书企业key
message_converter: LarkMessageConverter = LarkMessageConverter() message_converter: LarkMessageConverter = LarkMessageConverter()
event_converter: LarkEventConverter = LarkEventConverter() event_converter: LarkEventConverter = LarkEventConverter()
listeners: typing.Dict[ listeners: typing.Dict[
typing.Type[platform_events.Event], typing.Type[platform_events.Event],
typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None], typing.Callable[[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None],
] ]
config: dict quart_app: quart.Quart = pydantic.Field(exclude=True)
quart_app: quart.Quart
def __init__(self, config: dict, logger: EventLogger): def __init__(self, config: dict, logger: abstract_platform_logger.AbstractEventLogger):
self.config = config quart_app = quart.Quart(__name__)
self.logger = logger
self.quart_app = quart.Quart(__name__)
self.listeners = {}
@self.quart_app.route('/lark/callback', methods=['POST']) @quart_app.route('/lark/callback', methods=['POST'])
async def lark_callback(): async def lark_callback():
try: try:
data = await quart.request.json data = await quart.request.json
@@ -396,10 +390,20 @@ class LarkAdapter(adapter.MessagePlatformAdapter):
lark_oapi.EventDispatcherHandler.builder('', '').register_p2_im_message_receive_v1(sync_on_message).build() lark_oapi.EventDispatcherHandler.builder('', '').register_p2_im_message_receive_v1(sync_on_message).build()
) )
self.bot_account_id = config['bot_name'] bot_account_id = config['bot_name']
self.bot = lark_oapi.ws.Client(config['app_id'], config['app_secret'], event_handler=event_handler) bot = lark_oapi.ws.Client(config['app_id'], config['app_secret'], event_handler=event_handler)
self.api_client = lark_oapi.Client.builder().app_id(config['app_id']).app_secret(config['app_secret']).build() api_client = lark_oapi.Client.builder().app_id(config['app_id']).app_secret(config['app_secret']).build()
super().__init__(
config=config,
logger=logger,
listeners={},
quart_app=quart_app,
bot=bot,
api_client=api_client,
bot_account_id=bot_account_id,
)
async def send_message(self, target_type: str, target_id: str, message: platform_message.MessageChain): async def send_message(self, target_type: str, target_id: str, message: platform_message.MessageChain):
pass pass
@@ -448,14 +452,18 @@ class LarkAdapter(adapter.MessagePlatformAdapter):
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.MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], 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.MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None
],
): ):
self.listeners.pop(event_type) self.listeners.pop(event_type)

View File

@@ -11,11 +11,11 @@ import threading
import quart import quart
import aiohttp import aiohttp
from ... import adapter import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
from ....core import app from ....core import app
from ...types import message as platform_message import langbot_plugin.api.entities.builtin.platform.message as platform_message
from ...types import events as platform_events import langbot_plugin.api.entities.builtin.platform.events as platform_events
from ...types import entities as platform_entities import langbot_plugin.api.entities.builtin.platform.entities as platform_entities
from ....utils import image from ....utils import image
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from typing import Optional, Tuple from typing import Optional, Tuple
@@ -23,7 +23,7 @@ from functools import partial
from ...logger import EventLogger from ...logger import EventLogger
class GewechatMessageConverter(adapter.MessageConverter): class GewechatMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
def __init__(self, config: dict): def __init__(self, config: dict):
self.config = config self.config = config
@@ -398,7 +398,7 @@ class GewechatMessageConverter(adapter.MessageConverter):
return from_user_name.endswith('@chatroom') return from_user_name.endswith('@chatroom')
class GewechatEventConverter(adapter.EventConverter): class GewechatEventConverter(abstract_platform_adapter.AbstractEventConverter):
def __init__(self, config: dict): def __init__(self, config: dict):
self.config = config self.config = config
self.message_converter = GewechatMessageConverter(config) self.message_converter = GewechatMessageConverter(config)
@@ -458,7 +458,7 @@ class GewechatEventConverter(adapter.EventConverter):
) )
class GeWeChatAdapter(adapter.MessagePlatformAdapter): class GeWeChatAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
name: str = 'gewechat' # 定义适配器名称 name: str = 'gewechat' # 定义适配器名称
bot: gewechat_client.GewechatClient bot: gewechat_client.GewechatClient
@@ -475,7 +475,7 @@ class GeWeChatAdapter(adapter.MessagePlatformAdapter):
listeners: typing.Dict[ listeners: typing.Dict[
typing.Type[platform_events.Event], typing.Type[platform_events.Event],
typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None], typing.Callable[[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None],
] = {} ] = {}
def __init__(self, config: dict, ap: app.Application, logger: EventLogger): def __init__(self, config: dict, ap: app.Application, logger: EventLogger):
@@ -625,14 +625,18 @@ class GeWeChatAdapter(adapter.MessagePlatformAdapter):
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.MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], 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.MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None
],
): ):
pass pass

View File

@@ -9,15 +9,15 @@ import traceback
import nakuru import nakuru
import nakuru.entities.components as nkc import nakuru.entities.components as nkc
from ... import adapter as adapter_model
from ....pipeline.longtext.strategies import forward from ....pipeline.longtext.strategies import forward
from ...types import message as platform_message import langbot_plugin.api.entities.builtin.platform.message as platform_message
from ...types import entities as platform_entities import langbot_plugin.api.entities.builtin.platform.entities as platform_entities
from ...types import events as platform_events import langbot_plugin.api.entities.builtin.platform.events as platform_events
import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
from ...logger import EventLogger from ...logger import EventLogger
class NakuruProjectMessageConverter(adapter_model.MessageConverter): class NakuruProjectMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
"""消息转换器""" """消息转换器"""
@staticmethod @staticmethod
@@ -109,7 +109,7 @@ class NakuruProjectMessageConverter(adapter_model.MessageConverter):
return chain return chain
class NakuruProjectEventConverter(adapter_model.EventConverter): class NakuruProjectEventConverter(abstract_platform_adapter.AbstractEventConverter):
"""事件转换器""" """事件转换器"""
@staticmethod @staticmethod
@@ -164,7 +164,7 @@ class NakuruProjectEventConverter(adapter_model.EventConverter):
raise Exception('未支持转换的事件类型: ' + str(event)) raise Exception('未支持转换的事件类型: ' + str(event))
class NakuruAdapter(adapter_model.MessagePlatformAdapter): class NakuruAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
"""nakuru-project适配器""" """nakuru-project适配器"""
bot: nakuru.CQHTTP bot: nakuru.CQHTTP
@@ -256,7 +256,9 @@ class NakuruAdapter(adapter_model.MessagePlatformAdapter):
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.MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None
],
): ):
try: try:
source_cls = NakuruProjectEventConverter.yiri2target(event_type) source_cls = NakuruProjectEventConverter.yiri2target(event_type)
@@ -283,7 +285,9 @@ class NakuruAdapter(adapter_model.MessagePlatformAdapter):
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.MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None
],
): ):
nakuru_event_name = self.event_converter.yiri2target(event_type).__name__ nakuru_event_name = self.event_converter.yiri2target(event_type).__name__

View File

@@ -10,13 +10,13 @@ import botpy
import botpy.message as botpy_message import botpy.message as botpy_message
import botpy.types.message as botpy_message_type import botpy.types.message as botpy_message_type
from ... import adapter as adapter_model import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
from ....pipeline.longtext.strategies import forward from ....pipeline.longtext.strategies import forward
from ....core import app from ....core import app
from ....config import manager as cfg_mgr from ....config import manager as cfg_mgr
from ...types import entities as platform_entities import langbot_plugin.api.entities.builtin.platform.entities as platform_entities
from ...types import events as platform_events import langbot_plugin.api.entities.builtin.platform.events as platform_events
from ...types import message as platform_message import langbot_plugin.api.entities.builtin.platform.message as platform_message
from ...logger import EventLogger from ...logger import EventLogger
@@ -133,7 +133,7 @@ class OpenIDMapping(typing.Generic[K, V]):
return value return value
class OfficialMessageConverter(adapter_model.MessageConverter): class OfficialMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
"""QQ 官方消息转换器""" """QQ 官方消息转换器"""
@staticmethod @staticmethod
@@ -237,7 +237,7 @@ class OfficialMessageConverter(adapter_model.MessageConverter):
return chain return chain
class OfficialEventConverter(adapter_model.EventConverter): class OfficialEventConverter(abstract_platform_adapter.AbstractEventConverter):
"""事件转换器""" """事件转换器"""
def __init__(self): def __init__(self):
@@ -333,7 +333,7 @@ class OfficialEventConverter(adapter_model.EventConverter):
) )
class OfficialAdapter(adapter_model.MessagePlatformAdapter): class OfficialAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
"""QQ 官方消息适配器""" """QQ 官方消息适配器"""
bot: botpy.Client = None bot: botpy.Client = None
@@ -484,7 +484,9 @@ class OfficialAdapter(adapter_model.MessagePlatformAdapter):
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.MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None
],
): ):
try: try:
@@ -507,7 +509,9 @@ class OfficialAdapter(adapter_model.MessagePlatformAdapter):
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.MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None
],
): ):
delattr(self.bot, event_handler_mapping[event_type]) delattr(self.bot, event_handler_mapping[event_type])

View File

@@ -4,18 +4,18 @@ import asyncio
import traceback import traceback
import datetime import datetime
from pkg.platform.adapter import MessagePlatformAdapter import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
from pkg.platform.types import events as platform_events, message as platform_message
from libs.official_account_api.oaevent import OAEvent from libs.official_account_api.oaevent import OAEvent
from libs.official_account_api.api import OAClient from libs.official_account_api.api import OAClient
from libs.official_account_api.api import OAClientForLongerResponse from libs.official_account_api.api import OAClientForLongerResponse
from .. import adapter import langbot_plugin.api.entities.builtin.platform.entities as platform_entities
from ..types import entities as platform_entities import langbot_plugin.api.entities.builtin.platform.message as platform_message
import langbot_plugin.api.entities.builtin.platform.events as platform_events
from ...command.errors import ParamNotEnoughError from ...command.errors import ParamNotEnoughError
from ..logger import EventLogger from ..logger import EventLogger
class OAMessageConverter(adapter.MessageConverter): class OAMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
@staticmethod @staticmethod
async def yiri2target(message_chain: platform_message.MessageChain): async def yiri2target(message_chain: platform_message.MessageChain):
for msg in message_chain: for msg in message_chain:
@@ -33,7 +33,7 @@ class OAMessageConverter(adapter.MessageConverter):
return chain return chain
class OAEventConverter(adapter.EventConverter): class OAEventConverter(abstract_platform_adapter.AbstractEventConverter):
@staticmethod @staticmethod
async def target2yiri(event: OAEvent): async def target2yiri(event: OAEvent):
if event.type == 'text': if event.type == 'text':
@@ -55,7 +55,7 @@ class OAEventConverter(adapter.EventConverter):
return None return None
class OfficialAccountAdapter(adapter.MessagePlatformAdapter): class OfficialAccountAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
bot: OAClient | OAClientForLongerResponse bot: OAClient | OAClientForLongerResponse
bot_account_id: str bot_account_id: str
message_converter: OAMessageConverter = OAMessageConverter() message_converter: OAMessageConverter = OAMessageConverter()
@@ -116,7 +116,9 @@ class OfficialAccountAdapter(adapter.MessagePlatformAdapter):
def register_listener( def register_listener(
self, self,
event_type: type, event_type: type,
callback: typing.Callable[[platform_events.Event, MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], 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
@@ -147,6 +149,8 @@ class OfficialAccountAdapter(adapter.MessagePlatformAdapter):
async def unregister_listener( async def unregister_listener(
self, self,
event_type: type, event_type: type,
callback: typing.Callable[[platform_events.Event, MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None
],
): ):
return super().unregister_listener(event_type, callback) return super().unregister_listener(event_type, callback)

View File

@@ -5,10 +5,10 @@ import traceback
import datetime import datetime
from pkg.platform.adapter import MessagePlatformAdapter import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
from pkg.platform.types import events as platform_events, message as platform_message import langbot_plugin.api.entities.builtin.platform.message as platform_message
from .. import adapter import langbot_plugin.api.entities.builtin.platform.events as platform_events
from ..types import entities as platform_entities import langbot_plugin.api.entities.builtin.platform.entities as platform_entities
from ...command.errors import ParamNotEnoughError from ...command.errors import ParamNotEnoughError
from libs.qq_official_api.api import QQOfficialClient from libs.qq_official_api.api import QQOfficialClient
from libs.qq_official_api.qqofficialevent import QQOfficialEvent from libs.qq_official_api.qqofficialevent import QQOfficialEvent
@@ -16,7 +16,7 @@ from ...utils import image
from ..logger import EventLogger from ..logger import EventLogger
class QQOfficialMessageConverter(adapter.MessageConverter): class QQOfficialMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
@staticmethod @staticmethod
async def yiri2target(message_chain: platform_message.MessageChain): async def yiri2target(message_chain: platform_message.MessageChain):
content_list = [] content_list = []
@@ -45,7 +45,7 @@ class QQOfficialMessageConverter(adapter.MessageConverter):
return chain return chain
class QQOfficialEventConverter(adapter.EventConverter): class QQOfficialEventConverter(abstract_platform_adapter.AbstractEventConverter):
@staticmethod @staticmethod
async def yiri2target(event: platform_events.MessageEvent) -> QQOfficialEvent: async def yiri2target(event: platform_events.MessageEvent) -> QQOfficialEvent:
return event.source_platform_object return event.source_platform_object
@@ -131,7 +131,7 @@ class QQOfficialEventConverter(adapter.EventConverter):
) )
class QQOfficialAdapter(adapter.MessagePlatformAdapter): class QQOfficialAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
bot: QQOfficialClient bot: QQOfficialClient
config: dict config: dict
bot_account_id: str bot_account_id: str
@@ -212,7 +212,9 @@ class QQOfficialAdapter(adapter.MessagePlatformAdapter):
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.MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None
],
): ):
async def on_message(event: QQOfficialEvent): async def on_message(event: QQOfficialEvent):
self.bot_account_id = 'justbot' self.bot_account_id = 'justbot'
@@ -245,6 +247,8 @@ class QQOfficialAdapter(adapter.MessagePlatformAdapter):
def unregister_listener( def unregister_listener(
self, self,
event_type: type, event_type: type,
callback: typing.Callable[[platform_events.Event, MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None
],
): ):
return super().unregister_listener(event_type, callback) return super().unregister_listener(event_type, callback)

View File

@@ -6,17 +6,17 @@ import traceback
import datetime import datetime
from libs.slack_api.api import SlackClient from libs.slack_api.api import SlackClient
from pkg.platform.adapter import MessagePlatformAdapter import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
from pkg.platform.types import events as platform_events, message as platform_message
from libs.slack_api.slackevent import SlackEvent from libs.slack_api.slackevent import SlackEvent
from .. import adapter import langbot_plugin.api.entities.builtin.platform.events as platform_events
from ..types import entities as platform_entities import langbot_plugin.api.entities.builtin.platform.message as platform_message
import langbot_plugin.api.entities.builtin.platform.entities as platform_entities
from ...command.errors import ParamNotEnoughError from ...command.errors import ParamNotEnoughError
from ...utils import image from ...utils import image
from ..logger import EventLogger from ..logger import EventLogger
class SlackMessageConverter(adapter.MessageConverter): class SlackMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
@staticmethod @staticmethod
async def yiri2target(message_chain: platform_message.MessageChain): async def yiri2target(message_chain: platform_message.MessageChain):
content_list = [] content_list = []
@@ -43,7 +43,7 @@ class SlackMessageConverter(adapter.MessageConverter):
return chain return chain
class SlackEventConverter(adapter.EventConverter): class SlackEventConverter(abstract_platform_adapter.AbstractEventConverter):
@staticmethod @staticmethod
async def yiri2target(event: platform_events.MessageEvent) -> SlackEvent: async def yiri2target(event: platform_events.MessageEvent) -> SlackEvent:
return event.source_platform_object return event.source_platform_object
@@ -83,7 +83,7 @@ class SlackEventConverter(adapter.EventConverter):
) )
class SlackAdapter(adapter.MessagePlatformAdapter): class SlackAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
bot: SlackClient bot: SlackClient
bot_account_id: str bot_account_id: str
message_converter: SlackMessageConverter = SlackMessageConverter() message_converter: SlackMessageConverter = SlackMessageConverter()
@@ -132,7 +132,9 @@ class SlackAdapter(adapter.MessagePlatformAdapter):
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.MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None
],
): ):
async def on_message(event: SlackEvent): async def on_message(event: SlackEvent):
self.bot_account_id = 'SlackBot' self.bot_account_id = 'SlackBot'
@@ -163,6 +165,8 @@ class SlackAdapter(adapter.MessagePlatformAdapter):
async def unregister_listener( async def unregister_listener(
self, self,
event_type: type, event_type: type,
callback: typing.Callable[[platform_events.Event, MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None
],
): ):
return super().unregister_listener(event_type, callback) return super().unregister_listener(event_type, callback)

View File

@@ -9,15 +9,16 @@ import typing
import traceback import traceback
import base64 import base64
import aiohttp import aiohttp
import pydantic
from .. import adapter import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
from ..types import message as platform_message import langbot_plugin.api.entities.builtin.platform.message as platform_message
from ..types import events as platform_events import langbot_plugin.api.entities.builtin.platform.events as platform_events
from ..types import entities as platform_entities import langbot_plugin.api.entities.builtin.platform.entities as platform_entities
from ..logger import EventLogger import langbot_plugin.api.definition.abstract.platform.event_logger as abstract_platform_logger
class TelegramMessageConverter(adapter.MessageConverter): class TelegramMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
@staticmethod @staticmethod
async def yiri2target(message_chain: platform_message.MessageChain, bot: telegram.Bot) -> list[dict]: async def yiri2target(message_chain: platform_message.MessageChain, bot: telegram.Bot) -> list[dict]:
components = [] components = []
@@ -86,7 +87,7 @@ class TelegramMessageConverter(adapter.MessageConverter):
return platform_message.MessageChain(message_components) return platform_message.MessageChain(message_components)
class TelegramEventConverter(adapter.EventConverter): class TelegramEventConverter(abstract_platform_adapter.AbstractEventConverter):
@staticmethod @staticmethod
async def yiri2target(event: platform_events.MessageEvent, bot: telegram.Bot): async def yiri2target(event: platform_events.MessageEvent, bot: telegram.Bot):
return event.source_platform_object return event.source_platform_object
@@ -128,26 +129,19 @@ class TelegramEventConverter(adapter.EventConverter):
) )
class TelegramAdapter(adapter.MessagePlatformAdapter): class TelegramAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
bot: telegram.Bot bot: telegram.Bot = pydantic.Field(exclude=True)
application: telegram.ext.Application application: telegram.ext.Application = pydantic.Field(exclude=True)
bot_account_id: str
message_converter: TelegramMessageConverter = TelegramMessageConverter() message_converter: TelegramMessageConverter = TelegramMessageConverter()
event_converter: TelegramEventConverter = TelegramEventConverter() event_converter: TelegramEventConverter = TelegramEventConverter()
config: dict
listeners: typing.Dict[ listeners: typing.Dict[
typing.Type[platform_events.Event], typing.Type[platform_events.Event],
typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None], typing.Callable[[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None],
] = {} ] = {}
def __init__(self, config: dict, logger: EventLogger): def __init__(self, config: dict, logger: abstract_platform_logger.AbstractEventLogger):
self.config = config
self.logger = logger
async def telegram_callback(update: Update, context: ContextTypes.DEFAULT_TYPE): async def telegram_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
if update.message.from_user.is_bot: if update.message.from_user.is_bot:
return return
@@ -158,10 +152,16 @@ class TelegramAdapter(adapter.MessagePlatformAdapter):
except Exception: except Exception:
await self.logger.error(f'Error in telegram callback: {traceback.format_exc()}') await self.logger.error(f'Error in telegram callback: {traceback.format_exc()}')
self.application = ApplicationBuilder().token(self.config['token']).build() application = ApplicationBuilder().token(config['token']).build()
self.bot = self.application.bot bot = application.bot
self.application.add_handler( application.add_handler(MessageHandler(filters.TEXT | (filters.COMMAND) | filters.PHOTO, telegram_callback))
MessageHandler(filters.TEXT | (filters.COMMAND) | filters.PHOTO, telegram_callback) super().__init__(
config=config,
logger=logger,
bot=bot,
application=application,
bot_account_id='',
listeners={},
) )
async def send_message(self, target_type: str, target_id: str, message: platform_message.MessageChain): async def send_message(self, target_type: str, target_id: str, message: platform_message.MessageChain):
@@ -201,14 +201,18 @@ class TelegramAdapter(adapter.MessagePlatformAdapter):
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.MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], 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.MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None
],
): ):
self.listeners.pop(event_type) self.listeners.pop(event_type)

View File

@@ -3,17 +3,19 @@ import logging
import typing import typing
from datetime import datetime from datetime import datetime
from pydantic import BaseModel import pydantic
from .. import adapter as msadapter import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
from ..types import events as platform_events, message as platform_message, entities as platform_entities import langbot_plugin.api.entities.builtin.platform.message as platform_message
import langbot_plugin.api.entities.builtin.platform.events as platform_events
import langbot_plugin.api.entities.builtin.platform.entities as platform_entities
from ...core import app from ...core import app
from ..logger import EventLogger import langbot_plugin.api.definition.abstract.platform.event_logger as abstract_platform_logger
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class WebChatMessage(BaseModel): class WebChatMessage(pydantic.BaseModel):
id: int id: int
role: str role: str
content: str content: str
@@ -38,28 +40,35 @@ class WebChatSession:
return self.message_lists[pipeline_uuid] return self.message_lists[pipeline_uuid]
class WebChatAdapter(msadapter.MessagePlatformAdapter): class WebChatAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
"""WebChat调试适配器用于流水线调试""" """WebChat调试适配器用于流水线调试"""
webchat_person_session: WebChatSession webchat_person_session: WebChatSession = pydantic.Field(exclude=True, default_factory=WebChatSession)
webchat_group_session: WebChatSession webchat_group_session: WebChatSession = pydantic.Field(exclude=True, default_factory=WebChatSession)
ap: app.Application # set by bot manager ap: app.Application = pydantic.Field(exclude=True) # set by bot manager
listeners: typing.Dict[ listeners: dict[
typing.Type[platform_events.Event], typing.Type[platform_events.Event],
typing.Callable[[platform_events.Event, msadapter.MessagePlatformAdapter], None], typing.Callable[[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None],
] = {} ] = pydantic.Field(default_factory=dict, exclude=True)
def __init__(self, config: dict, logger: EventLogger): debug_messages: dict[str, list[dict]] = pydantic.Field(default_factory=dict, exclude=True)
self.logger = logger
self.config = config def __init__(self, config: dict, logger: abstract_platform_logger.AbstractEventLogger, ap: app.Application):
super().__init__(
config=config,
logger=logger,
ap=ap,
)
self.webchat_person_session = WebChatSession(id='webchatperson') self.webchat_person_session = WebChatSession(id='webchatperson')
self.webchat_group_session = WebChatSession(id='webchatgroup') self.webchat_group_session = WebChatSession(id='webchatgroup')
self.bot_account_id = 'webchatbot' self.bot_account_id = 'webchatbot'
self.debug_messages = {}
async def send_message( async def send_message(
self, self,
target_type: str, target_type: str,
@@ -112,7 +121,9 @@ class WebChatAdapter(msadapter.MessagePlatformAdapter):
def register_listener( def register_listener(
self, self,
event_type: typing.Type[platform_events.Event], event_type: typing.Type[platform_events.Event],
func: typing.Callable[[platform_events.Event, msadapter.MessagePlatformAdapter], typing.Awaitable[None]], func: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], typing.Awaitable[None]
],
): ):
"""注册事件监听器""" """注册事件监听器"""
self.listeners[event_type] = func self.listeners[event_type] = func
@@ -120,11 +131,16 @@ class WebChatAdapter(msadapter.MessagePlatformAdapter):
def unregister_listener( def unregister_listener(
self, self,
event_type: typing.Type[platform_events.Event], event_type: typing.Type[platform_events.Event],
func: typing.Callable[[platform_events.Event, msadapter.MessagePlatformAdapter], typing.Awaitable[None]], func: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], typing.Awaitable[None]
],
): ):
"""取消注册事件监听器""" """取消注册事件监听器"""
del self.listeners[event_type] del self.listeners[event_type]
async def is_muted(self, group_id: int) -> bool:
return False
async def run_async(self): async def run_async(self):
"""运行适配器""" """运行适配器"""
await self.logger.info('WebChat调试适配器已启动') await self.logger.info('WebChat调试适配器已启动')

View File

@@ -17,19 +17,19 @@ import threading
import quart import quart
from .. import adapter
from ...core import app from ...core import app
from ..types import message as platform_message
from ..types import events as platform_events
from ..types import entities as platform_entities
from ..logger import EventLogger from ..logger import EventLogger
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from typing import Optional, Tuple from typing import Optional, Tuple
from functools import partial from functools import partial
import logging import logging
import langbot_plugin.api.entities.builtin.platform.message as platform_message
import langbot_plugin.api.entities.builtin.platform.events as platform_events
import langbot_plugin.api.entities.builtin.platform.entities as platform_entities
import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
class WeChatPadMessageConverter(adapter.MessageConverter): class WeChatPadMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
def __init__(self, config: dict): def __init__(self, config: dict):
self.config = config self.config = config
self.bot = WeChatPadClient(self.config['wechatpad_url'], self.config['token']) self.bot = WeChatPadClient(self.config['wechatpad_url'], self.config['token'])
@@ -281,7 +281,7 @@ class WeChatPadMessageConverter(adapter.MessageConverter):
"""处理文件消息 (data_type=6)""" """处理文件消息 (data_type=6)"""
file_data = xml_data.find('.//appmsg') file_data = xml_data.find('.//appmsg')
if file_data.findtext('.//type', "") == "74": if file_data.findtext('.//type', '') == '74':
return None return None
else: else:
@@ -304,16 +304,19 @@ class WeChatPadMessageConverter(adapter.MessageConverter):
file_data = self.bot.cdn_download(aeskey=aeskey, file_type=5, file_url=cdnthumburl) file_data = self.bot.cdn_download(aeskey=aeskey, file_type=5, file_url=cdnthumburl)
file_base64 = file_data["Data"]['FileData'] file_base64 = file_data['Data']['FileData']
# print(file_data) # print(file_data)
file_size = file_data["Data"]['TotalSize'] file_size = file_data['Data']['TotalSize']
# print(file_base64) # print(file_base64)
return platform_message.MessageChain([ return platform_message.MessageChain(
platform_message.WeChatFile(file_id=file_id, file_name=file_name, file_size=file_size, [
file_base64=file_base64), platform_message.WeChatFile(
platform_message.WeChatForwardFile(xml_data=xml_data_str) file_id=file_id, file_name=file_name, file_size=file_size, file_base64=file_base64
]) ),
platform_message.WeChatForwardFile(xml_data=xml_data_str),
]
)
async def _handler_compound_link(self, message: dict, xml_data: ET.Element) -> platform_message.MessageChain: async def _handler_compound_link(self, message: dict, xml_data: ET.Element) -> platform_message.MessageChain:
"""处理链接消息(如公众号文章、外部网页)""" """处理链接消息(如公众号文章、外部网页)"""
@@ -416,7 +419,7 @@ class WeChatPadMessageConverter(adapter.MessageConverter):
return from_user_name.endswith('@chatroom') return from_user_name.endswith('@chatroom')
class WeChatPadEventConverter(adapter.EventConverter): class WeChatPadEventConverter(abstract_platform_adapter.AbstractEventConverter):
def __init__(self, config: dict): def __init__(self, config: dict):
self.config = config self.config = config
self.message_converter = WeChatPadMessageConverter(config) self.message_converter = WeChatPadMessageConverter(config)
@@ -476,7 +479,7 @@ class WeChatPadEventConverter(adapter.EventConverter):
) )
class WeChatPadAdapter(adapter.MessagePlatformAdapter): class WeChatPadAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
name: str = 'WeChatPad' # 定义适配器名称 name: str = 'WeChatPad' # 定义适配器名称
bot: WeChatPadClient bot: WeChatPadClient
@@ -495,7 +498,7 @@ class WeChatPadAdapter(adapter.MessagePlatformAdapter):
listeners: typing.Dict[ listeners: typing.Dict[
typing.Type[platform_events.Event], typing.Type[platform_events.Event],
typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None], typing.Callable[[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None],
] = {} ] = {}
def __init__(self, config: dict, ap: app.Application, logger: EventLogger): def __init__(self, config: dict, ap: app.Application, logger: EventLogger):
@@ -596,14 +599,18 @@ class WeChatPadAdapter(adapter.MessagePlatformAdapter):
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.MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], 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.MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None
],
): ):
pass pass

View File

@@ -6,17 +6,17 @@ import traceback
import datetime import datetime
from libs.wecom_api.api import WecomClient from libs.wecom_api.api import WecomClient
from pkg.platform.adapter import MessagePlatformAdapter import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
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 .. import adapter
from ..types import entities as platform_entities
from ...command.errors import ParamNotEnoughError from ...command.errors import ParamNotEnoughError
from ...utils import image from ...utils import image
from ..logger import EventLogger from ..logger import EventLogger
import langbot_plugin.api.entities.builtin.platform.message as platform_message
import langbot_plugin.api.entities.builtin.platform.events as platform_events
import langbot_plugin.api.entities.builtin.platform.entities as platform_entities
class WecomMessageConverter(adapter.MessageConverter): class WecomMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
@staticmethod @staticmethod
async def yiri2target(message_chain: platform_message.MessageChain, bot: WecomClient): async def yiri2target(message_chain: platform_message.MessageChain, bot: WecomClient):
content_list = [] content_list = []
@@ -70,7 +70,7 @@ class WecomMessageConverter(adapter.MessageConverter):
return chain return chain
class WecomEventConverter: class WecomEventConverter(abstract_platform_adapter.AbstractEventConverter):
@staticmethod @staticmethod
async def yiri2target(event: platform_events.Event, bot_account_id: int, bot: WecomClient) -> WecomEvent: async def yiri2target(event: platform_events.Event, bot_account_id: int, bot: WecomClient) -> WecomEvent:
# only for extracting user information # only for extracting user information
@@ -126,7 +126,7 @@ class WecomEventConverter:
return platform_events.FriendMessage(sender=friend, message_chain=yiri_chain, time=event.timestamp) return platform_events.FriendMessage(sender=friend, message_chain=yiri_chain, time=event.timestamp)
class WecomAdapter(adapter.MessagePlatformAdapter): class WecomAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
bot: WecomClient bot: WecomClient
bot_account_id: str bot_account_id: str
message_converter: WecomMessageConverter = WecomMessageConverter() message_converter: WecomMessageConverter = WecomMessageConverter()
@@ -192,7 +192,9 @@ class WecomAdapter(adapter.MessagePlatformAdapter):
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.MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None
],
): ):
async def on_message(event: WecomEvent): async def on_message(event: WecomEvent):
self.bot_account_id = event.receiver_id self.bot_account_id = event.receiver_id
@@ -224,6 +226,8 @@ class WecomAdapter(adapter.MessagePlatformAdapter):
async def unregister_listener( async def unregister_listener(
self, self,
event_type: type, event_type: type,
callback: typing.Callable[[platform_events.Event, MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None
],
): ):
return super().unregister_listener(event_type, callback) return super().unregister_listener(event_type, callback)

View File

@@ -4,18 +4,19 @@ import asyncio
import traceback import traceback
import datetime import datetime
import pydantic
from libs.wecom_customer_service_api.api import WecomCSClient from libs.wecom_customer_service_api.api import WecomCSClient
from pkg.platform.adapter import MessagePlatformAdapter import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
from pkg.platform.types import events as platform_events, message as platform_message
from libs.wecom_customer_service_api.wecomcsevent import WecomCSEvent from libs.wecom_customer_service_api.wecomcsevent import WecomCSEvent
from .. import adapter import langbot_plugin.api.entities.builtin.platform.entities as platform_entities
from ..types import entities as platform_entities import langbot_plugin.api.entities.builtin.platform.message as platform_message
import langbot_plugin.api.entities.builtin.platform.events as platform_events
from ...command.errors import ParamNotEnoughError from ...command.errors import ParamNotEnoughError
from ..logger import EventLogger import langbot_plugin.api.definition.abstract.platform.event_logger as abstract_platform_logger
class WecomMessageConverter(adapter.MessageConverter): class WecomMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
@staticmethod @staticmethod
async def yiri2target(message_chain: platform_message.MessageChain, bot: WecomCSClient): async def yiri2target(message_chain: platform_message.MessageChain, bot: WecomCSClient):
content_list = [] content_list = []
@@ -68,7 +69,7 @@ class WecomMessageConverter(adapter.MessageConverter):
return chain return chain
class WecomEventConverter: class WecomEventConverter(abstract_platform_adapter.AbstractEventConverter):
@staticmethod @staticmethod
async def yiri2target(event: platform_events.Event, bot_account_id: int, bot: WecomCSClient) -> WecomCSEvent: async def yiri2target(event: platform_events.Event, bot_account_id: int, bot: WecomCSClient) -> WecomCSEvent:
# only for extracting user information # only for extracting user information
@@ -116,17 +117,12 @@ class WecomEventConverter:
) )
class WecomCSAdapter(adapter.MessagePlatformAdapter): class WecomCSAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
bot: WecomCSClient bot: WecomCSClient = pydantic.Field(exclude=True)
bot_account_id: str
message_converter: WecomMessageConverter = WecomMessageConverter() message_converter: WecomMessageConverter = WecomMessageConverter()
event_converter: WecomEventConverter = WecomEventConverter() event_converter: WecomEventConverter = WecomEventConverter()
config: dict
def __init__(self, config: dict, logger: EventLogger):
self.config = config
self.logger = logger
def __init__(self, config: dict, logger: abstract_platform_logger.AbstractEventLogger):
required_keys = [ required_keys = [
'corpid', 'corpid',
'secret', 'secret',
@@ -137,12 +133,20 @@ class WecomCSAdapter(adapter.MessagePlatformAdapter):
if missing_keys: if missing_keys:
raise ParamNotEnoughError('企业微信客服缺少相关配置项,请查看文档或联系管理员') raise ParamNotEnoughError('企业微信客服缺少相关配置项,请查看文档或联系管理员')
self.bot = WecomCSClient( bot = WecomCSClient(
corpid=config['corpid'], corpid=config['corpid'],
secret=config['secret'], secret=config['secret'],
token=config['token'], token=config['token'],
EncodingAESKey=config['EncodingAESKey'], EncodingAESKey=config['EncodingAESKey'],
logger=self.logger, logger=logger,
)
super().__init__(
config=config,
logger=logger,
bot=bot,
bot_account_id='',
listeners={},
) )
async def reply_message( async def reply_message(
@@ -169,7 +173,9 @@ class WecomCSAdapter(adapter.MessagePlatformAdapter):
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.MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None
],
): ):
async def on_message(event: WecomCSEvent): async def on_message(event: WecomCSEvent):
self.bot_account_id = event.receiver_id self.bot_account_id = event.receiver_id
@@ -198,9 +204,14 @@ class WecomCSAdapter(adapter.MessagePlatformAdapter):
async def kill(self) -> bool: async def kill(self) -> bool:
return False return False
async def is_muted(self, group_id: int) -> bool:
return False
async def unregister_listener( async def unregister_listener(
self, self,
event_type: type, event_type: type,
callback: typing.Callable[[platform_events.Event, MessagePlatformAdapter], None], callback: typing.Callable[
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None
],
): ):
return super().unregister_listener(event_type, callback) return super().unregister_listener(event_type, callback)

View File

@@ -1,3 +0,0 @@
from .entities import *
from .events import *
from .message import *

View File

@@ -1,107 +0,0 @@
from typing import Dict, List, Type
import pydantic.v1.main as pdm
from pydantic.v1 import BaseModel
class PlatformMetaclass(pdm.ModelMetaclass):
"""此类是平台中使用的 pydantic 模型的元类的基类。"""
def to_camel(name: str) -> str:
"""将下划线命名风格转换为小驼峰命名。"""
if name[:2] == '__': # 不处理双下划线开头的特殊命名。
return name
name_parts = name.split('_')
return ''.join(name_parts[:1] + [x.title() for x in name_parts[1:]])
class PlatformBaseModel(BaseModel, metaclass=PlatformMetaclass):
"""模型基类。
启用了三项配置:
1. 允许解析时传入额外的值,并将额外值保存在模型中。
2. 允许通过别名访问字段。
3. 自动生成小驼峰风格的别名。
"""
def __init__(self, *args, **kwargs):
""""""
super().__init__(*args, **kwargs)
def __repr__(self) -> str:
return (
self.__class__.__name__ + '(' + ', '.join((f'{k}={repr(v)}' for k, v in self.__dict__.items() if v)) + ')'
)
class Config:
extra = 'allow'
allow_population_by_field_name = True
alias_generator = to_camel
class PlatformIndexedMetaclass(PlatformMetaclass):
"""可以通过子类名获取子类的类的元类。"""
__indexedbases__: List[Type['PlatformIndexedModel']] = []
__indexedmodel__ = None
def __new__(cls, name, bases, attrs, **kwargs):
new_cls = super().__new__(cls, name, bases, attrs, **kwargs)
# 第一类PlatformIndexedModel
if name == 'PlatformIndexedModel':
cls.__indexedmodel__ = new_cls
new_cls.__indexes__ = {}
return new_cls
# 第二类PlatformIndexedModel 的直接子类,这些是可以通过子类名获取子类的类。
if cls.__indexedmodel__ in bases:
cls.__indexedbases__.append(new_cls)
new_cls.__indexes__ = {}
return new_cls
# 第三类PlatformIndexedModel 的直接子类的子类,这些添加到直接子类的索引中。
for base in cls.__indexedbases__:
if issubclass(new_cls, base):
base.__indexes__[name] = new_cls
return new_cls
def __getitem__(cls, name):
return cls.get_subtype(name)
class PlatformIndexedModel(PlatformBaseModel, metaclass=PlatformIndexedMetaclass):
"""可以通过子类名获取子类的类。"""
__indexes__: Dict[str, Type['PlatformIndexedModel']]
@classmethod
def get_subtype(cls, name: str) -> Type['PlatformIndexedModel']:
"""根据类名称,获取相应的子类类型。
Args:
name: 类名称。
Returns:
Type['PlatformIndexedModel']: 子类类型。
"""
try:
type_ = cls.__indexes__.get(name)
if not (type_ and issubclass(type_, cls)):
raise ValueError(f'`{name}` 不是 `{cls.__name__}` 的子类!')
return type_
except AttributeError:
raise ValueError(f'`{name}` 不是 `{cls.__name__}` 的子类!') from None
@classmethod
def parse_subtype(cls, obj: dict) -> 'PlatformIndexedModel':
"""通过字典,构造对应的模型对象。
Args:
obj: 一个字典,包含了模型对象的属性。
Returns:
PlatformIndexedModel: 构造的对象。
"""
if cls in PlatformIndexedModel.__subclasses__():
ModelType = cls.get_subtype(obj['type'])
return ModelType.parse_obj(obj)
return super().parse_obj(obj)

View File

@@ -1,88 +0,0 @@
# -*- coding: utf-8 -*-
"""
此模块提供实体和配置项模型。
"""
import abc
from datetime import datetime
from enum import Enum
import typing
import pydantic.v1 as pydantic
class Entity(pydantic.BaseModel):
"""实体,表示一个用户或群。"""
id: int
"""ID。"""
@abc.abstractmethod
def get_name(self) -> str:
"""名称。"""
class Friend(Entity):
"""私聊对象。"""
id: typing.Union[int, str]
"""ID。"""
nickname: typing.Optional[str]
"""昵称。"""
remark: typing.Optional[str]
"""备注。"""
def get_name(self) -> str:
return self.nickname or self.remark or ''
class Permission(str, Enum):
"""群成员身份权限。"""
Member = 'MEMBER'
"""成员。"""
Administrator = 'ADMINISTRATOR'
"""管理员。"""
Owner = 'OWNER'
"""群主。"""
def __repr__(self) -> str:
return repr(self.value)
class Group(Entity):
"""群。"""
id: typing.Union[int, str]
"""群号。"""
name: str
"""群名称。"""
permission: Permission
"""Bot 在群中的权限。"""
def get_name(self) -> str:
return self.name
class GroupMember(Entity):
"""群成员。"""
id: typing.Union[int, str]
"""群员 ID。"""
member_name: str
"""群员名称。"""
permission: Permission
"""在群中的权限。"""
group: Group
"""群。"""
special_title: str = ''
"""群头衔。"""
join_timestamp: datetime = datetime.utcfromtimestamp(0)
"""加入群的时间。"""
last_speak_timestamp: datetime = datetime.utcfromtimestamp(0)
"""最后一次发言的时间。"""
mute_time_remaining: int = 0
"""禁言剩余时间。"""
def get_name(self) -> str:
return self.member_name

View File

@@ -1,106 +0,0 @@
# -*- coding: utf-8 -*-
"""
此模块提供事件模型。
"""
import typing
import pydantic.v1 as pydantic
from . import entities as platform_entities
from . import message as platform_message
class Event(pydantic.BaseModel):
"""事件基类。
Args:
type: 事件名。
"""
type: str
"""事件名。"""
def __repr__(self):
return (
self.__class__.__name__
+ '('
+ ', '.join((f'{k}={repr(v)}' for k, v in self.__dict__.items() if k != 'type' and v))
+ ')'
)
@classmethod
def parse_subtype(cls, obj: dict) -> 'Event':
try:
return typing.cast(Event, super().parse_subtype(obj))
except ValueError:
return Event(type=obj['type'])
@classmethod
def get_subtype(cls, name: str) -> typing.Type['Event']:
try:
return typing.cast(typing.Type[Event], super().get_subtype(name))
except ValueError:
return Event
###############################
# Message Event
class MessageEvent(Event):
"""消息事件。
Args:
type: 事件名。
message_chain: 消息内容。
"""
type: str
"""事件名。"""
message_chain: platform_message.MessageChain
"""消息内容。"""
time: float | None = None
"""消息发送时间戳。"""
source_platform_object: typing.Optional[typing.Any] = None
"""原消息平台对象。
供消息平台适配器开发者使用,如果回复用户时需要使用原消息事件对象的信息,
那么可以将其存到这个字段以供之后取出使用。"""
class FriendMessage(MessageEvent):
"""私聊消息。
Args:
type: 事件名。
sender: 发送消息的好友。
message_chain: 消息内容。
"""
type: str = 'FriendMessage'
"""事件名。"""
sender: platform_entities.Friend
"""发送消息的好友。"""
message_chain: platform_message.MessageChain
"""消息内容。"""
class GroupMessage(MessageEvent):
"""群消息。
Args:
type: 事件名。
sender: 发送消息的群成员。
message_chain: 消息内容。
"""
type: str = 'GroupMessage'
"""事件名。"""
sender: platform_entities.GroupMember
"""发送消息的群成员。"""
message_chain: platform_message.MessageChain
"""消息内容。"""
@property
def group(self) -> platform_entities.Group:
return self.sender.group

View File

@@ -1,975 +0,0 @@
import itertools
import logging
import typing
from datetime import datetime
from pathlib import Path
import base64
import aiofiles
import httpx
import pydantic.v1 as pydantic
from . import entities as platform_entities
from .base import PlatformBaseModel, PlatformIndexedMetaclass, PlatformIndexedModel
logger = logging.getLogger(__name__)
class MessageComponentMetaclass(PlatformIndexedMetaclass):
"""消息组件元类。"""
__message_component__ = None
def __new__(cls, name, bases, attrs, **kwargs):
new_cls = super().__new__(cls, name, bases, attrs, **kwargs)
if name == 'MessageComponent':
cls.__message_component__ = new_cls
if not cls.__message_component__:
return new_cls
for base in bases:
if issubclass(base, cls.__message_component__):
# 获取字段名
if hasattr(new_cls, '__fields__'):
# 忽略 type 字段
new_cls.__parameter_names__ = list(new_cls.__fields__)[1:]
else:
new_cls.__parameter_names__ = []
break
return new_cls
class MessageComponent(PlatformIndexedModel, metaclass=MessageComponentMetaclass):
"""消息组件。"""
type: str
"""消息组件类型。"""
def __str__(self):
return ''
def __repr__(self):
return (
self.__class__.__name__
+ '('
+ ', '.join((f'{k}={repr(v)}' for k, v in self.__dict__.items() if k != 'type' and v))
+ ')'
)
def __init__(self, *args, **kwargs):
# 解析参数列表,将位置参数转化为具名参数
parameter_names = self.__parameter_names__
if len(args) > len(parameter_names):
raise TypeError(f'`{self.type}`需要{len(parameter_names)}个参数,但传入了{len(args)}个。')
for name, value in zip(parameter_names, args):
if name in kwargs:
raise TypeError(f'在 `{self.type}` 中,具名参数 `{name}` 与位置参数重复。')
kwargs[name] = value
super().__init__(**kwargs)
TMessageComponent = typing.TypeVar('TMessageComponent', bound=MessageComponent)
class MessageChain(PlatformBaseModel):
"""消息链。
一个构造消息链的例子:
```py
message_chain = MessageChain([
AtAll(),
Plain("Hello World!"),
])
```
`Plain` 可以省略。
```py
message_chain = MessageChain([
AtAll(),
"Hello World!",
])
```
在调用 API 时,参数中需要 MessageChain 的,也可以使用 `List[MessageComponent]` 代替。
例如,以下两种写法是等价的:
```py
await bot.send_friend_message(12345678, [
Plain("Hello World!")
])
```
```py
await bot.send_friend_message(12345678, MessageChain([
Plain("Hello World!")
]))
```
可以使用 `in` 运算检查消息链中:
1. 是否有某个消息组件。
2. 是否有某个类型的消息组件。
```py
if AtAll in message_chain:
print('AtAll')
if At(bot.qq) in message_chain:
print('At Me')
```
"""
__root__: typing.List[MessageComponent]
@staticmethod
def _parse_message_chain(msg_chain: typing.Iterable):
result = []
for msg in msg_chain:
if isinstance(msg, dict):
result.append(MessageComponent.parse_subtype(msg))
elif isinstance(msg, MessageComponent):
result.append(msg)
elif isinstance(msg, str):
result.append(Plain(msg))
else:
raise TypeError(f'消息链中元素需为 dict 或 str 或 MessageComponent当前类型{type(msg)}')
return result
@pydantic.validator('__root__', always=True, pre=True)
def _parse_component(cls, msg_chain):
if isinstance(msg_chain, (str, MessageComponent)):
msg_chain = [msg_chain]
if not msg_chain:
msg_chain = []
return cls._parse_message_chain(msg_chain)
@classmethod
def parse_obj(cls, msg_chain: typing.Iterable):
"""通过列表形式的消息链,构造对应的 `MessageChain` 对象。
Args:
msg_chain: 列表形式的消息链。
"""
result = cls._parse_message_chain(msg_chain)
return cls(__root__=result)
def __init__(self, __root__: typing.Iterable[MessageComponent] = None):
super().__init__(__root__=__root__)
def __str__(self):
return ''.join(str(component) for component in self.__root__)
def __repr__(self):
return f'{self.__class__.__name__}({self.__root__!r})'
def __iter__(self):
yield from self.__root__
def get_first(self, t: typing.Type[TMessageComponent]) -> typing.Optional[TMessageComponent]:
"""获取消息链中第一个符合类型的消息组件。"""
for component in self:
if isinstance(component, t):
return component
return None
@typing.overload
def __getitem__(self, index: int) -> MessageComponent: ...
@typing.overload
def __getitem__(self, index: slice) -> typing.List[MessageComponent]: ...
@typing.overload
def __getitem__(self, index: typing.Type[TMessageComponent]) -> typing.List[TMessageComponent]: ...
@typing.overload
def __getitem__(
self, index: typing.Tuple[typing.Type[TMessageComponent], int]
) -> typing.List[TMessageComponent]: ...
def __getitem__(
self,
index: typing.Union[
int,
slice,
typing.Type[TMessageComponent],
typing.Tuple[typing.Type[TMessageComponent], int],
],
) -> typing.Union[MessageComponent, typing.List[MessageComponent], typing.List[TMessageComponent]]:
return self.get(index)
def __setitem__(
self,
key: typing.Union[int, slice],
value: typing.Union[MessageComponent, str, typing.Iterable[typing.Union[MessageComponent, str]]],
):
if isinstance(value, str):
value = Plain(value)
if isinstance(value, typing.Iterable):
value = (Plain(c) if isinstance(c, str) else c for c in value)
self.__root__[key] = value # type: ignore
def __delitem__(self, key: typing.Union[int, slice]):
del self.__root__[key]
def __reversed__(self) -> typing.Iterable[MessageComponent]:
return reversed(self.__root__)
def has(
self,
sub: typing.Union[MessageComponent, typing.Type[MessageComponent], 'MessageChain', str],
) -> bool:
"""判断消息链中:
1. 是否有某个消息组件。
2. 是否有某个类型的消息组件。
Args:
sub (`Union[MessageComponent, Type[MessageComponent], 'MessageChain', str]`):
若为 `MessageComponent`,则判断该组件是否在消息链中。
若为 `Type[MessageComponent]`,则判断该组件类型是否在消息链中。
Returns:
bool: 是否找到。
"""
if isinstance(sub, type): # 检测消息链中是否有某种类型的对象
for i in self:
if type(i) is sub:
return True
return False
if isinstance(sub, MessageComponent): # 检查消息链中是否有某个组件
for i in self:
if i == sub:
return True
return False
raise TypeError(f'类型不匹配,当前类型:{type(sub)}')
def __contains__(self, sub) -> bool:
return self.has(sub)
def __ge__(self, other):
return other in self
def __len__(self) -> int:
return len(self.__root__)
def __add__(self, other: typing.Union['MessageChain', MessageComponent, str]) -> 'MessageChain':
if isinstance(other, MessageChain):
return self.__class__(self.__root__ + other.__root__)
if isinstance(other, str):
return self.__class__(self.__root__ + [Plain(other)])
if isinstance(other, MessageComponent):
return self.__class__(self.__root__ + [other])
return NotImplemented
def __radd__(self, other: typing.Union[MessageComponent, str]) -> 'MessageChain':
if isinstance(other, MessageComponent):
return self.__class__([other] + self.__root__)
if isinstance(other, str):
return self.__class__([typing.cast(MessageComponent, Plain(other))] + self.__root__)
return NotImplemented
def __mul__(self, other: int):
if isinstance(other, int):
return self.__class__(self.__root__ * other)
return NotImplemented
def __rmul__(self, other: int):
return self.__mul__(other)
def __iadd__(self, other: typing.Iterable[typing.Union[MessageComponent, str]]):
self.extend(other)
def __imul__(self, other: int):
if isinstance(other, int):
self.__root__ *= other
return NotImplemented
def index(
self,
x: typing.Union[MessageComponent, typing.Type[MessageComponent]],
i: int = 0,
j: int = -1,
) -> int:
"""返回 x 在消息链中首次出现项的索引号(索引号在 i 或其后且在 j 之前)。
Args:
x (`Union[MessageComponent, Type[MessageComponent]]`):
要查找的消息元素或消息元素类型。
i: 从哪个位置开始查找。
j: 查找到哪个位置结束。
Returns:
int: 如果找到,则返回索引号。
Raises:
ValueError: 没有找到。
TypeError: 类型不匹配。
"""
if isinstance(x, type):
l = len(self)
if i < 0:
i += l
if i < 0:
i = 0
if j < 0:
j += l
if j > l:
j = l
for index in range(i, j):
if type(self[index]) is x:
return index
raise ValueError('消息链中不存在该类型的组件。')
if isinstance(x, MessageComponent):
return self.__root__.index(x, i, j)
raise TypeError(f'类型不匹配,当前类型:{type(x)}')
def count(self, x: typing.Union[MessageComponent, typing.Type[MessageComponent]]) -> int:
"""返回消息链中 x 出现的次数。
Args:
x (`Union[MessageComponent, Type[MessageComponent]]`):
要查找的消息元素或消息元素类型。
Returns:
int: 次数。
"""
if isinstance(x, type):
return sum(1 for i in self if type(i) is x)
if isinstance(x, MessageComponent):
return self.__root__.count(x)
raise TypeError(f'类型不匹配,当前类型:{type(x)}')
def extend(self, x: typing.Iterable[typing.Union[MessageComponent, str]]):
"""将另一个消息链中的元素添加到消息链末尾。
Args:
x: 另一个消息链,也可为消息元素或字符串元素的序列。
"""
self.__root__.extend(Plain(c) if isinstance(c, str) else c for c in x)
def append(self, x: typing.Union[MessageComponent, str]):
"""将一个消息元素或字符串元素添加到消息链末尾。
Args:
x: 消息元素或字符串元素。
"""
self.__root__.append(Plain(x) if isinstance(x, str) else x)
def insert(self, i: int, x: typing.Union[MessageComponent, str]):
"""将一个消息元素或字符串添加到消息链中指定位置。
Args:
i: 插入位置。
x: 消息元素或字符串元素。
"""
self.__root__.insert(i, Plain(x) if isinstance(x, str) else x)
def pop(self, i: int = -1) -> MessageComponent:
"""从消息链中移除并返回指定位置的元素。
Args:
i: 移除位置。默认为末尾。
Returns:
MessageComponent: 移除的元素。
"""
return self.__root__.pop(i)
def remove(self, x: typing.Union[MessageComponent, typing.Type[MessageComponent]]):
"""从消息链中移除指定元素或指定类型的一个元素。
Args:
x: 指定的元素或元素类型。
"""
if isinstance(x, type):
self.pop(self.index(x))
if isinstance(x, MessageComponent):
self.__root__.remove(x)
def exclude(
self,
x: typing.Union[MessageComponent, typing.Type[MessageComponent]],
count: int = -1,
) -> 'MessageChain':
"""返回移除指定元素或指定类型的元素后剩余的消息链。
Args:
x: 指定的元素或元素类型。
count: 至多移除的数量。默认为全部移除。
Returns:
MessageChain: 剩余的消息链。
"""
def _exclude():
nonlocal count
x_is_type = isinstance(x, type)
for c in self:
if count > 0 and ((x_is_type and type(c) is x) or c == x):
count -= 1
continue
yield c
return self.__class__(_exclude())
def reverse(self):
"""将消息链原地翻转。"""
self.__root__.reverse()
@classmethod
def join(cls, *args: typing.Iterable[typing.Union[str, MessageComponent]]):
return cls(Plain(c) if isinstance(c, str) else c for c in itertools.chain(*args))
@property
def source(self) -> typing.Optional['Source']:
"""获取消息链中的 `Source` 对象。"""
return self.get_first(Source)
@property
def message_id(self) -> int:
"""获取消息链的 message_id若无法获取返回 -1。"""
source = self.source
return source.id if source else -1
TMessage = typing.Union[
MessageChain,
typing.Iterable[typing.Union[MessageComponent, str]],
MessageComponent,
str,
]
"""可以转化为 MessageChain 的类型。"""
class Source(MessageComponent):
"""源。包含消息的基本信息。"""
type: str = 'Source'
"""消息组件类型。"""
id: typing.Union[int, str]
"""消息的识别号用于引用回复Source 类型永远为 MessageChain 的第一个元素)。"""
time: datetime
"""消息时间。"""
class Plain(MessageComponent):
"""纯文本。"""
type: str = 'Plain'
"""消息组件类型。"""
text: str
"""文字消息。"""
def __str__(self):
return self.text
def __repr__(self):
return f'Plain({self.text!r})'
class Quote(MessageComponent):
"""引用。"""
type: str = 'Quote'
"""消息组件类型。"""
id: typing.Optional[int] = None
"""被引用回复的原消息的 message_id。"""
group_id: typing.Optional[typing.Union[int, str]] = None
"""被引用回复的原消息所接收的群号当为好友消息时为0。"""
sender_id: typing.Optional[typing.Union[int, str]] = None
"""被引用回复的原消息的发送者的ID。"""
target_id: typing.Optional[typing.Union[int, str]] = None
"""被引用回复的原消息的接收者者的ID或群ID。"""
origin: MessageChain
"""被引用回复的原消息的消息链对象。"""
@pydantic.validator('origin', always=True, pre=True)
def origin_formater(cls, v):
return MessageChain.parse_obj(v)
class At(MessageComponent):
"""At某人。"""
type: str = 'At'
"""消息组件类型。"""
target: typing.Union[int, str]
"""群员 ID。"""
display: typing.Optional[str] = None
"""At时显示的文字发送消息时无效自动使用群名片。"""
def __eq__(self, other):
return isinstance(other, At) and self.target == other.target
def __str__(self):
return f'@{self.display or self.target}'
class AtAll(MessageComponent):
"""At全体。"""
type: str = 'AtAll'
"""消息组件类型。"""
def __str__(self):
return '@全体成员'
class Image(MessageComponent):
"""图片。"""
type: str = 'Image'
"""消息组件类型。"""
image_id: typing.Optional[str] = None
"""图片的 image_id不为空时将忽略 url 属性。"""
url: typing.Optional[pydantic.HttpUrl] = None
"""图片的 URL发送时可作网络图片的链接接收时为图片的链接可用于图片下载。"""
path: typing.Union[str, Path, None] = None
"""图片的路径,发送本地图片。"""
base64: typing.Optional[str] = None
"""图片的 Base64 编码。"""
def __eq__(self, other):
return isinstance(other, Image) and self.type == other.type and self.uuid == other.uuid
def __str__(self):
return '[图片]'
@pydantic.validator('path')
def validate_path(cls, path: typing.Union[str, Path, None]):
"""修复 path 参数的行为,使之相对于 LangBot 的启动路径。"""
if path:
try:
return str(Path(path).resolve(strict=True))
except FileNotFoundError:
raise ValueError(f'无效路径:{path}')
else:
return path
@property
def uuid(self):
image_id = self.image_id
if image_id[0] == '{': # 群图片
image_id = image_id[1:37]
elif image_id[0] == '/': # 好友图片
image_id = image_id[1:]
return image_id
async def get_bytes(self) -> typing.Tuple[bytes, str]:
"""获取图片的 bytes 和 mime type"""
if self.url:
async with httpx.AsyncClient() as client:
response = await client.get(self.url)
response.raise_for_status()
return response.content, response.headers.get('Content-Type')
elif self.base64:
mime_type = 'image/jpeg'
split_index = self.base64.find(';base64,')
if split_index == -1:
raise ValueError('Invalid base64 string')
mime_type = self.base64[5:split_index]
base64_data = self.base64[split_index + 8 :]
return base64.b64decode(base64_data), mime_type
elif self.path:
async with aiofiles.open(self.path, 'rb') as f:
return await f.read(), 'image/jpeg'
else:
raise ValueError('Can not get bytes from image')
@classmethod
async def from_local(
cls,
filename: typing.Union[str, Path, None] = None,
content: typing.Optional[bytes] = None,
) -> 'Image':
"""从本地文件路径加载图片,以 base64 的形式传递。
Args:
filename: 从本地文件路径加载图片,与 `content` 二选一。
content: 从本地文件内容加载图片,与 `filename` 二选一。
Returns:
Image: 图片对象。
"""
if content:
pass
elif filename:
path = Path(filename)
import aiofiles
async with aiofiles.open(path, 'rb') as f:
content = await f.read()
else:
raise ValueError('请指定图片路径或图片内容!')
import base64
img = cls(base64=base64.b64encode(content).decode())
return img
@classmethod
def from_unsafe_path(cls, path: typing.Union[str, Path]) -> 'Image':
"""从不安全的路径加载图片。
Args:
path: 从不安全的路径加载图片。
Returns:
Image: 图片对象。
"""
return cls.construct(path=str(path))
class Unknown(MessageComponent):
"""未知。"""
type: str = 'Unknown'
"""消息组件类型。"""
text: str
"""文本。"""
def __str__(self):
return f'Unknown Message: {self.text}'
class Voice(MessageComponent):
"""语音。"""
type: str = 'Voice'
"""消息组件类型。"""
voice_id: typing.Optional[str] = None
"""语音的 voice_id不为空时将忽略 url 属性。"""
url: typing.Optional[str] = None
"""语音的 URL发送时可作网络语音的链接接收时为语音文件的链接可用于语音下载。"""
path: typing.Optional[str] = None
"""语音的路径,发送本地语音。"""
base64: typing.Optional[str] = None
"""语音的 Base64 编码。"""
length: typing.Optional[int] = None
"""语音的长度,单位为秒。"""
@pydantic.validator('path')
def validate_path(cls, path: typing.Optional[str]):
"""修复 path 参数的行为,使之相对于 LangBot 的启动路径。"""
if path:
try:
return str(Path(path).resolve(strict=True))
except FileNotFoundError:
raise ValueError(f'无效路径:{path}')
else:
return path
def __str__(self):
return '[语音]'
async def download(
self,
filename: typing.Union[str, Path, None] = None,
directory: typing.Union[str, Path, None] = None,
):
"""下载语音到本地。
Args:
filename: 下载到本地的文件路径。与 `directory` 二选一。
directory: 下载到本地的文件夹路径。与 `filename` 二选一。
"""
if not self.url:
logger.warning(f'语音 `{self.voice_id}` 无 url 参数,下载失败。')
return
import httpx
async with httpx.AsyncClient() as client:
response = await client.get(self.url)
response.raise_for_status()
content = response.content
if filename:
path = Path(filename)
path.parent.mkdir(parents=True, exist_ok=True)
elif directory:
path = Path(directory)
path.mkdir(parents=True, exist_ok=True)
path = path / f'{self.voice_id}.silk'
else:
raise ValueError('请指定文件路径或文件夹路径!')
import aiofiles
async with aiofiles.open(path, 'wb') as f:
await f.write(content)
@classmethod
async def from_local(
cls,
filename: typing.Union[str, Path, None] = None,
content: typing.Optional[bytes] = None,
) -> 'Voice':
"""从本地文件路径加载语音,以 base64 的形式传递。
Args:
filename: 从本地文件路径加载语音,与 `content` 二选一。
content: 从本地文件内容加载语音,与 `filename` 二选一。
"""
if content:
pass
if filename:
path = Path(filename)
import aiofiles
async with aiofiles.open(path, 'rb') as f:
content = await f.read()
else:
raise ValueError('请指定语音路径或语音内容!')
import base64
img = cls(base64=base64.b64encode(content).decode())
return img
class ForwardMessageNode(pydantic.BaseModel):
"""合并转发中的一条消息。"""
sender_id: typing.Optional[typing.Union[int, str]] = None
"""发送人ID。"""
sender_name: typing.Optional[str] = None
"""显示名称。"""
message_chain: typing.Optional[MessageChain] = None
"""消息内容。"""
message_id: typing.Optional[int] = None
"""消息的 message_id。"""
time: typing.Optional[datetime] = None
"""发送时间。"""
@pydantic.validator('message_chain', check_fields=False)
def _validate_message_chain(cls, value: typing.Union[MessageChain, list]):
if isinstance(value, list):
return MessageChain.parse_obj(value)
return value
@classmethod
def create(
cls,
sender: typing.Union[platform_entities.Friend, platform_entities.GroupMember],
message: MessageChain,
) -> 'ForwardMessageNode':
"""从消息链生成转发消息。
Args:
sender: 发送人。
message: 消息内容。
Returns:
ForwardMessageNode: 生成的一条消息。
"""
return ForwardMessageNode(sender_id=sender.id, sender_name=sender.get_name(), message_chain=message)
class ForwardMessageDiaplay(pydantic.BaseModel):
title: str = '群聊的聊天记录'
brief: str = '[聊天记录]'
source: str = '聊天记录'
preview: typing.List[str] = []
summary: str = '查看x条转发消息'
class Forward(MessageComponent):
"""合并转发。"""
type: str = 'Forward'
"""消息组件类型。"""
display: ForwardMessageDiaplay
"""显示信息"""
node_list: typing.List[ForwardMessageNode]
"""转发消息节点列表。"""
def __init__(self, *args, **kwargs):
if len(args) == 1:
self.node_list = args[0]
super().__init__(**kwargs)
super().__init__(*args, **kwargs)
def __str__(self):
return '[聊天记录]'
class File(MessageComponent):
"""文件。"""
type: str = 'File'
"""消息组件类型。"""
id: str = ''
"""文件识别 ID。"""
name: str
"""文件名称。"""
size: int = 0
"""文件大小。"""
url: str
"""文件路径"""
def __str__(self):
return f'[文件]{self.name}'
class Face(MessageComponent):
"""系统表情
此处将超级表情骰子/划拳一同归类于face
当face_type为rps(划拳)时 face_id 对应的是手势
当face_type为dice(骰子)时 face_id 对应的是点数
"""
type: str = 'Face'
"""表情类型"""
face_type: str = 'face'
"""表情id"""
face_id: int = 0
"""表情名"""
face_name: str = ''
def __str__(self):
if self.face_type == 'face':
return f'[表情]{self.face_name}'
elif self.face_type == 'dice':
return f'[表情]{self.face_id}点的{self.face_name}'
elif self.face_type == 'rps':
return f'[表情]{self.face_name}({self.rps_data(self.face_id)})'
def rps_data(self,face_id):
rps_dict ={
1 : "",
2 : "剪刀",
3 : "石头",
}
return rps_dict[face_id]
# ================ 个人微信专用组件 ================
class WeChatMiniPrograms(MessageComponent):
"""小程序。个人微信专用组件。"""
type: str = 'WeChatMiniPrograms'
"""小程序id"""
mini_app_id: str
"""小程序归属用户id"""
user_name: str
"""小程序名称"""
display_name: typing.Optional[str] = ''
"""打开地址"""
page_path: typing.Optional[str] = ''
"""小程序标题"""
title: typing.Optional[str] = ''
"""首页图片"""
image_url: typing.Optional[str] = ''
class WeChatForwardMiniPrograms(MessageComponent):
"""转发小程序。个人微信专用组件。"""
type: str = 'WeChatForwardMiniPrograms'
"""xml数据"""
xml_data: str
"""首页图片"""
image_url: typing.Optional[str] = None
def __str__(self):
return self.xml_data
class WeChatEmoji(MessageComponent):
"""emoji表情。个人微信专用组件。"""
type: str = 'WeChatEmoji'
"""emojimd5"""
emoji_md5: str
"""emoji大小"""
emoji_size: int
class WeChatLink(MessageComponent):
"""发送链接。个人微信专用组件。"""
type: str = 'WeChatLink'
"""标题"""
link_title: str = ''
"""链接描述"""
link_desc: str = ''
"""链接地址"""
link_url: str = ''
"""链接略缩图"""
link_thumb_url: str = ''
class WeChatForwardLink(MessageComponent):
"""转发链接。个人微信专用组件。"""
type: str = 'WeChatForwardLink'
"""xml数据"""
xml_data: str
def __str__(self):
return self.xml_data
class WeChatForwardImage(MessageComponent):
"""转发图片。个人微信专用组件。"""
type: str = 'WeChatForwardImage'
"""xml数据"""
xml_data: str
def __str__(self):
return self.xml_data
class WeChatForwardFile(MessageComponent):
"""转发文件。个人微信专用组件。"""
type: str = 'WeChatForwardFile'
"""xml数据"""
xml_data: str
def __str__(self):
return self.xml_data
class WeChatAppMsg(MessageComponent):
"""通用appmsg发送。个人微信专用组件。"""
type: str = 'WeChatAppMsg'
"""xml数据"""
app_msg: str
def __str__(self):
return self.app_msg
class WeChatForwardQuote(MessageComponent):
"""转发引用消息。个人微信专用组件。"""
type: str = 'WeChatForwardQuote'
"""xml数据"""
app_msg: str
def __str__(self):
return self.app_msg
class WeChatFile(MessageComponent):
"""文件。"""
type: str = 'File'
"""消息组件类型。"""
file_id: str = ''
"""文件识别 ID。"""
file_name: str = ''
"""文件名称。"""
file_size: int = 0
"""文件大小。"""
file_path: str = ''
"""文件地址"""
file_base64: str = ''
"""base64"""
def __str__(self):
return f'[文件]{self.file_name}'

View File

@@ -5,8 +5,8 @@ import abc
from . import events from . import events
from ..core import app from ..core import app
from ..platform.types import message as platform_message import langbot_plugin.api.entities.builtin.platform.message as platform_message
from ..platform import adapter as platform_adapter import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
def register( def register(
@@ -115,7 +115,7 @@ class APIHost:
# ========== 插件可调用的 API主程序API ========== # ========== 插件可调用的 API主程序API ==========
def get_platform_adapters(self) -> list[platform_adapter.MessagePlatformAdapter]: def get_platform_adapters(self) -> list[abstract_platform_adapter.AbstractMessagePlatformAdapter]:
"""获取已启用的消息平台适配器列表 """获取已启用的消息平台适配器列表
Returns: Returns:
@@ -125,7 +125,7 @@ class APIHost:
async def send_active_message( async def send_active_message(
self, self,
adapter: platform_adapter.MessagePlatformAdapter, adapter: abstract_platform_adapter.AbstractMessagePlatformAdapter,
target_type: str, target_type: str,
target_id: str, target_id: str,
message: platform_message.MessageChain, message: platform_message.MessageChain,

View File

@@ -4,10 +4,10 @@ import typing
import pydantic.v1 as pydantic import pydantic.v1 as pydantic
from ..provider import entities as llm_entities
from ..platform.types import message as platform_message
import langbot_plugin.api.entities.builtin.provider.session as provider_session
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query
import langbot_plugin.api.entities.builtin.platform.message as platform_message
import langbot_plugin.api.entities.builtin.provider.session as provider_session
from ..provider import entities as llm_entities
class BaseEventModel(pydantic.BaseModel): class BaseEventModel(pydantic.BaseModel):

View File

@@ -6,7 +6,7 @@ import pydantic
from pkg.provider import entities from pkg.provider import entities
from ..platform.types import message as platform_message import langbot_plugin.api.entities.builtin.platform.message as platform_message
class FunctionCall(pydantic.BaseModel): class FunctionCall(pydantic.BaseModel):