Skip to content

Conversation

@Soulter
Copy link
Member

@Soulter Soulter commented Oct 1, 2025

fixes #XYZ


Motivation / 动机

优化 astrbot 内置插件的文件结构,提高可读性和可维护性。

Modifications / 改动点

将 main.py 的指令逻辑都移动到独立的文件中。

Verification Steps / 验证步骤

Screenshots or Test Results / 运行截图或测试结果

Compatibility & Breaking Changes / 兼容性与破坏性变更

  • 这是一个破坏性变更 (Breaking Change)。/ This is a breaking change.
  • 这不是一个破坏性变更。/ This is NOT a breaking change.

Checklist / 检查清单

  • 😊 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。/ If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
  • 👀 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”。/ My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
  • 🤓 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到了 requirements.txtpyproject.toml 文件相应位置。/ I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
  • 😮 我的更改没有引入恶意代码。/ My changes do not introduce malicious code.

Sourcery 总结

重构 packages/astrbot 中的内部插件代码,通过将 main.py 中所有的命令实现提取到专门的命令模块中,并更新接口以提高模块化和可读性。

增强功能:

  • 将单个命令处理器提取到 packages/astrbot/commands 下的独立类中
  • 委托 Main star 类使用新的命令模块,而不是内联逻辑
  • AbstractProvider.get_models 转换为异步方法以匹配异步使用
  • CommandParser.get 添加返回类型注解以改进类型检查
Original summary in English

Summary by Sourcery

Refactor the internal plugin code in packages/astrbot by extracting all command implementations from main.py into dedicated command modules and updating interfaces for better modularity and readability.

Enhancements:

  • Extract individual command handlers into separate classes under packages/astrbot/commands
  • Delegate Main star class to use new command modules instead of inline logic
  • Convert AbstractProvider.get_models to an async method to match asynchronous usage
  • Add return type annotation to CommandParser.get for improved typing

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes and they look great!

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

## Individual Comments

### Comment 1
<location> `packages/astrbot/main.py:33` </location>
<code_context>
+)


 class Main(star.Star):
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring command handler delegation by using mix-in inheritance for command classes instead of per-method delegation.

```markdown
Instead of composing and then writing one‐line delegators for every command, you can turn each `*Commands` class into a mix-in and let `Main` inherit them directly.  All decorated methods live in the mix-ins, so you:

1. Remove all the `self.xxx_c = XxxCommand(...)` lines from `Main.__init__`.
2. Drop every `async def help/llm/tool_ls/…` wrapper in `Main`.
3. Change your class signature to inherit the command mix-ins:

```python
from .commands import (
    HelpCommand, LLMCommands, ToolCommands, PluginCommands,
    AdminCommands, ConversationCommands, ProviderCommands,
    PersonaCommands, AlterCmdCommands, SetUnsetCommands,
    T2ICommand, TTSCommand, SIDCommand,
)

class Main(
    star.Star,
    HelpCommand, LLMCommands, ToolCommands, PluginCommands,
    AdminCommands, ConversationCommands, ProviderCommands,
    PersonaCommands, AlterCmdCommands, SetUnsetCommands,
    T2ICommand, TTSCommand, SIDCommand,
):
    def __init__(self, context: star.Context) -> None:
        super().__init__(context)
        self.context = context
        cfg = context.get_config()
        self.timezone = cfg.get("timezone") or None
        if self.timezone:
            logger.info(f"Timezone set to: {self.timezone}")
        try:
            self.ltm = LongTermMemory(self.context.astrbot_config_mgr, self.context)
        except Exception as e:
            logger.error(f"聊天增强 err: {e}")
```

4. In each mix-in, refer to `self.context` (from `Main`) instead of storing it again.

This removes a dozen boilerplate attributes and one‐liner methods, while still keeping every decorated handler in place.
```
</issue_to_address>

### Comment 2
<location> `packages/astrbot/commands/alter_cmd.py:12` </location>
<code_context>
+from enum import Enum
+
+
+class RstScene(Enum):
+    GROUP_UNIQUE_ON = ("group_unique_on", "群聊+会话隔离开启")
+    GROUP_UNIQUE_OFF = ("group_unique_off", "群聊+会话隔离关闭")
</code_context>

<issue_to_address>
**issue (complexity):** Consider moving RstScene to a shared module and refactoring alter_cmd into smaller private methods for clarity.

