update code with new language and api features

This commit is contained in:
13xforever 2023-04-20 21:22:50 +05:00
parent 0d99046a0b
commit d957936ede
No known key found for this signature in database
GPG Key ID: 2B2A36B482FE70C5
99 changed files with 719 additions and 768 deletions

View File

@ -23,8 +23,7 @@ public static class CirrusCi
static CirrusCi()
{
var collection = new ServiceCollection();
collection.AddClient(ExecutionStrategy.CacheAndNetwork)
.ConfigureHttpClient(c => c.BaseAddress = new("https://api.cirrus-ci.com/graphql"));
collection.AddClient(ExecutionStrategy.CacheAndNetwork).ConfigureHttpClient(c => c.BaseAddress = new("https://api.cirrus-ci.com/graphql"));
ServiceProvider = collection.BuildServiceProvider();
}

View File

@ -90,8 +90,8 @@ public static class ApiConfig
catch (Exception e)
{
Log.Fatal(e);
ReverseDirections = new Dictionary<string, char>();
ReverseReleaseTypes = new Dictionary<string, char>();
ReverseDirections = new();
ReverseReleaseTypes = new();
}
}
}

View File

@ -31,8 +31,7 @@ public class CompressionMessageHandler : DelegatingHandler
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (isServer
&& request.Content?.Headers.ContentEncoding != null
&& request.Content.Headers.ContentEncoding.FirstOrDefault() is string serverEncoding
&& request.Content?.Headers.ContentEncoding.FirstOrDefault() is string serverEncoding
&& Compressors.FirstOrDefault(c => c.EncodingType.Equals(serverEncoding, StringComparison.OrdinalIgnoreCase)) is ICompressor serverDecompressor)
{
request.Content = new DecompressedContent(request.Content, serverDecompressor);

View File

@ -8,16 +8,11 @@ public sealed class CompatApiCommitHashConverter : JsonConverter<string>
{
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Number
&& !reader.HasValueSequence
&& reader.ValueSpan.Length == 1
&& reader.ValueSpan[0] == (byte)'0')
{
_ = reader.GetInt32();
return null;
}
if (reader is not { TokenType: JsonTokenType.Number, HasValueSequence: false, ValueSpan: [(byte)'0'] })
return reader.GetString();
return reader.GetString();
_ = reader.GetInt32();
return null;
}
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)

View File

@ -18,15 +18,8 @@ public static class NamingStyles
return value;
}
public static string Dashed(string value)
{
return Delimitied(value, '-');
}
public static string Underscore(string value)
{
return Delimitied(value, '_');
}
public static string Dashed(string value) => Delimitied(value, '-');
public static string Underscore(string value) => Delimitied(value, '_');
private static string Delimitied(string value, char separator)
{

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Numerics;
namespace CompatApiClient.Utils;
@ -7,7 +8,7 @@ public static class Statistics
{
public static long Mean(this IEnumerable<long> data)
{
System.Numerics.BigInteger sum = 0;
BigInteger sum = 0;
var itemCount = 0;
foreach (var value in data)
{
@ -22,12 +23,12 @@ public static class Statistics
public static double StdDev(this IEnumerable<long> data)
{
System.Numerics.BigInteger σx = 0, σx2 = 0;
BigInteger σx = 0, σx2 = 0;
var n = 0;
foreach (var value in data)
{
σx += value;
σx2 += (System.Numerics.BigInteger)value * value;
σx2 += (BigInteger)value * value;
n++;
}
if (n == 0)

View File

@ -14,21 +14,15 @@ public static class UriExtensions
public static NameValueCollection ParseQueryString(Uri uri)
{
if (!uri.IsAbsoluteUri)
uri = new Uri(FakeHost, uri);
uri = new(FakeHost, uri);
return uri.ParseQueryString();
}
public static string? GetQueryParameter(this Uri uri, string name)
{
var parameters = ParseQueryString(uri);
return parameters[name];
}
=> ParseQueryString(uri)[name];
public static Uri AddQueryParameter(this Uri uri, string name, string value)
{
var queryValue = WebUtility.UrlEncode(name) + "=" + WebUtility.UrlEncode(value);
return AddQueryValue(uri, queryValue);
}
=> AddQueryValue(uri, WebUtility.UrlEncode(name) + "=" + WebUtility.UrlEncode(value));
public static Uri AddQueryParameters(Uri uri, IEnumerable<KeyValuePair<string, string>> parameters)
{
@ -105,16 +99,16 @@ public static class UriExtensions
if (isAbsolute)
{
var builder = new UriBuilder(uri) { Query = value };
return new Uri(builder.ToString());
return new(builder.ToString());
}
else
{
var startWithSlash = uri.OriginalString.StartsWith("/");
uri = new Uri(FakeHost, uri);
uri = new(FakeHost, uri);
var builder = new UriBuilder(uri) { Query = value };
var additionalStrip = startWithSlash ? 0 : 1;
var newUri = builder.ToString()[(FakeHost.OriginalString.Length + additionalStrip)..];
return new Uri(newUri, UriKind.Relative);
return new(newUri, UriKind.Relative);
}
}
}

View File

@ -50,13 +50,11 @@ public static class Utils
=> AsStorageUnit((long)bytes);
public static string AsStorageUnit(this long bytes)
{
if (bytes < UnderKB)
return $"{bytes} byte{(bytes == 1 ? "" : "s")}";
if (bytes < UnderMB)
return $"{bytes / 1024.0:0.##} KB";
if (bytes < UnderGB)
return $"{bytes / 1024.0 / 1024:0.##} MB";
return $"{bytes / 1024.0 / 1024 / 1024:0.##} GB";
}
=> bytes switch
{
< UnderKB => $"{bytes} byte{(bytes == 1 ? "" : "s")}",
< UnderMB => $"{bytes / 1024.0:0.##} KB",
< UnderGB => $"{bytes / (1024.0 * 1024):0.##} MB",
_ => $"{bytes / (1024.0 * 1024 * 1024):0.##} GB"
};
}

View File

@ -4,12 +4,13 @@ using System.Threading;
using System.Threading.Tasks;
using CompatApiClient;
using Microsoft.Extensions.Caching.Memory;
using Octokit;
namespace GithubClient;
public class Client
{
private readonly Octokit.GitHubClient client;
private readonly GitHubClient client;
private static readonly TimeSpan PrStatusCacheTime = TimeSpan.FromMinutes(3);
private static readonly TimeSpan IssueStatusCacheTime = TimeSpan.FromMinutes(30);
@ -22,26 +23,22 @@ public class Client
public Client(string? githubToken)
{
client = new Octokit.GitHubClient(new Octokit.ProductHeaderValue(ApiConfig.ProductName, ApiConfig.ProductVersion));
if (!string.IsNullOrEmpty(githubToken))
{
client.Credentials = new Octokit.Credentials(githubToken);
}
client = new(new ProductHeaderValue(ApiConfig.ProductName, ApiConfig.ProductVersion));
if (githubToken is {Length: >0})
client.Credentials = new(githubToken);
}
public async Task<Octokit.PullRequest?> GetPrInfoAsync(int pr, CancellationToken cancellationToken)
public async Task<PullRequest?> GetPrInfoAsync(int pr, CancellationToken cancellationToken)
{
if (StatusesCache.TryGetValue(pr, out Octokit.PullRequest? result))
if (StatusesCache.TryGetValue(pr, out PullRequest? result))
{
ApiConfig.Log.Debug($"Returned {nameof(Octokit.PullRequest)} for {pr} from cache");
ApiConfig.Log.Debug($"Returned {nameof(PullRequest)} for {pr} from cache");
return result;
}
try
{
var request = client.PullRequest.Get("RPCS3", "rpcs3", pr);
request.Wait(cancellationToken);
result = (await request.ConfigureAwait(false));
result = await client.PullRequest.Get("RPCS3", "rpcs3", pr).WaitAsync(cancellationToken).ConfigureAwait(false);
UpdateRateLimitStats();
}
catch (Exception e)
@ -50,61 +47,53 @@ public class Client
}
if (result == null)
{
ApiConfig.Log.Debug($"Failed to get {nameof(Octokit.PullRequest)}, returning empty result");
ApiConfig.Log.Debug($"Failed to get {nameof(PullRequest)}, returning empty result");
return new(pr);
}
StatusesCache.Set(pr, result, PrStatusCacheTime);
ApiConfig.Log.Debug($"Cached {nameof(Octokit.PullRequest)} for {pr} for {PrStatusCacheTime}");
ApiConfig.Log.Debug($"Cached {nameof(PullRequest)} for {pr} for {PrStatusCacheTime}");
return result;
}
public async Task<Octokit.Issue?> GetIssueInfoAsync(int issue, CancellationToken cancellationToken)
public async Task<Issue?> GetIssueInfoAsync(int issue, CancellationToken cancellationToken)
{
if (IssuesCache.TryGetValue(issue, out Octokit.Issue? result))
if (IssuesCache.TryGetValue(issue, out Issue? result))
{
ApiConfig.Log.Debug($"Returned {nameof(Octokit.Issue)} for {issue} from cache");
ApiConfig.Log.Debug($"Returned {nameof(Issue)} for {issue} from cache");
return result;
}
try
{
var request = client.Issue.Get("RPCS3", "rpcs3", issue);
request.Wait(cancellationToken);
result = (await request.ConfigureAwait(false));
result = await client.Issue.Get("RPCS3", "rpcs3", issue).WaitAsync(cancellationToken).ConfigureAwait(false);
UpdateRateLimitStats();
IssuesCache.Set(issue, result, IssueStatusCacheTime);
ApiConfig.Log.Debug($"Cached {nameof(Issue)} for {issue} for {IssueStatusCacheTime}");
return result;
}
catch (Exception e)
{
ApiConfig.Log.Error(e);
}
if (result == null)
{
ApiConfig.Log.Debug($"Failed to get {nameof(Octokit.Issue)}, returning empty result");
return new() { };
}
IssuesCache.Set(issue, result, IssueStatusCacheTime);
ApiConfig.Log.Debug($"Cached {nameof(Octokit.Issue)} for {issue} for {IssueStatusCacheTime}");
return result;
ApiConfig.Log.Debug($"Failed to get {nameof(Issue)}, returning empty result");
return new();
}
public Task<IReadOnlyList<Octokit.PullRequest>?> GetOpenPrsAsync(CancellationToken cancellationToken) => GetPrsWithStatusAsync(new Octokit.PullRequestRequest
public Task<IReadOnlyList<PullRequest>?> GetOpenPrsAsync(CancellationToken cancellationToken)
=> GetPrsWithStatusAsync(new() { State = ItemStateFilter.Open }, cancellationToken);
public Task<IReadOnlyList<PullRequest>?> GetClosedPrsAsync(CancellationToken cancellationToken) => GetPrsWithStatusAsync(new()
{
State = Octokit.ItemStateFilter.Open
State = ItemStateFilter.Closed,
SortProperty = PullRequestSort.Updated,
SortDirection = SortDirection.Descending
}, cancellationToken);
public Task<IReadOnlyList<Octokit.PullRequest>?> GetClosedPrsAsync(CancellationToken cancellationToken) => GetPrsWithStatusAsync(new Octokit.PullRequestRequest
private async Task<IReadOnlyList<PullRequest>?> GetPrsWithStatusAsync(PullRequestRequest filter, CancellationToken cancellationToken)
{
State = Octokit.ItemStateFilter.Closed,
SortProperty = Octokit.PullRequestSort.Updated,
SortDirection = Octokit.SortDirection.Descending
}, cancellationToken);
private async Task<IReadOnlyList<Octokit.PullRequest>?> GetPrsWithStatusAsync(Octokit.PullRequestRequest filter, CancellationToken cancellationToken)
{
var statusURI = "https://api.github.com/repos/RPCS3/rpcs3/pulls?state=" + filter.ToString();
if (StatusesCache.TryGetValue(statusURI, out IReadOnlyList<Octokit.PullRequest>? result))
var statusUri = "https://api.github.com/repos/RPCS3/rpcs3/pulls?state=" + filter;
if (StatusesCache.TryGetValue(statusUri, out IReadOnlyList<PullRequest>? result))
{
ApiConfig.Log.Debug("Returned list of opened PRs from cache");
return result;
@ -112,23 +101,17 @@ public class Client
try
{
var request = client.PullRequest.GetAllForRepository("RPCS3", "rpcs3", filter);
request.Wait(cancellationToken);
result = (await request.ConfigureAwait(false));
result = await client.PullRequest.GetAllForRepository("RPCS3", "rpcs3", filter).WaitAsync(cancellationToken).ConfigureAwait(false);
UpdateRateLimitStats();
StatusesCache.Set(statusUri, result, PrStatusCacheTime);
foreach (var prInfo in result)
StatusesCache.Set(prInfo.Number, prInfo, PrStatusCacheTime);
ApiConfig.Log.Debug($"Cached list of open PRs for {PrStatusCacheTime}");
}
catch (Exception e)
{
ApiConfig.Log.Error(e);
}
if (result != null)
{
StatusesCache.Set(statusURI, result, PrStatusCacheTime);
foreach (var prInfo in result)
StatusesCache.Set(prInfo.Number, prInfo, PrStatusCacheTime);
ApiConfig.Log.Debug($"Cached list of open PRs for {PrStatusCacheTime}");
}
return result;
}
@ -136,16 +119,12 @@ public class Client
{
var apiInfo = client.GetLastApiInfo();
if (apiInfo == null)
{
return;
}
RateLimit = apiInfo.RateLimit.Limit;
RateLimitRemaining = apiInfo.RateLimit.Remaining;
RateLimitResetTime = DateTimeOffset.FromUnixTimeSeconds(apiInfo.RateLimit.ResetAsUtcEpochSeconds).UtcDateTime;
if (RateLimitRemaining < 10)
ApiConfig.Log.Warn($"Github rate limit is low: {RateLimitRemaining} out of {RateLimit}, will be reset on {RateLimitResetTime:u}");
}
}

View File

@ -29,7 +29,7 @@ public class IrdClient
public IrdClient()
{
client = HttpClientFactory.Create(new CompressionMessageHandler());
jsonOptions = new JsonSerializerOptions
jsonOptions = new()
{
PropertyNamingPolicy = SpecialJsonNamingPolicy.SnakeCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
@ -209,9 +209,8 @@ public class IrdClient
var idx = html.LastIndexOf("</span>", StringComparison.Ordinal);
var result = html[(idx + 7)..].Trim();
if (string.IsNullOrEmpty(result))
return null;
return result;
if (result is {Length: >0})
return result;
return null;
}
}

View File

@ -38,8 +38,10 @@ public class Client
"pl-PL", "pt-BR", "pt-PT", "ru-RU", "ru-UA", "sv-SE", "tr-TR", "zh-Hans-CN", "zh-Hans-HK", "zh-Hant-HK", "zh-Hant-TW",
};
// Dest=87;ImageVersion=0001091d;SystemSoftwareVersion=4.8500;CDN=http://duk01.ps3.update.playstation.net/update/ps3/image/uk/2019_0828_c975768e5d70e105a72656f498cc9be9/PS3UPDAT.PUP;CDN_Timeout=30;
private static readonly Regex FwVersionInfo = new(@"Dest=(?<dest>\d+);ImageVersion=(?<image>[0-9a-f]+);SystemSoftwareVersion=(?<version>\d+\.\d+);CDN=(?<url>http[^;]+);CDN_Timeout=(?<timeout>\d+)",
RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.Singleline | RegexOptions.IgnoreCase);
private static readonly Regex FwVersionInfo = new(
@"Dest=(?<dest>\d+);ImageVersion=(?<image>[0-9a-f]+);SystemSoftwareVersion=(?<version>\d+\.\d+);CDN=(?<url>http[^;]+);CDN_Timeout=(?<timeout>\d+)",
RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.Singleline | RegexOptions.IgnoreCase
);
// directly from vsh.self
private static readonly string[] KnownFwLocales = { "jp", "us", "eu", "kr", "uk", "mx", "au", "sa", "tw", "ru", "cn", "br", };
@ -47,19 +49,19 @@ public class Client
public Client()
{
client = HttpClientFactory.Create(new CustomTlsCertificatesHandler(), new CompressionMessageHandler());
dashedJson = new JsonSerializerOptions
dashedJson = new()
{
PropertyNamingPolicy = SpecialJsonNamingPolicy.Dashed,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
IncludeFields = true,
};
snakeJson = new JsonSerializerOptions
snakeJson = new()
{
PropertyNamingPolicy = SpecialJsonNamingPolicy.SnakeCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
IncludeFields = true,
};
xmlFormatters = new MediaTypeFormatterCollection(new[] {new XmlMediaTypeFormatter {UseXmlSerializer = true}});
xmlFormatters = new(new[] {new XmlMediaTypeFormatter {UseXmlSerializer = true}});
}
public static string[] GetLocales() => KnownStoreLocales; // Sony removed the ability to get the full store list, now relying on geolocation service instead
@ -101,7 +103,6 @@ public class Client
{
message.Headers.Add("Cookie", sessionCookies);
response = await client.SendAsync(message, cancellationToken).ConfigureAwait(false);
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
var tries = 0;
while (response.StatusCode == HttpStatusCode.Redirect && tries < 10 && !cancellationToken.IsCancellationRequested)
@ -116,7 +117,7 @@ public class Client
tries++;
}
if (response.StatusCode == HttpStatusCode.Redirect)
return new List<string>(0);
return new(0);
}
using (response)
@ -127,7 +128,7 @@ public class Client
var matches = ContainerIdLink.Matches(html);
var result = new List<string>();
foreach (Match m in matches)
if (m.Groups["id"].Value is string id && !string.IsNullOrEmpty(id))
if (m.Groups["id"].Value is {Length: >0} id)
result.Add(id);
return result;
}
@ -344,7 +345,7 @@ public class Client
allVersions = allVersions.OrderByDescending(fwi => fwi.Version).ToList();
if (allVersions.Count == 0)
return new List<FirmwareInfo>(0);
return new(0);
var maxFw = allVersions.First();
var result = allVersions.Where(fwi => fwi.Version == maxFw.Version).ToList();
@ -372,7 +373,7 @@ public class Client
{
["country_code"] = country,
["language_code"] = language,
}!)
})
};
using (authMessage)
using (response = await client.SendAsync(authMessage, cancellationToken).ConfigureAwait(false))
@ -418,7 +419,7 @@ public class Client
if (string.IsNullOrEmpty(data))
return null;
if (FwVersionInfo.Match(data) is not Match m || !m.Success)
if (FwVersionInfo.Match(data) is not { Success: true } m)
return null;
var ver = m.Groups["version"].Value;
@ -429,7 +430,7 @@ public class Client
else
ver = ver[..4] + "." + ver[4..].TrimEnd('0'); //4.851 -> 4.85.1
}
return new FirmwareInfo { Version = ver, DownloadUrl = m.Groups["url"].Value, Locale = fwLocale};
return new() { Version = ver, DownloadUrl = m.Groups["url"].Value, Locale = fwLocale};
}
catch (Exception e)

