mirror of
https://github.com/RPCS3/discord-bot.git
synced 2026-01-31 01:25:22 +01:00
refactor to use using statement whenever it makes sense
This commit is contained in:
@@ -139,24 +139,20 @@ namespace AppveyorClient
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var message = new HttpRequestMessage(HttpMethod.Get, buildUrl))
|
||||
using var message = new HttpRequestMessage(HttpMethod.Get, buildUrl);
|
||||
message.Headers.UserAgent.Add(ProductInfoHeader);
|
||||
using var response = await client.SendAsync(message, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
message.Headers.UserAgent.Add(ProductInfoHeader);
|
||||
using (var response = await client.SendAsync(message, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
try
|
||||
{
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
var result = await response.Content.ReadAsAsync<BuildInfo>(formatters, cancellationToken).ConfigureAwait(false);
|
||||
ResponseCache.Set(buildUrl, result, CacheTime);
|
||||
//ApiConfig.Log.Debug($"Cached {nameof(BuildInfo)} for {buildUrl} for {CacheTime}");
|
||||
return result;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
}
|
||||
}
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
var result = await response.Content.ReadAsAsync<BuildInfo>(formatters, cancellationToken).ConfigureAwait(false);
|
||||
ResponseCache.Set(buildUrl, result, CacheTime);
|
||||
//ApiConfig.Log.Debug($"Cached {nameof(BuildInfo)} for {buildUrl} for {CacheTime}");
|
||||
return result;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -206,24 +202,20 @@ namespace AppveyorClient
|
||||
var requestUri = $"https://ci.appveyor.com/api/buildjobs/{jobId}/artifacts";
|
||||
try
|
||||
{
|
||||
using (var message = new HttpRequestMessage(HttpMethod.Get, requestUri))
|
||||
using var message = new HttpRequestMessage(HttpMethod.Get, requestUri);
|
||||
message.Headers.UserAgent.Add(ProductInfoHeader);
|
||||
using var response = await client.SendAsync(message, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
message.Headers.UserAgent.Add(ProductInfoHeader);
|
||||
using (var response = await client.SendAsync(message, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
try
|
||||
{
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
var result = await response.Content.ReadAsAsync<List<Artifact>>(formatters, cancellationToken).ConfigureAwait(false);
|
||||
ResponseCache.Set(requestUri, result, CacheTime);
|
||||
ApiConfig.Log.Debug($"Cached {nameof(Artifact)} for {jobId} for {CacheTime}");
|
||||
return result;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
}
|
||||
}
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
var result = await response.Content.ReadAsAsync<List<Artifact>>(formatters, cancellationToken).ConfigureAwait(false);
|
||||
ResponseCache.Set(requestUri, result, CacheTime);
|
||||
ApiConfig.Log.Debug($"Cached {nameof(Artifact)} for {jobId} for {CacheTime}");
|
||||
return result;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -273,23 +265,19 @@ namespace AppveyorClient
|
||||
Build build = null;
|
||||
do
|
||||
{
|
||||
using (var message = new HttpRequestMessage(HttpMethod.Get, historyUrl))
|
||||
using var message = new HttpRequestMessage(HttpMethod.Get, historyUrl);
|
||||
message.Headers.UserAgent.Add(ProductInfoHeader);
|
||||
using var response = await client.SendAsync(message, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
message.Headers.UserAgent.Add(ProductInfoHeader);
|
||||
using (var response = await client.SendAsync(message, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
try
|
||||
{
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
historyPage = await response.Content.ReadAsAsync<HistoryInfo>(formatters, cancellationToken).ConfigureAwait(false);
|
||||
build = historyPage?.Builds?.FirstOrDefault(selectPredicate);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
break;
|
||||
}
|
||||
}
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
historyPage = await response.Content.ReadAsAsync<HistoryInfo>(formatters, cancellationToken).ConfigureAwait(false);
|
||||
build = historyPage?.Builds?.FirstOrDefault(selectPredicate);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
break;
|
||||
}
|
||||
historyUrl = baseUrl.SetQueryParameter("startBuildId", historyPage?.Builds?.Last().BuildId.ToString());
|
||||
} while (build == null && historyPage?.Builds?.Count > 0 && takePredicate(historyPage));
|
||||
|
||||
@@ -36,20 +36,20 @@ namespace CompatApiClient
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var message = new HttpRequestMessage(HttpMethod.Get, url))
|
||||
using (var response = await client.SendAsync(message, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false))
|
||||
try
|
||||
{
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
var result = await response.Content.ReadAsAsync<CompatResult>(formatters, cancellationToken).ConfigureAwait(false);
|
||||
result.RequestBuilder = requestBuilder;
|
||||
result.RequestDuration = DateTime.UtcNow - startTime;
|
||||
return result;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response, false);
|
||||
}
|
||||
using var message = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
using var response = await client.SendAsync(message, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
var result = await response.Content.ReadAsAsync<CompatResult>(formatters, cancellationToken).ConfigureAwait(false);
|
||||
result.RequestBuilder = requestBuilder;
|
||||
result.RequestDuration = DateTime.UtcNow - startTime;
|
||||
return result;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response, false);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -69,16 +69,16 @@ namespace CompatApiClient
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var message = new HttpRequestMessage(HttpMethod.Get, "https://update.rpcs3.net/?api=v1&c=" + commit))
|
||||
using (var response = await client.SendAsync(message, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false))
|
||||
try
|
||||
{
|
||||
return await response.Content.ReadAsAsync<UpdateInfo>(formatters, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response, false);
|
||||
}
|
||||
using var message = new HttpRequestMessage(HttpMethod.Get, "https://update.rpcs3.net/?api=v1&c=" + commit);
|
||||
using var response = await client.SendAsync(message, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
return await response.Content.ReadAsAsync<UpdateInfo>(formatters, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response, false);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -11,26 +11,22 @@ namespace CompatApiClient.Compression
|
||||
|
||||
public virtual async Task<long> CompressAsync(Stream source, Stream destination)
|
||||
{
|
||||
using (var memStream = new MemoryStream())
|
||||
{
|
||||
using (var compressed = CreateCompressionStream(memStream))
|
||||
await source.CopyToAsync(compressed).ConfigureAwait(false);
|
||||
memStream.Seek(0, SeekOrigin.Begin);
|
||||
await memStream.CopyToAsync(destination).ConfigureAwait(false);
|
||||
return memStream.Length;
|
||||
}
|
||||
using var memStream = new MemoryStream();
|
||||
using (var compressed = CreateCompressionStream(memStream))
|
||||
await source.CopyToAsync(compressed).ConfigureAwait(false);
|
||||
memStream.Seek(0, SeekOrigin.Begin);
|
||||
await memStream.CopyToAsync(destination).ConfigureAwait(false);
|
||||
return memStream.Length;
|
||||
}
|
||||
|
||||
public virtual async Task<long> DecompressAsync(Stream source, Stream destination)
|
||||
{
|
||||
using (var memStream = new MemoryStream())
|
||||
{
|
||||
using (var decompressed = CreateDecompressionStream(source))
|
||||
await decompressed.CopyToAsync(memStream).ConfigureAwait(false);
|
||||
memStream.Seek(0, SeekOrigin.Begin);
|
||||
await memStream.CopyToAsync(destination).ConfigureAwait(false);
|
||||
return memStream.Length;
|
||||
}
|
||||
using var memStream = new MemoryStream();
|
||||
using (var decompressed = CreateDecompressionStream(source))
|
||||
await decompressed.CopyToAsync(memStream).ConfigureAwait(false);
|
||||
memStream.Seek(0, SeekOrigin.Begin);
|
||||
await memStream.CopyToAsync(destination).ConfigureAwait(false);
|
||||
return memStream.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,22 +52,18 @@ namespace GithubClient
|
||||
|
||||
try
|
||||
{
|
||||
using (var message = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/repos/RPCS3/rpcs3/pulls/" + pr))
|
||||
using var message = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/repos/RPCS3/rpcs3/pulls/" + pr);
|
||||
message.Headers.UserAgent.Add(ProductInfoHeader);
|
||||
using var response = await client.SendAsync(message, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
message.Headers.UserAgent.Add(ProductInfoHeader);
|
||||
using (var response = await client.SendAsync(message, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
try
|
||||
{
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
UpdateRateLimitStats(response.Headers);
|
||||
result = await response.Content.ReadAsAsync<PrInfo>(formatters, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
}
|
||||
}
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
UpdateRateLimitStats(response.Headers);
|
||||
result = await response.Content.ReadAsAsync<PrInfo>(formatters, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -95,22 +91,18 @@ namespace GithubClient
|
||||
|
||||
try
|
||||
{
|
||||
using (var message = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/repos/RPCS3/rpcs3/issues/" + issue))
|
||||
using var message = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/repos/RPCS3/rpcs3/issues/" + issue);
|
||||
message.Headers.UserAgent.Add(ProductInfoHeader);
|
||||
using var response = await client.SendAsync(message, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
message.Headers.UserAgent.Add(ProductInfoHeader);
|
||||
using (var response = await client.SendAsync(message, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
try
|
||||
{
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
UpdateRateLimitStats(response.Headers);
|
||||
result = await response.Content.ReadAsAsync<IssueInfo>(formatters, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
}
|
||||
}
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
UpdateRateLimitStats(response.Headers);
|
||||
result = await response.Content.ReadAsAsync<IssueInfo>(formatters, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -139,22 +131,18 @@ namespace GithubClient
|
||||
|
||||
try
|
||||
{
|
||||
using (var message = new HttpRequestMessage(HttpMethod.Get, requestUri))
|
||||
using var message = new HttpRequestMessage(HttpMethod.Get, requestUri);
|
||||
message.Headers.UserAgent.Add(ProductInfoHeader);
|
||||
using var response = await client.SendAsync(message, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
message.Headers.UserAgent.Add(ProductInfoHeader);
|
||||
using (var response = await client.SendAsync(message, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
try
|
||||
{
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
UpdateRateLimitStats(response.Headers);
|
||||
result = await response.Content.ReadAsAsync<List<PrInfo>>(formatters, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
}
|
||||
}
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
UpdateRateLimitStats(response.Headers);
|
||||
result = await response.Content.ReadAsAsync<List<PrInfo>>(formatters, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -179,22 +167,18 @@ namespace GithubClient
|
||||
|
||||
try
|
||||
{
|
||||
using (var message = new HttpRequestMessage(HttpMethod.Get, statusesUrl))
|
||||
using var message = new HttpRequestMessage(HttpMethod.Get, statusesUrl);
|
||||
message.Headers.UserAgent.Add(ProductInfoHeader);
|
||||
using var response = await client.SendAsync(message, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
message.Headers.UserAgent.Add(ProductInfoHeader);
|
||||
using (var response = await client.SendAsync(message, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
try
|
||||
{
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
UpdateRateLimitStats(response.Headers);
|
||||
result = await response.Content.ReadAsAsync<List<StatusInfo>>(formatters, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
}
|
||||
}
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
UpdateRateLimitStats(response.Headers);
|
||||
result = await response.Content.ReadAsAsync<List<StatusInfo>>(formatters, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -76,25 +76,25 @@ namespace IrdLibraryClient
|
||||
|
||||
["_"] = DateTime.UtcNow.Ticks.ToString(),
|
||||
});
|
||||
using (var getMessage = new HttpRequestMessage(HttpMethod.Get, requestUri))
|
||||
using (var response = await client.SendAsync(getMessage, cancellationToken).ConfigureAwait(false))
|
||||
try
|
||||
using var getMessage = new HttpRequestMessage(HttpMethod.Get, requestUri);
|
||||
using var response = await client.SendAsync(getMessage, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
var result = await response.Content.ReadAsAsync<SearchResult>(underscoreFormatters, cancellationToken).ConfigureAwait(false);
|
||||
result.Data ??= new List<SearchResultItem>(0);
|
||||
foreach (var item in result.Data)
|
||||
{
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
var result = await response.Content.ReadAsAsync<SearchResult>(underscoreFormatters, cancellationToken).ConfigureAwait(false);
|
||||
result.Data ??= new List<SearchResultItem>(0);
|
||||
foreach (var item in result.Data)
|
||||
{
|
||||
item.Filename = GetIrdFilename(item.Filename);
|
||||
item.Title = GetTitle(item.Title);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
return null;
|
||||
item.Filename = GetIrdFilename(item.Filename);
|
||||
item.Title = GetTitle(item.Title);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -18,62 +18,59 @@ namespace IrdLibraryClient.IrdFormat
|
||||
throw new ArgumentException("Data is too small to be a valid IRD structure", nameof(content));
|
||||
|
||||
if (BitConverter.ToInt32(content, 0) != Ird.Magic)
|
||||
using (var compressedStream = new MemoryStream(content, false))
|
||||
using (var gzip = new GZipStream(compressedStream, CompressionMode.Decompress))
|
||||
using (var decompressedStream = new MemoryStream())
|
||||
{
|
||||
gzip.CopyTo(decompressedStream);
|
||||
content = decompressedStream.ToArray();
|
||||
}
|
||||
{
|
||||
using var compressedStream = new MemoryStream(content, false);
|
||||
using var gzip = new GZipStream(compressedStream, CompressionMode.Decompress);
|
||||
using var decompressedStream = new MemoryStream();
|
||||
gzip.CopyTo(decompressedStream);
|
||||
content = decompressedStream.ToArray();
|
||||
}
|
||||
if (BitConverter.ToInt32(content, 0) != Ird.Magic)
|
||||
throw new FormatException("Not a valid IRD file");
|
||||
|
||||
var result = new Ird();
|
||||
using (var stream = new MemoryStream(content, false))
|
||||
using (var reader = new BinaryReader(stream, Encoding.UTF8))
|
||||
using var stream = new MemoryStream(content, false);
|
||||
using var reader = new BinaryReader(stream, Encoding.UTF8);
|
||||
reader.ReadInt32(); // magic
|
||||
result.Version = reader.ReadByte();
|
||||
result.ProductCode = Encoding.ASCII.GetString(reader.ReadBytes(9));
|
||||
result.TitleLength = reader.ReadByte();
|
||||
result.Title = Encoding.UTF8.GetString(reader.ReadBytes(result.TitleLength));
|
||||
result.UpdateVersion = Encoding.ASCII.GetString(reader.ReadBytes(4)).Trim();
|
||||
result.GameVersion = Encoding.ASCII.GetString(reader.ReadBytes(5)).Trim();
|
||||
result.AppVersion = Encoding.ASCII.GetString(reader.ReadBytes(5)).Trim();
|
||||
if (result.Version == 7)
|
||||
result.Id = reader.ReadInt32();
|
||||
result.HeaderLength = reader.ReadInt32();
|
||||
result.Header = reader.ReadBytes(result.HeaderLength);
|
||||
result.FooterLength = reader.ReadInt32();
|
||||
result.Footer = reader.ReadBytes(result.FooterLength);
|
||||
result.RegionCount = reader.ReadByte();
|
||||
result.RegionMd5Checksums = new List<byte[]>(result.RegionCount);
|
||||
for (var i = 0; i < result.RegionCount; i++)
|
||||
result.RegionMd5Checksums.Add(reader.ReadBytes(16));
|
||||
result.FileCount = reader.ReadInt32();
|
||||
result.Files = new List<IrdFile>(result.FileCount);
|
||||
for (var i = 0; i < result.FileCount; i++)
|
||||
{
|
||||
reader.ReadInt32(); // magic
|
||||
result.Version = reader.ReadByte();
|
||||
result.ProductCode = Encoding.ASCII.GetString(reader.ReadBytes(9));
|
||||
result.TitleLength = reader.ReadByte();
|
||||
result.Title = Encoding.UTF8.GetString(reader.ReadBytes(result.TitleLength));
|
||||
result.UpdateVersion = Encoding.ASCII.GetString(reader.ReadBytes(4)).Trim();
|
||||
result.GameVersion = Encoding.ASCII.GetString(reader.ReadBytes(5)).Trim();
|
||||
result.AppVersion = Encoding.ASCII.GetString(reader.ReadBytes(5)).Trim();
|
||||
if (result.Version == 7)
|
||||
result.Id = reader.ReadInt32();
|
||||
result.HeaderLength = reader.ReadInt32();
|
||||
result.Header = reader.ReadBytes(result.HeaderLength);
|
||||
result.FooterLength = reader.ReadInt32();
|
||||
result.Footer = reader.ReadBytes(result.FooterLength);
|
||||
result.RegionCount = reader.ReadByte();
|
||||
result.RegionMd5Checksums = new List<byte[]>(result.RegionCount);
|
||||
for (var i = 0; i < result.RegionCount; i++)
|
||||
result.RegionMd5Checksums.Add(reader.ReadBytes(16));
|
||||
result.FileCount = reader.ReadInt32();
|
||||
result.Files = new List<IrdFile>(result.FileCount);
|
||||
for (var i = 0; i < result.FileCount; i++)
|
||||
{
|
||||
var file = new IrdFile();
|
||||
file.Offset = reader.ReadInt64();
|
||||
file.Md5Checksum = reader.ReadBytes(16);
|
||||
result.Files.Add(file);
|
||||
}
|
||||
result.Unknown = reader.ReadInt32();
|
||||
if (result.Version == 9)
|
||||
result.Pic = reader.ReadBytes(115);
|
||||
result.Data1 = reader.ReadBytes(16);
|
||||
result.Data2 = reader.ReadBytes(16);
|
||||
if (result.Version < 9)
|
||||
result.Pic = reader.ReadBytes(115);
|
||||
result.Uid = reader.ReadInt32();
|
||||
var dataLength = reader.BaseStream.Position;
|
||||
result.Crc32 = reader.ReadUInt32();
|
||||
|
||||
var crc32 = Crc32Algorithm.Compute(content, 0, (int)dataLength);
|
||||
if (result.Crc32 != crc32)
|
||||
throw new InvalidDataException($"Corrupted IRD data, expected {result.Crc32:x8}, but was {crc32:x8}");
|
||||
var file = new IrdFile();
|
||||
file.Offset = reader.ReadInt64();
|
||||
file.Md5Checksum = reader.ReadBytes(16);
|
||||
result.Files.Add(file);
|
||||
}
|
||||
result.Unknown = reader.ReadInt32();
|
||||
if (result.Version == 9)
|
||||
result.Pic = reader.ReadBytes(115);
|
||||
result.Data1 = reader.ReadBytes(16);
|
||||
result.Data2 = reader.ReadBytes(16);
|
||||
if (result.Version < 9)
|
||||
result.Pic = reader.ReadBytes(115);
|
||||
result.Uid = reader.ReadInt32();
|
||||
var dataLength = reader.BaseStream.Position;
|
||||
result.Crc32 = reader.ReadUInt32();
|
||||
var crc32 = Crc32Algorithm.Compute(content, 0, (int)dataLength);
|
||||
if (result.Crc32 != crc32)
|
||||
throw new InvalidDataException($"Corrupted IRD data, expected {result.Crc32:x8}, but was {crc32:x8}");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,16 +10,16 @@ namespace IrdLibraryClient.IrdFormat
|
||||
{
|
||||
public static List<string> GetFilenames(this Ird ird)
|
||||
{
|
||||
using (var decompressedStream = new MemoryStream())
|
||||
using var decompressedStream = new MemoryStream();
|
||||
using (var compressedStream = new MemoryStream(ird.Header, false))
|
||||
{
|
||||
using (var compressedStream = new MemoryStream(ird.Header, false))
|
||||
using (var gzip = new GZipStream(compressedStream, CompressionMode.Decompress))
|
||||
gzip.CopyTo(decompressedStream);
|
||||
|
||||
decompressedStream.Seek(0, SeekOrigin.Begin);
|
||||
var reader = new CDReader(decompressedStream, true, true);
|
||||
return reader.GetFiles(reader.Root.FullName, "*.*", SearchOption.AllDirectories).Distinct().Select(n => n.TrimStart('\\').Replace('\\', '/').TrimEnd('.')).ToList();
|
||||
using var gzip = new GZipStream(compressedStream, CompressionMode.Decompress);
|
||||
gzip.CopyTo(decompressedStream);
|
||||
}
|
||||
|
||||
decompressedStream.Seek(0, SeekOrigin.Begin);
|
||||
var reader = new CDReader(decompressedStream, true, true);
|
||||
return reader.GetFiles(reader.Root.FullName, "*.*", SearchOption.AllDirectories).Distinct().Select(n => n.TrimStart('\\').Replace('\\', '/').TrimEnd('.')).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,18 +31,16 @@ namespace PsnClient
|
||||
|
||||
foreach (var resource in certNames)
|
||||
{
|
||||
using (var stream = current.GetManifestResourceStream(resource))
|
||||
using (var memStream = new MemoryStream())
|
||||
using var stream = current.GetManifestResourceStream(resource);
|
||||
using var memStream = new MemoryStream();
|
||||
stream.CopyTo(memStream);
|
||||
var cert = new X509Certificate2(memStream.ToArray());
|
||||
var cn = cert.GetNameInfo(X509NameType.SimpleName, false);
|
||||
if ((cn?.StartsWith("SCEI DNAS Root") ?? false))
|
||||
{
|
||||
stream.CopyTo(memStream);
|
||||
var cert = new X509Certificate2(memStream.ToArray());
|
||||
var cn = cert.GetNameInfo(X509NameType.SimpleName, false);
|
||||
if ((cn?.StartsWith("SCEI DNAS Root") ?? false))
|
||||
{
|
||||
CustomCACollecction.Add(cert);
|
||||
ApiConfig.Log.Debug($"Using Sony root CA with CN '{cn}' for custom certificate validation");
|
||||
importedCAs = true;
|
||||
}
|
||||
CustomCACollecction.Add(cert);
|
||||
ApiConfig.Log.Debug($"Using Sony root CA with CN '{cn}' for custom certificate validation");
|
||||
importedCAs = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,24 +73,22 @@ namespace PsnClient
|
||||
result = false;
|
||||
try
|
||||
{
|
||||
using (var customChain = new X509Chain(false))
|
||||
using var customChain = new X509Chain(false);
|
||||
var policy = customChain.ChainPolicy;
|
||||
policy.ExtraStore.AddRange(CustomCACollecction);
|
||||
policy.RevocationMode = X509RevocationMode.NoCheck;
|
||||
if (customChain.Build(certificate) && customChain.ChainStatus.All(s => s.Status == X509ChainStatusFlags.NoError))
|
||||
{
|
||||
var policy = customChain.ChainPolicy;
|
||||
policy.ExtraStore.AddRange(CustomCACollecction);
|
||||
policy.RevocationMode = X509RevocationMode.NoCheck;
|
||||
if (customChain.Build(certificate) && customChain.ChainStatus.All(s => s.Status == X509ChainStatusFlags.NoError))
|
||||
{
|
||||
ApiConfig.Log.Debug($"Successfully validated certificate {thumbprint} for {requestMessage.RequestUri.Host}");
|
||||
result = true;
|
||||
}
|
||||
if (!result)
|
||||
result = customChain.ChainStatus.All(s => s.Status == X509ChainStatusFlags.UntrustedRoot);
|
||||
if (!result)
|
||||
{
|
||||
ApiConfig.Log.Warn($"Failed to validate certificate {thumbprint} for {requestMessage.RequestUri.Host}");
|
||||
foreach (var s in customChain.ChainStatus)
|
||||
ApiConfig.Log.Debug($"{s.Status}: {s.StatusInformation}");
|
||||
}
|
||||
ApiConfig.Log.Debug($"Successfully validated certificate {thumbprint} for {requestMessage.RequestUri.Host}");
|
||||
result = true;
|
||||
}
|
||||
if (!result)
|
||||
result = customChain.ChainStatus.All(s => s.Status == X509ChainStatusFlags.UntrustedRoot);
|
||||
if (!result)
|
||||
{
|
||||
ApiConfig.Log.Warn($"Failed to validate certificate {thumbprint} for {requestMessage.RequestUri.Host}");
|
||||
foreach (var s in customChain.ChainStatus)
|
||||
ApiConfig.Log.Debug($"{s.Status}: {s.StatusInformation}");
|
||||
}
|
||||
ValidationCache[thumbprint] = result;
|
||||
}
|
||||
|
||||
@@ -69,20 +69,18 @@ namespace PsnClient
|
||||
try
|
||||
{
|
||||
var cookieHeaderValue = await GetSessionCookies(locale, cancellationToken).ConfigureAwait(false);
|
||||
using (var getMessage = new HttpRequestMessage(HttpMethod.Get, "https://store.playstation.com/kamaji/api/valkyrie_storefront/00_09_000/user/stores"))
|
||||
using var getMessage = new HttpRequestMessage(HttpMethod.Get, "https://store.playstation.com/kamaji/api/valkyrie_storefront/00_09_000/user/stores");
|
||||
getMessage.Headers.Add("Cookie", cookieHeaderValue);
|
||||
using var response = await client.SendAsync(getMessage, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
getMessage.Headers.Add("Cookie", cookieHeaderValue);
|
||||
using (var response = await client.SendAsync(getMessage, cancellationToken).ConfigureAwait(false))
|
||||
try
|
||||
{
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
return await response.Content.ReadAsAsync<Stores>(underscoreFormatters, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
return null;
|
||||
}
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
return await response.Content.ReadAsAsync<Stores>(underscoreFormatters, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -152,21 +150,21 @@ namespace PsnClient
|
||||
{
|
||||
var loc = locale.AsLocaleData();
|
||||
var baseUrl = $"https://store.playstation.com/valkyrie-api/{loc.language}/{loc.country}/999/storefront/{containerId}";
|
||||
using (var message = new HttpRequestMessage(HttpMethod.Get, baseUrl))
|
||||
using (var response = await client.SendAsync(message, cancellationToken).ConfigureAwait(false))
|
||||
try
|
||||
{
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
return null;
|
||||
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
return await response.Content.ReadAsAsync<StoreNavigation>(dashedFormatters, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
using var message = new HttpRequestMessage(HttpMethod.Get, baseUrl);
|
||||
using var response = await client.SendAsync(message, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
return null;
|
||||
}
|
||||
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
return await response.Content.ReadAsAsync<StoreNavigation>(dashedFormatters, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -186,21 +184,21 @@ namespace PsnClient
|
||||
filters["size"] = take.ToString();
|
||||
filters["bucket"] = "games";
|
||||
url = url.SetQueryParameters(filters);
|
||||
using (var message = new HttpRequestMessage(HttpMethod.Get, url))
|
||||
using (var response = await client.SendAsync(message, cancellationToken).ConfigureAwait(false))
|
||||
try
|
||||
{
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
return null;
|
||||
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
return await response.Content.ReadAsAsync<Container>(dashedFormatters, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
using var message = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
using var response = await client.SendAsync(message, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
return null;
|
||||
}
|
||||
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
return await response.Content.ReadAsAsync<Container>(dashedFormatters, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -214,21 +212,21 @@ namespace PsnClient
|
||||
try
|
||||
{
|
||||
var loc = locale.AsLocaleData();
|
||||
using (var message = new HttpRequestMessage(HttpMethod.Get, $"https://store.playstation.com/valkyrie-api/{loc.language}/{loc.country}/999/resolve/{contentId}?depth={depth}"))
|
||||
using (var response = await client.SendAsync(message, cancellationToken).ConfigureAwait(false))
|
||||
try
|
||||
{
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
return null;
|
||||
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
return await response.Content.ReadAsAsync<Container>(dashedFormatters, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
using var message = new HttpRequestMessage(HttpMethod.Get, $"https://store.playstation.com/valkyrie-api/{loc.language}/{loc.country}/999/resolve/{contentId}?depth={depth}");
|
||||
using var response = await client.SendAsync(message, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
return null;
|
||||
}
|
||||
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
return await response.Content.ReadAsAsync<Container>(dashedFormatters, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -242,23 +240,23 @@ namespace PsnClient
|
||||
if (ResponseCache.TryGetValue(productId, out TitlePatch patchInfo))
|
||||
return patchInfo;
|
||||
|
||||
using (var message = new HttpRequestMessage(HttpMethod.Get, $"https://a0.ww.np.dl.playstation.net/tpl/np/{productId}/{productId}-ver.xml"))
|
||||
using (var response = await client.SendAsync(message, cancellationToken).ConfigureAwait(false))
|
||||
try
|
||||
{
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
return null;
|
||||
using var message = new HttpRequestMessage(HttpMethod.Get, $"https://a0.ww.np.dl.playstation.net/tpl/np/{productId}/{productId}-ver.xml");
|
||||
using var response = await client.SendAsync(message, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
return null;
|
||||
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
patchInfo = await response.Content.ReadAsAsync<TitlePatch>(xmlFormatters, cancellationToken).ConfigureAwait(false);
|
||||
ResponseCache.Set(productId, patchInfo);
|
||||
return patchInfo ?? new TitlePatch { Tag = new TitlePatchTag { Packages = new TitlePatchPackage[0], }, };
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
throw e;
|
||||
}
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
patchInfo = await response.Content.ReadAsAsync<TitlePatch>(xmlFormatters, cancellationToken).ConfigureAwait(false);
|
||||
ResponseCache.Set(productId, patchInfo);
|
||||
return patchInfo ?? new TitlePatch { Tag = new TitlePatchTag { Packages = new TitlePatchPackage[0], }, };
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<TitleMeta> GetTitleMetaAsync(string productId, CancellationToken cancellationToken)
|
||||
@@ -270,23 +268,23 @@ namespace PsnClient
|
||||
var hash = TmdbHasher.GetTitleHash(id);
|
||||
try
|
||||
{
|
||||
using (var message = new HttpRequestMessage(HttpMethod.Get, $"https://tmdb.np.dl.playstation.net/tmdb/{id}_{hash}/{id}.xml"))
|
||||
using (var response = await client.SendAsync(message, cancellationToken).ConfigureAwait(false))
|
||||
try
|
||||
{
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
return null;
|
||||
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
meta = await response.Content.ReadAsAsync<TitleMeta>(xmlFormatters, cancellationToken).ConfigureAwait(false);
|
||||
ResponseCache.Set(id, meta);
|
||||
return meta;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
using var message = new HttpRequestMessage(HttpMethod.Get, $"https://tmdb.np.dl.playstation.net/tmdb/{id}_{hash}/{id}.xml");
|
||||
using var response = await client.SendAsync(message, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
return null;
|
||||
}
|
||||
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
meta = await response.Content.ReadAsAsync<TitleMeta>(xmlFormatters, cancellationToken).ConfigureAwait(false);
|
||||
ResponseCache.Set(id, meta);
|
||||
return meta;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -303,21 +301,21 @@ namespace PsnClient
|
||||
var searchId = Uri.EscapeUriString(search);
|
||||
var queryId = Uri.EscapeDataString(searchId);
|
||||
var uri = new Uri($"https://store.playstation.com/valkyrie-api/{loc.language}/{loc.country}/999/faceted-search/{searchId}?query={queryId}&game_content_type=games&size=30&bucket=games&platform=ps3&start=0");
|
||||
using (var message = new HttpRequestMessage(HttpMethod.Get, uri))
|
||||
using (var response = await client.SendAsync(message, cancellationToken).ConfigureAwait(false))
|
||||
try
|
||||
{
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
return null;
|
||||
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
return await response.Content.ReadAsAsync<Container>(dashedFormatters, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
using var message = new HttpRequestMessage(HttpMethod.Get, uri);
|
||||
using var response = await client.SendAsync(message, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
return null;
|
||||
}
|
||||
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
return await response.Content.ReadAsAsync<Container>(dashedFormatters, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -409,38 +407,38 @@ namespace PsnClient
|
||||
var uri = new Uri($"http://f{fwLocale}01.ps3.update.playstation.net/update/ps3/list/{fwLocale}/ps3-updatelist.txt");
|
||||
try
|
||||
{
|
||||
using (var message = new HttpRequestMessage(HttpMethod.Get, uri))
|
||||
using (var response = await client.SendAsync(message, cancellationToken).ConfigureAwait(false))
|
||||
try
|
||||
using var message = new HttpRequestMessage(HttpMethod.Get, uri);
|
||||
using var response = await client.SendAsync(message, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (response.StatusCode != HttpStatusCode.OK)
|
||||
return null;
|
||||
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
var data = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(data))
|
||||
return null;
|
||||
|
||||
if (FwVersionInfo.Match(data) is Match m && m.Success)
|
||||
{
|
||||
if (response.StatusCode != HttpStatusCode.OK)
|
||||
return null;
|
||||
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
var data = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(data))
|
||||
return null;
|
||||
|
||||
if (FwVersionInfo.Match(data) is Match m && m.Success)
|
||||
var ver = m.Groups["version"].Value;
|
||||
if (!string.IsNullOrEmpty(ver) && ver.Length > 4)
|
||||
{
|
||||
var ver = m.Groups["version"].Value;
|
||||
if (!string.IsNullOrEmpty(ver) && ver.Length > 4)
|
||||
{
|
||||
if (ver.EndsWith("00"))
|
||||
ver = ver[..4]; //4.85
|
||||
else
|
||||
ver = ver[..4] + "." + ver[4..].TrimEnd('0'); //4.851 -> 4.85.1
|
||||
}
|
||||
return new FirmwareInfo { Version = ver, DownloadUrl = m.Groups["url"].Value, Locale = fwLocale};
|
||||
if (ver.EndsWith("00"))
|
||||
ver = ver[..4]; //4.85
|
||||
else
|
||||
ver = ver[..4] + "." + ver[4..].TrimEnd('0'); //4.851 -> 4.85.1
|
||||
}
|
||||
return new FirmwareInfo { Version = ver, DownloadUrl = m.Groups["url"].Value, Locale = fwLocale};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -11,8 +11,8 @@ namespace PsnClient.Utils
|
||||
|
||||
public static string GetTitleHash(string productId)
|
||||
{
|
||||
using (var hmacSha1 = new HMACSHA1(HmacKey))
|
||||
return hmacSha1.ComputeHash(Encoding.ASCII.GetBytes(productId)).ToHexString();
|
||||
using var hmacSha1 = new HMACSHA1(HmacKey);
|
||||
return hmacSha1.ComputeHash(Encoding.ASCII.GetBytes(productId)).ToHexString();
|
||||
}
|
||||
|
||||
public static byte[] FromHexString(this string hexString)
|
||||
|
||||
@@ -13,8 +13,8 @@ namespace CompatBot.Commands.Attributes
|
||||
private const string Source = "https://cdn.discordapp.com/attachments/417347469521715210/534798232858001418/24qx11.jpg";
|
||||
private static readonly Lazy<byte[]> Poster = new Lazy<byte[]>(() =>
|
||||
{
|
||||
using (var client = HttpClientFactory.Create())
|
||||
return client.GetByteArrayAsync(Source).ConfigureAwait(true).GetAwaiter().GetResult();
|
||||
using var client = HttpClientFactory.Create();
|
||||
return client.GetByteArrayAsync(Source).ConfigureAwait(true).GetAwaiter().GetResult();
|
||||
});
|
||||
|
||||
public override async Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help)
|
||||
|
||||
@@ -56,68 +56,66 @@ namespace CompatBot.Commands
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var db = new BotDb())
|
||||
using var db = new BotDb();
|
||||
var timestamps = db.Warning
|
||||
.Where(w => w.Timestamp.HasValue && !w.Retracted)
|
||||
.OrderBy(w => w.Timestamp)
|
||||
.Select(w => w.Timestamp.Value)
|
||||
.ToList();
|
||||
var firstWarnTimestamp = timestamps.FirstOrDefault();
|
||||
var previousTimestamp = firstWarnTimestamp;
|
||||
var longestGapBetweenWarning = 0L;
|
||||
long longestGapStart = 0L, longestGapEnd = 0L;
|
||||
var span24h = TimeSpan.FromHours(24).Ticks;
|
||||
var currentSpan = new Queue<long>();
|
||||
long mostWarningsStart = 0L, mostWarningsEnd = 0L, daysWithoutWarnings = 0L;
|
||||
var mostWarnings = 0;
|
||||
for (var i = 0; i < timestamps.Count; i++)
|
||||
{
|
||||
var timestamps = db.Warning
|
||||
.Where(w => w.Timestamp.HasValue && !w.Retracted)
|
||||
.OrderBy(w => w.Timestamp)
|
||||
.Select(w => w.Timestamp.Value)
|
||||
.ToList();
|
||||
var firstWarnTimestamp = timestamps.FirstOrDefault();
|
||||
var previousTimestamp = firstWarnTimestamp;
|
||||
var longestGapBetweenWarning = 0L;
|
||||
long longestGapStart = 0L, longestGapEnd = 0L;
|
||||
var span24h = TimeSpan.FromHours(24).Ticks;
|
||||
var currentSpan = new Queue<long>();
|
||||
long mostWarningsStart = 0L, mostWarningsEnd = 0L, daysWithoutWarnings = 0L;
|
||||
var mostWarnings = 0;
|
||||
for (var i = 0; i < timestamps.Count; i++)
|
||||
var currentTimestamp = timestamps[i];
|
||||
var newGap = currentTimestamp - previousTimestamp;
|
||||
if (newGap > longestGapBetweenWarning)
|
||||
{
|
||||
var currentTimestamp = timestamps[i];
|
||||
var newGap = currentTimestamp - previousTimestamp;
|
||||
if (newGap > longestGapBetweenWarning)
|
||||
{
|
||||
longestGapBetweenWarning = newGap;
|
||||
longestGapStart = previousTimestamp;
|
||||
longestGapEnd = currentTimestamp;
|
||||
}
|
||||
if (newGap > span24h)
|
||||
daysWithoutWarnings += newGap / span24h;
|
||||
previousTimestamp = currentTimestamp;
|
||||
longestGapBetweenWarning = newGap;
|
||||
longestGapStart = previousTimestamp;
|
||||
longestGapEnd = currentTimestamp;
|
||||
}
|
||||
if (newGap > span24h)
|
||||
daysWithoutWarnings += newGap / span24h;
|
||||
previousTimestamp = currentTimestamp;
|
||||
|
||||
currentSpan.Enqueue(currentTimestamp);
|
||||
while (currentSpan.Count > 0 && currentTimestamp - currentSpan.Peek() > span24h)
|
||||
currentSpan.Dequeue();
|
||||
if (currentSpan.Count > mostWarnings)
|
||||
{
|
||||
mostWarnings = currentSpan.Count;
|
||||
mostWarningsStart = currentSpan.Peek();
|
||||
mostWarningsEnd = currentTimestamp;
|
||||
}
|
||||
}
|
||||
var yesterday = DateTime.UtcNow.AddDays(-1).Ticks;
|
||||
var warnCount = db.Warning.Count(w => w.Timestamp > yesterday);
|
||||
var lastWarn = timestamps.Any() ? timestamps.Last() : (long?)null;
|
||||
if (lastWarn.HasValue)
|
||||
longestGapBetweenWarning = Math.Max(longestGapBetweenWarning, DateTime.UtcNow.Ticks - lastWarn.Value);
|
||||
// most warnings per 24h
|
||||
var statsBuilder = new StringBuilder();
|
||||
if (longestGapBetweenWarning > 0)
|
||||
statsBuilder.AppendLine($@"Longest between warnings: **{TimeSpan.FromTicks(longestGapBetweenWarning).AsShortTimespan()}** between {longestGapStart.AsUtc():yyyy-MM-dd} and {longestGapEnd.AsUtc():yyyy-MM-dd}");
|
||||
if (mostWarnings > 0)
|
||||
statsBuilder.AppendLine($"Most warnings in 24h: **{mostWarnings}** on {mostWarningsEnd.AsUtc():yyyy-MM-dd}");
|
||||
if (daysWithoutWarnings > 0 && firstWarnTimestamp > 0)
|
||||
statsBuilder.AppendLine($"Full days without warnings: **{daysWithoutWarnings}** out of {(DateTime.UtcNow - firstWarnTimestamp.AsUtc()).TotalDays:0}");
|
||||
currentSpan.Enqueue(currentTimestamp);
|
||||
while (currentSpan.Count > 0 && currentTimestamp - currentSpan.Peek() > span24h)
|
||||
currentSpan.Dequeue();
|
||||
if (currentSpan.Count > mostWarnings)
|
||||
{
|
||||
statsBuilder.Append($"Warnings in the last 24h: **{warnCount}**");
|
||||
if (warnCount == 0)
|
||||
statsBuilder.Append(" ").Append(BotReactionsHandler.RandomPositiveReaction);
|
||||
statsBuilder.AppendLine();
|
||||
mostWarnings = currentSpan.Count;
|
||||
mostWarningsStart = currentSpan.Peek();
|
||||
mostWarningsEnd = currentTimestamp;
|
||||
}
|
||||
if (lastWarn.HasValue)
|
||||
statsBuilder.AppendLine($@"Time since last warning: {(DateTime.UtcNow - lastWarn.Value.AsUtc()).AsShortTimespan()}");
|
||||
embed.AddField("Warning stats", statsBuilder.ToString().TrimEnd(), true);
|
||||
}
|
||||
var yesterday = DateTime.UtcNow.AddDays(-1).Ticks;
|
||||
var warnCount = db.Warning.Count(w => w.Timestamp > yesterday);
|
||||
var lastWarn = timestamps.Any() ? timestamps.Last() : (long?)null;
|
||||
if (lastWarn.HasValue)
|
||||
longestGapBetweenWarning = Math.Max(longestGapBetweenWarning, DateTime.UtcNow.Ticks - lastWarn.Value);
|
||||
// most warnings per 24h
|
||||
var statsBuilder = new StringBuilder();
|
||||
if (longestGapBetweenWarning > 0)
|
||||
statsBuilder.AppendLine($@"Longest between warnings: **{TimeSpan.FromTicks(longestGapBetweenWarning).AsShortTimespan()}** between {longestGapStart.AsUtc():yyyy-MM-dd} and {longestGapEnd.AsUtc():yyyy-MM-dd}");
|
||||
if (mostWarnings > 0)
|
||||
statsBuilder.AppendLine($"Most warnings in 24h: **{mostWarnings}** on {mostWarningsEnd.AsUtc():yyyy-MM-dd}");
|
||||
if (daysWithoutWarnings > 0 && firstWarnTimestamp > 0)
|
||||
statsBuilder.AppendLine($"Full days without warnings: **{daysWithoutWarnings}** out of {(DateTime.UtcNow - firstWarnTimestamp.AsUtc()).TotalDays:0}");
|
||||
{
|
||||
statsBuilder.Append($"Warnings in the last 24h: **{warnCount}**");
|
||||
if (warnCount == 0)
|
||||
statsBuilder.Append(" ").Append(BotReactionsHandler.RandomPositiveReaction);
|
||||
statsBuilder.AppendLine();
|
||||
}
|
||||
if (lastWarn.HasValue)
|
||||
statsBuilder.AppendLine($@"Time since last warning: {(DateTime.UtcNow - lastWarn.Value.AsUtc()).AsShortTimespan()}");
|
||||
embed.AddField("Warning stats", statsBuilder.ToString().TrimEnd(), true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -192,21 +190,19 @@ namespace CompatBot.Commands
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var db = new ThumbnailDb())
|
||||
{
|
||||
var syscallCount = db.SyscallInfo.Where(sci => sci.Function.StartsWith("sys_")).Distinct().Count();
|
||||
var syscallModuleCount = db.SyscallInfo.Where(sci => sci.Function.StartsWith("sys_")).Select(sci => sci.Module).Distinct().Count();
|
||||
var totalFuncCount = db.SyscallInfo.Select(sci => sci.Function).Distinct().Count();
|
||||
var totalModuleCount = db.SyscallInfo.Select(sci => sci.Module).Distinct().Count();
|
||||
var fwCallCount = totalFuncCount - syscallCount;
|
||||
var fwModuleCount = totalModuleCount - syscallModuleCount;
|
||||
var gameCount = db.SyscallToProductMap.Select(m => m.ProductId).Distinct().Count();
|
||||
embed.AddField("SceCall Stats",
|
||||
$"Tracked game IDs: {gameCount}\n" +
|
||||
$"Tracked syscalls: {syscallCount} function{(syscallCount == 1 ? "" : "s")} in {syscallModuleCount} module{(syscallModuleCount == 1 ? "" : "s")}\n" +
|
||||
$"Tracked fw calls: {fwCallCount} function{(fwCallCount == 1 ? "" : "s")} in {fwModuleCount} module{(fwModuleCount == 1 ? "" : "s")}\n",
|
||||
true);
|
||||
}
|
||||
using var db = new ThumbnailDb();
|
||||
var syscallCount = db.SyscallInfo.Where(sci => sci.Function.StartsWith("sys_")).Distinct().Count();
|
||||
var syscallModuleCount = db.SyscallInfo.Where(sci => sci.Function.StartsWith("sys_")).Select(sci => sci.Module).Distinct().Count();
|
||||
var totalFuncCount = db.SyscallInfo.Select(sci => sci.Function).Distinct().Count();
|
||||
var totalModuleCount = db.SyscallInfo.Select(sci => sci.Module).Distinct().Count();
|
||||
var fwCallCount = totalFuncCount - syscallCount;
|
||||
var fwModuleCount = totalModuleCount - syscallModuleCount;
|
||||
var gameCount = db.SyscallToProductMap.Select(m => m.ProductId).Distinct().Count();
|
||||
embed.AddField("SceCall Stats",
|
||||
$"Tracked game IDs: {gameCount}\n" +
|
||||
$"Tracked syscalls: {syscallCount} function{(syscallCount == 1 ? "" : "s")} in {syscallModuleCount} module{(syscallModuleCount == 1 ? "" : "s")}\n" +
|
||||
$"Tracked fw calls: {fwCallCount} function{(fwCallCount == 1 ? "" : "s")} in {fwModuleCount} module{(fwModuleCount == 1 ? "" : "s")}\n",
|
||||
true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -218,17 +214,15 @@ namespace CompatBot.Commands
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var db = new BotDb())
|
||||
{
|
||||
var kots = db.Kot.Count();
|
||||
var doggos = db.Doggo.Count();
|
||||
if (kots == 0 && doggos == 0)
|
||||
return;
|
||||
using var db = new BotDb();
|
||||
var kots = db.Kot.Count();
|
||||
var doggos = db.Doggo.Count();
|
||||
if (kots == 0 && doggos == 0)
|
||||
return;
|
||||
|
||||
var diff = kots > doggos ? (double)kots / doggos - 1.0 : (double)doggos / kots - 1.0;
|
||||
var sign = double.IsNaN(diff) || (double.IsFinite(diff) && !double.IsNegative(diff) && diff < 0.05) ? ":" : (kots > doggos ? ">" : "<");
|
||||
embed.AddField("🐾 Stats", $"🐱 {kots - 1} {sign} {doggos - 1} 🐶", true);
|
||||
}
|
||||
var diff = kots > doggos ? (double)kots / doggos - 1.0 : (double)doggos / kots - 1.0;
|
||||
var sign = double.IsNaN(diff) || (double.IsFinite(diff) && !double.IsNegative(diff) && diff < 0.05) ? ":" : (kots > doggos ? ">" : "<");
|
||||
embed.AddField("🐾 Stats", $"🐱 {kots - 1} {sign} {doggos - 1} 🐶", true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -34,8 +34,8 @@ namespace CompatBot.Commands
|
||||
|
||||
static CompatList()
|
||||
{
|
||||
using (var db = new BotDb())
|
||||
lastUpdateInfo = db.BotState.FirstOrDefault(k => k.Key == Rpcs3UpdateStateKey)?.Value;
|
||||
using var db = new BotDb();
|
||||
lastUpdateInfo = db.BotState.FirstOrDefault(k => k.Key == Rpcs3UpdateStateKey)?.Value;
|
||||
}
|
||||
|
||||
[Command("compat"), Aliases("c", "compatibility")]
|
||||
|
||||
@@ -89,76 +89,70 @@ namespace CompatBot.Commands
|
||||
[Description("Adds a new content filter")]
|
||||
public async Task Add(CommandContext ctx, [RemainingText, Description("A plain string to match")] string trigger)
|
||||
{
|
||||
using (var db = new BotDb())
|
||||
using var db = new BotDb();
|
||||
Piracystring filter;
|
||||
if (string.IsNullOrEmpty(trigger))
|
||||
filter = new Piracystring();
|
||||
else
|
||||
{
|
||||
Piracystring filter;
|
||||
if (string.IsNullOrEmpty(trigger))
|
||||
filter = new Piracystring();
|
||||
filter = await db.Piracystring.FirstOrDefaultAsync(ps => ps.String == trigger && ps.Disabled).ConfigureAwait(false);
|
||||
if (filter == null)
|
||||
filter = new Piracystring {String = trigger};
|
||||
else
|
||||
{
|
||||
filter = await db.Piracystring.FirstOrDefaultAsync(ps => ps.String == trigger && ps.Disabled).ConfigureAwait(false);
|
||||
if (filter == null)
|
||||
filter = new Piracystring {String = trigger};
|
||||
else
|
||||
filter.Disabled = false;
|
||||
}
|
||||
var isNewFilter = filter.Id == default;
|
||||
if (isNewFilter)
|
||||
{
|
||||
filter.Context = FilterContext.Chat | FilterContext.Log;
|
||||
filter.Actions = FilterAction.RemoveContent | FilterAction.IssueWarning | FilterAction.SendMessage;
|
||||
}
|
||||
|
||||
var (success, msg) = await EditFilterPropertiesAsync(ctx, db, filter).ConfigureAwait(false);
|
||||
if (success)
|
||||
{
|
||||
if (isNewFilter)
|
||||
await db.Piracystring.AddAsync(filter).ConfigureAwait(false);
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
await msg.UpdateOrCreateMessageAsync(ctx.Channel, embed: FormatFilter(filter).WithTitle("Created a new content filter")).ConfigureAwait(false);
|
||||
var member = ctx.Member ?? ctx.Client.GetMember(ctx.User);
|
||||
var reportMsg = $"{member.GetMentionWithNickname()} added a new content filter: `{filter.String.Sanitize()}`";
|
||||
if (!string.IsNullOrEmpty(filter.ValidatingRegex))
|
||||
reportMsg += $"\nValidation: `{filter.ValidatingRegex}`";
|
||||
await ctx.Client.ReportAsync("🆕 Content filter created", reportMsg, null, ReportSeverity.Low).ConfigureAwait(false);
|
||||
ContentFilter.RebuildMatcher();
|
||||
}
|
||||
else
|
||||
await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Content filter creation aborted").ConfigureAwait(false);
|
||||
filter.Disabled = false;
|
||||
}
|
||||
var isNewFilter = filter.Id == default;
|
||||
if (isNewFilter)
|
||||
{
|
||||
filter.Context = FilterContext.Chat | FilterContext.Log;
|
||||
filter.Actions = FilterAction.RemoveContent | FilterAction.IssueWarning | FilterAction.SendMessage;
|
||||
}
|
||||
|
||||
var (success, msg) = await EditFilterPropertiesAsync(ctx, db, filter).ConfigureAwait(false);
|
||||
if (success)
|
||||
{
|
||||
if (isNewFilter)
|
||||
await db.Piracystring.AddAsync(filter).ConfigureAwait(false);
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
await msg.UpdateOrCreateMessageAsync(ctx.Channel, embed: FormatFilter(filter).WithTitle("Created a new content filter")).ConfigureAwait(false);
|
||||
var member = ctx.Member ?? ctx.Client.GetMember(ctx.User);
|
||||
var reportMsg = $"{member.GetMentionWithNickname()} added a new content filter: `{filter.String.Sanitize()}`";
|
||||
if (!string.IsNullOrEmpty(filter.ValidatingRegex))
|
||||
reportMsg += $"\nValidation: `{filter.ValidatingRegex}`";
|
||||
await ctx.Client.ReportAsync("🆕 Content filter created", reportMsg, null, ReportSeverity.Low).ConfigureAwait(false);
|
||||
ContentFilter.RebuildMatcher();
|
||||
}
|
||||
else
|
||||
await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Content filter creation aborted").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("edit"), Aliases("fix", "update", "change")]
|
||||
[Description("Modifies the specified content filter")]
|
||||
public async Task Edit(CommandContext ctx, [Description("Filter ID")] int id)
|
||||
{
|
||||
using (var db = new BotDb())
|
||||
using var db = new BotDb();
|
||||
var filter = await db.Piracystring.FirstOrDefaultAsync(ps => ps.Id == id && !ps.Disabled).ConfigureAwait(false);
|
||||
if (filter == null)
|
||||
{
|
||||
var filter = await db.Piracystring.FirstOrDefaultAsync(ps => ps.Id == id && !ps.Disabled).ConfigureAwait(false);
|
||||
if (filter == null)
|
||||
{
|
||||
await ctx.RespondAsync("Specified filter does not exist").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await EditFilterCmd(ctx, db, filter).ConfigureAwait(false);
|
||||
await ctx.RespondAsync("Specified filter does not exist").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await EditFilterCmd(ctx, db, filter).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("edit")]
|
||||
public async Task Edit(CommandContext ctx, [Description("Trigger to edit"), RemainingText] string trigger)
|
||||
{
|
||||
using (var db = new BotDb())
|
||||
using var db = new BotDb();
|
||||
var filter = await db.Piracystring.FirstOrDefaultAsync(ps => ps.String.Equals(trigger, StringComparison.InvariantCultureIgnoreCase) && !ps.Disabled).ConfigureAwait(false);
|
||||
if (filter == null)
|
||||
{
|
||||
var filter = await db.Piracystring.FirstOrDefaultAsync(ps => ps.String.Equals(trigger, StringComparison.InvariantCultureIgnoreCase) && !ps.Disabled).ConfigureAwait(false);
|
||||
if (filter == null)
|
||||
{
|
||||
await ctx.RespondAsync("Specified filter does not exist").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await EditFilterCmd(ctx, db, filter).ConfigureAwait(false);
|
||||
await ctx.RespondAsync("Specified filter does not exist").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await EditFilterCmd(ctx, db, filter).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("remove"), Aliases("delete", "del")]
|
||||
|
||||
@@ -28,71 +28,101 @@ namespace CompatBot.Commands
|
||||
var originalEventName = eventName = eventName.Trim(40);
|
||||
var current = DateTime.UtcNow;
|
||||
var currentTicks = current.Ticks;
|
||||
using (var db = new BotDb())
|
||||
using var db = new BotDb();
|
||||
var currentEvents = await db.EventSchedule.OrderBy(e => e.End).Where(e => e.Start <= currentTicks && e.End >= currentTicks).ToListAsync().ConfigureAwait(false);
|
||||
var nextEvent = await db.EventSchedule.OrderBy(e => e.Start).FirstOrDefaultAsync(e => e.Start > currentTicks).ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(eventName))
|
||||
{
|
||||
var currentEvents = await db.EventSchedule.OrderBy(e => e.End).Where(e => e.Start <= currentTicks && e.End >= currentTicks).ToListAsync().ConfigureAwait(false);
|
||||
var nextEvent = await db.EventSchedule.OrderBy(e => e.Start).FirstOrDefaultAsync(e => e.Start > currentTicks).ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(eventName))
|
||||
var nearestEventMsg = "";
|
||||
if (currentEvents.Count > 0)
|
||||
{
|
||||
var nearestEventMsg = "";
|
||||
if (currentEvents.Count > 0)
|
||||
if (currentEvents.Count == 1)
|
||||
nearestEventMsg = $"Current event: {currentEvents[0].Name} (going for {FormatCountdown(current - currentEvents[0].Start.AsUtc())})\n";
|
||||
else
|
||||
{
|
||||
if (currentEvents.Count == 1)
|
||||
nearestEventMsg = $"Current event: {currentEvents[0].Name} (going for {FormatCountdown(current - currentEvents[0].Start.AsUtc())})\n";
|
||||
else
|
||||
{
|
||||
nearestEventMsg = "Current events:\n";
|
||||
foreach (var e in currentEvents)
|
||||
nearestEventMsg += $"{e.Name} (going for {FormatCountdown(current - e.Start.AsUtc())})\n";
|
||||
}
|
||||
nearestEventMsg = "Current events:\n";
|
||||
foreach (var e in currentEvents)
|
||||
nearestEventMsg += $"{e.Name} (going for {FormatCountdown(current - e.Start.AsUtc())})\n";
|
||||
}
|
||||
if (nextEvent != null)
|
||||
nearestEventMsg += $"Next event: {nextEvent.Name} (starts in {FormatCountdown(nextEvent.Start.AsUtc() - current)})";
|
||||
await ctx.RespondAsync(nearestEventMsg.TrimEnd()).ConfigureAwait(false);
|
||||
}
|
||||
if (nextEvent != null)
|
||||
nearestEventMsg += $"Next event: {nextEvent.Name} (starts in {FormatCountdown(nextEvent.Start.AsUtc() - current)})";
|
||||
await ctx.RespondAsync(nearestEventMsg.TrimEnd()).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
eventName = await FuzzyMatchEventName(db, eventName).ConfigureAwait(false);
|
||||
var promo = "";
|
||||
if (currentEvents.Count > 0)
|
||||
promo = $"\nMeanwhile check out this {(string.IsNullOrEmpty(currentEvents[0].EventName) ? "" : currentEvents[0].EventName + " " + currentEvents[0].Year + " ")}event in progress: {currentEvents[0].Name} (going for {FormatCountdown(current - currentEvents[0].Start.AsUtc())})";
|
||||
else if (nextEvent != null)
|
||||
promo = $"\nMeanwhile check out this upcoming {(string.IsNullOrEmpty(nextEvent.EventName) ? "" : nextEvent.EventName + " " + nextEvent.Year + " ")}event: {nextEvent.Name} (starts in {FormatCountdown(nextEvent.Start.AsUtc() - current)})";
|
||||
var firstNamedEvent = await db.EventSchedule.OrderBy(e => e.Start).FirstOrDefaultAsync(e => e.Year >= current.Year && e.EventName == eventName).ConfigureAwait(false);
|
||||
if (firstNamedEvent == null)
|
||||
{
|
||||
var scheduleEntry = await FuzzyMatchEntryName(db, originalEventName).ConfigureAwait(false);
|
||||
var events = await db.EventSchedule.OrderBy(e => e.Start).Where(e => e.End > current.Ticks && e.Name == scheduleEntry).ToListAsync().ConfigureAwait(false);
|
||||
if (events.Any())
|
||||
{
|
||||
var eventListMsg = new StringBuilder();
|
||||
foreach (var eventEntry in events)
|
||||
{
|
||||
if (eventEntry.Start < current.Ticks)
|
||||
eventListMsg.AppendLine($"{eventEntry.Name} ends in {FormatCountdown(eventEntry.End.AsUtc() - current)}");
|
||||
else
|
||||
eventListMsg.AppendLine($"{eventEntry.Name} starts in {FormatCountdown(eventEntry.Start.AsUtc() - current)}");
|
||||
}
|
||||
await ctx.SendAutosplitMessageAsync(eventListMsg.ToString(), blockStart: "", blockEnd: "").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
eventName = await FuzzyMatchEventName(db, eventName).ConfigureAwait(false);
|
||||
var promo = "";
|
||||
if (currentEvents.Count > 0)
|
||||
promo = $"\nMeanwhile check out this {(string.IsNullOrEmpty(currentEvents[0].EventName) ? "" : currentEvents[0].EventName + " " + currentEvents[0].Year + " ")}event in progress: {currentEvents[0].Name} (going for {FormatCountdown(current - currentEvents[0].Start.AsUtc())})";
|
||||
else if (nextEvent != null)
|
||||
promo = $"\nMeanwhile check out this upcoming {(string.IsNullOrEmpty(nextEvent.EventName) ? "" : nextEvent.EventName + " " + nextEvent.Year + " ")}event: {nextEvent.Name} (starts in {FormatCountdown(nextEvent.Start.AsUtc() - current)})";
|
||||
var firstNamedEvent = await db.EventSchedule.OrderBy(e => e.Start).FirstOrDefaultAsync(e => e.Year >= current.Year && e.EventName == eventName).ConfigureAwait(false);
|
||||
if (firstNamedEvent == null)
|
||||
var noEventMsg = $"No information about the upcoming {eventName.Sanitize(replaceBackTicks: true)} at the moment";
|
||||
if (eventName.Length > 10)
|
||||
noEventMsg = "No information about such event at the moment";
|
||||
else if (ctx.User.Id == 259997001880436737ul || ctx.User.Id == 377190919327318018ul)
|
||||
{
|
||||
var scheduleEntry = await FuzzyMatchEntryName(db, originalEventName).ConfigureAwait(false);
|
||||
var events = await db.EventSchedule.OrderBy(e => e.Start).Where(e => e.End > current.Ticks && e.Name == scheduleEntry).ToListAsync().ConfigureAwait(false);
|
||||
if (events.Any())
|
||||
noEventMsg = $"Haha, very funny, {ctx.User.Mention}. So original. Never saw this joke before.";
|
||||
promo = null;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(promo))
|
||||
noEventMsg += promo;
|
||||
await ctx.RespondAsync(noEventMsg).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (firstNamedEvent.Start >= currentTicks)
|
||||
{
|
||||
var upcomingNamedEventMsg = $"__{FormatCountdown(firstNamedEvent.Start.AsUtc() - current)} until {eventName} {firstNamedEvent.Year}!__";
|
||||
if (string.IsNullOrEmpty(promo) || nextEvent?.Id == firstNamedEvent.Id)
|
||||
upcomingNamedEventMsg += $"\nFirst event: {firstNamedEvent.Name}";
|
||||
else
|
||||
upcomingNamedEventMsg += promo;
|
||||
await ctx.RespondAsync(upcomingNamedEventMsg).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var lastNamedEvent = await db.EventSchedule.OrderByDescending(e => e.End).FirstOrDefaultAsync(e => e.Year == current.Year && e.EventName == eventName).ConfigureAwait(false);
|
||||
if (lastNamedEvent.End <= currentTicks)
|
||||
{
|
||||
if (lastNamedEvent.End < current.AddMonths(-1).Ticks)
|
||||
{
|
||||
firstNamedEvent = await db.EventSchedule.OrderBy(e => e.Start).FirstOrDefaultAsync(e => e.Year >= current.Year + 1 && e.EventName == eventName).ConfigureAwait(false);
|
||||
if (firstNamedEvent == null)
|
||||
{
|
||||
var eventListMsg = new StringBuilder();
|
||||
foreach (var eventEntry in events)
|
||||
var noEventMsg = $"No information about the upcoming {eventName.Sanitize(replaceBackTicks: true)} at the moment";
|
||||
if (eventName.Length > 10)
|
||||
noEventMsg = "No information about such event at the moment";
|
||||
else if (ctx.User.Id == 259997001880436737ul || ctx.User.Id == 377190919327318018ul)
|
||||
{
|
||||
if (eventEntry.Start < current.Ticks)
|
||||
eventListMsg.AppendLine($"{eventEntry.Name} ends in {FormatCountdown(eventEntry.End.AsUtc() - current)}");
|
||||
else
|
||||
eventListMsg.AppendLine($"{eventEntry.Name} starts in {FormatCountdown(eventEntry.Start.AsUtc() - current)}");
|
||||
noEventMsg = $"Haha, very funny, {ctx.User.Mention}. So original. Never saw this joke before.";
|
||||
promo = null;
|
||||
}
|
||||
await ctx.SendAutosplitMessageAsync(eventListMsg.ToString(), blockStart: "", blockEnd: "").ConfigureAwait(false);
|
||||
if (!string.IsNullOrEmpty(promo))
|
||||
noEventMsg += promo;
|
||||
await ctx.RespondAsync(noEventMsg).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var noEventMsg = $"No information about the upcoming {eventName.Sanitize(replaceBackTicks: true)} at the moment";
|
||||
if (eventName.Length > 10)
|
||||
noEventMsg = "No information about such event at the moment";
|
||||
else if (ctx.User.Id == 259997001880436737ul || ctx.User.Id == 377190919327318018ul)
|
||||
{
|
||||
noEventMsg = $"Haha, very funny, {ctx.User.Mention}. So original. Never saw this joke before.";
|
||||
promo = null;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(promo))
|
||||
noEventMsg += promo;
|
||||
await ctx.RespondAsync(noEventMsg).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (firstNamedEvent.Start >= currentTicks)
|
||||
{
|
||||
|
||||
var upcomingNamedEventMsg = $"__{FormatCountdown(firstNamedEvent.Start.AsUtc() - current)} until {eventName} {firstNamedEvent.Year}!__";
|
||||
if (string.IsNullOrEmpty(promo) || nextEvent?.Id == firstNamedEvent.Id)
|
||||
upcomingNamedEventMsg += $"\nFirst event: {firstNamedEvent.Name}";
|
||||
@@ -102,53 +132,21 @@ namespace CompatBot.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
var lastNamedEvent = await db.EventSchedule.OrderByDescending(e => e.End).FirstOrDefaultAsync(e => e.Year == current.Year && e.EventName == eventName).ConfigureAwait(false);
|
||||
if (lastNamedEvent.End <= currentTicks)
|
||||
{
|
||||
if (lastNamedEvent.End < current.AddMonths(-1).Ticks)
|
||||
{
|
||||
firstNamedEvent = await db.EventSchedule.OrderBy(e => e.Start).FirstOrDefaultAsync(e => e.Year >= current.Year + 1 && e.EventName == eventName).ConfigureAwait(false);
|
||||
if (firstNamedEvent == null)
|
||||
{
|
||||
var noEventMsg = $"No information about the upcoming {eventName.Sanitize(replaceBackTicks: true)} at the moment";
|
||||
if (eventName.Length > 10)
|
||||
noEventMsg = "No information about such event at the moment";
|
||||
else if (ctx.User.Id == 259997001880436737ul || ctx.User.Id == 377190919327318018ul)
|
||||
{
|
||||
noEventMsg = $"Haha, very funny, {ctx.User.Mention}. So original. Never saw this joke before.";
|
||||
promo = null;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(promo))
|
||||
noEventMsg += promo;
|
||||
await ctx.RespondAsync(noEventMsg).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var upcomingNamedEventMsg = $"__{FormatCountdown(firstNamedEvent.Start.AsUtc() - current)} until {eventName} {firstNamedEvent.Year}!__";
|
||||
if (string.IsNullOrEmpty(promo) || nextEvent?.Id == firstNamedEvent.Id)
|
||||
upcomingNamedEventMsg += $"\nFirst event: {firstNamedEvent.Name}";
|
||||
else
|
||||
upcomingNamedEventMsg += promo;
|
||||
await ctx.RespondAsync(upcomingNamedEventMsg).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var e3EndedMsg = $"__{eventName} {current.Year} has concluded. See you next year! (maybe)__";
|
||||
if (!string.IsNullOrEmpty(promo))
|
||||
e3EndedMsg += promo;
|
||||
await ctx.RespondAsync(e3EndedMsg).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var currentNamedEvent = await db.EventSchedule.OrderBy(e => e.End).FirstOrDefaultAsync(e => e.Start <= currentTicks && e.End >= currentTicks && e.EventName == eventName).ConfigureAwait(false);
|
||||
var nextNamedEvent = await db.EventSchedule.OrderBy(e => e.Start).FirstOrDefaultAsync(e => e.Start > currentTicks && e.EventName == eventName).ConfigureAwait(false);
|
||||
var msg = $"__{eventName} {current.Year} is already in progress!__\n";
|
||||
if (currentNamedEvent != null)
|
||||
msg += $"Current event: {currentNamedEvent.Name} (going for {FormatCountdown(current - currentNamedEvent.Start.AsUtc())})\n";
|
||||
if (nextNamedEvent != null)
|
||||
msg += $"Next event: {nextNamedEvent.Name} (starts in {FormatCountdown(nextNamedEvent.Start.AsUtc() - current)})";
|
||||
await ctx.SendAutosplitMessageAsync(msg.TrimEnd(), blockStart: "", blockEnd: "").ConfigureAwait(false);
|
||||
var e3EndedMsg = $"__{eventName} {current.Year} has concluded. See you next year! (maybe)__";
|
||||
if (!string.IsNullOrEmpty(promo))
|
||||
e3EndedMsg += promo;
|
||||
await ctx.RespondAsync(e3EndedMsg).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var currentNamedEvent = await db.EventSchedule.OrderBy(e => e.End).FirstOrDefaultAsync(e => e.Start <= currentTicks && e.End >= currentTicks && e.EventName == eventName).ConfigureAwait(false);
|
||||
var nextNamedEvent = await db.EventSchedule.OrderBy(e => e.Start).FirstOrDefaultAsync(e => e.Start > currentTicks && e.EventName == eventName).ConfigureAwait(false);
|
||||
var msg = $"__{eventName} {current.Year} is already in progress!__\n";
|
||||
if (currentNamedEvent != null)
|
||||
msg += $"Current event: {currentNamedEvent.Name} (going for {FormatCountdown(current - currentNamedEvent.Start.AsUtc())})\n";
|
||||
if (nextNamedEvent != null)
|
||||
msg += $"Next event: {nextNamedEvent.Name} (starts in {FormatCountdown(nextNamedEvent.Start.AsUtc() - current)})";
|
||||
await ctx.SendAutosplitMessageAsync(msg.TrimEnd(), blockStart: "", blockEnd: "").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
protected async Task Add(CommandContext ctx, string eventName = null)
|
||||
@@ -206,30 +204,28 @@ namespace CompatBot.Commands
|
||||
|
||||
protected async Task Update(CommandContext ctx, int id, string eventName = null)
|
||||
{
|
||||
using (var db = new BotDb())
|
||||
using var db = new BotDb();
|
||||
var evt = eventName == null
|
||||
? db.EventSchedule.FirstOrDefault(e => e.Id == id)
|
||||
: db.EventSchedule.FirstOrDefault(e => e.Id == id && e.EventName == eventName);
|
||||
if (evt == null)
|
||||
{
|
||||
var evt = eventName == null
|
||||
? db.EventSchedule.FirstOrDefault(e => e.Id == id)
|
||||
: db.EventSchedule.FirstOrDefault(e => e.Id == id && e.EventName == eventName);
|
||||
if (evt == null)
|
||||
{
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"No event with id {id}").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"No event with id {id}").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var (success, msg) = await EditEventPropertiesAsync(ctx, evt, eventName).ConfigureAwait(false);
|
||||
if (success)
|
||||
{
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
if (LimitedToSpamChannel.IsSpamChannel(ctx.Channel))
|
||||
await msg.UpdateOrCreateMessageAsync(ctx.Channel, embed: FormatEvent(evt).WithTitle("Updated event schedule entry #" + evt.Id)).ConfigureAwait(false);
|
||||
else
|
||||
await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Updated the schedule entry").ConfigureAwait(false);
|
||||
}
|
||||
var (success, msg) = await EditEventPropertiesAsync(ctx, evt, eventName).ConfigureAwait(false);
|
||||
if (success)
|
||||
{
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
if (LimitedToSpamChannel.IsSpamChannel(ctx.Channel))
|
||||
await msg.UpdateOrCreateMessageAsync(ctx.Channel, embed: FormatEvent(evt).WithTitle("Updated event schedule entry #" + evt.Id)).ConfigureAwait(false);
|
||||
else
|
||||
{
|
||||
await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Event update aborted, changes weren't saved").ConfigureAwait(false);
|
||||
}
|
||||
await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Updated the schedule entry").ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Event update aborted, changes weren't saved").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -144,8 +144,8 @@ namespace CompatBot.Commands
|
||||
attachmentFilename = att.FileName;
|
||||
try
|
||||
{
|
||||
using (var httpClient = HttpClientFactory.Create(new CompressionMessageHandler()))
|
||||
attachment = await httpClient.GetByteArrayAsync(att.Url).ConfigureAwait(false);
|
||||
using var httpClient = HttpClientFactory.Create(new CompressionMessageHandler());
|
||||
attachment = await httpClient.GetByteArrayAsync(att.Url).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -157,21 +157,19 @@ namespace CompatBot.Commands
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, "An explanation for the term must be provided").ConfigureAwait(false);
|
||||
else
|
||||
{
|
||||
using (var db = new BotDb())
|
||||
using var db = new BotDb();
|
||||
if (await db.Explanation.AnyAsync(e => e.Keyword == term).ConfigureAwait(false))
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"`{term}` is already defined. Use `update` to update an existing term.").ConfigureAwait(false);
|
||||
else
|
||||
{
|
||||
if (await db.Explanation.AnyAsync(e => e.Keyword == term).ConfigureAwait(false))
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"`{term}` is already defined. Use `update` to update an existing term.").ConfigureAwait(false);
|
||||
else
|
||||
var entity = new Explanation
|
||||
{
|
||||
var entity = new Explanation
|
||||
{
|
||||
Keyword = term, Text = explanation ?? "", Attachment = attachment,
|
||||
AttachmentFilename = attachmentFilename
|
||||
};
|
||||
await db.Explanation.AddAsync(entity).ConfigureAwait(false);
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, $"`{term}` was successfully added").ConfigureAwait(false);
|
||||
}
|
||||
Keyword = term, Text = explanation ?? "", Attachment = attachment,
|
||||
AttachmentFilename = attachmentFilename
|
||||
};
|
||||
await db.Explanation.AddAsync(entity).ConfigureAwait(false);
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, $"`{term}` was successfully added").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -195,31 +193,29 @@ namespace CompatBot.Commands
|
||||
attachmentFilename = att.FileName;
|
||||
try
|
||||
{
|
||||
using (var httpClient = HttpClientFactory.Create(new CompressionMessageHandler()))
|
||||
attachment = await httpClient.GetByteArrayAsync(att.Url).ConfigureAwait(false);
|
||||
using var httpClient = HttpClientFactory.Create(new CompressionMessageHandler());
|
||||
attachment = await httpClient.GetByteArrayAsync(att.Url).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Config.Log.Warn(e, "Failed to download explanation attachment " + ctx);
|
||||
}
|
||||
}
|
||||
using (var db = new BotDb())
|
||||
using var db = new BotDb();
|
||||
var item = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == term).ConfigureAwait(false);
|
||||
if (item == null)
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"Term `{term}` is not defined").ConfigureAwait(false);
|
||||
else
|
||||
{
|
||||
var item = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == term).ConfigureAwait(false);
|
||||
if (item == null)
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"Term `{term}` is not defined").ConfigureAwait(false);
|
||||
else
|
||||
if (!string.IsNullOrEmpty(explanation))
|
||||
item.Text = explanation;
|
||||
if (attachment?.Length > 0)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(explanation))
|
||||
item.Text = explanation;
|
||||
if (attachment?.Length > 0)
|
||||
{
|
||||
item.Attachment = attachment;
|
||||
item.AttachmentFilename = attachmentFilename;
|
||||
}
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, "Term was updated").ConfigureAwait(false);
|
||||
item.Attachment = attachment;
|
||||
item.AttachmentFilename = attachmentFilename;
|
||||
}
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, "Term was updated").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,19 +226,17 @@ namespace CompatBot.Commands
|
||||
{
|
||||
oldTerm = oldTerm.ToLowerInvariant().StripQuotes();
|
||||
newTerm = newTerm.ToLowerInvariant().StripQuotes();
|
||||
using (var db = new BotDb())
|
||||
using var db = new BotDb();
|
||||
var item = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == oldTerm).ConfigureAwait(false);
|
||||
if (item == null)
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"Term `{oldTerm}` is not defined").ConfigureAwait(false);
|
||||
else if (await db.Explanation.AnyAsync(e => e.Keyword == newTerm).ConfigureAwait(false))
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"Term `{newTerm}` already defined, can't replace it with explanation for `{oldTerm}`").ConfigureAwait(false);
|
||||
else
|
||||
{
|
||||
var item = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == oldTerm).ConfigureAwait(false);
|
||||
if (item == null)
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"Term `{oldTerm}` is not defined").ConfigureAwait(false);
|
||||
else if (await db.Explanation.AnyAsync(e => e.Keyword == newTerm).ConfigureAwait(false))
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"Term `{newTerm}` already defined, can't replace it with explanation for `{oldTerm}`").ConfigureAwait(false);
|
||||
else
|
||||
{
|
||||
item.Keyword = newTerm;
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, $"Renamed `{oldTerm}` to `{newTerm}`").ConfigureAwait(false);
|
||||
}
|
||||
item.Keyword = newTerm;
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, $"Renamed `{oldTerm}` to `{newTerm}`").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,22 +258,20 @@ namespace CompatBot.Commands
|
||||
public async Task List(CommandContext ctx)
|
||||
{
|
||||
var responseChannel = await ctx.GetChannelForSpamAsync().ConfigureAwait(false);
|
||||
using (var db = new BotDb())
|
||||
{
|
||||
var keywords = await db.Explanation.Select(e => e.Keyword).OrderBy(t => t).ToListAsync().ConfigureAwait(false);
|
||||
if (keywords.Count == 0)
|
||||
await ctx.RespondAsync("Nothing has been defined yet").ConfigureAwait(false);
|
||||
else
|
||||
try
|
||||
{
|
||||
foreach (var embed in keywords.BreakInEmbeds(new DiscordEmbedBuilder {Title = TermListTitle, Color = Config.Colors.Help}))
|
||||
await responseChannel.SendMessageAsync(embed: embed).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Config.Log.Error(e);
|
||||
}
|
||||
}
|
||||
using var db = new BotDb();
|
||||
var keywords = await db.Explanation.Select(e => e.Keyword).OrderBy(t => t).ToListAsync().ConfigureAwait(false);
|
||||
if (keywords.Count == 0)
|
||||
await ctx.RespondAsync("Nothing has been defined yet").ConfigureAwait(false);
|
||||
else
|
||||
try
|
||||
{
|
||||
foreach (var embed in keywords.BreakInEmbeds(new DiscordEmbedBuilder {Title = TermListTitle, Color = Config.Colors.Help}))
|
||||
await responseChannel.SendMessageAsync(embed: embed).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Config.Log.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
[Group("remove"), Aliases("delete", "del", "erase", "obliterate"), RequiresBotModRole]
|
||||
@@ -290,17 +282,15 @@ namespace CompatBot.Commands
|
||||
public async Task RemoveExplanation(CommandContext ctx, [RemainingText, Description("Term to remove")] string term)
|
||||
{
|
||||
term = term.ToLowerInvariant().StripQuotes();
|
||||
using (var db = new BotDb())
|
||||
using var db = new BotDb();
|
||||
var item = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == term).ConfigureAwait(false);
|
||||
if (item == null)
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"Term `{term}` is not defined").ConfigureAwait(false);
|
||||
else
|
||||
{
|
||||
var item = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == term).ConfigureAwait(false);
|
||||
if (item == null)
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"Term `{term}` is not defined").ConfigureAwait(false);
|
||||
else
|
||||
{
|
||||
db.Explanation.Remove(item);
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, $"Removed `{term}`").ConfigureAwait(false);
|
||||
}
|
||||
db.Explanation.Remove(item);
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, $"Removed `{term}`").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,22 +299,20 @@ namespace CompatBot.Commands
|
||||
public async Task Attachment(CommandContext ctx, [RemainingText, Description("Term to remove")] string term)
|
||||
{
|
||||
term = term.ToLowerInvariant().StripQuotes();
|
||||
using (var db = new BotDb())
|
||||
using var db = new BotDb();
|
||||
var item = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == term).ConfigureAwait(false);
|
||||
if (item == null)
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"Term `{term}` is not defined").ConfigureAwait(false);
|
||||
else if (string.IsNullOrEmpty(item.AttachmentFilename))
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"Term `{term}` doesn't have any attachments").ConfigureAwait(false);
|
||||
else if (string.IsNullOrEmpty(item.Text))
|
||||
await RemoveExplanation(ctx, term).ConfigureAwait(false);
|
||||
else
|
||||
{
|
||||
var item = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == term).ConfigureAwait(false);
|
||||
if (item == null)
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"Term `{term}` is not defined").ConfigureAwait(false);
|
||||
else if (string.IsNullOrEmpty(item.AttachmentFilename))
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"Term `{term}` doesn't have any attachments").ConfigureAwait(false);
|
||||
else if (string.IsNullOrEmpty(item.Text))
|
||||
await RemoveExplanation(ctx, term).ConfigureAwait(false);
|
||||
else
|
||||
{
|
||||
item.Attachment = null;
|
||||
item.AttachmentFilename = null;
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, $"Removed attachment for `{term}`").ConfigureAwait(false);
|
||||
}
|
||||
item.Attachment = null;
|
||||
item.AttachmentFilename = null;
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, $"Removed attachment for `{term}`").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,21 +321,19 @@ namespace CompatBot.Commands
|
||||
public async Task Text(CommandContext ctx, [RemainingText, Description("Term to remove")] string term)
|
||||
{
|
||||
term = term.ToLowerInvariant().StripQuotes();
|
||||
using (var db = new BotDb())
|
||||
using var db = new BotDb();
|
||||
var item = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == term).ConfigureAwait(false);
|
||||
if (item == null)
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"Term `{term}` is not defined").ConfigureAwait(false);
|
||||
else if (string.IsNullOrEmpty(item.Text))
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"Term `{term}` doesn't have any text").ConfigureAwait(false);
|
||||
else if (string.IsNullOrEmpty(item.AttachmentFilename))
|
||||
await RemoveExplanation(ctx, term).ConfigureAwait(false);
|
||||
else
|
||||
{
|
||||
var item = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == term).ConfigureAwait(false);
|
||||
if (item == null)
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"Term `{term}` is not defined").ConfigureAwait(false);
|
||||
else if (string.IsNullOrEmpty(item.Text))
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"Term `{term}` doesn't have any text").ConfigureAwait(false);
|
||||
else if (string.IsNullOrEmpty(item.AttachmentFilename))
|
||||
await RemoveExplanation(ctx, term).ConfigureAwait(false);
|
||||
else
|
||||
{
|
||||
item.Text = "";
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, $"Removed explanation text for `{term}`").ConfigureAwait(false);
|
||||
}
|
||||
item.Text = "";
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, $"Removed explanation text for `{term}`").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -374,45 +360,44 @@ namespace CompatBot.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
using (var db = new BotDb())
|
||||
using var db = new BotDb();
|
||||
var item = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == termOrLink).ConfigureAwait(false);
|
||||
if (item == null)
|
||||
{
|
||||
var item = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == termOrLink).ConfigureAwait(false);
|
||||
if (item == null)
|
||||
var term = ctx.Message.Content.Split(' ', 2).Last();
|
||||
await ShowExplanation(ctx, term).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrEmpty(item.Text))
|
||||
{
|
||||
var term = ctx.Message.Content.Split(' ', 2).Last();
|
||||
await ShowExplanation(ctx, term).ConfigureAwait(false);
|
||||
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(item.Text));
|
||||
await ctx.Channel.SendFileAsync($"{termOrLink}.txt", stream).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
if (!string.IsNullOrEmpty(item.AttachmentFilename))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(item.Text))
|
||||
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(item.Text)))
|
||||
await ctx.Channel.SendFileAsync($"{termOrLink}.txt", stream).ConfigureAwait(false);
|
||||
if (!string.IsNullOrEmpty(item.AttachmentFilename))
|
||||
using (var stream = new MemoryStream(item.Attachment))
|
||||
await ctx.Channel.SendFileAsync(item.AttachmentFilename, stream).ConfigureAwait(false);
|
||||
using var stream = new MemoryStream(item.Attachment);
|
||||
await ctx.Channel.SendFileAsync(item.AttachmentFilename, stream).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static async Task<(Explanation explanation, string fuzzyMatch, double score)> LookupTerm(string term)
|
||||
{
|
||||
using var db = new BotDb();
|
||||
string fuzzyMatch = null;
|
||||
double coefficient;
|
||||
Explanation explanation;
|
||||
using (var db = new BotDb())
|
||||
var explanation = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == term).ConfigureAwait(false);
|
||||
if (explanation == null)
|
||||
{
|
||||
explanation = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == term).ConfigureAwait(false);
|
||||
if (explanation == null)
|
||||
{
|
||||
var termList = await db.Explanation.Select(e => e.Keyword).ToListAsync().ConfigureAwait(false);
|
||||
var bestSuggestion = termList.OrderByDescending(term.GetFuzzyCoefficientCached).First();
|
||||
coefficient = term.GetFuzzyCoefficientCached(bestSuggestion);
|
||||
explanation = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == bestSuggestion).ConfigureAwait(false);
|
||||
fuzzyMatch = bestSuggestion;
|
||||
}
|
||||
else
|
||||
coefficient = 2.0;
|
||||
var termList = await db.Explanation.Select(e => e.Keyword).ToListAsync().ConfigureAwait(false);
|
||||
var bestSuggestion = termList.OrderByDescending(term.GetFuzzyCoefficientCached).First();
|
||||
coefficient = term.GetFuzzyCoefficientCached(bestSuggestion);
|
||||
explanation = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == bestSuggestion).ConfigureAwait(false);
|
||||
fuzzyMatch = bestSuggestion;
|
||||
}
|
||||
else
|
||||
coefficient = 2.0;
|
||||
return (explanation, fuzzyMatch, coefficient);
|
||||
}
|
||||
|
||||
@@ -432,8 +417,10 @@ namespace CompatBot.Commands
|
||||
if (string.IsNullOrEmpty(explanation))
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, "Couldn't find any text in the specified message").ConfigureAwait(false);
|
||||
else
|
||||
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(explanation)))
|
||||
await ctx.Channel.SendFileAsync("explanation.txt", stream).ConfigureAwait(false);
|
||||
{
|
||||
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(explanation));
|
||||
await ctx.Channel.SendFileAsync("explanation.txt", stream).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,51 +22,49 @@ namespace CompatBot.Commands
|
||||
public async Task List(CommandContext ctx)
|
||||
{
|
||||
const string linkPrefix = "discord.gg/";
|
||||
using (var db = new BotDb())
|
||||
using var db = new BotDb();
|
||||
var whitelistedInvites = await db.WhitelistedInvites.ToListAsync().ConfigureAwait(false);
|
||||
if (whitelistedInvites.Count == 0)
|
||||
{
|
||||
var whitelistedInvites = await db.WhitelistedInvites.ToListAsync().ConfigureAwait(false);
|
||||
if (whitelistedInvites.Count == 0)
|
||||
{
|
||||
await ctx.RespondAsync("There are no whitelisted discord servers").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var table = new AsciiTable(
|
||||
new AsciiColumn("ID", alignToRight: true),
|
||||
new AsciiColumn("Server ID", alignToRight: true),
|
||||
new AsciiColumn("Invite", disabled: !ctx.Channel.IsPrivate),
|
||||
new AsciiColumn("Server Name")
|
||||
);
|
||||
foreach (var item in whitelistedInvites)
|
||||
{
|
||||
string guildName = null;
|
||||
if (!string.IsNullOrEmpty(item.InviteCode))
|
||||
try
|
||||
{
|
||||
var invite = await ctx.Client.GetInviteByCodeAsync(item.InviteCode).ConfigureAwait(false);
|
||||
guildName = invite?.Guild.Name;
|
||||
}
|
||||
catch { }
|
||||
if (string.IsNullOrEmpty(guildName))
|
||||
try
|
||||
{
|
||||
var guild = await ctx.Client.GetGuildAsync(item.GuildId).ConfigureAwait(false);
|
||||
guildName = guild.Name;
|
||||
}
|
||||
catch { }
|
||||
if (string.IsNullOrEmpty(guildName))
|
||||
guildName = item.Name ?? "";
|
||||
var link = "";
|
||||
if (!string.IsNullOrEmpty(item.InviteCode))
|
||||
link = linkPrefix + item.InviteCode;
|
||||
//discord expands invite links even if they're inside the code block for some reason
|
||||
table.Add(item.Id.ToString(), item.GuildId.ToString(), "\u200d" + link, guildName.Sanitize());
|
||||
}
|
||||
var result = new StringBuilder()
|
||||
.AppendLine("Whitelisted discord servers:")
|
||||
.Append(table);
|
||||
await ctx.SendAutosplitMessageAsync(result).ConfigureAwait(false);
|
||||
await ctx.RespondAsync("There are no whitelisted discord servers").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var table = new AsciiTable(
|
||||
new AsciiColumn("ID", alignToRight: true),
|
||||
new AsciiColumn("Server ID", alignToRight: true),
|
||||
new AsciiColumn("Invite", disabled: !ctx.Channel.IsPrivate),
|
||||
new AsciiColumn("Server Name")
|
||||
);
|
||||
foreach (var item in whitelistedInvites)
|
||||
{
|
||||
string guildName = null;
|
||||
if (!string.IsNullOrEmpty(item.InviteCode))
|
||||
try
|
||||
{
|
||||
var invite = await ctx.Client.GetInviteByCodeAsync(item.InviteCode).ConfigureAwait(false);
|
||||
guildName = invite?.Guild.Name;
|
||||
}
|
||||
catch { }
|
||||
if (string.IsNullOrEmpty(guildName))
|
||||
try
|
||||
{
|
||||
var guild = await ctx.Client.GetGuildAsync(item.GuildId).ConfigureAwait(false);
|
||||
guildName = guild.Name;
|
||||
}
|
||||
catch { }
|
||||
if (string.IsNullOrEmpty(guildName))
|
||||
guildName = item.Name ?? "";
|
||||
var link = "";
|
||||
if (!string.IsNullOrEmpty(item.InviteCode))
|
||||
link = linkPrefix + item.InviteCode;
|
||||
//discord expands invite links even if they're inside the code block for some reason
|
||||
table.Add(item.Id.ToString(), item.GuildId.ToString(), "\u200d" + link, guildName.Sanitize());
|
||||
}
|
||||
var result = new StringBuilder()
|
||||
.AppendLine("Whitelisted discord servers:")
|
||||
.Append(table);
|
||||
await ctx.SendAutosplitMessageAsync(result).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("whitelist"), Aliases("add", "allow"), Priority(10)]
|
||||
|
||||
@@ -46,35 +46,31 @@ namespace CompatBot.Commands
|
||||
{
|
||||
await ctx.ReactWithAsync(Config.Reactions.PleaseWait).ConfigureAwait(false);
|
||||
var members = GetMembers(ctx.Client);
|
||||
using (var compressedResult = new MemoryStream())
|
||||
using var compressedResult = new MemoryStream();
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
using (var memoryStream = new MemoryStream())
|
||||
using (var writer = new StreamWriter(memoryStream, new UTF8Encoding(false), 4096, true))
|
||||
{
|
||||
using (var writer = new StreamWriter(memoryStream, new UTF8Encoding(false), 4096, true))
|
||||
{
|
||||
foreach (var member in members)
|
||||
await writer.WriteLineAsync($"{member.Username}\t{member.Nickname}\t{member.JoinedAt:O}\t{(string.Join(',', member.Roles.Select(r => r.Name)))}").ConfigureAwait(false);
|
||||
await writer.FlushAsync().ConfigureAwait(false);
|
||||
}
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
if (memoryStream.Length <= Config.AttachmentSizeLimit)
|
||||
{
|
||||
await ctx.RespondWithFileAsync("names.txt", memoryStream).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
using (var gzip = new GZipStream(compressedResult, CompressionLevel.Optimal, true))
|
||||
{
|
||||
await memoryStream.CopyToAsync(gzip).ConfigureAwait(false);
|
||||
await gzip.FlushAsync().ConfigureAwait(false);
|
||||
}
|
||||
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.FlushAsync().ConfigureAwait(false);
|
||||
}
|
||||
compressedResult.Seek(0, SeekOrigin.Begin);
|
||||
if (compressedResult.Length <= Config.AttachmentSizeLimit)
|
||||
await ctx.RespondWithFileAsync("names.txt.gz", compressedResult).ConfigureAwait(false);
|
||||
else
|
||||
await ctx.RespondAsync($"Dump is too large: {compressedResult.Length} bytes").ConfigureAwait(false);
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
if (memoryStream.Length <= Config.AttachmentSizeLimit)
|
||||
{
|
||||
await ctx.RespondWithFileAsync("names.txt", memoryStream).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
using var gzip = new GZipStream(compressedResult, CompressionLevel.Optimal, true);
|
||||
await memoryStream.CopyToAsync(gzip).ConfigureAwait(false);
|
||||
await gzip.FlushAsync().ConfigureAwait(false);
|
||||
}
|
||||
compressedResult.Seek(0, SeekOrigin.Begin);
|
||||
if (compressedResult.Length <= Config.AttachmentSizeLimit)
|
||||
await ctx.RespondWithFileAsync("names.txt.gz", compressedResult).ConfigureAwait(false);
|
||||
else
|
||||
await ctx.RespondAsync($"Dump is too large: {compressedResult.Length} bytes").ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -208,40 +204,36 @@ namespace CompatBot.Commands
|
||||
checkedMembers.Add(member);
|
||||
}
|
||||
|
||||
using (var compressedStream = new MemoryStream())
|
||||
using var compressedStream = new MemoryStream();
|
||||
using var uncompressedStream = new MemoryStream();
|
||||
using (var writer = new StreamWriter(uncompressedStream, new UTF8Encoding(false), 4096, true))
|
||||
{
|
||||
using (var uncompressedStream = new MemoryStream())
|
||||
{
|
||||
using (var writer = new StreamWriter(uncompressedStream, new UTF8Encoding(false), 4096, true))
|
||||
{
|
||||
writer.Write(result.ToString());
|
||||
writer.Flush();
|
||||
}
|
||||
uncompressedStream.Seek(0, SeekOrigin.Begin);
|
||||
if (result.Length <= headerLength)
|
||||
{
|
||||
await ctx.RespondAsync("No potential name spoofing was detected").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (uncompressedStream.Length <= Config.AttachmentSizeLimit)
|
||||
{
|
||||
await ctx.RespondWithFileAsync("spoofing_check_results.txt", uncompressedStream).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
using (var gzip = new GZipStream(compressedStream, CompressionLevel.Optimal, true))
|
||||
{
|
||||
uncompressedStream.CopyTo(gzip);
|
||||
gzip.Flush();
|
||||
}
|
||||
compressedStream.Seek(0, SeekOrigin.Begin);
|
||||
if (compressedStream.Length <= Config.AttachmentSizeLimit)
|
||||
await ctx.RespondWithFileAsync("spoofing_check_results.txt.gz", compressedStream).ConfigureAwait(false);
|
||||
else
|
||||
await ctx.RespondAsync($"Dump is too large: {compressedStream.Length} bytes").ConfigureAwait(false);
|
||||
}
|
||||
writer.Write(result.ToString());
|
||||
writer.Flush();
|
||||
}
|
||||
uncompressedStream.Seek(0, SeekOrigin.Begin);
|
||||
if (result.Length <= headerLength)
|
||||
{
|
||||
await ctx.RespondAsync("No potential name spoofing was detected").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (uncompressedStream.Length <= Config.AttachmentSizeLimit)
|
||||
{
|
||||
await ctx.RespondWithFileAsync("spoofing_check_results.txt", uncompressedStream).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
using (var gzip = new GZipStream(compressedStream, CompressionLevel.Optimal, true))
|
||||
{
|
||||
uncompressedStream.CopyTo(gzip);
|
||||
gzip.Flush();
|
||||
}
|
||||
compressedStream.Seek(0, SeekOrigin.Begin);
|
||||
if (compressedStream.Length <= Config.AttachmentSizeLimit)
|
||||
await ctx.RespondWithFileAsync("spoofing_check_results.txt.gz", compressedStream).ConfigureAwait(false);
|
||||
else
|
||||
await ctx.RespondAsync($"Dump is too large: {compressedStream.Length} bytes").ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -106,14 +106,12 @@ namespace CompatBot.Commands
|
||||
var productIds = ProductCodeLookup.GetProductIds(search);
|
||||
if (productIds.Count > 0)
|
||||
{
|
||||
using (var db = new ThumbnailDb())
|
||||
{
|
||||
var contentId = await db.Thumbnail.FirstOrDefaultAsync(t => t.ProductCode == productIds[0].ToUpperInvariant()).ConfigureAwait(false);
|
||||
if (contentId?.ContentId != null)
|
||||
titleId = contentId.ContentId;
|
||||
if (contentId?.Name != null)
|
||||
search = contentId.Name;
|
||||
}
|
||||
using var db = new ThumbnailDb();
|
||||
var contentId = await db.Thumbnail.FirstOrDefaultAsync(t => t.ProductCode == productIds[0].ToUpperInvariant()).ConfigureAwait(false);
|
||||
if (contentId?.ContentId != null)
|
||||
titleId = contentId.ContentId;
|
||||
if (contentId?.Name != null)
|
||||
search = contentId.Name;
|
||||
}
|
||||
|
||||
var alteredSearch = search.Trim();
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace CompatBot.Commands
|
||||
[Description("Returns currently checked out bot commit")]
|
||||
public async Task Version(CommandContext ctx)
|
||||
{
|
||||
using (var git = new Process
|
||||
using var git = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo("git", "log -1 --oneline")
|
||||
{
|
||||
@@ -40,14 +40,12 @@ namespace CompatBot.Commands
|
||||
RedirectStandardOutput = true,
|
||||
StandardOutputEncoding = Encoding.UTF8,
|
||||
},
|
||||
})
|
||||
{
|
||||
git.Start();
|
||||
var stdout = await git.StandardOutput.ReadToEndAsync().ConfigureAwait(false);
|
||||
git.WaitForExit();
|
||||
if (!string.IsNullOrEmpty(stdout))
|
||||
await ctx.RespondAsync("```" + stdout + "```").ConfigureAwait(false);
|
||||
}
|
||||
};
|
||||
git.Start();
|
||||
var stdout = await git.StandardOutput.ReadToEndAsync().ConfigureAwait(false);
|
||||
git.WaitForExit();
|
||||
if (!string.IsNullOrEmpty(stdout))
|
||||
await ctx.RespondAsync("```" + stdout + "```").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("update"), Aliases("upgrade", "pull")]
|
||||
@@ -131,31 +129,29 @@ namespace CompatBot.Commands
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var db = new BotDb())
|
||||
using var db = new BotDb();
|
||||
var status = await db.BotState.FirstOrDefaultAsync(s => s.Key == "bot-status-activity").ConfigureAwait(false);
|
||||
var txt = await db.BotState.FirstOrDefaultAsync(s => s.Key == "bot-status-text").ConfigureAwait(false);
|
||||
if (Enum.TryParse(activity, true, out ActivityType activityType)
|
||||
&& !string.IsNullOrEmpty(message))
|
||||
{
|
||||
var status = await db.BotState.FirstOrDefaultAsync(s => s.Key == "bot-status-activity").ConfigureAwait(false);
|
||||
var txt = await db.BotState.FirstOrDefaultAsync(s => s.Key == "bot-status-text").ConfigureAwait(false);
|
||||
if (Enum.TryParse(activity, true, out ActivityType activityType)
|
||||
&& !string.IsNullOrEmpty(message))
|
||||
{
|
||||
if (status == null)
|
||||
await db.BotState.AddAsync(new BotState {Key = "bot-status-activity", Value = activity}).ConfigureAwait(false);
|
||||
else
|
||||
status.Value = activity;
|
||||
if (txt == null)
|
||||
await db.BotState.AddAsync(new BotState {Key = "bot-status-text", Value = message}).ConfigureAwait(false);
|
||||
else
|
||||
txt.Value = message;
|
||||
await ctx.Client.UpdateStatusAsync(new DiscordActivity(message, activityType), UserStatus.Online).ConfigureAwait(false);
|
||||
}
|
||||
if (status == null)
|
||||
await db.BotState.AddAsync(new BotState {Key = "bot-status-activity", Value = activity}).ConfigureAwait(false);
|
||||
else
|
||||
{
|
||||
if (status != null)
|
||||
db.BotState.Remove(status);
|
||||
await ctx.Client.UpdateStatusAsync(new DiscordActivity()).ConfigureAwait(false);
|
||||
}
|
||||
await db.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false);
|
||||
status.Value = activity;
|
||||
if (txt == null)
|
||||
await db.BotState.AddAsync(new BotState {Key = "bot-status-text", Value = message}).ConfigureAwait(false);
|
||||
else
|
||||
txt.Value = message;
|
||||
await ctx.Client.UpdateStatusAsync(new DiscordActivity(message, activityType), UserStatus.Online).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (status != null)
|
||||
db.BotState.Remove(status);
|
||||
await ctx.Client.UpdateStatusAsync(new DiscordActivity()).ConfigureAwait(false);
|
||||
}
|
||||
await db.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -110,22 +110,20 @@ namespace CompatBot.Commands
|
||||
{
|
||||
var logPath = Config.CurrentLogPath;
|
||||
var attachmentSizeLimit = Config.AttachmentSizeLimit;
|
||||
using (var log = File.Open(logPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
using (var result = new MemoryStream((int)Math.Min(attachmentSizeLimit, log.Length)))
|
||||
using var log = File.Open(logPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
using var result = new MemoryStream((int)Math.Min(attachmentSizeLimit, log.Length));
|
||||
using (var gzip = new GZipStream(result, CompressionLevel.Optimal, true))
|
||||
{
|
||||
using (var gzip = new GZipStream(result, CompressionLevel.Optimal, true))
|
||||
{
|
||||
await log.CopyToAsync(gzip, Config.Cts.Token).ConfigureAwait(false);
|
||||
await gzip.FlushAsync().ConfigureAwait(false);
|
||||
}
|
||||
if (result.Length <= attachmentSizeLimit)
|
||||
{
|
||||
result.Seek(0, SeekOrigin.Begin);
|
||||
await ctx.RespondWithFileAsync(Path.GetFileName(logPath) + ".gz", result).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, "Compressed log size is too large, ask Nicba for help :(", true).ConfigureAwait(false);
|
||||
await log.CopyToAsync(gzip, Config.Cts.Token).ConfigureAwait(false);
|
||||
await gzip.FlushAsync().ConfigureAwait(false);
|
||||
}
|
||||
if (result.Length <= attachmentSizeLimit)
|
||||
{
|
||||
result.Seek(0, SeekOrigin.Begin);
|
||||
await ctx.RespondWithFileAsync(Path.GetFileName(logPath) + ".gz", result).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, "Compressed log size is too large, ask Nicba for help :(", true).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -39,101 +39,99 @@ namespace CompatBot.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
using (var db = new ThumbnailDb())
|
||||
using var db = new ThumbnailDb();
|
||||
if (db.SyscallInfo.Any(sci => sci.Module == search || sci.Function == search))
|
||||
{
|
||||
if (db.SyscallInfo.Any(sci => sci.Module == search || sci.Function == search))
|
||||
var productInfoList = db.SyscallToProductMap.AsNoTracking()
|
||||
.Where(m => m.SyscallInfo.Module == search || m.SyscallInfo.Function == search)
|
||||
.Select(m => new {m.Product.ProductCode, Name = m.Product.Name.StripMarks() ?? "???"})
|
||||
.Distinct()
|
||||
.ToList();
|
||||
var groupedList = productInfoList
|
||||
.GroupBy(m => m.Name, m => m.ProductCode, StringComparer.InvariantCultureIgnoreCase)
|
||||
.OrderBy(g => g.Key, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
if (groupedList.Any())
|
||||
{
|
||||
var productInfoList = db.SyscallToProductMap.AsNoTracking()
|
||||
.Where(m => m.SyscallInfo.Module == search || m.SyscallInfo.Function == search)
|
||||
.Select(m => new {m.Product.ProductCode, Name = m.Product.Name.StripMarks() ?? "???"})
|
||||
.Distinct()
|
||||
.ToList();
|
||||
var groupedList = productInfoList
|
||||
.GroupBy(m => m.Name, m => m.ProductCode, StringComparer.InvariantCultureIgnoreCase)
|
||||
.OrderBy(g => g.Key, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
if (groupedList.Any())
|
||||
{
|
||||
var bigList = groupedList.Count >= Config.MaxSyscallResultLines;
|
||||
var bigList = groupedList.Count >= Config.MaxSyscallResultLines;
|
||||
|
||||
var result = new StringBuilder();
|
||||
var fullList = bigList ? new StringBuilder() : null;
|
||||
result.AppendLine($"List of games using `{search}`:```");
|
||||
var c = 0;
|
||||
foreach (var gi in groupedList)
|
||||
{
|
||||
var productIds = string.Join(", ", gi.Distinct().OrderBy(pc => pc).AsEnumerable());
|
||||
if (c < Config.MaxSyscallResultLines)
|
||||
result.AppendLine($"{gi.Key.Trim(60)} [{productIds}]");
|
||||
if (bigList)
|
||||
fullList.AppendLine($"{gi.Key} [{productIds}]");
|
||||
c++;
|
||||
}
|
||||
await ctx.SendAutosplitMessageAsync(result.Append("```")).ConfigureAwait(false);
|
||||
var result = new StringBuilder();
|
||||
var fullList = bigList ? new StringBuilder() : null;
|
||||
result.AppendLine($"List of games using `{search}`:```");
|
||||
var c = 0;
|
||||
foreach (var gi in groupedList)
|
||||
{
|
||||
var productIds = string.Join(", ", gi.Distinct().OrderBy(pc => pc).AsEnumerable());
|
||||
if (c < Config.MaxSyscallResultLines)
|
||||
result.AppendLine($"{gi.Key.Trim(60)} [{productIds}]");
|
||||
if (bigList)
|
||||
{
|
||||
using var memoryStream = new MemoryStream((int)(fullList.Capacity*1.25));
|
||||
using var streamWriter = new StreamWriter(memoryStream, Encoding.UTF8);
|
||||
await streamWriter.WriteAsync(fullList).ConfigureAwait(false);
|
||||
await streamWriter.FlushAsync().ConfigureAwait(false);
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
await ctx.RespondWithFileAsync($"{search}.txt", memoryStream, $"See attached file for full list of {groupedList.Count} entries").ConfigureAwait(false);
|
||||
}
|
||||
fullList.AppendLine($"{gi.Key} [{productIds}]");
|
||||
c++;
|
||||
}
|
||||
await ctx.SendAutosplitMessageAsync(result.Append("```")).ConfigureAwait(false);
|
||||
if (bigList)
|
||||
{
|
||||
using var memoryStream = new MemoryStream((int)(fullList.Capacity*1.25));
|
||||
using var streamWriter = new StreamWriter(memoryStream, Encoding.UTF8);
|
||||
await streamWriter.WriteAsync(fullList).ConfigureAwait(false);
|
||||
await streamWriter.FlushAsync().ConfigureAwait(false);
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
await ctx.RespondWithFileAsync($"{search}.txt", memoryStream, $"See attached file for full list of {groupedList.Count} entries").ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
await ctx.RespondAsync($"No games found that use `{search}`").ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
await ctx.RespondAsync($"No games found that use `{search}`").ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = new StringBuilder("Unknown entity name");
|
||||
var modules = await db.SyscallInfo.Select(sci => sci.Module).Distinct().ToListAsync().ConfigureAwait(false);
|
||||
var substrModules = modules.Where(m => m.Contains(search, StringComparison.CurrentCultureIgnoreCase));
|
||||
var fuzzyModules = modules
|
||||
.Select(m => (name: m, score: search.GetFuzzyCoefficientCached(m)))
|
||||
.Where(i => i.score > 0.6)
|
||||
.OrderByDescending(i => i.score)
|
||||
.Select(i => i.name)
|
||||
.ToList();
|
||||
modules = substrModules
|
||||
.Concat(fuzzyModules)
|
||||
.Distinct()
|
||||
.OrderBy(m => m, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
var modulesFound = modules.Any();
|
||||
if (modulesFound)
|
||||
{
|
||||
var result = new StringBuilder("Unknown entity name");
|
||||
var modules = await db.SyscallInfo.Select(sci => sci.Module).Distinct().ToListAsync().ConfigureAwait(false);
|
||||
var substrModules = modules.Where(m => m.Contains(search, StringComparison.CurrentCultureIgnoreCase));
|
||||
var fuzzyModules = modules
|
||||
.Select(m => (name: m, score: search.GetFuzzyCoefficientCached(m)))
|
||||
.Where(i => i.score > 0.6)
|
||||
.OrderByDescending(i => i.score)
|
||||
.Select(i => i.name)
|
||||
.ToList();
|
||||
modules = substrModules
|
||||
.Concat(fuzzyModules)
|
||||
.Distinct()
|
||||
.OrderBy(m => m, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
var modulesFound = modules.Any();
|
||||
if (modulesFound)
|
||||
{
|
||||
result.AppendLine(", possible modules:```");
|
||||
foreach (var m in modules)
|
||||
result.AppendLine(m);
|
||||
result.AppendLine("```");
|
||||
}
|
||||
|
||||
var functions = await db.SyscallInfo.Select(sci => sci.Function).Distinct().ToListAsync().ConfigureAwait(false);
|
||||
var substrFuncs = functions.Where(f => f.Contains(search, StringComparison.InvariantCultureIgnoreCase));
|
||||
var fuzzyFuncs = functions
|
||||
.Select(f => (name: f, score: search.GetFuzzyCoefficientCached(f)))
|
||||
.Where(i => i.score > 0.6)
|
||||
.OrderByDescending(i => i.score)
|
||||
.Select(i => i.name)
|
||||
.ToList();
|
||||
functions = substrFuncs
|
||||
.Concat(fuzzyFuncs)
|
||||
.Distinct()
|
||||
.OrderBy(f => f, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
var functionsFound = functions.Any();
|
||||
if (functionsFound)
|
||||
{
|
||||
if (modulesFound)
|
||||
result.AppendLine("Possible functions:```");
|
||||
else
|
||||
result.AppendLine(", possible functions:```");
|
||||
foreach (var f in functions)
|
||||
result.AppendLine(f);
|
||||
result.AppendLine("```");
|
||||
}
|
||||
await ctx.SendAutosplitMessageAsync(result).ConfigureAwait(false);
|
||||
result.AppendLine(", possible modules:```");
|
||||
foreach (var m in modules)
|
||||
result.AppendLine(m);
|
||||
result.AppendLine("```");
|
||||
}
|
||||
|
||||
var functions = await db.SyscallInfo.Select(sci => sci.Function).Distinct().ToListAsync().ConfigureAwait(false);
|
||||
var substrFuncs = functions.Where(f => f.Contains(search, StringComparison.InvariantCultureIgnoreCase));
|
||||
var fuzzyFuncs = functions
|
||||
.Select(f => (name: f, score: search.GetFuzzyCoefficientCached(f)))
|
||||
.Where(i => i.score > 0.6)
|
||||
.OrderByDescending(i => i.score)
|
||||
.Select(i => i.name)
|
||||
.ToList();
|
||||
functions = substrFuncs
|
||||
.Concat(fuzzyFuncs)
|
||||
.Distinct()
|
||||
.OrderBy(f => f, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
var functionsFound = functions.Any();
|
||||
if (functionsFound)
|
||||
{
|
||||
if (modulesFound)
|
||||
result.AppendLine("Possible functions:```");
|
||||
else
|
||||
result.AppendLine(", possible functions:```");
|
||||
foreach (var f in functions)
|
||||
result.AppendLine(f);
|
||||
result.AppendLine("```");
|
||||
}
|
||||
await ctx.SendAutosplitMessageAsync(result).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,30 +139,28 @@ namespace CompatBot.Commands
|
||||
{
|
||||
productId = productId.ToUpperInvariant();
|
||||
string title = null;
|
||||
using (var db = new ThumbnailDb())
|
||||
using var db = new ThumbnailDb();
|
||||
title = db.Thumbnail.FirstOrDefault(t => t.ProductCode == productId)?.Name;
|
||||
title = string.IsNullOrEmpty(title) ? productId : $"[{productId}] {title.Trim(40)}";
|
||||
var sysInfoList = db.SyscallToProductMap.AsNoTracking()
|
||||
.Where(m => m.Product.ProductCode == productId)
|
||||
.Select(m => m.SyscallInfo)
|
||||
.Distinct()
|
||||
.AsEnumerable()
|
||||
.OrderBy(sci => sci.Module)
|
||||
.ThenBy(sci => sci.Function)
|
||||
.ToList();
|
||||
if (ctx.User.Id == 216724245957312512UL)
|
||||
sysInfoList = sysInfoList.Where(i => i.Function.StartsWith("sys_", StringComparison.InvariantCultureIgnoreCase)).ToList();
|
||||
if (sysInfoList.Any())
|
||||
{
|
||||
title = db.Thumbnail.FirstOrDefault(t => t.ProductCode == productId)?.Name;
|
||||
title = string.IsNullOrEmpty(title) ? productId : $"[{productId}] {title.Trim(40)}";
|
||||
var sysInfoList = db.SyscallToProductMap.AsNoTracking()
|
||||
.Where(m => m.Product.ProductCode == productId)
|
||||
.Select(m => m.SyscallInfo)
|
||||
.Distinct()
|
||||
.AsEnumerable()
|
||||
.OrderBy(sci => sci.Module)
|
||||
.ThenBy(sci => sci.Function)
|
||||
.ToList();
|
||||
if (ctx.User.Id == 216724245957312512UL)
|
||||
sysInfoList = sysInfoList.Where(i => i.Function.StartsWith("sys_", StringComparison.InvariantCultureIgnoreCase)).ToList();
|
||||
if (sysInfoList.Any())
|
||||
{
|
||||
var result = new StringBuilder($"List of syscalls used by `{title}`:```").AppendLine();
|
||||
foreach (var sci in sysInfoList)
|
||||
result.AppendLine($"{sci.Module}: {sci.Function}");
|
||||
await ctx.SendAutosplitMessageAsync(result.Append("```")).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
await ctx.RespondAsync($"No information available for `{title}`").ConfigureAwait(false);
|
||||
var result = new StringBuilder($"List of syscalls used by `{title}`:```").AppendLine();
|
||||
foreach (var sci in sysInfoList)
|
||||
result.AppendLine($"{sci.Module}: {sci.Function}");
|
||||
await ctx.SendAutosplitMessageAsync(result.Append("```")).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
await ctx.RespondAsync($"No information available for `{title}`").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,21 +125,19 @@ namespace CompatBot.Commands
|
||||
[Description("Changes the state of the warning status")]
|
||||
public async Task Revert(CommandContext ctx, [Description("Warning ID to change")] int id)
|
||||
{
|
||||
using (var db = new BotDb())
|
||||
using var db = new BotDb();
|
||||
var warn = await db.Warning.FirstOrDefaultAsync(w => w.Id == id).ConfigureAwait(false);
|
||||
if (warn.Retracted)
|
||||
{
|
||||
var warn = await db.Warning.FirstOrDefaultAsync(w => w.Id == id).ConfigureAwait(false);
|
||||
if (warn.Retracted)
|
||||
{
|
||||
warn.Retracted = false;
|
||||
warn.RetractedBy = null;
|
||||
warn.RetractionReason = null;
|
||||
warn.RetractionTimestamp = null;
|
||||
await db.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, "Reissued the warning", true).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
await Remove(ctx, id).ConfigureAwait(false);
|
||||
warn.Retracted = false;
|
||||
warn.RetractedBy = null;
|
||||
warn.RetractionReason = null;
|
||||
warn.RetractionTimestamp = null;
|
||||
await db.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, "Reissued the warning", true).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
await Remove(ctx, id).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static async Task<bool> AddAsync(CommandContext ctx, ulong userId, string userName, DiscordUser issuer, string reason, string fullReason = null)
|
||||
|
||||
@@ -50,64 +50,62 @@ namespace CompatBot.Database
|
||||
private static async Task ImportAsync(BotDb dbContext, CancellationToken cancellationToken)
|
||||
{
|
||||
var db = dbContext.Database;
|
||||
using (var tx = await db.BeginTransactionAsync(cancellationToken))
|
||||
using var tx = await db.BeginTransactionAsync(cancellationToken);
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
// __EFMigrationsHistory table will be already created by the failed migration attempt
|
||||
// __EFMigrationsHistory table will be already created by the failed migration attempt
|
||||
#pragma warning disable EF1001 // Internal EF Core API usage.
|
||||
await db.ExecuteSqlRawAsync($"INSERT INTO `__EFMigrationsHistory`(`MigrationId`,`ProductVersion`) VALUES ({new InitialCreate().GetId()},'manual')", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync($"INSERT INTO `__EFMigrationsHistory`(`MigrationId`,`ProductVersion`) VALUES ({new Explanations().GetId()},'manual')", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync($"INSERT INTO `__EFMigrationsHistory`(`MigrationId`,`ProductVersion`) VALUES ({new InitialCreate().GetId()},'manual')", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync($"INSERT INTO `__EFMigrationsHistory`(`MigrationId`,`ProductVersion`) VALUES ({new Explanations().GetId()},'manual')", cancellationToken);
|
||||
#pragma warning restore EF1001 // Internal EF Core API usage.
|
||||
// create constraints on moderator
|
||||
await db.ExecuteSqlRawAsync(@"CREATE TABLE `temp_new_moderator` (
|
||||
// create constraints on moderator
|
||||
await db.ExecuteSqlRawAsync(@"CREATE TABLE `temp_new_moderator` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`discord_id` INTEGER NOT NULL,
|
||||
`sudoer` INTEGER NOT NULL
|
||||
)", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("INSERT INTO temp_new_moderator SELECT `id`,`discord_id`,`sudoer` FROM `moderator`", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("DROP TABLE `moderator`", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("ALTER TABLE `temp_new_moderator` RENAME TO `moderator`", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("CREATE UNIQUE INDEX `moderator_discord_id` ON `moderator` (`discord_id`)", cancellationToken);
|
||||
// create constraints on piracystring
|
||||
await db.ExecuteSqlRawAsync(@"CREATE TABLE `temp_new_piracystring` (
|
||||
await db.ExecuteSqlRawAsync("INSERT INTO temp_new_moderator SELECT `id`,`discord_id`,`sudoer` FROM `moderator`", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("DROP TABLE `moderator`", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("ALTER TABLE `temp_new_moderator` RENAME TO `moderator`", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("CREATE UNIQUE INDEX `moderator_discord_id` ON `moderator` (`discord_id`)", cancellationToken);
|
||||
// create constraints on piracystring
|
||||
await db.ExecuteSqlRawAsync(@"CREATE TABLE `temp_new_piracystring` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`string` varchar ( 255 ) NOT NULL
|
||||
)", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("INSERT INTO temp_new_piracystring SELECT `id`,`string` FROM `piracystring`", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("DROP TABLE `piracystring`", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("ALTER TABLE `temp_new_piracystring` RENAME TO `piracystring`", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("CREATE UNIQUE INDEX `piracystring_string` ON `piracystring` (`string`)", cancellationToken);
|
||||
// create constraints on warning
|
||||
await db.ExecuteSqlRawAsync(@"CREATE TABLE `temp_new_warning` (
|
||||
await db.ExecuteSqlRawAsync("INSERT INTO temp_new_piracystring SELECT `id`,`string` FROM `piracystring`", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("DROP TABLE `piracystring`", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("ALTER TABLE `temp_new_piracystring` RENAME TO `piracystring`", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("CREATE UNIQUE INDEX `piracystring_string` ON `piracystring` (`string`)", cancellationToken);
|
||||
// create constraints on warning
|
||||
await db.ExecuteSqlRawAsync(@"CREATE TABLE `temp_new_warning` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`discord_id` INTEGER NOT NULL,
|
||||
`reason` TEXT NOT NULL,
|
||||
`full_reason` TEXT NOT NULL,
|
||||
`issuer_id` INTEGER NOT NULL DEFAULT 0
|
||||
)", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("INSERT INTO temp_new_warning SELECT `id`,`discord_id`,`reason`,`full_reason`,`issuer_id` FROM `warning`", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("DROP TABLE `warning`", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("ALTER TABLE `temp_new_warning` RENAME TO `warning`", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("CREATE INDEX `warning_discord_id` ON `warning` (`discord_id`)", cancellationToken);
|
||||
// create constraints on explanation
|
||||
await db.ExecuteSqlRawAsync(@"CREATE TABLE `temp_new_explanation` (
|
||||
await db.ExecuteSqlRawAsync("INSERT INTO temp_new_warning SELECT `id`,`discord_id`,`reason`,`full_reason`,`issuer_id` FROM `warning`", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("DROP TABLE `warning`", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("ALTER TABLE `temp_new_warning` RENAME TO `warning`", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("CREATE INDEX `warning_discord_id` ON `warning` (`discord_id`)", cancellationToken);
|
||||
// create constraints on explanation
|
||||
await db.ExecuteSqlRawAsync(@"CREATE TABLE `temp_new_explanation` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`keyword` TEXT NOT NULL,
|
||||
`text` TEXT NOT NULL
|
||||
)", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("INSERT INTO temp_new_explanation SELECT `id`,`keyword`,`text` FROM `explanation`", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("DROP TABLE `explanation`", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("ALTER TABLE `temp_new_explanation` RENAME TO `explanation`", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("CREATE UNIQUE INDEX `explanation_keyword` ON `explanation` (`keyword`)", cancellationToken);
|
||||
tx.Commit();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//tx.Rollback();
|
||||
tx.Commit();
|
||||
throw e;
|
||||
}
|
||||
await db.ExecuteSqlRawAsync("INSERT INTO temp_new_explanation SELECT `id`,`keyword`,`text` FROM `explanation`", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("DROP TABLE `explanation`", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("ALTER TABLE `temp_new_explanation` RENAME TO `explanation`", cancellationToken);
|
||||
await db.ExecuteSqlRawAsync("CREATE UNIQUE INDEX `explanation_keyword` ON `explanation` (`keyword`)", cancellationToken);
|
||||
tx.Commit();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//tx.Rollback();
|
||||
tx.Commit();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ namespace CompatBot.Database.Providers
|
||||
try
|
||||
{
|
||||
using (var httpClient = HttpClientFactory.Create(new CompressionMessageHandler()))
|
||||
using (var response = await httpClient.GetStreamAsync("https://raw.githubusercontent.com/GPUOpen-Drivers/amd-vulkan-versions/master/amdversions.xml").ConfigureAwait(false))
|
||||
{
|
||||
using var response = await httpClient.GetStreamAsync("https://raw.githubusercontent.com/GPUOpen-Drivers/amd-vulkan-versions/master/amdversions.xml").ConfigureAwait(false);
|
||||
var xml = await XDocument.LoadAsync(response, LoadOptions.None, Config.Cts.Token).ConfigureAwait(false);
|
||||
foreach (var driver in xml.Root.Elements("driver"))
|
||||
{
|
||||
|
||||
@@ -11,9 +11,11 @@ namespace CompatBot.Database.Providers
|
||||
static DisabledCommandsProvider()
|
||||
{
|
||||
lock (DisabledCommands)
|
||||
using (var db = new BotDb())
|
||||
foreach (var cmd in db.DisabledCommands.ToList())
|
||||
DisabledCommands.Add(cmd.Command);
|
||||
{
|
||||
using var db = new BotDb();
|
||||
foreach (var cmd in db.DisabledCommands.ToList())
|
||||
DisabledCommands.Add(cmd.Command);
|
||||
}
|
||||
}
|
||||
|
||||
public static HashSet<string> Get() => DisabledCommands;
|
||||
@@ -22,26 +24,26 @@ namespace CompatBot.Database.Providers
|
||||
{
|
||||
lock (DisabledCommands)
|
||||
if (DisabledCommands.Add(command))
|
||||
using (var db = new BotDb())
|
||||
{
|
||||
db.DisabledCommands.Add(new DisabledCommand {Command = command});
|
||||
db.SaveChanges();
|
||||
}
|
||||
{
|
||||
using var db = new BotDb();
|
||||
db.DisabledCommands.Add(new DisabledCommand {Command = command});
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Enable(string command)
|
||||
{
|
||||
lock (DisabledCommands)
|
||||
if (DisabledCommands.Remove(command))
|
||||
using (var db = new BotDb())
|
||||
{
|
||||
var cmd = db.DisabledCommands.FirstOrDefault(c => c.Command == command);
|
||||
if (cmd == null)
|
||||
return;
|
||||
{
|
||||
using var db = new BotDb();
|
||||
var cmd = db.DisabledCommands.FirstOrDefault(c => c.Command == command);
|
||||
if (cmd == null)
|
||||
return;
|
||||
|
||||
db.DisabledCommands.Remove(cmd);
|
||||
db.SaveChanges();
|
||||
}
|
||||
db.DisabledCommands.Remove(cmd);
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Clear()
|
||||
@@ -49,11 +51,9 @@ namespace CompatBot.Database.Providers
|
||||
lock (DisabledCommands)
|
||||
{
|
||||
DisabledCommands.Clear();
|
||||
using (var db = new BotDb())
|
||||
{
|
||||
db.DisabledCommands.RemoveRange(db.DisabledCommands);
|
||||
db.SaveChanges();
|
||||
}
|
||||
using var db = new BotDb();
|
||||
db.DisabledCommands.RemoveRange(db.DisabledCommands);
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,27 +12,25 @@ namespace CompatBot.Database.Providers
|
||||
{
|
||||
public static bool IsWhitelisted(ulong guildId)
|
||||
{
|
||||
using (var db = new BotDb())
|
||||
return db.WhitelistedInvites.Any(i => i.GuildId == guildId);
|
||||
using var db = new BotDb();
|
||||
return db.WhitelistedInvites.Any(i => i.GuildId == guildId);
|
||||
}
|
||||
|
||||
public static async Task<bool> IsWhitelistedAsync(DiscordInvite invite)
|
||||
{
|
||||
var code = string.IsNullOrWhiteSpace(invite.Code) ? null : invite.Code;
|
||||
var name = string.IsNullOrWhiteSpace(invite.Guild.Name) ? null : invite.Guild.Name;
|
||||
using (var db = new BotDb())
|
||||
{
|
||||
var whitelistedInvite = await db.WhitelistedInvites.FirstOrDefaultAsync(i => i.GuildId == invite.Guild.Id);
|
||||
if (whitelistedInvite == null)
|
||||
return false;
|
||||
using var db = new BotDb();
|
||||
var whitelistedInvite = await db.WhitelistedInvites.FirstOrDefaultAsync(i => i.GuildId == invite.Guild.Id);
|
||||
if (whitelistedInvite == null)
|
||||
return false;
|
||||
|
||||
if (name != null && name != whitelistedInvite.Name)
|
||||
whitelistedInvite.Name = invite.Guild.Name;
|
||||
if (string.IsNullOrEmpty(whitelistedInvite.InviteCode) && code != null)
|
||||
whitelistedInvite.InviteCode = code;
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
if (name != null && name != whitelistedInvite.Name)
|
||||
whitelistedInvite.Name = invite.Guild.Name;
|
||||
if (string.IsNullOrEmpty(whitelistedInvite.InviteCode) && code != null)
|
||||
whitelistedInvite.InviteCode = code;
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static async Task<bool> AddAsync(DiscordInvite invite)
|
||||
@@ -65,16 +63,14 @@ namespace CompatBot.Database.Providers
|
||||
|
||||
public static async Task<bool> RemoveAsync(int id)
|
||||
{
|
||||
using (var db = new BotDb())
|
||||
{
|
||||
var dbItem = await db.WhitelistedInvites.FirstOrDefaultAsync(i => i.Id == id).ConfigureAwait(false);
|
||||
if (dbItem == null)
|
||||
return false;
|
||||
using var db = new BotDb();
|
||||
var dbItem = await db.WhitelistedInvites.FirstOrDefaultAsync(i => i.Id == id).ConfigureAwait(false);
|
||||
if (dbItem == null)
|
||||
return false;
|
||||
|
||||
db.WhitelistedInvites.Remove(dbItem);
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
db.WhitelistedInvites.Remove(dbItem);
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static async Task CleanupAsync(DiscordClient client)
|
||||
|
||||
@@ -48,30 +48,26 @@ namespace CompatBot.Database.Providers
|
||||
throw new ArgumentException(nameof(locale));
|
||||
|
||||
var id = GetId(locale, containerId);
|
||||
using (var db = new ThumbnailDb())
|
||||
{
|
||||
var timestamp = db.State.FirstOrDefault(s => s.Locale == id);
|
||||
if (timestamp == null)
|
||||
db.State.Add(new State {Locale = id, Timestamp = DateTime.UtcNow.Ticks});
|
||||
else
|
||||
timestamp.Timestamp = DateTime.UtcNow.Ticks;
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
using var db = new ThumbnailDb();
|
||||
var timestamp = db.State.FirstOrDefault(s => s.Locale == id);
|
||||
if (timestamp == null)
|
||||
db.State.Add(new State {Locale = id, Timestamp = DateTime.UtcNow.Ticks});
|
||||
else
|
||||
timestamp.Timestamp = DateTime.UtcNow.Ticks;
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static async Task CleanAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
using (var db = new ThumbnailDb())
|
||||
{
|
||||
var latestTimestamp = db.State.OrderByDescending(s => s.Timestamp).FirstOrDefault()?.Timestamp;
|
||||
if (!latestTimestamp.HasValue)
|
||||
return;
|
||||
using var db = new ThumbnailDb();
|
||||
var latestTimestamp = db.State.OrderByDescending(s => s.Timestamp).FirstOrDefault()?.Timestamp;
|
||||
if (!latestTimestamp.HasValue)
|
||||
return;
|
||||
|
||||
var cutOff = new DateTime(latestTimestamp.Value, DateTimeKind.Utc).Add(-CheckInterval);
|
||||
var oldItems = db.State.Where(s => s.Timestamp < cutOff.Ticks);
|
||||
db.State.RemoveRange(oldItems);
|
||||
await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
var cutOff = new DateTime(latestTimestamp.Value, DateTimeKind.Utc).Add(-CheckInterval);
|
||||
var oldItems = db.State.Where(s => s.Timestamp < cutOff.Ticks);
|
||||
db.State.RemoveRange(oldItems);
|
||||
await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static string GetId(string locale, string containerId)
|
||||
|
||||
@@ -31,29 +31,27 @@ namespace CompatBot.Database.Providers
|
||||
try
|
||||
{
|
||||
Config.Log.Debug("Got stats saving lock");
|
||||
using (var db = new BotDb())
|
||||
using var db = new BotDb();
|
||||
db.Stats.RemoveRange(db.Stats);
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
foreach (var cache in AllCaches)
|
||||
{
|
||||
db.Stats.RemoveRange(db.Stats);
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
foreach (var cache in AllCaches)
|
||||
{
|
||||
var category = cache.name;
|
||||
var entries = cache.cache.GetCacheEntries<string>();
|
||||
var savedKeys = new HashSet<string>();
|
||||
foreach (var entry in entries)
|
||||
if (savedKeys.Add(entry.Key))
|
||||
await db.Stats.AddAsync(new Stats
|
||||
{
|
||||
Category = category,
|
||||
Key = entry.Key,
|
||||
Value = ((int?) entry.Value.Value) ?? 0,
|
||||
ExpirationTimestamp = entry.Value.AbsoluteExpiration?.ToUniversalTime().Ticks ?? 0
|
||||
}).ConfigureAwait(false);
|
||||
else
|
||||
Config.Log.Warn($"Somehow there's another '{entry.Key}' in the {category} cache");
|
||||
}
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
var category = cache.name;
|
||||
var entries = cache.cache.GetCacheEntries<string>();
|
||||
var savedKeys = new HashSet<string>();
|
||||
foreach (var entry in entries)
|
||||
if (savedKeys.Add(entry.Key))
|
||||
await db.Stats.AddAsync(new Stats
|
||||
{
|
||||
Category = category,
|
||||
Key = entry.Key,
|
||||
Value = ((int?) entry.Value.Value) ?? 0,
|
||||
ExpirationTimestamp = entry.Value.AbsoluteExpiration?.ToUniversalTime().Ticks ?? 0
|
||||
}).ConfigureAwait(false);
|
||||
else
|
||||
Config.Log.Warn($"Somehow there's another '{entry.Key}' in the {category} cache");
|
||||
}
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@@ -75,18 +73,18 @@ namespace CompatBot.Database.Providers
|
||||
public static async Task RestoreAsync()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
using (var db = new BotDb())
|
||||
foreach (var cache in AllCaches)
|
||||
using var db = new BotDb();
|
||||
foreach (var cache in AllCaches)
|
||||
{
|
||||
var category = cache.name;
|
||||
var entries = await db.Stats.Where(e => e.Category == category).ToListAsync().ConfigureAwait(false);
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
var category = cache.name;
|
||||
var entries = await db.Stats.Where(e => e.Category == category).ToListAsync().ConfigureAwait(false);
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
var time = entry.ExpirationTimestamp.AsUtc();
|
||||
if (time > now)
|
||||
cache.cache.Set(entry.Key, entry.Value, time);
|
||||
}
|
||||
var time = entry.ExpirationTimestamp.AsUtc();
|
||||
if (time > now)
|
||||
cache.cache.Set(entry.Key, entry.Value, time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task BackgroundSaveAsync()
|
||||
|
||||
@@ -23,29 +23,27 @@ namespace CompatBot.Database.Providers
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var db = new ThumbnailDb())
|
||||
using var db = new ThumbnailDb();
|
||||
foreach (var productCodeMap in syscallInfo)
|
||||
{
|
||||
foreach (var productCodeMap in syscallInfo)
|
||||
var product = db.Thumbnail.AsNoTracking().FirstOrDefault(t => t.ProductCode == productCodeMap.Key)
|
||||
?? db.Thumbnail.Add(new Thumbnail {ProductCode = productCodeMap.Key}).Entity;
|
||||
if (product.Id == 0)
|
||||
await db.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false);
|
||||
|
||||
foreach (var moduleMap in productCodeMap.Value)
|
||||
foreach (var func in moduleMap.Value)
|
||||
{
|
||||
var product = db.Thumbnail.AsNoTracking().FirstOrDefault(t => t.ProductCode == productCodeMap.Key)
|
||||
?? db.Thumbnail.Add(new Thumbnail {ProductCode = productCodeMap.Key}).Entity;
|
||||
if (product.Id == 0)
|
||||
var syscall = db.SyscallInfo.AsNoTracking().FirstOrDefault(sci => sci.Module == moduleMap.Key.ToUtf8() && sci.Function == func.ToUtf8())
|
||||
?? db.SyscallInfo.Add(new SyscallInfo {Module = moduleMap.Key.ToUtf8(), Function = func.ToUtf8() }).Entity;
|
||||
if (syscall.Id == 0)
|
||||
await db.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false);
|
||||
|
||||
foreach (var moduleMap in productCodeMap.Value)
|
||||
foreach (var func in moduleMap.Value)
|
||||
{
|
||||
var syscall = db.SyscallInfo.AsNoTracking().FirstOrDefault(sci => sci.Module == moduleMap.Key.ToUtf8() && sci.Function == func.ToUtf8())
|
||||
?? db.SyscallInfo.Add(new SyscallInfo {Module = moduleMap.Key.ToUtf8(), Function = func.ToUtf8() }).Entity;
|
||||
if (syscall.Id == 0)
|
||||
await db.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false);
|
||||
|
||||
if (!db.SyscallToProductMap.Any(m => m.ProductId == product.Id && m.SyscallInfoId == syscall.Id))
|
||||
db.SyscallToProductMap.Add(new SyscallToProductMap {ProductId = product.Id, SyscallInfoId = syscall.Id});
|
||||
}
|
||||
if (!db.SyscallToProductMap.Any(m => m.ProductId == product.Id && m.SyscallInfoId == syscall.Id))
|
||||
db.SyscallToProductMap.Add(new SyscallToProductMap {ProductId = product.Id, SyscallInfoId = syscall.Id});
|
||||
}
|
||||
await db.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false);
|
||||
}
|
||||
await db.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -70,41 +70,39 @@ namespace CompatBot.Database.Providers
|
||||
return null;
|
||||
|
||||
productCode = productCode.ToUpperInvariant();
|
||||
using (var db = new ThumbnailDb())
|
||||
{
|
||||
var thumb = await db.Thumbnail.FirstOrDefaultAsync(
|
||||
t => t.ProductCode == productCode,
|
||||
cancellationToken: cancellationToken
|
||||
).ConfigureAwait(false);
|
||||
if (thumb?.Name is string title)
|
||||
return title;
|
||||
|
||||
var meta = await PsnClient.GetTitleMetaAsync(productCode, cancellationToken).ConfigureAwait(false);
|
||||
title = meta?.Name;
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(title))
|
||||
{
|
||||
if (thumb == null)
|
||||
thumb = (
|
||||
await db.Thumbnail.AddAsync(new Thumbnail
|
||||
{
|
||||
ProductCode = productCode,
|
||||
Name = title,
|
||||
}, cancellationToken).ConfigureAwait(false)
|
||||
).Entity;
|
||||
else
|
||||
thumb.Name = title;
|
||||
await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Config.Log.Warn(e);
|
||||
}
|
||||
|
||||
using var db = new ThumbnailDb();
|
||||
var thumb = await db.Thumbnail.FirstOrDefaultAsync(
|
||||
t => t.ProductCode == productCode,
|
||||
cancellationToken: cancellationToken
|
||||
).ConfigureAwait(false);
|
||||
if (thumb?.Name is string title)
|
||||
return title;
|
||||
|
||||
var meta = await PsnClient.GetTitleMetaAsync(productCode, cancellationToken).ConfigureAwait(false);
|
||||
title = meta?.Name;
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(title))
|
||||
{
|
||||
if (thumb == null)
|
||||
thumb = (
|
||||
await db.Thumbnail.AddAsync(new Thumbnail
|
||||
{
|
||||
ProductCode = productCode,
|
||||
Name = title,
|
||||
}, cancellationToken).ConfigureAwait(false)
|
||||
).Entity;
|
||||
else
|
||||
thumb.Name = title;
|
||||
await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Config.Log.Warn(e);
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
public static async Task<(string url, DiscordColor color)> GetThumbnailUrlWithColorAsync(DiscordClient client, string contentId, DiscordColor defaultColor, string url = null)
|
||||
@@ -113,50 +111,48 @@ namespace CompatBot.Database.Providers
|
||||
throw new ArgumentException("ContentID can't be empty", nameof(contentId));
|
||||
|
||||
contentId = contentId.ToUpperInvariant();
|
||||
using (var db = new ThumbnailDb())
|
||||
using var db = new ThumbnailDb();
|
||||
var info = await db.TitleInfo.FirstOrDefaultAsync(ti => ti.ContentId == contentId, Config.Cts.Token).ConfigureAwait(false);
|
||||
if (info == null)
|
||||
{
|
||||
var info = await db.TitleInfo.FirstOrDefaultAsync(ti => ti.ContentId == contentId, Config.Cts.Token).ConfigureAwait(false);
|
||||
if (info == null)
|
||||
info = new TitleInfo {ContentId = contentId, ThumbnailUrl = url, Timestamp = DateTime.UtcNow.Ticks};
|
||||
var thumb = await db.Thumbnail.FirstOrDefaultAsync(t => t.ContentId == contentId).ConfigureAwait(false);
|
||||
if (thumb?.EmbeddableUrl is string eUrl
|
||||
&& thumb.Url is string thumbUrl
|
||||
&& thumbUrl == url)
|
||||
info.ThumbnailEmbeddableUrl = eUrl;
|
||||
info = db.TitleInfo.Add(info).Entity;
|
||||
await db.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false);
|
||||
}
|
||||
DiscordColor? analyzedColor = null;
|
||||
if (string.IsNullOrEmpty(info.ThumbnailEmbeddableUrl))
|
||||
{
|
||||
var em = await GetEmbeddableUrlAsync(client, contentId, info.ThumbnailUrl).ConfigureAwait(false);
|
||||
if (em.url is string eUrl)
|
||||
{
|
||||
info = new TitleInfo {ContentId = contentId, ThumbnailUrl = url, Timestamp = DateTime.UtcNow.Ticks};
|
||||
var thumb = await db.Thumbnail.FirstOrDefaultAsync(t => t.ContentId == contentId).ConfigureAwait(false);
|
||||
if (thumb?.EmbeddableUrl is string eUrl
|
||||
&& thumb.Url is string thumbUrl
|
||||
&& thumbUrl == url)
|
||||
info.ThumbnailEmbeddableUrl = eUrl;
|
||||
info = db.TitleInfo.Add(info).Entity;
|
||||
info.ThumbnailEmbeddableUrl = eUrl;
|
||||
if (em.image is byte[] jpg)
|
||||
{
|
||||
analyzedColor = ColorGetter.Analyze(jpg, defaultColor);
|
||||
var c = analyzedColor.Value.Value;
|
||||
if (c != defaultColor.Value)
|
||||
info.EmbedColor = c;
|
||||
}
|
||||
await db.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false);
|
||||
}
|
||||
DiscordColor? analyzedColor = null;
|
||||
if (string.IsNullOrEmpty(info.ThumbnailEmbeddableUrl))
|
||||
{
|
||||
var em = await GetEmbeddableUrlAsync(client, contentId, info.ThumbnailUrl).ConfigureAwait(false);
|
||||
if (em.url is string eUrl)
|
||||
{
|
||||
info.ThumbnailEmbeddableUrl = eUrl;
|
||||
if (em.image is byte[] jpg)
|
||||
{
|
||||
analyzedColor = ColorGetter.Analyze(jpg, defaultColor);
|
||||
var c = analyzedColor.Value.Value;
|
||||
if (c != defaultColor.Value)
|
||||
info.EmbedColor = c;
|
||||
}
|
||||
await db.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
if ((!info.EmbedColor.HasValue && !analyzedColor.HasValue)
|
||||
|| (info.EmbedColor.HasValue && info.EmbedColor.Value == defaultColor.Value))
|
||||
{
|
||||
var c = await GetImageColorAsync(info.ThumbnailEmbeddableUrl, defaultColor).ConfigureAwait(false);
|
||||
if (c.HasValue && c.Value.Value != defaultColor.Value)
|
||||
{
|
||||
info.EmbedColor = c.Value.Value;
|
||||
await db.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
var color = info.EmbedColor.HasValue ? new DiscordColor(info.EmbedColor.Value) : defaultColor;
|
||||
return (info.ThumbnailEmbeddableUrl, color);
|
||||
}
|
||||
if ((!info.EmbedColor.HasValue && !analyzedColor.HasValue)
|
||||
|| (info.EmbedColor.HasValue && info.EmbedColor.Value == defaultColor.Value))
|
||||
{
|
||||
var c = await GetImageColorAsync(info.ThumbnailEmbeddableUrl, defaultColor).ConfigureAwait(false);
|
||||
if (c.HasValue && c.Value.Value != defaultColor.Value)
|
||||
{
|
||||
info.EmbedColor = c.Value.Value;
|
||||
await db.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
var color = info.EmbedColor.HasValue ? new DiscordColor(info.EmbedColor.Value) : defaultColor;
|
||||
return (info.ThumbnailEmbeddableUrl, color);
|
||||
}
|
||||
|
||||
public static async Task<(string url, byte[] image)> GetEmbeddableUrlAsync(DiscordClient client, string contentId, string url)
|
||||
@@ -166,20 +162,18 @@ namespace CompatBot.Database.Providers
|
||||
if (!string.IsNullOrEmpty(Path.GetExtension(url)))
|
||||
return (url, null);
|
||||
|
||||
using (var imgStream = await HttpClient.GetStreamAsync(url).ConfigureAwait(false))
|
||||
using (var memStream = new MemoryStream())
|
||||
{
|
||||
await imgStream.CopyToAsync(memStream).ConfigureAwait(false);
|
||||
// minimum jpg size is 119 bytes, png is 67 bytes
|
||||
if (memStream.Length < 64)
|
||||
return (null, null);
|
||||
using var imgStream = await HttpClient.GetStreamAsync(url).ConfigureAwait(false);
|
||||
using var memStream = new MemoryStream();
|
||||
await imgStream.CopyToAsync(memStream).ConfigureAwait(false);
|
||||
// minimum jpg size is 119 bytes, png is 67 bytes
|
||||
if (memStream.Length < 64)
|
||||
return (null, null);
|
||||
|
||||
memStream.Seek(0, SeekOrigin.Begin);
|
||||
var spam = await client.GetChannelAsync(Config.ThumbnailSpamId).ConfigureAwait(false);
|
||||
var message = await spam.SendFileAsync(contentId + ".jpg", memStream, contentId).ConfigureAwait(false);
|
||||
url = message.Attachments.First().Url;
|
||||
return (url, memStream.ToArray());
|
||||
}
|
||||
memStream.Seek(0, SeekOrigin.Begin);
|
||||
var spam = await client.GetChannelAsync(Config.ThumbnailSpamId).ConfigureAwait(false);
|
||||
var message = await spam.SendFileAsync(contentId + ".jpg", memStream, contentId).ConfigureAwait(false);
|
||||
url = message.Attachments.First().Url;
|
||||
return (url, memStream.ToArray());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -195,18 +189,16 @@ namespace CompatBot.Database.Providers
|
||||
if (string.IsNullOrEmpty(url))
|
||||
return null;
|
||||
|
||||
using (var imgStream = await HttpClient.GetStreamAsync(url).ConfigureAwait(false))
|
||||
using (var memStream = new MemoryStream())
|
||||
{
|
||||
await imgStream.CopyToAsync(memStream).ConfigureAwait(false);
|
||||
// minimum jpg size is 119 bytes, png is 67 bytes
|
||||
if (memStream.Length < 64)
|
||||
return null;
|
||||
using var imgStream = await HttpClient.GetStreamAsync(url).ConfigureAwait(false);
|
||||
using var memStream = new MemoryStream();
|
||||
await imgStream.CopyToAsync(memStream).ConfigureAwait(false);
|
||||
// minimum jpg size is 119 bytes, png is 67 bytes
|
||||
if (memStream.Length < 64)
|
||||
return null;
|
||||
|
||||
memStream.Seek(0, SeekOrigin.Begin);
|
||||
memStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
return ColorGetter.Analyze(memStream.ToArray(), defaultColor);
|
||||
}
|
||||
return ColorGetter.Analyze(memStream.ToArray(), defaultColor);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -56,12 +56,10 @@ namespace CompatBot.EventHandlers
|
||||
}
|
||||
else if (match.Groups["job_id"].Value is string jobId && !string.IsNullOrEmpty(jobId))
|
||||
{
|
||||
using (var timeoutCts = new CancellationTokenSource(Config.LogParsingTimeout))
|
||||
using (var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(Config.Cts.Token, timeoutCts.Token))
|
||||
{
|
||||
var buildInfo = await AppveyorClient.GetBuildAsync(jobId, combinedCts.Token).ConfigureAwait(false);
|
||||
pr = buildInfo?.PullRequestId;
|
||||
}
|
||||
using var timeoutCts = new CancellationTokenSource(Config.LogParsingTimeout);
|
||||
using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(Config.Cts.Token, timeoutCts.Token);
|
||||
var buildInfo = await AppveyorClient.GetBuildAsync(jobId, combinedCts.Token).ConfigureAwait(false);
|
||||
pr = buildInfo?.PullRequestId;
|
||||
}
|
||||
if (pr > 0)
|
||||
await Commands.Pr.LinkPrBuild(args.Client, args.Message, pr.Value).ConfigureAwait(false);
|
||||
|
||||
@@ -126,31 +126,31 @@ namespace CompatBot.EventHandlers
|
||||
#endif
|
||||
|
||||
if (!string.IsNullOrEmpty(args.Message.Content) && Paws.Matches(args.Message.Content) is MatchCollection mc)
|
||||
using (var db = new BotDb())
|
||||
{
|
||||
using var db = new BotDb();
|
||||
var matchedGroups = (from m in mc
|
||||
from Group g in m.Groups
|
||||
where g.Success && !string.IsNullOrEmpty(g.Value)
|
||||
select g.Name
|
||||
).Distinct()
|
||||
.ToArray();
|
||||
if (matchedGroups.Contains("kot"))
|
||||
{
|
||||
var matchedGroups = (from m in mc
|
||||
from Group g in m.Groups
|
||||
where g.Success && !string.IsNullOrEmpty(g.Value)
|
||||
select g.Name
|
||||
).Distinct()
|
||||
.ToArray();
|
||||
if (matchedGroups.Contains("kot"))
|
||||
if (!db.Kot.Any(k => k.UserId == args.Author.Id))
|
||||
{
|
||||
if (!db.Kot.Any(k => k.UserId == args.Author.Id))
|
||||
{
|
||||
db.Kot.Add(new Kot {UserId = args.Author.Id});
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
if (matchedGroups.Contains("doggo"))
|
||||
{
|
||||
if (!db.Doggo.Any(d => d.UserId == args.Author.Id))
|
||||
{
|
||||
db.Doggo.Add(new Doggo {UserId = args.Author.Id});
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
db.Kot.Add(new Kot {UserId = args.Author.Id});
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
if (matchedGroups.Contains("doggo"))
|
||||
{
|
||||
if (!db.Doggo.Any(d => d.UserId == args.Author.Id))
|
||||
{
|
||||
db.Doggo.Add(new Doggo {UserId = args.Author.Id});
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (needToSilence, needToThank) = NeedToSilence(args.Message);
|
||||
if (!(needToSilence || needToThank))
|
||||
|
||||
@@ -16,15 +16,13 @@ namespace CompatBot.EventHandlers
|
||||
|
||||
try
|
||||
{
|
||||
using (var db = new BotDb())
|
||||
{
|
||||
var status = await db.BotState.FirstOrDefaultAsync(s => s.Key == "bot-status-activity").ConfigureAwait(false);
|
||||
var txt = await db.BotState.FirstOrDefaultAsync(s => s.Key == "bot-status-text").ConfigureAwait(false);
|
||||
var msg = txt?.Value;
|
||||
if (Enum.TryParse(status?.Value ?? "Watching", true, out ActivityType activity)
|
||||
&& !string.IsNullOrEmpty(msg))
|
||||
await client.UpdateStatusAsync(new DiscordActivity(msg, activity), UserStatus.Online).ConfigureAwait(false);
|
||||
}
|
||||
using var db = new BotDb();
|
||||
var status = await db.BotState.FirstOrDefaultAsync(s => s.Key == "bot-status-activity").ConfigureAwait(false);
|
||||
var txt = await db.BotState.FirstOrDefaultAsync(s => s.Key == "bot-status-text").ConfigureAwait(false);
|
||||
var msg = txt?.Value;
|
||||
if (Enum.TryParse(status?.Value ?? "Watching", true, out ActivityType activity)
|
||||
&& !string.IsNullOrEmpty(msg))
|
||||
await client.UpdateStatusAsync(new DiscordActivity(msg, activity), UserStatus.Online).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -200,28 +200,24 @@ namespace CompatBot.EventHandlers
|
||||
{
|
||||
try
|
||||
{
|
||||
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.CacheControl = CacheControlHeaderValue.Parse("no-cache");
|
||||
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("RPCS3CompatibilityBot", "2.0"));
|
||||
using var response = await HttpClient.SendAsync(request);
|
||||
var html = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/html"));
|
||||
request.Headers.CacheControl = CacheControlHeaderValue.Parse("no-cache");
|
||||
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("RPCS3CompatibilityBot", "2.0"));
|
||||
using (var response = await HttpClient.SendAsync(request))
|
||||
{
|
||||
var html = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
if (string.IsNullOrEmpty(html))
|
||||
continue;
|
||||
if (string.IsNullOrEmpty(html))
|
||||
continue;
|
||||
|
||||
foreach (Match match in DiscordInviteLink.Matches(html))
|
||||
inviteCodes.Add(match.Groups["invite_id"].Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
hasInvalidInvites = true;
|
||||
Config.Log.Warn($"Got {response.StatusCode} from discord.me: {html}");
|
||||
}
|
||||
}
|
||||
foreach (Match match in DiscordInviteLink.Matches(html))
|
||||
inviteCodes.Add(match.Groups["invite_id"].Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
hasInvalidInvites = true;
|
||||
Config.Log.Warn($"Got {response.StatusCode} from discord.me: {html}");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -10,15 +10,13 @@ namespace CompatBot.EventHandlers
|
||||
{
|
||||
public static async Task OnMemberAdded(GuildMemberAddEventArgs args)
|
||||
{
|
||||
using (var db = new BotDb())
|
||||
using var db = new BotDb();
|
||||
var explanation = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == "motd").ConfigureAwait(false);
|
||||
if (explanation != null)
|
||||
{
|
||||
var explanation = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == "motd").ConfigureAwait(false);
|
||||
if (explanation != null)
|
||||
{
|
||||
var dm = await args.Member.CreateDmChannelAsync().ConfigureAwait(false);
|
||||
await dm.SendMessageAsync(explanation.Text, explanation.Attachment, explanation.AttachmentFilename).ConfigureAwait(false);
|
||||
Config.Log.Info($"Sent motd to {args.Member.GetMentionWithNickname()}");
|
||||
}
|
||||
var dm = await args.Member.CreateDmChannelAsync().ConfigureAwait(false);
|
||||
await dm.SendMessageAsync(explanation.Text, explanation.Attachment, explanation.AttachmentFilename).ConfigureAwait(false);
|
||||
Config.Log.Info($"Sent motd to {args.Member.GetMentionWithNickname()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,33 +31,31 @@ namespace CompatBot.EventHandlers.LogParsing.ArchiveHandlers
|
||||
|
||||
public async Task FillPipeAsync(Stream sourceStream, PipeWriter writer, CancellationToken cancellationToken)
|
||||
{
|
||||
using (var statsStream = new BufferCopyStream(sourceStream) )
|
||||
using (var gzipStream = new GZipStream(statsStream, CompressionMode.Decompress))
|
||||
using var statsStream = new BufferCopyStream(sourceStream);
|
||||
using var gzipStream = new GZipStream(statsStream, CompressionMode.Decompress);
|
||||
try
|
||||
{
|
||||
try
|
||||
int read;
|
||||
FlushResult flushed;
|
||||
do
|
||||
{
|
||||
int read;
|
||||
FlushResult flushed;
|
||||
do
|
||||
{
|
||||
var memory = writer.GetMemory(Config.MinimumBufferSize);
|
||||
read = await gzipStream.ReadAsync(memory, cancellationToken);
|
||||
writer.Advance(read);
|
||||
SourcePosition = statsStream.Position;
|
||||
flushed = await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
SourcePosition = statsStream.Position;
|
||||
} while (read > 0 && !(flushed.IsCompleted || flushed.IsCanceled || cancellationToken.IsCancellationRequested));
|
||||
var memory = writer.GetMemory(Config.MinimumBufferSize);
|
||||
read = await gzipStream.ReadAsync(memory, cancellationToken);
|
||||
writer.Advance(read);
|
||||
SourcePosition = statsStream.Position;
|
||||
flushed = await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
SourcePosition = statsStream.Position;
|
||||
} while (read > 0 && !(flushed.IsCompleted || flushed.IsCanceled || cancellationToken.IsCancellationRequested));
|
||||
|
||||
var buf = statsStream.GetBufferedBytes();
|
||||
if (buf.Length > 3)
|
||||
LogSize = BitConverter.ToInt32(buf.AsSpan(buf.Length - 4, 4));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Config.Log.Error(e, "Error filling the log pipe");
|
||||
writer.Complete(e);
|
||||
return;
|
||||
}
|
||||
var buf = statsStream.GetBufferedBytes();
|
||||
if (buf.Length > 3)
|
||||
LogSize = BitConverter.ToInt32(buf.AsSpan(buf.Length - 4, 4));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Config.Log.Error(e, "Error filling the log pipe");
|
||||
writer.Complete(e);
|
||||
return;
|
||||
}
|
||||
writer.Complete();
|
||||
}
|
||||
|
||||
@@ -36,34 +36,32 @@ namespace CompatBot.EventHandlers.LogParsing.ArchiveHandlers
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var statsStream = new BufferCopyStream(sourceStream))
|
||||
using (var rarReader = RarReader.Open(statsStream))
|
||||
while (rarReader.MoveToNextEntry())
|
||||
using var statsStream = new BufferCopyStream(sourceStream);
|
||||
using var rarReader = RarReader.Open(statsStream);
|
||||
while (rarReader.MoveToNextEntry())
|
||||
{
|
||||
if (!rarReader.Entry.IsDirectory
|
||||
&& rarReader.Entry.Key.EndsWith(".log", StringComparison.InvariantCultureIgnoreCase)
|
||||
&& !rarReader.Entry.Key.Contains("tty.log", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
if (!rarReader.Entry.IsDirectory
|
||||
&& rarReader.Entry.Key.EndsWith(".log", StringComparison.InvariantCultureIgnoreCase)
|
||||
&& !rarReader.Entry.Key.Contains("tty.log", StringComparison.InvariantCultureIgnoreCase))
|
||||
LogSize = rarReader.Entry.Size;
|
||||
using var rarStream = rarReader.OpenEntryStream();
|
||||
int read;
|
||||
FlushResult flushed;
|
||||
do
|
||||
{
|
||||
LogSize = rarReader.Entry.Size;
|
||||
using (var rarStream = rarReader.OpenEntryStream())
|
||||
{
|
||||
int read;
|
||||
FlushResult flushed;
|
||||
do
|
||||
{
|
||||
var memory = writer.GetMemory(Config.MinimumBufferSize);
|
||||
read = await rarStream.ReadAsync(memory, cancellationToken);
|
||||
writer.Advance(read);
|
||||
SourcePosition = statsStream.Position;
|
||||
flushed = await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
SourcePosition = statsStream.Position;
|
||||
} while (read > 0 && !(flushed.IsCompleted || flushed.IsCanceled || cancellationToken.IsCancellationRequested));
|
||||
}
|
||||
writer.Complete();
|
||||
return;
|
||||
}
|
||||
SourcePosition = statsStream.Position;
|
||||
var memory = writer.GetMemory(Config.MinimumBufferSize);
|
||||
read = await rarStream.ReadAsync(memory, cancellationToken);
|
||||
writer.Advance(read);
|
||||
SourcePosition = statsStream.Position;
|
||||
flushed = await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
SourcePosition = statsStream.Position;
|
||||
} while (read > 0 && !(flushed.IsCompleted || flushed.IsCanceled || cancellationToken.IsCancellationRequested));
|
||||
writer.Complete();
|
||||
return;
|
||||
}
|
||||
SourcePosition = statsStream.Position;
|
||||
}
|
||||
Config.Log.Warn("No rar entries that match the log criteria");
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -33,35 +33,31 @@ namespace CompatBot.EventHandlers.LogParsing.ArchiveHandlers
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var fileStream = new FileStream(Path.GetTempFileName(), FileMode.Create, FileAccess.ReadWrite, FileShare.Read, 16384, FileOptions.Asynchronous | FileOptions.RandomAccess | FileOptions.DeleteOnClose))
|
||||
{
|
||||
await sourceStream.CopyToAsync(fileStream, 16384, cancellationToken).ConfigureAwait(false);
|
||||
fileStream.Seek(0, SeekOrigin.Begin);
|
||||
using (var zipArchive = SevenZipArchive.Open(fileStream))
|
||||
using (var zipReader = zipArchive.ExtractAllEntries())
|
||||
while (zipReader.MoveToNextEntry())
|
||||
if (!zipReader.Entry.IsDirectory
|
||||
&& zipReader.Entry.Key.EndsWith(".log", StringComparison.InvariantCultureIgnoreCase)
|
||||
&& !zipReader.Entry.Key.Contains("tty.log", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
LogSize = zipReader.Entry.Size;
|
||||
using (var entryStream = zipReader.OpenEntryStream())
|
||||
{
|
||||
int read;
|
||||
FlushResult flushed;
|
||||
do
|
||||
{
|
||||
var memory = writer.GetMemory(Config.MinimumBufferSize);
|
||||
read = await entryStream.ReadAsync(memory, cancellationToken);
|
||||
writer.Advance(read);
|
||||
flushed = await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
} while (read > 0 && !(flushed.IsCompleted || flushed.IsCanceled || cancellationToken.IsCancellationRequested));
|
||||
}
|
||||
writer.Complete();
|
||||
return;
|
||||
}
|
||||
Config.Log.Warn("No 7z entries that match the log criteria");
|
||||
}
|
||||
using var fileStream = new FileStream(Path.GetTempFileName(), FileMode.Create, FileAccess.ReadWrite, FileShare.Read, 16384, FileOptions.Asynchronous | FileOptions.RandomAccess | FileOptions.DeleteOnClose);
|
||||
await sourceStream.CopyToAsync(fileStream, 16384, cancellationToken).ConfigureAwait(false);
|
||||
fileStream.Seek(0, SeekOrigin.Begin);
|
||||
using var zipArchive = SevenZipArchive.Open(fileStream);
|
||||
using var zipReader = zipArchive.ExtractAllEntries();
|
||||
while (zipReader.MoveToNextEntry())
|
||||
if (!zipReader.Entry.IsDirectory
|
||||
&& zipReader.Entry.Key.EndsWith(".log", StringComparison.InvariantCultureIgnoreCase)
|
||||
&& !zipReader.Entry.Key.Contains("tty.log", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
LogSize = zipReader.Entry.Size;
|
||||
using var entryStream = zipReader.OpenEntryStream();
|
||||
int read;
|
||||
FlushResult flushed;
|
||||
do
|
||||
{
|
||||
var memory = writer.GetMemory(Config.MinimumBufferSize);
|
||||
read = await entryStream.ReadAsync(memory, cancellationToken);
|
||||
writer.Advance(read);
|
||||
flushed = await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
} while (read > 0 && !(flushed.IsCompleted || flushed.IsCanceled || cancellationToken.IsCancellationRequested));
|
||||
writer.Complete();
|
||||
return;
|
||||
}
|
||||
Config.Log.Warn("No 7z entries that match the log criteria");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -36,34 +36,32 @@ namespace CompatBot.EventHandlers.LogParsing.ArchiveHandlers
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var statsStream = new BufferCopyStream(sourceStream))
|
||||
using (var zipReader = ZipReader.Open(statsStream))
|
||||
while (zipReader.MoveToNextEntry())
|
||||
using var statsStream = new BufferCopyStream(sourceStream);
|
||||
using var zipReader = ZipReader.Open(statsStream);
|
||||
while (zipReader.MoveToNextEntry())
|
||||
{
|
||||
if (!zipReader.Entry.IsDirectory
|
||||
&& zipReader.Entry.Key.EndsWith(".log", StringComparison.InvariantCultureIgnoreCase)
|
||||
&& !zipReader.Entry.Key.Contains("tty.log", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
if (!zipReader.Entry.IsDirectory
|
||||
&& zipReader.Entry.Key.EndsWith(".log", StringComparison.InvariantCultureIgnoreCase)
|
||||
&& !zipReader.Entry.Key.Contains("tty.log", StringComparison.InvariantCultureIgnoreCase))
|
||||
LogSize = zipReader.Entry.Size;
|
||||
using var rarStream = zipReader.OpenEntryStream();
|
||||
int read;
|
||||
FlushResult flushed;
|
||||
do
|
||||
{
|
||||
LogSize = zipReader.Entry.Size;
|
||||
using (var rarStream = zipReader.OpenEntryStream())
|
||||
{
|
||||
int read;
|
||||
FlushResult flushed;
|
||||
do
|
||||
{
|
||||
var memory = writer.GetMemory(Config.MinimumBufferSize);
|
||||
read = await rarStream.ReadAsync(memory, cancellationToken);
|
||||
writer.Advance(read);
|
||||
SourcePosition = statsStream.Position;
|
||||
flushed = await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
SourcePosition = statsStream.Position;
|
||||
} while (read > 0 && !(flushed.IsCompleted || flushed.IsCanceled || cancellationToken.IsCancellationRequested));
|
||||
}
|
||||
writer.Complete();
|
||||
return;
|
||||
}
|
||||
SourcePosition = statsStream.Position;
|
||||
var memory = writer.GetMemory(Config.MinimumBufferSize);
|
||||
read = await rarStream.ReadAsync(memory, cancellationToken);
|
||||
writer.Advance(read);
|
||||
SourcePosition = statsStream.Position;
|
||||
flushed = await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
SourcePosition = statsStream.Position;
|
||||
} while (read > 0 && !(flushed.IsCompleted || flushed.IsCanceled || cancellationToken.IsCancellationRequested));
|
||||
writer.Complete();
|
||||
return;
|
||||
}
|
||||
SourcePosition = statsStream.Position;
|
||||
}
|
||||
Config.Log.Warn("No rar entries that match the log criteria");
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -14,44 +14,42 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
|
||||
{
|
||||
public override async Task<(ISource source, string failReason)> FindHandlerAsync(DiscordMessage message, ICollection<IArchiveHandler> handlers)
|
||||
{
|
||||
using (var client = HttpClientFactory.Create())
|
||||
foreach (var attachment in message.Attachments)
|
||||
using var client = HttpClientFactory.Create();
|
||||
foreach (var attachment in message.Attachments)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var stream = await client.GetStreamAsync(attachment.Url).ConfigureAwait(false);
|
||||
var buf = bufferPool.Rent(1024);
|
||||
try
|
||||
{
|
||||
using (var stream = await client.GetStreamAsync(attachment.Url).ConfigureAwait(false))
|
||||
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
var buf = bufferPool.Rent(1024);
|
||||
try
|
||||
{
|
||||
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
var (canHandle, reason) = handler.CanHandle(attachment.FileName, attachment.FileSize, buf.AsSpan(0, read));
|
||||
if (canHandle)
|
||||
return (new DiscordAttachmentSource(attachment, handler, attachment.FileName, attachment.FileSize), null);
|
||||
else if (!string.IsNullOrEmpty(reason))
|
||||
return (null, reason);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
bufferPool.Return(buf);
|
||||
}
|
||||
var (canHandle, reason) = handler.CanHandle(attachment.FileName, attachment.FileSize, buf.AsSpan(0, read));
|
||||
if (canHandle)
|
||||
return (new DiscordAttachmentSource(attachment, handler, attachment.FileName, attachment.FileSize), null);
|
||||
else if (!string.IsNullOrEmpty(reason))
|
||||
return (null, reason);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
finally
|
||||
{
|
||||
Config.Log.Error(e, "Error sniffing the rar content");
|
||||
bufferPool.Return(buf);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Config.Log.Error(e, "Error sniffing the rar content");
|
||||
}
|
||||
}
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
private sealed class DiscordAttachmentSource : ISource
|
||||
{
|
||||
private DiscordAttachment attachment;
|
||||
private IArchiveHandler handler;
|
||||
private readonly DiscordAttachment attachment;
|
||||
private readonly IArchiveHandler handler;
|
||||
|
||||
public string SourceType => "Discord attachment";
|
||||
public string FileName { get; }
|
||||
@@ -69,9 +67,9 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
|
||||
|
||||
public async Task FillPipeAsync(PipeWriter writer, CancellationToken cancellationToken)
|
||||
{
|
||||
using (var client = HttpClientFactory.Create())
|
||||
using (var stream = await client.GetStreamAsync(attachment.Url).ConfigureAwait(false))
|
||||
await handler.FillPipeAsync(stream, writer, cancellationToken).ConfigureAwait(false);
|
||||
using var client = HttpClientFactory.Create();
|
||||
using var stream = await client.GetStreamAsync(attachment.Url).ConfigureAwait(false);
|
||||
await handler.FillPipeAsync(stream, writer, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,64 +27,62 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
|
||||
if (matches.Count == 0)
|
||||
return (null, null);
|
||||
|
||||
using (var client = HttpClientFactory.Create())
|
||||
foreach (Match m in matches)
|
||||
using var client = HttpClientFactory.Create();
|
||||
foreach (Match m in matches)
|
||||
{
|
||||
if (m.Groups["dropbox_link"].Value is string lnk
|
||||
&& !string.IsNullOrEmpty(lnk)
|
||||
&& Uri.TryCreate(lnk, UriKind.Absolute, out var uri))
|
||||
{
|
||||
if (m.Groups["dropbox_link"].Value is string lnk
|
||||
&& !string.IsNullOrEmpty(lnk)
|
||||
&& Uri.TryCreate(lnk, UriKind.Absolute, out var uri))
|
||||
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 string fname && !string.IsNullOrEmpty(fname))
|
||||
filename = fname;
|
||||
uri = response.RequestMessage.RequestUri;
|
||||
}
|
||||
|
||||
using var stream = await client.GetStreamAsync(uri).ConfigureAwait(false);
|
||||
var buf = bufferPool.Rent(1024);
|
||||
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))
|
||||
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
if (response?.Content?.Headers?.ContentLength > 0)
|
||||
filesize = (int)response.Content.Headers.ContentLength.Value;
|
||||
if (response?.Content?.Headers?.ContentDisposition?.FileNameStar is string fname && !string.IsNullOrEmpty(fname))
|
||||
filename = fname;
|
||||
uri = response.RequestMessage.RequestUri;
|
||||
}
|
||||
|
||||
using (var stream = await client.GetStreamAsync(uri).ConfigureAwait(false))
|
||||
{
|
||||
var buf = bufferPool.Rent(1024);
|
||||
try
|
||||
{
|
||||
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
var (canHandle, reason) = handler.CanHandle(filename, filesize, buf.AsSpan(0, read));
|
||||
if (canHandle)
|
||||
return (new DropboxSource(uri, handler, filename, filesize), null);
|
||||
else if (!string.IsNullOrEmpty(reason))
|
||||
return (null, reason);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
bufferPool.Return(buf);
|
||||
}
|
||||
var (canHandle, reason) = handler.CanHandle(filename, filesize, buf.AsSpan(0, read));
|
||||
if (canHandle)
|
||||
return (new DropboxSource(uri, handler, filename, filesize), null);
|
||||
else if (!string.IsNullOrEmpty(reason))
|
||||
return (null, reason);
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
finally
|
||||
{
|
||||
Config.Log.Warn(e, $"Error sniffing {m.Groups["dropbox_link"].Value}");
|
||||
bufferPool.Return(buf);
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
Config.Log.Warn(e, $"Error sniffing {m.Groups["dropbox_link"].Value}");
|
||||
}
|
||||
}
|
||||
}
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
private sealed class DropboxSource : ISource
|
||||
{
|
||||
private Uri uri;
|
||||
private IArchiveHandler handler;
|
||||
private readonly Uri uri;
|
||||
private readonly IArchiveHandler handler;
|
||||
|
||||
public string SourceType => "Dropbox";
|
||||
public string FileName { get; }
|
||||
@@ -102,9 +100,9 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
|
||||
|
||||
public async Task FillPipeAsync(PipeWriter writer, CancellationToken cancellationToken)
|
||||
{
|
||||
using (var client = HttpClientFactory.Create())
|
||||
using (var stream = await client.GetStreamAsync(uri).ConfigureAwait(false))
|
||||
await handler.FillPipeAsync(stream, writer, cancellationToken).ConfigureAwait(false);
|
||||
using var client = HttpClientFactory.Create();
|
||||
using var stream = await client.GetStreamAsync(uri).ConfigureAwait(false);
|
||||
await handler.FillPipeAsync(stream, writer, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,25 +31,23 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
|
||||
|
||||
public async Task FillPipeAsync(PipeWriter writer, CancellationToken cancellationToken)
|
||||
{
|
||||
using (var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
await handler.FillPipeAsync(stream, writer, cancellationToken).ConfigureAwait(false);
|
||||
using var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
await handler.FillPipeAsync(stream, writer, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static async Task<ISource> DetectArchiveHandlerAsync(string path, ICollection<IArchiveHandler> handlers)
|
||||
{
|
||||
var buf = new byte[1024];
|
||||
using (var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
using var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
var (canHandle, reason) = handler.CanHandle(Path.GetFileName(path), (int)stream.Length, buf.AsSpan(0, read));
|
||||
if (canHandle)
|
||||
return new FileSource(path, handler);
|
||||
else if (!string.IsNullOrEmpty(reason))
|
||||
throw new InvalidOperationException(reason);
|
||||
}
|
||||
|
||||
var (canHandle, reason) = handler.CanHandle(Path.GetFileName(path), (int)stream.Length, buf.AsSpan(0, read));
|
||||
if (canHandle)
|
||||
return new FileSource(path, handler);
|
||||
|
||||
if (!string.IsNullOrEmpty(reason))
|
||||
throw new InvalidOperationException(reason);
|
||||
}
|
||||
throw new InvalidOperationException("Unknown source type");
|
||||
}
|
||||
|
||||
@@ -25,65 +25,63 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
|
||||
if (matches.Count == 0)
|
||||
return (null, null);
|
||||
|
||||
using (var client = HttpClientFactory.Create())
|
||||
foreach (Match m in matches)
|
||||
using var client = HttpClientFactory.Create();
|
||||
foreach (Match m in matches)
|
||||
{
|
||||
if (m.Groups["link"].Value is string lnk
|
||||
&& !string.IsNullOrEmpty(lnk)
|
||||
&& Uri.TryCreate(lnk, UriKind.Absolute, out var uri)
|
||||
&& !"tty.log".Equals(m.Groups["filename"].Value, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
if (m.Groups["link"].Value is string lnk
|
||||
&& !string.IsNullOrEmpty(lnk)
|
||||
&& Uri.TryCreate(lnk, UriKind.Absolute, out var uri)
|
||||
&& !"tty.log".Equals(m.Groups["filename"].Value, StringComparison.InvariantCultureIgnoreCase))
|
||||
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 string fname && !string.IsNullOrEmpty(fname))
|
||||
filename = fname;
|
||||
uri = response.RequestMessage.RequestUri;
|
||||
}
|
||||
|
||||
using var stream = await client.GetStreamAsync(uri).ConfigureAwait(false);
|
||||
var buf = bufferPool.Rent(1024);
|
||||
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))
|
||||
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
if (response?.Content?.Headers?.ContentLength > 0)
|
||||
filesize = (int)response.Content.Headers.ContentLength.Value;
|
||||
if (response?.Content?.Headers?.ContentDisposition?.FileNameStar is string fname && !string.IsNullOrEmpty(fname))
|
||||
filename = fname;
|
||||
uri = response.RequestMessage.RequestUri;
|
||||
}
|
||||
|
||||
using (var stream = await client.GetStreamAsync(uri).ConfigureAwait(false))
|
||||
{
|
||||
var buf = bufferPool.Rent(1024);
|
||||
try
|
||||
{
|
||||
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
var (canHandle, reason) = handler.CanHandle(filename, filesize, buf.AsSpan(0, read));
|
||||
if (canHandle)
|
||||
return (new GenericSource(uri, handler, host, filename, filesize), null);
|
||||
else if (!string.IsNullOrEmpty(reason))
|
||||
return (null, reason);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
bufferPool.Return(buf);
|
||||
}
|
||||
var (canHandle, reason) = handler.CanHandle(filename, filesize, buf.AsSpan(0, read));
|
||||
if (canHandle)
|
||||
return (new GenericSource(uri, handler, host, filename, filesize), null);
|
||||
else if (!string.IsNullOrEmpty(reason))
|
||||
return (null, reason);
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
finally
|
||||
{
|
||||
Config.Log.Warn(e, $"Error sniffing {m.Groups["link"].Value}");
|
||||
bufferPool.Return(buf);
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
Config.Log.Warn(e, $"Error sniffing {m.Groups["link"].Value}");
|
||||
}
|
||||
}
|
||||
}
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
private sealed class GenericSource : ISource
|
||||
{
|
||||
private Uri uri;
|
||||
private IArchiveHandler handler;
|
||||
private readonly Uri uri;
|
||||
private readonly IArchiveHandler handler;
|
||||
|
||||
public string SourceType => "Generic link";
|
||||
public string FileName { get; }
|
||||
@@ -103,9 +101,9 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
|
||||
|
||||
public async Task FillPipeAsync(PipeWriter writer, CancellationToken cancellationToken)
|
||||
{
|
||||
using (var client = HttpClientFactory.Create())
|
||||
using (var stream = await client.GetStreamAsync(uri).ConfigureAwait(false))
|
||||
await handler.FillPipeAsync(stream, writer, cancellationToken).ConfigureAwait(false);
|
||||
using var client = HttpClientFactory.Create();
|
||||
using var stream = await client.GetStreamAsync(uri).ConfigureAwait(false);
|
||||
await handler.FillPipeAsync(stream, writer, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,20 +120,16 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
|
||||
try
|
||||
{
|
||||
var pipe = new Pipe();
|
||||
using (var pushStream = pipe.Writer.AsStream())
|
||||
{
|
||||
var progressTask = fileInfoRequest.DownloadAsync(pushStream, cancellationToken);
|
||||
using (var pullStream = pipe.Reader.AsStream())
|
||||
{
|
||||
var pipingTask = handler.FillPipeAsync(pullStream, writer, cancellationToken);
|
||||
var result = await progressTask.ConfigureAwait(false);
|
||||
if (result.Status != DownloadStatus.Completed)
|
||||
Config.Log.Error(result.Exception, "Failed to download file from Google Drive: " + result.Status);
|
||||
await pipe.Writer.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
pipe.Writer.Complete();
|
||||
await pipingTask.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
using var pushStream = pipe.Writer.AsStream();
|
||||
var progressTask = fileInfoRequest.DownloadAsync(pushStream, cancellationToken);
|
||||
using var pullStream = pipe.Reader.AsStream();
|
||||
var pipingTask = handler.FillPipeAsync(pullStream, writer, cancellationToken);
|
||||
var result = await progressTask.ConfigureAwait(false);
|
||||
if (result.Status != DownloadStatus.Completed)
|
||||
Config.Log.Error(result.Exception, "Failed to download file from Google Drive: " + result.Status);
|
||||
await pipe.Writer.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
pipe.Writer.Complete();
|
||||
await pipingTask.ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -91,8 +91,8 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
|
||||
|
||||
public async Task FillPipeAsync(PipeWriter writer, CancellationToken cancellationToken)
|
||||
{
|
||||
using (var stream = await client.DownloadAsync(uri, doodad, cancellationToken).ConfigureAwait(false))
|
||||
await handler.FillPipeAsync(stream, writer, cancellationToken).ConfigureAwait(false);
|
||||
using var stream = await client.DownloadAsync(uri, doodad, cancellationToken).ConfigureAwait(false);
|
||||
await handler.FillPipeAsync(stream, writer, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,50 +24,48 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
|
||||
if (matches.Count == 0)
|
||||
return (null, null);
|
||||
|
||||
using (var client = HttpClientFactory.Create())
|
||||
foreach (Match m in matches)
|
||||
using var client = HttpClientFactory.Create();
|
||||
foreach (Match m in matches)
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
if (m.Groups["pastebin_id"].Value is string pid
|
||||
&& !string.IsNullOrEmpty(pid))
|
||||
{
|
||||
if (m.Groups["pastebin_id"].Value is string pid
|
||||
&& !string.IsNullOrEmpty(pid))
|
||||
var uri = new Uri("https://pastebin.com/raw/" + pid);
|
||||
using var stream = await client.GetStreamAsync(uri).ConfigureAwait(false);
|
||||
var buf = bufferPool.Rent(1024);
|
||||
try
|
||||
{
|
||||
var uri = new Uri("https://pastebin.com/raw/" + pid);
|
||||
using (var stream = await client.GetStreamAsync(uri).ConfigureAwait(false))
|
||||
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
|
||||
var filename = pid + ".log";
|
||||
var filesize = stream.CanSeek ? (int)stream.Length : 0;
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
var buf = bufferPool.Rent(1024);
|
||||
try
|
||||
{
|
||||
var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
|
||||
var filename = pid + ".log";
|
||||
var filesize = stream.CanSeek ? (int)stream.Length : 0;
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
var (canHandle, reason) = handler.CanHandle(filename, filesize, buf.AsSpan(0, read));
|
||||
if (canHandle)
|
||||
return (new PastebinSource(uri, filename, filesize, handler), null);
|
||||
else if (!string.IsNullOrEmpty(reason))
|
||||
return (null, reason);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
bufferPool.Return(buf);
|
||||
}
|
||||
var (canHandle, reason) = handler.CanHandle(filename, filesize, buf.AsSpan(0, read));
|
||||
if (canHandle)
|
||||
return (new PastebinSource(uri, filename, filesize, handler), null);
|
||||
else if (!string.IsNullOrEmpty(reason))
|
||||
return (null, reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Config.Log.Warn(e, $"Error sniffing {m.Groups["mega_link"].Value}");
|
||||
finally
|
||||
{
|
||||
bufferPool.Return(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Config.Log.Warn(e, $"Error sniffing {m.Groups["mega_link"].Value}");
|
||||
}
|
||||
}
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
private sealed class PastebinSource : ISource
|
||||
{
|
||||
private Uri uri;
|
||||
private readonly Uri uri;
|
||||
private readonly IArchiveHandler handler;
|
||||
public long SourceFilePosition => handler.SourcePosition;
|
||||
public long LogFileSize => handler.LogSize;
|
||||
@@ -86,9 +84,9 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
|
||||
|
||||
public async Task FillPipeAsync(PipeWriter writer, CancellationToken cancellationToken)
|
||||
{
|
||||
using (var client = HttpClientFactory.Create())
|
||||
using (var stream = await client.GetStreamAsync(uri).ConfigureAwait(false))
|
||||
await handler.FillPipeAsync(stream, writer, cancellationToken).ConfigureAwait(false);
|
||||
using var client = HttpClientFactory.Create();
|
||||
using var stream = await client.GetStreamAsync(uri).ConfigureAwait(false);
|
||||
await handler.FillPipeAsync(stream, writer, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,8 +97,8 @@ namespace CompatBot.EventHandlers
|
||||
|
||||
LogParseState result = null;
|
||||
using (var timeout = new CancellationTokenSource(Config.LogParsingTimeout))
|
||||
using (var combinedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, Config.Cts.Token))
|
||||
{
|
||||
using var combinedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, Config.Cts.Token);
|
||||
var tries = 0;
|
||||
do
|
||||
{
|
||||
|
||||
@@ -63,9 +63,8 @@ namespace CompatBot.EventHandlers
|
||||
|
||||
public static async Task<Explanation> GetExplanationAsync(string term)
|
||||
{
|
||||
Explanation result;
|
||||
using (var db = new BotDb())
|
||||
result = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == term).ConfigureAwait(false);
|
||||
using var db = new BotDb();
|
||||
var result = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == term).ConfigureAwait(false);
|
||||
return result ?? DefaultExplanation[term];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,14 +18,12 @@ namespace CompatBot.EventHandlers
|
||||
if (!args.Message.Attachments.Any())
|
||||
return;
|
||||
|
||||
using (var db = new ThumbnailDb())
|
||||
using var db = new ThumbnailDb();
|
||||
var thumb = db.Thumbnail.FirstOrDefault(i => i.ContentId == args.Message.Content);
|
||||
if (thumb?.EmbeddableUrl is string url && !string.IsNullOrEmpty(url) && args.Message.Attachments.Any(a => a.Url == url))
|
||||
{
|
||||
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))
|
||||
{
|
||||
thumb.EmbeddableUrl = null;
|
||||
await db.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false);
|
||||
}
|
||||
thumb.EmbeddableUrl = null;
|
||||
await db.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,21 +38,19 @@ namespace CompatBot
|
||||
}
|
||||
|
||||
var singleInstanceCheckThread = new Thread(() =>
|
||||
{
|
||||
using (var instanceLock = new Mutex(false, @"Global\RPCS3 Compatibility Bot"))
|
||||
{
|
||||
if (instanceLock.WaitOne(1000))
|
||||
try
|
||||
{
|
||||
InstanceCheck.Release();
|
||||
ShutdownCheck.Wait();
|
||||
}
|
||||
finally
|
||||
{
|
||||
instanceLock.ReleaseMutex();
|
||||
}
|
||||
}
|
||||
});
|
||||
{
|
||||
using var instanceLock = new Mutex(false, @"Global\RPCS3 Compatibility Bot");
|
||||
if (instanceLock.WaitOne(1000))
|
||||
try
|
||||
{
|
||||
InstanceCheck.Release();
|
||||
ShutdownCheck.Wait();
|
||||
}
|
||||
finally
|
||||
{
|
||||
instanceLock.ReleaseMutex();
|
||||
}
|
||||
});
|
||||
try
|
||||
{
|
||||
singleInstanceCheckThread.Start();
|
||||
@@ -122,211 +120,207 @@ namespace CompatBot
|
||||
Token = Config.Token,
|
||||
TokenType = TokenType.Bot,
|
||||
};
|
||||
using (var client = new DiscordClient(config))
|
||||
using var client = new DiscordClient(config);
|
||||
var commands = client.UseCommandsNext(new CommandsNextConfiguration
|
||||
{
|
||||
var commands = client.UseCommandsNext(new CommandsNextConfiguration
|
||||
StringPrefixes = new[] {Config.CommandPrefix, Config.AutoRemoveCommandPrefix},
|
||||
Services = new ServiceCollection().BuildServiceProvider(),
|
||||
});
|
||||
commands.RegisterConverter(new TextOnlyDiscordChannelConverter());
|
||||
commands.RegisterCommands<Misc>();
|
||||
commands.RegisterCommands<CompatList>();
|
||||
commands.RegisterCommands<Sudo>();
|
||||
commands.RegisterCommands<CommandsManagement>();
|
||||
commands.RegisterCommands<ContentFilters>();
|
||||
commands.RegisterCommands<Warnings>();
|
||||
commands.RegisterCommands<Explain>();
|
||||
commands.RegisterCommands<Psn>();
|
||||
commands.RegisterCommands<Invites>();
|
||||
commands.RegisterCommands<Moderation>();
|
||||
commands.RegisterCommands<Ird>();
|
||||
commands.RegisterCommands<BotMath>();
|
||||
commands.RegisterCommands<Pr>();
|
||||
commands.RegisterCommands<Events>();
|
||||
commands.RegisterCommands<E3>();
|
||||
commands.RegisterCommands<Cyberpunk2077>();
|
||||
commands.RegisterCommands<Rpcs3Ama>();
|
||||
commands.RegisterCommands<BotStats>();
|
||||
commands.RegisterCommands<Syscall>();
|
||||
|
||||
commands.CommandErrored += UnknownCommandHandler.OnError;
|
||||
|
||||
var interactivityConfig = new InteractivityConfiguration { };
|
||||
client.UseInteractivity(interactivityConfig);
|
||||
|
||||
client.Ready += async r =>
|
||||
{
|
||||
Config.Log.Info("Bot is ready to serve!");
|
||||
Config.Log.Info("");
|
||||
Config.Log.Info($"Bot user id : {r.Client.CurrentUser.Id} ({r.Client.CurrentUser.Username})");
|
||||
Config.Log.Info($"Bot admin id : {Config.BotAdminId} ({(await r.Client.GetUserAsync(Config.BotAdminId)).Username})");
|
||||
Config.Log.Info("");
|
||||
};
|
||||
client.GuildAvailable += async gaArgs =>
|
||||
{
|
||||
await BotStatusMonitor.RefreshAsync(gaArgs.Client).ConfigureAwait(false);
|
||||
Watchdog.DisconnectTimestamps.Clear();
|
||||
if (gaArgs.Guild.Id != Config.BotGuildId)
|
||||
{
|
||||
StringPrefixes = new[] {Config.CommandPrefix, Config.AutoRemoveCommandPrefix},
|
||||
Services = new ServiceCollection().BuildServiceProvider(),
|
||||
});
|
||||
commands.RegisterConverter(new TextOnlyDiscordChannelConverter());
|
||||
commands.RegisterCommands<Misc>();
|
||||
commands.RegisterCommands<CompatList>();
|
||||
commands.RegisterCommands<Sudo>();
|
||||
commands.RegisterCommands<CommandsManagement>();
|
||||
commands.RegisterCommands<ContentFilters>();
|
||||
commands.RegisterCommands<Warnings>();
|
||||
commands.RegisterCommands<Explain>();
|
||||
commands.RegisterCommands<Psn>();
|
||||
commands.RegisterCommands<Invites>();
|
||||
commands.RegisterCommands<Moderation>();
|
||||
commands.RegisterCommands<Ird>();
|
||||
commands.RegisterCommands<BotMath>();
|
||||
commands.RegisterCommands<Pr>();
|
||||
commands.RegisterCommands<Events>();
|
||||
commands.RegisterCommands<E3>();
|
||||
commands.RegisterCommands<Cyberpunk2077>();
|
||||
commands.RegisterCommands<Rpcs3Ama>();
|
||||
commands.RegisterCommands<BotStats>();
|
||||
commands.RegisterCommands<Syscall>();
|
||||
|
||||
commands.CommandErrored += UnknownCommandHandler.OnError;
|
||||
|
||||
var interactivityConfig = new InteractivityConfiguration { };
|
||||
client.UseInteractivity(interactivityConfig);
|
||||
|
||||
client.Ready += async r =>
|
||||
{
|
||||
Config.Log.Info("Bot is ready to serve!");
|
||||
Config.Log.Info("");
|
||||
Config.Log.Info($"Bot user id : {r.Client.CurrentUser.Id} ({r.Client.CurrentUser.Username})");
|
||||
Config.Log.Info($"Bot admin id : {Config.BotAdminId} ({(await r.Client.GetUserAsync(Config.BotAdminId)).Username})");
|
||||
Config.Log.Info("");
|
||||
};
|
||||
client.GuildAvailable += async gaArgs =>
|
||||
{
|
||||
await BotStatusMonitor.RefreshAsync(gaArgs.Client).ConfigureAwait(false);
|
||||
Watchdog.DisconnectTimestamps.Clear();
|
||||
if (gaArgs.Guild.Id != Config.BotGuildId)
|
||||
{
|
||||
#if DEBUG
|
||||
Config.Log.Warn($"Unknown discord server {gaArgs.Guild.Id} ({gaArgs.Guild.Name})");
|
||||
Config.Log.Warn($"Unknown discord server {gaArgs.Guild.Id} ({gaArgs.Guild.Name})");
|
||||
#else
|
||||
Config.Log.Warn($"Unknown discord server {gaArgs.Guild.Id} ({gaArgs.Guild.Name}), leaving...");
|
||||
await gaArgs.Guild.LeaveAsync().ConfigureAwait(false);
|
||||
Config.Log.Warn($"Unknown discord server {gaArgs.Guild.Id} ({gaArgs.Guild.Name}), leaving...");
|
||||
await gaArgs.Guild.LeaveAsync().ConfigureAwait(false);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
Config.Log.Info($"Server {gaArgs.Guild.Name} is available now");
|
||||
Config.Log.Info($"Checking moderation backlogs in {gaArgs.Guild.Name}...");
|
||||
try
|
||||
{
|
||||
await Task.WhenAll(
|
||||
Starbucks.CheckBacklogAsync(gaArgs.Client, gaArgs.Guild).ContinueWith(_ => Config.Log.Info($"Starbucks backlog checked in {gaArgs.Guild.Name}."), TaskScheduler.Default),
|
||||
DiscordInviteFilter.CheckBacklogAsync(gaArgs.Client, gaArgs.Guild).ContinueWith(_ => Config.Log.Info($"Discord invites backlog checked in {gaArgs.Guild.Name}."), TaskScheduler.Default)
|
||||
).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Config.Log.Warn(e, "Error running backlog tasks");
|
||||
}
|
||||
Config.Log.Info($"All moderation backlogs checked in {gaArgs.Guild.Name}.");
|
||||
};
|
||||
client.GuildUnavailable += guArgs =>
|
||||
{
|
||||
Config.Log.Warn($"{guArgs.Guild.Name} is unavailable");
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
client.MessageReactionAdded += Starbucks.Handler;
|
||||
client.MessageReactionAdded += AntipiracyMonitor.OnReaction;
|
||||
|
||||
client.MessageCreated += AntipiracyMonitor.OnMessageCreated; // should be first
|
||||
client.MessageCreated += ProductCodeLookup.OnMessageCreated;
|
||||
client.MessageCreated += LogParsingHandler.OnMessageCreated;
|
||||
client.MessageCreated += LogAsTextMonitor.OnMessageCreated;
|
||||
client.MessageCreated += DiscordInviteFilter.OnMessageCreated;
|
||||
client.MessageCreated += PostLogHelpHandler.OnMessageCreated;
|
||||
client.MessageCreated += BotReactionsHandler.OnMessageCreated;
|
||||
client.MessageCreated += AppveyorLinksHandler.OnMessageCreated;
|
||||
client.MessageCreated += GithubLinksHandler.OnMessageCreated;
|
||||
client.MessageCreated += NewBuildsMonitor.OnMessageCreated;
|
||||
client.MessageCreated += TableFlipMonitor.OnMessageCreated;
|
||||
client.MessageCreated += IsTheGamePlayableHandler.OnMessageCreated;
|
||||
client.MessageCreated += EmpathySimulationHandler.OnMessageCreated;
|
||||
|
||||
client.MessageUpdated += AntipiracyMonitor.OnMessageUpdated;
|
||||
client.MessageUpdated += DiscordInviteFilter.OnMessageUpdated;
|
||||
client.MessageUpdated += EmpathySimulationHandler.OnMessageUpdated;
|
||||
|
||||
client.MessageDeleted += ThumbnailCacheMonitor.OnMessageDeleted;
|
||||
client.MessageDeleted += EmpathySimulationHandler.OnMessageDeleted;
|
||||
|
||||
client.UserUpdated += UsernameSpoofMonitor.OnUserUpdated;
|
||||
client.UserUpdated += UsernameZalgoMonitor.OnUserUpdated;
|
||||
|
||||
client.GuildMemberAdded += Greeter.OnMemberAdded;
|
||||
client.GuildMemberAdded += UsernameSpoofMonitor.OnMemberAdded;
|
||||
client.GuildMemberAdded += UsernameZalgoMonitor.OnMemberAdded;
|
||||
|
||||
client.GuildMemberUpdated += UsernameSpoofMonitor.OnMemberUpdated;
|
||||
client.GuildMemberUpdated += UsernameZalgoMonitor.OnMemberUpdated;
|
||||
|
||||
client.DebugLogger.LogMessageReceived += (sender, eventArgs) =>
|
||||
{
|
||||
Action<Exception, string> logLevel = Config.Log.Info;
|
||||
if (eventArgs.Level == LogLevel.Debug)
|
||||
logLevel = Config.Log.Debug;
|
||||
else if (eventArgs.Level == LogLevel.Info)
|
||||
{
|
||||
//logLevel = Config.Log.Info;
|
||||
if (eventArgs.Message?.Contains("Session resumed") ?? false)
|
||||
Watchdog.DisconnectTimestamps.Clear();
|
||||
}
|
||||
else if (eventArgs.Level == LogLevel.Warning)
|
||||
{
|
||||
logLevel = Config.Log.Warn;
|
||||
if (eventArgs.Message?.Contains("Dispatch:PRESENCES_REPLACE") ?? false)
|
||||
BotStatusMonitor.RefreshAsync(client).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
else if (eventArgs.Level == LogLevel.Error)
|
||||
logLevel = Config.Log.Error;
|
||||
else if (eventArgs.Level == LogLevel.Critical)
|
||||
{
|
||||
logLevel = Config.Log.Fatal;
|
||||
if ((eventArgs.Message?.Contains("Socket connection terminated") ?? false)
|
||||
|| (eventArgs.Message?.Contains("heartbeats were skipped. Issuing reconnect.") ?? false))
|
||||
Watchdog.DisconnectTimestamps.Enqueue(DateTime.UtcNow);
|
||||
}
|
||||
logLevel(eventArgs.Exception, eventArgs.Message);
|
||||
};
|
||||
Watchdog.DisconnectTimestamps.Enqueue(DateTime.UtcNow);
|
||||
return;
|
||||
}
|
||||
|
||||
Config.Log.Info($"Server {gaArgs.Guild.Name} is available now");
|
||||
Config.Log.Info($"Checking moderation backlogs in {gaArgs.Guild.Name}...");
|
||||
try
|
||||
{
|
||||
await client.ConnectAsync().ConfigureAwait(false);
|
||||
await Task.WhenAll(
|
||||
Starbucks.CheckBacklogAsync(gaArgs.Client, gaArgs.Guild).ContinueWith(_ => Config.Log.Info($"Starbucks backlog checked in {gaArgs.Guild.Name}."), TaskScheduler.Default),
|
||||
DiscordInviteFilter.CheckBacklogAsync(gaArgs.Client, gaArgs.Guild).ContinueWith(_ => Config.Log.Info($"Discord invites backlog checked in {gaArgs.Guild.Name}."), TaskScheduler.Default)
|
||||
).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Config.Log.Error(e, "Failed to connect to Discord: " + e.Message);
|
||||
throw;
|
||||
Config.Log.Warn(e, "Error running backlog tasks");
|
||||
}
|
||||
Config.Log.Info($"All moderation backlogs checked in {gaArgs.Guild.Name}.");
|
||||
};
|
||||
client.GuildUnavailable += guArgs =>
|
||||
{
|
||||
Config.Log.Warn($"{guArgs.Guild.Name} is unavailable");
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
ulong? channelId = null;
|
||||
if (SandboxDetector.Detect() == SandboxType.Docker)
|
||||
client.MessageReactionAdded += Starbucks.Handler;
|
||||
client.MessageReactionAdded += AntipiracyMonitor.OnReaction;
|
||||
|
||||
client.MessageCreated += AntipiracyMonitor.OnMessageCreated; // should be first
|
||||
client.MessageCreated += ProductCodeLookup.OnMessageCreated;
|
||||
client.MessageCreated += LogParsingHandler.OnMessageCreated;
|
||||
client.MessageCreated += LogAsTextMonitor.OnMessageCreated;
|
||||
client.MessageCreated += DiscordInviteFilter.OnMessageCreated;
|
||||
client.MessageCreated += PostLogHelpHandler.OnMessageCreated;
|
||||
client.MessageCreated += BotReactionsHandler.OnMessageCreated;
|
||||
client.MessageCreated += AppveyorLinksHandler.OnMessageCreated;
|
||||
client.MessageCreated += GithubLinksHandler.OnMessageCreated;
|
||||
client.MessageCreated += NewBuildsMonitor.OnMessageCreated;
|
||||
client.MessageCreated += TableFlipMonitor.OnMessageCreated;
|
||||
client.MessageCreated += IsTheGamePlayableHandler.OnMessageCreated;
|
||||
client.MessageCreated += EmpathySimulationHandler.OnMessageCreated;
|
||||
|
||||
client.MessageUpdated += AntipiracyMonitor.OnMessageUpdated;
|
||||
client.MessageUpdated += DiscordInviteFilter.OnMessageUpdated;
|
||||
client.MessageUpdated += EmpathySimulationHandler.OnMessageUpdated;
|
||||
|
||||
client.MessageDeleted += ThumbnailCacheMonitor.OnMessageDeleted;
|
||||
client.MessageDeleted += EmpathySimulationHandler.OnMessageDeleted;
|
||||
|
||||
client.UserUpdated += UsernameSpoofMonitor.OnUserUpdated;
|
||||
client.UserUpdated += UsernameZalgoMonitor.OnUserUpdated;
|
||||
|
||||
client.GuildMemberAdded += Greeter.OnMemberAdded;
|
||||
client.GuildMemberAdded += UsernameSpoofMonitor.OnMemberAdded;
|
||||
client.GuildMemberAdded += UsernameZalgoMonitor.OnMemberAdded;
|
||||
|
||||
client.GuildMemberUpdated += UsernameSpoofMonitor.OnMemberUpdated;
|
||||
client.GuildMemberUpdated += UsernameZalgoMonitor.OnMemberUpdated;
|
||||
|
||||
client.DebugLogger.LogMessageReceived += (sender, eventArgs) =>
|
||||
{
|
||||
Action<Exception, string> logLevel = Config.Log.Info;
|
||||
if (eventArgs.Level == LogLevel.Debug)
|
||||
logLevel = Config.Log.Debug;
|
||||
else if (eventArgs.Level == LogLevel.Info)
|
||||
{
|
||||
using (var db = new BotDb())
|
||||
{
|
||||
var chState = db.BotState.FirstOrDefault(k => k.Key == "bot-restart-channel");
|
||||
if (chState != null)
|
||||
{
|
||||
if (ulong.TryParse(chState.Value, out var ch))
|
||||
channelId = ch;
|
||||
db.BotState.Remove(chState);
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
//logLevel = Config.Log.Info;
|
||||
if (eventArgs.Message?.Contains("Session resumed") ?? false)
|
||||
Watchdog.DisconnectTimestamps.Clear();
|
||||
}
|
||||
if (args.LastOrDefault() is string strCh && ulong.TryParse(strCh, out var chId))
|
||||
channelId = chId;
|
||||
|
||||
if (channelId.HasValue)
|
||||
else if (eventArgs.Level == LogLevel.Warning)
|
||||
{
|
||||
Config.Log.Info($"Found channelId {channelId}");
|
||||
DiscordChannel channel;
|
||||
if (channelId == InvalidChannelId)
|
||||
{
|
||||
channel = await client.GetChannelAsync(Config.ThumbnailSpamId).ConfigureAwait(false);
|
||||
await channel.SendMessageAsync("Bot has suffered some catastrophic failure and was restarted").ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
channel = await client.GetChannelAsync(channelId.Value).ConfigureAwait(false);
|
||||
await channel.SendMessageAsync("Bot is up and running").ConfigureAwait(false);
|
||||
}
|
||||
logLevel = Config.Log.Warn;
|
||||
if (eventArgs.Message?.Contains("Dispatch:PRESENCES_REPLACE") ?? false)
|
||||
BotStatusMonitor.RefreshAsync(client).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
else if (eventArgs.Level == LogLevel.Error)
|
||||
logLevel = Config.Log.Error;
|
||||
else if (eventArgs.Level == LogLevel.Critical)
|
||||
{
|
||||
logLevel = Config.Log.Fatal;
|
||||
if ((eventArgs.Message?.Contains("Socket connection terminated") ?? false)
|
||||
|| (eventArgs.Message?.Contains("heartbeats were skipped. Issuing reconnect.") ?? false))
|
||||
Watchdog.DisconnectTimestamps.Enqueue(DateTime.UtcNow);
|
||||
}
|
||||
logLevel(eventArgs.Exception, eventArgs.Message);
|
||||
};
|
||||
Watchdog.DisconnectTimestamps.Enqueue(DateTime.UtcNow);
|
||||
|
||||
try
|
||||
{
|
||||
await client.ConnectAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Config.Log.Error(e, "Failed to connect to Discord: " + e.Message);
|
||||
throw;
|
||||
}
|
||||
|
||||
ulong? channelId = null;
|
||||
if (SandboxDetector.Detect() == SandboxType.Docker)
|
||||
{
|
||||
using var db = new BotDb();
|
||||
var chState = db.BotState.FirstOrDefault(k => k.Key == "bot-restart-channel");
|
||||
if (chState != null)
|
||||
{
|
||||
if (ulong.TryParse(chState.Value, out var ch))
|
||||
channelId = ch;
|
||||
db.BotState.Remove(chState);
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
if (args.LastOrDefault() is string strCh && ulong.TryParse(strCh, out var chId))
|
||||
channelId = chId;
|
||||
|
||||
if (channelId.HasValue)
|
||||
{
|
||||
Config.Log.Info($"Found channelId {channelId}");
|
||||
DiscordChannel channel;
|
||||
if (channelId == InvalidChannelId)
|
||||
{
|
||||
channel = await client.GetChannelAsync(Config.ThumbnailSpamId).ConfigureAwait(false);
|
||||
await channel.SendMessageAsync("Bot has suffered some catastrophic failure and was restarted").ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Config.Log.Debug($"Args count: {args.Length}");
|
||||
var pArgs = args.Select(a => a == Config.Token ? "<Token>" : $"[{a}]");
|
||||
Config.Log.Debug("Args: " + string.Join(" ", pArgs));
|
||||
channel = await client.GetChannelAsync(channelId.Value).ConfigureAwait(false);
|
||||
await channel.SendMessageAsync("Bot is up and running").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Config.Log.Debug($"Args count: {args.Length}");
|
||||
var pArgs = args.Select(a => a == Config.Token ? "<Token>" : $"[{a}]");
|
||||
Config.Log.Debug("Args: " + string.Join(" ", pArgs));
|
||||
}
|
||||
|
||||
Config.Log.Debug("Running RPCS3 update check thread");
|
||||
backgroundTasks = Task.WhenAll(
|
||||
backgroundTasks,
|
||||
NewBuildsMonitor.MonitorAsync(client),
|
||||
Watchdog.Watch(client),
|
||||
InviteWhitelistProvider.CleanupAsync(client)
|
||||
);
|
||||
Config.Log.Debug("Running RPCS3 update check thread");
|
||||
backgroundTasks = Task.WhenAll(
|
||||
backgroundTasks,
|
||||
NewBuildsMonitor.MonitorAsync(client),
|
||||
Watchdog.Watch(client),
|
||||
InviteWhitelistProvider.CleanupAsync(client)
|
||||
);
|
||||
|
||||
while (!Config.Cts.IsCancellationRequested)
|
||||
{
|
||||
if (client.Ping > 1000)
|
||||
Config.Log.Warn($"High ping detected: {client.Ping}");
|
||||
await Task.Delay(TimeSpan.FromMinutes(1), Config.Cts.Token).ContinueWith(dt => {/* in case it was cancelled */}, TaskScheduler.Default).ConfigureAwait(false);
|
||||
}
|
||||
while (!Config.Cts.IsCancellationRequested)
|
||||
{
|
||||
if (client.Ping > 1000)
|
||||
Config.Log.Warn($"High ping detected: {client.Ping}");
|
||||
await Task.Delay(TimeSpan.FromMinutes(1), Config.Cts.Token).ContinueWith(dt => {/* in case it was cancelled */}, TaskScheduler.Default).ConfigureAwait(false);
|
||||
}
|
||||
await backgroundTasks.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -77,64 +77,57 @@ namespace CompatBot.ThumbScrapper
|
||||
using (var downloadStream = await HttpClient.GetStreamAsync(TitleDownloadLink).ConfigureAwait(false))
|
||||
await downloadStream.CopyToAsync(fileStream, 16384, cancellationToken).ConfigureAwait(false);
|
||||
fileStream.Seek(0, SeekOrigin.Begin);
|
||||
using (var zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Read))
|
||||
using var zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Read);
|
||||
var logEntry = zipArchive.Entries.FirstOrDefault(e => e.Name.EndsWith(".xml", StringComparison.InvariantCultureIgnoreCase));
|
||||
if (logEntry == null)
|
||||
throw new InvalidOperationException("No zip entries that match the .xml criteria");
|
||||
|
||||
using var zipStream = logEntry.Open();
|
||||
using var xmlReader = XmlReader.Create(zipStream, new XmlReaderSettings { Async = true });
|
||||
xmlReader.ReadToFollowing("PS3TDB");
|
||||
var version = xmlReader.GetAttribute("version");
|
||||
if (!DateTime.TryParseExact(version, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var timestamp))
|
||||
return;
|
||||
|
||||
if (ScrapeStateProvider.IsFresh("PS3TDB", timestamp))
|
||||
{
|
||||
var logEntry = zipArchive.Entries.FirstOrDefault(e => e.Name.EndsWith(".xml", StringComparison.InvariantCultureIgnoreCase));
|
||||
if (logEntry == null)
|
||||
throw new InvalidOperationException("No zip entries that match the .xml criteria");
|
||||
await ScrapeStateProvider.SetLastRunTimestampAsync("PS3TDB").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
using (var zipStream = logEntry.Open())
|
||||
using (var xmlReader = XmlReader.Create(zipStream, new XmlReaderSettings { Async = true }))
|
||||
while (!cancellationToken.IsCancellationRequested && xmlReader.ReadToFollowing("game"))
|
||||
{
|
||||
if (xmlReader.ReadToFollowing("id"))
|
||||
{
|
||||
xmlReader.ReadToFollowing("PS3TDB");
|
||||
var version = xmlReader.GetAttribute("version");
|
||||
if (!DateTime.TryParseExact(version, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var timestamp))
|
||||
return;
|
||||
var productId = (await xmlReader.ReadElementContentAsStringAsync().ConfigureAwait(false)).ToUpperInvariant();
|
||||
if (!ProductCodeLookup.ProductCode.IsMatch(productId))
|
||||
continue;
|
||||
|
||||
if (ScrapeStateProvider.IsFresh("PS3TDB", timestamp))
|
||||
{
|
||||
await ScrapeStateProvider.SetLastRunTimestampAsync("PS3TDB").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
string title = null;
|
||||
if (xmlReader.ReadToFollowing("locale") && xmlReader.ReadToFollowing("title"))
|
||||
title = await xmlReader.ReadElementContentAsStringAsync().ConfigureAwait(false);
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested && xmlReader.ReadToFollowing("game"))
|
||||
if (!string.IsNullOrEmpty(title))
|
||||
{
|
||||
if (xmlReader.ReadToFollowing("id"))
|
||||
using var db = new ThumbnailDb();
|
||||
var item = await db.Thumbnail.FirstOrDefaultAsync(t => t.ProductCode == productId, cancellationToken).ConfigureAwait(false);
|
||||
if (item == null)
|
||||
{
|
||||
var productId = (await xmlReader.ReadElementContentAsStringAsync().ConfigureAwait(false)).ToUpperInvariant();
|
||||
if (!ProductCodeLookup.ProductCode.IsMatch(productId))
|
||||
continue;
|
||||
|
||||
string title = null;
|
||||
if (xmlReader.ReadToFollowing("locale") && xmlReader.ReadToFollowing("title"))
|
||||
title = await xmlReader.ReadElementContentAsStringAsync().ConfigureAwait(false);
|
||||
|
||||
if (!string.IsNullOrEmpty(title))
|
||||
await db.Thumbnail.AddAsync(new Thumbnail {ProductCode = productId, Name = title}, cancellationToken).ConfigureAwait(false);
|
||||
await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (item.Name != title && item.Timestamp == 0)
|
||||
{
|
||||
using (var db = new ThumbnailDb())
|
||||
{
|
||||
var item = await db.Thumbnail.FirstOrDefaultAsync(t => t.ProductCode == productId, cancellationToken).ConfigureAwait(false);
|
||||
if (item == null)
|
||||
{
|
||||
await db.Thumbnail.AddAsync(new Thumbnail {ProductCode = productId, Name = title}, cancellationToken).ConfigureAwait(false);
|
||||
await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (item.Name != title && item.Timestamp == 0)
|
||||
{
|
||||
item.Name = title;
|
||||
await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
item.Name = title;
|
||||
await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
await ScrapeStateProvider.SetLastRunTimestampAsync("PS3TDB").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
}
|
||||
await ScrapeStateProvider.SetLastRunTimestampAsync("PS3TDB").ConfigureAwait(false);
|
||||
}
|
||||
await ScrapeStateProvider.SetLastRunTimestampAsync(container).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -349,42 +349,40 @@ namespace CompatBot.ThumbScrapper
|
||||
return;
|
||||
|
||||
name = string.IsNullOrEmpty(name) ? null : name;
|
||||
using (var db = new ThumbnailDb())
|
||||
using var db = new ThumbnailDb();
|
||||
var savedItem = db.Thumbnail.FirstOrDefault(t => t.ProductCode == productCode);
|
||||
if (savedItem == null)
|
||||
{
|
||||
var savedItem = db.Thumbnail.FirstOrDefault(t => t.ProductCode == productCode);
|
||||
if (savedItem == null)
|
||||
var newItem = new Thumbnail
|
||||
{
|
||||
var newItem = new Thumbnail
|
||||
{
|
||||
ProductCode = productCode,
|
||||
ContentId = contentId,
|
||||
Name = name,
|
||||
Url = url,
|
||||
Timestamp = DateTime.UtcNow.Ticks,
|
||||
};
|
||||
db.Thumbnail.Add(newItem);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(url))
|
||||
{
|
||||
if (string.IsNullOrEmpty(savedItem.Url))
|
||||
savedItem.Url = url;
|
||||
if (string.IsNullOrEmpty(savedItem.Name) && !string.IsNullOrEmpty(name))
|
||||
savedItem.Name = name;
|
||||
if (!ScrapeStateProvider.IsFresh(savedItem.Timestamp))
|
||||
{
|
||||
if (savedItem.Url != url)
|
||||
{
|
||||
savedItem.Url = url;
|
||||
savedItem.EmbeddableUrl = null;
|
||||
}
|
||||
if (name != null && savedItem.Name != name)
|
||||
savedItem.Name = name;
|
||||
}
|
||||
savedItem.ContentId = contentId;
|
||||
savedItem.Timestamp = DateTime.UtcNow.Ticks;
|
||||
}
|
||||
await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
ProductCode = productCode,
|
||||
ContentId = contentId,
|
||||
Name = name,
|
||||
Url = url,
|
||||
Timestamp = DateTime.UtcNow.Ticks,
|
||||
};
|
||||
db.Thumbnail.Add(newItem);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(url))
|
||||
{
|
||||
if (string.IsNullOrEmpty(savedItem.Url))
|
||||
savedItem.Url = url;
|
||||
if (string.IsNullOrEmpty(savedItem.Name) && !string.IsNullOrEmpty(name))
|
||||
savedItem.Name = name;
|
||||
if (!ScrapeStateProvider.IsFresh(savedItem.Timestamp))
|
||||
{
|
||||
if (savedItem.Url != url)
|
||||
{
|
||||
savedItem.Url = url;
|
||||
savedItem.EmbeddableUrl = null;
|
||||
}
|
||||
if (name != null && savedItem.Name != name)
|
||||
savedItem.Name = name;
|
||||
}
|
||||
savedItem.ContentId = contentId;
|
||||
savedItem.Timestamp = DateTime.UtcNow.Ticks;
|
||||
}
|
||||
await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async Task ScrapeContainerIdsAsync(string locale, string containerId, HashSet<string> knownContainerIds, CancellationToken cancellationToken)
|
||||
|
||||
@@ -18,24 +18,22 @@ namespace CompatBot.Utils
|
||||
return defaultColor;
|
||||
|
||||
var analyzer = new ColorThief();
|
||||
using (var stream = new MemoryStream(jpg))
|
||||
{
|
||||
var bmp = new Bitmap(stream, false);
|
||||
var palette = analyzer.GetPalette(bmp, 4, ignoreWhite: false);
|
||||
var colors = palette
|
||||
.Select(p => new {c = p.Color, hsl = p.Color.ToHsl()})
|
||||
.OrderBy(p => Math.Abs(0.75 - p.hsl.L))
|
||||
.ThenByDescending(p => p.hsl.S)
|
||||
.ToList();
|
||||
using var stream = new MemoryStream(jpg);
|
||||
var bmp = new Bitmap(stream, false);
|
||||
var palette = analyzer.GetPalette(bmp, 4, ignoreWhite: false);
|
||||
var colors = palette
|
||||
.Select(p => new {c = p.Color, hsl = p.Color.ToHsl()})
|
||||
.OrderBy(p => Math.Abs(0.75 - p.hsl.L))
|
||||
.ThenByDescending(p => p.hsl.S)
|
||||
.ToList();
|
||||
#if DEBUG
|
||||
|
||||
Config.Log.Trace("Selected palette:");
|
||||
foreach (var cl in colors)
|
||||
Config.Log.Trace($"{cl.c.ToHexString()}, HSL: {cl.hsl.H+90:#00} {cl.hsl.S:0.00} {cl.hsl.L:0.00}");
|
||||
Config.Log.Trace("Selected palette:");
|
||||
foreach (var cl in colors)
|
||||
Config.Log.Trace($"{cl.c.ToHexString()}, HSL: {cl.hsl.H+90:#00} {cl.hsl.S:0.00} {cl.hsl.L:0.00}");
|
||||
#endif
|
||||
var c = colors[0].c;
|
||||
return new DiscordColor(c.R, c.G, c.B);
|
||||
}
|
||||
var c = colors[0].c;
|
||||
return new DiscordColor(c.R, c.G, c.B);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -14,21 +14,19 @@ namespace CompatBot.Utils
|
||||
if (selector == null)
|
||||
throw new ArgumentNullException(nameof(selector));
|
||||
|
||||
using (var e = source.GetEnumerator())
|
||||
using var e = source.GetEnumerator();
|
||||
if (!e.MoveNext())
|
||||
yield break;
|
||||
|
||||
T prev = e.Current;
|
||||
if (!e.MoveNext())
|
||||
yield break;
|
||||
|
||||
do
|
||||
{
|
||||
if (!e.MoveNext())
|
||||
yield break;
|
||||
|
||||
T prev = e.Current;
|
||||
if (!e.MoveNext())
|
||||
yield break;
|
||||
|
||||
do
|
||||
{
|
||||
yield return selector(prev, e.Current);
|
||||
prev = e.Current;
|
||||
} while (e.MoveNext());
|
||||
}
|
||||
yield return selector(prev, e.Current);
|
||||
prev = e.Current;
|
||||
} while (e.MoveNext());
|
||||
}
|
||||
|
||||
public static IEnumerable<T> Single<T>(T item)
|
||||
|
||||
@@ -22,32 +22,32 @@ namespace HomoglyphConverter
|
||||
var assembly = Assembly.GetAssembly(typeof(ConfusablesBuilder));
|
||||
var resourceName = assembly.GetManifestResourceNames().FirstOrDefault(n => n.EndsWith("confusables.txt.gz", StringComparison.InvariantCultureIgnoreCase));
|
||||
using (var stream = assembly.GetManifestResourceStream(resourceName))
|
||||
using (var gzip = new GZipStream(stream, CompressionMode.Decompress))
|
||||
using (var reader = new StreamReader(gzip, Encoding.UTF8, false))
|
||||
{
|
||||
using var gzip = new GZipStream(stream, CompressionMode.Decompress);
|
||||
using var reader = new StreamReader(gzip, Encoding.UTF8, false);
|
||||
string line;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
string line;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
if (string.IsNullOrEmpty(line) || line.StartsWith("#"))
|
||||
continue;
|
||||
|
||||
var lineParts = line.Split(CommentSplitter, 2);
|
||||
var mapping = lineParts[0].Split(FieldSplitter, 3);
|
||||
if (mapping.Length < 2)
|
||||
throw new InvalidOperationException("Invalid confusable mapping line: " + line);
|
||||
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(line) || line.StartsWith("#"))
|
||||
continue;
|
||||
|
||||
var lineParts = line.Split(CommentSplitter, 2);
|
||||
var mapping = lineParts[0].Split(FieldSplitter, 3);
|
||||
if (mapping.Length < 2)
|
||||
throw new InvalidOperationException("Invalid confusable mapping line: " + line);
|
||||
|
||||
try
|
||||
{
|
||||
var confusableChar = uint.Parse(mapping[0].Trim(), NumberStyles.HexNumber);
|
||||
var skeletonChars = mapping[1].Split(PairSplitter, StringSplitOptions.RemoveEmptyEntries).Select(l => uint.Parse(l, NumberStyles.HexNumber)).ToArray();
|
||||
result.Add(confusableChar, skeletonChars);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidOperationException("Invalid confusable mapping line:" + line, e);
|
||||
}
|
||||
var confusableChar = uint.Parse(mapping[0].Trim(), NumberStyles.HexNumber);
|
||||
var skeletonChars = mapping[1].Split(PairSplitter, StringSplitOptions.RemoveEmptyEntries).Select(l => uint.Parse(l, NumberStyles.HexNumber)).ToArray();
|
||||
result.Add(confusableChar, skeletonChars);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidOperationException("Invalid confusable mapping line:" + line, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result.Count == 0)
|
||||
throw new InvalidOperationException("Empty confusable mapping source");
|
||||
|
||||
|
||||
@@ -19,15 +19,15 @@ namespace Tests
|
||||
var resultPath = Path.Combine(Path.GetDirectoryName(samplePath), "zalgo.txt");
|
||||
|
||||
var names = await File.ReadAllLinesAsync(samplePath, Encoding.UTF8);
|
||||
using (var r = File.Open(resultPath, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||
using (var w = new StreamWriter(r, new UTF8Encoding(false)))
|
||||
foreach (var line in names)
|
||||
{
|
||||
var user = UserInfo.Parse(line);
|
||||
var isZalgo = UsernameZalgoMonitor.NeedsRename(user.DisplayName);
|
||||
if (isZalgo)
|
||||
await w.WriteLineAsync(user.DisplayName).ConfigureAwait(false);
|
||||
}
|
||||
using var r = File.Open(resultPath, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
using var w = new StreamWriter(r, new UTF8Encoding(false));
|
||||
foreach (var line in names)
|
||||
{
|
||||
var user = UserInfo.Parse(line);
|
||||
var isZalgo = UsernameZalgoMonitor.NeedsRename(user.DisplayName);
|
||||
if (isZalgo)
|
||||
await w.WriteLineAsync(user.DisplayName).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[Test, Explicit("Requires external data")]
|
||||
@@ -39,7 +39,8 @@ namespace Tests
|
||||
var stats = new int[10];
|
||||
var names = await File.ReadAllLinesAsync(samplePath, Encoding.UTF8);
|
||||
using (var r = File.Open(resultPath, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||
using (var w = new StreamWriter(r, new UTF8Encoding(false)))
|
||||
{
|
||||
using var w = new StreamWriter(r, new UTF8Encoding(false));
|
||||
foreach (var line in names)
|
||||
{
|
||||
var user = UserInfo.Parse(line);
|
||||
@@ -49,6 +50,7 @@ namespace Tests
|
||||
await w.WriteAsync('\t').ConfigureAwait(false);
|
||||
await w.WriteLineAsync(user.DisplayName).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < stats.Length && stats[i] > 0; i++)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user