ProjectMakoto.Plugins.Score.../Commands/ScoreSaberCommandAbstractions.cs
2025-04-07 21:35:11 +02:00

365 lines
19 KiB
C#

// 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().AddEmbed(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().AddEmbed(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().AddEmbed(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().AddEmbed(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().AddEmbed(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().AddEmbed(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.AddEmbed(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.AddEmbed(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.ToString();
builder = builder.AddEmbed(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().AddEmbed(embed.AsError(ctx, "Score Saber")));
}
catch (ForbiddenException)
{
embed.Description = cmd.GetString(CommandKey.ForbiddenError, true);
_ = await ctx.BaseCommand.RespondOrEdit(new DiscordMessageBuilder().AddEmbed(embed.AsError(ctx, "Score Saber")));
}
catch (NotFoundException)
{
embed.Description = cmd.GetString(CommandKey.Profile.InvalidId, true);
_ = await ctx.BaseCommand.RespondOrEdit(new DiscordMessageBuilder().AddEmbed(embed.AsError(ctx, "Score Saber")));
}
catch (UnprocessableEntityException)
{
embed.Description = cmd.GetString(CommandKey.Profile.InvalidId, true);
_ = await ctx.BaseCommand.RespondOrEdit(new DiscordMessageBuilder().AddEmbed(embed.AsError(ctx, "Score Saber")));
}
catch (Exception)
{
throw;
}
}
}