mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-04 04:54:36 +00:00
feat: migrate aiocqhttp adapter to eba
This commit is contained in:
5
src/langbot/pkg/platform/adapters/aiocqhttp/__init__.py
Normal file
5
src/langbot/pkg/platform/adapters/aiocqhttp/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from langbot.pkg.platform.adapters.aiocqhttp.adapter import AiocqhttpAdapter
|
||||
|
||||
__all__ = ['AiocqhttpAdapter']
|
||||
172
src/langbot/pkg/platform/adapters/aiocqhttp/adapter.py
Normal file
172
src/langbot/pkg/platform/adapters/aiocqhttp/adapter.py
Normal file
@@ -0,0 +1,172 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import traceback
|
||||
import typing
|
||||
|
||||
import aiocqhttp
|
||||
import pydantic
|
||||
|
||||
import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
|
||||
import langbot_plugin.api.definition.abstract.platform.event_logger as abstract_platform_logger
|
||||
from langbot.pkg.platform.adapters.aiocqhttp.api_impl import AiocqhttpAPIMixin
|
||||
from langbot.pkg.platform.adapters.aiocqhttp.event_converter import AiocqhttpEventConverter
|
||||
from langbot.pkg.platform.adapters.aiocqhttp.message_converter import AiocqhttpMessageConverter
|
||||
from langbot.pkg.platform.adapters.aiocqhttp.platform_api import PLATFORM_API_MAP
|
||||
from langbot_plugin.api.entities.builtin.platform import events as platform_events
|
||||
from langbot_plugin.api.entities.builtin.platform.errors import NotSupportedError
|
||||
|
||||
|
||||
class AiocqhttpAdapter(AiocqhttpAPIMixin, abstract_platform_adapter.AbstractPlatformAdapter):
|
||||
bot: aiocqhttp.CQHttp = pydantic.Field(exclude=True)
|
||||
|
||||
message_converter: AiocqhttpMessageConverter = AiocqhttpMessageConverter()
|
||||
event_converter: AiocqhttpEventConverter = AiocqhttpEventConverter()
|
||||
|
||||
config: dict
|
||||
listeners: dict[
|
||||
typing.Type[platform_events.Event],
|
||||
typing.Callable[[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None],
|
||||
] = {}
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
def __init__(self, config: dict, logger: abstract_platform_logger.AbstractEventLogger):
|
||||
run_config = dict(config)
|
||||
|
||||
async def shutdown_trigger_placeholder():
|
||||
while True:
|
||||
await asyncio.sleep(1)
|
||||
|
||||
run_config['shutdown_trigger'] = shutdown_trigger_placeholder
|
||||
access_token = run_config.pop('access-token', '') or None
|
||||
bot = aiocqhttp.CQHttp(access_token=access_token)
|
||||
|
||||
super().__init__(
|
||||
config=run_config,
|
||||
logger=logger,
|
||||
bot=bot,
|
||||
bot_account_id='',
|
||||
listeners={},
|
||||
)
|
||||
self._register_native_handlers()
|
||||
|
||||
def get_supported_events(self) -> list[str]:
|
||||
return [
|
||||
'message.received',
|
||||
'message.deleted',
|
||||
'group.member_joined',
|
||||
'group.member_left',
|
||||
'group.member_banned',
|
||||
'friend.request_received',
|
||||
'friend.added',
|
||||
'bot.invited_to_group',
|
||||
'bot.removed_from_group',
|
||||
'bot.muted',
|
||||
'bot.unmuted',
|
||||
'platform.specific',
|
||||
]
|
||||
|
||||
def get_supported_apis(self) -> list[str]:
|
||||
return [
|
||||
'send_message',
|
||||
'reply_message',
|
||||
'delete_message',
|
||||
'forward_message',
|
||||
'get_message',
|
||||
'get_group_info',
|
||||
'get_group_list',
|
||||
'get_group_member_list',
|
||||
'get_group_member_info',
|
||||
'set_group_name',
|
||||
'get_user_info',
|
||||
'get_friend_list',
|
||||
'approve_friend_request',
|
||||
'approve_group_invite',
|
||||
'mute_member',
|
||||
'unmute_member',
|
||||
'kick_member',
|
||||
'leave_group',
|
||||
'call_platform_api',
|
||||
]
|
||||
|
||||
async def call_platform_api(self, action: str, params: dict = {}) -> dict:
|
||||
handler = PLATFORM_API_MAP.get(action)
|
||||
if handler is None:
|
||||
raise NotSupportedError(f'call_platform_api:{action}')
|
||||
return await handler(self.bot, params)
|
||||
|
||||
def register_listener(
|
||||
self,
|
||||
event_type: typing.Type[platform_events.Event],
|
||||
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, abstract_platform_adapter.AbstractMessagePlatformAdapter], None
|
||||
],
|
||||
):
|
||||
registered = self.listeners.get(event_type)
|
||||
if registered is callback:
|
||||
self.listeners.pop(event_type, None)
|
||||
|
||||
async def run_async(self):
|
||||
await self.bot._server_app.run_task(**self.config)
|
||||
|
||||
async def kill(self) -> bool:
|
||||
return False
|
||||
|
||||
def _register_native_handlers(self):
|
||||
@self.bot.on_message()
|
||||
async def on_message(event: aiocqhttp.Event):
|
||||
await self._handle_native_event(event)
|
||||
|
||||
@self.bot.on_notice()
|
||||
async def on_notice(event: aiocqhttp.Event):
|
||||
await self._handle_native_event(event)
|
||||
|
||||
@self.bot.on_request()
|
||||
async def on_request(event: aiocqhttp.Event):
|
||||
await self._handle_native_event(event)
|
||||
|
||||
@self.bot.on_websocket_connection
|
||||
async def on_websocket_connection(event: aiocqhttp.Event):
|
||||
self.bot_account_id = str(getattr(event, 'self_id', '') or self.bot_account_id)
|
||||
await self.logger.info(f'WebSocket connection established, bot id: {self.bot_account_id}')
|
||||
await self._dispatch_native_event(event)
|
||||
|
||||
async def _handle_native_event(self, event: aiocqhttp.Event):
|
||||
self.bot_account_id = str(getattr(event, 'self_id', '') or self.bot_account_id)
|
||||
if getattr(event, 'type', None) == 'message' and str(getattr(event, 'user_id', '')) == self.bot_account_id:
|
||||
return
|
||||
try:
|
||||
if getattr(event, 'type', None) == 'message' and (
|
||||
platform_events.FriendMessage in self.listeners or platform_events.GroupMessage in self.listeners
|
||||
):
|
||||
legacy_event = await self.event_converter.target2legacy(event, self.bot)
|
||||
if legacy_event:
|
||||
callback = self.listeners.get(type(legacy_event))
|
||||
if callback:
|
||||
await callback(legacy_event, self)
|
||||
await self._dispatch_native_event(event)
|
||||
except Exception:
|
||||
await self.logger.error(f'Error in aiocqhttp native event: {traceback.format_exc()}')
|
||||
|
||||
async def _dispatch_native_event(self, event: aiocqhttp.Event):
|
||||
eba_event = await self.event_converter.target2yiri(event, self.bot, self.bot_account_id)
|
||||
if eba_event:
|
||||
await self._dispatch_eba_event(eba_event)
|
||||
|
||||
async def _dispatch_eba_event(self, event: platform_events.EBAEvent):
|
||||
for event_type in (type(event), platform_events.EBAEvent, platform_events.Event):
|
||||
callback = self.listeners.get(event_type)
|
||||
if callback:
|
||||
await callback(event, self)
|
||||
return
|
||||
238
src/langbot/pkg/platform/adapters/aiocqhttp/api_impl.py
Normal file
238
src/langbot/pkg/platform/adapters/aiocqhttp/api_impl.py
Normal file
@@ -0,0 +1,238 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
|
||||
import aiocqhttp
|
||||
|
||||
from langbot.pkg.platform.adapters.aiocqhttp.event_converter import AiocqhttpEventConverter
|
||||
from langbot.pkg.platform.adapters.aiocqhttp.message_converter import AiocqhttpMessageConverter
|
||||
from langbot_plugin.api.entities.builtin.platform import entities as platform_entities
|
||||
from langbot_plugin.api.entities.builtin.platform import events as platform_events
|
||||
from langbot_plugin.api.entities.builtin.platform import message as platform_message
|
||||
from langbot_plugin.api.entities.builtin.platform.errors import NotSupportedError
|
||||
|
||||
|
||||
class AiocqhttpAPIMixin:
|
||||
bot: aiocqhttp.CQHttp
|
||||
|
||||
async def send_message(
|
||||
self,
|
||||
target_type: str,
|
||||
target_id: str,
|
||||
message: platform_message.MessageChain,
|
||||
) -> platform_events.MessageResult:
|
||||
forward = message.get_first(platform_message.Forward)
|
||||
if forward and target_type == 'group':
|
||||
raw = await self._send_forward_message(int(target_id), typing.cast(platform_message.Forward, forward))
|
||||
return platform_events.MessageResult(message_id=raw.get('message_id'), raw=raw)
|
||||
|
||||
aiocq_msg, _, _ = await AiocqhttpMessageConverter.yiri2target(message)
|
||||
if target_type == 'group':
|
||||
raw = await self.bot.send_group_msg(group_id=int(target_id), message=aiocq_msg)
|
||||
elif target_type in ('person', 'private'):
|
||||
raw = await self.bot.send_private_msg(user_id=int(target_id), message=aiocq_msg)
|
||||
else:
|
||||
raise ValueError(f'Unsupported aiocqhttp target_type: {target_type}')
|
||||
return platform_events.MessageResult(message_id=(raw or {}).get('message_id'), raw=raw or {})
|
||||
|
||||
async def reply_message(
|
||||
self,
|
||||
message_source: platform_events.MessageEvent,
|
||||
message: platform_message.MessageChain,
|
||||
quote_origin: bool = False,
|
||||
) -> platform_events.MessageResult:
|
||||
assert isinstance(message_source.source_platform_object, aiocqhttp.Event)
|
||||
aiocq_msg, _, _ = await AiocqhttpMessageConverter.yiri2target(message)
|
||||
if quote_origin:
|
||||
source_id = getattr(message_source, 'message_id', None) or message_source.message_chain.message_id
|
||||
aiocq_msg = aiocqhttp.MessageSegment.reply(source_id) + aiocq_msg
|
||||
raw = await self.bot.send(message_source.source_platform_object, aiocq_msg)
|
||||
return platform_events.MessageResult(message_id=(raw or {}).get('message_id'), raw=raw or {})
|
||||
|
||||
async def delete_message(
|
||||
self,
|
||||
chat_type: str,
|
||||
chat_id: typing.Union[int, str],
|
||||
message_id: typing.Union[int, str],
|
||||
) -> None:
|
||||
await self.bot.delete_msg(message_id=int(message_id))
|
||||
|
||||
async def forward_message(
|
||||
self,
|
||||
from_chat_type: str,
|
||||
from_chat_id: typing.Union[int, str],
|
||||
message_id: typing.Union[int, str],
|
||||
to_chat_type: str,
|
||||
to_chat_id: typing.Union[int, str],
|
||||
) -> platform_events.MessageResult:
|
||||
raw_message = await self.bot.get_msg(message_id=int(message_id))
|
||||
target_message = aiocqhttp.Message(raw_message.get('message', []))
|
||||
if to_chat_type == 'group':
|
||||
raw = await self.bot.send_group_msg(group_id=int(to_chat_id), message=target_message)
|
||||
elif to_chat_type in ('person', 'private'):
|
||||
raw = await self.bot.send_private_msg(user_id=int(to_chat_id), message=target_message)
|
||||
else:
|
||||
raise ValueError(f'Unsupported aiocqhttp to_chat_type: {to_chat_type}')
|
||||
return platform_events.MessageResult(message_id=(raw or {}).get('message_id'), raw=raw or {})
|
||||
|
||||
async def get_message(
|
||||
self,
|
||||
chat_type: str,
|
||||
chat_id: typing.Union[int, str],
|
||||
message_id: typing.Union[int, str],
|
||||
) -> platform_events.MessageReceivedEvent:
|
||||
raw = await self.bot.get_msg(message_id=int(message_id))
|
||||
message_type = raw.get('message_type') or chat_type
|
||||
event = aiocqhttp.Event.from_payload(
|
||||
{
|
||||
'post_type': 'message',
|
||||
'message_type': 'group' if message_type == 'group' else 'private',
|
||||
'sub_type': raw.get('sub_type', 'normal'),
|
||||
'time': raw.get('time', 0),
|
||||
'self_id': self.bot_account_id or 0,
|
||||
'message_id': raw.get('message_id', message_id),
|
||||
'user_id': raw.get('sender', {}).get('user_id') or raw.get('user_id') or chat_id,
|
||||
'group_id': raw.get('group_id') or (chat_id if message_type == 'group' else None),
|
||||
'message': raw.get('message', []),
|
||||
'raw_message': raw.get('raw_message', ''),
|
||||
'sender': raw.get('sender', {}),
|
||||
}
|
||||
)
|
||||
return await AiocqhttpEventConverter.message_to_eba(event, self.bot)
|
||||
|
||||
async def get_group_info(self, group_id: typing.Union[int, str]) -> platform_entities.UserGroup:
|
||||
raw = await self.bot.get_group_info(group_id=int(group_id))
|
||||
return platform_entities.UserGroup(
|
||||
id=raw.get('group_id', group_id),
|
||||
name=raw.get('group_name', ''),
|
||||
member_count=raw.get('member_count'),
|
||||
)
|
||||
|
||||
async def get_group_list(self) -> list[platform_entities.UserGroup]:
|
||||
raw_list = await self.bot.get_group_list()
|
||||
return [
|
||||
platform_entities.UserGroup(
|
||||
id=item.get('group_id', ''),
|
||||
name=item.get('group_name', ''),
|
||||
member_count=item.get('member_count'),
|
||||
)
|
||||
for item in raw_list
|
||||
]
|
||||
|
||||
async def get_group_member_list(
|
||||
self,
|
||||
group_id: typing.Union[int, str],
|
||||
) -> list[platform_entities.UserGroupMember]:
|
||||
raw_list = await self.bot.get_group_member_list(group_id=int(group_id))
|
||||
return [self._member_to_entity(item, group_id) for item in raw_list]
|
||||
|
||||
async def get_group_member_info(
|
||||
self,
|
||||
group_id: typing.Union[int, str],
|
||||
user_id: typing.Union[int, str],
|
||||
) -> platform_entities.UserGroupMember:
|
||||
raw = await self.bot.get_group_member_info(group_id=int(group_id), user_id=int(user_id), no_cache=True)
|
||||
return self._member_to_entity(raw, group_id)
|
||||
|
||||
async def set_group_name(self, group_id: typing.Union[int, str], name: str) -> None:
|
||||
await self.bot.set_group_name(group_id=int(group_id), group_name=name)
|
||||
|
||||
async def mute_member(
|
||||
self,
|
||||
group_id: typing.Union[int, str],
|
||||
user_id: typing.Union[int, str],
|
||||
duration: int = 0,
|
||||
) -> None:
|
||||
await self.bot.set_group_ban(group_id=int(group_id), user_id=int(user_id), duration=int(duration))
|
||||
|
||||
async def unmute_member(self, group_id: typing.Union[int, str], user_id: typing.Union[int, str]) -> None:
|
||||
await self.bot.set_group_ban(group_id=int(group_id), user_id=int(user_id), duration=0)
|
||||
|
||||
async def kick_member(self, group_id: typing.Union[int, str], user_id: typing.Union[int, str]) -> None:
|
||||
await self.bot.set_group_kick(group_id=int(group_id), user_id=int(user_id), reject_add_request=False)
|
||||
|
||||
async def leave_group(self, group_id: typing.Union[int, str]) -> None:
|
||||
await self.bot.set_group_leave(group_id=int(group_id), is_dismiss=False)
|
||||
|
||||
async def get_user_info(self, user_id: typing.Union[int, str]) -> platform_entities.User:
|
||||
raw = await self.bot.get_stranger_info(user_id=int(user_id), no_cache=True)
|
||||
return platform_entities.User(
|
||||
id=raw.get('user_id', user_id),
|
||||
nickname=raw.get('nickname', ''),
|
||||
avatar_url=raw.get('avatar_url'),
|
||||
)
|
||||
|
||||
async def get_friend_list(self) -> list[platform_entities.User]:
|
||||
raw_list = await self.bot.get_friend_list()
|
||||
return [
|
||||
platform_entities.User(
|
||||
id=item.get('user_id', ''),
|
||||
nickname=item.get('nickname', ''),
|
||||
remark=item.get('remark'),
|
||||
)
|
||||
for item in raw_list
|
||||
]
|
||||
|
||||
async def approve_friend_request(
|
||||
self,
|
||||
request_id: typing.Union[int, str],
|
||||
approve: bool = True,
|
||||
remark: typing.Optional[str] = None,
|
||||
) -> None:
|
||||
await self.bot.set_friend_add_request(flag=str(request_id), approve=approve, remark=remark or '')
|
||||
|
||||
async def approve_group_invite(self, request_id: typing.Union[int, str], approve: bool = True) -> None:
|
||||
await self.bot.set_group_add_request(flag=str(request_id), sub_type='invite', approve=approve, reason='')
|
||||
|
||||
async def upload_file(self, file_data: bytes, filename: str) -> str:
|
||||
raise NotSupportedError('upload_file')
|
||||
|
||||
async def get_file_url(self, file_id: str) -> str:
|
||||
raise NotSupportedError('get_file_url')
|
||||
|
||||
@staticmethod
|
||||
def _member_to_entity(raw: dict, group_id: typing.Union[int, str]) -> platform_entities.UserGroupMember:
|
||||
role = platform_entities.MemberRole.MEMBER
|
||||
if raw.get('role') == 'owner':
|
||||
role = platform_entities.MemberRole.OWNER
|
||||
elif raw.get('role') == 'admin':
|
||||
role = platform_entities.MemberRole.ADMIN
|
||||
return platform_entities.UserGroupMember(
|
||||
user=platform_entities.User(
|
||||
id=raw.get('user_id', ''),
|
||||
nickname=raw.get('nickname', ''),
|
||||
remark=raw.get('card') or raw.get('remark'),
|
||||
),
|
||||
group_id=group_id,
|
||||
role=role,
|
||||
display_name=raw.get('card') or raw.get('nickname'),
|
||||
joined_at=float(raw['join_time']) if raw.get('join_time') else None,
|
||||
title=raw.get('title'),
|
||||
)
|
||||
|
||||
async def _send_forward_message(self, group_id: int, forward: platform_message.Forward) -> dict:
|
||||
messages = []
|
||||
for node in forward.node_list:
|
||||
if not node.message_chain:
|
||||
continue
|
||||
content, _, _ = await AiocqhttpMessageConverter.yiri2target(node.message_chain)
|
||||
if not content:
|
||||
continue
|
||||
messages.append(
|
||||
{
|
||||
'type': 'node',
|
||||
'data': {
|
||||
'user_id': str(node.sender_id or self.bot_account_id or '10000'),
|
||||
'nickname': node.sender_name or 'LangBot',
|
||||
'content': list(content),
|
||||
},
|
||||
}
|
||||
)
|
||||
if not messages:
|
||||
return {}
|
||||
try:
|
||||
return await self.bot.call_action(
|
||||
'send_forward_msg', group_id=group_id, user_id=str(self.bot_account_id), messages=messages
|
||||
)
|
||||
except Exception:
|
||||
return await self.bot.call_action('send_group_forward_msg', group_id=group_id, messages=messages)
|
||||
244
src/langbot/pkg/platform/adapters/aiocqhttp/event_converter.py
Normal file
244
src/langbot/pkg/platform/adapters/aiocqhttp/event_converter.py
Normal file
@@ -0,0 +1,244 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
|
||||
import aiocqhttp
|
||||
|
||||
import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
|
||||
from langbot.pkg.platform.adapters.aiocqhttp.message_converter import AiocqhttpMessageConverter
|
||||
from langbot_plugin.api.entities.builtin.platform import entities as platform_entities
|
||||
from langbot_plugin.api.entities.builtin.platform import events as platform_events
|
||||
|
||||
|
||||
class AiocqhttpEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
||||
@staticmethod
|
||||
async def yiri2target(event: platform_events.Event, bot_account_id: int | str | None = None):
|
||||
return getattr(event, 'source_platform_object', None)
|
||||
|
||||
@staticmethod
|
||||
async def target2yiri(
|
||||
event: aiocqhttp.Event,
|
||||
bot: aiocqhttp.CQHttp | None = None,
|
||||
bot_user_id: int | str | None = None,
|
||||
) -> platform_events.Event | None:
|
||||
event_type = getattr(event, 'type', None)
|
||||
if event_type == 'message':
|
||||
return await AiocqhttpEventConverter.message_to_eba(event, bot)
|
||||
if event_type == 'notice':
|
||||
return AiocqhttpEventConverter.notice_to_eba(event, bot_user_id)
|
||||
if event_type == 'request':
|
||||
return AiocqhttpEventConverter.request_to_eba(event)
|
||||
if event_type == 'meta_event':
|
||||
return AiocqhttpEventConverter.platform_specific(event, f'meta.{getattr(event, "detail_type", "")}')
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
async def target2legacy(
|
||||
event: aiocqhttp.Event,
|
||||
bot: aiocqhttp.CQHttp | None = None,
|
||||
) -> platform_events.FriendMessage | platform_events.GroupMessage | None:
|
||||
eba_event = await AiocqhttpEventConverter.message_to_eba(event, bot)
|
||||
if eba_event:
|
||||
return eba_event.to_legacy_event()
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
async def message_to_eba(
|
||||
event: aiocqhttp.Event,
|
||||
bot: aiocqhttp.CQHttp | None = None,
|
||||
) -> platform_events.MessageReceivedEvent:
|
||||
message_chain = await AiocqhttpMessageConverter.target2yiri(
|
||||
getattr(event, 'message', []),
|
||||
getattr(event, 'message_id', -1),
|
||||
getattr(event, 'time', None),
|
||||
bot,
|
||||
)
|
||||
message_type = getattr(event, 'message_type', getattr(event, 'detail_type', 'private'))
|
||||
group = None
|
||||
chat_type = platform_entities.ChatType.PRIVATE
|
||||
chat_id = getattr(event, 'user_id', '')
|
||||
if message_type == 'group':
|
||||
chat_type = platform_entities.ChatType.GROUP
|
||||
chat_id = getattr(event, 'group_id', '')
|
||||
group = AiocqhttpEventConverter.group_from_event(event)
|
||||
|
||||
return platform_events.MessageReceivedEvent(
|
||||
type='message.received',
|
||||
adapter_name='aiocqhttp',
|
||||
message_id=getattr(event, 'message_id', ''),
|
||||
message_chain=message_chain,
|
||||
sender=AiocqhttpEventConverter.user_from_sender(event),
|
||||
chat_type=chat_type,
|
||||
chat_id=chat_id,
|
||||
group=group,
|
||||
timestamp=float(getattr(event, 'time', 0) or 0),
|
||||
source_platform_object=event,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def notice_to_eba(
|
||||
event: aiocqhttp.Event,
|
||||
bot_user_id: int | str | None = None,
|
||||
) -> platform_events.EBAEvent:
|
||||
notice_type = getattr(event, 'notice_type', getattr(event, 'detail_type', ''))
|
||||
if notice_type in ('group_recall', 'friend_recall'):
|
||||
return platform_events.MessageDeletedEvent(
|
||||
type='message.deleted',
|
||||
adapter_name='aiocqhttp',
|
||||
message_id=getattr(event, 'message_id', ''),
|
||||
operator=AiocqhttpEventConverter.user(getattr(event, 'operator_id', None)),
|
||||
chat_type=platform_entities.ChatType.GROUP
|
||||
if notice_type == 'group_recall'
|
||||
else platform_entities.ChatType.PRIVATE,
|
||||
chat_id=getattr(event, 'group_id', getattr(event, 'user_id', '')),
|
||||
group=AiocqhttpEventConverter.group_from_event(event) if notice_type == 'group_recall' else None,
|
||||
timestamp=float(getattr(event, 'time', 0) or 0),
|
||||
source_platform_object=event,
|
||||
)
|
||||
if notice_type == 'group_increase':
|
||||
group = AiocqhttpEventConverter.group_from_event(event)
|
||||
user = AiocqhttpEventConverter.user(getattr(event, 'user_id', ''))
|
||||
inviter_id = getattr(event, 'operator_id', None)
|
||||
if AiocqhttpEventConverter._is_bot_user(getattr(event, 'user_id', None), bot_user_id, event):
|
||||
return platform_events.BotInvitedToGroupEvent(
|
||||
type='bot.invited_to_group',
|
||||
adapter_name='aiocqhttp',
|
||||
group=group,
|
||||
inviter=AiocqhttpEventConverter.user(inviter_id) if inviter_id else None,
|
||||
timestamp=float(getattr(event, 'time', 0) or 0),
|
||||
source_platform_object=event,
|
||||
)
|
||||
return platform_events.MemberJoinedEvent(
|
||||
type='group.member_joined',
|
||||
adapter_name='aiocqhttp',
|
||||
group=group,
|
||||
member=user,
|
||||
inviter=AiocqhttpEventConverter.user(inviter_id) if inviter_id else None,
|
||||
join_type=getattr(event, 'sub_type', None) or 'direct',
|
||||
timestamp=float(getattr(event, 'time', 0) or 0),
|
||||
source_platform_object=event,
|
||||
)
|
||||
if notice_type == 'group_decrease':
|
||||
group = AiocqhttpEventConverter.group_from_event(event)
|
||||
operator = AiocqhttpEventConverter.user(getattr(event, 'operator_id', None))
|
||||
if AiocqhttpEventConverter._is_bot_user(getattr(event, 'user_id', None), bot_user_id, event):
|
||||
return platform_events.BotRemovedFromGroupEvent(
|
||||
type='bot.removed_from_group',
|
||||
adapter_name='aiocqhttp',
|
||||
group=group,
|
||||
operator=operator,
|
||||
timestamp=float(getattr(event, 'time', 0) or 0),
|
||||
source_platform_object=event,
|
||||
)
|
||||
return platform_events.MemberLeftEvent(
|
||||
type='group.member_left',
|
||||
adapter_name='aiocqhttp',
|
||||
group=group,
|
||||
member=AiocqhttpEventConverter.user(getattr(event, 'user_id', '')),
|
||||
is_kicked=getattr(event, 'sub_type', '') in ('kick', 'kick_me'),
|
||||
operator=operator,
|
||||
timestamp=float(getattr(event, 'time', 0) or 0),
|
||||
source_platform_object=event,
|
||||
)
|
||||
if notice_type == 'group_ban':
|
||||
group = AiocqhttpEventConverter.group_from_event(event)
|
||||
duration = int(getattr(event, 'duration', 0) or 0)
|
||||
operator = AiocqhttpEventConverter.user(getattr(event, 'operator_id', None))
|
||||
if AiocqhttpEventConverter._is_bot_user(getattr(event, 'user_id', None), bot_user_id, event):
|
||||
event_cls = platform_events.BotMutedEvent if duration > 0 else platform_events.BotUnmutedEvent
|
||||
kwargs: dict[str, typing.Any] = {
|
||||
'type': 'bot.muted' if duration > 0 else 'bot.unmuted',
|
||||
'adapter_name': 'aiocqhttp',
|
||||
'group': group,
|
||||
'operator': operator,
|
||||
'timestamp': float(getattr(event, 'time', 0) or 0),
|
||||
'source_platform_object': event,
|
||||
}
|
||||
if duration > 0:
|
||||
kwargs['duration'] = duration
|
||||
return event_cls(**kwargs)
|
||||
if duration > 0:
|
||||
return platform_events.MemberBannedEvent(
|
||||
type='group.member_banned',
|
||||
adapter_name='aiocqhttp',
|
||||
group=group,
|
||||
member=AiocqhttpEventConverter.user(getattr(event, 'user_id', '')),
|
||||
operator=operator,
|
||||
duration=duration,
|
||||
timestamp=float(getattr(event, 'time', 0) or 0),
|
||||
source_platform_object=event,
|
||||
)
|
||||
if notice_type == 'friend_add':
|
||||
return platform_events.FriendAddedEvent(
|
||||
type='friend.added',
|
||||
adapter_name='aiocqhttp',
|
||||
user=AiocqhttpEventConverter.user(getattr(event, 'user_id', '')),
|
||||
timestamp=float(getattr(event, 'time', 0) or 0),
|
||||
source_platform_object=event,
|
||||
)
|
||||
return AiocqhttpEventConverter.platform_specific(event, f'notice.{notice_type}')
|
||||
|
||||
@staticmethod
|
||||
def request_to_eba(event: aiocqhttp.Event) -> platform_events.EBAEvent:
|
||||
request_type = getattr(event, 'request_type', getattr(event, 'detail_type', ''))
|
||||
if request_type == 'friend':
|
||||
return platform_events.FriendRequestReceivedEvent(
|
||||
type='friend.request_received',
|
||||
adapter_name='aiocqhttp',
|
||||
request_id=getattr(event, 'flag', ''),
|
||||
user=AiocqhttpEventConverter.user(getattr(event, 'user_id', '')),
|
||||
message=getattr(event, 'comment', None),
|
||||
timestamp=float(getattr(event, 'time', 0) or 0),
|
||||
source_platform_object=event,
|
||||
)
|
||||
if request_type == 'group' and getattr(event, 'sub_type', '') == 'invite':
|
||||
return platform_events.BotInvitedToGroupEvent(
|
||||
type='bot.invited_to_group',
|
||||
adapter_name='aiocqhttp',
|
||||
group=AiocqhttpEventConverter.group_from_event(event),
|
||||
inviter=AiocqhttpEventConverter.user(getattr(event, 'user_id', '')),
|
||||
request_id=getattr(event, 'flag', ''),
|
||||
timestamp=float(getattr(event, 'time', 0) or 0),
|
||||
source_platform_object=event,
|
||||
)
|
||||
return AiocqhttpEventConverter.platform_specific(event, f'request.{request_type}')
|
||||
|
||||
@staticmethod
|
||||
def user_from_sender(event: aiocqhttp.Event) -> platform_entities.User:
|
||||
sender = getattr(event, 'sender', {}) or {}
|
||||
nickname = sender.get('card') or sender.get('nickname') or ''
|
||||
return platform_entities.User(
|
||||
id=sender.get('user_id', getattr(event, 'user_id', '')),
|
||||
nickname=nickname,
|
||||
remark=sender.get('remark'),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def user(user_id: typing.Union[int, str, None], nickname: str = '') -> platform_entities.User | None:
|
||||
if user_id is None or user_id == '':
|
||||
return None
|
||||
return platform_entities.User(id=user_id, nickname=nickname)
|
||||
|
||||
@staticmethod
|
||||
def group_from_event(event: aiocqhttp.Event) -> platform_entities.UserGroup:
|
||||
return platform_entities.UserGroup(
|
||||
id=getattr(event, 'group_id', ''),
|
||||
name=getattr(event, 'group_name', '') or '',
|
||||
member_count=getattr(event, 'member_count', None),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def platform_specific(event: aiocqhttp.Event, action: str) -> platform_events.PlatformSpecificEvent:
|
||||
return platform_events.PlatformSpecificEvent(
|
||||
type='platform.specific',
|
||||
adapter_name='aiocqhttp',
|
||||
action=action,
|
||||
data={key: value for key, value in dict(event).items() if key not in {'message'}},
|
||||
timestamp=float(getattr(event, 'time', 0) or 0),
|
||||
source_platform_object=event,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _is_bot_user(user_id: typing.Any, bot_user_id: typing.Any, event: aiocqhttp.Event) -> bool:
|
||||
candidate = bot_user_id or getattr(event, 'self_id', None)
|
||||
return candidate is not None and user_id is not None and str(user_id) == str(candidate)
|
||||
131
src/langbot/pkg/platform/adapters/aiocqhttp/manifest.yaml
Normal file
131
src/langbot/pkg/platform/adapters/aiocqhttp/manifest.yaml
Normal file
@@ -0,0 +1,131 @@
|
||||
apiVersion: v1
|
||||
kind: MessagePlatformAdapter
|
||||
|
||||
metadata:
|
||||
name: aiocqhttp-eba
|
||||
label:
|
||||
en_US: OneBot v11 (EBA)
|
||||
zh_Hans: OneBot v11 (EBA)
|
||||
zh_Hant: OneBot v11 (EBA)
|
||||
description:
|
||||
en_US: OneBot v11 adapter for QQ-compatible protocol endpoints (EBA architecture)
|
||||
zh_Hans: OneBot v11 适配器,用于接入 QQ 兼容协议端(EBA 架构版本)
|
||||
zh_Hant: OneBot v11 適配器,用於接入 QQ 相容協定端(EBA 架構版本)
|
||||
icon: onebot.svg
|
||||
|
||||
spec:
|
||||
categories:
|
||||
- protocol
|
||||
help_links:
|
||||
zh: https://link.langbot.app/zh/platforms/aiocqhttp
|
||||
en: https://link.langbot.app/en/platforms/aiocqhttp
|
||||
ja: https://link.langbot.app/ja/platforms/aiocqhttp
|
||||
config:
|
||||
- name: host
|
||||
label:
|
||||
en_US: Host
|
||||
zh_Hans: 主机
|
||||
zh_Hant: 主機
|
||||
description:
|
||||
en_US: The host that OneBot v11 listens on for reverse WebSocket connections. Unless you know what you're doing, use 0.0.0.0
|
||||
zh_Hans: OneBot v11 反向 WebSocket 监听主机,除非你知道自己在做什么,否则请写 0.0.0.0
|
||||
zh_Hant: OneBot v11 反向 WebSocket 監聽主機,除非你知道自己在做什麼,否則請填 0.0.0.0
|
||||
type: string
|
||||
required: true
|
||||
default: 0.0.0.0
|
||||
- name: port
|
||||
label:
|
||||
en_US: Port
|
||||
zh_Hans: 端口
|
||||
zh_Hant: 連接埠
|
||||
description:
|
||||
en_US: Reverse WebSocket listen port
|
||||
zh_Hans: 反向 WebSocket 监听端口
|
||||
zh_Hant: 反向 WebSocket 監聽連接埠
|
||||
type: integer
|
||||
required: true
|
||||
default: 2280
|
||||
- name: access-token
|
||||
label:
|
||||
en_US: Access Token
|
||||
zh_Hans: 访问令牌
|
||||
zh_Hant: 存取令牌
|
||||
description:
|
||||
en_US: Custom connection token for the protocol endpoint. Leave empty if the endpoint has no token configured
|
||||
zh_Hans: 自定义的协议端连接令牌;若协议端未设置,则不填
|
||||
zh_Hant: 自訂的協定端連線令牌;若協定端未設定,則不填
|
||||
type: string
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
supported_events:
|
||||
- message.received
|
||||
- message.deleted
|
||||
- group.member_joined
|
||||
- group.member_left
|
||||
- group.member_banned
|
||||
- friend.request_received
|
||||
- friend.added
|
||||
- bot.invited_to_group
|
||||
- bot.removed_from_group
|
||||
- bot.muted
|
||||
- bot.unmuted
|
||||
- platform.specific
|
||||
|
||||
supported_apis:
|
||||
required:
|
||||
- send_message
|
||||
- reply_message
|
||||
optional:
|
||||
- delete_message
|
||||
- forward_message
|
||||
- get_message
|
||||
- get_group_info
|
||||
- get_group_list
|
||||
- get_group_member_list
|
||||
- get_group_member_info
|
||||
- set_group_name
|
||||
- get_user_info
|
||||
- get_friend_list
|
||||
- approve_friend_request
|
||||
- approve_group_invite
|
||||
- mute_member
|
||||
- unmute_member
|
||||
- kick_member
|
||||
- leave_group
|
||||
- call_platform_api
|
||||
|
||||
platform_specific_apis:
|
||||
- action: get_login_info
|
||||
description: { en_US: "Get current bot account information", zh_Hans: "获取当前机器人账号信息" }
|
||||
- action: get_status
|
||||
description: { en_US: "Get endpoint status", zh_Hans: "获取协议端状态" }
|
||||
- action: get_version_info
|
||||
description: { en_US: "Get endpoint version information", zh_Hans: "获取协议端版本信息" }
|
||||
- action: get_group_honor_info
|
||||
description: { en_US: "Get group honor information", zh_Hans: "获取群荣誉信息" }
|
||||
- action: set_group_card
|
||||
description: { en_US: "Set a member group card", zh_Hans: "设置群名片" }
|
||||
- action: set_group_special_title
|
||||
description: { en_US: "Set a member special title", zh_Hans: "设置群专属头衔" }
|
||||
- action: set_group_admin
|
||||
description: { en_US: "Set group administrator status", zh_Hans: "设置群管理员" }
|
||||
- action: set_group_whole_ban
|
||||
description: { en_US: "Enable or disable whole-group mute", zh_Hans: "设置全员禁言" }
|
||||
- action: send_group_forward_msg
|
||||
description: { en_US: "Send a merged forward message", zh_Hans: "发送合并转发消息" }
|
||||
- action: get_forward_msg
|
||||
description: { en_US: "Get merged forward message content", zh_Hans: "获取合并转发消息内容" }
|
||||
- action: get_record
|
||||
description: { en_US: "Get voice file", zh_Hans: "获取语音文件" }
|
||||
- action: get_image
|
||||
description: { en_US: "Get image file", zh_Hans: "获取图片文件" }
|
||||
- action: can_send_image
|
||||
description: { en_US: "Check whether images can be sent", zh_Hans: "检查是否可以发送图片" }
|
||||
- action: can_send_record
|
||||
description: { en_US: "Check whether voice messages can be sent", zh_Hans: "检查是否可以发送语音" }
|
||||
|
||||
execution:
|
||||
python:
|
||||
path: ./adapter.py
|
||||
attr: AiocqhttpAdapter
|
||||
251
src/langbot/pkg/platform/adapters/aiocqhttp/message_converter.py
Normal file
251
src/langbot/pkg/platform/adapters/aiocqhttp/message_converter.py
Normal file
@@ -0,0 +1,251 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import typing
|
||||
|
||||
import aiocqhttp
|
||||
|
||||
import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
|
||||
from langbot_plugin.api.entities.builtin.platform import message as platform_message
|
||||
|
||||
|
||||
FACE_NAMES = {
|
||||
'14': '微笑',
|
||||
'21': '可爱',
|
||||
'23': '傲慢',
|
||||
'24': '饥饿',
|
||||
'25': '困',
|
||||
'26': '惊恐',
|
||||
'27': '流汗',
|
||||
'28': '憨笑',
|
||||
'29': '悠闲',
|
||||
'30': '奋斗',
|
||||
'32': '疑问',
|
||||
'33': '嘘',
|
||||
'34': '晕',
|
||||
'38': '敲打',
|
||||
'39': '再见',
|
||||
'42': '爱情',
|
||||
'43': '跳跳',
|
||||
'49': '拥抱',
|
||||
'53': '蛋糕',
|
||||
'63': '玫瑰',
|
||||
'66': '爱心',
|
||||
'74': '太阳',
|
||||
'75': '月亮',
|
||||
'76': '赞',
|
||||
'78': '握手',
|
||||
'79': '胜利',
|
||||
'85': '飞吻',
|
||||
'89': '西瓜',
|
||||
'96': '冷汗',
|
||||
'97': '擦汗',
|
||||
'98': '抠鼻',
|
||||
'99': '鼓掌',
|
||||
'100': '糗大了',
|
||||
'101': '坏笑',
|
||||
'102': '左哼哼',
|
||||
'103': '右哼哼',
|
||||
'104': '哈欠',
|
||||
'106': '委屈',
|
||||
'111': '可怜',
|
||||
'120': '拳头',
|
||||
'122': '爱你',
|
||||
'123': 'NO',
|
||||
'124': 'OK',
|
||||
'129': '挥手',
|
||||
'144': '喝彩',
|
||||
'147': '棒棒糖',
|
||||
'171': '茶',
|
||||
'173': '泪奔',
|
||||
'174': '无奈',
|
||||
'175': '卖萌',
|
||||
'179': 'doge',
|
||||
'180': '惊喜',
|
||||
'182': '笑哭',
|
||||
'201': '点赞',
|
||||
'203': '托脸',
|
||||
'212': '托腮',
|
||||
'264': '捂脸',
|
||||
'271': '吃瓜',
|
||||
'285': '摸鱼',
|
||||
}
|
||||
|
||||
|
||||
class AiocqhttpMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
|
||||
@staticmethod
|
||||
async def yiri2target(
|
||||
message_chain: platform_message.MessageChain,
|
||||
) -> tuple[aiocqhttp.Message, typing.Union[int, str, None], datetime.datetime | None]:
|
||||
target = aiocqhttp.Message()
|
||||
source_id: typing.Union[int, str, None] = None
|
||||
source_time: datetime.datetime | None = None
|
||||
|
||||
for component in message_chain:
|
||||
if isinstance(component, platform_message.Source):
|
||||
source_id = component.id
|
||||
source_time = component.time
|
||||
elif isinstance(component, platform_message.Plain):
|
||||
target.append(aiocqhttp.MessageSegment.text(component.text))
|
||||
elif isinstance(component, platform_message.At):
|
||||
target.append(aiocqhttp.MessageSegment.at(component.target))
|
||||
elif isinstance(component, platform_message.AtAll):
|
||||
target.append(aiocqhttp.MessageSegment.at('all'))
|
||||
elif isinstance(component, platform_message.Image):
|
||||
file_arg = AiocqhttpMessageConverter._file_arg(component)
|
||||
if file_arg:
|
||||
target.append(aiocqhttp.MessageSegment.image(file_arg))
|
||||
elif isinstance(component, platform_message.Voice):
|
||||
file_arg = AiocqhttpMessageConverter._file_arg(component)
|
||||
if file_arg:
|
||||
target.append(aiocqhttp.MessageSegment.record(file_arg))
|
||||
elif isinstance(component, platform_message.File):
|
||||
file_arg = component.url or component.path or component.base64 or component.id
|
||||
target.append({'type': 'file', 'data': {'file': file_arg, 'name': component.name or 'file'}})
|
||||
elif isinstance(component, platform_message.Face):
|
||||
if component.face_type == 'rps':
|
||||
target.append(aiocqhttp.MessageSegment.rps())
|
||||
elif component.face_type == 'dice':
|
||||
target.append(aiocqhttp.MessageSegment.dice())
|
||||
else:
|
||||
target.append(aiocqhttp.MessageSegment.face(component.face_id))
|
||||
elif isinstance(component, platform_message.Forward):
|
||||
for node in component.node_list:
|
||||
if node.message_chain:
|
||||
node_message, _, _ = await AiocqhttpMessageConverter.yiri2target(node.message_chain)
|
||||
target.extend(node_message)
|
||||
elif isinstance(component, platform_message.Quote) and component.id is not None:
|
||||
target.append(aiocqhttp.MessageSegment.reply(component.id))
|
||||
else:
|
||||
target.append(aiocqhttp.MessageSegment.text(str(component)))
|
||||
|
||||
return target, source_id, source_time
|
||||
|
||||
@staticmethod
|
||||
async def target2yiri(
|
||||
message: typing.Any,
|
||||
message_id: typing.Union[int, str] = -1,
|
||||
timestamp: float | None = None,
|
||||
bot: aiocqhttp.CQHttp | None = None,
|
||||
) -> platform_message.MessageChain:
|
||||
target = aiocqhttp.Message(message)
|
||||
message_time = datetime.datetime.fromtimestamp(timestamp) if timestamp else datetime.datetime.now()
|
||||
components: list[platform_message.MessageComponent] = [
|
||||
platform_message.Source(id=message_id, time=message_time),
|
||||
]
|
||||
|
||||
for segment in target:
|
||||
if segment.type == 'text':
|
||||
components.append(platform_message.Plain(text=segment.data.get('text', '')))
|
||||
elif segment.type == 'at':
|
||||
qq = str(segment.data.get('qq', ''))
|
||||
components.append(platform_message.AtAll() if qq == 'all' else platform_message.At(target=qq))
|
||||
elif segment.type == 'image':
|
||||
if segment.data.get('emoji_package_id'):
|
||||
components.append(
|
||||
platform_message.Face(
|
||||
face_id=int(segment.data.get('emoji_package_id') or 0),
|
||||
face_name=segment.data.get('summary', ''),
|
||||
)
|
||||
)
|
||||
else:
|
||||
components.append(
|
||||
platform_message.Image(
|
||||
image_id=str(segment.data.get('file', '')),
|
||||
url=segment.data.get('url') or segment.data.get('file') or '',
|
||||
)
|
||||
)
|
||||
elif segment.type == 'record':
|
||||
components.append(
|
||||
platform_message.Voice(
|
||||
voice_id=str(segment.data.get('file', '')),
|
||||
url=segment.data.get('url') or segment.data.get('file') or '',
|
||||
)
|
||||
)
|
||||
elif segment.type == 'file':
|
||||
components.append(
|
||||
platform_message.File(
|
||||
id=str(segment.data.get('file_id') or segment.data.get('file') or ''),
|
||||
name=segment.data.get('name') or segment.data.get('file') or '',
|
||||
size=int(segment.data.get('size') or segment.data.get('file_size') or 0),
|
||||
url=segment.data.get('url') or segment.data.get('file_url') or '',
|
||||
)
|
||||
)
|
||||
elif segment.type == 'reply':
|
||||
quote = await AiocqhttpMessageConverter._quote_from_reply_segment(segment, bot)
|
||||
components.append(quote)
|
||||
elif segment.type == 'face':
|
||||
face_id = str(segment.data.get('id', 0))
|
||||
face_name = ''
|
||||
raw = segment.data.get('raw')
|
||||
if isinstance(raw, dict):
|
||||
face_name = str(raw.get('faceText') or '')
|
||||
components.append(
|
||||
platform_message.Face(
|
||||
face_id=int(face_id or 0),
|
||||
face_name=face_name.replace('/', '') or FACE_NAMES.get(face_id, ''),
|
||||
)
|
||||
)
|
||||
elif segment.type == 'rps':
|
||||
components.append(
|
||||
platform_message.Face(
|
||||
face_type='rps',
|
||||
face_id=int(segment.data.get('result') or 0),
|
||||
face_name='猜拳',
|
||||
)
|
||||
)
|
||||
elif segment.type == 'dice':
|
||||
components.append(
|
||||
platform_message.Face(
|
||||
face_type='dice',
|
||||
face_id=int(segment.data.get('result') or 0),
|
||||
face_name='骰子',
|
||||
)
|
||||
)
|
||||
else:
|
||||
components.append(platform_message.Unknown(text=f'{segment.type}:{segment.data}'))
|
||||
|
||||
return platform_message.MessageChain(components)
|
||||
|
||||
@staticmethod
|
||||
def _file_arg(component: platform_message.Image | platform_message.Voice) -> str:
|
||||
if component.base64:
|
||||
_, _, payload = component.base64.partition(',')
|
||||
return f'base64://{payload or component.base64}'
|
||||
if component.url:
|
||||
return component.url
|
||||
if component.path:
|
||||
return str(component.path)
|
||||
return ''
|
||||
|
||||
@staticmethod
|
||||
async def _quote_from_reply_segment(
|
||||
segment: aiocqhttp.MessageSegment,
|
||||
bot: aiocqhttp.CQHttp | None,
|
||||
) -> platform_message.Quote:
|
||||
reply_id = segment.data.get('id')
|
||||
origin = platform_message.MessageChain([])
|
||||
sender_id = None
|
||||
group_id = None
|
||||
target_id = None
|
||||
if bot is not None and reply_id is not None:
|
||||
try:
|
||||
message_data = await bot.get_msg(message_id=int(reply_id))
|
||||
sender_id = message_data.get('sender', {}).get('user_id') or message_data.get('user_id')
|
||||
group_id = message_data.get('group_id')
|
||||
target_id = group_id or sender_id
|
||||
origin = await AiocqhttpMessageConverter.target2yiri(
|
||||
message_data.get('message', []),
|
||||
message_data.get('message_id', reply_id),
|
||||
message_data.get('time'),
|
||||
bot=None,
|
||||
)
|
||||
except Exception:
|
||||
origin = platform_message.MessageChain([])
|
||||
return platform_message.Quote(
|
||||
id=reply_id,
|
||||
group_id=group_id,
|
||||
sender_id=sender_id,
|
||||
target_id=target_id,
|
||||
origin=origin,
|
||||
)
|
||||
7
src/langbot/pkg/platform/adapters/aiocqhttp/onebot.svg
Normal file
7
src/langbot/pkg/platform/adapters/aiocqhttp/onebot.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg width="96" height="96" viewBox="0 0 96 96" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="96" height="96" rx="20" fill="#16A34A"/>
|
||||
<path d="M24 33C24 25.268 30.268 19 38 19H58C65.732 19 72 25.268 72 33V51C72 58.732 65.732 65 58 65H41.5L29 77V64.059C26.024 61.514 24 57.729 24 51V33Z" fill="white"/>
|
||||
<circle cx="39" cy="42" r="5" fill="#16A34A"/>
|
||||
<circle cx="57" cy="42" r="5" fill="#16A34A"/>
|
||||
<path d="M39 53C44.5 57 51.5 57 57 53" stroke="#16A34A" stroke-width="5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 527 B |
84
src/langbot/pkg/platform/adapters/aiocqhttp/platform_api.py
Normal file
84
src/langbot/pkg/platform/adapters/aiocqhttp/platform_api.py
Normal file
@@ -0,0 +1,84 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
|
||||
import aiocqhttp
|
||||
|
||||
|
||||
async def _call(bot: aiocqhttp.CQHttp, action: str, params: dict[str, typing.Any]) -> dict:
|
||||
result = await bot.call_action(action, **params)
|
||||
return result or {}
|
||||
|
||||
|
||||
async def get_login_info(bot: aiocqhttp.CQHttp, params: dict) -> dict:
|
||||
return await _call(bot, 'get_login_info', params)
|
||||
|
||||
|
||||
async def get_status(bot: aiocqhttp.CQHttp, params: dict) -> dict:
|
||||
return await _call(bot, 'get_status', params)
|
||||
|
||||
|
||||
async def get_version_info(bot: aiocqhttp.CQHttp, params: dict) -> dict:
|
||||
return await _call(bot, 'get_version_info', params)
|
||||
|
||||
|
||||
async def get_group_honor_info(bot: aiocqhttp.CQHttp, params: dict) -> dict:
|
||||
return await _call(bot, 'get_group_honor_info', params)
|
||||
|
||||
|
||||
async def set_group_card(bot: aiocqhttp.CQHttp, params: dict) -> dict:
|
||||
return await _call(bot, 'set_group_card', params)
|
||||
|
||||
|
||||
async def set_group_special_title(bot: aiocqhttp.CQHttp, params: dict) -> dict:
|
||||
return await _call(bot, 'set_group_special_title', params)
|
||||
|
||||
|
||||
async def set_group_admin(bot: aiocqhttp.CQHttp, params: dict) -> dict:
|
||||
return await _call(bot, 'set_group_admin', params)
|
||||
|
||||
|
||||
async def set_group_whole_ban(bot: aiocqhttp.CQHttp, params: dict) -> dict:
|
||||
return await _call(bot, 'set_group_whole_ban', params)
|
||||
|
||||
|
||||
async def send_group_forward_msg(bot: aiocqhttp.CQHttp, params: dict) -> dict:
|
||||
return await _call(bot, 'send_group_forward_msg', params)
|
||||
|
||||
|
||||
async def get_forward_msg(bot: aiocqhttp.CQHttp, params: dict) -> dict:
|
||||
return await _call(bot, 'get_forward_msg', params)
|
||||
|
||||
|
||||
async def get_record(bot: aiocqhttp.CQHttp, params: dict) -> dict:
|
||||
return await _call(bot, 'get_record', params)
|
||||
|
||||
|
||||
async def get_image(bot: aiocqhttp.CQHttp, params: dict) -> dict:
|
||||
return await _call(bot, 'get_image', params)
|
||||
|
||||
|
||||
async def can_send_image(bot: aiocqhttp.CQHttp, params: dict) -> dict:
|
||||
return await _call(bot, 'can_send_image', params)
|
||||
|
||||
|
||||
async def can_send_record(bot: aiocqhttp.CQHttp, params: dict) -> dict:
|
||||
return await _call(bot, 'can_send_record', params)
|
||||
|
||||
|
||||
PLATFORM_API_MAP = {
|
||||
'get_login_info': get_login_info,
|
||||
'get_status': get_status,
|
||||
'get_version_info': get_version_info,
|
||||
'get_group_honor_info': get_group_honor_info,
|
||||
'set_group_card': set_group_card,
|
||||
'set_group_special_title': set_group_special_title,
|
||||
'set_group_admin': set_group_admin,
|
||||
'set_group_whole_ban': set_group_whole_ban,
|
||||
'send_group_forward_msg': send_group_forward_msg,
|
||||
'get_forward_msg': get_forward_msg,
|
||||
'get_record': get_record,
|
||||
'get_image': get_image,
|
||||
'can_send_image': can_send_image,
|
||||
'can_send_record': can_send_record,
|
||||
}
|
||||
9
src/langbot/pkg/platform/adapters/aiocqhttp/types.py
Normal file
9
src/langbot/pkg/platform/adapters/aiocqhttp/types.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
|
||||
import aiocqhttp
|
||||
|
||||
|
||||
TargetMessage = typing.Union[str, list, dict, aiocqhttp.Message]
|
||||
OneBotResponse = dict[str, typing.Any] | None
|
||||
Reference in New Issue
Block a user