Initial commit

This commit is contained in:
Mira 2025-01-27 18:36:44 +01:00
commit b0318f5602
Signed by untrusted user who does not match committer: Xorog
GPG key ID: 983798ED9C3E7C36
26 changed files with 3247 additions and 0 deletions

View file

@ -0,0 +1,365 @@
// 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.Enums.ScoreSaber;
using ProjectMakoto.Exceptions;
using ProjectMakoto.Plugins.ScoreSaber;
namespace ProjectMakoto.Commands;
internal static class ScoreSaberCommandAbstractions
{
internal static async Task SendScoreSaberProfile(SharedCommandContext ctx, BaseCommand cmd, string id = "", bool AddLinkButton = true)
{
var CommandKey = ((Plugins.ScoreSaber.Entities.Translations)ScoreSaberPlugin.Plugin!.Translations).Commands.ScoreSaber;
if (string.IsNullOrWhiteSpace(id))
{
if (ScoreSaberPlugin.Plugin!.Users![ctx.User.Id].ScoreSaberId != 0)
{
id = ScoreSaberPlugin.Plugin!.Users![ctx.User.Id].ScoreSaberId.ToString();
}
else
{
ctx.BaseCommand.SendSyntaxError();
return;
}
}
var embed = new DiscordEmbedBuilder
{
Description = cmd.GetString(CommandKey.Profile.LoadingPlayer, true)
}.AsLoading(ctx, "Score Saber");
_ = await ctx.BaseCommand.RespondOrEdit(new DiscordMessageBuilder().WithEmbed(embed));
try
{
var player = await ScoreSaberPlugin.ScoreSaber!.GetPlayerById(id);
CancellationTokenSource cancellationTokenSource = new();
DiscordButtonComponent ShowProfileButton = new(ButtonStyle.Primary, "getmain", cmd.GetString(CommandKey.Profile.ShowProfile), false, new DiscordComponentEmoji(DiscordEmoji.FromUnicode("👤")));
DiscordButtonComponent TopScoresButton = new(ButtonStyle.Primary, "gettopscores", cmd.GetString(CommandKey.Profile.ShowTopScores), false, new DiscordComponentEmoji(DiscordEmoji.FromUnicode("🎇")));
DiscordButtonComponent RecentScoresButton = new(ButtonStyle.Primary, "getrecentscores", cmd.GetString(CommandKey.Profile.ShowRecentScores), false, new DiscordComponentEmoji(DiscordEmoji.FromUnicode("🕒")));
DiscordButtonComponent LinkButton = new(ButtonStyle.Primary, "thats_me", cmd.GetString(CommandKey.Profile.LinkProfileToAccount), false, new DiscordComponentEmoji(DiscordEmoji.FromUnicode("↘")));
DiscordLinkButtonComponent OpenProfileInBrowser = new($"https://scoresaber.com/u/{id}", cmd.GetString(CommandKey.Profile.OpenInBrowser), false);
List<DiscordComponent> ProfileInteractionRow = new()
{
OpenProfileInBrowser,
TopScoresButton,
RecentScoresButton
};
List<DiscordComponent> RecentScoreInteractionRow = new()
{
OpenProfileInBrowser,
ShowProfileButton,
TopScoresButton
};
List<DiscordComponent> TopScoreInteractionRow = new()
{
OpenProfileInBrowser,
ShowProfileButton,
RecentScoresButton
};
PlayerScores? CachedTopScores = null;
PlayerScores? CachedRecentScores = null;
async Task RunInteraction(DiscordClient s, ComponentInteractionCreateEventArgs e)
{
_ = Task.Run(async () =>
{
if (e.Message?.Id == ctx.ResponseMessage.Id && e.User.Id == ctx.User.Id)
{
_ = e.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate);
if (e.GetCustomId() == "thats_me")
{
AddLinkButton = false;
_ = ShowProfile().Add(ctx.Bot, ctx);
ScoreSaberPlugin.Plugin!.Users![ctx.User.Id].ScoreSaberId = Convert.ToUInt64(player.id);
var new_msg = await cmd.RespondOrEdit(new DiscordMessageBuilder().WithEmbed(new DiscordEmbedBuilder
{
Description = cmd.GetString(CommandKey.Profile.LinkSuccessful).Build(true,
new TVar("ProfileName", player.name),
new TVar("ProfileId", player.id),
new TVar("ProfileCommand", $"{ctx.Prefix}scoresaber profile"),
new TVar("UnlinkCommand", $"{ctx.Prefix}scoresaber unlink"))
}.AsSuccess(ctx, "Score Saber")));
await Task.Delay(5000);
}
else if (e.GetCustomId() == "gettopscores")
{
try
{
CachedTopScores ??= await ScoreSaberPlugin.ScoreSaber!.GetScoresById(id, ScoreType.Top);
_ = ShowScores(CachedTopScores, ScoreType.Top).Add(ctx.Bot, ctx);
}
catch (InternalServerErrorException)
{
cancellationTokenSource.Cancel();
ctx.Client.ComponentInteractionCreated -= RunInteraction;
embed = embed.AsError(ctx, "Score Saber");
embed.Description = cmd.GetString(CommandKey.InternalServerError, true);
_ = await ctx.BaseCommand.RespondOrEdit(new DiscordMessageBuilder().WithEmbed(embed));
return;
}
catch (ForbiddenException)
{
cancellationTokenSource.Cancel();
ctx.Client.ComponentInteractionCreated -= RunInteraction;
embed = embed.AsError(ctx, "Score Saber");
embed.Description = cmd.GetString(CommandKey.ForbiddenError, true);
_ = await ctx.BaseCommand.RespondOrEdit(new DiscordMessageBuilder().WithEmbed(embed));
return;
}
catch (Exception)
{
throw;
}
}
else if (e.GetCustomId() == "getrecentscores")
{
try
{
CachedRecentScores ??= await ScoreSaberPlugin.ScoreSaber!.GetScoresById(id, ScoreType.Recent);
_ = ShowScores(CachedRecentScores, ScoreType.Recent).Add(ctx.Bot, ctx);
}
catch (InternalServerErrorException)
{
cancellationTokenSource.Cancel();
ctx.Client.ComponentInteractionCreated -= RunInteraction;
embed = embed.AsError(ctx, "Score Saber");
embed.Description = cmd.GetString(CommandKey.InternalServerError, true);
_ = await ctx.BaseCommand.RespondOrEdit(new DiscordMessageBuilder().WithEmbed(embed));
return;
}
catch (ForbiddenException)
{
cancellationTokenSource.Cancel();
ctx.Client.ComponentInteractionCreated -= RunInteraction;
embed = embed.AsError(ctx, "Score Saber");
embed.Description = cmd.GetString(CommandKey.ForbiddenError, true);
_ = await ctx.BaseCommand.RespondOrEdit(new DiscordMessageBuilder().WithEmbed(embed));
return;
}
catch (Exception)
{
throw;
}
}
else if (e.GetCustomId() == "getmain")
{
_ = ShowProfile().Add(ctx.Bot, ctx);
}
cancellationTokenSource.Cancel();
cancellationTokenSource = new();
try
{
await Task.Delay(120000, cancellationTokenSource.Token);
ctx.Client.ComponentInteractionCreated -= RunInteraction;
ctx.BaseCommand.ModifyToTimedOut(true);
}
catch { }
}
}).Add(ctx.Bot, ctx);
}
async Task ShowScores(PlayerScores scores, ScoreType scoreType)
{
_ = embed.ClearFields();
embed.ImageUrl = "";
if (!player.inactive)
embed.Description = $":globe_with_meridians: **#{player.rank}** 󠂪 󠂪 󠂪| 󠂪 󠂪 󠂪:flag_{player.country.ToLower()}: **#{player.countryRank}**\n\n" +
$"{(scoreType == ScoreType.Top ? $"**{cmd.GetString(CommandKey.Profile.TopScores)}**" : $"**{cmd.GetString(CommandKey.Profile.RecentScores)}**")}";
else
embed.Description = $"{cmd.GetString(CommandKey.Profile.InactiveUser, true)}\n\n" +
$"{(scoreType == ScoreType.Top ? $"**{cmd.GetString(CommandKey.Profile.TopScores)}**" : $"**{cmd.GetString(CommandKey.Profile.RecentScores)}**")}";
foreach (var score in scores.playerScores.Take(5))
{
var page = Math.Ceiling((decimal)score.score.rank / (decimal)12);
decimal rank = score.score.rank / 6;
var odd = (rank % 2 != 0);
_ = embed.AddField(new DiscordEmbedField($"{score.leaderboard.songName.FullSanitize()}{(!string.IsNullOrWhiteSpace(score.leaderboard.songSubName) ? $" {score.leaderboard.songSubName.FullSanitize()}" : "")} - {score.leaderboard.songAuthorName.FullSanitize()} [{score.leaderboard.levelAuthorName.FullSanitize()}]".TruncateWithIndication(256),
$":globe_with_meridians: **#{score.score.rank}** 󠂪 󠂪| 󠂪 󠂪 {Formatter.Timestamp(score.score.timeSet, TimestampFormat.RelativeTime)}\n" +
$"{(score.leaderboard.ranked ? $"**`{((decimal)((decimal)score.score.modifiedScore / (decimal)score.leaderboard.maxScore) * 100).ToString("N2", CultureInfo.CreateSpecificCulture("en-US"))}%`**󠂪 󠂪 󠂪| 󠂪 󠂪 󠂪**`{(score.score.pp).ToString("N2", CultureInfo.CreateSpecificCulture("en-US"))}pp [{(score.score.pp * score.score.weight).ToString("N2", CultureInfo.CreateSpecificCulture("en-US"))}pp]`**\n" : "\n")}" +
$"`{score.score.modifiedScore.ToString("N0", CultureInfo.CreateSpecificCulture("en-US"))}` 󠂪 󠂪| 󠂪 󠂪 **{(score.score.fullCombo ? " `FC`" : $"{false.ToEmote(ctx.Bot)} `{score.score.missedNotes + score.score.badCuts}`")}**\n" +
$"{cmd.GetString(CommandKey.Profile.MapLeaderboard)}: `{ctx.Prefix}scoresaber map-leaderboard {score.leaderboard.difficulty.leaderboardId} {page}{(odd ? " 1" : "")}`"));
}
DiscordMessageBuilder builder = new();
if (ScoreSaberPlugin.Plugin!.Users![ctx.User.Id].ScoreSaberId == 0 && AddLinkButton)
_ = builder.AddComponents(LinkButton);
_ = await ctx.BaseCommand.RespondOrEdit(builder.WithEmbed(embed).AddComponents((scoreType == ScoreType.Top ? TopScoreInteractionRow : RecentScoreInteractionRow)));
}
var LoadedGraph = "";
async Task ShowProfile()
{
embed = embed.AsInfo(ctx, "Score Saber");
_ = embed.ClearFields();
embed.Title = $"{player.name.FullSanitize()} 󠂪 󠂪 󠂪| 󠂪 󠂪 󠂪`{player.pp.ToString("N2", CultureInfo.CreateSpecificCulture("en-US"))}pp`";
embed.Thumbnail = new DiscordEmbedBuilder.EmbedThumbnail { Url = player.profilePicture };
if (!player.inactive)
embed.Description = $":globe_with_meridians: **#{player.rank}** 󠂪 󠂪 󠂪| 󠂪 󠂪 󠂪:flag_{player.country.ToLower()}: **#{player.countryRank}**\n";
else
embed.Description = $"{cmd.GetString(CommandKey.Profile.InactiveUser, true)}";
_ = embed.AddField(new DiscordEmbedField(cmd.GetString(CommandKey.Profile.RankedPlayCount), $"`{player.scoreStats.rankedPlayCount}`", true));
_ = embed.AddField(new DiscordEmbedField(cmd.GetString(CommandKey.Profile.TotalRankedScore), $"`{player.scoreStats.totalRankedScore.ToString("N0", CultureInfo.GetCultureInfo("en-US"))}`", true));
_ = embed.AddField(new DiscordEmbedField(cmd.GetString(CommandKey.Profile.AverageRankedAccuracy), $"`{Math.Round(player.scoreStats.averageRankedAccuracy, 2).ToString().Replace(",", ".")}%`", true));
_ = embed.AddField(new DiscordEmbedField(cmd.GetString(CommandKey.Profile.TotalPlayCount), $"`{player.scoreStats.totalPlayCount}`", true));
_ = embed.AddField(new DiscordEmbedField(cmd.GetString(CommandKey.Profile.TotalScore), $"`{player.scoreStats.totalScore.ToString("N", CultureInfo.GetCultureInfo("en-US")).Replace(".000", "")}`", true));
_ = embed.AddField(new DiscordEmbedField(cmd.GetString(CommandKey.Profile.ReplaysWatched), $"`{player.scoreStats.replaysWatched}`", true));
DiscordMessageBuilder builder = new();
if (ScoreSaberPlugin.Plugin!.Users![ctx.User.Id].ScoreSaberId == 0 && AddLinkButton)
_ = builder.AddComponents(LinkButton);
if (!string.IsNullOrWhiteSpace(LoadedGraph))
{
embed = embed.AsInfo(ctx, "Score Saber");
embed.ImageUrl = LoadedGraph;
_ = builder.AddComponents(ProfileInteractionRow);
}
_ = await ctx.BaseCommand.RespondOrEdit(builder.WithEmbed(embed));
var file = $"{Guid.NewGuid()}.png";
var labels = new List<string>();
for (var i = 50; i >= 0; i -= 2)
{
if (i == 0)
{
labels.Add(cmd.GetString(CommandKey.Profile.GraphToday));
break;
}
if (i == 2)
{
labels.Add(cmd.GetString(CommandKey.Profile.GraphDays, false, new TVar("Count", i)));
continue;
}
labels.Add(cmd.GetString(CommandKey.Profile.GraphDays, false, new TVar("Count", i)));
labels.Add("");
}
if (string.IsNullOrWhiteSpace(LoadedGraph))
try
{
if (player.inactive)
throw new Exception("Player is inactive");
var qc = ctx.Bot.ChartsClient.GetChart(1000, 500, labels, new ChartGeneration.Dataset[]
{
new(cmd.GetString(CommandKey.Profile.Placement),
player.histories.Split(",").Append(player.rank.ToString()),
"getGradientFillHelper('vertical', ['#6b76da', '#a336eb', '#FC0000'])", "yaxis2", true),
}, -1, -1);
qc.ToFile(file);
using var stream = File.Open(file, FileMode.Open);
var asset = await (await ctx.Client.GetChannelAsync(ctx.Bot.status.SafeReadOnlyConfig.Channels.GraphAssets)).SendMessageAsync(new DiscordMessageBuilder().WithFile(file, stream));
LoadedGraph = asset.Attachments[0].Url;
embed = embed.AsInfo(ctx, "Score Saber");
embed.ImageUrl = asset.Attachments[0].Url;
builder = builder.WithEmbed(embed);
_ = builder.AddComponents(ProfileInteractionRow);
_ = await ctx.BaseCommand.RespondOrEdit(builder);
}
catch (Exception)
{
embed = embed.AsInfo(ctx, "Score Saber");
_ = builder.AddComponents(ProfileInteractionRow);
_ = await ctx.BaseCommand.RespondOrEdit(builder);
}
try
{
await Task.Delay(1000);
File.Delete(file);
}
catch { }
}
_ = ShowProfile().Add(ctx.Bot, ctx);
ctx.Client.ComponentInteractionCreated += RunInteraction;
try
{
await Task.Delay(120000, cancellationTokenSource.Token);
ctx.Client.ComponentInteractionCreated -= RunInteraction;
ctx.BaseCommand.ModifyToTimedOut(true);
}
catch { }
}
catch (InternalServerErrorException)
{
embed.Description = cmd.GetString(CommandKey.InternalServerError, true);
_ = await ctx.BaseCommand.RespondOrEdit(new DiscordMessageBuilder().WithEmbed(embed.AsError(ctx, "Score Saber")));
}
catch (ForbiddenException)
{
embed.Description = cmd.GetString(CommandKey.ForbiddenError, true);
_ = await ctx.BaseCommand.RespondOrEdit(new DiscordMessageBuilder().WithEmbed(embed.AsError(ctx, "Score Saber")));
}
catch (NotFoundException)
{
embed.Description = cmd.GetString(CommandKey.Profile.InvalidId, true);
_ = await ctx.BaseCommand.RespondOrEdit(new DiscordMessageBuilder().WithEmbed(embed.AsError(ctx, "Score Saber")));
}
catch (UnprocessableEntityException)
{
embed.Description = cmd.GetString(CommandKey.Profile.InvalidId, true);
_ = await ctx.BaseCommand.RespondOrEdit(new DiscordMessageBuilder().WithEmbed(embed.AsError(ctx, "Score Saber")));
}
catch (Exception)
{
throw;
}
}
}

