如何在不重启discord.py机器人的情况下更新命令选择项?
我用discord.py开发的机器人依赖一个频繁更新的数据库,其中有个团队机器人命令会提供从数据库表中获取的课程主题选择项。目前功能正常,但机器人运行时如果数据库表更新了,必须重启机器人才能显示新的选择项。
原命令代码:
@app_commands.command() @app_commands.default_permissions(administrator=True) @app_commands.choices(lesson=lesson_choices()) async def create_or_update_mark(self, interaction: Interaction, student: discord.Member, lesson: Choice[str], logiks: app_commands.Range[int, 0, 8]): with MarkRepository(student_id=student.id, lesson_title=lesson.value) as lr: lr.create_or_update(mark_dto=MarkCreateDTO(logiks=logiks)) await interaction.response.send_message("logiks has been changed", ephemeral=True)
选择项列表来自lesson_choices函数:
def lesson_choices() -> list[Choice[str]]: return [ Choice(name=lesson.title, value=lesson.title) for lesson in LessonRepository.get_all_lessons() ]
显然装饰器在代码启动时就确定了选择项,这不符合需求。我尝试用autocomplete但没成功实现基础选项,可能是实现方式有误。
尝试的autocomplete命令代码:
@app_commands.command() @app_commands.default_permissions(administrator=True) @app_commands.autocomplete(lesson=lesson_autocomplete) async def create_or_update_mark(self, interaction: Interaction, student: discord.Member, lesson: str, logiks: app_commands.Range[int, 0, 8]): with MarkRepository(student_id=student.id, lesson_title=lesson) as lr: lr.create_or_update(mark_dto=MarkCreateDTO(logiks=logiks)) await interaction.response.send_message("logiks has been changed", ephemeral=True)
lesson_autocomplete函数:
async def lesson_autocomplete(interaction: Interaction, current: str) -> list[app_commands.Choice[str]]: lessons = [lesson_dto.title for lesson_dto in LessonRepository.get_all_lessons()] print(lessons) return [ app_commands.Choice(name=lesson, value=lesson) for lesson in lessons if current.lower() in lesson.lower() ]
请问有什么方法可以在不重启机器人的情况下更新选择项?
用autocomplete的思路是对的,问题出在自动补全函数的逻辑和细节处理上,以下是修正方案:
1. 修正自动补全函数逻辑
当用户未输入任何内容(current为空)时,直接返回所有课程选项,同时遵守Discord API最多返回25个选项的限制:
async def lesson_autocomplete(interaction: Interaction, current: str) -> list[app_commands.Choice[str]]: lessons = [lesson_dto.title for lesson_dto in LessonRepository.get_all_lessons()] # 未输入内容时返回所有课程(最多25个),输入后按关键词过滤 if not current: return [app_commands.Choice(name=lesson, value=lesson) for lesson in lessons[:25]] # 过滤匹配的课程,同样限制数量 filtered = [lesson for lesson in lessons if current.lower() in lesson.lower()] return [app_commands.Choice(name=lesson, value=lesson) for lesson in filtered[:25]]
2. 确保命令同步生效
修改自动补全逻辑后,需要重新同步命令到Discord服务器,不需要重启机器人,执行以下代码即可:
# 针对特定服务器同步(速度快,适合团队机器人) await tree.sync(guild=discord.Object(id=你的服务器ID)) # 全局同步(需Discord审核,不建议频繁操作) # await tree.sync()
3. 验证数据库查询的实时性
确认LessonRepository.get_all_lessons()每次调用都直接从数据库拉取最新数据,没有使用静态缓存。如果有缓存机制,需移除或增加缓存失效逻辑,保证每次自动补全都能获取最新课程列表。
4. 增加课程合法性校验(可选)
自动补全允许用户手动输入内容,为避免无效课程,可在命令逻辑中校验输入的课程是否存在:
async def create_or_update_mark(self, interaction: Interaction, student: discord.Member, lesson: str, logiks: app_commands.Range[int, 0, 8]): # 校验课程是否存在 lesson_titles = [lesson_dto.title for lesson_dto in LessonRepository.get_all_lessons()] if lesson not in lesson_titles: await interaction.response.send_message("该课程不存在,请选择列表中的选项", ephemeral=True) return # 原业务逻辑 with MarkRepository(student_id=student.id, lesson_title=lesson) as lr: lr.create_or_update(mark_dto=MarkCreateDTO(logiks=logiks)) await interaction.response.send_message("logiks分数已更新", ephemeral=True)
为什么原choices装饰器不行?
@app_commands.choices是在代码加载阶段执行的,只会初始化一次选择项,后续数据库更新不会触发重新查询,必须重启机器人才能生效。而autocomplete是用户每次触发命令选择时实时调用函数获取最新选项,完全适配动态更新的需求。
内容的提问来源于stack exchange,提问作者Magan

