// Project Makoto // Copyright (C) 2024 Fortunevale // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY using System.Globalization; using ProjectMakoto.Entities.ScoreSaber; using ProjectMakoto.Exceptions; using ProjectMakoto.Plugins.ScoreSaber; namespace ProjectMakoto.Commands; internal sealed class ScoreSaberSearchCommand : BaseCommand { public override Task ExecuteCommand(SharedCommandContext ctx, Dictionary arguments) { return Task.Run(async () => { var CommandKey = ((Plugins.ScoreSaber.Entities.Translations)ScoreSaberPlugin.Plugin!.Translations).Commands.ScoreSaber; var name = (string)arguments["name"]; if (await ctx.DbUser.Cooldown.WaitForHeavy(ctx)) return; DiscordStringSelectComponent GetContinents(string default_code) { List continents = new() { new DiscordStringSelectComponentOption(this.GetString(CommandKey.Search.NoCountryFilter), "no_country", "", (default_code == "no_country")) }; foreach (var b in ctx.Bot.CountryCodes.List.GroupBy(x => x.Value.ContinentCode).Select(x => x.First()).Take(24)) { continents.Add(new DiscordStringSelectComponentOption($"{b.Value.ContinentName}", b.Value.ContinentCode, "", (default_code == b.Value.ContinentCode))); } return new DiscordStringSelectComponent(this.GetString(CommandKey.Search.SelectContinentDropdown), continents as IEnumerable, "continent_selection"); } DiscordStringSelectComponent GetCountries(string continent_code, string default_country, int page) { List countries = new(); var currentCountryList = ctx.Bot.CountryCodes.List.Where(x => x.Value.ContinentCode.ToLower() == continent_code.ToLower()).Skip((page - 1) * 25).Take(25).ToList(); foreach (var b in currentCountryList) { DiscordEmoji flag_emote = null; try { flag_emote = DiscordEmoji.FromName(ctx.Client, $":flag_{b.Key.ToLower()}:"); } catch (Exception) { flag_emote = DiscordEmoji.FromUnicode("⬜"); } countries.Add(new DiscordStringSelectComponentOption($"{b.Value.Name}", b.Key, "", (b.Key == default_country), new DiscordComponentEmoji(flag_emote))); } return new DiscordStringSelectComponent(this.GetString(CommandKey.Search.SelectCountryDropdown), countries as IEnumerable, "country_selection"); } var start_search_button = new DiscordButtonComponent(ButtonStyle.Success, "start_search", this.GetString(CommandKey.Search.StartSearch), false, new DiscordComponentEmoji(DiscordEmoji.FromUnicode("🔎"))); var next_step_button = new DiscordButtonComponent(ButtonStyle.Primary, "next_step", this.GetString(CommandKey.Search.NextStep), false, new DiscordComponentEmoji(DiscordEmoji.FromUnicode("▶"))); var previous_page_button = new DiscordButtonComponent(ButtonStyle.Primary, "prev_page", this.GetString(this.t.Common.PreviousPage), false, new DiscordComponentEmoji(DiscordEmoji.FromUnicode("◀"))); var next_page_button = new DiscordButtonComponent(ButtonStyle.Primary, "next_page", this.GetString(this.t.Common.NextPage), false, new DiscordComponentEmoji(DiscordEmoji.FromUnicode("▶"))); var embed = new DiscordEmbedBuilder { Description = this.GetString(CommandKey.Search.SelectContinent, true) }.AsAwaitingInput(ctx, "Score Saber"); _ = await this.RespondOrEdit(new DiscordMessageBuilder().AddEmbed(embed).AddComponents(GetContinents("no_country")).AddComponents(start_search_button)); CancellationTokenSource tokenSource = new(); var selectedContinent = "no_country"; var selectedCountry = "no_country"; var lastFetchedPage = -1; var currentPage = 1; var currentFetchedPage = 1; var playerSelection = false; PlayerSearch lastSearch = null; async Task RunDropdownInteraction(DiscordClient s, ComponentInteractionCreateEventArgs e) { _ = Task.Run(async () => { try { if (e.Message?.Id == ctx.ResponseMessage.Id && e.User.Id == ctx.User.Id) { _ = e.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate); async Task RefreshCountryList() { embed.Description = this.GetString(CommandKey.Search.SelectCountry, true); if (selectedCountry != "no_country") { embed.Description += $"\n`{this.GetString(CommandKey.Search.SelectedCountry)}: '{ctx.Bot.CountryCodes.List[selectedCountry].Name}'`"; } var page = GetCountries(selectedContinent, selectedCountry, currentPage); var builder = new DiscordMessageBuilder().AddEmbed(embed).AddComponents(page); if (currentPage == 1 && ctx.Bot.CountryCodes.List.Where(x => x.Value.ContinentCode.ToLower() == selectedContinent.ToLower()).Count() > 25) { _ = builder.AddComponents(next_page_button); } if (currentPage != 1) { if (ctx.Bot.CountryCodes.List.Where(x => x.Value.ContinentCode.ToLower() == selectedContinent.ToLower()).Skip((currentPage - 1) * 25).Count() > 25) _ = builder.AddComponents(next_page_button); _ = builder.AddComponents(previous_page_button); } if (selectedCountry != "no_country") _ = builder.AddComponents(start_search_button); _ = await this.RespondOrEdit(builder); } async Task RefreshPlayerList() { ctx.Client.ComponentInteractionCreated -= RunDropdownInteraction; embed.Description = this.GetString(CommandKey.Search.Searching, true); _ = await this.RespondOrEdit(new DiscordMessageBuilder().AddEmbed(embed.AsLoading(ctx, "Score Saber"))); if (currentFetchedPage != lastFetchedPage) { try { lastSearch = await ScoreSaberPlugin.ScoreSaber!.SearchPlayer(name, currentFetchedPage, (selectedCountry != "no_country" ? selectedCountry : "")); } catch (InternalServerErrorException) { tokenSource.Cancel(); ctx.Client.ComponentInteractionCreated -= RunDropdownInteraction; embed.Description = this.GetString(CommandKey.InternalServerError, true); _ = await this.RespondOrEdit(new DiscordMessageBuilder().AddEmbed(embed.AsError(ctx, "Score Saber"))); return; } catch (ForbiddenException) { tokenSource.Cancel(); ctx.Client.ComponentInteractionCreated -= RunDropdownInteraction; embed.Description = this.GetString(CommandKey.ForbiddenError, true); _ = await this.RespondOrEdit(new DiscordMessageBuilder().AddEmbed(embed.AsError(ctx, "Score Saber"))); return; } catch (Exception) { throw; } lastFetchedPage = currentFetchedPage; } List playerDropDownOptions = new(); var playerList = lastSearch.players.Skip((currentPage - 1) * 25).Take(25).ToList(); foreach (var b in playerList) { playerDropDownOptions.Add(new DiscordStringSelectComponentOption($"{b.name.FullSanitize()} | {b.pp.ToString("N2", CultureInfo.CreateSpecificCulture("en-US"))}pp", b.id, $"🌐 #{b.rank} | {b.country.IsoCountryCodeToFlagEmoji()} #{b.countryRank}")); } var player_dropdown = new DiscordStringSelectComponent(this.GetString(CommandKey.Search.SelectPlayer), playerDropDownOptions as IEnumerable, "player_selection"); var builder = new DiscordMessageBuilder().AddComponents(player_dropdown); var added_next = false; if (currentPage == 1 && lastSearch.players.Length > 25) { _ = builder.AddComponents(next_page_button); added_next = true; } if (currentPage != 1 || lastFetchedPage != 1) { if ((lastSearch.players.Skip((currentPage - 1) * 25).Take(25).Count() > 25 || ((((lastSearch.metadata.total - (currentFetchedPage - 1)) * 50) > 0) && player_dropdown.Options.Count == 25)) && !added_next) _ = builder.AddComponents(next_page_button); _ = builder.AddComponents(previous_page_button); } ctx.Client.ComponentInteractionCreated += RunDropdownInteraction; embed.Description = this.GetString(CommandKey.Search.FoundCount, true, new TVar("TotalCount", lastSearch.metadata.total)); _ = await this.RespondOrEdit(builder.AddEmbed(embed.AsSuccess(ctx, "Score Saber"))); } if (e.GetCustomId() == "start_search") { tokenSource.Cancel(); tokenSource = null; playerSelection = true; currentPage = 1; await RefreshPlayerList(); tokenSource = new(); } else if (e.GetCustomId() == "player_selection") { ctx.Client.ComponentInteractionCreated -= RunDropdownInteraction; tokenSource.Cancel(); tokenSource = null; await ScoreSaberCommandAbstractions.SendScoreSaberProfile(ctx, this, e.Values.First()); return; } else if (e.GetCustomId() == "next_step") { _ = RefreshCountryList(); } else if (e.GetCustomId() == "country_selection") { selectedCountry = e.Values.First(); _ = RefreshCountryList(); } else if (e.GetCustomId() == "prev_page") { if (playerSelection) { if (currentPage == 1) { currentPage = 2; currentFetchedPage -= 1; } else currentPage -= 1; tokenSource.Cancel(); tokenSource = null; await RefreshPlayerList(); tokenSource = new(); } else { currentPage -= 1; _ = RefreshCountryList(); } } else if (e.GetCustomId() == "next_page") { if (playerSelection) { if (currentPage == 2) { currentPage = 1; currentFetchedPage += 1; } else currentPage += 1; tokenSource.Cancel(); tokenSource = null; await RefreshPlayerList(); tokenSource = new(); } else { currentPage += 1; _ = RefreshCountryList(); } } else if (e.GetCustomId() == "continent_selection") { selectedContinent = e.Values.First(); _ = selectedContinent != "no_country" ? await this.RespondOrEdit(new DiscordMessageBuilder().AddEmbed(embed).AddComponents(GetContinents(selectedContinent)).AddComponents(next_step_button)) : await this.RespondOrEdit(new DiscordMessageBuilder().AddEmbed(embed).AddComponents(GetContinents(selectedContinent)).AddComponents(start_search_button)); } try { tokenSource.Cancel(); tokenSource = new(); await Task.Delay(120000, tokenSource.Token); this.ModifyToTimedOut(); ctx.Client.ComponentInteractionCreated -= RunDropdownInteraction; } catch { } } } catch (NotFoundException) { embed.Description = this.GetString(CommandKey.Search.NoSearchResult, true); _ = await this.RespondOrEdit(new DiscordMessageBuilder().AddEmbed(embed.AsError(ctx, "Score Saber"))); } catch (Exception) { ctx.Client.ComponentInteractionCreated -= RunDropdownInteraction; throw; } }).Add(ctx.Bot, ctx); } ctx.Client.ComponentInteractionCreated += RunDropdownInteraction; try { await Task.Delay(120000, tokenSource.Token); this.ModifyToTimedOut(); ctx.Client.ComponentInteractionCreated -= RunDropdownInteraction; } catch { } }); } }