mirror of
https://github.com/RPCS3/discord-bot.git
synced 2024-11-23 02:09:38 +00:00
update code with new language and api features
This commit is contained in:
parent
0d99046a0b
commit
d957936ede
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
};
|
||||
}
|
@ -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}");
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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")
|
||||
);
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
}
|
@ -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");
|
||||
}
|
@ -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));
|
||||
}
|
@ -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));
|
||||
}
|
@ -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;
|
||||
}
|
@ -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}");
|
||||
|
@ -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/>)");
|
||||
}
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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))
|
||||
|
@ -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**")
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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('-', ' ');
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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()}.
|
||||
""");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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++)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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 = "`")
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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))
|
||||
|
@ -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")}";
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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];
|
||||
|
8
ExternalAnnotations/DSharpPlus.CommandsNext.xml
Normal file
8
ExternalAnnotations/DSharpPlus.CommandsNext.xml
Normal 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>
|
@ -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);
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user