mirror of
https://github.com/RPCS3/discord-bot.git
synced 2024-12-13 13:46:20 +00:00
commit
6a11348113
@ -16,6 +16,7 @@ using DSharpPlus;
|
||||
using DSharpPlus.CommandsNext;
|
||||
using DSharpPlus.CommandsNext.Attributes;
|
||||
using DSharpPlus.Entities;
|
||||
using DuoVia.FuzzyStrings;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CompatBot.Commands
|
||||
@ -111,13 +112,12 @@ Example usage:
|
||||
[Description("Provides information about available filters for the !top command")]
|
||||
public async Task Filters(CommandContext ctx)
|
||||
{
|
||||
var getDmTask = ctx.CreateDmAsync();
|
||||
var embed = new DiscordEmbedBuilder {Description = "List of recognized tokens in each filter category", Color = Config.Colors.Help}
|
||||
.AddField("Statuses", DicToDesc(ApiConfig.Statuses))
|
||||
.AddField("Release types", DicToDesc(ApiConfig.ReleaseTypes))
|
||||
.Build();
|
||||
var dm = await getDmTask.ConfigureAwait(false);
|
||||
await dm.SendMessageAsync(embed: embed).ConfigureAwait(false);
|
||||
var ch = await ctx.GetChannelForSpamAsync().ConfigureAwait(false);
|
||||
await ch.SendMessageAsync(embed: embed).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Group("latest"), Aliases("download"), TriggersTyping]
|
||||
@ -215,7 +215,10 @@ Example usage:
|
||||
return;
|
||||
}
|
||||
|
||||
var channel = LimitedToSpamChannel.IsSpamChannel(ctx.Channel) ? ctx.Channel : await ctx.Member.CreateDmChannelAsync().ConfigureAwait(false);
|
||||
#if DEBUG
|
||||
await Task.Delay(5_000).ConfigureAwait(false);
|
||||
#endif
|
||||
var channel = await ctx.GetChannelForSpamAsync().ConfigureAwait(false);
|
||||
foreach (var msg in FormatSearchResults(ctx, result))
|
||||
await channel.SendAutosplitMessageAsync(msg, blockStart:"", blockEnd:"").ConfigureAwait(false);
|
||||
}
|
||||
@ -234,12 +237,14 @@ Example usage:
|
||||
if (string.IsNullOrEmpty(request.customHeader))
|
||||
{
|
||||
result.AppendLine($"{authorMention} searched for: ***{request.search.Sanitize()}***");
|
||||
if (request.search.Contains("persona", StringComparison.InvariantCultureIgnoreCase))
|
||||
if (request.search.Contains("persona", StringComparison.InvariantCultureIgnoreCase)
|
||||
|| request.search.Contains("p5", StringComparison.InvariantCultureIgnoreCase))
|
||||
result.AppendLine("Did you try searching for ***Unnamed*** instead?");
|
||||
else if (!ctx.Channel.IsPrivate &&
|
||||
ctx.Message.Author.Id == 197163728867688448 &&
|
||||
(compatResult.Results.Values.Any(i => i.Title.Contains("afrika", StringComparison.InvariantCultureIgnoreCase) ||
|
||||
i.Title.Contains("africa", StringComparison.InvariantCultureIgnoreCase)))
|
||||
else if (!ctx.Channel.IsPrivate
|
||||
&& ctx.Message.Author.Id == 197163728867688448
|
||||
&& (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);
|
||||
@ -258,9 +263,13 @@ Example usage:
|
||||
|
||||
if (returnCode.displayResults)
|
||||
{
|
||||
foreach (var resultInfo in compatResult.Results.Take(request.amountRequested))
|
||||
var sortedList = compatResult.GetSortedList();
|
||||
foreach (var resultInfo in sortedList.Take(request.amountRequested))
|
||||
{
|
||||
var info = resultInfo.AsString();
|
||||
#if DEBUG
|
||||
info = $"`{CompatApiResultUtils.GetScore(request.search, resultInfo.Value):0.000000}` {info}";
|
||||
#endif
|
||||
result.AppendLine(info);
|
||||
}
|
||||
yield return result.ToString();
|
||||
|
@ -24,9 +24,10 @@ namespace CompatBot.Commands
|
||||
[GroupCommand]
|
||||
public async Task ShowExplanation(CommandContext ctx, [RemainingText, Description("Term to explain")] string term)
|
||||
{
|
||||
var sourceTerm = term;
|
||||
if (string.IsNullOrEmpty(term))
|
||||
{
|
||||
var ch = LimitedToSpamChannel.IsSpamChannel(ctx.Channel) ? ctx.Channel : await ctx.Member.CreateDmChannelAsync().ConfigureAwait(false);
|
||||
var ch = await ctx.GetChannelForSpamAsync().ConfigureAwait(false);
|
||||
await ch.SendMessageAsync("Please specify term to explain").ConfigureAwait(false);
|
||||
await List(ctx).ConfigureAwait(false);
|
||||
return;
|
||||
@ -39,42 +40,48 @@ namespace CompatBot.Commands
|
||||
return;
|
||||
|
||||
term = term.ToLowerInvariant();
|
||||
using (var db = new BotDb())
|
||||
var result = await LookupTerm(term).ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(result.explanation))
|
||||
{
|
||||
var explanation = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == term).ConfigureAwait(false);
|
||||
if (explanation != null)
|
||||
term = term.StripQuotes();
|
||||
var idx = term.LastIndexOf(" to ");
|
||||
if (idx > 0)
|
||||
{
|
||||
await ctx.RespondAsync(explanation.Text).ConfigureAwait(false);
|
||||
return;
|
||||
var potentialUserId = term.Substring(idx + 4).Trim();
|
||||
bool hasMention = false;
|
||||
try
|
||||
{
|
||||
var lookup = await new DiscordUserConverter().ConvertAsync(potentialUserId, ctx)
|
||||
.ConfigureAwait(false);
|
||||
hasMention = lookup.HasValue;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
if (hasMention)
|
||||
{
|
||||
term = term.Substring(0, idx).TrimEnd();
|
||||
result = await LookupTerm(term).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
term = term.StripQuotes();
|
||||
var idx = term.LastIndexOf(" to ");
|
||||
if (idx > 0)
|
||||
try
|
||||
{
|
||||
var potentialUserId = term.Substring(idx + 4).Trim();
|
||||
bool hasMention = false;
|
||||
try
|
||||
if (!string.IsNullOrEmpty(result.explanation))
|
||||
{
|
||||
var lookup = await new DiscordUserConverter().ConvertAsync(potentialUserId, ctx).ConfigureAwait(false);
|
||||
hasMention = lookup.HasValue;
|
||||
}
|
||||
catch { }
|
||||
if (hasMention)
|
||||
{
|
||||
term = term.Substring(0, idx).TrimEnd();
|
||||
using (var db = new BotDb())
|
||||
{
|
||||
var explanation = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == term).ConfigureAwait(false);
|
||||
if (explanation != null)
|
||||
{
|
||||
await ctx.RespondAsync(explanation.Text).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrEmpty(result.fuzzyMatch))
|
||||
await ctx.RespondAsync($"Showing explanation for `{result.fuzzyMatch}`:").ConfigureAwait(false);
|
||||
await ctx.RespondAsync(result.explanation).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Config.Log.Error(e, "Failed to explain " + sourceTerm);
|
||||
return;
|
||||
}
|
||||
|
||||
string inSpecificLocation = null;
|
||||
if (!LimitedToSpamChannel.IsSpamChannel(ctx.Channel))
|
||||
@ -172,7 +179,7 @@ namespace CompatBot.Commands
|
||||
[Description("List all known terms that could be used for !explain command")]
|
||||
public async Task List(CommandContext ctx)
|
||||
{
|
||||
var responseChannel = LimitedToSpamChannel.IsSpamChannel(ctx.Channel) ? ctx.Channel : await ctx.CreateDmAsync().ConfigureAwait(false);
|
||||
var responseChannel = await ctx.GetChannelForSpamAsync().ConfigureAwait(false);
|
||||
using (var db = new BotDb())
|
||||
{
|
||||
var keywords = await db.Explanation.Select(e => e.Keyword).OrderBy(t => t).ToListAsync().ConfigureAwait(false);
|
||||
@ -246,6 +253,27 @@ namespace CompatBot.Commands
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<(string explanation, string fuzzyMatch)> LookupTerm(string term)
|
||||
{
|
||||
string fuzzyMatch = null;
|
||||
Explanation explanation;
|
||||
using (var db = new BotDb())
|
||||
{
|
||||
explanation = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == term).ConfigureAwait(false);
|
||||
if (explanation == null)
|
||||
{
|
||||
var termList = await db.Explanation.Select(e => e.Keyword).ToListAsync().ConfigureAwait(false);
|
||||
var bestSuggestion = termList.OrderByDescending(term.GetFuzzyCoefficientCached).First();
|
||||
if (term.GetFuzzyCoefficientCached(bestSuggestion) > 0.2)
|
||||
{
|
||||
explanation = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == bestSuggestion).ConfigureAwait(false);
|
||||
fuzzyMatch = bestSuggestion;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (explanation?.Text, fuzzyMatch);
|
||||
}
|
||||
|
||||
private async Task DumpLink(CommandContext ctx, string messageLink)
|
||||
{
|
||||
string explanation = null;
|
||||
|
@ -75,7 +75,7 @@ namespace CompatBot.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
var responseChannel = LimitedToSpamChannel.IsSpamChannel(ctx.Channel) ? ctx.Channel : await ctx.CreateDmAsync().ConfigureAwait(false);
|
||||
var responseChannel = await ctx.GetChannelForSpamAsync().ConfigureAwait(false);
|
||||
const int maxTitleLength = 80;
|
||||
var maxNum = openPrList.Max(pr => pr.Number).ToString().Length + 1;
|
||||
var maxAuthor = openPrList.Max(pr => pr.User.Login.Length);
|
||||
|
@ -16,19 +16,20 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DSharpPlus" Version="4.0.0-nightly-00560" />
|
||||
<PackageReference Include="DSharpPlus.CommandsNext" Version="4.0.0-nightly-00560" />
|
||||
<PackageReference Include="DSharpPlus.Interactivity" Version="4.0.0-nightly-00560" />
|
||||
<PackageReference Include="MathParser.org-mXparser" Version="4.2.2" />
|
||||
<PackageReference Include="DSharpPlus" Version="4.0.0-nightly-00564" />
|
||||
<PackageReference Include="DSharpPlus.CommandsNext" Version="4.0.0-nightly-00564" />
|
||||
<PackageReference Include="DSharpPlus.Interactivity" Version="4.0.0-nightly-00564" />
|
||||
<PackageReference Include="DuoVia.FuzzyStrings" Version="2.0.1" />
|
||||
<PackageReference Include="MathParser.org-mXparser" Version="4.3.1" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.2.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="2.2.0" />
|
||||
<PackageReference Include="NLog" Version="4.5.11" />
|
||||
<PackageReference Include="NReco.Text.AhoCorasickDoubleArrayTrie" Version="1.0.1" />
|
||||
<PackageReference Include="SharpCompress" Version="0.22.0" />
|
||||
<PackageReference Include="System.IO.Pipelines" Version="4.5.2" />
|
||||
<PackageReference Include="System.IO.Pipelines" Version="4.5.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -2,7 +2,6 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CompatApiClient;
|
||||
using CompatApiClient.Utils;
|
||||
@ -12,11 +11,10 @@ using DSharpPlus.EventArgs;
|
||||
|
||||
namespace CompatBot.EventHandlers
|
||||
{
|
||||
internal sealed class IsTheGamePlayableHandler
|
||||
internal static class IsTheGamePlayableHandler
|
||||
{
|
||||
private const RegexOptions DefaultOptions = RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.ExplicitCapture;
|
||||
private static readonly Regex GameNameStatusMention = new Regex(@"((is|does|can I play)\s+|^)(?<game_title>.+?)(\s+((now|currently)\s+)?((is )?playable|work(ing)?)|\?)", DefaultOptions);
|
||||
private static readonly SemaphoreSlim TheDoor = new SemaphoreSlim(1, 1);
|
||||
private static readonly Regex GameNameStatusMention = new Regex(@"((is|does|can I play)\s+|(?<dumb>^))(?<game_title>.+?)(\s+((now|currently)\s+)?((is )?playable|work(s|ing)?)(?(dumb)\?))", DefaultOptions);
|
||||
private static readonly ConcurrentDictionary<ulong, DateTime> CooldownBuckets = new ConcurrentDictionary<ulong, DateTime>();
|
||||
private static readonly TimeSpan CooldownThreshold = TimeSpan.FromSeconds(5);
|
||||
private static readonly Client Client = new Client();
|
||||
@ -67,7 +65,10 @@ namespace CompatBot.EventHandlers
|
||||
var status = await Client.GetCompatResultAsync(requestBuilder, Config.Cts.Token).ConfigureAwait(false);
|
||||
if (status.ReturnCode == 0 && status.Results.Any())
|
||||
{
|
||||
var info = status.Results.First().Value;
|
||||
var info = status.GetSortedList().First().Value;
|
||||
if (CompatApiResultUtils.GetScore(gameTitle, info) < 0.2)
|
||||
return;
|
||||
|
||||
var botSpamChannel = await args.Client.GetChannelAsync(Config.BotSpamId).ConfigureAwait(false);
|
||||
var msg = $"{args.Author.Mention} {info.Title} is {info.Status.ToLowerInvariant()} since {info.ToUpdated()}\n" +
|
||||
$"for more results please use compatibility list (<https://rpcs3.net/compatibility>) or `{Config.CommandPrefix}c` command in {botSpamChannel.Mention} (`!c {gameTitle.Sanitize()}`)";
|
||||
|
@ -143,6 +143,8 @@ namespace CompatBot.EventHandlers.LogParsing
|
||||
["'sys_fs_open' failed"] = new Regex(@"'sys_fs_open' failed .+\xE2\x80\x9C/dev_bdvd/(?<broken_filename>.+)\xE2\x80\x9D.*?\r?$", DefaultOptions),
|
||||
["'sys_fs_opendir' failed"] = new Regex(@"'sys_fs_opendir' failed .+\xE2\x80\x9C/dev_bdvd/(?<broken_directory>.+)\xE2\x80\x9D.*?\r?$", DefaultOptions),
|
||||
["LDR: EDAT: "] = new Regex(@"EDAT: Block at offset (?<edat_block_offset>0x[0-9a-f]+) has invalid hash!.*?\r?$", DefaultOptions),
|
||||
["PS3 firmware is not installed"] = new Regex(@"(?<fw_missing_msg>PS3 firmware is not installed.+)\r?$", DefaultOptions),
|
||||
["do you have the PS3 firmware installed"] = new Regex(@"(?<fw_missing_something>do you have the PS3 firmware installed.*)\r?$", DefaultOptions),
|
||||
},
|
||||
OnSectionEnd = MarkAsCompleteAndReset,
|
||||
EndTrigger = "All threads stopped...",
|
||||
@ -186,7 +188,9 @@ namespace CompatBot.EventHandlers.LogParsing
|
||||
"build_and_specs",
|
||||
"vulkan_gpu", "d3d_gpu",
|
||||
"driver_version", "driver_manuf",
|
||||
"driver_manuf_new", "driver_version_new"
|
||||
"driver_manuf_new", "driver_version_new",
|
||||
"vulkan_found_device", "vulkan_compatible_device_name",
|
||||
"vulkan_gpu", "vulkan_driver_version_raw"
|
||||
);
|
||||
#if DEBUG
|
||||
Console.WriteLine("===== cleared");
|
||||
|
@ -11,7 +11,7 @@ namespace CompatBot.EventHandlers
|
||||
private static readonly TimeSpan PassiveCheckInterval = TimeSpan.FromHours(1);
|
||||
private static readonly TimeSpan ActiveCheckInterval = TimeSpan.FromSeconds(5);
|
||||
public static TimeSpan CheckInterval { get; private set; } = PassiveCheckInterval;
|
||||
public static DateTime? RapidStart { get; private set; } = null;
|
||||
public static DateTime? RapidStart { get; private set; }
|
||||
|
||||
public static Task OnMessageCreated(MessageCreateEventArgs args)
|
||||
{
|
||||
|
@ -40,12 +40,14 @@ namespace CompatBot.EventHandlers
|
||||
|
||||
try
|
||||
{
|
||||
var explanation = await GetLogUploadExplanationAsync().ConfigureAwait(false);
|
||||
|
||||
var lastBotMessages = await args.Channel.GetMessagesBeforeAsync(args.Message.Id, 20, DateTime.UtcNow.AddSeconds(-30)).ConfigureAwait(false);
|
||||
foreach (var msg in lastBotMessages)
|
||||
if (BotShutupHandler.NeedToSilence(msg).needToChill)
|
||||
if (BotShutupHandler.NeedToSilence(msg).needToChill
|
||||
|| (msg.Author.IsCurrent && msg.Content == explanation))
|
||||
return;
|
||||
|
||||
var explanation = await GetLogUploadExplanationAsync().ConfigureAwait(false);
|
||||
await args.Channel.SendMessageAsync(explanation).ConfigureAwait(false);
|
||||
lastMention = DateTime.UtcNow;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using CompatBot.Commands.Attributes;
|
||||
using DSharpPlus.CommandsNext;
|
||||
using DSharpPlus.Entities;
|
||||
|
||||
@ -14,6 +15,11 @@ namespace CompatBot.Utils
|
||||
return ctx.Channel.IsPrivate ? ctx.Channel : await ctx.Member.CreateDmChannelAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static Task<DiscordChannel> GetChannelForSpamAsync(this CommandContext ctx)
|
||||
{
|
||||
return LimitedToSpamChannel.IsSpamChannel(ctx.Channel) ? Task.FromResult(ctx.Channel) : ctx.CreateDmAsync();
|
||||
}
|
||||
|
||||
public static Task<string> GetUserNameAsync(this CommandContext ctx, ulong userId, bool? forDmPurposes = null, string defaultName = "Unknown user")
|
||||
{
|
||||
return ctx.Client.GetUserNameAsync(ctx.Channel, userId, forDmPurposes, defaultName);
|
||||
|
39
CompatBot/Utils/CompatApiResultUtils.cs
Normal file
39
CompatBot/Utils/CompatApiResultUtils.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CompatApiClient.POCOs;
|
||||
|
||||
namespace CompatBot.Utils
|
||||
{
|
||||
internal static class CompatApiResultUtils
|
||||
{
|
||||
public static List<KeyValuePair<string, TitleInfo>> GetSortedList(this CompatResult result)
|
||||
{
|
||||
var search = result.RequestBuilder.search;
|
||||
var sortedList = result.Results.ToList();
|
||||
if (!string.IsNullOrEmpty(search))
|
||||
sortedList = sortedList
|
||||
.OrderByDescending(kvp => GetScore(search, kvp.Value))
|
||||
.ThenBy(kvp => kvp.Value.Title)
|
||||
.ThenBy(kvp => kvp.Key)
|
||||
.ToList();
|
||||
if (GetScore(search, sortedList.First().Value) < 0.2)
|
||||
sortedList = sortedList
|
||||
.OrderBy(kvp => kvp.Value.Title)
|
||||
.ThenBy(kvp => kvp.Key)
|
||||
.ToList();
|
||||
return sortedList;
|
||||
}
|
||||
|
||||
public static double GetScore(string search, TitleInfo titleInfo)
|
||||
{
|
||||
var score = Math.Max(
|
||||
search.GetFuzzyCoefficientCached(titleInfo.Title),
|
||||
search.GetFuzzyCoefficientCached(titleInfo.AlternativeTitle)
|
||||
);
|
||||
if (score > 0.3)
|
||||
return score;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -476,6 +476,11 @@ namespace CompatBot.Utils.ResultFormatters
|
||||
if (!string.IsNullOrEmpty(items["xaudio_init_error"]))
|
||||
notes.AppendLine("XAudio initialization failed; make sure you have audio output device working");
|
||||
|
||||
if (!string.IsNullOrEmpty(items["fw_missing_msg"])
|
||||
|| !string.IsNullOrEmpty(items["fw_missing_something"]))
|
||||
notes.AppendLine("PS3 firmware is missing or corrupted");
|
||||
|
||||
|
||||
if (state.Error == LogParseState.ErrorCode.SizeLimit)
|
||||
notes.AppendLine("The log was too large, so only the last processed run is shown");
|
||||
|
||||
|
@ -3,6 +3,9 @@ using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using DuoVia.FuzzyStrings;
|
||||
using HomoglyphConverter;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace CompatBot.Utils
|
||||
{
|
||||
@ -13,6 +16,7 @@ namespace CompatBot.Utils
|
||||
.GetEncoding()
|
||||
?? Encoding.ASCII;
|
||||
private static readonly Encoding Utf8 = new UTF8Encoding(false);
|
||||
private static readonly MemoryCache FuzzyPairCache = new MemoryCache(new MemoryCacheOptions {ExpirationScanFrequency = TimeSpan.FromMinutes(10)});
|
||||
|
||||
private static readonly HashSet<char> SpaceCharacters = new HashSet<char>
|
||||
{
|
||||
@ -105,5 +109,62 @@ namespace CompatBot.Utils
|
||||
|
||||
return len == 0 ? "" : str.Substring(start, len);
|
||||
}
|
||||
|
||||
internal static string GetAcronym(this string str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
return str;
|
||||
|
||||
var result = "";
|
||||
bool previousWasLetter = false;
|
||||
foreach (var c in str)
|
||||
{
|
||||
var isLetter = char.IsLetterOrDigit(c);
|
||||
if (isLetter && !previousWasLetter)
|
||||
result += c;
|
||||
previousWasLetter = isLetter;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static double GetFuzzyCoefficientCached(this string strA, string strB)
|
||||
{
|
||||
strA = strA?.ToLowerInvariant() ?? "";
|
||||
strB = strB?.ToLowerInvariant() ?? "";
|
||||
var cacheKey = GetFuzzyCacheKey(strA, strB);
|
||||
if (!FuzzyPairCache.TryGetValue(cacheKey, out FuzzyCacheValue match)
|
||||
|| strA != match.StrA
|
||||
|| strB != match.StrB)
|
||||
match = new FuzzyCacheValue
|
||||
{
|
||||
StrA = strA,
|
||||
StrB = strB,
|
||||
Coefficient = Normalizer.ToCanonicalForm(strA).GetScoreWithAcronym(Normalizer.ToCanonicalForm(strB)),
|
||||
};
|
||||
FuzzyPairCache.Set(cacheKey, match);
|
||||
return match.Coefficient;
|
||||
}
|
||||
|
||||
private static double GetScoreWithAcronym(this string strA, string strB)
|
||||
{
|
||||
return Math.Max(
|
||||
strA.DiceCoefficient(strB),
|
||||
strA.DiceCoefficient(strB.GetAcronym().ToLowerInvariant())
|
||||
);
|
||||
}
|
||||
|
||||
private static (long, int) GetFuzzyCacheKey(string strA, string strB)
|
||||
{
|
||||
var hashPair = (((long) (strA.GetHashCode())) << (sizeof(int) * 8)) | (((long) strB.GetHashCode()) & ((long) uint.MaxValue));
|
||||
var lengthPair = (strA.Length << (sizeof(short) * 8)) | (strB.Length & ushort.MaxValue);
|
||||
return (hashPair, lengthPair);
|
||||
}
|
||||
|
||||
private class FuzzyCacheValue
|
||||
{
|
||||
public string StrA;
|
||||
public string StrB;
|
||||
public double Coefficient;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user