# -*- coding: utf-8 -*- """ Discord 适配器 (Discord Adapter) 此模块负责与 Discord API 建立连接,接收 Discord 消息, 并将其转换为通用数据模型 (Universal Data Models), 同时提供将通用消息段发送回 Discord 的能力。 """ import asyncio import json import os import io import requests from typing import Union, List, Optional try: import discord DISCORD_AVAILABLE = True except ImportError: DISCORD_AVAILABLE = False from core.utils.logger import ModuleLogger from .router import DiscordToOneBotConverter from core.managers.redis_manager import redis_manager class DiscordAdapter(discord.Client if DISCORD_AVAILABLE else object): """ Discord 客户端适配器。 继承自 discord.Client,负责处理 Discord 的底层事件。 """ def __init__(self, token: str): if not DISCORD_AVAILABLE: raise ImportError("discord.py 未安装,请运行 `pip install discord.py`") # 必须声明 Intents,否则无法读取消息内容 intents = discord.Intents.default() intents.message_content = True super().__init__(intents=intents) self.token = token self.logger = ModuleLogger("DiscordAdapter") self.send_channel = None async def on_ready(self): """当 Bot 成功连接到 Discord 时触发""" self.logger.success(f"Discord Bot 已登录: {self.user} (ID: {self.user.id})") # 启动 Redis 订阅以处理跨平台消息 asyncio.create_task(self.start_redis_subscription()) async def on_message(self, message: 'discord.Message'): """当收到 Discord 消息时触发""" # 忽略机器人自己的消息 if message.author.bot: return self.logger.info(f"[Discord 消息] {message.author}: {message.content}") # 1. 将 discord.Message 伪装成 OneBot 事件模型 # 2. 触发业务逻辑 # 将伪装后的事件丢给现有的命令管理器 (matcher) from core.managers.command_manager import matcher # matcher.handle_event 需要 bot 实例和 event 实例 # 我们在 create_mock_event 中已经注入了一个假的 bot 对象 try: mock_event = DiscordToOneBotConverter.create_mock_event(message, self) await matcher.handle_event(mock_event.bot, mock_event) except Exception as e: self.logger.error(f"处理 Discord 消息时发生异常: {e}") async def start_redis_subscription(self): """启动 Redis 订阅以处理跨平台消息发送""" if redis_manager.redis is None: self.logger.warning("[DiscordAdapter] Redis 未初始化,跳过订阅") return try: channel_name = "neobot_discord_send" pubsub = redis_manager.redis.pubsub() await pubsub.subscribe(channel_name) self.logger.success(f"[DiscordAdapter] 已订阅 Redis 频道: {channel_name}") async for message in pubsub.listen(): if message["type"] == "message": try: data = json.loads(message["data"]) if data.get("type") == "send_message": await self.handle_send_message(data) except json.JSONDecodeError as e: self.logger.error(f"[DiscordAdapter] 解析 Redis 消息失败: {e}") except Exception as e: self.logger.error(f"[DiscordAdapter] 处理 Redis 消息失败: {e}") except Exception as e: self.logger.error(f"[DiscordAdapter] Redis 订阅异常: {e}") async def handle_send_message(self, data: dict): """处理来自 Redis 的消息发送请求""" try: channel_id = data.get("channel_id") content = data.get("content", "") attachments = data.get("attachments", []) if channel_id is None: self.logger.error("[DiscordAdapter] 缺少 channel_id") return channel = self.get_channel(channel_id) if channel is None: self.logger.error(f"[DiscordAdapter] 未找到频道: {channel_id}") return self.logger.info(f"[DiscordAdapter] 正在发送消息到频道 {channel_id}") # 发送内容和附件(合并为一条消息) if content or attachments: await channel.send(content=content, files=[discord.File(fp=io.BytesIO(requests.get(attachment_url).content), filename=os.path.basename(attachment_url)) for attachment_url in attachments if attachment_url.startswith('http')] if attachments else None) self.logger.success(f"[DiscordAdapter] 消息已发送到频道 {channel_id}") except Exception as e: self.logger.error(f"[DiscordAdapter] 发送消息失败: {e}") async def start_client(self): """启动 Discord 客户端(非阻塞方式)""" if not DISCORD_AVAILABLE: self.logger.error("无法启动 Discord 客户端:discord.py 未安装") return try: self.logger.info("正在连接 Discord...") await self.start(self.token) except Exception as e: self.logger.error(f"Discord 连接失败: {e}")