Skip to content

Discord 适配器增强功能#9125

Draft
NLKASHEI wants to merge 11 commits into
AstrBotDevs:masterfrom
NLKASHEI:patch-1
Draft

Discord 适配器增强功能#9125
NLKASHEI wants to merge 11 commits into
AstrBotDevs:masterfrom
NLKASHEI:patch-1

Conversation

@NLKASHEI

@NLKASHEI NLKASHEI commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Discord 平台适配器 Bug 修复

概述

共修复 18 个 Bug,覆盖空值安全、逻辑错误、异常处理、资源生命周期和代码质量。


修复清单

一、空值安全(6 项)

# 位置 问题 修复
1 __init__ self.client 未初始化,terminate()send_by_session() 过早调用时抛 AttributeError 添加 self.client = None
2 send_by_session / handle_msg self.clientNone 时直接访问 .user 属性崩溃 统一加 if self.client is None or self.client.user is None 守卫
3 handle_msg client 空值检查放在 DiscordPlatformEvent 创建之后,白创建对象 检查移到 event 创建之前
4 _convert_message / send_by_session / _create_dynamic_callback cast(str, self.bot_self_id)bot_self_idNone 时,self_id 仍是 None,下游 is_mentioned()int(None) 崩溃 改为 str(...) if ... is not None else "unknown"
5 meta() cast(str, self.config.get("id")) — 配置缺 "id" 时返回 None 改为 str(... or "")
6 send_by_session str(self.bot_self_id)bot_self_idNone 时发送者 ID 变成字符串 "None" "unknown" 兜底

二、逻辑错误(4 项)