```suggestion
// 1) Move RstScene into a shared module instead of re-defining it:
– class RstScene(Enum): …
+ // in astrbot/core/star/rst_scene.py
+ class RstScene(Enum):
+     GROUP_UNIQUE_ON = ("group_unique_on", "群聊会话隔离开启")
+     GROUP_UNIQUE_OFF = ("group_unique_off", "群聊会话隔离关闭")
+     PRIVATE = ("private", "私聊")
+
+     @property
+     def key(self): return self.value[0]
+     @property
+     def name(self): return self.value[1]
+     @classmethod
+     def from_index(cls, i): return {1:cls.GROUP_UNIQUE_ON,2:cls.GROUP_UNIQUE_OFF,3:cls.PRIVATE}[i]

// 2) Extract alter_cmd’s three branches into small private methods:
– async def alter_cmd(self, event: AstrMessageEvent):
–     … all the nested ifs …
+ async def alter_cmd(self, event: AstrMessageEvent):
+     tokens = self.parse_commands(event.message_str)
+     if self._is_reset_config(tokens):
+         return await self._handle_reset_config(event)
+     if self._is_reset_scene(tokens):
+         return await self._handle_reset_scene(event, tokens)
+     return await self._handle_permission_update(event, tokens)
+
+ def _is_reset_config(self, t):    return t.len==3 and t.tokens[1]=="reset" and t.tokens[2]=="config"
+ def _is_reset_scene(self, t):     return t.len>=4 and t.tokens[1]=="reset" and t.tokens[2]=="scene"
+
+ async def _handle_reset_config(self, event):
+     from astrbot.api import sp
+     cfg = await sp.global_get("alter_cmd", {})
+     reset = cfg.get("astrbot",{}).get("reset",{})
+     menu = (
+         f"reset命令权限配置\n"
+         f"1. 群聊会话隔离开: {reset.get('group_unique_on','admin')}\n"
+         f"2. 群聊会话隔离关: {reset.get('group_unique_off','admin')}\n"
+         f"3. 私聊: {reset.get('private','member')}\n"
+         "/alter_cmd reset scene <场景编号> <admin/member>"
+     )
+     await event.send(MessageChain().message(menu))
+
+ async def _handle_reset_scene(self, event, tokens):
+     num, perm = tokens.get(3), tokens.get(4)
+     if not num or not perm or not num.isdigit() or int(num)<1 or int(num)>3 or perm not in {"admin","member"}:
+         return await event.send(MessageChain().message("场景编号必须1-3且权限为admin/member"))
+     scene = RstScene.from_index(int(num))
+     await self._update_reset_permission(scene.key, perm)
+     await event.send(MessageChain().message(f"已将 reset 在{scene.name}设为{perm}"))
+
+ async def _handle_permission_update(self, event, tokens):
+     name, typ = " ".join(tokens.tokens[1:-1]), tokens.get(-1)
+     if typ not in {"admin","member"}:
+         return await event.send(MessageChain().message("指令类型错误,可选 admin, member"))
+     handler = self._find_handler(name)
+     if not handler:
+         return await event.send(MessageChain().message("未找到该指令"))
+     await self._save_permission(handler, typ)
+     await event.send(MessageChain().message(f"已将「{name}」权限调整为{typ}"))
+
+ def _find_handler(self, name):
+     for md in star_handlers_registry:
+         for f in md.event_filters:
+             if isinstance(f,(CommandFilter,CommandGroupFilter)) and f.equals(name):
+                 return md
+     return None
+
+ async def _save_permission(self, md, typ):
+     from astrbot.api import sp, event as evt_mod
+     cfg = await sp.global_get("alter_cmd",{})
+     pl = cfg.setdefault(star_map[md.handler_module_path].name,{})
+     pl.setdefault(md.handler_name,{})["permission"]=typ
+     await sp.global_put("alter_cmd",cfg)
+     # inject or update PermissionTypeFilter
+     for f in md.event_filters:
+         if isinstance(f,PermissionTypeFilter):
+             f.permission_type = evt_mod.filter.PermissionType.ADMIN if typ=="admin" else evt_mod.filter.PermissionType.MEMBER
+             return
+     md.event_filters.insert(0, PermissionTypeFilter(
+         evt_mod.filter.PermissionType.ADMIN if typ=="admin" else evt_mod.filter.PermissionType.MEMBER
+     ))
```
</issue_to_address>

### Comment 3
<location> `packages/astrbot/commands/provider.py:12` </location>
<code_context>
+    def __init__(self, context: star.Context):
+        self.context = context
+
+    async def provider(
+        self, event: AstrMessageEvent, idx: Union[str, int, None] = None, idx2: Union[int, None] = None
+    ):
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring the provider method by splitting listing and switching logic into separate handlers to reduce long conditional branches.

```markdown
拆分 `provider` 方法,把“列出”和“切换”逻辑抽成独立 handler,避免长 if/elif。比如:

1. 在类里增加一个映射表和两个私有方法:  
```python
PROVIDER_MAP = {
    None:  (ProviderType.CHAT_COMPLETION,   "LLM 提供商",  "get_all_providers",    "get_using_provider"),
    "tts": (ProviderType.TEXT_TO_SPEECH,    "TTS 提供商",  "get_all_tts_providers", "get_using_tts_provider"),
    "stt": (ProviderType.SPEECH_TO_TEXT,    "STT 提供商",  "get_all_stt_providers", "get_using_stt_provider"),
}

async def _list_providers(self, event: AstrMessageEvent):
    ret = []
    umo = event.unified_msg_origin
    for key,(ptype, title, all_fn, use_fn) in self.PROVIDER_MAP.items():
        providers = getattr(self.context, all_fn)()
        if not providers: continue
        ret.append(f"## 载入的 {title}")
        using = getattr(self.context, use_fn)(umo)
        for i, prov in enumerate(providers, 1):
            line = f"{i}. {prov.meta().id}"
            if using and using.meta().id == prov.meta().id:
                line += " (当前使用)"
            ret.append(line)
    ret.append("使用 /provider <序号> 切换 LLM,/provider tts/stt <序号> 切换 TTS/STT。")
    event.set_result(MessageEventResult().message("\n".join(ret)))
```

2. 切换逻辑统一到 `_switch_provider````python
async def _switch_provider(
    self, event: AstrMessageEvent, key: Union[str,int], idx: int
):
    if key not in self.PROVIDER_MAP:
        return event.set_result(MessageEventResult().message("无效参数。"))
    ptype, _, all_fn, _ = self.PROVIDER_MAP[key]
    providers = getattr(self.context, all_fn)()
    if not idx or idx < 1 or idx > len(providers):
        return event.set_result(MessageEventResult().message("无效的序号。"))
    pid = providers[idx - 1].meta().id
    await self.context.provider_manager.set_provider(
        provider_id=pid,
        provider_type=ptype,
        umo=event.unified_msg_origin,
    )
    event.set_result(MessageEventResult().message(f"成功切换到 {pid}"))
```

3. 最后把原 `provider` 简化为:  
```python
async def provider(self, event, key=None, idx=None):
    if key is None:
        return await self._list_providers(event)
    return await self._switch_provider(event, key, idx)
}
```

同理,`model_ls``key` 也能按“list/switch”拆成两小块,减少嵌套、提升可读性。
```
</issue_to_address>

### Comment 4
<location> `packages/astrbot/commands/conversation.py:132-137` </location>
<code_context>
        session_curr_cid = await conv_mgr.get_curr_conversation_id(umo)

        if not session_curr_cid:
            session_curr_cid = await conv_mgr.new_conversation(
                umo, message.get_platform_id()
            )

</code_context>

<issue_to_address>
**suggestion (code-quality):** Use `or` for providing a fallback value ([`use-or-for-fallback`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/use-or-for-fallback))

```suggestion
        session_curr_cid = await conv_mgr.get_curr_conversation_id(umo) or await conv_mgr.new_conversation(
                        umo, message.get_platform_id()
                    )

```

<br/><details><summary>Explanation</summary>Thanks to the flexibility of Python's `or` operator, you can use a single
assignment statement, even if a variable can retrieve its value from various
sources. This is shorter and easier to read than using multiple assignments with
`if not` conditions.
</details>
</issue_to_address>

### Comment 5
<location> `packages/astrbot/commands/conversation.py:151` </location>
<code_context>
            f"{history if history else '无历史记录'}\n\n"

</code_context>

<issue_to_address>
**suggestion (code-quality):** Replace if-expression with `or` ([`or-if-exp-identity`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/or-if-exp-identity))

```suggestion
            f"{history or '无历史记录'}\n\n"
```

<br/><details><summary>Explanation</summary>Here we find ourselves setting a value if it evaluates to `True`, and otherwise
using a default.

The 'After' case is a bit easier to read and avoids the duplication of
`input_currency`.

It works because the left-hand side is evaluated first. If it evaluates to
true then `currency` will be set to this and the right-hand side will not be
evaluated. If it evaluates to false the right-hand side will be evaluated and
`currency` will be set to `DEFAULT_CURRENCY`.
</details>
</issue_to_address>

### Comment 6
<location> `packages/astrbot/commands/conversation.py:202` </location>
<code_context>
            title = conv.title if conv.title else "新对话"

</code_context>

<issue_to_address>
**suggestion (code-quality):** Replace if-expression with `or` ([`or-if-exp-identity`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/or-if-exp-identity))

```suggestion
            title = conv.title or "新对话"
```

<br/><details><summary>Explanation</summary>Here we find ourselves setting a value if it evaluates to `True`, and otherwise
using a default.

The 'After' case is a bit easier to read and avoids the duplication of
`input_currency`.

It works because the left-hand side is evaluated first. If it evaluates to
true then `currency` will be set to this and the right-hand side will not be
evaluated. If it evaluates to false the right-hand side will be evaluated and
`currency` will be set to `DEFAULT_CURRENCY`.
</details>
</issue_to_address>

### Comment 7
<location> `packages/astrbot/commands/conversation.py:357` </location>
<code_context>
            title = conversation.title if conversation.title else "新对话"

</code_context>

<issue_to_address>
**suggestion (code-quality):** Replace if-expression with `or` ([`or-if-exp-identity`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/or-if-exp-identity))

```suggestion
            title = conversation.title or "新对话"
```

<br/><details><summary>Explanation</summary>Here we find ourselves setting a value if it evaluates to `True`, and otherwise
using a default.

The 'After' case is a bit easier to read and avoids the duplication of
`input_currency`.

It works because the left-hand side is evaluated first. If it evaluates to
true then `currency` will be set to this and the right-hand side will not be
evaluated. If it evaluates to false the right-hand side will be evaluated and
`currency` will be set to `DEFAULT_CURRENCY`.
</details>
</issue_to_address>

### Comment 8
<location> `packages/astrbot/commands/persona.py:38` </location>
<code_context>
            curr_cid_title = conv.title if conv.title else "新对话"

</code_context>

<issue_to_address>
**suggestion (code-quality):** Replace if-expression with `or` ([`or-if-exp-identity`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/or-if-exp-identity))

```suggestion
            curr_cid_title = conv.title or "新对话"
```

<br/><details><summary>Explanation</summary>Here we find ourselves setting a value if it evaluates to `True`, and otherwise
using a default.

The 'After' case is a bit easier to read and avoids the duplication of
`input_currency`.

It works because the left-hand side is evaluated first. If it evaluates to
true then `currency` will be set to this and the right-hand side will not be
evaluated. If it evaluates to false the right-hand side will be evaluated and
`currency` will be set to `DEFAULT_CURRENCY`.
</details>
</issue_to_address>

### Comment 9
<location> `packages/astrbot/commands/admin.py:13-20` </location>
<code_context>
    async def op(self, event: AstrMessageEvent, admin_id: str = ""):
        """授权管理员。op <admin_id>"""
        if admin_id == "":
            event.set_result(
                MessageEventResult().message(
                    "使用方法: /op <id> 授权管理员;/deop <id> 取消管理员。可通过 /sid 获取 ID。"
                )
            )
            return
        self.context.get_config()["admins_id"].append(str(admin_id))
        self.context.get_config().save_config()
        event.set_result(MessageEventResult().message("授权成功。"))

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Replaces an empty collection equality with a boolean operation ([`simplify-empty-collection-comparison`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/simplify-empty-collection-comparison/))
- Remove unnecessary casts to int, str, float or bool ([`remove-unnecessary-cast`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/remove-unnecessary-cast/))
</issue_to_address>

### Comment 10
<location> `packages/astrbot/commands/admin.py:26-34` </location>
<code_context>
    async def deop(self, event: AstrMessageEvent, admin_id: str = ""):
        """取消授权管理员。deop <admin_id>"""
        if admin_id == "":
            event.set_result(
                MessageEventResult().message(
                    "使用方法: /deop <id> 取消管理员。可通过 /sid 获取 ID。"
                )
            )
            return
        try:
            self.context.get_config()["admins_id"].remove(str(admin_id))
            self.context.get_config().save_config()
            event.set_result(MessageEventResult().message("取消授权成功。"))
        except ValueError:
            event.set_result(
                MessageEventResult().message("此用户 ID 不在管理员名单内。")
            )

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Replaces an empty collection equality with a boolean operation ([`simplify-empty-collection-comparison`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/simplify-empty-collection-comparison/))
- Remove unnecessary casts to int, str, float or bool ([`remove-unnecessary-cast`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/remove-unnecessary-cast/))
</issue_to_address>

### Comment 11
<location> `packages/astrbot/commands/admin.py:44-52` </location>
<code_context>
    async def wl(self, event: AstrMessageEvent, sid: str = ""):
        """添加白名单。wl <sid>"""
        if sid == "":
            event.set_result(
                MessageEventResult().message(
                    "使用方法: /wl <id> 添加白名单;/dwl <id> 删除白名单。可通过 /sid 获取 ID。"
                )
            )
            return
        cfg = self.context.get_config(umo=event.unified_msg_origin)
        cfg["platform_settings"]["id_whitelist"].append(str(sid))
        cfg.save_config()
        event.set_result(MessageEventResult().message("添加白名单成功。"))

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Replaces an empty collection equality with a boolean operation ([`simplify-empty-collection-comparison`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/simplify-empty-collection-comparison/))
- Remove unnecessary casts to int, str, float or bool ([`remove-unnecessary-cast`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/remove-unnecessary-cast/))
</issue_to_address>