View File

@ -21,7 +21,7 @@ public sealed class Client
public Client()
{
client = HttpClientFactory.Create(new CompressionMessageHandler());
jsonOptions = new JsonSerializerOptions
jsonOptions = new()
{
PropertyNamingPolicy = SpecialJsonNamingPolicy.SnakeCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
@ -36,7 +36,7 @@ public sealed class Client
{
try
{
var uri = new Uri($"https://cloud-api.yandex.net/v1/disk/public/resources").SetQueryParameters(
var uri = new Uri("https://cloud-api.yandex.net/v1/disk/public/resources").SetQueryParameters(
("public_key", publicUri.ToString()),
("fields", "size,name,file")
);

View File

@ -23,8 +23,7 @@ internal class LimitedToSpamChannel: CheckBaseAttribute
try
{
var msgList = await ctx.Channel.GetMessagesCachedAsync(10).ConfigureAwait(false);
if (msgList.Any(m => m.Author.IsCurrent
&& m.Content is string s
if (msgList.Any(m => m is {Author.IsCurrent: true, Content: {Length: >0} s }
&& s.Contains(ctx.Command.QualifiedName, StringComparison.OrdinalIgnoreCase)))
{
await ctx.ReactWithAsync(Config.Reactions.Failure).ConfigureAwait(false);

View File

@ -11,7 +11,5 @@ internal class RequiresBotModRole: CheckBaseAttributeWithReactions
public RequiresBotModRole() : base(reactOnFailure: Config.Reactions.Denied) { }
protected override Task<bool> IsAllowed(CommandContext ctx, bool help)
{
return Task.FromResult(ModProvider.IsMod(ctx.User.Id));
}
=> Task.FromResult(ModProvider.IsMod(ctx.User.Id));
}

View File

@ -9,7 +9,5 @@ namespace CompatBot.Commands.Attributes;
internal class RequiresNotMedia: CheckBaseAttribute
{
public override Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help)
{
return Task.FromResult(ctx.Channel.Name != "media");
}
=> Task.FromResult(ctx.Channel.Name != "media");
}

View File

@ -11,7 +11,5 @@ internal class RequiresSupporterRole: CheckBaseAttributeWithReactions
public RequiresSupporterRole() : base(reactOnFailure: Config.Reactions.Denied) { }
protected override Task<bool> IsAllowed(CommandContext ctx, bool help)
{
return Task.FromResult(ctx.User.IsWhitelisted(ctx.Client, ctx.Guild) || ctx.User.IsSupporter(ctx.Client, ctx.Guild));
}
=> Task.FromResult(ctx.User.IsWhitelisted(ctx.Client, ctx.Guild) || ctx.User.IsSupporter(ctx.Client, ctx.Guild));
}

View File

@ -11,7 +11,5 @@ internal class RequiresWhitelistedRole: CheckBaseAttributeWithReactions
public RequiresWhitelistedRole() : base(reactOnFailure: Config.Reactions.Denied) { }
protected override Task<bool> IsAllowed(CommandContext ctx, bool help)
{
return Task.FromResult(ctx.User.IsWhitelisted(ctx.Client, ctx.Guild));
}
=> Task.FromResult(ctx.User.IsWhitelisted(ctx.Client, ctx.Guild));
}

View File

@ -9,7 +9,5 @@ internal class TriggersTyping: Attribute
public bool InDmOnly { get; set; }
public bool ExecuteCheck(CommandContext ctx)
{
return !InDmOnly || ctx.Channel.IsPrivate;
}
=> !InDmOnly || ctx.Channel.IsPrivate;
}

View File

@ -15,8 +15,7 @@ internal class BaseApplicationCommandModuleCustom : ApplicationCommandModule
public override async Task<bool> BeforeSlashExecutionAsync(InteractionContext ctx)
{
executionStart = DateTimeOffset.UtcNow;
if (ctx.Channel.Name == "media" && ctx.Interaction is { Type: InteractionType.ApplicationCommand, Data.Name: not ("warn" or "report") })
if (ctx is {Channel.Name: "media", Interaction: { Type: InteractionType.ApplicationCommand, Data.Name: not ("warn" or "report") } })
{
//todo: look what's available in data
Config.Log.Info($"Ignoring slash command from {ctx.User.Username} (<@{ctx.User.Id}>) in #media: {ctx.Interaction.Data}");

View File

@ -29,7 +29,10 @@ internal sealed class BotMath : BaseCommandModuleCustom
return;
}
var result = @"Something went wrong ¯\\_(ツ)\_/¯" + "\nMath is hard, yo";
var result = """
Something went wrong ¯\\\_()\_/¯
Math is hard, yo
""";
try
{
var expr = new Expression(expression);
@ -45,5 +48,5 @@ internal sealed class BotMath : BaseCommandModuleCustom
[Command("help"), LimitedToSpamChannel, Cooldown(1, 5, CooldownBucketType.Channel)]
[Description("General math expression help, or description of specific math word")]
public Task Help(CommandContext ctx)
=> ctx.Channel.SendMessageAsync("Help for all the features and built-in constants and functions could be found at <https://mathparser.org/mxparser-math-collection/>");
=> ctx.Channel.SendMessageAsync("Help for all the features and built-in constants and functions could be found at [mXparser website](<https://mathparser.org/mxparser-math-collection/>)");
}

View File

@ -37,19 +37,33 @@ internal sealed class BotStats: BaseCommandModuleCustom
var osInfo = RuntimeInformation.OSDescription;
if (Environment.OSVersion.Platform is PlatformID.Unix or PlatformID.MacOSX)
osInfo = RuntimeInformation.RuntimeIdentifier;
var gcMemInfo = GC.GetGCMemoryInfo();
var apiMsm = ApiConfig.MemoryStreamManager;
var botMsm = Config.MemoryStreamManager;
var apiLpsTotal = apiMsm.LargePoolInUseSize + apiMsm.LargePoolFreeSize;
var apiSpsTotal = apiMsm.SmallPoolInUseSize + apiMsm.SmallPoolFreeSize;
var botLpsTotal = botMsm.LargePoolInUseSize + botMsm.LargePoolFreeSize;
var botSpsTotal = botMsm.SmallPoolInUseSize + botMsm.SmallPoolFreeSize;
embed.AddField("API Tokens", GetConfiguredApiStats(), true)
.AddField("Memory Usage", $"GC: {GC.GetGCMemoryInfo().HeapSizeBytes.AsStorageUnit()}/{GC.GetGCMemoryInfo().TotalAvailableMemoryBytes.AsStorageUnit()}\n" +
$"API pools: L: {ApiConfig.MemoryStreamManager.LargePoolInUseSize.AsStorageUnit()}/{(ApiConfig.MemoryStreamManager.LargePoolInUseSize + ApiConfig.MemoryStreamManager.LargePoolFreeSize).AsStorageUnit()}" +
$" S: {ApiConfig.MemoryStreamManager.SmallPoolInUseSize.AsStorageUnit()}/{(ApiConfig.MemoryStreamManager.SmallPoolInUseSize + ApiConfig.MemoryStreamManager.SmallPoolFreeSize).AsStorageUnit()}\n" +
$"Bot pools: L: {Config.MemoryStreamManager.LargePoolInUseSize.AsStorageUnit()}/{(Config.MemoryStreamManager.LargePoolInUseSize + Config.MemoryStreamManager.LargePoolFreeSize).AsStorageUnit()}" +
$" S: {Config.MemoryStreamManager.SmallPoolInUseSize.AsStorageUnit()}/{(Config.MemoryStreamManager.SmallPoolInUseSize + Config.MemoryStreamManager.SmallPoolFreeSize).AsStorageUnit()}", true)
.AddField("GitHub Rate Limit", $"{GithubClient.Client.RateLimitRemaining} out of {GithubClient.Client.RateLimit} calls available\nReset in {(GithubClient.Client.RateLimitResetTime - DateTime.UtcNow).AsShortTimespan()}", true)
.AddField(".NET Info", $"{RuntimeInformation.FrameworkDescription}\n" +
$"{(System.Runtime.GCSettings.IsServerGC ? "Server" : "Workstation")} GC Mode", true)
.AddField("Runtime Info", $"Confinement: {SandboxDetector.Detect()}\n" +
$"OS: {osInfo}\n" +
$"CPUs: {Environment.ProcessorCount}\n" +
$"Time zones: {TimeParser.TimeZoneMap.Count} out of {TimeParser.TimeZoneAcronyms.Count} resolved, {TimeZoneInfo.GetSystemTimeZones().Count} total", true);
.AddField("Memory Usage", $"""
GC: {gcMemInfo.HeapSizeBytes.AsStorageUnit()}/{gcMemInfo.TotalAvailableMemoryBytes.AsStorageUnit()}
API pools: L: {apiMsm.LargePoolInUseSize.AsStorageUnit()}/{apiLpsTotal.AsStorageUnit()} S: {apiMsm.SmallPoolInUseSize.AsStorageUnit()}/{apiSpsTotal.AsStorageUnit()}
Bot pools: L: {botMsm.LargePoolInUseSize.AsStorageUnit()}/{botLpsTotal.AsStorageUnit()} S: {botMsm.SmallPoolInUseSize.AsStorageUnit()}/{botSpsTotal.AsStorageUnit()}
""", true)
.AddField("GitHub Rate Limit", $"""
{GithubClient.Client.RateLimitRemaining} out of {GithubClient.Client.RateLimit} calls available
Reset in {(GithubClient.Client.RateLimitResetTime - DateTime.UtcNow).AsShortTimespan()}
""", true)
.AddField(".NET Info", $"""
{RuntimeInformation.FrameworkDescription}
{(System.Runtime.GCSettings.IsServerGC ? "Server" : "Workstation")} GC Mode
""", true)
.AddField("Runtime Info", $"""
Confinement: {SandboxDetector.Detect()}
OS: {osInfo}
CPUs: {Environment.ProcessorCount}
Time zones: {TimeParser.TimeZoneMap.Count} out of {TimeParser.TimeZoneAcronyms.Count} resolved, {TimeZoneInfo.GetSystemTimeZones().Count} total
""", true);
AppendPiracyStats(embed);
AppendCmdStats(embed);
AppendExplainStats(embed);
@ -70,16 +84,13 @@ internal sealed class BotStats: BaseCommandModuleCustom
public Task Hardware(CommandContext ctx, [Description("Desired period in days, default is 30")] int period = 30) => Commands.Hardware.ShowStats(ctx, period);
private static string GetConfiguredApiStats()
{
return new StringBuilder()
.Append(GoogleDriveHandler.ValidateCredentials() ? "✅" : "❌").AppendLine(" Google Drive")
.Append(string.IsNullOrEmpty(Config.AzureDevOpsToken) ? "❌" : "✅").AppendLine(" Azure DevOps")
.Append(string.IsNullOrEmpty(Config.AzureComputerVisionKey) ? "❌" : "✅").AppendLine(" Computer Vision")
.Append(string.IsNullOrEmpty(Config.AzureAppInsightsConnectionString) ? "❌" : "✅").AppendLine(" AppInsights")
.Append(string.IsNullOrEmpty(Config.GithubToken) ? "❌" : "✅").AppendLine(" Github")
.ToString()
.Trim();
}
=> $"""
{(GoogleDriveHandler.ValidateCredentials() ? "✅" : "❌")} Google Drive
{(string.IsNullOrEmpty(Config.AzureDevOpsToken) ? "❌" : "✅")} Azure DevOps
{(string.IsNullOrEmpty(Config.AzureComputerVisionKey) ? "❌" : "✅")} Computer Vision
{(string.IsNullOrEmpty(Config.AzureAppInsightsConnectionString) ? "❌" : "✅")} AppInsights
{(string.IsNullOrEmpty(Config.GithubToken) ? "❌" : "✅")} GitHub
""";
private static void AppendPiracyStats(DiscordEmbedBuilder embed)
{
@ -228,11 +239,11 @@ internal sealed class BotStats: BaseCommandModuleCustom
var totalFuncCount = db.SyscallInfo.AsNoTracking().Select(sci => sci.Function).Distinct().Count();
var fwCallCount = totalFuncCount - syscallCount;
var gameCount = db.SyscallToProductMap.AsNoTracking().Select(m => m.ProductId).Distinct().Count();
embed.AddField("SceCall Stats",
$"Tracked game IDs: {gameCount}\n" +
$"Tracked syscalls: {syscallCount} function{(syscallCount == 1 ? "" : "s")}\n" +
$"Tracked fw calls: {fwCallCount} function{(fwCallCount == 1 ? "" : "s")}\n",
true);
embed.AddField("SceCall Stats", $"""
Tracked game IDs: {gameCount}
Tracked syscalls: {syscallCount} function{(syscallCount == 1 ? "" : "s")}
Tracked fw calls: {fwCallCount} function{(fwCallCount == 1 ? "" : "s")}
""", true);
}
catch (Exception e)
{
@ -258,15 +269,12 @@ internal sealed class BotStats: BaseCommandModuleCustom
.OrderByDescending(s => s.count)
.FirstOrDefault();
var cpuInfo = "";
if (cpu is not null)
cpuInfo = $"\nPopular CPU: {cpu.maker} {cpu.name} ({cpu.count*100.0/monthCount:0.##}%)";
embed.AddField("Hardware Stats",
$"Total: {totalCount} system{(totalCount == 1 ? "" : "s")}\n" +
$"Last 30 days: {monthCount} system{(monthCount == 1 ? "" : "s")}" +
cpuInfo,
true);
var cpuInfo = cpu is null ? "" : $"Popular CPU: {cpu.maker} {cpu.name} ({cpu.count * 100.0 / monthCount:0.##}%)";
embed.AddField("Hardware Stats", $"""
Total: {totalCount} system{(totalCount == 1 ? "" : "s")}
Last 30 days: {monthCount} system{(monthCount == 1 ? "" : "s")}
{cpuInfo}
""".TrimEnd(), true);
}
catch (Exception e)
{

View File

@ -22,7 +22,10 @@ public sealed class CommandsManagement : BaseCommandModule
var list = DisabledCommandsProvider.Get();
if (list.Count > 0)
{
var result = new StringBuilder("Currently disabled commands:").AppendLine().AppendLine("```");
var result = new StringBuilder("""
Currently disabled commands:
```
""");
foreach (var cmd in list)
result.AppendLine(cmd);
await ctx.SendAutosplitMessageAsync(result.Append("```")).ConfigureAwait(false);

View File

@ -41,7 +41,10 @@ internal sealed class CompatList : BaseCommandModuleCustom
private const string Rpcs3UpdateStateKey = "Rpcs3UpdateState";
private const string Rpcs3UpdateBuildKey = "Rpcs3UpdateBuild";
private static UpdateInfo? cachedUpdateInfo;
private static readonly Regex UpdateVersionRegex = new(@"v(?<version>\d+\.\d+\.\d+)-(?<build>\d+)-(?<commit>[0-9a-f]+)\b", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture);
private static readonly Regex UpdateVersionRegex = new(
@"v(?<version>\d+\.\d+\.\d+)-(?<build>\d+)-(?<commit>[0-9a-f]+)\b",
RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture
);
static CompatList()
{
@ -153,7 +156,7 @@ internal sealed class CompatList : BaseCommandModuleCustom
if (exactStatus)
result.Append(" only");
result.Append(" games");
if (scoreType == "critic" || scoreType == "user")
if (scoreType is "critic" or "user")
result.Append($" according to {scoreType}s");
result.AppendLine(":");
foreach (var (title, score, _) in resultList)
@ -170,17 +173,12 @@ internal sealed class CompatList : BaseCommandModuleCustom
public sealed class UpdatesCheck: BaseCommandModuleCustom
{
[GroupCommand]
public Task Latest(CommandContext ctx)
{
return CheckForRpcs3Updates(ctx.Client, ctx.Channel);
}
public Task Latest(CommandContext ctx) => CheckForRpcs3Updates(ctx.Client, ctx.Channel);
[Command("since")]
[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)
{
return CheckForRpcs3Updates(ctx.Client, ctx.Channel, commit);
}
=> CheckForRpcs3Updates(ctx.Client, ctx.Channel, commit);
[Command("clear"), RequiresBotModRole]
[Description("Clears update info cache that is used to post new build announcements")]
@ -345,7 +343,7 @@ internal sealed class CompatList : BaseCommandModuleCustom
{
var updateInfo = await Client.GetUpdateAsync(cancellationToken, mergedPr.MergeCommitSha).ConfigureAwait(false)
?? new UpdateInfo {ReturnCode = -1};
if (updateInfo.ReturnCode == 0 || updateInfo.ReturnCode == 1) // latest or known build
if (updateInfo.ReturnCode is 0 or 1) // latest or known build
{
updateInfo.LatestBuild = updateInfo.CurrentBuild;
updateInfo.CurrentBuild = null;
@ -503,7 +501,7 @@ internal sealed class CompatList : BaseCommandModuleCustom
if (trimmedList.Count > 0)
sortedList = trimmedList;
var searchTerm = request.Search ?? @"¯\_(ツ)_/¯";
var searchTerm = request.Search ?? @"¯\\\_(ツ)\_/¯";
var searchHits = sortedList.Where(t => t.score > 0.5
|| (t.info.Title?.StartsWith(searchTerm, StringComparison.InvariantCultureIgnoreCase) ?? false)
|| (t.info.AlternativeTitle?.StartsWith(searchTerm, StringComparison.InvariantCultureIgnoreCase) ?? false));
@ -684,8 +682,6 @@ internal sealed class CompatList : BaseCommandModuleCustom
ProductCode = productCode,
Name = titleInfo.Title,
}).ConfigureAwait(false)).Entity;
if (dbItem is null)
continue;
dbItem.Name = titleInfo.Title;
if (Enum.TryParse(titleInfo.Status, out CompatStatus status))

View File

@ -406,10 +406,10 @@ internal sealed class ContentFilters: BaseCommandModuleCustom
step1:
// step 1: define trigger string
var embed = FormatFilter(filter, errorMsg, 1)
.WithDescription(
"Any simple string that is used to flag potential content for a check using Validation regex.\n" +
"**Must** be sufficiently long to reduce the number of checks."
);
.WithDescription("""
Any simple string that is used to flag potential content for a check using Validation regex.
**Must** be sufficiently long to reduce the number of checks.
""");
saveEdit.SetEnabled(filter.IsComplete());
var messageBuilder = new DiscordMessageBuilder()
.WithContent("Please specify a new **trigger**")
@ -453,12 +453,12 @@ internal sealed class ContentFilters: BaseCommandModuleCustom
step2:
// step 2: context of the filter where it is applicable
embed = FormatFilter(filter, errorMsg, 2)
.WithDescription(
"Context of the filter indicates where it is applicable.\n" +
$"**`C`** = **`{FilterContext.Chat}`** will apply it in filtering discord messages.\n" +
$"**`L`** = **`{FilterContext.Log}`** will apply it during log parsing.\n" +
"Reactions will toggle the context, text message will set the specified flags."
);
.WithDescription($"""
Context of the filter indicates where it is applicable.
**`C`** = **`{FilterContext.Chat}`** will apply it in filtering discord messages.
**`L`** = **`{FilterContext.Log}`** will apply it during log parsing.
Reactions will toggle the context, text message will set the specified flags.
""");
saveEdit.SetEnabled(filter.IsComplete());
contextChat.SetEmoji(filter.Context.HasFlag(FilterContext.Chat) ? minus : plus);
contextLog.SetEmoji(filter.Context.HasFlag(FilterContext.Log) ? minus : plus);
@ -529,16 +529,16 @@ internal sealed class ContentFilters: BaseCommandModuleCustom
step3:
// step 3: actions that should be performed on match
embed = FormatFilter(filter, errorMsg, 3)
.WithDescription(
"Actions that will be executed on positive match.\n" +
$"**`R`** = **`{FilterAction.RemoveContent}`** will remove the message / log.\n" +
$"**`W`** = **`{FilterAction.IssueWarning}`** will issue a warning to the user.\n" +
$"**`M`** = **`{FilterAction.SendMessage}`** send _a_ message with an explanation of why it was removed.\n" +
$"**`E`** = **`{FilterAction.ShowExplain}`** show `explain` for the specified term (**not implemented**).\n" +
$"**`U`** = **`{FilterAction.MuteModQueue}`** mute mod queue reporting for this action.\n" +
$"**`K`** = **`{FilterAction.Kick}`** kick user from server.\n" +
"Buttons will toggle the action, text message will set the specified flags."
);
.WithDescription($"""
Actions that will be executed on positive match.
**`R`** = **`{FilterAction.RemoveContent}`** will remove the message / log.
**`W`** = **`{FilterAction.IssueWarning}`** will issue a warning to the user.
**`M`** = **`{FilterAction.SendMessage}`** send _a_ message with an explanation of why it was removed.
**`E`** = **`{FilterAction.ShowExplain}`** show `explain` for the specified term (**not implemented**).
**`U`** = **`{FilterAction.MuteModQueue}`** mute mod queue reporting for this action.
**`K`** = **`{FilterAction.Kick}`** kick user from server.
Buttons will toggle the action, text message will set the specified flags.
""");
actionR.SetEmoji(filter.Actions.HasFlag(FilterAction.RemoveContent) ? minus : plus);
actionW.SetEmoji(filter.Actions.HasFlag(FilterAction.IssueWarning) ? minus : plus);
actionM.SetEmoji(filter.Actions.HasFlag(FilterAction.SendMessage) ? minus : plus);
@ -666,11 +666,11 @@ internal sealed class ContentFilters: BaseCommandModuleCustom
step4:
// step 4: validation regex to filter out false positives of the plaintext triggers
embed = FormatFilter(filter, errorMsg, 4)
.WithDescription(
"Validation [regex](https://docs.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference) to optionally perform more strict trigger check.\n" +
"**Please [test](https://regex101.com/) your regex**. Following flags are enabled: Multiline, IgnoreCase.\n" +
"Additional validation can help reduce false positives of a plaintext trigger match."
);
.WithDescription("""
Validation [regex](https://docs.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference) to optionally perform more strict trigger check.
**Please [test](https://regex101.com/) your regex**. Following flags are enabled: Multiline, IgnoreCase.
Additional validation can help reduce false positives of a plaintext trigger match.
""");
var next = (filter.Actions & (FilterAction.SendMessage | FilterAction.ShowExplain)) == 0 ? firstPage : nextPage;
trash.SetDisabled(string.IsNullOrEmpty(filter.ValidatingRegex));
saveEdit.SetEnabled(filter.IsComplete());
@ -775,10 +775,10 @@ internal sealed class ContentFilters: BaseCommandModuleCustom
step6:
// step 6: show explanation for the term
embed = FormatFilter(filter, errorMsg, 6)
.WithDescription(
"Explanation term that is used to show an explanation.\n" +
"**__Currently not implemented__**."
);
.WithDescription("""
Explanation term that is used to show an explanation.
**__Currently not implemented__**.
""");
saveEdit.SetEnabled(filter.IsComplete());
messageBuilder = new DiscordMessageBuilder()
.WithContent("Please specify filter **explanation term**")

View File

@ -25,12 +25,12 @@ internal sealed class DevOnly : BaseCommandModuleCustom
#pragma warning disable 8321
static void addRandomStuff(DiscordEmbedBuilder emb)
{
var txt = "😾 lasjdf wqoieyr osdf `Vreoh Sdab` wohe `270`\n" +
"🤔 salfhiosfhsero hskfh shufwei oufhwehw e wkihrwe h\n" +
" sakfjas f hs `ASfhewighehw safds` asfw\n" +
"🔮 ¯\\\\\\_(ツ)\\_/¯";
emb.AddField("Random section", txt, false);
emb.AddField("Random section", """
😾 lasjdf wqoieyr osdf `Vreoh Sdab` wohe `270`
🤔 salfhiosfhsero hskfh shufwei oufhwehw e wkihrwe h
sakfjas f hs `ASfhewighehw safds` asfw
🔮 ¯\\\_()\_/¯
""", false);
}
#pragma warning restore 8321
var embed = new DiscordEmbedBuilder()

View File

@ -81,7 +81,7 @@ internal class EventsBaseCommand: BaseCommandModuleCustom
var noEventMsg = $"No information about the upcoming {eventName?.Sanitize(replaceBackTicks: true)} at the moment";
if (eventName?.Length > 10)
noEventMsg = "No information about such event at the moment";
else if (ctx.User.Id == 259997001880436737ul || ctx.User.Id == 377190919327318018ul)
else if (ctx.User.Id is 259997001880436737ul or 377190919327318018ul)
{
noEventMsg = $"Haha, very funny, {ctx.User.Mention}. So original. Never saw this joke before.";
promo = null;
@ -114,7 +114,7 @@ internal class EventsBaseCommand: BaseCommandModuleCustom
var noEventMsg = $"No information about the upcoming {eventName?.Sanitize(replaceBackTicks: true)} at the moment";
if (eventName?.Length > 10)
noEventMsg = "No information about such event at the moment";
else if (ctx.User.Id == 259997001880436737ul || ctx.User.Id == 377190919327318018ul)
else if (ctx.User.Id is 259997001880436737ul or 377190919327318018ul)
{
noEventMsg = $"Haha, very funny, {ctx.User.Mention}. So original. Never saw this joke before.";
promo = null;
@ -301,7 +301,10 @@ internal class EventsBaseCommand: BaseCommandModuleCustom
saveEdit.SetEnabled(evt.IsComplete());
messageBuilder = new DiscordMessageBuilder()
.WithContent("Please specify a new **start date and time**")
.WithEmbed(FormatEvent(evt, errorMsg, 1).WithDescription($"Example: `{DateTime.UtcNow:yyyy-MM-dd HH:mm}`\nBy default all times use UTC, only limited number of time zones supported"))
.WithEmbed(FormatEvent(evt, errorMsg, 1).WithDescription($"""
Example: `{DateTime.UtcNow:yyyy-MM-dd HH:mm}`
By default all times use UTC, only limited number of time zones supported
"""))
.AddComponents(lastPage, nextPage)
.AddComponents(saveEdit, abort);
errorMsg = null;

View File

@ -67,7 +67,7 @@ internal sealed class Explain: BaseCommandModuleCustom
var hasMention = false;
term = term.ToLowerInvariant();
var result = await LookupTerm(term).ConfigureAwait(false);
if (result.explanation == null || !string.IsNullOrEmpty(result.fuzzyMatch))
if (result is {explanation: null} or {fuzzyMatch.Length: >0})
{
term = term.StripQuotes();
var idx = term.LastIndexOf(" to ", StringComparison.Ordinal);
@ -116,7 +116,7 @@ internal sealed class Explain: BaseCommandModuleCustom
term = term.ToLowerInvariant().StripQuotes();
byte[]? attachment = null;
string? attachmentFilename = null;
if (ctx.Message.Attachments.FirstOrDefault() is DiscordAttachment att)
if (ctx.Message.Attachments is [DiscordAttachment att, ..])
{
attachmentFilename = att.FileName;
try
@ -165,7 +165,7 @@ internal sealed class Explain: BaseCommandModuleCustom
term = term.ToLowerInvariant().StripQuotes();
byte[]? attachment = null;
string? attachmentFilename = null;
if (ctx.Message.Attachments.FirstOrDefault() is DiscordAttachment att)
if (ctx.Message.Attachments is [DiscordAttachment att, ..])
{
attachmentFilename = att.FileName;
try
@ -346,12 +346,12 @@ internal sealed class Explain: BaseCommandModuleCustom
}
else
{
if (!string.IsNullOrEmpty(item.Text))
if (item is { Text.Length: > 0 })
{
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(item.Text));
await ctx.Channel.SendMessageAsync(new DiscordMessageBuilder().AddFile($"{termOrLink}.txt", stream)).ConfigureAwait(false);
}
if (!string.IsNullOrEmpty(item.AttachmentFilename) && item.Attachment?.Length > 0)
if (item is { AttachmentFilename.Length: > 0, Attachment.Length: > 0 })
{
await using var stream = new MemoryStream(item.Attachment);
await ctx.Channel.SendMessageAsync(new DiscordMessageBuilder().AddFile(item.AttachmentFilename, stream)).ConfigureAwait(false);

View File

@ -71,9 +71,8 @@ internal sealed class ForcedNicknames : BaseCommandModuleCustom
else
{
if (enforceRules.Nickname == expectedNickname)
{
continue;
}
enforceRules.Nickname = expectedNickname;
}
}
@ -187,7 +186,7 @@ internal sealed class ForcedNicknames : BaseCommandModuleCustom
var hex = BitConverter.ToString(nameBytes).Replace('-', ' ');
var result = $"User ID: {discordUser.Id}\nUsername: {hex}";
var member = ctx.Client.GetMember(ctx.Guild, discordUser);
if (member?.Nickname is string {Length: >0} nickname)
if (member is { Nickname: { Length: > 0 } nickname })
{
nameBytes = StringUtils.Utf8.GetBytes(nickname);
hex = BitConverter.ToString(nameBytes).Replace('-', ' ');

View File

@ -46,20 +46,20 @@ internal sealed class Fortune : BaseCommandModuleCustom
fortune = await db.Fortune.AsNoTracking().Skip(selectedId).FirstOrDefaultAsync().ConfigureAwait(false);
} while (fortune is null);
var msg = fortune.Content.FixTypography();
var msgParts = msg.Split('\n');
var tmp = new StringBuilder();
var quote = true;
foreach (var l in msgParts)
foreach (var l in fortune.Content.FixTypography().Split('\n'))
{
quote &= !l.StartsWith(" ");
if (quote)
tmp.Append("> ");
tmp.Append(l).Append('\n');
}
msg = tmp.ToString().TrimEnd().FixSpaces();
var msgBuilder = new DiscordMessageBuilder()
.WithContent($"{user.Mention}, your fortune for today:\n{msg}")
.WithContent($"""
{user.Mention}, your fortune for today:
{tmp.ToString().TrimEnd().FixSpaces()}
""")
.WithReply(message.Id);
await message.Channel.SendMessageAsync(msgBuilder).ConfigureAwait(false);
}
@ -136,7 +136,7 @@ internal sealed class Fortune : BaseCommandModuleCustom
|| buf.Length > 0)
&& !Config.Cts.IsCancellationRequested)
{
if (line == "%" || line is null)
if (line is "%" or null)
{
var content = buf.ToString().Replace("\r\n", "\n").Trim();
if (content.Length > 1900)
@ -187,7 +187,7 @@ internal sealed class Fortune : BaseCommandModuleCustom
var progressMsg = $"Imported {count} fortune{(count == 1 ? "" : "s")}";
if (skipped > 0)
progressMsg += $", skipped {skipped}";
if (response.Content.Headers.ContentLength is long len && len > 0)
if (response.Content.Headers.ContentLength is long len and > 0)
progressMsg += $" ({stream.Position * 100.0 / len:0.##}%)";
await msg.UpdateOrCreateMessageAsync(ctx.Channel, progressMsg).ConfigureAwait(false);
stopwatch.Restart();
@ -246,7 +246,7 @@ internal sealed class Fortune : BaseCommandModuleCustom
[Description("Clears fortune database. Use with caution")]
public async Task Clear(CommandContext ctx, [RemainingText, Description("Must be `with my blessing, I swear I exported the backup`")] string confirmation)
{
if (confirmation != "with my blessing, I swear I exported the backup")
if (confirmation is not "with my blessing, I swear I exported the backup")
{
await ctx.ReactWithAsync(Config.Reactions.Failure).ConfigureAwait(false);
return;

View File

@ -130,13 +130,17 @@ internal sealed class Hardware: BaseCommandModuleCustom
var lowRam = mem.Where(i => i.Mem < 4 * 1024 - margin).Sum(i => i.Count);
var ram4to6 = mem.Where(i => i.Mem is >= 4 * 1024 - margin and < 6 * 1024 - margin).Sum(i => i.Count);
var ram6to8 = mem.Where(i => i.Mem is >= 6 * 1024 - margin and < 8 * 1024 - margin).Sum(i => i.Count);
var highRam = mem.Where(i => i.Mem >= 8 * 1024 - margin).Sum(i => i.Count);
var ram8to16 = mem.Where(i => i.Mem is >= 8 * 1024 - margin and < 16 * 1024 - margin).Sum(i => i.Count);
var ram16to32 = mem.Where(i => i.Mem is >= 16 * 1024 - margin and < 32 * 1024 - margin).Sum(i => i.Count);
var highRam = mem.Where(i => i.Mem >= 32 * 1024 - margin).Sum(i => i.Count);
var ramStats = new (int Count, string Mem)[]
{
(lowRam, "less than 4 GB"),
(ram4to6, "4 to 6 GB"),
(ram6to8, "6 to 8 GB"),
(highRam, "8 GB or more"),
(ram8to16, "8 to 16 GB"),
(ram16to32, "16 to 32 GB"),
(highRam, "32 GB or more"),
}
.Where(i => i.Count > 0)
.Take(top)

View File

@ -164,8 +164,8 @@ internal sealed class Misc: BaseCommandModuleCustom
if (!int.TryParse(m.Groups["num"].Value, out var num))
num = 1;
if (int.TryParse(m.Groups["face"].Value, out var face)
&& 0 < num && num < 101
&& 1 < face && face < 1001)
&& num is > 0 and < 101
&& face is > 1 and < 1001)
{
List<int> rolls;
lock (rng) rolls = Enumerable.Range(0, num).Select(_ => rng.Next(face) + 1).ToList();
@ -312,7 +312,7 @@ internal sealed class Misc: BaseCommandModuleCustom
{
try
{
var funMult = DateTime.UtcNow.Month == 4 && DateTime.UtcNow.Day == 1 ? 100 : Config.FunMultiplier;
var funMult = DateTime.UtcNow is {Month: 4, Day: 1} ? 100 : Config.FunMultiplier;
var choices = RateAnswers;
var choiceFlags = new HashSet<char>();
whatever = whatever.ToLowerInvariant().StripInvisibleAndDiacritics();

View File

@ -51,7 +51,7 @@ internal sealed partial class Moderation
await using var memoryStream = Config.MemoryStreamManager.GetStream();
await using var writer = new StreamWriter(memoryStream, new UTF8Encoding(false), 4096, true);
foreach (var member in members)
await writer.WriteLineAsync($"{member.Username}\t{member.Nickname}\t{member.JoinedAt:O}\t{(string.Join(',', member.Roles.Select(r => r.Name)))}").ConfigureAwait(false);
await writer.WriteLineAsync($"{member.Username}\t{member.Nickname}\t{member.JoinedAt:O}\t{string.Join(',', member.Roles.Select(r => r.Name))}").ConfigureAwait(false);
await writer.FlushAsync().ConfigureAwait(false);
memoryStream.Seek(0, SeekOrigin.Begin);
if (memoryStream.Length <= ctx.GetAttachmentSizeLimit())
@ -192,11 +192,13 @@ internal sealed partial class Moderation
}
}
/*
#if DEBUG
[Command("locales"), Aliases("locale", "languages", "language", "lang", "loc")]
public async Task UserLocales(CommandContext ctx)
{
#pragma warning disable VSTHRD103
if (!CheckLock.Wait(0))
#pragma warning restore VSTHRD103
{
await ctx.Channel.SendMessageAsync("Another check is already in progress").ConfigureAwait(false);
return;
@ -236,7 +238,7 @@ internal sealed partial class Moderation
await ctx.RemoveReactionAsync(Config.Reactions.PleaseWait).ConfigureAwait(false);
}
}
*/
#endif
private static List<DiscordMember> GetMembers(DiscordClient client)
{

View File

@ -284,7 +284,10 @@ internal sealed class Pr: BaseCommandModuleCustom
var avgBuildTime = (await azureClient.GetPipelineDurationAsync(Config.Cts.Token).ConfigureAwait(false)).Mean;
if (now < mergeTime + avgBuildTime)
waitTime = mergeTime + avgBuildTime - now;
embed.AddField("Latest master build", $"This pull request has been merged, and will be part of `master` very soon.\nPlease check again in {waitTime.AsTimeDeltaDescription()}.");
embed.AddField("Latest master build", $"""
This pull request has been merged, and will be part of `master` very soon.
Please check again in {waitTime.AsTimeDeltaDescription()}.
""");
}
}
}

View File

@ -18,23 +18,19 @@ internal sealed class SlashMisc: BaseApplicationCommandModuleCustom
Title = "RPCS3 Compatibility Bot",
Url = "https://github.com/RPCS3/discord-bot",
Color = DiscordColor.Purple,
}.AddField("Made by",
$"""
}.AddField("Made by", $"""
💮 13xforever
🇭🇷 Roberto Anić Banić aka nicba1010
{clienthax} clienthax
"""
)
.AddField("People who ~~broke~~ helped test the bot",
$"""
).AddField("People who ~~broke~~ helped test the bot", $"""
🐱 Juhn
{hcorion} hcorion
🙃 TGE
🍒 Maru
Tourghool
"""
)
.WithFooter($"Running {Config.GitRevision}");
).WithFooter($"Running {Config.GitRevision}");
await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().AddEmbed(embed.Build()).AsEphemeral());
}
}

View File

@ -31,9 +31,9 @@ internal partial class Sudo
foreach (var v in setVars)
{
#if DEBUG
result.Append(v.Key![SqlConfiguration.ConfigVarPrefix.Length ..]).Append(" = ").AppendLine(v.Value);
result.Append(v.Key[SqlConfiguration.ConfigVarPrefix.Length ..]).Append(" = ").AppendLine(v.Value);
#else
result.AppendLine(v.Key![(SqlConfiguration.ConfigVarPrefix.Length)..]);
result.AppendLine(v.Key[(SqlConfiguration.ConfigVarPrefix.Length)..]);
#endif
}
await ctx.Channel.SendMessageAsync(result.ToString()).ConfigureAwait(false);
@ -53,7 +53,7 @@ internal partial class Sudo
var stateValue = await db.BotState.FirstOrDefaultAsync(v => v.Key == key).ConfigureAwait(false);
if (stateValue == null)
{
stateValue = new BotState {Key = key, Value = value};
stateValue = new() {Key = key, Value = value};
await db.BotState.AddAsync(stateValue).ConfigureAwait(false);
}
else

View File

@ -30,7 +30,7 @@ internal partial class Sudo
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Checking for dotnet updates...").ConfigureAwait(false);
var (updated, stdout) = await UpdateAsync(version).ConfigureAwait(false);
if (!string.IsNullOrEmpty(stdout))
await ctx.SendAutosplitMessageAsync("```" + stdout + "```").ConfigureAwait(false);
await ctx.SendAutosplitMessageAsync($"```{stdout}```").ConfigureAwait(false);
if (!updated)
return;

View File

@ -21,8 +21,10 @@ internal partial class Sudo
if (await ModProvider.AddAsync(user.Id).ConfigureAwait(false))
{
await ctx.ReactWithAsync(Config.Reactions.Success,
$"{user.Mention} was successfully added as moderator!\n" +
$"Try using `{ctx.Prefix}help` to see new commands available to you"
$"""
{user.Mention} was successfully added as moderator!
Try using `{ctx.Prefix}help` to see new commands available to you
"""
).ConfigureAwait(false);
}
else
@ -36,7 +38,7 @@ internal partial class Sudo
if (ctx.Client.CurrentApplication.Owners.Any(u => u.Id == user.Id))
{
var dm = await user.CreateDmChannelAsync().ConfigureAwait(false);
await dm.SendMessageAsync($@"Just letting you know that {ctx.Message.Author.Mention} just tried to strip you off of your mod role ¯\\_(ツ)_/¯").ConfigureAwait(false);
await dm.SendMessageAsync($@"Just letting you know that {ctx.Message.Author.Mention} just tried to strip you off of your mod role ¯\\\_(ツ)\_/¯").ConfigureAwait(false);
await ctx.ReactWithAsync(Config.Reactions.Denied, $"{ctx.Message.Author.Mention} why would you even try this?! Alerting {user.Mention}", true).ConfigureAwait(false);
}
else if (await ModProvider.RemoveAsync(user.Id).ConfigureAwait(false))

View File

@ -33,7 +33,6 @@ internal sealed partial class Sudo : BaseCommandModuleCustom
public async Task Say(CommandContext ctx, [RemainingText, Description("Message text to send")] string message)
{
var msgParts = message.Split(' ', 2, StringSplitOptions.TrimEntries);
var channel = ctx.Channel;
DiscordMessage? ogMsg = null;
if (msgParts.Length > 1)

View File

@ -117,7 +117,7 @@ internal sealed class Vision: BaseCommandModuleCustom
//resize and shrink file size to get under azure limits
var quality = 90;
var resized = false;
if (img.Width > 4000 || img.Height > 4000)
if (img is {Width: >4000} or {Height: >4000})
{
img.Mutate(i => i.Resize(new ResizeOptions {Size = new(3840, 2160), Mode = ResizeMode.Min,}));
resized = true;

View File

@ -52,7 +52,6 @@ internal sealed partial class Warnings: BaseCommandModuleCustom
var interact = ctx.Client.GetInteractivity();
await using var db = new BotDb();
var warnings = await db.Warning.Where(w => id.Equals(w.Id)).ToListAsync().ConfigureAwait(false);
if (warnings.Count == 0)
{
await ctx.ReactWithAsync(Config.Reactions.Denied, $"{ctx.Message.Author.Mention} Warn not found", true);
@ -60,7 +59,6 @@ internal sealed partial class Warnings: BaseCommandModuleCustom
}
var warningToEdit = warnings.First();
if (warningToEdit.IssuerId != ctx.User.Id)
{
await ctx.ReactWithAsync(Config.Reactions.Denied, $"{ctx.Message.Author.Mention} This warn wasn't issued by you :(", true);
@ -83,7 +81,6 @@ internal sealed partial class Warnings: BaseCommandModuleCustom
}
warningToEdit.Reason = response.Result.Content;
await db.SaveChangesAsync().ConfigureAwait(false);
await ctx.Channel.SendMessageAsync($"Warning successfully edited!").ConfigureAwait(false);
}
@ -166,7 +163,7 @@ internal sealed partial class Warnings: BaseCommandModuleCustom
{
await using var db = new BotDb();
var warn = await db.Warning.FirstOrDefaultAsync(w => w.Id == id).ConfigureAwait(false);
if (warn?.Retracted is true)
if (warn is { Retracted: true })
{
warn.Retracted = false;
warn.RetractedBy = null;

View File

@ -27,7 +27,7 @@ internal class BotDb: DbContext
#if DEBUG
optionsBuilder.UseLoggerFactory(Config.LoggerFactory);
#endif
optionsBuilder.UseSqlite($"Data Source=\"{dbPath}\"");
optionsBuilder.UseSqlite($""" Data Source="{dbPath}" """);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)

View File

@ -18,20 +18,20 @@ public static class DbImporter
public static async Task<bool> UpgradeAsync(CancellationToken cancellationToken)
{
await using (var db = new BotDb())
if (!await UpgradeAsync(db, Config.Cts.Token))
if (!await UpgradeAsync(db, cancellationToken).ConfigureAwait(false))
return false;
await using (var db = new ThumbnailDb())
{
if (!await UpgradeAsync(db, Config.Cts.Token))
if (!await UpgradeAsync(db,cancellationToken).ConfigureAwait(false))
return false;
if (!await ImportNamesPool(db, Config.Cts.Token))
if (!await ImportNamesPool(db, cancellationToken).ConfigureAwait(false))
return false;
}
await using (var db = new HardwareDb())
if (!await UpgradeAsync(db, Config.Cts.Token))
if (!await UpgradeAsync(db, cancellationToken).ConfigureAwait(false))
return false;
return true;

View File

@ -15,7 +15,7 @@ internal class HardwareDb : DbContext
#if DEBUG
optionsBuilder.UseLoggerFactory(Config.LoggerFactory);
#endif
optionsBuilder.UseSqlite($"Data Source=\"{dbPath}\"");
optionsBuilder.UseSqlite($""" Data Source="{dbPath}" """);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)

View File

@ -276,8 +276,7 @@ internal static class ContentFilter
public static string? GetMatchedScope(Piracystring trigger, string? context)
=> context is { Length: >0 }
&& trigger.ValidatingRegex is { Length: >0 } pattern
&& Regex.Match(context, pattern, RegexOptions.IgnoreCase | RegexOptions.Multiline) is { Success: true } m
&& m.Groups.Count > 0
&& Regex.Match(context, pattern, RegexOptions.IgnoreCase | RegexOptions.Multiline) is { Success: true, Groups.Count: > 0 } m
? m.Groups[0].Value.Trim(256)
: null;
}

View File

@ -26,7 +26,7 @@ internal static class DisabledCommandsProvider
if (DisabledCommands.Add(command))
{
using var db = new BotDb();
db.DisabledCommands.Add(new DisabledCommand {Command = command});
db.DisabledCommands.Add(new() {Command = command});
db.SaveChanges();
}
}

View File

@ -19,7 +19,6 @@ internal static class ModProvider
}
public static bool IsMod(ulong userId) => Moderators.ContainsKey(userId);
public static bool IsSudoer(ulong userId) => Moderators.TryGetValue(userId, out var mod) && mod.Sudoer;
public static async Task<bool> AddAsync(ulong userId)
@ -34,6 +33,7 @@ internal static class ModProvider
{
if (IsMod(userId))
return false;
Moderators[userId] = newMod;
}
return true;

View File

@ -20,7 +20,7 @@ internal static class ScrapeStateProvider
var id = GetId(locale, containerId);
using var db = new ThumbnailDb();
var timestamp = string.IsNullOrEmpty(id) ? db.State.OrderBy(s => s.Timestamp).FirstOrDefault() : db.State.FirstOrDefault(s => s.Locale == id);
if (timestamp?.Timestamp is long checkDate && checkDate > 0)
if (timestamp is { Timestamp: long checkDate and > 0 })
return IsFresh(new DateTime(checkDate, DateTimeKind.Utc));
return false;
}
@ -29,7 +29,7 @@ internal static class ScrapeStateProvider
{
using var db = new ThumbnailDb();
var timestamp = string.IsNullOrEmpty(locale) ? db.State.OrderBy(s => s.Timestamp).FirstOrDefault() : db.State.FirstOrDefault(s => s.Locale == locale);
if (timestamp?.Timestamp is long checkDate && checkDate > 0)
if (timestamp is { Timestamp: long checkDate and > 0 })
return new DateTime(checkDate, DateTimeKind.Utc) > dataTimestamp;
return false;
}

View File

@ -25,13 +25,13 @@ internal static class ThumbnailProvider
productCode = productCode.ToUpperInvariant();
var tmdbInfo = await PsnClient.GetTitleMetaAsync(productCode, Config.Cts.Token).ConfigureAwait(false);
if (tmdbInfo?.Icon.Url is string tmdbIconUrl)
if (tmdbInfo is { Icon.Url: string tmdbIconUrl })
return tmdbIconUrl;
await using var db = new ThumbnailDb();
var thumb = await db.Thumbnail.FirstOrDefaultAsync(t => t.ProductCode == productCode).ConfigureAwait(false);
//todo: add search task if not found
if (thumb?.EmbeddableUrl is string embeddableUrl && !string.IsNullOrEmpty(embeddableUrl))
if (thumb?.EmbeddableUrl is {Length: >0} embeddableUrl)
return embeddableUrl;
if (string.IsNullOrEmpty(thumb?.Url) || !ScrapeStateProvider.IsFresh(thumb.Timestamp))
@ -40,7 +40,7 @@ internal static class ThumbnailProvider
if (!string.IsNullOrEmpty(gameTdbCoverUrl))
{
if (thumb is null)
thumb = (await db.Thumbnail.AddAsync(new Thumbnail {ProductCode = productCode, Url = gameTdbCoverUrl}).ConfigureAwait(false)).Entity;
thumb = (await db.Thumbnail.AddAsync(new() {ProductCode = productCode, Url = gameTdbCoverUrl}).ConfigureAwait(false)).Entity;
else
thumb.Url = gameTdbCoverUrl;
thumb.Timestamp = DateTime.UtcNow.Ticks;
@ -81,7 +81,7 @@ internal static class ThumbnailProvider
if (!string.IsNullOrEmpty(title))
{
if (thumb == null)
await db.Thumbnail.AddAsync(new Thumbnail
await db.Thumbnail.AddAsync(new()
{
ProductCode = productCode,
Name = title,
@ -107,7 +107,7 @@ internal static class ThumbnailProvider
contentId = contentId.ToUpperInvariant();
await using var db = new ThumbnailDb();
var info = await db.Thumbnail.FirstOrDefaultAsync(ti => ti.ContentId == contentId, Config.Cts.Token).ConfigureAwait(false);
info ??= new Thumbnail{Url = url};
info ??= new() {Url = url};
if (info.Url is null)
return (null, defaultColor);
@ -139,7 +139,7 @@ internal static class ThumbnailProvider
await db.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false);
}
}
var color = info.EmbedColor.HasValue ? new DiscordColor(info.EmbedColor.Value) : defaultColor;
var color = info.EmbedColor.HasValue ? new(info.EmbedColor.Value) : defaultColor;
return (info.EmbeddableUrl, color);
}

View File

@ -21,7 +21,7 @@ public static class TitleUpdateInfoProvider
productId = productId.ToUpper();
var (update, xml) = await Client.GetTitleUpdatesAsync(productId, cancellationToken).ConfigureAwait(false);
if (xml is string {Length: > 10})
if (xml is {Length: > 10})
{
var xmlChecksum = xml.GetStableHash();
await using var db = new ThumbnailDb();
@ -38,7 +38,7 @@ public static class TitleUpdateInfoProvider
updateInfo.Timestamp = DateTime.UtcNow.Ticks;
await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}
if ((update?.Tag?.Packages?.Length ?? 0) == 0)
if (update?.Tag?.Packages?.Length is null or 0)
{
await using var db = new ThumbnailDb();
var updateInfo = db.GameUpdateInfo.FirstOrDefault(ui => ui.ProductCode == productId);
@ -50,8 +50,9 @@ public static class TitleUpdateInfoProvider
update = (TitlePatch?)xmlSerializer.Deserialize(memStream);
if (update is not null)
update.OfflineCacheTimestamp = updateInfo.Timestamp.AsUtc();
}
return update;
}
return update;
}
@ -65,7 +66,7 @@ public static class TitleUpdateInfoProvider
{
var updateInfo = db.GameUpdateInfo.AsNoTracking().FirstOrDefault(ui => ui.ProductCode == titleId);
if (!cancellationToken.IsCancellationRequested
&& ((updateInfo?.Timestamp ?? 0) == 0 || updateInfo!.Timestamp.AsUtc() < DateTime.UtcNow.AddMonths(-1)))
&& (updateInfo?.Timestamp is null or 0L || updateInfo.Timestamp.AsUtc() < DateTime.UtcNow.AddMonths(-1)))
{
await GetAsync(titleId, cancellationToken).ConfigureAwait(false);
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);

View File

@ -23,7 +23,7 @@ internal class ThumbnailDb : DbContext
#if DEBUG
optionsBuilder.UseLoggerFactory(Config.LoggerFactory);
#endif
optionsBuilder.UseSqlite($"Data Source=\"{dbPath}\"");
optionsBuilder.UseSqlite($""" Data Source="{dbPath}" """);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)

View File

@ -132,7 +132,7 @@ namespace CompatBot.EventHandlers
await using var db = new BotDb();
var matchedGroups = (from m in mc
from Group g in m.Groups
where g.Success && !string.IsNullOrEmpty(g.Value)
where g is { Success: true, Value.Length: > 0 }
select g.Name
).Distinct()
.ToArray();
@ -193,7 +193,7 @@ namespace CompatBot.EventHandlers
await msg.DeleteAsync("asked to shut up").ConfigureAwait(false);
}
else
await args.Message.ReactWithAsync(DiscordEmoji.FromUnicode("🙅"), @"No can do, boss ¯\\_(ツ)\_/¯").ConfigureAwait(false);
await args.Message.ReactWithAsync(DiscordEmoji.FromUnicode("🙅"), @"No can do, boss ¯\\\_(ツ)\_/¯").ConfigureAwait(false);
}
}
}

