botupdate, part 2

This commit is contained in:
13xforever
2020-11-13 02:48:49 +05:00
parent d65444f0be
commit 610c01318a
55 changed files with 543 additions and 558 deletions

View File

@@ -171,17 +171,16 @@ namespace PsnClient
}
}
public async Task<Container?> GetGameContainerAsync(string locale, string containerId, int start, int take, Dictionary<string, string?>? filters, CancellationToken cancellationToken)
public async Task<Container?> GetGameContainerAsync(string locale, string containerId, int start, int take, Dictionary<string, string> filters, CancellationToken cancellationToken)
{
try
{
var (language, country) = locale.AsLocaleData();
var url = new Uri($"https://store.playstation.com/valkyrie-api/{language}/{country}/999/container/{containerId}");
filters ??= new Dictionary<string, string?>();
filters["start"] = start.ToString();
filters["size"] = take.ToString();
filters["bucket"] = "games";
url = url.SetQueryParameters(filters);
url = url.SetQueryParameters(filters!);
using var message = new HttpRequestMessage(HttpMethod.Get, url);
using var response = await client.SendAsync(message, cancellationToken).ConfigureAwait(false);
try

View File

@@ -212,7 +212,7 @@ namespace CompatBot.Commands
ContentFilter.RebuildMatcher();
}
private async Task EditFilterCmd(CommandContext ctx, BotDb db, Piracystring filter)
private static async Task EditFilterCmd(CommandContext ctx, BotDb db, Piracystring filter)
{
var (success, msg) = await EditFilterPropertiesAsync(ctx, db, filter).ConfigureAwait(false);
if (success)
@@ -273,9 +273,10 @@ namespace CompatBot.Commands
"Any simple string that is used to flag potential content for a check using Validation regex.\n" +
"**Must** be sufficiently long to reduce the number of checks."
);
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Please specify a new **trigger**", embed: embed).ConfigureAwait(false);
errorMsg = null;
(msg, txt, emoji) = await interact.WaitForMessageOrReactionAsync(msg, ctx.User, InteractTimeout, abort, lastPage, nextPage, (filter.IsComplete() ? saveEdit : null)).ConfigureAwait(false);
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Please specify a new **trigger**", embed: embed).ConfigureAwait(false);
var tmp = await interact.WaitForMessageOrReactionAsync(msg, ctx.User, InteractTimeout, abort, lastPage, nextPage, (filter.IsComplete() ? saveEdit : null)).ConfigureAwait(false);
(msg, txt, emoji) = tmp; //workaround compiler warning
if (emoji != null)
{
if (emoji.Emoji == abort)
@@ -317,9 +318,10 @@ namespace CompatBot.Commands
$"**`L`** = **`{FilterContext.Log}`** will apply it during log parsing.\n" +
"Reactions will toggle the context, text message will set the specified flags."
);
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Please specify filter **context(s)**", embed: embed).ConfigureAwait(false);
errorMsg = null;
(msg, txt, emoji) = await interact.WaitForMessageOrReactionAsync(msg, ctx.User, InteractTimeout, abort, previousPage, nextPage, letterC, letterL, (filter.IsComplete() ? saveEdit : null)).ConfigureAwait(false);
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Please specify filter **context(s)**", embed: embed).ConfigureAwait(false);
tmp = await interact.WaitForMessageOrReactionAsync(msg, ctx.User, InteractTimeout, abort, previousPage, nextPage, letterC, letterL, (filter.IsComplete() ? saveEdit : null)).ConfigureAwait(false);
(msg, txt, emoji) = tmp; //workaround compiler warning
if (emoji != null)
{
if (emoji.Emoji == abort)
@@ -388,9 +390,10 @@ namespace CompatBot.Commands
$"**`U`** = **`{FilterAction.MuteModQueue}`** mute mod queue reporting for this action.\n" +
"Reactions will toggle the action, text message will set the specified flags."
);
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Please specify filter **action(s)**", embed: embed).ConfigureAwait(false);
errorMsg = null;
(msg, txt, emoji) = await interact.WaitForMessageOrReactionAsync(msg, ctx.User, InteractTimeout, abort, previousPage, nextPage, letterR, letterW, letterM, letterE, letterU, (filter.IsComplete() ? saveEdit : null)).ConfigureAwait(false);
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Please specify filter **action(s)**", embed: embed).ConfigureAwait(false);
tmp = await interact.WaitForMessageOrReactionAsync(msg, ctx.User, InteractTimeout, abort, previousPage, nextPage, letterR, letterW, letterM, letterE, letterU, (filter.IsComplete() ? saveEdit : null)).ConfigureAwait(false);
(msg, txt, emoji) = tmp; //workaround compiler warning
if (emoji != null)
{
if (emoji.Emoji == abort)
@@ -497,10 +500,11 @@ namespace CompatBot.Commands
"**Please [test](https://regex101.com/) your regex**. Following flags are enabled: Multiline, IgnoreCase.\n" +
"Additional validation can help reduce false positives of a plaintext trigger match."
);
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Please specify filter **validation regex**", embed: embed).ConfigureAwait(false);
errorMsg = null;
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Please specify filter **validation regex**", embed: embed).ConfigureAwait(false);
var next = (filter.Actions & (FilterAction.SendMessage | FilterAction.ShowExplain)) == 0 ? firstPage : nextPage;
(msg, txt, emoji) = await interact.WaitForMessageOrReactionAsync(msg, ctx.User, InteractTimeout, abort, previousPage, next, (string.IsNullOrEmpty(filter.ValidatingRegex) ? null : trash), (filter.IsComplete() ? saveEdit : null)).ConfigureAwait(false);
tmp = await interact.WaitForMessageOrReactionAsync(msg, ctx.User, InteractTimeout, abort, previousPage, next, (string.IsNullOrEmpty(filter.ValidatingRegex) ? null : trash), (filter.IsComplete() ? saveEdit : null)).ConfigureAwait(false);
(msg, txt, emoji) = tmp; //workaround compiler warning
if (emoji != null)
{
if (emoji.Emoji == abort)
@@ -554,10 +558,11 @@ namespace CompatBot.Commands
"Optional custom message sent to the user.\n" +
"If left empty, default piracy warning message will be used."
);
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Please specify filter **validation regex**", embed: embed).ConfigureAwait(false);
errorMsg = null;
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Please specify filter **validation regex**", embed: embed).ConfigureAwait(false);
next = (filter.Actions.HasFlag(FilterAction.ShowExplain) ? nextPage : firstPage);
(msg, txt, emoji) = await interact.WaitForMessageOrReactionAsync(msg, ctx.User, InteractTimeout, abort, previousPage, next, (filter.IsComplete() ? saveEdit : null)).ConfigureAwait(false);
tmp = await interact.WaitForMessageOrReactionAsync(msg, ctx.User, InteractTimeout, abort, previousPage, next, (filter.IsComplete() ? saveEdit : null)).ConfigureAwait(false);
(msg, txt, emoji) = tmp; //workaround compiler warning
if (emoji != null)
{
if (emoji.Emoji == abort)
@@ -594,9 +599,10 @@ namespace CompatBot.Commands
"Explanation term that is used to show an explanation.\n" +
"**__Currently not implemented__**."
);
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Please specify filter **explanation term**", embed: embed).ConfigureAwait(false);
errorMsg = null;
(msg, txt, emoji) = await interact.WaitForMessageOrReactionAsync(msg, ctx.User, InteractTimeout, abort, previousPage, firstPage, (filter.IsComplete() ? saveEdit : null)).ConfigureAwait(false);
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Please specify filter **explanation term**", embed: embed).ConfigureAwait(false);
tmp = await interact.WaitForMessageOrReactionAsync(msg, ctx.User, InteractTimeout, abort, previousPage, firstPage, (filter.IsComplete() ? saveEdit : null)).ConfigureAwait(false);
(msg, txt, emoji) = tmp; //workaround compiler warning
if (emoji != null)
{
if (emoji.Emoji == abort)
@@ -641,9 +647,10 @@ namespace CompatBot.Commands
if (errorMsg == null && !filter.IsComplete())
errorMsg = "Some required properties are not defined";
embed = FormatFilter(filter, errorMsg);
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Does this look good? (y/n)", embed: embed.Build()).ConfigureAwait(false);
errorMsg = null;
(msg, txt, emoji) = await interact.WaitForMessageOrReactionAsync(msg, ctx.User, InteractTimeout, abort, previousPage, firstPage, (filter.IsComplete() ? saveEdit : null)).ConfigureAwait(false);
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Does this look good? (y/n)", embed: embed.Build()).ConfigureAwait(false);
tmp = await interact.WaitForMessageOrReactionAsync(msg, ctx.User, InteractTimeout, abort, previousPage, firstPage, (filter.IsComplete() ? saveEdit : null)).ConfigureAwait(false);
(msg, txt, emoji) = tmp; //workaround compiler warning
if (emoji != null)
{
if (emoji.Emoji == abort)

View File

@@ -10,7 +10,7 @@ namespace CompatBot.Commands
internal sealed class Events: EventsBaseCommand
{
[GroupCommand]
public Task NearestGenericEvent(CommandContext ctx, [Description("Optional event name"), RemainingText] string eventName = null)
public Task NearestGenericEvent(CommandContext ctx, [Description("Optional event name"), RemainingText] string? eventName = null)
=> NearestEvent(ctx, eventName);
[Command("add"), RequiresBotModRole]
@@ -56,7 +56,7 @@ namespace CompatBot.Commands
[Command("countdown")]
[Description("Provides countdown for the nearest known event")]
public Task Countdown(CommandContext ctx, string eventName = null)
public Task Countdown(CommandContext ctx, string? eventName = null)
=> NearestEvent(ctx, eventName);
}
}

View File

@@ -23,12 +23,12 @@ namespace CompatBot.Commands
private static readonly Regex Duration = new Regex(@"((?<days>\d+)(\.|d\s*))?((?<hours>\d+)(\:|h\s*))?((?<mins>\d+)m?)?",
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.ExplicitCapture);
protected async Task NearestEvent(CommandContext ctx, string eventName = null)
protected static async Task NearestEvent(CommandContext ctx, string? eventName = null)
{
var originalEventName = eventName = eventName.Trim(40);
var originalEventName = eventName = eventName?.Trim(40);
var current = DateTime.UtcNow;
var currentTicks = current.Ticks;
using var db = new BotDb();
await 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))
@@ -76,8 +76,8 @@ namespace CompatBot.Commands
return;
}
var noEventMsg = $"No information about the upcoming {eventName.Sanitize(replaceBackTicks: true)} at the moment";
if (eventName.Length > 10)
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)
{
@@ -109,8 +109,8 @@ namespace CompatBot.Commands
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)
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)
{
@@ -149,17 +149,15 @@ namespace CompatBot.Commands
await ctx.SendAutosplitMessageAsync(msg.TrimEnd(), blockStart: "", blockEnd: "").ConfigureAwait(false);
}
protected async Task Add(CommandContext ctx, string eventName = null)
protected static async Task Add(CommandContext ctx, string? eventName = null)
{
var evt = new EventSchedule();
var (success, msg) = await EditEventPropertiesAsync(ctx, evt, eventName).ConfigureAwait(false);
if (success)
{
using (var db = new BotDb())
{
await db.EventSchedule.AddAsync(evt).ConfigureAwait(false);
await db.SaveChangesAsync().ConfigureAwait(false);
}
await using var db = new BotDb();
await db.EventSchedule.AddAsync(evt).ConfigureAwait(false);
await db.SaveChangesAsync().ConfigureAwait(false);
await ctx.ReactWithAsync(Config.Reactions.Success).ConfigureAwait(false);
if (LimitedToSpamChannel.IsSpamChannel(ctx.Channel))
await msg.UpdateOrCreateMessageAsync(ctx.Channel, embed: FormatEvent(evt).WithTitle("Created new event schedule entry #" + evt.Id)).ConfigureAwait(false);
@@ -170,41 +168,35 @@ namespace CompatBot.Commands
await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Event creation aborted").ConfigureAwait(false);
}
protected async Task Remove(CommandContext ctx, params int[] ids)
protected static async Task Remove(CommandContext ctx, params int[] ids)
{
int removedCount;
using (var db = new BotDb())
{
var eventsToRemove = await db.EventSchedule.Where(e3e => ids.Contains(e3e.Id)).ToListAsync().ConfigureAwait(false);
db.EventSchedule.RemoveRange(eventsToRemove);
removedCount = await db.SaveChangesAsync().ConfigureAwait(false);
}
await using var db = new BotDb();
var eventsToRemove = await db.EventSchedule.Where(e3e => ids.Contains(e3e.Id)).ToListAsync().ConfigureAwait(false);
db.EventSchedule.RemoveRange(eventsToRemove);
var removedCount = await db.SaveChangesAsync().ConfigureAwait(false);
if (removedCount == ids.Length)
await ctx.RespondAsync($"Event{StringUtils.GetSuffix(ids.Length)} successfully removed!").ConfigureAwait(false);
else
await ctx.RespondAsync($"Removed {removedCount} event{StringUtils.GetSuffix(removedCount)}, but was asked to remove {ids.Length}").ConfigureAwait(false);
}
protected async Task Clear(CommandContext ctx, int? year = null)
protected static async Task Clear(CommandContext ctx, int? year = null)
{
var currentYear = DateTime.UtcNow.Year;
int removedCount;
using (var db = new BotDb())
{
var itemsToRemove = await db.EventSchedule.Where(e =>
year.HasValue
? e.Year == year
: e.Year < currentYear
).ToListAsync().ConfigureAwait(false);
db.EventSchedule.RemoveRange(itemsToRemove);
removedCount = await db.SaveChangesAsync().ConfigureAwait(false);
}
await using var db = new BotDb();
var itemsToRemove = await db.EventSchedule.Where(e =>
year.HasValue
? e.Year == year
: e.Year < currentYear
).ToListAsync().ConfigureAwait(false);
db.EventSchedule.RemoveRange(itemsToRemove);
var removedCount = await db.SaveChangesAsync().ConfigureAwait(false);
await ctx.RespondAsync($"Removed {removedCount} event{(removedCount == 1 ? "" : "s")}").ConfigureAwait(false);
}
protected async Task Update(CommandContext ctx, int id, string eventName = null)
protected static async Task Update(CommandContext ctx, int id, string? eventName = null)
{
using var db = new BotDb();
await 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);
@@ -224,36 +216,31 @@ namespace CompatBot.Commands
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);
}
}
protected async Task List(CommandContext ctx, string eventName = null, int? year = null)
protected static async Task List(CommandContext ctx, string? eventName = null, int? year = null)
{
var showAll = "all".Equals(eventName, StringComparison.InvariantCultureIgnoreCase);
var currentTicks = DateTime.UtcNow.Ticks;
List<EventSchedule> events;
using (var db = new BotDb())
await using var db = new BotDb();
IQueryable<EventSchedule> query = db.EventSchedule;
if (year.HasValue)
query = query.Where(e => e.Year == year);
else
{
IQueryable<EventSchedule> query = db.EventSchedule;
if (year.HasValue)
query = query.Where(e => e.Year == year);
else
{
if (!ctx.Channel.IsPrivate && !showAll)
query = query.Where(e => e.End > currentTicks);
}
if (!string.IsNullOrEmpty(eventName) && !showAll)
{
eventName = await FuzzyMatchEventName(db, eventName).ConfigureAwait(false);
query = query.Where(e => e.EventName == eventName);
}
events = await query
.OrderBy(e => e.Start)
.ToListAsync()
.ConfigureAwait(false);
if (!ctx.Channel.IsPrivate && !showAll)
query = query.Where(e => e.End > currentTicks);
}
if (!string.IsNullOrEmpty(eventName) && !showAll)
{
eventName = await FuzzyMatchEventName(db, eventName).ConfigureAwait(false);
query = query.Where(e => e.EventName == eventName);
}
List<EventSchedule> events = await query
.OrderBy(e => e.Start)
.ToListAsync()
.ConfigureAwait(false);
if (events.Count == 0)
{
await ctx.RespondAsync("There are no events to show").ConfigureAwait(false);
@@ -280,7 +267,7 @@ namespace CompatBot.Commands
var printName = string.IsNullOrEmpty(currentEvent) ? "Various independent events" : $"**{currentEvent} {currentYear} schedule**";
msg.AppendLine($"{printName} (UTC):");
}
msg.Append(StringUtils.InvisibleSpacer).Append("`");
msg.Append(StringUtils.InvisibleSpacer).Append('`');
if (ModProvider.IsMod(ctx.Message.Author.Id))
msg.Append($"[{evt.Id:0000}] ");
msg.Append($"{evt.Start.AsUtc():u}");
@@ -292,7 +279,7 @@ namespace CompatBot.Commands
await ch.SendAutosplitMessageAsync(msg, blockStart: "", blockEnd: "").ConfigureAwait(false);
}
private async Task<(bool success, DiscordMessage message)> EditEventPropertiesAsync(CommandContext ctx, EventSchedule evt, string eventName = null)
private static async Task<(bool success, DiscordMessage? message)> EditEventPropertiesAsync(CommandContext ctx, EventSchedule evt, string? eventName = null)
{
var interact = ctx.Client.GetInteractivity();
var abort = DiscordEmoji.FromUnicode("🛑");
@@ -304,17 +291,18 @@ namespace CompatBot.Commands
var saveEdit = DiscordEmoji.FromUnicode("💾");
var skipEventNameStep = !string.IsNullOrEmpty(eventName);
DiscordMessage msg = null;
string errorMsg = null;
DiscordMessage txt;
MessageReactionAddEventArgs emoji;
DiscordMessage? msg = null;
string? errorMsg = null;
DiscordMessage? txt;
MessageReactionAddEventArgs? emoji;
step1:
// step 1: get the new start date
var embed = FormatEvent(evt, errorMsg, 1).WithDescription($"Example: `{DateTime.UtcNow:yyyy-MM-dd HH:mm} PST`\nBy default all times use UTC, only limited number of time zones supported");
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Please specify a new **start date and time**", embed: embed).ConfigureAwait(false);
errorMsg = null;
(msg, txt, emoji) = await interact.WaitForMessageOrReactionAsync(msg, ctx.User, InteractTimeout, abort, lastPage, nextPage, (evt.IsComplete() ? saveEdit : null)).ConfigureAwait(false);
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Please specify a new **start date and time**", embed: embed).ConfigureAwait(false);
var tmp = await interact.WaitForMessageOrReactionAsync(msg, ctx.User, InteractTimeout, abort, lastPage, nextPage, (evt.IsComplete() ? saveEdit : null)).ConfigureAwait(false);
(msg, txt, emoji) = tmp; // nullability code analysis workaround
if (emoji != null)
{
if (emoji.Emoji == abort)
@@ -348,9 +336,10 @@ namespace CompatBot.Commands
step2:
// step 2: get the new duration
embed = FormatEvent(evt, errorMsg, 2).WithDescription("Example: `2d 1h 15m`, or `2.1:00`");
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Please specify a new **event duration**", embed: embed.Build()).ConfigureAwait(false);
errorMsg = null;
(msg, txt, emoji) = await interact.WaitForMessageOrReactionAsync(msg, ctx.User, InteractTimeout, abort, previousPage, nextPage, (evt.IsComplete() ? saveEdit : null)).ConfigureAwait(false);
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Please specify a new **event duration**", embed: embed.Build()).ConfigureAwait(false);
tmp = await interact.WaitForMessageOrReactionAsync(msg, ctx.User, InteractTimeout, abort, previousPage, nextPage, (evt.IsComplete() ? saveEdit : null)).ConfigureAwait(false);
(msg, txt, emoji) = tmp; // nullability code analysis workaround
if (emoji != null)
{
if (emoji.Emoji == abort)
@@ -382,9 +371,10 @@ namespace CompatBot.Commands
step3:
// step 3: get the new event name
embed = FormatEvent(evt, errorMsg, 3);
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Please specify a new **event name**", embed: embed.Build()).ConfigureAwait(false);
errorMsg = null;
(msg, txt, emoji) = await interact.WaitForMessageOrReactionAsync(msg, ctx.User, InteractTimeout, abort, previousPage, (string.IsNullOrEmpty(evt.EventName) ? null : trash), nextPage, (evt.IsComplete() ? saveEdit : null)).ConfigureAwait(false);
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Please specify a new **event name**", embed: embed.Build()).ConfigureAwait(false);
tmp = await interact.WaitForMessageOrReactionAsync(msg, ctx.User, InteractTimeout, abort, previousPage, (string.IsNullOrEmpty(evt.EventName) ? null : trash), nextPage, (evt.IsComplete() ? saveEdit : null)).ConfigureAwait(false);
(msg, txt, emoji) = tmp; // nullability code analysis workaround
if (emoji != null)
{
if (emoji.Emoji == abort)
@@ -409,7 +399,8 @@ namespace CompatBot.Commands
embed = FormatEvent(evt, errorMsg, 4);
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Please specify a new **schedule entry title**", embed: embed.Build()).ConfigureAwait(false);
errorMsg = null;
(msg, txt, emoji) = await interact.WaitForMessageOrReactionAsync(msg, ctx.User, InteractTimeout, abort, previousPage, firstPage, (evt.IsComplete() ? saveEdit : null)).ConfigureAwait(false);
tmp = await interact.WaitForMessageOrReactionAsync(msg, ctx.User, InteractTimeout, abort, previousPage, firstPage, (evt.IsComplete() ? saveEdit : null)).ConfigureAwait(false);
(msg, txt, emoji) = tmp; // nullability code analysis workaround
if (emoji != null)
{
if (emoji.Emoji == abort)
@@ -448,7 +439,8 @@ namespace CompatBot.Commands
embed = FormatEvent(evt, errorMsg);
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Does this look good? (y/n)", embed: embed.Build()).ConfigureAwait(false);
errorMsg = null;
(msg, txt, emoji) = await interact.WaitForMessageOrReactionAsync(msg, ctx.User, InteractTimeout, abort, previousPage, firstPage, (evt.IsComplete() ? saveEdit : null)).ConfigureAwait(false);
tmp = await interact.WaitForMessageOrReactionAsync(msg, ctx.User, InteractTimeout, abort, previousPage, firstPage, (evt.IsComplete() ? saveEdit : null)).ConfigureAwait(false);
(msg, txt, emoji) = tmp; // nullability code analysis workaround
if (emoji != null)
{
if (emoji.Emoji == abort)
@@ -497,7 +489,7 @@ namespace CompatBot.Commands
return (false, msg);
}
private static string NameWithoutLink(string name)
private static string? NameWithoutLink(string? name)
{
if (string.IsNullOrEmpty(name))
return name;
@@ -514,14 +506,14 @@ namespace CompatBot.Commands
return name;
}
private static async Task<string> FuzzyMatchEventName(BotDb db, string eventName)
private static async Task<string?> FuzzyMatchEventName(BotDb db, string? eventName)
{
var knownEventNames = await db.EventSchedule.Select(e => e.EventName).Distinct().ToListAsync().ConfigureAwait(false);
var (score, name) = knownEventNames.Select(n => (score: eventName.GetFuzzyCoefficientCached(n), name: n)).OrderByDescending(t => t.score).FirstOrDefault();
return score > 0.8 ? name : eventName;
}
private static async Task<string> FuzzyMatchEntryName(BotDb db, string eventName)
private static async Task<string?> FuzzyMatchEntryName(BotDb db, string? eventName)
{
var now = DateTime.UtcNow.Ticks;
var knownNames = await db.EventSchedule.Where(e => e.End > now).Select(e => e.Name).ToListAsync().ConfigureAwait(false);
@@ -539,9 +531,9 @@ namespace CompatBot.Commands
return null;
}
int.TryParse(d.Groups["days"].Value, out var days);
int.TryParse(d.Groups["hours"].Value, out var hours);
int.TryParse(d.Groups["mins"].Value, out var mins);
_ = int.TryParse(d.Groups["days"].Value, out var days);
_ = int.TryParse(d.Groups["hours"].Value, out var hours);
_ = int.TryParse(d.Groups["mins"].Value, out var mins);
if (days == 0 && hours == 0 && mins == 0)
{
if (react)
@@ -575,7 +567,7 @@ namespace CompatBot.Commands
return result;
}
private static DiscordEmbedBuilder FormatEvent(EventSchedule evt, string error = null, int highlight = -1)
private static DiscordEmbedBuilder FormatEvent(EventSchedule evt, string? error = null, int highlight = -1)
{
var start = evt.Start.AsUtc();
var field = 1;

View File

@@ -258,7 +258,7 @@ namespace CompatBot.Commands
term = term.ToLowerInvariant().StripQuotes();
await using var db = new BotDb();
var item = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == term).ConfigureAwait(false);
if (item == null)
if (item is null)
await ctx.ReactWithAsync(Config.Reactions.Failure, $"Term `{term}` is not defined").ConfigureAwait(false);
else
{
@@ -275,7 +275,7 @@ namespace CompatBot.Commands
term = term.ToLowerInvariant().StripQuotes();
await using var db = new BotDb();
var item = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == term).ConfigureAwait(false);
if (item == null)
if (item is 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);
@@ -314,7 +314,7 @@ namespace CompatBot.Commands
[Command("dump"), Aliases("download")]
[Description("Returns explanation text as a file attachment")]
public async Task Dump(CommandContext ctx, [RemainingText, Description("Term to dump **or** a link to a message containing the explanation")] string termOrLink = null)
public async Task Dump(CommandContext ctx, [RemainingText, Description("Term to dump **or** a link to a message containing the explanation")] string? termOrLink = null)
{
if (string.IsNullOrEmpty(termOrLink))
{
@@ -336,7 +336,7 @@ namespace CompatBot.Commands
await using var db = new BotDb();
var item = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == termOrLink).ConfigureAwait(false);
if (item == null)
if (item is null)
{
var term = ctx.Message.Content.Split(' ', 2).Last();
await ShowExplanation(ctx, term).ConfigureAwait(false);

View File

@@ -38,12 +38,12 @@ namespace CompatBot.Commands
);
foreach (var item in whitelistedInvites)
{
string guildName = null;
string? guildName = null;
if (!string.IsNullOrEmpty(item.InviteCode))
try
{
var invite = await ctx.Client.GetInviteByCodeAsync(item.InviteCode).ConfigureAwait(false);
guildName = invite?.Guild.Name;
guildName = invite.Guild.Name;
}
catch { }
if (string.IsNullOrEmpty(guildName))

View File

@@ -145,9 +145,9 @@ namespace CompatBot.Commands
[Command("roll")]
[Description("Generates a random number between 1 and maxValue. Can also roll dices like `2d6`. Default is 1d6")]
public async Task Roll(CommandContext ctx, [Description("Some positive natural number")] int maxValue = 6, [RemainingText, Description("Optional text")] string comment = null)
public async Task Roll(CommandContext ctx, [Description("Some positive natural number")] int maxValue = 6, [RemainingText, Description("Optional text")] string? comment = null)
{
string result = null;
string? result = null;
if (maxValue > 1)
lock (rng) result = (rng.Next(maxValue) + 1).ToString();
if (string.IsNullOrEmpty(result))
@@ -177,8 +177,7 @@ namespace CompatBot.Commands
lock (rng) rolls = Enumerable.Range(0, num).Select(_ => rng.Next(face) + 1).ToList();
var total = rolls.Sum();
var totalStr = total.ToString();
int.TryParse(m.Groups["mod"].Value, out var mod);
if (mod > 0)
if (int.TryParse(m.Groups["mod"].Value, out var mod) && mod > 0)
totalStr += $" + {mod} = {total + mod}";
var rollsStr = string.Join(' ', rolls);
if (rolls.Count > 1)
@@ -322,8 +321,9 @@ namespace CompatBot.Commands
var kdUser = await ctx.Client.GetUserAsync(272631898877198337ul).ConfigureAwait(false);
var kdMember = ctx.Client.GetMember(kdUser);
var kdMatch = new HashSet<string>(new[] {kdUser.Id.ToString(), kdUser.Username, kdMember?.DisplayName ?? "kd-11", "kd", "kd-11", "kd11", });
var botMember = ctx.Client.GetMember(ctx.Client.CurrentUser);
var botMatch = new HashSet<string>(new[] {botMember.Id.ToString(), botMember.Username, botMember.DisplayName, "yourself", "urself", "yoself",});
var botUser = ctx.Client.CurrentUser;
var botMember = ctx.Client.GetMember(botUser);
var botMatch = new HashSet<string>(new[] {botUser.Id.ToString(), botUser.Username, botMember?.DisplayName ?? "RPCS3 bot", "yourself", "urself", "yoself",});
var prefix = DateTime.UtcNow.ToString("yyyyMMddHH");
var words = whatever.Split(Separators);
@@ -350,7 +350,7 @@ namespace CompatBot.Commands
word = word[..^2];
}
void MakeCustomRoleRating(DiscordMember mem)
void MakeCustomRoleRating(DiscordMember? mem)
{
if (mem != null && !choiceFlags.Contains('f'))
{
@@ -372,7 +372,7 @@ namespace CompatBot.Commands
}
var appended = false;
DiscordMember member = null;
DiscordMember? member = null;
if (Me.Contains(word))
{
member = ctx.Member;
@@ -401,7 +401,7 @@ namespace CompatBot.Commands
result.Clear();
appended = true;
}
if (member == null && i == 0 && await ctx.ResolveMemberAsync(word).ConfigureAwait(false) is DiscordMember m)
if (member is null && i == 0 && await ctx.ResolveMemberAsync(word).ConfigureAwait(false) is DiscordMember m)
member = m;
if (member != null)
{
@@ -435,7 +435,7 @@ namespace CompatBot.Commands
}
if (!appended)
result.Append(word);
result.Append(suffix).Append(" ");
result.Append(suffix).Append(' ');
}
whatever = result.ToString();
var cutIdx = whatever.LastIndexOf("never mind");
@@ -465,7 +465,7 @@ namespace CompatBot.Commands
[Command("meme"), Aliases("memes"), Cooldown(1, 30, CooldownBucketType.Channel), Hidden]
[Description("No, memes are not implemented yet")]
public async Task Memes(CommandContext ctx, [RemainingText] string _ = null)
public async Task Memes(CommandContext ctx, [RemainingText] string? _ = null)
{
var ch = await ctx.GetChannelForSpamAsync().ConfigureAwait(false);
await ch.SendMessageAsync($"{ctx.User.Mention} congratulations, you're the meme").ConfigureAwait(false);
@@ -488,8 +488,8 @@ namespace CompatBot.Commands
public async Task ProductCode(CommandContext ctx, [RemainingText, Description("Product code such as BLUS12345 or SCES")] string productCode)
{
productCode = ProductCodeLookup.GetProductIds(productCode).FirstOrDefault() ?? productCode;
productCode = productCode?.ToUpperInvariant();
if (productCode?.Length > 3)
productCode = productCode.ToUpperInvariant();
if (productCode.Length > 3)
{
var dsc = ProductCodeDecoder.Decode(productCode);
var info = string.Join('\n', dsc);

View File

@@ -14,7 +14,7 @@ namespace CompatBot.Commands
{
[Command("report"), RequiresWhitelistedRole]
[Description("Adds specified message to the moderation queue")]
public async Task Report(CommandContext ctx, [Description("Message ID from current channel to report")] ulong messageId, [RemainingText, Description("Optional report comment")] string comment = null)
public async Task Report(CommandContext ctx, [Description("Message ID from current channel to report")] ulong messageId, [RemainingText, Description("Optional report comment")] string? comment = null)
{
try
{
@@ -29,7 +29,7 @@ namespace CompatBot.Commands
[Command("report"), RequiresWhitelistedRole]
[Description("Adds specified message to the moderation queue")]
public async Task Report(CommandContext ctx, [Description("Message link to report")] string messageLink, [RemainingText, Description("Optional report comment")] string comment = null)
public async Task Report(CommandContext ctx, [Description("Message link to report")] string messageLink, [RemainingText, Description("Optional report comment")] string? comment = null)
{
try
{
@@ -94,10 +94,10 @@ namespace CompatBot.Commands
await ctx.ReactWithAsync(Config.Reactions.Success).ConfigureAwait(false);
}
public static async Task ToggleBadUpdateAnnouncementAsync(DiscordMessage message)
public static async Task ToggleBadUpdateAnnouncementAsync(DiscordMessage? message)
{
var embed = message?.Embeds?.FirstOrDefault();
if (embed == null)
if (message is null || embed is null)
return;
var result = new DiscordEmbedBuilder(embed);
@@ -132,7 +132,7 @@ namespace CompatBot.Commands
await message.UpdateOrCreateMessageAsync(message.Channel, embed: result).ConfigureAwait(false);
}
private static async Task ReportMessage(CommandContext ctx, string comment, DiscordMessage msg)
private static async Task ReportMessage(CommandContext ctx, string? comment, DiscordMessage msg)
{
if (msg.Reactions.Any(r => r.IsMe && r.Emoji == Config.Reactions.Moderated))
{

View File

@@ -24,7 +24,7 @@ namespace CompatBot.Commands
[Description("Commands to check for various stuff on PSN")]
public sealed class Check: BaseCommandModuleCustom
{
private static string latestFwVersion = null;
private static string? latestFwVersion = null;
[Command("updates"), Aliases("update")]
[Description("Checks if specified product has any updates")]
@@ -75,10 +75,8 @@ namespace CompatBot.Commands
if (!ctx.Channel.IsPrivate
&& ctx.Message.Author.Id == 197163728867688448
&& (
embeds[0].Title.Contains("africa", StringComparison.InvariantCultureIgnoreCase) ||
embeds[0].Title.Contains("afrika", StringComparison.InvariantCultureIgnoreCase)
))
&& (embeds[0].Title.Contains("africa", StringComparison.InvariantCultureIgnoreCase)
|| embeds[0].Title.Contains("afrika", StringComparison.InvariantCultureIgnoreCase)))
{
foreach (var embed in embeds)
{
@@ -90,10 +88,10 @@ namespace CompatBot.Commands
if (!string.IsNullOrEmpty(embed.Thumbnail?.Url))
embed.WithThumbnail("https://cdn.discordapp.com/attachments/417347469521715210/516340151589535745/onionoff.png");
}
var sqvat = ctx.Client.GetEmoji(":sqvat:", Config.Reactions.No);
var sqvat = ctx.Client.GetEmoji(":sqvat:", Config.Reactions.No)!;
await ctx.Message.ReactWithAsync(sqvat).ConfigureAwait(false);
}
if (embeds.Count > 1 || embeds[0].Fields?.Count > 0)
if (embeds.Count > 1 || embeds[0].Fields.Count > 0)
embeds[^1] = embeds.Last().WithFooter("Note that you need to install ALL listed updates, one by one");
foreach (var embed in embeds)
await ctx.RespondAsync(embed: embed).ConfigureAwait(false);
@@ -135,7 +133,7 @@ namespace CompatBot.Commands
await ctx.RespondAsync(embed: embed).ConfigureAwait(false);
}
internal static async Task CheckFwUpdateForAnnouncementAsync(DiscordClient client, List<FirmwareInfo> fwList = null)
internal static async Task CheckFwUpdateForAnnouncementAsync(DiscordClient client, List<FirmwareInfo>? fwList = null)
{
fwList ??= await Client.GetHighestFwVersionAsync(Config.Cts.Token).ConfigureAwait(false);
if (fwList.Count == 0)

View File

@@ -1,20 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq;
using System.Threading.Tasks;
using CompatBot.Commands.Attributes;
using CompatBot.Database;
using CompatBot.Database.Providers;
using CompatBot.EventHandlers;
using CompatBot.ThumbScrapper;
using CompatBot.Utils;
using DSharpPlus.CommandsNext;
using DSharpPlus.CommandsNext.Attributes;
using DSharpPlus.Entities;
using DSharpPlus.Interactivity;
using Microsoft.EntityFrameworkCore;
using PsnClient;
using PsnClient.POCOs;
namespace CompatBot.Commands
{
@@ -23,15 +17,13 @@ namespace CompatBot.Commands
internal sealed partial class Psn: BaseCommandModuleCustom
{
private static readonly Client Client = new Client();
private static readonly DiscordColor PsnBlue = new DiscordColor(0x0071cd);
[Command("rename"), Aliases("setname", "settitle"), RequiresBotModRole]
[Description("Command to set or change game title for specific product code")]
public async Task Rename(CommandContext ctx, [Description("Product code such as BLUS12345")] string productCode, [RemainingText, Description("New game title to save in the database")] string title)
{
productCode = productCode.ToUpperInvariant();
using var db = new ThumbnailDb();
await using var db = new ThumbnailDb();
var item = db.Thumbnail.FirstOrDefault(t => t.ProductCode == productCode);
if (item == null)
await ctx.ReactWithAsync(Config.Reactions.Failure, $"Unknown product code {productCode}", true).ConfigureAwait(false);
@@ -48,9 +40,9 @@ namespace CompatBot.Commands
public async Task Add(CommandContext ctx, [Description("Product code such as BLUS12345")] string contentId, [RemainingText, Description("New game title to save in the database")] string title)
{
contentId = contentId.ToUpperInvariant();
var productCode = contentId;
var productCodeMatch = ProductCodeLookup.ProductCode.Match(contentId);
var contentIdMatch = PsnScraper.ContentIdMatcher.Match(contentId);
string productCode;
if (contentIdMatch.Success)
{
productCode = contentIdMatch.Groups["product_id"].Value;
@@ -58,30 +50,30 @@ namespace CompatBot.Commands
else if (productCodeMatch.Success)
{
productCode = productCodeMatch.Groups["letters"].Value + productCodeMatch.Groups["numbers"].Value;
contentId = null;
contentId = "";
}
else
{
await ctx.ReactWithAsync(Config.Reactions.Failure, "Invalid content id", true).ConfigureAwait(false);
return;
}
using var db = new ThumbnailDb();
await using var db = new ThumbnailDb();
var item = db.Thumbnail.FirstOrDefault(t => t.ProductCode == productCode);
if (item != null)
await ctx.ReactWithAsync(Config.Reactions.Failure, $"Product code {contentId} already exists", true).ConfigureAwait(false);
else
if (item is null)
{
item = new Thumbnail
{
ProductCode = contentId,
ContentId = contentId,
ProductCode = productCode,
ContentId = string.IsNullOrEmpty(contentId) ? null : contentId,
Name = title,
};
db.Add(item);
await db.AddAsync(item).ConfigureAwait(false);
await db.SaveChangesAsync().ConfigureAwait(false);
await ctx.ReactWithAsync(Config.Reactions.Success, "Title added successfully").ConfigureAwait(false);
}
else
await ctx.ReactWithAsync(Config.Reactions.Failure, $"Product code {contentId} already exists", true).ConfigureAwait(false);
}
}
}

View File

@@ -23,17 +23,17 @@ namespace CompatBot.Commands
[Description("Lists set variable names")]
public async Task List(CommandContext ctx)
{
using var db = new BotDb();
var setVars = await db.BotState.AsNoTracking().Where(v => v.Key.StartsWith(SqlConfiguration.ConfigVarPrefix)).ToListAsync().ConfigureAwait(false);
await using var db = new BotDb();
var setVars = await db.BotState.AsNoTracking().Where(v => v.Key != null && v.Key.StartsWith(SqlConfiguration.ConfigVarPrefix)).ToListAsync().ConfigureAwait(false);
if (setVars.Any())
{
var result = new StringBuilder("Set variables:").AppendLine();
foreach (var v in setVars)
{
#if DEBUG
result.Append(v.Key[SqlConfiguration.ConfigVarPrefix.Length ..]).Append(" = ").AppendLine(v.Value);
result.Append(v.Key![SqlConfiguration.ConfigVarPrefix.Length ..]).Append(" = ").AppendLine(v.Value);
#else
result.AppendLine(v.Key[(SqlConfiguration.ConfigVarPrefix.Length)..]);
result.AppendLine(v.Key![(SqlConfiguration.ConfigVarPrefix.Length)..]);
#endif
}
await ctx.RespondAsync(result.ToString()).ConfigureAwait(false);
@@ -49,12 +49,12 @@ namespace CompatBot.Commands
Config.inMemorySettings[key] = value;
Config.RebuildConfiguration();
key = SqlConfiguration.ConfigVarPrefix + key;
using var db = new BotDb();
await using var db = new BotDb();
var v = await db.BotState.Where(v => v.Key == key).FirstOrDefaultAsync().ConfigureAwait(false);
if (v == null)
{
v = new BotState {Key = key, Value = value};
db.BotState.Add(v);
await db.BotState.AddAsync(v).ConfigureAwait(false);
}
else
v.Value = value;
@@ -69,7 +69,7 @@ namespace CompatBot.Commands
Config.inMemorySettings.TryRemove(key, out _);
Config.RebuildConfiguration();
key = SqlConfiguration.ConfigVarPrefix + key;
using var db = new BotDb();
await using var db = new BotDb();
var v = await db.BotState.Where(v => v.Key == key).FirstOrDefaultAsync().ConfigureAwait(false);
if (v != null)
{

View File

@@ -40,7 +40,7 @@ namespace CompatBot.Commands
};
git.Start();
var stdout = await git.StandardOutput.ReadToEndAsync().ConfigureAwait(false);
git.WaitForExit();
await git.WaitForExitAsync().ConfigureAwait(false);
if (!string.IsNullOrEmpty(stdout))
await ctx.RespondAsync("```" + stdout + "```").ConfigureAwait(false);
}
@@ -51,7 +51,7 @@ namespace CompatBot.Commands
{
if (await lockObj.WaitAsync(0).ConfigureAwait(false))
{
DiscordMessage msg = null;
DiscordMessage? msg = null;
try
{
Config.Log.Info("Checking for available updates...");
@@ -69,7 +69,7 @@ namespace CompatBot.Commands
}
catch (Exception e)
{
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Updating failed: " + e.Message).ConfigureAwait(false);
await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Updating failed: " + e.Message).ConfigureAwait(false);
}
finally
{
@@ -86,7 +86,7 @@ namespace CompatBot.Commands
{
if (await lockObj.WaitAsync(0).ConfigureAwait(false))
{
DiscordMessage msg = null;
DiscordMessage? msg = null;
try
{
msg = await ctx.RespondAsync("Saving state...").ConfigureAwait(false);
@@ -96,7 +96,7 @@ namespace CompatBot.Commands
}
catch (Exception e)
{
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Restarting failed: " + e.Message).ConfigureAwait(false);
await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Restarting failed: " + e.Message).ConfigureAwait(false);
}
finally
{
@@ -126,7 +126,7 @@ namespace CompatBot.Commands
{
try
{
using var db = new BotDb();
await 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)
@@ -164,7 +164,7 @@ namespace CompatBot.Commands
try
{
await CompatList.ImportMetacriticScoresAsync().ConfigureAwait(false);
using var db = new ThumbnailDb();
await using var db = new ThumbnailDb();
var linkedItems = await db.Thumbnail.CountAsync(i => i.MetacriticId != null).ConfigureAwait(false);
await ctx.RespondAsync($"Importing Metacritic info was successful, linked {linkedItems} items").ConfigureAwait(false);
}
@@ -190,7 +190,7 @@ namespace CompatBot.Commands
};
git.Start();
var stdout = await git.StandardOutput.ReadToEndAsync().ConfigureAwait(false);
git.WaitForExit();
await git.WaitForExitAsync().ConfigureAwait(false);
if (string.IsNullOrEmpty(stdout))
return (false, stdout);
@@ -200,7 +200,7 @@ namespace CompatBot.Commands
return (true, stdout);
}
internal static void Restart(ulong channelId, string restartMsg)
internal static void Restart(ulong channelId, string? restartMsg)
{
Config.Log.Info($"Saving channelId {channelId} into settings...");
using var db = new BotDb();

View File

@@ -29,21 +29,19 @@ namespace CompatBot.Commands
try
{
var @fixed = 0;
using (var db = new BotDb())
{
foreach (var warning in db.Warning)
if (!string.IsNullOrEmpty(warning.FullReason))
await using var db = new BotDb();
foreach (var warning in db.Warning)
if (!string.IsNullOrEmpty(warning.FullReason))
{
var match = Timestamp.Match(warning.FullReason);
if (match.Success && DateTime.TryParse(match.Groups["date"].Value, out var timestamp))
{
var match = Timestamp.Match(warning.FullReason);
if (match.Success && DateTime.TryParse(match.Groups["date"].Value, out var timestamp))
{
warning.Timestamp = timestamp.Ticks;
warning.FullReason = warning.FullReason[(match.Groups["cutout"].Value.Length)..];
@fixed++;
}
warning.Timestamp = timestamp.Ticks;
warning.FullReason = warning.FullReason[(match.Groups["cutout"].Value.Length)..];
@fixed++;
}
await db.SaveChangesAsync().ConfigureAwait(false);
}
}
await db.SaveChangesAsync().ConfigureAwait(false);
await ctx.RespondAsync($"Fixed {@fixed} records").ConfigureAwait(false);
}
catch (Exception e)
@@ -60,19 +58,17 @@ namespace CompatBot.Commands
try
{
var @fixed = 0;
using (var db = new BotDb())
await using var db = new BotDb();
foreach (var warning in db.Warning)
{
foreach (var warning in db.Warning)
var newReason = await FixChannelMentionAsync(ctx, warning.Reason).ConfigureAwait(false);
if (newReason != warning.Reason && newReason != null)
{
var newReason = await FixChannelMentionAsync(ctx, warning.Reason).ConfigureAwait(false);
if (newReason != warning.Reason)
{
warning.Reason = newReason;
@fixed++;
}
warning.Reason = newReason;
@fixed++;
}
await db.SaveChangesAsync().ConfigureAwait(false);
}
await db.SaveChangesAsync().ConfigureAwait(false);
await ctx.RespondAsync($"Fixed {@fixed} records").ConfigureAwait(false);
}
catch (Exception e)
@@ -114,7 +110,7 @@ namespace CompatBot.Commands
public async Task TitleMarks(CommandContext ctx)
{
var changed = 0;
using var db = new ThumbnailDb();
await using var db = new ThumbnailDb();
foreach (var thumb in db.Thumbnail)
{
if (string.IsNullOrEmpty(thumb.Name))
@@ -127,12 +123,12 @@ namespace CompatBot.Commands
newTitle = newTitle[..^17];
if (newTitle.EndsWith("downloadable game", StringComparison.OrdinalIgnoreCase))
newTitle = newTitle[..^18];
newTitle.TrimEnd();
if (newTitle != thumb.Name)
{
changed++;
thumb.Name = newTitle;
}
newTitle = newTitle.TrimEnd();
if (newTitle == thumb.Name)
continue;
changed++;
thumb.Name = newTitle;
}
await db.SaveChangesAsync();
await ctx.RespondAsync($"Fixed {changed} title{(changed == 1 ? "" : "s")}").ConfigureAwait(false);
@@ -143,12 +139,16 @@ namespace CompatBot.Commands
public async Task MetacriticLinks(CommandContext ctx, [Description("Remove links for trial and demo versions only")] bool demosOnly = true)
{
var changed = 0;
using var db = new ThumbnailDb();
await using var db = new ThumbnailDb();
foreach (var thumb in db.Thumbnail.Where(t => t.MetacriticId != null))
{
if (!demosOnly || Regex.IsMatch(thumb.Name, @"\b(demo|trial)\b", RegexOptions.IgnoreCase | RegexOptions.Singleline))
thumb.MetacriticId = null;
if (demosOnly
&& thumb.Name != null
&& !Regex.IsMatch(thumb.Name, @"\b(demo|trial)\b", RegexOptions.IgnoreCase | RegexOptions.Singleline))
continue;
thumb.MetacriticId = null;
changed++;
}
await db.SaveChangesAsync();
await ctx.RespondAsync($"Fixed {changed} title{(changed == 1 ? "" : "s")}").ConfigureAwait(false);

View File

@@ -46,9 +46,7 @@ namespace CompatBot.Commands
[Command("say"), Priority(10)]
[Description("Make bot say things, optionally in a specific channel")]
public Task Say(CommandContext ctx, [RemainingText, Description("Message text to send")] string message)
{
return Say(ctx, ctx.Channel, message);
}
=> Say(ctx, ctx.Channel, message);
[Command("react")]
[Description("Add reactions to the specified message")]
@@ -61,7 +59,7 @@ namespace CompatBot.Commands
try
{
var message = await ctx.GetMessageAsync(messageLink).ConfigureAwait(false);
if (message == null)
if (message is null)
{
await ctx.ReactWithAsync(Config.Reactions.Failure, "Couldn't find the message").ConfigureAwait(false);
return;
@@ -83,7 +81,7 @@ namespace CompatBot.Commands
var endIdx = emojis.IndexOf('>', i);
if (endIdx < i)
endIdx = emojis.Length;
emoji = emojis.Substring(i, endIdx - i);
emoji = emojis[i..endIdx];
i = endIdx - 1;
var emojiId = ulong.Parse(emoji[(emoji.LastIndexOf(':') + 1)..]);
de = DiscordEmoji.FromGuildEmote(ctx.Client, emojiId);
@@ -94,9 +92,7 @@ namespace CompatBot.Commands
await message.ReactWithAsync(de).ConfigureAwait(false);
}
}
catch
{
}
catch { }
}
}
catch (Exception e)
@@ -113,20 +109,18 @@ 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 = Config.MemoryStreamManager.GetStream();
using (var gzip = new GZipStream(result, CompressionLevel.Optimal, true))
{
await log.CopyToAsync(gzip, Config.Cts.Token).ConfigureAwait(false);
await gzip.FlushAsync().ConfigureAwait(false);
}
await using var log = File.Open(logPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
await using var result = Config.MemoryStreamManager.GetStream();
await 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 ctx.ReactWithAsync(Config.Reactions.Failure, "Compressed log size is too large, ask 13xforever for help :(", true).ConfigureAwait(false);
}
catch (Exception e)
{
@@ -142,28 +136,28 @@ namespace CompatBot.Commands
try
{
string dbPath;
using (var db = new ThumbnailDb())
using (var connection = db.Database.GetDbConnection())
await using (var db = new ThumbnailDb())
await using (var connection = db.Database.GetDbConnection())
dbPath = connection.DataSource;
var attachmentSizeLimit = Config.AttachmentSizeLimit;
var dbDir = Path.GetDirectoryName(dbPath);
var dbDir = Path.GetDirectoryName(dbPath) ?? ".";
var dbName = Path.GetFileNameWithoutExtension(dbPath);
using var result = Config.MemoryStreamManager.GetStream();
using (var zip = new ZipArchive(result, ZipArchiveMode.Create, true))
foreach (var fname in Directory.EnumerateFiles(dbDir, $"{dbName}.*", new EnumerationOptions {IgnoreInaccessible = true, RecurseSubdirectories = false,}))
{
using var dbData = File.Open(fname, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using var entryStream = zip.CreateEntry(Path.GetFileName(fname), CompressionLevel.Optimal).Open();
await dbData.CopyToAsync(entryStream, Config.Cts.Token).ConfigureAwait(false);
await entryStream.FlushAsync().ConfigureAwait(false);
}
await using var result = Config.MemoryStreamManager.GetStream();
using var zip = new ZipArchive(result, ZipArchiveMode.Create, true);
foreach (var fname in Directory.EnumerateFiles(dbDir, $"{dbName}.*", new EnumerationOptions {IgnoreInaccessible = true, RecurseSubdirectories = false,}))
{
await using var dbData = File.Open(fname, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
await using var entryStream = zip.CreateEntry(Path.GetFileName(fname), CompressionLevel.Optimal).Open();
await dbData.CopyToAsync(entryStream, Config.Cts.Token).ConfigureAwait(false);
await entryStream.FlushAsync().ConfigureAwait(false);
}
if (result.Length <= attachmentSizeLimit)
{
result.Seek(0, SeekOrigin.Begin);
await ctx.RespondWithFileAsync(Path.GetFileName(dbName) + ".zip", result).ConfigureAwait(false);
}
else
await ctx.ReactWithAsync(Config.Reactions.Failure, "Compressed Thumbs.db size is too large, ask Nicba for help :(", true).ConfigureAwait(false);
await ctx.ReactWithAsync(Config.Reactions.Failure, "Compressed Thumbs.db size is too large, ask 13xforever for help :(", true).ConfigureAwait(false);
}
catch (Exception e)
{

View File

@@ -40,12 +40,13 @@ namespace CompatBot.Commands
return;
}
using var db = new ThumbnailDb();
await using var db = new ThumbnailDb();
if (db.SyscallInfo.Any(sci => sci.Function == search))
{
var productInfoList = db.SyscallToProductMap.AsNoTracking()
.Where(m => m.SyscallInfo.Function == search)
.Select(m => new {m.Product.ProductCode, Name = m.Product.Name.StripMarks() ?? "???"})
.AsEnumerable()
.Select(m => new {m.Product.ProductCode, Name = m.Product.Name?.StripMarks() ?? "???"})
.Distinct()
.ToList();
var groupedList = productInfoList
@@ -55,7 +56,6 @@ namespace CompatBot.Commands
if (groupedList.Any())
{
var bigList = groupedList.Count >= Config.MaxSyscallResultLines;
var result = new StringBuilder();
var fullList = bigList ? new StringBuilder() : null;
result.AppendLine($"List of games using `{search}`:```");
@@ -66,14 +66,14 @@ namespace CompatBot.Commands
if (c < Config.MaxSyscallResultLines)
result.AppendLine($"{gi.Key.Trim(60)} [{productIds}]");
if (bigList)
fullList.AppendLine($"{gi.Key} [{productIds}]");
fullList!.AppendLine($"{gi.Key} [{productIds}]");
c++;
}
await ctx.SendAutosplitMessageAsync(result.Append("```")).ConfigureAwait(false);
if (bigList)
{
using var memoryStream = Config.MemoryStreamManager.GetStream();
using var streamWriter = new StreamWriter(memoryStream, Encoding.UTF8);
await using var memoryStream = Config.MemoryStreamManager.GetStream();
await using var streamWriter = new StreamWriter(memoryStream, Encoding.UTF8);
await streamWriter.WriteAsync(fullList).ConfigureAwait(false);
await streamWriter.FlushAsync().ConfigureAwait(false);
memoryStream.Seek(0, SeekOrigin.Begin);
@@ -115,7 +115,7 @@ namespace CompatBot.Commands
[Description("Provides an option to rename function call")]
public async Task Rename(CommandContext ctx, [Description("Old function name")] string oldFunctionName, [Description("New function name")] string newFunctionName)
{
using var db = new ThumbnailDb();
await using var db = new ThumbnailDb();
var oldMatches = await db.SyscallInfo.Where(sci => sci.Function == oldFunctionName).ToListAsync().ConfigureAwait(false);
if (oldMatches.Count == 0)
{
@@ -144,10 +144,10 @@ namespace CompatBot.Commands
await ctx.RespondAsync($"Function `{oldFunctionName}` was successfully renamed to `{newFunctionName}`").ConfigureAwait(false);
}
private async Task ReturnSyscallsByGameAsync(CommandContext ctx, string productId)
private static async Task ReturnSyscallsByGameAsync(CommandContext ctx, string productId)
{
productId = productId.ToUpperInvariant();
using var db = new ThumbnailDb();
await using var db = new ThumbnailDb();
var title = db.Thumbnail.FirstOrDefault(t => t.ProductCode == productId)?.Name;
title = string.IsNullOrEmpty(title) ? productId : $"[{productId}] {title.Trim(40)}";
var sysInfoList = db.SyscallToProductMap.AsNoTracking()
@@ -162,8 +162,8 @@ namespace CompatBot.Commands
var result = new StringBuilder();
foreach (var sci in sysInfoList)
result.AppendLine(sci.Function);
using var memoryStream = Config.MemoryStreamManager.GetStream();
using var streamWriter = new StreamWriter(memoryStream, Encoding.UTF8);
await using var memoryStream = Config.MemoryStreamManager.GetStream();
await using var streamWriter = new StreamWriter(memoryStream, Encoding.UTF8);
await streamWriter.WriteAsync(result).ConfigureAwait(false);
await streamWriter.FlushAsync().ConfigureAwait(false);
memoryStream.Seek(0, SeekOrigin.Begin);

View File

@@ -20,20 +20,31 @@ namespace CompatBot.Database.Providers
if (await SyncObj.WaitAsync(0).ConfigureAwait(false))
try
{
using (var httpClient = HttpClientFactory.Create(new CompressionMessageHandler()))
using var httpClient = HttpClientFactory.Create(new CompressionMessageHandler());
await 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);
if (xml.Root is null)
{
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"))
{
var winVer = (string)driver.Element("windows-version");
var vkVer = (string)driver.Element("vulkan-version");
var driverVer = (string)driver.Attribute("version");
if (!VulkanToDriver.TryGetValue(vkVer, out var verList))
VulkanToDriver[vkVer] = (verList = new List<string>());
verList.Insert(0, driverVer);
Config.Log.Warn("Failed to update AMD version mapping");
return;
}
foreach (var driver in xml.Root.Elements("driver"))
{
var winVer = (string?)driver.Element("windows-version");
var vkVer = (string?)driver.Element("vulkan-version");
var driverVer = (string?)driver.Attribute("version");
if (vkVer is null)
continue;
if (!VulkanToDriver.TryGetValue(vkVer, out var verList))
VulkanToDriver[vkVer] = verList = new();
if (string.IsNullOrEmpty(driverVer))
continue;
verList.Insert(0, driverVer);
if (!string.IsNullOrEmpty(winVer))
OpenglToDriver[winVer] = driverVer;
}
}
foreach (var key in VulkanToDriver.Keys.ToList())
VulkanToDriver[key] = VulkanToDriver[key].Distinct().ToList();
@@ -50,50 +61,50 @@ namespace CompatBot.Database.Providers
public static async Task<string> GetFromOpenglAsync(string openglVersion, bool autoRefresh = true)
{
if (OpenglToDriver.TryGetValue(openglVersion, out var result) && result != null)
if (OpenglToDriver.TryGetValue(openglVersion, out var result))
return result;
if (Version.TryParse(openglVersion, out var glVersion))
if (!Version.TryParse(openglVersion, out var glVersion))
return openglVersion;
var glVersions = new List<(Version glVer, string driverVer)>(OpenglToDriver.Count);
foreach (var key in OpenglToDriver.Keys)
{
var glVersions = new List<(Version v, string vv)>(OpenglToDriver.Count);
foreach (var key in OpenglToDriver.Keys)
{
if (Version.TryParse(key, out var ver))
glVersions.Add((ver, OpenglToDriver[key]));
}
if (glVersions.Count == 0)
return openglVersion;
glVersions.Sort((l, r) => l.v < r.v ? -1 : l.v > r.v ? 1 : 0);
if (glVersion < glVersions[0].v)
return $"older than {glVersions[0].vv} ({openglVersion})";
var newest = glVersions.Last();
if (glVersion > newest.v)
{
if (autoRefresh)
{
await RefreshAsync().ConfigureAwait(false);
return await GetFromOpenglAsync(openglVersion, false).ConfigureAwait(false);
}
return $"newer than {newest.vv} ({openglVersion})";
}
var approximate = glVersions.FirstOrDefault(v => v.v.Minor == glVersion.Minor && v.v.Build == glVersion.Build);
if (!string.IsNullOrEmpty(approximate.vv))
return $"{approximate.vv} rev {glVersion.Revision}";
if (string.IsNullOrEmpty(approximate.vv))
for (var i = 0; i < glVersions.Count - 1; i++)
if (glVersion > glVersions[i].v && glVersion < glVersions[i + 1].v)
{
approximate = glVersions[i];
break;
}
if (!string.IsNullOrEmpty(approximate.vv))
return $"probably {approximate.vv}";
if (Version.TryParse(key, out var ver))
glVersions.Add((ver, OpenglToDriver[key]));
}
if (glVersions.Count == 0)
return openglVersion;
glVersions.Sort((l, r) => l.glVer < r.glVer ? -1 : l.glVer > r.glVer ? 1 : 0);
if (glVersion < glVersions[0].glVer)
return $"older than {glVersions[0].driverVer} ({openglVersion})";
var newest = glVersions.Last();
if (glVersion > newest.glVer)
{
if (autoRefresh)
{
await RefreshAsync().ConfigureAwait(false);
return await GetFromOpenglAsync(openglVersion, false).ConfigureAwait(false);
}
return $"newer than {newest.driverVer} ({openglVersion})";
}
var approximate = glVersions.FirstOrDefault(v => v.glVer.Minor == glVersion.Minor && v.glVer.Build == glVersion.Build);
if (!string.IsNullOrEmpty(approximate.driverVer))
return $"{approximate.driverVer} rev {glVersion.Revision}";
if (string.IsNullOrEmpty(approximate.driverVer))
for (var i = 0; i < glVersions.Count - 1; i++)
if (glVersion > glVersions[i].glVer && glVersion < glVersions[i + 1].glVer)
{
approximate = glVersions[i];
break;
}
if (!string.IsNullOrEmpty(approximate.driverVer))
return $"probably {approximate.driverVer}";
return openglVersion;
}
@@ -112,7 +123,7 @@ namespace CompatBot.Database.Providers
if (Version.TryParse(vulkanVersion, out var vkVer))
{
var vkVersions = new List<(Version v, string vv)>(VulkanToDriver.Count);
var vkVersions = new List<(Version vkVer, string driverVer)>(VulkanToDriver.Count);
foreach (var key in VulkanToDriver.Keys)
{
if (Version.TryParse(key, out var ver))
@@ -121,39 +132,35 @@ namespace CompatBot.Database.Providers
if (vkVersions.Count == 0)
return vulkanVersion;
vkVersions.Sort((l, r) => l.v < r.v ? -1 : l.v > r.v ? 1 : 0);
if (vkVer < vkVersions[0].v)
return $"older than {vkVersions[0].vv} ({vulkanVersion})";
vkVersions.Sort((l, r) => l.vkVer < r.vkVer ? -1 : l.vkVer > r.vkVer ? 1 : 0);
if (vkVer < vkVersions[0].vkVer)
return $"older than {vkVersions[0].driverVer} ({vulkanVersion})";
var newest = vkVersions.Last();
if (vkVer > newest.v)
var (version, driverVer) = vkVersions.Last();
if (vkVer > version)
{
if (autoRefresh)
{
await RefreshAsync().ConfigureAwait(false);
return await GetFromVulkanAsync(vulkanVersion, false).ConfigureAwait(false);
}
return $"newer than {newest.vv} ({vulkanVersion})";
if (!autoRefresh)
return $"newer than {driverVer} ({vulkanVersion})";
await RefreshAsync().ConfigureAwait(false);
return await GetFromVulkanAsync(vulkanVersion, false).ConfigureAwait(false);
}
else
for (var i = 1; i < vkVersions.Count; i++)
{
for (var i = 1; i < vkVersions.Count; i++)
{
if (vkVer < vkVersions[i].v)
{
var lowerVer = vkVersions[i - 1].v;
var mapKey = VulkanToDriver.Keys.FirstOrDefault(k => Version.Parse(k) == lowerVer);
if (mapKey != null)
{
if (VulkanToDriver.TryGetValue(mapKey, out var driverList))
{
var oldestLowerVersion = driverList.Select(Version.Parse).OrderByDescending(v => v).First();
return $"unknown version between {oldestLowerVersion} and {vkVersions[i].vv} ({vulkanVersion})";
}
}
}
}
if (vkVer >= vkVersions[i].vkVer)
continue;
var lowerVer = vkVersions[i - 1].vkVer;
var mapKey = VulkanToDriver.Keys.FirstOrDefault(k => Version.Parse(k) == lowerVer);
if (mapKey is null)
continue;
if (!VulkanToDriver.TryGetValue(mapKey, out var driverList))
continue;
var oldestLowerVersion = driverList.Select(Version.Parse).OrderByDescending(v => v).First();
return $"unknown version between {oldestLowerVersion} and {vkVersions[i].driverVer} ({vulkanVersion})";
}
}

View File

@@ -18,25 +18,22 @@ namespace CompatBot.Database.Providers
{
internal static class ContentFilter
{
private static Dictionary<FilterContext, AhoCorasickDoubleArrayTrie<Piracystring>> filters = new Dictionary<FilterContext, AhoCorasickDoubleArrayTrie<Piracystring>>();
private static Dictionary<FilterContext, AhoCorasickDoubleArrayTrie<Piracystring>?> filters = new Dictionary<FilterContext, AhoCorasickDoubleArrayTrie<Piracystring>?>();
private static readonly MemoryCache ResponseAntispamCache = new MemoryCache(new MemoryCacheOptions{ ExpirationScanFrequency = TimeSpan.FromMinutes(5)});
private static readonly MemoryCache ReportAntispamCache = new MemoryCache(new MemoryCacheOptions{ ExpirationScanFrequency = TimeSpan.FromMinutes(5)});
private static readonly TimeSpan CacheTime = TimeSpan.FromMinutes(15);
static ContentFilter()
{
RebuildMatcher();
}
static ContentFilter() => RebuildMatcher();
public static Task<Piracystring> FindTriggerAsync(FilterContext ctx, string str)
public static Task<Piracystring?> FindTriggerAsync(FilterContext ctx, string str)
{
if (string.IsNullOrEmpty(str))
return Task.FromResult((Piracystring)null);
return Task.FromResult((Piracystring?)null);
if (!filters.TryGetValue(ctx, out var matcher))
return Task.FromResult((Piracystring)null);
return Task.FromResult((Piracystring?)null);
Piracystring result = null;
Piracystring? result = null;
matcher?.ParseText(str, h =>
{
if (string.IsNullOrEmpty(h.Value.ValidatingRegex) || Regex.IsMatch(str, h.Value.ValidatingRegex, RegexOptions.IgnoreCase | RegexOptions.Multiline))
@@ -66,7 +63,7 @@ namespace CompatBot.Database.Providers
public static void RebuildMatcher()
{
var newFilters = new Dictionary<FilterContext, AhoCorasickDoubleArrayTrie<Piracystring>>();
var newFilters = new Dictionary<FilterContext, AhoCorasickDoubleArrayTrie<Piracystring>?>();
using (var db = new BotDb())
foreach (FilterContext ctx in Enum.GetValues(typeof(FilterContext)))
{
@@ -134,11 +131,8 @@ namespace CompatBot.Database.Providers
return (trigger.Actions & (~suppressActions) & (FilterAction.IssueWarning | FilterAction.RemoveContent)) == 0;
}
public static async Task PerformFilterActions(DiscordClient client, DiscordMessage message, Piracystring trigger, FilterAction ignoreFlags = 0, string triggerContext = null, string infraction = null, string warningReason = null)
public static async Task PerformFilterActions(DiscordClient client, DiscordMessage message, Piracystring trigger, FilterAction ignoreFlags = 0, string? triggerContext = null, string? infraction = null, string? warningReason = null)
{
if (trigger == null)
return;
var severity = ReportSeverity.Low;
var completedActions = new List<FilterAction>();
if (trigger.Actions.HasFlag(FilterAction.RemoveContent) && !ignoreFlags.HasFlag(FilterAction.RemoveContent))
@@ -157,7 +151,8 @@ namespace CompatBot.Database.Providers
try
{
var author = client.GetMember(message.Author);
Config.Log.Debug($"Removed message from {author.GetMentionWithNickname()} in #{message.Channel.Name}: {message.Content}");
var username = author?.GetMentionWithNickname() ?? message.Author.GetUsernameWithNickname(client);
Config.Log.Debug($"Removed message from {username} in #{message.Channel.Name}: {message.Content}");
}
catch (Exception e)
{
@@ -203,7 +198,9 @@ namespace CompatBot.Database.Providers
}
}
if (trigger.Actions.HasFlag(FilterAction.ShowExplain) && !ignoreFlags.HasFlag(FilterAction.ShowExplain))
if (trigger.Actions.HasFlag(FilterAction.ShowExplain)
&& !ignoreFlags.HasFlag(FilterAction.ShowExplain)
&& !string.IsNullOrEmpty(trigger.ExplainTerm))
{
var result = await Explain.LookupTerm(trigger.ExplainTerm).ConfigureAwait(false);
await Explain.SendExplanation(result, trigger.ExplainTerm, message).ConfigureAwait(false);

View File

@@ -31,25 +31,24 @@ namespace CompatBot.Database.Providers
try
{
Config.Log.Debug("Got stats saving lock");
using var db = new BotDb();
await using var db = new BotDb();
db.Stats.RemoveRange(db.Stats);
await db.SaveChangesAsync().ConfigureAwait(false);
foreach (var cache in AllCaches)
foreach (var (category, cache) in AllCaches)
{
var category = cache.name;
var entries = cache.cache.GetCacheEntries<string>();
var entries = cache.GetCacheEntries<string>();
var savedKeys = new HashSet<string>();
foreach (var entry in entries)
if (savedKeys.Add(entry.Key))
foreach (var (key, value) in entries)
if (savedKeys.Add(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
Key = key,
Value = (int?)value?.Value ?? 0,
ExpirationTimestamp = value?.AbsoluteExpiration?.ToUniversalTime().Ticks ?? 0
}).ConfigureAwait(false);
else
Config.Log.Warn($"Somehow there's another '{entry.Key}' in the {category} cache");
Config.Log.Warn($"Somehow there's another '{key}' in the {category} cache");
}
await db.SaveChangesAsync().ConfigureAwait(false);
}
@@ -73,16 +72,15 @@ namespace CompatBot.Database.Providers
public static async Task RestoreAsync()
{
var now = DateTime.UtcNow;
using var db = new BotDb();
foreach (var cache in AllCaches)
await using var db = new BotDb();
foreach (var (category, 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 time = entry.ExpirationTimestamp.AsUtc();
if (time > now)
cache.cache.Set(entry.Key, entry.Value, time);
cache.Set(entry.Key, entry.Value, time);
}
}
}

View File

@@ -22,9 +22,6 @@ namespace CompatBot.EventHandlers
{
private const RegexOptions DefaultOptions = RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase | RegexOptions.Multiline;
private static readonly Regex InviteLink = new Regex(@"(https?://)?discord(((app\.com/invite|\.gg)/(?<invite_id>[a-z0-9\-]+))|(\.me/(?<me_id>.*?))(\s|>|$))", DefaultOptions);
private static readonly Regex DiscordInviteLink = new Regex(@"(https?://)?discord(app\.com/invite|\.gg)/(?<invite_id>[a-z0-9\-]+)", DefaultOptions);
private static readonly Regex DiscordMeLink = new Regex(@"(https?://)?discord\.me/(?<me_id>.*?)(\s|$)", DefaultOptions);
private static readonly HttpClient HttpClient = HttpClientFactory.Create(new CompressionMessageHandler());
private static readonly MemoryCache InviteCodeCache = new MemoryCache(new MemoryCacheOptions{ExpirationScanFrequency = TimeSpan.FromHours(1)});
private static readonly TimeSpan CacheDuration = TimeSpan.FromHours(24);
@@ -178,7 +175,7 @@ namespace CompatBot.EventHandlers
return true;
}
public static async Task<(bool hasInvalidInvite, bool attemptToWorkaround, List<DiscordInvite> invites)> GetInvitesAsync(this DiscordClient client, string message, DiscordUser author = null, bool tryMessageAsACode = false)
public static async Task<(bool hasInvalidInvite, bool attemptToWorkaround, List<DiscordInvite> invites)> GetInvitesAsync(this DiscordClient client, string message, DiscordUser? author = null, bool tryMessageAsACode = false)
{
if (string.IsNullOrEmpty(message))
return (false, false, new List<DiscordInvite>(0));

View File

@@ -15,7 +15,7 @@ namespace CompatBot.EventHandlers.LogParsing.ArchiveHandlers
public long LogSize { get; private set; }
public long SourcePosition { get; private set; }
public (bool result, string reason) CanHandle(string fileName, int fileSize, ReadOnlySpan<byte> header)
public (bool result, string? reason) CanHandle(string fileName, int fileSize, ReadOnlySpan<byte> header)
{
if (header.Length >= Header.Length)
{
@@ -31,8 +31,8 @@ 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);
await using var statsStream = new BufferCopyStream(sourceStream);
await using var gzipStream = new GZipStream(statsStream, CompressionMode.Decompress);
try
{
int read;
@@ -44,7 +44,6 @@ namespace CompatBot.EventHandlers.LogParsing.ArchiveHandlers
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();

View File

@@ -8,7 +8,7 @@ namespace CompatBot.EventHandlers.LogParsing.ArchiveHandlers
{
public interface IArchiveHandler
{
(bool result, string reason) CanHandle(string fileName, int fileSize, ReadOnlySpan<byte> header);
(bool result, string? reason) CanHandle(string fileName, int fileSize, ReadOnlySpan<byte> header);
Task FillPipeAsync(Stream sourceStream, PipeWriter writer, CancellationToken cancellationToken);
long LogSize { get; }
long SourcePosition { get; }

View File

@@ -12,7 +12,7 @@ namespace CompatBot.EventHandlers.LogParsing.ArchiveHandlers
public long LogSize { get; private set; }
public long SourcePosition { get; private set; }
public (bool result, string reason) CanHandle(string fileName, int fileSize, ReadOnlySpan<byte> header)
public (bool result, string? reason) CanHandle(string fileName, int fileSize, ReadOnlySpan<byte> header)
{
LogSize = fileSize;
if (fileName.Contains("tty.log", StringComparison.InvariantCultureIgnoreCase))
@@ -35,6 +35,7 @@ namespace CompatBot.EventHandlers.LogParsing.ArchiveHandlers
var memory = writer.GetMemory(Config.MinimumBufferSize);
read = await sourceStream.ReadAsync(memory, cancellationToken);
writer.Advance(read);
SourcePosition = sourceStream.Position;
flushed = await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
} while (read > 0 && !(flushed.IsCompleted || flushed.IsCanceled || cancellationToken.IsCancellationRequested));
}

View File

@@ -5,7 +5,6 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CompatBot.Utils;
using SharpCompress.Archives.Rar;
using SharpCompress.Readers.Rar;
namespace CompatBot.EventHandlers.LogParsing.ArchiveHandlers
@@ -17,7 +16,7 @@ namespace CompatBot.EventHandlers.LogParsing.ArchiveHandlers
public long LogSize { get; private set; }
public long SourcePosition { get; private set; }
public (bool result, string reason) CanHandle(string fileName, int fileSize, ReadOnlySpan<byte> header)
public (bool result, string? reason) CanHandle(string fileName, int fileSize, ReadOnlySpan<byte> header)
{
if (header.Length >= Header.Length && header.Slice(0, Header.Length).SequenceEqual(Header)
|| fileName.EndsWith(".rar", StringComparison.InvariantCultureIgnoreCase))
@@ -36,7 +35,7 @@ namespace CompatBot.EventHandlers.LogParsing.ArchiveHandlers
{
try
{
using var statsStream = new BufferCopyStream(sourceStream);
await using var statsStream = new BufferCopyStream(sourceStream);
using var rarReader = RarReader.Open(statsStream);
while (rarReader.MoveToNextEntry())
{
@@ -45,7 +44,7 @@ namespace CompatBot.EventHandlers.LogParsing.ArchiveHandlers
&& !rarReader.Entry.Key.Contains("tty.log", StringComparison.InvariantCultureIgnoreCase))
{
LogSize = rarReader.Entry.Size;
using var rarStream = rarReader.OpenEntryStream();
await using var rarStream = rarReader.OpenEntryStream();
int read;
FlushResult flushed;
do

View File

@@ -15,7 +15,7 @@ namespace CompatBot.EventHandlers.LogParsing.ArchiveHandlers
public long LogSize { get; private set; }
public long SourcePosition { get; private set; }
public (bool result, string reason) CanHandle(string fileName, int fileSize, ReadOnlySpan<byte> header)
public (bool result, string? reason) CanHandle(string fileName, int fileSize, ReadOnlySpan<byte> header)
{
if (header.Length >= Header.Length && header.Slice(0, Header.Length).SequenceEqual(Header)
|| fileName.EndsWith(".7z", StringComparison.InvariantCultureIgnoreCase))
@@ -33,7 +33,7 @@ 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 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);
@@ -44,7 +44,7 @@ namespace CompatBot.EventHandlers.LogParsing.ArchiveHandlers
&& !zipReader.Entry.Key.Contains("tty.log", StringComparison.InvariantCultureIgnoreCase))
{
LogSize = zipReader.Entry.Size;
using var entryStream = zipReader.OpenEntryStream();
await using var entryStream = zipReader.OpenEntryStream();
int read;
FlushResult flushed;
do

View File

@@ -16,7 +16,7 @@ namespace CompatBot.EventHandlers.LogParsing.ArchiveHandlers
public long LogSize { get; private set; }
public long SourcePosition { get; private set; }
public (bool result, string reason) CanHandle(string fileName, int fileSize, ReadOnlySpan<byte> header)
public (bool result, string? reason) CanHandle(string fileName, int fileSize, ReadOnlySpan<byte> header)
{
if (header.Length >= Header.Length && header.Slice(0, Header.Length).SequenceEqual(Header)
@@ -36,7 +36,7 @@ namespace CompatBot.EventHandlers.LogParsing.ArchiveHandlers
{
try
{
using var statsStream = new BufferCopyStream(sourceStream);
await using var statsStream = new BufferCopyStream(sourceStream);
using var zipReader = ZipReader.Open(statsStream);
while (zipReader.MoveToNextEntry())
{
@@ -45,7 +45,7 @@ namespace CompatBot.EventHandlers.LogParsing.ArchiveHandlers
&& !zipReader.Entry.Key.Contains("tty.log", StringComparison.InvariantCultureIgnoreCase))
{
LogSize = zipReader.Entry.Size;
using var rarStream = zipReader.OpenEntryStream();
await using var rarStream = zipReader.OpenEntryStream();
int read;
FlushResult flushed;
do

View File

@@ -306,7 +306,7 @@ namespace CompatBot.EventHandlers.LogParsing
{
foreach (var key in keys)
{
if (state.CompleteCollection?[key] is string value)
if (state.CompletedCollection?[key] is string value)
state.WipCollection[key] = value;
if (state.CompleteMultiValueCollection?[key] is UniqueList<string> collection)
state.WipMultiValueCollection[key] = collection;
@@ -329,7 +329,7 @@ namespace CompatBot.EventHandlers.LogParsing
private static void MarkAsComplete(LogParseState state)
{
state.CompleteCollection = state.WipCollection;
state.CompletedCollection = state.WipCollection;
state.CompleteMultiValueCollection = state.WipMultiValueCollection;
Config.Log.Trace("----- complete section");
}

View File

@@ -8,8 +8,8 @@ namespace CompatBot.EventHandlers.LogParsing.POCOs
{
public class LogParseState
{
public NameValueCollection CompleteCollection = null;
public NameUniqueObjectCollection<string> CompleteMultiValueCollection = null;
public NameValueCollection? CompletedCollection;
public NameUniqueObjectCollection<string>? CompleteMultiValueCollection;
public NameValueCollection WipCollection = new NameValueCollection();
public NameUniqueObjectCollection<string> WipMultiValueCollection = new NameUniqueObjectCollection<string>();
public readonly Dictionary<string, int> ValueHitStats = new Dictionary<string, int>();
@@ -17,8 +17,8 @@ namespace CompatBot.EventHandlers.LogParsing.POCOs
public int Id = 0;
public ErrorCode Error = ErrorCode.None;
public readonly Dictionary<int, (Piracystring filter, string context)> FilterTriggers = new Dictionary<int, (Piracystring filter, string context)>();
public Piracystring SelectedFilter;
public string SelectedFilterContext;
public Piracystring? SelectedFilter;
public string? SelectedFilterContext;
public long ReadBytes;
public long TotalBytes;
public int LinesAfterConfig;

View File

@@ -13,6 +13,6 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
protected const int SnoopBufferSize = 4096;
internal static readonly ArrayPool<byte> bufferPool = ArrayPool<byte>.Create(SnoopBufferSize, 64);
public abstract Task<(ISource source, string failReason)> FindHandlerAsync(DiscordMessage message, ICollection<IArchiveHandler> handlers);
public abstract Task<(ISource? source, string? failReason)> FindHandlerAsync(DiscordMessage message, ICollection<IArchiveHandler> handlers);
}
}

View File

@@ -12,14 +12,14 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
{
internal sealed class DiscordAttachmentHandler : BaseSourceHandler
{
public override async Task<(ISource source, string failReason)> FindHandlerAsync(DiscordMessage message, ICollection<IArchiveHandler> handlers)
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)
{
try
{
using var stream = await client.GetStreamAsync(attachment.Url).ConfigureAwait(false);
await using var stream = await client.GetStreamAsync(attachment.Url).ConfigureAwait(false);
var buf = bufferPool.Rent(SnoopBufferSize);
try
{
@@ -68,7 +68,7 @@ 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 using var stream = await client.GetStreamAsync(attachment.Url, cancellationToken).ConfigureAwait(false);
await handler.FillPipeAsync(stream, writer, cancellationToken).ConfigureAwait(false);
}
}

View File

@@ -18,7 +18,7 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
//https://www.dropbox.com/s/62ls9lw5i52fuib/RPCS3.log.gz?dl=0
private static readonly Regex ExternalLink = new Regex(@"(?<dropbox_link>(https?://)?(www\.)?dropbox\.com/s/(?<dropbox_id>[^/\s]+)/(?<filename>[^/\?\s])(/dl=[01])?)", DefaultOptions);
public override async Task<(ISource source, string failReason)> FindHandlerAsync(DiscordMessage message, ICollection<IArchiveHandler> handlers)
public override async Task<(ISource? source, string? failReason)> FindHandlerAsync(DiscordMessage message, ICollection<IArchiveHandler> handlers)
{
if (string.IsNullOrEmpty(message.Content))
return (null, null);
@@ -43,14 +43,14 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
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)
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))
if (response.Content.Headers.ContentDisposition?.FileNameStar is string fname && !string.IsNullOrEmpty(fname))
filename = fname;
uri = response.RequestMessage.RequestUri;
uri = response.RequestMessage?.RequestUri;
}
using var stream = await client.GetStreamAsync(uri).ConfigureAwait(false);
await using var stream = await client.GetStreamAsync(uri).ConfigureAwait(false);
var buf = bufferPool.Rent(SnoopBufferSize);
try
{
@@ -81,7 +81,7 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
private sealed class DropboxSource : ISource
{
private readonly Uri uri;
private readonly Uri? uri;
private readonly IArchiveHandler handler;
public string SourceType => "Dropbox";
@@ -90,7 +90,7 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
public long SourceFilePosition => handler.SourcePosition;
public long LogFileSize => handler.LogSize;
internal DropboxSource(Uri uri, IArchiveHandler handler, string fileName, int fileSize)
internal DropboxSource(Uri? uri, IArchiveHandler handler, string fileName, int fileSize)
{
this.uri = uri;
this.handler = handler;
@@ -101,7 +101,7 @@ 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 using var stream = await client.GetStreamAsync(uri, cancellationToken).ConfigureAwait(false);
await handler.FillPipeAsync(stream, writer, cancellationToken).ConfigureAwait(false);
}
}

View File

@@ -16,7 +16,7 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
{
private static readonly Regex ExternalLink = new Regex(@"(?<link>(https?://)?(github\.com/RPCS3/rpcs3|cdn\.discordapp\.com/attachments)/.*/(?<filename>[^/\?\s]+\.(gz|zip|rar|7z|log)))", DefaultOptions);
public override async Task<(ISource source, string failReason)> FindHandlerAsync(DiscordMessage message, ICollection<IArchiveHandler> handlers)
public override async Task<(ISource? source, string? failReason)> FindHandlerAsync(DiscordMessage message, ICollection<IArchiveHandler> handlers)
{
if (string.IsNullOrEmpty(message.Content))
return (null, null);
@@ -42,14 +42,14 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
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)
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))
if (response.Content.Headers.ContentDisposition?.FileNameStar is string fname && !string.IsNullOrEmpty(fname))
filename = fname;
uri = response.RequestMessage.RequestUri;
uri = response.RequestMessage?.RequestUri;
}
using var stream = await client.GetStreamAsync(uri).ConfigureAwait(false);
await using var stream = await client.GetStreamAsync(uri).ConfigureAwait(false);
var buf = bufferPool.Rent(SnoopBufferSize);
try
{
@@ -79,7 +79,7 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
private sealed class GenericSource : ISource
{
private readonly Uri uri;
private readonly Uri? uri;
private readonly IArchiveHandler handler;
public string SourceType => "Generic link";
@@ -89,7 +89,7 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
public long SourceFilePosition => handler.SourcePosition;
public long LogFileSize => handler.LogSize;
internal GenericSource(Uri uri, IArchiveHandler handler, string host, string fileName, int fileSize)
internal GenericSource(Uri? uri, IArchiveHandler handler, string host, string fileName, int fileSize)
{
this.uri = uri;
this.handler = handler;
@@ -101,7 +101,7 @@ 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 using var stream = await client.GetStreamAsync(uri, cancellationToken).ConfigureAwait(false);
await handler.FillPipeAsync(stream, writer, cancellationToken).ConfigureAwait(false);
}
}

View File

@@ -22,7 +22,7 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
private static readonly string[] Scopes = { DriveService.Scope.DriveReadonly };
private static readonly string ApplicationName = "RPCS3 Compatibility Bot 2.0";
public override async Task<(ISource source, string failReason)> FindHandlerAsync(DiscordMessage message, ICollection<IArchiveHandler> handlers)
public override async Task<(ISource? source, string? failReason)> FindHandlerAsync(DiscordMessage message, ICollection<IArchiveHandler> handlers)
{
if (string.IsNullOrEmpty(message.Content))
return (null, null);
@@ -45,15 +45,15 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
var fileInfoRequest = client.Files.Get(fid);
fileInfoRequest.Fields = "name, size, kind";
var fileMeta = await fileInfoRequest.ExecuteAsync(Config.Cts.Token).ConfigureAwait(false);
if (fileMeta.Kind == "drive#file")
if (fileMeta.Kind == "drive#file" && fileMeta.Size > 0)
{
var buf = bufferPool.Rent(SnoopBufferSize);
try
{
int read;
using (var stream = new MemoryStream(buf, true))
await using (var stream = new MemoryStream(buf, true))
{
var limit = Math.Min(SnoopBufferSize, (int)fileMeta.Size) - 1;
var limit = Math.Min(SnoopBufferSize, fileMeta.Size.Value) - 1;
var progress = await fileInfoRequest.DownloadRangeAsync(stream, new RangeHeaderValue(0, limit), Config.Cts.Token).ConfigureAwait(false);
if (progress.Status != DownloadStatus.Completed)
continue;
@@ -103,9 +103,9 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
public long SourceFilePosition => handler.SourcePosition;
public long LogFileSize => handler.LogSize;
private FilesResource.GetRequest fileInfoRequest;
private FileMeta fileMeta;
private IArchiveHandler handler;
private readonly FilesResource.GetRequest fileInfoRequest;
private readonly FileMeta fileMeta;
private readonly IArchiveHandler handler;
public GoogleDriveSource(FilesResource.GetRequest fileInfoRequest, FileMeta fileMeta, IArchiveHandler handler)
{
@@ -119,9 +119,9 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
try
{
var pipe = new Pipe();
using var pushStream = pipe.Writer.AsStream();
await using var pushStream = pipe.Writer.AsStream();
var progressTask = fileInfoRequest.DownloadAsync(pushStream, cancellationToken);
using var pullStream = pipe.Reader.AsStream();
await using var pullStream = pipe.Reader.AsStream();
var pipingTask = handler.FillPipeAsync(pullStream, writer, cancellationToken);
var result = await progressTask.ConfigureAwait(false);
if (result.Status != DownloadStatus.Completed)

View File

@@ -9,7 +9,7 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
{
public interface ISourceHandler
{
Task<(ISource source, string failReason)> FindHandlerAsync(DiscordMessage message, ICollection<IArchiveHandler> handlers);
Task<(ISource? source, string? failReason)> FindHandlerAsync(DiscordMessage message, ICollection<IArchiveHandler> handlers);
}
public interface ISource

View File

@@ -18,7 +18,7 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
private static readonly Regex ExternalLink = new Regex(@"(?<mega_link>(https?://)?mega(\.co)?\.nz/(#(?<mega_id>[^/>\s]+)|file/(?<new_mega_id>[^/>\s]+)))", DefaultOptions);
private static readonly IProgress<double> doodad = new Progress<double>(_ => { });
public override async Task<(ISource source, string failReason)> FindHandlerAsync(DiscordMessage message, ICollection<IArchiveHandler> handlers)
public override async Task<(ISource? source, string? failReason)> FindHandlerAsync(DiscordMessage message, ICollection<IArchiveHandler> handlers)
{
if (string.IsNullOrEmpty(message.Content))
return (null, null);
@@ -44,7 +44,7 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
try
{
int read;
using (var stream = await client.DownloadAsync(uri, doodad, Config.Cts.Token).ConfigureAwait(false))
await using (var stream = await client.DownloadAsync(uri, doodad, Config.Cts.Token).ConfigureAwait(false))
read = await stream.ReadBytesAsync(buf).ConfigureAwait(false);
foreach (var handler in handlers)
{
@@ -72,10 +72,10 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
private sealed class MegaSource : ISource
{
private IMegaApiClient client;
private Uri uri;
private INodeInfo node;
private IArchiveHandler handler;
private readonly IMegaApiClient client;
private readonly Uri uri;
private readonly INodeInfo node;
private readonly IArchiveHandler handler;
public string SourceType => "Mega";
public string FileName => node.Name;
@@ -93,7 +93,7 @@ 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 using var stream = await client.DownloadAsync(uri, doodad, cancellationToken).ConfigureAwait(false);
await handler.FillPipeAsync(stream, writer, cancellationToken).ConfigureAwait(false);
}
}

View File

@@ -21,7 +21,7 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
private static readonly Regex ExternalLink = new Regex(@"(?<onedrive_link>(https?://)?(1drv\.ms|onedrive\.live\.com)/[^>\s]+)", DefaultOptions);
private static readonly Client client = new Client();
public async override Task<(ISource source, string failReason)> FindHandlerAsync(DiscordMessage message, ICollection<IArchiveHandler> handlers)
public async override Task<(ISource? source, string? failReason)> FindHandlerAsync(DiscordMessage message, ICollection<IArchiveHandler> handlers)
{
if (string.IsNullOrEmpty(message.Content))
return (null, null);
@@ -38,15 +38,16 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
if (m.Groups["onedrive_link"].Value is string lnk
&& !string.IsNullOrEmpty(lnk)
&& Uri.TryCreate(lnk, UriKind.Absolute, out var uri)
&& await client.ResolveContentLinkAsync(uri, Config.Cts.Token).ConfigureAwait(false) is DriveItemMeta itemMeta)
&& await client.ResolveContentLinkAsync(uri, Config.Cts.Token).ConfigureAwait(false) is DriveItemMeta itemMeta
&& itemMeta.ContentDownloadUrl is string downloadUrl)
{
try
{
var filename = itemMeta.Name;
var filename = itemMeta.Name ?? "";
var filesize = itemMeta.Size;
uri = new Uri(itemMeta.ContentDownloadUrl);
uri = new Uri(downloadUrl);
using var stream = await httpClient.GetStreamAsync(uri).ConfigureAwait(false);
await using var stream = await httpClient.GetStreamAsync(uri).ConfigureAwait(false);
var buf = bufferPool.Rent(SnoopBufferSize);
try
{
@@ -102,7 +103,7 @@ 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 using var stream = await client.GetStreamAsync(uri, cancellationToken).ConfigureAwait(false);
await handler.FillPipeAsync(stream, writer, cancellationToken).ConfigureAwait(false);
}
}

View File

@@ -15,7 +15,7 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
{
private static readonly Regex ExternalLink = new Regex(@"(?<pastebin_link>(https?://)pastebin.com/(raw/)?(?<pastebin_id>[^/>\s]+))", DefaultOptions);
public override async Task<(ISource source, string failReason)> FindHandlerAsync(DiscordMessage message, ICollection<IArchiveHandler> handlers)
public override async Task<(ISource? source, string? failReason)> FindHandlerAsync(DiscordMessage message, ICollection<IArchiveHandler> handlers)
{
if (string.IsNullOrEmpty(message.Content))
return (null, null);
@@ -33,7 +33,7 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers
&& !string.IsNullOrEmpty(pid))
{
var uri = new Uri("https://pastebin.com/raw/" + pid);
using var stream = await client.GetStreamAsync(uri).ConfigureAwait(false);
await using var stream = await client.GetStreamAsync(uri).ConfigureAwait(false);
var buf = bufferPool.Rent(SnoopBufferSize);
try
{
@@ -85,7 +85,7 @@ 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 using var stream = await client.GetStreamAsync(uri, cancellationToken).ConfigureAwait(false);
await handler.FillPipeAsync(stream, writer, cancellationToken).ConfigureAwait(false);
}
}

View File

@@ -48,7 +48,7 @@ namespace CompatBot.EventHandlers
};
private static readonly SemaphoreSlim QueueLimiter = new SemaphoreSlim(Math.Max(1, Environment.ProcessorCount / 2), Math.Max(1, Environment.ProcessorCount / 2));
private delegate void OnLog(DiscordClient client, DiscordChannel channel, DiscordMessage message, DiscordMember requester = null, bool checkExternalLinks = false, bool force = false);
private delegate void OnLog(DiscordClient client, DiscordChannel channel, DiscordMessage message, DiscordMember? requester = null, bool checkExternalLinks = false, bool force = false);
private static event OnLog OnNewLog;
static LogParsingHandler() => OnNewLog += EnqueueLogProcessing;
@@ -72,12 +72,11 @@ namespace CompatBot.EventHandlers
return Task.CompletedTask;
}
public static async void EnqueueLogProcessing(DiscordClient client, DiscordChannel channel, DiscordMessage message, DiscordMember requester = null, bool checkExternalLinks = false, bool force = false)
public static async void EnqueueLogProcessing(DiscordClient client, DiscordChannel channel, DiscordMessage message, DiscordMember? requester = null, bool checkExternalLinks = false, bool force = false)
{
var start = DateTimeOffset.UtcNow;
try
{
// ReSharper disable once MethodHasAsyncOverload
if (!QueueLimiter.Wait(0))
{
Config.TelemetryClient?.TrackRequest(nameof(LogParsingHandler), start, TimeSpan.Zero, HttpStatusCode.TooManyRequests.ToString(), false);
@@ -85,9 +84,9 @@ namespace CompatBot.EventHandlers
return;
}
bool parsedLog = false;
var parsedLog = false;
var startTime = Stopwatch.StartNew();
DiscordMessage botMsg = null;
DiscordMessage? botMsg = null;
try
{
var possibleHandlers = sourceHandlers.Select(h => h.FindHandlerAsync(message, archiveHandlers).ConfigureAwait(false).GetAwaiter().GetResult()).ToList();
@@ -105,7 +104,7 @@ namespace CompatBot.EventHandlers
botMsg = await channel.SendMessageAsync(embed: analyzingProgressEmbed.AddAuthor(client, message, source)).ConfigureAwait(false);
parsedLog = true;
LogParseState result = null, tmpResult = null;
LogParseState? result = null, tmpResult;
using (var timeout = new CancellationTokenSource(Config.LogParsingTimeoutInSec))
{
using var combinedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, Config.Cts.Token);
@@ -196,7 +195,7 @@ namespace CompatBot.EventHandlers
if (result.SelectedFilter != null)
{
var ignoreFlags = FilterAction.IssueWarning | FilterAction.SendMessage | FilterAction.ShowExplain;
await ContentFilter.PerformFilterActions(client, message, result.SelectedFilter, ignoreFlags, result.SelectedFilterContext).ConfigureAwait(false);
await ContentFilter.PerformFilterActions(client, message, result.SelectedFilter, ignoreFlags, result.SelectedFilterContext!).ConfigureAwait(false);
}
if (!force && string.IsNullOrEmpty(message.Content) && !isSpamChannel)
@@ -268,7 +267,7 @@ namespace CompatBot.EventHandlers
return;
}
var linkStart = message.Content.IndexOf("http");
var linkStart = message.Content.IndexOf("http", StringComparison.Ordinal);
if (linkStart > -1)
{
var link = message.Content[linkStart..].Split(linkSeparator, 2)[0];
@@ -294,9 +293,9 @@ namespace CompatBot.EventHandlers
}
}
public static async Task<LogParseState> ParseLogAsync(ISource source, Func<Task> onProgressAsync, CancellationToken cancellationToken)
public static async Task<LogParseState?> ParseLogAsync(ISource source, Func<Task> onProgressAsync, CancellationToken cancellationToken)
{
LogParseState result = null;
LogParseState? result = null;
try
{
try

View File

@@ -40,7 +40,7 @@ namespace CompatBot.EventHandlers
public static async Task MonitorAsync(DiscordClient client)
{
var lastCheck = DateTime.UtcNow.AddDays(-1);
Exception lastException = null;
Exception? lastException = null;
while (!Config.Cts.IsCancellationRequested)
{
var now = DateTime.UtcNow;

View File

@@ -26,16 +26,14 @@ namespace CompatBot.EventHandlers
public static async Task OnMessageCreated(DiscordClient _, MessageCreateEventArgs args)
{
if (DefaultHandlerFilter.IsFluff(args?.Message))
if (DefaultHandlerFilter.IsFluff(args.Message))
return;
#if !DEBUG
if (!"help".Equals(args?.Channel?.Name, StringComparison.InvariantCultureIgnoreCase))
if (!"help".Equals(args.Channel.Name, StringComparison.InvariantCultureIgnoreCase))
return;
if (DateTime.UtcNow - lastMention < ThrottlingThreshold)
return;
#endif
var match = UploadLogMention.Match(args.Message.Content);
if (!match.Success || string.IsNullOrEmpty(match.Groups["help"].Value))
@@ -50,7 +48,7 @@ namespace CompatBot.EventHandlers
var lastBotMessages = await args.Channel.GetMessagesBeforeCachedAsync(args.Message.Id, 10).ConfigureAwait(false);
foreach (var msg in lastBotMessages)
if (BotReactionsHandler.NeedToSilence(msg).needToChill
|| (msg.Author.IsCurrent && msg.Content == explanation.Text))
|| msg.Author.IsCurrent && msg.Content == explanation.Text)
return;
await args.Channel.SendMessageAsync(explanation.Text, explanation.Attachment, explanation.AttachmentFilename).ConfigureAwait(false);
@@ -64,7 +62,7 @@ namespace CompatBot.EventHandlers
public static async Task<Explanation> GetExplanationAsync(string term)
{
using var db = new BotDb();
await using var db = new BotDb();
var result = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == term).ConfigureAwait(false);
return result ?? DefaultExplanation[term];
}

View File

@@ -85,9 +85,7 @@ namespace CompatBot.EventHandlers
};
public static Task Handler(DiscordClient c, MessageReactionAddEventArgs args)
{
return CheckMessageAsync(c, args.Channel, args.User, args.Message, args.Emoji, false);
}
=> CheckMessageAsync(c, args.Channel, args.User, args.Message, args.Emoji, false);
public static async Task CheckBacklogAsync(DiscordClient client, DiscordGuild guild)
{
@@ -163,12 +161,12 @@ namespace CompatBot.EventHandlers
.Distinct()
.Select(u => channel.Guild
.GetMemberAsync(u.Id)
.ContinueWith(ct => ct.IsCompletedSuccessfully ? ct : Task.FromResult((DiscordMember)null), TaskScheduler.Default))
.ContinueWith(ct => ct.IsCompletedSuccessfully ? ct : Task.FromResult((DiscordMember?)null), TaskScheduler.Default))
.ToList() //force eager task creation
.Select(t => t.Unwrap().ConfigureAwait(false).GetAwaiter().GetResult())
.Where(m => m != null)
.ToList();
var reporters = members.Where(m => m.Roles.Any()).ToList();
var reporters = members.Where(m => m!.Roles.Any()).ToList();
if (reporters.Count < Config.Moderation.StarbucksThreshold)
return;

View File

@@ -25,7 +25,7 @@ namespace CompatBot.EventHandlers
if (ex is InvalidOperationException && ex.Message.Contains("No matching subcommands were found"))
ex = new CommandNotFoundException(e.Command.Name);
if (!(ex is CommandNotFoundException cnfe))
if (ex is not CommandNotFoundException cnfe)
{
Config.Log.Error(e.Exception);

View File

@@ -39,12 +39,12 @@ namespace CompatBot
if (args.Length > 0 && args[0] == "--dry-run")
{
Console.WriteLine("Database path: " + Path.GetDirectoryName(Path.GetFullPath(DbImporter.GetDbPath("fake.db", Environment.SpecialFolder.ApplicationData))));
if (Assembly.GetEntryAssembly().GetCustomAttribute<UserSecretsIdAttribute>() != null)
if (Assembly.GetEntryAssembly()?.GetCustomAttribute<UserSecretsIdAttribute>() != null)
Console.WriteLine("Bot config path: " + Path.GetDirectoryName(Path.GetFullPath(Config.GoogleApiConfigPath)));
return;
}
if (Process.GetCurrentProcess().Id == 0)
if (Environment.ProcessId == 0)
Config.Log.Info("Well, this was unexpected");
var singleInstanceCheckThread = new Thread(() =>
{
@@ -95,11 +95,11 @@ namespace CompatBot
}
}
using (var db = new BotDb())
await using (var db = new BotDb())
if (!await DbImporter.UpgradeAsync(db, Config.Cts.Token))
return;
using (var db = new ThumbnailDb())
await using (var db = new ThumbnailDb())
if (!await DbImporter.UpgradeAsync(db, Config.Cts.Token))
return;
@@ -296,8 +296,8 @@ namespace CompatBot
}
ulong? channelId = null;
string restartMsg = null;
using (var db = new BotDb())
string? restartMsg = null;
await using (var db = new BotDb())
{
var chState = db.BotState.FirstOrDefault(k => k.Key == "bot-restart-channel");
if (chState != null)
@@ -312,7 +312,7 @@ namespace CompatBot
restartMsg = msgState.Value;
db.BotState.Remove(msgState);
}
db.SaveChanges();
await db.SaveChangesAsync().ConfigureAwait(false);
}
if (string.IsNullOrEmpty(restartMsg))
restartMsg = null;

View File

@@ -23,7 +23,7 @@ namespace CompatBot.ThumbScrapper
private static DateTime StoreRefreshTimestamp = DateTime.MinValue;
private static readonly SemaphoreSlim QueueLimiter = new SemaphoreSlim(32, 32);
public async Task RunAsync(CancellationToken cancellationToken)
public static async Task RunAsync(CancellationToken cancellationToken)
{
do
{
@@ -53,7 +53,7 @@ namespace CompatBot.ThumbScrapper
if (!match.Success)
return;
if (!QueueLimiter.Wait(0))
if (!QueueLimiter.Wait(0, cancellationToken))
return;
try
@@ -153,14 +153,15 @@ namespace CompatBot.ThumbScrapper
// get all containers from all the menus
var stores = await Client.GetStoresAsync(locale, cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrEmpty(stores?.Data.BaseUrl))
containerIds.Add(Path.GetFileName(stores.Data.BaseUrl));
foreach (var id in containerIds)
{
if (cancellationToken.IsCancellationRequested)
return;
containerIds?.Add(Path.GetFileName(stores.Data.BaseUrl));
if (containerIds != null)
foreach (var id in containerIds)
{
if (cancellationToken.IsCancellationRequested)
return;
await ScrapeContainerIdsAsync(locale, id, knownContainers, cancellationToken).ConfigureAwait(false);
}
await ScrapeContainerIdsAsync(locale, id, knownContainers, cancellationToken).ConfigureAwait(false);
}
Config.Log.Debug($"\tFound {knownContainers.Count} containers");
// now let's scrape for actual games in every container
@@ -192,8 +193,8 @@ namespace CompatBot.ThumbScrapper
do
{
var tries = 0;
Container container = null;
bool error = false;
Container? container = null;
var error = false;
do
{
try
@@ -224,7 +225,7 @@ namespace CompatBot.ThumbScrapper
// returned items are just ids that need to be resolved
if (returned > 0)
{
foreach (var item in container.Data.Relationships.Children.Data)
foreach (var item in container.Data!.Relationships!.Children!.Data!)
{
if (cancellationToken.IsCancellationRequested)
return;
@@ -291,11 +292,11 @@ namespace CompatBot.ThumbScrapper
private static bool NeedLookup(string contentId)
{
using (var db = new ThumbnailDb())
if (db.Thumbnail.FirstOrDefault(t => t.ContentId == contentId) is Thumbnail thumbnail)
if (!string.IsNullOrEmpty(thumbnail.Url))
if (ScrapeStateProvider.IsFresh(new DateTime(thumbnail.Timestamp, DateTimeKind.Utc)))
return false;
using var db = new ThumbnailDb();
if (db.Thumbnail.FirstOrDefault(t => t.ContentId == contentId) is Thumbnail thumbnail)
if (!string.IsNullOrEmpty(thumbnail.Url))
if (ScrapeStateProvider.IsFresh(new DateTime(thumbnail.Timestamp, DateTimeKind.Utc)))
return false;
return true;
}
@@ -338,7 +339,7 @@ namespace CompatBot.ThumbScrapper
}
}
private static async Task AddOrUpdateThumbnailAsync(string contentId, string name, string url, CancellationToken cancellationToken)
private static async Task AddOrUpdateThumbnailAsync(string contentId, string? name, string? url, CancellationToken cancellationToken)
{
var match = ContentIdMatcher.Match(contentId);
if (!match.Success)
@@ -349,7 +350,7 @@ namespace CompatBot.ThumbScrapper
return;
name = string.IsNullOrEmpty(name) ? null : name;
using var db = new ThumbnailDb();
await using var db = new ThumbnailDb();
var savedItem = db.Thumbnail.FirstOrDefault(t => t.ProductCode == productCode);
if (savedItem == null)
{
@@ -361,7 +362,7 @@ namespace CompatBot.ThumbScrapper
Url = url,
Timestamp = DateTime.UtcNow.Ticks,
};
db.Thumbnail.Add(newItem);
await db.Thumbnail.AddAsync(newItem, cancellationToken).ConfigureAwait(false);
}
else if (!string.IsNullOrEmpty(url))
{
@@ -426,8 +427,6 @@ namespace CompatBot.ThumbScrapper
}
private static void PrintError(Exception e)
{
Config.Log.Error(e, "Error scraping thumbnails");
}
=> Config.Log.Error(e, "Error scraping thumbnails");
}
}

View File

@@ -5,9 +5,9 @@ namespace CompatBot.Utils
{
internal static class DefaultHandlerFilter
{
internal static bool IsFluff(DiscordMessage message)
internal static bool IsFluff(DiscordMessage? message)
{
if (message == null)
if (message is null)
return true;
if (message.Author.IsBotSafeCheck())

View File

@@ -134,13 +134,13 @@ namespace CompatBot.Utils
}
}
public static async Task<DiscordMessage> ReportAsync(this DiscordClient client, string infraction, DiscordMessage message, IEnumerable<DiscordMember> reporters, string? comment, ReportSeverity severity)
public static async Task<DiscordMessage> ReportAsync(this DiscordClient client, string infraction, DiscordMessage message, IEnumerable<DiscordMember?> reporters, string? comment, ReportSeverity severity)
{
var getLogChannelTask = client.GetChannelAsync(Config.BotLogId);
var embedBuilder = MakeReportTemplate(client, infraction, message, severity);
var reportText = string.IsNullOrEmpty(comment) ? "" : comment.Sanitize() + Environment.NewLine;
embedBuilder.Description = (reportText + embedBuilder.Description).Trim(EmbedPager.MaxDescriptionLength);
var mentions = reporters.Select(GetMentionWithNickname);
var mentions = reporters.Where(m => m is not null).Select(GetMentionWithNickname!);
embedBuilder.AddField("Reporters", string.Join(Environment.NewLine, mentions));
var logChannel = await getLogChannelTask.ConfigureAwait(false);
return await logChannel.SendMessageAsync(embed: embedBuilder.Build(), mentions: Config.AllowedMentions.Nothing).ConfigureAwait(false);

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using CompatApiClient.Compression;

View File

@@ -15,7 +15,7 @@ namespace CompatBot.Utils
this InteractivityExtension interactivity,
DiscordMessage message,
DiscordUser user,
params DiscordEmoji[] reactions)
params DiscordEmoji?[] reactions)
=> WaitForMessageOrReactionAsync(interactivity, message, user, null, reactions);
public static async Task<(DiscordMessage? message, DiscordMessage? text, MessageReactionAddEventArgs? reaction)> WaitForMessageOrReactionAsync(

View File

@@ -7,14 +7,14 @@ namespace CompatBot.Utils.ResultFormatters
{
public static class IrdSearchResultFormatter
{
public static DiscordEmbedBuilder AsEmbed(this SearchResult searchResult)
public static DiscordEmbedBuilder AsEmbed(this SearchResult? searchResult)
{
var result = new DiscordEmbedBuilder
{
//Title = "IRD Library Search Result",
Color = Config.Colors.DownloadLinks,
};
if (searchResult.Data.Count == 0)
if (searchResult?.Data is null or {Count: 0})
{
result.Color = Config.Colors.LogResultFailed;
result.Description = "No matches were found";
@@ -23,14 +23,15 @@ namespace CompatBot.Utils.ResultFormatters
foreach (var item in searchResult.Data)
{
var parts = item.Filename?.Split('-');
if (parts == null)
parts = new string[] {null, null};
else if (parts.Length == 1)
parts = new[] {null, item.Filename};
if (string.IsNullOrEmpty(item.Filename))
continue;
string[] parts = item.Filename.Split('-');
if (parts.Length == 1)
parts = new[] {"", item.Filename};
result.AddField(
$"[{parts?[0]} v{item.GameVersion}] {item.Title?.Sanitize().Trim(EmbedPager.MaxFieldTitleLength)}",
$"[⏬ `{parts[1]?.Sanitize().Trim(200)}`]({IrdClient.GetDownloadLink(item.Filename)}) [ Info]({IrdClient.GetInfoLink(item.Filename)})"
$"[{parts[0]} v{item.GameVersion}] {item.Title?.Sanitize().Trim(EmbedPager.MaxFieldTitleLength)}",
$"[⏬ `{parts[1].Sanitize().Trim(200)}`]({IrdClient.GetDownloadLink(item.Filename)}) [ Info]({IrdClient.GetInfoLink(item.Filename)})"
);
}
return result;

View File

@@ -51,8 +51,8 @@ namespace CompatBot.Utils.ResultFormatters
items["cpu_model"] = cpuModel;
items["thread_count"] = cpuInfo.Groups["thread_count"].Value;
items["memory_amount"] = cpuInfo.Groups["memory_amount"].Value;
items["cpu_tsc"] = cpuInfo.Groups["tsc"]?.Value;
items["cpu_extensions"] = cpuInfo.Groups["cpu_extensions"]?.Value;
items["cpu_tsc"] = cpuInfo.Groups["tsc"].Value;
items["cpu_extensions"] = cpuInfo.Groups["cpu_extensions"].Value;
}
if (osInfo.Success)
{
@@ -135,7 +135,7 @@ namespace CompatBot.Utils.ResultFormatters
private const int ColumnWidth = 30;
private static (string name, List<string> lines) BuildCpuSection(NameValueCollection items)
private static (string? name, List<string>? lines) BuildCpuSection(NameValueCollection items)
{
if (string.IsNullOrEmpty(items["ppu_decoder"]))
return (null, null);
@@ -155,7 +155,7 @@ namespace CompatBot.Utils.ResultFormatters
return ("CPU Settings", lines);
}
private static (string name, List<string> lines) BuildGpuSection(NameValueCollection items)
private static (string? name, List<string>? lines) BuildGpuSection(NameValueCollection items)
{
if (string.IsNullOrEmpty(items["renderer"]))
return (null, null);
@@ -199,7 +199,7 @@ namespace CompatBot.Utils.ResultFormatters
return ("GPU Settings", lines);
}
private static void BuildSettingsSections(DiscordEmbedBuilder builder, NameValueCollection items, (string name, List<string> lines) colA, (string name, List<string> lines) colB)
private static void BuildSettingsSections(DiscordEmbedBuilder builder, NameValueCollection items, (string? name, List<string>? lines) colA, (string? name, List<string>? lines) colB)
{
if (colA.lines?.Count > 0 && colB.lines?.Count > 0)
{
@@ -212,19 +212,19 @@ namespace CompatBot.Utils.ResultFormatters
var linesToSkip = colAToRemove - linesToRemove;
var tmp = colA.lines;
colA.lines = new List<string>(tmp.Count - linesToRemove);
for (var i = 0; i < tmp.Count; i++)
if (!tmp[i].EndsWith("N/A") || (linesToSkip--) > 0)
colA.lines.Add(tmp[i]);
foreach (var t in tmp)
if (!t.EndsWith("N/A") || linesToSkip-- > 0)
colA.lines.Add(t);
linesToSkip = colBToRemove - linesToRemove;
tmp = colB.lines;
colB.lines = new List<string>(tmp.Count - linesToRemove);
for (var i = 0; i < tmp.Count; i++)
if (!tmp[i].EndsWith("N/A") || (linesToSkip--) > 0)
if (!tmp[i].EndsWith("N/A") || linesToSkip-- > 0)
colB.lines.Add(tmp[i]);
}
AddSettingsSection(builder, colA.name, colA.lines, isCustomSettings);
AddSettingsSection(builder, colB.name, colB.lines, isCustomSettings);
AddSettingsSection(builder, colA.name!, colA.lines, isCustomSettings);
AddSettingsSection(builder, colB.name!, colB.lines, isCustomSettings);
}
}
@@ -232,7 +232,7 @@ namespace CompatBot.Utils.ResultFormatters
{
var result = new StringBuilder();
foreach (var line in lines)
result.Append("`").Append(line).AppendLine("`");
result.Append('`').Append(line).AppendLine("`");
if (isCustomSettings)
name = "Per-game " + name;
builder.AddField(name, result.ToString().FixSpaces(), true);

View File

@@ -21,7 +21,7 @@ namespace CompatBot.Utils.ResultFormatters
private static async Task BuildNotesSectionAsync(DiscordEmbedBuilder builder, LogParseState state, DiscordClient discordClient)
{
var items = state.CompleteCollection;
var items = state.CompletedCollection;
var multiItems = state.CompleteMultiValueCollection;
var notes = new List<string>();
var (_, brokenDump, longestPath) = await HasBrokenFilesAsync(state).ConfigureAwait(false);
@@ -35,8 +35,8 @@ namespace CompatBot.Utils.ResultFormatters
if (multiItems["fatal_error"] is UniqueList<string> fatalErrors && fatalErrors.Any())
{
var contexts = multiItems["fatal_error_context"];
var reducedFatalErros = GroupSimilar(fatalErrors);
foreach (var (fatalError, count, similarity) in reducedFatalErros)
var reducedFatalErrors = GroupSimilar(fatalErrors);
foreach (var (fatalError, count, similarity) in reducedFatalErrors)
{
var knownFatal = false;
if (fatalError.Contains("psf.cpp", StringComparison.InvariantCultureIgnoreCase)
@@ -176,13 +176,13 @@ namespace CompatBot.Utils.ResultFormatters
items["ldr_disc_full"],
items["ldr_path_full"],
items["ldr_boot_path_full"],
items["elf_boot_path_full"]
items["elf_boot_path_full"],
}.Where(s => !string.IsNullOrEmpty(s));
const int maxPath = 260;
const int maxFolderPath = 260 - 1 - 8 - 3;
foreach (var p in knownPaths)
{
if (p.Length > maxPath)
if (p!.Length > maxPath)
{
notes.Add($"⚠ Some file paths are longer than {maxPath} characters");
break;
@@ -309,7 +309,7 @@ namespace CompatBot.Utils.ResultFormatters
if (cpu.StartsWith("Intel") || cpu.StartsWith("Pentium"))
{
if (!items["cpu_extensions"].Contains("TSX")
if (items["cpu_extensions"]?.Contains("TSX") is not true
&& (cpu.Contains("Core2")
|| cpu.Contains("Celeron")
|| cpu.Contains("Atom")
@@ -323,9 +323,9 @@ namespace CompatBot.Utils.ResultFormatters
}
}
Version oglVersion = null;
Version? oglVersion = null;
if (items["opengl_version"] is string oglVersionString)
Version.TryParse(oglVersionString, out oglVersion);
_ = Version.TryParse(oglVersionString, out oglVersion);
if (items["glsl_version"] is string glslVersionString &&
Version.TryParse(glslVersionString, out var glslVersion))
{
@@ -352,7 +352,7 @@ namespace CompatBot.Utils.ResultFormatters
var modelNumber = intelMatch.Groups["gpu_model_number"].Value;
if (!string.IsNullOrEmpty(modelNumber) && modelNumber.StartsWith('P'))
modelNumber = modelNumber[1..];
int.TryParse(modelNumber, out var modelNumberInt);
_ = int.TryParse(modelNumber, out var modelNumberInt);
if (family == "UHD" || family == "Iris Plus" || modelNumberInt > 500 && modelNumberInt < 1000)
notes.Add("⚠ Intel iGPUs are not officially supported; visual glitches are to be expected");
else
@@ -566,12 +566,13 @@ namespace CompatBot.Utils.ResultFormatters
if (items["rap_file"] is UniqueList<string> raps && raps.Any())
{
var limitTo = 5;
var licenseNames = raps
List<string> licenseNames = raps
.Select(Path.GetFileName)
.Where(l => !string.IsNullOrEmpty(l))
.Distinct()
.Except(KnownBogusLicenses)
.OrderBy(l => l)
.ToList();
.ToList()!;
var formattedLicenseNames = licenseNames
.Select(p => $"{StringUtils.InvisibleSpacer}`{p}`")
.ToList();
@@ -591,7 +592,12 @@ namespace CompatBot.Utils.ResultFormatters
builder.AddField("Missing Licenses", content);
var gameRegion = serial?.Length > 3 ? new[] {serial[2]} : Enumerable.Empty<char>();
var dlcRegions = licenseNames.Select(n => n[9]).Concat(gameRegion).Distinct().ToArray();
var dlcRegions = licenseNames
.Where(l => l.Length > 9)
.Select(n => n[9])
.Concat(gameRegion)
.Distinct()
.ToArray();
if (dlcRegions.Length > 1)
generalNotes.Add($"🤔 That is a very interesting DLC collection from {dlcRegions.Length} different regions");
if (KnownCustomLicenses.Overlaps(licenseNames))
@@ -602,10 +608,10 @@ namespace CompatBot.Utils.ResultFormatters
private static async Task<(bool irdChecked, bool broken, int longestPath)> HasBrokenFilesAsync(LogParseState state)
{
var items = state.CompleteCollection;
var items = state.CompletedCollection;
var multiItems = state.CompleteMultiValueCollection;
var defaultLongestPath = "/PS3_GAME/USRDIR/".Length + (1+8+3)*2; // usually there's at least one more level for data files
if (!(items["serial"] is string productCode))
if (items["serial"] is not string productCode)
return (false, false, defaultLongestPath);
if (!productCode.StartsWith("B") && !productCode.StartsWith("M"))
@@ -656,7 +662,10 @@ namespace CompatBot.Utils.ResultFormatters
return (true, true, longestPath);
}
var knownDirs = new HashSet<string>(knownFiles.Select(f => Path.GetDirectoryName(f).Replace('\\', '/')),
var knownDirs = new HashSet<string>(
knownFiles
.Select(f => Path.GetDirectoryName(f)?.Replace('\\', '/'))
.Where(p => !string.IsNullOrEmpty(p))!,
StringComparer.InvariantCultureIgnoreCase);
var brokenDirs = missingDirs.Where(knownDirs.Contains).ToList();
if (brokenDirs.Count > 0)

View File

@@ -15,11 +15,11 @@ namespace CompatBot.Utils.ResultFormatters
{
private static void BuildWeirdSettingsSection(DiscordEmbedBuilder builder, LogParseState state, List<string> generalNotes)
{
var items = state.CompleteCollection;
var items = state.CompletedCollection;
var multiItems = state.CompleteMultiValueCollection;
var notes = new List<string>();
var serial = items["serial"] ?? "";
int.TryParse(items["thread_count"], out var threadCount);
_ = int.TryParse(items["thread_count"], out var threadCount);
if (items["disable_logs"] == EnabledMark)
notes.Add("❗ `Silence All Logs` is enabled, please disable and upload a new log");
else if (!string.IsNullOrWhiteSpace(items["log_disabled_channels"])
@@ -83,8 +83,8 @@ namespace CompatBot.Utils.ResultFormatters
&& !(items["resolution"] == "1920x1080"
&& Known1080pIds.Contains(serial)))
notes.Add("⚠ `Resolution` was changed from the recommended `1280x720`");
var dimensions = items["resolution"].Split("x");
if (dimensions.Length > 1
var dimensions = items["resolution"]?.Split("x");
if (dimensions?.Length > 1
&& int.TryParse(dimensions[0], out var width)
&& int.TryParse(dimensions[1], out var height))
{
@@ -172,7 +172,7 @@ namespace CompatBot.Utils.ResultFormatters
}
var vsync = items["vsync"] == EnabledMark;
string vkPm;
string? vkPm;
if (items["rsx_present_mode"] is string pm)
RsxPresentModeMap.TryGetValue(pm, out vkPm);
else
@@ -354,8 +354,8 @@ namespace CompatBot.Utils.ResultFormatters
CheckGt6Settings(serial, items, notes, generalNotes);
CheckSly4Settings(serial, items, notes);
CheckDragonsCrownSettings(serial, items, notes);
CheckLbpSettings(serial, items, notes, generalNotes);
CheckKillzone3Settings(serial, items, notes, ppuPatches, patchNames);
CheckLbpSettings(serial, items, generalNotes);
CheckKillzone3Settings(serial, items, notes, patchNames);
}
else if (items["game_title"] == "vsh.self")
CheckVshSettings(items, notes, generalNotes);
@@ -393,7 +393,7 @@ namespace CompatBot.Utils.ResultFormatters
if (items["shader_mode"] == "Interpreter only")
notes.Add("⚠ `Shader Interpreter Only` mode is not accurate and very demanding");
else if (!items["shader_mode"].StartsWith("Async"))
else if (items["shader_mode"]?.StartsWith("Async") is not true)
notes.Add("❔ Async shader compilation is disabled");
if (items["driver_recovery_timeout"] is string driverRecoveryTimeout
&& int.TryParse(driverRecoveryTimeout, out var drtValue)
@@ -579,7 +579,7 @@ namespace CompatBot.Utils.ResultFormatters
private static void CheckJojoSettings(string serial, LogParseState state, List<string> notes, Dictionary<string, int> ppuPatches, HashSet<string> ppuHashes, List<string> generalNotes)
{
var items = state.CompleteCollection;
var items = state.CompletedCollection;
if (AllStarBattleIds.Contains(serial) || serial == "BLJS10318" || serial == "NPJB00753")
{
if (items["audio_buffering"] == EnabledMark && items["audio_buffer_duration"] != "20")
@@ -733,7 +733,7 @@ namespace CompatBot.Utils.ResultFormatters
&& items["spu_block_size"] is string blockSize
&& blockSize != "Safe")
notes.Add("⚠ Please change `SPU Block Size` to `Safe` for this game");
*/
*/
}
else if (GowAscIds.Contains(serial))
generalNotes.Add(" This game is known to be very unstable");
@@ -918,7 +918,7 @@ namespace CompatBot.Utils.ResultFormatters
"NPEA00321", "NPEA90084", "NPEA90085", "NPEA90086", "NPHA80140", "NPJA90178", "NPUA70133",
};
private static void CheckKillzone3Settings(string serial, NameValueCollection items, List<string> notes, Dictionary<string, int> ppuPatches, UniqueList<string> patchNames)
private static void CheckKillzone3Settings(string serial, NameValueCollection items, List<string> notes, UniqueList<string> patchNames)
{
if (!Killzone3Ids.Contains(serial))
return;
@@ -1074,13 +1074,6 @@ namespace CompatBot.Utils.ResultFormatters
notes.Add("⚠ Please enable `Read Color Buffer`");
needChanges = true;
}
/*
if (items["write_depth_buffers"] == DisabledMark)
{
notes.Add(" `Write Depth Buffers` might be required");
needChanges = true;
}
*/
if (items["read_depth_buffer"] == DisabledMark)
{
notes.Add(" `Read Depth Buffer` might be required");
@@ -1183,7 +1176,7 @@ namespace CompatBot.Utils.ResultFormatters
"NPUA70117", "NPHA80163", // lbp2 demo and beta
};
private static void CheckLbpSettings(string serial, NameValueCollection items, List<string> notes, List<string> generalNotes)
private static void CheckLbpSettings(string serial, NameValueCollection items, List<string> generalNotes)
{
if (!AllLbpGames.Contains(serial))
return;

View File

@@ -231,10 +231,10 @@ namespace CompatBot.Utils.ResultFormatters
public static async Task<DiscordEmbedBuilder> AsEmbedAsync(this LogParseState state, DiscordClient client, DiscordMessage message, ISource source)
{
DiscordEmbedBuilder builder;
state.CompleteCollection ??= state.WipCollection;
state.CompletedCollection ??= state.WipCollection;
state.CompleteMultiValueCollection ??= state.WipMultiValueCollection;
var collection = state.CompleteCollection;
if (collection?.Count > 0)
var collection = state.CompletedCollection;
if (collection.Count > 0)
{
var ldrGameSerial = collection["ldr_game_serial"] ?? collection["ldr_path_serial"];
if (collection["serial"] is string serial
@@ -296,8 +296,8 @@ namespace CompatBot.Utils.ResultFormatters
private static void CleanupValues(LogParseState state)
{
var items = state.CompleteCollection;
var multiItems = state.CompleteMultiValueCollection;
var items = state.CompletedCollection!;
var multiItems = state.CompleteMultiValueCollection!;
if (items["strict_rendering_mode"] == "true")
items["resolution_scale"] = "Strict Mode";
if (items["spu_threads"] == "0")
@@ -639,7 +639,7 @@ namespace CompatBot.Utils.ResultFormatters
8 => "7",
9 => "8",
10 => "8.1",
int v when v >= 20 && v < 30 => ((v % 10) switch
>= 20 and < 30 => (driverVer.Major % 10) switch
{
// see https://en.wikipedia.org/wiki/Windows_Display_Driver_Model#WDDM_2.0
0 => "10",
@@ -651,7 +651,7 @@ namespace CompatBot.Utils.ResultFormatters
6 => "10 1903",
7 => "10 2004",
_ => null,
}),
},
_ => null,
};
}
@@ -676,34 +676,37 @@ namespace CompatBot.Utils.ResultFormatters
},
10 => windowsVersion.Build switch
{
int v when v < 10240 => ("10 TH1 Build " + v),
< 10240 => "10 TH1 Build " + windowsVersion.Build,
10240 => "10 1507",
int v when v < 10586 => ("10 TH2 Build " + v),
< 10586 => "10 TH2 Build " + windowsVersion.Build,
10586 => "10 1511",
int v when v < 14393 => ("10 RS1 Build " + v),
< 14393 => "10 RS1 Build " + windowsVersion.Build,
14393 => "10 1607",
int v when v < 15063 => ("10 RS2 Build " + v),
< 15063 => "10 RS2 Build " + windowsVersion.Build,
15063 => "10 1703",
int v when v < 16299 => ("10 RS3 Build " + v),
< 16299 => "10 RS3 Build " + windowsVersion.Build,
16299 => "10 1709",
int v when v < 17134 => ("10 RS4 Build " + v),
< 17134 => "10 RS4 Build " + windowsVersion.Build,
17134 => "10 1803",
int v when v < 17763 => ("10 RS5 Build " + v),
< 17763 => "10 RS5 Build " + windowsVersion.Build,
17763 => "10 1809",
int v when v < 18362 => ("10 19H1 Build " + v),
< 18362 => "10 19H1 Build " + windowsVersion.Build,
18362 => "10 1903",
18363 => "10 1909",
int v when v < 19041 => ("10 20H1 Build " + v),
< 19041 => "10 20H1 Build " + windowsVersion.Build,
19041 => "10 2004",
19042 => "10 20H2",
int v when v < 19536 => ("10 Beta Build " + v),
_ => ("10 21H1 Build " + windowsVersion.Build)
< 19536 => "10 Beta Build " + windowsVersion.Build,
_ => "10 21H1 Build " + windowsVersion.Build
},
_ => null,
};
private static string GetLinuxVersion(string release, string version)
private static string? GetLinuxVersion(string? release, string version)
{
if (string.IsNullOrEmpty(release))
return null;
var kernelVersion = release;
if (LinuxKernelVersion.Match(release) is Match m && m.Success)
kernelVersion = m.Groups["version"].Value;

View File

@@ -14,7 +14,7 @@ namespace CompatBot.Utils.ResultFormatters
internal static class TitlePatchFormatter
{
// thanks BCES00569
public static async Task<List<DiscordEmbedBuilder>> AsEmbedAsync(this TitlePatch patch, DiscordClient client, string productCode)
public static async Task<List<DiscordEmbedBuilder>> AsEmbedAsync(this TitlePatch? patch, DiscordClient client, string productCode)
{
var result = new List<DiscordEmbedBuilder>();
var pkgs = patch?.Tag?.Packages;

View File

@@ -54,7 +54,7 @@ namespace Tests
Config.Log.Debug(msg);
}
Config.Log.Debug("~~~~~~~~~~~~~~~~~~~~");
Assert.That(result.CompleteCollection, Is.Not.Null.And.Not.Empty);
Assert.That(result.CompletedCollection, Is.Not.Null.And.Not.Empty);
}
}
}

View File

@@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=ASMJIT/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=blit/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Confusables/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=framerate/@EntryIndexedValue">True</s:Boolean>
@@ -6,11 +7,15 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=liblv/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=lwmutex/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Metacritic/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=MLAA/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=mtrsx/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Multithreaded/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Nier/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=recompiler/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Ryzen/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=shaders/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=sprx/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Syscalls/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=vblank/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=vsync/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Vulkan/@EntryIndexedValue">True</s:Boolean>