View file

@ -0,0 +1,220 @@
// 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 ScoreSaberMapLeaderboardCommand : BaseCommand
{
public override Task ExecuteCommand(SharedCommandContext ctx, Dictionary<string, object> arguments)
{
return Task.Run(async () =>
{
var CommandKey = ((Plugins.ScoreSaber.Entities.Translations)ScoreSaberPlugin.Plugin!.Translations).Commands.ScoreSaber;
var boardId = (int)arguments["leaderboardid"];
var Page = (uint)arguments["page"];
var Internal_Page = (int)arguments["internal_page"];
if (await ctx.DbUser.Cooldown.WaitForHeavy(ctx))
return;
if (Page <= 0 || !(Internal_Page is 0 or 1))
{
this.SendSyntaxError();
return;
}
var embed = new DiscordEmbedBuilder
{
Description = this.GetString(CommandKey.MapLeaderboard.LoadingScoreboard, true)
}.AsLoading(ctx, "Score Saber");
_ = await this.RespondOrEdit(new DiscordMessageBuilder().WithEmbed(embed));
var NextPageId = Guid.NewGuid().ToString();
var PrevPageId = Guid.NewGuid().ToString();
var InternalPage = Internal_Page;
var scoreSaberPage = Page;
Leaderboard leaderboard;
int TotalPages;
try
{
leaderboard = await ScoreSaberPlugin.ScoreSaber!.GetScoreboardById(boardId.ToString());
var scores = await ScoreSaberPlugin.ScoreSaber!.GetScoreboardScoresById(boardId.ToString());
TotalPages = scores.Metadata.TotalPages / scores.Metadata.ItemCount;
}
catch (InternalServerErrorException)
{
embed.Description = this.GetString(CommandKey.InternalServerError, true);
_ = await this.RespondOrEdit(new DiscordMessageBuilder().WithEmbed(embed.AsError(ctx, "Score Saber")));
return;
}
catch (NotFoundException)
{
embed.Description = this.GetString(CommandKey.MapLeaderboard.ScoreboardNotExist, true);
_ = await this.RespondOrEdit(new DiscordMessageBuilder().WithEmbed(embed.AsError(ctx, "Score Saber")));
throw;
}
catch (ForbiddenException)
{
embed.Description = this.GetString(CommandKey.ForbiddenError, true);
_ = await this.RespondOrEdit(new DiscordMessageBuilder().WithEmbed(embed.AsError(ctx, "Score Saber")));
return;
}
catch (Exception)
{
throw;
}
CancellationTokenSource cancellationTokenSource = new();
Dictionary<uint, LeaderboardScores> cachedPages = new();
ctx.Client.ComponentInteractionCreated += RunInteraction;
async Task RunInteraction(DiscordClient s, ComponentInteractionCreateEventArgs e)
{
_ = Task.Run(async () =>
{
if (e.Message?.Id == ctx.ResponseMessage.Id && e.User.Id == ctx.User.Id)
{
_ = e.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate);
cancellationTokenSource.Cancel();
cancellationTokenSource = new();
if (e.GetCustomId() == NextPageId)
{
if (InternalPage == 1)
{
InternalPage = 0;
scoreSaberPage++;
}
else if (InternalPage == 0)
{
InternalPage = 1;
}
await SendPage(InternalPage, scoreSaberPage);
}
else if (e.GetCustomId() == PrevPageId)
{
if (InternalPage == 1)
{
InternalPage = 0;
}
else if (InternalPage == 0)
{
InternalPage = 1;
scoreSaberPage--;
}
await SendPage(InternalPage, scoreSaberPage);
}
try
{
await Task.Delay(60000, cancellationTokenSource.Token);
ctx.Client.ComponentInteractionCreated -= RunInteraction;
this.ModifyToTimedOut();
}
catch { }
}
}).Add(ctx.Bot, ctx);
}
async Task SendPage(int internalPage, uint scoreSaberPage)
{
if (scoreSaberPage > TotalPages)
{
embed.Description = this.GetString(CommandKey.MapLeaderboard.PageNotExist, true, new TVar("Page", scoreSaberPage));
_ = await this.RespondOrEdit(new DiscordMessageBuilder().WithEmbed(embed.AsError(ctx, "Score Saber")));
return;
}
LeaderboardScores scores;
try
{
if (!cachedPages.ContainsKey(scoreSaberPage))
cachedPages.Add(scoreSaberPage, await ScoreSaberPlugin.ScoreSaber!.GetScoreboardScoresById(boardId.ToString(), scoreSaberPage));
scores = cachedPages[scoreSaberPage];
}
catch (InternalServerErrorException)
{
embed.Description = this.GetString(CommandKey.InternalServerError, true);
_ = await this.RespondOrEdit(new DiscordMessageBuilder().WithEmbed(embed.AsError(ctx, "Score Saber")));
return;
}
catch (NotFoundException)
{
embed.Description = this.GetString(CommandKey.MapLeaderboard.ScoreboardNotExist, true);
_ = await this.RespondOrEdit(new DiscordMessageBuilder().WithEmbed(embed.AsError(ctx, "Score Saber")));
throw;
}
catch (ForbiddenException)
{
embed.Description = this.GetString(CommandKey.ForbiddenError, true);
_ = await this.RespondOrEdit(new DiscordMessageBuilder().WithEmbed(embed.AsError(ctx, "Score Saber")));
return;
}
catch (Exception)
{
throw;
}
embed = embed.AsInfo(ctx, "Score Saber");
embed.Title = $"{leaderboard.leaderboardInfo.songName.FullSanitize()}{(!string.IsNullOrWhiteSpace(leaderboard.leaderboardInfo.songSubName) ? $" {leaderboard.leaderboardInfo.songSubName.FullSanitize()}" : "")} - {leaderboard.leaderboardInfo.songAuthorName.FullSanitize()} [{leaderboard.leaderboardInfo.levelAuthorName.FullSanitize()}]".TruncateWithIndication(256);
embed.Description = "";
embed.Author.IconUrl = ctx.Guild.IconUrl;
embed.Thumbnail = new DiscordEmbedBuilder.EmbedThumbnail { Url = leaderboard.leaderboardInfo.coverImage };
embed.Footer = ctx.GenerateUsedByFooter($"{this.GetString(this.t.Common.Page)} {scoreSaberPage}/{TotalPages}");
_ = embed.ClearFields();
foreach (var score in scores.Scores.ToList().Skip(internalPage * 6).Take(6))
{
_ = embed.AddField(new DiscordEmbedField($"**#{score.Rank}** {score.Player.Country.IsoCountryCodeToFlagEmoji()} `{score.Player.Name.SanitizeForCode()}`󠂪 󠂪| 󠂪 󠂪{Formatter.Timestamp(score.Timestamp, TimestampFormat.RelativeTime)}",
$"{(leaderboard.leaderboardInfo.ranked ? $"**`{((decimal)((decimal)score.ModifiedScore / (decimal)leaderboard.leaderboardInfo.maxScore) * 100).ToString("N2", CultureInfo.CreateSpecificCulture("en-US"))}%`**󠂪 󠂪 󠂪| 󠂪 󠂪 󠂪**`{(score.PP).ToString("N2", CultureInfo.CreateSpecificCulture("en-US"))}pp`**󠂪 󠂪| 󠂪 󠂪" : "󠂪 󠂪| 󠂪 󠂪")}" +
$"`{score.ModifiedScore.ToString("N0", CultureInfo.CreateSpecificCulture("en-US"))}`󠂪 󠂪| 󠂪 󠂪**{(score.FullCombo ? " `FC`" : $"{false.ToEmote(ctx.Bot)} `{score.MissedNotes + score.BadCuts}`")}**\n" +
$"{this.GetString(CommandKey.MapLeaderboard.Profile)}: `{ctx.Prefix}scoresaber profile {score.Player.Id}`"));
}
var previousPageButton = new DiscordButtonComponent(ButtonStyle.Primary, PrevPageId, this.GetString(this.t.Common.PreviousPage), (scoreSaberPage + InternalPage - 1 <= 0), new DiscordComponentEmoji(DiscordEmoji.FromUnicode("◀")));
var nextPageButton = new DiscordButtonComponent(ButtonStyle.Primary, NextPageId, this.GetString(this.t.Common.NextPage), (scoreSaberPage + 1 > scores.Metadata.TotalPages / scores.Metadata.ItemCount), new DiscordComponentEmoji(DiscordEmoji.FromUnicode("▶")));
_ = await this.RespondOrEdit(new DiscordMessageBuilder().WithEmbed(embed).AddComponents(new List<DiscordComponent> { previousPageButton, nextPageButton }));
};
await SendPage(InternalPage, scoreSaberPage);
try
{
await Task.Delay(60000, cancellationTokenSource.Token);
ctx.Client.ComponentInteractionCreated -= RunInteraction;
this.ModifyToTimedOut();
}
catch { }
});
}
}

