Compare commits

...

2 Commits

Author SHA1 Message Date
Soulter
d612c9eed4 fix: english logging 2026-04-03 14:01:11 +08:00
Soulter
12039fe7d0 fix: resolve Discord/Misskey hot reload issue by fixing client_self_id misuse
fixes: #7187
closes: #7188
2026-04-03 13:41:56 +08:00
7 changed files with 99 additions and 87 deletions

View File

@@ -35,11 +35,11 @@ class DiscordBotClient(discord.Bot):
async def on_ready(self) -> None:
"""当机器人成功连接并准备就绪时触发"""
if self.user is None:
logger.error("[Discord] 客户端未正确加载用户信息 (self.user is None)")
logger.error("[Discord] Bot user not loaded correctly (self.user is None)")
return
logger.info(f"[Discord] 已作为 {self.user} (ID: {self.user.id}) 登录")
logger.info("[Discord] 客户端已准备就绪。")
logger.info(f"[Discord] Logged in as {self.user} (ID: {self.user.id})")
logger.info("[Discord] Client is ready.")
if self.on_ready_once_callback and not self._ready_once_fired:
self._ready_once_fired = True
@@ -47,7 +47,7 @@ class DiscordBotClient(discord.Bot):
await self.on_ready_once_callback()
except Exception as e:
logger.error(
f"[Discord] on_ready_once_callback 执行失败: {e}",
f"[Discord] Failed to execute on_ready_once_callback: {e}",
exc_info=True,
)
@@ -99,7 +99,7 @@ class DiscordBotClient(discord.Bot):
return
logger.debug(
f"[Discord] 收到原始消息 from {message.author.name}: {message.content}",
f"[Discord] Received raw message from {message.author.name}: {message.content}",
)
if self.on_message_received:

View File

@@ -46,7 +46,7 @@ class DiscordPlatformAdapter(Platform):
) -> None:
super().__init__(platform_config, event_queue)
self.settings = platform_settings
self.client_self_id: str | None = None
self.bot_self_id: str | None = None
self.registered_handlers = []
# 指令注册相关
self.enable_command_register = self.config.get("discord_command_register", True)
@@ -64,7 +64,7 @@ class DiscordPlatformAdapter(Platform):
"""通过会话发送消息"""
if self.client.user is None:
logger.error(
"[Discord] 客户端未就绪 (self.client.user is None),无法发送消息"
"[Discord] Client is not ready (self.client.user is None); message send skipped"
)
return
@@ -92,10 +92,10 @@ class DiscordPlatformAdapter(Platform):
message_obj.message_str = message_chain.get_plain_text()
message_obj.sender = MessageMember(
user_id=str(self.client_self_id),
user_id=str(self.bot_self_id),
nickname=self.client.user.display_name,
)
message_obj.self_id = cast(str, self.client_self_id)
message_obj.self_id = cast(str, self.bot_self_id)
message_obj.session_id = session.session_id
message_obj.message = message_chain.chain
@@ -115,7 +115,7 @@ class DiscordPlatformAdapter(Platform):
"""返回平台元数据"""
return PlatformMetadata(
"discord",
"Discord 适配器",
"Discord Adapter",
id=cast(str, self.config.get("id")),
default_config_tmpl=self.config,
support_streaming_message=False,
@@ -127,16 +127,18 @@ class DiscordPlatformAdapter(Platform):
# 初始化回调函数
async def on_received(message_data) -> None:
logger.debug(f"[Discord] 收到消息: {message_data}")
if self.client_self_id is None:
self.client_self_id = message_data.get("bot_id")
logger.debug(f"[Discord] Message received: {message_data}")
if self.bot_self_id is None:
self.bot_self_id = message_data.get("bot_id")
abm = await self.convert_message(data=message_data)
await self.handle_msg(abm)
# 初始化 Discord 客户端
token = str(self.config.get("discord_token"))
if not token:
logger.error("[Discord] Bot Token 未配置。请在配置文件中正确设置 token。")
logger.error(
"[Discord] Bot token is not configured. Please set a valid token in the config file."
)
return
proxy = self.config.get("discord_proxy") or None
@@ -144,12 +146,17 @@ class DiscordPlatformAdapter(Platform):
self.client.on_message_received = on_received
async def callback() -> None:
if self.enable_command_register:
await self._collect_and_register_commands()
if self.activity_name:
await self.client.change_presence(
status=discord.Status.online,
activity=discord.CustomActivity(name=self.activity_name),
try:
if self.enable_command_register:
await self._collect_and_register_commands()
if self.activity_name:
await self.client.change_presence(
status=discord.Status.online,
activity=discord.CustomActivity(name=self.activity_name),
)
except Exception as e:
logger.error(
f"[Discord] on_ready_once_callback err: {e}", exc_info=True
)
self.client.on_ready_once_callback = callback
@@ -158,11 +165,16 @@ class DiscordPlatformAdapter(Platform):
self._polling_task = asyncio.create_task(self.client.start_polling())
await self.shutdown_event.wait()
except discord.errors.LoginFailure:
logger.error("[Discord] 登录失败。请检查你的 Bot Token 是否正确。")
logger.error(
"[Discord] Login failed. Please check whether the bot token is correct."
)
except discord.errors.ConnectionClosed:
logger.warning("[Discord] 与 Discord 的连接已关闭。")
logger.warning("[Discord] Connection with Discord has been closed.")
except Exception as e:
logger.error(f"[Discord] 适配器运行时发生意外错误: {e}", exc_info=True)
logger.error(
f"[Discord] Unexpected error while adapter is running: {e}",
exc_info=True,
)
def _get_message_type(
self,
@@ -241,7 +253,7 @@ class DiscordPlatformAdapter(Platform):
)
abm.message = message_chain
abm.raw_message = message
abm.self_id = cast(str, self.client_self_id)
abm.self_id = cast(str, self.bot_self_id)
abm.session_id = str(message.channel.id)
abm.message_id = str(message.id)
return abm
@@ -264,7 +276,7 @@ class DiscordPlatformAdapter(Platform):
if self.client.user is None:
logger.error(
"[Discord] 客户端未就绪 (self.client.user is None),无法处理消息"
"[Discord] Client is not ready (self.client.user is None); message handling skipped"
)
return
@@ -283,7 +295,7 @@ class DiscordPlatformAdapter(Platform):
raw_message = message.raw_message
if not isinstance(raw_message, discord.Message):
logger.warning(
f"[Discord] 收到非 Message 类型的消息: {type(raw_message)},已忽略。"
f"[Discord] Non-Message type received and ignored: {type(raw_message)}"
)
return
@@ -324,20 +336,9 @@ class DiscordPlatformAdapter(Platform):
@override
async def terminate(self) -> None:
"""终止适配器"""
logger.info("[Discord] 正在终止适配器... (step 1: cancel polling task)")
logger.info("[Discord] Shutting down adapter...")
self.shutdown_event.set()
# 优先 cancel polling_task
if self._polling_task:
self._polling_task.cancel()
try:
await asyncio.wait_for(self._polling_task, timeout=10)
except asyncio.CancelledError:
logger.info("[Discord] polling_task 已取消。")
except Exception as e:
logger.warning(f"[Discord] polling_task 取消异常: {e}")
logger.info("[Discord] 正在清理已注册的斜杠指令... (step 2)")
# 清理指令
logger.info("[Discord] Cleaning up commands...")
if self.enable_command_register and self.client:
try:
await asyncio.wait_for(
@@ -347,16 +348,29 @@ class DiscordPlatformAdapter(Platform):
),
timeout=10,
)
logger.info("[Discord] 指令清理完成。")
logger.info("[Discord] Commands cleaned up successfully.")
except Exception as e:
logger.error(f"[Discord] 清理指令时发生错误: {e}", exc_info=True)
logger.info("[Discord] 正在关闭 Discord 客户端... (step 3)")
logger.warning(
f"[Discord] Error occurred while cleaning up commands: {e}"
)
if self._polling_task:
self._polling_task.cancel()
try:
await asyncio.wait_for(self._polling_task, timeout=10)
except asyncio.CancelledError:
logger.info("[Discord] Polling task cancelled successfully.")
except Exception as e:
logger.warning(
f"[Discord] Error occurred while cancelling polling task: {e}"
)
logger.info("[Discord] Closing client connection...")
if self.client and hasattr(self.client, "close"):
try:
await asyncio.wait_for(self.client.close(), timeout=10)
except Exception as e:
logger.warning(f"[Discord] 客户端关闭异常: {e}")
logger.info("[Discord] 适配器已终止。")
logger.warning(f"[Discord] Error occurred while closing client: {e}")
logger.info("[Discord] Adapter shutdown complete.")
def register_handler(self, handler_info) -> None:
"""注册处理器信息"""
@@ -364,7 +378,7 @@ class DiscordPlatformAdapter(Platform):
async def _collect_and_register_commands(self) -> None:
"""收集所有指令并注册到Discord"""
logger.info("[Discord] 开始收集并注册斜杠指令...")
logger.info("[Discord] Collecting and registering slash commands...")
registered_commands = []
for handler_md in star_handlers_registry:
@@ -405,15 +419,15 @@ class DiscordPlatformAdapter(Platform):
if registered_commands:
logger.info(
f"[Discord] 准备同步 {len(registered_commands)} 个指令: {', '.join(registered_commands)}",
f"[Discord] Ready to sync {len(registered_commands)} commands: {', '.join(registered_commands)}",
)
else:
logger.info("[Discord] 没有发现可注册的指令。")
logger.info("[Discord] No commands found for registration.")
# 使用 Pycord 的方法同步指令
# 注意:这可能需要一些时间,并且有频率限制
await self.client.sync_commands()
logger.info("[Discord] 指令同步完成。")
logger.info("[Discord] Command synchronization completed.")
def _create_dynamic_callback(self, cmd_name: str):
"""为每个指令动态创建一个异步回调函数"""
@@ -422,17 +436,17 @@ class DiscordPlatformAdapter(Platform):
ctx: discord.ApplicationContext, params: str | None = None
) -> None:
# 将平台特定的前缀'/'剥离以适配通用的CommandFilter
logger.debug(f"[Discord] 回调函数触发: {cmd_name}")
logger.debug(f"[Discord] 回调函数参数: {ctx}")
logger.debug(f"[Discord] 回调函数参数: {params}")
logger.debug(f"[Discord] Callback triggered: {cmd_name}")
logger.debug(f"[Discord] Callback context: {ctx}")
logger.debug(f"[Discord] Callback params: {params}")
message_str_for_filter = cmd_name
if params:
message_str_for_filter += f" {params}"
logger.debug(
f"[Discord] 斜杠指令 '{cmd_name}' 被触发。 "
f"原始参数: '{params}'. "
f"构建的指令字符串: '{message_str_for_filter}'",
f"[Discord] Slash command '{cmd_name}' triggered. "
f"Raw params: '{params}'. "
f"Built command string: '{message_str_for_filter}'",
)
# 尝试立即响应,防止超时
@@ -441,7 +455,7 @@ class DiscordPlatformAdapter(Platform):
await ctx.defer()
followup_webhook = ctx.followup
except Exception as e:
logger.warning(f"[Discord] 指令 '{cmd_name}' defer 失败: {e}")
logger.warning(f"[Discord] Failed to defer command '{cmd_name}': {e}")
# 2. 构建 AstrBotMessage
channel = ctx.channel
@@ -465,7 +479,7 @@ class DiscordPlatformAdapter(Platform):
)
abm.message = [Plain(text=message_str_for_filter)]
abm.raw_message = ctx.interaction
abm.self_id = cast(str, self.client_self_id)
abm.self_id = cast(str, self.bot_self_id)
abm.session_id = str(ctx.channel_id)
abm.message_id = str(ctx.interaction.id)
@@ -503,10 +517,10 @@ class DiscordPlatformAdapter(Platform):
# Discord 斜杠指令名称规范
if not re.match(r"^[a-z0-9_-]{1,32}$", cmd_name):
logger.debug(f"[Discord] 跳过不符合规范的指令: {cmd_name}")
logger.debug(f"[Discord] Skipping invalid slash command format: {cmd_name}")
return None
description = handler_metadata.desc or f"指令: {cmd_name}"
description = handler_metadata.desc or f"Command: {cmd_name}"
if len(description) > 100:
description = f"{description[:97]}..."

View File

@@ -93,7 +93,7 @@ class MisskeyPlatformAdapter(Platform):
self.api: MisskeyAPI | None = None
self._running = False
self.client_self_id = ""
self.bot_self_id = ""
self._bot_username = ""
self._user_cache = {}
@@ -138,10 +138,10 @@ class MisskeyPlatformAdapter(Platform):
try:
user_info = await self.api.get_current_user()
self.client_self_id = str(user_info.get("id", ""))
self.bot_self_id = str(user_info.get("id", ""))
self._bot_username = user_info.get("username", "")
logger.info(
f"[Misskey] 已连接用户: {self._bot_username} (ID: {self.client_self_id})",
f"[Misskey] 已连接用户: {self._bot_username} (ID: {self.bot_self_id})",
)
except Exception as e:
logger.error(f"[Misskey] 获取用户信息失败: {e}")
@@ -312,9 +312,9 @@ class MisskeyPlatformAdapter(Platform):
)
room_id = data.get("toRoomId")
logger.debug(
f"[Misskey] 收到聊天事件: sender_id={sender_id}, room_id={room_id}, is_self={sender_id == self.client_self_id}",
f"[Misskey] 收到聊天事件: sender_id={sender_id}, room_id={room_id}, is_self={sender_id == self.bot_self_id}",
)
if sender_id == self.client_self_id:
if sender_id == self.bot_self_id:
return
if room_id:
@@ -354,13 +354,13 @@ class MisskeyPlatformAdapter(Platform):
mentions = note.get("mentions", [])
if self._bot_username and f"@{self._bot_username}" in text:
return True
if self.client_self_id in [str(uid) for uid in mentions]:
if self.bot_self_id in [str(uid) for uid in mentions]:
return True
reply = note.get("reply")
if reply and isinstance(reply, dict):
reply_user_id = str(reply.get("user", {}).get("id", ""))
if reply_user_id == self.client_self_id:
if reply_user_id == self.bot_self_id:
return bool(self._bot_username and f"@{self._bot_username}" in text)
return False
@@ -598,7 +598,7 @@ class MisskeyPlatformAdapter(Platform):
visibility, visible_user_ids = resolve_message_visibility(
user_id=user_id_for_cache,
user_cache=self._user_cache,
self_id=self.client_self_id,
self_id=self.bot_self_id,
default_visibility=self.default_visibility,
)
logger.debug(
@@ -637,14 +637,14 @@ class MisskeyPlatformAdapter(Platform):
message = create_base_message(
raw_data,
sender_info,
self.client_self_id,
self.bot_self_id,
is_chat=False,
)
cache_user_info(
self._user_cache,
sender_info,
raw_data,
self.client_self_id,
self.bot_self_id,
is_chat=False,
)
@@ -656,7 +656,7 @@ class MisskeyPlatformAdapter(Platform):
message,
raw_text,
self._bot_username,
self.client_self_id,
self.bot_self_id,
)
message_parts.extend(text_parts)
@@ -685,14 +685,14 @@ class MisskeyPlatformAdapter(Platform):
message = create_base_message(
raw_data,
sender_info,
self.client_self_id,
self.bot_self_id,
is_chat=True,
)
cache_user_info(
self._user_cache,
sender_info,
raw_data,
self.client_self_id,
self.bot_self_id,
is_chat=True,
)
@@ -713,7 +713,7 @@ class MisskeyPlatformAdapter(Platform):
message = create_base_message(
raw_data,
sender_info,
self.client_self_id,
self.bot_self_id,
is_chat=False,
room_id=room_id,
)
@@ -722,10 +722,10 @@ class MisskeyPlatformAdapter(Platform):
self._user_cache,
sender_info,
raw_data,
self.client_self_id,
self.bot_self_id,
is_chat=False,
)
cache_room_info(self._user_cache, raw_data, self.client_self_id)
cache_room_info(self._user_cache, raw_data, self.bot_self_id)
raw_text = raw_data.get("text", "")
message_parts = []
@@ -736,7 +736,7 @@ class MisskeyPlatformAdapter(Platform):
message,
raw_text,
self._bot_username,
self.client_self_id,
self.bot_self_id,
)
message_parts.extend(text_parts)
else:

View File

@@ -335,7 +335,7 @@ def extract_sender_info(
def create_base_message(
raw_data: dict[str, Any],
sender_info: dict[str, Any],
client_self_id: str,
bot_self_id: str,
is_chat: bool = False,
room_id: str | None = None,
) -> AstrBotMessage:
@@ -367,7 +367,7 @@ def create_base_message(
session_id if sender_info["sender_id"] else f"{session_prefix}%unknown"
)
message.message_id = str(raw_data.get("id", ""))
message.self_id = client_self_id
message.self_id = bot_self_id
return message
@@ -376,7 +376,7 @@ def process_at_mention(
message: AstrBotMessage,
raw_text: str,
bot_username: str,
client_self_id: str,
bot_self_id: str,
) -> tuple[list[str], str]:
"""处理@提及逻辑,返回消息部分列表和处理后的文本"""
message_parts = []
@@ -386,7 +386,7 @@ def process_at_mention(
if bot_username and raw_text.startswith(f"@{bot_username}"):
at_mention = f"@{bot_username}"
message.message.append(Comp.At(qq=client_self_id))
message.message.append(Comp.At(qq=bot_self_id))
remaining_text = raw_text[len(at_mention) :].strip()
if remaining_text:
message.message.append(Comp.Plain(remaining_text))
@@ -401,7 +401,7 @@ def cache_user_info(
user_cache: dict[str, Any],
sender_info: dict[str, Any],
raw_data: dict[str, Any],
client_self_id: str,
bot_self_id: str,
is_chat: bool = False,
) -> None:
"""缓存用户信息"""
@@ -410,7 +410,7 @@ def cache_user_info(
"username": sender_info["username"],
"nickname": sender_info["nickname"],
"visibility": "specified",
"visible_user_ids": [client_self_id, sender_info["sender_id"]],
"visible_user_ids": [bot_self_id, sender_info["sender_id"]],
}
else:
user_cache_data = {
@@ -428,7 +428,7 @@ def cache_user_info(
def cache_room_info(
user_cache: dict[str, Any],
raw_data: dict[str, Any],
client_self_id: str,
bot_self_id: str,
) -> None:
"""缓存房间信息"""
room_data = raw_data.get("toRoom")
@@ -442,7 +442,7 @@ def cache_room_info(
"room_description": room_data.get("description", ""),
"owner_id": room_data.get("ownerId", ""),
"visibility": "specified",
"visible_user_ids": [client_self_id],
"visible_user_ids": [bot_self_id],
}

View File

@@ -50,7 +50,6 @@ class TelegramPlatformAdapter(Platform):
) -> None:
super().__init__(platform_config, event_queue)
self.settings = platform_settings
self.client_self_id = uuid.uuid4().hex[:8]
base_url = self.config.get(
"telegram_api_base_url",
@@ -336,6 +335,8 @@ class TelegramPlatformAdapter(Platform):
return None
def _apply_caption() -> None:
if not update.message:
return
if update.message.caption:
message.message_str = update.message.caption
message.message.append(Comp.Plain(message.message_str))

View File

@@ -148,7 +148,6 @@ class WecomPlatformAdapter(Platform):
) -> None:
super().__init__(platform_config, event_queue)
self.settingss = platform_settings
self.client_self_id = uuid.uuid4().hex[:8]
self.api_base_url = platform_config.get(
"api_base_url",
"https://qyapi.weixin.qq.com/cgi-bin/",

View File

@@ -2,7 +2,6 @@ import asyncio
import os
import sys
import time
import uuid
from collections.abc import Callable, Coroutine
from typing import Any, cast
@@ -324,7 +323,6 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
) -> None:
super().__init__(platform_config, event_queue)
self.settingss = platform_settings
self.client_self_id = uuid.uuid4().hex[:8]
self.api_base_url = platform_config.get(
"api_base_url",
"https://api.weixin.qq.com/cgi-bin/",