如何在DSharp+中错误场景下重置SlashCommand冷却时间
Discord机器人冷却时间问题解决方向
我正在为朋友和学校项目开发一款Hazard/Fantasy Discord机器人,遇到冷却时间相关问题:玩家使用/blackjack命令时,若下注金额低于最低要求(50美元)或超过自身余额,会显示对应错误,但仍会触发该命令的冷却机制。不清楚Discord冷却时间的存储方式,也不知道如何自定义可存储到本地数据库的冷却属性,需要解决方向。
现有命令代码:
[SlashCommand("blackjack", "Play a game of blackjack!")] [SlashCooldown(1, 60 * 5, SlashCooldownBucketType.User)] public async Task Blackjack(InteractionContext ctx, [Option("amount", "Amount to bet (Minimum 50$)")] double amountGiven) { await ctx.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource); var userProfile = await _playerService.GetOrCreatePlayerAsync(ctx.Member.Id); var amount = (int)amountGiven; DiscordEmbedBuilder? moneyErrorMessage = null; if(amount < MINIMUM_MONEY_REQ) { moneyErrorMessage = new DiscordEmbedBuilder() .WithDescription("You need atleast 50$ to play blackjack!") .WithColor(DiscordColor.Red); } else if(amount > userProfile.Balance) { moneyErrorMessage = new DiscordEmbedBuilder() .WithDescription($"You wanted to bet {amount}$ but you have only {userProfile.Balance}$") .WithColor(DiscordColor.Red); } if (moneyErrorMessage != null) { await ctx.EditResponseAsync(new DiscordWebhookBuilder().AddEmbed(moneyErrorMessage)); // TODO: Some way to reset the cooldown of that command return; } }
现有错误处理代码:
private async Task OnErrorOccured(SlashCommandsExtension sender, SlashCommandErrorEventArgs e) { await e.Context.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource); var errorEmbed = new DiscordEmbedBuilder() .WithColor(DiscordColor.Red); if(e.Exception is SlashExecutionChecksFailedException) { var castedException = (SlashExecutionChecksFailedException)e.Exception; var timeLeft = String.Empty; foreach (var error in castedException.FailedChecks) { var cooldown = (SlashCooldownAttribute)error; TimeSpan rawTime = cooldown.GetRemainingCooldown(e.Context); if (rawTime.Days != 0) timeLeft += $"{rawTime.Days} days, "; if (rawTime.Hours != 0) timeLeft += $"{rawTime.Hours} hours, "; if (rawTime.Minutes!= 0) timeLeft += $"{rawTime.Minutes} minutes, "; if (rawTime.Seconds != 0) timeLeft += $"{rawTime.Seconds} seconds"; } errorEmbed .WithTitle("**HOLD UP!**") .WithDescription($"To use that command you need to wait {timeLeft}"); } }
解决方向
一、放弃内置冷却,改用自定义后置检查
内置SlashCooldownAttribute会在命令执行前触发冷却计数,无法在参数验证失败后回退。最优方案是自己实现冷却逻辑,放在参数验证通过后执行:
- 移除命令上的
[SlashCooldown]特性 - 用本地数据库(如SQLite、EF Core)存储冷却记录,结构包含:用户ID、命令名称、冷却结束时间戳
- 在参数验证通过后,再检查冷却状态,通过则更新冷却记录
示例代码片段:
[SlashCommand("blackjack", "Play a game of blackjack!")] public async Task Blackjack(InteractionContext ctx, [Option("amount", "Amount to bet (Minimum 50$)")] double amountGiven) { await ctx.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource); var userProfile = await _playerService.GetOrCreatePlayerAsync(ctx.Member.Id); var amount = (int)amountGiven; // 先执行参数验证 DiscordEmbedBuilder? moneyErrorMessage = null; if(amount < MINIMUM_MONEY_REQ) { moneyErrorMessage = new DiscordEmbedBuilder() .WithDescription("至少需要50美元才能玩二十一点!") .WithColor(DiscordColor.Red); } else if(amount > userProfile.Balance) { moneyErrorMessage = new DiscordEmbedBuilder() .WithDescription($"你想下注{amount}美元,但你只有{userProfile.Balance}美元") .WithColor(DiscordColor.Red); } if (moneyErrorMessage != null) { await ctx.EditResponseAsync(new DiscordWebhookBuilder().AddEmbed(moneyErrorMessage)); return; } // 参数验证通过后,检查自定义冷却 var cooldownKey = $"blackjack_{ctx.Member.Id}"; var lastUsedTime = await _cooldownService.GetLastUsedTimeAsync(cooldownKey); var now = DateTime.UtcNow; var cooldownDuration = TimeSpan.FromMinutes(5); if(lastUsedTime.HasValue && now - lastUsedTime.Value < cooldownDuration) { var remaining = cooldownDuration - (now - lastUsedTime.Value); var errorEmbed = new DiscordEmbedBuilder() .WithTitle("**稍等!**") .WithDescription($"使用该命令还需等待{remaining.Minutes}分钟{remaining.Seconds}秒") .WithColor(DiscordColor.Red); await ctx.EditResponseAsync(new DiscordWebhookBuilder().AddEmbed(errorEmbed)); return; } // 冷却通过,更新数据库中的冷却记录 await _cooldownService.UpdateLastUsedTimeAsync(cooldownKey, now); // 后续执行二十一点游戏逻辑... }
二、扩展内置冷却特性(进阶方案)
如果不想完全放弃内置特性,可以继承SlashCooldownAttribute重写逻辑,让冷却状态存储到本地数据库:
- 创建自定义冷却特性,继承
SlashCooldownAttribute - 重写
GetRemainingCooldown和新增ResetCooldown方法,对接数据库 - 参数验证失败时,调用重置方法清除冷却记录
示例代码片段:
public class CustomSlashCooldown : SlashCooldownAttribute { private readonly ICooldownService _cooldownService; public CustomSlashCooldown(int maxUses, double cooldownSeconds, SlashCooldownBucketType bucketType) : base(maxUses, cooldownSeconds, bucketType) { _cooldownService = ServiceProvider.GetRequiredService<ICooldownService>(); } public override TimeSpan GetRemainingCooldown(InteractionContext ctx) { var bucketKey = GetBucketKey(ctx); var lastUsed = _cooldownService.GetLastUsedTime(bucketKey); if(!lastUsed.HasValue) return TimeSpan.Zero; var elapsed = DateTime.UtcNow - lastUsed.Value; return elapsed >= CooldownDuration ? TimeSpan.Zero : CooldownDuration - elapsed; } public void ResetCooldown(InteractionContext ctx) { var bucketKey = GetBucketKey(ctx); _cooldownService.ClearCooldown(bucketKey); } private string GetBucketKey(InteractionContext ctx) { return BucketType switch { SlashCooldownBucketType.User => $"user_{ctx.Member.Id}_blackjack", _ => throw new NotImplementedException() }; } } // 命令中使用自定义特性 [SlashCommand("blackjack", "Play a game of blackjack!")] [CustomSlashCooldown(1, 60 * 5, SlashCooldownBucketType.User)] public async Task Blackjack(InteractionContext ctx, double amountGiven) { // ...参数验证逻辑... if (moneyErrorMessage != null) { await ctx.EditResponseAsync(new DiscordWebhookBuilder().AddEmbed(moneyErrorMessage)); // 获取自定义冷却特性并重置 var cooldownAttr = GetType().GetMethod(nameof(Blackjack))? .GetCustomAttribute<CustomSlashCooldown>(); cooldownAttr?.ResetCooldown(ctx); return; } // ...游戏逻辑... }
三、数据库冷却记录结构建议
以SQLite为例,冷却表结构可设计为:
CREATE TABLE IF NOT EXISTS CommandCooldowns ( Id INTEGER PRIMARY KEY AUTOINCREMENT, BucketKey TEXT NOT NULL UNIQUE, LastUsedTime TEXT NOT NULL -- 存储UTC时间戳或ISO格式字符串 );
对应的C#实体类:
public class CommandCooldown { public int Id { get; set; } public string BucketKey { get; set; } = string.Empty; public DateTime LastUsedTime { get; set; } }
内容的提问来源于stack exchange,提问作者Kailaer
相关产品推荐
相关产品推荐

