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()
|
static CirrusCi()
|
||||||
{
|
{
|
||||||
var collection = new ServiceCollection();
|
var collection = new ServiceCollection();
|
||||||
collection.AddClient(ExecutionStrategy.CacheAndNetwork)
|
collection.AddClient(ExecutionStrategy.CacheAndNetwork).ConfigureHttpClient(c => c.BaseAddress = new("https://api.cirrus-ci.com/graphql"));
|
||||||
.ConfigureHttpClient(c => c.BaseAddress = new("https://api.cirrus-ci.com/graphql"));
|
|
||||||
ServiceProvider = collection.BuildServiceProvider();
|
ServiceProvider = collection.BuildServiceProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,8 +90,8 @@ public static class ApiConfig
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Log.Fatal(e);
|
Log.Fatal(e);
|
||||||
ReverseDirections = new Dictionary<string, char>();
|
ReverseDirections = new();
|
||||||
ReverseReleaseTypes = new Dictionary<string, char>();
|
ReverseReleaseTypes = new();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -31,8 +31,7 @@ public class CompressionMessageHandler : DelegatingHandler
|
|||||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (isServer
|
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)
|
&& Compressors.FirstOrDefault(c => c.EncodingType.Equals(serverEncoding, StringComparison.OrdinalIgnoreCase)) is ICompressor serverDecompressor)
|
||||||
{
|
{
|
||||||
request.Content = new DecompressedContent(request.Content, 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)
|
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
if (reader.TokenType == JsonTokenType.Number
|
if (reader is not { TokenType: JsonTokenType.Number, HasValueSequence: false, ValueSpan: [(byte)'0'] })
|
||||||
&& !reader.HasValueSequence
|
return reader.GetString();
|
||||||
&& reader.ValueSpan.Length == 1
|
|
||||||
&& reader.ValueSpan[0] == (byte)'0')
|
_ = reader.GetInt32();
|
||||||
{
|
return null;
|
||||||
_ = reader.GetInt32();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return reader.GetString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
|
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
|
||||||
|
@ -18,15 +18,8 @@ public static class NamingStyles
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string Dashed(string value)
|
public static string Dashed(string value) => Delimitied(value, '-');
|
||||||
{
|
public static string Underscore(string value) => Delimitied(value, '_');
|
||||||
return Delimitied(value, '-');
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string Underscore(string value)
|
|
||||||
{
|
|
||||||
return Delimitied(value, '_');
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string Delimitied(string value, char separator)
|
private static string Delimitied(string value, char separator)
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
namespace CompatApiClient.Utils;
|
namespace CompatApiClient.Utils;
|
||||||
|
|
||||||
@ -7,7 +8,7 @@ public static class Statistics
|
|||||||
{
|
{
|
||||||
public static long Mean(this IEnumerable<long> data)
|
public static long Mean(this IEnumerable<long> data)
|
||||||
{
|
{
|
||||||
System.Numerics.BigInteger sum = 0;
|
BigInteger sum = 0;
|
||||||
var itemCount = 0;
|
var itemCount = 0;
|
||||||
foreach (var value in data)
|
foreach (var value in data)
|
||||||
{
|
{
|
||||||
@ -22,12 +23,12 @@ public static class Statistics
|
|||||||
|
|
||||||
public static double StdDev(this IEnumerable<long> data)
|
public static double StdDev(this IEnumerable<long> data)
|
||||||
{
|
{
|
||||||
System.Numerics.BigInteger σx = 0, σx2 = 0;
|
BigInteger σx = 0, σx2 = 0;
|
||||||
var n = 0;
|
var n = 0;
|
||||||
foreach (var value in data)
|
foreach (var value in data)
|
||||||
{
|
{
|
||||||
σx += value;
|
σx += value;
|
||||||
σx2 += (System.Numerics.BigInteger)value * value;
|
σx2 += (BigInteger)value * value;
|
||||||
n++;
|
n++;
|
||||||
}
|
}
|
||||||
if (n == 0)
|
if (n == 0)
|
||||||
|
@ -14,21 +14,15 @@ public static class UriExtensions
|
|||||||
public static NameValueCollection ParseQueryString(Uri uri)
|
public static NameValueCollection ParseQueryString(Uri uri)
|
||||||
{
|
{
|
||||||
if (!uri.IsAbsoluteUri)
|
if (!uri.IsAbsoluteUri)
|
||||||
uri = new Uri(FakeHost, uri);
|
uri = new(FakeHost, uri);
|
||||||
return uri.ParseQueryString();
|
return uri.ParseQueryString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string? GetQueryParameter(this Uri uri, string name)
|
public static string? GetQueryParameter(this Uri uri, string name)
|
||||||
{
|
=> ParseQueryString(uri)[name];
|
||||||
var parameters = ParseQueryString(uri);
|
|
||||||
return parameters[name];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Uri AddQueryParameter(this Uri uri, string name, string value)
|
public static Uri AddQueryParameter(this Uri uri, string name, string value)
|
||||||
{
|
=> AddQueryValue(uri, WebUtility.UrlEncode(name) + "=" + WebUtility.UrlEncode(value));
|
||||||
var queryValue = WebUtility.UrlEncode(name) + "=" + WebUtility.UrlEncode(value);
|
|
||||||
return AddQueryValue(uri, queryValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Uri AddQueryParameters(Uri uri, IEnumerable<KeyValuePair<string, string>> parameters)
|
public static Uri AddQueryParameters(Uri uri, IEnumerable<KeyValuePair<string, string>> parameters)
|
||||||
{
|
{
|
||||||
@ -105,16 +99,16 @@ public static class UriExtensions
|
|||||||
if (isAbsolute)
|
if (isAbsolute)
|
||||||
{
|
{
|
||||||
var builder = new UriBuilder(uri) { Query = value };
|
var builder = new UriBuilder(uri) { Query = value };
|
||||||
return new Uri(builder.ToString());
|
return new(builder.ToString());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var startWithSlash = uri.OriginalString.StartsWith("/");
|
var startWithSlash = uri.OriginalString.StartsWith("/");
|
||||||
uri = new Uri(FakeHost, uri);
|
uri = new(FakeHost, uri);
|
||||||
var builder = new UriBuilder(uri) { Query = value };
|
var builder = new UriBuilder(uri) { Query = value };
|
||||||
var additionalStrip = startWithSlash ? 0 : 1;
|
var additionalStrip = startWithSlash ? 0 : 1;
|
||||||
var newUri = builder.ToString()[(FakeHost.OriginalString.Length + additionalStrip)..];
|
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);
|
=> AsStorageUnit((long)bytes);
|
||||||
|
|
||||||
public static string AsStorageUnit(this long bytes)
|
public static string AsStorageUnit(this long bytes)
|
||||||
{
|
=> bytes switch
|
||||||
if (bytes < UnderKB)
|
{
|
||||||
return $"{bytes} byte{(bytes == 1 ? "" : "s")}";
|
< UnderKB => $"{bytes} byte{(bytes == 1 ? "" : "s")}",
|
||||||
if (bytes < UnderMB)
|
< UnderMB => $"{bytes / 1024.0:0.##} KB",
|
||||||
return $"{bytes / 1024.0:0.##} KB";
|
< UnderGB => $"{bytes / (1024.0 * 1024):0.##} MB",
|
||||||
if (bytes < UnderGB)
|
_ => $"{bytes / (1024.0 * 1024 * 1024):0.##} GB"
|
||||||
return $"{bytes / 1024.0 / 1024:0.##} MB";
|
};
|
||||||
return $"{bytes / 1024.0 / 1024 / 1024:0.##} GB";
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -4,12 +4,13 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CompatApiClient;
|
using CompatApiClient;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
using Octokit;
|
||||||
|
|
||||||
namespace GithubClient;
|
namespace GithubClient;
|
||||||
|
|
||||||
public class Client
|
public class Client
|
||||||
{
|
{
|
||||||
private readonly Octokit.GitHubClient client;
|
private readonly GitHubClient client;
|
||||||
|
|
||||||
private static readonly TimeSpan PrStatusCacheTime = TimeSpan.FromMinutes(3);
|
private static readonly TimeSpan PrStatusCacheTime = TimeSpan.FromMinutes(3);
|
||||||
private static readonly TimeSpan IssueStatusCacheTime = TimeSpan.FromMinutes(30);
|
private static readonly TimeSpan IssueStatusCacheTime = TimeSpan.FromMinutes(30);
|
||||||
@ -22,26 +23,22 @@ public class Client
|
|||||||
|
|
||||||
public Client(string? githubToken)
|
public Client(string? githubToken)
|
||||||
{
|
{
|
||||||
client = new Octokit.GitHubClient(new Octokit.ProductHeaderValue(ApiConfig.ProductName, ApiConfig.ProductVersion));
|
client = new(new ProductHeaderValue(ApiConfig.ProductName, ApiConfig.ProductVersion));
|
||||||
if (!string.IsNullOrEmpty(githubToken))
|
if (githubToken is {Length: >0})
|
||||||
{
|
client.Credentials = new(githubToken);
|
||||||
client.Credentials = new Octokit.Credentials(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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var request = client.PullRequest.Get("RPCS3", "rpcs3", pr);
|
result = await client.PullRequest.Get("RPCS3", "rpcs3", pr).WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
request.Wait(cancellationToken);
|
|
||||||
result = (await request.ConfigureAwait(false));
|
|
||||||
UpdateRateLimitStats();
|
UpdateRateLimitStats();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@ -50,61 +47,53 @@ public class Client
|
|||||||
}
|
}
|
||||||
if (result == null)
|
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);
|
return new(pr);
|
||||||
}
|
}
|
||||||
|
|
||||||
StatusesCache.Set(pr, result, PrStatusCacheTime);
|
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;
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var request = client.Issue.Get("RPCS3", "rpcs3", issue);
|
result = await client.Issue.Get("RPCS3", "rpcs3", issue).WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
request.Wait(cancellationToken);
|
|
||||||
result = (await request.ConfigureAwait(false));
|
|
||||||
UpdateRateLimitStats();
|
UpdateRateLimitStats();
|
||||||
|
IssuesCache.Set(issue, result, IssueStatusCacheTime);
|
||||||
|
ApiConfig.Log.Debug($"Cached {nameof(Issue)} for {issue} for {IssueStatusCacheTime}");
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
ApiConfig.Log.Error(e);
|
ApiConfig.Log.Error(e);
|
||||||
}
|
}
|
||||||
if (result == null)
|
ApiConfig.Log.Debug($"Failed to get {nameof(Issue)}, returning empty result");
|
||||||
{
|
return new();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
}, 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,
|
var statusUri = "https://api.github.com/repos/RPCS3/rpcs3/pulls?state=" + filter;
|
||||||
SortProperty = Octokit.PullRequestSort.Updated,
|
if (StatusesCache.TryGetValue(statusUri, out IReadOnlyList<PullRequest>? result))
|
||||||
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))
|
|
||||||
{
|
{
|
||||||
ApiConfig.Log.Debug("Returned list of opened PRs from cache");
|
ApiConfig.Log.Debug("Returned list of opened PRs from cache");
|
||||||
return result;
|
return result;
|
||||||
@ -112,23 +101,17 @@ public class Client
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var request = client.PullRequest.GetAllForRepository("RPCS3", "rpcs3", filter);
|
result = await client.PullRequest.GetAllForRepository("RPCS3", "rpcs3", filter).WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
request.Wait(cancellationToken);
|
|
||||||
|
|
||||||
result = (await request.ConfigureAwait(false));
|
|
||||||
UpdateRateLimitStats();
|
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)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
ApiConfig.Log.Error(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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,16 +119,12 @@ public class Client
|
|||||||
{
|
{
|
||||||
var apiInfo = client.GetLastApiInfo();
|
var apiInfo = client.GetLastApiInfo();
|
||||||
if (apiInfo == null)
|
if (apiInfo == null)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
RateLimit = apiInfo.RateLimit.Limit;
|
RateLimit = apiInfo.RateLimit.Limit;
|
||||||
RateLimitRemaining = apiInfo.RateLimit.Remaining;
|
RateLimitRemaining = apiInfo.RateLimit.Remaining;
|
||||||
RateLimitResetTime = DateTimeOffset.FromUnixTimeSeconds(apiInfo.RateLimit.ResetAsUtcEpochSeconds).UtcDateTime;
|
RateLimitResetTime = DateTimeOffset.FromUnixTimeSeconds(apiInfo.RateLimit.ResetAsUtcEpochSeconds).UtcDateTime;
|
||||||
|
|
||||||
if (RateLimitRemaining < 10)
|
if (RateLimitRemaining < 10)
|
||||||
ApiConfig.Log.Warn($"Github rate limit is low: {RateLimitRemaining} out of {RateLimit}, will be reset on {RateLimitResetTime:u}");
|
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()
|
public IrdClient()
|
||||||
{
|
{
|
||||||
client = HttpClientFactory.Create(new CompressionMessageHandler());
|
client = HttpClientFactory.Create(new CompressionMessageHandler());
|
||||||
jsonOptions = new JsonSerializerOptions
|
jsonOptions = new()
|
||||||
{
|
{
|
||||||
PropertyNamingPolicy = SpecialJsonNamingPolicy.SnakeCase,
|
PropertyNamingPolicy = SpecialJsonNamingPolicy.SnakeCase,
|
||||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||||
@ -209,9 +209,8 @@ public class IrdClient
|
|||||||
|
|
||||||
var idx = html.LastIndexOf("</span>", StringComparison.Ordinal);
|
var idx = html.LastIndexOf("</span>", StringComparison.Ordinal);
|
||||||
var result = html[(idx + 7)..].Trim();
|
var result = html[(idx + 7)..].Trim();
|
||||||
if (string.IsNullOrEmpty(result))
|
if (result is {Length: >0})
|
||||||
return null;
|
return result;
|
||||||
|
return null;
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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",
|
"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;
|
// 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+)",
|
private static readonly Regex FwVersionInfo = new(
|
||||||
RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.Singleline | RegexOptions.IgnoreCase);
|
@"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
|
// directly from vsh.self
|
||||||
private static readonly string[] KnownFwLocales = { "jp", "us", "eu", "kr", "uk", "mx", "au", "sa", "tw", "ru", "cn", "br", };
|
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()
|
public Client()
|
||||||
{
|
{
|
||||||
client = HttpClientFactory.Create(new CustomTlsCertificatesHandler(), new CompressionMessageHandler());
|
client = HttpClientFactory.Create(new CustomTlsCertificatesHandler(), new CompressionMessageHandler());
|
||||||
dashedJson = new JsonSerializerOptions
|
dashedJson = new()
|
||||||
{
|
{
|
||||||
PropertyNamingPolicy = SpecialJsonNamingPolicy.Dashed,
|
PropertyNamingPolicy = SpecialJsonNamingPolicy.Dashed,
|
||||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||||
IncludeFields = true,
|
IncludeFields = true,
|
||||||
};
|
};
|
||||||
snakeJson = new JsonSerializerOptions
|
snakeJson = new()
|
||||||
{
|
{
|
||||||
PropertyNamingPolicy = SpecialJsonNamingPolicy.SnakeCase,
|
PropertyNamingPolicy = SpecialJsonNamingPolicy.SnakeCase,
|
||||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||||
IncludeFields = true,
|
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
|
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);
|
message.Headers.Add("Cookie", sessionCookies);
|
||||||
response = await client.SendAsync(message, cancellationToken).ConfigureAwait(false);
|
response = await client.SendAsync(message, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||||
var tries = 0;
|
var tries = 0;
|
||||||
while (response.StatusCode == HttpStatusCode.Redirect && tries < 10 && !cancellationToken.IsCancellationRequested)
|
while (response.StatusCode == HttpStatusCode.Redirect && tries < 10 && !cancellationToken.IsCancellationRequested)
|
||||||
@ -116,7 +117,7 @@ public class Client
|
|||||||
tries++;
|
tries++;
|
||||||
}
|
}
|
||||||
if (response.StatusCode == HttpStatusCode.Redirect)
|
if (response.StatusCode == HttpStatusCode.Redirect)
|
||||||
return new List<string>(0);
|
return new(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
using (response)
|
using (response)
|
||||||
@ -127,7 +128,7 @@ public class Client
|
|||||||
var matches = ContainerIdLink.Matches(html);
|
var matches = ContainerIdLink.Matches(html);
|
||||||
var result = new List<string>();
|
var result = new List<string>();
|
||||||
foreach (Match m in matches)
|
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);
|
result.Add(id);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -344,7 +345,7 @@ public class Client
|
|||||||
|
|
||||||
allVersions = allVersions.OrderByDescending(fwi => fwi.Version).ToList();
|
allVersions = allVersions.OrderByDescending(fwi => fwi.Version).ToList();
|
||||||
if (allVersions.Count == 0)
|
if (allVersions.Count == 0)
|
||||||
return new List<FirmwareInfo>(0);
|
return new(0);
|
||||||
|
|
||||||
var maxFw = allVersions.First();
|
var maxFw = allVersions.First();
|
||||||
var result = allVersions.Where(fwi => fwi.Version == maxFw.Version).ToList();
|
var result = allVersions.Where(fwi => fwi.Version == maxFw.Version).ToList();
|
||||||
@ -372,7 +373,7 @@ public class Client
|
|||||||
{
|
{
|
||||||
["country_code"] = country,
|
["country_code"] = country,
|
||||||
["language_code"] = language,
|
["language_code"] = language,
|
||||||
}!)
|
})
|
||||||
};
|
};
|
||||||
using (authMessage)
|
using (authMessage)
|
||||||
using (response = await client.SendAsync(authMessage, cancellationToken).ConfigureAwait(false))
|
using (response = await client.SendAsync(authMessage, cancellationToken).ConfigureAwait(false))
|
||||||
@ -418,7 +419,7 @@ public class Client
|
|||||||
if (string.IsNullOrEmpty(data))
|
if (string.IsNullOrEmpty(data))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (FwVersionInfo.Match(data) is not Match m || !m.Success)
|
if (FwVersionInfo.Match(data) is not { Success: true } m)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var ver = m.Groups["version"].Value;
|
var ver = m.Groups["version"].Value;
|
||||||
@ -429,7 +430,7 @@ public class Client
|
|||||||
else
|
else
|
||||||
ver = ver[..4] + "." + ver[4..].TrimEnd('0'); //4.851 -> 4.85.1
|
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)
|
catch (Exception e)
|
||||||
|
@ -21,7 +21,7 @@ public sealed class Client
|
|||||||
public Client()
|
public Client()
|
||||||
{
|
{
|
||||||
client = HttpClientFactory.Create(new CompressionMessageHandler());
|
client = HttpClientFactory.Create(new CompressionMessageHandler());
|
||||||
jsonOptions = new JsonSerializerOptions
|
jsonOptions = new()
|
||||||
{
|
{
|
||||||
PropertyNamingPolicy = SpecialJsonNamingPolicy.SnakeCase,
|
PropertyNamingPolicy = SpecialJsonNamingPolicy.SnakeCase,
|
||||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||||
@ -36,7 +36,7 @@ public sealed class Client
|
|||||||
{
|
{
|
||||||
try
|
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()),
|
("public_key", publicUri.ToString()),
|
||||||
("fields", "size,name,file")
|
("fields", "size,name,file")
|
||||||
);
|
);
|
||||||
|
@ -23,8 +23,7 @@ internal class LimitedToSpamChannel: CheckBaseAttribute
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var msgList = await ctx.Channel.GetMessagesCachedAsync(10).ConfigureAwait(false);
|
var msgList = await ctx.Channel.GetMessagesCachedAsync(10).ConfigureAwait(false);
|
||||||
if (msgList.Any(m => m.Author.IsCurrent
|
if (msgList.Any(m => m is {Author.IsCurrent: true, Content: {Length: >0} s }
|
||||||
&& m.Content is string s
|
|
||||||
&& s.Contains(ctx.Command.QualifiedName, StringComparison.OrdinalIgnoreCase)))
|
&& s.Contains(ctx.Command.QualifiedName, StringComparison.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
await ctx.ReactWithAsync(Config.Reactions.Failure).ConfigureAwait(false);
|
await ctx.ReactWithAsync(Config.Reactions.Failure).ConfigureAwait(false);
|
||||||
|
@ -11,7 +11,5 @@ internal class RequiresBotModRole: CheckBaseAttributeWithReactions
|
|||||||
public RequiresBotModRole() : base(reactOnFailure: Config.Reactions.Denied) { }
|
public RequiresBotModRole() : base(reactOnFailure: Config.Reactions.Denied) { }
|
||||||
|
|
||||||
protected override Task<bool> IsAllowed(CommandContext ctx, bool help)
|
protected override Task<bool> IsAllowed(CommandContext ctx, bool help)
|
||||||
{
|
=> Task.FromResult(ModProvider.IsMod(ctx.User.Id));
|
||||||
return Task.FromResult(ModProvider.IsMod(ctx.User.Id));
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -9,7 +9,5 @@ namespace CompatBot.Commands.Attributes;
|
|||||||
internal class RequiresNotMedia: CheckBaseAttribute
|
internal class RequiresNotMedia: CheckBaseAttribute
|
||||||
{
|
{
|
||||||
public override Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help)
|
public override Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help)
|
||||||
{
|
=> Task.FromResult(ctx.Channel.Name != "media");
|
||||||
return Task.FromResult(ctx.Channel.Name != "media");
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -11,7 +11,5 @@ internal class RequiresSupporterRole: CheckBaseAttributeWithReactions
|
|||||||
public RequiresSupporterRole() : base(reactOnFailure: Config.Reactions.Denied) { }
|
public RequiresSupporterRole() : base(reactOnFailure: Config.Reactions.Denied) { }
|
||||||
|
|
||||||
protected override Task<bool> IsAllowed(CommandContext ctx, bool help)
|
protected override Task<bool> IsAllowed(CommandContext ctx, bool help)
|
||||||
{
|
=> Task.FromResult(ctx.User.IsWhitelisted(ctx.Client, ctx.Guild) || ctx.User.IsSupporter(ctx.Client, ctx.Guild));
|
||||||
return 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) { }
|
public RequiresWhitelistedRole() : base(reactOnFailure: Config.Reactions.Denied) { }
|
||||||
|
|
||||||
protected override Task<bool> IsAllowed(CommandContext ctx, bool help)
|
protected override Task<bool> IsAllowed(CommandContext ctx, bool help)
|
||||||
{
|
=> Task.FromResult(ctx.User.IsWhitelisted(ctx.Client, ctx.Guild));
|
||||||
return Task.FromResult(ctx.User.IsWhitelisted(ctx.Client, ctx.Guild));
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -9,7 +9,5 @@ internal class TriggersTyping: Attribute
|
|||||||
public bool InDmOnly { get; set; }
|
public bool InDmOnly { get; set; }
|
||||||
|
|
||||||
public bool ExecuteCheck(CommandContext ctx)
|
public bool ExecuteCheck(CommandContext ctx)
|
||||||
{
|
=> !InDmOnly || ctx.Channel.IsPrivate;
|
||||||
return !InDmOnly || ctx.Channel.IsPrivate;
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -15,8 +15,7 @@ internal class BaseApplicationCommandModuleCustom : ApplicationCommandModule
|
|||||||
public override async Task<bool> BeforeSlashExecutionAsync(InteractionContext ctx)
|
public override async Task<bool> BeforeSlashExecutionAsync(InteractionContext ctx)
|
||||||
{
|
{
|
||||||
executionStart = DateTimeOffset.UtcNow;
|
executionStart = DateTimeOffset.UtcNow;
|
||||||
|
if (ctx is {Channel.Name: "media", Interaction: { Type: InteractionType.ApplicationCommand, Data.Name: not ("warn" or "report") } })
|
||||||
if (ctx.Channel.Name == "media" && ctx.Interaction is { Type: InteractionType.ApplicationCommand, Data.Name: not ("warn" or "report") })
|
|
||||||
{
|
{
|
||||||
//todo: look what's available in data
|
//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}");
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = @"Something went wrong ¯\\_(ツ)\_/¯" + "\nMath is hard, yo";
|
var result = """
|
||||||
|
Something went wrong ¯\\\_(ツ)\_/¯
|
||||||
|
Math is hard, yo
|
||||||
|
""";
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var expr = new Expression(expression);
|
var expr = new Expression(expression);
|
||||||
@ -45,5 +48,5 @@ internal sealed class BotMath : BaseCommandModuleCustom
|
|||||||
[Command("help"), LimitedToSpamChannel, Cooldown(1, 5, CooldownBucketType.Channel)]
|
[Command("help"), LimitedToSpamChannel, Cooldown(1, 5, CooldownBucketType.Channel)]
|
||||||
[Description("General math expression help, or description of specific math word")]
|
[Description("General math expression help, or description of specific math word")]
|
||||||
public Task Help(CommandContext ctx)
|
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;
|
var osInfo = RuntimeInformation.OSDescription;
|
||||||
if (Environment.OSVersion.Platform is PlatformID.Unix or PlatformID.MacOSX)
|
if (Environment.OSVersion.Platform is PlatformID.Unix or PlatformID.MacOSX)
|
||||||
osInfo = RuntimeInformation.RuntimeIdentifier;
|
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)
|
embed.AddField("API Tokens", GetConfiguredApiStats(), true)
|
||||||
.AddField("Memory Usage", $"GC: {GC.GetGCMemoryInfo().HeapSizeBytes.AsStorageUnit()}/{GC.GetGCMemoryInfo().TotalAvailableMemoryBytes.AsStorageUnit()}\n" +
|
.AddField("Memory Usage", $"""
|
||||||
$"API pools: L: {ApiConfig.MemoryStreamManager.LargePoolInUseSize.AsStorageUnit()}/{(ApiConfig.MemoryStreamManager.LargePoolInUseSize + ApiConfig.MemoryStreamManager.LargePoolFreeSize).AsStorageUnit()}" +
|
GC: {gcMemInfo.HeapSizeBytes.AsStorageUnit()}/{gcMemInfo.TotalAvailableMemoryBytes.AsStorageUnit()}
|
||||||
$" S: {ApiConfig.MemoryStreamManager.SmallPoolInUseSize.AsStorageUnit()}/{(ApiConfig.MemoryStreamManager.SmallPoolInUseSize + ApiConfig.MemoryStreamManager.SmallPoolFreeSize).AsStorageUnit()}\n" +
|
API pools: L: {apiMsm.LargePoolInUseSize.AsStorageUnit()}/{apiLpsTotal.AsStorageUnit()} S: {apiMsm.SmallPoolInUseSize.AsStorageUnit()}/{apiSpsTotal.AsStorageUnit()}
|
||||||
$"Bot pools: L: {Config.MemoryStreamManager.LargePoolInUseSize.AsStorageUnit()}/{(Config.MemoryStreamManager.LargePoolInUseSize + Config.MemoryStreamManager.LargePoolFreeSize).AsStorageUnit()}" +
|
Bot pools: L: {botMsm.LargePoolInUseSize.AsStorageUnit()}/{botLpsTotal.AsStorageUnit()} S: {botMsm.SmallPoolInUseSize.AsStorageUnit()}/{botSpsTotal.AsStorageUnit()}
|
||||||
$" S: {Config.MemoryStreamManager.SmallPoolInUseSize.AsStorageUnit()}/{(Config.MemoryStreamManager.SmallPoolInUseSize + Config.MemoryStreamManager.SmallPoolFreeSize).AsStorageUnit()}", true)
|
""", 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("GitHub Rate Limit", $"""
|
||||||
.AddField(".NET Info", $"{RuntimeInformation.FrameworkDescription}\n" +
|
{GithubClient.Client.RateLimitRemaining} out of {GithubClient.Client.RateLimit} calls available
|
||||||
$"{(System.Runtime.GCSettings.IsServerGC ? "Server" : "Workstation")} GC Mode", true)
|
Reset in {(GithubClient.Client.RateLimitResetTime - DateTime.UtcNow).AsShortTimespan()}
|
||||||
.AddField("Runtime Info", $"Confinement: {SandboxDetector.Detect()}\n" +
|
""", true)
|
||||||
$"OS: {osInfo}\n" +
|
.AddField(".NET Info", $"""
|
||||||
$"CPUs: {Environment.ProcessorCount}\n" +
|
{RuntimeInformation.FrameworkDescription}
|
||||||
$"Time zones: {TimeParser.TimeZoneMap.Count} out of {TimeParser.TimeZoneAcronyms.Count} resolved, {TimeZoneInfo.GetSystemTimeZones().Count} total", true);
|
{(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);
|
AppendPiracyStats(embed);
|
||||||
AppendCmdStats(embed);
|
AppendCmdStats(embed);
|
||||||
AppendExplainStats(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);
|
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()
|
private static string GetConfiguredApiStats()
|
||||||
{
|
=> $"""
|
||||||
return new StringBuilder()
|
{(GoogleDriveHandler.ValidateCredentials() ? "✅" : "❌")} Google Drive
|
||||||
.Append(GoogleDriveHandler.ValidateCredentials() ? "✅" : "❌").AppendLine(" Google Drive")
|
{(string.IsNullOrEmpty(Config.AzureDevOpsToken) ? "❌" : "✅")} Azure DevOps
|
||||||
.Append(string.IsNullOrEmpty(Config.AzureDevOpsToken) ? "❌" : "✅").AppendLine(" Azure DevOps")
|
{(string.IsNullOrEmpty(Config.AzureComputerVisionKey) ? "❌" : "✅")} Computer Vision
|
||||||
.Append(string.IsNullOrEmpty(Config.AzureComputerVisionKey) ? "❌" : "✅").AppendLine(" Computer Vision")
|
{(string.IsNullOrEmpty(Config.AzureAppInsightsConnectionString) ? "❌" : "✅")} AppInsights
|
||||||
.Append(string.IsNullOrEmpty(Config.AzureAppInsightsConnectionString) ? "❌" : "✅").AppendLine(" AppInsights")
|
{(string.IsNullOrEmpty(Config.GithubToken) ? "❌" : "✅")} GitHub
|
||||||
.Append(string.IsNullOrEmpty(Config.GithubToken) ? "❌" : "✅").AppendLine(" Github")
|
""";
|
||||||
.ToString()
|
|
||||||
.Trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void AppendPiracyStats(DiscordEmbedBuilder embed)
|
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 totalFuncCount = db.SyscallInfo.AsNoTracking().Select(sci => sci.Function).Distinct().Count();
|
||||||
var fwCallCount = totalFuncCount - syscallCount;
|
var fwCallCount = totalFuncCount - syscallCount;
|
||||||
var gameCount = db.SyscallToProductMap.AsNoTracking().Select(m => m.ProductId).Distinct().Count();
|
var gameCount = db.SyscallToProductMap.AsNoTracking().Select(m => m.ProductId).Distinct().Count();
|
||||||
embed.AddField("SceCall Stats",
|
embed.AddField("SceCall Stats", $"""
|
||||||
$"Tracked game IDs: {gameCount}\n" +
|
Tracked game IDs: {gameCount}
|
||||||
$"Tracked syscalls: {syscallCount} function{(syscallCount == 1 ? "" : "s")}\n" +
|
Tracked syscalls: {syscallCount} function{(syscallCount == 1 ? "" : "s")}
|
||||||
$"Tracked fw calls: {fwCallCount} function{(fwCallCount == 1 ? "" : "s")}\n",
|
Tracked fw calls: {fwCallCount} function{(fwCallCount == 1 ? "" : "s")}
|
||||||
true);
|
""", true);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@ -258,15 +269,12 @@ internal sealed class BotStats: BaseCommandModuleCustom
|
|||||||
.OrderByDescending(s => s.count)
|
.OrderByDescending(s => s.count)
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|
||||||
var cpuInfo = "";
|
var cpuInfo = cpu is null ? "" : $"Popular CPU: {cpu.maker} {cpu.name} ({cpu.count * 100.0 / monthCount:0.##}%)";
|
||||||
if (cpu is not null)
|
embed.AddField("Hardware Stats", $"""
|
||||||
cpuInfo = $"\nPopular CPU: {cpu.maker} {cpu.name} ({cpu.count*100.0/monthCount:0.##}%)";
|
Total: {totalCount} system{(totalCount == 1 ? "" : "s")}
|
||||||
embed.AddField("Hardware Stats",
|
Last 30 days: {monthCount} system{(monthCount == 1 ? "" : "s")}
|
||||||
$"Total: {totalCount} system{(totalCount == 1 ? "" : "s")}\n" +
|
{cpuInfo}
|
||||||
$"Last 30 days: {monthCount} system{(monthCount == 1 ? "" : "s")}" +
|
""".TrimEnd(), true);
|
||||||
cpuInfo,
|
|
||||||
true);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -22,7 +22,10 @@ public sealed class CommandsManagement : BaseCommandModule
|
|||||||
var list = DisabledCommandsProvider.Get();
|
var list = DisabledCommandsProvider.Get();
|
||||||
if (list.Count > 0)
|
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)
|
foreach (var cmd in list)
|
||||||
result.AppendLine(cmd);
|
result.AppendLine(cmd);
|
||||||
await ctx.SendAutosplitMessageAsync(result.Append("```")).ConfigureAwait(false);
|
await ctx.SendAutosplitMessageAsync(result.Append("```")).ConfigureAwait(false);
|
||||||
|
@ -41,7 +41,10 @@ internal sealed class CompatList : BaseCommandModuleCustom
|
|||||||
private const string Rpcs3UpdateStateKey = "Rpcs3UpdateState";
|
private const string Rpcs3UpdateStateKey = "Rpcs3UpdateState";
|
||||||
private const string Rpcs3UpdateBuildKey = "Rpcs3UpdateBuild";
|
private const string Rpcs3UpdateBuildKey = "Rpcs3UpdateBuild";
|
||||||
private static UpdateInfo? cachedUpdateInfo;
|
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()
|
static CompatList()
|
||||||
{
|
{
|
||||||
@ -153,7 +156,7 @@ internal sealed class CompatList : BaseCommandModuleCustom
|
|||||||
if (exactStatus)
|
if (exactStatus)
|
||||||
result.Append(" only");
|
result.Append(" only");
|
||||||
result.Append(" games");
|
result.Append(" games");
|
||||||
if (scoreType == "critic" || scoreType == "user")
|
if (scoreType is "critic" or "user")
|
||||||
result.Append($" according to {scoreType}s");
|
result.Append($" according to {scoreType}s");
|
||||||
result.AppendLine(":");
|
result.AppendLine(":");
|
||||||
foreach (var (title, score, _) in resultList)
|
foreach (var (title, score, _) in resultList)
|
||||||
@ -170,17 +173,12 @@ internal sealed class CompatList : BaseCommandModuleCustom
|
|||||||
public sealed class UpdatesCheck: BaseCommandModuleCustom
|
public sealed class UpdatesCheck: BaseCommandModuleCustom
|
||||||
{
|
{
|
||||||
[GroupCommand]
|
[GroupCommand]
|
||||||
public Task Latest(CommandContext ctx)
|
public Task Latest(CommandContext ctx) => CheckForRpcs3Updates(ctx.Client, ctx.Channel);
|
||||||
{
|
|
||||||
return CheckForRpcs3Updates(ctx.Client, ctx.Channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Command("since")]
|
[Command("since")]
|
||||||
[Description("Show additional info about changes since specified update")]
|
[Description("Show additional info about changes since specified update")]
|
||||||
public Task Since(CommandContext ctx, [Description("Commit hash of the update, such as `46abe0f31`")] string commit)
|
public Task Since(CommandContext ctx, [Description("Commit hash of the update, such as `46abe0f31`")] string commit)
|
||||||
{
|
=> CheckForRpcs3Updates(ctx.Client, ctx.Channel, commit);
|
||||||
return CheckForRpcs3Updates(ctx.Client, ctx.Channel, commit);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Command("clear"), RequiresBotModRole]
|
[Command("clear"), RequiresBotModRole]
|
||||||
[Description("Clears update info cache that is used to post new build announcements")]
|
[Description("Clears update info cache that is used to post new build announcements")]
|
||||||
@ -345,7 +343,7 @@ internal sealed class CompatList : BaseCommandModuleCustom
|
|||||||
{
|
{
|
||||||
var updateInfo = await Client.GetUpdateAsync(cancellationToken, mergedPr.MergeCommitSha).ConfigureAwait(false)
|
var updateInfo = await Client.GetUpdateAsync(cancellationToken, mergedPr.MergeCommitSha).ConfigureAwait(false)
|
||||||
?? new UpdateInfo {ReturnCode = -1};
|
?? 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.LatestBuild = updateInfo.CurrentBuild;
|
||||||
updateInfo.CurrentBuild = null;
|
updateInfo.CurrentBuild = null;
|
||||||
@ -503,7 +501,7 @@ internal sealed class CompatList : BaseCommandModuleCustom
|
|||||||
if (trimmedList.Count > 0)
|
if (trimmedList.Count > 0)
|
||||||
sortedList = trimmedList;
|
sortedList = trimmedList;
|
||||||
|
|
||||||
var searchTerm = request.Search ?? @"¯\_(ツ)_/¯";
|
var searchTerm = request.Search ?? @"¯\\\_(ツ)\_/¯";
|
||||||
var searchHits = sortedList.Where(t => t.score > 0.5
|
var searchHits = sortedList.Where(t => t.score > 0.5
|
||||||
|| (t.info.Title?.StartsWith(searchTerm, StringComparison.InvariantCultureIgnoreCase) ?? false)
|
|| (t.info.Title?.StartsWith(searchTerm, StringComparison.InvariantCultureIgnoreCase) ?? false)
|
||||||
|| (t.info.AlternativeTitle?.StartsWith(searchTerm, StringComparison.InvariantCultureIgnoreCase) ?? false));
|
|| (t.info.AlternativeTitle?.StartsWith(searchTerm, StringComparison.InvariantCultureIgnoreCase) ?? false));
|
||||||
@ -684,8 +682,6 @@ internal sealed class CompatList : BaseCommandModuleCustom
|
|||||||
ProductCode = productCode,
|
ProductCode = productCode,
|
||||||
Name = titleInfo.Title,
|
Name = titleInfo.Title,
|
||||||
}).ConfigureAwait(false)).Entity;
|
}).ConfigureAwait(false)).Entity;
|
||||||
if (dbItem is null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
dbItem.Name = titleInfo.Title;
|
dbItem.Name = titleInfo.Title;
|
||||||
if (Enum.TryParse(titleInfo.Status, out CompatStatus status))
|
if (Enum.TryParse(titleInfo.Status, out CompatStatus status))
|
||||||
|
@ -406,10 +406,10 @@ internal sealed class ContentFilters: BaseCommandModuleCustom
|
|||||||
step1:
|
step1:
|
||||||
// step 1: define trigger string
|
// step 1: define trigger string
|
||||||
var embed = FormatFilter(filter, errorMsg, 1)
|
var embed = FormatFilter(filter, errorMsg, 1)
|
||||||
.WithDescription(
|
.WithDescription("""
|
||||||
"Any simple string that is used to flag potential content for a check using Validation regex.\n" +
|
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."
|
**Must** be sufficiently long to reduce the number of checks.
|
||||||
);
|
""");
|
||||||
saveEdit.SetEnabled(filter.IsComplete());
|
saveEdit.SetEnabled(filter.IsComplete());
|
||||||
var messageBuilder = new DiscordMessageBuilder()
|
var messageBuilder = new DiscordMessageBuilder()
|
||||||
.WithContent("Please specify a new **trigger**")
|
.WithContent("Please specify a new **trigger**")
|
||||||
@ -453,12 +453,12 @@ internal sealed class ContentFilters: BaseCommandModuleCustom
|
|||||||
step2:
|
step2:
|
||||||
// step 2: context of the filter where it is applicable
|
// step 2: context of the filter where it is applicable
|
||||||
embed = FormatFilter(filter, errorMsg, 2)
|
embed = FormatFilter(filter, errorMsg, 2)
|
||||||
.WithDescription(
|
.WithDescription($"""
|
||||||
"Context of the filter indicates where it is applicable.\n" +
|
Context of the filter indicates where it is applicable.
|
||||||
$"**`C`** = **`{FilterContext.Chat}`** will apply it in filtering discord messages.\n" +
|
**`C`** = **`{FilterContext.Chat}`** will apply it in filtering discord messages.
|
||||||
$"**`L`** = **`{FilterContext.Log}`** will apply it during log parsing.\n" +
|
**`L`** = **`{FilterContext.Log}`** will apply it during log parsing.
|
||||||
"Reactions will toggle the context, text message will set the specified flags."
|
Reactions will toggle the context, text message will set the specified flags.
|
||||||
);
|
""");
|
||||||
saveEdit.SetEnabled(filter.IsComplete());
|
saveEdit.SetEnabled(filter.IsComplete());
|
||||||
contextChat.SetEmoji(filter.Context.HasFlag(FilterContext.Chat) ? minus : plus);
|
contextChat.SetEmoji(filter.Context.HasFlag(FilterContext.Chat) ? minus : plus);
|
||||||
contextLog.SetEmoji(filter.Context.HasFlag(FilterContext.Log) ? minus : plus);
|
contextLog.SetEmoji(filter.Context.HasFlag(FilterContext.Log) ? minus : plus);
|
||||||
@ -529,16 +529,16 @@ internal sealed class ContentFilters: BaseCommandModuleCustom
|
|||||||
step3:
|
step3:
|
||||||
// step 3: actions that should be performed on match
|
// step 3: actions that should be performed on match
|
||||||
embed = FormatFilter(filter, errorMsg, 3)
|
embed = FormatFilter(filter, errorMsg, 3)
|
||||||
.WithDescription(
|
.WithDescription($"""
|
||||||
"Actions that will be executed on positive match.\n" +
|
Actions that will be executed on positive match.
|
||||||
$"**`R`** = **`{FilterAction.RemoveContent}`** will remove the message / log.\n" +
|
**`R`** = **`{FilterAction.RemoveContent}`** will remove the message / log.
|
||||||
$"**`W`** = **`{FilterAction.IssueWarning}`** will issue a warning to the user.\n" +
|
**`W`** = **`{FilterAction.IssueWarning}`** will issue a warning to the user.
|
||||||
$"**`M`** = **`{FilterAction.SendMessage}`** send _a_ message with an explanation of why it was removed.\n" +
|
**`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**).\n" +
|
**`E`** = **`{FilterAction.ShowExplain}`** show `explain` for the specified term (**not implemented**).
|
||||||
$"**`U`** = **`{FilterAction.MuteModQueue}`** mute mod queue reporting for this action.\n" +
|
**`U`** = **`{FilterAction.MuteModQueue}`** mute mod queue reporting for this action.
|
||||||
$"**`K`** = **`{FilterAction.Kick}`** kick user from server.\n" +
|
**`K`** = **`{FilterAction.Kick}`** kick user from server.
|
||||||
"Buttons will toggle the action, text message will set the specified flags."
|
Buttons will toggle the action, text message will set the specified flags.
|
||||||
);
|
""");
|
||||||
actionR.SetEmoji(filter.Actions.HasFlag(FilterAction.RemoveContent) ? minus : plus);
|
actionR.SetEmoji(filter.Actions.HasFlag(FilterAction.RemoveContent) ? minus : plus);
|
||||||
actionW.SetEmoji(filter.Actions.HasFlag(FilterAction.IssueWarning) ? minus : plus);
|
actionW.SetEmoji(filter.Actions.HasFlag(FilterAction.IssueWarning) ? minus : plus);
|
||||||
actionM.SetEmoji(filter.Actions.HasFlag(FilterAction.SendMessage) ? minus : plus);
|
actionM.SetEmoji(filter.Actions.HasFlag(FilterAction.SendMessage) ? minus : plus);
|
||||||
@ -666,11 +666,11 @@ internal sealed class ContentFilters: BaseCommandModuleCustom
|
|||||||
step4:
|
step4:
|
||||||
// step 4: validation regex to filter out false positives of the plaintext triggers
|
// step 4: validation regex to filter out false positives of the plaintext triggers
|
||||||
embed = FormatFilter(filter, errorMsg, 4)
|
embed = FormatFilter(filter, errorMsg, 4)
|
||||||
.WithDescription(
|
.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" +
|
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.\n" +
|
**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."
|
Additional validation can help reduce false positives of a plaintext trigger match.
|
||||||
);
|
""");
|
||||||
var next = (filter.Actions & (FilterAction.SendMessage | FilterAction.ShowExplain)) == 0 ? firstPage : nextPage;
|
var next = (filter.Actions & (FilterAction.SendMessage | FilterAction.ShowExplain)) == 0 ? firstPage : nextPage;
|
||||||
trash.SetDisabled(string.IsNullOrEmpty(filter.ValidatingRegex));
|
trash.SetDisabled(string.IsNullOrEmpty(filter.ValidatingRegex));
|
||||||
saveEdit.SetEnabled(filter.IsComplete());
|
saveEdit.SetEnabled(filter.IsComplete());
|
||||||
@ -775,10 +775,10 @@ internal sealed class ContentFilters: BaseCommandModuleCustom
|
|||||||
step6:
|
step6:
|
||||||
// step 6: show explanation for the term
|
// step 6: show explanation for the term
|
||||||
embed = FormatFilter(filter, errorMsg, 6)
|
embed = FormatFilter(filter, errorMsg, 6)
|
||||||
.WithDescription(
|
.WithDescription("""
|
||||||
"Explanation term that is used to show an explanation.\n" +
|
Explanation term that is used to show an explanation.
|
||||||
"**__Currently not implemented__**."
|
**__Currently not implemented__**.
|
||||||
);
|
""");
|
||||||
saveEdit.SetEnabled(filter.IsComplete());
|
saveEdit.SetEnabled(filter.IsComplete());
|
||||||
messageBuilder = new DiscordMessageBuilder()
|
messageBuilder = new DiscordMessageBuilder()
|
||||||
.WithContent("Please specify filter **explanation term**")
|
.WithContent("Please specify filter **explanation term**")
|
||||||
|
@ -25,12 +25,12 @@ internal sealed class DevOnly : BaseCommandModuleCustom
|
|||||||
#pragma warning disable 8321
|
#pragma warning disable 8321
|
||||||
static void addRandomStuff(DiscordEmbedBuilder emb)
|
static void addRandomStuff(DiscordEmbedBuilder emb)
|
||||||
{
|
{
|
||||||
var txt = "😾 lasjdf wqoieyr osdf `Vreoh Sdab` wohe `270`\n" +
|
emb.AddField("Random section", """
|
||||||
"🤔 salfhiosfhsero hskfh shufwei oufhwehw e wkihrwe h\n" +
|
😾 lasjdf wqoieyr osdf `Vreoh Sdab` wohe `270`
|
||||||
"ℹ️ sakfjas f hs `ASfhewighehw safds` asfw\n" +
|
🤔 salfhiosfhsero hskfh shufwei oufhwehw e wkihrwe h
|
||||||
"🔮 ¯\\\\\\_(ツ)\\_/¯";
|
ℹ️ sakfjas f hs `ASfhewighehw safds` asfw
|
||||||
|
🔮 ¯\\\_(ツ)\_/¯
|
||||||
emb.AddField("Random section", txt, false);
|
""", false);
|
||||||
}
|
}
|
||||||
#pragma warning restore 8321
|
#pragma warning restore 8321
|
||||||
var embed = new DiscordEmbedBuilder()
|
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";
|
var noEventMsg = $"No information about the upcoming {eventName?.Sanitize(replaceBackTicks: true)} at the moment";
|
||||||
if (eventName?.Length > 10)
|
if (eventName?.Length > 10)
|
||||||
noEventMsg = "No information about such event at the moment";
|
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.";
|
noEventMsg = $"Haha, very funny, {ctx.User.Mention}. So original. Never saw this joke before.";
|
||||||
promo = null;
|
promo = null;
|
||||||
@ -114,7 +114,7 @@ internal class EventsBaseCommand: BaseCommandModuleCustom
|
|||||||
var noEventMsg = $"No information about the upcoming {eventName?.Sanitize(replaceBackTicks: true)} at the moment";
|
var noEventMsg = $"No information about the upcoming {eventName?.Sanitize(replaceBackTicks: true)} at the moment";
|
||||||
if (eventName?.Length > 10)
|
if (eventName?.Length > 10)
|
||||||
noEventMsg = "No information about such event at the moment";
|
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.";
|
noEventMsg = $"Haha, very funny, {ctx.User.Mention}. So original. Never saw this joke before.";
|
||||||
promo = null;
|
promo = null;
|
||||||
@ -301,7 +301,10 @@ internal class EventsBaseCommand: BaseCommandModuleCustom
|
|||||||
saveEdit.SetEnabled(evt.IsComplete());
|
saveEdit.SetEnabled(evt.IsComplete());
|
||||||
messageBuilder = new DiscordMessageBuilder()
|
messageBuilder = new DiscordMessageBuilder()
|
||||||
.WithContent("Please specify a new **start date and time**")
|
.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(lastPage, nextPage)
|
||||||
.AddComponents(saveEdit, abort);
|
.AddComponents(saveEdit, abort);
|
||||||
errorMsg = null;
|
errorMsg = null;
|
||||||
|
@ -67,7 +67,7 @@ internal sealed class Explain: BaseCommandModuleCustom
|
|||||||
var hasMention = false;
|
var hasMention = false;
|
||||||
term = term.ToLowerInvariant();
|
term = term.ToLowerInvariant();
|
||||||
var result = await LookupTerm(term).ConfigureAwait(false);
|
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();
|
term = term.StripQuotes();
|
||||||
var idx = term.LastIndexOf(" to ", StringComparison.Ordinal);
|
var idx = term.LastIndexOf(" to ", StringComparison.Ordinal);
|
||||||
@ -116,7 +116,7 @@ internal sealed class Explain: BaseCommandModuleCustom
|
|||||||
term = term.ToLowerInvariant().StripQuotes();
|
term = term.ToLowerInvariant().StripQuotes();
|
||||||
byte[]? attachment = null;
|
byte[]? attachment = null;
|
||||||
string? attachmentFilename = null;
|
string? attachmentFilename = null;
|
||||||
if (ctx.Message.Attachments.FirstOrDefault() is DiscordAttachment att)
|
if (ctx.Message.Attachments is [DiscordAttachment att, ..])
|
||||||
{
|
{
|
||||||
attachmentFilename = att.FileName;
|
attachmentFilename = att.FileName;
|
||||||
try
|
try
|
||||||
@ -165,7 +165,7 @@ internal sealed class Explain: BaseCommandModuleCustom
|
|||||||
term = term.ToLowerInvariant().StripQuotes();
|
term = term.ToLowerInvariant().StripQuotes();
|
||||||
byte[]? attachment = null;
|
byte[]? attachment = null;
|
||||||
string? attachmentFilename = null;
|
string? attachmentFilename = null;
|
||||||
if (ctx.Message.Attachments.FirstOrDefault() is DiscordAttachment att)
|
if (ctx.Message.Attachments is [DiscordAttachment att, ..])
|
||||||
{
|
{
|
||||||
attachmentFilename = att.FileName;
|
attachmentFilename = att.FileName;
|
||||||
try
|
try
|
||||||
@ -346,12 +346,12 @@ internal sealed class Explain: BaseCommandModuleCustom
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(item.Text))
|
if (item is { Text.Length: > 0 })
|
||||||
{
|
{
|
||||||
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(item.Text));
|
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(item.Text));
|
||||||
await ctx.Channel.SendMessageAsync(new DiscordMessageBuilder().AddFile($"{termOrLink}.txt", stream)).ConfigureAwait(false);
|
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 using var stream = new MemoryStream(item.Attachment);
|
||||||
await ctx.Channel.SendMessageAsync(new DiscordMessageBuilder().AddFile(item.AttachmentFilename, stream)).ConfigureAwait(false);
|
await ctx.Channel.SendMessageAsync(new DiscordMessageBuilder().AddFile(item.AttachmentFilename, stream)).ConfigureAwait(false);
|
||||||
|
@ -71,9 +71,8 @@ internal sealed class ForcedNicknames : BaseCommandModuleCustom
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (enforceRules.Nickname == expectedNickname)
|
if (enforceRules.Nickname == expectedNickname)
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
enforceRules.Nickname = expectedNickname;
|
enforceRules.Nickname = expectedNickname;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -187,7 +186,7 @@ internal sealed class ForcedNicknames : BaseCommandModuleCustom
|
|||||||
var hex = BitConverter.ToString(nameBytes).Replace('-', ' ');
|
var hex = BitConverter.ToString(nameBytes).Replace('-', ' ');
|
||||||
var result = $"User ID: {discordUser.Id}\nUsername: {hex}";
|
var result = $"User ID: {discordUser.Id}\nUsername: {hex}";
|
||||||
var member = ctx.Client.GetMember(ctx.Guild, discordUser);
|
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);
|
nameBytes = StringUtils.Utf8.GetBytes(nickname);
|
||||||
hex = BitConverter.ToString(nameBytes).Replace('-', ' ');
|
hex = BitConverter.ToString(nameBytes).Replace('-', ' ');
|
||||||
|
@ -46,20 +46,20 @@ internal sealed class Fortune : BaseCommandModuleCustom
|
|||||||
fortune = await db.Fortune.AsNoTracking().Skip(selectedId).FirstOrDefaultAsync().ConfigureAwait(false);
|
fortune = await db.Fortune.AsNoTracking().Skip(selectedId).FirstOrDefaultAsync().ConfigureAwait(false);
|
||||||
} while (fortune is null);
|
} while (fortune is null);
|
||||||
|
|
||||||
var msg = fortune.Content.FixTypography();
|
|
||||||
var msgParts = msg.Split('\n');
|
|
||||||
var tmp = new StringBuilder();
|
var tmp = new StringBuilder();
|
||||||
var quote = true;
|
var quote = true;
|
||||||
foreach (var l in msgParts)
|
foreach (var l in fortune.Content.FixTypography().Split('\n'))
|
||||||
{
|
{
|
||||||
quote &= !l.StartsWith(" ");
|
quote &= !l.StartsWith(" ");
|
||||||
if (quote)
|
if (quote)
|
||||||
tmp.Append("> ");
|
tmp.Append("> ");
|
||||||
tmp.Append(l).Append('\n');
|
tmp.Append(l).Append('\n');
|
||||||
}
|
}
|
||||||
msg = tmp.ToString().TrimEnd().FixSpaces();
|
|
||||||
var msgBuilder = new DiscordMessageBuilder()
|
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);
|
.WithReply(message.Id);
|
||||||
await message.Channel.SendMessageAsync(msgBuilder).ConfigureAwait(false);
|
await message.Channel.SendMessageAsync(msgBuilder).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@ -136,7 +136,7 @@ internal sealed class Fortune : BaseCommandModuleCustom
|
|||||||
|| buf.Length > 0)
|
|| buf.Length > 0)
|
||||||
&& !Config.Cts.IsCancellationRequested)
|
&& !Config.Cts.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
if (line == "%" || line is null)
|
if (line is "%" or null)
|
||||||
{
|
{
|
||||||
var content = buf.ToString().Replace("\r\n", "\n").Trim();
|
var content = buf.ToString().Replace("\r\n", "\n").Trim();
|
||||||
if (content.Length > 1900)
|
if (content.Length > 1900)
|
||||||
@ -187,7 +187,7 @@ internal sealed class Fortune : BaseCommandModuleCustom
|
|||||||
var progressMsg = $"Imported {count} fortune{(count == 1 ? "" : "s")}";
|
var progressMsg = $"Imported {count} fortune{(count == 1 ? "" : "s")}";
|
||||||
if (skipped > 0)
|
if (skipped > 0)
|
||||||
progressMsg += $", skipped {skipped}";
|
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.##}%)";
|
progressMsg += $" ({stream.Position * 100.0 / len:0.##}%)";
|
||||||
await msg.UpdateOrCreateMessageAsync(ctx.Channel, progressMsg).ConfigureAwait(false);
|
await msg.UpdateOrCreateMessageAsync(ctx.Channel, progressMsg).ConfigureAwait(false);
|
||||||
stopwatch.Restart();
|
stopwatch.Restart();
|
||||||
@ -246,7 +246,7 @@ internal sealed class Fortune : BaseCommandModuleCustom
|
|||||||
[Description("Clears fortune database. Use with caution")]
|
[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)
|
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);
|
await ctx.ReactWithAsync(Config.Reactions.Failure).ConfigureAwait(false);
|
||||||
return;
|
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 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 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 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)[]
|
var ramStats = new (int Count, string Mem)[]
|
||||||
{
|
{
|
||||||
(lowRam, "less than 4 GB"),
|
(lowRam, "less than 4 GB"),
|
||||||
(ram4to6, "4 to 6 GB"),
|
(ram4to6, "4 to 6 GB"),
|
||||||
(ram6to8, "6 to 8 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)
|
.Where(i => i.Count > 0)
|
||||||
.Take(top)
|
.Take(top)
|
||||||
|
@ -164,8 +164,8 @@ internal sealed class Misc: BaseCommandModuleCustom
|
|||||||
if (!int.TryParse(m.Groups["num"].Value, out var num))
|
if (!int.TryParse(m.Groups["num"].Value, out var num))
|
||||||
num = 1;
|
num = 1;
|
||||||
if (int.TryParse(m.Groups["face"].Value, out var face)
|
if (int.TryParse(m.Groups["face"].Value, out var face)
|
||||||
&& 0 < num && num < 101
|
&& num is > 0 and < 101
|
||||||
&& 1 < face && face < 1001)
|
&& face is > 1 and < 1001)
|
||||||
{
|
{
|
||||||
List<int> rolls;
|
List<int> rolls;
|
||||||
lock (rng) rolls = Enumerable.Range(0, num).Select(_ => rng.Next(face) + 1).ToList();
|
lock (rng) rolls = Enumerable.Range(0, num).Select(_ => rng.Next(face) + 1).ToList();
|
||||||
@ -312,7 +312,7 @@ internal sealed class Misc: BaseCommandModuleCustom
|
|||||||
{
|
{
|
||||||
try
|
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 choices = RateAnswers;
|
||||||
var choiceFlags = new HashSet<char>();
|
var choiceFlags = new HashSet<char>();
|
||||||
whatever = whatever.ToLowerInvariant().StripInvisibleAndDiacritics();
|
whatever = whatever.ToLowerInvariant().StripInvisibleAndDiacritics();
|
||||||
|
@ -51,7 +51,7 @@ internal sealed partial class Moderation
|
|||||||
await using var memoryStream = Config.MemoryStreamManager.GetStream();
|
await using var memoryStream = Config.MemoryStreamManager.GetStream();
|
||||||
await using var writer = new StreamWriter(memoryStream, new UTF8Encoding(false), 4096, true);
|
await using var writer = new StreamWriter(memoryStream, new UTF8Encoding(false), 4096, true);
|
||||||
foreach (var member in members)
|
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);
|
await writer.FlushAsync().ConfigureAwait(false);
|
||||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||||
if (memoryStream.Length <= ctx.GetAttachmentSizeLimit())
|
if (memoryStream.Length <= ctx.GetAttachmentSizeLimit())
|
||||||
@ -192,11 +192,13 @@ internal sealed partial class Moderation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
#if DEBUG
|
||||||
[Command("locales"), Aliases("locale", "languages", "language", "lang", "loc")]
|
[Command("locales"), Aliases("locale", "languages", "language", "lang", "loc")]
|
||||||
public async Task UserLocales(CommandContext ctx)
|
public async Task UserLocales(CommandContext ctx)
|
||||||
{
|
{
|
||||||
|
#pragma warning disable VSTHRD103
|
||||||
if (!CheckLock.Wait(0))
|
if (!CheckLock.Wait(0))
|
||||||
|
#pragma warning restore VSTHRD103
|
||||||
{
|
{
|
||||||
await ctx.Channel.SendMessageAsync("Another check is already in progress").ConfigureAwait(false);
|
await ctx.Channel.SendMessageAsync("Another check is already in progress").ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
@ -236,7 +238,7 @@ internal sealed partial class Moderation
|
|||||||
await ctx.RemoveReactionAsync(Config.Reactions.PleaseWait).ConfigureAwait(false);
|
await ctx.RemoveReactionAsync(Config.Reactions.PleaseWait).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
#endif
|
||||||
|
|
||||||
private static List<DiscordMember> GetMembers(DiscordClient client)
|
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;
|
var avgBuildTime = (await azureClient.GetPipelineDurationAsync(Config.Cts.Token).ConfigureAwait(false)).Mean;
|
||||||
if (now < mergeTime + avgBuildTime)
|
if (now < mergeTime + avgBuildTime)
|
||||||
waitTime = mergeTime + avgBuildTime - now;
|
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",
|
Title = "RPCS3 Compatibility Bot",
|
||||||
Url = "https://github.com/RPCS3/discord-bot",
|
Url = "https://github.com/RPCS3/discord-bot",
|
||||||
Color = DiscordColor.Purple,
|
Color = DiscordColor.Purple,
|
||||||
}.AddField("Made by",
|
}.AddField("Made by", $"""
|
||||||
$"""
|
|
||||||
💮 13xforever
|
💮 13xforever
|
||||||
🇭🇷 Roberto Anić Banić aka nicba1010
|
🇭🇷 Roberto Anić Banić aka nicba1010
|
||||||
{clienthax} clienthax
|
{clienthax} clienthax
|
||||||
"""
|
"""
|
||||||
)
|
).AddField("People who ~~broke~~ helped test the bot", $"""
|
||||||
.AddField("People who ~~broke~~ helped test the bot",
|
|
||||||
$"""
|
|
||||||
🐱 Juhn
|
🐱 Juhn
|
||||||
{hcorion} hcorion
|
{hcorion} hcorion
|
||||||
🙃 TGE
|
🙃 TGE
|
||||||
🍒 Maru
|
🍒 Maru
|
||||||
♋ Tourghool
|
♋ Tourghool
|
||||||
"""
|
"""
|
||||||
)
|
).WithFooter($"Running {Config.GitRevision}");
|
||||||
.WithFooter($"Running {Config.GitRevision}");
|
|
||||||
await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().AddEmbed(embed.Build()).AsEphemeral());
|
await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().AddEmbed(embed.Build()).AsEphemeral());
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -31,9 +31,9 @@ internal partial class Sudo
|
|||||||
foreach (var v in setVars)
|
foreach (var v in setVars)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#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
|
#else
|
||||||
result.AppendLine(v.Key![(SqlConfiguration.ConfigVarPrefix.Length)..]);
|
result.AppendLine(v.Key[(SqlConfiguration.ConfigVarPrefix.Length)..]);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
await ctx.Channel.SendMessageAsync(result.ToString()).ConfigureAwait(false);
|
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);
|
var stateValue = await db.BotState.FirstOrDefaultAsync(v => v.Key == key).ConfigureAwait(false);
|
||||||
if (stateValue == null)
|
if (stateValue == null)
|
||||||
{
|
{
|
||||||
stateValue = new BotState {Key = key, Value = value};
|
stateValue = new() {Key = key, Value = value};
|
||||||
await db.BotState.AddAsync(stateValue).ConfigureAwait(false);
|
await db.BotState.AddAsync(stateValue).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -30,7 +30,7 @@ internal partial class Sudo
|
|||||||
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Checking for dotnet updates...").ConfigureAwait(false);
|
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Checking for dotnet updates...").ConfigureAwait(false);
|
||||||
var (updated, stdout) = await UpdateAsync(version).ConfigureAwait(false);
|
var (updated, stdout) = await UpdateAsync(version).ConfigureAwait(false);
|
||||||
if (!string.IsNullOrEmpty(stdout))
|
if (!string.IsNullOrEmpty(stdout))
|
||||||
await ctx.SendAutosplitMessageAsync("```" + stdout + "```").ConfigureAwait(false);
|
await ctx.SendAutosplitMessageAsync($"```{stdout}```").ConfigureAwait(false);
|
||||||
if (!updated)
|
if (!updated)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -21,8 +21,10 @@ internal partial class Sudo
|
|||||||
if (await ModProvider.AddAsync(user.Id).ConfigureAwait(false))
|
if (await ModProvider.AddAsync(user.Id).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
await ctx.ReactWithAsync(Config.Reactions.Success,
|
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);
|
).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -36,7 +38,7 @@ internal partial class Sudo
|
|||||||
if (ctx.Client.CurrentApplication.Owners.Any(u => u.Id == user.Id))
|
if (ctx.Client.CurrentApplication.Owners.Any(u => u.Id == user.Id))
|
||||||
{
|
{
|
||||||
var dm = await user.CreateDmChannelAsync().ConfigureAwait(false);
|
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);
|
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))
|
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)
|
public async Task Say(CommandContext ctx, [RemainingText, Description("Message text to send")] string message)
|
||||||
{
|
{
|
||||||
var msgParts = message.Split(' ', 2, StringSplitOptions.TrimEntries);
|
var msgParts = message.Split(' ', 2, StringSplitOptions.TrimEntries);
|
||||||
|
|
||||||
var channel = ctx.Channel;
|
var channel = ctx.Channel;
|
||||||
DiscordMessage? ogMsg = null;
|
DiscordMessage? ogMsg = null;
|
||||||
if (msgParts.Length > 1)
|
if (msgParts.Length > 1)
|
||||||
|
@ -117,7 +117,7 @@ internal sealed class Vision: BaseCommandModuleCustom
|
|||||||
//resize and shrink file size to get under azure limits
|
//resize and shrink file size to get under azure limits
|
||||||
var quality = 90;
|
var quality = 90;
|
||||||
var resized = false;
|
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,}));
|
img.Mutate(i => i.Resize(new ResizeOptions {Size = new(3840, 2160), Mode = ResizeMode.Min,}));
|
||||||
resized = true;
|
resized = true;
|
||||||
|
@ -52,7 +52,6 @@ internal sealed partial class Warnings: BaseCommandModuleCustom
|
|||||||
var interact = ctx.Client.GetInteractivity();
|
var interact = ctx.Client.GetInteractivity();
|
||||||
await using var db = new BotDb();
|
await using var db = new BotDb();
|
||||||
var warnings = await db.Warning.Where(w => id.Equals(w.Id)).ToListAsync().ConfigureAwait(false);
|
var warnings = await db.Warning.Where(w => id.Equals(w.Id)).ToListAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
if (warnings.Count == 0)
|
if (warnings.Count == 0)
|
||||||
{
|
{
|
||||||
await ctx.ReactWithAsync(Config.Reactions.Denied, $"{ctx.Message.Author.Mention} Warn not found", true);
|
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();
|
var warningToEdit = warnings.First();
|
||||||
|
|
||||||
if (warningToEdit.IssuerId != ctx.User.Id)
|
if (warningToEdit.IssuerId != ctx.User.Id)
|
||||||
{
|
{
|
||||||
await ctx.ReactWithAsync(Config.Reactions.Denied, $"{ctx.Message.Author.Mention} This warn wasn't issued by you :(", true);
|
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;
|
warningToEdit.Reason = response.Result.Content;
|
||||||
|
|
||||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||||
await ctx.Channel.SendMessageAsync($"Warning successfully edited!").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();
|
await using var db = new BotDb();
|
||||||
var warn = await db.Warning.FirstOrDefaultAsync(w => w.Id == id).ConfigureAwait(false);
|
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.Retracted = false;
|
||||||
warn.RetractedBy = null;
|
warn.RetractedBy = null;
|
||||||
|
@ -27,7 +27,7 @@ internal class BotDb: DbContext
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
optionsBuilder.UseLoggerFactory(Config.LoggerFactory);
|
optionsBuilder.UseLoggerFactory(Config.LoggerFactory);
|
||||||
#endif
|
#endif
|
||||||
optionsBuilder.UseSqlite($"Data Source=\"{dbPath}\"");
|
optionsBuilder.UseSqlite($""" Data Source="{dbPath}" """);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
|
@ -18,20 +18,20 @@ public static class DbImporter
|
|||||||
public static async Task<bool> UpgradeAsync(CancellationToken cancellationToken)
|
public static async Task<bool> UpgradeAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await using (var db = new BotDb())
|
await using (var db = new BotDb())
|
||||||
if (!await UpgradeAsync(db, Config.Cts.Token))
|
if (!await UpgradeAsync(db, cancellationToken).ConfigureAwait(false))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
await using (var db = new ThumbnailDb())
|
await using (var db = new ThumbnailDb())
|
||||||
{
|
{
|
||||||
if (!await UpgradeAsync(db, Config.Cts.Token))
|
if (!await UpgradeAsync(db,cancellationToken).ConfigureAwait(false))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!await ImportNamesPool(db, Config.Cts.Token))
|
if (!await ImportNamesPool(db, cancellationToken).ConfigureAwait(false))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
await using (var db = new HardwareDb())
|
await using (var db = new HardwareDb())
|
||||||
if (!await UpgradeAsync(db, Config.Cts.Token))
|
if (!await UpgradeAsync(db, cancellationToken).ConfigureAwait(false))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -15,7 +15,7 @@ internal class HardwareDb : DbContext
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
optionsBuilder.UseLoggerFactory(Config.LoggerFactory);
|
optionsBuilder.UseLoggerFactory(Config.LoggerFactory);
|
||||||
#endif
|
#endif
|
||||||
optionsBuilder.UseSqlite($"Data Source=\"{dbPath}\"");
|
optionsBuilder.UseSqlite($""" Data Source="{dbPath}" """);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
|
@ -276,8 +276,7 @@ internal static class ContentFilter
|
|||||||
public static string? GetMatchedScope(Piracystring trigger, string? context)
|
public static string? GetMatchedScope(Piracystring trigger, string? context)
|
||||||
=> context is { Length: >0 }
|
=> context is { Length: >0 }
|
||||||
&& trigger.ValidatingRegex is { Length: >0 } pattern
|
&& trigger.ValidatingRegex is { Length: >0 } pattern
|
||||||
&& Regex.Match(context, pattern, RegexOptions.IgnoreCase | RegexOptions.Multiline) is { Success: true } m
|
&& Regex.Match(context, pattern, RegexOptions.IgnoreCase | RegexOptions.Multiline) is { Success: true, Groups.Count: > 0 } m
|
||||||
&& m.Groups.Count > 0
|
|
||||||
? m.Groups[0].Value.Trim(256)
|
? m.Groups[0].Value.Trim(256)
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
@ -26,7 +26,7 @@ internal static class DisabledCommandsProvider
|
|||||||
if (DisabledCommands.Add(command))
|
if (DisabledCommands.Add(command))
|
||||||
{
|
{
|
||||||
using var db = new BotDb();
|
using var db = new BotDb();
|
||||||
db.DisabledCommands.Add(new DisabledCommand {Command = command});
|
db.DisabledCommands.Add(new() {Command = command});
|
||||||
db.SaveChanges();
|
db.SaveChanges();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@ internal static class ModProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsMod(ulong userId) => Moderators.ContainsKey(userId);
|
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 bool IsSudoer(ulong userId) => Moderators.TryGetValue(userId, out var mod) && mod.Sudoer;
|
||||||
|
|
||||||
public static async Task<bool> AddAsync(ulong userId)
|
public static async Task<bool> AddAsync(ulong userId)
|
||||||
@ -34,6 +33,7 @@ internal static class ModProvider
|
|||||||
{
|
{
|
||||||
if (IsMod(userId))
|
if (IsMod(userId))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Moderators[userId] = newMod;
|
Moderators[userId] = newMod;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -20,7 +20,7 @@ internal static class ScrapeStateProvider
|
|||||||
var id = GetId(locale, containerId);
|
var id = GetId(locale, containerId);
|
||||||
using var db = new ThumbnailDb();
|
using var db = new ThumbnailDb();
|
||||||
var timestamp = string.IsNullOrEmpty(id) ? db.State.OrderBy(s => s.Timestamp).FirstOrDefault() : db.State.FirstOrDefault(s => s.Locale == id);
|
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 IsFresh(new DateTime(checkDate, DateTimeKind.Utc));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -29,7 +29,7 @@ internal static class ScrapeStateProvider
|
|||||||
{
|
{
|
||||||
using var db = new ThumbnailDb();
|
using var db = new ThumbnailDb();
|
||||||
var timestamp = string.IsNullOrEmpty(locale) ? db.State.OrderBy(s => s.Timestamp).FirstOrDefault() : db.State.FirstOrDefault(s => s.Locale == locale);
|
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 new DateTime(checkDate, DateTimeKind.Utc) > dataTimestamp;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -25,13 +25,13 @@ internal static class ThumbnailProvider
|
|||||||
|
|
||||||
productCode = productCode.ToUpperInvariant();
|
productCode = productCode.ToUpperInvariant();
|
||||||
var tmdbInfo = await PsnClient.GetTitleMetaAsync(productCode, Config.Cts.Token).ConfigureAwait(false);
|
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;
|
return tmdbIconUrl;
|
||||||
|
|
||||||
await using var db = new ThumbnailDb();
|
await using var db = new ThumbnailDb();
|
||||||
var thumb = await db.Thumbnail.FirstOrDefaultAsync(t => t.ProductCode == productCode).ConfigureAwait(false);
|
var thumb = await db.Thumbnail.FirstOrDefaultAsync(t => t.ProductCode == productCode).ConfigureAwait(false);
|
||||||
//todo: add search task if not found
|
//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;
|
return embeddableUrl;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(thumb?.Url) || !ScrapeStateProvider.IsFresh(thumb.Timestamp))
|
if (string.IsNullOrEmpty(thumb?.Url) || !ScrapeStateProvider.IsFresh(thumb.Timestamp))
|
||||||
@ -40,7 +40,7 @@ internal static class ThumbnailProvider
|
|||||||
if (!string.IsNullOrEmpty(gameTdbCoverUrl))
|
if (!string.IsNullOrEmpty(gameTdbCoverUrl))
|
||||||
{
|
{
|
||||||
if (thumb is null)
|
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
|
else
|
||||||
thumb.Url = gameTdbCoverUrl;
|
thumb.Url = gameTdbCoverUrl;
|
||||||
thumb.Timestamp = DateTime.UtcNow.Ticks;
|
thumb.Timestamp = DateTime.UtcNow.Ticks;
|
||||||
@ -81,7 +81,7 @@ internal static class ThumbnailProvider
|
|||||||
if (!string.IsNullOrEmpty(title))
|
if (!string.IsNullOrEmpty(title))
|
||||||
{
|
{
|
||||||
if (thumb == null)
|
if (thumb == null)
|
||||||
await db.Thumbnail.AddAsync(new Thumbnail
|
await db.Thumbnail.AddAsync(new()
|
||||||
{
|
{
|
||||||
ProductCode = productCode,
|
ProductCode = productCode,
|
||||||
Name = title,
|
Name = title,
|
||||||
@ -107,7 +107,7 @@ internal static class ThumbnailProvider
|
|||||||
contentId = contentId.ToUpperInvariant();
|
contentId = contentId.ToUpperInvariant();
|
||||||
await using var db = new ThumbnailDb();
|
await using var db = new ThumbnailDb();
|
||||||
var info = await db.Thumbnail.FirstOrDefaultAsync(ti => ti.ContentId == contentId, Config.Cts.Token).ConfigureAwait(false);
|
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)
|
if (info.Url is null)
|
||||||
return (null, defaultColor);
|
return (null, defaultColor);
|
||||||
|
|
||||||
@ -139,7 +139,7 @@ internal static class ThumbnailProvider
|
|||||||
await db.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false);
|
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);
|
return (info.EmbeddableUrl, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ public static class TitleUpdateInfoProvider
|
|||||||
|
|
||||||
productId = productId.ToUpper();
|
productId = productId.ToUpper();
|
||||||
var (update, xml) = await Client.GetTitleUpdatesAsync(productId, cancellationToken).ConfigureAwait(false);
|
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();
|
var xmlChecksum = xml.GetStableHash();
|
||||||
await using var db = new ThumbnailDb();
|
await using var db = new ThumbnailDb();
|
||||||
@ -38,7 +38,7 @@ public static class TitleUpdateInfoProvider
|
|||||||
updateInfo.Timestamp = DateTime.UtcNow.Ticks;
|
updateInfo.Timestamp = DateTime.UtcNow.Ticks;
|
||||||
await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
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();
|
await using var db = new ThumbnailDb();
|
||||||
var updateInfo = db.GameUpdateInfo.FirstOrDefault(ui => ui.ProductCode == productId);
|
var updateInfo = db.GameUpdateInfo.FirstOrDefault(ui => ui.ProductCode == productId);
|
||||||
@ -50,8 +50,9 @@ public static class TitleUpdateInfoProvider
|
|||||||
update = (TitlePatch?)xmlSerializer.Deserialize(memStream);
|
update = (TitlePatch?)xmlSerializer.Deserialize(memStream);
|
||||||
if (update is not null)
|
if (update is not null)
|
||||||
update.OfflineCacheTimestamp = updateInfo.Timestamp.AsUtc();
|
update.OfflineCacheTimestamp = updateInfo.Timestamp.AsUtc();
|
||||||
|
|
||||||
|
return update;
|
||||||
}
|
}
|
||||||
|
|
||||||
return update;
|
return update;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +66,7 @@ public static class TitleUpdateInfoProvider
|
|||||||
{
|
{
|
||||||
var updateInfo = db.GameUpdateInfo.AsNoTracking().FirstOrDefault(ui => ui.ProductCode == titleId);
|
var updateInfo = db.GameUpdateInfo.AsNoTracking().FirstOrDefault(ui => ui.ProductCode == titleId);
|
||||||
if (!cancellationToken.IsCancellationRequested
|
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 GetAsync(titleId, cancellationToken).ConfigureAwait(false);
|
||||||
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
|
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
|
||||||
|
@ -23,7 +23,7 @@ internal class ThumbnailDb : DbContext
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
optionsBuilder.UseLoggerFactory(Config.LoggerFactory);
|
optionsBuilder.UseLoggerFactory(Config.LoggerFactory);
|
||||||
#endif
|
#endif
|
||||||
optionsBuilder.UseSqlite($"Data Source=\"{dbPath}\"");
|
optionsBuilder.UseSqlite($""" Data Source="{dbPath}" """);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
|
@ -132,7 +132,7 @@ namespace CompatBot.EventHandlers
|
|||||||
await using var db = new BotDb();
|
await using var db = new BotDb();
|
||||||
var matchedGroups = (from m in mc
|
var matchedGroups = (from m in mc
|
||||||
from Group g in m.Groups
|
from Group g in m.Groups
|
||||||
where g.Success && !string.IsNullOrEmpty(g.Value)
|
where g is { Success: true, Value.Length: > 0 }
|
||||||
select g.Name
|
select g.Name
|
||||||
).Distinct()
|
).Distinct()
|
||||||
.ToArray();
|
.ToArray();
|
||||||
@ -193,7 +193,7 @@ namespace CompatBot.EventHandlers
|
|||||||
await msg.DeleteAsync("asked to shut up").ConfigureAwait(false);
|
await msg.DeleteAsync("asked to shut up").ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
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)
|
if (attachmentContent?.Count > 0)
|
||||||
foreach (var f in attachmentContent.Values)
|
foreach (var f in attachmentContent.Values)
|
||||||
#pragma warning disable VSTHRD103
|
await f.DisposeAsync();
|
||||||
f.Dispose();
|
|
||||||
#pragma warning restore VSTHRD103
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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)
|
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))
|
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 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();
|
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)
|
if (inviteCodes.Count == 0 && discordMeLinks.Count == 0 && !tryMessageAsACode)
|
||||||
return (false, attemptedWorkaround, new List<DiscordInvite>(0));
|
return (false, attemptedWorkaround, new(0));
|
||||||
|
|
||||||
var hasInvalidInvites = false;
|
var hasInvalidInvites = false;
|
||||||
foreach (var meLink in discordMeLinks)
|
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 handler = new HttpClientHandler {AllowAutoRedirect = false}; // needed to store cloudflare session cookies
|
||||||
using var httpClient = HttpClientFactory.Create(handler, new CompressionMessageHandler());
|
using var httpClient = HttpClientFactory.Create(handler, new CompressionMessageHandler());
|
||||||
using var request = new HttpRequestMessage(HttpMethod.Get, "https://discord.me/" + meLink);
|
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.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);
|
using var response = await httpClient.SendAsync(request).ConfigureAwait(false);
|
||||||
var html = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
var html = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||||
if (response.IsSuccessStatusCode)
|
if (response.IsSuccessStatusCode)
|
||||||
@ -244,8 +244,8 @@ internal static class DiscordInviteFilter
|
|||||||
["serverEid"] = serverEidMatch.Groups["server_eid"].Value,
|
["serverEid"] = serverEidMatch.Groups["server_eid"].Value,
|
||||||
}!),
|
}!),
|
||||||
};
|
};
|
||||||
postRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/html"));
|
postRequest.Headers.Accept.Add(new("text/html"));
|
||||||
postRequest.Headers.UserAgent.Add(new ProductInfoHeaderValue("RPCS3CompatibilityBot", "2.0"));
|
postRequest.Headers.UserAgent.Add(new("RPCS3CompatibilityBot", "2.0"));
|
||||||
using var postResponse = await httpClient.SendAsync(postRequest).ConfigureAwait(false);
|
using var postResponse = await httpClient.SendAsync(postRequest).ConfigureAwait(false);
|
||||||
if (postResponse.StatusCode == HttpStatusCode.Redirect)
|
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);
|
using var getDiscordRequest = new HttpRequestMessage(HttpMethod.Get, "https://discord.me/server/join/redirect/" + redirectId);
|
||||||
getDiscordRequest.Headers.CacheControl = CacheControlHeaderValue.Parse("no-cache");
|
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);
|
using var discordRedirect = await httpClient.SendAsync(getDiscordRequest).ConfigureAwait(false);
|
||||||
if (discordRedirect.StatusCode == HttpStatusCode.Redirect)
|
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_2"].Value))
|
||||||
.Concat(matches.Select(m => m.Groups["game_title_3"].Value))
|
.Concat(matches.Select(m => m.Groups["game_title_3"].Value))
|
||||||
.FirstOrDefault(t => !string.IsNullOrEmpty(t));
|
.FirstOrDefault(t => !string.IsNullOrEmpty(t));
|
||||||
if (string.IsNullOrEmpty(gameTitle) || gameTitle.Length < 2)
|
if (gameTitle is null or { Length: < 2 })
|
||||||
return;
|
return;
|
||||||
|
|
||||||
gameTitle = CompatList.FixGameTitleSearch(gameTitle);
|
gameTitle = CompatList.FixGameTitleSearch(gameTitle);
|
||||||
@ -89,7 +89,7 @@ internal static class IsTheGamePlayableHandler
|
|||||||
if (!string.IsNullOrEmpty(info.Date))
|
if (!string.IsNullOrEmpty(info.Date))
|
||||||
msg += $" since {info.ToUpdated()}";
|
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);
|
await args.Channel.SendMessageAsync(msg).ConfigureAwait(false);
|
||||||
CooldownBuckets[args.Channel.Id] = DateTime.UtcNow;
|
CooldownBuckets[args.Channel.Id] = DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ internal sealed class GzipHandler: IArchiveHandler
|
|||||||
{
|
{
|
||||||
if (header.Length >= Header.Length)
|
if (header.Length >= Header.Length)
|
||||||
{
|
{
|
||||||
if (header.Slice(0, Header.Length).SequenceEqual(Header))
|
if (header[..Header.Length].SequenceEqual(Header))
|
||||||
return (true, null);
|
return (true, null);
|
||||||
}
|
}
|
||||||
else if (fileName.EndsWith(".log.gz", StringComparison.InvariantCultureIgnoreCase)
|
else if (fileName.EndsWith(".log.gz", StringComparison.InvariantCultureIgnoreCase)
|
||||||
|
@ -18,7 +18,7 @@ internal sealed class PlainTextHandler: IArchiveHandler
|
|||||||
if (fileName.Contains("tty.log", StringComparison.InvariantCultureIgnoreCase))
|
if (fileName.Contains("tty.log", StringComparison.InvariantCultureIgnoreCase))
|
||||||
return (false, null);
|
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 (true, null);
|
||||||
|
|
||||||
return (false, 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)
|
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))
|
|| header.Length == 0 && fileName.EndsWith(".rar", StringComparison.InvariantCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
var firstEntry = Encoding.ASCII.GetString(header);
|
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)
|
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))
|
|| header.Length == 0 && fileName.EndsWith(".7z", StringComparison.InvariantCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
if (fileSize > Config.AttachmentSizeLimit)
|
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)
|
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))
|
|| header.Length == 0 && fileName.EndsWith(".zip", StringComparison.InvariantCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
var firstEntry = Encoding.ASCII.GetString(header);
|
var firstEntry = Encoding.ASCII.GetString(header);
|
||||||
|
@ -13,7 +13,6 @@ namespace CompatBot.EventHandlers.LogParsing;
|
|||||||
internal static partial class LogParser
|
internal static partial class LogParser
|
||||||
{
|
{
|
||||||
private static readonly byte[] Bom = {0xEF, 0xBB, 0xBF};
|
private static readonly byte[] Bom = {0xEF, 0xBB, 0xBF};
|
||||||
|
|
||||||
private static readonly PoorMansTaskScheduler<LogParseState> TaskScheduler = new();
|
private static readonly PoorMansTaskScheduler<LogParseState> TaskScheduler = new();
|
||||||
|
|
||||||
public static async Task<LogParseState> ReadPipeAsync(PipeReader reader, CancellationToken cancellationToken)
|
public static async Task<LogParseState> ReadPipeAsync(PipeReader reader, CancellationToken cancellationToken)
|
||||||
|
@ -60,8 +60,7 @@ internal partial class LogParser
|
|||||||
if (trigger == "{PPU[" || trigger == "⁂")
|
if (trigger == "{PPU[" || trigger == "⁂")
|
||||||
{
|
{
|
||||||
if (state.WipCollection["serial"] is string serial
|
if (state.WipCollection["serial"] is string serial
|
||||||
&& extractor.Match(buffer) is Match match
|
&& extractor.Match(buffer) is { Success: true } match
|
||||||
&& match.Success
|
|
||||||
&& match.Groups["syscall_name"].Value is string syscallName)
|
&& match.Groups["syscall_name"].Value is string syscallName)
|
||||||
{
|
{
|
||||||
lock (state)
|
lock (state)
|
||||||
@ -81,8 +80,7 @@ internal partial class LogParser
|
|||||||
foreach (Match match in matches)
|
foreach (Match match in matches)
|
||||||
foreach (Group group in match.Groups)
|
foreach (Group group in match.Groups)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(group.Name)
|
if (group.Name is null or "" or "0"
|
||||||
|| group.Name == "0"
|
|
||||||
|| string.IsNullOrWhiteSpace(group.Value))
|
|| string.IsNullOrWhiteSpace(group.Value))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
@ -30,51 +30,50 @@ internal sealed class DropboxHandler : BaseSourceHandler
|
|||||||
using var client = HttpClientFactory.Create();
|
using var client = HttpClientFactory.Create();
|
||||||
foreach (Match m in matches)
|
foreach (Match m in matches)
|
||||||
{
|
{
|
||||||
if (m.Groups["dropbox_link"].Value is string lnk
|
if (m.Groups["dropbox_link"].Value is not { Length: > 0 } lnk
|
||||||
&& !string.IsNullOrEmpty(lnk)
|
|| !Uri.TryCreate(lnk, UriKind.Absolute, out var uri))
|
||||||
&& 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
|
try
|
||||||
{
|
{
|
||||||
uri = uri.SetQueryParameter("dl", "1");
|
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
|
||||||
var filename = Path.GetFileName(lnk);
|
foreach (var handler in handlers)
|
||||||
var filesize = -1;
|
|
||||||
|
|
||||||
using (var request = new HttpRequestMessage(HttpMethod.Head, uri))
|
|
||||||
{
|
{
|
||||||
using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Config.Cts.Token);
|
var (canHandle, reason) = handler.CanHandle(filename, filesize, buf.AsSpan(0, read));
|
||||||
if (response.Content.Headers.ContentLength > 0)
|
if (canHandle)
|
||||||
filesize = (int)response.Content.Headers.ContentLength.Value;
|
return (new DropboxSource(uri, handler, filename, filesize), null);
|
||||||
if (response.Content.Headers.ContentDisposition?.FileNameStar is string fname && !string.IsNullOrEmpty(fname))
|
else if (!string.IsNullOrEmpty(reason))
|
||||||
filename = fname;
|
return (null, reason);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
catch (Exception e)
|
|
||||||
{
|
{
|
||||||
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);
|
return (null, null);
|
||||||
}
|
}
|
||||||
|
@ -28,51 +28,50 @@ internal sealed class GenericLinkHandler : BaseSourceHandler
|
|||||||
using var client = HttpClientFactory.Create();
|
using var client = HttpClientFactory.Create();
|
||||||
foreach (Match m in matches)
|
foreach (Match m in matches)
|
||||||
{
|
{
|
||||||
if (m.Groups["link"].Value is string lnk
|
if (m.Groups["link"].Value is not { Length: > 0 } lnk
|
||||||
&& !string.IsNullOrEmpty(lnk)
|
|| !Uri.TryCreate(lnk, UriKind.Absolute, out var uri)
|
||||||
&& Uri.TryCreate(lnk, UriKind.Absolute, out var uri)
|
|| "tty.log".Equals(m.Groups["filename"].Value, StringComparison.InvariantCultureIgnoreCase))
|
||||||
&& !"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
|
try
|
||||||
{
|
{
|
||||||
var host = uri.Host;
|
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
|
||||||
var filename = Path.GetFileName(lnk);
|
foreach (var handler in handlers)
|
||||||
var filesize = -1;
|
|
||||||
|
|
||||||
using (var request = new HttpRequestMessage(HttpMethod.Head, uri))
|
|
||||||
{
|
{
|
||||||
using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Config.Cts.Token);
|
var (canHandle, reason) = handler.CanHandle(filename, filesize, buf.AsSpan(0, read));
|
||||||
if (response.Content.Headers.ContentLength > 0)
|
if (canHandle)
|
||||||
filesize = (int)response.Content.Headers.ContentLength.Value;
|
return (new GenericSource(uri, handler, host, filename, filesize), null);
|
||||||
if (response.Content.Headers.ContentDisposition?.FileNameStar is string fname && !string.IsNullOrEmpty(fname))
|
else if (!string.IsNullOrEmpty(reason))
|
||||||
filename = fname;
|
return (null, reason);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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);
|
return (null, null);
|
||||||
}
|
}
|
||||||
|
@ -39,42 +39,38 @@ internal sealed class GoogleDriveHandler: BaseSourceHandler
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (m.Groups["gdrive_id"].Value is string fid
|
if (m.Groups["gdrive_id"].Value is not { Length: > 0 } fid)
|
||||||
&& !string.IsNullOrEmpty(fid))
|
continue;
|
||||||
|
|
||||||
|
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
|
||||||
{
|
{
|
||||||
var fileInfoRequest = client.Files.Get(fid);
|
await using var stream = new MemoryStream(buf, true);
|
||||||
fileInfoRequest.Fields = "name, size, kind";
|
var limit = Math.Min(SnoopBufferSize, fileMeta.Size.Value) - 1;
|
||||||
var fileMeta = await fileInfoRequest.ExecuteAsync(Config.Cts.Token).ConfigureAwait(false);
|
var progress = await fileInfoRequest.DownloadRangeAsync(stream, new RangeHeaderValue(0, limit), Config.Cts.Token).ConfigureAwait(false);
|
||||||
if (fileMeta.Kind == "drive#file" && fileMeta.Size > 0)
|
if (progress.Status != DownloadStatus.Completed)
|
||||||
{
|
continue;
|
||||||
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;
|
|
||||||
|
|
||||||
read = (int)progress.BytesDownloaded;
|
var read = (int)progress.BytesDownloaded;
|
||||||
}
|
foreach (var handler in handlers)
|
||||||
foreach (var handler in handlers)
|
{
|
||||||
{
|
var (canHandle, reason) = handler.CanHandle(fileMeta.Name, (int)fileMeta.Size, buf.AsSpan(0, read));
|
||||||
var (canHandle, reason) = handler.CanHandle(fileMeta.Name, (int)fileMeta.Size, buf.AsSpan(0, read));
|
if (canHandle)
|
||||||
if (canHandle)
|
return (new GoogleDriveSource(client, fileInfoRequest, fileMeta, handler), null);
|
||||||
return (new GoogleDriveSource(client, fileInfoRequest, fileMeta, handler), null);
|
else if (!string.IsNullOrEmpty(reason))
|
||||||
else if (!string.IsNullOrEmpty(reason))
|
return(null, reason);
|
||||||
return(null, reason);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
BufferPool.Return(buf);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
BufferPool.Return(buf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -31,56 +31,55 @@ internal sealed class MediafireHandler : BaseSourceHandler
|
|||||||
using var client = HttpClientFactory.Create();
|
using var client = HttpClientFactory.Create();
|
||||||
foreach (Match m in matches)
|
foreach (Match m in matches)
|
||||||
{
|
{
|
||||||
if (m.Groups["mediafire_link"].Value is string lnk
|
if (m.Groups["mediafire_link"].Value is not { Length: > 0 } lnk
|
||||||
&& !string.IsNullOrEmpty(lnk)
|
|| !Uri.TryCreate(lnk, UriKind.Absolute, out var webLink))
|
||||||
&& 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
|
try
|
||||||
{
|
{
|
||||||
var filename = m.Groups["filename"].Value;
|
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
|
||||||
var filesize = -1;
|
foreach (var handler in handlers)
|
||||||
|
|
||||||
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);
|
var (canHandle, reason) = handler.CanHandle(filename, filesize, buf.AsSpan(0, read));
|
||||||
if (response.Content.Headers.ContentLength > 0)
|
if (canHandle)
|
||||||
filesize = (int)response.Content.Headers.ContentLength.Value;
|
return (new MediafireSource(directLink, handler, filename, filesize), null);
|
||||||
if (response.Content.Headers.ContentDisposition?.FileName is string fname && !string.IsNullOrEmpty(fname))
|
else if (!string.IsNullOrEmpty(reason))
|
||||||
filename = fname;
|
return (null, reason);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
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);
|
return (null, null);
|
||||||
}
|
}
|
||||||
|
@ -33,34 +33,32 @@ internal sealed class MegaHandler : BaseSourceHandler
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (m.Groups["mega_link"].Value is string lnk
|
if (m.Groups["mega_link"].Value is not { Length: > 0 } lnk
|
||||||
&& !string.IsNullOrEmpty(lnk)
|
|| !Uri.TryCreate(lnk, UriKind.Absolute, out var uri))
|
||||||
&& 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);
|
await using var stream = await client.DownloadAsync(uri, Doodad, Config.Cts.Token).ConfigureAwait(false);
|
||||||
if (node.Type == NodeType.File)
|
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
|
||||||
|
foreach (var handler in handlers)
|
||||||
{
|
{
|
||||||
var buf = BufferPool.Rent(SnoopBufferSize);
|
var (canHandle, reason) = handler.CanHandle(node.Name, (int)node.Size, buf.AsSpan(0, read));
|
||||||
try
|
if (canHandle)
|
||||||
{
|
return (new MegaSource(client, uri, node, handler), null);
|
||||||
int read;
|
else if (!string.IsNullOrEmpty(reason))
|
||||||
await using (var stream = await client.DownloadAsync(uri, Doodad, Config.Cts.Token).ConfigureAwait(false))
|
return (null, reason);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
BufferPool.Return(buf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -32,42 +32,39 @@ internal sealed class OneDriveSourceHandler : BaseSourceHandler
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (m.Groups["onedrive_link"].Value is string lnk
|
if (m.Groups["onedrive_link"].Value is not { Length: > 0 } lnk
|
||||||
&& !string.IsNullOrEmpty(lnk)
|
|| !Uri.TryCreate(lnk, UriKind.Absolute, out var uri)
|
||||||
&& Uri.TryCreate(lnk, UriKind.Absolute, out var uri)
|
|| await Client.ResolveContentLinkAsync(uri, Config.Cts.Token).ConfigureAwait(false) is not { ContentDownloadUrl: { Length: > 0 } downloadUrl } itemMeta)
|
||||||
&& await Client.ResolveContentLinkAsync(uri, Config.Cts.Token).ConfigureAwait(false) is DriveItemMeta itemMeta
|
continue;
|
||||||
&& itemMeta.ContentDownloadUrl is string downloadUrl)
|
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
|
try
|
||||||
{
|
{
|
||||||
var filename = itemMeta.Name ?? "";
|
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
|
||||||
var filesize = itemMeta.Size;
|
foreach (var handler in handlers)
|
||||||
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);
|
var (canHandle, reason) = handler.CanHandle(filename, filesize, buf.AsSpan(0, read));
|
||||||
foreach (var handler in handlers)
|
if (canHandle)
|
||||||
{
|
return (new OneDriveSource(uri, handler, filename, filesize), null);
|
||||||
var (canHandle, reason) = handler.CanHandle(filename, filesize, buf.AsSpan(0, read));
|
else if (!string.IsNullOrEmpty(reason))
|
||||||
if (canHandle)
|
return (null, reason);
|
||||||
return (new OneDriveSource(uri, handler, filename, filesize), null);
|
|
||||||
else if (!string.IsNullOrEmpty(reason))
|
|
||||||
return (null, reason);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
BufferPool.Return(buf);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -29,31 +29,30 @@ internal sealed class PastebinHandler : BaseSourceHandler
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (m.Groups["pastebin_id"].Value is string pid
|
if (m.Groups["pastebin_id"].Value is not { Length: > 0 } pid)
|
||||||
&& !string.IsNullOrEmpty(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);
|
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
|
||||||
await using var stream = await client.GetStreamAsync(uri).ConfigureAwait(false);
|
var filename = pid + ".log";
|
||||||
var buf = BufferPool.Rent(SnoopBufferSize);
|
var filesize = stream.CanSeek ? (int)stream.Length : 0;
|
||||||
try
|
foreach (var handler in handlers)
|
||||||
{
|
{
|
||||||
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
|
var (canHandle, reason) = handler.CanHandle(filename, filesize, buf.AsSpan(0, read));
|
||||||
var filename = pid + ".log";
|
if (canHandle)
|
||||||
var filesize = stream.CanSeek ? (int)stream.Length : 0;
|
return (new PastebinSource(uri, filename, filesize, handler), null);
|
||||||
foreach (var handler in handlers)
|
else if (!string.IsNullOrEmpty(reason))
|
||||||
{
|
return (null, reason);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
BufferPool.Return(buf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -29,49 +29,48 @@ internal sealed class YandexDiskHandler: BaseSourceHandler
|
|||||||
using var client = HttpClientFactory.Create();
|
using var client = HttpClientFactory.Create();
|
||||||
foreach (Match m in matches)
|
foreach (Match m in matches)
|
||||||
{
|
{
|
||||||
if (m.Groups["yadisk_link"].Value is string lnk
|
if (m.Groups["yadisk_link"].Value is not { Length: > 0 } lnk
|
||||||
&& !string.IsNullOrEmpty(lnk)
|
|| !Uri.TryCreate(lnk, UriKind.Absolute, out var webLink))
|
||||||
&& 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
|
try
|
||||||
{
|
{
|
||||||
var filename = "";
|
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
|
||||||
var filesize = -1;
|
foreach (var handler in handlers)
|
||||||
|
|
||||||
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);
|
var (canHandle, reason) = handler.CanHandle(filename, filesize, buf.AsSpan(0, read));
|
||||||
foreach (var handler in handlers)
|
if (canHandle)
|
||||||
{
|
return (new YaDiskSource(resourceInfo.File, handler, filename, filesize), null);
|
||||||
var (canHandle, reason) = handler.CanHandle(filename, filesize, buf.AsSpan(0, read));
|
else if (!string.IsNullOrEmpty(reason))
|
||||||
if (canHandle)
|
return (null, reason);
|
||||||
return (new YaDiskSource(resourceInfo.File, handler, filename, filesize), null);
|
|
||||||
else if (!string.IsNullOrEmpty(reason))
|
|
||||||
return (null, reason);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
BufferPool.Return(buf);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
catch (Exception e)
|
|
||||||
{
|
{
|
||||||
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);
|
return (null, null);
|
||||||
}
|
}
|
||||||
|
@ -136,8 +136,10 @@ public static class LogParsingHandler
|
|||||||
{
|
{
|
||||||
botMsg = await botMsg.UpdateOrCreateMessageAsync(channel, embed: new DiscordEmbedBuilder
|
botMsg = await botMsg.UpdateOrCreateMessageAsync(channel, embed: new DiscordEmbedBuilder
|
||||||
{
|
{
|
||||||
Description = "Log analysis failed, most likely cause is a truncated/invalid log.\n" +
|
Description = """
|
||||||
"Please run the game again and re-upload a new copy.",
|
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,
|
Color = Config.Colors.LogResultFailed,
|
||||||
}
|
}
|
||||||
.AddAuthor(client, message, source)
|
.AddAuthor(client, message, source)
|
||||||
@ -154,8 +156,8 @@ public static class LogParsingHandler
|
|||||||
{
|
{
|
||||||
if (result.SelectedFilter is null)
|
if (result.SelectedFilter is null)
|
||||||
{
|
{
|
||||||
Config.Log.Error("Piracy was detectedin log, but no trigger provided");
|
Config.Log.Error("Piracy was detected in log, but no trigger provided");
|
||||||
result.SelectedFilter = new Piracystring
|
result.SelectedFilter = new()
|
||||||
{
|
{
|
||||||
String = "Unknown trigger, plz kick 13xforever",
|
String = "Unknown trigger, plz kick 13xforever",
|
||||||
Actions = FilterAction.IssueWarning | FilterAction.RemoveContent,
|
Actions = FilterAction.IssueWarning | FilterAction.RemoveContent,
|
||||||
@ -188,13 +190,14 @@ public static class LogParsingHandler
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
botMsg = await botMsg.UpdateOrCreateMessageAsync(channel,
|
botMsg = await botMsg.UpdateOrCreateMessageAsync(channel,
|
||||||
$"{message.Author.Mention}, please read carefully:\n" +
|
$"""
|
||||||
"🏴☠️ **Pirated content detected** 🏴☠️\n" +
|
# 🏴☠️ **Pirated content detected** 🏴☠️
|
||||||
"__You are being denied further support until you legally dump the game__.\n" +
|
{message.Author.Mention}, please read carefully:
|
||||||
"Please note that the RPCS3 community and its developers do not support piracy.\n" +
|
__You are being denied further support until you legally dump the game__.
|
||||||
"Most of the issues with pirated dumps occur due to them being modified in some way " +
|
Please note that the RPCS3 community and its developers do not support piracy.
|
||||||
"that prevent them from working on RPCS3.\n" +
|
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 at <https://rpcs3.net/quickstart>"
|
If you need help obtaining valid working dump of the game you own, please read [the quickstart guide](<https://rpcs3.net/quickstart>).
|
||||||
|
"""
|
||||||
).ConfigureAwait(false);
|
).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@ -233,8 +236,7 @@ public static class LogParsingHandler
|
|||||||
if (isHelpChannel)
|
if (isHelpChannel)
|
||||||
await botMsg.UpdateOrCreateMessageAsync(
|
await botMsg.UpdateOrCreateMessageAsync(
|
||||||
channel,
|
channel,
|
||||||
$"{message.Author.Mention} please describe the issue if you require help, " +
|
$"{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"
|
||||||
$"or upload log in {botSpamChannel.Mention} if you only need to check your logs automatically"
|
|
||||||
).ConfigureAwait(false);
|
).ConfigureAwait(false);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -278,7 +280,11 @@ public static class LogParsingHandler
|
|||||||
{
|
{
|
||||||
case "TXT":
|
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);
|
Config.TelemetryClient?.TrackRequest(nameof(LogParsingHandler), start, DateTimeOffset.UtcNow - start, HttpStatusCode.BadRequest.ToString(), true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -398,7 +404,7 @@ public static class LogParsingHandler
|
|||||||
private static DiscordEmbedBuilder GetAnalyzingMsgEmbed(DiscordClient client)
|
private static DiscordEmbedBuilder GetAnalyzingMsgEmbed(DiscordClient client)
|
||||||
{
|
{
|
||||||
var indicator = client.GetEmoji(":kannamag:", Config.Reactions.PleaseWait);
|
var indicator = client.GetEmoji(":kannamag:", Config.Reactions.PleaseWait);
|
||||||
return new DiscordEmbedBuilder
|
return new()
|
||||||
{
|
{
|
||||||
Description = $"{indicator} Looking at the log, please wait... {indicator}",
|
Description = $"{indicator} Looking at the log, please wait... {indicator}",
|
||||||
Color = Config.Colors.LogUnknown,
|
Color = Config.Colors.LogUnknown,
|
||||||
|
@ -24,9 +24,8 @@ internal static class NewBuildsMonitor
|
|||||||
if (args.Author.IsBotSafeCheck()
|
if (args.Author.IsBotSafeCheck()
|
||||||
&& !args.Author.IsCurrent
|
&& !args.Author.IsCurrent
|
||||||
&& "github".Equals(args.Channel.Name, StringComparison.InvariantCultureIgnoreCase)
|
&& "github".Equals(args.Channel.Name, StringComparison.InvariantCultureIgnoreCase)
|
||||||
&& args.Message.Embeds.FirstOrDefault() is DiscordEmbed embed
|
&& args.Message?.Embeds is [{ Title: { Length: > 0 } title }, ..]
|
||||||
&& !string.IsNullOrEmpty(embed.Title)
|
&& BuildResult.IsMatch(title)
|
||||||
&& BuildResult.IsMatch(embed.Title)
|
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Config.Log.Info("Found new PR merge message");
|
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 TimeSpan ThrottlingThreshold = TimeSpan.FromSeconds(5);
|
||||||
private static readonly Dictionary<string, Explanation> DefaultExplanation = new()
|
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." },
|
["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 Explanation { Text = "Please remove all the traces of video drivers using DDU, and then reinstall the latest driver version for your GPU." },
|
["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);
|
private static DateTime lastMention = DateTime.UtcNow.AddHours(-1);
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ internal static class ThumbnailCacheMonitor
|
|||||||
|
|
||||||
await using var db = new ThumbnailDb();
|
await using var db = new ThumbnailDb();
|
||||||
var thumb = db.Thumbnail.FirstOrDefault(i => i.ContentId == args.Message.Content);
|
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;
|
thumb.EmbeddableUrl = null;
|
||||||
await db.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false);
|
await db.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false);
|
||||||
|
@ -15,8 +15,10 @@ namespace CompatBot.EventHandlers;
|
|||||||
|
|
||||||
internal static class UnknownCommandHandler
|
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",
|
private static readonly Regex BinaryQuestion = new(
|
||||||
RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase);
|
@"^\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)
|
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();
|
var suggestedName = StripZalgo(m.DisplayName, m.Username, m.Id).Sanitize();
|
||||||
await c.ReportAsync("🔣 Potential display name issue",
|
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,
|
null,
|
||||||
ReportSeverity.Low);
|
ReportSeverity.Low);
|
||||||
await DmAndRenameUserAsync(c, m, suggestedName).ConfigureAwait(false);
|
await DmAndRenameUserAsync(c, m, suggestedName).ConfigureAwait(false);
|
||||||
@ -60,7 +63,10 @@ public static class UsernameZalgoMonitor
|
|||||||
{
|
{
|
||||||
var suggestedName = StripZalgo(name, fallback, args.Member.Id).Sanitize();
|
var suggestedName = StripZalgo(name, fallback, args.Member.Id).Sanitize();
|
||||||
await c.ReportAsync("🔣 Potential display name issue",
|
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,
|
null,
|
||||||
ReportSeverity.Low);
|
ReportSeverity.Low);
|
||||||
await DmAndRenameUserAsync(c, member, suggestedName).ConfigureAwait(false);
|
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();
|
var suggestedName = StripZalgo(name, args.Member.Username, args.Member.Id).Sanitize();
|
||||||
await c.ReportAsync("🔣 Potential display name issue",
|
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,
|
null,
|
||||||
ReportSeverity.Low);
|
ReportSeverity.Low);
|
||||||
await DmAndRenameUserAsync(c, args.Member, suggestedName).ConfigureAwait(false);
|
await DmAndRenameUserAsync(c, args.Member, suggestedName).ConfigureAwait(false);
|
||||||
@ -106,9 +115,11 @@ public static class UsernameZalgoMonitor
|
|||||||
var renameTask = member.ModifyAsync(m => m.Nickname = suggestedName);
|
var renameTask = member.ModifyAsync(m => m.Nickname = suggestedName);
|
||||||
Config.Log.Info($"Renamed {member.Username}#{member.Discriminator} ({member.Id}) to {suggestedName}");
|
Config.Log.Info($"Renamed {member.Username}#{member.Discriminator} ({member.Id}) to {suggestedName}");
|
||||||
var rulesChannel = await client.GetChannelAsync(Config.BotRulesChannelId).ConfigureAwait(false);
|
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" +
|
var msg = $"""
|
||||||
"I'm not perfect and can't clean all the junk in names in some cases, so change your nickname at your discretion.\n" +
|
Hello, your current _display name_ is breaking {rulesChannel.Mention} #7, so you have been renamed to `{suggestedName}`.
|
||||||
"You can change your _display name_ by clicking on the server name at the top left and selecting **Change Nickname**.";
|
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);
|
var dm = await member.CreateDmChannelAsync().ConfigureAwait(false);
|
||||||
await dm.SendMessageAsync(msg).ConfigureAwait(false);
|
await dm.SendMessageAsync(msg).ConfigureAwait(false);
|
||||||
await renameTask.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 HttpClient HttpClient = HttpClientFactory.Create(new CompressionMessageHandler());
|
||||||
private static readonly Uri TitleDownloadLink = new("https://www.gametdb.com/ps3tdb.zip?LANG=EN");
|
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"};
|
//private static readonly List<string> PreferredOrder = new List<string>{"coverHQ", "coverM", "cover"};
|
||||||
|
|
||||||
public static async Task RunAsync(CancellationToken cancellationToken)
|
public static async Task RunAsync(CancellationToken cancellationToken)
|
||||||
@ -47,10 +50,14 @@ internal static class GameTdbScraper
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var html = await HttpClient.GetStringAsync("https://www.gametdb.com/PS3/" + productCode).ConfigureAwait(false);
|
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();
|
var coverLinks = CoverArtLink.Matches(html)
|
||||||
return coverLinks.FirstOrDefault(l => l.Contains("coverHQ", StringComparison.InvariantCultureIgnoreCase)) ??
|
.Select(m => m.Groups["cover_link"].Value)
|
||||||
coverLinks.FirstOrDefault(l => l.Contains("coverM", StringComparison.InvariantCultureIgnoreCase)) ??
|
.Distinct()
|
||||||
coverLinks.FirstOrDefault();
|
.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)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -17,7 +17,10 @@ namespace CompatBot.ThumbScrapper;
|
|||||||
internal sealed class PsnScraper
|
internal sealed class PsnScraper
|
||||||
{
|
{
|
||||||
private static readonly PsnClient.Client Client = new();
|
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 readonly SemaphoreSlim LockObj = new(1, 1);
|
||||||
private static List<string> psnStores = new();
|
private static List<string> psnStores = new();
|
||||||
private static DateTime storeRefreshTimestamp = DateTime.MinValue;
|
private static DateTime storeRefreshTimestamp = DateTime.MinValue;
|
||||||
@ -63,7 +66,7 @@ internal sealed class PsnScraper
|
|||||||
await LockObj.WaitAsync(cancellationToken).ConfigureAwait(false);
|
await LockObj.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
storesToScrape = new List<string>(psnStores);
|
storesToScrape = new(psnStores);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -126,7 +129,7 @@ internal sealed class PsnScraper
|
|||||||
await LockObj.WaitAsync(cancellationToken).ConfigureAwait(false);
|
await LockObj.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
storesToScrape = new List<string>(psnStores);
|
storesToScrape = new(psnStores);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
@ -5,18 +5,13 @@ namespace CompatBot.Utils;
|
|||||||
internal static class BotDbExtensions
|
internal static class BotDbExtensions
|
||||||
{
|
{
|
||||||
public static bool IsComplete(this EventSchedule evt)
|
public static bool IsComplete(this EventSchedule evt)
|
||||||
{
|
=> evt is { Start: > 0, Year: > 0, Name.Length: >0 }
|
||||||
return evt.Start > 0
|
&& evt.End > evt.Start;
|
||||||
&& evt.End > evt.Start
|
|
||||||
&& evt.Year > 0
|
|
||||||
&& !string.IsNullOrEmpty(evt.Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsComplete(this Piracystring filter)
|
public static bool IsComplete(this Piracystring filter)
|
||||||
{
|
{
|
||||||
var result = !string.IsNullOrEmpty(filter.String)
|
var result = filter.Actions != 0
|
||||||
&& filter.String.Length >= Config.MinimumPiracyTriggerLength
|
&& filter.String.Length >= Config.MinimumPiracyTriggerLength;
|
||||||
&& filter.Actions != 0;
|
|
||||||
if (result && filter.Actions.HasFlag(FilterAction.ShowExplain))
|
if (result && filter.Actions.HasFlag(FilterAction.ShowExplain))
|
||||||
result = !string.IsNullOrEmpty(filter.ExplainTerm);
|
result = !string.IsNullOrEmpty(filter.ExplainTerm);
|
||||||
return result;
|
return result;
|
||||||
|
@ -4,19 +4,8 @@ namespace CompatBot.Utils;
|
|||||||
|
|
||||||
public static class DateTimeEx
|
public static class DateTimeEx
|
||||||
{
|
{
|
||||||
public static DateTime AsUtc(this DateTime dateTime)
|
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);
|
||||||
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 string AsShortTimespan(this TimeSpan timeSpan)
|
public static string AsShortTimespan(this TimeSpan timeSpan)
|
||||||
{
|
{
|
||||||
|
@ -14,11 +14,7 @@ public static class DiceCoefficientOptimized
|
|||||||
var bgCount1 = input.Length - 1;
|
var bgCount1 = input.Length - 1;
|
||||||
var bgCount2 = comparedTo.Length - 1;
|
var bgCount2 = comparedTo.Length - 1;
|
||||||
if (comparedTo.Length < input.Length)
|
if (comparedTo.Length < input.Length)
|
||||||
{
|
(input, comparedTo) = (comparedTo, input);
|
||||||
var tmp = input;
|
|
||||||
input = comparedTo;
|
|
||||||
comparedTo = tmp;
|
|
||||||
}
|
|
||||||
var matches = 0;
|
var matches = 0;
|
||||||
for (var i = 0; i < input.Length - 1; i++)
|
for (var i = 0; i < input.Length - 1; i++)
|
||||||
for (var j = 0; j < comparedTo.Length - 1; j++)
|
for (var j = 0; j < comparedTo.Length - 1; j++)
|
||||||
|
@ -15,12 +15,7 @@ namespace CompatBot.Utils;
|
|||||||
public static class DiscordClientExtensions
|
public static class DiscordClientExtensions
|
||||||
{
|
{
|
||||||
public static DiscordMember? GetMember(this DiscordClient client, DiscordGuild? guild, ulong userId)
|
public static DiscordMember? GetMember(this DiscordClient client, DiscordGuild? guild, ulong userId)
|
||||||
{
|
=> guild is null ? GetMember(client, userId) : GetMember(client, guild.Id, userId);
|
||||||
if (guild is null)
|
|
||||||
return GetMember(client, userId);
|
|
||||||
|
|
||||||
return GetMember(client, guild.Id, userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DiscordMember? GetMember(this DiscordClient client, ulong guildId, ulong userId)
|
public static DiscordMember? GetMember(this DiscordClient client, ulong guildId, ulong userId)
|
||||||
=> (from g in client.Guilds
|
=> (from g in client.Guilds
|
||||||
@ -87,15 +82,10 @@ public static class DiscordClientExtensions
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Task RemoveReactionAsync(this CommandContext ctx, DiscordEmoji emoji)
|
public static Task RemoveReactionAsync(this CommandContext ctx, DiscordEmoji emoji) => RemoveReactionAsync(ctx.Message, emoji);
|
||||||
{
|
|
||||||
return RemoveReactionAsync(ctx.Message, emoji);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Task ReactWithAsync(this CommandContext ctx, DiscordEmoji emoji, string? fallbackMessage = null, bool? showBoth = null)
|
public static Task ReactWithAsync(this CommandContext ctx, DiscordEmoji emoji, string? fallbackMessage = null, bool? showBoth = null)
|
||||||
{
|
=> ReactWithAsync(ctx.Message, emoji, fallbackMessage, showBoth ?? (ctx.Prefix == Config.AutoRemoveCommandPrefix));
|
||||||
return 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)
|
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)
|
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)
|
public static string GetUsernameWithNickname(this DiscordUser user, DiscordClient client, DiscordGuild? guild = null)
|
||||||
{
|
=> client.GetMember(guild, user).GetUsernameWithNickname()
|
||||||
return client.GetMember(guild, user).GetUsernameWithNickname()
|
?? $"`{user.Username.Sanitize()}#{user.Discriminator}`";
|
||||||
?? $"`{user.Username.Sanitize()}#{user.Discriminator}`";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string? GetUsernameWithNickname(this DiscordMember? member)
|
public static string? GetUsernameWithNickname(this DiscordMember? member)
|
||||||
{
|
=> member is null
|
||||||
if (member == null)
|
? null
|
||||||
return null;
|
: string.IsNullOrEmpty(member.Nickname)
|
||||||
|
? $"`{member.Username.Sanitize()}#{member.Discriminator}`"
|
||||||
return string.IsNullOrEmpty(member.Nickname) ? $"`{member.Username.Sanitize()}#{member.Discriminator}`" : $"`{member.Username.Sanitize()}#{member.Discriminator}` (shown as `{member.Nickname.Sanitize()}`)";
|
: $"`{member.Username.Sanitize()}#{member.Discriminator}` (shown as `{member.Nickname.Sanitize()}`)";
|
||||||
}
|
|
||||||
|
|
||||||
public static DiscordEmoji? GetEmoji(this DiscordClient client, string? emojiName, string? fallbackEmoji = null)
|
public static DiscordEmoji? GetEmoji(this DiscordClient client, string? emojiName, string? fallbackEmoji = null)
|
||||||
=> GetEmoji(client, emojiName, fallbackEmoji == null ? null : DiscordEmoji.FromUnicode(fallbackEmoji));
|
=> 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))
|
if (DiscordEmoji.TryFromName(client, emojiName, true, out var result))
|
||||||
return result;
|
return result;
|
||||||
else if (DiscordEmoji.TryFromName(client, $":{emojiName}:", true, out result))
|
|
||||||
|
if (DiscordEmoji.TryFromName(client, $":{emojiName}:", true, out result))
|
||||||
return result;
|
return result;
|
||||||
else if (DiscordEmoji.TryFromUnicode(emojiName, out result))
|
|
||||||
|
if (DiscordEmoji.TryFromUnicode(emojiName, out result))
|
||||||
return result;
|
return result;
|
||||||
return fallbackEmoji;
|
return fallbackEmoji;
|
||||||
}
|
}
|
||||||
|
@ -38,11 +38,10 @@ public static class EnumerableExtensions
|
|||||||
{
|
{
|
||||||
if (collection.Count > 0)
|
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)];
|
return collection[rng.Next(collection.Count)];
|
||||||
}
|
}
|
||||||
else
|
return default;
|
||||||
return default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool AnyPatchesApplied(this Dictionary<string, int> patches)
|
public static bool AnyPatchesApplied(this Dictionary<string, int> patches)
|
||||||
|
@ -24,7 +24,7 @@ internal static class FilterActionExtensions
|
|||||||
.Cast<FilterAction>()
|
.Cast<FilterAction>()
|
||||||
.Select(fa => flags.HasFlag(fa) ? ActionFlags[fa] : '-')
|
.Select(fa => flags.HasFlag(fa) ? ActionFlags[fa] : '-')
|
||||||
.ToArray();
|
.ToArray();
|
||||||
return new string(result);
|
return new(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetLegend(string wrapChar = "`")
|
public static string GetLegend(string wrapChar = "`")
|
||||||
|
@ -212,7 +212,7 @@ public static class StringUtils
|
|||||||
{
|
{
|
||||||
result.Append('(');
|
result.Append('(');
|
||||||
for (var i = 0; i < count; i++)
|
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(')');
|
result.Append(')');
|
||||||
}
|
}
|
||||||
span = span.Slice(1);
|
span = span.Slice(1);
|
||||||
|
@ -54,10 +54,10 @@ internal class FixedLengthBuffer<TKey, TValue>: IList<TValue>
|
|||||||
|
|
||||||
public void TrimExcess()
|
public void TrimExcess()
|
||||||
{
|
{
|
||||||
if (Count <= FixedLengthBuffer<TKey, TValue>.MaxLength)
|
if (Count <= MaxLength)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
TrimOldItems(Count - FixedLengthBuffer<TKey, TValue>.MaxLength);
|
TrimOldItems(Count - MaxLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<TValue> GetOldItems(int count)
|
public List<TValue> GetOldItems(int count)
|
||||||
@ -65,9 +65,9 @@ internal class FixedLengthBuffer<TKey, TValue>: IList<TValue>
|
|||||||
|
|
||||||
public List<TValue> GetExcess()
|
public List<TValue> GetExcess()
|
||||||
{
|
{
|
||||||
if (Count <= FixedLengthBuffer<TKey, TValue>.MaxLength)
|
if (Count <= MaxLength)
|
||||||
return new List<TValue>(0);
|
return new(0);
|
||||||
return GetOldItems(Count - FixedLengthBuffer<TKey, TValue>.MaxLength);
|
return GetOldItems(Count - MaxLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TValue? Evict(TKey key)
|
public TValue? Evict(TKey key)
|
||||||
@ -116,7 +116,7 @@ internal class FixedLengthBuffer<TKey, TValue>: IList<TValue>
|
|||||||
public int Count => lookup.Count;
|
public int Count => lookup.Count;
|
||||||
public bool IsReadOnly => false;
|
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];
|
public TValue this[TKey index] => lookup[index];
|
||||||
|
|
||||||
private static int MaxLength => Config.ChannelMessageHistorySize;
|
private static int MaxLength => Config.ChannelMessageHistorySize;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
|
||||||
namespace CompatBot.Utils;
|
namespace CompatBot.Utils;
|
||||||
|
|
||||||
public class NameUniqueObjectCollection<TValue>: IDictionary<string, UniqueList<TValue>>
|
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)
|
public NameUniqueObjectCollection(IEqualityComparer<string>? keyComparer = null, IEqualityComparer<TValue>? valueComparer = null)
|
||||||
{
|
{
|
||||||
dict = new Dictionary<string, UniqueList<TValue>>(keyComparer);
|
dict = new(keyComparer);
|
||||||
this.valueComparer = valueComparer;
|
this.valueComparer = valueComparer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,18 +35,21 @@ public class NameUniqueObjectCollection<TValue>: IDictionary<string, UniqueList<
|
|||||||
c.Add(value);
|
c.Add(value);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dict[key] = c = new UniqueList<TValue>(valueComparer);
|
dict[key] = c = new(valueComparer);
|
||||||
c.Add(value);
|
c.Add(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Clear() => dict.Clear();
|
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)
|
||||||
bool ICollection<KeyValuePair<string, UniqueList<TValue>>>.Remove(KeyValuePair<string, UniqueList<TValue>> item) => ((IDictionary<string, UniqueList<TValue>>)dict).Remove(item);
|
=> ((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);
|
||||||
|
|
||||||
public int Count => dict.Count;
|
public int Count => dict.Count;
|
||||||
public bool IsReadOnly => false;
|
public bool IsReadOnly => false;
|
||||||
@ -59,7 +63,7 @@ public class NameUniqueObjectCollection<TValue>: IDictionary<string, UniqueList<
|
|||||||
if (dict.TryGetValue(key, out value!))
|
if (dict.TryGetValue(key, out value!))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
dict[key] = value = new UniqueList<TValue>(valueComparer);
|
dict[key] = value = new(valueComparer);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,14 +61,18 @@ public static class OpenSslConfigurator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await writer.WriteLineAsync("openssl_conf = default_conf").ConfigureAwait(false);
|
await writer.WriteLineAsync("""
|
||||||
await writer.WriteLineAsync("[default_conf]").ConfigureAwait(false);
|
openssl_conf = default_conf
|
||||||
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);
|
|
||||||
|
|
||||||
|
[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)
|
if (content.Length > 0)
|
||||||
await writer.WriteAsync(content).ConfigureAwait(false);
|
await writer.WriteAsync(content).ConfigureAwait(false);
|
||||||
await writer.FlushAsync().ConfigureAwait(false);
|
await writer.FlushAsync().ConfigureAwait(false);
|
||||||
|
@ -219,42 +219,42 @@ public static class ProductCodeDecoder
|
|||||||
if (productCode.IsEmpty || productCode.Length < 2)
|
if (productCode.IsEmpty || productCode.Length < 2)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
switch ((productCode[0], productCode[1]))
|
switch (productCode)
|
||||||
{
|
{
|
||||||
case ('0', '0'):
|
case ['0', '0', ..]:
|
||||||
case ('0', '1'):
|
case ['0', '1', ..]:
|
||||||
case ('0', '2'):
|
case ['0', '2', ..]:
|
||||||
{
|
{
|
||||||
result.Add("European code range");
|
result.Add("European code range");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ('2', '0'):
|
case ['2', '0', ..]:
|
||||||
{
|
{
|
||||||
result.Add("Korean code range");
|
result.Add("Korean code range");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ('3', '0'):
|
case ['3', '0', ..]:
|
||||||
case ('3', '1'):
|
case ['3', '1', ..]:
|
||||||
case ('4', '1'):
|
case ['4', '1', ..]:
|
||||||
case ('8', '1'):
|
case ['8', '1', ..]:
|
||||||
case ('8', '2'):
|
case ['8', '2', ..]:
|
||||||
case ('8', '3'):
|
case ['8', '3', ..]:
|
||||||
case ('9', '0'):
|
case ['9', '0', ..]:
|
||||||
case ('9', '1'):
|
case ['9', '1', ..]:
|
||||||
case ('9', '4'):
|
case ['9', '4', ..]:
|
||||||
case ('9', '8'):
|
case ['9', '8', ..]:
|
||||||
case ('9', '9'):
|
case ['9', '9', ..]:
|
||||||
{
|
{
|
||||||
result.Add("USA code range");
|
result.Add("USA code range");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ('5', '0'):
|
case ['5', '0', ..]:
|
||||||
{
|
{
|
||||||
result.Add("Asian code range");
|
result.Add("Asian code range");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ('6', '0'):
|
case ['6', '0', ..]:
|
||||||
case ('6', '1'):
|
case ['6', '1', ..]:
|
||||||
{
|
{
|
||||||
result.Add("Japanese code range");
|
result.Add("Japanese code range");
|
||||||
break;
|
break;
|
||||||
@ -561,9 +561,9 @@ public static class ProductCodeDecoder
|
|||||||
|
|
||||||
private static void DecodeMediaC(in ReadOnlySpan<char> productCode, List<string> result)
|
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");
|
result.Add("Playstation 4 software");
|
||||||
return;
|
return;
|
||||||
|
@ -11,8 +11,10 @@ namespace CompatBot.Utils.ResultFormatters;
|
|||||||
internal static class FwInfoFormatter
|
internal static class FwInfoFormatter
|
||||||
{
|
{
|
||||||
//2019_0828_c975768e5d70e105a72656f498cc9be9/PS3UPDAT.PUP
|
//2019_0828_c975768e5d70e105a72656f498cc9be9/PS3UPDAT.PUP
|
||||||
private static readonly Regex FwLinkInfo = new(@"(?<year>\d{4})_(?<month>\d\d)(?<day>\d\d)_(?<md5>[0-9a-f]+)",
|
private static readonly Regex FwLinkInfo = new(
|
||||||
RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.Singleline | RegexOptions.IgnoreCase);
|
@"(?<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)
|
private static readonly Dictionary<string, string> RegionToFlagMap = new(StringComparer.InvariantCultureIgnoreCase)
|
||||||
{
|
{
|
||||||
["us"] = "🇺🇸",
|
["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" +
|
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}";
|
$"It is available in {fwInfoList.Count} region{(fwInfoList.Count == 1 ? "" : "s")} out of {RegionToFlagMap.Count}";
|
||||||
result.AddField("Checksums", $"MD5: `{info.Groups["md5"].Value}`\n" +
|
result.AddField("Checksums", $"""
|
||||||
"You can use [HashCheck](https://github.com/gurnec/HashCheck/releases/latest) to verify your download");
|
MD5: `{info.Groups["md5"].Value}`
|
||||||
|
You can use [HashCheck](https://github.com/gurnec/HashCheck/releases/latest) to verify your download
|
||||||
|
""");
|
||||||
var links = new StringBuilder();
|
var links = new StringBuilder();
|
||||||
foreach (var fwi in fwInfoList)
|
foreach (var fwi in fwInfoList)
|
||||||
{
|
{
|
||||||
|
@ -448,7 +448,7 @@ internal static partial class LogParserResult
|
|||||||
msg += $" (update available: v{gameUpVer})";
|
msg += $" (update available: v{gameUpVer})";
|
||||||
notes.Add(msg);
|
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
|
&& ProgramHashPatch.Match(firstPpuPatch) is { Success: true } m
|
||||||
&& m.Groups["hash"].Value is string firstPpuHash)
|
&& m.Groups["hash"].Value is string firstPpuHash)
|
||||||
{
|
{
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
using DSharpPlus.Entities;
|
using DSharpPlus.Entities;
|
||||||
|
using Octokit;
|
||||||
|
|
||||||
namespace CompatBot.Utils.ResultFormatters;
|
namespace CompatBot.Utils.ResultFormatters;
|
||||||
|
|
||||||
internal static class PrInfoFormatter
|
internal static class PrInfoFormatter
|
||||||
{
|
{
|
||||||
public static DiscordEmbedBuilder AsEmbed(this Octokit.PullRequest prInfo)
|
public static DiscordEmbedBuilder AsEmbed(this PullRequest prInfo)
|
||||||
{
|
{
|
||||||
var state = prInfo.GetState();
|
var state = prInfo.GetState();
|
||||||
var stateLabel = state.state == null ? null : $"[{state.state}] ";
|
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};
|
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 state = issueInfo.GetState();
|
||||||
var stateLabel = state.state == null ? null : $"[{state.state}] ";
|
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};
|
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);
|
return ("Open", Config.Colors.PrOpen);
|
||||||
|
|
||||||
if (prInfo.State == Octokit.ItemState.Closed)
|
if (prInfo.State == ItemState.Closed)
|
||||||
{
|
{
|
||||||
if (prInfo.MergedAt.HasValue)
|
if (prInfo.MergedAt.HasValue)
|
||||||
return ("Merged", Config.Colors.PrMerged);
|
return ("Merged", Config.Colors.PrMerged);
|
||||||
@ -36,12 +37,12 @@ internal static class PrInfoFormatter
|
|||||||
return (null, Config.Colors.DownloadLinks);
|
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);
|
return ("Open", Config.Colors.PrOpen);
|
||||||
|
|
||||||
if (issueInfo.State == Octokit.ItemState.Closed)
|
if (issueInfo.State == ItemState.Closed)
|
||||||
return ("Closed", Config.Colors.PrClosed);
|
return ("Closed", Config.Colors.PrClosed);
|
||||||
|
|
||||||
return (null, Config.Colors.DownloadLinks);
|
return (null, Config.Colors.DownloadLinks);
|
||||||
|
@ -29,16 +29,8 @@ internal static class TitleInfoFormatter
|
|||||||
? date.ToString(ApiConfig.DateOutputFormat)
|
? date.ToString(ApiConfig.DateOutputFormat)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
private static string? ToPrString(this TitleInfo info, string? defaultString, bool link = false)
|
private static string ToPrString(this TitleInfo info)
|
||||||
{
|
=> $"[#{info.Pr}](<https://github.com/RPCS3/rpcs3/pull/{info.Pr}>)";
|
||||||
if ((info.Pr ?? 0) == 0)
|
|
||||||
return defaultString;
|
|
||||||
|
|
||||||
if (link)
|
|
||||||
return $"[#{info.Pr}](https://github.com/RPCS3/rpcs3/pull/{info.Pr})";
|
|
||||||
|
|
||||||
return $"#{info.Pr}";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string AsString(this TitleInfo info, string titleId)
|
public static string AsString(this TitleInfo info, string titleId)
|
||||||
{
|
{
|
||||||
@ -56,9 +48,11 @@ internal static class TitleInfoFormatter
|
|||||||
result += " ";
|
result += " ";
|
||||||
else
|
else
|
||||||
result += $" since {info.ToUpdated(),-10}";
|
result += $" since {info.ToUpdated(),-10}";
|
||||||
result += $" (PR {info.ToPrString("#????"),-5})`";
|
result += '`';
|
||||||
|
if (info.Pr > 0)
|
||||||
|
result += $" PR {info.ToPrString(),-5}";
|
||||||
if (info.Thread > 0)
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +71,7 @@ internal static class TitleInfoFormatter
|
|||||||
var thumb = db.Thumbnail.FirstOrDefault(t => t.ProductCode == titleId);
|
var thumb = db.Thumbnail.FirstOrDefault(t => t.ProductCode == titleId);
|
||||||
if (thumb?.CompatibilityStatus != null)
|
if (thumb?.CompatibilityStatus != null)
|
||||||
{
|
{
|
||||||
info = new TitleInfo
|
info = new()
|
||||||
{
|
{
|
||||||
Date = thumb.CompatibilityChangeDate?.AsUtc().ToString("yyyy-MM-dd"),
|
Date = thumb.CompatibilityChangeDate?.AsUtc().ToString("yyyy-MM-dd"),
|
||||||
Status = thumb.CompatibilityStatus.ToString(),
|
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
|
// apparently there's no formatting in the footer, but you need to escape everything in description; ugh
|
||||||
var onlineOnlyPart = info.Network == 1 ? " 🌐" : "";
|
var onlineOnlyPart = info.Network == 1 ? " 🌐" : "";
|
||||||
var pr = info.ToPrString(null, true);
|
|
||||||
var desc = $"{info.Status} since {info.ToUpdated() ?? "forever"}";
|
var desc = $"{info.Status} since {info.ToUpdated() ?? "forever"}";
|
||||||
if (pr != null)
|
if (info.Pr > 0)
|
||||||
desc += $" (PR {pr})";
|
desc += $" (PR {info.ToPrString()})";
|
||||||
if (!forLog && !string.IsNullOrEmpty(info.AlternativeTitle))
|
if (!forLog && !string.IsNullOrEmpty(info.AlternativeTitle))
|
||||||
desc = info.AlternativeTitle + Environment.NewLine + desc;
|
desc = info.AlternativeTitle + Environment.NewLine + desc;
|
||||||
if (!string.IsNullOrEmpty(info.WikiTitle))
|
if (!string.IsNullOrEmpty(info.WikiTitle))
|
||||||
|
@ -234,35 +234,32 @@ internal static class UpdateInfoFormatter
|
|||||||
var seconds = (int)delta.TotalSeconds;
|
var seconds = (int)delta.TotalSeconds;
|
||||||
return $"{seconds} second{(seconds == 1 ? "" : "s")}";
|
return $"{seconds} second{(seconds == 1 ? "" : "s")}";
|
||||||
}
|
}
|
||||||
else if (delta.TotalHours < 1)
|
if (delta.TotalHours < 1)
|
||||||
{
|
{
|
||||||
var minutes = (int)delta.TotalMinutes;
|
var minutes = (int)delta.TotalMinutes;
|
||||||
return $"{minutes} minute{(minutes == 1 ? "" : "s")}";
|
return $"{minutes} minute{(minutes == 1 ? "" : "s")}";
|
||||||
}
|
}
|
||||||
else if (delta.TotalDays < 1)
|
if (delta.TotalDays < 1)
|
||||||
{
|
{
|
||||||
var hours = (int) delta.TotalHours;
|
var hours = (int) delta.TotalHours;
|
||||||
return $"{hours} hour{(hours == 1 ? "": "s")}";
|
return $"{hours} hour{(hours == 1 ? "": "s")}";
|
||||||
}
|
}
|
||||||
else if (delta.TotalDays < 7)
|
if (delta.TotalDays < 7)
|
||||||
{
|
{
|
||||||
var days = (int) delta.TotalDays;
|
var days = (int) delta.TotalDays;
|
||||||
return $"{days} day{(days == 1 ? "": "s")}";
|
return $"{days} day{(days == 1 ? "": "s")}";
|
||||||
}
|
}
|
||||||
else if (delta.TotalDays < 30)
|
if (delta.TotalDays < 30)
|
||||||
{
|
{
|
||||||
var weeks = (int)(delta.TotalDays/7);
|
var weeks = (int)(delta.TotalDays/7);
|
||||||
return $"{weeks} week{(weeks == 1 ? "" : "s")}";
|
return $"{weeks} week{(weeks == 1 ? "" : "s")}";
|
||||||
}
|
}
|
||||||
else if (delta.TotalDays < 365)
|
if (delta.TotalDays < 365)
|
||||||
{
|
{
|
||||||
var months = (int)(delta.TotalDays/30);
|
var months = (int)(delta.TotalDays/30);
|
||||||
return $"{months} month{(months == 1 ? "" : "s")}";
|
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))
|
if (TimeZoneAcronyms.ContainsKey(a))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!standardNames.ContainsKey(a))
|
standardNames.TryAdd(a, tzi);
|
||||||
standardNames[a] = tzi;
|
|
||||||
a = tzi.DaylightName.GetAcronym(includeAllCaps: true, includeAllDigits: true);
|
a = tzi.DaylightName.GetAcronym(includeAllCaps: true, includeAllDigits: true);
|
||||||
if (TimeZoneAcronyms.ContainsKey(a) || standardNames.ContainsKey(a))
|
if (TimeZoneAcronyms.ContainsKey(a) || standardNames.ContainsKey(a))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!daylightNames.ContainsKey(a))
|
daylightNames.TryAdd(a, tzi);
|
||||||
daylightNames[a] = tzi;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Config.Log.Trace("[TZI] Total matched acronyms: " + result.Count);
|
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();
|
public IEnumerator<T> GetEnumerator() => list.GetEnumerator();
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)list).GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)list).GetEnumerator();
|
||||||
|
|
||||||
public void Add(T item)
|
public void Add(T item)
|
||||||
@ -65,7 +64,6 @@ public class UniqueList<T>: IList<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
public bool Contains(T item) => set.Contains(item);
|
public bool Contains(T item) => set.Contains(item);
|
||||||
|
|
||||||
public void CopyTo(T[] array, int arrayIndex) => list.CopyTo(array, arrayIndex);
|
public void CopyTo(T[] array, int arrayIndex) => list.CopyTo(array, arrayIndex);
|
||||||
|
|
||||||
public bool Remove(T item)
|
public bool Remove(T item)
|
||||||
@ -75,9 +73,7 @@ public class UniqueList<T>: IList<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int Count => list.Count;
|
public int Count => list.Count;
|
||||||
|
|
||||||
public bool IsReadOnly => false;
|
public bool IsReadOnly => false;
|
||||||
|
|
||||||
public int IndexOf(T item) => list.IndexOf(item);
|
public int IndexOf(T item) => list.IndexOf(item);
|
||||||
|
|
||||||
public void Insert(int index, T item)
|
public void Insert(int index, T item)
|
||||||
|
@ -19,12 +19,12 @@ internal class Utf8ToLatin1RegexPatternEncoderFallback : EncoderFallback
|
|||||||
private readonly byte[] buffer = new byte[MaxBufferSize];
|
private readonly byte[] buffer = new byte[MaxBufferSize];
|
||||||
private int size, remaining;
|
private int size, remaining;
|
||||||
private static readonly Encoding Utf8 = new UTF8Encoding(false);
|
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()
|
static CustomMapperFallbackBuffer()
|
||||||
{
|
{
|
||||||
for (var i = 0; i < 256; i++)
|
for (var i = 0; i < 256; i++)
|
||||||
byteToHex.Add(i.ToString("X2"));
|
ByteToHex.Add(i.ToString("X2"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public CustomMapperFallbackBuffer()
|
public CustomMapperFallbackBuffer()
|
||||||
@ -59,7 +59,7 @@ internal class Utf8ToLatin1RegexPatternEncoderFallback : EncoderFallback
|
|||||||
for (var i = 0; i < count; i++)
|
for (var i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
ref var b = ref tmp[i];
|
ref var b = ref tmp[i];
|
||||||
var s = byteToHex[b];
|
var s = ByteToHex[b];
|
||||||
var offset = i * 4 + 1;
|
var offset = i * 4 + 1;
|
||||||
buffer[offset + 0] = (byte)'\\';
|
buffer[offset + 0] = (byte)'\\';
|
||||||
buffer[offset + 2] =(byte)s[0];
|
buffer[offset + 2] =(byte)s[0];
|
||||||
@ -81,7 +81,7 @@ internal class Utf8ToLatin1RegexPatternEncoderFallback : EncoderFallback
|
|||||||
for (var i = 0; i < count; i++)
|
for (var i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
ref var b = ref tmp[i];
|
ref var b = ref tmp[i];
|
||||||
var s = byteToHex[b];
|
var s = ByteToHex[b];
|
||||||
var offset = i * 4 + 1;
|
var offset = i * 4 + 1;
|
||||||
buffer[offset + 0] = (byte)'\\';
|
buffer[offset + 0] = (byte)'\\';
|
||||||
buffer[offset + 2] =(byte)s[0];
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
@ -26,9 +27,10 @@ public static class Normalizer
|
|||||||
};
|
};
|
||||||
|
|
||||||
// as per https://www.unicode.org/reports/tr39/#Confusable_Detection
|
// 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;
|
return input;
|
||||||
|
|
||||||
// step 1: Convert X to NFD format, as described in [UAX15].
|
// step 1: Convert X to NFD format, as described in [UAX15].
|
||||||
@ -39,9 +41,10 @@ public static class Normalizer
|
|||||||
return input.Normalize(NormalizationForm.FormD);
|
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;
|
return input;
|
||||||
|
|
||||||
input = ToSkeletonString(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">
|
<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: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/=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/=Bandicam/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=BCAS/@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>
|
<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/=BLKS/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=BLUS/@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/=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/=CJS7/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Confusables/@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/=Cubeb/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=desync/@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/=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/=edat/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=framerate/@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>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=getllar/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
Loading…
Reference in New Issue
Block a user