
本文详细介绍了在 `discord.py` 机器人中,如何实现基于数据库动态更新的命令选择项,避免因数据库变更而需要重启机器人。通过利用 `app_commands.autocomplete` 结合 `app_commands.Transformer` 和本地缓存机制,我们能够构建高效、响应迅速且上下文感知的交互式选项,同时强调异步数据库操作和智能模糊匹配的重要性。
在 discord.py 开发中,当机器人命令的选项(choices)依赖于频繁更新的数据库内容时,直接使用 @app_commands.choices 装饰器会遇到一个常见问题:这些选项在机器人启动时被静态加载,无法在运行时动态更新。这意味着如果数据库中的数据发生变化,除非重启机器人,否则命令选项将不会反映这些新更改。为了解决这一问题,discord.py 提供了 app_commands.autocomplete 和 app_commands.Transformer 机制,允许我们构建高度动态且响应迅速的命令选项。
考虑以下场景,一个机器人需要提供课程标题作为命令选项,而这些课程标题存储在数据库中:
def lesson_choices() -> list[Choice[str]]:
# 假设 LessonRepository.get_all_lessons() 从数据库获取所有课程
return [
Choice(name=lesson.title, value=lesson.title)
for lesson in LessonRepository.get_all_lessons()
]
@app_commands.command()
@app_commands.default_permissions(administrator=True)
@app_commands.choices(lesson=lesson_choices()) # 这里的 choices 是静态的
async def create_or_update_mark(self, interaction: Interaction,
student: discord.Member,
lesson: Choice[str],
logiks: app_commands.Range[int, 0, 8]):
# ... 命令逻辑 ...这种方法的问题在于 lesson_choices() 函数在机器人启动时只被调用一次,其返回的选项列表被硬编码到命令定义中。如果 LessonRepository 中的数据在机器人运行期间更新,lesson 参数的可用选项将不会随之改变。
为了实现动态更新,discord.py 提供了 autocomplete 回调函数。当用户开始输入命令参数时,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()]
# 简单的模糊匹配,可能不够智能
return [
app_commands.Choice(name=lesson, value=lesson)
for lesson in lessons if current.lower() in lesson.lower()
]
@app_commands.command()
@app_commands.default_permissions(administrator=True)
@app_commands.autocomplete(lesson=lesson_autocomplete) # 使用 autocomplete
async def create_or_update_mark(self, interaction: Interaction,
student: discord.Member,
lesson: str, # 注意这里 lesson 的类型变为 str
logiks: app_commands.Range[int, 0, 8]):
# ... 命令逻辑 ...尽管 autocomplete 实现了动态性,但上述实现存在以下问题:
Explainpaper
阅读学术论文的更好方法,你的学术论文阅读助手。
89
查看详情
为了彻底解决上述问题,推荐使用 discord.py 的 app_commands.Transformer 结合本地缓存和异步数据库操作。Transformer 允许我们封装复杂的参数处理逻辑,包括 autocomplete 和最终参数值的转换。
以下是一个完整的示例,展示如何使用 Transformer 实现动态、高效的命令选项:
import discord
from discord.ext import commands
from discord import app_commands
import difflib
from typing import TYPE_CHECKING, Dict, List, Any, Optional, TypeAlias, Union
# 定义类型别名,提高可读性
GUILD_ID: TypeAlias = int
MEMBER_ID: TypeAlias = int
LESSON_ID: TypeAlias = int
# 假设的课程数据模型
class Lesson:
id: int
title: str
# 可以添加其他课程相关数据
def __init__(self, id: int, title: str):
self.id = id
self.title = title
# 模拟数据库操作层
class LessonRepository:
_lessons_db: Dict[int, Lesson] = {} # 模拟数据库存储
@staticmethod
async def get_all_lessons() -> List[Lesson]:
# 模拟异步数据库查询
await discord.utils.sleep_until_next_event_loop_tick() # 模拟IO等待
return list(LessonRepository._lessons_db.values())
@staticmethod
async def get_lesson_by_id(lesson_id: int) -> Optional[Lesson]:
await discord.utils.sleep_until_next_event_loop_tick()
return LessonRepository._lessons_db.get(lesson_id)
@staticmethod
async def add_lesson(lesson: Lesson):
await discord.utils.sleep_until_next_event_loop_tick()
LessonRepository._lessons_db[lesson.id] = lesson
@staticmethod
async def update_lesson_title(lesson_id: int, new_title: str):
await discord.utils.sleep_until_next_event_loop_tick()
if lesson_id in LessonRepository._lessons_db:
LessonRepository._lessons_db[lesson_id].title = new_title
# 机器人主类
class MyBot(commands.Bot):
# 类型检查时用于提示存在此函数
if TYPE_CHECKING:
some_function_for_loading_the_lessons_cache: Any
def __init__(self, *args: Any, **kwargs: Any):
super().__init__(*args, **kwargs)
# 缓存结构:{guild_id: {student_id: {lesson_id: Lesson}}}
# 实际应用中,如果课程不与特定学生或服务器绑定,可以简化缓存结构
self.lessons_cache: Dict[GUILD_ID, Dict[MEMBER_ID, Dict[LESSON_ID, Lesson]]] = {}
async def setup_hook(self):
"""
此异步函数在机器人启动时调用一次,用于加载缓存和同步应用命令。
"""
print("Bot setup_hook called.")
# 同步所有应用命令到 Discord
await self.tree.sync()
print("Application commands synced.")
# 首次加载缓存数据
await self._load_lessons_cache()
print("Lessons cache loaded.")
async def _load_lessons_cache(self):
"""
从数据库加载所有课程到缓存中。
实际应用中,可能需要根据 guild_id 和 member_id 进行更细致的加载。
这里为了演示,假设所有课程对所有学生和服务器都可用。
"""
all_lessons = await LessonRepository.get_all_lessons()
# 简化缓存逻辑,假设所有课程适用于所有服务器和学生
# 实际中,你可能需要根据用户或服务器ID来过滤课程
# 这里只是一个演示如何填充缓存的通用方法
# 假设一个默认的 guild_id 和 student_id 来存储所有课程
# 在实际应用中,这部分逻辑需要根据你的业务需求进行调整
# 例如,可以从数据库中获取所有有效的 guild_id 和 student_id
# 为演示目的,我们假设一个虚拟的 guild_id 和 student_id
virtual_guild_id = 1 # 替换为你的实际 guild_id
virtual_student_id = 1 # 替换为你的实际 student_id
if virtual_guild_id not in self.lessons_cache:
self.lessons_cache[virtual_guild_id] = {}
if virtual_student_id not in self.lessons_cache[virtual_guild_id]:
self.lessons_cache[virtual_guild_id][virtual_student_id] = {}
for lesson in all_lessons:
self.lessons_cache[virtual_guild_id][virtual_student_id][lesson.id] = lesson
print(f"Cache content after load: {self.lessons_cache}")
async def update_lesson_in_cache(self, lesson_id: int, new_title: str):
"""
更新缓存中的课程信息,模拟数据库更新后的缓存同步。
"""
# 遍历所有缓存层级,更新对应的课程
for guild_id in self.lessons_cache:
for student_id in self.lessons_cache[guild_id]:
if lesson_id in self.lessons_cache[guild_id][student_id]:
self.lessons_cache[guild_id][student_id][lesson_id].title = new_title
print(f"Cache updated for lesson ID {lesson_id} with new title: {new_title}")
return
# 课程 Transformer
class LessonTransformer(app_commands.Transformer):
async def find_similar_lesson_titles(self, lessons: Dict[LESSON_ID, Lesson], title: str) -> Dict[LESSON_ID, Lesson]:
"""
使用 difflib 查找相似的课程标题。
"""
lesson_titles = [lesson.title for lesson in lessons.values()]
# 获取与输入标题最接近的15个匹配项,cutoff=0.6表示相似度阈值
similar_titles = difflib.get_close_matches(title, lesson_titles, n=15, cutoff=0.6)
# 根据相似标题构建返回字典
result_lessons = {}
for lesson_id, lesson_obj in lessons.items():
if lesson_obj.title in similar_titles:
result_lessons[lesson_id] = lesson_obj
return result_lessons
async def autocomplete(self, interaction: discord.Interaction[MyBot], value: str, /) -> List[app_commands.Choice[str]]:
"""
提供课程名称的自动补全建议。
"""
# 前提:此命令只能在服务器(guild)中调用
assert interaction.guild is not None
# 检查用户是否已填写“student”参数,以便进行更精确的过滤
student: Optional[discord.Member] = interaction.namespace.get('student')
# 获取当前服务器的所有课程
guild_lessons_cache: Dict[MEMBER_ID, Dict[LESSON_ID, Lesson]] = interaction.client.lessons_cache.get(
interaction.guild.id, {}
)
if student is None:
# 如果没有指定学生,则显示所有学生的课程(扁平化处理)
flat_lessons: Dict[LESSON_ID, Lesson] = {}
for student_lessons in guild_lessons_cache.values():
flat_lessons.update(student_lessons)
similar_lessons = await self.find_similar_lesson_titles(flat_lessons, value)
else:
# 如果指定了学生,则只显示该学生的课程
student_lessons: Dict[LESSON_ID, Lesson] = guild_lessons_cache.get(student.id, {})
similar_lessons = await self.find_similar_lesson_titles(student_lessons, value)
# 返回自动补全选项,value 存储课程ID
return [
app_commands.Choice(name=lesson.title, value=str(lesson_id))
for lesson_id, lesson in similar_lessons.items()
]
async def transform(self, interaction: discord.Interaction[MyBot], value: str, /) -> Union[Lesson, LESSON_ID]:
"""
将用户输入的字符串值转换为 Lesson 对象或课程ID。
"""
# 前提:此命令只能在服务器(guild)中调用
assert interaction.guild is not None
# 自动补全只是建议,最终用户可能输入任意值,需要进行验证
if not value.isdigit():
# 如果不是数字,说明用户没有选择自动补全的建议(其value是ID),而是手动输入了文本
# 此时可以尝试根据文本查找,或者抛出错误
raise app_commands.AppCommandError("无效的课程ID或名称。请从建议列表中选择。")
lesson_id = int(value)
student: Optional[discord.Member] = interaction.namespace.get('student')
# 从缓存中查找课程
guild_lessons_cache = interaction.client.lessons_cache.get(interaction.guild.id, {})
if student is None:
# 如果没有指定学生,需要遍历所有学生来查找课程
for student_lessons in guild_lessons_cache.values():
lesson = student_lessons.get(lesson_id)
if lesson:
return lesson # 找到课程对象
# 如果遍历所有学生都没找到,则返回ID,让后续逻辑处理验证
return lesson_id
else:
# 如果指定了学生,直接从该学生的课程中查找
student_lessons = guild_lessons_cache.get(student.id, {})
lesson = student_lessons.get(lesson_id)
if lesson is None:
raise app_commands.AppCommandError("无效的课程ID或该学生没有此课程。")
return lesson
# 命令 Cog
class MarkCog(commands.Cog):
def __init__(self, bot: MyBot):
self.bot = bot
@app_commands.command(name="update_mark", description="创建或更新学生成绩")
@app_commands.guild_only() # 确保只在服务器中使用
@app_commands.default_permissions(administrator=True)
async def create_or_update_mark(
self,
interaction: discord.Interaction[MyBot],
student: discord.Member,
# 使用 Transform 装饰器,将 lesson 参数的类型处理委托给 LessonTransformer
lesson: app_commands.Transform[Union[Lesson, LESSON_ID], LessonTransformer],
logiks: app_commands.Range[int, 0, 8],
):
assert interaction.guild is not None
# Transformer 返回的 lesson 可能是 Lesson 对象或 LESSON_ID (int)
# 需要在这里进行最终的类型检查和验证
if isinstance(lesson, int):
# 如果 Transformer 返回的是 ID,说明在 transform 阶段没有找到具体的 Lesson 对象
# 此时需要再次从缓存中查找并验证,或者抛出错误
potential_lesson = None
guild_lessons_cache = interaction.client.lessons_cache.get(interaction.guild.id, {})
student_lessons = guild_lessons_cache.get(student.id, {})
potential_lesson = student_lessons.get(lesson)
if potential_lesson is None:
await interaction.response.send_message("无法找到指定的课程或该学生没有此课程。", ephemeral=True)
return
lesson = potential_lesson
# 至此,lesson 变量保证是一个 Lesson 对象
# 在这里执行你的业务逻辑,例如更新数据库
# 假设 MarkRepository 是异步的
# with MarkRepository(student_id=student.id, lesson_title=lesson.title) as lr:
# lr.create_or_update(mark_dto=MarkCreateDTO(logiks=logiks))
# 模拟更新数据库和缓存
# await LessonRepository.update_lesson_title(lesson.id, f"{lesson.title}_updated")
# await self.bot.update_lesson_in_cache(lesson.id, f"{lesson.title}_updated")
await interaction.response.send_message(
f"学生 {student.display_name} 的课程 '{lesson.title}' 成绩已更新为 {logiks}。",
ephemeral=True
)
# 机器人启动和添加 Cog
async def main():
# 模拟初始化一些课程数据
await LessonRepository.add_lesson(Lesson(id=101, title="数学基础"))
await LessonRepository.add_lesson(Lesson(id=102, title="物理概论"))
await LessonRepository.add_lesson(Lesson(id=103, title="化学实验"))
await LessonRepository.add_lesson(Lesson(id=104, title="生物探索"))
await LessonRepository.add_lesson(Lesson(id=201, title="高级编程"))
await LessonRepository.add_lesson(Lesson(id=202, title="数据结构"))
intents = discord.Intents.default()
intents.members = True # 如果需要获取成员信息,请开启此意图
intents.message_content = True # 如果需要处理消息内容,请开启此意图
bot = MyBot(command_prefix="!", intents=intents)
await bot.add_cog(MarkCog(bot))
# 替换为你的 Bot Token
await bot.start("YOUR_BOT_TOKEN")
if __name__ == "__main__":
import asyncio
asyncio.run(main())Lesson 类和 LessonRepository:
MyBot 类:
LessonTransformer 类:
以上就是Discord.py 动态命令选项:无需重启更新数据库驱动的交互式选择的详细内容,更多请关注其它相关文章!
# 遍历
# ip项目的营销推广方案设计
# seo基本步骤
# 医疗网站建设定做
# 自驾旅游营销推广文案
# 宿州网站建设工作室
# 白云区律师网站建设培训
# 惠阳家具网站建设报价
# 建设集团网站设计推荐
# 许昌网站排名优化怎么选
# 通化模板网站建设价格
# 实际应用
# 在这里
# 他已
# python
# 数据库中
# 是一个
# 重启
# 启动时
# 回调
# 加载
# 标准库
# 常见问题
# ai
# 回调函数
# app
# 编码
# git
相关栏目:
【
Google疑问12 】
【
Facebook疑问10 】
【
优化推广96088 】
【
技术知识133117 】
【
IDC资讯59369 】
【
网络运营7196 】
【
IT资讯61894 】
相关推荐:
mysql离线安装后如何启动_mysql离线安装完成后启动服务的方法
AI图层蒙版怎么用_AI图层蒙版应用技巧与设计实例
《三角洲行动》战斗步枪与机枪类改装代码分享
解决J*aScript动态图片上传中ID重复问题:在同一页面显示多张独立图片
lol小红书怎么|直播|?lol小红书|直播|是什么意思?
暴风影音官网正式版_暴风影音手机版官网下载安卓
解决SQLAlchemy模型跨文件关联的Linter兼容性指南
胃动力不足?试试这5个调理方法
申通快递查询 申通物流快递单实时查询入口
PySimpleGUI中实现键盘按键与按钮事件绑定教程
CSS绝对定位与溢出控制:实现背景元素局部显示不触发滚动条
J*a中为什么强调组合优于继承_组合模式带来的灵活性与可维护性解析
精通VS Code多光标编辑以实现闪电般快速的修改
b站如何剪辑视频_b站必剪app使用教程
rabbitmq 持久化有什么缺点?
顺丰快递在线查询系统 顺丰快递官方查单入口
微信朋友圈怎么设置三天可见 微信朋友圈设置指定天数可见步骤【教程】
Python自动化抓取GBGB赛狗比赛结果:日期范围与赛道筛选教程
Win10如何查看已安装的更新补丁 Win10卸载指定更新教程【教程】
Lar*el Eloquent中通过Join查询关联数据表:解决多行子查询问题
Win10显卡驱动安装失败怎么办 Win10使用DDU彻底卸载驱动【解决】
天天漫画2025最新入口 天天漫画永久有效登录入口
店铺如何做视频号推广?做视频号推广有用吗?
Win10共享文件夹设置方法 Win10局域网文件共享全攻略【教程】
Firefox OS应用开发:解决XMLHttpRequest跨域请求阻塞问题
解决Pandas DataFrame高度碎片化警告:高效创建多列的策略
抖音小程序怎么开通?小程序开通条件是什么?
口腔诊所管理软件推荐
创客贴登录页面入口 创客贴网页版最新网址链接
win11讲述人怎么关闭 Win11屏幕朗读辅助功能禁用方法【技巧】
《三国:谋定天下》平民全阶段通用阵容
《绿竹漫游》关闭消息通知方法
汽水音乐车机版官网5.0 汽水音乐车机版5.0版本下载入口
PHP odbc_fetch_array 返回值处理:如何正确访问嵌套数组元素
苹果iPhone14ProMax如何新建AppleID_iPhone14ProMax新建AppleID具体流程
vivo手机视频通话美颜怎么设置_vivo视频通话美颜开启方法
《兴业银行》注册登录方法
邮政快递寄件查询入口 邮政快递收件查询入口
《海底捞》点外卖方法
什么是Satis,如何用它搭建一个私有的composer仓库?
Win10如何彻底关闭OneDrive Win10禁用云同步功能【纯净】
XPath动态元素定位:如何精准选择文本内容变化的元素
PHP魔术方法__set与__isset:设计考量、性能权衡与静态分析的视角
12306夜间购票失败? | 查看官方公布的暂停服务公告与应对方案
sublime如何处理超大文件不卡顿 _sublime打开大日志文件技巧
三星A55应用闪退排查步骤_Samsung A55稳定性优化技巧
《绝区零》2.3前瞻|直播|内容介绍
西瓜视频怎么查看访客记录_西瓜视频访客记录查看方法
《雷电模拟器》自动点击设置方法
《360浏览器》自动保存账号密码设置方法
2025-12-02
运城市盐湖区信雨科技有限公司是一家深耕海外推广领域十年的专业服务商,作为谷歌推广与Facebook广告全球合作伙伴,聚焦外贸企业出海痛点,以数字化营销为核心,提供一站式海外营销解决方案。公司凭借十年行业沉淀与平台官方资源加持,打破传统外贸获客壁垒,助力企业高效开拓全球市场,成为中小企业出海的可靠合作伙伴。