fix compatibility commands

This commit is contained in:
13xforever
2025-03-15 20:55:14 +05:00
parent 9fb994c14f
commit 12143b4889
9 changed files with 235 additions and 170 deletions

View File

@@ -13,7 +13,7 @@ internal sealed class BotMath
[Command("calculate"), DefaultGroupCommand] [Command("calculate"), DefaultGroupCommand]
[Description("Math; there you go, Juhn")] [Description("Math; there you go, Juhn")]
public async ValueTask Calc(SlashCommandContext ctx, [RemainingText, Description("Math expression or `help` for syntax link")] string expression) public async ValueTask Calc(SlashCommandContext ctx, [Description("Math expression or `help` for syntax link")] string expression)
{ {
var ephemeral = !ctx.Channel.IsSpamChannel(); var ephemeral = !ctx.Channel.IsSpamChannel();
if (expression.Equals("help", StringComparison.OrdinalIgnoreCase)) if (expression.Equals("help", StringComparison.OrdinalIgnoreCase))

View File

@@ -14,7 +14,7 @@ internal sealed class BotStatus
{ {
[Command("status")] [Command("status")]
[Description("Bot subsystem configuration status and various runtime stats")] [Description("Bot subsystem configuration status and various runtime stats")]
public async Task Show(SlashCommandContext ctx) public async ValueTask Show(SlashCommandContext ctx)
{ {
var latency = ctx.Client.GetConnectionLatency(Config.BotGuildId); var latency = ctx.Client.GetConnectionLatency(Config.BotGuildId);
var embed = new DiscordEmbedBuilder var embed = new DiscordEmbedBuilder

View File

@@ -0,0 +1,103 @@
using CompatApiClient.Utils;
using CompatBot.Database;
using DSharpPlus.Commands.Processors.SlashCommands.ArgumentModifiers;
using Microsoft.EntityFrameworkCore;
namespace CompatBot.Commands;
internal sealed partial class CompatList
{
public sealed class Top
{
[Command("top")]
[Description("Top game lists based on Metacritic scores and compatibility status")]
public async ValueTask Show(SlashCommandContext ctx,
[Description("Number of entries in the list")] int number = 10,
[Description("Filter by compatibility status"), SlashChoiceProvider<CompatListStatusChoiceProvider>] string status = "playable",
[Description("Listing type"), SlashChoiceProvider<ScoreTypeChoiceProvider>] string type = "both")
{
var ephemeral = !ctx.Channel.IsSpamChannel() && !ctx.Channel.IsOfftopicChannel();
await ctx.DeferResponseAsync(ephemeral).ConfigureAwait(false);
status = status.ToLowerInvariant();
type = type.ToLowerInvariant();
number = number.Clamp(1, 100);
var exactStatus = status.EndsWith("only");
if (exactStatus)
status = status[..^4];
if (!Enum.TryParse(status, true, out CompatStatus s))
s = CompatStatus.Playable;
await using var db = new ThumbnailDb();
var queryBase = db.Thumbnail.AsNoTracking();
if (exactStatus)
queryBase = queryBase.Where(g => g.CompatibilityStatus == s);
else
queryBase = queryBase.Where(g => g.CompatibilityStatus >= s);
queryBase = queryBase.Where(g => g.Metacritic != null).Include(t => t.Metacritic);
var query = type switch
{
"critic" => queryBase.Where(t => t.Metacritic!.CriticScore > 0).AsEnumerable().Select(t =>
(title: t.Metacritic!.Title, score: t.Metacritic!.CriticScore!.Value,
second: t.Metacritic.UserScore ?? t.Metacritic.CriticScore.Value)),
"user" => queryBase.Where(t => t.Metacritic!.UserScore > 0).AsEnumerable().Select(t =>
(title: t.Metacritic!.Title, score: t.Metacritic!.UserScore!.Value,
second: t.Metacritic.CriticScore ?? t.Metacritic.UserScore.Value)),
_ => queryBase.AsEnumerable().Select(t => (title: t.Metacritic!.Title,
score: Math.Max(t.Metacritic.CriticScore ?? 0, t.Metacritic.UserScore ?? 0), second: (byte)0)),
};
var resultList = query.Where(i => i.score > 0)
.OrderByDescending(i => i.score)
.ThenByDescending(i => i.second)
.Distinct()
.Take(number)
.ToList();
if (resultList.Count > 0)
{
var result = new StringBuilder($"Best {s.ToString().ToLower()}");
if (exactStatus)
result.Append(" only");
result.Append(" games");
if (type is "critic" or "user")
result.Append($" according to {type}s");
result.AppendLine(":");
foreach (var (title, score, _) in resultList)
result.AppendLine($"`{score:00}` {title}");
var formattedResults = AutosplitResponseHelper.AutosplitMessage(result.ToString(), blockStart: null, blockEnd: null);
await ctx.RespondAsync(formattedResults[0], ephemeral).ConfigureAwait(false);
}
else
await ctx.RespondAsync("Failed to generate list", ephemeral).ConfigureAwait(false);
}
public class CompatListStatusChoiceProvider : IChoiceProvider
{
private static readonly IReadOnlyList<DiscordApplicationCommandOptionChoice> compatListStatus =
[
new("playable", "playable"),
new("ingame or better", "ingame"),
new("intro or better", "intro"),
new("loadable or better", "loadable"),
new("only ingame", "ingameOnly"),
new("only intro", "introOnly"),
new("only loadable", "loadableOnly"),
];
public ValueTask<IEnumerable<DiscordApplicationCommandOptionChoice>> ProvideAsync(CommandParameter parameter)
=> ValueTask.FromResult<IEnumerable<DiscordApplicationCommandOptionChoice>>(compatListStatus);
}
public class ScoreTypeChoiceProvider : IChoiceProvider
{
private static readonly IReadOnlyList<DiscordApplicationCommandOptionChoice> scoreType =
[
new("combined", "both"),
new("critic score", "critic"),
new("user score", "user"),
];
public ValueTask<IEnumerable<DiscordApplicationCommandOptionChoice>> ProvideAsync(CommandParameter parameter)
=> ValueTask.FromResult<IEnumerable<DiscordApplicationCommandOptionChoice>>(scoreType);
}
}
}

View File

@@ -39,140 +39,70 @@ internal sealed partial class CompatList
lastUpdateInfo = db.BotState.FirstOrDefault(k => k.Key == Rpcs3UpdateStateKey)?.Value; lastUpdateInfo = db.BotState.FirstOrDefault(k => k.Key == Rpcs3UpdateStateKey)?.Value;
lastFullBuildNumber = db.BotState.FirstOrDefault(k => k.Key == Rpcs3UpdateBuildKey)?.Value; lastFullBuildNumber = db.BotState.FirstOrDefault(k => k.Key == Rpcs3UpdateBuildKey)?.Value;
//lastUpdateInfo = "8022"; //lastUpdateInfo = "8022";
if (lastUpdateInfo is string strPr if (lastUpdateInfo is {Length: >0} strPr && int.TryParse(strPr, out var pr))
&& int.TryParse(strPr, out var pr))
{ {
try try
{ {
var prInfo = GithubClient.GetPrInfoAsync(pr, Config.Cts.Token).ConfigureAwait(false).GetAwaiter().GetResult(); var prInfo = GithubClient.GetPrInfoAsync(pr, Config.Cts.Token).ConfigureAwait(false).GetAwaiter().GetResult();
cachedUpdateInfo = Client.GetUpdateAsync(Config.Cts.Token, prInfo?.MergeCommitSha).ConfigureAwait(false).GetAwaiter().GetResult(); cachedUpdateInfo = Client.GetUpdateAsync(Config.Cts.Token, prInfo?.MergeCommitSha).ConfigureAwait(false).GetAwaiter().GetResult();
if (cachedUpdateInfo?.CurrentBuild != null) if (cachedUpdateInfo?.CurrentBuild == null)
{ return;
cachedUpdateInfo.LatestBuild = cachedUpdateInfo.CurrentBuild;
cachedUpdateInfo.CurrentBuild = null; cachedUpdateInfo.LatestBuild = cachedUpdateInfo.CurrentBuild;
} cachedUpdateInfo.CurrentBuild = null;
} }
catch { } catch { }
} }
} }
/* [Command("compatibility")]
[Command("compatibility"), TextAlias("c", "compat")] [Description("Search the game compatibility list")]
[Description("Searches the compatibility database, USE: !compat search term")] public async ValueTask Compat(SlashCommandContext ctx, [Description("Game title or product code to look up")] string title)
public async Task Compat(CommandContext ctx, [RemainingText, Description("Game title to look up")] string? title)
{ {
title = title?.TrimEager().Truncate(40); if (await ContentFilter.FindTriggerAsync(FilterContext.Chat, title).ConfigureAwait(false) is not null)
if (string.IsNullOrEmpty(title))
{ {
var prompt = await ctx.Channel.SendMessageAsync($"{ctx.Message.Author.Mention} what game do you want to check?").ConfigureAwait(false); await ctx.RespondAsync("Invalid game title or product code.", true).ConfigureAwait(false);
var interact = ctx.Client.GetInteractivity(); return;
var response = await interact.WaitForMessageAsync(m => m.Author == ctx.Message.Author && m.Channel == ctx.Channel).ConfigureAwait(false);
if (string.IsNullOrEmpty(response.Result?.Content) || response.Result.Content.StartsWith(Config.CommandPrefix))
{
await prompt.ModifyAsync("You should specify what you're looking for").ConfigureAwait(false);
return;
}
DeletedMessagesMonitor.RemovedByBotCache.Set(prompt.Id, true, DeletedMessagesMonitor.CacheRetainTime);
await prompt.DeleteAsync().ConfigureAwait(false);
title = response.Result.Content.TrimEager().Truncate(40);
} }
var ephemeral = !ctx.Channel.IsSpamChannel();
await ctx.DeferResponseAsync(ephemeral).ConfigureAwait(false);
if (!await DiscordInviteFilter.CheckMessageInvitesAreSafeAsync(ctx.Client, ctx.Message).ConfigureAwait(false)) var productCodes = ProductCodeLookup.GetProductIds(title);
return; if (productCodes.Count > 0)
if (!await ContentFilter.IsClean(ctx.Client, ctx.Message).ConfigureAwait(false))
return;
var productCodes = ProductCodeLookup.GetProductIds(ctx.Message.Content);
if (productCodes.Any())
{ {
await ProductCodeLookup.LookupAndPostProductCodeEmbedAsync(ctx.Client, ctx.Message, ctx.Channel, productCodes).ConfigureAwait(false); var formattedResults = await ProductCodeLookup.LookupProductCodeAndFormatAsync(ctx.Client, productCodes).ConfigureAwait(false);
await ctx.RespondAsync(embed: formattedResults[0].builder, ephemeral: ephemeral).ConfigureAwait(false);
return; return;
} }
try try
{ {
title = title.TrimEager().Truncate(40);
var requestBuilder = RequestBuilder.Start().SetSearch(title); var requestBuilder = RequestBuilder.Start().SetSearch(title);
await DoRequestAndRespond(ctx, requestBuilder).ConfigureAwait(false); await DoRequestAndRespondAsync(ctx, ephemeral, requestBuilder).ConfigureAwait(false);
} }
catch (Exception e) catch (Exception e)
{ {
Config.Log.Error(e, "Failed to get compat list info"); Config.Log.Error(e, "Failed to get compat list info");
} }
} }
*/
[Command("top"), LimitedToOfftopicChannel] //[Command("latest")]
//[Cooldown(1, 5, CooldownBucketType.Channel)] [Description("Links to the latest RPCS3 build")]
[Description("Provides top game lists based on Metacritic and compatibility lists")]
public async Task Top(CommandContext ctx,
[Description("Number of entries in the list")] int number = 10,
[Description("One of `playable`, `ingame`, `intro`, `loadable`, or `<status>Only`")] string status = "playable",
[Description("One of `both`, `critic`, or `user`")] string scoreType = "both")
{
status = status.ToLowerInvariant();
scoreType = scoreType.ToLowerInvariant();
number = number.Clamp(1, 100);
var exactStatus = status.EndsWith("only");
if (exactStatus)
status = status[..^4];
if (!Enum.TryParse(status, true, out CompatStatus s))
s = CompatStatus.Playable;
await using var db = new ThumbnailDb();
var queryBase = db.Thumbnail.AsNoTracking();
if (exactStatus)
queryBase = queryBase.Where(g => g.CompatibilityStatus == s);
else
queryBase = queryBase.Where(g => g.CompatibilityStatus >= s);
queryBase = queryBase.Where(g => g.Metacritic != null).Include(t => t.Metacritic);
var query = scoreType switch
{
"critic" => queryBase.Where(t => t.Metacritic!.CriticScore > 0).AsEnumerable().Select(t => (title: t.Metacritic!.Title, score: t.Metacritic!.CriticScore!.Value, second: t.Metacritic.UserScore ?? t.Metacritic.CriticScore.Value)),
"user" => queryBase.Where(t => t.Metacritic!.UserScore > 0).AsEnumerable().Select(t => (title: t.Metacritic!.Title, score: t.Metacritic!.UserScore!.Value, second: t.Metacritic.CriticScore ?? t.Metacritic.UserScore.Value)),
_ => queryBase.AsEnumerable().Select(t => (title: t.Metacritic!.Title, score: Math.Max(t.Metacritic.CriticScore ?? 0, t.Metacritic.UserScore ?? 0), second: (byte)0)),
};
var resultList = query.Where(i => i.score > 0)
.OrderByDescending(i => i.score)
.ThenByDescending(i => i.second)
.Distinct()
.Take(number)
.ToList();
if (resultList.Count > 0)
{
var result = new StringBuilder($"Best {s.ToString().ToLower()}");
if (exactStatus)
result.Append(" only");
result.Append(" games");
if (scoreType is "critic" or "user")
result.Append($" according to {scoreType}s");
result.AppendLine(":");
foreach (var (title, score, _) in resultList)
result.AppendLine($"`{score:00}` {title}");
await ctx.SendAutosplitMessageAsync(result, blockStart: null, blockEnd: null).ConfigureAwait(false);
}
else
await ctx.Channel.SendMessageAsync("Failed to generate list").ConfigureAwait(false);
}
[Command("latest"), TriggersTyping]
[Description("Provides links to the latest RPCS3 build")]
//[Cooldown(1, 30, CooldownBucketType.Channel)]
public sealed class UpdatesCheck public sealed class UpdatesCheck
{ {
/*
[Command("build"), DefaultGroupCommand] [Command("build"), DefaultGroupCommand]
public Task Latest(CommandContext ctx) => CheckForRpcs3Updates(ctx.Client, ctx.Channel); public Task Latest(SlashCommandContext ctx) => CheckForRpcs3Updates(ctx.Client, ctx.Channel);
[Command("since")] [Command("since")]
[Description("Show additional info about changes since specified update")] [Description("Show additional info about changes since specified update")]
public Task Since(CommandContext ctx, [Description("Commit hash of the update, such as `46abe0f31`")] string commit) public Task Since(SlashCommandContext ctx, [Description("Commit hash of the update, such as `46abe0f31`")] string commit)
=> CheckForRpcs3Updates(ctx.Client, ctx.Channel, commit); => CheckForRpcs3Updates(ctx.Client, ctx.Channel, commit);
[Command("clear"), RequiresBotModRole] [Command("clear"), RequiresBotModRole]
[Description("Clears update info cache that is used to post new build announcements")] [Description("Clears update info cache that is used to post new build announcements")]
public Task Clear(CommandContext ctx) public Task Clear(SlashCommandContext ctx)
{ {
lastUpdateInfo = null; lastUpdateInfo = null;
lastFullBuildNumber = null; lastFullBuildNumber = null;
@@ -181,14 +111,15 @@ internal sealed partial class CompatList
[Command("set"), RequiresBotModRole] [Command("set"), RequiresBotModRole]
[Description("Sets update info cache that is used to check if new updates are available")] [Description("Sets update info cache that is used to check if new updates are available")]
public Task Set(CommandContext ctx, string lastUpdatePr) public Task Set(SlashCommandContext ctx, string lastUpdatePr)
{ {
lastUpdateInfo = lastUpdatePr; lastUpdateInfo = lastUpdatePr;
lastFullBuildNumber = null; lastFullBuildNumber = null;
return CheckForRpcs3Updates(ctx.Client, null); return CheckForRpcs3Updates(ctx.Client, null);
} }
*/
public static async Task<bool> CheckForRpcs3Updates(DiscordClient discordClient, DiscordChannel? channel, string? sinceCommit = null, DiscordMessage? emptyBotMsg = null) public static async ValueTask<bool> CheckForRpcs3Updates(DiscordClient discordClient, DiscordChannel? channel, string? sinceCommit = null, DiscordMessage? emptyBotMsg = null)
{ {
var updateAnnouncement = channel is null; var updateAnnouncement = channel is null;
var updateAnnouncementRestore = emptyBotMsg != null; var updateAnnouncementRestore = emptyBotMsg != null;
@@ -271,7 +202,7 @@ internal sealed partial class CompatList
if (embed.Color.Value.Value == Config.Colors.Maintenance.Value) if (embed.Color.Value.Value == Config.Colors.Maintenance.Value)
return false; return false;
await CheckMissedBuildsBetween(discordClient, compatChannel, lastUpdateInfo, latestUpdatePr, Config.Cts.Token).ConfigureAwait(false); await CheckMissedBuildsBetweenAsync(discordClient, compatChannel, lastUpdateInfo, latestUpdatePr, Config.Cts.Token).ConfigureAwait(false);
await compatChannel.SendMessageAsync(embed: embed.Build()).ConfigureAwait(false); await compatChannel.SendMessageAsync(embed: embed.Build()).ConfigureAwait(false);
lastUpdateInfo = latestUpdatePr; lastUpdateInfo = latestUpdatePr;
@@ -302,7 +233,7 @@ internal sealed partial class CompatList
return false; return false;
} }
private static async Task CheckMissedBuildsBetween(DiscordClient discordClient, DiscordChannel compatChannel, string? previousUpdatePr, string? latestUpdatePr, CancellationToken cancellationToken) private static async ValueTask CheckMissedBuildsBetweenAsync(DiscordClient discordClient, DiscordChannel compatChannel, string? previousUpdatePr, string? latestUpdatePr, CancellationToken cancellationToken)
{ {
if (!int.TryParse(previousUpdatePr, out var oldestPr) if (!int.TryParse(previousUpdatePr, out var oldestPr)
|| !int.TryParse(latestUpdatePr, out var newestPr)) || !int.TryParse(latestUpdatePr, out var newestPr))
@@ -394,8 +325,7 @@ internal sealed partial class CompatList
} }
} }
/* private static async ValueTask DoRequestAndRespondAsync(SlashCommandContext ctx, bool ephemeral, RequestBuilder requestBuilder)
private static async Task DoRequestAndRespond(CommandContext ctx, RequestBuilder requestBuilder)
{ {
Config.Log.Info(requestBuilder.Build()); Config.Log.Info(requestBuilder.Build());
CompatResult? result = null; CompatResult? result = null;
@@ -411,7 +341,7 @@ internal sealed partial class CompatList
{ {
if (result == null) if (result == null)
{ {
await ctx.Channel.SendMessageAsync(embed: TitleInfo.CommunicationError.AsEmbed(null)).ConfigureAwait(false); await ctx.RespondAsync(embed: TitleInfo.CommunicationError.AsEmbed(null), ephemeral).ConfigureAwait(false);
return; return;
} }
} }
@@ -419,14 +349,20 @@ internal sealed partial class CompatList
#if DEBUG #if DEBUG
await Task.Delay(5_000).ConfigureAwait(false); await Task.Delay(5_000).ConfigureAwait(false);
#endif #endif
var channel = await ctx.GetChannelForSpamAsync().ConfigureAwait(false);
if (result?.Results?.Count == 1) if (result?.Results?.Count == 1)
await ProductCodeLookup.LookupAndPostProductCodeEmbedAsync(ctx.Client, ctx.Message, ctx.Channel, [..result.Results.Keys]).ConfigureAwait(false); {
var formattedResults = await ProductCodeLookup.LookupProductCodeAndFormatAsync(ctx.Client, [..result.Results.Keys]).ConfigureAwait(false);
await ctx.RespondAsync(embed: formattedResults[0].builder, ephemeral: ephemeral).ConfigureAwait(false);
}
else if (result != null) else if (result != null)
{
var builder = new StringBuilder();
foreach (var msg in FormatSearchResults(ctx, result)) foreach (var msg in FormatSearchResults(ctx, result))
await channel.SendAutosplitMessageAsync(msg, blockStart: "", blockEnd: "").ConfigureAwait(false); builder.AppendLine(msg);
var formattedResults = AutosplitResponseHelper.AutosplitMessage(builder.ToString(), blockStart: null, blockEnd: null);
await ctx.RespondAsync(formattedResults[0], ephemeral).ConfigureAwait(false);
}
} }
*/
internal static CompatResult GetLocalCompatResult(RequestBuilder requestBuilder) internal static CompatResult GetLocalCompatResult(RequestBuilder requestBuilder)
{ {
@@ -457,62 +393,51 @@ internal sealed partial class CompatList
return result; return result;
} }
/* private static IEnumerable<string> FormatSearchResults(SlashCommandContext ctx, CompatResult compatResult)
private static IEnumerable<string> FormatSearchResults(CommandContext ctx, CompatResult compatResult)
{ {
var returnCode = ApiConfig.ReturnCodes[compatResult.ReturnCode]; var returnCode = ApiConfig.ReturnCodes[compatResult.ReturnCode];
var request = compatResult.RequestBuilder; var request = compatResult.RequestBuilder;
if (returnCode.overrideAll) if (returnCode.overrideAll)
yield return string.Format(returnCode.info, ctx.Message.Author.Mention); yield return string.Format(returnCode.info, ctx.User.Mention);
else else
{ {
var authorMention = ctx.Channel.IsPrivate ? "You" : ctx.Message.Author.Mention; var authorMention = ctx.Channel.IsPrivate ? "You" : ctx.User.Mention;
var result = new StringBuilder(); var result = new StringBuilder();
result.AppendLine($"{authorMention} searched for: ***{request.Search?.Sanitize(replaceBackTicks: true)}***"); result.AppendLine($"{authorMention} searched for: ***{request.Search?.Sanitize(replaceBackTicks: true)}***");
if (request.Search?.Contains("persona", StringComparison.InvariantCultureIgnoreCase) is true if (request.Search?.Contains("persona", StringComparison.InvariantCultureIgnoreCase) is true
|| request.Search?.Contains("p5", StringComparison.InvariantCultureIgnoreCase) is true) || request.Search?.Contains("p5", StringComparison.InvariantCultureIgnoreCase) is true)
result.AppendLine("Did you try searching for **__Unnamed__** instead?"); result.AppendLine("Did you try searching for **__Unnamed__** instead?");
else if (ctx.IsOnionLike()
&& compatResult.Results.Values.Any(i =>
i.Title.Contains("afrika", StringComparison.InvariantCultureIgnoreCase)
|| i.Title.Contains("africa", StringComparison.InvariantCultureIgnoreCase))
)
{
var sqvat = ctx.Client.GetEmoji(":sqvat:", Config.Reactions.No);
result.AppendLine($"One day this meme will die {sqvat}");
}
result.AppendFormat(returnCode.info, compatResult.SearchTerm); result.AppendFormat(returnCode.info, compatResult.SearchTerm);
yield return result.ToString(); yield return result.ToString();
result.Clear(); result.Clear();
if (returnCode.displayResults) if (!returnCode.displayResults)
{ yield break;
var sortedList = compatResult.GetSortedList();
var trimmedList = sortedList.Where(i => i.score > 0).ToList(); var sortedList = compatResult.GetSortedList();
if (trimmedList.Count > 0) var trimmedList = sortedList.Where(i => i.score > 0).ToList();
sortedList = trimmedList; if (trimmedList.Count > 0)
sortedList = trimmedList;
var searchTerm = request.Search ?? @"¯\\\_(ツ)\_/¯"; var searchTerm = request.Search ?? @"¯\\\_(ツ)\_/¯";
var searchHits = sortedList.Where(t => t.score > 0.5 var searchHits = sortedList.Where(t => t.score > 0.5
|| (t.info.Title?.StartsWith(searchTerm, StringComparison.InvariantCultureIgnoreCase) ?? false) || (t.info.Title?.StartsWith(searchTerm, StringComparison.InvariantCultureIgnoreCase) ?? false)
|| (t.info.AlternativeTitle?.StartsWith(searchTerm, StringComparison.InvariantCultureIgnoreCase) ?? false)); || (t.info.AlternativeTitle?.StartsWith(searchTerm, StringComparison.InvariantCultureIgnoreCase) ?? false));
foreach (var title in searchHits.Select(t => t.info.Title).Distinct()) foreach (var title in searchHits.Select(t => t.info.Title).Distinct())
StatsStorage.IncGameStat(title); StatsStorage.IncGameStat(title);
foreach (var resultInfo in sortedList.Take(request.AmountRequested)) foreach (var resultInfo in sortedList.Take(request.AmountRequested))
{ {
var info = resultInfo.AsString(); var info = resultInfo.AsString();
#if DEBUG #if DEBUG
info = $"{StringUtils.InvisibleSpacer}`{CompatApiResultUtils.GetScore(request.Search, resultInfo.info):0.000000}` {info}"; info = $"{StringUtils.InvisibleSpacer}`{CompatApiResultUtils.GetScore(request.Search, resultInfo.info):0.000000}` {info}";
#endif #endif
result.AppendLine(info); result.AppendLine(info);
}
yield return result.ToString();
} }
yield return result.ToString();
} }
} }
*/
public static string FixGameTitleSearch(string title) public static string FixGameTitleSearch(string title)
{ {
@@ -570,7 +495,7 @@ internal sealed partial class CompatList
await db.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false); await db.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false);
} }
public static async Task ImportMetacriticScoresAsync() public static async ValueTask ImportMetacriticScoresAsync()
{ {
var scoreJson = "metacritic_ps3.json"; var scoreJson = "metacritic_ps3.json";
string json; string json;

View File

@@ -33,8 +33,8 @@ internal static partial class DiscordInviteFilter
return true; return true;
#if !DEBUG #if !DEBUG
if (await message.Author.IsWhitelistedAsync(client, message.Channel.Guild).ConfigureAwait(false)) if (await message.Author.IsWhitelistedAsync(client, message.Channel.Guild).ConfigureAwait(false))
return true; return true;
#endif #endif
if (message.Reactions.Any(r => r.Emoji == Config.Reactions.Moderated && r.IsMe)) if (message.Reactions.Any(r => r.Emoji == Config.Reactions.Moderated && r.IsMe))

View File

@@ -4,7 +4,7 @@ namespace CompatBot.EventHandlers;
internal static class GlobalButtonHandler internal static class GlobalButtonHandler
{ {
private const string ReplaceWithUpdatesPrefix = "replace with game updates:"; internal const string ReplaceWithUpdatesPrefix = "replace with game updates:";
public static async Task OnComponentInteraction(DiscordClient sender, ComponentInteractionCreatedEventArgs e) public static async Task OnComponentInteraction(DiscordClient sender, ComponentInteractionCreatedEventArgs e)
{ {

View File

@@ -46,35 +46,21 @@ internal static partial class ProductCodeLookup
await LookupAndPostProductCodeEmbedAsync(c, args.Message, args.Channel, codesToLookup).ConfigureAwait(false); await LookupAndPostProductCodeEmbedAsync(c, args.Message, args.Channel, codesToLookup).ConfigureAwait(false);
} }
public static async Task LookupAndPostProductCodeEmbedAsync(DiscordClient client, DiscordMessage message, DiscordChannel channel, List<string> codesToLookup) public static async ValueTask LookupAndPostProductCodeEmbedAsync(DiscordClient client, DiscordMessage message, DiscordChannel channel, List<string> codesToLookup)
{ {
await message.ReactWithAsync(Config.Reactions.PleaseWait).ConfigureAwait(false); await message.ReactWithAsync(Config.Reactions.PleaseWait).ConfigureAwait(false);
try try
{ {
var results = new List<(string code, Task<DiscordEmbedBuilder> task)>(codesToLookup.Count);
foreach (var code in codesToLookup)
results.Add((code, client.LookupGameInfoAsync(code)));
var formattedResults = new List<(string code, DiscordEmbedBuilder builder)>(results.Count);
foreach (var (code, task) in results)
try
{
formattedResults.Add((code, await task.ConfigureAwait(false)));
}
catch (Exception e)
{
Config.Log.Warn(e, $"Couldn't get product code info for {code}");
}
// get only results with unique titles
formattedResults = formattedResults.DistinctBy(e => e.builder.Title).ToList();
var lookupEmoji = new DiscordComponentEmoji(DiscordEmoji.FromUnicode("🔍")); var lookupEmoji = new DiscordComponentEmoji(DiscordEmoji.FromUnicode("🔍"));
var formattedResults = await LookupProductCodeAndFormatAsync(client, codesToLookup).ConfigureAwait(false);
foreach (var result in formattedResults) foreach (var result in formattedResults)
try try
{ {
var messageBuilder = new DiscordMessageBuilder().AddEmbed(result.builder); var messageBuilder = new DiscordMessageBuilder().AddEmbed(result.builder);
if (channel.IsSpamChannel()) //todo: pass author from context and update OnCheckUpdatesButtonClick in psn check updates
messageBuilder.AddComponents(new DiscordButtonComponent(DiscordButtonStyle.Secondary, $"replace with game updates:{message.Author.Id}:{message.Id}:{result.code}", "Check for updates", emoji: lookupEmoji)); if (message is {Author: not null} && channel.IsSpamChannel())
messageBuilder.AddComponents(new DiscordButtonComponent(DiscordButtonStyle.Secondary, $"{GlobalButtonHandler.ReplaceWithUpdatesPrefix}{message.Author.Id}:{message.Id}:{result.code}", "Check for updates", emoji: lookupEmoji));
await DiscordMessageExtensions.UpdateOrCreateMessageAsync(null, channel, messageBuilder).ConfigureAwait(false); await DiscordMessageExtensions.UpdateOrCreateMessageAsync(null, channel, messageBuilder).ConfigureAwait(false);
} }
catch (Exception e) catch (Exception e)
@@ -88,10 +74,27 @@ internal static partial class ProductCodeLookup
} }
} }
internal static async ValueTask<List<(string code, DiscordEmbedBuilder builder)>> LookupProductCodeAndFormatAsync(DiscordClient client, List<string> codesToLookup)
{
var results = codesToLookup.Select(code => (code, client.LookupGameInfoAsync(code))).ToList();
var formattedResults = new List<(string code, DiscordEmbedBuilder builder)>(results.Count);
foreach (var (code, task) in results)
try
{
formattedResults.Add((code, await task.ConfigureAwait(false)));
}
catch (Exception e)
{
Config.Log.Warn(e, $"Couldn't get product code info for {code}");
}
// get only results with unique titles
return formattedResults.DistinctBy(e => e.builder.Title).ToList();
}
public static List<string> GetProductIds(string? input) public static List<string> GetProductIds(string? input)
{ {
if (string.IsNullOrEmpty(input)) if (string.IsNullOrEmpty(input))
return new(0); return [];
return Pattern().Matches(input) return Pattern().Matches(input)
.Select(match => (match.Groups["letters"].Value + match.Groups["numbers"]).ToUpper()) .Select(match => (match.Groups["letters"].Value + match.Groups["numbers"]).ToUpper())
@@ -102,7 +105,7 @@ internal static partial class ProductCodeLookup
public static async Task<DiscordEmbedBuilder> LookupGameInfoAsync(this DiscordClient client, string? code, string? gameTitle = null, bool forLog = false, string? category = null) public static async Task<DiscordEmbedBuilder> LookupGameInfoAsync(this DiscordClient client, string? code, string? gameTitle = null, bool forLog = false, string? category = null)
=> (await LookupGameInfoWithEmbedAsync(client, code, gameTitle, forLog, category).ConfigureAwait(false)).embedBuilder; => (await LookupGameInfoWithEmbedAsync(client, code, gameTitle, forLog, category).ConfigureAwait(false)).embedBuilder;
public static async Task<(DiscordEmbedBuilder embedBuilder, CompatResult? compatResult)> LookupGameInfoWithEmbedAsync(this DiscordClient client, string? code, string? gameTitle = null, bool forLog = false, string? category = null) public static async ValueTask<(DiscordEmbedBuilder embedBuilder, CompatResult? compatResult)> LookupGameInfoWithEmbedAsync(this DiscordClient client, string? code, string? gameTitle = null, bool forLog = false, string? category = null)
{ {
if (string.IsNullOrEmpty(code)) if (string.IsNullOrEmpty(code))
return (TitleInfo.Unknown.AsEmbed(code, gameTitle, forLog), null); return (TitleInfo.Unknown.AsEmbed(code, gameTitle, forLog), null);

View File

@@ -7,13 +7,13 @@ public static class AutosplitResponseHelper
public static Task SendAutosplitMessageAsync(this CommandContext ctx, StringBuilder message, int blockSize = EmbedPager.MaxMessageLength, string? blockEnd = "\n```", string? blockStart = "```\n") public static Task SendAutosplitMessageAsync(this CommandContext ctx, StringBuilder message, int blockSize = EmbedPager.MaxMessageLength, string? blockEnd = "\n```", string? blockStart = "```\n")
=> ctx.Channel.SendAutosplitMessageAsync(message, blockSize, blockEnd, blockStart); => ctx.Channel.SendAutosplitMessageAsync(message, blockSize, blockEnd, blockStart);
public static Task SendAutosplitMessageAsync(this CommandContext ctx, string message, int blockSize = EmbedPager.MaxMessageLength, string? blockEnd = "\n```", string? blockStart = "```\n") public static ValueTask SendAutosplitMessageAsync(this CommandContext ctx, string message, int blockSize = EmbedPager.MaxMessageLength, string? blockEnd = "\n```", string? blockStart = "```\n")
=> ctx.Channel.SendAutosplitMessageAsync(message, blockSize, blockEnd, blockStart); => ctx.Channel.SendAutosplitMessageAsync(message, blockSize, blockEnd, blockStart);
public static async Task SendAutosplitMessageAsync(this DiscordChannel channel, StringBuilder message, int blockSize = EmbedPager.MaxMessageLength, string? blockEnd = "\n```", string? blockStart = "```\n") public static async Task SendAutosplitMessageAsync(this DiscordChannel channel, StringBuilder message, int blockSize = EmbedPager.MaxMessageLength, string? blockEnd = "\n```", string? blockStart = "```\n")
=> await SendAutosplitMessageAsync(channel, message.ToString(), blockSize, blockEnd, blockStart).ConfigureAwait(false); => await SendAutosplitMessageAsync(channel, message.ToString(), blockSize, blockEnd, blockStart).ConfigureAwait(false);
public static async Task SendAutosplitMessageAsync(this DiscordChannel channel, string message, int blockSize = EmbedPager.MaxMessageLength, string? blockEnd = "\n```", string? blockStart = "```\n") public static async ValueTask SendAutosplitMessageAsync(this DiscordChannel channel, string message, int blockSize = EmbedPager.MaxMessageLength, string? blockEnd = "\n```", string? blockStart = "```\n")
{ {
if (string.IsNullOrEmpty(message)) if (string.IsNullOrEmpty(message))
return; return;
@@ -55,4 +55,33 @@ public static class AutosplitResponseHelper
Config.Log.Warn(e, "And yet the message length was " + remainingContent.Length); Config.Log.Warn(e, "And yet the message length was " + remainingContent.Length);
} }
} }
public static List<string> AutosplitMessage(string message, int blockSize = EmbedPager.MaxMessageLength, string? blockEnd = "\n```", string? blockStart = "```\n")
{
var result = new List<string>();
if (string.IsNullOrEmpty(message))
return [];
blockEnd ??= "";
blockStart ??= "";
var maxContentSize = blockSize - blockEnd.Length - blockStart.Length;
var buffer = new StringBuilder();
foreach (var line in message.Split(Environment.NewLine).Select(l => l.Trim(maxContentSize)))
{
if (buffer.Length + line.Length + blockEnd.Length > blockSize)
{
var content = buffer.ToString().Trim(blockSize - blockEnd.Length) + blockEnd;
if (content.Length > blockSize)
Config.Log.Error($"Somehow managed to go over {blockSize} characters in a message");
result.Add(content);
buffer.Clear().Append(blockStart);
}
else
buffer.Append('\n');
buffer.Append(line);
}
var remainingContent = buffer.ToString().Trim(blockSize);
result.Add(remainingContent);
return result;
}
} }

View File

@@ -0,0 +1,5 @@
<assembly name="DSharpPlus.Commands">
<member name="T:DSharpPlus.Commands.CommandAttribute">
<attribute ctor="M:JetBrains.Annotations.MeansImplicitUseAttribute.#ctor"/>
</member>
</assembly>