View file

@ -0,0 +1,79 @@
// 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.Text.RegularExpressions;
using ProjectMakoto.Plugins.ScoreSaber;
namespace ProjectMakoto.Commands;
internal sealed class ScoreSaberProfileCommand : BaseCommand
{
public override Task ExecuteCommand(SharedCommandContext ctx, Dictionary<string, object> arguments)
{
return Task.Run(async () =>
{
var CommandKey = ((Plugins.ScoreSaber.Entities.Translations)ScoreSaberPlugin.Plugin!.Translations).Commands.ScoreSaber;
var id = (string)arguments["profile"];
if (await ctx.DbUser.Cooldown.WaitForHeavy(ctx))
return;
var AddLinkButton = true;
if ((string.IsNullOrWhiteSpace(id) || id.Contains('@')))
{
DiscordUser user;
try
{
if (!string.IsNullOrWhiteSpace(id))
{
if (Regex.IsMatch(id, @"<@((!?)(\d*))>", RegexOptions.ExplicitCapture))
user = await ctx.Client.GetUserAsync(Convert.ToUInt64(Regex.Match(id, @"<@((!?)(\d*))>").Groups[3].Value));
else
{
_ = await this.RespondOrEdit(new DiscordMessageBuilder().WithEmbed(new DiscordEmbedBuilder
{
Description = this.GetString(CommandKey.Profile.InvalidInput, true)
}.AsError(ctx, "Score Saber")));
return;
}
}
else
user = ctx.User;
}
catch (DisCatSharp.Exceptions.NotFoundException)
{
_ = await this.RespondOrEdit(new DiscordMessageBuilder().WithEmbed(new DiscordEmbedBuilder
{
Description = this.GetString(CommandKey.Profile.NoUser, true)
}.AsError(ctx, "Score Saber")));
return;
}
if (ScoreSaberPlugin.Plugin!.Users![ctx.User.Id].ScoreSaberId != 0)
{
id = ScoreSaberPlugin.Plugin!.Users![ctx.User.Id].ScoreSaberId.ToString();
AddLinkButton = false;
}
else
{
_ = await this.RespondOrEdit(new DiscordMessageBuilder().WithEmbed(new DiscordEmbedBuilder
{
Description = this.GetString(CommandKey.Profile.NoProfile, true)
}.AsError(ctx, "Score Saber")));
return;
}
}
await ScoreSaberCommandAbstractions.SendScoreSaberProfile(ctx, this, id, AddLinkButton);
});
}
}