View File

@ -74,9 +74,7 @@ internal static class DeletedMessagesMonitor
{
if (attachmentContent?.Count > 0)
foreach (var f in attachmentContent.Values)
#pragma warning disable VSTHRD103
f.Dispose();
#pragma warning restore VSTHRD103
await f.DisposeAsync();
}
}
}

View File

@ -187,7 +187,7 @@ internal static class DiscordInviteFilter
public static async Task<(bool hasInvalidInvite, bool attemptToWorkaround, List<DiscordInvite> invites)> GetInvitesAsync(this DiscordClient client, string message, DiscordUser? author = null, bool tryMessageAsACode = false)
{
if (string.IsNullOrEmpty(message))
return (false, false, new List<DiscordInvite>(0));
return (false, false, new(0));
var inviteCodes = new HashSet<string>(InviteLink.Matches(message).Select(m => m.Groups["invite_id"].Value).Where(s => !string.IsNullOrEmpty(s)));
var discordMeLinks = InviteLink.Matches(message).Select(m => m.Groups["me_id"].Value).Distinct().Where(s => !string.IsNullOrEmpty(s)).ToList();
@ -202,7 +202,7 @@ internal static class DiscordInviteFilter
}
}
if (inviteCodes.Count == 0 && discordMeLinks.Count == 0 && !tryMessageAsACode)
return (false, attemptedWorkaround, new List<DiscordInvite>(0));
return (false, attemptedWorkaround, new(0));
var hasInvalidInvites = false;
foreach (var meLink in discordMeLinks)
@ -221,9 +221,9 @@ internal static class DiscordInviteFilter
using var handler = new HttpClientHandler {AllowAutoRedirect = false}; // needed to store cloudflare session cookies
using var httpClient = HttpClientFactory.Create(handler, new CompressionMessageHandler());
using var request = new HttpRequestMessage(HttpMethod.Get, "https://discord.me/" + meLink);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/html"));
request.Headers.Accept.Add(new("text/html"));
request.Headers.CacheControl = CacheControlHeaderValue.Parse("no-cache");
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("RPCS3CompatibilityBot", "2.0"));
request.Headers.UserAgent.Add(new("RPCS3CompatibilityBot", "2.0"));
using var response = await httpClient.SendAsync(request).ConfigureAwait(false);
var html = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
if (response.IsSuccessStatusCode)
@ -244,8 +244,8 @@ internal static class DiscordInviteFilter
["serverEid"] = serverEidMatch.Groups["server_eid"].Value,
}!),
};
postRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/html"));
postRequest.Headers.UserAgent.Add(new ProductInfoHeaderValue("RPCS3CompatibilityBot", "2.0"));
postRequest.Headers.Accept.Add(new("text/html"));
postRequest.Headers.UserAgent.Add(new("RPCS3CompatibilityBot", "2.0"));
using var postResponse = await httpClient.SendAsync(postRequest).ConfigureAwait(false);
if (postResponse.StatusCode == HttpStatusCode.Redirect)
{
@ -254,7 +254,7 @@ internal static class DiscordInviteFilter
{
using var getDiscordRequest = new HttpRequestMessage(HttpMethod.Get, "https://discord.me/server/join/redirect/" + redirectId);
getDiscordRequest.Headers.CacheControl = CacheControlHeaderValue.Parse("no-cache");
getDiscordRequest.Headers.UserAgent.Add(new ProductInfoHeaderValue("RPCS3CompatibilityBot", "2.0"));
getDiscordRequest.Headers.UserAgent.Add(new("RPCS3CompatibilityBot", "2.0"));
using var discordRedirect = await httpClient.SendAsync(getDiscordRequest).ConfigureAwait(false);
if (discordRedirect.StatusCode == HttpStatusCode.Redirect)
{

View File

@ -58,7 +58,7 @@ internal static class IsTheGamePlayableHandler
.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 (string.IsNullOrEmpty(gameTitle) || gameTitle.Length < 2)
if (gameTitle is null or { Length: < 2 })
return;
gameTitle = CompatList.FixGameTitleSearch(gameTitle);
@ -89,7 +89,7 @@ internal static class IsTheGamePlayableHandler
if (!string.IsNullOrEmpty(info.Date))
msg += $" since {info.ToUpdated()}";
}
msg += $"\nfor more results please use compatibility list (<https://rpcs3.net/compatibility>) or `{Config.CommandPrefix}c` command in {botSpamChannel.Mention} (`!c {gameTitle.Sanitize()}`)";
msg += $"\nfor more results please use [compatibility list](<https://rpcs3.net/compatibility>) or `{Config.CommandPrefix}c` command in {botSpamChannel.Mention} (`!c {gameTitle.Sanitize()}`)";
await args.Channel.SendMessageAsync(msg).ConfigureAwait(false);
CooldownBuckets[args.Channel.Id] = DateTime.UtcNow;
}

View File

@ -19,7 +19,7 @@ internal sealed class GzipHandler: IArchiveHandler
{
if (header.Length >= Header.Length)
{
if (header.Slice(0, Header.Length).SequenceEqual(Header))
if (header[..Header.Length].SequenceEqual(Header))
return (true, null);
}
else if (fileName.EndsWith(".log.gz", StringComparison.InvariantCultureIgnoreCase)

View File

@ -18,7 +18,7 @@ internal sealed class PlainTextHandler: IArchiveHandler
if (fileName.Contains("tty.log", StringComparison.InvariantCultureIgnoreCase))
return (false, null);
if (header.Length > 10 && Encoding.UTF8.GetString(header.Slice(0, 30)).Contains("RPCS3 v"))
if (header.Length > 10 && Encoding.UTF8.GetString(header[..30]).Contains("RPCS3 v"))
return (true, null);
return (false, null);

View File

@ -18,7 +18,7 @@ internal sealed class RarHandler: IArchiveHandler
public (bool result, string? reason) CanHandle(string fileName, int fileSize, ReadOnlySpan<byte> header)
{
if (header.Length >= Header.Length && header.Slice(0, Header.Length).SequenceEqual(Header)
if (header.Length >= Header.Length && header[..Header.Length].SequenceEqual(Header)
|| header.Length == 0 && fileName.EndsWith(".rar", StringComparison.InvariantCultureIgnoreCase))
{
var firstEntry = Encoding.ASCII.GetString(header);

View File

@ -17,7 +17,7 @@ internal sealed class SevenZipHandler: IArchiveHandler
public (bool result, string? reason) CanHandle(string fileName, int fileSize, ReadOnlySpan<byte> header)
{
if (header.Length >= Header.Length && header.Slice(0, Header.Length).SequenceEqual(Header)
if (header.Length >= Header.Length && header[..Header.Length].SequenceEqual(Header)
|| header.Length == 0 && fileName.EndsWith(".7z", StringComparison.InvariantCultureIgnoreCase))
{
if (fileSize > Config.AttachmentSizeLimit)

View File

@ -19,7 +19,7 @@ internal sealed class ZipHandler: IArchiveHandler
public (bool result, string? reason) CanHandle(string fileName, int fileSize, ReadOnlySpan<byte> header)
{
if (header.Length >= Header.Length && header.Slice(0, Header.Length).SequenceEqual(Header)
if (header.Length >= Header.Length && header[..Header.Length].SequenceEqual(Header)
|| header.Length == 0 && fileName.EndsWith(".zip", StringComparison.InvariantCultureIgnoreCase))
{
var firstEntry = Encoding.ASCII.GetString(header);

View File

@ -13,7 +13,6 @@ namespace CompatBot.EventHandlers.LogParsing;
internal static partial class LogParser
{
private static readonly byte[] Bom = {0xEF, 0xBB, 0xBF};
private static readonly PoorMansTaskScheduler<LogParseState> TaskScheduler = new();
public static async Task<LogParseState> ReadPipeAsync(PipeReader reader, CancellationToken cancellationToken)

View File

@ -60,8 +60,7 @@ internal partial class LogParser
if (trigger == "{PPU[" || trigger == "⁂")
{
if (state.WipCollection["serial"] is string serial
&& extractor.Match(buffer) is Match match
&& match.Success
&& extractor.Match(buffer) is { Success: true } match
&& match.Groups["syscall_name"].Value is string syscallName)
{
lock (state)
@ -81,8 +80,7 @@ internal partial class LogParser
foreach (Match match in matches)
foreach (Group group in match.Groups)
{
if (string.IsNullOrEmpty(group.Name)
|| group.Name == "0"
if (group.Name is null or "" or "0"
|| string.IsNullOrWhiteSpace(group.Value))
continue;

View File

@ -30,51 +30,50 @@ internal sealed class DropboxHandler : BaseSourceHandler
using var client = HttpClientFactory.Create();
foreach (Match m in matches)
{
if (m.Groups["dropbox_link"].Value is string lnk
&& !string.IsNullOrEmpty(lnk)
&& Uri.TryCreate(lnk, UriKind.Absolute, out var uri))
if (m.Groups["dropbox_link"].Value is not { Length: > 0 } lnk
|| !Uri.TryCreate(lnk, UriKind.Absolute, out var uri))
continue;
try
{
uri = uri.SetQueryParameter("dl", "1");
var filename = Path.GetFileName(lnk);
var filesize = -1;
using (var request = new HttpRequestMessage(HttpMethod.Head, uri))
{
using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Config.Cts.Token);
if (response.Content.Headers.ContentLength > 0)
filesize = (int)response.Content.Headers.ContentLength.Value;
if (response.Content.Headers.ContentDisposition?.FileNameStar is {Length: >0} fname)
filename = fname;
uri = response.RequestMessage?.RequestUri;
}
await using var stream = await client.GetStreamAsync(uri).ConfigureAwait(false);
var buf = BufferPool.Rent(SnoopBufferSize);
try
{
uri = uri.SetQueryParameter("dl", "1");
var filename = Path.GetFileName(lnk);
var filesize = -1;
using (var request = new HttpRequestMessage(HttpMethod.Head, uri))
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
foreach (var handler in handlers)
{
using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Config.Cts.Token);
if (response.Content.Headers.ContentLength > 0)
filesize = (int)response.Content.Headers.ContentLength.Value;
if (response.Content.Headers.ContentDisposition?.FileNameStar is string fname && !string.IsNullOrEmpty(fname))
filename = fname;
uri = response.RequestMessage?.RequestUri;
}
await using var stream = await client.GetStreamAsync(uri).ConfigureAwait(false);
var buf = BufferPool.Rent(SnoopBufferSize);
try
{
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
foreach (var handler in handlers)
{
var (canHandle, reason) = handler.CanHandle(filename, filesize, buf.AsSpan(0, read));
if (canHandle)
return (new DropboxSource(uri, handler, filename, filesize), null);
else if (!string.IsNullOrEmpty(reason))
return (null, reason);
}
}
finally
{
BufferPool.Return(buf);
var (canHandle, reason) = handler.CanHandle(filename, filesize, buf.AsSpan(0, read));
if (canHandle)
return (new DropboxSource(uri, handler, filename, filesize), null);
else if (!string.IsNullOrEmpty(reason))
return (null, reason);
}
}
catch (Exception e)
finally
{
Config.Log.Warn(e, $"Error sniffing {m.Groups["dropbox_link"].Value}");
BufferPool.Return(buf);
}
}
catch (Exception e)
{
Config.Log.Warn(e, $"Error sniffing {m.Groups["dropbox_link"].Value}");
}
}
return (null, null);
}

View File

@ -28,51 +28,50 @@ internal sealed class GenericLinkHandler : BaseSourceHandler
using var client = HttpClientFactory.Create();
foreach (Match m in matches)
{
if (m.Groups["link"].Value is string lnk
&& !string.IsNullOrEmpty(lnk)
&& Uri.TryCreate(lnk, UriKind.Absolute, out var uri)
&& !"tty.log".Equals(m.Groups["filename"].Value, StringComparison.InvariantCultureIgnoreCase))
if (m.Groups["link"].Value is not { Length: > 0 } lnk
|| !Uri.TryCreate(lnk, UriKind.Absolute, out var uri)
|| "tty.log".Equals(m.Groups["filename"].Value, StringComparison.InvariantCultureIgnoreCase))
continue;
try
{
var host = uri.Host;
var filename = Path.GetFileName(lnk);
var filesize = -1;
using (var request = new HttpRequestMessage(HttpMethod.Head, uri))
{
using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Config.Cts.Token);
if (response.Content.Headers.ContentLength > 0)
filesize = (int)response.Content.Headers.ContentLength.Value;
if (response.Content.Headers.ContentDisposition?.FileNameStar is {Length: >0} fname)
filename = fname;
uri = response.RequestMessage?.RequestUri;
}
await using var stream = await client.GetStreamAsync(uri).ConfigureAwait(false);
var buf = BufferPool.Rent(SnoopBufferSize);
try
{
var host = uri.Host;
var filename = Path.GetFileName(lnk);
var filesize = -1;
using (var request = new HttpRequestMessage(HttpMethod.Head, uri))
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
foreach (var handler in handlers)
{
using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Config.Cts.Token);
if (response.Content.Headers.ContentLength > 0)
filesize = (int)response.Content.Headers.ContentLength.Value;
if (response.Content.Headers.ContentDisposition?.FileNameStar is string fname && !string.IsNullOrEmpty(fname))
filename = fname;
uri = response.RequestMessage?.RequestUri;
}
await using var stream = await client.GetStreamAsync(uri).ConfigureAwait(false);
var buf = BufferPool.Rent(SnoopBufferSize);
try
{
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
foreach (var handler in handlers)
{
var (canHandle, reason) = handler.CanHandle(filename, filesize, buf.AsSpan(0, read));
if (canHandle)
return (new GenericSource(uri, handler, host, filename, filesize), null);
else if (!string.IsNullOrEmpty(reason))
return (null, reason);
}
}
finally
{
BufferPool.Return(buf);
var (canHandle, reason) = handler.CanHandle(filename, filesize, buf.AsSpan(0, read));
if (canHandle)
return (new GenericSource(uri, handler, host, filename, filesize), null);
else if (!string.IsNullOrEmpty(reason))
return (null, reason);
}
}
catch (Exception e)
finally
{
Config.Log.Warn(e, $"Error sniffing {m.Groups["link"].Value}");
BufferPool.Return(buf);
}
}
catch (Exception e)
{
Config.Log.Warn(e, $"Error sniffing {m.Groups["link"].Value}");
}
}
return (null, null);
}

View File

@ -39,42 +39,38 @@ internal sealed class GoogleDriveHandler: BaseSourceHandler
{
try
{
if (m.Groups["gdrive_id"].Value is string fid
&& !string.IsNullOrEmpty(fid))
{
var fileInfoRequest = client.Files.Get(fid);
fileInfoRequest.Fields = "name, size, kind";
var fileMeta = await fileInfoRequest.ExecuteAsync(Config.Cts.Token).ConfigureAwait(false);
if (fileMeta.Kind == "drive#file" && fileMeta.Size > 0)
{
var buf = BufferPool.Rent(SnoopBufferSize);
try
{
int read;
await using (var stream = new MemoryStream(buf, true))
{
var limit = Math.Min(SnoopBufferSize, fileMeta.Size.Value) - 1;
var progress = await fileInfoRequest.DownloadRangeAsync(stream, new RangeHeaderValue(0, limit), Config.Cts.Token).ConfigureAwait(false);
if (progress.Status != DownloadStatus.Completed)
continue;
if (m.Groups["gdrive_id"].Value is not { Length: > 0 } fid)
continue;
read = (int)progress.BytesDownloaded;
}
foreach (var handler in handlers)
{
var (canHandle, reason) = handler.CanHandle(fileMeta.Name, (int)fileMeta.Size, buf.AsSpan(0, read));
if (canHandle)
return (new GoogleDriveSource(client, fileInfoRequest, fileMeta, handler), null);
else if (!string.IsNullOrEmpty(reason))
return(null, reason);
}
}
finally
{
BufferPool.Return(buf);
}
var fileInfoRequest = client.Files.Get(fid);
fileInfoRequest.Fields = "name, size, kind";
var fileMeta = await fileInfoRequest.ExecuteAsync(Config.Cts.Token).ConfigureAwait(false);
if (fileMeta is not { Kind: "drive#file", Size: > 0 })
continue;
var buf = BufferPool.Rent(SnoopBufferSize);
try
{
await using var stream = new MemoryStream(buf, true);
var limit = Math.Min(SnoopBufferSize, fileMeta.Size.Value) - 1;
var progress = await fileInfoRequest.DownloadRangeAsync(stream, new RangeHeaderValue(0, limit), Config.Cts.Token).ConfigureAwait(false);
if (progress.Status != DownloadStatus.Completed)
continue;
var read = (int)progress.BytesDownloaded;
foreach (var handler in handlers)
{
var (canHandle, reason) = handler.CanHandle(fileMeta.Name, (int)fileMeta.Size, buf.AsSpan(0, read));
if (canHandle)
return (new GoogleDriveSource(client, fileInfoRequest, fileMeta, handler), null);
else if (!string.IsNullOrEmpty(reason))
return(null, reason);
}
}
finally
{
BufferPool.Return(buf);
}
}
catch (Exception e)
{

View File

@ -31,56 +31,55 @@ internal sealed class MediafireHandler : BaseSourceHandler
using var client = HttpClientFactory.Create();
foreach (Match m in matches)
{
if (m.Groups["mediafire_link"].Value is string lnk
&& !string.IsNullOrEmpty(lnk)
&& Uri.TryCreate(lnk, UriKind.Absolute, out var webLink))
if (m.Groups["mediafire_link"].Value is not { Length: > 0 } lnk
|| !Uri.TryCreate(lnk, UriKind.Absolute, out var webLink))
continue;
try
{
var filename = m.Groups["filename"].Value;
var filesize = -1;
Config.Log.Debug($"Trying to get download link for {webLink}...");
var directLink = await Client.GetDirectDownloadLinkAsync(webLink, Config.Cts.Token).ConfigureAwait(false);
if (directLink is null)
return (null, null);
Config.Log.Debug($"Trying to get content size for {directLink}...");
using (var request = new HttpRequestMessage(HttpMethod.Head, directLink))
{
using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Config.Cts.Token);
if (response.Content.Headers.ContentLength > 0)
filesize = (int)response.Content.Headers.ContentLength.Value;
if (response.Content.Headers.ContentDisposition?.FileName is {Length: >0} fname)
filename = fname;
}
Config.Log.Debug($"Trying to get content stream for {directLink}...");
await using var stream = await client.GetStreamAsync(directLink).ConfigureAwait(false);
var buf = BufferPool.Rent(SnoopBufferSize);
try
{
var filename = m.Groups["filename"].Value;
var filesize = -1;
Config.Log.Debug($"Trying to get download link for {webLink}...");
var directLink = await Client.GetDirectDownloadLinkAsync(webLink, Config.Cts.Token).ConfigureAwait(false);
if (directLink is null)
return (null, null);
Config.Log.Debug($"Trying to get content size for {directLink}...");
using (var request = new HttpRequestMessage(HttpMethod.Head, directLink))
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
foreach (var handler in handlers)
{
using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Config.Cts.Token);
if (response.Content.Headers.ContentLength > 0)
filesize = (int)response.Content.Headers.ContentLength.Value;
if (response.Content.Headers.ContentDisposition?.FileName is string fname && !string.IsNullOrEmpty(fname))
filename = fname;
}
Config.Log.Debug($"Trying to get content stream for {directLink}...");
await using var stream = await client.GetStreamAsync(directLink).ConfigureAwait(false);
var buf = BufferPool.Rent(SnoopBufferSize);
try
{
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
foreach (var handler in handlers)
{
var (canHandle, reason) = handler.CanHandle(filename, filesize, buf.AsSpan(0, read));
if (canHandle)
return (new MediafireSource(directLink, handler, filename, filesize), null);
else if (!string.IsNullOrEmpty(reason))
return (null, reason);
}
Config.Log.Debug("MediaFire Response:\n" + Encoding.UTF8.GetString(buf, 0, read));
}
finally
{
BufferPool.Return(buf);
var (canHandle, reason) = handler.CanHandle(filename, filesize, buf.AsSpan(0, read));
if (canHandle)
return (new MediafireSource(directLink, handler, filename, filesize), null);
else if (!string.IsNullOrEmpty(reason))
return (null, reason);
}
Config.Log.Debug("MediaFire Response:\n" + Encoding.UTF8.GetString(buf, 0, read));
}
catch (Exception e)
finally
{
Config.Log.Warn(e, $"Error sniffing {m.Groups["mediafire_link"].Value}");
BufferPool.Return(buf);
}
}
catch (Exception e)
{
Config.Log.Warn(e, $"Error sniffing {m.Groups["mediafire_link"].Value}");
}
}
return (null, null);
}

View File

@ -33,34 +33,32 @@ internal sealed class MegaHandler : BaseSourceHandler
{
try
{
if (m.Groups["mega_link"].Value is string lnk
&& !string.IsNullOrEmpty(lnk)
&& Uri.TryCreate(lnk, UriKind.Absolute, out var uri))
if (m.Groups["mega_link"].Value is not { Length: > 0 } lnk
|| !Uri.TryCreate(lnk, UriKind.Absolute, out var uri))
continue;
var node = await client.GetNodeFromLinkAsync(uri).ConfigureAwait(false);
if (node.Type is not NodeType.File)
continue;
var buf = BufferPool.Rent(SnoopBufferSize);
try
{
var node = await client.GetNodeFromLinkAsync(uri).ConfigureAwait(false);
if (node.Type == NodeType.File)
await using var stream = await client.DownloadAsync(uri, Doodad, Config.Cts.Token).ConfigureAwait(false);
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
foreach (var handler in handlers)
{
var buf = BufferPool.Rent(SnoopBufferSize);
try
{
int read;
await using (var stream = await client.DownloadAsync(uri, Doodad, Config.Cts.Token).ConfigureAwait(false))
read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
foreach (var handler in handlers)
{
var (canHandle, reason) = handler.CanHandle(node.Name, (int)node.Size, buf.AsSpan(0, read));
if (canHandle)
return (new MegaSource(client, uri, node, handler), null);
else if (!string.IsNullOrEmpty(reason))
return (null, reason);
}
}
finally
{
BufferPool.Return(buf);
}
var (canHandle, reason) = handler.CanHandle(node.Name, (int)node.Size, buf.AsSpan(0, read));
if (canHandle)
return (new MegaSource(client, uri, node, handler), null);
else if (!string.IsNullOrEmpty(reason))
return (null, reason);
}
}
finally
{
BufferPool.Return(buf);
}
}
catch (Exception e)
{

View File

@ -32,42 +32,39 @@ internal sealed class OneDriveSourceHandler : BaseSourceHandler
{
try
{
if (m.Groups["onedrive_link"].Value is string lnk
&& !string.IsNullOrEmpty(lnk)
&& Uri.TryCreate(lnk, UriKind.Absolute, out var uri)
&& await Client.ResolveContentLinkAsync(uri, Config.Cts.Token).ConfigureAwait(false) is DriveItemMeta itemMeta
&& itemMeta.ContentDownloadUrl is string downloadUrl)
if (m.Groups["onedrive_link"].Value is not { Length: > 0 } lnk
|| !Uri.TryCreate(lnk, UriKind.Absolute, out var uri)
|| await Client.ResolveContentLinkAsync(uri, Config.Cts.Token).ConfigureAwait(false) is not { ContentDownloadUrl: { Length: > 0 } downloadUrl } itemMeta)
continue;
try
{
var filename = itemMeta.Name ?? "";
var filesize = itemMeta.Size;
uri = new(downloadUrl);
await using var stream = await httpClient.GetStreamAsync(uri).ConfigureAwait(false);
var buf = BufferPool.Rent(SnoopBufferSize);
try
{
var filename = itemMeta.Name ?? "";
var filesize = itemMeta.Size;
uri = new Uri(downloadUrl);
await using var stream = await httpClient.GetStreamAsync(uri).ConfigureAwait(false);
var buf = BufferPool.Rent(SnoopBufferSize);
try
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
foreach (var handler in handlers)
{
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
foreach (var handler in handlers)
{
var (canHandle, reason) = handler.CanHandle(filename, filesize, buf.AsSpan(0, read));
if (canHandle)
return (new OneDriveSource(uri, handler, filename, filesize), null);
else if (!string.IsNullOrEmpty(reason))
return (null, reason);
}
}
finally
{
BufferPool.Return(buf);
var (canHandle, reason) = handler.CanHandle(filename, filesize, buf.AsSpan(0, read));
if (canHandle)
return (new OneDriveSource(uri, handler, filename, filesize), null);
else if (!string.IsNullOrEmpty(reason))
return (null, reason);
}
}
catch (Exception e)
finally
{
Config.Log.Warn(e, $"Error sniffing {m.Groups["link"].Value}");
BufferPool.Return(buf);
}
}
catch (Exception e)
{
Config.Log.Warn(e, $"Error sniffing {m.Groups["link"].Value}");
}
}
catch (Exception e)
{

View File

@ -29,31 +29,30 @@ internal sealed class PastebinHandler : BaseSourceHandler
{
try
{
if (m.Groups["pastebin_id"].Value is string pid
&& !string.IsNullOrEmpty(pid))
if (m.Groups["pastebin_id"].Value is not { Length: > 0 } pid)
continue;
var uri = new Uri("https://pastebin.com/raw/" + pid);
await using var stream = await client.GetStreamAsync(uri).ConfigureAwait(false);
var buf = BufferPool.Rent(SnoopBufferSize);
try
{
var uri = new Uri("https://pastebin.com/raw/" + pid);
await using var stream = await client.GetStreamAsync(uri).ConfigureAwait(false);
var buf = BufferPool.Rent(SnoopBufferSize);
try
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
var filename = pid + ".log";
var filesize = stream.CanSeek ? (int)stream.Length : 0;
foreach (var handler in handlers)
{
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
var filename = pid + ".log";
var filesize = stream.CanSeek ? (int)stream.Length : 0;
foreach (var handler in handlers)
{
var (canHandle, reason) = handler.CanHandle(filename, filesize, buf.AsSpan(0, read));
if (canHandle)
return (new PastebinSource(uri, filename, filesize, handler), null);
else if (!string.IsNullOrEmpty(reason))
return (null, reason);
}
}
finally
{
BufferPool.Return(buf);
var (canHandle, reason) = handler.CanHandle(filename, filesize, buf.AsSpan(0, read));
if (canHandle)
return (new PastebinSource(uri, filename, filesize, handler), null);
else if (!string.IsNullOrEmpty(reason))
return (null, reason);
}
}
finally
{
BufferPool.Return(buf);
}
}
catch (Exception e)
{

View File

@ -29,49 +29,48 @@ internal sealed class YandexDiskHandler: BaseSourceHandler
using var client = HttpClientFactory.Create();
foreach (Match m in matches)
{
if (m.Groups["yadisk_link"].Value is string lnk
&& !string.IsNullOrEmpty(lnk)
&& Uri.TryCreate(lnk, UriKind.Absolute, out var webLink))
if (m.Groups["yadisk_link"].Value is not { Length: > 0 } lnk
|| !Uri.TryCreate(lnk, UriKind.Absolute, out var webLink))
continue;
try
{
var filename = "";
var filesize = -1;
var resourceInfo = await Client.GetResourceInfoAsync(webLink, Config.Cts.Token).ConfigureAwait(false);
if (string.IsNullOrEmpty(resourceInfo?.File))
return (null, null);
if (resourceInfo.Size.HasValue)
filesize = resourceInfo.Size.Value;
if (!string.IsNullOrEmpty(resourceInfo.Name))
filename = resourceInfo.Name;
await using var stream = await client.GetStreamAsync(resourceInfo.File).ConfigureAwait(false);
var buf = BufferPool.Rent(SnoopBufferSize);
try
{
var filename = "";
var filesize = -1;
var resourceInfo = await Client.GetResourceInfoAsync(webLink, Config.Cts.Token).ConfigureAwait(false);
if (string.IsNullOrEmpty(resourceInfo?.File))
return (null, null);
if (resourceInfo.Size.HasValue)
filesize = resourceInfo.Size.Value;
if (!string.IsNullOrEmpty(resourceInfo.Name))
filename = resourceInfo.Name;
await using var stream = await client.GetStreamAsync(resourceInfo.File).ConfigureAwait(false);
var buf = BufferPool.Rent(SnoopBufferSize);
try
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
foreach (var handler in handlers)
{
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
foreach (var handler in handlers)
{
var (canHandle, reason) = handler.CanHandle(filename, filesize, buf.AsSpan(0, read));
if (canHandle)
return (new YaDiskSource(resourceInfo.File, handler, filename, filesize), null);
else if (!string.IsNullOrEmpty(reason))
return (null, reason);
}
}
finally
{
BufferPool.Return(buf);
var (canHandle, reason) = handler.CanHandle(filename, filesize, buf.AsSpan(0, read));
if (canHandle)
return (new YaDiskSource(resourceInfo.File, handler, filename, filesize), null);
else if (!string.IsNullOrEmpty(reason))
return (null, reason);
}
}
catch (Exception e)
finally
{
Config.Log.Warn(e, $"Error sniffing {m.Groups["yadisk_link"].Value}");
BufferPool.Return(buf);
}
}
catch (Exception e)
{
Config.Log.Warn(e, $"Error sniffing {m.Groups["yadisk_link"].Value}");
}
}
return (null, null);
}

View File

@ -136,8 +136,10 @@ public static class LogParsingHandler
{
botMsg = await botMsg.UpdateOrCreateMessageAsync(channel, embed: new DiscordEmbedBuilder
{
Description = "Log analysis failed, most likely cause is a truncated/invalid log.\n" +
"Please run the game again and re-upload a new copy.",
Description = """
Log analysis failed, most likely cause is a truncated/invalid log.
Please run the game again and re-upload a new copy.
""",
Color = Config.Colors.LogResultFailed,
}
.AddAuthor(client, message, source)
@ -154,8 +156,8 @@ public static class LogParsingHandler
{
if (result.SelectedFilter is null)
{
Config.Log.Error("Piracy was detectedin log, but no trigger provided");
result.SelectedFilter = new Piracystring
Config.Log.Error("Piracy was detected in log, but no trigger provided");
result.SelectedFilter = new()
{
String = "Unknown trigger, plz kick 13xforever",
Actions = FilterAction.IssueWarning | FilterAction.RemoveContent,
@ -188,13 +190,14 @@ public static class LogParsingHandler
try
{
botMsg = await botMsg.UpdateOrCreateMessageAsync(channel,
$"{message.Author.Mention}, please read carefully:\n" +
"🏴‍☠️ **Pirated content detected** 🏴‍☠️\n" +
"__You are being denied further support until you legally dump the game__.\n" +
"Please note that the RPCS3 community and its developers do not support piracy.\n" +
"Most of the issues with pirated dumps occur due to them being modified in some way " +
"that prevent them from working on RPCS3.\n" +
"If you need help obtaining valid working dump of the game you own, please read the quickstart guide at <https://rpcs3.net/quickstart>"
$"""
# 🏴 **Pirated content detected** 🏴
{message.Author.Mention}, please read carefully:
__You are being denied further support until you legally dump the game__.
Please note that the RPCS3 community and its developers do not support piracy.
Most of the issues with pirated dumps occur due to them being modified in some way that prevent them from working on RPCS3.
If you need help obtaining valid working dump of the game you own, please read [the quickstart guide](<https://rpcs3.net/quickstart>).
"""
).ConfigureAwait(false);
}
catch (Exception e)
@ -233,8 +236,7 @@ public static class LogParsingHandler
if (isHelpChannel)
await botMsg.UpdateOrCreateMessageAsync(
channel,
$"{message.Author.Mention} please describe the issue if you require help, " +
$"or upload log in {botSpamChannel.Mention} if you only need to check your logs automatically"
$"{message.Author.Mention} please describe the issue if you require help, or upload log in {botSpamChannel.Mention} if you only need to check your logs automatically"
).ConfigureAwait(false);
else
{
@ -278,7 +280,11 @@ public static class LogParsingHandler
{
case "TXT":
{
await channel.SendMessageAsync($"{message.Author.Mention} Please upload the full RPCS3.log.gz (or RPCS3.log with a zip/rar icon) file after closing the emulator instead of copying the logs from RPCS3's interface, as it doesn't contain all the required information.").ConfigureAwait(false);
await channel.SendMessageAsync(
$"{message.Author.Mention}, please upload the full RPCS3.log.gz (or RPCS3.log with a zip/rar icon) file " +
"after closing the emulator instead of copying the logs from RPCS3's interface, " +
"as it doesn't contain all the required information."
).ConfigureAwait(false);
Config.TelemetryClient?.TrackRequest(nameof(LogParsingHandler), start, DateTimeOffset.UtcNow - start, HttpStatusCode.BadRequest.ToString(), true);
return;
}
@ -398,7 +404,7 @@ public static class LogParsingHandler
private static DiscordEmbedBuilder GetAnalyzingMsgEmbed(DiscordClient client)
{
var indicator = client.GetEmoji(":kannamag:", Config.Reactions.PleaseWait);
return new DiscordEmbedBuilder
return new()
{
Description = $"{indicator} Looking at the log, please wait... {indicator}",
Color = Config.Colors.LogUnknown,

View File

@ -24,9 +24,8 @@ internal static class NewBuildsMonitor
if (args.Author.IsBotSafeCheck()
&& !args.Author.IsCurrent
&& "github".Equals(args.Channel.Name, StringComparison.InvariantCultureIgnoreCase)
&& args.Message.Embeds.FirstOrDefault() is DiscordEmbed embed
&& !string.IsNullOrEmpty(embed.Title)
&& BuildResult.IsMatch(embed.Title)
&& args.Message?.Embeds is [{ Title: { Length: > 0 } title }, ..]
&& BuildResult.IsMatch(title)
)
{
Config.Log.Info("Found new PR merge message");

View File

@ -20,8 +20,8 @@ internal static class PostLogHelpHandler
private static readonly TimeSpan ThrottlingThreshold = TimeSpan.FromSeconds(5);
private static readonly Dictionary<string, Explanation> DefaultExplanation = new()
{
["log"] = new Explanation { Text = "To upload log, run the game, then completely close RPCS3, then drag and drop rpcs3.log.gz from the RPCS3 folder into Discord. The file may have a zip or rar icon." },
["vulkan-1"] = new Explanation { Text = "Please remove all the traces of video drivers using DDU, and then reinstall the latest driver version for your GPU." },
["log"] = new() { Text = "To upload log, run the game, then completely close RPCS3, then drag and drop rpcs3.log.gz from the RPCS3 folder into Discord. The file may have a zip or rar icon." },
["vulkan-1"] = new() { Text = "Please remove all the traces of video drivers using DDU, and then reinstall the latest driver version for your GPU." },
};
private static DateTime lastMention = DateTime.UtcNow.AddHours(-1);

View File

@ -21,7 +21,7 @@ internal static class ThumbnailCacheMonitor
await using var db = new ThumbnailDb();
var thumb = db.Thumbnail.FirstOrDefault(i => i.ContentId == args.Message.Content);
if (thumb?.EmbeddableUrl is string url && !string.IsNullOrEmpty(url) && args.Message.Attachments.Any(a => a.Url == url))
if (thumb is { EmbeddableUrl: { Length: > 0 } url } && args.Message.Attachments.Any(a => a.Url == url))
{
thumb.EmbeddableUrl = null;
await db.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false);

View File

@ -15,8 +15,10 @@ namespace CompatBot.EventHandlers;
internal static class UnknownCommandHandler
{
private static readonly Regex BinaryQuestion = new(@"^\s*(am I|(are|is|do(es)|did|can(?!\s+of\s+)|should|must|have)(n't)?|shall|shan't|may|will|won't)\b",
RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase);
private static readonly Regex BinaryQuestion = new(
@"^\s*(am I|(are|is|do(es)|did|can(?!\s+of\s+)|should|must|have)(n't)?|shall|shan't|may|will|won't)\b",
RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase
);
public static Task OnError(CommandsNextExtension cne, CommandErrorEventArgs e)
{

View File

@ -29,7 +29,10 @@ public static class UsernameZalgoMonitor
{
var suggestedName = StripZalgo(m.DisplayName, m.Username, m.Id).Sanitize();
await c.ReportAsync("🔣 Potential display name issue",
$"User {m.GetMentionWithNickname()} has changed their __username__ and is now shown as **{m.DisplayName.Sanitize()}**\nAutomatically renamed to: **{suggestedName}**",
$"""
User {m.GetMentionWithNickname()} has changed their __username__ and is now shown as **{m.DisplayName.Sanitize()}**
Automatically renamed to: **{suggestedName}**
""",
null,
ReportSeverity.Low);
await DmAndRenameUserAsync(c, m, suggestedName).ConfigureAwait(false);
@ -60,7 +63,10 @@ public static class UsernameZalgoMonitor
{
var suggestedName = StripZalgo(name, fallback, args.Member.Id).Sanitize();
await c.ReportAsync("🔣 Potential display name issue",
$"Member {member.GetMentionWithNickname()} has changed their __display name__ and is now shown as **{name.Sanitize()}**\nAutomatically renamed to: **{suggestedName}**",
$"""
Member {member.GetMentionWithNickname()} has changed their __display name__ and is now shown as **{name.Sanitize()}**
Automatically renamed to: **{suggestedName}**
""",
null,
ReportSeverity.Low);
await DmAndRenameUserAsync(c, member, suggestedName).ConfigureAwait(false);
@ -81,7 +87,10 @@ public static class UsernameZalgoMonitor
{
var suggestedName = StripZalgo(name, args.Member.Username, args.Member.Id).Sanitize();
await c.ReportAsync("🔣 Potential display name issue",
$"New member joined the server: {args.Member.GetMentionWithNickname()} and is shown as **{name.Sanitize()}**\nAutomatically renamed to: **{suggestedName}**",
$"""
New member joined the server: {args.Member.GetMentionWithNickname()} and is shown as **{name.Sanitize()}**
Automatically renamed to: **{suggestedName}**
""",
null,
ReportSeverity.Low);
await DmAndRenameUserAsync(c, args.Member, suggestedName).ConfigureAwait(false);
@ -106,9 +115,11 @@ public static class UsernameZalgoMonitor
var renameTask = member.ModifyAsync(m => m.Nickname = suggestedName);
Config.Log.Info($"Renamed {member.Username}#{member.Discriminator} ({member.Id}) to {suggestedName}");
var rulesChannel = await client.GetChannelAsync(Config.BotRulesChannelId).ConfigureAwait(false);
var msg = $"Hello, your current _display name_ is breaking {rulesChannel.Mention} #7, so you have been renamed to `{suggestedName}`.\n" +
"I'm not perfect and can't clean all the junk in names in some cases, so change your nickname at your discretion.\n" +
"You can change your _display name_ by clicking on the server name at the top left and selecting **Change Nickname**.";
var msg = $"""
Hello, your current _display name_ is breaking {rulesChannel.Mention} #7, so you have been renamed to `{suggestedName}`.
I'm not perfect and can't clean all the junk in names in some cases, so change your nickname at your discretion.
You can change your _display name_ by clicking on the server name at the top left and selecting **Change Nickname**.
""";
var dm = await member.CreateDmChannelAsync().ConfigureAwait(false);
await dm.SendMessageAsync(msg).ConfigureAwait(false);
await renameTask.ConfigureAwait(false);

View File

@ -20,7 +20,10 @@ internal static class GameTdbScraper
{
private static readonly HttpClient HttpClient = HttpClientFactory.Create(new CompressionMessageHandler());
private static readonly Uri TitleDownloadLink = new("https://www.gametdb.com/ps3tdb.zip?LANG=EN");
private static readonly Regex CoverArtLink = new(@"(?<cover_link>https?://art\.gametdb\.com/ps3/cover(?!full)[/\w\d]+\.jpg(\?\d+)?)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.ExplicitCapture);
private static readonly Regex CoverArtLink = new(
@"(?<cover_link>https?://art\.gametdb\.com/ps3/cover(?!full)[/\w\d]+\.jpg(\?\d+)?)",
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.ExplicitCapture
);
//private static readonly List<string> PreferredOrder = new List<string>{"coverHQ", "coverM", "cover"};
public static async Task RunAsync(CancellationToken cancellationToken)
@ -47,10 +50,14 @@ internal static class GameTdbScraper
try
{
var html = await HttpClient.GetStringAsync("https://www.gametdb.com/PS3/" + productCode).ConfigureAwait(false);
var coverLinks = CoverArtLink.Matches(html).Select(m => m.Groups["cover_link"].Value).Distinct().Where(l => l.Contains(productCode, StringComparison.InvariantCultureIgnoreCase)).ToList();
return coverLinks.FirstOrDefault(l => l.Contains("coverHQ", StringComparison.InvariantCultureIgnoreCase)) ??
coverLinks.FirstOrDefault(l => l.Contains("coverM", StringComparison.InvariantCultureIgnoreCase)) ??
coverLinks.FirstOrDefault();
var coverLinks = CoverArtLink.Matches(html)
.Select(m => m.Groups["cover_link"].Value)
.Distinct()
.Where(l => l.Contains(productCode, StringComparison.InvariantCultureIgnoreCase))
.ToList();
return coverLinks.FirstOrDefault(l => l.Contains("coverHQ", StringComparison.InvariantCultureIgnoreCase))
?? coverLinks.FirstOrDefault(l => l.Contains("coverM", StringComparison.InvariantCultureIgnoreCase))
?? coverLinks.FirstOrDefault();
}
catch (Exception e)
{

View File

@ -17,7 +17,10 @@ namespace CompatBot.ThumbScrapper;
internal sealed class PsnScraper
{
private static readonly PsnClient.Client Client = new();
public static readonly Regex ContentIdMatcher = new(@"(?<content_id>(?<service_id>(?<service_letters>\w\w)(?<service_number>\d{4}))-(?<product_id>(?<product_letters>\w{4})(?<product_number>\d{5}))_(?<part>\d\d)-(?<label>\w{16}))", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.ExplicitCapture);
public static readonly Regex ContentIdMatcher = new(
@"(?<content_id>(?<service_id>(?<service_letters>\w\w)(?<service_number>\d{4}))-(?<product_id>(?<product_letters>\w{4})(?<product_number>\d{5}))_(?<part>\d\d)-(?<label>\w{16}))",
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.ExplicitCapture
);
private static readonly SemaphoreSlim LockObj = new(1, 1);
private static List<string> psnStores = new();
private static DateTime storeRefreshTimestamp = DateTime.MinValue;
@ -63,7 +66,7 @@ internal sealed class PsnScraper
await LockObj.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
storesToScrape = new List<string>(psnStores);
storesToScrape = new(psnStores);
}
finally
{
@ -126,7 +129,7 @@ internal sealed class PsnScraper
await LockObj.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
storesToScrape = new List<string>(psnStores);
storesToScrape = new(psnStores);
}
finally
{

View File

@ -5,18 +5,13 @@ namespace CompatBot.Utils;
internal static class BotDbExtensions
{
public static bool IsComplete(this EventSchedule evt)
{
return evt.Start > 0
&& evt.End > evt.Start
&& evt.Year > 0
&& !string.IsNullOrEmpty(evt.Name);
}
=> evt is { Start: > 0, Year: > 0, Name.Length: >0 }
&& evt.End > evt.Start;
public static bool IsComplete(this Piracystring filter)
{
var result = !string.IsNullOrEmpty(filter.String)
&& filter.String.Length >= Config.MinimumPiracyTriggerLength
&& filter.Actions != 0;
var result = filter.Actions != 0
&& filter.String.Length >= Config.MinimumPiracyTriggerLength;
if (result && filter.Actions.HasFlag(FilterAction.ShowExplain))
result = !string.IsNullOrEmpty(filter.ExplainTerm);
return result;

View File

@ -4,19 +4,8 @@ namespace CompatBot.Utils;
public static class DateTimeEx
{
public static DateTime AsUtc(this DateTime dateTime)
{
if (dateTime.Kind == DateTimeKind.Utc)
return dateTime;
return new DateTime(dateTime.Ticks, DateTimeKind.Utc);
}
public static DateTime AsUtc(this long ticks)
{
return new(ticks, DateTimeKind.Utc);
}
public static DateTime AsUtc(this DateTime dateTime) => dateTime.Kind == DateTimeKind.Utc ? dateTime : new(dateTime.Ticks, DateTimeKind.Utc);
public static DateTime AsUtc(this long ticks) => new(ticks, DateTimeKind.Utc);
public static string AsShortTimespan(this TimeSpan timeSpan)
{

View File

@ -14,11 +14,7 @@ public static class DiceCoefficientOptimized
var bgCount1 = input.Length - 1;
var bgCount2 = comparedTo.Length - 1;
if (comparedTo.Length < input.Length)
{
var tmp = input;
input = comparedTo;
comparedTo = tmp;
}
(input, comparedTo) = (comparedTo, input);
var matches = 0;
for (var i = 0; i < input.Length - 1; i++)
for (var j = 0; j < comparedTo.Length - 1; j++)

View File

@ -15,12 +15,7 @@ namespace CompatBot.Utils;
public static class DiscordClientExtensions
{
public static DiscordMember? GetMember(this DiscordClient client, DiscordGuild? guild, ulong userId)
{
if (guild is null)
return GetMember(client, userId);
return GetMember(client, guild.Id, userId);
}
=> guild is null ? GetMember(client, userId) : GetMember(client, guild.Id, userId);
public static DiscordMember? GetMember(this DiscordClient client, ulong guildId, ulong userId)
=> (from g in client.Guilds
@ -87,15 +82,10 @@ public static class DiscordClientExtensions
}
}
public static Task RemoveReactionAsync(this CommandContext ctx, DiscordEmoji emoji)
{
return RemoveReactionAsync(ctx.Message, emoji);
}
public static Task RemoveReactionAsync(this CommandContext ctx, DiscordEmoji emoji) => RemoveReactionAsync(ctx.Message, emoji);
public static Task ReactWithAsync(this CommandContext ctx, DiscordEmoji emoji, string? fallbackMessage = null, bool? showBoth = null)
{
return ReactWithAsync(ctx.Message, emoji, fallbackMessage, showBoth ?? (ctx.Prefix == Config.AutoRemoveCommandPrefix));
}
=> ReactWithAsync(ctx.Message, emoji, fallbackMessage, showBoth ?? (ctx.Prefix == Config.AutoRemoveCommandPrefix));
public static async Task<IReadOnlyCollection<DiscordMessage>> GetMessagesBeforeAsync(this DiscordChannel channel, ulong beforeMessageId, int limit = 100, DateTime? timeLimit = null)
{
@ -161,21 +151,20 @@ public static class DiscordClientExtensions
}
public static string GetMentionWithNickname(this DiscordMember member)
=> string.IsNullOrEmpty(member.Nickname) ? $"<@{member.Id}> (`{member.Username.Sanitize()}#{member.Discriminator}`)" : $"<@{member.Id}> (`{member.Username.Sanitize()}#{member.Discriminator}`, shown as `{member.Nickname.Sanitize()}`)";
=> string.IsNullOrEmpty(member.Nickname)
? $"<@{member.Id}> (`{member.Username.Sanitize()}#{member.Discriminator}`)"
: $"<@{member.Id}> (`{member.Username.Sanitize()}#{member.Discriminator}`, shown as `{member.Nickname.Sanitize()}`)";
public static string GetUsernameWithNickname(this DiscordUser user, DiscordClient client, DiscordGuild? guild = null)
{
return client.GetMember(guild, user).GetUsernameWithNickname()
?? $"`{user.Username.Sanitize()}#{user.Discriminator}`";
}
=> client.GetMember(guild, user).GetUsernameWithNickname()
?? $"`{user.Username.Sanitize()}#{user.Discriminator}`";
public static string? GetUsernameWithNickname(this DiscordMember? member)
{
if (member == null)
return null;
return string.IsNullOrEmpty(member.Nickname) ? $"`{member.Username.Sanitize()}#{member.Discriminator}`" : $"`{member.Username.Sanitize()}#{member.Discriminator}` (shown as `{member.Nickname.Sanitize()}`)";
}
=> member is null
? null
: string.IsNullOrEmpty(member.Nickname)
? $"`{member.Username.Sanitize()}#{member.Discriminator}`"
: $"`{member.Username.Sanitize()}#{member.Discriminator}` (shown as `{member.Nickname.Sanitize()}`)";
public static DiscordEmoji? GetEmoji(this DiscordClient client, string? emojiName, string? fallbackEmoji = null)
=> GetEmoji(client, emojiName, fallbackEmoji == null ? null : DiscordEmoji.FromUnicode(fallbackEmoji));
@ -187,9 +176,11 @@ public static class DiscordClientExtensions
if (DiscordEmoji.TryFromName(client, emojiName, true, out var result))
return result;
else if (DiscordEmoji.TryFromName(client, $":{emojiName}:", true, out result))
if (DiscordEmoji.TryFromName(client, $":{emojiName}:", true, out result))
return result;
else if (DiscordEmoji.TryFromUnicode(emojiName, out result))
if (DiscordEmoji.TryFromUnicode(emojiName, out result))
return result;
return fallbackEmoji;
}

View File

@ -38,11 +38,10 @@ public static class EnumerableExtensions
{
if (collection.Count > 0)
{
var rng = seed.HasValue ? new Random(seed.Value) : new Random();
var rng = seed.HasValue ? new(seed.Value) : new Random();
return collection[rng.Next(collection.Count)];
}
else
return default;
return default;
}
public static bool AnyPatchesApplied(this Dictionary<string, int> patches)

View File

@ -24,7 +24,7 @@ internal static class FilterActionExtensions
.Cast<FilterAction>()
.Select(fa => flags.HasFlag(fa) ? ActionFlags[fa] : '-')
.ToArray();
return new string(result);
return new(result);
}
public static string GetLegend(string wrapChar = "`")

View File

@ -212,7 +212,7 @@ public static class StringUtils
{
result.Append('(');
for (var i = 0; i < count; i++)
result.Append(@"\x").Append(Utf8ToLatin1RegexPatternEncoderFallback.CustomMapperFallbackBuffer.byteToHex[tmp[i]]);
result.Append(@"\x").Append(Utf8ToLatin1RegexPatternEncoderFallback.CustomMapperFallbackBuffer.ByteToHex[tmp[i]]);
result.Append(')');
}
span = span.Slice(1);

View File

@ -54,10 +54,10 @@ internal class FixedLengthBuffer<TKey, TValue>: IList<TValue>
public void TrimExcess()
{
if (Count <= FixedLengthBuffer<TKey, TValue>.MaxLength)
if (Count <= MaxLength)
return;
TrimOldItems(Count - FixedLengthBuffer<TKey, TValue>.MaxLength);
TrimOldItems(Count - MaxLength);
}
public List<TValue> GetOldItems(int count)
@ -65,9 +65,9 @@ internal class FixedLengthBuffer<TKey, TValue>: IList<TValue>
public List<TValue> GetExcess()
{
if (Count <= FixedLengthBuffer<TKey, TValue>.MaxLength)
return new List<TValue>(0);
return GetOldItems(Count - FixedLengthBuffer<TKey, TValue>.MaxLength);
if (Count <= MaxLength)
return new(0);
return GetOldItems(Count - MaxLength);
}
public TValue? Evict(TKey key)
@ -116,7 +116,7 @@ internal class FixedLengthBuffer<TKey, TValue>: IList<TValue>
public int Count => lookup.Count;
public bool IsReadOnly => false;
public bool NeedTrimming => Count > FixedLengthBuffer<TKey, TValue>.MaxLength + 20;
public bool NeedTrimming => Count > MaxLength + 20;
public TValue this[TKey index] => lookup[index];
private static int MaxLength => Config.ChannelMessageHistorySize;

View File

@ -1,6 +1,7 @@
using System.Collections;
using System.Collections.Generic;
namespace CompatBot.Utils;
public class NameUniqueObjectCollection<TValue>: IDictionary<string, UniqueList<TValue>>
@ -10,7 +11,7 @@ public class NameUniqueObjectCollection<TValue>: IDictionary<string, UniqueList<
public NameUniqueObjectCollection(IEqualityComparer<string>? keyComparer = null, IEqualityComparer<TValue>? valueComparer = null)
{
dict = new Dictionary<string, UniqueList<TValue>>(keyComparer);
dict = new(keyComparer);
this.valueComparer = valueComparer;
}
@ -34,18 +35,21 @@ public class NameUniqueObjectCollection<TValue>: IDictionary<string, UniqueList<
c.Add(value);
else
{
dict[key] = c = new UniqueList<TValue>(valueComparer);
dict[key] = c = new(valueComparer);
c.Add(value);
}
}
public void Clear() => dict.Clear();
bool ICollection<KeyValuePair<string, UniqueList<TValue>>>.Contains(KeyValuePair<string, UniqueList<TValue>> item) => ((IDictionary<string, UniqueList<TValue>>)dict).Contains(item);
bool ICollection<KeyValuePair<string, UniqueList<TValue>>>.Contains(KeyValuePair<string, UniqueList<TValue>> item)
=> ((IDictionary<string, UniqueList<TValue>>)dict).Contains(item);
void ICollection<KeyValuePair<string, UniqueList<TValue>>>.CopyTo(KeyValuePair<string, UniqueList<TValue>>[] array, int arrayIndex) => ((IDictionary<string, UniqueList<TValue>>)dict).CopyTo(array, arrayIndex);
void ICollection<KeyValuePair<string, UniqueList<TValue>>>.CopyTo(KeyValuePair<string, UniqueList<TValue>>[] array, int arrayIndex)
=> ((IDictionary<string, UniqueList<TValue>>)dict).CopyTo(array, arrayIndex);
bool ICollection<KeyValuePair<string, UniqueList<TValue>>>.Remove(KeyValuePair<string, UniqueList<TValue>> item) => ((IDictionary<string, UniqueList<TValue>>)dict).Remove(item);
bool ICollection<KeyValuePair<string, UniqueList<TValue>>>.Remove(KeyValuePair<string, UniqueList<TValue>> item)
=> ((IDictionary<string, UniqueList<TValue>>)dict).Remove(item);
public int Count => dict.Count;
public bool IsReadOnly => false;
@ -59,7 +63,7 @@ public class NameUniqueObjectCollection<TValue>: IDictionary<string, UniqueList<
if (dict.TryGetValue(key, out value!))
return true;
dict[key] = value = new UniqueList<TValue>(valueComparer);
dict[key] = value = new(valueComparer);
return false;
}

View File

@ -61,14 +61,18 @@ public static class OpenSslConfigurator
}
}
await writer.WriteLineAsync("openssl_conf = default_conf").ConfigureAwait(false);
await writer.WriteLineAsync("[default_conf]").ConfigureAwait(false);
await writer.WriteLineAsync("ssl_conf = ssl_sect").ConfigureAwait(false);
await writer.WriteLineAsync("[ssl_sect]").ConfigureAwait(false);
await writer.WriteLineAsync("system_default = system_default_sect").ConfigureAwait(false);
await writer.WriteLineAsync("[system_default_sect]").ConfigureAwait(false);
await writer.WriteLineAsync("CipherString = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384").ConfigureAwait(false);
await writer.WriteLineAsync("""
openssl_conf = default_conf
[default_conf]
ssl_conf = ssl_sect
[ssl_sect]
system_default = system_default_sect
[system_default_sect]
CipherString = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384
""").ConfigureAwait(false);
if (content.Length > 0)
await writer.WriteAsync(content).ConfigureAwait(false);
await writer.FlushAsync().ConfigureAwait(false);

View File

@ -219,42 +219,42 @@ public static class ProductCodeDecoder
if (productCode.IsEmpty || productCode.Length < 2)
return;
switch ((productCode[0], productCode[1]))
switch (productCode)
{
case ('0', '0'):
case ('0', '1'):
case ('0', '2'):
case ['0', '0', ..]:
case ['0', '1', ..]:
case ['0', '2', ..]:
{
result.Add("European code range");
break;
}
case ('2', '0'):
case ['2', '0', ..]:
{
result.Add("Korean code range");
break;
}
case ('3', '0'):
case ('3', '1'):
case ('4', '1'):
case ('8', '1'):
case ('8', '2'):
case ('8', '3'):
case ('9', '0'):
case ('9', '1'):
case ('9', '4'):
case ('9', '8'):
case ('9', '9'):
case ['3', '0', ..]:
case ['3', '1', ..]:
case ['4', '1', ..]:
case ['8', '1', ..]:
case ['8', '2', ..]:
case ['8', '3', ..]:
case ['9', '0', ..]:
case ['9', '1', ..]:
case ['9', '4', ..]:
case ['9', '8', ..]:
case ['9', '9', ..]:
{
result.Add("USA code range");
break;
}
case ('5', '0'):
case ['5', '0', ..]:
{
result.Add("Asian code range");
break;
}
case ('6', '0'):
case ('6', '1'):
case ['6', '0', ..]:
case ['6', '1', ..]:
{
result.Add("Japanese code range");
break;
@ -561,9 +561,9 @@ public static class ProductCodeDecoder
private static void DecodeMediaC(in ReadOnlySpan<char> productCode, List<string> result)
{
switch ((productCode[0], productCode[1], productCode[2]))
switch (productCode)
{
case ('U', 'S', 'A'):
case ['U', 'S', 'A', ..]:
{
result.Add("Playstation 4 software");
return;

View File

@ -11,8 +11,10 @@ namespace CompatBot.Utils.ResultFormatters;
internal static class FwInfoFormatter
{
//2019_0828_c975768e5d70e105a72656f498cc9be9/PS3UPDAT.PUP
private static readonly Regex FwLinkInfo = new(@"(?<year>\d{4})_(?<month>\d\d)(?<day>\d\d)_(?<md5>[0-9a-f]+)",
RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.Singleline | RegexOptions.IgnoreCase);
private static readonly Regex FwLinkInfo = new(
@"(?<year>\d{4})_(?<month>\d\d)(?<day>\d\d)_(?<md5>[0-9a-f]+)",
RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.Singleline | RegexOptions.IgnoreCase
);
private static readonly Dictionary<string, string> RegionToFlagMap = new(StringComparer.InvariantCultureIgnoreCase)
{
["us"] = "🇺🇸",
@ -41,8 +43,10 @@ internal static class FwInfoFormatter
{
result.Description = $"Latest version is **{fwInfoList[0].Version}** released on {info.Groups["year"].Value}-{info.Groups["month"].Value}-{info.Groups["day"].Value}\n" +
$"It is available in {fwInfoList.Count} region{(fwInfoList.Count == 1 ? "" : "s")} out of {RegionToFlagMap.Count}";
result.AddField("Checksums", $"MD5: `{info.Groups["md5"].Value}`\n" +
"You can use [HashCheck](https://github.com/gurnec/HashCheck/releases/latest) to verify your download");
result.AddField("Checksums", $"""
MD5: `{info.Groups["md5"].Value}`
You can use [HashCheck](https://github.com/gurnec/HashCheck/releases/latest) to verify your download
""");
var links = new StringBuilder();
foreach (var fwi in fwInfoList)
{

View File

@ -448,7 +448,7 @@ internal static partial class LogParserResult
msg += $" (update available: v{gameUpVer})";
notes.Add(msg);
}
if (multiItems["ppu_patch"].FirstOrDefault() is string firstPpuPatch
if (multiItems["ppu_patch"] is [string firstPpuPatch, ..]
&& ProgramHashPatch.Match(firstPpuPatch) is { Success: true } m
&& m.Groups["hash"].Value is string firstPpuHash)
{

View File

@ -1,10 +1,11 @@
using DSharpPlus.Entities;
using Octokit;
namespace CompatBot.Utils.ResultFormatters;
internal static class PrInfoFormatter
{
public static DiscordEmbedBuilder AsEmbed(this Octokit.PullRequest prInfo)
public static DiscordEmbedBuilder AsEmbed(this PullRequest prInfo)
{
var state = prInfo.GetState();
var stateLabel = state.state == null ? null : $"[{state.state}] ";
@ -12,7 +13,7 @@ internal static class PrInfoFormatter
return new() {Title = title, Url = prInfo.HtmlUrl, Description = prInfo.Title, Color = state.color};
}
public static DiscordEmbedBuilder AsEmbed(this Octokit.Issue issueInfo)
public static DiscordEmbedBuilder AsEmbed(this Issue issueInfo)
{
var state = issueInfo.GetState();
var stateLabel = state.state == null ? null : $"[{state.state}] ";
@ -20,12 +21,12 @@ internal static class PrInfoFormatter
return new() {Title = title, Url = issueInfo.HtmlUrl, Description = issueInfo.Title, Color = state.color};
}
public static (string? state, DiscordColor color) GetState(this Octokit.PullRequest prInfo)
public static (string? state, DiscordColor color) GetState(this PullRequest prInfo)
{
if (prInfo.State == Octokit.ItemState.Open)
if (prInfo.State == ItemState.Open)
return ("Open", Config.Colors.PrOpen);
if (prInfo.State == Octokit.ItemState.Closed)
if (prInfo.State == ItemState.Closed)
{
if (prInfo.MergedAt.HasValue)
return ("Merged", Config.Colors.PrMerged);
@ -36,12 +37,12 @@ internal static class PrInfoFormatter
return (null, Config.Colors.DownloadLinks);
}
public static (string? state, DiscordColor color) GetState(this Octokit.Issue issueInfo)
public static (string? state, DiscordColor color) GetState(this Issue issueInfo)
{
if (issueInfo.State == Octokit.ItemState.Open)
if (issueInfo.State == ItemState.Open)
return ("Open", Config.Colors.PrOpen);
if (issueInfo.State == Octokit.ItemState.Closed)
if (issueInfo.State == ItemState.Closed)
return ("Closed", Config.Colors.PrClosed);
return (null, Config.Colors.DownloadLinks);

View File

@ -29,16 +29,8 @@ internal static class TitleInfoFormatter
? date.ToString(ApiConfig.DateOutputFormat)
: null;
private static string? ToPrString(this TitleInfo info, string? defaultString, bool link = false)
{
if ((info.Pr ?? 0) == 0)
return defaultString;
if (link)
return $"[#{info.Pr}](https://github.com/RPCS3/rpcs3/pull/{info.Pr})";
return $"#{info.Pr}";
}
private static string ToPrString(this TitleInfo info)
=> $"[#{info.Pr}](<https://github.com/RPCS3/rpcs3/pull/{info.Pr}>)";
public static string AsString(this TitleInfo info, string titleId)
{
@ -56,9 +48,11 @@ internal static class TitleInfoFormatter
result += " ";
else
result += $" since {info.ToUpdated(),-10}";
result += $" (PR {info.ToPrString("#????"),-5})`";
result += '`';
if (info.Pr > 0)
result += $" PR {info.ToPrString(),-5}";
if (info.Thread > 0)
result += $" <https://forums.rpcs3.net/thread-{info.Thread}.html>";
result += $", [forum](<https://forums.rpcs3.net/thread-{info.Thread}.html>)";
return result;
}
@ -77,7 +71,7 @@ internal static class TitleInfoFormatter
var thumb = db.Thumbnail.FirstOrDefault(t => t.ProductCode == titleId);
if (thumb?.CompatibilityStatus != null)
{
info = new TitleInfo
info = new()
{
Date = thumb.CompatibilityChangeDate?.AsUtc().ToString("yyyy-MM-dd"),
Status = thumb.CompatibilityStatus.ToString(),
@ -90,10 +84,9 @@ internal static class TitleInfoFormatter
{
// apparently there's no formatting in the footer, but you need to escape everything in description; ugh
var onlineOnlyPart = info.Network == 1 ? " 🌐" : "";
var pr = info.ToPrString(null, true);
var desc = $"{info.Status} since {info.ToUpdated() ?? "forever"}";
if (pr != null)
desc += $" (PR {pr})";
if (info.Pr > 0)
desc += $" (PR {info.ToPrString()})";
if (!forLog && !string.IsNullOrEmpty(info.AlternativeTitle))
desc = info.AlternativeTitle + Environment.NewLine + desc;
if (!string.IsNullOrEmpty(info.WikiTitle))

View File

@ -234,35 +234,32 @@ internal static class UpdateInfoFormatter
var seconds = (int)delta.TotalSeconds;
return $"{seconds} second{(seconds == 1 ? "" : "s")}";
}
else if (delta.TotalHours < 1)
if (delta.TotalHours < 1)
{
var minutes = (int)delta.TotalMinutes;
return $"{minutes} minute{(minutes == 1 ? "" : "s")}";
}
else if (delta.TotalDays < 1)
if (delta.TotalDays < 1)
{
var hours = (int) delta.TotalHours;
return $"{hours} hour{(hours == 1 ? "": "s")}";
}
else if (delta.TotalDays < 7)
if (delta.TotalDays < 7)
{
var days = (int) delta.TotalDays;
return $"{days} day{(days == 1 ? "": "s")}";
}
else if (delta.TotalDays < 30)
if (delta.TotalDays < 30)
{
var weeks = (int)(delta.TotalDays/7);
return $"{weeks} week{(weeks == 1 ? "" : "s")}";
}
else if (delta.TotalDays < 365)
if (delta.TotalDays < 365)
{
var months = (int)(delta.TotalDays/30);
return $"{months} month{(months == 1 ? "" : "s")}";
}
else
{
var years = (int)(delta.TotalDays/365);
return $"{years} year{(years == 1 ? "" : "s")}";
}
var years = (int)(delta.TotalDays/365);
return $"{years} year{(years == 1 ? "" : "s")}";
}
}

View File

@ -55,14 +55,12 @@ public static class TimeParser
if (TimeZoneAcronyms.ContainsKey(a))
continue;
if (!standardNames.ContainsKey(a))
standardNames[a] = tzi;
standardNames.TryAdd(a, tzi);
a = tzi.DaylightName.GetAcronym(includeAllCaps: true, includeAllDigits: true);
if (TimeZoneAcronyms.ContainsKey(a) || standardNames.ContainsKey(a))
continue;
if (!daylightNames.ContainsKey(a))
daylightNames[a] = tzi;
daylightNames.TryAdd(a, tzi);
}
}
Config.Log.Trace("[TZI] Total matched acronyms: " + result.Count);

View File

@ -43,7 +43,6 @@ public class UniqueList<T>: IList<T>
}
public IEnumerator<T> GetEnumerator() => list.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)list).GetEnumerator();
public void Add(T item)
@ -65,7 +64,6 @@ public class UniqueList<T>: IList<T>
}
public bool Contains(T item) => set.Contains(item);
public void CopyTo(T[] array, int arrayIndex) => list.CopyTo(array, arrayIndex);
public bool Remove(T item)
@ -75,9 +73,7 @@ public class UniqueList<T>: IList<T>
}
public int Count => list.Count;
public bool IsReadOnly => false;
public int IndexOf(T item) => list.IndexOf(item);
public void Insert(int index, T item)

View File

@ -19,12 +19,12 @@ internal class Utf8ToLatin1RegexPatternEncoderFallback : EncoderFallback
private readonly byte[] buffer = new byte[MaxBufferSize];
private int size, remaining;
private static readonly Encoding Utf8 = new UTF8Encoding(false);
internal static readonly List<string> byteToHex = new(256);
internal static readonly List<string> ByteToHex = new(256);
static CustomMapperFallbackBuffer()
{
for (var i = 0; i < 256; i++)
byteToHex.Add(i.ToString("X2"));
ByteToHex.Add(i.ToString("X2"));
}
public CustomMapperFallbackBuffer()
@ -59,7 +59,7 @@ internal class Utf8ToLatin1RegexPatternEncoderFallback : EncoderFallback
for (var i = 0; i < count; i++)
{
ref var b = ref tmp[i];
var s = byteToHex[b];
var s = ByteToHex[b];
var offset = i * 4 + 1;
buffer[offset + 0] = (byte)'\\';
buffer[offset + 2] =(byte)s[0];
@ -81,7 +81,7 @@ internal class Utf8ToLatin1RegexPatternEncoderFallback : EncoderFallback
for (var i = 0; i < count; i++)
{
ref var b = ref tmp[i];
var s = byteToHex[b];
var s = ByteToHex[b];
var offset = i * 4 + 1;
buffer[offset + 0] = (byte)'\\';
buffer[offset + 2] =(byte)s[0];

View File

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

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
@ -26,9 +27,10 @@ public static class Normalizer
};
// as per https://www.unicode.org/reports/tr39/#Confusable_Detection
private static string ToSkeletonString(this string input)
[return: NotNullIfNotNull(nameof(input))]
private static string? ToSkeletonString(this string? input)
{
if (string.IsNullOrEmpty(input))
if (input is null or "")
return input;
// step 1: Convert X to NFD format, as described in [UAX15].
@ -39,9 +41,10 @@ public static class Normalizer
return input.Normalize(NormalizationForm.FormD);
}
public static string ToCanonicalForm(this string input)
[return: NotNullIfNotNull(nameof(input))]
public static string? ToCanonicalForm(this string? input)
{
if (string.IsNullOrEmpty(input))
if (input is null or "")
return input;
input = ToSkeletonString(input);

View File

@ -1,6 +1,7 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=6D9CA448_002D60C1_002D4D66_002D91D6_002DEC6C586508E6_002Fd_003ADatabase_002Fd_003AMigrations/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=ASMJIT/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Autosplit/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Bandicam/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=BCAS/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=BCES/@EntryIndexedValue">True</s:Boolean>
@ -16,11 +17,13 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=BLKS/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=BLUS/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Celeron/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=CHACHA/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=CJS7/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Confusables/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Cubeb/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=desync/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=EBOOT/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=ECDHE/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=edat/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=framerate/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=getllar/@EntryIndexedValue">True</s:Boolean>