You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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会在命令执行前触发冷却计数,无法在参数验证失败后回退。最优方案是自己实现冷却逻辑,放在参数验证通过后执行:

  1. 移除命令上的[SlashCooldown]特性
  2. 用本地数据库(如SQLite、EF Core)存储冷却记录,结构包含:用户ID、命令名称、冷却结束时间戳
  3. 在参数验证通过后,再检查冷却状态,通过则更新冷却记录

示例代码片段:

[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重写逻辑,让冷却状态存储到本地数据库:

  1. 创建自定义冷却特性,继承SlashCooldownAttribute
  2. 重写GetRemainingCooldown和新增ResetCooldown方法,对接数据库
  3. 参数验证失败时,调用重置方法清除冷却记录

示例代码片段:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 18:47:54