View file

@ -0,0 +1,313 @@
// 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<string, object> 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<DiscordStringSelectComponentOption> 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<DiscordStringSelectComponentOption>, "continent_selection");
}
DiscordStringSelectComponent GetCountries(string continent_code, string default_country, int page)
{
List<DiscordStringSelectComponentOption> 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<DiscordStringSelectComponentOption>, "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().WithEmbed(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().WithEmbed(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().WithEmbed(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().WithEmbed(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().WithEmbed(embed.AsError(ctx, "Score Saber")));
return;
}
catch (Exception)
{
throw;
}
lastFetchedPage = currentFetchedPage;
}
List<DiscordStringSelectComponentOption> 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<DiscordStringSelectComponentOption>, "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.WithEmbed(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().WithEmbed(embed).AddComponents(GetContinents(selectedContinent)).AddComponents(next_step_button))
: await this.RespondOrEdit(new DiscordMessageBuilder().WithEmbed(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().WithEmbed(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 { }
});
}
}

View file

@ -0,0 +1,43 @@
// 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 ProjectMakoto.Plugins.ScoreSaber;
namespace ProjectMakoto.Commands;
internal sealed class ScoreSaberUnlinkCommand : BaseCommand
{
public override Task ExecuteCommand(SharedCommandContext ctx, Dictionary<string, object> arguments)
{
return Task.Run(async () =>
{
var CommandKey = ((Plugins.ScoreSaber.Entities.Translations)ScoreSaberPlugin.Plugin!.Translations).Commands.ScoreSaber;
if (await ctx.DbUser.Cooldown.WaitForHeavy(ctx))
return;
if (ScoreSaberPlugin.Plugin!.Users![ctx.User.Id].ScoreSaberId != 0)
{
ScoreSaberPlugin.Plugin!.Users![ctx.User.Id].ScoreSaberId = 0;
_ = await this.RespondOrEdit(new DiscordMessageBuilder().WithEmbed(new DiscordEmbedBuilder
{
Description = this.GetString(CommandKey.Unlink.Unlinked, true)
}.AsSuccess(ctx, "Score Saber")));
}
else
{
_ = await this.RespondOrEdit(new DiscordMessageBuilder().WithEmbed(new DiscordEmbedBuilder
{
Description = this.GetString(CommandKey.Unlink.NoLink, true)
}.AsError(ctx, "Score Saber")));
}
});
}
}