discord.py多选择菜单交互数据混淆问题及优化咨询
模拟赛车Discord举报系统问题与解决方案
问题背景
正在为模拟赛车Discord社区搭建举报交互功能,流程如下:
- 展示带有4个选择菜单的View,用户每完成一个菜单选择,对应检查值设为1
- 当所有4个检查值均为1时,弹出Modal让用户输入补充文本信息
- 用户提交Modal后,所有输入内容以Embed形式发布到论坛频道的线程中
核心问题:多用户同时填写时输入内容会混淆,推测是全局变量存储用户输入导致被其他用户操作覆盖。
咨询问题
- 如何避免多用户输入数据混淆?
- 实现多选择菜单的最佳方案是什么?不想用
select_check这类全局变量。 - 有没有无需冷却机制的方法,能在用户A未完成表单时锁定命令,禁止用户B使用?
解决方案
1. 避免数据混淆的核心思路
彻底抛弃全局变量,将用户输入数据绑定到每个用户独立的View实例上。每个用户触发/report命令时,都会生成专属的View实例,该实例内部维护自身的选择状态和输入数据,不同用户的实例完全隔离,不会互相覆盖。
2. 多Select Menu的最佳实现方案
将所有Select组件整合到同一个自定义View类中,在View内部维护选择状态和数据:
- 自定义View类,内部添加所有需要的Select组件
- 在View类中定义属性存储每个选择的结果(如
reported_users、selected_track) - 每个Select的回调方法仅更新当前View实例的属性,而非全局变量
- 通过View实例内部的方法判断是否所有必填选择已完成,触发Modal
3. 锁定命令禁止多用户同时使用(无需冷却)
维护一个全局集合记录正在进行举报流程的用户ID:
- 用户触发
/report时,先检查自身ID是否在集合中 - 若存在则提示已有未完成举报;若不存在则加入集合并启动流程
- 当用户完成提交、流程超时或出错时,将用户ID从集合中移除
修改后的完整代码
import discord from discord.ext import commands from discord.ui import View, UserSelect, Select from discord import app_commands import json with open('krfconfig.json') as config_file: config = json.load(config_file) # 全局集合:记录正在进行举报的用户ID active_reports = set() class SurveyModal(discord.ui.Modal, title='Report'): def __init__(self, view): super().__init__() self.view = view # 关联对应的View实例,获取选择数据 wann = discord.ui.TextInput( label='When did it happen?', placeholder="z.B. Rennen Runde 15", max_length=40 ) was = discord.ui.TextInput( label='Give us a short description', style=discord.TextStyle.paragraph, max_length=1000 ) media = discord.ui.TextInput( label='Media', style=discord.TextStyle.paragraph, max_length=500, placeholder="Videos/Screenshots vorher hochladen und hier als Link posten, z.B Youtube, Streamable etc.." ) async def on_submit(self, interaction: discord.Interaction): # 从关联的View实例获取用户选择数据 reported_users = self.view.reported_users selected_track = self.view.selected_track channel = self.view.channel author = interaction.user.name auticon = interaction.user.display_avatar repuser_string = ', '.join([user.name for user in reported_users]) embed = discord.Embed(title=f'Report-Ticket', color=discord.Color.magenta()) embed.set_author(name=author, icon_url=auticon) embed.add_field(name='Report against:', value=f'{repuser_string} \n\u200b', inline=False) embed.add_field(name='When it happened:', value=f'{self.wann.value} \n\u200b', inline=False) embed.add_field(name='Short description:', value=f'{self.was.value} \n\u200b', inline=False) embed.add_field(name='Media:', value=self.media.value, inline=False) thr_name = f"{selected_track}-Report: {author} vs. {repuser_string}" await interaction.response.send_message(f'Report gesendet!', ephemeral=True) await channel.create_thread( name=thr_name, content=f'Reportverfahren: {interaction.user.mention} vs. {repuser_string}', embed=embed ) # 完成举报后移除活跃记录 active_reports.discard(interaction.user.id) async def on_error(self, interaction: discord.Interaction, error: Exception) -> None: # 提交出错时也移除活跃记录 active_reports.discard(interaction.user.id) await interaction.response.send_message('提交时出错,请重试', ephemeral=True) class ReportView(View): def __init__(self, channel, user_id): super().__init__(timeout=300) self.channel = channel self.user_id = user_id # 记录当前使用的用户ID # 初始化选择数据存储 self.reported_users = None self.selected_track = None # 添加组件到View self.add_item(self.ReportedUserSelect()) self.add_item(self.TrackSelect()) # 检查是否所有必填选择已完成 def all_selections_completed(self): return self.reported_users is not None and self.selected_track is not None # 被举报用户选择组件 class ReportedUserSelect(UserSelect): def __init__(self): super().__init__(placeholder="Who do you want to Report?", max_values=19, min_values=1) async def callback(self, interaction: discord.Interaction): self.view.reported_users = self.values if self.view.all_selections_completed(): await interaction.response.send_modal(SurveyModal(self.view)) await interaction.delete_original_response() self.view.stop() # 停止View监听 else: await interaction.response.defer() # 赛道选择组件 class TrackSelect(Select): def __init__(self): options = [ discord.SelectOption(label="Australia"), discord.SelectOption(label="Bahrain"), discord.SelectOption(label="Belgium") ] super().__init__(placeholder="Which Track?", max_values=1, min_values=1, options=options) async def callback(self, interaction: discord.Interaction): self.view.selected_track = self.values[0] if self.view.all_selections_completed(): await interaction.response.send_modal(SurveyModal(self.view)) await interaction.delete_original_response() self.view.stop() # 停止View监听 else: await interaction.response.defer() async def on_timeout(self): # 流程超时后移除活跃记录 active_reports.discard(self.user_id) class Report(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot @commands.Cog.listener() async def on_ready(self): print('Report cog loaded.') @app_commands.command(name="report", description='Use this to Report others!') @app_commands.checks.has_any_role('Liga A', 'Liga B', 'Liga C', 'Liga A Reserve', 'Liga B Reserve', 'Liga C Reserve') async def fillsurvey(self, interaction: discord.Interaction): # 检查用户是否已有未完成的举报 if interaction.user.id in active_reports: await interaction.response.send_message('你已有一个未完成的举报流程,请先完成或等待超时', ephemeral=True) return channel = interaction.guild.get_channel(config["REPORT_CHANNEL"]) # 创建用户专属的View实例 report_view = ReportView(channel, interaction.user.id) # 添加到活跃记录集合 active_reports.add(interaction.user.id) await interaction.response.send_message(view=report_view, ephemeral=True) async def setup(bot: commands.Bot): await bot.add_cog(Report(bot))
内容的提问来源于stack exchange,提问作者sicco14
相关产品推荐
相关产品推荐

