mirror of
https://github.com/RPCS3/discord-bot.git
synced 2026-01-31 01:25:22 +01:00
136 lines
6.0 KiB
C#
136 lines
6.0 KiB
C#
using System.Collections.Concurrent;
|
|
using System.Text.RegularExpressions;
|
|
using CompatApiClient;
|
|
using CompatApiClient.POCOs;
|
|
using CompatApiClient.Utils;
|
|
using CompatBot.Commands;
|
|
using CompatBot.Database.Providers;
|
|
using CompatBot.Utils.ResultFormatters;
|
|
|
|
namespace CompatBot.EventHandlers;
|
|
|
|
internal static partial class IsTheGamePlayableHandler
|
|
{
|
|
[GeneratedRegex(
|
|
@"(\b((is|does|can I play|any(one|1) tr(y|ied)|how's|(wonder(ing)?|me|knows?) if)\s+)(?<game_title_1>.+?)\s+((now|currently|at all|possibly|fully|(on (this|the) )emu(lator))\s+)?((it?s )?playable|work(s|ing)?|runs?|doing))\b"+
|
|
@"|(\b(((can I|possible to) (play|run)|any(one|1) tr(y|ied)|compat[ai]bility (with|of))\s+)(?<game_title_2>.+?)(\s+((now|currently|at all|possibly|fully)\s+)?((it?s )?playable|work(s|ing)?|on (it|this))\b|\?|$))"+
|
|
@"|(^(?<game_title_3>.+?)\s+((is )?(playable|work(s|ing)?))\?)",
|
|
RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.ExplicitCapture
|
|
)]
|
|
private static partial Regex GameNameStatusMention();
|
|
private static readonly ConcurrentDictionary<ulong, DateTime> CooldownBuckets = new();
|
|
private static readonly TimeSpan CooldownThreshold = TimeSpan.FromSeconds(5);
|
|
private static readonly Client Client = new();
|
|
|
|
public static async Task OnMessageCreated(DiscordClient c, MessageCreatedEventArgs args)
|
|
{
|
|
if (DefaultHandlerFilter.IsFluff(args.Message) || args.Channel is null)
|
|
return;
|
|
|
|
#if !DEBUG
|
|
if (!(args.Channel.Id == Config.BotGeneralChannelId
|
|
|| args.Channel.IsHelpChannel()))
|
|
return;
|
|
|
|
if (CooldownBuckets.TryGetValue(args.Channel.Id, out var lastCheck)
|
|
&& DateTime.UtcNow - lastCheck < CooldownThreshold)
|
|
return;
|
|
|
|
if (await args.Author.IsSmartlistedAsync(c, args.Guild).ConfigureAwait(false))
|
|
return;
|
|
#endif
|
|
|
|
var matches = GameNameStatusMention().Matches(args.Message.Content);
|
|
if (!matches.Any())
|
|
return;
|
|
|
|
try
|
|
{
|
|
var gameTitle = matches.Select(m => m.Groups["game_title_1"].Value)
|
|
.Concat(matches.Select(m => m.Groups["game_title_2"].Value))
|
|
.Concat(matches.Select(m => m.Groups["game_title_3"].Value))
|
|
.FirstOrDefault(t => !string.IsNullOrEmpty(t));
|
|
if (gameTitle is null or { Length: < 2 })
|
|
return;
|
|
|
|
gameTitle = CompatList.FixGameTitleSearch(gameTitle);
|
|
if (gameTitle.Length < 4)
|
|
return;
|
|
|
|
if (ProductCodeLookup.Pattern().IsMatch(args.Message.Content))
|
|
return;
|
|
|
|
var (_, info) = await LookupGameAsync(args.Channel, args.Message, gameTitle).ConfigureAwait(false);
|
|
if (string.IsNullOrEmpty(info?.Status))
|
|
return;
|
|
|
|
gameTitle = info.Title?.StripMarks();
|
|
if (string.IsNullOrEmpty(gameTitle))
|
|
return;
|
|
|
|
var botSpamChannel = await c.GetChannelAsync(Config.BotSpamId).ConfigureAwait(false);
|
|
var status = info.Status.ToLowerInvariant();
|
|
string msg;
|
|
if (status == "unknown")
|
|
msg = $"{args.Message.Author.Mention} {gameTitle} status is {status}";
|
|
else
|
|
{
|
|
if (status != "playable")
|
|
status += " (not playable)";
|
|
msg = $"{args.Message.Author.Mention} {gameTitle} is {status}";
|
|
if (!string.IsNullOrEmpty(info.Date))
|
|
msg += $" since {info.ToUpdated()}";
|
|
}
|
|
msg += $"\nfor more results please use [compatibility list](<https://rpcs3.net/compatibility>) or `/compatibility title:{gameTitle.Sanitize()}` command in {botSpamChannel.Mention}";
|
|
await args.Channel.SendMessageAsync(msg).ConfigureAwait(false);
|
|
CooldownBuckets[args.Channel.Id] = DateTime.UtcNow;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Config.Log.Error(e);
|
|
}
|
|
}
|
|
|
|
public static async Task<(string? productCode, TitleInfo? info)> LookupGameAsync(DiscordChannel channel, DiscordMessage message, string gameTitle)
|
|
{
|
|
var lastBotMessages = await channel.GetMessagesBeforeAsync(message.Id, 20, DateTime.UtcNow.AddSeconds(-30)).ConfigureAwait(false);
|
|
foreach (var msg in lastBotMessages)
|
|
if (BotReactionsHandler.NeedToSilence(msg).needToChill)
|
|
return (null, null);
|
|
|
|
try
|
|
{
|
|
var requestBuilder = RequestBuilder.Start().SetSearch(gameTitle);
|
|
var searchCompatListTask = Client.GetCompatResultAsync(requestBuilder, Config.Cts.Token);
|
|
var localList = await CompatList.GetLocalCompatResultAsync(requestBuilder).ConfigureAwait(false);
|
|
var status = await searchCompatListTask.ConfigureAwait(false);
|
|
status = status?.Append(localList);
|
|
if (status is null
|
|
|| status.ReturnCode is not CompatApiStatus.Success and not CompatApiStatus.NoExactMatch
|
|
|| status.Results.Count is 0)
|
|
return (null, null);
|
|
|
|
var sortedList = status.GetSortedList();
|
|
var bestMatch = sortedList.First();
|
|
var listWithStatus = sortedList
|
|
.TakeWhile(i => Math.Abs(i.score - bestMatch.score) < double.Epsilon)
|
|
.Where(i => !string.IsNullOrEmpty(i.info.Status) && i.info.Status != "Unknown")
|
|
.ToList();
|
|
if (listWithStatus.Count > 0)
|
|
bestMatch = listWithStatus.First();
|
|
var (code, info, score) = bestMatch;
|
|
Config.Log.Debug($"Looked up \"{gameTitle}\", got \"{info.Title}\" with score {score}");
|
|
if (score < Config.GameTitleMatchThreshold)
|
|
return (null, null);
|
|
|
|
if (!string.IsNullOrEmpty(info.Title))
|
|
StatsStorage.IncGameStat(info.Title);
|
|
return (code, info);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Config.Log.Warn(e);
|
|
return (null, null);
|
|
}
|
|
}
|
|
} |