mirror of
https://github.com/langbot-app/LangBot.git
synced 2025-11-25 19:37:36 +08:00
feat: switch message platform adapters to sdk
This commit is contained in:
@@ -3,7 +3,7 @@ from quart import request
|
||||
import httpx
|
||||
from quart import Quart
|
||||
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
|
||||
import json
|
||||
import traceback
|
||||
|
||||
@@ -4,7 +4,7 @@ from quart import Quart, jsonify, request
|
||||
from slack_sdk.web.async_client import AsyncWebClient
|
||||
from .slackevent import SlackEvent
|
||||
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:
|
||||
|
||||
@@ -8,7 +8,7 @@ from quart import Quart
|
||||
import xml.etree.ElementTree as ET
|
||||
from typing import Callable, Dict, Any
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ from quart import Quart
|
||||
import xml.etree.ElementTree as ET
|
||||
from typing import Callable
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import pydantic
|
||||
|
||||
import langbot_plugin.api.entities.builtin.provider.session as provider_session
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from ...core import app
|
||||
from .. import stage, entities
|
||||
from . import filter as filter_model, entities as filter_entities
|
||||
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
|
||||
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query
|
||||
from . import filters
|
||||
|
||||
@@ -4,9 +4,9 @@ import enum
|
||||
import typing
|
||||
|
||||
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.platform.message as platform_message
|
||||
|
||||
|
||||
class ResultType(enum.Enum):
|
||||
|
||||
@@ -5,7 +5,7 @@ import traceback
|
||||
|
||||
from . import strategy
|
||||
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
|
||||
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query
|
||||
from . import strategies
|
||||
|
||||
@@ -4,8 +4,8 @@ from __future__ import annotations
|
||||
|
||||
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.platform.message as platform_message
|
||||
|
||||
ForwardMessageDiaplay = platform_message.ForwardMessageDiaplay
|
||||
Forward = platform_message.Forward
|
||||
|
||||
@@ -8,10 +8,10 @@ import re
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
import functools
|
||||
from ....platform.types import message as platform_message
|
||||
|
||||
from .. import strategy as strategy_model
|
||||
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')
|
||||
|
||||
@@ -4,7 +4,8 @@ import typing
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ from ..core import app
|
||||
from . import entities as pipeline_entities
|
||||
from ..entity.persistence import pipeline as persistence_pipeline
|
||||
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 ..utils import importutil
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import typing
|
||||
|
||||
from ..platform import adapter as msadapter
|
||||
from ..platform.types import message as platform_message
|
||||
from ..platform.types import 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
|
||||
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.definition.abstract.platform.adapter as abstract_platform_adapter
|
||||
|
||||
|
||||
class QueryPool:
|
||||
@@ -35,7 +35,7 @@ class QueryPool:
|
||||
sender_id: typing.Union[int, str],
|
||||
message_event: platform_events.MessageEvent,
|
||||
message_chain: platform_message.MessageChain,
|
||||
adapter: msadapter.MessagePlatformAdapter,
|
||||
adapter: abstract_platform_adapter.AbstractMessagePlatformAdapter,
|
||||
pipeline_uuid: typing.Optional[str] = None,
|
||||
) -> pipeline_query.Query:
|
||||
async with self.condition:
|
||||
|
||||
@@ -5,7 +5,7 @@ import datetime
|
||||
from .. import stage, entities
|
||||
from langbot_plugin.api.entities.builtin.provider import message as provider_message
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from ... import entities
|
||||
from ....provider import runner as runner_module
|
||||
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 ....provider import runners
|
||||
import langbot_plugin.api.entities.builtin.provider.session as provider_session
|
||||
|
||||
@@ -4,9 +4,9 @@ import typing
|
||||
|
||||
from .. import handler
|
||||
from ... import entities
|
||||
from langbot_plugin.api.entities.builtin.provider import message as provider_message
|
||||
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.pipeline.query as pipeline_query
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ import random
|
||||
import asyncio
|
||||
|
||||
|
||||
from ...platform.types import events as platform_events
|
||||
from ...platform.types import message as platform_message
|
||||
import langbot_plugin.api.entities.builtin.platform.events as platform_events
|
||||
import langbot_plugin.api.entities.builtin.platform.message as platform_message
|
||||
|
||||
from .. import stage, entities
|
||||
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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):
|
||||
|
||||
@@ -5,7 +5,7 @@ import typing
|
||||
from ...core import app
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
|
||||
from .. import rule as rule_model
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from .. import rule as rule_model
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import random
|
||||
|
||||
from .. import rule as rule_model
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import re
|
||||
|
||||
from .. import rule as rule_model
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import typing
|
||||
from .. import entities
|
||||
from .. import stage
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,15 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import asyncio
|
||||
import traceback
|
||||
import sqlalchemy
|
||||
|
||||
# FriendMessage, Image, MessageChain, Plain
|
||||
from . import adapter as msadapter
|
||||
|
||||
from ..core import app, entities as core_entities, taskmgr
|
||||
from .types import events as platform_events, message as platform_message
|
||||
|
||||
from ..discover import engine
|
||||
|
||||
@@ -20,11 +15,9 @@ from ..entity.errors import platform as platform_errors
|
||||
from .logger import EventLogger
|
||||
|
||||
import langbot_plugin.api.entities.builtin.provider.session as provider_session
|
||||
|
||||
# 处理 3.4 移除了 YiriMirai 之后,插件的兼容性问题
|
||||
from . import types as mirai
|
||||
|
||||
sys.modules['mirai'] = mirai
|
||||
import langbot_plugin.api.entities.builtin.platform.events as platform_events
|
||||
import langbot_plugin.api.entities.builtin.platform.message as platform_message
|
||||
import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
|
||||
|
||||
|
||||
class RuntimeBot:
|
||||
@@ -36,7 +29,7 @@ class RuntimeBot:
|
||||
|
||||
enable: bool
|
||||
|
||||
adapter: msadapter.MessagePlatformAdapter
|
||||
adapter: abstract_platform_adapter.AbstractMessagePlatformAdapter
|
||||
|
||||
task_wrapper: taskmgr.TaskWrapper
|
||||
|
||||
@@ -48,7 +41,7 @@ class RuntimeBot:
|
||||
self,
|
||||
ap: app.Application,
|
||||
bot_entity: persistence_bot.Bot,
|
||||
adapter: msadapter.MessagePlatformAdapter,
|
||||
adapter: abstract_platform_adapter.AbstractMessagePlatformAdapter,
|
||||
logger: EventLogger,
|
||||
):
|
||||
self.ap = ap
|
||||
@@ -61,7 +54,7 @@ class RuntimeBot:
|
||||
async def initialize(self):
|
||||
async def on_friend_message(
|
||||
event: platform_events.FriendMessage,
|
||||
adapter: msadapter.MessagePlatformAdapter,
|
||||
adapter: abstract_platform_adapter.AbstractMessagePlatformAdapter,
|
||||
):
|
||||
image_components = [
|
||||
component for component in event.message_chain if isinstance(component, platform_message.Image)
|
||||
@@ -86,7 +79,7 @@ class RuntimeBot:
|
||||
|
||||
async def on_group_message(
|
||||
event: platform_events.GroupMessage,
|
||||
adapter: msadapter.MessagePlatformAdapter,
|
||||
adapter: abstract_platform_adapter.AbstractMessagePlatformAdapter,
|
||||
):
|
||||
image_components = [
|
||||
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_dict: dict[str, type[msadapter.MessagePlatformAdapter]]
|
||||
adapter_dict: dict[str, type[abstract_platform_adapter.AbstractMessagePlatformAdapter]]
|
||||
|
||||
def __init__(self, ap: app.Application = None):
|
||||
self.ap = ap
|
||||
@@ -163,7 +156,7 @@ class PlatformManager:
|
||||
|
||||
async def initialize(self):
|
||||
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:
|
||||
adapter_dict[component.metadata.name] = component.get_python_component_class()
|
||||
self.adapter_dict = adapter_dict
|
||||
@@ -175,6 +168,7 @@ class PlatformManager:
|
||||
webchat_adapter_inst = webchat_adapter_class(
|
||||
{},
|
||||
webchat_logger,
|
||||
ap=self.ap,
|
||||
)
|
||||
webchat_adapter_inst.ap = self.ap
|
||||
|
||||
@@ -195,7 +189,7 @@ class PlatformManager:
|
||||
|
||||
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]
|
||||
|
||||
async def load_bots_from_db(self):
|
||||
@@ -275,43 +269,6 @@ class PlatformManager:
|
||||
return component
|
||||
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):
|
||||
# This method will only be called when the application launching
|
||||
await self.webchat_proxy_bot.run()
|
||||
|
||||
@@ -9,7 +9,8 @@ import traceback
|
||||
import uuid
|
||||
|
||||
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):
|
||||
@@ -55,7 +56,7 @@ MAX_LOG_COUNT = 200
|
||||
DELETE_COUNT_PER_TIME = 50
|
||||
|
||||
|
||||
class EventLogger:
|
||||
class EventLogger(abstract_platform_event_logger.AbstractEventLogger):
|
||||
"""used for logging bot events"""
|
||||
|
||||
ap: app.Application
|
||||
|
||||
@@ -5,16 +5,17 @@ import traceback
|
||||
import datetime
|
||||
|
||||
import aiocqhttp
|
||||
import pydantic
|
||||
|
||||
from .. import adapter
|
||||
from ..types import message as platform_message
|
||||
from ..types import events as platform_events
|
||||
from ..types import entities as platform_entities
|
||||
import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
|
||||
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 ...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
|
||||
async def yiri2target(
|
||||
message_chain: platform_message.MessageChain,
|
||||
@@ -69,7 +70,6 @@ class AiocqhttpMessageConverter(adapter.MessageConverter):
|
||||
elif msg.face_type=='dice':
|
||||
msg_list.append(aiocqhttp.MessageSegment.dice())
|
||||
|
||||
|
||||
else:
|
||||
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_name = file_data.get('file_name')
|
||||
file_path = file_data.get('file')
|
||||
_ = file_path
|
||||
file_url = file_data.get('file_url')
|
||||
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))
|
||||
@@ -211,7 +212,7 @@ class AiocqhttpMessageConverter(adapter.MessageConverter):
|
||||
return chain
|
||||
|
||||
|
||||
class AiocqhttpEventConverter(adapter.EventConverter):
|
||||
class AiocqhttpEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
||||
@staticmethod
|
||||
async def yiri2target(event: platform_events.MessageEvent, bot_account_id: int):
|
||||
return event.source_platform_object
|
||||
@@ -262,21 +263,19 @@ class AiocqhttpEventConverter(adapter.EventConverter):
|
||||
)
|
||||
|
||||
|
||||
class AiocqhttpAdapter(adapter.MessagePlatformAdapter):
|
||||
bot: aiocqhttp.CQHttp
|
||||
|
||||
bot_account_id: int
|
||||
class AiocqhttpAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
bot: aiocqhttp.CQHttp = pydantic.Field(exclude=True, default_factory=aiocqhttp.CQHttp)
|
||||
|
||||
message_converter: AiocqhttpMessageConverter = AiocqhttpMessageConverter()
|
||||
event_converter: AiocqhttpEventConverter = AiocqhttpEventConverter()
|
||||
|
||||
config: dict
|
||||
|
||||
on_websocket_connection_event_cache: typing.List[typing.Callable[[aiocqhttp.Event], None]] = []
|
||||
|
||||
def __init__(self, config: dict, logger: EventLogger):
|
||||
self.config = config
|
||||
self.logger = logger
|
||||
def __init__(self, config: dict, logger: abstract_platform_logger.AbstractEventLogger):
|
||||
super().__init__(
|
||||
config=config,
|
||||
logger=logger,
|
||||
)
|
||||
|
||||
async def shutdown_trigger_placeholder():
|
||||
while True:
|
||||
@@ -296,7 +295,6 @@ class AiocqhttpAdapter(adapter.MessagePlatformAdapter):
|
||||
aiocq_msg = (await AiocqhttpMessageConverter.yiri2target(message))[0]
|
||||
|
||||
if target_type == 'group':
|
||||
|
||||
await self.bot.send_group_msg(group_id=int(target_id), message=aiocq_msg)
|
||||
elif target_type == 'person':
|
||||
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(
|
||||
self,
|
||||
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):
|
||||
self.bot_account_id = event.self_id
|
||||
@@ -351,7 +351,9 @@ class AiocqhttpAdapter(adapter.MessagePlatformAdapter):
|
||||
def unregister_listener(
|
||||
self,
|
||||
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)
|
||||
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import traceback
|
||||
import typing
|
||||
from libs.dingtalk_api.dingtalkevent import DingTalkEvent
|
||||
from pkg.platform.types import message as platform_message
|
||||
from pkg.platform.adapter import MessagePlatformAdapter
|
||||
from .. import adapter
|
||||
from ..types import 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.definition.abstract.platform.adapter as abstract_platform_adapter
|
||||
import langbot_plugin.api.entities.builtin.platform.events as platform_events
|
||||
import langbot_plugin.api.entities.builtin.platform.entities as platform_entities
|
||||
from libs.dingtalk_api.api import DingTalkClient
|
||||
import datetime
|
||||
from ..logger import EventLogger
|
||||
|
||||
|
||||
class DingTalkMessageConverter(adapter.MessageConverter):
|
||||
class DingTalkMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
|
||||
@staticmethod
|
||||
async def yiri2target(message_chain: platform_message.MessageChain):
|
||||
content = ''
|
||||
@@ -47,7 +46,7 @@ class DingTalkMessageConverter(adapter.MessageConverter):
|
||||
return chain
|
||||
|
||||
|
||||
class DingTalkEventConverter(adapter.EventConverter):
|
||||
class DingTalkEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
||||
@staticmethod
|
||||
async def yiri2target(event: platform_events.MessageEvent):
|
||||
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_account_id: str
|
||||
message_converter: DingTalkMessageConverter = DingTalkMessageConverter()
|
||||
@@ -137,7 +136,9 @@ class DingTalkAdapter(adapter.MessagePlatformAdapter):
|
||||
def register_listener(
|
||||
self,
|
||||
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):
|
||||
try:
|
||||
@@ -171,6 +172,8 @@ class DingTalkAdapter(adapter.MessagePlatformAdapter):
|
||||
async def unregister_listener(
|
||||
self,
|
||||
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)
|
||||
|
||||
@@ -10,15 +10,16 @@ import os
|
||||
import datetime
|
||||
|
||||
import aiohttp
|
||||
import pydantic
|
||||
|
||||
from .. import adapter
|
||||
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
|
||||
import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
|
||||
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.event_logger as abstract_platform_logger
|
||||
|
||||
|
||||
class DiscordMessageConverter(adapter.MessageConverter):
|
||||
class DiscordMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
|
||||
@staticmethod
|
||||
async def yiri2target(
|
||||
message_chain: platform_message.MessageChain,
|
||||
@@ -111,7 +112,7 @@ class DiscordMessageConverter(adapter.MessageConverter):
|
||||
return platform_message.MessageChain(element_list)
|
||||
|
||||
|
||||
class DiscordEventConverter(adapter.EventConverter):
|
||||
class DiscordEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
||||
@staticmethod
|
||||
async def yiri2target(event: platform_events.Event) -> discord.Message:
|
||||
pass
|
||||
@@ -153,26 +154,21 @@ class DiscordEventConverter(adapter.EventConverter):
|
||||
)
|
||||
|
||||
|
||||
class DiscordAdapter(adapter.MessagePlatformAdapter):
|
||||
bot: discord.Client
|
||||
|
||||
bot_account_id: str # 用于在流水线中识别at是否是本bot,直接以bot_name作为标识
|
||||
|
||||
config: dict
|
||||
class DiscordAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
bot: discord.Client = pydantic.Field(exclude=True)
|
||||
|
||||
message_converter: DiscordMessageConverter = DiscordMessageConverter()
|
||||
event_converter: DiscordEventConverter = DiscordEventConverter()
|
||||
|
||||
listeners: typing.Dict[
|
||||
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):
|
||||
self.config = config
|
||||
self.logger = logger
|
||||
def __init__(self, config: dict, logger: abstract_platform_logger.AbstractEventLogger):
|
||||
bot_account_id = config['client_id']
|
||||
|
||||
self.bot_account_id = self.config['client_id']
|
||||
listeners = {}
|
||||
|
||||
adapter_self = self
|
||||
|
||||
@@ -192,7 +188,15 @@ class DiscordAdapter(adapter.MessagePlatformAdapter):
|
||||
if 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):
|
||||
pass
|
||||
@@ -227,14 +231,18 @@ class DiscordAdapter(adapter.MessagePlatformAdapter):
|
||||
def register_listener(
|
||||
self,
|
||||
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
|
||||
|
||||
def unregister_listener(
|
||||
self,
|
||||
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)
|
||||
|
||||
|
||||
@@ -17,12 +17,13 @@ import aiohttp
|
||||
import lark_oapi.ws.exception
|
||||
import quart
|
||||
from lark_oapi.api.im.v1 import *
|
||||
import pydantic
|
||||
|
||||
from .. import adapter
|
||||
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
|
||||
import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
|
||||
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.event_logger as abstract_platform_logger
|
||||
|
||||
|
||||
class AESCipher(object):
|
||||
@@ -51,7 +52,7 @@ class AESCipher(object):
|
||||
return self.decrypt(enc).decode('utf8')
|
||||
|
||||
|
||||
class LarkMessageConverter(adapter.MessageConverter):
|
||||
class LarkMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
|
||||
@staticmethod
|
||||
async def yiri2target(
|
||||
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)
|
||||
|
||||
|
||||
class LarkEventConverter(adapter.EventConverter):
|
||||
class LarkEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
||||
@staticmethod
|
||||
async def yiri2target(
|
||||
event: platform_events.MessageEvent,
|
||||
@@ -319,31 +320,24 @@ class LarkEventConverter(adapter.EventConverter):
|
||||
)
|
||||
|
||||
|
||||
class LarkAdapter(adapter.MessagePlatformAdapter):
|
||||
bot: lark_oapi.ws.Client
|
||||
api_client: lark_oapi.Client
|
||||
|
||||
bot_account_id: str # 用于在流水线中识别at是否是本bot,直接以bot_name作为标识
|
||||
lark_tenant_key: str # 飞书企业key
|
||||
class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
bot: lark_oapi.ws.Client = pydantic.Field(exclude=True)
|
||||
api_client: lark_oapi.Client = pydantic.Field(exclude=True)
|
||||
|
||||
message_converter: LarkMessageConverter = LarkMessageConverter()
|
||||
event_converter: LarkEventConverter = LarkEventConverter()
|
||||
|
||||
listeners: typing.Dict[
|
||||
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
|
||||
quart_app: quart.Quart = pydantic.Field(exclude=True)
|
||||
|
||||
def __init__(self, config: dict, logger: EventLogger):
|
||||
self.config = config
|
||||
self.logger = logger
|
||||
self.quart_app = quart.Quart(__name__)
|
||||
self.listeners = {}
|
||||
def __init__(self, config: dict, logger: abstract_platform_logger.AbstractEventLogger):
|
||||
quart_app = quart.Quart(__name__)
|
||||
|
||||
@self.quart_app.route('/lark/callback', methods=['POST'])
|
||||
@quart_app.route('/lark/callback', methods=['POST'])
|
||||
async def lark_callback():
|
||||
try:
|
||||
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()
|
||||
)
|
||||
|
||||
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)
|
||||
self.api_client = lark_oapi.Client.builder().app_id(config['app_id']).app_secret(config['app_secret']).build()
|
||||
bot = lark_oapi.ws.Client(config['app_id'], config['app_secret'], event_handler=event_handler)
|
||||
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):
|
||||
pass
|
||||
@@ -448,14 +452,18 @@ class LarkAdapter(adapter.MessagePlatformAdapter):
|
||||
def register_listener(
|
||||
self,
|
||||
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
|
||||
|
||||
def unregister_listener(
|
||||
self,
|
||||
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)
|
||||
|
||||
|
||||
@@ -11,11 +11,11 @@ import threading
|
||||
import quart
|
||||
import aiohttp
|
||||
|
||||
from ... import adapter
|
||||
import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
|
||||
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
|
||||
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 ....utils import image
|
||||
import xml.etree.ElementTree as ET
|
||||
from typing import Optional, Tuple
|
||||
@@ -23,7 +23,7 @@ from functools import partial
|
||||
from ...logger import EventLogger
|
||||
|
||||
|
||||
class GewechatMessageConverter(adapter.MessageConverter):
|
||||
class GewechatMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
|
||||
def __init__(self, config: dict):
|
||||
self.config = config
|
||||
|
||||
@@ -398,7 +398,7 @@ class GewechatMessageConverter(adapter.MessageConverter):
|
||||
return from_user_name.endswith('@chatroom')
|
||||
|
||||
|
||||
class GewechatEventConverter(adapter.EventConverter):
|
||||
class GewechatEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
||||
def __init__(self, config: dict):
|
||||
self.config = 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' # 定义适配器名称
|
||||
|
||||
bot: gewechat_client.GewechatClient
|
||||
@@ -475,7 +475,7 @@ class GeWeChatAdapter(adapter.MessagePlatformAdapter):
|
||||
|
||||
listeners: typing.Dict[
|
||||
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):
|
||||
@@ -625,14 +625,18 @@ class GeWeChatAdapter(adapter.MessagePlatformAdapter):
|
||||
def register_listener(
|
||||
self,
|
||||
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
|
||||
|
||||
def unregister_listener(
|
||||
self,
|
||||
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
|
||||
|
||||
|
||||
@@ -9,15 +9,15 @@ import traceback
|
||||
import nakuru
|
||||
import nakuru.entities.components as nkc
|
||||
|
||||
from ... import adapter as adapter_model
|
||||
from ....pipeline.longtext.strategies import forward
|
||||
from ...types import message as platform_message
|
||||
from ...types import entities as platform_entities
|
||||
from ...types import events as platform_events
|
||||
import langbot_plugin.api.entities.builtin.platform.message as platform_message
|
||||
import langbot_plugin.api.entities.builtin.platform.entities as platform_entities
|
||||
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
|
||||
|
||||
|
||||
class NakuruProjectMessageConverter(adapter_model.MessageConverter):
|
||||
class NakuruProjectMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
|
||||
"""消息转换器"""
|
||||
|
||||
@staticmethod
|
||||
@@ -109,7 +109,7 @@ class NakuruProjectMessageConverter(adapter_model.MessageConverter):
|
||||
return chain
|
||||
|
||||
|
||||
class NakuruProjectEventConverter(adapter_model.EventConverter):
|
||||
class NakuruProjectEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
||||
"""事件转换器"""
|
||||
|
||||
@staticmethod
|
||||
@@ -164,7 +164,7 @@ class NakuruProjectEventConverter(adapter_model.EventConverter):
|
||||
raise Exception('未支持转换的事件类型: ' + str(event))
|
||||
|
||||
|
||||
class NakuruAdapter(adapter_model.MessagePlatformAdapter):
|
||||
class NakuruAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
"""nakuru-project适配器"""
|
||||
|
||||
bot: nakuru.CQHTTP
|
||||
@@ -256,7 +256,9 @@ class NakuruAdapter(adapter_model.MessagePlatformAdapter):
|
||||
def register_listener(
|
||||
self,
|
||||
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:
|
||||
source_cls = NakuruProjectEventConverter.yiri2target(event_type)
|
||||
@@ -283,7 +285,9 @@ class NakuruAdapter(adapter_model.MessagePlatformAdapter):
|
||||
def unregister_listener(
|
||||
self,
|
||||
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__
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@ import botpy
|
||||
import botpy.message as botpy_message
|
||||
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 ....core import app
|
||||
from ....config import manager as cfg_mgr
|
||||
from ...types import entities as platform_entities
|
||||
from ...types import events as platform_events
|
||||
from ...types import message as platform_message
|
||||
import langbot_plugin.api.entities.builtin.platform.entities as platform_entities
|
||||
import langbot_plugin.api.entities.builtin.platform.events as platform_events
|
||||
import langbot_plugin.api.entities.builtin.platform.message as platform_message
|
||||
from ...logger import EventLogger
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ class OpenIDMapping(typing.Generic[K, V]):
|
||||
return value
|
||||
|
||||
|
||||
class OfficialMessageConverter(adapter_model.MessageConverter):
|
||||
class OfficialMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
|
||||
"""QQ 官方消息转换器"""
|
||||
|
||||
@staticmethod
|
||||
@@ -237,7 +237,7 @@ class OfficialMessageConverter(adapter_model.MessageConverter):
|
||||
return chain
|
||||
|
||||
|
||||
class OfficialEventConverter(adapter_model.EventConverter):
|
||||
class OfficialEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
||||
"""事件转换器"""
|
||||
|
||||
def __init__(self):
|
||||
@@ -333,7 +333,7 @@ class OfficialEventConverter(adapter_model.EventConverter):
|
||||
)
|
||||
|
||||
|
||||
class OfficialAdapter(adapter_model.MessagePlatformAdapter):
|
||||
class OfficialAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
"""QQ 官方消息适配器"""
|
||||
|
||||
bot: botpy.Client = None
|
||||
@@ -484,7 +484,9 @@ class OfficialAdapter(adapter_model.MessagePlatformAdapter):
|
||||
def register_listener(
|
||||
self,
|
||||
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:
|
||||
|
||||
@@ -507,7 +509,9 @@ class OfficialAdapter(adapter_model.MessagePlatformAdapter):
|
||||
def unregister_listener(
|
||||
self,
|
||||
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])
|
||||
|
||||
|
||||
@@ -4,18 +4,18 @@ import asyncio
|
||||
import traceback
|
||||
|
||||
import datetime
|
||||
from pkg.platform.adapter import MessagePlatformAdapter
|
||||
from pkg.platform.types import events as platform_events, message as platform_message
|
||||
import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
|
||||
from libs.official_account_api.oaevent import OAEvent
|
||||
from libs.official_account_api.api import OAClient
|
||||
from libs.official_account_api.api import OAClientForLongerResponse
|
||||
from .. import adapter
|
||||
from ..types import entities as platform_entities
|
||||
import langbot_plugin.api.entities.builtin.platform.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 ..logger import EventLogger
|
||||
|
||||
|
||||
class OAMessageConverter(adapter.MessageConverter):
|
||||
class OAMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
|
||||
@staticmethod
|
||||
async def yiri2target(message_chain: platform_message.MessageChain):
|
||||
for msg in message_chain:
|
||||
@@ -33,7 +33,7 @@ class OAMessageConverter(adapter.MessageConverter):
|
||||
return chain
|
||||
|
||||
|
||||
class OAEventConverter(adapter.EventConverter):
|
||||
class OAEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
||||
@staticmethod
|
||||
async def target2yiri(event: OAEvent):
|
||||
if event.type == 'text':
|
||||
@@ -55,7 +55,7 @@ class OAEventConverter(adapter.EventConverter):
|
||||
return None
|
||||
|
||||
|
||||
class OfficialAccountAdapter(adapter.MessagePlatformAdapter):
|
||||
class OfficialAccountAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
bot: OAClient | OAClientForLongerResponse
|
||||
bot_account_id: str
|
||||
message_converter: OAMessageConverter = OAMessageConverter()
|
||||
@@ -116,7 +116,9 @@ class OfficialAccountAdapter(adapter.MessagePlatformAdapter):
|
||||
def register_listener(
|
||||
self,
|
||||
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):
|
||||
self.bot_account_id = event.receiver_id
|
||||
@@ -147,6 +149,8 @@ class OfficialAccountAdapter(adapter.MessagePlatformAdapter):
|
||||
async def unregister_listener(
|
||||
self,
|
||||
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)
|
||||
|
||||
@@ -5,10 +5,10 @@ import traceback
|
||||
|
||||
import datetime
|
||||
|
||||
from pkg.platform.adapter import MessagePlatformAdapter
|
||||
from pkg.platform.types import events as platform_events, message as platform_message
|
||||
from .. import adapter
|
||||
from ..types import entities as platform_entities
|
||||
import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
|
||||
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 ...command.errors import ParamNotEnoughError
|
||||
from libs.qq_official_api.api import QQOfficialClient
|
||||
from libs.qq_official_api.qqofficialevent import QQOfficialEvent
|
||||
@@ -16,7 +16,7 @@ from ...utils import image
|
||||
from ..logger import EventLogger
|
||||
|
||||
|
||||
class QQOfficialMessageConverter(adapter.MessageConverter):
|
||||
class QQOfficialMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
|
||||
@staticmethod
|
||||
async def yiri2target(message_chain: platform_message.MessageChain):
|
||||
content_list = []
|
||||
@@ -45,7 +45,7 @@ class QQOfficialMessageConverter(adapter.MessageConverter):
|
||||
return chain
|
||||
|
||||
|
||||
class QQOfficialEventConverter(adapter.EventConverter):
|
||||
class QQOfficialEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
||||
@staticmethod
|
||||
async def yiri2target(event: platform_events.MessageEvent) -> QQOfficialEvent:
|
||||
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
|
||||
config: dict
|
||||
bot_account_id: str
|
||||
@@ -212,7 +212,9 @@ class QQOfficialAdapter(adapter.MessagePlatformAdapter):
|
||||
def register_listener(
|
||||
self,
|
||||
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):
|
||||
self.bot_account_id = 'justbot'
|
||||
@@ -245,6 +247,8 @@ class QQOfficialAdapter(adapter.MessagePlatformAdapter):
|
||||
def unregister_listener(
|
||||
self,
|
||||
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)
|
||||
|
||||
@@ -6,17 +6,17 @@ import traceback
|
||||
import datetime
|
||||
|
||||
from libs.slack_api.api import SlackClient
|
||||
from pkg.platform.adapter import MessagePlatformAdapter
|
||||
from pkg.platform.types import events as platform_events, message as platform_message
|
||||
import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
|
||||
from libs.slack_api.slackevent import SlackEvent
|
||||
from .. import adapter
|
||||
from ..types import entities as platform_entities
|
||||
import langbot_plugin.api.entities.builtin.platform.events as platform_events
|
||||
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 ...utils import image
|
||||
from ..logger import EventLogger
|
||||
|
||||
|
||||
class SlackMessageConverter(adapter.MessageConverter):
|
||||
class SlackMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
|
||||
@staticmethod
|
||||
async def yiri2target(message_chain: platform_message.MessageChain):
|
||||
content_list = []
|
||||
@@ -43,7 +43,7 @@ class SlackMessageConverter(adapter.MessageConverter):
|
||||
return chain
|
||||
|
||||
|
||||
class SlackEventConverter(adapter.EventConverter):
|
||||
class SlackEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
||||
@staticmethod
|
||||
async def yiri2target(event: platform_events.MessageEvent) -> SlackEvent:
|
||||
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_account_id: str
|
||||
message_converter: SlackMessageConverter = SlackMessageConverter()
|
||||
@@ -132,7 +132,9 @@ class SlackAdapter(adapter.MessagePlatformAdapter):
|
||||
def register_listener(
|
||||
self,
|
||||
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):
|
||||
self.bot_account_id = 'SlackBot'
|
||||
@@ -163,6 +165,8 @@ class SlackAdapter(adapter.MessagePlatformAdapter):
|
||||
async def unregister_listener(
|
||||
self,
|
||||
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)
|
||||
|
||||
@@ -9,15 +9,16 @@ import typing
|
||||
import traceback
|
||||
import base64
|
||||
import aiohttp
|
||||
import pydantic
|
||||
|
||||
from .. import adapter
|
||||
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
|
||||
import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
|
||||
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.event_logger as abstract_platform_logger
|
||||
|
||||
|
||||
class TelegramMessageConverter(adapter.MessageConverter):
|
||||
class TelegramMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
|
||||
@staticmethod
|
||||
async def yiri2target(message_chain: platform_message.MessageChain, bot: telegram.Bot) -> list[dict]:
|
||||
components = []
|
||||
@@ -86,7 +87,7 @@ class TelegramMessageConverter(adapter.MessageConverter):
|
||||
return platform_message.MessageChain(message_components)
|
||||
|
||||
|
||||
class TelegramEventConverter(adapter.EventConverter):
|
||||
class TelegramEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
||||
@staticmethod
|
||||
async def yiri2target(event: platform_events.MessageEvent, bot: telegram.Bot):
|
||||
return event.source_platform_object
|
||||
@@ -128,26 +129,19 @@ class TelegramEventConverter(adapter.EventConverter):
|
||||
)
|
||||
|
||||
|
||||
class TelegramAdapter(adapter.MessagePlatformAdapter):
|
||||
bot: telegram.Bot
|
||||
application: telegram.ext.Application
|
||||
|
||||
bot_account_id: str
|
||||
class TelegramAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
bot: telegram.Bot = pydantic.Field(exclude=True)
|
||||
application: telegram.ext.Application = pydantic.Field(exclude=True)
|
||||
|
||||
message_converter: TelegramMessageConverter = TelegramMessageConverter()
|
||||
event_converter: TelegramEventConverter = TelegramEventConverter()
|
||||
|
||||
config: dict
|
||||
|
||||
listeners: typing.Dict[
|
||||
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):
|
||||
self.config = config
|
||||
self.logger = logger
|
||||
|
||||
def __init__(self, config: dict, logger: abstract_platform_logger.AbstractEventLogger):
|
||||
async def telegram_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
if update.message.from_user.is_bot:
|
||||
return
|
||||
@@ -158,10 +152,16 @@ class TelegramAdapter(adapter.MessagePlatformAdapter):
|
||||
except Exception:
|
||||
await self.logger.error(f'Error in telegram callback: {traceback.format_exc()}')
|
||||
|
||||
self.application = ApplicationBuilder().token(self.config['token']).build()
|
||||
self.bot = self.application.bot
|
||||
self.application.add_handler(
|
||||
MessageHandler(filters.TEXT | (filters.COMMAND) | filters.PHOTO, telegram_callback)
|
||||
application = ApplicationBuilder().token(config['token']).build()
|
||||
bot = application.bot
|
||||
application.add_handler(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):
|
||||
@@ -201,14 +201,18 @@ class TelegramAdapter(adapter.MessagePlatformAdapter):
|
||||
def register_listener(
|
||||
self,
|
||||
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
|
||||
|
||||
def unregister_listener(
|
||||
self,
|
||||
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)
|
||||
|
||||
|
||||
@@ -3,17 +3,19 @@ import logging
|
||||
import typing
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel
|
||||
import pydantic
|
||||
|
||||
from .. import adapter as msadapter
|
||||
from ..types import events as platform_events, message as platform_message, entities as platform_entities
|
||||
import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
|
||||
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 ..logger import EventLogger
|
||||
import langbot_plugin.api.definition.abstract.platform.event_logger as abstract_platform_logger
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WebChatMessage(BaseModel):
|
||||
class WebChatMessage(pydantic.BaseModel):
|
||||
id: int
|
||||
role: str
|
||||
content: str
|
||||
@@ -38,28 +40,35 @@ class WebChatSession:
|
||||
return self.message_lists[pipeline_uuid]
|
||||
|
||||
|
||||
class WebChatAdapter(msadapter.MessagePlatformAdapter):
|
||||
class WebChatAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
"""WebChat调试适配器,用于流水线调试"""
|
||||
|
||||
webchat_person_session: WebChatSession
|
||||
webchat_group_session: WebChatSession
|
||||
webchat_person_session: WebChatSession = pydantic.Field(exclude=True, default_factory=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.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):
|
||||
self.logger = logger
|
||||
self.config = config
|
||||
debug_messages: dict[str, list[dict]] = pydantic.Field(default_factory=dict, exclude=True)
|
||||
|
||||
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_group_session = WebChatSession(id='webchatgroup')
|
||||
|
||||
self.bot_account_id = 'webchatbot'
|
||||
|
||||
self.debug_messages = {}
|
||||
|
||||
async def send_message(
|
||||
self,
|
||||
target_type: str,
|
||||
@@ -112,7 +121,9 @@ class WebChatAdapter(msadapter.MessagePlatformAdapter):
|
||||
def register_listener(
|
||||
self,
|
||||
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
|
||||
@@ -120,11 +131,16 @@ class WebChatAdapter(msadapter.MessagePlatformAdapter):
|
||||
def unregister_listener(
|
||||
self,
|
||||
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]
|
||||
|
||||
async def is_muted(self, group_id: int) -> bool:
|
||||
return False
|
||||
|
||||
async def run_async(self):
|
||||
"""运行适配器"""
|
||||
await self.logger.info('WebChat调试适配器已启动')
|
||||
|
||||
@@ -17,19 +17,19 @@ import threading
|
||||
|
||||
import quart
|
||||
|
||||
from .. import adapter
|
||||
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
|
||||
import xml.etree.ElementTree as ET
|
||||
from typing import Optional, Tuple
|
||||
from functools import partial
|
||||
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):
|
||||
self.config = config
|
||||
self.bot = WeChatPadClient(self.config['wechatpad_url'], self.config['token'])
|
||||
@@ -281,7 +281,7 @@ class WeChatPadMessageConverter(adapter.MessageConverter):
|
||||
"""处理文件消息 (data_type=6)"""
|
||||
file_data = xml_data.find('.//appmsg')
|
||||
|
||||
if file_data.findtext('.//type', "") == "74":
|
||||
if file_data.findtext('.//type', '') == '74':
|
||||
return None
|
||||
|
||||
else:
|
||||
@@ -304,16 +304,19 @@ class WeChatPadMessageConverter(adapter.MessageConverter):
|
||||
|
||||
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)
|
||||
file_size = file_data["Data"]['TotalSize']
|
||||
file_size = file_data['Data']['TotalSize']
|
||||
|
||||
# print(file_base64)
|
||||
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.WeChatForwardFile(xml_data=xml_data_str)
|
||||
])
|
||||
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.WeChatForwardFile(xml_data=xml_data_str),
|
||||
]
|
||||
)
|
||||
|
||||
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')
|
||||
|
||||
|
||||
class WeChatPadEventConverter(adapter.EventConverter):
|
||||
class WeChatPadEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
||||
def __init__(self, config: dict):
|
||||
self.config = 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' # 定义适配器名称
|
||||
|
||||
bot: WeChatPadClient
|
||||
@@ -495,7 +498,7 @@ class WeChatPadAdapter(adapter.MessagePlatformAdapter):
|
||||
|
||||
listeners: typing.Dict[
|
||||
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):
|
||||
@@ -596,14 +599,18 @@ class WeChatPadAdapter(adapter.MessagePlatformAdapter):
|
||||
def register_listener(
|
||||
self,
|
||||
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
|
||||
|
||||
def unregister_listener(
|
||||
self,
|
||||
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
|
||||
|
||||
|
||||
@@ -6,17 +6,17 @@ import traceback
|
||||
import datetime
|
||||
|
||||
from libs.wecom_api.api import WecomClient
|
||||
from pkg.platform.adapter import MessagePlatformAdapter
|
||||
from pkg.platform.types import events as platform_events, message as platform_message
|
||||
import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
|
||||
from libs.wecom_api.wecomevent import WecomEvent
|
||||
from .. import adapter
|
||||
from ..types import entities as platform_entities
|
||||
from ...command.errors import ParamNotEnoughError
|
||||
from ...utils import image
|
||||
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
|
||||
async def yiri2target(message_chain: platform_message.MessageChain, bot: WecomClient):
|
||||
content_list = []
|
||||
@@ -70,7 +70,7 @@ class WecomMessageConverter(adapter.MessageConverter):
|
||||
return chain
|
||||
|
||||
|
||||
class WecomEventConverter:
|
||||
class WecomEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
||||
@staticmethod
|
||||
async def yiri2target(event: platform_events.Event, bot_account_id: int, bot: WecomClient) -> WecomEvent:
|
||||
# only for extracting user information
|
||||
@@ -126,7 +126,7 @@ class WecomEventConverter:
|
||||
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_account_id: str
|
||||
message_converter: WecomMessageConverter = WecomMessageConverter()
|
||||
@@ -192,7 +192,9 @@ class WecomAdapter(adapter.MessagePlatformAdapter):
|
||||
def register_listener(
|
||||
self,
|
||||
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):
|
||||
self.bot_account_id = event.receiver_id
|
||||
@@ -224,6 +226,8 @@ class WecomAdapter(adapter.MessagePlatformAdapter):
|
||||
async def unregister_listener(
|
||||
self,
|
||||
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)
|
||||
|
||||
@@ -4,18 +4,19 @@ import asyncio
|
||||
import traceback
|
||||
|
||||
import datetime
|
||||
import pydantic
|
||||
|
||||
from libs.wecom_customer_service_api.api import WecomCSClient
|
||||
from pkg.platform.adapter import MessagePlatformAdapter
|
||||
from pkg.platform.types import events as platform_events, message as platform_message
|
||||
import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
|
||||
from libs.wecom_customer_service_api.wecomcsevent import WecomCSEvent
|
||||
from .. import adapter
|
||||
from ..types import entities as platform_entities
|
||||
import langbot_plugin.api.entities.builtin.platform.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 ..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
|
||||
async def yiri2target(message_chain: platform_message.MessageChain, bot: WecomCSClient):
|
||||
content_list = []
|
||||
@@ -68,7 +69,7 @@ class WecomMessageConverter(adapter.MessageConverter):
|
||||
return chain
|
||||
|
||||
|
||||
class WecomEventConverter:
|
||||
class WecomEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
||||
@staticmethod
|
||||
async def yiri2target(event: platform_events.Event, bot_account_id: int, bot: WecomCSClient) -> WecomCSEvent:
|
||||
# only for extracting user information
|
||||
@@ -116,17 +117,12 @@ class WecomEventConverter:
|
||||
)
|
||||
|
||||
|
||||
class WecomCSAdapter(adapter.MessagePlatformAdapter):
|
||||
bot: WecomCSClient
|
||||
bot_account_id: str
|
||||
class WecomCSAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
bot: WecomCSClient = pydantic.Field(exclude=True)
|
||||
message_converter: WecomMessageConverter = WecomMessageConverter()
|
||||
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 = [
|
||||
'corpid',
|
||||
'secret',
|
||||
@@ -137,12 +133,20 @@ class WecomCSAdapter(adapter.MessagePlatformAdapter):
|
||||
if missing_keys:
|
||||
raise ParamNotEnoughError('企业微信客服缺少相关配置项,请查看文档或联系管理员')
|
||||
|
||||
self.bot = WecomCSClient(
|
||||
bot = WecomCSClient(
|
||||
corpid=config['corpid'],
|
||||
secret=config['secret'],
|
||||
token=config['token'],
|
||||
EncodingAESKey=config['EncodingAESKey'],
|
||||
logger=self.logger,
|
||||
logger=logger,
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
config=config,
|
||||
logger=logger,
|
||||
bot=bot,
|
||||
bot_account_id='',
|
||||
listeners={},
|
||||
)
|
||||
|
||||
async def reply_message(
|
||||
@@ -169,7 +173,9 @@ class WecomCSAdapter(adapter.MessagePlatformAdapter):
|
||||
def register_listener(
|
||||
self,
|
||||
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):
|
||||
self.bot_account_id = event.receiver_id
|
||||
@@ -198,9 +204,14 @@ class WecomCSAdapter(adapter.MessagePlatformAdapter):
|
||||
async def kill(self) -> bool:
|
||||
return False
|
||||
|
||||
async def is_muted(self, group_id: int) -> bool:
|
||||
return False
|
||||
|
||||
async def unregister_listener(
|
||||
self,
|
||||
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)
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
from .entities import *
|
||||
from .events import *
|
||||
from .message import *
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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}'
|
||||
@@ -5,8 +5,8 @@ import abc
|
||||
|
||||
from . import events
|
||||
from ..core import app
|
||||
from ..platform.types import message as platform_message
|
||||
from ..platform import adapter as platform_adapter
|
||||
import langbot_plugin.api.entities.builtin.platform.message as platform_message
|
||||
import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
|
||||
|
||||
|
||||
def register(
|
||||
@@ -115,7 +115,7 @@ class APIHost:
|
||||
|
||||
# ========== 插件可调用的 API(主程序API) ==========
|
||||
|
||||
def get_platform_adapters(self) -> list[platform_adapter.MessagePlatformAdapter]:
|
||||
def get_platform_adapters(self) -> list[abstract_platform_adapter.AbstractMessagePlatformAdapter]:
|
||||
"""获取已启用的消息平台适配器列表
|
||||
|
||||
Returns:
|
||||
@@ -125,7 +125,7 @@ class APIHost:
|
||||
|
||||
async def send_active_message(
|
||||
self,
|
||||
adapter: platform_adapter.MessagePlatformAdapter,
|
||||
adapter: abstract_platform_adapter.AbstractMessagePlatformAdapter,
|
||||
target_type: str,
|
||||
target_id: str,
|
||||
message: platform_message.MessageChain,
|
||||
|
||||
@@ -4,10 +4,10 @@ import typing
|
||||
|
||||
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.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):
|
||||
|
||||
@@ -6,7 +6,7 @@ import pydantic
|
||||
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):
|
||||
|
||||
Reference in New Issue
Block a user