How to fix bot throwing NotFound error when using SlashCommand and button to edit message? DSharpPlus

44 Views Asked by At

So the command works following. User selects category, and types query. Bot uses GraphQL client to search for stuff, sends message with 5 results. User now clicks button from 1-5 to select one object and bot edits the message. This works completely fine when using the command and buttons for the first time. But when trying second time whole process, the bot throws message NotFound in console:

[2023-12-25 02:56:21 +01:00] [104 /EventHandler] [Error] Event handler exception for event COMPONENT_INTERACTED thrown from System.Threading.Tasks.Task <SearchCommand>b__0(DSharpPlus.DiscordClient, DSharpPlus.EventArgs.ComponentInteractionCreateEventArgs) (defined in AnimeSharpBot.SearchModule+<>c__DisplayClass0_0)
DSharpPlus.Exceptions.NotFoundException: Not found: 404
   at DSharpPlus.Net.DiscordApiClient.CreateInteractionResponseAsync(UInt64 interaction_id, String interaction_token, InteractionResponseType type, DiscordInteractionResponseBuilder builder)
   at AnimeSharpBot.SearchModule.ButtonClick(DiscordClient sender, ComponentInteractionCreateEventArgs e, IEnumerable`1 animeDetails) in C:\Users\mrady\RiderProjects\AnimeSharpBot\SearchModule.cs:line 110
   at AnimeSharpBot.SearchModule.<>c__DisplayClass0_0.<<SearchCommand>b__0>d.MoveNext() in C:\Users\mrady\RiderProjects\AnimeSharpBot\SearchModule.cs:line 44
--- End of stack trace from previous location ---
   at DSharpPlus.AsyncEvents.AsyncEvent`2.<>c__DisplayClass7_0.<<InvokeAsync>b__0>d.MoveNext()

It's very confusing to me because the functionality works but getting that error for some reason. How can I fix it? Here is my code:

using System.Text;
using DSharpPlus;
using DSharpPlus.Entities;
using DSharpPlus.SlashCommands;
using AniListNet;
using AniListNet.Objects;
using AniListNet.Parameters;
using DSharpPlus.EventArgs;

namespace AnimeSharpBot;

public class SearchModule : ApplicationCommandModule
{
    [SlashCommand("search", "Search for anime, manga, staff, character, or studio")]
    public async Task SearchCommand(InteractionContext ctx,
        [Option("category", "Category to search for")]
        [Choice("Anime", "anime")]
        [Choice("Manga", "manga")]
        [Choice("Staff", "staff")]
        [Choice("Character", "character")]
        [Choice("Studio", "studio")]
        string category,
        [Option("query", "Search query")]
        string query)
    {
        var (resultMessage, animeDetails) = await SearchAsync(category, query);

        var row = new DiscordComponent[]
        {
            new DiscordButtonComponent(ButtonStyle.Primary, "option_1", "1"),
            new DiscordButtonComponent(ButtonStyle.Primary, "option_2", "2"),
            new DiscordButtonComponent(ButtonStyle.Primary, "option_3", "3"),
            new DiscordButtonComponent(ButtonStyle.Primary, "option_4", "4"),
            new DiscordButtonComponent(ButtonStyle.Primary, "option_5", "5"),
        };

        var message = new DiscordMessageBuilder()
            .WithContent(resultMessage)
            .AddComponents(row)
            .SendAsync(ctx.Channel);

        var client = ctx.Client;
        client.ComponentInteractionCreated += async (sender, e) =>
            await ButtonClick(sender, e, animeDetails);
    }

    private async Task<(string ResultMessage, IEnumerable<Media> AnimeDetails)> SearchAsync(string category, string query)
    {
        var resultMessage = new StringBuilder();
        var client = new AniClient();
        IEnumerable<Media> animeDetails = null;

        if (category.Equals("anime", StringComparison.OrdinalIgnoreCase))
        {
            var results = await client.SearchMediaAsync(new SearchMediaFilter
            {
                Query = query,
                Type = MediaType.Anime,
                Sort = MediaSort.Popularity,
                Format = new Dictionary<MediaFormat, bool>
                {
                    { MediaFormat.TV, true },
                    { MediaFormat.Movie, true },
                    { MediaFormat.TVShort, true }
                }
            });

            for (int i = 0; i < Math.Min(results.Data.Length, 5); i++)
            {
                var animeTitle = results.Data[i].Title.EnglishTitle;
                resultMessage.AppendLine($"Result {i + 1}: {animeTitle}");
            }
            
            animeDetails = results.Data;
        }
        else if (category.Equals("manga", StringComparison.OrdinalIgnoreCase))
        {
            
        }
        else if (category.Equals("staff", StringComparison.OrdinalIgnoreCase))
        {
            
        }
        else if (category.Equals("character", StringComparison.OrdinalIgnoreCase))
        {
            
        }
        else if (category.Equals("studio", StringComparison.OrdinalIgnoreCase))
        {
            
        }
        else
        {
            return ("Please choose a valid category (anime, manga, staff, character, studio).", null);
        }

        return (resultMessage.ToString(), animeDetails);
    }

    private async Task ButtonClick(DiscordClient sender, ComponentInteractionCreateEventArgs e, IEnumerable<Media> animeDetails)
    {
        int selectedOption = int.Parse(e.Id.Substring("option_".Length));
        
        var selectedAnime = animeDetails.ElementAtOrDefault(selectedOption - 1);

        if (selectedAnime != null)
        {
            await e.Interaction.CreateResponseAsync(InteractionResponseType.UpdateMessage,
                new DiscordInteractionResponseBuilder().WithContent($"You selected option {selectedOption}! Here are the details:\n{selectedAnime.Title.EnglishTitle} (ID: {selectedAnime.Id})\n{selectedAnime.Description}"));
        }
    }
}

You can make any suggestion apart from fixing it if you'd like if something is really bothering.

1

There are 1 best solutions below

0
ExplodatedFaces On

The source of your error is found in this code here, I commented in the place where it is caused and the source of the issue

    private async Task ButtonClick(DiscordClient sender, ComponentInteractionCreateEventArgs e,
        IEnumerable<Media> animeDetails) //404 caused in second arg
    {
        int selectedOption = int.Parse(e.Id.Substring("option_".Length));
        
        var selectedAnime = animeDetails.ElementAtOrDefault(selectedOption - 1);

        if (selectedAnime != null)
        {
            await e.Interaction.CreateResponseAsync(InteractionResponseType.UpdateMessage, //404 source, I set the 3 second countdown right here
                new DiscordInteractionResponseBuilder().WithContent($"You selected option {selectedOption}! Here are the details:\n{selectedAnime.Title.EnglishTitle} (ID: {selectedAnime.Id})\n{selectedAnime.Description}"));
        }
    }

The issue occurs when you call the ComponentInteractionCreated event by getting the ComponentInteractionCreateEventArgs more than 3 seconds after the first ComponentInteractionCreated event is fired. You can fix this by using DeferredMessageUpdate instead of UpdateMessage to fire follow up messages. DeferredMessageUpdate API info can be found here and DiscordFollowUpMessageBuilder can be found here

You can find the example that discusses this from DSharpPlus directly here

Another way to fix this would be to delete the original message the bot sent and resend the same message with the same content and buttons when an interaction occurs but that's a little more jank and for use cases where the API doesn't actually agree with what you're doing.