# 位置 问题 修复
7 _get_message_type GroupChannel(群组私聊)被误判为 FRIEND_MESSAGE(原逻辑:isinstance(DMChannel) or guild is None 新增 from discord.channel import GroupChannel,显式 isinstance(channel, GroupChannel) 判断
8 _convert_message 三元表达式 self.client.user.id if self.client and self.client.user else None — Python 先求值真值分支再检查条件,属性不存在时仍崩溃 替换为 if/else
9 run() str(self.config.get("discord_token"))None"None" 字符串,绕过 if not token: 空值检查,导致用无效 token 连接 Discord 先取原始值判空,再 str()
10 send_by_session session_id.split("_")[1] 对多下划线 ID(如 discord_123_456)会截断 改为 split("_", 1)[1]

三、异常处理与鲁棒性(4 项)

# 位置 问题 修复
11 handle_msg except Exception 裸捕获,可能吞掉 KeyboardInterrupt 改为 except discord.DiscordException
12 run() polling task 异常(如 LoginFailure)被静默吞掉,适配器进入僵尸状态直到 terminate() 才暴露 新增 _on_polling_task_done 回调:记录错误日志 + 设置 shutdown_event 唤醒 run()
13 terminate() 命令清理在 polling 取消之前执行,可能与运行中的 polling 产生竞争 调整顺序:先取消 polling → 再清理命令 → 最后关闭连接
14 convert_message 音频附件未通过 MediaResolver 转换为 .wav,导致 CI 测试失败 恢复 MediaResolverto_path(target_format="wav") 转换,同时设置 file/url/path

四、代码质量(4 项)

# 位置 问题 修复
15 __init__ self.settings = platform_settings — 全文无引用,死代码 删除
16 send_by_session message_chain.get_plain_text() 连续调用两次 复用 message_obj.message_str
17 import cast 导入已无调用点(所有 cast(str, ...) 已被替换) 删除
18 send_by_session 注释说明 session_id 前缀剥离行为 添加 "discord_123456" → "123456" 注释

文件变更

discord_platform_adapter.py:576 行(原始)→ ~640 行(修复后)

无破坏性 API 变更,所有修复均为防御性、向后兼容。

1. 所有命令参数统一显示为 params → 用 inspect.signature() 按函数签名生成独立 Option
2. 命令描述用插件 desc → 改用 handler.__doc__(docstring)
3. star_map 无异常保护 → 加 try/except KeyError
4. 重复 import inspect → 移除
5. Option description 优化 → "请输入 {name}"
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. area:platform The bug / feature is about IM platform adapter, such as QQ, Lark, Telegram, WebChat and so on. labels Jul 3, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • In send_by_session and handle_msg you now construct DiscordPlatformEvent in two slightly different places; consider refactoring this into a single helper/factory to avoid duplication and keep behavior consistent between normal handling and temporary send events.
  • In _create_dynamic_callback, building params_str from kwargs.values() makes the command string depend on the kwargs dict order; using the param_names list to iterate in a defined order (and to skip unexpected keys) would make argument reconstruction more predictable and stable.
  • In _build_slash_options you swallow all exceptions with a bare except Exception: pass; adding at least a debug log with the handler name and error will make it much easier to diagnose broken signatures or unexpected annotations without failing silently.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `send_by_session` and `handle_msg` you now construct `DiscordPlatformEvent` in two slightly different places; consider refactoring this into a single helper/factory to avoid duplication and keep behavior consistent between normal handling and temporary send events.
- In `_create_dynamic_callback`, building `params_str` from `kwargs.values()` makes the command string depend on the kwargs dict order; using the `param_names` list to iterate in a defined order (and to skip unexpected keys) would make argument reconstruction more predictable and stable.
- In `_build_slash_options` you swallow all exceptions with a bare `except Exception: pass`; adding at least a debug log with the handler name and error will make it much easier to diagnose broken signatures or unexpected annotations without failing silently.

## Individual Comments

### Comment 1
<location path="astrbot/core/platform/sources/discord/discord_platform_adapter.py" line_range="445-454" />
<code_context>
+    @staticmethod
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Improve robustness of type mapping and error handling in _build_slash_options

Only bare `int` annotations are treated as integer options; types like `Optional[int]`, `bool`, or `float` all degrade to string. The blanket `except Exception: pass` also hides any signature inspection failures. Please extend the type handling (e.g., via `typing.get_origin`/`get_args` for optionals and other primitives) and log caught exceptions at least at debug level instead of silently ignoring them.

Suggested implementation:

```python
import inspect
from typing import get_origin, get_args

```

```python
    @staticmethod
    def _build_slash_options(handler) -> list:
        """从 handler 函数签名提取参数,映射到 Discord SlashCommandOptionType"""
        options = []
        try:
            sig = inspect.signature(handler)
            for name, param in sig.parameters.items():
                # 跳过常见的上下文参数
                if name in ("self", "event"):
                    continue

                # 默认按字符串处理
                opt_type = discord.SlashCommandOptionType.string

                annotation = param.annotation
                # 无类型标注时保持默认字符串
                if annotation is not inspect._empty:
                    origin = get_origin(annotation)
                    args = get_args(annotation)

                    # 处理 Optional[T] 或 Union[T, None] 形式
                    if origin is typing.Union and args:
                        non_none_args = [t for t in args if t is not type(None)]  # noqa: E721
                        if len(non_none_args) == 1:
                            annotation = non_none_args[0]
                            origin = get_origin(annotation)
                            args = get_args(annotation)

                    # 简单的原始类型映射
                    if annotation is int:
                        opt_type = discord.SlashCommandOptionType.integer
                    elif annotation is bool:
                        opt_type = discord.SlashCommandOptionType.boolean
                    elif annotation is float:
                        # Discord 对数字使用 number 类型
                        opt_type = discord.SlashCommandOptionType.number

```

1. The updated implementation assumes a logger is available; if this module does not yet define one, add:
   `import logging` at the top of the file and `logger = logging.getLogger(__name__)` near the top-level.
2. Replace any existing bare `except Exception: pass` around `_build_slash_options`'s logic with:
   `except Exception as exc: logger.debug("Failed to build slash options for %r: %s", handler, exc, exc_info=True)` so that failures are logged instead of silently ignored.
3. Ensure `typing` is imported as `import typing` if not already present, since the new code references `typing.Union`.
</issue_to_address>

### Comment 2
<location path="astrbot/core/platform/sources/discord/discord_platform_adapter.py" line_range="470-478" />
<code_context>
+            pass
+        return options
+
+    def _create_dynamic_callback(self, cmd_name: str, param_names: list | None = None):
         """为每个指令动态创建一个异步回调函数"""
+        param_names = param_names or []
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Use param_names to control argument ordering and filtering when building params_str

Right now the callback ignores `param_names` and just joins `kwargs.values()`, which can include unexpected keys and a different order than the handler’s signature. Please construct `params_str` by iterating over `param_names` and reading `kwargs[name]` when available, skipping unknown keys. This will keep argument order stable and avoid serializing unintended kwargs into the command string.

Suggested implementation:

```python
            # 1. 嘗試立即响应,防止超时 (移到最前面)
            followup_webhook = None
            # 将平台特定的前缀'/'剥离,以适配通用的CommandFilter
            logger.debug(f"[Discord] Callback triggered: {cmd_name}")
            logger.debug(f"[Discord] Callback context: {ctx}")

            # 根据 param_names 控制参数顺序和过滤,避免意外 kwargs
            ordered_args: list[str] = []
            if param_names:
                for name in param_names:
                    if name in kwargs:
                        ordered_args.append(str(kwargs[name]))
            else:
                # 作为回退,保持与旧实现行为一致(但不依赖于 param_names)
                ordered_args = [str(v) for v in kwargs.values()]

            params_str = " ".join(ordered_args)

```

1. 确保在调用 `_create_dynamic_callback` 时传入的 `param_names` 顺序与对应 handler 的参数顺序一致(例如从函数签名或 Discord option 定义中提取),否则无法保证期望的参数顺序。
2. 如果下游代码已经在 `dynamic_callback` 中构造了 `params_str`(例如原来有一行 `params_str = " ".join(str(v) for v in kwargs.values())` 但不在本段上下文中),需要将那行删除或替换为这里的 `params_str` 构造逻辑,以避免重复或不一致的行为。
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread astrbot/core/platform/sources/discord/discord_platform_adapter.py Outdated
Comment thread astrbot/core/platform/sources/discord/discord_platform_adapter.py Outdated

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the Discord platform adapter to dynamically generate Discord slash command options from handler function signatures, support Unicode command names, and use handler docstrings as command descriptions. It also removes audio record handling and the create_event helper method. The reviewer feedback suggests improving the slash command option generator to support optional, union, float, and boolean types. Additionally, it is recommended to use the extracted parameter names to safely order arguments in the dynamic callback, restore the create_event helper to avoid duplicate instantiation, and simplify the plugin activation check using star_map.get() instead of a try-except block.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread astrbot/core/platform/sources/discord/discord_platform_adapter.py Outdated
Comment on lines +495 to +500
# 从 kwargs 收集参数值,拼接为命令字符串
params_str = " ".join(str(v) for v in kwargs.values() if v is not None)
logger.debug(f"[Discord] Callback params: {params_str}, kwargs: {kwargs}")
message_str_for_filter = cmd_name
if params:
message_str_for_filter += f" {params}"
if params_str:
message_str_for_filter += f" {params_str}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

这里有两个需要注意的问题:

  1. 未使用的变量:传入 _create_dynamic_callbackparam_names 列表在函数体内完全没有被使用。
  2. 参数顺序与安全性:直接遍历 kwargs.values() 拼接参数可能会受到不属于命令选项的额外 kwargs 的干扰。建议使用 param_names 来按函数签名的确切顺序提取参数值。

⚠️ 重要提示:由于通用的 CommandFilter 内部使用简单的 .split(" ") 来解析参数,如果用户在 Discord 斜杠指令中输入的某个参数值包含空格(例如 message="hello world"),拼接为 params_str 后会被 CommandFilter 错误地拆分为多个参数,从而导致解析失败。这是目前文本命令过滤器和结构化斜杠指令对接时的固有局限性,建议在后续版本中考虑让 CommandFilter 支持直接接收已解析的参数字典。

Suggested change
# 从 kwargs 收集参数值,拼接为命令字符串
params_str = " ".join(str(v) for v in kwargs.values() if v is not None)
logger.debug(f"[Discord] Callback params: {params_str}, kwargs: {kwargs}")
message_str_for_filter = cmd_name
if params:
message_str_for_filter += f" {params}"
if params_str:
message_str_for_filter += f" {params_str}"
# 从 kwargs 收集参数值,拼接为命令字符串
params_str = " ".join(str(kwargs[name]) for name in param_names if kwargs.get(name) is not None)
logger.debug(f"[Discord] Callback params: {params_str}, kwargs: {kwargs}")
message_str_for_filter = cmd_name
if params_str:
message_str_for_filter += f" {params_str}"

Comment thread astrbot/core/platform/sources/discord/discord_platform_adapter.py Outdated
Comment thread astrbot/core/platform/sources/discord/discord_platform_adapter.py Outdated
@NLKASHEI
NLKASHEI marked this pull request as draft July 3, 2026 09:14
@NLKASHEI
NLKASHEI marked this pull request as ready for review July 3, 2026 09:20
@NLKASHEI
NLKASHEI marked this pull request as draft July 3, 2026 09:20
Refactor slash command option handling and improve error logging.
@NLKASHEI
NLKASHEI marked this pull request as ready for review July 3, 2026 09:21

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="astrbot/core/platform/sources/discord/discord_platform_adapter.py" line_range="446-447" />
<code_context>
         return getattr(error, "code", None) == 30034

-    def _create_dynamic_callback(self, cmd_name: str):
+    @staticmethod
+    def _build_slash_options(handler) -> list:
+        """从 handler 函数签名提取参数,映射到 Discord SlashCommandOptionType"""
+        options = []
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Broad exception handling in _build_slash_options can hide misconfigurations of handlers.

Catching all exceptions here, logging only at debug, and returning an empty `options` list means misconfigured or unexpectedly annotated handlers quietly become slash commands with no options. To make these issues visible, either log failures at warning/error level or re-raise unexpected errors so registration fails fast instead of silently degrading behavior.

Suggested implementation:

```python
    @staticmethod
    def _build_slash_options(handler) -> list:
        """从 handler 函数签名提取参数,映射到 Discord SlashCommandOptionType"""
        options = []
        # 这里不再捕获所有异常,以避免静默吞掉处理器配置错误。
        # 如果 handler 的签名或注解有问题,应让异常向上传播,从而在注册阶段暴露问题。
        sig = inspect.signature(handler)
        for name, param in sig.parameters.items():
            if name in ("self", "event"):
                continue
            annotation = param.annotation
            # 处理 Optional[T] 或 T | None
            origin = typing.get_origin(annotation)
            union_types = {typing.Union}
            if hasattr(types, "UnionType"):

```

1.`_build_slash_options` 的后半部分,如果当前存在类似 `except Exception as exc: ...` 的广泛异常处理,请将其改为:
   - 要么完全移除 `except`(让异常直接抛出),
   - 要么仅捕获预期的、可恢复的异常类型(例如对特定注解解析错误),并使用 `logger.warning``logger.error` 记录详细信息后重新抛出异常:
   ```python
   except SomeExpectedError as exc:
       logger.warning("Failed to build slash options for %r: %s", handler, exc)
       raise
   ```
2. 如果当前仅使用 `logger.debug` 记录失败并返回空列表,请将日志级别提升到 `warning``error`,并确保在真正的配置错误情况下调用 `raise`,以便命令注册在出错时不会静默成功。
3. 如果本模块尚未定义 logger(例如 `logger = logging.getLogger(__name__)`),需要在文件顶部导入 `logging` 并定义一个模块级 logger,以支持上述 warning/error 级别的日志记录。
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread astrbot/core/platform/sources/discord/discord_platform_adapter.py Outdated
@NLKASHEI

NLKASHEI commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

本文档说明修复版相对原版的改动、动机与行为差异。


1. 概述

修复版在保留原版 AstrBot + Pycord Discord 适配器整体架构的前提下,重点处理了:

  1. 稳定性:未初始化访问、异常打挂事件循环、冷启动取 channel 失败等
  2. 正确性:session 副作用、channel id 污染、音频链路、mention 剥离范围
  3. 斜杠指令:参数 Option 化、重复注册、名称规范(含中文)、同步范围与配额
  4. 可运维性:更明确的日志、单项失败隔离、关闭流程更干净

中文斜杠命令名:Discord CHAT_INPUT 允许 Unicode 字母/数字(含中文),修复版允许注册(须 1–32 位且 name == name.lower())。原版正则未加 re.ASCII 时中文也能通过本地校验;修复版明确按 Unicode 规则校验并写清日志,避免误以为「Discord 不支持中文命令名」。


2. 初始化与生命周期

项目 原版 修复版
self.client 仅在 run() 内赋值,run 前访问易 AttributeError __init__self.client = None,统一 _client_ready()
guild_id 配置原样使用(常为 str) _normalize_guild_idint | None,非法值打日志并置空
bot_self_id 主要依赖首条消息的 bot_id _ensure_bot_self_id():ready / 收消息 / 发送 / 处理 / slash 多处补齐
收消息异常 未统一捕获,可能影响上层回调 on_received 整体 try/except,单条失败不拖垮适配器
polling 任务 创建 task 后无 done 回调 add_done_callback,崩溃时打错误日志
token / proxy 直接 get token strip,空串视为未配置;proxy 空串变 None
terminate 有清理逻辑,但 client 可能不存在 全程 client is not None 判断;cancel 后超时/取消分支更清晰;task 置 None

相关辅助方法(修复版新增)


3. 会话发送 send_by_session

项目 原版 修复版
改写 session.session_id 原地 split("_")[1],污染调用方对象 仅本地变量解析,不修改传入 session
多段 _ split("_")[1] 只取第二段,易截错 rsplit("_", 1)[-1] 取最后一段
取 channel get_channel(缓存) 先缓存,失败再 fetch_channel_resolve_channel
事件构造 create_event 同样走 create_event(修复过程中曾内联过,最终恢复统一入口)
发送失败 异常直接上浮 打带 channel 的错误日志后再抛出

4. 消息转换与媒体

4.1 文本与 Mention

项目 原版 修复版
User mention 剥离 仅开头 bot 的 <@id> / <@!id> 同样只剥 本 bot;支持循环剥连续 bot mention
Role mention 剥离 bot 角色且开头匹配则剥一个 循环:可连续剥 bot user + bot role
误剥其它人 @ 原版已尽量避免 保持「只剥 bot」,不用宽正则剥任意 mention
get_member mention 检测侧有 try 剥离侧也加 try/except,避免偶发异常

4.2 附件与音频

项目 原版 修复版
content_type is None 需同时判断 truthy 再 startswith ct = (content_type or "").lower(),更稳
空 url 附件 仍可能入链 无 url 直接跳过
Record file + url 同样 file + url
音频落地 convert_messageMediaResolver 转 wav 保留;失败 try/except 打日志,保留原 URL
作者字段 display_name display_namename"" 兜底

说明:中间某一版修复稿曾误删音频解析,当前修复版已与原版对齐并加强异常保护。

4.3 session / group id

项目 原版 修复版
_get_channel_id str(getattr(..., None)) 可能得到 "None" 无 id 返回 ""
abm.session_id str(message.channel.id) 安全取 id,无则 ""

5. 事件处理 handle_msg / create_event

项目 原版 修复版
create_event 有独立方法 保留send_by_sessionhandle_msg 共用
客户端未就绪 检查 client.user _client_ready(),并补 message.self_id
斜杠优先 interaction_followup_webhook 则 wake 相同
User / Role 提及唤醒 相同逻辑 + mentions 空列表与异常防护
discord.Message 忽略并 warning 相同

6. 斜杠指令注册与回调

这是修复版与原版差异最大的一块。

6.1 命令收集

项目 原版 修复版
插件是否激活 star_map[path].activated,缺 key 可能 KeyError star_map.get(...),无插件跳过
重复命令名 可重复 add_application_command seen_names 去重并 warning
单条注册失败 可能中断整批 单命令 try/except,失败 continue
参数 Option 固定 一个可选 params: str 从 handler 签名 生成多个 Option;无参数时回退 原版式 params
Option 数量 恒为 1 最多 25,超出截断
Option 顺序 不适用 required 在前,符合 Discord 要求
*args / **kwargs 不涉及 建 Option 时跳过 VAR_POSITIONAL / VAR_KEYWORD
非法 option 名 校验小写 + 字符集,跳过
类型注解 get_type_hints + Optional 解包;int/float/bool/string
同步范围 sync_commands() 配置了 guild_idsync_commands(guild_ids=[...]),便于调试、减轻全局延迟/配额
日配额 30034 有专门处理 保留;其它异常也 warning

6.2 命令名规则(含中文)

项目 原版 修复版
正则 ^[-_'\w]{1,32}$(无 ASCII flag) ^[-_\w]{1,32}$(Unicode \w中文可过
小写 cmd_name != cmd_name.lower() 则跳过 相同
日志 debug「Skipping invalid…」 warning,说明可用中文/字母/数字/_/-,须小写 1–32
描述 多用 handler_metadata.desc 优先 docstring 首行,再 desc,再默认;空串/超长兜底(≤100)

结论:中文斜杠命令名可以注册(例如 /帮助),前提是名称本身满足 Discord 长度与字符规则。大写英文、空格、.、超长等仍会被跳过。

子命令(有 parent_command_names)与 CommandGroupFilter注册为 slash,与原版一致。

6.3 动态回调

项目 原版 修复版
签名 (ctx, params: str | None = None) 按 option 名生成显式参数 + *args/**kwargs 兜底;生成失败回退纯 kwargs
defer wait_for(ctx.defer(), 2.5) 先看 response.is_done(),再 defer
defer 失败 打日志 return 尽量 ephemeral 提示用户后 return
业务异常 无统一捕获 _handle 内 try/except,followup 提示「指令执行出错」
参数拼命令串 cmd_name + " " + params param_names 顺序拼接;含空格/引号用双引号转义,减少简单 split 错位
bool str 序列化为 true / false
channel 为空 有 guild_id 兜底 相同,且 channel_id is None 时用 ""

7. 其它小改动

  • 去掉对 shlex 的依赖,改为自实现双引号转义(避免 Windows/locale 差异)
  • 常量:_DISCORD_MAX_OPTIONS = 25_DISCORD_DESC_MAX = 100
  • register_handler、元数据 meta()、平台注册装饰器与原版语义一致
  • MediaResolver 正常使用(非死 import)

8. 已知限制(架构层,非本次必改)

  1. 斜杠多参 → 文本命令串
    仍拼成 命令名 参数1 参数2… 交给 AstrBot CommandFilter。若下游只按空格拆且不认引号,含空格参数仍可能错位。

  2. 有明确签名时不再提供万能 params
    handler 已有 foo, bar 时只有这两个 Option;自由长文本需写在某个 string 参数里。无参 handler 仍回退 params

  3. 复杂 Discord 类型
    未映射 User / Channel / Role / Attachment 等 Option 类型,一律 string/int/float/bool。

  4. 子命令 / 命令组
    仍不注册为 Discord 原生 slash 子结构。

  5. IDE / Pylance
    工作区若未安装 discord / astrbot,会出现导入与 @override 告警,与运行时逻辑无关。


9. 建议验证清单

上线或替换前建议在真实 Discord 环境覆盖:

  • 无 token / 错误 token 日志与退出行为
  • 普通消息、@bot、@角色(bot 拥有角色)唤醒
  • 图片 / 语音附件(音频是否转成可用路径)
  • 会话主动 send_by_session(含冷启动后首次发送)
  • 无参 slash、多参 slash、参数含空格
  • 中文命令名注册与触发(如 /帮助
  • 重复命令名、非法命令名日志
  • guild_id 调试同步 vs 全局同步
  • 正常 terminate:清命令、断连、无挂死 task

10. 变更对照速查

类别 是否修复/增强
client 未初始化崩溃
session.session_id 原地修改
channel 缓存未命中 fetch_channel
"None" 污染 group/session id
音频 MediaResolver ✅ 保留并加固
star_map KeyError
slash 多 Option + 无参 params 回退
重复 slash 名
required option 顺序 / 最多 25 个
中文命令名 ✅ 明确允许
guild_id 类型与同步范围
defer / 执行异常用户提示
收消息 / 单项注册异常隔离
mention 只剥 bot、可连续剥
子命令组注册为 slash ❌ 仍不支持(同原版)
User/Channel 等 Option 类型 ❌ 未做

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:platform The bug / feature is about IM platform adapter, such as QQ, Lark, Telegram, WebChat and so on. size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant