feat: add DiscordAdapter skeleton and register in factory

This commit is contained in:
SXP-Simon
2026-02-08 16:23:57 +08:00
parent e0d81ec5c5
commit 8e09a9e1cf
3 changed files with 323 additions and 2 deletions
@@ -1,4 +1,5 @@
# Platform Adapters
# 平台适配器
from .onebot_adapter import OneBotAdapter
from .discord_adapter import DiscordAdapter
__all__ = ["OneBotAdapter"]
__all__ = ["OneBotAdapter", "DiscordAdapter"]
@@ -0,0 +1,314 @@
"""
Discord 平台适配器
为 Discord 平台提供消息获取、发送和群组管理功能。
这是一个骨架实现,展示如何为新平台创建适配器。
注意:Discord 的消息获取需要使用 Discord API
具体实现取决于 AstrBot 的 Discord 集成方式。
"""
from datetime import datetime, timedelta
from typing import List, Optional, Any, Dict
from ....domain.value_objects.unified_message import (
UnifiedMessage,
MessageContent,
MessageContentType,
)
from ....domain.value_objects.platform_capabilities import (
PlatformCapabilities,
DISCORD_CAPABILITIES,
)
from ....domain.value_objects.unified_group import UnifiedGroup, UnifiedMember
from ..base import PlatformAdapter
class DiscordAdapter(PlatformAdapter):
"""
Discord 平台适配器
实现 PlatformAdapter 接口,提供 Discord 平台的消息操作。
使用方式:
1. 通过 PlatformAdapterFactory.create("discord", bot_instance, config) 创建
2. 或直接实例化:DiscordAdapter(bot_instance, config)
配置参数:
- bot_user_id: 机器人的 Discord 用户 ID(用于过滤自己的消息)
"""
def __init__(self, bot_instance: Any, config: dict = None):
super().__init__(bot_instance, config)
# 机器人自己的用户 ID,用于过滤消息
self.bot_user_id = str(config.get("bot_user_id", "")) if config else ""
def _init_capabilities(self) -> PlatformCapabilities:
"""初始化 Discord 平台能力"""
return DISCORD_CAPABILITIES
# ==================== IMessageRepository ====================
async def fetch_messages(
self,
group_id: str,
days: int = 1,
max_count: int = 100,
before_id: Optional[str] = None,
) -> List[UnifiedMessage]:
"""
获取 Discord 频道消息历史
参数:
group_id: Discord 频道 ID
days: 获取多少天内的消息
max_count: 最大消息数量
before_id: 从此消息 ID 之前开始获取(用于分页)
返回:
UnifiedMessage 列表
"""
# TODO: 实现 Discord 消息获取逻辑
# 需要根据 AstrBot 的 Discord 集成方式来实现
# 通常需要调用 Discord API 的 channel.history() 方法
# 示例实现框架:
# if not hasattr(self.bot, "get_channel"):
# return []
#
# try:
# channel = self.bot.get_channel(int(group_id))
# if not channel:
# return []
#
# end_time = datetime.now()
# start_time = end_time - timedelta(days=days)
#
# messages = []
# async for msg in channel.history(limit=max_count, after=start_time):
# if str(msg.author.id) == self.bot_user_id:
# continue
# unified = self._convert_message(msg, group_id)
# if unified:
# messages.append(unified)
#
# return messages
# except Exception:
# return []
return []
def _convert_message(self, raw_msg: Any, group_id: str) -> Optional[UnifiedMessage]:
"""
将 Discord 消息转换为统一格式
参数:
raw_msg: Discord 原始消息对象
group_id: 频道 ID
返回:
UnifiedMessage 或 None
"""
# TODO: 实现 Discord 消息转换逻辑
# 示例:
# try:
# contents = []
#
# # 文本内容
# if raw_msg.content:
# contents.append(MessageContent(
# type=MessageContentType.TEXT,
# text=raw_msg.content
# ))
#
# # 图片附件
# for attachment in raw_msg.attachments:
# if attachment.content_type and attachment.content_type.startswith("image/"):
# contents.append(MessageContent(
# type=MessageContentType.IMAGE,
# url=attachment.url
# ))
#
# return UnifiedMessage(
# message_id=str(raw_msg.id),
# sender_id=str(raw_msg.author.id),
# sender_name=raw_msg.author.display_name,
# sender_card=raw_msg.author.nick,
# group_id=group_id,
# text_content=raw_msg.content,
# contents=tuple(contents),
# timestamp=int(raw_msg.created_at.timestamp()),
# platform="discord",
# reply_to_id=str(raw_msg.reference.message_id) if raw_msg.reference else None,
# )
# except Exception:
# return None
return None
def convert_to_raw_format(self, messages: List[UnifiedMessage]) -> List[dict]:
"""
将统一消息格式转换为 Discord 原生格式
用于与现有分析器的向后兼容。
Discord 格式与 OneBot 不同,这里返回通用字典格式。
"""
raw_messages = []
for msg in messages:
# Discord 风格的消息格式
raw_msg = {
"id": msg.message_id,
"channel_id": msg.group_id,
"author": {
"id": msg.sender_id,
"username": msg.sender_name,
"nick": msg.sender_card,
},
"content": msg.text_content,
"timestamp": msg.timestamp,
"attachments": [],
"embeds": [],
}
# 处理附件
for content in msg.contents:
if content.type == MessageContentType.IMAGE:
raw_msg["attachments"].append({
"url": content.url,
"content_type": "image/png",
})
raw_messages.append(raw_msg)
return raw_messages
# ==================== IMessageSender ====================
async def send_text(
self,
group_id: str,
text: str,
reply_to: Optional[str] = None,
) -> bool:
"""发送文本消息到 Discord 频道"""
# TODO: 实现 Discord 消息发送
# 示例:
# try:
# channel = self.bot.get_channel(int(group_id))
# if not channel:
# return False
#
# if reply_to:
# ref_msg = await channel.fetch_message(int(reply_to))
# await channel.send(text, reference=ref_msg)
# else:
# await channel.send(text)
# return True
# except Exception:
# return False
return False
async def send_image(
self,
group_id: str,
image_path: str,
caption: str = "",
) -> bool:
"""发送图片到 Discord 频道"""
# TODO: 实现 Discord 图片发送
return False
async def send_file(
self,
group_id: str,
file_path: str,
filename: Optional[str] = None,
) -> bool:
"""发送文件到 Discord 频道"""
# TODO: 实现 Discord 文件发送
return False
# ==================== IGroupInfoRepository ====================
async def get_group_info(self, group_id: str) -> Optional[UnifiedGroup]:
"""获取 Discord 频道信息"""
# TODO: 实现 Discord 频道信息获取
# 示例:
# try:
# channel = self.bot.get_channel(int(group_id))
# if not channel:
# return None
#
# return UnifiedGroup(
# group_id=str(channel.id),
# group_name=channel.name,
# member_count=channel.guild.member_count if hasattr(channel, "guild") else 0,
# owner_id=str(channel.guild.owner_id) if hasattr(channel, "guild") else None,
# platform="discord",
# )
# except Exception:
# return None
return None
async def get_group_list(self) -> List[str]:
"""获取机器人所在的所有频道 ID"""
# TODO: 实现 Discord 频道列表获取
return []
async def get_member_list(self, group_id: str) -> List[UnifiedMember]:
"""获取 Discord 服务器成员列表"""
# TODO: 实现 Discord 成员列表获取
return []
async def get_member_info(
self,
group_id: str,
user_id: str,
) -> Optional[UnifiedMember]:
"""获取特定成员信息"""
# TODO: 实现 Discord 成员信息获取
return None
# ==================== IAvatarRepository ====================
async def get_user_avatar_url(
self,
user_id: str,
size: int = 100,
) -> Optional[str]:
"""获取 Discord 用户头像 URL"""
# Discord 头像 URL 格式
# https://cdn.discordapp.com/avatars/{user_id}/{avatar_hash}.png?size={size}
# 需要知道用户的 avatar_hash,这里返回默认头像
return f"https://cdn.discordapp.com/embed/avatars/{int(user_id) % 5}.png"
async def get_user_avatar_data(
self,
user_id: str,
size: int = 100,
) -> Optional[str]:
"""获取 Discord 用户头像 Base64 数据"""
# TODO: 实现头像数据获取
return None
async def get_group_avatar_url(
self,
group_id: str,
size: int = 100,
) -> Optional[str]:
"""获取 Discord 服务器图标 URL"""
# TODO: 实现服务器图标获取
return None
async def batch_get_avatar_urls(
self,
user_ids: List[str],
size: int = 100,
) -> Dict[str, Optional[str]]:
"""批量获取 Discord 用户头像 URL"""
return {
user_id: await self.get_user_avatar_url(user_id, size)
for user_id in user_ids
}
+6
View File
@@ -69,6 +69,12 @@ def _register_adapters():
PlatformAdapterFactory.register("onebot", OneBotAdapter)
except ImportError:
pass
try:
from .adapters.discord_adapter import DiscordAdapter
PlatformAdapterFactory.register("discord", DiscordAdapter)
except ImportError:
pass
_register_adapters()