### Comment 12
<location> `packages/astrbot/commands/admin.py:58-67` </location>
<code_context>
    async def dwl(self, event: AstrMessageEvent, sid: str = ""):
        """删除白名单。dwl <sid>"""
        if sid == "":
            event.set_result(
                MessageEventResult().message(
                    "使用方法: /dwl <id> 删除白名单。可通过 /sid 获取 ID。"
                )
            )
            return
        try:
            cfg = self.context.get_config(umo=event.unified_msg_origin)
            cfg["platform_settings"]["id_whitelist"].remove(str(sid))
            cfg.save_config()
            event.set_result(MessageEventResult().message("删除白名单成功。"))
        except ValueError:
            event.set_result(MessageEventResult().message("此 SID 不在白名单内。"))

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Replaces an empty collection equality with a boolean operation ([`simplify-empty-collection-comparison`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/simplify-empty-collection-comparison/))
- Remove unnecessary casts to int, str, float or bool ([`remove-unnecessary-cast`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/remove-unnecessary-cast/))
</issue_to_address>

### Comment 13
<location> `packages/astrbot/commands/alter_cmd.py:47` </location>
<code_context>
    async def alter_cmd(self, event: AstrMessageEvent):
        token = self.parse_commands(event.message_str)
        if token.len < 3:
            await event.send(
                MessageChain().message(
                    "该指令用于设置指令或指令组的权限。\n"
                    "格式: /alter_cmd <cmd_name> <admin/member>\n"
                    "例1: /alter_cmd c1 admin 将 c1 设为管理员指令\n"
                    "例2: /alter_cmd g1 c1 admin 将 g1 指令组的 c1 子指令设为管理员指令\n"
                    "/alter_cmd reset config 打开 reset 权限配置"
                )
            )
            return

        cmd_name = " ".join(token.tokens[1:-1])
        cmd_type = token.get(-1)

        if cmd_name == "reset" and cmd_type == "config":
            from astrbot.api import sp

            alter_cmd_cfg = await sp.global_get("alter_cmd", {})
            plugin_ = alter_cmd_cfg.get("astrbot", {})
            reset_cfg = plugin_.get("reset", {})

            group_unique_on = reset_cfg.get("group_unique_on", "admin")
            group_unique_off = reset_cfg.get("group_unique_off", "admin")
            private = reset_cfg.get("private", "member")

            config_menu = f"""reset命令权限细粒度配置
                当前配置:
                1. 群聊+会话隔离开: {group_unique_on}
                2. 群聊+会话隔离关: {group_unique_off}
                3. 私聊: {private}
                修改指令格式:
                /alter_cmd reset scene <场景编号> <admin/member>
                例如: /alter_cmd reset scene 2 member"""
            await event.send(MessageChain().message(config_menu))
            return

        if cmd_name == "reset" and cmd_type == "scene" and token.len >= 4:
            scene_num = token.get(3)
            perm_type = token.get(4)

            if scene_num is None or perm_type is None:
                await event.send(MessageChain().message("场景编号和权限类型不能为空"))
                return

            if not scene_num.isdigit() or int(scene_num) < 1 or int(scene_num) > 3:
                await event.send(
                    MessageChain().message("场景编号必须是 1-3 之间的数字")
                )
                return

            if perm_type not in ["admin", "member"]:
                await event.send(
                    MessageChain().message("权限类型错误,只能是 admin 或 member")
                )
                return

            scene_num = int(scene_num)
            scene = RstScene.from_index(scene_num)
            scene_key = scene.key

            await self.update_reset_permission(scene_key, perm_type)

            await event.send(
                MessageChain().message(
                    f"已将 reset 命令在{scene.name}场景下的权限设为{perm_type}"
                )
            )
            return

        if cmd_type not in ["admin", "member"]:
            await event.send(
                MessageChain().message("指令类型错误,可选类型有 admin, member")
            )
            return

        # 查找指令
        found_command = None
        cmd_group = False
        for handler in star_handlers_registry:
            assert isinstance(handler, StarHandlerMetadata)
            for filter_ in handler.event_filters:
                if isinstance(filter_, CommandFilter):
                    if filter_.equals(cmd_name):
                        found_command = handler
                        break
                elif isinstance(filter_, CommandGroupFilter):
                    if filter_.equals(cmd_name):
                        found_command = handler
                        cmd_group = True
                        break

        if not found_command:
            await event.send(MessageChain().message("未找到该指令"))
            return

        found_plugin = star_map[found_command.handler_module_path]

        from astrbot.api import sp

        alter_cmd_cfg = await sp.global_get("alter_cmd", {})
        plugin_ = alter_cmd_cfg.get(found_plugin.name, {})
        cfg = plugin_.get(found_command.handler_name, {})
        cfg["permission"] = cmd_type
        plugin_[found_command.handler_name] = cfg
        alter_cmd_cfg[found_plugin.name] = plugin_

        await sp.global_put("alter_cmd", alter_cmd_cfg)

        # 注入权限过滤器
        found_permission_filter = False
        for filter_ in found_command.event_filters:
            if isinstance(filter_, PermissionTypeFilter):
                if cmd_type == "admin":
                    import astrbot.api.event.filter as filter

                    filter_.permission_type = filter.PermissionType.ADMIN
                else:
                    import astrbot.api.event.filter as filter

                    filter_.permission_type = filter.PermissionType.MEMBER
                found_permission_filter = True
                break
        if not found_permission_filter:
            import astrbot.api.event.filter as filter

            found_command.event_filters.insert(
                0,
                PermissionTypeFilter(
                    filter.PermissionType.ADMIN
                    if cmd_type == "admin"
                    else filter.PermissionType.MEMBER
                ),
            )
        cmd_group_str = "指令组" if cmd_group else "指令"
        await event.send(
            MessageChain().message(
                f"已将「{cmd_name}」{cmd_group_str} 的权限级别调整为 {cmd_type}。"
            )
        )

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Hoist repeated code outside conditional statement ([`hoist-statement-from-if`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/hoist-statement-from-if/))
- Low code quality found in AlterCmdCommands.alter\_cmd - 10% ([`low-code-quality`](https://docs.sourcery.ai/Reference/Default-Rules/comments/low-code-quality/))

<br/><details><summary>Explanation</summary>
The quality score for this function is below the quality threshold of 25%.
This score is a combination of the method length, cognitive complexity and working memory.

How can you solve this?

It might be worth refactoring this function to make it shorter and more readable.

- Reduce the function length by extracting pieces of functionality out into
  their own functions. This is the most important thing you can do - ideally a
  function should be less than 10 lines.
- Reduce nesting, perhaps by introducing guard clauses to return early.
- Ensure that variables are tightly scoped, so that code using related concepts
  sits together within the function rather than being scattered.</details>
</issue_to_address>

### Comment 14
<location> `packages/astrbot/commands/conversation.py:146` </location>
<code_context>
    async def his(self, message: AstrMessageEvent, page: int = 1):
        """查看对话记录"""
        if not self.context.get_using_provider(message.unified_msg_origin):
            message.set_result(
                MessageEventResult().message("未找到任何 LLM 提供商。请先配置。")
            )
            return

        size_per_page = 6

        conv_mgr = self.context.conversation_manager
        umo = message.unified_msg_origin
        session_curr_cid = await conv_mgr.get_curr_conversation_id(umo)

        if not session_curr_cid:
            session_curr_cid = await conv_mgr.new_conversation(
                umo, message.get_platform_id()
            )

        contexts, total_pages = await conv_mgr.get_human_readable_context(
            umo, session_curr_cid, page, size_per_page
        )

        history = ""
        for context in contexts:
            if len(context) > 150:
                context = context[:150] + "..."
            history += f"{context}\n"

        ret = (
            f"当前对话历史记录:"
            f"{history if history else '无历史记录'}\n\n"
            f"第 {page} 页 | 共 {total_pages} 页\n"
            f"*输入 /history 2 跳转到第 2 页"
        )

        message.set_result(MessageEventResult().message(ret).use_t2i(False))

</code_context>

<issue_to_address>
**suggestion (code-quality):** Use f-string instead of string concatenation ([`use-fstring-for-concatenation`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-fstring-for-concatenation/))

```suggestion
                context = f"{context[:150]}..."
```
</issue_to_address>

### Comment 15
<location> `packages/astrbot/commands/conversation.py:158` </location>
<code_context>
    async def convs(self, message: AstrMessageEvent, page: int = 1):
        """查看对话列表"""

        provider = self.context.get_using_provider(message.unified_msg_origin)
        if provider and provider.meta().type == "dify":
            """原有的Dify处理逻辑保持不变"""
            ret = "Dify 对话列表:\n"
            assert isinstance(provider, ProviderDify)
            data = await provider.api_client.get_chat_convs(message.unified_msg_origin)
            idx = 1
            for conv in data["data"]:
                ts_h = datetime.datetime.fromtimestamp(conv["updated_at"]).strftime(
                    "%m-%d %H:%M"
                )
                ret += f"{idx}. {conv['name']}({conv['id'][:4]})\n  上次更新:{ts_h}\n"
                idx += 1
            if idx == 1:
                ret += "没有找到任何对话。"
            dify_cid = provider.conversation_ids.get(message.unified_msg_origin, None)
            ret += f"\n\n用户: {message.unified_msg_origin}\n当前对话: {dify_cid}\n使用 /switch <序号> 切换对话。"
            message.set_result(MessageEventResult().message(ret))
            return

        size_per_page = 6
        """获取所有对话列表"""
        conversations_all = await self.context.conversation_manager.get_conversations(
            message.unified_msg_origin
        )
        """计算总页数"""
        total_pages = (len(conversations_all) + size_per_page - 1) // size_per_page
        """确保页码有效"""
        page = max(1, min(page, total_pages))
        """分页处理"""
        start_idx = (page - 1) * size_per_page
        end_idx = start_idx + size_per_page
        conversations_paged = conversations_all[start_idx:end_idx]

        ret = "对话列表:\n---\n"
        """全局序号从当前页的第一个开始"""
        global_index = start_idx + 1

        """生成所有对话的标题字典"""
        _titles = {}
        for conv in conversations_all:
            title = conv.title if conv.title else "新对话"
            _titles[conv.cid] = title

        """遍历分页后的对话生成列表显示"""
        for conv in conversations_paged:
            persona_id = conv.persona_id
            if not persona_id or persona_id == "[%None]":
                persona = await self.context.persona_manager.get_default_persona_v3(
                    umo=message.unified_msg_origin
                )
                persona_id = persona["name"]
            title = _titles.get(conv.cid, "新对话")
            ret += f"{global_index}. {title}({conv.cid[:4]})\n  人格情景: {persona_id}\n  上次更新: {datetime.datetime.fromtimestamp(conv.updated_at).strftime('%m-%d %H:%M')}\n"
            global_index += 1

        ret += "---\n"
        curr_cid = await self.context.conversation_manager.get_curr_conversation_id(
            message.unified_msg_origin
        )
        if curr_cid:
            """从所有对话的标题字典中获取标题"""
            title = _titles.get(curr_cid, "新对话")
            ret += f"\n当前对话: {title}({curr_cid[:4]})"
        else:
            ret += "\n当前对话: 无"

        unique_session = self.context.get_config()["platform_settings"][
            "unique_session"
        ]
        if unique_session:
            ret += "\n会话隔离粒度: 个人"
        else:
            ret += "\n会话隔离粒度: 群聊"

        ret += f"\n第 {page} 页 | 共 {total_pages} 页"
        ret += "\n*输入 /ls 2 跳转到第 2 页"

        message.set_result(MessageEventResult().message(ret).use_t2i(False))
        return

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Use named expression to simplify assignment and conditional ([`use-named-expression`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-named-expression/))
- Low code quality found in ConversationCommands.convs - 23% ([`low-code-quality`](https://docs.sourcery.ai/Reference/Default-Rules/comments/low-code-quality/))

<br/><details><summary>Explanation</summary>
The quality score for this function is below the quality threshold of 25%.
This score is a combination of the method length, cognitive complexity and working memory.

How can you solve this?

It might be worth refactoring this function to make it shorter and more readable.

- Reduce the function length by extracting pieces of functionality out into
  their own functions. This is the most important thing you can do - ideally a
  function should be less than 10 lines.
- Reduce nesting, perhaps by introducing guard clauses to return early.
- Ensure that variables are tightly scoped, so that code using related concepts
  sits together within the function rather than being scattered.</details>
</issue_to_address>

### Comment 16
<location> `packages/astrbot/commands/conversation.py:407-408` </location>
<code_context>
    async def del_conv(self, message: AstrMessageEvent):
        """删除当前对话"""
        is_unique_session = self.context.get_config()["platform_settings"][
            "unique_session"
        ]
        if message.get_group_id() and not is_unique_session and message.role != "admin":
            # 群聊,没开独立会话,发送人不是管理员
            message.set_result(
                MessageEventResult().message(
                    f"会话处于群聊,并且未开启独立会话,并且您 (ID {message.get_sender_id()}) 不是管理员,因此没有权限删除当前对话。"
                )
            )
            return

        provider = self.context.get_using_provider(message.unified_msg_origin)
        if provider and provider.meta().type == "dify":
            assert isinstance(provider, ProviderDify)
            dify_cid = provider.conversation_ids.pop(message.unified_msg_origin, None)
            if dify_cid:
                await provider.api_client.delete_chat_conv(
                    message.unified_msg_origin, dify_cid
                )
            message.set_result(
                MessageEventResult().message(
                    "删除当前对话成功。不再处于对话状态,使用 /switch 序号 切换到其他对话或 /new 创建。"
                )
            )
            return

        session_curr_cid = (
            await self.context.conversation_manager.get_curr_conversation_id(
                message.unified_msg_origin
            )
        )

        if not session_curr_cid:
            message.set_result(
                MessageEventResult().message(
                    "当前未处于对话状态,请 /switch 序号 切换或 /new 创建。"
                )
            )
            return

        await self.context.conversation_manager.delete_conversation(
            message.unified_msg_origin, session_curr_cid
        )
        message.set_result(
            MessageEventResult().message(
                "删除当前对话成功。不再处于对话状态,使用 /switch 序号 切换到其他对话或 /new 创建。"
            )
        )

</code_context>

<issue_to_address>
**suggestion (code-quality):** Use named expression to simplify assignment and conditional ([`use-named-expression`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-named-expression/))

```suggestion
            if dify_cid := provider.conversation_ids.pop(
                message.unified_msg_origin, None
            ):
```
</issue_to_address>

### Comment 17
<location> `packages/astrbot/commands/llm.py:12-13` </location>
<code_context>
    async def llm(self, event: AstrMessageEvent):
        """开启/关闭 LLM"""
        cfg = self.context.get_config(umo=event.unified_msg_origin)
        enable = cfg["provider_settings"]["enable"]
        if enable:
            cfg["provider_settings"]["enable"] = False
            status = "关闭"
        else:
            cfg["provider_settings"]["enable"] = True
            status = "开启"
        cfg.save_config()
        await event.send(MessageChain().message(f"{status} LLM 聊天功能。"))

</code_context>

<issue_to_address>
**suggestion (code-quality):** Use named expression to simplify assignment and conditional ([`use-named-expression`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-named-expression/))

```suggestion
        if enable := cfg["provider_settings"]["enable"]:
```
</issue_to_address>

### Comment 18
<location> `packages/astrbot/commands/persona.py:33` </location>
<code_context>
    async def persona(self, message: AstrMessageEvent):
        l = message.message_str.split(" ")  # noqa: E741
        umo = message.unified_msg_origin

        curr_persona_name = "无"
        cid = await self.context.conversation_manager.get_curr_conversation_id(umo)
        default_persona = await self.context.persona_manager.get_default_persona_v3(
            umo=umo
        )
        curr_cid_title = "无"
        if cid:
            conv = await self.context.conversation_manager.get_conversation(
                unified_msg_origin=umo,
                conversation_id=cid,
                create_if_not_exists=True,
            )
            if conv is None:
                message.set_result(
                    MessageEventResult().message(
                        "当前对话不存在,请先使用 /new 新建一个对话。"
                    )
                )
                return
            if not conv.persona_id and not conv.persona_id == "[%None]":
                curr_persona_name = default_persona["name"]
            else:
                curr_persona_name = conv.persona_id

            curr_cid_title = conv.title if conv.title else "新对话"
            curr_cid_title += f"({cid[:4]})"

        if len(l) == 1:
            message.set_result(
                MessageEventResult()
                .message(
                    f"""[Persona]

- 人格情景列表: `/persona list`
- 设置人格情景: `/persona 人格`
- 人格情景详细信息: `/persona view 人格`
- 取消人格: `/persona unset`

默认人格情景: {default_persona["name"]}
当前对话 {curr_cid_title} 的人格情景: {curr_persona_name}

配置人格情景请前往管理面板-配置页
"""
                )
                .use_t2i(False)
            )
        elif l[1] == "list":
            msg = "人格列表:\n"
            for persona in self.context.provider_manager.personas:
                msg += f"- {persona['name']}\n"
            msg += "\n\n*输入 `/persona view 人格名` 查看人格详细信息"
            message.set_result(MessageEventResult().message(msg))
        elif l[1] == "view":
            if len(l) == 2:
                message.set_result(MessageEventResult().message("请输入人格情景名"))
                return
            ps = l[2].strip()
            if persona := next(
                builtins.filter(
                    lambda persona: persona["name"] == ps,
                    self.context.provider_manager.personas,
                ),
                None,
            ):
                msg = f"人格{ps}的详细信息:\n"
                msg += f"{persona['prompt']}\n"
            else:
                msg = f"人格{ps}不存在"
            message.set_result(MessageEventResult().message(msg))
        elif l[1] == "unset":
            if not cid:
                message.set_result(
                    MessageEventResult().message("当前没有对话,无法取消人格。")
                )
                return
            await self.context.conversation_manager.update_conversation_persona_id(
                message.unified_msg_origin, "[%None]"
            )
            message.set_result(MessageEventResult().message("取消人格成功。"))
        else:
            ps = "".join(l[1:]).strip()
            if not cid:
                message.set_result(
                    MessageEventResult().message(
                        "当前没有对话,请先开始对话或使用 /new 创建一个对话。"
                    )
                )
                return
            if persona := next(
                builtins.filter(
                    lambda persona: persona["name"] == ps,
                    self.context.provider_manager.personas,
                ),
                None,
            ):
                await self.context.conversation_manager.update_conversation_persona_id(
                    message.unified_msg_origin, ps
                )
                message.set_result(
                    MessageEventResult().message(
                        "设置成功。如果您正在切换到不同的人格,请注意使用 /reset 来清空上下文,防止原人格对话影响现人格。"
                    )
                )
            else:
                message.set_result(
                    MessageEventResult().message(
                        "不存在该人格情景。使用 /persona list 查看所有。"
                    )
                )

</code_context>

<issue_to_address>
**suggestion (code-quality):** We've found these issues:

- Simplify logical expression using De Morgan identities ([`de-morgan`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/de-morgan/))
- Low code quality found in PersonaCommands.persona - 19% ([`low-code-quality`](https://docs.sourcery.ai/Reference/Default-Rules/comments/low-code-quality/))

```suggestion
            if not conv.persona_id and conv.persona_id != "[%None]":
```

<br/><details><summary>Explanation</summary>
The quality score for this function is below the quality threshold of 25%.
This score is a combination of the method length, cognitive complexity and working memory.

How can you solve this?

It might be worth refactoring this function to make it shorter and more readable.

- Reduce the function length by extracting pieces of functionality out into
  their own functions. This is the most important thing you can do - ideally a
  function should be less than 10 lines.
- Reduce nesting, perhaps by introducing guard clauses to return early.
- Ensure that variables are tightly scoped, so that code using related concepts
  sits together within the function rather than being scattered.</details>
</issue_to_address>

### Comment 19
<location> `packages/astrbot/commands/plugin.py:105-115` </location>
<code_context>
    async def plugin_help(self, event: AstrMessageEvent, plugin_name: str = ""):
        """获取插件帮助"""
        if not plugin_name:
            event.set_result(
                MessageEventResult().message("/plugin help <插件名> 查看插件信息。")
            )
            return
        plugin = self.context.get_registered_star(plugin_name)
        if plugin is None:
            event.set_result(MessageEventResult().message("未找到此插件。"))
            return
        help_msg = ""
        help_msg += f"\n\n✨ 作者: {plugin.author}\n✨ 版本: {plugin.version}"
        command_handlers = []
        command_names = []
        for handler in star_handlers_registry:
            assert isinstance(handler, StarHandlerMetadata)
            if handler.handler_module_path != plugin.module_path:
                continue
            for filter_ in handler.event_filters:
                if isinstance(filter_, CommandFilter):
                    command_handlers.append(handler)
                    command_names.append(filter_.command_name)
                    break
                elif isinstance(filter_, CommandGroupFilter):
                    command_handlers.append(handler)
                    command_names.append(filter_.group_name)

        if len(command_handlers) > 0:
            help_msg += "\n\n🔧 指令列表:\n"
            for i in range(len(command_handlers)):
                help_msg += f"- {command_names[i]}"
                if command_handlers[i].desc:
                    help_msg += f": {command_handlers[i].desc}"
                help_msg += "\n"

            help_msg += "\nTip: 指令的触发需要添加唤醒前缀,默认为 /。"

        ret = f"🧩 插件 {plugin_name} 帮助信息:\n" + help_msg
        ret += "更多帮助信息请查看插件仓库 README。"
        event.set_result(MessageEventResult().message(ret).use_t2i(False))

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Simplify sequence length comparison ([`simplify-len-comparison`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/simplify-len-comparison/))
- Use f-string instead of string concatenation ([`use-fstring-for-concatenation`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-fstring-for-concatenation/))
</issue_to_address>

### Comment 20
<location> `packages/astrbot/commands/provider.py:12` </location>
<code_context>
    async def provider(
        self, event: AstrMessageEvent, idx: Union[str, int, None] = None, idx2: Union[int, None] = None
    ):
        """查看或者切换 LLM Provider"""
        umo = event.unified_msg_origin

        if idx is None:
            ret = "## 载入的 LLM 提供商\n"
            for idx, llm in enumerate(self.context.get_all_providers()):
                id_ = llm.meta().id
                ret += f"{idx + 1}. {id_} ({llm.meta().model})"
                provider_using = self.context.get_using_provider(umo=umo)
                if provider_using and provider_using.meta().id == id_:
                    ret += " (当前使用)"
                ret += "\n"

            tts_providers = self.context.get_all_tts_providers()
            if tts_providers:
                ret += "\n## 载入的 TTS 提供商\n"
                for idx, tts in enumerate(tts_providers):
                    id_ = tts.meta().id
                    ret += f"{idx + 1}. {id_}"
                    tts_using = self.context.get_using_tts_provider(umo=umo)
                    if tts_using and tts_using.meta().id == id_:
                        ret += " (当前使用)"
                    ret += "\n"

            stt_providers = self.context.get_all_stt_providers()
            if stt_providers:
                ret += "\n## 载入的 STT 提供商\n"
                for idx, stt in enumerate(stt_providers):
                    id_ = stt.meta().id
                    ret += f"{idx + 1}. {id_}"
                    stt_using = self.context.get_using_stt_provider(umo=umo)
                    if stt_using and stt_using.meta().id == id_:
                        ret += " (当前使用)"
                    ret += "\n"

            ret += "\n使用 /provider <序号> 切换 LLM 提供商。"

            if tts_providers:
                ret += "\n使用 /provider tts <序号> 切换 TTS 提供商。"
            if stt_providers:
                ret += "\n使用 /provider stt <切换> STT 提供商。"

            event.set_result(MessageEventResult().message(ret))
        elif idx == "tts":
            if idx2 is None:
                event.set_result(MessageEventResult().message("请输入序号。"))
                return
            else:
                if idx2 > len(self.context.get_all_tts_providers()) or idx2 < 1:
                    event.set_result(MessageEventResult().message("无效的序号。"))
                provider = self.context.get_all_tts_providers()[idx2 - 1]
                id_ = provider.meta().id
                await self.context.provider_manager.set_provider(
                    provider_id=id_,
                    provider_type=ProviderType.TEXT_TO_SPEECH,
                    umo=umo,
                )
                event.set_result(MessageEventResult().message(f"成功切换到 {id_}。"))
        elif idx == "stt":
            if idx2 is None:
                event.set_result(MessageEventResult().message("请输入序号。"))
                return
            else:
                if idx2 > len(self.context.get_all_stt_providers()) or idx2 < 1:
                    event.set_result(MessageEventResult().message("无效的序号。"))
                provider = self.context.get_all_stt_providers()[idx2 - 1]
                id_ = provider.meta().id
                await self.context.provider_manager.set_provider(
                    provider_id=id_,
                    provider_type=ProviderType.SPEECH_TO_TEXT,
                    umo=umo,
                )
                event.set_result(MessageEventResult().message(f"成功切换到 {id_}。"))
        elif isinstance(idx, int):
            if idx > len(self.context.get_all_providers()) or idx < 1:
                event.set_result(MessageEventResult().message("无效的序号。"))

            provider = self.context.get_all_providers()[idx - 1]
            id_ = provider.meta().id
            await self.context.provider_manager.set_provider(
                provider_id=id_,
                provider_type=ProviderType.CHAT_COMPLETION,
                umo=umo,
            )
            event.set_result(MessageEventResult().message(f"成功切换到 {id_}。"))
        else:
            event.set_result(MessageEventResult().message("无效的参数。"))

</code_context>

<issue_to_address>
**issue (code-quality):** Low code quality found in ProviderCommands.provider - 11% ([`low-code-quality`](https://docs.sourcery.ai/Reference/Default-Rules/comments/low-code-quality/))

<br/><details><summary>Explanation</summary>The quality score for this function is below the quality threshold of 25%.
This score is a combination of the method length, cognitive complexity and working memory.

How can you solve this?

It might be worth refactoring this function to make it shorter and more readable.

- Reduce the function length by extracting pieces of functionality out into
  their own functions. This is the most important thing you can do - ideally a
  function should be less than 10 lines.
- Reduce nesting, perhaps by introducing guard clauses to return early.
- Ensure that variables are tightly scoped, so that code using related concepts
  sits together within the function rather than being scattered.</details>
</issue_to_address>

### Comment 21
<location> `packages/astrbot/commands/provider.py:117` </location>
<code_context>
    async def model_ls(
        self, message: AstrMessageEvent, idx_or_name: Union[int, str, None] = None
    ):
        """查看或者切换模型"""
        prov = self.context.get_using_provider(message.unified_msg_origin)
        if not prov:
            message.set_result(
                MessageEventResult().message("未找到任何 LLM 提供商。请先配置。")
            )
            return
        # 定义正则表达式匹配 API 密钥
        api_key_pattern = re.compile(r"key=[^&'\" ]+")

        if idx_or_name is None:
            models = []
            try:
                models = await prov.get_models()
            except BaseException as e:
                err_msg = api_key_pattern.sub("key=***", str(e))
                message.set_result(
                    MessageEventResult()
                    .message("获取模型列表失败: " + err_msg)
                    .use_t2i(False)
                )
                return
            i = 1
            ret = "下面列出了此服务提供商可用模型:"
            for model in models:
                ret += f"\n{i}. {model}"
                i += 1

            curr_model = prov.get_model() or "无"
            ret += f"\n当前模型: [{curr_model}]"

            ret += "\nTips: 使用 /model <模型名/编号>,即可实时更换模型。如目标模型不存在于上表,请输入模型名。"
            message.set_result(MessageEventResult().message(ret).use_t2i(False))
        else:
            if isinstance(idx_or_name, int):
                models = []
                try:
                    models = await prov.get_models()
                except BaseException as e:
                    message.set_result(
                        MessageEventResult().message("获取模型列表失败: " + str(e))
                    )
                    return
                if idx_or_name > len(models) or idx_or_name < 1:
                    message.set_result(MessageEventResult().message("模型序号错误。"))
                else:
                    try:
                        new_model = models[idx_or_name - 1]
                        prov.set_model(new_model)
                    except BaseException as e:
                        message.set_result(
                            MessageEventResult().message("切换模型未知错误: " + str(e))
                        )
                    message.set_result(MessageEventResult().message("切换模型成功。"))
            else:
                prov.set_model(idx_or_name)
                message.set_result(
                    MessageEventResult().message(f"切换模型到 {prov.get_model()}。")
                )

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Use f-string instead of string concatenation [×3] ([`use-fstring-for-concatenation`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-fstring-for-concatenation/))
- Move assignment closer to its usage within a block ([`move-assign-in-block`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/move-assign-in-block/))
- Replace manual loop counter with call to enumerate ([`convert-to-enumerate`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/convert-to-enumerate/))
</issue_to_address>

### Comment 22
<location> `packages/astrbot/commands/provider.py:195-197` </location>
<code_context>
    async def key(self, message: AstrMessageEvent, index: Union[int, None] = None):
        prov = self.context.get_using_provider(message.unified_msg_origin)
        if not prov:
            message.set_result(
                MessageEventResult().message("未找到任何 LLM 提供商。请先配置。")
            )
            return

        if index is None:
            keys_data = prov.get_keys()
            curr_key = prov.get_current_key()
            ret = "Key:"
            for i, k in enumerate(keys_data):
                ret += f"\n{i + 1}. {k[:8]}"

            ret += f"\n当前 Key: {curr_key[:8]}"
            ret += "\n当前模型: " + prov.get_model()
            ret += "\n使用 /key <idx> 切换 Key。"

            message.set_result(MessageEventResult().message(ret).use_t2i(False))
        else:
            keys_data = prov.get_keys()
            if index > len(keys_data) or index < 1:
                message.set_result(MessageEventResult().message("Key 序号错误。"))
            else:
                try:
                    new_key = keys_data[index - 1]
                    prov.set_key(new_key)
                except BaseException as e:
                    message.set_result(
                        MessageEventResult().message("切换 Key 未知错误: " + str(e))
                    )
                message.set_result(MessageEventResult().message("切换 Key 成功。"))

</code_context>

<issue_to_address>
**suggestion (code-quality):** Use f-string instead of string concatenation ([`use-fstring-for-concatenation`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-fstring-for-concatenation/))

```suggestion
                    message.set_result(MessageEventResult().message(f"切换 Key 未知错误: {str(e)}"))
```
</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.

Soulter and others added 8 commits October 1, 2025 14:24
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
@Soulter Soulter merged commit afe007c into master Oct 1, 2025
5 checks passed
@Soulter Soulter deleted the refactor/package-astrbot branch October 18, 2025 01:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants