diff --git a/CompatBot/Commands/Attributes/CheckBaseAttributeWithReactions.cs b/CompatBot/Commands/Attributes/CheckBaseAttributeWithReactions.cs index d46a6db8..322bea80 100644 --- a/CompatBot/Commands/Attributes/CheckBaseAttributeWithReactions.cs +++ b/CompatBot/Commands/Attributes/CheckBaseAttributeWithReactions.cs @@ -1,7 +1,5 @@ -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; using CompatBot.Utils; -using DSharpPlus; using DSharpPlus.CommandsNext; using DSharpPlus.CommandsNext.Attributes; using DSharpPlus.Entities; @@ -24,7 +22,7 @@ namespace CompatBot.Commands.Attributes public override async Task ExecuteCheckAsync(CommandContext ctx, bool help) { var result = await IsAllowed(ctx, help); - Config.Log.Debug($"Check for {GetType().Name} resulted in {result}"); + Config.Log.Debug($"Check for {GetType().Name} and user {ctx.User.Username}#{ctx.User.Discriminator} ({ctx.User.Id}) resulted in {result}"); if (result) { if (ReactOnSuccess != null && !help) diff --git a/CompatBot/Commands/EventsBaseCommand.cs b/CompatBot/Commands/EventsBaseCommand.cs index 289ab3ad..5a9a0067 100644 --- a/CompatBot/Commands/EventsBaseCommand.cs +++ b/CompatBot/Commands/EventsBaseCommand.cs @@ -304,15 +304,13 @@ namespace CompatBot.Commands } else if (txt != null) { - var newStartTime = FixTimeString(txt.Content); - if (!DateTime.TryParse(newStartTime, out var newTime)) + if (!TimeParser.TryParse(txt.Content, out var newTime)) { - errorMsg = $"Couldn't parse `{newStartTime}` as a start date and time"; + errorMsg = $"Couldn't parse `{txt.Content}` as a start date and time"; goto step1; } var duration = evt.End - evt.Start; - newTime = Normalize(newTime); evt.Start = newTime.Ticks; evt.End = evt.Start + duration; evt.Year = newTime.Year; @@ -487,25 +485,6 @@ namespace CompatBot.Commands return score > 0.5 ? name : eventName; } - private static string FixTimeString(string dateTime) - { - return dateTime.ToUpperInvariant() - .Replace("PST", "-08:00") - .Replace("EST", "-05:00") - .Replace("BST", "-03:00") - .Replace("JST", "+09:00") - .Replace("AEST", "+10:00"); - } - - private static DateTime Normalize(DateTime date) - { - if (date.Kind == DateTimeKind.Utc) - return date; - if (date.Kind == DateTimeKind.Local) - return date.ToUniversalTime(); - return date.AsUtc(); - } - private static async Task TryParseTimeSpanAsync(CommandContext ctx, string duration, bool react = true) { var d = Duration.Match(duration); diff --git a/CompatBot/Commands/Moderation.cs b/CompatBot/Commands/Moderation.cs index 58f2e7fe..26d90c0a 100644 --- a/CompatBot/Commands/Moderation.cs +++ b/CompatBot/Commands/Moderation.cs @@ -87,7 +87,7 @@ namespace CompatBot.Commands } await ctx.Client.ReportAsync("👀 Message report", msg, new[] {ctx.Message.Author}, comment, ReportSeverity.Medium).ConfigureAwait(false); - await msg.ReactWithAsync(ctx.Client, Config.Reactions.Moderated).ConfigureAwait(false); + await msg.ReactWithAsync(Config.Reactions.Moderated).ConfigureAwait(false); await ctx.ReactWithAsync(Config.Reactions.Success, "Message reported").ConfigureAwait(false); } } diff --git a/CompatBot/Commands/Pr.cs b/CompatBot/Commands/Pr.cs index d1c28e47..eca3559c 100644 --- a/CompatBot/Commands/Pr.cs +++ b/CompatBot/Commands/Pr.cs @@ -91,7 +91,7 @@ namespace CompatBot.Commands var prInfo = await githubClient.GetPrInfoAsync(pr, Config.Cts.Token).ConfigureAwait(false); if (prInfo.Number == 0) { - await message.ReactWithAsync(client, Config.Reactions.Failure, prInfo.Message ?? "PR not found").ConfigureAwait(false); + await message.ReactWithAsync(Config.Reactions.Failure, prInfo.Message ?? "PR not found").ConfigureAwait(false); return; } diff --git a/CompatBot/Commands/Psn.Check.cs b/CompatBot/Commands/Psn.Check.cs index aa6ff1b0..8f525e0f 100644 --- a/CompatBot/Commands/Psn.Check.cs +++ b/CompatBot/Commands/Psn.Check.cs @@ -85,7 +85,7 @@ namespace CompatBot.Commands embed.ThumbnailUrl = "https://cdn.discordapp.com/attachments/417347469521715210/516340151589535745/onionoff.png"; } var sqvat = ctx.Client.GetEmoji(":sqvat:", Config.Reactions.No); - await ctx.Message.ReactWithAsync(ctx.Client, sqvat).ConfigureAwait(false); + await ctx.Message.ReactWithAsync(sqvat).ConfigureAwait(false); } if (embeds.Count > 1 || embeds[0].Fields?.Count > 0) embeds[embeds.Count - 1] = embeds.Last().WithFooter("Note that you need to install all listed updates one by one"); diff --git a/CompatBot/Commands/Sudo.cs b/CompatBot/Commands/Sudo.cs index be065ccc..a7629b9a 100644 --- a/CompatBot/Commands/Sudo.cs +++ b/CompatBot/Commands/Sudo.cs @@ -88,7 +88,7 @@ namespace CompatBot.Commands else de = DiscordEmoji.FromUnicode(emoji + c); emoji = ""; - await message.ReactWithAsync(ctx.Client, de).ConfigureAwait(false); + await message.ReactWithAsync(de).ConfigureAwait(false); } } catch diff --git a/CompatBot/EventHandlers/AppveyorLinksHandler.cs b/CompatBot/EventHandlers/AppveyorLinksHandler.cs index e95a2c3c..b5c4efe3 100644 --- a/CompatBot/EventHandlers/AppveyorLinksHandler.cs +++ b/CompatBot/EventHandlers/AppveyorLinksHandler.cs @@ -28,7 +28,7 @@ namespace CompatBot.EventHandlers if (matches.Count == 0) return; - await args.Message.ReactWithAsync(args.Client, Config.Reactions.PleaseWait).ConfigureAwait(false); + await args.Message.ReactWithAsync(Config.Reactions.PleaseWait).ConfigureAwait(false); try { diff --git a/CompatBot/EventHandlers/BotReactionsHandler.cs b/CompatBot/EventHandlers/BotReactionsHandler.cs index 5dad109e..0366f2f4 100644 --- a/CompatBot/EventHandlers/BotReactionsHandler.cs +++ b/CompatBot/EventHandlers/BotReactionsHandler.cs @@ -131,7 +131,7 @@ namespace CompatBot.EventHandlers emoji = ThankYouReactions[rng.Next(ThankYouReactions.Length)]; thankYouMessage = LimitedToSpamChannel.IsSpamChannel(args.Channel) ? ThankYouMessages[rng.Next(ThankYouMessages.Length)] : null; } - await args.Message.ReactWithAsync(args.Client, emoji, thankYouMessage).ConfigureAwait(false); + await args.Message.ReactWithAsync(emoji, thankYouMessage).ConfigureAwait(false); } if (needToSilence) { @@ -142,7 +142,7 @@ namespace CompatBot.EventHandlers emoji = SadReactions[rng.Next(SadReactions.Length)]; sadMessage = SadMessages[rng.Next(SadMessages.Length)]; } - await args.Message.ReactWithAsync(args.Client, emoji, sadMessage).ConfigureAwait(false); + await args.Message.ReactWithAsync(emoji, sadMessage).ConfigureAwait(false); if (args.Author.IsSmartlisted(args.Client, args.Message.Channel.Guild)) { @@ -154,7 +154,7 @@ namespace CompatBot.EventHandlers await msg.DeleteAsync("asked to shut up").ConfigureAwait(false); } else - await args.Message.ReactWithAsync(args.Client, DiscordEmoji.FromUnicode("🙅"), @"No can do, boss ¯\\_(ツ)\_/¯").ConfigureAwait(false); + await args.Message.ReactWithAsync(DiscordEmoji.FromUnicode("🙅"), @"No can do, boss ¯\\_(ツ)\_/¯").ConfigureAwait(false); } } } diff --git a/CompatBot/EventHandlers/DiscordInviteFilter.cs b/CompatBot/EventHandlers/DiscordInviteFilter.cs index 2bf26e91..336f2482 100644 --- a/CompatBot/EventHandlers/DiscordInviteFilter.cs +++ b/CompatBot/EventHandlers/DiscordInviteFilter.cs @@ -111,9 +111,7 @@ namespace CompatBot.EventHandlers { Config.Log.Warn(e); await client.ReportAsync("🛃 An unapproved discord invite", message, "In invalid or expired invite", null, ReportSeverity.Medium).ConfigureAwait(false); - await message.ReactWithAsync( - client, - Config.Reactions.Moderated, + await message.ReactWithAsync(Config.Reactions.Moderated, $"{message.Author.Mention} please remove this expired or invalid invite, and refrain from posting it again until you have received an approval from a moderator.", true ).ConfigureAwait(false); diff --git a/CompatBot/EventHandlers/ProductCodeLookup.cs b/CompatBot/EventHandlers/ProductCodeLookup.cs index c4a4044d..c4c71de2 100644 --- a/CompatBot/EventHandlers/ProductCodeLookup.cs +++ b/CompatBot/EventHandlers/ProductCodeLookup.cs @@ -59,7 +59,7 @@ namespace CompatBot.EventHandlers public static async Task LookupAndPostProductCodeEmbedAsync(DiscordClient client, DiscordMessage message, List codesToLookup) { - await message.ReactWithAsync(client, Config.Reactions.PleaseWait).ConfigureAwait(false); + await message.ReactWithAsync(Config.Reactions.PleaseWait).ConfigureAwait(false); try { var results = new List<(string code, Task task)>(codesToLookup.Count); @@ -149,7 +149,7 @@ namespace CompatBot.EventHandlers titleInfoEmbed.Title = "How about no (๑•ิཬ•ั๑)"; if (!string.IsNullOrEmpty(titleInfoEmbed.ThumbnailUrl)) titleInfoEmbed.ThumbnailUrl = "https://cdn.discordapp.com/attachments/417347469521715210/516340151589535745/onionoff.png"; - await message.ReactWithAsync(client, sqvat).ConfigureAwait(false); + await message.ReactWithAsync(sqvat).ConfigureAwait(false); } } } diff --git a/CompatBot/EventHandlers/Starbucks.cs b/CompatBot/EventHandlers/Starbucks.cs index 3cb2626b..e64d40a2 100644 --- a/CompatBot/EventHandlers/Starbucks.cs +++ b/CompatBot/EventHandlers/Starbucks.cs @@ -173,7 +173,7 @@ namespace CompatBot.EventHandlers if (reporters.Count < Config.Moderation.StarbucksThreshold) return; - await message.ReactWithAsync(client, emoji).ConfigureAwait(false); + await message.ReactWithAsync(emoji).ConfigureAwait(false); await client.ReportAsync(Config.Reactions.Starbucks + " Media talk report", message, reporters, null, ReportSeverity.Medium).ConfigureAwait(false); } @@ -193,24 +193,26 @@ namespace CompatBot.EventHandlers } - private static async Task CheckGameFansAsync(DiscordClient client, DiscordChannel channel, DiscordMessage message) + private static Task CheckGameFansAsync(DiscordClient client, DiscordChannel channel, DiscordMessage message) { var bot = client.GetMember(channel.Guild, client.CurrentUser); + var ch = channel.IsPrivate ? channel.Users.FirstOrDefault(u => u.Id != client.CurrentUser.Id)?.Username + "'s DM" : "#" + channel.Name; if (!channel.PermissionsFor(bot).HasPermission(Permissions.AddReactions)) { - Config.Log.Debug($"No permissions to react in #{channel.Name}"); - return; + Config.Log.Debug($"No permissions to react in {ch}"); + return Task.CompletedTask; } var mood = client.GetEmoji(":sqvat:", "😒"); if (message.Reactions.Any(r => r.Emoji == mood && r.IsMe)) - return; + return Task.CompletedTask; var reactionMsg = string.Concat(message.Reactions.Select(r => TextMap.TryGetValue(r.Emoji, out var txt) ? txt : " ")).Trim(); if (string.IsNullOrEmpty(reactionMsg)) - return; + return Task.CompletedTask; - Config.Log.Debug("Emoji text: " + reactionMsg); + Config.Log.Debug($"Emoji text: {reactionMsg} (in {ch})"); + return Task.CompletedTask; } } } diff --git a/CompatBot/Utils/Extensions/DiscordClientExtensions.cs b/CompatBot/Utils/Extensions/DiscordClientExtensions.cs index 0eb422bf..6be2cc7f 100644 --- a/CompatBot/Utils/Extensions/DiscordClientExtensions.cs +++ b/CompatBot/Utils/Extensions/DiscordClientExtensions.cs @@ -66,7 +66,7 @@ namespace CompatBot.Utils } } - public static async Task ReactWithAsync(this DiscordMessage message, DiscordClient client, DiscordEmoji emoji, string fallbackMessage = null, bool? showBoth = null) + public static async Task ReactWithAsync(this DiscordMessage message, DiscordEmoji emoji, string fallbackMessage = null, bool? showBoth = null) { try { @@ -90,7 +90,7 @@ namespace CompatBot.Utils public static Task ReactWithAsync(this CommandContext ctx, DiscordEmoji emoji, string fallbackMessage = null, bool? showBoth = null) { - return ReactWithAsync(ctx.Message, ctx.Client, emoji, fallbackMessage, showBoth ?? (ctx.Prefix == Config.AutoRemoveCommandPrefix)); + return ReactWithAsync(ctx.Message, emoji, fallbackMessage, showBoth ?? (ctx.Prefix == Config.AutoRemoveCommandPrefix)); } public static async Task> GetMessagesBeforeAsync(this DiscordChannel channel, ulong beforeMessageId, int limit = 100, DateTime? timeLimit = null) diff --git a/CompatBot/Utils/Extensions/InteractivityExtensions.cs b/CompatBot/Utils/Extensions/InteractivityExtensions.cs index 92387cf5..2cbaf775 100644 --- a/CompatBot/Utils/Extensions/InteractivityExtensions.cs +++ b/CompatBot/Utils/Extensions/InteractivityExtensions.cs @@ -33,7 +33,7 @@ namespace CompatBot.Utils { reactions = reactions.Where(r => r != null).ToArray(); foreach (var emoji in reactions) - await message.ReactWithAsync(interactivity.Client, emoji).ConfigureAwait(false); + await message.ReactWithAsync(emoji).ConfigureAwait(false); var expectedChannel = message.Channel; var waitTextResponseTask = interactivity.WaitForMessageAsync(m => m.Author == user && m.Channel == expectedChannel && !string.IsNullOrEmpty(m.Content), timeout); var waitReactionResponse = interactivity.WaitForReactionAsync(arg => reactions.Contains(arg.Emoji), message, user, timeout); diff --git a/CompatBot/Utils/ResultFormatters/LogParserResultFormatter.WeirdSettingsSection.cs b/CompatBot/Utils/ResultFormatters/LogParserResultFormatter.WeirdSettingsSection.cs index c2d010e2..1828bbe7 100644 --- a/CompatBot/Utils/ResultFormatters/LogParserResultFormatter.WeirdSettingsSection.cs +++ b/CompatBot/Utils/ResultFormatters/LogParserResultFormatter.WeirdSettingsSection.cs @@ -388,7 +388,7 @@ namespace CompatBot.Utils.ResultFormatters } } - private static HashSet DesIds = new HashSet + private static readonly HashSet DesIds = new HashSet { "BLES00932", "BLUS30443", "BCJS30022", "BCJS70013", "NPEB01202", "NPUB30910", "NPJA00102", diff --git a/CompatBot/Utils/TimeParser.cs b/CompatBot/Utils/TimeParser.cs new file mode 100644 index 00000000..b3fb43f8 --- /dev/null +++ b/CompatBot/Utils/TimeParser.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Remotion.Linq.Clauses; + +namespace CompatBot.Utils +{ + public static class TimeParser + { + private static readonly Dictionary TimeZoneMap; + + static TimeParser() + { + var tzAcronyms = new Dictionary + { + ["PT"] = new[] { "Pacific Standard Time" }, + ["PST"] = new[] { "Pacific Standard Time" }, + ["PDT"] = new[] { "Pacific Standard Time" }, + ["EST"] = new[] { "Eastern Standard Time" }, + ["EDT"] = new[] { "Eastern Standard Time" }, + ["CEST"] = new[] { "Central European Standard Time" }, + ["BST"] = new[] { "British Summer Time", "GMT Standard Time" }, + ["JST"] = new[] { "Japan Standard Time", "Tokyo Standard Time" }, + }; + var uniqueNames = new HashSet( + from tznl in tzAcronyms.Values + from tzn in tznl + select tzn + ); + var tzList = TimeZoneInfo.GetSystemTimeZones(); + var result = new Dictionary(); + foreach (var tzi in tzList) + { + if (uniqueNames.Contains(tzi.StandardName) || uniqueNames.Contains(tzi.StandardName)) + { + var acronyms = from tza in tzAcronyms + where tza.Value.Contains(tzi.StandardName) || tza.Value.Contains(tzi.DaylightName) + select tza.Key; + foreach (var tza in acronyms) + result[tza] = tzi; + } + } + TimeZoneMap = result; + } + + public static bool TryParse(string dateTime, out DateTime result) + { + result = default; + if (string.IsNullOrEmpty(dateTime)) + return false; + + dateTime = dateTime.ToUpperInvariant(); + if (char.IsDigit(dateTime[dateTime.Length - 1])) + { + return DateTime.TryParse(dateTime, out result); + } + + var cutIdx = dateTime.LastIndexOf(' '); + if (cutIdx < 0) + return false; + + var tza = dateTime.Substring(cutIdx + 1); + dateTime = dateTime.Substring(0, cutIdx); + if (TimeZoneMap.TryGetValue(tza, out var tzi)) + { + if (!DateTime.TryParse(dateTime, out result)) + return false; + + result = TimeZoneInfo.ConvertTimeToUtc(result, tzi); + return true; + } + + return false; + } + + + public static DateTime Normalize(this DateTime date) + { + if (date.Kind == DateTimeKind.Utc) + return date; + if (date.Kind == DateTimeKind.Local) + return date.ToUniversalTime(); + return date.AsUtc(); + } + } +} diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 88168a02..e704708b 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -9,7 +9,7 @@ - + diff --git a/Tests/TimeParserTests.cs b/Tests/TimeParserTests.cs new file mode 100644 index 00000000..bee148e2 --- /dev/null +++ b/Tests/TimeParserTests.cs @@ -0,0 +1,20 @@ +using System; +using CompatBot.Utils; +using NUnit.Framework; + +namespace Tests +{ + [TestFixture] + public class TimeParserTests + { + [TestCase("2019-8-19 6:00 PT", "2019-08-19 13:00")] + [TestCase("2019-8-19 17:00 bst", "2019-08-19 16:00")] + public void TimeZoneConverterTest(string input, string utcInput) + { + var utc = DateTime.Parse(utcInput).Normalize(); + Assert.That(TimeParser.TryParse(input, out var result), Is.True); + Assert.That(result, Is.EqualTo(utc)); + Assert.That(result.Kind, Is.EqualTo(DateTimeKind.Utc)); + } + } +}