From 51464c87c2a8078e7238cce0c189be09d9418c15 Mon Sep 17 00:00:00 2001 From: CriticalFlaw Date: Tue, 28 Jan 2020 08:03:30 -0500 Subject: [PATCH 1/2] Backend Improvements (2.4) - Certain commands will now remove user messages that called the command. - Code cleanup and improvements. - Fixed an issue where last paginated item would be removed if it was the last one on the list when the user calls for the next list item. - Fixed the tf2 item schema command not returning higher quality images when available. - Fixed twitch command not returning results if the follower count is outside the integer range. - Fixed various text grammar and wording inconsistencies. - Removed the Smash Bros. command. - Reorganized the command modules. - The poll command is now preset to run for three minutes. - Updated NuGet packages. - Updated Twitch and Steam command API services. - Updated outputs for pokemon, speedrun, steam user, tf2 schema, tf2 server, poll --- src/FlawBOT.Core/Common/HelpFormatter.cs | 7 +- src/FlawBOT.Core/Dockerfile | 20 - src/FlawBOT.Core/FlawBOT.Core.csproj | 9 +- src/FlawBOT.Core/Modules/Bot/BotModule.cs | 134 ----- src/FlawBOT.Core/Modules/Bot/OwnerModule.cs | 143 ----- .../Modules/Games/PokemonModule.cs | 40 +- src/FlawBOT.Core/Modules/Games/SmashModule.cs | 53 -- .../Modules/Games/SpeedrunModule.cs | 28 +- src/FlawBOT.Core/Modules/Games/SteamModule.cs | 74 +-- .../Modules/Games/TeamFortressModule.cs | 74 ++- src/FlawBOT.Core/Modules/Misc/MathModule.cs | 10 +- src/FlawBOT.Core/Modules/Misc/MiscModule.cs | 36 +- src/FlawBOT.Core/Modules/Misc/PollModule.cs | 19 +- .../Modules/Search/AmiiboModule.cs | 16 +- .../Modules/Search/DictionaryModule.cs | 17 +- .../Modules/Search/GoodReadsModule.cs | 21 +- .../Modules/Search/GoogleModule.cs | 26 +- .../Modules/Search/ImgurModule.cs | 7 +- src/FlawBOT.Core/Modules/Search/NASAModule.cs | 9 +- src/FlawBOT.Core/Modules/Search/OMDBModule.cs | 4 +- .../Modules/Search/RedditModule.cs | 2 +- .../Modules/Search/SimpsonsModule.cs | 36 +- .../Modules/Search/TwitchModule.cs | 23 +- .../Modules/Search/WikipediaModule.cs | 2 +- .../Modules/Search/YoutubeModule.cs | 16 +- src/FlawBOT.Core/Modules/Server/BotModule.cs | 267 +++++++++ .../Modules/Server/ChannelModule.cs | 70 ++- .../Modules/Server/EmojiModule.cs | 40 +- src/FlawBOT.Core/Modules/Server/RoleModule.cs | 62 +-- .../Modules/Server/ServerModule.cs | 42 +- src/FlawBOT.Core/Modules/Server/UserModule.cs | 124 +++-- src/FlawBOT.Core/Program.cs | 42 +- .../Properties/Resources.Designer.cs | 41 +- src/FlawBOT.Core/Properties/Resources.resx | 23 +- .../Properties/launchSettings.json | 10 - .../FlawBOT.Framework.csproj | 18 +- .../Models/Games/SmashData.cs | 53 -- .../Models/Search/TwitchData.cs | 144 ++--- .../Models/{Bot => Server}/BotData.cs | 11 + .../Models/{Bot => Server}/BotHandlers.cs | 0 .../Properties/Resources.Designer.cs | 2 +- .../Properties/Resources.resx | 2 +- .../Services/Games/PokemonService.cs | 6 +- .../Services/Games/SmashService.cs | 39 -- .../Services/Games/SpeedrunService.cs | 9 +- .../Services/Games/SteamService.cs | 21 +- .../Services/Games/TeamFortressService.cs | 8 +- .../Services/Misc/MiscService.cs | 4 +- .../Services/Search/AmiiboService.cs | 2 +- .../Services/Search/DictionaryService.cs | 2 +- .../Services/Search/GoodReadsService.cs | 20 + .../Services/Search/GoogleService.cs | 8 +- .../Services/Search/ImgurService.cs | 2 +- .../Services/Search/NASAService.cs | 2 +- .../Services/Search/OMDBService.cs | 2 +- .../Services/Search/SimpsonsService.cs | 8 +- .../Services/Search/TwitchService.cs | 15 +- .../Services/Search/WikipediaService.cs | 2 +- .../Services/{Bot => Server}/BotService.cs | 14 +- src/FlawBOT.Test/FlawBOT.Test.csproj | 7 +- src/FlawBOT.Test/Games/SmashTests.cs | 513 ------------------ src/FlawBOT.Test/Search/ImgurTests.cs | 4 +- 62 files changed, 870 insertions(+), 1595 deletions(-) delete mode 100644 src/FlawBOT.Core/Dockerfile delete mode 100644 src/FlawBOT.Core/Modules/Bot/BotModule.cs delete mode 100644 src/FlawBOT.Core/Modules/Bot/OwnerModule.cs delete mode 100644 src/FlawBOT.Core/Modules/Games/SmashModule.cs create mode 100644 src/FlawBOT.Core/Modules/Server/BotModule.cs delete mode 100644 src/FlawBOT.Core/Properties/launchSettings.json delete mode 100644 src/FlawBOT.Framework/Models/Games/SmashData.cs rename src/FlawBOT.Framework/Models/{Bot => Server}/BotData.cs (89%) rename src/FlawBOT.Framework/Models/{Bot => Server}/BotHandlers.cs (100%) delete mode 100644 src/FlawBOT.Framework/Services/Games/SmashService.cs rename src/FlawBOT.Framework/Services/{Bot => Server}/BotService.cs (81%) delete mode 100644 src/FlawBOT.Test/Games/SmashTests.cs diff --git a/src/FlawBOT.Core/Common/HelpFormatter.cs b/src/FlawBOT.Core/Common/HelpFormatter.cs index 81718cdb..754507ca 100644 --- a/src/FlawBOT.Core/Common/HelpFormatter.cs +++ b/src/FlawBOT.Core/Common/HelpFormatter.cs @@ -28,7 +28,7 @@ public override CommandHelpMessage Build() if (!string.IsNullOrWhiteSpace(name)) { output.WithTitle(name); - desc = string.IsNullOrWhiteSpace(description) ? "No description provided." : description; + desc = description ?? "No description provided."; } output.WithDescription(desc); return new CommandHelpMessage(embed: output); @@ -48,7 +48,7 @@ public override BaseHelpFormatter WithCommand(Command cmd) { ab.Append(Formatter.InlineCode($"[{CommandsNext.GetUserFriendlyTypeName(arg.Type)}]")); ab.Append(" "); - ab.Append(string.IsNullOrWhiteSpace(arg.Description) ? "No description provided." : arg.Description); + ab.Append(arg.Description ?? "No description provided."); if (arg.IsOptional) { ab.Append(" (def: ").Append(Formatter.InlineCode(arg.DefaultValue is null ? "None" : arg.DefaultValue.ToString())).Append(")"); @@ -56,8 +56,7 @@ public override BaseHelpFormatter WithCommand(Command cmd) } ab.AppendLine(); } - string args = ab.ToString(); - output.AddField($"{(cmd.Overloads.Count > 1 ? $"Overload #{overload.Priority}" : "Arguments")}", string.IsNullOrWhiteSpace(args) ? "No arguments." : args); + output.AddField($"{(cmd.Overloads.Count > 1 ? $"Overload #{overload.Priority}" : "Arguments")}", ab.ToString() ?? "No arguments."); } } diff --git a/src/FlawBOT.Core/Dockerfile b/src/FlawBOT.Core/Dockerfile deleted file mode 100644 index 18cb0962..00000000 --- a/src/FlawBOT.Core/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -FROM mcr.microsoft.com/dotnet/core/runtime:2.2-stretch-slim AS base -WORKDIR /app - -FROM mcr.microsoft.com/dotnet/core/sdk:2.2-stretch AS build -WORKDIR /src -COPY ["FlawBOT.Core/FlawBOT.Core.csproj", "FlawBOT.Core/"] -COPY ["FlawBOT.Core/NuGet.Config", "FlawBOT.Core/"] -COPY ["FlawBOT.Framework/FlawBOT.Framework.csproj", "FlawBOT.Framework/"] -RUN dotnet restore "FlawBOT.Core/FlawBOT.Core.csproj" -COPY . . -WORKDIR "/src/FlawBOT.Core" -RUN dotnet build "FlawBOT.Core.csproj" -c Release -o /app - -FROM build AS publish -RUN dotnet publish "FlawBOT.Core.csproj" -c Release -o /app - -FROM base AS final -WORKDIR /app -COPY --from=publish /app . -ENTRYPOINT ["dotnet", "FlawBOT.Core.dll"] \ No newline at end of file diff --git a/src/FlawBOT.Core/FlawBOT.Core.csproj b/src/FlawBOT.Core/FlawBOT.Core.csproj index 42fefd51..83fd8e3f 100644 --- a/src/FlawBOT.Core/FlawBOT.Core.csproj +++ b/src/FlawBOT.Core/FlawBOT.Core.csproj @@ -11,12 +11,11 @@ 7.2 2.4.0.0 2.4.0.0 - Linux FlawBOT.Core FlawBOT.Core Multipurpose Discord bot written in C# using DSharpPlus. Application Core. - https://discordbots.org/bot/339833029013012483 + https://top.gg/bot/339833029013012483 https://github.com/CriticalFlaw/FlawBOT CriticalFlaw @@ -41,9 +40,9 @@ - - - + + + diff --git a/src/FlawBOT.Core/Modules/Bot/BotModule.cs b/src/FlawBOT.Core/Modules/Bot/BotModule.cs deleted file mode 100644 index 6291ba4a..00000000 --- a/src/FlawBOT.Core/Modules/Bot/BotModule.cs +++ /dev/null @@ -1,134 +0,0 @@ -using DSharpPlus; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; -using DSharpPlus.Entities; -using DSharpPlus.Interactivity; -using FlawBOT.Common; -using FlawBOT.Core.Properties; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Services; -using System; -using System.Threading.Tasks; - -namespace FlawBOT.Modules -{ - [Group("bot")] - [Description("Basic commands for interacting with FlawBOT")] - [Cooldown(3, 5, CooldownBucketType.Channel)] - public class BotModule : BaseCommandModule - { - #region COMMAND_INFO - - [Command("info")] - [Aliases("i")] - [Description("Retrieve FlawBOT information")] - public async Task BotInfo(CommandContext ctx) - { - var uptime = DateTime.Now - SharedData.ProcessStarted; - var output = new DiscordEmbedBuilder() - .WithTitle(SharedData.Name) - .WithDescription("A multipurpose Discord bot written in C# with [DSharpPlus](https://github.com/DSharpPlus/DSharpPlus/).") - .AddField(":clock1: Uptime", $"{(int)uptime.TotalDays:00} days {uptime.Hours:00}:{uptime.Minutes:00}:{uptime.Seconds:00}", true) - .AddField(":link: Links", $"[Commands]({SharedData.GitHubLink}wiki) **|** [Invite]({SharedData.InviteLink}) **|** [GitHub]({SharedData.GitHubLink})", true) - .WithFooter("Thank you for using " + SharedData.Name + $" (v{SharedData.Version})") - .WithUrl(SharedData.GitHubLink) - .WithColor(SharedData.DefaultColor); - await ctx.RespondAsync(embed: output.Build()); - } - - #endregion COMMAND_INFO - - #region COMMAND_LEAVE - - [Command("leave")] - [Description("Make FlawBOT leave the current server")] - [RequireUserPermissions(Permissions.Administrator)] - public async Task LeaveAsync(CommandContext ctx) - { - await BotServices.SendEmbedAsync(ctx, $"Are you sure you want {SharedData.Name} to leave this server?\nRespond with **yes** to proceed or wait 10 seconds to cancel this operation."); - var interactivity = await ctx.Client.GetInteractivity().WaitForMessageAsync(m => m.Channel.Id == ctx.Channel.Id && m.Author.Id == ctx.User.Id && m.Content.ToLowerInvariant() == "yes", TimeSpan.FromSeconds(10)); - if (interactivity.Result is null) - await BotServices.SendEmbedAsync(ctx, Resources.REQUEST_TIMEOUT); - else - { - await BotServices.SendEmbedAsync(ctx, "Thank you for using " + SharedData.Name); - await ctx.Guild.LeaveAsync(); - } - } - - #endregion COMMAND_LEAVE - - #region COMMAND_PING - - [Command("ping")] - [Aliases("pong")] - [Description("Ping the FlawBOT client")] - public async Task Ping(CommandContext ctx) - { - await ctx.RespondAsync($":ping_pong: Pong! Ping: **{ctx.Client.Ping}**ms"); - } - - #endregion COMMAND_PING - - #region COMMAND_REPORT - - [Command("report"), Hidden] - [Aliases("issue")] - [Description("Report a problem with FlawBOT to the developer. Please do not abuse.")] - public async Task ReportIssue(CommandContext ctx, - [Description("Detailed description of the issue")] [RemainingText] string report) - { - if (string.IsNullOrWhiteSpace(report) || report.Length < 50) - await ctx.RespondAsync(Resources.ERR_REPORT_CHAR_LENGTH); - else - { - await BotServices.SendEmbedAsync(ctx, "The following information will be sent to the developer for investigation: User ID, Server ID, Server Name and Server Owner Name.\nRespond with **yes** in the next 10 seconds to proceed, otherwise the operation will be canceled."); - var interactivity = await ctx.Client.GetInteractivity().WaitForMessageAsync(m => m.Channel.Id == ctx.Channel.Id && m.Author.Id == ctx.User.Id && m.Content.ToLowerInvariant() == "yes", TimeSpan.FromSeconds(10)); - if (interactivity.Result is null) - await BotServices.SendEmbedAsync(ctx, Resources.REQUEST_TIMEOUT); - else - { - var dm = await ctx.Member.CreateDmChannelAsync(); - var output = new DiscordEmbedBuilder() - .WithAuthor(ctx.Guild.Owner.Username + "#" + ctx.Guild.Owner.Discriminator, iconUrl: ctx.User.AvatarUrl ?? ctx.User.DefaultAvatarUrl) - .AddField("Issue", report) - .AddField("Sent By", ctx.User.Username + "#" + ctx.User.Discriminator) - .AddField("Server", ctx.Guild.Name + $" (ID: {ctx.Guild.Id})") - .AddField("Owner", ctx.Guild.Owner.Username + "#" + ctx.Guild.Owner.Discriminator) - .AddField("Confirm", $"[Click here to add this issue to GitHub]({SharedData.GitHubLink}/issues/new)") - .WithColor(SharedData.DefaultColor); - await dm.SendMessageAsync(embed: output.Build()); - await BotServices.SendEmbedAsync(ctx, "Thank You! Your report has been submitted.", EmbedType.Good); - } - } - } - - #endregion COMMAND_REPORT - - #region COMMAND_SAY - - [Command("say"), Hidden] - [Aliases("echo")] - [Description("Make FlawBOT repeat a message")] - public Task Say(CommandContext ctx, - [Description("Message for the bot to repeat")] [RemainingText] string message) - { - return ctx.RespondAsync((string.IsNullOrWhiteSpace(message)) ? ":thinking:" : message); - } - - #endregion COMMAND_SAY - - #region COMMAND_UPTIME - - [Command("uptime")] - [Description("Retrieve the FlawBOT uptime")] - public async Task Uptime(CommandContext ctx) - { - var uptime = DateTime.Now - SharedData.ProcessStarted; - var days = (uptime.Days > 0) ? $"({uptime.Days:00} days)" : null; - await BotServices.SendEmbedAsync(ctx, ":clock1: " + SharedData.Name + $" has been online for {uptime.Hours:00}:{uptime.Minutes:00} {days}"); - } - - #endregion COMMAND_UPTIME - } -} \ No newline at end of file diff --git a/src/FlawBOT.Core/Modules/Bot/OwnerModule.cs b/src/FlawBOT.Core/Modules/Bot/OwnerModule.cs deleted file mode 100644 index ecd1a110..00000000 --- a/src/FlawBOT.Core/Modules/Bot/OwnerModule.cs +++ /dev/null @@ -1,143 +0,0 @@ -using DSharpPlus; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; -using DSharpPlus.Entities; -using FlawBOT.Common; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Services; -using System.Threading.Tasks; - -namespace FlawBOT.Modules -{ - [Group("sudo"), Hidden] - [Description("Owner commands for controlling FlawBOT")] - [Cooldown(3, 5, CooldownBucketType.Global)] - public class OwnerModule : BaseCommandModule - { - #region COMMAND_ACTIVITY - - [RequireOwner] - [Command("activity"), Hidden] - [Aliases("setactivity")] - [Description("Set FlawBOT's activity")] - public async Task SetBotActivity(CommandContext ctx, - [Description("Name of the activity")] [RemainingText] string activity) - { - if (string.IsNullOrWhiteSpace(activity)) - { - await ctx.Client.UpdateStatusAsync(activity: null); - await BotServices.SendEmbedAsync(ctx, SharedData.Name + " activity has been changed to " + Formatter.Bold("Normal")); - } - else - { - // TODO: Set the activity type - var game = new DiscordActivity(activity); - await ctx.Client.UpdateStatusAsync(activity: game); - await BotServices.SendEmbedAsync(ctx, SharedData.Name + " activity has been changed to " + Formatter.Bold("Playing " + game.Name), EmbedType.Good); - } - } - - #endregion COMMAND_ACTIVITY - - #region COMMAND_AVATAR - - [RequireOwner] - [Command("avatar"), Hidden] - [Aliases("setavatar")] - [Description("Set FlawBOT's avatar")] - public async Task SetBotAvatar(CommandContext ctx, - [Description("Image URL. Must be in jpg, png or img format.")] string query) - { - var stream = BotServices.CheckImageInput(ctx, query).Result; - if (stream.Length <= 0) return; - await ctx.Client.UpdateCurrentUserAsync(avatar: stream); - await BotServices.SendEmbedAsync(ctx, SharedData.Name + " avatar has been updated!", EmbedType.Good); - } - - #endregion COMMAND_AVATAR - - #region COMMAND_STATUS - - [RequireOwner] - [Command("status"), Hidden] - [Aliases("setstatus")] - [Description("Set FlawBOT's status")] - public async Task SetBotStatus(CommandContext ctx, - [Description("Activity Status. Online, Idle, DND or Offline")] [RemainingText] string status) - { - status = (string.IsNullOrWhiteSpace(status)) ? "ONLINE" : status; - switch (status.Trim().ToUpperInvariant()) - { - case "OFF": - case "OFFLINE": - await ctx.Client.UpdateStatusAsync(userStatus: UserStatus.Offline); - await BotServices.SendEmbedAsync(ctx, SharedData.Name + " status has been changed to **Offline**"); - break; - - case "INVISIBLE": - await ctx.Client.UpdateStatusAsync(userStatus: UserStatus.Invisible); - await BotServices.SendEmbedAsync(ctx, SharedData.Name + " status has been changed to **Invisible**"); - break; - - case "IDLE": - await ctx.Client.UpdateStatusAsync(userStatus: UserStatus.Idle); - await BotServices.SendEmbedAsync(ctx, SharedData.Name + " status has been changed to **Idle**", EmbedType.Warning); - break; - - case "DND": - case "DO NOT DISTURB": - await ctx.Client.UpdateStatusAsync(userStatus: UserStatus.DoNotDisturb); - await BotServices.SendEmbedAsync(ctx, SharedData.Name + " status has been changed to **Do Not Disturb**", EmbedType.Error); - break; - - default: - await ctx.Client.UpdateStatusAsync(userStatus: UserStatus.Online); - await BotServices.SendEmbedAsync(ctx, SharedData.Name + " status has been changed to **Online**", EmbedType.Good); - break; - } - } - - #endregion COMMAND_STATUS - - #region COMMAND_UPDATE - - [RequireOwner] - [Command("update"), Hidden] - [Aliases("refresh")] - [Description("Update FlawBOT libraries")] - public async Task Update(CommandContext ctx) - { - var message = await ctx.RespondAsync("Starting update..."); - await SteamService.UpdateSteamListAsync().ConfigureAwait(false); - await TeamFortressService.LoadTF2SchemaAsync().ConfigureAwait(false); - await PokemonService.UpdatePokemonListAsync().ConfigureAwait(false); - await message.ModifyAsync("Starting update...done!"); - } - - #endregion COMMAND_UPDATE - - #region COMMAND_USERNAME - - [RequireOwner] - [Command("username"), Hidden] - [Aliases("setusername", "name", "setname", "nickname")] - [Description("Set FlawBOT's username")] - public async Task SetBotUsername(CommandContext ctx, - [Description("New bot username")] [RemainingText] string name) - { - var oldUsername = ctx.Client.CurrentUser.Username; - if (string.IsNullOrWhiteSpace(name)) - { - await ctx.Client.UpdateCurrentUserAsync(username: SharedData.Name); - await BotServices.SendEmbedAsync(ctx, oldUsername + " username has been changed to " + SharedData.Name); - } - else - { - await ctx.Client.UpdateCurrentUserAsync(username: name); - await BotServices.SendEmbedAsync(ctx, oldUsername + " username has been changed to " + ctx.Client.CurrentUser.Username, EmbedType.Good); - } - } - - #endregion COMMAND_USERNAME - } -} \ No newline at end of file diff --git a/src/FlawBOT.Core/Modules/Games/PokemonModule.cs b/src/FlawBOT.Core/Modules/Games/PokemonModule.cs index 090e8e95..7d8a06dd 100644 --- a/src/FlawBOT.Core/Modules/Games/PokemonModule.cs +++ b/src/FlawBOT.Core/Modules/Games/PokemonModule.cs @@ -6,6 +6,7 @@ using FlawBOT.Framework.Models; using FlawBOT.Framework.Services; using System; +using System.Linq; using System.Text; using System.Threading.Tasks; @@ -17,39 +18,42 @@ public class PokemonModule : BaseCommandModule #region COMMAND_POKEMON [Command("pokemon")] - [Aliases("poke")] + [Aliases("poke", "pk")] [Description("Retrieve a Pokemon card")] public async Task Pokemon(CommandContext ctx, [Description("Name of the pokemon")] [RemainingText] string query) { - var results = await PokemonService.GetPokemonCardsAsync(query); + var results = await PokemonService.GetPokemonCardsAsync(query).ConfigureAwait(false); if (results.Cards.Count == 0) - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing); + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing).ConfigureAwait(false); else { - foreach (var value in results.Cards) + foreach (var dex in results.Cards) { - var card = PokemonService.GetExactPokemonAsync(value.ID); + var card = PokemonService.GetExactPokemonAsync(dex.ID); var output = new DiscordEmbedBuilder() - .WithTitle(card.Name + $" (PokeDex ID: {card.NationalPokedexNumber})") - .AddField("Subtype", card.SubType ?? "Unknown", true) - .AddField("Health Points", card.Hp ?? "Unknown", true) - .AddField("Artist", card.Artist ?? "Unknown", true) - .AddField("Rarity", card.Rarity ?? "Unknown", true) + .WithTitle(card.Name) + .WithDescription("Pokédex ID: " + card.NationalPokedexNumber.ToString() ?? "Unknown") .AddField("Series", card.Series ?? "Unknown", true) + .AddField("Rarity", card.Rarity ?? "Unknown", true) + .AddField("HP", card.Hp ?? "Unknown", true) .WithImageUrl(card.ImageUrlHiRes ?? card.ImageUrl) - .WithColor(DiscordColor.Gold) - .WithFooter("Type 'next' within 10 seconds for the next card"); + .WithFooter(!card.Equals(results.Cards.Last()) ? "Type 'next' within 10 seconds for the next pokemon" : "This is the last found pokemon on the list.") + .WithColor(DiscordColor.Gold); - var types = new StringBuilder(); + var values = new StringBuilder(); foreach (var type in card.Types) - types.Append(type); - output.AddField("Type(s)", types.ToString() ?? "Unknown", true); - await ctx.RespondAsync(embed: output.Build()); + values.Append(type); + output.AddField("Types", values.ToString() ?? "Unknown", true); + foreach (var weakness in card.Weaknesses) + values.Append(weakness.Value); + output.AddField("Weaknesses", values.ToString() ?? "Unknown", true); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); - var interactivity = await ctx.Client.GetInteractivity().WaitForMessageAsync(m => m.Channel.Id == ctx.Channel.Id && m.Content.ToLowerInvariant() == "next", TimeSpan.FromSeconds(10)); + var interactivity = await ctx.Client.GetInteractivity().WaitForMessageAsync(m => m.Channel.Id == ctx.Channel.Id && string.Equals(m.Content, "next", StringComparison.InvariantCultureIgnoreCase), TimeSpan.FromSeconds(10)).ConfigureAwait(false); if (interactivity.Result is null) break; - await BotServices.RemoveMessage(interactivity.Result); + if (!card.Equals(results.Cards.Last())) + await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); } } } diff --git a/src/FlawBOT.Core/Modules/Games/SmashModule.cs b/src/FlawBOT.Core/Modules/Games/SmashModule.cs deleted file mode 100644 index 365caa00..00000000 --- a/src/FlawBOT.Core/Modules/Games/SmashModule.cs +++ /dev/null @@ -1,53 +0,0 @@ -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; -using DSharpPlus.Entities; -using FlawBOT.Core.Properties; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Services; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; - -namespace FlawBOT.Modules -{ - [Cooldown(3, 5, CooldownBucketType.Channel)] - public class SmashModule : BaseCommandModule - { - #region COMMAND_SMASH - - [Command("smash")] - [Aliases("smashbros", "sb", "sbu")] - [Description("Retrieve Smash Bros. Ultimate character information")] - public async Task GetCharacter(CommandContext ctx, - [Description("Name of the Smash character")] [RemainingText] string query) - { - if (!BotServices.CheckUserInput(query)) return; - var results = await SmashService.GetSmashCharacterAsync(query); - if (results is null) - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_SMASH, EmbedType.Missing); - else - { - var output = new DiscordEmbedBuilder() - .WithTitle(results.DisplayName) - .WithThumbnailUrl(results.ThumbnailUrl) - .WithColor(new DiscordColor(results.ColorTheme)) - .WithUrl(results.FullUrl); - - var attributes = await SmashService.GetCharacterAttributesAsync(results.OwnerId); - var attributesProcessed = new List(); - foreach (var attribute in attributes) - { - if (attributesProcessed.Contains(attribute.Name)) continue; - var values = new StringBuilder(); - foreach (var value in attribute.Attributes) - values.Append(value.Name + ": " + value.Value + "\n"); - output.AddField(attribute.Name, values.ToString() ?? "Unknown", true); - attributesProcessed.Add(attribute.Name); - } - await ctx.RespondAsync(embed: output.Build()); - } - } - - #endregion COMMAND_SMASH - } -} \ No newline at end of file diff --git a/src/FlawBOT.Core/Modules/Games/SpeedrunModule.cs b/src/FlawBOT.Core/Modules/Games/SpeedrunModule.cs index e14a4ff2..552dab76 100644 --- a/src/FlawBOT.Core/Modules/Games/SpeedrunModule.cs +++ b/src/FlawBOT.Core/Modules/Games/SpeedrunModule.cs @@ -15,29 +15,29 @@ public class SpeedrunModule : BaseCommandModule #region COMMAND_SPEEDRUN [Command("speedrun")] + [Aliases("game")] [Description("Search Speedrun.com for a given game")] public async Task Speedrun(CommandContext ctx, [Description("Game to search on Speedrun.com")] [RemainingText] string query) { if (!BotServices.CheckUserInput(query)) return; - var results = SpeedrunService.GetSpeedrunGameAsync(query).Result.Data.FirstOrDefault(); - if (results is null) - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing); + var game = SpeedrunService.GetSpeedrunGameAsync(query).Result.Data.FirstOrDefault(); + if (game is null) + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing).ConfigureAwait(false); else { var output = new DiscordEmbedBuilder() - .WithTitle(results.Names.International) - .AddField("Release Date", results.ReleaseDate.ToString() ?? "Unknown", true) - .AddField("Emulators Allowed?", (results.RuleSet.EmulatorsAllowed) ? "Yes" : "No", true) - .AddField("Developers", (results.Developers.Count > 0) ? SpeedrunService.GetSpeedrunExtraAsync(results.Developers.Take(3).ToList(), SpeedrunExtras.Developers).Result : "Unknown", true) - .AddField("Publishers", (results.Publishers.Count > 0) ? SpeedrunService.GetSpeedrunExtraAsync(results.Publishers.Take(3).ToList(), SpeedrunExtras.Publishers).Result : "Unknown", true) - .AddField("Genres", (results.Genres.Count > 0) ? SpeedrunService.GetSpeedrunExtraAsync(results.Genres.Take(3).ToList(), SpeedrunExtras.Genres).Result : "Unknown", true) - .AddField("Platforms", (results.Platforms.Count > 0) ? SpeedrunService.GetSpeedrunExtraAsync(results.Platforms.Take(3).ToList(), SpeedrunExtras.Platforms).Result : "Unknown", true) - .WithFooter($"ID: {results.ID} - Abbreviation: {results.Abbreviation}") - .WithImageUrl(results.Assets.CoverLarge.URL ?? results.Assets.Icon.URL) - .WithUrl(results.WebLink) + .WithTitle(game.Names.International) + .AddField("Release Date", game.ReleaseDate ?? "Unknown", true) + .AddField("Developers", (game.Developers.Count > 0) ? SpeedrunService.GetSpeedrunExtraAsync(game.Developers, SpeedrunExtras.Developers).Result : "Unknown", true) + .AddField("Publishers", (game.Publishers.Count > 0) ? SpeedrunService.GetSpeedrunExtraAsync(game.Publishers, SpeedrunExtras.Publishers).Result : "Unknown", true) + .AddField("Platforms", (game.Platforms.Count > 0) ? SpeedrunService.GetSpeedrunExtraAsync(game.Platforms, SpeedrunExtras.Platforms).Result : "Unknown", true) + .AddField("Genres", (game.Genres.Count > 0) ? SpeedrunService.GetSpeedrunExtraAsync(game.Genres, SpeedrunExtras.Genres).Result : "Unknown", true) + .WithFooter($"ID: {game.ID} - Abbreviation: {game.Abbreviation}") + .WithThumbnailUrl(game.Assets.CoverLarge.URL ?? game.Assets.Icon.URL) + .WithUrl(game.WebLink) .WithColor(new DiscordColor("#0F7A4D")); - await ctx.RespondAsync(embed: output.Build()); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } } diff --git a/src/FlawBOT.Core/Modules/Games/SteamModule.cs b/src/FlawBOT.Core/Modules/Games/SteamModule.cs index b121f716..dab20302 100644 --- a/src/FlawBOT.Core/Modules/Games/SteamModule.cs +++ b/src/FlawBOT.Core/Modules/Games/SteamModule.cs @@ -6,6 +6,8 @@ using FlawBOT.Framework.Services; using Steam.Models.SteamCommunity; using System.Globalization; +using System.Linq; +using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using UserStatus = Steam.Models.SteamCommunity.UserStatus; @@ -24,29 +26,33 @@ public class SteamModule : BaseCommandModule public async Task SteamGame(CommandContext ctx, [Description("Game to find on Steam")] [RemainingText] string query = "Team Fortress 2") { - var check = false; - while (check == false) - try - { - var app = SteamService.GetSteamAppAsync(query).Result; - var output = new DiscordEmbedBuilder() - .WithTitle(app.Name) - .WithDescription((Regex.Replace(app.DetailedDescription.Length <= 500 ? app.DetailedDescription : app.DetailedDescription.Substring(0, 500) + "...", "<[^>]*>", "")) ?? "Unknown") - .AddField("Developers", app.Developers[0] ?? "Unknown", true) - .AddField("Publisher", app.Publishers[0] ?? "Unknown", true) - .AddField("Release Date", app.ReleaseDate.Date ?? "Unknown", true) - .AddField("Metacritic", app.Metacritic.Score.ToString() ?? "Unknown", true) - .WithThumbnailUrl(app.HeaderImage) - .WithUrl("http://store.steampowered.com/app/" + app.SteamAppId.ToString()) - .WithFooter("App ID: " + app.SteamAppId.ToString()) - .WithColor(new DiscordColor("#1B2838")); - await ctx.RespondAsync(embed: output.Build()); - check = true; - } - catch - { - check = false; - } + try + { + var app = SteamService.GetSteamAppAsync(query).Result; + var output = new DiscordEmbedBuilder() + .WithTitle(app.Name) + .WithDescription((Regex.Replace(app.DetailedDescription.Length <= 500 ? app.DetailedDescription : app.DetailedDescription.Substring(0, 250) + "...", "<[^>]*>", "")) ?? "Unknown") + .AddField("Release Date", app.ReleaseDate.Date ?? "Unknown", true) + .AddField("Developers", app.Developers[0] ?? "Unknown", true) + .AddField("Publisher", app.Publishers[0] ?? "Unknown", true) + .AddField("Price", app.PriceOverview.FinalFormatted ?? "Unknown", true) + .AddField("Metacritic", app.Metacritic.Score.ToString() ?? "Unknown", true) + .WithThumbnailUrl(app.HeaderImage) + .WithUrl("http://store.steampowered.com/app/" + app.SteamAppId.ToString()) + .WithFooter("App ID: " + app.SteamAppId.ToString()) + .WithColor(new DiscordColor("#1B2838")); + + var genres = new StringBuilder(); + foreach (var genre in app.Genres.Take(3)) + genres.Append(genre).Append('\n'); + output.AddField("Types", genres.ToString() ?? "Unknown", true); + + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); + } + catch + { + await ctx.RespondAsync("Unable to retrieve game information from the Steam API.").ConfigureAwait(false); + } } #endregion COMMAND_GAME @@ -62,35 +68,33 @@ public async Task SteamUser(CommandContext ctx, var profile = SteamService.GetSteamUserProfileAsync(query).Result; var summary = SteamService.GetSteamUserSummaryAsync(query).Result; if (profile is null && summary is null) - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing); + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing).ConfigureAwait(false); else { var output = new DiscordEmbedBuilder().WithTitle(summary.Data.Nickname); if (summary.Data.ProfileVisibility != ProfileVisibility.Public) - await BotServices.SendEmbedAsync(ctx, "This profile is private...", EmbedType.Warning); + await BotServices.SendEmbedAsync(ctx, "This profile is private...", EmbedType.Warning).ConfigureAwait(false); else { output.WithThumbnailUrl(profile.AvatarFull.ToString()); - output.WithColor(new DiscordColor("#1B2838")); - output.WithUrl("http://steamcommunity.com/id/" + profile.SteamID); - output.WithFooter("Steam ID: " + profile.SteamID); output.AddField("Member since", summary.Data.AccountCreatedDate.ToUniversalTime().ToString(CultureInfo.CurrentCulture), true); - if (!string.IsNullOrWhiteSpace(profile.Summary)) - output.WithDescription(Regex.Replace(profile.Summary, "<[^>]*>", "")); + output.AddField("Location", profile.Location ?? "Unknown", true); + output.WithDescription(!string.IsNullOrWhiteSpace(profile.Summary) ? Regex.Replace(profile.Summary, "<[^>]*>", "") : string.Empty); if (summary.Data.UserStatus != UserStatus.Offline) output.AddField("Status", summary.Data.UserStatus.ToString(), true); else output.AddField("Last seen", summary.Data.LastLoggedOffDate.ToUniversalTime().ToString(CultureInfo.CurrentCulture), true); - output.AddField("VAC Banned?", profile.IsVacBanned ? "YES" : "NO", true); - output.AddField("Trade Bans?", profile.TradeBanState, true); if (profile.InGameInfo != null) { output.AddField("In-Game", $"[{profile.InGameInfo.GameName}]({profile.InGameInfo.GameLink})", true); output.AddField("Game Server IP", profile.InGameServerIP, true); output.WithImageUrl(profile.InGameInfo.GameLogoSmall); } + output.WithUrl("http://steamcommunity.com/id/" + profile.SteamID); + output.WithColor(new DiscordColor("#1B2838")); + output.WithFooter("Steam ID: " + profile.SteamID); } - await ctx.RespondAsync(embed: output.Build()); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } } @@ -105,9 +109,9 @@ public async Task SteamServerLink(CommandContext ctx, { var regex = new Regex(@"\s*(?'ip'\S+)\s*", RegexOptions.Compiled).Match(link); if (regex.Success) - await ctx.RespondAsync(string.Format($"steam://connect/{regex.Groups["ip"].Value}/{regex.Groups["pw"].Value}")); + await ctx.RespondAsync(string.Format($"steam://connect/{regex.Groups["ip"].Value}/{regex.Groups["pw"].Value}")).ConfigureAwait(false); else - await BotServices.SendEmbedAsync(ctx, Resources.ERR_STEAM_CONNECT_FORMAT, EmbedType.Warning); + await BotServices.SendEmbedAsync(ctx, Resources.ERR_STEAM_CONNECT_FORMAT, EmbedType.Warning).ConfigureAwait(false); } #endregion COMMAND_CONNECT diff --git a/src/FlawBOT.Core/Modules/Games/TeamFortressModule.cs b/src/FlawBOT.Core/Modules/Games/TeamFortressModule.cs index ededb2ac..680d8933 100644 --- a/src/FlawBOT.Core/Modules/Games/TeamFortressModule.cs +++ b/src/FlawBOT.Core/Modules/Games/TeamFortressModule.cs @@ -29,27 +29,28 @@ public async Task TF2Item(CommandContext ctx, { var item = TeamFortressService.GetSchemaItemAsync(query); if (item is null) - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing); + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing).ConfigureAwait(false); else { var textInfo = new CultureInfo("en-US", false).TextInfo; var output = new DiscordEmbedBuilder() .WithTitle(item.ItemName) - .WithDescription((!string.IsNullOrWhiteSpace(item.Description)) ? item.Description : "") - .AddField("Giftable", (item.Capabilities.Giftable) ? "Yes" : "No", true) - .AddField("Nameable", (item.Capabilities.Renamable) ? "Yes" : "No", true) - .AddField("Item Slot:", (!string.IsNullOrWhiteSpace(item.ItemSlot)) ? textInfo.ToTitleCase(item.ItemSlot) : "Unknown", true) - .WithImageUrl(item.ImageURL) + .WithDescription(item.Description ?? string.Empty) + .WithImageUrl(item.ImageURL_Large ?? item.ImageURL) .WithUrl("https://wiki.teamfortress.com/wiki/" + item.ItemName.Replace(' ', '_')) .WithFooter("ID: " + item.DefIndex) .WithColor(new DiscordColor("#E7B53B")); - var userClasses = new StringBuilder(); + var classes = new StringBuilder(); foreach (var className in item.UsedByClasses) - userClasses.Append(className + "\n"); - output.AddField("Worn by:", userClasses.ToString() ?? "Unknown", true); - - await ctx.RespondAsync(embed: output.Build()); + classes.Append(className + (!className.Equals(item.UsedByClasses.Last()) ? ", " : string.Empty)); + output.AddField("Used by:", classes.ToString() ?? "Unknown", true); + output.AddField("Item Slot:", textInfo.ToTitleCase(item.ItemSlot) ?? "Unknown", true); + output.AddField("Item Type:", item.ItemType ?? "Unknown", true); + output.AddField("Giftable:", (item.Capabilities.Giftable) ? "Yes" : "No", true); + output.AddField("Nameable:", (item.Capabilities.Renamable) ? "Yes" : "No", true); + output.AddField("Restriction:", item.HolidayRestriction ?? "None", true); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } } @@ -64,17 +65,17 @@ public async Task TF2Map(CommandContext ctx, [Description("Normalized map name, like pl_upward")] string query) { if (!BotServices.CheckUserInput(query)) return; - var results = await TeamFortressService.GetMapStatsAsync(query.ToLowerInvariant()); + var results = await TeamFortressService.GetMapStatsAsync(query.ToLowerInvariant()).ConfigureAwait(false); if (results.MapName is null) - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing); + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing).ConfigureAwait(false); else { double.TryParse(results.AvgPlayers, out var avg_players); var output = new DiscordEmbedBuilder() .WithTitle(results.MapName) - .AddField("Official", results.OfficialMap ? "YES" : "NO", true) + .AddField("Official", results.OfficialMap ? "Yes" : "No", true) .AddField("Game Mode", results.GameModes.First() ?? "Unknown", true) - .AddField("First Seen", results.FirstSeen.ToString() ?? "Unknown", true) + //.AddField("First Seen", results.ServerTypes.ToString() ?? "Unknown", true) .AddField("Avg. Players", Math.Round(avg_players, 2).ToString() ?? "Unknown", true) .AddField("Highest Player Count", results.HighestPlayerCount.ToString() ?? "Unknown", true) .AddField("Highest Server Count", results.HighestServerCount.ToString() ?? "Unknown", true) @@ -89,7 +90,7 @@ public async Task TF2Map(CommandContext ctx, if (related_maps.Length > 0) output.AddField("Related Map(s)", related_maps.ToString(), true); - await ctx.RespondAsync(embed: output.Build()); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } } @@ -101,28 +102,25 @@ public async Task TF2Map(CommandContext ctx, [Description("Retrieve the latest news article from teamwork.tf")] public async Task TF2News(CommandContext ctx) { - var results = await TeamFortressService.GetNewsOverviewAsync(); + var results = await TeamFortressService.GetNewsOverviewAsync().ConfigureAwait(false); if (results is null || results.Count == 0) - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing); + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing).ConfigureAwait(false); else { while (results.Count > 0) { var output = new DiscordEmbedBuilder() - .WithFooter("Type next in the next 10 seconds for more news articles.") + .WithFooter((results.Count > 5) ? "Type 'next' in the next 10 seconds for more news articles." : "This all the Team Fortress 2 community news at the moment.") .WithColor(new DiscordColor("#E7B53B")); foreach (var result in results.Take(5)) - { output.AddField(result.Title, result.Link); - results.Remove(result); - } - var message = await ctx.RespondAsync("Latest news articles from teamwork.tf", embed: output.Build()); + var message = await ctx.RespondAsync("Latest news articles from teamwork.tf", embed: output.Build()).ConfigureAwait(false); - var interactivity = await ctx.Client.GetInteractivity().WaitForMessageAsync(m => m.Channel.Id == ctx.Channel.Id && m.Content.ToLowerInvariant() == "next", TimeSpan.FromSeconds(10)); + var interactivity = await ctx.Client.GetInteractivity().WaitForMessageAsync(m => m.Channel.Id == ctx.Channel.Id && string.Equals(m.Content, "next", StringComparison.InvariantCultureIgnoreCase), TimeSpan.FromSeconds(10)).ConfigureAwait(false); if (interactivity.Result is null) break; - await BotServices.RemoveMessage(interactivity.Result); - await BotServices.RemoveMessage(message); + await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); + await BotServices.RemoveMessage(message).ConfigureAwait(false); } } } @@ -139,35 +137,35 @@ public async Task TF2Servers(CommandContext ctx, { if (!BotServices.CheckUserInput(query)) return; query = TeamFortressService.NormalizedGameMode(query); - var results = await TeamFortressService.GetServersAsync(query.Trim().Replace(' ', '-')); + var results = await TeamFortressService.GetServersAsync(query.Trim().Replace(' ', '-')).ConfigureAwait(false); if (results.Count <= 0) - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing); + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing).ConfigureAwait(false); else { var random = new Random(); - results = results.OrderBy(x => random.Next()).ToList(); + results = results.OrderBy(_ => random.Next()).ToList(); foreach (var server in results.Where(n => n.GameModes.Contains(query))) { var output = new DiscordEmbedBuilder() .WithTitle(server.Name) .WithDescription("steam://connect/" + server.IP + ":" + server.Port) .AddField("Provider", server.Provider ?? "Unknown", true) - .AddField("Max Players", (server.PlayerCount.ToString() ?? "Unknown") + "/" + (server.PlayerMax.ToString() ?? "Unknown"), true) + .AddField("Player Count", (server.PlayerCount.ToString() ?? "Unknown") + "/" + (server.PlayerMax.ToString() ?? "Unknown"), true) + .AddField("Password Lock", (server.HasPassword == true) ? "Yes" : "No", true) + .AddField("Random Crits", server.HasRandomCrits == true ? "Yes" : "No", true) + .AddField("Instant Respawn", server.HasNoSpawnTimer ? "Yes" : "No", true) + .AddField("All Talk", server.HasAllTalk ? "Yes" : "No", true) .AddField("Current Map", server.MapName ?? "Unknown", true) .AddField("Next Map", server.NextMap ?? "Unknown", true) - .AddField("Passworded?", (server.HasPassword == true) ? "Yes" : "No", true) - .AddField("Roll the Dice?", server.HasRTD ? "Yes" : "No", true) - .AddField("Random Crits?", server.HasRandomCrits == true ? "Yes" : "No", true) - .AddField("All Talk?", server.HasAllTalk ? "Yes" : "No", true) .WithImageUrl("https://teamwork.tf" + server.MapThumbnail) .WithFooter("Type 'next' within 10 seconds for the next server") .WithColor(new DiscordColor("#E7B53B")); - var message = await ctx.RespondAsync(embed: output.Build()); + var message = await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); - var interactivity = await ctx.Client.GetInteractivity().WaitForMessageAsync(m => m.Channel.Id == ctx.Channel.Id && m.Content.ToLowerInvariant() == "next", TimeSpan.FromSeconds(10)); + var interactivity = await ctx.Client.GetInteractivity().WaitForMessageAsync(m => m.Channel.Id == ctx.Channel.Id && string.Equals(m.Content, "next", StringComparison.InvariantCultureIgnoreCase), TimeSpan.FromSeconds(10)).ConfigureAwait(false); if (interactivity.Result is null) break; - await BotServices.RemoveMessage(interactivity.Result); - await BotServices.RemoveMessage(message); + await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); + await BotServices.RemoveMessage(message).ConfigureAwait(false); } } } diff --git a/src/FlawBOT.Core/Modules/Misc/MathModule.cs b/src/FlawBOT.Core/Modules/Misc/MathModule.cs index 1e0bb1cd..5bf09556 100644 --- a/src/FlawBOT.Core/Modules/Misc/MathModule.cs +++ b/src/FlawBOT.Core/Modules/Misc/MathModule.cs @@ -50,13 +50,13 @@ public async Task Math(CommandContext ctx, } var output = new DiscordEmbedBuilder() - .WithTitle($":1234: The result is {result:#,##0.00}") + .WithDescription($":1234: The result is {result:#,##0.00}") .WithColor(DiscordColor.CornflowerBlue); - await ctx.RespondAsync(embed: output.Build()); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } catch { - await BotServices.SendEmbedAsync(ctx, Resources.ERR_MATH_EQUATION, EmbedType.Warning); + await BotServices.SendEmbedAsync(ctx, Resources.ERR_MATH_EQUATION, EmbedType.Warning).ConfigureAwait(false); } } @@ -71,9 +71,9 @@ public async Task Sum(CommandContext ctx, [Description("Numbers to sum up")] params int[] args) { var output = new DiscordEmbedBuilder() - .WithTitle($":1234: The sum is {args.Sum():#,##0}") + .WithDescription($":1234: The sum is {args.Sum():#,##0}") .WithColor(DiscordColor.CornflowerBlue); - await ctx.RespondAsync(embed: output.Build()); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } #endregion COMMAND_SUM diff --git a/src/FlawBOT.Core/Modules/Misc/MiscModule.cs b/src/FlawBOT.Core/Modules/Misc/MiscModule.cs index 20f90b85..fcc94001 100644 --- a/src/FlawBOT.Core/Modules/Misc/MiscModule.cs +++ b/src/FlawBOT.Core/Modules/Misc/MiscModule.cs @@ -25,10 +25,8 @@ public class MiscModule : BaseCommandModule public Task EightBall(CommandContext ctx, [Description("Question to ask the 8-Ball")] [RemainingText] string question = "") { - if (string.IsNullOrWhiteSpace(question)) - return BotServices.SendEmbedAsync(ctx, Resources.ERR_8BALL_QUESTION, EmbedType.Warning); var output = new DiscordEmbedBuilder() - .WithDescription(":8ball: " + ctx.User.Mention + " " + EightBallService.GetAnswer()) + .WithDescription(":8ball: " + EightBallService.GetAnswer() + "(" + ctx.User.Mention + ")") .WithColor(DiscordColor.Black); return ctx.RespondAsync(embed: output.Build()); } @@ -44,9 +42,9 @@ public async Task CatFact(CommandContext ctx) { var results = CatService.GetCatFactAsync().Result; var output = new DiscordEmbedBuilder() - .WithTitle($":cat: {JObject.Parse(results)["fact"]}") + .WithDescription($":cat: {JObject.Parse(results)["fact"]}") .WithColor(DiscordColor.Orange); - await ctx.RespondAsync(embed: output.Build()); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } #endregion COMMAND_CATFACT @@ -60,12 +58,11 @@ public async Task CatPic(CommandContext ctx) { var results = CatService.GetCatPhotoAsync().Result; if (string.IsNullOrWhiteSpace(results)) - await BotServices.SendEmbedAsync(ctx, "Connection to random.cat failed!", EmbedType.Warning); + await BotServices.SendEmbedAsync(ctx, "Connection to random.cat failed!", EmbedType.Warning).ConfigureAwait(false); var output = new DiscordEmbedBuilder() - .WithTitle(":cat: Meow!") .WithImageUrl(results) .WithColor(DiscordColor.Orange); - await ctx.RespondAsync(embed: output.Build()); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } #endregion COMMAND_CATPIC @@ -79,7 +76,7 @@ public Task CoinFlip(CommandContext ctx) { var random = new Random(); var output = new DiscordEmbedBuilder() - .WithDescription(ctx.User.Mention + " flipped " + Formatter.Bold(Convert.ToBoolean(random.Next(0, 2)) ? "Heads" : "Tails")) + .WithDescription(ctx.User.Username + " flipped a coin and got " + Formatter.Bold(Convert.ToBoolean(random.Next(0, 2)) ? "Heads" : "Tails")) .WithColor(SharedData.DefaultColor); return ctx.RespondAsync(embed: output.Build()); } @@ -96,7 +93,7 @@ public async Task GetColor(CommandContext ctx, { var regex = new Regex(@"^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$", RegexOptions.Compiled).Match(color.ToString()); if (!regex.Success) - await BotServices.SendEmbedAsync(ctx, Resources.ERR_COLOR_INVALID, EmbedType.Warning); + await BotServices.SendEmbedAsync(ctx, Resources.ERR_COLOR_INVALID, EmbedType.Warning).ConfigureAwait(false); else { var output = new DiscordEmbedBuilder() @@ -104,7 +101,7 @@ public async Task GetColor(CommandContext ctx, .AddField("RGB:", $"{color.R} {color.G} {color.B}", true) .AddField("Decimal:", color.Value.ToString(), true) .WithColor(color); - await ctx.RespondAsync(embed: output.Build()); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } } @@ -113,13 +110,13 @@ public async Task GetColor(CommandContext ctx, #region COMMAND_DICEROLL [Command("diceroll")] - [Aliases("dice", "roll", "rolldice")] + [Aliases("dice", "roll", "rolldice", "die")] [Description("Roll a six-sided die")] public Task RollDice(CommandContext ctx) { var random = new Random(); var output = new DiscordEmbedBuilder() - .WithDescription(ctx.User.Mention + " rolled a " + Formatter.Bold(random.Next(1, 7).ToString())) + .WithDescription(ctx.User.Username + " rolled a die and got " + Formatter.Bold(random.Next(1, 7).ToString())) .WithColor(SharedData.DefaultColor); return ctx.RespondAsync(embed: output.Build()); } @@ -135,14 +132,13 @@ public async Task DogPic(CommandContext ctx) { var results = DogService.GetDogPhotoAsync().Result; if (results.Status != "success") - await BotServices.SendEmbedAsync(ctx, Resources.ERR_DOG_PHOTO, EmbedType.Warning); + await BotServices.SendEmbedAsync(ctx, Resources.ERR_DOG_PHOTO, EmbedType.Warning).ConfigureAwait(false); else { var output = new DiscordEmbedBuilder() - .WithTitle(":dog: Woof!") .WithImageUrl(results.Message) .WithColor(DiscordColor.Brown); - await ctx.RespondAsync(embed: output.Build()); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } } @@ -157,9 +153,9 @@ public async Task Greet(CommandContext ctx, [Description("User to say hello to")] [RemainingText] DiscordMember member) { if (member is null) - await ctx.RespondAsync($":wave: Hello, " + ctx.User.Mention); + await ctx.RespondAsync($":wave: Hello, " + ctx.User.Mention).ConfigureAwait(false); else - await ctx.RespondAsync($":wave: Welcome " + member.Mention + " to " + ctx.Guild.Name + ". Enjoy your stay!"); + await ctx.RespondAsync($":wave: Welcome " + member.Mention + " to " + ctx.Guild.Name + ". Enjoy your stay!").ConfigureAwait(false); } #endregion COMMAND_HELLO @@ -175,7 +171,7 @@ public async Task IPLocation(CommandContext ctx, if (string.IsNullOrWhiteSpace(address) || !IPAddress.TryParse(address, out IPAddress ip)) return; var results = GoogleService.GetIPLocationAsync(ip).Result; if (results.Status != "success") - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_LOCATION, EmbedType.Warning); + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_LOCATION, EmbedType.Warning).ConfigureAwait(false); else { var output = new DiscordEmbedBuilder() @@ -185,7 +181,7 @@ public async Task IPLocation(CommandContext ctx, .AddField("Latitude", results.Latitude.ToString(), true) .WithFooter($"IP: {results.Query}") .WithColor(new DiscordColor("#4d2f63")); - await ctx.RespondAsync(embed: output.Build()); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } } diff --git a/src/FlawBOT.Core/Modules/Misc/PollModule.cs b/src/FlawBOT.Core/Modules/Misc/PollModule.cs index 239be59a..2a576a99 100644 --- a/src/FlawBOT.Core/Modules/Misc/PollModule.cs +++ b/src/FlawBOT.Core/Modules/Misc/PollModule.cs @@ -20,27 +20,24 @@ public class PollModule : BaseCommandModule [Command("poll")] [Description("Run a Yay or Nay poll in the current channel")] public async Task Poll(CommandContext ctx, - [Description("Poll timer, in minutes")] string time, [Description("Question to be polled")] [RemainingText] string question) { - if (!int.TryParse(time, out var minutes)) - await BotServices.SendEmbedAsync(ctx, Resources.ERR_POLL_MINUTES, EmbedType.Warning); - else if (!BotServices.CheckUserInput(question)) - await BotServices.SendEmbedAsync(ctx, Resources.ERR_POLL_QUESTION, EmbedType.Warning); + if (!BotServices.CheckUserInput(question)) + await BotServices.SendEmbedAsync(ctx, Resources.ERR_POLL_QUESTION, EmbedType.Warning).ConfigureAwait(false); else { var interactivity = ctx.Client.GetInteractivity(); var pollOptions = new List(); pollOptions.Add(DiscordEmoji.FromName(ctx.Client, ":thumbsup:")); pollOptions.Add(DiscordEmoji.FromName(ctx.Client, ":thumbsdown:")); - var duration = new TimeSpan(0, 0, minutes, 0, 0); - var output = new DiscordEmbedBuilder().WithDescription(ctx.User.Mention + ": " + question); - var message = await ctx.RespondAsync(embed: output.Build()); + var duration = new TimeSpan(0, 0, 3, 0, 0); + var output = new DiscordEmbedBuilder().WithDescription(ctx.User.Mention + "asked: " + question + "\nThis poll ends in 3 minutes."); + var message = await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); foreach (var react in pollOptions) - await message.CreateReactionAsync(react); - var pollResult = await interactivity.CollectReactionsAsync(message, duration); + await message.CreateReactionAsync(react).ConfigureAwait(false); + var pollResult = await interactivity.CollectReactionsAsync(message, duration).ConfigureAwait(false); var results = pollResult.Where(x => pollOptions.Contains(x.Emoji)).Select(x => $"{x.Emoji} wins the poll with **{x.Total}** votes"); - await ctx.RespondAsync(string.Join("\n", results)); + await ctx.RespondAsync(string.Join("\n", results)).ConfigureAwait(false); } } diff --git a/src/FlawBOT.Core/Modules/Search/AmiiboModule.cs b/src/FlawBOT.Core/Modules/Search/AmiiboModule.cs index 3ff0905b..aa4cd7b2 100644 --- a/src/FlawBOT.Core/Modules/Search/AmiiboModule.cs +++ b/src/FlawBOT.Core/Modules/Search/AmiiboModule.cs @@ -6,6 +6,7 @@ using FlawBOT.Framework.Models; using FlawBOT.Framework.Services; using System; +using System.Linq; using System.Threading.Tasks; namespace FlawBOT.Modules @@ -22,9 +23,9 @@ public async Task GetAmiibo(CommandContext ctx, [Description("Name of the Amiibo figurine")] [RemainingText] string query) { if (!BotServices.CheckUserInput(query)) return; - var results = await AmiiboService.GetAmiiboFigurineAsync(query); + var results = await AmiiboService.GetAmiiboFigurineAsync(query).ConfigureAwait(false); if (results is null) - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing); + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing).ConfigureAwait(false); else { foreach (var amiibo in results.Amiibo) @@ -38,15 +39,16 @@ public async Task GetAmiibo(CommandContext ctx, .AddField(":flag_eu: Release:", amiibo.ReleaseDate.European, true) .AddField(":flag_au: Release:", amiibo.ReleaseDate.Australian, true) .WithImageUrl(amiibo.Image) - .WithFooter((results.Amiibo.Count > 1) ? "Type 'next' within 10 seconds for the next amiibo" : "This is the only amiibo of this name") + .WithFooter(!amiibo.Equals(results.Amiibo.Last()) ? "Type 'next' within 10 seconds for the next amiibo" : "This is the last found amiibo on the list.") .WithColor(new DiscordColor("#E70009")); - var message = await ctx.RespondAsync(embed: output.Build()); + var message = await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); if (results.Amiibo.Count == 1) continue; - var interactivity = await ctx.Client.GetInteractivity().WaitForMessageAsync(m => m.Channel.Id == ctx.Channel.Id && m.Content.ToLowerInvariant() == "next", TimeSpan.FromSeconds(10)); + var interactivity = await ctx.Client.GetInteractivity().WaitForMessageAsync(m => m.Channel.Id == ctx.Channel.Id && string.Equals(m.Content, "next", StringComparison.InvariantCultureIgnoreCase), TimeSpan.FromSeconds(10)).ConfigureAwait(false); if (interactivity.Result is null) break; - await BotServices.RemoveMessage(interactivity.Result); - await BotServices.RemoveMessage(message); + await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); + if (!amiibo.Equals(results.Amiibo.Last())) + await BotServices.RemoveMessage(message).ConfigureAwait(false); } } } diff --git a/src/FlawBOT.Core/Modules/Search/DictionaryModule.cs b/src/FlawBOT.Core/Modules/Search/DictionaryModule.cs index 9b128a6c..33479618 100644 --- a/src/FlawBOT.Core/Modules/Search/DictionaryModule.cs +++ b/src/FlawBOT.Core/Modules/Search/DictionaryModule.cs @@ -24,28 +24,29 @@ public async Task UrbanDictionary(CommandContext ctx, [Description("Query to pass to Urban Dictionary")] [RemainingText] string query) { if (!BotServices.CheckUserInput(query)) return; - var results = await DictionaryService.GetDictionaryForTermAsync(query); + var results = await DictionaryService.GetDictionaryForTermAsync(query).ConfigureAwait(false); if (results.ResultType == "no_results") - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing); + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing).ConfigureAwait(false); else { foreach (var definition in results.List) { var output = new DiscordEmbedBuilder() - .WithTitle("Urban Dictionary definition for " + Formatter.Bold(query) + (!string.IsNullOrWhiteSpace(definition.Author) ? $" by {definition.Author}" : "")) + .WithTitle("Urban Dictionary definition for " + Formatter.Bold(query) + (!string.IsNullOrWhiteSpace(definition.Author) ? " by " + definition.Author : string.Empty)) .WithDescription(definition.Definition.Length < 500 ? definition.Definition : definition.Definition.Take(500) + "...") .AddField("Example", definition.Example ?? "None") .AddField(":thumbsup:", definition.ThumbsUp.ToString(), true) .AddField(":thumbsdown:", definition.ThumbsDown.ToString(), true) .WithUrl(definition.Permalink) - .WithFooter("Type 'next' within 10 seconds for the next definition") + .WithFooter(!definition.Equals(results.List.Last()) ? "Type 'next' within 10 seconds for the next definition" : "This is the last found definition on the list.") .WithColor(new DiscordColor("#1F2439")); - var message = await ctx.RespondAsync(embed: output.Build()); + var message = await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); - var interactivity = await ctx.Client.GetInteractivity().WaitForMessageAsync(m => m.Channel.Id == ctx.Channel.Id && m.Content.ToLowerInvariant() == "next", TimeSpan.FromSeconds(10)); + var interactivity = await ctx.Client.GetInteractivity().WaitForMessageAsync(m => m.Channel.Id == ctx.Channel.Id && string.Equals(m.Content, "next", StringComparison.InvariantCultureIgnoreCase), TimeSpan.FromSeconds(10)).ConfigureAwait(false); if (interactivity.Result is null) break; - await BotServices.RemoveMessage(interactivity.Result); - await BotServices.RemoveMessage(message); + await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); + if (!definition.Equals(results.List.Last())) + await BotServices.RemoveMessage(message).ConfigureAwait(false); } } } diff --git a/src/FlawBOT.Core/Modules/Search/GoodReadsModule.cs b/src/FlawBOT.Core/Modules/Search/GoodReadsModule.cs index 5a443d49..0730fa72 100644 --- a/src/FlawBOT.Core/Modules/Search/GoodReadsModule.cs +++ b/src/FlawBOT.Core/Modules/Search/GoodReadsModule.cs @@ -7,6 +7,7 @@ using FlawBOT.Framework.Services; using FlawBOT.Framework.Services.Search; using System; +using System.Linq; using System.Threading.Tasks; namespace FlawBOT.Modules @@ -25,7 +26,7 @@ public async Task Books(CommandContext ctx, if (!BotServices.CheckUserInput(query)) return; var results = GoodReadsService.GetBookDataAsync(query).Result.Search; if (results.ResultCount <= 0) - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing); + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing).ConfigureAwait(false); else { foreach (var book in results.Results) @@ -33,17 +34,19 @@ public async Task Books(CommandContext ctx, // TODO: Add page count, publication, ISBN, URLs var output = new DiscordEmbedBuilder() .WithTitle(book.Book.Title) - .AddField("Author", book.Book.Author.Name ?? "Unknown", true) - .AddField("Publication Year", book.PublicationYear.Text ?? "Unknown", true) - .AddField("Ratings", $"Average {book.RatingAverage} ({book.RatingCount.Text} total ratings)", true) - .WithImageUrl(book.Book.ImageUrl ?? book.Book.ImageUrlSmall) - .WithFooter("Type 'next' within 10 seconds for the next book.") + .AddField("Written by", book.Book.Author.Name ?? "Unknown", true) + .AddField("Publication Date", GoodReadsService.GetPublicationDate(book), true) + .AddField("Avg. Rating", book.RatingAverage ?? "Unknown", true) + .WithThumbnailUrl(book.Book.ImageUrl ?? book.Book.ImageUrlSmall) + .WithFooter((results.Results.Count > 1) ? "Type 'next' within 10 seconds for the next book." : "This is the only book of this name") .WithColor(new DiscordColor("#372213")); - var message = await ctx.RespondAsync(embed: output.Build()); + var message = await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); - var interactivity = await ctx.Client.GetInteractivity().WaitForMessageAsync(m => m.Channel.Id == ctx.Channel.Id && m.Content.ToLowerInvariant() == "next", TimeSpan.FromSeconds(10)); + var interactivity = await ctx.Client.GetInteractivity().WaitForMessageAsync(m => m.Channel.Id == ctx.Channel.Id && string.Equals(m.Content, "next", StringComparison.InvariantCultureIgnoreCase), TimeSpan.FromSeconds(10)).ConfigureAwait(false); if (interactivity.Result is null) break; - await BotServices.RemoveMessage(interactivity.Result); + await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); + if (!book.Equals(results.Results.Last())) + await BotServices.RemoveMessage(message).ConfigureAwait(false); } } } diff --git a/src/FlawBOT.Core/Modules/Search/GoogleModule.cs b/src/FlawBOT.Core/Modules/Search/GoogleModule.cs index 3a18befe..7f2c2075 100644 --- a/src/FlawBOT.Core/Modules/Search/GoogleModule.cs +++ b/src/FlawBOT.Core/Modules/Search/GoogleModule.cs @@ -27,14 +27,14 @@ public async Task GetTime(CommandContext ctx, if (!BotServices.CheckUserInput(location)) return; var results = GoogleService.GetTimeDataAsync(location).Result; if (results.status != "OK") - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing); + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing).ConfigureAwait(false); else { var output = new DiscordEmbedBuilder() - .WithTitle(":clock1: Time in " + results.Results[0].FormattedAddress) + .WithTitle(":clock1: Current time in " + results.Results[0].FormattedAddress) .WithDescription(Formatter.Bold(results.Time.ToShortTimeString()) + " " + results.Timezone.timeZoneName) .WithColor(SharedData.DefaultColor); - await ctx.RespondAsync(embed: output.Build()); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } } @@ -47,9 +47,9 @@ public async Task GetTime(CommandContext ctx, public async Task News(CommandContext ctx, [Description("Article topic to find on Google News")] [RemainingText] string query) { - var results = await GoogleService.GetNewsDataAsync(query); + var results = await GoogleService.GetNewsDataAsync(query).ConfigureAwait(false); if (results.Status != "ok") - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing); + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing).ConfigureAwait(false); else { while (results.Articles.Count > 0) @@ -63,12 +63,12 @@ public async Task News(CommandContext ctx, output.AddField(result.Title, result.Url); results.Articles.Remove(result); } - var message = await ctx.RespondAsync("Latest Google News articles from News API", embed: output.Build()); + var message = await ctx.RespondAsync("Latest Google News articles from News API", embed: output.Build()).ConfigureAwait(false); - var interactivity = await ctx.Client.GetInteractivity().WaitForMessageAsync(m => m.Channel.Id == ctx.Channel.Id && m.Content.ToLowerInvariant() == "next", TimeSpan.FromSeconds(10)); + var interactivity = await ctx.Client.GetInteractivity().WaitForMessageAsync(m => m.Channel.Id == ctx.Channel.Id && m.Content.ToLowerInvariant() == "next", TimeSpan.FromSeconds(10)).ConfigureAwait(false); if (interactivity.Result is null) break; - await BotServices.RemoveMessage(interactivity.Result); - await BotServices.RemoveMessage(message); + await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); + await BotServices.RemoveMessage(message).ConfigureAwait(false); } } } @@ -83,21 +83,21 @@ public async Task Weather(CommandContext ctx, [Description("Location to retrieve weather data from")] [RemainingText] string query) { if (!BotServices.CheckUserInput(query)) return; - var results = await GoogleService.GetWeatherDataAsync(query); + var results = await GoogleService.GetWeatherDataAsync(query).ConfigureAwait(false); if (results.COD == 404) - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_LOCATION, EmbedType.Missing); + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_LOCATION, EmbedType.Missing).ConfigureAwait(false); else { Func format = GoogleService.CelsiusToFahrenheit; var output = new DiscordEmbedBuilder() .WithTitle(":partly_sunny: Current weather in " + results.Name + ", " + results.Sys.Country) .AddField("Temperature", $"{results.Main.Temperature:F1}°C / {format(results.Main.Temperature):F1}°F", true) - .AddField("Conditions", string.Join(", ", results.Weather.Select(w => w.Main)), true) + //.AddField("Conditions", string.Join(", ", results.Weather.Select(w => w.Main)), true) .AddField("Humidity", $"{results.Main.Humidity}%", true) .AddField("Wind Speed", $"{results.Wind.Speed}m/s", true) .WithUrl("https://openweathermap.org/city/" + results.ID) .WithColor(SharedData.DefaultColor); - await ctx.RespondAsync(embed: output.Build()); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } } diff --git a/src/FlawBOT.Core/Modules/Search/ImgurModule.cs b/src/FlawBOT.Core/Modules/Search/ImgurModule.cs index 4032f0f1..f96bfab6 100644 --- a/src/FlawBOT.Core/Modules/Search/ImgurModule.cs +++ b/src/FlawBOT.Core/Modules/Search/ImgurModule.cs @@ -14,6 +14,7 @@ public class ImgurModule : BaseCommandModule #region COMMAND_IMGUR [Command("imgur")] + [Aliases("image")] [Description("Retrieve an imager from Imgur")] public async Task Imgur(CommandContext ctx, [Description("Search query to pass to Imgur")] [RemainingText] string query) @@ -22,15 +23,15 @@ public async Task Imgur(CommandContext ctx, switch (results) { case GalleryAlbum _: - await ctx.RespondAsync(((GalleryAlbum)results).Link); + await ctx.RespondAsync(((GalleryAlbum)results).Link).ConfigureAwait(false); break; case GalleryImage _: - await ctx.RespondAsync(((GalleryImage)results).Link); + await ctx.RespondAsync(((GalleryImage)results).Link).ConfigureAwait(false); break; default: - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing); + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing).ConfigureAwait(false); break; } } diff --git a/src/FlawBOT.Core/Modules/Search/NASAModule.cs b/src/FlawBOT.Core/Modules/Search/NASAModule.cs index 61ed5a50..abae339d 100644 --- a/src/FlawBOT.Core/Modules/Search/NASAModule.cs +++ b/src/FlawBOT.Core/Modules/Search/NASAModule.cs @@ -18,18 +18,17 @@ public class NASAModule : BaseCommandModule [Description("Retrieve NASA's Astronomy Picture of the Day")] public async Task NASA(CommandContext ctx) { - var results = await NASAService.GetNASAImage(); + var results = await NASAService.GetNASAImage().ConfigureAwait(false); if (results is null) - await BotServices.SendEmbedAsync(ctx, Resources.ERR_NASA_API, EmbedType.Missing); + await BotServices.SendEmbedAsync(ctx, Resources.ERR_NASA_API, EmbedType.Missing).ConfigureAwait(false); else { var output = new DiscordEmbedBuilder() .WithTitle(results.Title) - .WithDescription(results.Description) .WithImageUrl(results.ImageHD ?? results.ImageSD) - .WithFooter(results.Date + " " + (results.Copyright ?? "")) + .WithFooter(results.Description) .WithColor(new DiscordColor("#0B3D91")); - await ctx.RespondAsync(embed: output.Build()); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } } diff --git a/src/FlawBOT.Core/Modules/Search/OMDBModule.cs b/src/FlawBOT.Core/Modules/Search/OMDBModule.cs index 37270714..7d19eff1 100644 --- a/src/FlawBOT.Core/Modules/Search/OMDBModule.cs +++ b/src/FlawBOT.Core/Modules/Search/OMDBModule.cs @@ -23,7 +23,7 @@ public async Task OMDB(CommandContext ctx, if (!BotServices.CheckUserInput(query)) return; var results = OMDBService.GetMovieDataAsync(query.Replace(" ", "+")).Result; if (results.Response == "False") - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing); + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing).ConfigureAwait(false); else { var output = new DiscordEmbedBuilder() @@ -42,7 +42,7 @@ public async Task OMDB(CommandContext ctx, .AddField("Actors", results.Actors, true) .WithColor(DiscordColor.Goldenrod); if (results.Poster != "N/A") output.WithImageUrl(results.Poster); - await ctx.RespondAsync(embed: output.Build()); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } } diff --git a/src/FlawBOT.Core/Modules/Search/RedditModule.cs b/src/FlawBOT.Core/Modules/Search/RedditModule.cs index 00198e33..8c32b864 100644 --- a/src/FlawBOT.Core/Modules/Search/RedditModule.cs +++ b/src/FlawBOT.Core/Modules/Search/RedditModule.cs @@ -33,7 +33,7 @@ private async Task RedditPost(CommandContext ctx, string query, RedditCategory c { if (!BotServices.CheckUserInput(query)) return; var results = RedditService.GetEmbeddedResults(query, category); - await ctx.RespondAsync("Search results for r/" + Formatter.Bold(query), embed: results); + await ctx.RespondAsync("Search results for r/" + Formatter.Bold(query), embed: results).ConfigureAwait(false); } #endregion COMMAND_POST diff --git a/src/FlawBOT.Core/Modules/Search/SimpsonsModule.cs b/src/FlawBOT.Core/Modules/Search/SimpsonsModule.cs index 36638bab..265dc91b 100644 --- a/src/FlawBOT.Core/Modules/Search/SimpsonsModule.cs +++ b/src/FlawBOT.Core/Modules/Search/SimpsonsModule.cs @@ -17,8 +17,8 @@ public class SimpsonsModule : BaseCommandModule [Description("Retrieve a random Simpsons screenshot and episode")] public async Task Simpsons(CommandContext ctx) { - var results = await SimpsonsService.GetSimpsonsDataAsync(SimpsonsService.SiteRoot.Frinkiac); - await ctx.RespondAsync(embed: results.Build()); + var results = await SimpsonsService.GetSimpsonsDataAsync(SimpsonsService.SiteRoot.Frinkiac).ConfigureAwait(false); + await ctx.RespondAsync(embed: results.Build()).ConfigureAwait(false); } #endregion COMMAND_SIMPSONS @@ -31,14 +31,14 @@ public async Task Simpsons(CommandContext ctx) public async Task SimpsonsGIF(CommandContext ctx, [Description("Inputting anything will add episode information")] [RemainingText] string input) { - var output = await SimpsonsService.GetSimpsonsGifAsync(SimpsonsService.SiteRoot.Frinkiac); + var output = await SimpsonsService.GetSimpsonsGifAsync(SimpsonsService.SiteRoot.Frinkiac).ConfigureAwait(false); if (string.IsNullOrWhiteSpace(input)) - await ctx.RespondAsync(output); + await ctx.RespondAsync(output).ConfigureAwait(false); else // Include episode information if any kind of parameter is inputted { - var results = await SimpsonsService.GetSimpsonsDataAsync(SimpsonsService.SiteRoot.Frinkiac); + var results = await SimpsonsService.GetSimpsonsDataAsync(SimpsonsService.SiteRoot.Frinkiac).ConfigureAwait(false); results.WithFooter(Resources.SIMPSONS_GIF_WARNING); - await ctx.RespondAsync(output, embed: results.Build()); + await ctx.RespondAsync(output, embed: results.Build()).ConfigureAwait(false); } } @@ -51,9 +51,9 @@ public async Task SimpsonsGIF(CommandContext ctx, [Description("Retrieve a random Futurama screenshot and episode")] public async Task Futurama(CommandContext ctx) { - var results = await SimpsonsService.GetSimpsonsDataAsync(SimpsonsService.SiteRoot.Morbotron); + var results = await SimpsonsService.GetSimpsonsDataAsync(SimpsonsService.SiteRoot.Morbotron).ConfigureAwait(false); results.WithColor(new DiscordColor("#69E398")); - await ctx.RespondAsync(embed: results.Build()); + await ctx.RespondAsync(embed: results.Build()).ConfigureAwait(false); } #endregion COMMAND_FUTURAMA @@ -66,15 +66,15 @@ public async Task Futurama(CommandContext ctx) public async Task FuturamaGIF(CommandContext ctx, [Description("Inputting anything will add episode information")] [RemainingText] string input) { - var output = await SimpsonsService.GetSimpsonsGifAsync(SimpsonsService.SiteRoot.Morbotron); + var output = await SimpsonsService.GetSimpsonsGifAsync(SimpsonsService.SiteRoot.Morbotron).ConfigureAwait(false); if (string.IsNullOrWhiteSpace(input)) - await ctx.RespondAsync(output); + await ctx.RespondAsync(output).ConfigureAwait(false); else // Include episode information if any kind of parameter is inputted { - var results = await SimpsonsService.GetSimpsonsDataAsync(SimpsonsService.SiteRoot.Morbotron); + var results = await SimpsonsService.GetSimpsonsDataAsync(SimpsonsService.SiteRoot.Morbotron).ConfigureAwait(false); results.WithFooter(Resources.SIMPSONS_GIF_WARNING); results.WithColor(new DiscordColor("#69E398")); - await ctx.RespondAsync(output, embed: results.Build()); + await ctx.RespondAsync(output, embed: results.Build()).ConfigureAwait(false); } } @@ -87,9 +87,9 @@ public async Task FuturamaGIF(CommandContext ctx, [Description("Retrieve a random Rick and Morty screenshot and episode")] public async Task RickMorty(CommandContext ctx) { - var results = await SimpsonsService.GetSimpsonsDataAsync(SimpsonsService.SiteRoot.MasterOfAllScience); + var results = await SimpsonsService.GetSimpsonsDataAsync(SimpsonsService.SiteRoot.MasterOfAllScience).ConfigureAwait(false); results.WithColor(new DiscordColor("#ABD5EC")); - await ctx.RespondAsync(embed: results.Build()); + await ctx.RespondAsync(embed: results.Build()).ConfigureAwait(false); } #endregion COMMAND_RICKMORTY @@ -102,15 +102,15 @@ public async Task RickMorty(CommandContext ctx) public async Task RickMortyGif(CommandContext ctx, [Description("Inputting anything will add episode information")] [RemainingText] string input) { - var output = await SimpsonsService.GetSimpsonsGifAsync(SimpsonsService.SiteRoot.MasterOfAllScience); + var output = await SimpsonsService.GetSimpsonsGifAsync(SimpsonsService.SiteRoot.MasterOfAllScience).ConfigureAwait(false); if (string.IsNullOrWhiteSpace(input)) - await ctx.RespondAsync(output); + await ctx.RespondAsync(output).ConfigureAwait(false); else // Include episode information if any kind of parameter is inputted { - var results = await SimpsonsService.GetSimpsonsDataAsync(SimpsonsService.SiteRoot.MasterOfAllScience); + var results = await SimpsonsService.GetSimpsonsDataAsync(SimpsonsService.SiteRoot.MasterOfAllScience).ConfigureAwait(false); results.WithFooter(Resources.SIMPSONS_GIF_WARNING); results.WithColor(new DiscordColor("#ABD5EC")); - await ctx.RespondAsync(output, embed: results.Build()); + await ctx.RespondAsync(output, embed: results.Build()).ConfigureAwait(false); } } diff --git a/src/FlawBOT.Core/Modules/Search/TwitchModule.cs b/src/FlawBOT.Core/Modules/Search/TwitchModule.cs index 4ae34308..22b3f128 100644 --- a/src/FlawBOT.Core/Modules/Search/TwitchModule.cs +++ b/src/FlawBOT.Core/Modules/Search/TwitchModule.cs @@ -20,22 +20,21 @@ public async Task Twitch(CommandContext ctx, [Description("Channel to find on Twitch")] [RemainingText] string query) { if (!BotServices.CheckUserInput(query)) return; - var results = await TwitchService.GetTwitchDataAsync(query); - if (results.Stream is null) - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_TWITCH, EmbedType.Missing); + var results = await TwitchService.GetTwitchDataAsync(query).ConfigureAwait(false); + if (results.Stream.Count == 0) + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_TWITCH, EmbedType.Missing).ConfigureAwait(false); else { - var stream = results.Stream; + var stream = results.Stream[0]; var output = new DiscordEmbedBuilder() - .WithTitle(stream.Channel.Name + " is now live on Twitch!") - .WithDescription(stream.Channel.Status) - .AddField("Now Playing", (stream.Game) ?? "Nothing") - .AddField("Start Time", stream.CreatedAt.ToString(), true) - .AddField("Viewers", $"{stream.Viewers:#,##0}", true) - .WithThumbnailUrl(stream.Channel.Logo) - .WithUrl(stream.Channel.Url) + .WithTitle(stream.UserName + " is live on Twitch!") + .WithDescription(stream.Title) + .AddField("Start Time:", stream.StartTime, true) + .AddField("View Count:", stream.ViewCount.ToString(), true) + .WithImageUrl(stream.ThumbnailUrl.Replace("{width}", "500").Replace("{height}", "300")) + .WithUrl("https://www.twitch.tv/" + stream.UserName) .WithColor(new DiscordColor("#6441A5")); - await ctx.RespondAsync(embed: output.Build()); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } } diff --git a/src/FlawBOT.Core/Modules/Search/WikipediaModule.cs b/src/FlawBOT.Core/Modules/Search/WikipediaModule.cs index dd1f329d..02b9815d 100644 --- a/src/FlawBOT.Core/Modules/Search/WikipediaModule.cs +++ b/src/FlawBOT.Core/Modules/Search/WikipediaModule.cs @@ -21,7 +21,7 @@ public async Task Wikipedia(CommandContext ctx, if (!BotServices.CheckUserInput(query)) return; var results = WikipediaService.GetWikipediaDataAsync(query).Result; if (results.Missing) - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_WIKIPEDIA, EmbedType.Missing); + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_WIKIPEDIA, EmbedType.Missing).ConfigureAwait(false); else await ctx.Channel.SendMessageAsync(results.FullUrl).ConfigureAwait(false); } diff --git a/src/FlawBOT.Core/Modules/Search/YoutubeModule.cs b/src/FlawBOT.Core/Modules/Search/YoutubeModule.cs index 46175c5e..a46ec442 100644 --- a/src/FlawBOT.Core/Modules/Search/YoutubeModule.cs +++ b/src/FlawBOT.Core/Modules/Search/YoutubeModule.cs @@ -22,8 +22,8 @@ public async Task YouTubeChannel(CommandContext ctx, { if (!BotServices.CheckUserInput(query)) return; var service = new YoutubeService(); - var results = await service.GetEmbeddedResults(query, 5, "channel"); - await ctx.RespondAsync("Search results for " + Formatter.Bold(query), embed: results); + var results = await service.GetEmbeddedResults(query, 5, "channel").ConfigureAwait(false); + await ctx.RespondAsync("Search results for " + Formatter.Bold(query), embed: results).ConfigureAwait(false); } #endregion COMMAND_CHANNEL @@ -38,8 +38,8 @@ public async Task YouTubePlaylist(CommandContext ctx, { if (!BotServices.CheckUserInput(query)) return; var service = new YoutubeService(); - var results = await service.GetEmbeddedResults(query, 5, "playlist"); - await ctx.RespondAsync("Search results for " + Formatter.Bold(query), embed: results); + var results = await service.GetEmbeddedResults(query, 5, "playlist").ConfigureAwait(false); + await ctx.RespondAsync("Search results for " + Formatter.Bold(query), embed: results).ConfigureAwait(false); } #endregion COMMAND_PLAYLIST @@ -54,8 +54,8 @@ public async Task YouTubeVideo(CommandContext ctx, { if (!BotServices.CheckUserInput(query)) return; var service = new YoutubeService(); - var results = await service.GetFirstVideoResultAsync(query); - await ctx.RespondAsync(results); + var results = await service.GetFirstVideoResultAsync(query).ConfigureAwait(false); + await ctx.RespondAsync(results).ConfigureAwait(false); } #endregion COMMAND_SEARCH @@ -70,8 +70,8 @@ public async Task YouTubeSearch(CommandContext ctx, { if (!BotServices.CheckUserInput(query)) return; var service = new YoutubeService(); - var results = await service.GetEmbeddedResults(query, 5, "video"); - await ctx.RespondAsync("Search results for " + Formatter.Bold(query), embed: results); + var results = await service.GetEmbeddedResults(query, 5, "video").ConfigureAwait(false); + await ctx.RespondAsync("Search results for " + Formatter.Bold(query), embed: results).ConfigureAwait(false); } #endregion COMMAND_VIDEO diff --git a/src/FlawBOT.Core/Modules/Server/BotModule.cs b/src/FlawBOT.Core/Modules/Server/BotModule.cs new file mode 100644 index 00000000..da14a3c9 --- /dev/null +++ b/src/FlawBOT.Core/Modules/Server/BotModule.cs @@ -0,0 +1,267 @@ +using DSharpPlus; +using DSharpPlus.CommandsNext; +using DSharpPlus.CommandsNext.Attributes; +using DSharpPlus.Entities; +using DSharpPlus.Interactivity; +using FlawBOT.Common; +using FlawBOT.Core.Properties; +using FlawBOT.Framework.Models; +using FlawBOT.Framework.Services; +using System; +using System.Threading.Tasks; + +namespace FlawBOT.Modules +{ + [Group("bot")] + [Description("Basic commands for interacting with FlawBOT")] + [Cooldown(3, 5, CooldownBucketType.Channel)] + public class BotModule : BaseCommandModule + { + #region COMMAND_INFO + + [Command("info")] + [Aliases("i")] + [Description("Retrieve FlawBOT information")] + public async Task BotInfo(CommandContext ctx) + { + var uptime = DateTime.Now - SharedData.ProcessStarted; + var output = new DiscordEmbedBuilder() + .WithTitle(SharedData.Name) + .WithDescription("A multipurpose Discord bot written in C# with [DSharpPlus](https://github.com/DSharpPlus/DSharpPlus/).") + .AddField(":clock1: Uptime", $"{(int)uptime.TotalDays:00} days {uptime.Hours:00}:{uptime.Minutes:00}:{uptime.Seconds:00}", true) + .AddField(":link: Links", $"[Commands]({SharedData.GitHubLink}wiki) **|** [Invite]({SharedData.InviteLink}) **|** [GitHub]({SharedData.GitHubLink})", true) + .WithFooter("Thank you for using " + SharedData.Name + $" (v{SharedData.Version})") + .WithUrl(SharedData.GitHubLink) + .WithColor(SharedData.DefaultColor); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); + } + + #endregion COMMAND_INFO + + #region COMMAND_LEAVE + + [Command("leave")] + [Description("Make FlawBOT leave the current server")] + [RequireUserPermissions(Permissions.Administrator)] + public async Task LeaveAsync(CommandContext ctx) + { + await ctx.RespondAsync($"Are you sure you want {SharedData.Name} to leave this server?").ConfigureAwait(false); + var message = await ctx.RespondAsync("Respond with **yes** to proceed or wait 10 seconds to cancel this operation.").ConfigureAwait(false); + var interactivity = await ctx.Client.GetInteractivity().WaitForMessageAsync(m => m.Channel.Id == ctx.Channel.Id && m.Author.Id == ctx.User.Id && string.Equals(m.Content, "yes", StringComparison.InvariantCultureIgnoreCase), TimeSpan.FromSeconds(10)).ConfigureAwait(false); + if (interactivity.Result is null) + await message.ModifyAsync("~~" + message.Content + "~~ " + Resources.REQUEST_TIMEOUT).ConfigureAwait(false); + else + { + await BotServices.SendEmbedAsync(ctx, "Thank you for using " + SharedData.Name).ConfigureAwait(false); + await ctx.Guild.LeaveAsync().ConfigureAwait(false); + } + } + + #endregion COMMAND_LEAVE + + #region COMMAND_PING + + [Command("ping")] + [Aliases("pong")] + [Description("Ping the FlawBOT client")] + public async Task Ping(CommandContext ctx) + { + await ctx.RespondAsync($":ping_pong: Pong! Ping: **{ctx.Client.Ping}**ms").ConfigureAwait(false); + } + + #endregion COMMAND_PING + + #region COMMAND_REPORT + + [Command("report"), Hidden] + [Aliases("issue")] + [Description("Report a problem with FlawBOT to the developer. Please do not abuse.")] + public async Task ReportIssue(CommandContext ctx, + [Description("Detailed description of the issue")] [RemainingText] string report) + { + if (string.IsNullOrWhiteSpace(report) || report.Length < 50) + await ctx.RespondAsync(Resources.ERR_REPORT_CHAR_LENGTH).ConfigureAwait(false); + else + { + await ctx.RespondAsync("The following information will be sent to the developer for investigation: User ID, Server ID, Server Name and Server Owner Name.").ConfigureAwait(false); + var message = await ctx.RespondAsync("Respond with **yes** to proceed or wait 10 seconds to cancel this operation.").ConfigureAwait(false); + var interactivity = await ctx.Client.GetInteractivity().WaitForMessageAsync(m => m.Channel.Id == ctx.Channel.Id && m.Author.Id == ctx.User.Id && string.Equals(m.Content, "yes", StringComparison.InvariantCultureIgnoreCase), TimeSpan.FromSeconds(10)).ConfigureAwait(false); + if (interactivity.Result is null) + await message.ModifyAsync("~~" + message.Content + "~~ " + Resources.REQUEST_TIMEOUT).ConfigureAwait(false); + else + { + var dm = await ctx.Member.CreateDmChannelAsync().ConfigureAwait(false); + var output = new DiscordEmbedBuilder() + .WithAuthor(ctx.Guild.Owner.Username + "#" + ctx.Guild.Owner.Discriminator, iconUrl: ctx.User.AvatarUrl ?? ctx.User.DefaultAvatarUrl) + .AddField("Issue", report) + .AddField("Sent By", ctx.User.Username + "#" + ctx.User.Discriminator) + .AddField("Server", ctx.Guild.Name + $" (ID: {ctx.Guild.Id})") + .AddField("Owner", ctx.Guild.Owner.Username + "#" + ctx.Guild.Owner.Discriminator) + .AddField("Confirm", $"[Click here to add this issue to GitHub]({SharedData.GitHubLink}/issues/new)") + .WithColor(SharedData.DefaultColor); + await dm.SendMessageAsync(embed: output.Build()).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, "Thank You! Your report has been submitted.", EmbedType.Good).ConfigureAwait(false); + } + } + } + + #endregion COMMAND_REPORT + + #region COMMAND_SAY + + [Command("say"), Hidden] + [Aliases("echo")] + [Description("Make FlawBOT repeat a message")] + public async Task Say(CommandContext ctx, + [Description("Message for the bot to repeat")] [RemainingText] string message) + { + await BotServices.RemoveMessage(ctx.Message).ConfigureAwait(false); + await ctx.RespondAsync(message ?? ":thinking:").ConfigureAwait(false); + } + + #endregion COMMAND_SAY + + #region COMMAND_UPTIME + + [Command("uptime")] + [Description("Retrieve the FlawBOT uptime")] + public async Task Uptime(CommandContext ctx) + { + var uptime = DateTime.Now - SharedData.ProcessStarted; + var days = (uptime.Days > 0) ? $"({uptime.Days:00} days)" : string.Empty; + await BotServices.SendEmbedAsync(ctx, $":clock1: {SharedData.Name} has been online for {uptime.Hours:00}:{uptime.Minutes:00}:{uptime.Seconds} {days}").ConfigureAwait(false); + } + + #endregion COMMAND_UPTIME + + #region OWNERS-ONLY + + #region COMMAND_ACTIVITY + + [RequireOwner] + [Command("activity"), Hidden] + [Aliases("setactivity")] + [Description("Set FlawBOT's activity")] + public async Task SetBotActivity(CommandContext ctx, + [Description("Name of the activity")] [RemainingText] string activity) + { + if (string.IsNullOrWhiteSpace(activity)) + { + await ctx.Client.UpdateStatusAsync(activity: null).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, SharedData.Name + " activity has been changed to Normal").ConfigureAwait(false); + } + else + { + // TODO: Set the activity type + var game = new DiscordActivity(activity); + await ctx.Client.UpdateStatusAsync(activity: game).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, SharedData.Name + " activity has been changed to Playing " + game.Name, EmbedType.Good).ConfigureAwait(false); + } + } + + #endregion COMMAND_ACTIVITY + + #region COMMAND_AVATAR + + [RequireOwner] + [Command("avatar"), Hidden] + [Aliases("setavatar")] + [Description("Set FlawBOT's avatar")] + public async Task SetBotAvatar(CommandContext ctx, + [Description("Image URL. Must be in jpg, png or img format.")] string query) + { + var stream = BotServices.CheckImageInput(ctx, query).Result; + if (stream.Length <= 0) return; + await ctx.Client.UpdateCurrentUserAsync(avatar: stream).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, SharedData.Name + " avatar has been updated!", EmbedType.Good).ConfigureAwait(false); + } + + #endregion COMMAND_AVATAR + + #region COMMAND_STATUS + + [RequireOwner] + [Command("status"), Hidden] + [Aliases("setstatus")] + [Description("Set FlawBOT's status")] + public async Task SetBotStatus(CommandContext ctx, + [Description("Activity Status. Online, Idle, DND or Offline")] [RemainingText] string status) + { + status = status ?? "ONLINE"; + switch (status.Trim().ToUpperInvariant()) + { + case "OFF": + case "OFFLINE": + await ctx.Client.UpdateStatusAsync(userStatus: UserStatus.Offline).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, SharedData.Name + " status has been changed to Offline").ConfigureAwait(false); + break; + + case "INVISIBLE": + await ctx.Client.UpdateStatusAsync(userStatus: UserStatus.Invisible).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, SharedData.Name + " status has been changed to Invisible").ConfigureAwait(false); + break; + + case "IDLE": + await ctx.Client.UpdateStatusAsync(userStatus: UserStatus.Idle).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, SharedData.Name + " status has been changed to Idle").ConfigureAwait(false); + break; + + case "DND": + case "DO NOT DISTURB": + await ctx.Client.UpdateStatusAsync(userStatus: UserStatus.DoNotDisturb).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, SharedData.Name + " status has been changed to Do Not Disturb").ConfigureAwait(false); + break; + + default: + await ctx.Client.UpdateStatusAsync(userStatus: UserStatus.Online).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, SharedData.Name + " status has been changed to Online").ConfigureAwait(false); + break; + } + } + + #endregion COMMAND_STATUS + + #region COMMAND_UPDATE + + [RequireOwner] + [Command("update"), Hidden] + [Aliases("refresh")] + [Description("Update FlawBOT libraries")] + public async Task Update(CommandContext ctx) + { + var message = await ctx.RespondAsync("Starting update...").ConfigureAwait(false); + await SteamService.UpdateSteamListAsync().ConfigureAwait(false); + await TeamFortressService.LoadTF2SchemaAsync().ConfigureAwait(false); + await PokemonService.UpdatePokemonListAsync().ConfigureAwait(false); + await message.ModifyAsync("Starting update...done!").ConfigureAwait(false); + } + + #endregion COMMAND_UPDATE + + #region COMMAND_USERNAME + + [RequireOwner] + [Command("username"), Hidden] + [Aliases("setusername", "name", "setname", "nickname")] + [Description("Set FlawBOT's username")] + public async Task SetBotUsername(CommandContext ctx, + [Description("New bot username")] [RemainingText] string name) + { + var oldUsername = ctx.Client.CurrentUser.Username; + if (string.IsNullOrWhiteSpace(name)) + { + await ctx.Client.UpdateCurrentUserAsync(username: SharedData.Name).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, oldUsername + " username has been changed to " + SharedData.Name).ConfigureAwait(false); + } + else + { + await ctx.Client.UpdateCurrentUserAsync(username: name).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, oldUsername + " username has been changed to " + ctx.Client.CurrentUser.Username, EmbedType.Good).ConfigureAwait(false); + } + } + + #endregion COMMAND_USERNAME + + #endregion OWNERS-ONLY + } +} \ No newline at end of file diff --git a/src/FlawBOT.Core/Modules/Server/ChannelModule.cs b/src/FlawBOT.Core/Modules/Server/ChannelModule.cs index 3de1947d..b14e4035 100644 --- a/src/FlawBOT.Core/Modules/Server/ChannelModule.cs +++ b/src/FlawBOT.Core/Modules/Server/ChannelModule.cs @@ -32,11 +32,11 @@ public async Task CreateChannelCategory(CommandContext ctx, [Description("New category name")] [RemainingText] string name) { if (!BotServices.CheckChannelName(name)) - await BotServices.SendEmbedAsync(ctx, Resources.ERR_CHANNEL_NAME, EmbedType.Warning); + await BotServices.SendEmbedAsync(ctx, Resources.ERR_CHANNEL_NAME, EmbedType.Warning).ConfigureAwait(false); else { var category = await ctx.Guild.CreateChannelCategoryAsync(name.Trim()).ConfigureAwait(false); - await BotServices.SendEmbedAsync(ctx, "Successfully created category " + Formatter.Bold(category.Name), EmbedType.Good); + await BotServices.SendEmbedAsync(ctx, "Successfully created category " + Formatter.Bold(category.Name), EmbedType.Good).ConfigureAwait(false); } } @@ -53,7 +53,7 @@ public async Task Clean(CommandContext ctx, { var messages = await ctx.Channel.GetMessagesAsync(BotServices.LimitToRange(limit)).ConfigureAwait(false); await ctx.Channel.DeleteMessagesAsync(messages).ConfigureAwait(false); - await BotServices.SendEmbedAsync(ctx, Formatter.Bold(messages.Count.ToString()) + " message(s) removed from #" + ctx.Channel.Name, EmbedType.Good); + await BotServices.SendEmbedAsync(ctx, Formatter.Bold(messages.Count.ToString()) + " message(s) removed from #" + ctx.Channel.Name, EmbedType.Good).ConfigureAwait(false); } #endregion CHANNEL_CLEAN @@ -70,16 +70,16 @@ public async Task RemoveTextChannel(CommandContext ctx, // Set the current channel for deletion if one isn't provided by the user channel = channel ?? ctx.Channel; - var prompt = await ctx.RespondAsync("You're about to delete the " + Formatter.Bold(channel.ToString()) + "\nRespond with **yes** if you want to proceed or wait 10 seconds to cancel the operation."); - var interactivity = await ctx.Client.GetInteractivity().WaitForMessageAsync(m => m.Channel.Id == ctx.Channel.Id && m.Content.ToLowerInvariant() == "yes", TimeSpan.FromSeconds(10)); + var prompt = await ctx.RespondAsync("You're about to delete the " + Formatter.Bold(channel.ToString()) + "\nRespond with **yes** if you want to proceed or wait 10 seconds to cancel the operation.").ConfigureAwait(false); + var interactivity = await ctx.Client.GetInteractivity().WaitForMessageAsync(m => m.Channel.Id == ctx.Channel.Id && m.Content.ToLowerInvariant() == "yes", TimeSpan.FromSeconds(10)).ConfigureAwait(false); if (interactivity.Result is null) - await BotServices.SendEmbedAsync(ctx, Resources.REQUEST_TIMEOUT); + await BotServices.SendEmbedAsync(ctx, Resources.REQUEST_TIMEOUT).ConfigureAwait(false); else { - await BotServices.RemoveMessage(interactivity.Result); - await BotServices.RemoveMessage(prompt); - await BotServices.SendEmbedAsync(ctx, "Successfully deleted " + Formatter.Bold(channel.Name), EmbedType.Good); - await channel.DeleteAsync(); + await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); + await BotServices.RemoveMessage(prompt).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, "Successfully deleted " + Formatter.Bold(channel.Name), EmbedType.Good).ConfigureAwait(false); + await channel.DeleteAsync().ConfigureAwait(false); } } @@ -105,8 +105,8 @@ public Task GetChannel(CommandContext ctx, .WithTitle(channel.Name + $" (ID: {channel.Id})") .WithDescription("Channel topic: " + Formatter.Italic(channel.Topic) ?? "") .AddField("Type", channel.Type.ToString(), true) - .AddField("Private", channel.IsPrivate ? "YES" : "NO", true) - .AddField("NSFW", channel.IsNSFW ? "YES" : "NO", true) + .AddField("Private", channel.IsPrivate ? "Yes" : "No", true) + .AddField("NSFW", channel.IsNSFW ? "Yes" : "No", true) .WithThumbnailUrl(ctx.Guild.IconUrl) .WithFooter("Created on " + channel.CreationTimestamp.DateTime.ToString(CultureInfo.InvariantCulture)) .WithColor(SharedData.DefaultColor); @@ -134,6 +134,20 @@ public Task GetChannel(CommandContext ctx, #endregion COMMAND_INFO + #region COMMAND_JOIN + + [Command("join")] + [Aliases("j")] + [Description("Be placed into a specified voice channel")] + public async Task JoinVoiceChannel(CommandContext ctx, + [Description("Name of voice channel to join")] [RemainingText] DiscordChannel channel = null) + { + if (channel.Type == ChannelType.Voice) + await ctx.Member.PlaceInAsync(channel).ConfigureAwait(false); + } + + #endregion COMMAND_JOIN + #region CHANNEL_PURGE [Command("purge")] @@ -144,8 +158,8 @@ public async Task Purge(CommandContext ctx, [Description("Number of messages to purge")] [RemainingText] int limit = 0) { var messages = await ctx.Channel.GetMessagesAsync(BotServices.LimitToRange(limit)).ConfigureAwait(false); - await ctx.Channel.DeleteMessagesAsync(messages.Where(m => m.Author.Id == member.Id)); - await BotServices.SendEmbedAsync(ctx, $"Purged **{limit}** messages by {member.Username}#{member.Discriminator} (ID:{member.Id})", EmbedType.Good); + await ctx.Channel.DeleteMessagesAsync(messages.Where(m => m.Author.Id == member.Id)).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, $"Purged **{limit}** messages by {member.Username}#{member.Discriminator} (ID:{member.Id})", EmbedType.Good).ConfigureAwait(false); } #endregion CHANNEL_PURGE @@ -161,12 +175,12 @@ public async Task SetChannelName(CommandContext ctx, [Description("New channel name")] [RemainingText] string name) { if (!BotServices.CheckChannelName(name)) - await BotServices.SendEmbedAsync(ctx, Resources.ERR_CHANNEL_NAME, EmbedType.Warning); + await BotServices.SendEmbedAsync(ctx, Resources.ERR_CHANNEL_NAME, EmbedType.Warning).ConfigureAwait(false); else { var old_name = channel.Name; - await channel.ModifyAsync(new Action(m => m.Name = name.Trim().Replace(" ", "-"))); - await BotServices.SendEmbedAsync(ctx, $"Successfully renamed the channel " + Formatter.Bold(old_name) + " to " + Formatter.Bold(name), EmbedType.Good); + await channel.ModifyAsync(new Action(m => m.Name = name.Trim().Replace(" ", "-"))).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, $"Successfully renamed the channel " + Formatter.Bold(old_name) + " to " + Formatter.Bold(name), EmbedType.Good).ConfigureAwait(false); } } @@ -182,13 +196,13 @@ public async Task CreateTextChannel(CommandContext ctx, [Description("New text channel name")] [RemainingText] string name = "") { if (!BotServices.CheckChannelName(name)) - await BotServices.SendEmbedAsync(ctx, Resources.ERR_CHANNEL_NAME, EmbedType.Warning); + await BotServices.SendEmbedAsync(ctx, Resources.ERR_CHANNEL_NAME, EmbedType.Warning).ConfigureAwait(false); else if (ctx.Guild.Channels.Any(chn => string.Compare(name, chn.Value.Name, true) == 0)) - await BotServices.SendEmbedAsync(ctx, Resources.ERR_CHANNEL_EXISTS, EmbedType.Warning); + await BotServices.SendEmbedAsync(ctx, Resources.ERR_CHANNEL_EXISTS, EmbedType.Warning).ConfigureAwait(false); else { - var channel = await ctx.Guild.CreateTextChannelAsync(name.Trim().Replace(" ", "-")); - await BotServices.SendEmbedAsync(ctx, "Successfully created the text channel " + Formatter.Bold(channel.Name), EmbedType.Good); + var channel = await ctx.Guild.CreateTextChannelAsync(name.Trim().Replace(" ", "-")).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, "Successfully created the text channel " + Formatter.Bold(channel.Name), EmbedType.Good).ConfigureAwait(false); } } @@ -204,12 +218,12 @@ public async Task SetChannelTopic(CommandContext ctx, [Description("New channel topic")] [RemainingText] string topic = "") { if (topic.Length > 1024) - await BotServices.SendEmbedAsync(ctx, Resources.ERR_CHANNEL_TOPIC, EmbedType.Warning); + await BotServices.SendEmbedAsync(ctx, Resources.ERR_CHANNEL_TOPIC, EmbedType.Warning).ConfigureAwait(false); else { - await ctx.Channel.ModifyAsync(chn => chn.Topic = topic); + await ctx.Channel.ModifyAsync(chn => chn.Topic = topic).ConfigureAwait(false); if (!string.IsNullOrWhiteSpace(topic)) - await BotServices.SendEmbedAsync(ctx, "Successfully changed the channel topic to " + Formatter.Bold(topic), EmbedType.Good); + await BotServices.SendEmbedAsync(ctx, "Successfully changed the channel topic to " + Formatter.Bold(topic), EmbedType.Good).ConfigureAwait(false); } } @@ -225,13 +239,13 @@ public async Task CreateVoiceChannel(CommandContext ctx, [Description("New voice channel name")] [RemainingText] string name = "") { if (!BotServices.CheckChannelName(name)) - await BotServices.SendEmbedAsync(ctx, Resources.ERR_CHANNEL_NAME, EmbedType.Warning); + await BotServices.SendEmbedAsync(ctx, Resources.ERR_CHANNEL_NAME, EmbedType.Warning).ConfigureAwait(false); else if (ctx.Guild.Channels.Any(chn => string.Compare(name, chn.Value.Name, true) == 0)) - await BotServices.SendEmbedAsync(ctx, Resources.ERR_CHANNEL_EXISTS, EmbedType.Warning); + await BotServices.SendEmbedAsync(ctx, Resources.ERR_CHANNEL_EXISTS, EmbedType.Warning).ConfigureAwait(false); else { - var channel = await ctx.Guild.CreateVoiceChannelAsync(name: name.Trim().Replace(" ", "-")); - await BotServices.SendEmbedAsync(ctx, "Successfully created the voice channel " + Formatter.Bold(channel.Name), EmbedType.Good); + var channel = await ctx.Guild.CreateVoiceChannelAsync(name: name.Trim().Replace(" ", "-")).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, "Successfully created the voice channel " + Formatter.Bold(channel.Name), EmbedType.Good).ConfigureAwait(false); } } diff --git a/src/FlawBOT.Core/Modules/Server/EmojiModule.cs b/src/FlawBOT.Core/Modules/Server/EmojiModule.cs index f3ced8ce..3eaa2207 100644 --- a/src/FlawBOT.Core/Modules/Server/EmojiModule.cs +++ b/src/FlawBOT.Core/Modules/Server/EmojiModule.cs @@ -33,29 +33,29 @@ public async Task AddAsync(CommandContext ctx, try { if (string.IsNullOrWhiteSpace(query) || query.Length < 2 || query.Length > 50) - await BotServices.SendEmbedAsync(ctx, Resources.ERR_ROLE_NAME, EmbedType.Warning); + await BotServices.SendEmbedAsync(ctx, Resources.ERR_ROLE_NAME, EmbedType.Warning).ConfigureAwait(false); if (url is null) if (!ctx.Message.Attachments.Any() || !Uri.TryCreate(ctx.Message.Attachments.First().Url, UriKind.Absolute, out url)) - await BotServices.SendEmbedAsync(ctx, Resources.ERR_EMOJI_IMAGE, EmbedType.Warning); + await BotServices.SendEmbedAsync(ctx, Resources.ERR_EMOJI_IMAGE, EmbedType.Warning).ConfigureAwait(false); var _handler = new HttpClientHandler { AllowAutoRedirect = false }; var _http = new HttpClient(_handler, true); var response = await _http.GetAsync(url).ConfigureAwait(false); if (!response.Content.Headers.ContentType.MediaType.StartsWith("image/")) return; - using (response = await _http.GetAsync(url)) - using (var stream = await response.Content.ReadAsStreamAsync()) + using (response = await _http.GetAsync(url).ConfigureAwait(false)) + using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) { if (stream.Length >= 256000) - await BotServices.SendEmbedAsync(ctx, Resources.ERR_EMOJI_SIZE, EmbedType.Warning); - var emoji = await ctx.Guild.CreateEmojiAsync(query, stream); - await BotServices.SendEmbedAsync(ctx, "Successfully added " + Formatter.Bold(emoji.Name), EmbedType.Good); + await BotServices.SendEmbedAsync(ctx, Resources.ERR_EMOJI_SIZE, EmbedType.Warning).ConfigureAwait(false); + var emoji = await ctx.Guild.CreateEmojiAsync(query, stream).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, "Successfully added " + Formatter.Bold(emoji.Name), EmbedType.Good).ConfigureAwait(false); } } catch { - await BotServices.SendEmbedAsync(ctx, Resources.ERR_EMOJI_ADD, EmbedType.Error); + await BotServices.SendEmbedAsync(ctx, Resources.ERR_EMOJI_ADD, EmbedType.Error).ConfigureAwait(false); } } @@ -72,13 +72,13 @@ public async Task DeleteAsync(CommandContext ctx, { try { - var emoji = await ctx.Guild.GetEmojiAsync(query.Id); - await ctx.Guild.DeleteEmojiAsync(emoji); - await BotServices.SendEmbedAsync(ctx, "Successfully deleted " + Formatter.Bold(emoji.Name), EmbedType.Good); + var emoji = await ctx.Guild.GetEmojiAsync(query.Id).ConfigureAwait(false); + await ctx.Guild.DeleteEmojiAsync(emoji).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, "Successfully deleted " + Formatter.Bold(emoji.Name), EmbedType.Good).ConfigureAwait(false); } catch (NotFoundException) { - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_EMOJI, EmbedType.Missing); + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_EMOJI, EmbedType.Missing).ConfigureAwait(false); } } @@ -97,17 +97,17 @@ public async Task ModifyAsync(CommandContext ctx, try { if (string.IsNullOrWhiteSpace(name)) - await BotServices.SendEmbedAsync(ctx, Resources.ERR_EMOJI_NAME, EmbedType.Warning); + await BotServices.SendEmbedAsync(ctx, Resources.ERR_EMOJI_NAME, EmbedType.Warning).ConfigureAwait(false); else { - var emoji = await ctx.Guild.GetEmojiAsync(query.Id); - emoji = await ctx.Guild.ModifyEmojiAsync(emoji, name: name); - await BotServices.SendEmbedAsync(ctx, "Successfully renamed emoji to " + Formatter.Bold(emoji.Name), EmbedType.Good); + var emoji = await ctx.Guild.GetEmojiAsync(query.Id).ConfigureAwait(false); + emoji = await ctx.Guild.ModifyEmojiAsync(emoji, name: name).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, "Successfully renamed emoji to " + Formatter.Bold(emoji.Name), EmbedType.Good).ConfigureAwait(false); } } catch (NotFoundException) { - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_EMOJI, EmbedType.Missing); + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_EMOJI, EmbedType.Missing).ConfigureAwait(false); } } @@ -121,7 +121,7 @@ public async Task ModifyAsync(CommandContext ctx, public async Task GetEmoji(CommandContext ctx, [Description("Server emoji information to retrieve.")] DiscordEmoji query) { - var emoji = await ctx.Guild.GetEmojiAsync(query.Id); + var emoji = await ctx.Guild.GetEmojiAsync(query.Id).ConfigureAwait(false); var output = new DiscordEmbedBuilder() .AddField("Name", emoji.Name, true) .AddField("Server", emoji.Guild.Name, true) @@ -129,7 +129,7 @@ public async Task GetEmoji(CommandContext ctx, .AddField("Creation Date", emoji.CreationTimestamp.ToString(), true) .WithColor(DiscordColor.PhthaloBlue) .WithThumbnailUrl(emoji.Url); - await ctx.RespondAsync(embed: output.Build()); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } #endregion COMMAND_INFO @@ -149,7 +149,7 @@ public async Task GetEmojiList(CommandContext ctx) .WithTitle("Emojis available for " + ctx.Guild.Name) .WithDescription(emojiList.ToString()) .WithColor(DiscordColor.PhthaloBlue); - await ctx.RespondAsync(embed: output.Build()); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } #endregion COMMAND_LIST diff --git a/src/FlawBOT.Core/Modules/Server/RoleModule.cs b/src/FlawBOT.Core/Modules/Server/RoleModule.cs index cf4df74d..78d4d704 100644 --- a/src/FlawBOT.Core/Modules/Server/RoleModule.cs +++ b/src/FlawBOT.Core/Modules/Server/RoleModule.cs @@ -37,10 +37,10 @@ public async Task ColorRole(CommandContext ctx, var output = new DiscordEmbedBuilder() .WithTitle("Successfully set the color for the role " + Formatter.Bold(role.Name) + " to " + Formatter.InlineCode(role.Color.ToString())) .WithColor(color); - await ctx.RespondAsync(embed: output.Build()); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } else - await BotServices.SendEmbedAsync(ctx, "Invalid color code. Please enter a HEX color code like #E7B53B", EmbedType.Warning); + await BotServices.SendEmbedAsync(ctx, "Invalid color code. Please enter a HEX color code like #E7B53B", EmbedType.Warning).ConfigureAwait(false); } #endregion COMMAND_COLOR @@ -55,11 +55,11 @@ public async Task CreateRole(CommandContext ctx, [Description("New role name")] [RemainingText] string role = "") { if (string.IsNullOrWhiteSpace(role)) - await BotServices.SendEmbedAsync(ctx, Resources.ERR_ROLE_NAME, EmbedType.Warning); + await BotServices.SendEmbedAsync(ctx, Resources.ERR_ROLE_NAME, EmbedType.Warning).ConfigureAwait(false); else { - await ctx.Guild.CreateRoleAsync(role); - await BotServices.SendEmbedAsync(ctx, "Successfully created the server role " + Formatter.Bold(role), EmbedType.Good); + await ctx.Guild.CreateRoleAsync(role).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, "Successfully created the server role " + Formatter.Bold(role), EmbedType.Good).ConfigureAwait(false); } } @@ -75,11 +75,11 @@ public async Task DeleteRole(CommandContext ctx, [Description("Server role to delete")] [RemainingText] DiscordRole role = null) { if (role is null) - await BotServices.SendEmbedAsync(ctx, Resources.ERR_ROLE_EXISTING, EmbedType.Warning); + await BotServices.SendEmbedAsync(ctx, Resources.ERR_ROLE_EXISTING, EmbedType.Warning).ConfigureAwait(false); else { - await role.DeleteAsync(); - await BotServices.SendEmbedAsync(ctx, "Successfully removed the server role " + Formatter.Bold(role.Name), EmbedType.Good); + await role.DeleteAsync().ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, "Successfully removed the server role " + Formatter.Bold(role.Name), EmbedType.Good).ConfigureAwait(false); } } @@ -94,19 +94,19 @@ public async Task GetRole(CommandContext ctx, [Description("Server role information to retrieve")] [RemainingText] DiscordRole role = null) { if (role is null) - await BotServices.SendEmbedAsync(ctx, Resources.ERR_ROLE_EXISTING, EmbedType.Warning); + await BotServices.SendEmbedAsync(ctx, Resources.ERR_ROLE_EXISTING, EmbedType.Warning).ConfigureAwait(false); else { var output = new DiscordEmbedBuilder() .WithTitle(role.Name + $" (ID: {role.Id})") .WithDescription($"Created on {role.CreationTimestamp.DateTime.ToString(CultureInfo.InvariantCulture)}") - .AddField("Hoisted", role.IsHoisted ? "YES" : "NO", true) - .AddField("Mentionable", role.IsMentionable ? "YES" : "NO", true) + .AddField("Hoisted", role.IsHoisted ? "Yes" : "No", true) + .AddField("Mentionable", role.IsMentionable ? "Yes" : "No", true) .AddField("Permissions", role.Permissions.ToPermissionString()) .WithThumbnailUrl(ctx.Guild.IconUrl) .WithFooter($"{ctx.Guild.Name} / #{ctx.Channel.Name} / {DateTime.Now}") .WithColor(role.Color); - await ctx.RespondAsync(embed: output.Build()); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } } @@ -120,12 +120,12 @@ public async Task UsersInRole(CommandContext ctx, [Description("Server role")] [RemainingText] DiscordRole role = null) { if (role is null) - await BotServices.SendEmbedAsync(ctx, Resources.ERR_ROLE_EXISTING, EmbedType.Warning); + await BotServices.SendEmbedAsync(ctx, Resources.ERR_ROLE_EXISTING, EmbedType.Warning).ConfigureAwait(false); else { var userCount = 0; var usersList = new StringBuilder(); - var users = (await ctx.Guild.GetAllMembersAsync()).ToArray(); + var users = (await ctx.Guild.GetAllMembersAsync().ConfigureAwait(false)).ToArray(); foreach (var user in users) if (user.Roles.Contains(role)) { @@ -137,9 +137,9 @@ public async Task UsersInRole(CommandContext ctx, } if (usersList.Length == 0) - await BotServices.SendEmbedAsync(ctx, Formatter.Bold(role.Name) + " has no members"); + await BotServices.SendEmbedAsync(ctx, Formatter.Bold(role.Name) + " has no members").ConfigureAwait(false); else - await BotServices.SendEmbedAsync(ctx, Formatter.Bold(role.Name) + $" has **{userCount}** member(s): {usersList}"); + await BotServices.SendEmbedAsync(ctx, Formatter.Bold(role.Name) + $" has **{userCount}** member(s): {usersList}").ConfigureAwait(false); } } @@ -156,13 +156,13 @@ public async Task MentionRole(CommandContext ctx, if (role is null) return; if (role.IsMentionable) { - await role.ModifyAsync(mentionable: false); - await BotServices.SendEmbedAsync(ctx, Formatter.Bold(role.Name) + " is now **not-mentionable**"); + await role.ModifyAsync(mentionable: false).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, Formatter.Bold(role.Name) + " is now **not-mentionable**").ConfigureAwait(false); } else { - await role.ModifyAsync(mentionable: true); - await BotServices.SendEmbedAsync(ctx, Formatter.Bold(role.Name) + " is now **mentionable**"); + await role.ModifyAsync(mentionable: true).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, Formatter.Bold(role.Name) + " is now **mentionable**").ConfigureAwait(false); } } @@ -180,8 +180,8 @@ public async Task RemoveUserRole(CommandContext ctx, if (role != null) { member = member ?? ctx.Member; - await member.RevokeRoleAsync(role); - await BotServices.SendEmbedAsync(ctx, Formatter.Bold(member.DisplayName) + " has been removed from the role " + Formatter.Bold(role.Name), EmbedType.Good); + await member.RevokeRoleAsync(role).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, Formatter.Bold(member.DisplayName) + " has been removed from the role " + Formatter.Bold(role.Name), EmbedType.Good).ConfigureAwait(false); } } @@ -196,13 +196,13 @@ public async Task RemoveUserRoles(CommandContext ctx, [Description("Server user to get revoked")] DiscordMember member) { if (member.Roles.Count() == 0) - await BotServices.SendEmbedAsync(ctx, Resources.ERR_ROLE_NONE, EmbedType.Warning); + await BotServices.SendEmbedAsync(ctx, Resources.ERR_ROLE_NONE, EmbedType.Warning).ConfigureAwait(false); else if (member.Roles.Max(r => r.Position) >= ctx.Member.Roles.Max(r => r.Position)) - await BotServices.SendEmbedAsync(ctx, Resources.ERR_ROLE_NOT_ALLOWED, EmbedType.Warning); + await BotServices.SendEmbedAsync(ctx, Resources.ERR_ROLE_NOT_ALLOWED, EmbedType.Warning).ConfigureAwait(false); else { await member.ReplaceRolesAsync(Enumerable.Empty()).ConfigureAwait(false); - await BotServices.SendEmbedAsync(ctx, "Removed all roles from " + Formatter.Bold(member.DisplayName), EmbedType.Good); + await BotServices.SendEmbedAsync(ctx, "Removed all roles from " + Formatter.Bold(member.DisplayName), EmbedType.Good).ConfigureAwait(false); } } @@ -219,8 +219,8 @@ public async Task SetUserRole(CommandContext ctx, [Description("Server role to assign to the user")] [RemainingText] DiscordRole role) { member = member ?? ctx.Member; - await member.GrantRoleAsync(role); - await BotServices.SendEmbedAsync(ctx, member.DisplayName + " been granted the role " + Formatter.Bold(role.Name), EmbedType.Good); + await member.GrantRoleAsync(role).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, member.DisplayName + " been granted the role " + Formatter.Bold(role.Name), EmbedType.Good).ConfigureAwait(false); } #endregion COMMAND_SETROLE @@ -238,13 +238,13 @@ public async Task SidebarRole(CommandContext ctx, if (role.IsHoisted) { - await role.ModifyAsync(hoist: false); - await BotServices.SendEmbedAsync(ctx, Formatter.Bold(role.Name) + " is now **hidden**"); + await role.ModifyAsync(hoist: false).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, Formatter.Bold(role.Name) + " is now **hidden**").ConfigureAwait(false); } else { - await role.ModifyAsync(hoist: true); - await BotServices.SendEmbedAsync(ctx, Formatter.Bold(role.Name) + " is now **displayed**"); + await role.ModifyAsync(hoist: true).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, Formatter.Bold(role.Name) + " is now **displayed**").ConfigureAwait(false); } } diff --git a/src/FlawBOT.Core/Modules/Server/ServerModule.cs b/src/FlawBOT.Core/Modules/Server/ServerModule.cs index 63488e7a..03ca0f9b 100644 --- a/src/FlawBOT.Core/Modules/Server/ServerModule.cs +++ b/src/FlawBOT.Core/Modules/Server/ServerModule.cs @@ -7,6 +7,7 @@ using FlawBOT.Framework.Services; using System; using System.Globalization; +using System.Linq; using System.Text; using System.Threading.Tasks; @@ -30,12 +31,12 @@ public async Task SetServerAvatar(CommandContext ctx, try { var stream = BotServices.CheckImageInput(ctx, query).Result; - await ctx.Guild.ModifyAsync(chn => chn.Icon = stream); - await BotServices.SendEmbedAsync(ctx, ctx.Guild.Name + " server avatar has been updated!", EmbedType.Good); + await ctx.Guild.ModifyAsync(chn => chn.Icon = stream).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, ctx.Guild.Name + " server avatar has been updated!", EmbedType.Good).ConfigureAwait(false); } catch { - await BotServices.SendEmbedAsync(ctx, ctx.Guild.Name + " server avatar has not been updated!", EmbedType.Error); + await BotServices.SendEmbedAsync(ctx, ctx.Guild.Name + " server avatar has not been updated!", EmbedType.Error).ConfigureAwait(false); } } @@ -49,8 +50,9 @@ public async Task SetServerAvatar(CommandContext ctx, public async Task GetServer(CommandContext ctx) { var output = new DiscordEmbedBuilder() - .WithAuthor($"Owner: {ctx.Guild.Owner.Username}#{ctx.Guild.Owner.Discriminator}", iconUrl: string.IsNullOrEmpty(ctx.Guild.Owner.AvatarHash) ? null : ctx.Guild.Owner.AvatarUrl) - .WithTitle(ctx.Guild.Name + $" (ID: {ctx.Guild.Id.ToString()})") + .WithAuthor($"Owner: {ctx.Guild.Owner.Username}#{ctx.Guild.Owner.Discriminator}", iconUrl: ctx.Guild.Owner.AvatarUrl ?? string.Empty) + .WithTitle(ctx.Guild.Name) + .WithDescription("ID: " + ctx.Guild.Id.ToString()) .AddField("Created on", ctx.Guild.CreationTimestamp.DateTime.ToString(CultureInfo.InvariantCulture), true) .AddField("Member Count", ctx.Guild.MemberCount.ToString(), true) .AddField("Region", ctx.Guild.VoiceRegion.Name.ToUpperInvariant(), true) @@ -70,9 +72,9 @@ public async Task GetServer(CommandContext ctx) var emojis = new StringBuilder(); foreach (var emoji in ctx.Guild.Emojis) - emojis.Append(emoji.Value.Name); + emojis.Append(emoji.Value.Name + (!emoji.Equals(ctx.Guild.Emojis.Last()) ? ", " : string.Empty)); if (emojis.Length != 0) output.AddField("Emojis", emojis.ToString(), true); - await ctx.RespondAsync(embed: output.Build()); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } #endregion COMMAND_INFO @@ -83,7 +85,7 @@ public async Task GetServer(CommandContext ctx) [Description("Retrieve an instant invite link to the server")] public async Task InviteAsync(CommandContext ctx) { - await ctx.RespondAsync("Instant Invite to " + Formatter.Bold(ctx.Guild.Name) + ":https://discord.gg/" + ctx.Channel.CreateInviteAsync().Result.Code); + await ctx.RespondAsync("Instant Invite to " + Formatter.Bold(ctx.Guild.Name) + ":https://discord.gg/" + ctx.Channel.CreateInviteAsync().Result.Code).ConfigureAwait(false); } #endregion COMMAND_INVITE @@ -97,19 +99,19 @@ public async Task PruneUsers(CommandContext ctx, [Description("Number of days the user had to be inactive to get pruned")] [RemainingText] int days = 7) { if (days < 1 || days > 30) - await BotServices.SendEmbedAsync(ctx, "Number of days must be between 1 and 30", EmbedType.Warning); - int count = await ctx.Guild.GetPruneCountAsync(days); + await BotServices.SendEmbedAsync(ctx, "Number of days must be between 1 and 30", EmbedType.Warning).ConfigureAwait(false); + int count = await ctx.Guild.GetPruneCountAsync(days).ConfigureAwait(false); if (count == 0) { - await BotServices.SendEmbedAsync(ctx, "No inactive members found to prune", EmbedType.Warning); + await ctx.RespondAsync("No inactive members found to prune").ConfigureAwait(false); return; } - var prompt = await ctx.RespondAsync($"Pruning will remove {Formatter.Bold(count.ToString())} member(s).\nRespond with **yes** to continue."); - var interactivity = await ctx.Client.GetInteractivity().WaitForMessageAsync(m => m.Channel.Id == ctx.Channel.Id && m.Content.ToLowerInvariant() == "yes", TimeSpan.FromSeconds(10)); + var prompt = await ctx.RespondAsync($"Pruning will remove {Formatter.Bold(count.ToString())} member(s).\nRespond with **yes** to continue.").ConfigureAwait(false); + var interactivity = await ctx.Client.GetInteractivity().WaitForMessageAsync(m => m.Channel.Id == ctx.Channel.Id && m.Content.ToLowerInvariant() == "yes", TimeSpan.FromSeconds(10)).ConfigureAwait(false); if (interactivity.Result is null) return; - await BotServices.RemoveMessage(interactivity.Result); - await BotServices.RemoveMessage(prompt); - await ctx.Guild.PruneAsync(days); + await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); + await BotServices.RemoveMessage(prompt).ConfigureAwait(false); + await ctx.Guild.PruneAsync(days).ConfigureAwait(false); } #endregion COMMAND_PRUNE @@ -124,11 +126,11 @@ public async Task SetServerName(CommandContext ctx, [Description("New server name")] [RemainingText] string name = "") { if (string.IsNullOrWhiteSpace(name) || (name.Length > 100)) - await BotServices.SendEmbedAsync(ctx, "Server name cannot be blank or over 100 characters!", EmbedType.Warning); + await BotServices.SendEmbedAsync(ctx, "Server name cannot be blank or over 100 characters!", EmbedType.Warning).ConfigureAwait(false); else { - await ctx.Guild.ModifyAsync(srv => srv.Name = name); - await BotServices.SendEmbedAsync(ctx, "Server name has been changed to " + Formatter.Bold(name), EmbedType.Good); + await ctx.Guild.ModifyAsync(srv => srv.Name = name).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, "Server name has been changed to " + Formatter.Bold(name), EmbedType.Good).ConfigureAwait(false); } } @@ -154,7 +156,7 @@ public async Task Warn(CommandContext ctx, if (!string.IsNullOrWhiteSpace(reason)) output.AddField("Warning message:", reason); var dm = await member.CreateDmChannelAsync().ConfigureAwait(false); if (dm is null) - await BotServices.SendEmbedAsync(ctx, "Unable to direct message this user", EmbedType.Warning); + await BotServices.SendEmbedAsync(ctx, "Unable to direct message this user", EmbedType.Warning).ConfigureAwait(false); else { await dm.SendMessageAsync(embed: output.Build()).ConfigureAwait(false); diff --git a/src/FlawBOT.Core/Modules/Server/UserModule.cs b/src/FlawBOT.Core/Modules/Server/UserModule.cs index b0e7714c..ea970c1d 100644 --- a/src/FlawBOT.Core/Modules/Server/UserModule.cs +++ b/src/FlawBOT.Core/Modules/Server/UserModule.cs @@ -20,18 +20,17 @@ public class UserModule : BaseCommandModule #region COMMAND_AVATAR [Command("avatar")] - [Aliases("getavatar")] - [Description("Retrieve server user's avatar")] + [Aliases("getavatar", "image", "pfp")] + [Description("Retrieve server user's profile picture")] public async Task GetAvatar(CommandContext ctx, - [Description("Server user whose avatar to retrieve")] [RemainingText] DiscordMember member) + [Description("Server user whose profile picture to retrieve")] [RemainingText] DiscordMember member) { member = member ?? ctx.Member; var output = new DiscordEmbedBuilder() - .WithDescription(member.Mention + "'s avatar...") .WithImageUrl(member.AvatarUrl) - .WithUrl("https://images.google.com/searchbyimage?image_url=" + member.AvatarUrl) + .WithUrl("https://images.google.com/searchbyimage?image_url=" + member.AvatarUrl) // UNUSED .WithColor(DiscordColor.Lilac); - await ctx.RespondAsync(embed: output.Build()); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } #endregion COMMAND_AVATAR @@ -46,13 +45,12 @@ public async Task Ban(CommandContext ctx, [Description("Reason for the ban")] [RemainingText] string reason = null) { if (ctx.Member.Id == member.Id) - await BotServices.SendEmbedAsync(ctx, "You cannot ban yourself!", EmbedType.Warning); + await ctx.RespondAsync("You cannot ban yourself.").ConfigureAwait(false); else { - var ustr = $"{ctx.User.Username}#{ctx.User.Discriminator} ({ctx.User.Id})"; - var rstr = string.IsNullOrWhiteSpace(reason) ? "No reason provided" : $": {reason}"; - await ctx.Guild.BanMemberAsync(member, 7, ustr + ": " + rstr); - await BotServices.SendEmbedAsync(ctx, $"Banned: {member.DisplayName}#{member.Discriminator} (ID:{member.Id})\nReason: {rstr}\nBanned by: {ctx.Member.DisplayName}#{ctx.Member.Discriminator}", EmbedType.Good); + await ctx.Guild.BanMemberAsync(member, 7, reason).ConfigureAwait(false); + await BotServices.RemoveMessage(ctx.Message).ConfigureAwait(false); + await BotServices.SendUserStateChangeAsync(ctx, UserStateChange.Ban, member, reason ?? "No reason provided."); } } @@ -61,6 +59,7 @@ public async Task Ban(CommandContext ctx, #region COMMAND_DEAFEN [Command("deafen")] + [Aliases("deaf")] [Description("Deafen server user")] [RequirePermissions(Permissions.DeafenMembers)] public async Task Deafen(CommandContext ctx, @@ -68,12 +67,12 @@ public async Task Deafen(CommandContext ctx, [Description("Reason for the deafen")] [RemainingText] string reason = null) { if (member.IsDeafened) - await BotServices.SendEmbedAsync(ctx, $"{member.DisplayName}#{member.Discriminator} is already **deafened**!", EmbedType.Warning); + await ctx.RespondAsync($"{member.DisplayName}#{member.Discriminator} is already **deafened**.").ConfigureAwait(false); else { - var rstr = string.IsNullOrWhiteSpace(reason) ? "No reason provided" : $": {reason}"; - await member.SetDeafAsync(true, rstr); - await BotServices.SendEmbedAsync(ctx, $"Deafened: {member.DisplayName}#{member.Discriminator} (ID:{member.Id})\nReason: {rstr}\nDeafened by: {ctx.Member.DisplayName}#{ctx.Member.Discriminator}", EmbedType.Good); + await member.SetDeafAsync(true, reason).ConfigureAwait(false); + await BotServices.RemoveMessage(ctx.Message).ConfigureAwait(false); + await BotServices.SendUserStateChangeAsync(ctx, UserStateChange.Deafen, member, reason ?? "No reason provided."); } } @@ -93,12 +92,12 @@ public async Task GetUser(CommandContext ctx, var perms = permsobj.ToPermissionString(); var output = new DiscordEmbedBuilder() .WithTitle($"@{member.Username}#{member.Discriminator}") - .WithDescription("Nickname: ") - .AddField("ID", member.Id.ToString(), true) + .WithDescription("ID: " + member.Id.ToString()) .AddField("Registered on", member.CreationTimestamp.DateTime.ToString(CultureInfo.InvariantCulture), true) .AddField("Joined on", member.JoinedAt.DateTime.ToString(CultureInfo.InvariantCulture), true) - .AddField("Muted?", member.IsMuted ? "YES" : "NO", true) - .AddField("Deafened?", member.IsDeafened ? "YES" : "NO", true) + .AddField("Nickname", member.Nickname ?? "None", true) + .AddField("Muted?", member.IsMuted ? "Yes" : "No", true) + .AddField("Deafened?", member.IsDeafened ? "Yes" : "No", true) .WithThumbnailUrl(member.AvatarUrl) .WithFooter($"{ctx.Guild.Name} / #{ctx.Channel.Name} / {DateTime.Now}") .WithColor(member.Color); @@ -106,21 +105,15 @@ public async Task GetUser(CommandContext ctx, output.Title += " __[BOT]__ "; if (member.IsOwner) output.Title += " __[OWNER]__ "; - output.AddField("Verified?", member.Verified == true ? "YES" : "NO", true); - output.AddField("Secured?", member.MfaEnabled == true ? "YES" : "NO", true); - if (!string.IsNullOrWhiteSpace(member.Nickname)) - output.Description += member.Nickname; + output.AddField("Verified?", member.Verified == true ? "Yes" : "No", true); foreach (var role in member.Roles) roles.Append($"[`{role.Name}`] "); - if (roles.Length == 0) - roles.Append("*None*"); - output.AddField("Roles", roles.ToString(), true); + if (roles.Length > 0) + output.AddField("Roles", roles.ToString(), true); if (((permsobj & Permissions.Administrator) | (permsobj & Permissions.AccessChannels)) == 0) perms = $"**This user cannot see this channel!**\n{perms}"; - if (string.IsNullOrWhiteSpace(perms)) - perms = "*None*"; - output.AddField("Permissions", perms); - await ctx.RespondAsync(embed: output.Build()); + output.AddField("Permissions", perms ?? "*None*"); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } #endregion COMMAND_INFO @@ -128,6 +121,7 @@ public async Task GetUser(CommandContext ctx, #region COMMAND_KICK [Command("kick")] + [Aliases("remove")] [Description("Kick server user")] [RequirePermissions(Permissions.KickMembers)] public async Task Kick(CommandContext ctx, @@ -135,13 +129,12 @@ public async Task Kick(CommandContext ctx, [Description("Reason for the kick")] [RemainingText] string reason = null) { if (ctx.Member.Id == member.Id) - await BotServices.SendEmbedAsync(ctx, "You cannot kick yourself!", EmbedType.Warning); + await ctx.RespondAsync("You cannot kick yourself.").ConfigureAwait(false); else { - var ustr = $"{ctx.User.Username}#{ctx.User.Discriminator} ({ctx.User.Id})"; - var rstr = string.IsNullOrWhiteSpace(reason) ? "No reason provided" : $": {reason}"; - await member.RemoveAsync($"{ustr}: {rstr}"); - await BotServices.SendEmbedAsync(ctx, $"Kicked: {member.DisplayName}#{member.Discriminator} (ID:{member.Id})\nReason: {rstr}\nKicked by {ctx.Member.DisplayName}#{ctx.Member.Discriminator}", EmbedType.Good); + await member.RemoveAsync(reason).ConfigureAwait(false); + await BotServices.RemoveMessage(ctx.Message).ConfigureAwait(false); + await BotServices.SendUserStateChangeAsync(ctx, UserStateChange.Kick, member, reason ?? "No reason provided."); } } @@ -150,6 +143,7 @@ public async Task Kick(CommandContext ctx, #region COMMAND_MUTE [Command("mute")] + [Aliases("silence")] [Description("Mute server user")] [RequirePermissions(Permissions.MuteMembers)] public async Task Mute(CommandContext ctx, @@ -157,13 +151,12 @@ public async Task Mute(CommandContext ctx, [Description("Reason for the mute")] [RemainingText] string reason = null) { if (member.IsMuted) - await BotServices.SendEmbedAsync(ctx, $"{member.DisplayName}#{member.Discriminator} is already **muted**!", EmbedType.Warning); + await ctx.RespondAsync($"{member.DisplayName}#{member.Discriminator} is already **muted**.").ConfigureAwait(false); else { - var ustr = $"{ctx.User.Username}#{ctx.User.Discriminator} ({ctx.User.Id})"; - var rstr = string.IsNullOrWhiteSpace(reason) ? "No reason provided" : $": {reason}"; - await member.SetMuteAsync(true, $"{ustr}: {rstr}"); - await BotServices.SendEmbedAsync(ctx, $"Muted: {member.DisplayName}#{member.Discriminator} (ID:{member.Id})\nReason: {rstr}\nMuted by {ctx.Member.DisplayName}#{ctx.Member.Discriminator}", EmbedType.Good); + await member.SetMuteAsync(true, reason).ConfigureAwait(false); + await BotServices.RemoveMessage(ctx.Message).ConfigureAwait(false); + await BotServices.SendUserStateChangeAsync(ctx, UserStateChange.Mute, member, reason ?? "No reason provided."); } } @@ -172,7 +165,7 @@ public async Task Mute(CommandContext ctx, #region COMMAND_NICKNAME [Command("nickname")] - [Aliases("setnick")] + [Aliases("setnick", "nick")] [Description("Set server user's nickname")] [RequireUserPermissions(Permissions.ChangeNickname)] public async Task SetUserName(CommandContext ctx, @@ -181,9 +174,9 @@ public async Task SetUserName(CommandContext ctx, { member = member ?? ctx.Member; var nickname = member.DisplayName; - await member.ModifyAsync(usr => usr.Nickname = name); - if (!string.IsNullOrWhiteSpace(name)) - await BotServices.SendEmbedAsync(ctx, $"{nickname}'s nickname has been changed to **{name}**", EmbedType.Good); + await member.ModifyAsync(usr => usr.Nickname = name).ConfigureAwait(false); + var response = (!string.IsNullOrWhiteSpace(name)) ? $"{nickname}'s nickname has been changed to **{name}**" : $"{nickname}'s nickname has been reset."; + await BotServices.SendEmbedAsync(ctx, response, EmbedType.Good).ConfigureAwait(false); } #endregion COMMAND_NICKNAME @@ -206,7 +199,7 @@ public async Task ListServerPermissions(CommandContext ctx, .WithTitle($"Permissions for {member.Username} in #{channel.Name}:") .WithDescription(perms) .WithColor(DiscordColor.Turquoise); - await ctx.RespondAsync(embed: output.Build()); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } #endregion COMMAND_PERMS @@ -217,11 +210,13 @@ public async Task ListServerPermissions(CommandContext ctx, [Description("Unban server user")] [RequirePermissions(Permissions.BanMembers)] public async Task Remove(CommandContext ctx, - [Description("Discord user ID to unban from the server")] ulong userID) + [Description("Discord user ID to unban from the server")] ulong userID, + [Description("Reason for the deafen")] [RemainingText] string reason = null) { var member = await ctx.Client.GetUserAsync(userID).ConfigureAwait(false); - await ctx.Guild.UnbanMemberAsync(member).ConfigureAwait(false); - await BotServices.SendEmbedAsync(ctx, $"Unbanned {member.Username}#{member.Discriminator} (ID:{member.Id})", EmbedType.Good); + await ctx.Guild.UnbanMemberAsync(member, reason ?? "No reason provided.").ConfigureAwait(false); + await BotServices.RemoveMessage(ctx.Message).ConfigureAwait(false); + await ctx.RespondAsync($"Unbanned Discord User #{member} from the server.").ConfigureAwait(false); } #endregion COMMAND_UNBAN @@ -229,13 +224,16 @@ public async Task Remove(CommandContext ctx, #region COMMAND_UNDEAFEN [Command("undeafen")] + [Aliases("undeaf")] [Description("Undeafen server user")] [RequirePermissions(Permissions.DeafenMembers)] public async Task Undeafen(CommandContext ctx, - [Description("Server user to undeafen")] [RemainingText] DiscordMember member) + [Description("Server user to undeafen")] [RemainingText] DiscordMember member, + [Description("Reason for the deafen")] [RemainingText] string reason = null) { - await member.SetDeafAsync(false); - await BotServices.SendEmbedAsync(ctx, $"Undeafened {member.DisplayName}#{member.Discriminator} (ID:{member.Id})", EmbedType.Good); + await member.SetDeafAsync(false, reason).ConfigureAwait(false); + await BotServices.RemoveMessage(ctx.Message).ConfigureAwait(false); + await BotServices.SendUserStateChangeAsync(ctx, UserStateChange.Undeafen, member, reason ?? "No reason provided"); } #endregion COMMAND_UNDEAFEN @@ -246,13 +244,27 @@ public async Task Undeafen(CommandContext ctx, [Description("Unmute server user")] [RequirePermissions(Permissions.MuteMembers)] public async Task Unmute(CommandContext ctx, - [Description("Server user to unmute")] [RemainingText] DiscordMember member) + [Description("Server user to unmute")] [RemainingText] DiscordMember member, + [Description("Reason for the deafen")] [RemainingText] string reason = null) { - var ustr = $"{ctx.User.Username}#{ctx.User.Discriminator} (ID: {ctx.User.Id})"; - await member.SetMuteAsync(false, ustr); - await BotServices.SendEmbedAsync(ctx, $"Unmuted {member.Username}#{member.Discriminator} (ID:{member.Id})", EmbedType.Good); + await member.SetMuteAsync(false, reason).ConfigureAwait(false); + await BotServices.RemoveMessage(ctx.Message).ConfigureAwait(false); + await BotServices.SendUserStateChangeAsync(ctx, UserStateChange.Unmute, member, reason ?? "No reason provided"); } #endregion COMMAND_UNMUTE } -} \ No newline at end of file +} + +- Code cleanup and improvements. +- Updated NuGet packages. +- Updated Twitch and Steam command API services. +- Reorganized the command modules. +- Removed the Smash Bros. command. +- Updated outputs for pokemon, speedrun, steam user, tf2 schema, tf2 server, poll +- Fixed twitch command not returning results if the follower count is outside the integer range. +- Fixed the tf2 item schema command not returning higher quality images when available. +- Certain commands will now remove user messages that called the command. +- The poll command is now preset to run for three minutes. +- Fixed an issue where last paginated item would be removed if it was the last one on the list when the user calls for the next list item. +- Fixed various text grammar and wording inconsistencies. \ No newline at end of file diff --git a/src/FlawBOT.Core/Program.cs b/src/FlawBOT.Core/Program.cs index 1ce52f50..132b42a6 100644 --- a/src/FlawBOT.Core/Program.cs +++ b/src/FlawBOT.Core/Program.cs @@ -41,7 +41,7 @@ public static async Task Main(string[] args) Console.WriteLine($"Inner exception: {e.InnerException.GetType()} :\n{e.InnerException.Message}"); Console.ReadKey(); } - Console.WriteLine("\nPowering off..."); + Console.WriteLine("\nShutting down..."); } public async Task RunBotAsync() @@ -54,7 +54,7 @@ public async Task RunBotAsync() Token = TokenHandler.Tokens.DiscordToken, TokenType = TokenType.Bot, AutoReconnect = true, - LogLevel = LogLevel.Info, + LogLevel = LogLevel.Debug, UseInternalLogHandler = false, GatewayCompressionLevel = GatewayCompressionLevel.Stream, LargeThreshold = 250 @@ -77,12 +77,9 @@ public async Task RunBotAsync() }); Commands.CommandExecuted += Commands_CommandExecuted; Commands.CommandErrored += Commands_CommandErrored; - Commands.SetHelpFormatter(); - Commands.RegisterCommands(); - Commands.RegisterCommands(); Commands.RegisterCommands(); Commands.RegisterCommands(); - Commands.RegisterCommands(); + Commands.RegisterCommands(); Commands.RegisterCommands(); Commands.RegisterCommands(); Commands.RegisterCommands(); @@ -96,15 +93,16 @@ public async Task RunBotAsync() Commands.RegisterCommands(); Commands.RegisterCommands(); Commands.RegisterCommands(); - Commands.RegisterCommands(); Commands.RegisterCommands(); Commands.RegisterCommands(); Commands.RegisterCommands(); + Commands.RegisterCommands(); Commands.RegisterCommands(); Commands.RegisterCommands(); Commands.RegisterCommands(); Commands.RegisterCommands(); Commands.RegisterCommands(); + Commands.SetHelpFormatter(); // Start the uptime counter Console.Title = SharedData.Name + " (" + SharedData.Version + ")"; @@ -112,19 +110,19 @@ public async Task RunBotAsync() await SteamService.UpdateSteamListAsync().ConfigureAwait(false); await TeamFortressService.LoadTF2SchemaAsync().ConfigureAwait(false); await PokemonService.UpdatePokemonListAsync().ConfigureAwait(false); - await Client.ConnectAsync(); // Connect and log into Discord + await Client.ConnectAsync().ConfigureAwait(false); // Connect and log into Discord await Task.Delay(-1).ConfigureAwait(false); // Prevent the console window from closing } private static Task Client_Ready(ReadyEventArgs e) { - e.Client.DebugLogger.LogMessage(LogLevel.Info, SharedData.Name, SharedData.Name + ", version: " + SharedData.Version, DateTime.Now); + e.Client.DebugLogger.LogMessage(LogLevel.Info, SharedData.Name, $"{SharedData.Name}, version: {SharedData.Version}", DateTime.Now); return Task.CompletedTask; } private static Task Client_ClientError(ClientErrorEventArgs e) { - e.Client.DebugLogger.LogMessage(LogLevel.Error, SharedData.Name, "Exception occurred: " + e.Exception.GetType() + ": " + e.Exception.Message, DateTime.Now); + e.Client.DebugLogger.LogMessage(LogLevel.Error, SharedData.Name, $"Exception occurred: {e.Exception.GetType()}: {e.Exception.Message}", DateTime.Now); return Task.CompletedTask; } @@ -145,33 +143,33 @@ private static async Task Commands_CommandErrored(CommandErrorEventArgs e) return; default: - await BotServices.SendEmbedAsync(e.Context, $"Command **{e.Command.QualifiedName}** could not be executed.", EmbedType.Error); + await BotServices.SendEmbedAsync(e.Context, $"Command **{e.Command.QualifiedName}** could not be executed.", EmbedType.Error).ConfigureAwait(false); foreach (var check in cfe.FailedChecks) { switch (check) { case RequirePermissionsAttribute perms: - await BotServices.SendEmbedAsync(e.Context, $"- One of us does not have the required permissions ({perms.Permissions.ToPermissionString()})!", EmbedType.Error); + await BotServices.SendEmbedAsync(e.Context, $"- One of us does not have the required permissions ({perms.Permissions.ToPermissionString()})!", EmbedType.Error).ConfigureAwait(false); break; case RequireUserPermissionsAttribute uperms: - await BotServices.SendEmbedAsync(e.Context, $"- You do not have sufficient permissions ({uperms.Permissions.ToPermissionString()})!", EmbedType.Error); + await BotServices.SendEmbedAsync(e.Context, $"- You do not have sufficient permissions ({uperms.Permissions.ToPermissionString()})!", EmbedType.Error).ConfigureAwait(false); break; case RequireBotPermissionsAttribute bperms: - await BotServices.SendEmbedAsync(e.Context, $"- I do not have sufficient permissions ({bperms.Permissions.ToPermissionString()})!", EmbedType.Error); + await BotServices.SendEmbedAsync(e.Context, $"- I do not have sufficient permissions ({bperms.Permissions.ToPermissionString()})!", EmbedType.Error).ConfigureAwait(false); break; case RequireOwnerAttribute _: - await BotServices.SendEmbedAsync(e.Context, $"- This command is reserved only for the bot owner.", EmbedType.Error); + await BotServices.SendEmbedAsync(e.Context, $"- This command is reserved only for the bot owner.", EmbedType.Error).ConfigureAwait(false); break; case RequirePrefixesAttribute pa: - await BotServices.SendEmbedAsync(e.Context, $"- This command can only be invoked with the following prefixes: {string.Join(" ", pa.Prefixes)}.", EmbedType.Error); + await BotServices.SendEmbedAsync(e.Context, $"- This command can only be invoked with the following prefixes: {string.Join(" ", pa.Prefixes)}.", EmbedType.Error).ConfigureAwait(false); break; default: - await BotServices.SendEmbedAsync(e.Context, "Unknown check triggered. Please notify the developer using the command *.bot report*", EmbedType.Error); + await BotServices.SendEmbedAsync(e.Context, "Unknown check triggered. Please notify the developer using the command *.bot report*", EmbedType.Error).ConfigureAwait(false); break; } } @@ -188,24 +186,24 @@ private static async Task Commands_CommandErrored(CommandErrorEventArgs e) break; case ArgumentNullException _: - await BotServices.SendEmbedAsync(e.Context, "Not enough arguments supplied to the command!", EmbedType.Error); + await BotServices.SendEmbedAsync(e.Context, "Not enough arguments supplied to the command!", EmbedType.Error).ConfigureAwait(false); break; case ArgumentException _: if (e.Exception.Message.Contains("Not enough arguments supplied to the command")) - await BotServices.SendEmbedAsync(e.Context, "Not enough arguments supplied to the command!", EmbedType.Error); + await BotServices.SendEmbedAsync(e.Context, "Not enough arguments supplied to the command!", EmbedType.Error).ConfigureAwait(false); break; case InvalidDataException _: if (e.Exception.Message.Contains("The data within the stream was not valid image data")) - await BotServices.SendEmbedAsync(e.Context, "Provided URL is not an image type!", EmbedType.Error); + await BotServices.SendEmbedAsync(e.Context, "Provided URL is not an image type!", EmbedType.Error).ConfigureAwait(false); break; default: if (e.Exception.Message.Contains("Given emote was not found")) - await BotServices.SendEmbedAsync(e.Context, "Suggested emote was not found!", EmbedType.Error); + await BotServices.SendEmbedAsync(e.Context, "Suggested emote was not found!", EmbedType.Error).ConfigureAwait(false); if (e.Exception.Message.Contains("Unauthorized: 403")) - await BotServices.SendEmbedAsync(e.Context, "Insufficient Permissions", EmbedType.Error); + await BotServices.SendEmbedAsync(e.Context, "Insufficient Permissions", EmbedType.Error).ConfigureAwait(false); else e.Context.Client.DebugLogger.LogMessage(LogLevel.Error, SharedData.Name, $"{e.Context.User.Username} tried executing '{e.Command?.QualifiedName ?? ""}' but it errored: {e.Exception.GetType()}: {e.Exception.Message ?? ""}", DateTime.Now); // DEBUG ONLY break; diff --git a/src/FlawBOT.Core/Properties/Resources.Designer.cs b/src/FlawBOT.Core/Properties/Resources.Designer.cs index 67e3ed6e..644bd8fa 100644 --- a/src/FlawBOT.Core/Properties/Resources.Designer.cs +++ b/src/FlawBOT.Core/Properties/Resources.Designer.cs @@ -60,15 +60,6 @@ internal Resources() { } } - /// - /// Looks up a localized string similar to :8ball: The almighty 8 ball requests a question.. - /// - internal static string ERR_8BALL_QUESTION { - get { - return ResourceManager.GetString("ERR_8BALL_QUESTION", resourceCulture); - } - } - /// /// Looks up a localized string similar to Channel with the same name already exists.. /// @@ -106,7 +97,7 @@ internal static string ERR_COLOR_INVALID { } /// - /// Looks up a localized string similar to Unable to retrieve a dog photo.. + /// Looks up a localized string similar to Unable to reach the Dog.CEO API.. /// internal static string ERR_DOG_PHOTO { get { @@ -169,16 +160,7 @@ internal static string ERR_NASA_API { } /// - /// Looks up a localized string similar to Invalid number of minutes, try **.poll 3 Should I order pizza tonight?**. - /// - internal static string ERR_POLL_MINUTES { - get { - return ResourceManager.GetString("ERR_POLL_MINUTES", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to You need to provide a poll question.. + /// Looks up a localized string similar to Please provide a poll question.. /// internal static string ERR_POLL_QUESTION { get { @@ -250,7 +232,7 @@ internal static string NOT_FOUND_EMOJI { } /// - /// Looks up a localized string similar to No results found!. + /// Looks up a localized string similar to No results found.. /// internal static string NOT_FOUND_GENERIC { get { @@ -259,7 +241,7 @@ internal static string NOT_FOUND_GENERIC { } /// - /// Looks up a localized string similar to Location not found!. + /// Looks up a localized string similar to Location not found.. /// internal static string NOT_FOUND_LOCATION { get { @@ -268,7 +250,7 @@ internal static string NOT_FOUND_LOCATION { } /// - /// Looks up a localized string similar to Reddit post not found!. + /// Looks up a localized string similar to Reddit post not found.. /// internal static string NOT_FOUND_REDDIT { get { @@ -277,16 +259,7 @@ internal static string NOT_FOUND_REDDIT { } /// - /// Looks up a localized string similar to Smash character not found or not yet available! See the available characters here: http://kuroganehammer.com/Ultimate. - /// - internal static string NOT_FOUND_SMASH { - get { - return ResourceManager.GetString("NOT_FOUND_SMASH", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Subreddit not found!. + /// Looks up a localized string similar to Subreddit not found.. /// internal static string NOT_FOUND_SUBREDDIT { get { @@ -295,7 +268,7 @@ internal static string NOT_FOUND_SUBREDDIT { } /// - /// Looks up a localized string similar to Twitch channel not found or it's offline.. + /// Looks up a localized string similar to Twitch channel is offline or was not found.. /// internal static string NOT_FOUND_TWITCH { get { diff --git a/src/FlawBOT.Core/Properties/Resources.resx b/src/FlawBOT.Core/Properties/Resources.resx index a9fe45ea..9b2a4710 100644 --- a/src/FlawBOT.Core/Properties/Resources.resx +++ b/src/FlawBOT.Core/Properties/Resources.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - :8ball: The almighty 8 ball requests a question. - Channel with the same name already exists. @@ -133,7 +130,7 @@ Invalid color code. Please enter a HEX color code like #E7B53B. - Unable to retrieve a dog photo. + Unable to reach the Dog.CEO API. Unable to create the server emoji. Either the HTTP request failed or Discord prevented the operation from completing. @@ -153,11 +150,8 @@ Unable to reach the NASA API. - - Invalid number of minutes, try **.poll 3 Should I order pizza tonight?** - - You need to provide a poll question. + Please provide a poll question. Please provide more information on the issue (50 characters minimum). @@ -181,22 +175,19 @@ Emoji not found in the server list. - No results found! + No results found. - Location not found! + Location not found. - Reddit post not found! - - - Smash character not found or not yet available! See the available characters here: http://kuroganehammer.com/Ultimate + Reddit post not found. - Subreddit not found! + Subreddit not found. - Twitch channel not found or it's offline. + Twitch channel is offline or was not found. Wikipedia page not found. diff --git a/src/FlawBOT.Core/Properties/launchSettings.json b/src/FlawBOT.Core/Properties/launchSettings.json deleted file mode 100644 index 7276b364..00000000 --- a/src/FlawBOT.Core/Properties/launchSettings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "profiles": { - "FlawBOT.Core": { - "commandName": "Project" - }, - "Docker": { - "commandName": "Docker" - } - } -} \ No newline at end of file diff --git a/src/FlawBOT.Framework/FlawBOT.Framework.csproj b/src/FlawBOT.Framework/FlawBOT.Framework.csproj index aca23d61..51ebffd6 100644 --- a/src/FlawBOT.Framework/FlawBOT.Framework.csproj +++ b/src/FlawBOT.Framework/FlawBOT.Framework.csproj @@ -2,7 +2,7 @@ netcoreapp3.0 - 2.4.1.0 + 2.4.0.0 2.4.0 2.4.0.0 CriticalFlaw @@ -15,17 +15,17 @@ - - - - + + + + - + - - - + + + diff --git a/src/FlawBOT.Framework/Models/Games/SmashData.cs b/src/FlawBOT.Framework/Models/Games/SmashData.cs deleted file mode 100644 index 4010e347..00000000 --- a/src/FlawBOT.Framework/Models/Games/SmashData.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Newtonsoft.Json; -using System.Collections.Generic; - -namespace FlawBOT.Framework.Models -{ - public class SmashCharacter - { - [JsonProperty("InstanceId")] - public string InstanceId { get; set; } - - [JsonProperty("Name")] - public string Name { get; set; } - - [JsonProperty("OwnerId")] - public int OwnerId { get; set; } - - [JsonProperty("FullUrl")] - public string FullUrl { get; set; } - - [JsonProperty("MainImageUrl")] - public string MainImageUrl { get; set; } - - [JsonProperty("ThumbnailUrl")] - public string ThumbnailUrl { get; set; } - - [JsonProperty("ColorTheme")] - public string ColorTheme { get; set; } - - [JsonProperty("DisplayName")] - public string DisplayName { get; set; } - - [JsonProperty("Game")] - public string Game { get; set; } - } - - public class SmashCharacterAttributes - { - [JsonProperty("Name")] - public string Name { get; set; } - - [JsonProperty("Values")] - public List Attributes { get; set; } - } - - public class Attribute - { - [JsonProperty("Name")] - public string Name { get; set; } - - [JsonProperty("Value")] - public string Value { get; set; } - } -} \ No newline at end of file diff --git a/src/FlawBOT.Framework/Models/Search/TwitchData.cs b/src/FlawBOT.Framework/Models/Search/TwitchData.cs index 072bfeba..774f73a5 100644 --- a/src/FlawBOT.Framework/Models/Search/TwitchData.cs +++ b/src/FlawBOT.Framework/Models/Search/TwitchData.cs @@ -1,140 +1,68 @@ using Newtonsoft.Json; -using System; +using System.Collections.Generic; namespace FlawBOT.Framework.Models { public class TwitchData { - [JsonProperty("stream")] - public Stream Stream { get; set; } + [JsonProperty("data")] + public List Stream { get; set; } } public class Stream { - [JsonProperty("game")] - public string Game { get; set; } + [JsonProperty("id")] + public string ID { get; set; } - [JsonProperty("viewers")] - public int Viewers { get; set; } + [JsonProperty("user_id")] + public int UserID { get; set; } - [JsonProperty("average_fps")] - public double AverageFPS { get; set; } + [JsonProperty("user_name")] + public string UserName { get; set; } - [JsonProperty("delay")] - public int Delay { get; set; } + [JsonProperty("game_id")] + public int GameID { get; set; } - [JsonProperty("created_at")] - public DateTime CreatedAt { get; set; } + [JsonProperty("type")] + public string Type { get; set; } - [JsonProperty("stream_type")] - public string StreamType { get; set; } + [JsonProperty("title")] + public string Title { get; set; } - [JsonProperty("channel")] - public Channel Channel { get; set; } - } - - public class Channel - { - [JsonProperty("mature")] - public bool IsMature { get; set; } - - [JsonProperty("partner")] - public bool IsPartner { get; set; } + [JsonProperty("viewer_count")] + public int ViewCount { get; set; } - [JsonProperty("status")] - public string Status { get; set; } - - [JsonProperty("broadcaster_language")] - public string BroadcasterLanguage { get; set; } - - [JsonProperty("broadcaster_software")] - public string Software { get; set; } - - [JsonProperty("display_name")] - public string DisplayName { get; set; } - - [JsonProperty("game")] - public string Game { get; set; } + [JsonProperty("started_at")] + public string StartTime { get; set; } [JsonProperty("language")] public string Language { get; set; } - [JsonProperty("_id")] - public int Id { get; set; } - - [JsonProperty("name")] - public string Name { get; set; } - - [JsonProperty("created_at")] - public DateTime CreatedAt { get; set; } - - [JsonProperty("updated_at")] - public DateTime UpdatedAt { get; set; } - - [JsonProperty("delay")] - public object Delay { get; set; } - - [JsonProperty("logo")] - public string Logo { get; set; } - - [JsonProperty("banner")] - public object Banner { get; set; } - - [JsonProperty("video_banner")] - public string VideoBanner { get; set; } - - [JsonProperty("background")] - public object Background { get; set; } - - [JsonProperty("profile_banner")] - public string ProfileBanner { get; set; } - - [JsonProperty("profile_banner_background_color")] - public string ProfileBannerColor { get; set; } - - [JsonProperty("url")] - public string Url { get; set; } - - [JsonProperty("views")] - public int Views { get; set; } - - [JsonProperty("followers")] - public int Followers { get; set; } - - [JsonProperty("_links")] - public Links Links { get; set; } + [JsonProperty("thumbnail_url")] + public string ThumbnailUrl { get; set; } } - public class Links + public class Streamer { - [JsonProperty("self")] - public string Self { get; set; } - - [JsonProperty("follows")] - public string Follows { get; set; } - - [JsonProperty("commercial")] - public string Commercial { get; set; } + [JsonProperty("id")] + public int ID { get; set; } - [JsonProperty("stream_key")] - public string StreamKey { get; set; } + [JsonProperty("login")] + public string Login { get; set; } - [JsonProperty("chat")] - public string Chat { get; set; } - - [JsonProperty("features")] - public string Features { get; set; } + [JsonProperty("display_name")] + public string DisplayName { get; set; } - [JsonProperty("subscriptions")] - public string Subscriptions { get; set; } + [JsonProperty("description")] + public string Description { get; set; } - [JsonProperty("editors")] - public string Editors { get; set; } + [JsonProperty("profile_image_url")] + public string ProfileImageUrl { get; set; } - [JsonProperty("teams")] - public string Teams { get; set; } + [JsonProperty("offline_image_url")] + public string OfflineImageUrl { get; set; } - [JsonProperty("videos")] - public string Videos { get; set; } + [JsonProperty("view_count")] + public int ViewCount { get; set; } } } \ No newline at end of file diff --git a/src/FlawBOT.Framework/Models/Bot/BotData.cs b/src/FlawBOT.Framework/Models/Server/BotData.cs similarity index 89% rename from src/FlawBOT.Framework/Models/Bot/BotData.cs rename to src/FlawBOT.Framework/Models/Server/BotData.cs index 6336b1c6..81e1c61e 100644 --- a/src/FlawBOT.Framework/Models/Bot/BotData.cs +++ b/src/FlawBOT.Framework/Models/Server/BotData.cs @@ -49,4 +49,15 @@ public enum EmbedType Missing, Error } + + public enum UserStateChange + { + Ban, + Deafen, + Kick, + Mute, + Unban, + Undeafen, + Unmute + } } \ No newline at end of file diff --git a/src/FlawBOT.Framework/Models/Bot/BotHandlers.cs b/src/FlawBOT.Framework/Models/Server/BotHandlers.cs similarity index 100% rename from src/FlawBOT.Framework/Models/Bot/BotHandlers.cs rename to src/FlawBOT.Framework/Models/Server/BotHandlers.cs diff --git a/src/FlawBOT.Framework/Properties/Resources.Designer.cs b/src/FlawBOT.Framework/Properties/Resources.Designer.cs index af1e15c1..47641a74 100644 --- a/src/FlawBOT.Framework/Properties/Resources.Designer.cs +++ b/src/FlawBOT.Framework/Properties/Resources.Designer.cs @@ -232,7 +232,7 @@ internal static string API_TradeTF { } /// - /// Looks up a localized string similar to https://api.twitch.tv/helix/streams/. + /// Looks up a localized string similar to https://api.twitch.tv/helix/. /// internal static string API_Twitch { get { diff --git a/src/FlawBOT.Framework/Properties/Resources.resx b/src/FlawBOT.Framework/Properties/Resources.resx index 41ff864f..846bc3b8 100644 --- a/src/FlawBOT.Framework/Properties/Resources.resx +++ b/src/FlawBOT.Framework/Properties/Resources.resx @@ -175,7 +175,7 @@ https://www.trade.tf/api/schema_440.json - https://api.twitch.tv/helix/streams/ + https://api.twitch.tv/helix/ https://en.wikipedia.org//w/api.php?action=query&format=json&prop=info&redirects=1&formatversion=2&inprop=url diff --git a/src/FlawBOT.Framework/Services/Games/PokemonService.cs b/src/FlawBOT.Framework/Services/Games/PokemonService.cs index 7c369840..1ed10c62 100644 --- a/src/FlawBOT.Framework/Services/Games/PokemonService.cs +++ b/src/FlawBOT.Framework/Services/Games/PokemonService.cs @@ -16,8 +16,8 @@ public class PokemonService : HttpHandler public static async Task GetPokemonCardsAsync(string query) { - query = (string.IsNullOrWhiteSpace(query)) ? GetRandomPokemonAsync() : query; - var results = await _http.GetStringAsync(Resources.API_PokemonTCG + "?name=" + query.ToLowerInvariant().Trim()); + query = query ?? GetRandomPokemonAsync(); + var results = await _http.GetStringAsync(Resources.API_PokemonTCG + "?name=" + query.ToLowerInvariant().Trim()).ConfigureAwait(false); return JsonConvert.DeserializeObject(results); } @@ -36,7 +36,7 @@ public static async Task UpdatePokemonListAsync() { try { - var list = await _http.GetStringAsync(Resources.API_Pokemon); + var list = await _http.GetStringAsync(Resources.API_Pokemon).ConfigureAwait(false); var results = JsonConvert.DeserializeObject(list).Results; PokemonList.Clear(); foreach (var pokemon in results) diff --git a/src/FlawBOT.Framework/Services/Games/SmashService.cs b/src/FlawBOT.Framework/Services/Games/SmashService.cs deleted file mode 100644 index f7663636..00000000 --- a/src/FlawBOT.Framework/Services/Games/SmashService.cs +++ /dev/null @@ -1,39 +0,0 @@ -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Properties; -using Newtonsoft.Json; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace FlawBOT.Framework.Services -{ - public class SmashService : HttpHandler - { - public static async Task GetSmashCharacterAsync(string query) - { - try - { - var results = await _http.GetStringAsync(Resources.API_SmashBros + "name/" + query.ToLowerInvariant() + "?game=ultimate"); - return JsonConvert.DeserializeObject(results); - } - catch - { - return null; - } - } - - public static async Task> GetCharacterAttributesAsync(int characterID) - { - try - { - var output = await _http.GetStringAsync(Resources.API_SmashBros + characterID + "/characterattributes?game=ultimate"); - var attributes = JsonConvert.DeserializeObject>(output); - return attributes.Distinct().ToList(); - } - catch - { - return null; - } - } - } -} \ No newline at end of file diff --git a/src/FlawBOT.Framework/Services/Games/SpeedrunService.cs b/src/FlawBOT.Framework/Services/Games/SpeedrunService.cs index fdb17a1e..0044421a 100644 --- a/src/FlawBOT.Framework/Services/Games/SpeedrunService.cs +++ b/src/FlawBOT.Framework/Services/Games/SpeedrunService.cs @@ -3,6 +3,7 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.Linq; using System.Text; using System.Threading.Tasks; @@ -18,7 +19,7 @@ public static async Task GetSpeedrunGameAsync(string query) { try { - var results = await _http.GetStringAsync(Resources.API_Speedrun + "games?name=" + Uri.EscapeUriString(query.Trim()) + "&max=1"); + var results = await _http.GetStringAsync(Resources.API_Speedrun + "games?name=" + Uri.EscapeUriString(query.Trim()) + "&max=1").ConfigureAwait(false); return JsonConvert.DeserializeObject(results); } catch @@ -36,11 +37,11 @@ public static async Task GetSpeedrunExtraAsync(List queryList, S try { var results = new StringBuilder(); - foreach (var query in queryList) + foreach (var query in queryList.Take(3)) { - var output = await _http.GetStringAsync(Resources.API_Speedrun + search.ToString().ToLowerInvariant() + "/" + query); + var output = await _http.GetStringAsync(Resources.API_Speedrun + search.ToString().ToLowerInvariant() + "/" + query).ConfigureAwait(false); var name = JsonConvert.DeserializeObject(output).Data.Name; - results.Append(name + "\n"); + results.Append(name + (!query.Equals(queryList.Take(3).Last()) ? ", " : string.Empty)); } return results.ToString(); } diff --git a/src/FlawBOT.Framework/Services/Games/SteamService.cs b/src/FlawBOT.Framework/Services/Games/SteamService.cs index ba0fab82..8ce1b91b 100644 --- a/src/FlawBOT.Framework/Services/Games/SteamService.cs +++ b/src/FlawBOT.Framework/Services/Games/SteamService.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net.Http; using System.Threading.Tasks; namespace FlawBOT.Framework.Services @@ -15,29 +16,31 @@ namespace FlawBOT.Framework.Services public class SteamService : HttpHandler { public static Dictionary SteamAppList { get; set; } = new Dictionary(); + public static SteamWebInterfaceFactory SteamInterface; public static async Task GetSteamAppAsync(string query) { var store = new SteamStore(); var random = new Random(); - var game = SteamAppList.FirstOrDefault(n => n.Value.ToUpperInvariant() == query.ToUpperInvariant()).Key; + var game = SteamAppList.FirstOrDefault(n => string.Equals(n.Value, query, StringComparison.InvariantCultureIgnoreCase)).Key; var appId = (game >= 0) ? game : SteamAppList.Keys.ToArray()[random.Next(0, SteamAppList.Keys.Count - 1)]; return await store.GetStoreAppDetailsAsync(appId).ConfigureAwait(false); } public static async Task GetSteamAppsListAsync() { - var result = await _http.GetStringAsync(Resources.API_SteamGames); + var result = await _http.GetStringAsync(Resources.API_SteamGames).ConfigureAwait(false); return JsonConvert.DeserializeObject(result); } public static async Task GetSteamUserProfileAsync(string query) { - var steam = new SteamUser(TokenHandler.Tokens.SteamToken); + SteamInterface = new SteamWebInterfaceFactory(TokenHandler.Tokens.SteamToken); + var steam = SteamInterface.CreateSteamWebInterface(new HttpClient()); SteamCommunityProfileModel profile; try { - var decode = await steam.ResolveVanityUrlAsync(query); + var decode = await steam.ResolveVanityUrlAsync(query).ConfigureAwait(false); profile = await steam.GetCommunityProfileAsync(decode.Data).ConfigureAwait(false); } catch @@ -49,11 +52,12 @@ public static async Task GetSteamUserProfileAsync(st public static async Task> GetSteamUserSummaryAsync(string query) { - var steam = new SteamUser(TokenHandler.Tokens.SteamToken); + SteamInterface = new SteamWebInterfaceFactory(TokenHandler.Tokens.SteamToken); + var steam = SteamInterface.CreateSteamWebInterface(new HttpClient()); ISteamWebResponse summary; try { - var decode = await steam.ResolveVanityUrlAsync(query); + var decode = await steam.ResolveVanityUrlAsync(query).ConfigureAwait(false); summary = await steam.GetPlayerSummaryAsync(decode.Data).ConfigureAwait(false); } catch @@ -67,8 +71,9 @@ public static async Task UpdateSteamListAsync() { try { - var client = new SteamApps(TokenHandler.Tokens.SteamToken); - var games = (await client.GetAppListAsync()).Data; + SteamInterface = new SteamWebInterfaceFactory(TokenHandler.Tokens.SteamToken); + var steam = SteamInterface.CreateSteamWebInterface(new HttpClient()); + var games = (await steam.GetAppListAsync().ConfigureAwait(false)).Data; SteamAppList.Clear(); foreach (var game in games) if (!string.IsNullOrWhiteSpace(game.Name)) diff --git a/src/FlawBOT.Framework/Services/Games/TeamFortressService.cs b/src/FlawBOT.Framework/Services/Games/TeamFortressService.cs index 4947251b..642b7fb0 100644 --- a/src/FlawBOT.Framework/Services/Games/TeamFortressService.cs +++ b/src/FlawBOT.Framework/Services/Games/TeamFortressService.cs @@ -21,7 +21,7 @@ public static async Task LoadTF2SchemaAsync() { try { - var schema = await _http.GetStringAsync(Resources.API_TradeTF + "?key=" + TokenHandler.Tokens.TFSchemaToken); + var schema = await _http.GetStringAsync(Resources.API_TradeTF + "?key=" + TokenHandler.Tokens.TFSchemaToken).ConfigureAwait(false); var results = JsonConvert.DeserializeObject(schema); ItemSchema.Clear(); foreach (var item in results.Results.Items) @@ -40,19 +40,19 @@ public static async Task LoadTF2SchemaAsync() public static async Task> GetNewsOverviewAsync() { - var results = await _http.GetStringAsync(Resources.API_TeamworkTF + "news?key=" + TokenHandler.Tokens.TeamworkToken); + var results = await _http.GetStringAsync(Resources.API_TeamworkTF + "news?key=" + TokenHandler.Tokens.TeamworkToken).ConfigureAwait(false); return JsonConvert.DeserializeObject>(results); } public static async Task> GetServersAsync(string query) { - var results = await _http.GetStringAsync(Resources.API_TeamworkTF + "quickplay/" + query + "/servers?key=" + TokenHandler.Tokens.TeamworkToken); + var results = await _http.GetStringAsync(Resources.API_TeamworkTF + "quickplay/" + query + "/servers?key=" + TokenHandler.Tokens.TeamworkToken).ConfigureAwait(false); return JsonConvert.DeserializeObject>(results); } public static async Task GetMapStatsAsync(string query) { - var results = await _http.GetStringAsync(Resources.API_TeamworkTF + "map-stats/map/" + NormalizedMapName(query) + "?key=" + TokenHandler.Tokens.TeamworkToken); + var results = await _http.GetStringAsync(Resources.API_TeamworkTF + "map-stats/map/" + query + "?key=" + TokenHandler.Tokens.TeamworkToken).ConfigureAwait(false); return JsonConvert.DeserializeObject(results); } diff --git a/src/FlawBOT.Framework/Services/Misc/MiscService.cs b/src/FlawBOT.Framework/Services/Misc/MiscService.cs index 076819ba..9c66c2c0 100644 --- a/src/FlawBOT.Framework/Services/Misc/MiscService.cs +++ b/src/FlawBOT.Framework/Services/Misc/MiscService.cs @@ -46,7 +46,7 @@ public class CatService : HttpHandler { public static async Task GetCatFactAsync() { - return await _http.GetStringAsync(Resources.API_CatFacts); + return await _http.GetStringAsync(Resources.API_CatFacts).ConfigureAwait(false); } public static async Task GetCatPhotoAsync() @@ -60,7 +60,7 @@ public class DogService : HttpHandler { public static async Task GetDogPhotoAsync() { - var results = await _http.GetStringAsync(Resources.API_DogPhoto); + var results = await _http.GetStringAsync(Resources.API_DogPhoto).ConfigureAwait(false); return JsonConvert.DeserializeObject(results); } } diff --git a/src/FlawBOT.Framework/Services/Search/AmiiboService.cs b/src/FlawBOT.Framework/Services/Search/AmiiboService.cs index 5aa555bc..ea28483e 100644 --- a/src/FlawBOT.Framework/Services/Search/AmiiboService.cs +++ b/src/FlawBOT.Framework/Services/Search/AmiiboService.cs @@ -11,7 +11,7 @@ public static async Task GetAmiiboFigurineAsync(string query) { try { - var results = await _http.GetStringAsync(Resources.API_Amiibo + "?name=" + query.ToLowerInvariant()); + var results = await _http.GetStringAsync(Resources.API_Amiibo + "?name=" + query.ToLowerInvariant()).ConfigureAwait(false); return JsonConvert.DeserializeObject(results); } catch diff --git a/src/FlawBOT.Framework/Services/Search/DictionaryService.cs b/src/FlawBOT.Framework/Services/Search/DictionaryService.cs index 2afa66a8..71f12e06 100644 --- a/src/FlawBOT.Framework/Services/Search/DictionaryService.cs +++ b/src/FlawBOT.Framework/Services/Search/DictionaryService.cs @@ -10,7 +10,7 @@ public class DictionaryService : HttpHandler { public static async Task GetDictionaryForTermAsync(string query) { - var results = await _http.GetStringAsync(Resources.API_Dictionary + "?term=" + WebUtility.UrlEncode(query.Trim())); + var results = await _http.GetStringAsync(Resources.API_Dictionary + "?term=" + WebUtility.UrlEncode(query.Trim())).ConfigureAwait(false); return JsonConvert.DeserializeObject(results); } } diff --git a/src/FlawBOT.Framework/Services/Search/GoodReadsService.cs b/src/FlawBOT.Framework/Services/Search/GoodReadsService.cs index 544b69ac..c3032b55 100644 --- a/src/FlawBOT.Framework/Services/Search/GoodReadsService.cs +++ b/src/FlawBOT.Framework/Services/Search/GoodReadsService.cs @@ -1,6 +1,7 @@ using FlawBOT.Framework.Models; using FlawBOT.Framework.Properties; using System.Net; +using System.Text; using System.Threading.Tasks; using System.Xml.Serialization; @@ -14,5 +15,24 @@ public static async Task GetBookDataAsync(string query) var results = await _http.GetStreamAsync(Resources.API_GoodReads + "?key=" + TokenHandler.Tokens.GoodReadsToken + "&q=" + WebUtility.UrlEncode(query)).ConfigureAwait(false); return (GoodreadsResponse)_serializer.Deserialize(results); } + + /// + /// Retrieve and format the book's publication date. + /// + /// Good Reads book to retrieve publication date from. + public static string GetPublicationDate(Work book) + { + try + { + var results = new StringBuilder(); + // TODO: Add the day and month + results.Append($"{book.PublicationMonth.Text ?? "01"}-{book.PublicationDay.Text ?? "01"}-{book.PublicationYear.Text}"); + return results.ToString(); + } + catch + { + return string.Empty; + } + } } } \ No newline at end of file diff --git a/src/FlawBOT.Framework/Services/Search/GoogleService.cs b/src/FlawBOT.Framework/Services/Search/GoogleService.cs index 1b708f8b..765a41af 100644 --- a/src/FlawBOT.Framework/Services/Search/GoogleService.cs +++ b/src/FlawBOT.Framework/Services/Search/GoogleService.cs @@ -18,7 +18,7 @@ public static async Task GetTimeDataAsync(string query) var latitude = results.Results[0].Geometry.Location.Latitude; var longitude = results.Results[0].Geometry.Location.Longitude; var currentSeconds = (int)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds; - var timeResource = await _http.GetStringAsync(Resources.API_Google_Time + "?location=" + latitude + "," + longitude + "×tamp=" + currentSeconds + "&key=" + TokenHandler.Tokens.GoogleToken); + var timeResource = await _http.GetStringAsync(Resources.API_Google_Time + "?location=" + latitude + "," + longitude + "×tamp=" + currentSeconds + "&key=" + TokenHandler.Tokens.GoogleToken).ConfigureAwait(false); results.Timezone = JsonConvert.DeserializeObject(timeResource); results.Time = DateTime.UtcNow.AddSeconds(results.Timezone.dstOffset + results.Timezone.rawOffset); return results; @@ -31,14 +31,14 @@ public static async Task GetTimeDataAsync(string query) public static async Task GetWeatherDataAsync(string query) { - var results = await _http.GetStringAsync(Resources.API_Google_Weather + "?q=" + query + "&appid=42cd627dd60debf25a5739e50a217d74&units=metric"); + var results = await _http.GetStringAsync(Resources.API_Google_Weather + "?q=" + query + "&appid=42cd627dd60debf25a5739e50a217d74&units=metric").ConfigureAwait(false); return JsonConvert.DeserializeObject(results); } public async static Task GetLocationGeoData(string query) { _http.DefaultRequestHeaders.Clear(); - var result = await _http.GetStringAsync(Resources.API_Google_Geo + "?address=" + query + "&key=" + TokenHandler.Tokens.GoogleToken); + var result = await _http.GetStringAsync(Resources.API_Google_Geo + "?address=" + query + "&key=" + TokenHandler.Tokens.GoogleToken).ConfigureAwait(false); var results = JsonConvert.DeserializeObject(result); if (results.status == "OK") return results; return null; @@ -52,7 +52,7 @@ public static async Task GetIPLocationAsync(IPAddress query) public static async Task GetNewsDataAsync(string query = "") { - var results = await _http.GetStringAsync(Resources.API_News + "&q=" + query + "&apiKey=" + TokenHandler.Tokens.NewsToken); + var results = await _http.GetStringAsync(Resources.API_News + "&q=" + query + "&apiKey=" + TokenHandler.Tokens.NewsToken).ConfigureAwait(false); return JsonConvert.DeserializeObject(results); } diff --git a/src/FlawBOT.Framework/Services/Search/ImgurService.cs b/src/FlawBOT.Framework/Services/Search/ImgurService.cs index 5503b4df..e865fd3c 100644 --- a/src/FlawBOT.Framework/Services/Search/ImgurService.cs +++ b/src/FlawBOT.Framework/Services/Search/ImgurService.cs @@ -16,7 +16,7 @@ public static async Task GetImgurGalleryAsync(string query, Galler var random = new Random(); var imgur = new ImgurClient(TokenHandler.Tokens.ImgurToken); var endpoint = new GalleryEndpoint(imgur); - var gallery = string.IsNullOrWhiteSpace(query) ? (await endpoint.GetRandomGalleryAsync()).ToList() : (await endpoint.SearchGalleryAsync(query, order, time)).ToList(); + var gallery = string.IsNullOrWhiteSpace(query) ? (await endpoint.GetRandomGalleryAsync().ConfigureAwait(false)).ToList() : (await endpoint.SearchGalleryAsync(query, order, time).ConfigureAwait(false)).ToList(); return gallery.Any() ? gallery[random.Next(0, gallery.Count)] : null; } } diff --git a/src/FlawBOT.Framework/Services/Search/NASAService.cs b/src/FlawBOT.Framework/Services/Search/NASAService.cs index ee79c2bf..c989d64c 100644 --- a/src/FlawBOT.Framework/Services/Search/NASAService.cs +++ b/src/FlawBOT.Framework/Services/Search/NASAService.cs @@ -9,7 +9,7 @@ public class NASAService : HttpHandler { public static async Task GetNASAImage() { - var results = await _http.GetStringAsync(Resources.API_NASA + "?api_key=" + TokenHandler.Tokens.NASAToken); + var results = await _http.GetStringAsync(Resources.API_NASA + "?api_key=" + TokenHandler.Tokens.NASAToken).ConfigureAwait(false); return JsonConvert.DeserializeObject(results); } } diff --git a/src/FlawBOT.Framework/Services/Search/OMDBService.cs b/src/FlawBOT.Framework/Services/Search/OMDBService.cs index 1da13fb2..da2db0d7 100644 --- a/src/FlawBOT.Framework/Services/Search/OMDBService.cs +++ b/src/FlawBOT.Framework/Services/Search/OMDBService.cs @@ -8,7 +8,7 @@ public class OMDBService { public static async Task GetMovieDataAsync(string query) { - return await new OMDbClient(TokenHandler.Tokens.OMDBToken, true).GetItemByTitle(query.ToLowerInvariant().Replace("&", "%26")); + return await new OMDbClient(TokenHandler.Tokens.OMDBToken, true).GetItemByTitle(query.ToLowerInvariant().Replace("&", "%26")).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/FlawBOT.Framework/Services/Search/SimpsonsService.cs b/src/FlawBOT.Framework/Services/Search/SimpsonsService.cs index 8a904612..a7aa60ec 100644 --- a/src/FlawBOT.Framework/Services/Search/SimpsonsService.cs +++ b/src/FlawBOT.Framework/Services/Search/SimpsonsService.cs @@ -10,16 +10,16 @@ public class SimpsonsService : HttpHandler { public static async Task GetSimpsonsDataAsync(SiteRoot site) { - var output = await _http.GetStringAsync($"https://{site}.com/api/random"); + var output = await _http.GetStringAsync($"https://{site}.com/api/random").ConfigureAwait(false); var results = JsonConvert.DeserializeObject(output); return EmbedSimpsonsEpisode(results, site); } public static async Task GetSimpsonsGifAsync(SiteRoot site) { - var result = await _http.GetStringAsync($"https://{site}.com/api/random"); + var result = await _http.GetStringAsync($"https://{site}.com/api/random").ConfigureAwait(false); var content = JsonConvert.DeserializeObject(result); - var frames_result = await _http.GetStringAsync($"https://{site}.com/api/frames/{content.Episode.Key}/{content.Frame.Timestamp}/3000/4000"); + var frames_result = await _http.GetStringAsync($"https://{site}.com/api/frames/{content.Episode.Key}/{content.Frame.Timestamp}/3000/4000").ConfigureAwait(false); var frames = JsonConvert.DeserializeObject>(frames_result); var start = frames[0].Timestamp; var end = frames[frames.Count - 1].Timestamp; @@ -31,9 +31,9 @@ public static DiscordEmbedBuilder EmbedSimpsonsEpisode(SimpsonsData data, SiteRo var output = new DiscordEmbedBuilder() .WithTitle(data.Episode.Title) .AddField("Season/Episode", data.Episode.Key, true) - .AddField("Air Date", data.Episode.OriginalAirDate, true) .AddField("Writer", data.Episode.Writer, true) .AddField("Director", data.Episode.Director, true) + .WithFooter("Original Air Date: " + data.Episode.OriginalAirDate) .WithImageUrl($"https://{site}.com/img/{data.Frame.Episode}/{data.Frame.Timestamp}.jpg") .WithColor(new DiscordColor("#FFBB22")) .WithUrl(data.Episode.WikiLink); diff --git a/src/FlawBOT.Framework/Services/Search/TwitchService.cs b/src/FlawBOT.Framework/Services/Search/TwitchService.cs index 8c068833..e5f7552e 100644 --- a/src/FlawBOT.Framework/Services/Search/TwitchService.cs +++ b/src/FlawBOT.Framework/Services/Search/TwitchService.cs @@ -2,9 +2,10 @@ using FlawBOT.Framework.Properties; using Newtonsoft.Json; using System.Collections.Generic; +using System.Net.Http; using System.Threading.Tasks; using TwitchLib.Api; -using TwitchLib.Api.Helix.Models.Games; +using TwitchLib.Api.Helix.Models.Users; namespace FlawBOT.Framework.Services { @@ -12,16 +13,20 @@ public class TwitchService : HttpHandler { public static async Task GetTwitchDataAsync(string query) { - var results = await _http.GetStringAsync(Resources.API_Twitch + query + "?client_id=" + TokenHandler.Tokens.TwitchToken); + using var request = new HttpRequestMessage(new HttpMethod("GET"), Resources.API_Twitch + "streams?user_login=" + query); + request.Headers.TryAddWithoutValidation("Client-ID", TokenHandler.Tokens.TwitchToken); + var response = await _http.SendAsync(request).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + var results = await response.Content.ReadAsStringAsync().ConfigureAwait(false); return JsonConvert.DeserializeObject(results); } - public static async Task GetTwitchGameAsync(string query) + public static async Task GetTwitchUserAsync(string query) { var client = new TwitchAPI(); client.Settings.ClientId = TokenHandler.Tokens.TwitchToken; - var games = new List { query }; - return await client.Helix.Games.GetGamesAsync(gameIds: games); + var users = new List { query }; + return await client.Helix.Users.GetUsersAsync(logins: users).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/FlawBOT.Framework/Services/Search/WikipediaService.cs b/src/FlawBOT.Framework/Services/Search/WikipediaService.cs index c3d7a570..2379fc00 100644 --- a/src/FlawBOT.Framework/Services/Search/WikipediaService.cs +++ b/src/FlawBOT.Framework/Services/Search/WikipediaService.cs @@ -10,7 +10,7 @@ public class WikipediaService : HttpHandler { public static async Task GetWikipediaDataAsync(string query) { - var results = await _http.GetStringAsync(Resources.API_Wikipedia + "&titles=" + Uri.EscapeDataString(query)); + var results = await _http.GetStringAsync(Resources.API_Wikipedia + "&titles=" + Uri.EscapeDataString(query)).ConfigureAwait(false); return JsonConvert.DeserializeObject(results).Query.Pages[0]; } } diff --git a/src/FlawBOT.Framework/Services/Bot/BotService.cs b/src/FlawBOT.Framework/Services/Server/BotService.cs similarity index 81% rename from src/FlawBOT.Framework/Services/Bot/BotService.cs rename to src/FlawBOT.Framework/Services/Server/BotService.cs index 019d5d05..c994fbcb 100644 --- a/src/FlawBOT.Framework/Services/Bot/BotService.cs +++ b/src/FlawBOT.Framework/Services/Server/BotService.cs @@ -42,9 +42,17 @@ public static async Task SendEmbedAsync(CommandContext ctx, string message, Embe break; } var output = new DiscordEmbedBuilder() - .WithTitle(prefix + message) + .WithDescription(prefix + message) .WithColor(color); - await ctx.RespondAsync(embed: output.Build()); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); + } + + public static async Task SendUserStateChangeAsync(CommandContext ctx, UserStateChange state, DiscordMember user, string reason) + { + var output = new DiscordEmbedBuilder() + .WithDescription($"{state}: {user.DisplayName}#{user.Discriminator}\nIdentifier: {user.Id}\nReason: {reason}\nIssued by: {ctx.Member.DisplayName}#{ctx.Member.Discriminator}") + .WithColor(DiscordColor.Green); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } public static bool CheckUserInput(string input) @@ -68,7 +76,7 @@ public static async Task RemoveMessage(DiscordMessage message) { try { - await message.DeleteAsync(); + await message.DeleteAsync().ConfigureAwait(false); return true; } catch diff --git a/src/FlawBOT.Test/FlawBOT.Test.csproj b/src/FlawBOT.Test/FlawBOT.Test.csproj index ec05337e..bf4716ba 100644 --- a/src/FlawBOT.Test/FlawBOT.Test.csproj +++ b/src/FlawBOT.Test/FlawBOT.Test.csproj @@ -17,8 +17,11 @@ - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/src/FlawBOT.Test/Games/SmashTests.cs b/src/FlawBOT.Test/Games/SmashTests.cs deleted file mode 100644 index e4df21e7..00000000 --- a/src/FlawBOT.Test/Games/SmashTests.cs +++ /dev/null @@ -1,513 +0,0 @@ -using FlawBOT.Framework.Services; -using NUnit.Framework; - -namespace GamesModule -{ - [TestFixture] - internal class SmashTests - { - [Test] - [Ignore("Character data currently unavailable")] - public void Bayonetta() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Bayonetta").Result); - } - - [Test] - public void Bowser() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Bowser").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void BowserJr() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Bowser Jr.").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void CaptainFalcon() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Captain Falcon").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void Charizard() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Charizard").Result); - } - - [Test] - public void Chrom() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Chrom").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void Cloud() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Cloud").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void Corrin() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Corrin").Result); - } - - [Test] - public void Daisy() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Daisy").Result); - } - - [Test] - public void DarkPit() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Dark Pit").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void DarkSamus() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Dark Samus").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void DiddyKong() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Diddy Kong").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void DonkeyKong() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Donkey Kong").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void DrMario() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Dr.Mario").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void DuckHunt() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Duck Hunt").Result); - } - - [Test] - public void Falco() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Falco").Result); - } - - [Test] - public void Fox() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Fox").Result); - } - - [Test] - public void Ganondorf() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Ganondorf").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void Greninja() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Greninja").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void IceClimbers() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Ice Climbers").Result); - } - - [Test] - public void Ike() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Ike").Result); - } - - [Test] - public void Incineroar() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Incineroar").Result); - } - - [Test] - public void Inkling() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Inkling").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void Isabelle() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Isabelle").Result); - } - - [Test] - public void Ivysaur() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Ivysaur").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void Jigglypuff() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Jigglypuff").Result); - } - - [Test] - public void Joker() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Joker").Result); - } - - [Test] - public void Ken() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Ken").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void KingDedede() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("King Dedede").Result); - } - - [Test] - public void KingKRool() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("King K.Rool").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void Kirby() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Kirby").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void Link() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Link").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void LittleMac() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Little Mac").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void Lucario() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Lucario").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void Lucas() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Lucas").Result); - } - - [Test] - public void Lucina() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Lucina").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void Luigi() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Luigi").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void Mario() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Mario").Result); - } - - [Test] - public void Marth() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Marth").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void MegaMan() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Mega Man").Result); - } - - [Test] - public void MetaKnight() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Meta Knight").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void Mewtwo() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Mewtwo").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void MiiBrawler() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Mii Brawler").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void MiiSwordfighter() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Mii Swordfighter").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void MiiGunner() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Mii Gunner").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void MrGameWatch() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Mr.Game & Watch").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void Ness() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Ness").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void Olimar() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Olimar").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void PacMan() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Pac-Man").Result); - } - - [Test] - public void Palutena() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Palutena").Result); - } - - [Test] - public void Peach() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Peach").Result); - } - - [Test] - public void Pichu() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Pichu").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void Pikachu() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Pikachu").Result); - } - - [Test] - public void PiranhaPlant() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Piranha Plant").Result); - } - - [Test] - public void Pit() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Pit").Result); - } - - [Test] - public void Richter() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Richter").Result); - } - - [Test] - public void Ridley() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Ridley").Result); - } - - [Test] - public void ROB() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("R.O.B.").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void Robin() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Robin").Result); - } - - [Test] - public void RosalinaLuma() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Rosalina & Luma").Result); - } - - [Test] - public void Roy() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Roy").Result); - } - - [Test] - public void Ryu() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Ryu").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void Samus() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Samus").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void Sheik() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Sheik").Result); - } - - [Test] - public void Shulk() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Shulk").Result); - } - - [Test] - public void Simon() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Simon").Result); - } - - [Test] - public void Snake() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Snake").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void Sonic() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Sonic").Result); - } - - [Test] - public void Squirtle() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Squirtle").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void ToonLink() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Toon Link").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void Villager() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Villager").Result); - } - - [Test] - public void Wario() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Wario").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void WiiFitTrainer() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Wii Fit Trainer").Result); - } - - [Test] - public void Wolf() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Wolf").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void YoungLink() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Young Link").Result); - } - - [Test] - public void Zelda() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Zelda").Result); - } - - [Test] - [Ignore("Character data currently unavailable")] - public void ZeroSuitSamus() - { - Assert.IsNotNull(SmashService.GetSmashCharacterAsync("Zero Suit Samus").Result); - } - } -} \ No newline at end of file diff --git a/src/FlawBOT.Test/Search/ImgurTests.cs b/src/FlawBOT.Test/Search/ImgurTests.cs index 2bc8c40e..5eedcda9 100644 --- a/src/FlawBOT.Test/Search/ImgurTests.cs +++ b/src/FlawBOT.Test/Search/ImgurTests.cs @@ -11,10 +11,10 @@ internal class ImgurTests [Test] public async Task GetImgurGalleryData() { - var results = await ImgurService.GetImgurGalleryAsync("cats", GallerySortOrder.Top, TimeWindow.All); + var results = await ImgurService.GetImgurGalleryAsync("cats", GallerySortOrder.Top, TimeWindow.All).ConfigureAwait(false); Assert.IsNotNull(results); - results = await ImgurService.GetImgurGalleryAsync("dogs", GallerySortOrder.Top, TimeWindow.All); + results = await ImgurService.GetImgurGalleryAsync("dogs", GallerySortOrder.Top, TimeWindow.All).ConfigureAwait(false); Assert.IsNotNull(results); } } From 900e51ca9dab69e43171e57994d5859def03c6cb Mon Sep 17 00:00:00 2001 From: CriticalFlaw Date: Tue, 28 Jan 2020 08:07:22 -0500 Subject: [PATCH 2/2] Update UserModule.cs --- src/FlawBOT.Core/Modules/Server/UserModule.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/FlawBOT.Core/Modules/Server/UserModule.cs b/src/FlawBOT.Core/Modules/Server/UserModule.cs index ea970c1d..4e3c8099 100644 --- a/src/FlawBOT.Core/Modules/Server/UserModule.cs +++ b/src/FlawBOT.Core/Modules/Server/UserModule.cs @@ -255,16 +255,3 @@ public async Task Unmute(CommandContext ctx, #endregion COMMAND_UNMUTE } } - -- Code cleanup and improvements. -- Updated NuGet packages. -- Updated Twitch and Steam command API services. -- Reorganized the command modules. -- Removed the Smash Bros. command. -- Updated outputs for pokemon, speedrun, steam user, tf2 schema, tf2 server, poll -- Fixed twitch command not returning results if the follower count is outside the integer range. -- Fixed the tf2 item schema command not returning higher quality images when available. -- Certain commands will now remove user messages that called the command. -- The poll command is now preset to run for three minutes. -- Fixed an issue where last paginated item would be removed if it was the last one on the list when the user calls for the next list item. -- Fixed various text grammar and wording inconsistencies. \ No newline at end of file