diff --git a/.gitignore b/.gitignore index 1ea847e7..f6f24756 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ -# Created by https://www.gitignore.io/api/visualstudio -# Edit at https://www.gitignore.io/?templates=visualstudio +# Created by https://www.toptal.com/developers/gitignore/api/visualstudio +# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudio ### VisualStudio ### ## Ignore Visual Studio temporary files, build results, and @@ -34,6 +34,7 @@ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ +[Ll]ogs/ # Visual Studio 2015/2017 cache/options directory .vs/ @@ -73,6 +74,7 @@ StyleCopReport.xml *_p.c *_h.h *.ilk +*.meta *.obj *.iobj *.pch @@ -130,9 +132,6 @@ _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user -# JustCode is a .NET coding add-in -.JustCode - # TeamCity is a build add-in _TeamCity* @@ -143,6 +142,9 @@ _TeamCity* .axoCover/* !.axoCover/settings.json +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + # Visual Studio code coverage results *.coverage *.coveragexml @@ -352,4 +354,8 @@ healthchecksdb # Backup folder for Package Reference Convert tool in Visual Studio 2017 MigrationBackup/ -# End of https://www.gitignore.io/api/visualstudio \ No newline at end of file +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# End of https://www.toptal.com/developers/gitignore/api/visualstudio +src/FlawBOT/Lavalink.jar diff --git a/README.md b/README.md index dc2076d7..26efa32a 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,12 @@ To run your own instance of FlawBOT, [clone this repository](https://github.com/ ## API Tokens * [Discord](https://discordapp.com/developers/applications/me) (*required*) -* [Google](https://console.cloud.google.com/projectselector/apis/credentials) (*YouTube, Geological Data*) * [Steam](https://steamcommunity.com/dev/apikey) * [Imgur](https://api.imgur.com/oauth2/addclient) * [OMDB](http://www.omdbapi.com/apikey.aspx) * [Twitch](https://dev.twitch.tv/dashboard/apps/create) * [NASA](https://api.nasa.gov/) -* [Teamwork.TF](https://teamwork.tf/api) -* [News API](https://newsapi.org/) \ No newline at end of file +* [Teamwork.TF](https://github.com/teamworktf/website_api) +* [News API](https://newsapi.org/) +* [WeatherStack](https://weatherstack.com/) +* [YouTube](https://console.cloud.google.com/projectselector/apis/credentials) \ No newline at end of file diff --git a/src/FlawBOT.Core/Common/SharedData.cs b/src/FlawBOT.Core/Common/SharedData.cs deleted file mode 100644 index db0aece8..00000000 --- a/src/FlawBOT.Core/Common/SharedData.cs +++ /dev/null @@ -1,16 +0,0 @@ -using DSharpPlus.Entities; -using System; -using System.Reflection; - -namespace FlawBOT.Common -{ - public class SharedData - { - public static string Name { get; } = "FlawBOT"; - public static string Version { get; } = Assembly.GetExecutingAssembly().GetName().Version?.ToString(); - public static string GitHubLink { get; set; } = "https://github.com/CriticalFlaw/FlawBOT/"; - public static string InviteLink { get; } = "https://discordapp.com/oauth2/authorize?client_id=339833029013012483&scope=bot&permissions=66186303"; - public static DiscordColor DefaultColor { get; set; } = new DiscordColor("#00FF7F"); - public static DateTime ProcessStarted { get; set; } - } -} \ No newline at end of file diff --git a/src/FlawBOT.Core/FlawBOT.Core.csproj b/src/FlawBOT.Core/FlawBOT.Core.csproj deleted file mode 100644 index 3e6b6d3d..00000000 --- a/src/FlawBOT.Core/FlawBOT.Core.csproj +++ /dev/null @@ -1,75 +0,0 @@ - - - - Exe - netcoreapp3.1 - icon.ico - FlawBOT.Program - false - 2.6.1 - - 7.2 - 2.6.1.0 - 2.6.1.0 - FlawBOT.Core - FlawBOT.Core - - Multipurpose Discord bot written in C# using DSharpPlus. Application Core. - https://top.gg/bot/339833029013012483 - https://github.com/CriticalFlaw/FlawBOT - - CriticalFlaw - FlawBOT - Igor Nikitin - en-CA - - - - false - AnyCPU - - - - - - - - - PreserveNewest - - - - - - - - - - - - - - - - - - ..\FlawBOT.Framework\Resources\PokemonTcgSdk.dll - - - - - - True - True - Resources.resx - - - - - - ResXFileCodeGenerator - Resources.Designer.cs - - - - diff --git a/src/FlawBOT.Core/FlawBOT.cs b/src/FlawBOT.Core/FlawBOT.cs deleted file mode 100644 index 8ff20a7d..00000000 --- a/src/FlawBOT.Core/FlawBOT.cs +++ /dev/null @@ -1,266 +0,0 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using DSharpPlus; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; -using DSharpPlus.CommandsNext.Exceptions; -using DSharpPlus.Entities; -using DSharpPlus.EventArgs; -using DSharpPlus.Interactivity; -using DSharpPlus.Interactivity.Enums; -using DSharpPlus.Lavalink; -using DSharpPlus.VoiceNext; -using FlawBOT.Common; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Services; -using FlawBOT.Modules; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace FlawBOT -{ - internal sealed class FlawBOT - { - internal static EventId EventId { get; } = new EventId(1000, "FlawBot"); - public DiscordClient Client { get; set; } - private CommandsNextExtension Commands { get; } - private InteractivityExtension Interactivity { get; } - private VoiceNextExtension Voice { get; } - private LavalinkExtension LavaLink { get; } - - public FlawBOT(int shardId = 0) - { - var depot = new ServiceCollection(); - - // Setup Client - Client = new DiscordClient(new DiscordConfiguration - { - Token = TokenHandler.Tokens.DiscordToken, - TokenType = TokenType.Bot, - AutoReconnect = true, - MinimumLogLevel = LogLevel.Information, - GatewayCompressionLevel = GatewayCompressionLevel.Stream, - LargeThreshold = 250, - MessageCacheSize = 2048, - LogTimestampFormat = "yyyy-MM-dd HH:mm:ss zzz", - ShardId = shardId, - //ShardCount = 0 - }); - Client.Ready += Client_Ready; - //Client.GuildAvailable += Client_GuildAvailable; - Client.ClientErrored += Client_Error; - //Client.SocketErrored += Client_SocketError; - //Client.GuildCreated += Client_GuildCreated; - //Client.VoiceStateUpdated += Client_VoiceStateUpdated; - //Client.GuildDownloadCompleted += Client_GuildDownloadCompleted; - //Client.GuildUpdated += Client_GuildUpdated; - //Client.ChannelDeleted += Client_ChannelDeleted; - - // Setup Commands - Commands = Client.UseCommandsNext(new CommandsNextConfiguration - { - PrefixResolver = PrefixResolverAsync, // Set the command prefix that will be used by the bot - EnableDms = false, // Set the boolean for responding to direct messages - EnableMentionPrefix = true, // Set the boolean for mentioning the bot as a command prefix - CaseSensitive = false, - //StringPrefixes = null, - //Services = depot.BuildServiceProvider(true), - //IgnoreExtraArguments = false, - //UseDefaultCommandHandler = false - }); - Commands.CommandExecuted += Commands_Executed; - Commands.CommandErrored += Commands_Error; - Commands.SetHelpFormatter(); - 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.RegisterCommands(); - Commands.RegisterCommands(); - Commands.RegisterCommands(); - Commands.RegisterCommands(); - Commands.RegisterCommands(); - Commands.RegisterCommands(); - Commands.RegisterCommands(); - Commands.RegisterCommands(); - Commands.RegisterCommands(); - Commands.RegisterCommands(); - Commands.RegisterCommands(); - - // Setup Interactivity - Interactivity = Client.UseInteractivity(new InteractivityConfiguration - { - PaginationBehaviour = PaginationBehaviour.Ignore, - Timeout = TimeSpan.FromMinutes(2) - }); - - // Setup Voice - Voice = Client.UseVoiceNext(new VoiceNextConfiguration - { - AudioFormat = AudioFormat.Default, - EnableIncoming = false - }); - - // Setup LavaLink - LavaLink = Client.UseLavalink(); - - // Start the uptime counter - Console.Title = $"{SharedData.Name}-{SharedData.Version}"; - SharedData.ProcessStarted = DateTime.Now; - } - - public async Task RunAsync() - { - // Update any other services that are being used. - await TeamFortressService.UpdateTf2SchemaAsync().ConfigureAwait(false); - await PokemonService.UpdatePokemonListAsync().ConfigureAwait(false); - - // Set the initial activity and connect the bot to Discord - var act = new DiscordActivity("Night of Fire", ActivityType.ListeningTo); - await Client.ConnectAsync(act, UserStatus.DoNotDisturb).ConfigureAwait(false); - } - - public async Task StopAsync() - { - await Client.DisconnectAsync().ConfigureAwait(false); - } - - private Task Client_Ready(DiscordClient sender, ReadyEventArgs e) - { - sender.Logger.LogInformation(EventId, $"{SharedData.Name}, version: {SharedData.Version}"); - return Task.CompletedTask; - } - - private Task Client_Error(DiscordClient sender, ClientErrorEventArgs e) - { - sender.Logger.LogError(EventId, $"Exception occurred: {e.Exception.GetType()}: {e.Exception.Message}"); - return Task.CompletedTask; - } - - private Task Commands_Executed(CommandsNextExtension sender, CommandExecutionEventArgs e) - { - e.Context.Client.Logger.LogInformation(EventId, $"'{e.Command.QualifiedName}' executed by {e.Context.User.Username} from {e.Context.Guild.Name} : {e.Context.Channel.Name}"); - return Task.CompletedTask; - } - - private async Task Commands_Error(CommandsNextExtension sender, CommandErrorEventArgs e) - { - if (e.Exception is CommandNotFoundException && (e.Command == null || e.Command.QualifiedName != "help")) return; - - e.Context.Client.Logger.LogError(EventId, e.Exception, "Exception occurred during {0}'s invocation of '{1}'", e.Context.User.Username, e.Context.Command.QualifiedName); - - // TO-DO: Refactor this error handler. - switch (e.Exception) - { - case ChecksFailedException cfe: - switch (cfe.FailedChecks[0]) - { - case CooldownAttribute _: - return; - - default: - 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).ConfigureAwait(false); - break; - - case RequireUserPermissionsAttribute perms: - await BotServices.SendEmbedAsync(e.Context, - $"- You do not have sufficient permissions ({perms.Permissions.ToPermissionString()})!", - EmbedType.Error).ConfigureAwait(false); - break; - - case RequireBotPermissionsAttribute perms: - await BotServices.SendEmbedAsync(e.Context, - $"- I do not have sufficient permissions ({perms.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) - .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).ConfigureAwait(false); - break; - - default: - await BotServices.SendEmbedAsync(e.Context, - "Unknown check triggered. Please notify the developer using the command *.bot report*", - EmbedType.Error).ConfigureAwait(false); - break; - } - - break; - } - - break; - - case CommandNotFoundException _: - //await BotServices.SendEmbedAsync(e.Context, "This command does not exist!", EmbedType.Error); - break; - - case NullReferenceException _: - //await BotServices.SendEmbedAsync(e.Context, Resources.NOT_FOUND_GENERIC, EmbedType.Missing); - break; - - case ArgumentNullException _: - 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) - .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) - .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) - .ConfigureAwait(false); - if (e.Exception.Message.Contains("Unauthorized: 403")) - await BotServices.SendEmbedAsync(e.Context, "Insufficient Permissions", EmbedType.Error) - .ConfigureAwait(false); - else - e.Context.Client.Logger.LogError(EventId, $"{e.Context.User.Username} tried executing '{e.Command?.QualifiedName ?? ""}' but it errored: {e.Exception.GetType()}: {e.Exception.Message}"); // DEBUG ONLY - break; - } - } - - private static Task PrefixResolverAsync(DiscordMessage m) - { - return Task.FromResult(m.GetStringPrefixLength(TokenHandler.Tokens.CommandPrefix)); - } - } -} \ No newline at end of file diff --git a/src/FlawBOT.Core/Modules/Games/PokemonModule.cs b/src/FlawBOT.Core/Modules/Games/PokemonModule.cs deleted file mode 100644 index a868ed1a..00000000 --- a/src/FlawBOT.Core/Modules/Games/PokemonModule.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; -using DSharpPlus.Entities; -using FlawBOT.Core.Properties; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Services; - -namespace FlawBOT.Modules -{ - [Cooldown(3, 5, CooldownBucketType.Channel)] - public class PokemonModule : BaseCommandModule - { - #region COMMAND_POKEMON - - [Command("pokemon")] - [Aliases("poke", "pk")] - [Description("Retrieve a Pokémon card")] - public async Task Pokemon(CommandContext ctx, - [Description("Name of the Pokémon")] [RemainingText] string query) - { - var results = await PokemonService.GetPokemonCardsAsync(query).ConfigureAwait(false); - if (results.Cards.Count == 0) - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing) - .ConfigureAwait(false); - else - foreach (var dex in results.Cards) - { - var card = PokemonService.GetExactPokemon(dex.Id); - var output = new DiscordEmbedBuilder() - .WithTitle(card.Name + $" (#{card.NationalPokedexNumber})") - .AddField("Series", card.Series ?? "Unknown", true) - .AddField("Rarity", card.Rarity ?? "Unknown", true) - .AddField("HP", card.Hp ?? "Unknown", true) - .AddField("Ability", card.Ability != null ? card.Ability.Name : "Unknown", true) - .WithImageUrl(card.ImageUrlHiRes ?? card.ImageUrl) - .WithFooter(!string.Equals(card.Id, results.Cards.Last().Id) - ? "Type 'next' within 10 seconds for the next Pokémon" - : "This is the last found Pokémon on the list.") - .WithColor(DiscordColor.Gold); - - var types = new StringBuilder(); - foreach (var type in card.Types) - types.Append(type); - output.AddField("Types", types.ToString() ?? "Unknown", true); - - var weaknesses = new StringBuilder(); - foreach (var weakness in card.Weaknesses) - weaknesses.Append(weakness.Type); - output.AddField("Weaknesses", weaknesses.ToString() ?? "Unknown", true); - await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); - - if (results.Cards.Count == 1) continue; - var interactivity = await BotServices.GetUserInteractivity(ctx, "next", 10).ConfigureAwait(false); - if (interactivity.Result is null) break; - if (!string.Equals(card.Id, results.Cards.Last().Id)) - await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); - } - } - - #endregion COMMAND_POKEMON - } -} \ No newline at end of file diff --git a/src/FlawBOT.Core/Modules/Games/SpeedrunModule.cs b/src/FlawBOT.Core/Modules/Games/SpeedrunModule.cs deleted file mode 100644 index 3f1b5730..00000000 --- a/src/FlawBOT.Core/Modules/Games/SpeedrunModule.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; -using DSharpPlus.Entities; -using FlawBOT.Core.Properties; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Services; - -namespace FlawBOT.Modules -{ - [Cooldown(3, 5, CooldownBucketType.Channel)] - public class SpeedrunModule : BaseCommandModule - { - #region COMMAND_SPEEDRUN - - [Command("speedrun")] - [Aliases("game", "run")] - [Description("Retrieve a game from Speedrun.com")] - 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; - if (results is null || results.Count == 0) - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing) - .ConfigureAwait(false); - else - foreach (var game in results) - { - var output = new DiscordEmbedBuilder() - .WithTitle(game.Names.International) - .AddField("Release Date", game.ReleaseDate ?? "Unknown", true) - .AddField("Developers", SpeedrunService.GetSpeedrunExtraAsync(game.Developers, SpeedrunExtras.Developers).Result ?? "Unknown", true) - .AddField("Publishers", SpeedrunService.GetSpeedrunExtraAsync(game.Publishers, SpeedrunExtras.Publishers).Result ?? "Unknown", true) - .AddField("Platforms", SpeedrunService.GetSpeedrunExtraAsync(game.Platforms, SpeedrunExtras.Platforms).Result ?? "Unknown") - .WithFooter($"ID: {game.Id} - Abbreviation: {game.Abbreviation}") - .WithThumbnail(game.Assets.CoverLarge.Url ?? game.Assets.Icon.Url) - .WithUrl(game.WebLink) - .WithColor(new DiscordColor("#0F7A4D")); - - var link = game.Links.First(x => x.Rel == "categories").Url; - var categories = SpeedrunService.GetSpeedrunCategoryAsync(link).Result.Data; - var category = new StringBuilder(); - if (categories != null || categories.Count > 0) - foreach (var x in categories) - category.Append($"[{x.Name}]({x.Weblink}) **|** "); - output.AddField("Categories", category.ToString() ?? "Unknown", true); - await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); - - if (results.Count == 1) continue; - var interactivity = await BotServices.GetUserInteractivity(ctx, "next", 10).ConfigureAwait(false); - if (interactivity.Result is null) break; - await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); - if (!game.Equals(results.Last())) - await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); - } - } - - #endregion COMMAND_SPEEDRUN - } -} \ No newline at end of file diff --git a/src/FlawBOT.Core/Modules/Games/SteamModule.cs b/src/FlawBOT.Core/Modules/Games/SteamModule.cs deleted file mode 100644 index 7157d0c4..00000000 --- a/src/FlawBOT.Core/Modules/Games/SteamModule.cs +++ /dev/null @@ -1,147 +0,0 @@ -using System.Globalization; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; -using DSharpPlus.Entities; -using FlawBOT.Core.Properties; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Services; -using Steam.Models.SteamCommunity; -using UserStatus = Steam.Models.SteamCommunity.UserStatus; - -namespace FlawBOT.Modules -{ - [Group("steam")] - [Description("Commands finding Steam games and users")] - [Cooldown(3, 5, CooldownBucketType.Channel)] - public class SteamModule : BaseCommandModule - { - #region COMMAND_GAME - - [Command("game")] - [Description("Retrieve Steam game information")] - public async Task SteamGame(CommandContext ctx, - [Description("Game to find on Steam")] [RemainingText] string query = "Team Fortress 2") - { - try - { - var app = SteamService.GetSteamAppAsync(query).Result; - if (app is null) - { - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing) - .ConfigureAwait(false); - } - else - { - 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.IsFree ? "Free" : app.PriceOverview.FinalFormatted ?? "Unknown", true) - .AddField("Metacritic", app.Metacritic != null ? app.Metacritic.Score.ToString() : "Unknown", true) - .WithThumbnail(app.HeaderImage) - .WithUrl("http://store.steampowered.com/app/" + app.SteamAppId) - .WithFooter("App ID: " + app.SteamAppId) - .WithColor(new DiscordColor("#1B2838")); - - var genres = new StringBuilder(); - foreach (var genre in app.Genres.Take(3)) - genres.Append(genre.Description).Append(!genre.Equals(app.Genres.Last()) ? ", " : string.Empty); - output.AddField("Genres", 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 - - #region COMMAND_USER - - [Command("user")] - [Aliases("player")] - [Description("Retrieve Steam user information")] - public async Task SteamUser(CommandContext ctx, - [Description("User to find on Steam")] [RemainingText] string query) - { - if (!BotServices.CheckUserInput(query)) return; - var profile = SteamService.GetSteamProfileAsync(query).Result; - var summary = SteamService.GetSteamSummaryAsync(query).Result; - if (profile is null && summary is null) - { - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing) - .ConfigureAwait(false); - } - else - { - if (summary.Data.ProfileVisibility != ProfileVisibility.Public) - { - await BotServices.SendEmbedAsync(ctx, "This profile is private...", EmbedType.Warning) - .ConfigureAwait(false); - } - else - { - var output = new DiscordEmbedBuilder() - .WithTitle(summary.Data.Nickname) - .WithDescription(Regex.Replace(profile?.Summary ?? string.Empty, "<[^>]*>", "") ?? string.Empty) - .AddField("Member since", summary.Data.AccountCreatedDate.ToUniversalTime().ToString(CultureInfo.CurrentCulture), true) - .WithThumbnail(profile?.AvatarFull.ToString() ?? profile.Avatar.ToString()) - .WithColor(new DiscordColor("#1B2838")) - .WithUrl("http://steamcommunity.com/profiles/" + profile.SteamID) - .WithFooter("Steam ID: " + profile.SteamID); - - 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); - - if (!string.IsNullOrWhiteSpace(profile.Location)) - output.AddField("Location", profile.Location); - - 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); - } - - await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); - } - } - } - - #endregion COMMAND_USER - - #region COMMAND_CONNECT - - [Command("connect")] - [Aliases("link")] - [Description("Format a game connection string into a link")] - public async Task SteamServerLink(CommandContext ctx, - [Description("Connection string")] [RemainingText] string link) - { - 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}")) - .ConfigureAwait(false); - else - await BotServices.SendEmbedAsync(ctx, Resources.ERR_STEAM_CONNECT_FORMAT, EmbedType.Warning) - .ConfigureAwait(false); - } - - #endregion COMMAND_CONNECT - } -} \ No newline at end of file diff --git a/src/FlawBOT.Core/Modules/Games/TeamFortressModule.cs b/src/FlawBOT.Core/Modules/Games/TeamFortressModule.cs deleted file mode 100644 index 163ca7aa..00000000 --- a/src/FlawBOT.Core/Modules/Games/TeamFortressModule.cs +++ /dev/null @@ -1,306 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; -using DSharpPlus.Entities; -using FlawBOT.Core.Properties; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Services; -using TeamworkTF.Sharp; - -namespace FlawBOT.Modules -{ - [Group("tf2")] - [Description("Commands related to Team Fortress 2")] - [Cooldown(3, 5, CooldownBucketType.Channel)] - public class TeamFortressModule : BaseCommandModule - { - #region COMMAND_SCHEMA - - [Command("item")] - [Aliases("schema", "hat")] - [Description("Retrieve an item from the latest TF2 item schema")] - public async Task Tf2Item(CommandContext ctx, - [Description("Item to find in the TF2 schema")] [RemainingText] string query = "The Scattergun") - { - var item = TeamFortressService.GetSchemaItem(query); - if (item is null) - { - 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(item.ItemDescription ?? string.Empty) - .WithImageUrl(item.ImageUrlLarge ?? item.ImageUrl) - .WithUrl("https://wiki.teamfortress.com/wiki/" + item.ItemName.Replace(' ', '_')) - .WithFooter("ID: " + item.DefIndex) - .WithColor(new DiscordColor("#E7B53B")); - - var classes = new StringBuilder(); - foreach (var className in item.UsedByClasses) - classes.Append(className).Append(!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.ItemTypeName ?? "Unknown", true); - output.AddField("Giftable:", item.Capabilities.CanGiftWrap == true ? "Yes" : "No", true); - output.AddField("Nameable:", item.Capabilities.Nameable ? "Yes" : "No", true); - output.AddField("Restriction:", item.HolidayRestriction ?? "None", true); - await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); - } - } - - #endregion COMMAND_SCHEMA - - #region COMMAND_MAP - - [Command("map")] - [Aliases("maps")] - [Description("Retrieve map information from teamwork.tf")] - 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()).ConfigureAwait(false); - if (results is null) - { - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing) - .ConfigureAwait(false); - } - else - { - double.TryParse(results.AllTimeAvgPlayers, out var avgPlayers); - var output = new DiscordEmbedBuilder() - .WithTitle(results.MapName) - .AddField("Highest Server Count", results.HighestServers.ToString() ?? "Unknown", true) - .AddField("Highest Player Count", results.HighestPlayers.ToString() ?? "Unknown", true) - .AddField("Avg. Players", Math.Round(avgPlayers, 2).ToString(CultureInfo.InvariantCulture) ?? "Unknown", true) - .WithFooter("Statistics retrieved from teamwork.tf - refreshed every 5 minutes") - .WithImageUrl(results.Thumbnail) - .WithUrl("https://wiki.teamfortress.com/wiki/" + results.MapName) - .WithColor(new DiscordColor("#E7B53B")); - - if (results.RelatedMaps.Count > 0) - { - var maps = new StringBuilder(); - foreach (var map in results.RelatedMaps.Take(4)) - maps.Append(map + "\n"); - output.AddField("Related Map(s)", maps.ToString(), true); - } - - if (results.ExtraInfo != null) - { - var links = new StringBuilder(); - if (results.ExtraInfo.SteamWorkshopUrl != null) - links.Append($"[Steam Workshop]({results.ExtraInfo.SteamWorkshopUrl}) **|**"); - if (results.ExtraInfo.TF2MapsUrl != null) - links.Append($"[TF2Maps]({results.ExtraInfo.TF2MapsUrl}) **|**"); - if (results.ExtraInfo.GameBananaUrl != null) - links.Append($"[GameBanana]({results.ExtraInfo.GameBananaUrl}) **|**"); - output.AddField("Links", links.ToString(), true); - } - - if (results.GameModes.Count > 0) - { - var desc = TeamFortressService.GetGameModeAsync(results.GameModes.FirstOrDefault()).Result; - output.WithDescription(desc.Title + " - " + desc.Description); - output.WithColor(new DiscordColor($"#{desc.Color}")); - } - - await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); - } - } - - #endregion COMMAND_MAP - - #region COMMAND_NEWS - - [Command("news")] - [Description("Retrieve the latest news article from teamwork.tf")] - public async Task Tf2News(CommandContext ctx, - [Description("Page number from which to retrieve the news")] int query = 0) - { - var results = await TeamFortressService.GetNewsOverviewAsync(query).ConfigureAwait(false); - if (results is null) - { - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing) - .ConfigureAwait(false); - } - else - { - var output = new DiscordEmbedBuilder().WithColor(new DiscordColor("#E7B53B")); - foreach (var result in results.Take(5)) - output.AddField(result.CreatedAt.Date.ToString(), $"{result.Type}: [{result.Title}]({result.Link.AbsoluteUri})"); - await ctx.RespondAsync("Latest news articles from teamwork.tf", embed: output.Build()) - .ConfigureAwait(false); - } - } - - #endregion COMMAND_NEWS - - #region COMMAND_CREATORS - - [Command("creator")] - [Aliases("creators", "youtuber")] - [Description("Retrieve a community creator profile from teamwork.tf")] - public async Task Tf2Creators(CommandContext ctx, - [Description("Name of the community creator to find")] [RemainingText] string query) - { - if (!BotServices.CheckUserInput(query)) return; - var steamId = SteamService.GetSteamUserId(query).Result.Data; - var results = await TeamFortressService.GetCreatorByIdAsync(steamId).ConfigureAwait(false); - if (results is null) - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing) - .ConfigureAwait(false); - else - foreach (var creator in results) - { - var user = results.FirstOrDefault(); - var output = new DiscordEmbedBuilder() - .WithTitle(user?.Name) - .WithDescription("Main Class: " + user?.Main.ToString()?.ToUpper()) - .WithThumbnail(user?.ThumbnailUrl) - .WithUrl(user?.Link) - .WithColor(new DiscordColor("#E7B53B")) - .WithFooter(!creator.Equals(results.Last()) - ? "Type 'next' within 10 seconds for the next creator" - : "Data retrieved from teamwork.tf"); - - var links = new StringBuilder(); - if (creator.DiscordGroup != null) - links.Append($"[Discord]({Resources.URL_DISCORD}{creator.DiscordGroup}) **|** "); - if (!string.IsNullOrWhiteSpace(creator.Steam)) - links.Append($"[Steam]({Resources.URL_STEAM_USER}{creator.Steam}) **|** "); - if (creator.SteamGroup != null) - links.Append($"[Steam Group]({Resources.URL_STEAM_GROUP}{creator.SteamGroup}) **|** "); - if (creator.Twitch != null) - links.Append($"[Twitch]({Resources.URL_TWITCH_CHANNEL}{creator.Twitch}) **|** "); - if (!string.IsNullOrWhiteSpace(creator.Twitter)) - links.Append($"[Twitter]({Resources.URL_YOUTUBE_CHANNEL}{creator.Twitter}) **|** "); - if (!string.IsNullOrWhiteSpace(creator.Youtube)) - links.Append($"[YouTube]({Resources.URL_YOUTUBE_CHANNEL}{creator.Youtube})"); - output.AddField("Links", links.ToString(), true); - var message = await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); - - if (results.Count == 1) continue; - var interactivity = await BotServices.GetUserInteractivity(ctx, "next", 10).ConfigureAwait(false); - if (interactivity.Result is null) break; - await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); - if (!creator.Equals(results.Last())) - await BotServices.RemoveMessage(message).ConfigureAwait(false); - } - } - - #endregion COMMAND_CREATORS - - #region COMMAND_SERVERS - - [Command("server")] - [Aliases("servers")] - [Description("Retrieve a list of servers with given game-mode")] - public async Task Tf2ServerByMode(CommandContext ctx, - [Description("Name of the game-mode, like payload")] [RemainingText] string query) - { - if (!BotServices.CheckUserInput(query)) return; - var results = await TeamFortressService.GetGameModeServerAsync(query.Trim().Replace(' ', '-')) - .ConfigureAwait(false); - if (results is null) - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing) - .ConfigureAwait(false); - else - await OutputServerInfo(ctx, results.OrderBy(_ => new Random().Next()).ToList()).ConfigureAwait(false); - } - - [Command("ip")] - [Aliases("find")] - [Description("Retrieve a game server with given ip address")] - public async Task Tf2ServerByIp(CommandContext ctx, - [Description("Game server IP address, like 164.132.233.16")] string ip, - [Description("Game server port, like 27022")] int port = 0) - { - if (!BotServices.CheckUserInput(ip)) return; - var regex = new Regex(@"\s*(?'ip'\S+)\s*", RegexOptions.Compiled).Match(ip); - if (!regex.Success) return; - var results = await TeamFortressService.GetGameServerInfoAsync(ip, port).ConfigureAwait(false); - if (results is null || results.Count <= 0) - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing) - .ConfigureAwait(false); - else - await OutputServerInfo(ctx, results.OrderBy(_ => new Random().Next()).ToList()).ConfigureAwait(false); - } - - public async Task OutputServerInfo(CommandContext ctx, List results) - { - foreach (var server in results) - { - var output = new DiscordEmbedBuilder() - .WithTitle(server.Name) - .WithDescription("steam://connect/" + server.Ip + ":" + server.Port) - .AddField("Provider", server.Provider != null ? server.Provider.Name : "Unknown", true) - .AddField("Player Count", (server.Players.ToString() ?? "Unknown") + "/" + (server.MaxPlayers.ToString() ?? "Unknown"), true) - .AddField("Password Lock", server.HasPassword ? "Yes" : "No", true) - .AddField("Random Crits", server.HasRandomCrits == true ? "Yes" : "No", true) - .AddField("Instant Respawn", server.HasNoRespawnTime ? "Yes" : "No", true) - .AddField("All Talk", server.HasAllTalk ? "Yes" : "No", true) - .AddField("Current Map", server.MapName ?? "Unknown", true) - .AddField("Next Map", server.MapNameNext ?? "Unknown", true) - .WithFooter("Type 'next' within 10 seconds for the next server") - .WithColor(new DiscordColor("#E7B53B")); - - var thumbnailUrl = await TeamFortressService.GetMapThumbnailAsync(server.MapName).ConfigureAwait(false); - output.WithImageUrl(thumbnailUrl.Name); - - var message = await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); - - if (results.Count == 1) continue; - var interactivity = await BotServices.GetUserInteractivity(ctx, "next", 10).ConfigureAwait(false); - if (interactivity.Result is null) break; - if (!server.Equals(results.Last())) - await BotServices.RemoveMessage(message).ConfigureAwait(false); - await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); - } - } - - [Command("server-list")] - [Aliases("serverList")] - [Description("Retrieve a curated list of servers")] - public async Task Tf2ServerList(CommandContext ctx) - { - var results = await TeamFortressService.GetCustomServerListsAsync().ConfigureAwait(false); - results = results.OrderBy(_ => new Random().Next()).ToList(); - foreach (var server in results) - { - var output = new DiscordEmbedBuilder() - .WithTitle(server.Name) - .WithDescription( - Regex.Replace( - server.DescriptionLarge.Length <= 500 - ? server.DescriptionLarge - : server.DescriptionLarge.Substring(0, 250) + "...", "<[^>]*>", "") ?? "Unknown") - .AddField("Created by", server.Creator.Name ?? "Unknown", true) - .AddField("Subscribers", server.Subscribed.ToString() ?? "Unknown", true) - .WithUrl(Resources.URL_TEAMWORK_SERVERS + server.Id) - .WithFooter("Type 'next' within 10 seconds for the next server list") - .WithColor(new DiscordColor("#E7B53B")); - var message = await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); - - if (results.Count == 1) continue; - var interactivity = await BotServices.GetUserInteractivity(ctx, "next", 10).ConfigureAwait(false); - if (interactivity.Result is null) break; - if (!server.Equals(results.Last())) - await BotServices.RemoveMessage(message).ConfigureAwait(false); - await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); - } - } - - #endregion COMMAND_SERVERS - } -} \ No newline at end of file diff --git a/src/FlawBOT.Core/Modules/Misc/MiscModule.cs b/src/FlawBOT.Core/Modules/Misc/MiscModule.cs deleted file mode 100644 index 2351cf59..00000000 --- a/src/FlawBOT.Core/Modules/Misc/MiscModule.cs +++ /dev/null @@ -1,199 +0,0 @@ -using System; -using System.Net; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using DSharpPlus; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; -using DSharpPlus.Entities; -using FlawBOT.Common; -using FlawBOT.Core.Properties; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Services; -using Newtonsoft.Json.Linq; - -namespace FlawBOT.Modules -{ - [Cooldown(3, 5, CooldownBucketType.Channel)] - public class MiscModule : BaseCommandModule - { - #region COMMAND_8BALL - - [Command("ask")] - [Aliases("8b", "8ball", "ball")] - [Description("Ask an 8-ball a question")] - public Task EightBall(CommandContext ctx, - [Description("Question to ask the 8-Ball")] [RemainingText] string question = "") - { - if (string.IsNullOrWhiteSpace(question)) return null; - var output = new DiscordEmbedBuilder() - .WithDescription(":8ball: " + EightBallService.GetRandomAnswer() + " (" + ctx.User.Mention + ")") - .WithColor(DiscordColor.Black); - return ctx.RespondAsync(embed: output.Build()); - } - - #endregion COMMAND_8BALL - - #region COMMAND_CATFACT - - [Command("cat")] - [Aliases("catfact")] - [Description("Retrieve a random cat fact")] - public async Task CatFact(CommandContext ctx) - { - var results = CatService.GetCatFactAsync().Result; - var output = new DiscordEmbedBuilder() - .WithDescription($":cat: {JObject.Parse(results)["fact"]}") - .WithColor(DiscordColor.Orange); - await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); - } - - #endregion COMMAND_CATFACT - - #region COMMAND_CATPIC - - [Command("randomcat")] - [Aliases("meow")] - [Description("Retrieve a random cat photo")] - 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) - .ConfigureAwait(false); - var output = new DiscordEmbedBuilder() - .WithImageUrl(results) - .WithColor(DiscordColor.Orange); - await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); - } - - #endregion COMMAND_CATPIC - - #region COMMAND_COINFLIP - - [Command("coinflip")] - [Aliases("coin", "flip")] - [Description("Flip a coin")] - public Task CoinFlip(CommandContext ctx) - { - var random = new Random(); - var output = new DiscordEmbedBuilder() - .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()); - } - - #endregion COMMAND_COINFLIP - - #region COMMAND_COLOR - - [Command("color")] - [Aliases("clr")] - [Description("Retrieve color values for a given HEX code")] - public async Task GetColor(CommandContext ctx, - [Description("HEX color code to process")] DiscordColor color) - { - 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) - .ConfigureAwait(false); - } - else - { - var output = new DiscordEmbedBuilder() - .AddField("HEX:", $"#{color.Value:X}") - .AddField("RGB:", $"{color.R} {color.G} {color.B}") - .AddField("Decimal:", color.Value.ToString()) - .WithColor(color); - await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); - } - } - - #endregion COMMAND_COLOR - - #region COMMAND_DICEROLL - - [Command("diceroll")] - [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.Username + " rolled a die and got " + Formatter.Bold(random.Next(1, 7).ToString())) - .WithColor(SharedData.DefaultColor); - return ctx.RespondAsync(embed: output.Build()); - } - - #endregion COMMAND_DICEROLL - - #region COMMAND_DOGPIC - - [Command("randomdog")] - [Aliases("woof", "dog", "bark")] - [Description("Retrieve a random dog photo")] - 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).ConfigureAwait(false); - } - else - { - var output = new DiscordEmbedBuilder() - .WithImageUrl(results.Message) - .WithColor(DiscordColor.Brown); - await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); - } - } - - #endregion COMMAND_DOGPIC - - #region COMMAND_HELLO - - [Command("hello")] - [Aliases("hi", "howdy")] - [Description("Welcome another user to the server")] - 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).ConfigureAwait(false); - else - await ctx.RespondAsync(":wave: Welcome " + member.Mention + " to " + ctx.Guild.Name + ". Enjoy your stay!").ConfigureAwait(false); - } - - #endregion COMMAND_HELLO - - #region COMMAND_IP - - [Command("ip")] - [Aliases("ipstack", "track")] - [Description("Retrieve IP address geolocation information")] - public async Task IpLocation(CommandContext ctx, - [Description("IP Address")] string address) - { - if (string.IsNullOrWhiteSpace(address) || !IPAddress.TryParse(address, out var ip)) return; - var results = GoogleService.GetIpLocationAsync(ip).Result; - if (results.Status != "success") - { - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_LOCATION, EmbedType.Warning) - .ConfigureAwait(false); - } - else - { - var output = new DiscordEmbedBuilder() - .AddField("Location", $"{results.City}, {results.Region}, {results.Country}") - .AddField("ISP", results.Isp) - .AddField("Coordinates", $"{results.Latitude}°N, {results.Longitude}°W") - .WithFooter($"IP: {results.Query}") - .WithColor(new DiscordColor("#4d2f63")); - await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); - } - } - - #endregion COMMAND_IP - } -} \ No newline at end of file diff --git a/src/FlawBOT.Core/Modules/Misc/PollModule.cs b/src/FlawBOT.Core/Modules/Misc/PollModule.cs deleted file mode 100644 index 797b792b..00000000 --- a/src/FlawBOT.Core/Modules/Misc/PollModule.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; -using DSharpPlus.Entities; -using DSharpPlus.Interactivity; -using FlawBOT.Core.Properties; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Services; - -namespace FlawBOT.Modules -{ - [Cooldown(3, 5, CooldownBucketType.Channel)] - public class PollModule : BaseCommandModule - { - #region COMMAND_POLL - - [Command("poll")] - [Description("Run a Yay or Nay poll in the current channel")] - public async Task Poll(CommandContext ctx, - [Description("Question to be polled")] [RemainingText] string question) - { - 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 - { - DiscordEmoji.FromName(ctx.Client, ":thumbsup:"), - DiscordEmoji.FromName(ctx.Client, ":thumbsdown:") - }; - 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).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)).ConfigureAwait(false); - } - } - - #endregion COMMAND_POLL - } -} \ No newline at end of file diff --git a/src/FlawBOT.Core/Modules/Search/AmiiboModule.cs b/src/FlawBOT.Core/Modules/Search/AmiiboModule.cs deleted file mode 100644 index a86a6507..00000000 --- a/src/FlawBOT.Core/Modules/Search/AmiiboModule.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Linq; -using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; -using DSharpPlus.Entities; -using FlawBOT.Core.Properties; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Services; - -namespace FlawBOT.Modules -{ - [Cooldown(3, 5, CooldownBucketType.Channel)] - public class AmiiboModule : BaseCommandModule - { - #region COMMAND_AMIIBO - - [Command("amiibo")] - [Aliases("amib")] - [Description("Retrieve Amiibo figurine information")] - public async Task GetAmiibo(CommandContext ctx, - [Description("Name of the Amiibo figurine")] [RemainingText] string query) - { - if (!BotServices.CheckUserInput(query)) return; - var results = await AmiiboService.GetAmiiboDataAsync(query).ConfigureAwait(false); - if (results is null) - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing) - .ConfigureAwait(false); - else - foreach (var amiibo in results.Amiibo) - { - var output = new DiscordEmbedBuilder() - .WithTitle(amiibo.Name) - .AddField("Amiibo Series", amiibo.AmiiboSeries, true) - .AddField("Game Series", amiibo.GameSeries, true) - .AddField(":flag_us: Release:", amiibo.ReleaseDate.American, true) - .AddField(":flag_jp: Release:", amiibo.ReleaseDate.Japanese, true) - .AddField(":flag_eu: Release:", amiibo.ReleaseDate.European, true) - .AddField(":flag_au: Release:", amiibo.ReleaseDate.Australian, true) - .WithImageUrl(amiibo.Image) - .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()).ConfigureAwait(false); - - if (results.Amiibo.Count == 1) continue; - var interactivity = await BotServices.GetUserInteractivity(ctx, "next", 10).ConfigureAwait(false); - if (interactivity.Result is null) break; - await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); - if (!amiibo.Equals(results.Amiibo.Last())) - await BotServices.RemoveMessage(message).ConfigureAwait(false); - } - } - - #endregion COMMAND_AMIIBO - } -} \ No newline at end of file diff --git a/src/FlawBOT.Core/Modules/Search/DictionaryModule.cs b/src/FlawBOT.Core/Modules/Search/DictionaryModule.cs deleted file mode 100644 index 50d28f40..00000000 --- a/src/FlawBOT.Core/Modules/Search/DictionaryModule.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Linq; -using System.Threading.Tasks; -using DSharpPlus; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; -using DSharpPlus.Entities; -using FlawBOT.Core.Properties; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Services; - -namespace FlawBOT.Modules -{ - [Cooldown(3, 5, CooldownBucketType.Channel)] - public class DictionaryModule : BaseCommandModule - { - #region COMMAND_DICTIONARY - - [Command("dictionary")] - [Aliases("define", "def", "dic")] - [Description("Retrieve an Urban Dictionary definition of a word or phrase")] - 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.GetDictionaryDefinitionAsync(query).ConfigureAwait(false); - if (results.ResultType == "no_results" || results.List.Count == 0) - 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)) - .WithDescription(!string.IsNullOrWhiteSpace(definition.Author) - ? "Submitted by: " + definition.Author - : string.Empty) - .AddField("Definition", 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(!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()).ConfigureAwait(false); - - if (results.List.Count == 1) continue; - var interactivity = await BotServices.GetUserInteractivity(ctx, "next", 10).ConfigureAwait(false); - if (interactivity.Result is null) break; - await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); - if (!definition.Equals(results.List.Last())) - await BotServices.RemoveMessage(message).ConfigureAwait(false); - } - } - - #endregion COMMAND_DICTIONARY - } -} \ No newline at end of file diff --git a/src/FlawBOT.Core/Modules/Search/GoogleModule.cs b/src/FlawBOT.Core/Modules/Search/GoogleModule.cs deleted file mode 100644 index d2099998..00000000 --- a/src/FlawBOT.Core/Modules/Search/GoogleModule.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Threading.Tasks; -using DSharpPlus; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; -using DSharpPlus.Entities; -using FlawBOT.Common; -using FlawBOT.Core.Properties; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Services; - -namespace FlawBOT.Modules -{ - [Cooldown(3, 5, CooldownBucketType.Channel)] - public class GoogleModule : BaseCommandModule - { - #region COMMAND_TIME - - [Command("time")] - [Aliases("clock")] - [Description("Retrieve the time for specified location")] - public async Task GetTime(CommandContext ctx, - [Description("Location to retrieve time data from")] [RemainingText] string location) - { - if (!BotServices.CheckUserInput(location)) return; - var results = GoogleService.GetTimeDataAsync(location).Result; - if (results == null || results.Status != "OK") - { - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing) - .ConfigureAwait(false); - } - else - { - var output = new DiscordEmbedBuilder() - .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()).ConfigureAwait(false); - } - } - - #endregion COMMAND_TIME - - #region COMMAND_NEWS - - [Command("news")] - [Description("Retrieve the latest news articles from NewsAPI.org")] - public async Task News(CommandContext ctx, - [Description("Article topic to find on Google News")] [RemainingText] string query) - { - var results = await GoogleService.GetNewsDataAsync(query).ConfigureAwait(false); - if (results.Status != "ok") - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing) - .ConfigureAwait(false); - else - while (results.Articles.Count > 0) - { - var output = new DiscordEmbedBuilder() - .WithFooter("Type 'next' within 10 seconds for the next five articles.") - .WithColor(new DiscordColor("#253B80")); - - foreach (var result in results.Articles.Take(5)) - { - output.AddField(result.PublishDate.ToString(CultureInfo.InvariantCulture), $"[{result.Title}]({result.Url})"); - results.Articles.Remove(result); - } - - var message = await ctx - .RespondAsync("Latest Google News articles from News API", embed: output.Build()) - .ConfigureAwait(false); - - if (results.Articles.Count == 5) continue; - var interactivity = await BotServices.GetUserInteractivity(ctx, "next", 10).ConfigureAwait(false); - if (interactivity.Result is null) break; - await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); - await BotServices.RemoveMessage(message).ConfigureAwait(false); - } - } - - #endregion COMMAND_NEWS - - #region COMMAND_WEATHER - - [Command("weather")] - [Description("Retrieve the weather for specified location")] - 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).ConfigureAwait(false); - if (results == null || results.Cod == 404) - { - 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("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()).ConfigureAwait(false); - } - } - - #endregion COMMAND_WEATHER - } -} \ No newline at end of file diff --git a/src/FlawBOT.Core/Modules/Search/OMDBModule.cs b/src/FlawBOT.Core/Modules/Search/OMDBModule.cs deleted file mode 100644 index 6a0c9532..00000000 --- a/src/FlawBOT.Core/Modules/Search/OMDBModule.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Linq; -using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; -using DSharpPlus.Entities; -using FlawBOT.Core.Properties; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Services; - -namespace FlawBOT.Modules -{ - [Cooldown(3, 5, CooldownBucketType.Channel)] - public class OmdbModule : BaseCommandModule - { - #region COMMAND_OMDB - - [Command("omdb")] - [Aliases("imdb", "movie")] - [Description("Retrieve a movie or TV show from OMDB")] - public async Task Omdb(CommandContext ctx, - [Description("Movie or TV show to find on OMDB")] [RemainingText] string query) - { - 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) - .ConfigureAwait(false); - } - else - { - var output = new DiscordEmbedBuilder() - .WithTitle(results.Title) - .WithDescription(results.Plot.Length < 500 ? results.Plot : results.Plot.Take(500) + "...") - .AddField("Released", results.Released, true) - .AddField("Runtime", results.Runtime, true) - .AddField("Genre", results.Genre, true) - .AddField("Rating", results.Rated, true) - .AddField("Country", results.Country, true) - .AddField("Box Office", results.BoxOffice, true) - .AddField("Production", results.Production, true) - .AddField("IMDb Rating", results.IMDbRating, true) - .AddField("Metacritic", results.Metascore, true) - .AddField("Directors", results.Director) - .AddField("Actors", results.Actors) - .WithColor(DiscordColor.Goldenrod); - if (results.Poster != "N/A") output.WithImageUrl(results.Poster); - await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); - } - } - - #endregion COMMAND_OMDB - } -} \ No newline at end of file diff --git a/src/FlawBOT.Core/Modules/Search/TwitchModule.cs b/src/FlawBOT.Core/Modules/Search/TwitchModule.cs deleted file mode 100644 index 3e99fcb2..00000000 --- a/src/FlawBOT.Core/Modules/Search/TwitchModule.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; -using DSharpPlus.Entities; -using FlawBOT.Core.Properties; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Services; - -namespace FlawBOT.Modules -{ - [Cooldown(3, 5, CooldownBucketType.Channel)] - public class TwitchModule : BaseCommandModule - { - #region COMMAND_TWITCH - - [Command("twitch")] - [Aliases("stream")] - [Description("Retrieve Twitch stream information")] - 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).ConfigureAwait(false); - if (results.Stream.Count == 0) - { - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_TWITCH, EmbedType.Missing) - .ConfigureAwait(false); - } - else - { - var stream = results.Stream[0]; - var output = new DiscordEmbedBuilder() - .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()).ConfigureAwait(false); - } - } - - #endregion COMMAND_TWITCH - } -} \ No newline at end of file diff --git a/src/FlawBOT.Core/Modules/Search/WikipediaModule.cs b/src/FlawBOT.Core/Modules/Search/WikipediaModule.cs deleted file mode 100644 index 02e71de2..00000000 --- a/src/FlawBOT.Core/Modules/Search/WikipediaModule.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; -using FlawBOT.Core.Properties; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Services; - -namespace FlawBOT.Modules -{ - [Cooldown(3, 5, CooldownBucketType.Channel)] - public class WikipediaModule : BaseCommandModule - { - #region COMMAND_WIKIPEDIA - - [Command("wiki")] - [Aliases("wikipedia")] - [Description("Search Wikipedia for a given query")] - public async Task Wikipedia(CommandContext ctx, - [Description("Query to search on Wikipedia")] [RemainingText] string query) - { - if (!BotServices.CheckUserInput(query)) return; - var results = WikipediaService.GetWikipediaDataAsync(query).Result; - if (results.Missing) - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_WIKIPEDIA, EmbedType.Missing) - .ConfigureAwait(false); - else - await ctx.Channel.SendMessageAsync(results.FullUrl).ConfigureAwait(false); - } - - #endregion COMMAND_WIKIPEDIA - } -} \ No newline at end of file diff --git a/src/FlawBOT.Core/Modules/Search/YoutubeModule.cs b/src/FlawBOT.Core/Modules/Search/YoutubeModule.cs deleted file mode 100644 index 2352763a..00000000 --- a/src/FlawBOT.Core/Modules/Search/YoutubeModule.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System.Threading.Tasks; -using DSharpPlus; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; -using FlawBOT.Framework.Services; - -namespace FlawBOT.Modules -{ - [Group("youtube")] - [Aliases("yt")] - [Description("Commands for finding YouTube videos, channels and playlists")] - [Cooldown(3, 5, CooldownBucketType.Channel)] - public class YouTubeModule : BaseCommandModule - { - #region COMMAND_CHANNEL - - [Command("channel")] - [Aliases("channels", "chn")] - [Description("Retrieve a list of YouTube channel given a query")] - public async Task YouTubeChannel(CommandContext ctx, - [Description("Channels to find on YouTube")] [RemainingText] string query) - { - if (!BotServices.CheckUserInput(query)) return; - var service = new YoutubeService(); - 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 - - #region COMMAND_PLAYLIST - - [Command("playlist")] - [Aliases("playlists", "list")] - [Description("Retrieve a list of YouTube playlists given a query")] - public async Task YouTubePlaylist(CommandContext ctx, - [Description("Playlist to find on YouTube")] [RemainingText] string query) - { - if (!BotServices.CheckUserInput(query)) return; - var service = new YoutubeService(); - 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 - - #region COMMAND_SEARCH - - [Command("search")] - [Aliases("find")] - [Description("Retrieve the first YouTube search result given a query")] - public async Task YouTubeVideo(CommandContext ctx, - [Description("First result video to find on YouTube")] [RemainingText] string query) - { - if (!BotServices.CheckUserInput(query)) return; - var service = new YoutubeService(); - var results = await service.GetFirstVideoResultAsync(query).ConfigureAwait(false); - await ctx.RespondAsync(results).ConfigureAwait(false); - } - - #endregion COMMAND_SEARCH - - #region COMMAND_VIDEO - - [Command("video")] - [Aliases("videos", "vid")] - [Description("Retrieve a list of YouTube videos given a query")] - public async Task YouTubeSearch(CommandContext ctx, - [Description("Video to find on YouTube")] [RemainingText] string query) - { - if (!BotServices.CheckUserInput(query)) return; - var service = new YoutubeService(); - 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 - } -} \ No newline at end of file diff --git a/src/FlawBOT.Core/Modules/Server/BotModule.cs b/src/FlawBOT.Core/Modules/Server/BotModule.cs deleted file mode 100644 index 3fd92414..00000000 --- a/src/FlawBOT.Core/Modules/Server/BotModule.cs +++ /dev/null @@ -1,320 +0,0 @@ -using System; -using System.Threading.Tasks; -using DSharpPlus; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; -using DSharpPlus.Entities; -using FlawBOT.Common; -using FlawBOT.Core.Properties; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Services; - -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", "about")] - [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 BotServices.GetUserInteractivity(ctx, "yes", 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 BotServices.SendEmbedAsync(ctx, $":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 BotServices.GetUserInteractivity(ctx, "yes", 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_TTS - - [Command("tts")] - [Aliases("talk")] - [Description("Make FlawBOT repeat a message in text-to-speech")] - [RequirePermissions(Permissions.SendTtsMessages)] - public Task SayTts(CommandContext ctx, - [Description("Message for the bot to convert to speech")] [RemainingText] string text) - { - return string.IsNullOrWhiteSpace(text) ? ctx.RespondAsync("I need something to say...") : ctx.RespondAsync(Formatter.BlockCode(Formatter.Strip(text)), true); - } - - #endregion COMMAND_TTS - - #region COMMAND_UPTIME - - [Command("uptime")] - [Aliases("time")] - [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().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(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", "pfp", "photo")] - [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", "state")] - [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 TeamFortressService.UpdateTf2SchemaAsync().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", "nick")] - [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(SharedData.Name).ConfigureAwait(false); - await BotServices.SendEmbedAsync(ctx, oldUsername + " username has been changed to " + SharedData.Name) - .ConfigureAwait(false); - } - else - { - await ctx.Client.UpdateCurrentUserAsync(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/RoleModule.cs b/src/FlawBOT.Core/Modules/Server/RoleModule.cs deleted file mode 100644 index 2d727244..00000000 --- a/src/FlawBOT.Core/Modules/Server/RoleModule.cs +++ /dev/null @@ -1,287 +0,0 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using DSharpPlus; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; -using DSharpPlus.Entities; -using FlawBOT.Core.Properties; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Services; - -namespace FlawBOT.Modules -{ - [Group("role")] - [Aliases("roles", "rl")] - [Description("Commands for controlling server roles")] - [Cooldown(3, 5, CooldownBucketType.Guild)] - public class RoleModule : BaseCommandModule - { - #region COMMAND_COLOR - - [Command("color")] - [Aliases("clr")] - [Description("Set the role color")] - [RequirePermissions(Permissions.ManageRoles)] - public async Task ColorRole(CommandContext ctx, - [Description("HEX color code to set for the role")] DiscordColor color, - [Description("Server role to recolor")] [RemainingText] DiscordRole role) - { - var regex = new Regex(@"^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$", RegexOptions.Compiled).Match(color.ToString()); - if (regex.Success) - { - await role.ModifyAsync(color: color).ConfigureAwait(false); - 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()).ConfigureAwait(false); - } - else - { - await BotServices.SendEmbedAsync(ctx, "Invalid color code. Please enter a HEX color code like #E7B53B", EmbedType.Warning).ConfigureAwait(false); - } - } - - #endregion COMMAND_COLOR - - #region COMMAND_CREATE - - [Command("create")] - [Aliases("new")] - [Description("Create a server role")] - [RequirePermissions(Permissions.ManageRoles)] - 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).ConfigureAwait(false); - } - else - { - await ctx.Guild.CreateRoleAsync(role).ConfigureAwait(false); - await BotServices - .SendEmbedAsync(ctx, "Successfully created the server role " + Formatter.Bold(role), EmbedType.Good) - .ConfigureAwait(false); - } - } - - #endregion COMMAND_CREATE - - #region COMMAND_DELETE - - [Command("delete")] - [Aliases("remove")] - [Description("Delete a server role")] - [RequirePermissions(Permissions.ManageRoles)] - 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) - .ConfigureAwait(false); - } - else - { - await role.DeleteAsync().ConfigureAwait(false); - await BotServices.SendEmbedAsync(ctx, "Successfully removed the server role " + Formatter.Bold(role.Name), EmbedType.Good) - .ConfigureAwait(false); - } - } - - #endregion COMMAND_DELETE - - #region COMMAND_INFO - - [Command("info")] - [Aliases("i")] - [Description("Retrieve role information")] - 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) - .ConfigureAwait(false); - } - else - { - var output = new DiscordEmbedBuilder() - .WithTitle(role.Name) - .WithDescription("ID: " + role.Id) - .AddField("Creation Date", role.CreationTimestamp.DateTime.ToString(CultureInfo.InvariantCulture), true) - .AddField("Hoisted", role.IsHoisted ? "Yes" : "No", true) - .AddField("Mentionable", role.IsMentionable ? "Yes" : "No", true) - .AddField("Permissions", role.Permissions.ToPermissionString()) - .WithThumbnail(ctx.Guild.IconUrl) - .WithFooter($"{ctx.Guild.Name} / #{ctx.Channel.Name} / {DateTime.Now}") - .WithColor(role.Color); - await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); - } - } - - #endregion COMMAND_INFO - - #region COMMAND_INROLE - - [Command("inrole")] - [Description("Retrieve a list of users in a given role")] - 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) - .ConfigureAwait(false); - } - else - { - var userCount = 0; - var usersList = new StringBuilder(); - var users = (await ctx.Guild.GetAllMembersAsync().ConfigureAwait(false)).ToArray(); - foreach (var user in users) - if (user.Roles.Contains(role)) - { - userCount++; - if (user.Equals(users.Last())) - usersList.Append(user.DisplayName); - else - usersList.Append(user.DisplayName).Append(", "); - } - - if (usersList.Length == 0) - 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}") - .ConfigureAwait(false); - } - } - - #endregion COMMAND_INROLE - - #region COMMAND_MENTION - - [Command("mention")] - [Description("Toggle whether this role can be mentioned by others")] - [RequirePermissions(Permissions.ManageRoles)] - public async Task MentionRole(CommandContext ctx, - [Description("Server role to toggle")] [RemainingText] DiscordRole role) - { - if (role is null) return; - if (role.IsMentionable) - { - 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).ConfigureAwait(false); - await BotServices.SendEmbedAsync(ctx, Formatter.Bold(role.Name) + " is now **mentionable**") - .ConfigureAwait(false); - } - } - - #endregion COMMAND_MENTION - - #region COMMAND_REVOKEROLE - - [Command("revoke")] - [Description("Remove a role from server user")] - [RequirePermissions(Permissions.ManageRoles)] - public async Task RemoveUserRole(CommandContext ctx, - [Description("Server user to get revoked")] DiscordMember member, - [Description("Server role to revoke from user")] [RemainingText] DiscordRole role) - { - if (role != null) - { - member = member ?? ctx.Member; - 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); - } - } - - #endregion COMMAND_REVOKEROLE - - #region COMMAND_REVOKEROLES - - [Command("revokeall")] - [Description("Remove all role from server user")] - [RequirePermissions(Permissions.ManageRoles)] - public async Task RemoveUserRoles(CommandContext ctx, - [Description("Server user to get revoked")] DiscordMember member) - { - if (!member.Roles.Any()) - { - 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) - .ConfigureAwait(false); - } - else - { - await member.ReplaceRolesAsync(Enumerable.Empty()).ConfigureAwait(false); - await BotServices - .SendEmbedAsync(ctx, "Removed all roles from " + Formatter.Bold(member.DisplayName), EmbedType.Good) - .ConfigureAwait(false); - } - } - - #endregion COMMAND_REVOKEROLES - - #region COMMAND_SETROLE - - [Command("setrole")] - [Aliases("addrole", "sr")] - [Description("Assign a role to server user")] - [RequirePermissions(Permissions.ManageRoles)] - public async Task SetUserRole(CommandContext ctx, - [Description("Server user to get role assigned")] DiscordMember member, - [Description("Server role to assign to the user")] [RemainingText] DiscordRole role) - { - member = member ?? ctx.Member; - 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 - - #region COMMAND_SHOW - - [Command("show")] - [Aliases("display", "hide")] - [Description("Toggle whether this role is seen or not")] - [RequirePermissions(Permissions.ManageRoles)] - public async Task SidebarRole(CommandContext ctx, - [Description("Server role to toggle")] [RemainingText] DiscordRole role) - { - if (role is null) return; - - if (role.IsHoisted) - { - 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).ConfigureAwait(false); - await BotServices.SendEmbedAsync(ctx, Formatter.Bold(role.Name) + " is now **displayed**") - .ConfigureAwait(false); - } - } - - #endregion COMMAND_SHOW - } -} \ No newline at end of file diff --git a/src/FlawBOT.Core/NuGet.Config b/src/FlawBOT.Core/NuGet.Config deleted file mode 100644 index a9c2d32f..00000000 --- a/src/FlawBOT.Core/NuGet.Config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/FlawBOT.Core/Properties/Resources.Designer.cs b/src/FlawBOT.Core/Properties/Resources.Designer.cs deleted file mode 100644 index 58e4f504..00000000 --- a/src/FlawBOT.Core/Properties/Resources.Designer.cs +++ /dev/null @@ -1,398 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace FlawBOT.Core.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FlawBOT.Core.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Channel with the same name already exists.. - /// - internal static string ERR_CHANNEL_EXISTS { - get { - return ResourceManager.GetString("ERR_CHANNEL_EXISTS", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The name cannot be blank or over 100 characters.. - /// - internal static string ERR_CHANNEL_NAME { - get { - return ResourceManager.GetString("ERR_CHANNEL_NAME", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Channel topic must be less than 1024 characters long.. - /// - internal static string ERR_CHANNEL_TOPIC { - get { - return ResourceManager.GetString("ERR_CHANNEL_TOPIC", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid color code. Please enter a HEX color code like #E7B53B.. - /// - internal static string ERR_COLOR_INVALID { - get { - return ResourceManager.GetString("ERR_COLOR_INVALID", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to reach the Dog.CEO API.. - /// - internal static string ERR_DOG_PHOTO { - get { - return ResourceManager.GetString("ERR_DOG_PHOTO", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to create the server emoji. Either the HTTP request failed or Discord prevented the operation from completing.. - /// - internal static string ERR_EMOJI_ADD { - get { - return ResourceManager.GetString("ERR_EMOJI_ADD", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Please provide a URL pointing to an emoji image or attach an image.. - /// - internal static string ERR_EMOJI_IMAGE { - get { - return ResourceManager.GetString("ERR_EMOJI_IMAGE", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Server emoji name is invalid.. - /// - internal static string ERR_EMOJI_NAME { - get { - return ResourceManager.GetString("ERR_EMOJI_NAME", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to create the server emoji. Either the image size was too large.. - /// - internal static string ERR_EMOJI_SIZE { - get { - return ResourceManager.GetString("ERR_EMOJI_SIZE", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Exception occurred: {0} : - ///{1}. - /// - internal static string ERR_EXCEPTION { - get { - return ResourceManager.GetString("ERR_EXCEPTION", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Inner exception: {0} : - ///{1}. - /// - internal static string ERR_EXCEPTION_INNER { - get { - return ResourceManager.GetString("ERR_EXCEPTION_INNER", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Error calculating math equation, make sure your values are integers and the operation is valid!. - /// - internal static string ERR_MATH_EQUATION { - get { - return ResourceManager.GetString("ERR_MATH_EQUATION", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to reach the NASA API.. - /// - internal static string ERR_NASA_API { - get { - return ResourceManager.GetString("ERR_NASA_API", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Please provide a poll question.. - /// - internal static string ERR_POLL_QUESTION { - get { - return ResourceManager.GetString("ERR_POLL_QUESTION", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Please provide more information on the issue (50 characters minimum).. - /// - internal static string ERR_REPORT_CHAR_LENGTH { - get { - return ResourceManager.GetString("ERR_REPORT_CHAR_LENGTH", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Please provide an existing server role.. - /// - internal static string ERR_ROLE_EXISTING { - get { - return ResourceManager.GetString("ERR_ROLE_EXISTING", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Role name cannot be blank.. - /// - internal static string ERR_ROLE_NAME { - get { - return ResourceManager.GetString("ERR_ROLE_NAME", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to This user doesn't have any roles.. - /// - internal static string ERR_ROLE_NONE { - get { - return ResourceManager.GetString("ERR_ROLE_NONE", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to You are unauthorized to remove roles from this user.. - /// - internal static string ERR_ROLE_NOT_ALLOWED { - get { - return ResourceManager.GetString("ERR_ROLE_NOT_ALLOWED", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid connection info, follow the format: 123.345.56.789:000; password hello. - /// - internal static string ERR_STEAM_CONNECT_FORMAT { - get { - return ResourceManager.GetString("ERR_STEAM_CONNECT_FORMAT", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Shutting Down. - /// - internal static string INFO_SHUTDOWN { - get { - return ResourceManager.GetString("INFO_SHUTDOWN", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Emoji not found in the server list.. - /// - internal static string NOT_FOUND_EMOJI { - get { - return ResourceManager.GetString("NOT_FOUND_EMOJI", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No results found.. - /// - internal static string NOT_FOUND_GENERIC { - get { - return ResourceManager.GetString("NOT_FOUND_GENERIC", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Location not found.. - /// - internal static string NOT_FOUND_LOCATION { - get { - return ResourceManager.GetString("NOT_FOUND_LOCATION", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Reddit post not found.. - /// - internal static string NOT_FOUND_REDDIT { - get { - return ResourceManager.GetString("NOT_FOUND_REDDIT", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Subreddit not found.. - /// - internal static string NOT_FOUND_SUBREDDIT { - get { - return ResourceManager.GetString("NOT_FOUND_SUBREDDIT", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Twitch channel is offline or was not found.. - /// - internal static string NOT_FOUND_TWITCH { - get { - return ResourceManager.GetString("NOT_FOUND_TWITCH", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Wikipedia page not found.. - /// - internal static string NOT_FOUND_WIKIPEDIA { - get { - return ResourceManager.GetString("NOT_FOUND_WIKIPEDIA", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Request timed out.... - /// - internal static string REQUEST_TIMEOUT { - get { - return ResourceManager.GetString("REQUEST_TIMEOUT", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Note: First time gifs take a few minutes to properly generate.. - /// - internal static string SIMPSONS_GIF_WARNING { - get { - return ResourceManager.GetString("SIMPSONS_GIF_WARNING", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to https://discord.gg/. - /// - internal static string URL_DISCORD { - get { - return ResourceManager.GetString("URL_DISCORD", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to https://steamcommunity.com/groups/. - /// - internal static string URL_STEAM_GROUP { - get { - return ResourceManager.GetString("URL_STEAM_GROUP", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to https://steamcommunity.com/id/. - /// - internal static string URL_STEAM_USER { - get { - return ResourceManager.GetString("URL_STEAM_USER", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to https://teamwork.tf/community/customserverlist/. - /// - internal static string URL_TEAMWORK_SERVERS { - get { - return ResourceManager.GetString("URL_TEAMWORK_SERVERS", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to https://www.twitch.tv/. - /// - internal static string URL_TWITCH_CHANNEL { - get { - return ResourceManager.GetString("URL_TWITCH_CHANNEL", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to https://twitter.com/. - /// - internal static string URL_TWITTER { - get { - return ResourceManager.GetString("URL_TWITTER", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to https://www.youtube.com/channel/. - /// - internal static string URL_YOUTUBE_CHANNEL { - get { - return ResourceManager.GetString("URL_YOUTUBE_CHANNEL", resourceCulture); - } - } - } -} diff --git a/src/FlawBOT.Framework/FlawBOT.Framework.csproj b/src/FlawBOT.Framework/FlawBOT.Framework.csproj deleted file mode 100644 index f8724e49..00000000 --- a/src/FlawBOT.Framework/FlawBOT.Framework.csproj +++ /dev/null @@ -1,50 +0,0 @@ - - - - netcoreapp3.1 - 2.6.1.0 - 2.6.1 - 2.6.1.0 - Igor Nikitin - CriticalFlaw - FlawBOT - Multipurpose Discord bot written in C# using DSharpPlus. Application Framework. - https://discordbots.org/bot/339833029013012483 - https://github.com/CriticalFlaw/FlawBOT - en-CA - - - - - - - - - - - - - - - - - - - - - - - True - True - Resources.resx - - - - - - ResXFileCodeGenerator - Resources.Designer.cs - - - - diff --git a/src/FlawBOT.Framework/Models/Games/AmiiboData.cs b/src/FlawBOT.Framework/Models/Games/AmiiboData.cs deleted file mode 100644 index aaa0bccd..00000000 --- a/src/FlawBOT.Framework/Models/Games/AmiiboData.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Newtonsoft.Json; -using System.Collections.Generic; - -namespace FlawBOT.Framework.Models -{ - public class AmiiboData - { - [JsonProperty("amiibo")] - public List Amiibo { get; set; } - } - - public class Amiibo - { - [JsonProperty("amiiboSeries")] - public string AmiiboSeries { get; set; } - - [JsonProperty("gameSeries")] - public string GameSeries { get; set; } - - [JsonProperty("image")] - public string Image { get; set; } - - [JsonProperty("name")] - public string Name { get; set; } - - [JsonProperty("release")] - public Release ReleaseDate { get; set; } - } - - public class Release - { - [JsonProperty("au")] - public string Australian { get; set; } - - [JsonProperty("eu")] - public string European { get; set; } - - [JsonProperty("jp")] - public string Japanese { get; set; } - - [JsonProperty("na")] - public string American { get; set; } - } -} \ No newline at end of file diff --git a/src/FlawBOT.Framework/Models/Games/PokemonData.cs b/src/FlawBOT.Framework/Models/Games/PokemonData.cs deleted file mode 100644 index c89955a7..00000000 --- a/src/FlawBOT.Framework/Models/Games/PokemonData.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Newtonsoft.Json; -using System.Collections.Generic; - -namespace FlawBOT.Framework.Models -{ - public class PokemonCards - { - [JsonProperty("cards")] - public List Cards { get; set; } - } - - public class Card - { - [JsonProperty("id")] - public string Id { get; set; } - } - - public class PokemonData - { - [JsonProperty("results")] - public List Results { get; set; } - } - - public class DataResult - { - [JsonProperty("name")] - public string Name { get; set; } - } -} \ No newline at end of file diff --git a/src/FlawBOT.Framework/Models/Games/SpeedrunData.cs b/src/FlawBOT.Framework/Models/Games/SpeedrunData.cs deleted file mode 100644 index a63aef59..00000000 --- a/src/FlawBOT.Framework/Models/Games/SpeedrunData.cs +++ /dev/null @@ -1,221 +0,0 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Generic; - -namespace FlawBOT.Framework.Models -{ - #region GAME - - public class SpeedrunGame - { - [JsonProperty("data")] - public List Data { get; set; } - } - - public class Data - { - [JsonProperty("id")] - public string Id { get; set; } - - [JsonProperty("names")] - public Names Names { get; set; } - - [JsonProperty("abbreviation")] - public string Abbreviation { get; set; } - - [JsonProperty("weblink")] - public string WebLink { get; set; } - - [JsonProperty("release-date")] - public string ReleaseDate { get; set; } - - [JsonProperty("platforms")] - public List Platforms { get; set; } - - [JsonProperty("developers")] - public List Developers { get; set; } - - [JsonProperty("publishers")] - public List Publishers { get; set; } - - [JsonProperty("assets")] - public Assets Assets { get; set; } - - [JsonProperty("links")] - public List Links { get; set; } - - [JsonIgnore] - [JsonProperty("released")] - public int ReleaseYear { get; set; } - - [JsonIgnore] - [JsonProperty("ruleset")] - public int RuleSet { get; set; } - - [JsonIgnore] - [JsonProperty("romhack")] - public int RomHack { get; set; } - - [JsonIgnore] - [JsonProperty("gametypes")] - public int GameTypes { get; set; } - - [JsonIgnore] - [JsonProperty("regions")] - public int Regions { get; set; } - - [JsonIgnore] - [JsonProperty("genres")] - public int Genres { get; set; } - - [JsonIgnore] - [JsonProperty("engines")] - public int Engines { get; set; } - - [JsonIgnore] - [JsonProperty("created")] - public int Created { get; set; } - } - - public class Names - { - [JsonProperty("international")] - public string International { get; set; } - - [JsonIgnore] - [JsonProperty("japanese")] - public string Japanese { get; set; } - - [JsonIgnore] - [JsonProperty("twitch")] - public string Twitch { get; set; } - } - - public class Assets - { - [JsonProperty("logo")] - public Image Logo { get; set; } - - [JsonProperty("cover-small")] - public Image CoverSmall { get; set; } - - [JsonProperty("cover-medium")] - public Image CoverMedium { get; set; } - - [JsonProperty("cover-large")] - public Image CoverLarge { get; set; } - - [JsonProperty("icon")] - public Image Icon { get; set; } - - [JsonProperty("foreground")] - public object Foreground { get; set; } - } - - public class Image - { - [JsonProperty("uri")] - public string Url { get; set; } - - [JsonIgnore] - [JsonProperty("width")] - public int Width { get; set; } - - [JsonIgnore] - [JsonProperty("height")] - public int Height { get; set; } - } - - public class Link - { - [JsonProperty("rel")] - public string Rel { get; set; } - - [JsonProperty("uri")] - public string Url { get; set; } - } - - #endregion GAME - - #region CATEGORY - - public class SpeedrunCategory - { - [JsonProperty("data")] - public List Data { get; set; } - } - - public class CategoryData - { - [JsonProperty("id")] - public string Id { get; set; } - - [JsonProperty("name")] - public string Name { get; set; } - - [JsonProperty("weblink")] - public Uri Weblink { get; set; } - - [JsonIgnore] - [JsonProperty("type")] - public string Type { get; set; } - - [JsonProperty("rules")] - public string Rules { get; set; } - - [JsonProperty("players")] - public Players Players { get; set; } - - [JsonIgnore] - [JsonProperty("miscellaneous")] - public bool Miscellaneous { get; set; } - - [JsonProperty("links")] - public List Links { get; set; } - } - - public class Players - { - [JsonProperty("type")] - public string Type { get; set; } - - [JsonProperty("value")] - public long Value { get; set; } - } - - #endregion CATEGORY - - #region EXTRA - - public class SpeedrunExtra - { - [JsonProperty("data")] - public ExtraData Data { get; set; } - } - - public class ExtraData - { - [JsonProperty("id")] - public string Id { get; set; } - - [JsonProperty("name")] - public string Name { get; set; } - - [JsonIgnore] - [JsonProperty("released")] - public int? Released { get; set; } - - [JsonProperty("links")] - public List Links { get; set; } - } - - public enum SpeedrunExtras - { - Platforms, - Genres, - Developers, - Publishers - } - - #endregion EXTRA -} \ No newline at end of file diff --git a/src/FlawBOT.Framework/Models/Misc/MiscData.cs b/src/FlawBOT.Framework/Models/Misc/MiscData.cs deleted file mode 100644 index 51018a4e..00000000 --- a/src/FlawBOT.Framework/Models/Misc/MiscData.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Newtonsoft.Json; - -namespace FlawBOT.Framework.Models -{ - public class DogData - { - [JsonProperty("status")] - public string Status { get; set; } - - [JsonProperty("message")] - public string Message { get; set; } - } - - public class IpLocationData - { - [JsonProperty("@as")] - public string Title { get; set; } - - [JsonProperty("city")] - public string City { get; set; } - - [JsonProperty("country")] - public string Country { get; set; } - - [JsonProperty("countryCode")] - public string CountryCode { get; set; } - - [JsonProperty("isp")] - public string Isp { get; set; } - - [JsonProperty("lat")] - public double Latitude { get; set; } - - [JsonProperty("lon")] - public double Longitude { get; set; } - - [JsonProperty("query")] - public string Query { get; set; } - - [JsonProperty("regionName")] - public string Region { get; set; } - - [JsonProperty("region")] - public string RegionCode { get; set; } - - [JsonProperty("status")] - public string Status { get; set; } - - [JsonProperty("timezone")] - public string TimeZone { get; set; } - - [JsonProperty("zip")] - public string ZipCode { get; set; } - } -} \ No newline at end of file diff --git a/src/FlawBOT.Framework/Models/Search/DictionaryData.cs b/src/FlawBOT.Framework/Models/Search/DictionaryData.cs deleted file mode 100644 index 4c1bde2f..00000000 --- a/src/FlawBOT.Framework/Models/Search/DictionaryData.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Newtonsoft.Json; -using System.Collections.Generic; - -namespace FlawBOT.Framework.Models -{ - public class DictionaryData - { - [JsonProperty("result_type")] - public string ResultType { get; set; } - - [JsonProperty("list")] - public List List { get; set; } - } - - public class DictionaryDataList - { - [JsonProperty("definition")] - public string Definition { get; set; } - - [JsonProperty("author")] - public string Author { get; set; } - - [JsonProperty("permalink")] - public string Permalink { get; set; } - - [JsonProperty("example")] - public string Example { get; set; } - - [JsonProperty("thumbs_up")] - public int ThumbsUp { get; set; } - - [JsonProperty("thumbs_down")] - public int ThumbsDown { get; set; } - } -} \ No newline at end of file diff --git a/src/FlawBOT.Framework/Models/Search/GoogleData.cs b/src/FlawBOT.Framework/Models/Search/GoogleData.cs deleted file mode 100644 index a7631318..00000000 --- a/src/FlawBOT.Framework/Models/Search/GoogleData.cs +++ /dev/null @@ -1,163 +0,0 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Generic; - -namespace FlawBOT.Framework.Models -{ - #region TIME - - public class TimeData - { - [JsonProperty("results")] - private GeolocationModel[] _results; - - [JsonProperty("timezone")] - private TimeZoneResult _timeZone; - - [JsonProperty("status")] - public string Status { get; set; } - - public GeolocationModel[] Results { get; } - public TimeZoneResult Timezone { get; set; } - public DateTime Time { get; set; } - - public class GeolocationModel - { - [JsonProperty("formatted_address")] - public string FormattedAddress { get; set; } - - [JsonProperty("geometry")] - public GeometryModel Geometry { get; set; } - - public class GeometryModel - { - [JsonProperty("location")] - public LocationModel Location { get; set; } - - public class LocationModel - { - [JsonProperty("lat")] - public float Latitude { get; set; } - - [JsonProperty("lng")] - public float Longitude { get; set; } - } - } - } - - public class TimeZoneResult - { - [JsonProperty("dstOffset")] - public double DstOffset { get; set; } - - [JsonProperty("rawOffset")] - public double RawOffset { get; set; } - - [JsonProperty("timeZoneName")] - public string TimeZoneName { get; set; } - } - } - - #endregion TIME - - #region NEWS - - public class NewsData - { - [JsonProperty("status")] - public string Status { get; set; } - - [JsonProperty("articles")] - public List
Articles { get; set; } - } - - public class Article - { - [JsonProperty("author")] - public string Author { get; set; } - - [JsonProperty("title")] - public string Title { get; set; } - - [JsonProperty("description")] - public string Description { get; set; } - - [JsonProperty("url")] - public string Url { get; set; } - - [JsonProperty("urlToImage")] - public string UrlImage { get; set; } - - [JsonProperty("publishedAt")] - public DateTime PublishDate { get; set; } - - [JsonProperty("content")] - public string Content { get; set; } - } - - #endregion NEWS - - #region WEATHER - - public class WeatherData - { - [JsonProperty("id")] - public int Id { get; set; } - - [JsonProperty("name")] - public string Name { get; set; } - - [JsonProperty("sys")] - public Sys Sys { get; set; } - - [JsonProperty("main")] - public Main Main { get; set; } - - [JsonProperty("wind")] - public Wind Wind { get; set; } - - [JsonProperty("weather")] - public List Weather { get; set; } - - [JsonProperty("cod")] - public int Cod { get; set; } - } - - public class Sys - { - [JsonProperty("country")] - public string Country { get; set; } - } - - public class Main - { - [JsonProperty("temp")] - public double Temperature { get; set; } - - [JsonProperty("humidity")] - public float Humidity { get; set; } - - [JsonProperty("tempMin")] - public double MinTemp { get; set; } - - [JsonProperty("tempMax")] - public double MaxTemp { get; set; } - } - - public class Wind - { - [JsonProperty("speed")] - public double Speed { get; set; } - } - - public class Weather - { - [JsonProperty("id")] - public int Id { get; set; } - - [JsonProperty("main")] - public string Main { get; set; } - } - - #endregion WEATHER -} \ No newline at end of file diff --git a/src/FlawBOT.Framework/Models/Search/NASAData.cs b/src/FlawBOT.Framework/Models/Search/NASAData.cs deleted file mode 100644 index defca0bd..00000000 --- a/src/FlawBOT.Framework/Models/Search/NASAData.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Newtonsoft.Json; - -namespace FlawBOT.Framework.Models -{ - public class NasaData - { - [JsonProperty("copyright")] - public string Copyright { get; set; } - - [JsonProperty("date")] - public string Date { get; set; } - - [JsonProperty("explanation")] - public string Description { get; set; } - - [JsonProperty("hdurl")] - public string ImageHd { get; set; } - - [JsonProperty("title")] - public string Title { get; set; } - - [JsonProperty("url")] - public string ImageSd { get; set; } - } -} \ No newline at end of file diff --git a/src/FlawBOT.Framework/Models/Search/SimpsonsData.cs b/src/FlawBOT.Framework/Models/Search/SimpsonsData.cs deleted file mode 100644 index 9bd648db..00000000 --- a/src/FlawBOT.Framework/Models/Search/SimpsonsData.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Newtonsoft.Json; - -namespace FlawBOT.Framework.Models -{ - public class SimpsonsData - { - [JsonProperty("Episode")] - public Episode Episode { get; set; } - - [JsonProperty("Frame")] - public Frame Frame { get; set; } - } - - public class Episode - { - [JsonProperty("Id")] - public int Id { get; set; } - - [JsonProperty("Key")] - public string Key { get; set; } - - [JsonProperty("Title")] - public string Title { get; set; } - - [JsonProperty("Director")] - public string Director { get; set; } - - [JsonProperty("Writer")] - public string Writer { get; set; } - - [JsonProperty("OriginalAirDate")] - public string OriginalAirDate { get; set; } - - [JsonProperty("WikiLink")] - public string WikiLink { get; set; } - } - - public class Frame - { - [JsonProperty("Id")] - public int Id { get; set; } - - [JsonProperty("Episode")] - public string Episode { get; set; } - - [JsonProperty("Timestamp")] - public int Timestamp { get; set; } - } -} \ No newline at end of file diff --git a/src/FlawBOT.Framework/Models/Search/SteamData.cs b/src/FlawBOT.Framework/Models/Search/SteamData.cs deleted file mode 100644 index 0a35e31d..00000000 --- a/src/FlawBOT.Framework/Models/Search/SteamData.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Newtonsoft.Json; -using System.Collections.Generic; - -namespace FlawBOT.Framework.Models -{ - public class SteamData - { - [JsonProperty("applist")] - public Applist AppList { get; set; } - } - - public class Applist - { - [JsonProperty("apps")] - public List Apps { get; set; } - } - - public class App - { - [JsonProperty("appid")] - public int Id { get; set; } - - [JsonProperty("name")] - public string Name { 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 deleted file mode 100644 index a9e66408..00000000 --- a/src/FlawBOT.Framework/Models/Search/TwitchData.cs +++ /dev/null @@ -1,68 +0,0 @@ -using Newtonsoft.Json; -using System.Collections.Generic; - -namespace FlawBOT.Framework.Models -{ - public class TwitchData - { - [JsonProperty("data")] - public List Stream { get; set; } - } - - public class Stream - { - [JsonProperty("id")] - public string Id { get; set; } - - [JsonProperty("user_id")] - public int UserId { get; set; } - - [JsonProperty("user_name")] - public string UserName { get; set; } - - [JsonProperty("game_id")] - public int GameId { get; set; } - - [JsonProperty("type")] - public string Type { get; set; } - - [JsonProperty("title")] - public string Title { get; set; } - - [JsonProperty("viewer_count")] - public int ViewCount { get; set; } - - [JsonProperty("started_at")] - public string StartTime { get; set; } - - [JsonProperty("language")] - public string Language { get; set; } - - [JsonProperty("thumbnail_url")] - public string ThumbnailUrl { get; set; } - } - - public class Streamer - { - [JsonProperty("id")] - public int Id { get; set; } - - [JsonProperty("login")] - public string Login { get; set; } - - [JsonProperty("display_name")] - public string DisplayName { get; set; } - - [JsonProperty("description")] - public string Description { get; set; } - - [JsonProperty("profile_image_url")] - public string ProfileImageUrl { get; set; } - - [JsonProperty("offline_image_url")] - public string OfflineImageUrl { get; set; } - - [JsonProperty("view_count")] - public int ViewCount { get; set; } - } -} \ No newline at end of file diff --git a/src/FlawBOT.Framework/Models/Search/WikipediaData.cs b/src/FlawBOT.Framework/Models/Search/WikipediaData.cs deleted file mode 100644 index 43a97638..00000000 --- a/src/FlawBOT.Framework/Models/Search/WikipediaData.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Newtonsoft.Json; - -namespace FlawBOT.Framework.Models -{ - public class WikipediaData - { - [JsonProperty("Query")] - public WikipediaQuery Query { get; set; } - - public class WikipediaQuery - { - [JsonProperty("Pages")] - public WikipediaPage[] Pages { get; set; } - - public class WikipediaPage - { - [JsonProperty("Missing")] - public bool Missing { get; set; } - - [JsonProperty("Title")] - public string Title { get; set; } - - [JsonProperty("FullUrl")] - public string FullUrl { get; set; } - } - } - } -} \ No newline at end of file diff --git a/src/FlawBOT.Framework/Models/Server/BotData.cs b/src/FlawBOT.Framework/Models/Server/BotData.cs deleted file mode 100644 index 6bbda0fc..00000000 --- a/src/FlawBOT.Framework/Models/Server/BotData.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Newtonsoft.Json; - -namespace FlawBOT.Framework.Models -{ - public class TokenData - { - [JsonProperty("prefix")] - public string CommandPrefix { get; private set; } - - [JsonProperty("discord")] - public string DiscordToken { get; private set; } - - [JsonProperty("google", NullValueHandling = NullValueHandling.Ignore)] - public string GoogleToken { get; private set; } - - [JsonProperty("steam", NullValueHandling = NullValueHandling.Ignore)] - public string SteamToken { get; private set; } - - [JsonProperty("imgur", NullValueHandling = NullValueHandling.Ignore)] - public string ImgurToken { get; private set; } - - [JsonProperty("omdb", NullValueHandling = NullValueHandling.Ignore)] - public string OmdbToken { get; private set; } - - [JsonProperty("twitch", NullValueHandling = NullValueHandling.Ignore)] - public string TwitchToken { get; private set; } - - [JsonProperty("nasa", NullValueHandling = NullValueHandling.Ignore)] - public string NasaToken { get; private set; } - - [JsonProperty("teamworktf", NullValueHandling = NullValueHandling.Ignore)] - public string TeamworkToken { get; private set; } - - [JsonProperty("news", NullValueHandling = NullValueHandling.Ignore)] - public string NewsToken { get; private set; } - } - - public enum EmbedType - { - Default, - Good, - Warning, - Missing, - Error - } - - public enum UserStateChange - { - Ban, - Deafen, - Kick, - Mute, - Unban, - Undeafen, - Unmute - } -} \ No newline at end of file diff --git a/src/FlawBOT.Framework/Properties/Resources.Designer.cs b/src/FlawBOT.Framework/Properties/Resources.Designer.cs deleted file mode 100644 index 309fd57b..00000000 --- a/src/FlawBOT.Framework/Properties/Resources.Designer.cs +++ /dev/null @@ -1,279 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace FlawBOT.Framework.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FlawBOT.Framework.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to https://www.amiiboapi.com/api/amiibo/. - /// - internal static string API_Amiibo { - get { - return ResourceManager.GetString("API_Amiibo", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to https://catfact.ninja/fact. - /// - internal static string API_CatFacts { - get { - return ResourceManager.GetString("API_CatFacts", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to http://aws.random.cat/meow. - /// - internal static string API_CatPhoto { - get { - return ResourceManager.GetString("API_CatPhoto", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to http://api.urbandictionary.com/v0/define. - /// - internal static string API_Dictionary { - get { - return ResourceManager.GetString("API_Dictionary", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to https://dog.ceo/api/breeds/image/random. - /// - internal static string API_DogPhoto { - get { - return ResourceManager.GetString("API_DogPhoto", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to https://maps.googleapis.com/maps/api/geocode/json. - /// - internal static string API_Google_Geo { - get { - return ResourceManager.GetString("API_Google_Geo", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to https://maps.googleapis.com/maps/api/timezone/json. - /// - internal static string API_Google_Time { - get { - return ResourceManager.GetString("API_Google_Time", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to http://api.openweathermap.org/data/2.5/weather. - /// - internal static string API_Google_Weather { - get { - return ResourceManager.GetString("API_Google_Weather", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to http://ip-api.com/json/. - /// - internal static string API_IPLocation { - get { - return ResourceManager.GetString("API_IPLocation", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to https://api.nasa.gov/planetary/apod. - /// - internal static string API_NASA { - get { - return ResourceManager.GetString("API_NASA", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to https://newsapi.org/v2/everything?sources=google-news. - /// - internal static string API_News { - get { - return ResourceManager.GetString("API_News", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to https://pokeapi.co/api/v2/pokemon/?limit=800. - /// - internal static string API_Pokemon { - get { - return ResourceManager.GetString("API_Pokemon", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to https://api.pokemontcg.io/v1/cards. - /// - internal static string API_PokemonTCG { - get { - return ResourceManager.GetString("API_PokemonTCG", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to https://api.kuroganehammer.com/api/characters/. - /// - internal static string API_SmashBros { - get { - return ResourceManager.GetString("API_SmashBros", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to https://www.speedrun.com/api/v1/. - /// - internal static string API_Speedrun { - get { - return ResourceManager.GetString("API_Speedrun", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to http://api.steampowered.com/ISteamApps/GetAppList/v0002/. - /// - internal static string API_SteamGames { - get { - return ResourceManager.GetString("API_SteamGames", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to https://teamwork.tf/api/v1/. - /// - internal static string API_TeamworkTF { - get { - return ResourceManager.GetString("API_TeamworkTF", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to https://www.trade.tf/api/schema_440.json. - /// - internal static string API_TradeTF { - get { - return ResourceManager.GetString("API_TradeTF", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to https://api.twitch.tv/helix/. - /// - internal static string API_Twitch { - get { - return ResourceManager.GetString("API_Twitch", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to https://en.wikipedia.org//w/api.php?action=query&format=json&prop=info&redirects=1&formatversion=2&inprop=url. - /// - internal static string API_Wikipedia { - get { - return ResourceManager.GetString("API_Wikipedia", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Error updating the Pokémon list. {0}. - /// - internal static string ERR_POKEMON_LIST { - get { - return ResourceManager.GetString("ERR_POKEMON_LIST", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unknown Reddit category. - /// - internal static string ERR_REDDIT_UNKNOWN_CAT { - get { - return ResourceManager.GetString("ERR_REDDIT_UNKNOWN_CAT", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Error updating Steam games list. {0}. - /// - internal static string ERR_STEAM_LIST { - get { - return ResourceManager.GetString("ERR_STEAM_LIST", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Error updating TF2 item schema. {0}. - /// - internal static string ERR_TF2_LIST { - get { - return ResourceManager.GetString("ERR_TF2_LIST", resourceCulture); - } - } - } -} diff --git a/src/FlawBOT.Framework/Properties/Resources.resx b/src/FlawBOT.Framework/Properties/Resources.resx deleted file mode 100644 index adc78aca..00000000 --- a/src/FlawBOT.Framework/Properties/Resources.resx +++ /dev/null @@ -1,192 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - https://www.amiiboapi.com/api/amiibo/ - - - https://catfact.ninja/fact - - - http://aws.random.cat/meow - - - http://api.urbandictionary.com/v0/define - - - https://dog.ceo/api/breeds/image/random - - - https://maps.googleapis.com/maps/api/geocode/json - - - https://maps.googleapis.com/maps/api/timezone/json - - - http://api.openweathermap.org/data/2.5/weather - - - http://ip-api.com/json/ - - - https://api.nasa.gov/planetary/apod - - - https://newsapi.org/v2/everything?sources=google-news - - - https://pokeapi.co/api/v2/pokemon/?limit=800 - - - https://api.pokemontcg.io/v1/cards - - - https://api.kuroganehammer.com/api/characters/ - - - https://www.speedrun.com/api/v1/ - - - http://api.steampowered.com/ISteamApps/GetAppList/v0002/ - - - https://teamwork.tf/api/v1/ - - - https://www.trade.tf/api/schema_440.json - - - https://api.twitch.tv/helix/ - - - https://en.wikipedia.org//w/api.php?action=query&format=json&prop=info&redirects=1&formatversion=2&inprop=url - - - Error updating the Pokémon list. {0} - - - Unknown Reddit category - - - Error updating Steam games list. {0} - - - Error updating TF2 item schema. {0} - - \ No newline at end of file diff --git a/src/FlawBOT.Framework/Services/Games/SteamService.cs b/src/FlawBOT.Framework/Services/Games/SteamService.cs deleted file mode 100644 index ccaff151..00000000 --- a/src/FlawBOT.Framework/Services/Games/SteamService.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; -using FlawBOT.Framework.Models; -using Microsoft.Extensions.Options; -using Steam.Models.SteamCommunity; -using Steam.Models.SteamStore; -using SteamWebAPI2.Interfaces; -using SteamWebAPI2.Utilities; - -namespace FlawBOT.Framework.Services -{ - public class SteamService : HttpHandler - { - private static SteamWebInterfaceFactory _steamInterface; - - /// https://github.com/babelshift/SteamWebAPI2/issues/81 - public static async Task GetSteamAppAsync(string query) - { - try - { - _steamInterface = new SteamWebInterfaceFactory(TokenHandler.Tokens.SteamToken); - var factoryOptions = new SteamWebInterfaceFactoryOptions - { - SteamWebApiKey = TokenHandler.Tokens.SteamToken - }; - var store = new SteamWebInterfaceFactory(Options.Create(factoryOptions)).CreateSteamStoreInterface(); - var list = await _steamInterface.CreateSteamWebInterface(new HttpClient()).GetAppListAsync(); - var appId = list.Data.First(n => string.Equals(n.Name, query, StringComparison.InvariantCultureIgnoreCase)).AppId; - return await store.GetStoreAppDetailsAsync(appId).ConfigureAwait(false); - } - catch - { - return null; - } - } - - public static async Task GetSteamProfileAsync(string query) - { - try - { - _steamInterface = new SteamWebInterfaceFactory(TokenHandler.Tokens.SteamToken); - var steam = _steamInterface.CreateSteamWebInterface(new HttpClient()); - if (ulong.TryParse(query, out var steamId)) - return await steam.GetCommunityProfileAsync(steamId).ConfigureAwait(false); - return await steam.GetCommunityProfileAsync(GetSteamUserId(query).Result.Data).ConfigureAwait(false); - } - catch - { - return null; - } - } - - public static async Task> GetSteamSummaryAsync(string query) - { - try - { - _steamInterface = new SteamWebInterfaceFactory(TokenHandler.Tokens.SteamToken); - var steam = _steamInterface.CreateSteamWebInterface(new HttpClient()); - if (ulong.TryParse(query, out var steamId)) - return await steam.GetPlayerSummaryAsync(steamId).ConfigureAwait(false); - return await steam.GetPlayerSummaryAsync(GetSteamUserId(query).Result.Data).ConfigureAwait(false); - } - catch - { - return null; - } - } - - public static async Task> GetSteamUserId(string query) - { - try - { - var steam = _steamInterface.CreateSteamWebInterface(new HttpClient()); - return await steam.ResolveVanityUrlAsync(query).ConfigureAwait(false); - } - catch - { - return null; - } - } - } -} \ No newline at end of file diff --git a/src/FlawBOT.Framework/Services/Games/TeamFortressService.cs b/src/FlawBOT.Framework/Services/Games/TeamFortressService.cs deleted file mode 100644 index 262d3a3c..00000000 --- a/src/FlawBOT.Framework/Services/Games/TeamFortressService.cs +++ /dev/null @@ -1,136 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Properties; -using SteamWebAPI2.Interfaces; -using SteamWebAPI2.Models.GameEconomy; -using SteamWebAPI2.Utilities; -using TeamworkTF.Sharp; - -namespace FlawBOT.Framework.Services -{ - public class TeamFortressService : HttpHandler - { - private static SteamWebInterfaceFactory _steamInterface; - private static List ItemSchemaList { get; set; } = new List(); - - #region NEWS - - public static async Task> GetNewsOverviewAsync(int page = 0, string provider = "") - { - List results; - if (page > 0) - results = await new TeamworkClient(TokenHandler.Tokens.TeamworkToken).GetNewsByPageAsync(page) - .ConfigureAwait(false); - else if (provider != string.Empty) - results = await new TeamworkClient(TokenHandler.Tokens.TeamworkToken).GetNewsByProviderAsync(provider) - .ConfigureAwait(false); - else - results = await new TeamworkClient(TokenHandler.Tokens.TeamworkToken).GetNewsOverviewAsync() - .ConfigureAwait(false); - return results.Where(x => x.Type != "tf2-notification").ToList(); - } - - #endregion NEWS - - #region CREATORS - - public static async Task> GetCreatorByIdAsync(ulong query) - { - return await new TeamworkClient(TokenHandler.Tokens.TeamworkToken).GetCreatorByIDAsync(query.ToString()) - .ConfigureAwait(false); - } - - #endregion CREATORS - - #region SCHEMA - - public static SchemaItem GetSchemaItem(string query) - { - return ItemSchemaList.FirstOrDefault(n => - n.ItemName.Contains(query, StringComparison.InvariantCultureIgnoreCase)); - } - - public static async Task UpdateTf2SchemaAsync() - { - try - { - _steamInterface = new SteamWebInterfaceFactory(TokenHandler.Tokens.SteamToken); - var steam = _steamInterface.CreateSteamWebInterface(AppId.TeamFortress2, new HttpClient()); - var games = await steam.GetSchemaItemsForTF2Async().ConfigureAwait(false); - ItemSchemaList.Clear(); - foreach (var game in games.Data.Result.Items) - if (!string.IsNullOrWhiteSpace(game.Name)) - ItemSchemaList.Add(game); - return true; - } - catch (Exception ex) - { - Console.WriteLine(Resources.ERR_TF2_LIST, ex.Message); - return false; - } - } - - #endregion SCHEMA - - #region MAPS - - private static async Task> GetMapsBySearchAsync(string query) - { - return await new TeamworkClient(TokenHandler.Tokens.TeamworkToken).GetMapsBySearchAsync(query) - .ConfigureAwait(false); - } - - public static async Task GetMapStatsAsync(string query) - { - var map = GetMapsBySearchAsync(query).Result.FirstOrDefault()?.Name; - return await new TeamworkClient(TokenHandler.Tokens.TeamworkToken).GetMapStatsAsync(map) - .ConfigureAwait(false); - } - - public static async Task GetMapThumbnailAsync(string query) - { - return await new TeamworkClient(TokenHandler.Tokens.TeamworkToken).GetMapThumbnailAsync(query) - .ConfigureAwait(false); - } - - #endregion MAPS - - #region SERVERS - - public static async Task GetGameModeAsync(string query) - { - return await new TeamworkClient(TokenHandler.Tokens.TeamworkToken).GetGameModeAsync(query) - .ConfigureAwait(false); - } - - public static async Task GetGameModeListAsync() - { - return await new TeamworkClient(TokenHandler.Tokens.TeamworkToken).GetGameModeListAsync() - .ConfigureAwait(false); - } - - public static async Task> GetGameModeServerAsync(string query) - { - return await new TeamworkClient(TokenHandler.Tokens.TeamworkToken).GetGameModeServerAsync(query) - .ConfigureAwait(false); - } - - public static async Task> GetGameServerInfoAsync(string ip, int port) - { - return await new TeamworkClient(TokenHandler.Tokens.TeamworkToken).GetGameServerInfoAsync(ip, port) - .ConfigureAwait(false); - } - - public static async Task> GetCustomServerListsAsync() - { - return await new TeamworkClient(TokenHandler.Tokens.TeamworkToken).GetCustomServerListsAsync() - .ConfigureAwait(false); - } - - #endregion SERVERS - } -} \ No newline at end of file diff --git a/src/FlawBOT.Framework/Services/Search/BookService.cs b/src/FlawBOT.Framework/Services/Search/BookService.cs deleted file mode 100644 index a18972c1..00000000 --- a/src/FlawBOT.Framework/Services/Search/BookService.cs +++ /dev/null @@ -1,18 +0,0 @@ -using FlawBOT.Framework.Models; -using Google.Apis.Books.v1; -using Google.Apis.Services; - -namespace FlawBOT.Framework.Services -{ - public class BookService : HttpHandler - { - public BookService() - { - var booksService = new BooksService(new BaseClientService.Initializer - { - ApiKey = TokenHandler.Tokens.GoogleToken, - ApplicationName = "FlawBOT" - }); - } - } -} \ No newline at end of file diff --git a/src/FlawBOT.Framework/Services/Search/GoogleService.cs b/src/FlawBOT.Framework/Services/Search/GoogleService.cs deleted file mode 100644 index 79a94efd..00000000 --- a/src/FlawBOT.Framework/Services/Search/GoogleService.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Net; -using System.Threading.Tasks; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Properties; -using Newtonsoft.Json; - -namespace FlawBOT.Framework.Services -{ - public class GoogleService : HttpHandler - { - public static async Task GetTimeDataAsync(string query) - { - try - { - var results = GetLocationGeoData(query.Replace(" ", "")).Result; - if (results is null) return null; - 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) - .ConfigureAwait(false); - results.Timezone = JsonConvert.DeserializeObject(timeResource); - results.Time = DateTime.UtcNow.AddSeconds(results.Timezone.DstOffset + results.Timezone.RawOffset); - return results; - } - catch - { - return null; - } - } - - public static async Task GetWeatherDataAsync(string query) - { - try - { - var results = await Http - .GetStringAsync(Resources.API_Google_Weather + "?q=" + query + "&appid=42cd627dd60debf25a5739e50a217d74&units=metric").ConfigureAwait(false); - return JsonConvert.DeserializeObject(results); - } - catch - { - return null; - } - } - - private static async Task GetLocationGeoData(string query) - { - Http.DefaultRequestHeaders.Clear(); - var result = await Http - .GetStringAsync(Resources.API_Google_Geo + "?address=" + query + "&key=" + TokenHandler.Tokens.GoogleToken).ConfigureAwait(false); - var results = JsonConvert.DeserializeObject(result); - return results.Status == "OK" ? results : null; - } - - public static async Task GetIpLocationAsync(IPAddress query) - { - var result = await Http.GetStringAsync(Resources.API_IPLocation + query).ConfigureAwait(false); - return JsonConvert.DeserializeObject(result); - } - - public static async Task GetNewsDataAsync(string query = "") - { - var results = await Http - .GetStringAsync(Resources.API_News + "&q=" + query + "&apiKey=" + TokenHandler.Tokens.NewsToken) - .ConfigureAwait(false); - return JsonConvert.DeserializeObject(results); - } - - public static double CelsiusToFahrenheit(double cel) - { - return cel * 1.8f + 32; - } - } -} \ No newline at end of file diff --git a/src/FlawBOT.Framework/Services/Search/OMDBService.cs b/src/FlawBOT.Framework/Services/Search/OMDBService.cs deleted file mode 100644 index ac8e9829..00000000 --- a/src/FlawBOT.Framework/Services/Search/OMDBService.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Threading.Tasks; -using FlawBOT.Framework.Models; -using OMDbSharp; -using OMDbSharp.Objects; - -namespace FlawBOT.Framework.Services -{ - public static class OmdbService - { - public static async Task GetMovieDataAsync(string query) - { - return await new OMDbClient(TokenHandler.Tokens.OmdbToken, false) - .GetItemByTitle(query.ToLowerInvariant().Replace("&", "%26")).ConfigureAwait(false); - } - } -} \ No newline at end of file diff --git a/src/FlawBOT.Framework/Services/Search/TwitchService.cs b/src/FlawBOT.Framework/Services/Search/TwitchService.cs deleted file mode 100644 index 7c7b0ba2..00000000 --- a/src/FlawBOT.Framework/Services/Search/TwitchService.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Net.Http; -using System.Threading.Tasks; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Properties; -using Newtonsoft.Json; - -namespace FlawBOT.Framework.Services -{ - public class TwitchService : HttpHandler - { - public static async Task GetTwitchDataAsync(string query) - { - 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); - } - } -} \ No newline at end of file diff --git a/src/FlawBOT.Framework/Services/Search/WikipediaService.cs b/src/FlawBOT.Framework/Services/Search/WikipediaService.cs deleted file mode 100644 index 51a42b80..00000000 --- a/src/FlawBOT.Framework/Services/Search/WikipediaService.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Threading.Tasks; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Properties; -using Newtonsoft.Json; - -namespace FlawBOT.Framework.Services -{ - public class WikipediaService : HttpHandler - { - public static async Task GetWikipediaDataAsync(string query) - { - var results = await Http.GetStringAsync(Resources.API_Wikipedia + "&titles=" + Uri.EscapeDataString(query)) - .ConfigureAwait(false); - return JsonConvert.DeserializeObject(results).Query.Pages[0]; - } - } -} \ No newline at end of file diff --git a/src/FlawBOT.Test/FlawBOT.Test.csproj b/src/FlawBOT.Test/FlawBOT.Test.csproj index 267da600..b03dd1a1 100644 --- a/src/FlawBOT.Test/FlawBOT.Test.csproj +++ b/src/FlawBOT.Test/FlawBOT.Test.csproj @@ -1,31 +1,33 @@  - - netcoreapp3.1 - false - 2.6.1.0 - 2.6.1.0 - 2.6.1 - Igor Nikitin - CriticalFlaw - FlawBOT - Multipurpose Discord bot written in C# using DSharpPlus. Application Tests. - https://discordbots.org/bot/339833029013012483 - https://github.com/CriticalFlaw/FlawBOT - en-CA - + + net5.0 + false + 3.0.0.0 + 3.0.0.0 + 3.0.0 + Igor Nikitin + CriticalFlaw + FlawBOT + Multipurpose Discord bot written in C# using DSharpPlus. Application Tests. + https://github.com/CriticalFlaw/FlawBOT + en-CA + - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + - - - + + + - + + + + \ No newline at end of file diff --git a/src/FlawBOT.Test/Misc/MiscTests.cs b/src/FlawBOT.Test/Misc/MiscTests.cs deleted file mode 100644 index b39cade3..00000000 --- a/src/FlawBOT.Test/Misc/MiscTests.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Net; -using FlawBOT.Framework.Services; -using NUnit.Framework; - -namespace MiscModule -{ - [TestFixture] - internal class MiscTests - { - [Test] - public void GetCatFact() - { - Assert.IsNotNull(CatService.GetCatFactAsync().Result); - } - - [Test] - public void GetDogPhoto() - { - Assert.IsTrue(DogService.GetDogPhotoAsync().Result.Status == "success"); - } - - [Test] - public void GetIpLocation() - { - Assert.IsTrue(GoogleService.GetIpLocationAsync(IPAddress.Parse("123.123.123.123")).Result.Status == "success"); - } - - [Test] - public void GetRandomAnswer() - { - Assert.IsNotNull(EightBallService.GetRandomAnswer()); - } - } -} \ No newline at end of file diff --git a/src/FlawBOT.Test/Search/AmiiboTests.cs b/src/FlawBOT.Test/Modules/Amiibo.cs similarity index 76% rename from src/FlawBOT.Test/Search/AmiiboTests.cs rename to src/FlawBOT.Test/Modules/Amiibo.cs index af677047..add568d8 100644 --- a/src/FlawBOT.Test/Search/AmiiboTests.cs +++ b/src/FlawBOT.Test/Modules/Amiibo.cs @@ -1,10 +1,10 @@ -using FlawBOT.Framework.Services; +using FlawBOT.Services; using NUnit.Framework; -namespace SearchModule +namespace Modules { [TestFixture] - internal class AmiiboTests + internal class Amiibo { [Test] public void GetAmiiboData() diff --git a/src/FlawBOT.Test/Search/DictionaryTests.cs b/src/FlawBOT.Test/Modules/Dictionary.cs similarity index 79% rename from src/FlawBOT.Test/Search/DictionaryTests.cs rename to src/FlawBOT.Test/Modules/Dictionary.cs index da06861f..5a471f9a 100644 --- a/src/FlawBOT.Test/Search/DictionaryTests.cs +++ b/src/FlawBOT.Test/Modules/Dictionary.cs @@ -1,10 +1,10 @@ -using FlawBOT.Framework.Services; +using FlawBOT.Services; using NUnit.Framework; -namespace SearchModule +namespace Modules { [TestFixture] - internal class DictionaryTests + internal class Dictionary { [Test] public void GetDictionaryDefinition() diff --git a/src/FlawBOT.Test/Search/ImgurTests.cs b/src/FlawBOT.Test/Modules/Imgur.cs similarity index 83% rename from src/FlawBOT.Test/Search/ImgurTests.cs rename to src/FlawBOT.Test/Modules/Imgur.cs index 439ff309..00ff9462 100644 --- a/src/FlawBOT.Test/Search/ImgurTests.cs +++ b/src/FlawBOT.Test/Modules/Imgur.cs @@ -1,11 +1,11 @@ using System.Threading.Tasks; -using FlawBOT.Framework.Services; +using FlawBOT.Services; using NUnit.Framework; -namespace SearchModule +namespace Modules { [TestFixture] - internal class ImgurTests + internal class Imgur { [Test] public async Task GetImgurGalleryData() diff --git a/src/FlawBOT.Test/Modules/Misc.cs b/src/FlawBOT.Test/Modules/Misc.cs new file mode 100644 index 00000000..d1726bb7 --- /dev/null +++ b/src/FlawBOT.Test/Modules/Misc.cs @@ -0,0 +1,27 @@ +using FlawBOT.Services; +using NUnit.Framework; + +namespace Modules +{ + [TestFixture] + internal class Misc + { + [Test] + public void GetCatFact() + { + Assert.IsNotNull(MiscService.GetCatFactAsync().Result); + } + + [Test] + public void GetDogPhoto() + { + Assert.IsTrue(MiscService.GetDogPhotoAsync().Result.Status == "success"); + } + + [Test] + public void GetRandomAnswer() + { + Assert.IsNotNull(MiscService.GetRandomAnswer()); + } + } +} \ No newline at end of file diff --git a/src/FlawBOT.Test/Search/NASATests.cs b/src/FlawBOT.Test/Modules/NASA.cs similarity index 68% rename from src/FlawBOT.Test/Search/NASATests.cs rename to src/FlawBOT.Test/Modules/NASA.cs index 91aab286..251c146e 100644 --- a/src/FlawBOT.Test/Search/NASATests.cs +++ b/src/FlawBOT.Test/Modules/NASA.cs @@ -1,10 +1,10 @@ -using FlawBOT.Framework.Services; +using FlawBOT.Services; using NUnit.Framework; -namespace SearchModule +namespace Modules { [TestFixture] - internal class NasaTests + internal class NASA { [Test] public void GetNasaData() diff --git a/src/FlawBOT.Test/Modules/News.cs b/src/FlawBOT.Test/Modules/News.cs new file mode 100644 index 00000000..93789d6a --- /dev/null +++ b/src/FlawBOT.Test/Modules/News.cs @@ -0,0 +1,16 @@ +using FlawBOT.Services; +using NUnit.Framework; + +namespace Modules +{ + [TestFixture] + internal class News + { + [Test] + public void GetNewsData() + { + Assert.IsTrue(NewsService.GetNewsDataAsync().Result.Status == "ok"); + Assert.IsTrue(NewsService.GetNewsDataAsync("Nintendo").Result.Status == "ok"); + } + } +} \ No newline at end of file diff --git a/src/FlawBOT.Test/Search/OMDBTests.cs b/src/FlawBOT.Test/Modules/OMDB.cs similarity index 70% rename from src/FlawBOT.Test/Search/OMDBTests.cs rename to src/FlawBOT.Test/Modules/OMDB.cs index 432f7d24..bc901120 100644 --- a/src/FlawBOT.Test/Search/OMDBTests.cs +++ b/src/FlawBOT.Test/Modules/OMDB.cs @@ -1,10 +1,10 @@ -using FlawBOT.Framework.Services; +using FlawBOT.Services; using NUnit.Framework; -namespace SearchModule +namespace Modules { [TestFixture] - internal class OmdbTests + internal class OMDB { [Test] public void GetMovieData() diff --git a/src/FlawBOT.Test/Games/PokemonTests.cs b/src/FlawBOT.Test/Modules/Pokemon.cs similarity index 86% rename from src/FlawBOT.Test/Games/PokemonTests.cs rename to src/FlawBOT.Test/Modules/Pokemon.cs index 48a79844..2c91d353 100644 --- a/src/FlawBOT.Test/Games/PokemonTests.cs +++ b/src/FlawBOT.Test/Modules/Pokemon.cs @@ -1,10 +1,10 @@ -using FlawBOT.Framework.Services; +using FlawBOT.Services; using NUnit.Framework; -namespace GamesModule +namespace Modules { [TestFixture] - internal class PokemonTests + internal class Pokemon { [Test] public void GetPokemonCards() diff --git a/src/FlawBOT.Test/Search/RedditTests.cs b/src/FlawBOT.Test/Modules/Reddit.cs similarity index 81% rename from src/FlawBOT.Test/Search/RedditTests.cs rename to src/FlawBOT.Test/Modules/Reddit.cs index 1af3e5e4..4b328e3d 100644 --- a/src/FlawBOT.Test/Search/RedditTests.cs +++ b/src/FlawBOT.Test/Modules/Reddit.cs @@ -1,10 +1,10 @@ -using FlawBOT.Framework.Services; +using FlawBOT.Services; using NUnit.Framework; -namespace SearchModule +namespace Modules { [TestFixture] - internal class RedditTests + internal class Reddit { [Test] public void GetEmbeddedResults() diff --git a/src/FlawBOT.Test/Search/SimpsonsTests.cs b/src/FlawBOT.Test/Modules/Simpsons.cs similarity index 79% rename from src/FlawBOT.Test/Search/SimpsonsTests.cs rename to src/FlawBOT.Test/Modules/Simpsons.cs index 335b0dda..7025071f 100644 --- a/src/FlawBOT.Test/Search/SimpsonsTests.cs +++ b/src/FlawBOT.Test/Modules/Simpsons.cs @@ -1,10 +1,10 @@ -using FlawBOT.Framework.Services; +using FlawBOT.Services; using NUnit.Framework; -namespace SearchModule +namespace Modules { [TestFixture] - internal class SimpsonsTests + internal class Simpsons { [Test] public void GetFuturamaEpisode() @@ -15,7 +15,8 @@ public void GetFuturamaEpisode() [Test] public void GetRickMortyEpisode() { - Assert.NotNull(SimpsonsService.GetSimpsonsDataAsync(SimpsonsService.SiteRoot.MasterOfAllScience).Result.Title); + Assert.NotNull(SimpsonsService.GetSimpsonsDataAsync(SimpsonsService.SiteRoot.MasterOfAllScience).Result + .Title); } [Test] diff --git a/src/FlawBOT.Test/Games/SpeedrunTests.cs b/src/FlawBOT.Test/Modules/Speedrun.cs similarity index 78% rename from src/FlawBOT.Test/Games/SpeedrunTests.cs rename to src/FlawBOT.Test/Modules/Speedrun.cs index 2d49e0a9..7b3cc471 100644 --- a/src/FlawBOT.Test/Games/SpeedrunTests.cs +++ b/src/FlawBOT.Test/Modules/Speedrun.cs @@ -1,10 +1,10 @@ -using FlawBOT.Framework.Services; +using FlawBOT.Services; using NUnit.Framework; -namespace GamesModule +namespace Modules { [TestFixture] - internal class SpeedrunTests + internal class Speedrun { [Test] public void GetSpeedrunGame() diff --git a/src/FlawBOT.Test/Games/SteamTests.cs b/src/FlawBOT.Test/Modules/Steam.cs similarity index 68% rename from src/FlawBOT.Test/Games/SteamTests.cs rename to src/FlawBOT.Test/Modules/Steam.cs index f930bbf0..ff7059aa 100644 --- a/src/FlawBOT.Test/Games/SteamTests.cs +++ b/src/FlawBOT.Test/Modules/Steam.cs @@ -1,11 +1,11 @@ using System.Text.RegularExpressions; -using FlawBOT.Framework.Services; +using FlawBOT.Services; using NUnit.Framework; -namespace GamesModule +namespace Modules { [TestFixture] - internal class SteamTests + internal class Steam { [Test] public void GenerateConnectionLink() @@ -18,9 +18,14 @@ public void GenerateConnectionLink() public void GetSteamProfile() { Assert.IsNotNull(SteamService.GetSteamProfileAsync("criticalflaw").Result); - Assert.IsNotNull(SteamService.GetSteamSummaryAsync("criticalflaw").Result); Assert.IsNull(SteamService.GetSteamProfileAsync("99999999999999999").Result); - Assert.IsNull(SteamService.GetSteamSummaryAsync("99999999999999999").Result); + } + + [Test] + [Order(1)] + public void UpdateSteamAppList() + { + Assert.IsTrue(SteamService.UpdateSteamAppListAsync().Result); } } } \ No newline at end of file diff --git a/src/FlawBOT.Test/Games/TeamFortressTests.cs b/src/FlawBOT.Test/Modules/TeamFortress.cs similarity index 73% rename from src/FlawBOT.Test/Games/TeamFortressTests.cs rename to src/FlawBOT.Test/Modules/TeamFortress.cs index 069e4f61..312961aa 100644 --- a/src/FlawBOT.Test/Games/TeamFortressTests.cs +++ b/src/FlawBOT.Test/Modules/TeamFortress.cs @@ -1,10 +1,10 @@ -using FlawBOT.Framework.Services; +using FlawBOT.Services; using NUnit.Framework; -namespace GamesModule +namespace Modules { [TestFixture] - internal class TeamFortressTests + internal class TeamFortress { [Test] public void GetMapStats() @@ -17,7 +17,7 @@ public void GetMapStats() [Test] public void GetNewsOverview() { - Assert.IsNotNull(TeamFortressService.GetNewsOverviewAsync().Result); + Assert.IsNotNull(TeamFortressService.GetNewsArticlesAsync().Result); } [Test] @@ -31,8 +31,8 @@ public void GetSchemaItem() [Test] public void GetServers() { - Assert.IsNotNull(TeamFortressService.GetGameModeServerAsync("payload").Result); - Assert.IsNull(TeamFortressService.GetGameModeServerAsync("payloader").Result); + Assert.IsNotNull(TeamFortressService.GetServersByGameModeAsync("payload").Result); + Assert.IsNull(TeamFortressService.GetServersByGameModeAsync("payloader").Result); } [Test] diff --git a/src/FlawBOT.Test/Search/TwitchTests.cs b/src/FlawBOT.Test/Modules/Twitch.cs similarity index 69% rename from src/FlawBOT.Test/Search/TwitchTests.cs rename to src/FlawBOT.Test/Modules/Twitch.cs index 93ed1eda..8ff00279 100644 --- a/src/FlawBOT.Test/Search/TwitchTests.cs +++ b/src/FlawBOT.Test/Modules/Twitch.cs @@ -1,10 +1,10 @@ -using FlawBOT.Framework.Services; +using FlawBOT.Services; using NUnit.Framework; -namespace SearchModule +namespace Modules { [TestFixture] - internal class TwitchTests + internal class Twitch { [Test] public void GetStreamData() diff --git a/src/FlawBOT.Test/Modules/Wikipedia.cs b/src/FlawBOT.Test/Modules/Wikipedia.cs new file mode 100644 index 00000000..05f2a1d9 --- /dev/null +++ b/src/FlawBOT.Test/Modules/Wikipedia.cs @@ -0,0 +1,15 @@ +using FlawBOT.Services; +using NUnit.Framework; + +namespace Modules +{ + [TestFixture] + internal class Wikipedia + { + [Test] + public void GetWikipediaPage() + { + Assert.IsNull(WikipediaService.GetWikipediaDataAsync("Russia").Error); + } + } +} \ No newline at end of file diff --git a/src/FlawBOT.Test/Modules/World.cs b/src/FlawBOT.Test/Modules/World.cs new file mode 100644 index 00000000..29ff29d5 --- /dev/null +++ b/src/FlawBOT.Test/Modules/World.cs @@ -0,0 +1,23 @@ +using System.Net; +using FlawBOT.Services; +using NUnit.Framework; + +namespace Modules +{ + [TestFixture] + internal class World + { + [Test] + public void GetIpLocation() + { + Assert.IsNotNull(WorldService.GetIpLocationAsync(IPAddress.Parse("123.123.123.123")).Result.Type); + } + + [Test] + public void GetWeatherData() + { + Assert.IsNotNull(WorldService.GetWeatherDataAsync("Ottawa").Result); + Assert.IsNull(WorldService.GetWeatherDataAsync("Ottura").Result); + } + } +} \ No newline at end of file diff --git a/src/FlawBOT.Test/Search/GoogleTests.cs b/src/FlawBOT.Test/Search/GoogleTests.cs deleted file mode 100644 index dfae428d..00000000 --- a/src/FlawBOT.Test/Search/GoogleTests.cs +++ /dev/null @@ -1,30 +0,0 @@ -using FlawBOT.Framework.Services; -using NUnit.Framework; - -namespace SearchModule -{ - [TestFixture] - internal class GoogleTests - { - [Test] - public void GetNewsData() - { - Assert.IsTrue(GoogleService.GetNewsDataAsync().Result.Status == "ok"); - Assert.IsTrue(GoogleService.GetNewsDataAsync("Nintendo").Result.Status == "ok"); - } - - [Test] - public void GetTimeData() - { - Assert.IsNotNull(GoogleService.GetTimeDataAsync("Ottawa").Result.Status); - Assert.IsNull(GoogleService.GetTimeDataAsync("Ottura").Result); - } - - [Test] - public void GetWeatherData() - { - Assert.IsNotNull(GoogleService.GetWeatherDataAsync("Ottawa").Result); - Assert.IsNull(GoogleService.GetWeatherDataAsync("Ottura").Result); - } - } -} \ No newline at end of file diff --git a/src/FlawBOT.Test/Search/WikipediaTests.cs b/src/FlawBOT.Test/Search/WikipediaTests.cs deleted file mode 100644 index eb2cd31c..00000000 --- a/src/FlawBOT.Test/Search/WikipediaTests.cs +++ /dev/null @@ -1,15 +0,0 @@ -using FlawBOT.Framework.Services; -using NUnit.Framework; - -namespace SearchModule -{ - [TestFixture] - internal class WikipediaTests - { - [Test] - public void GetWikipediaPage() - { - Assert.IsFalse(WikipediaService.GetWikipediaDataAsync("Russia").Result.Missing); - } - } -} \ No newline at end of file diff --git a/src/FlawBOT.Test/TestSetup.cs b/src/FlawBOT.Test/TestSetup.cs index 37ba0800..4949bf5a 100644 --- a/src/FlawBOT.Test/TestSetup.cs +++ b/src/FlawBOT.Test/TestSetup.cs @@ -1,6 +1,7 @@ using System.IO; using System.Text; -using FlawBOT.Framework.Models; +using FlawBOT.Common; +using FlawBOT.Models; using Newtonsoft.Json; using NUnit.Framework; @@ -13,7 +14,7 @@ internal class TestSetup public void PreTest() { var json = new StreamReader(File.OpenRead("config.json"), new UTF8Encoding(false)).ReadToEnd(); - TokenHandler.Tokens = JsonConvert.DeserializeObject(json); + SharedData.Tokens = JsonConvert.DeserializeObject(json); } } } \ No newline at end of file diff --git a/src/FlawBOT.sln b/src/FlawBOT.sln index 7b659f46..0aa312df 100644 --- a/src/FlawBOT.sln +++ b/src/FlawBOT.sln @@ -3,12 +3,10 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29020.237 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FlawBOT.Core", "FlawBOT.Core\FlawBOT.Core.csproj", "{C3A935C2-D74A-47A0-A1F6-BA1FADA54F6F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FlawBOT", "FlawBOT\FlawBOT.csproj", "{C3A935C2-D74A-47A0-A1F6-BA1FADA54F6F}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FlawBOT.Test", "FlawBOT.Test\FlawBOT.Test.csproj", "{7E946A49-2539-45FA-A5C3-DC3411CBC399}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FlawBOT.Framework", "FlawBOT.Framework\FlawBOT.Framework.csproj", "{B4C5837A-C766-4E51-A619-BA151CEF6D79}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -23,10 +21,6 @@ Global {7E946A49-2539-45FA-A5C3-DC3411CBC399}.Debug|Any CPU.Build.0 = Debug|Any CPU {7E946A49-2539-45FA-A5C3-DC3411CBC399}.Release|Any CPU.ActiveCfg = Release|Any CPU {7E946A49-2539-45FA-A5C3-DC3411CBC399}.Release|Any CPU.Build.0 = Release|Any CPU - {B4C5837A-C766-4E51-A619-BA151CEF6D79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B4C5837A-C766-4E51-A619-BA151CEF6D79}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B4C5837A-C766-4E51-A619-BA151CEF6D79}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B4C5837A-C766-4E51-A619-BA151CEF6D79}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/FlawBOT/Common/Exceptions.cs b/src/FlawBOT/Common/Exceptions.cs new file mode 100644 index 00000000..9ad145b1 --- /dev/null +++ b/src/FlawBOT/Common/Exceptions.cs @@ -0,0 +1,101 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using DSharpPlus; +using DSharpPlus.CommandsNext; +using DSharpPlus.CommandsNext.Attributes; +using DSharpPlus.CommandsNext.Exceptions; +using DSharpPlus.Exceptions; +using FlawBOT.Services; +using Microsoft.Extensions.Logging; + +namespace FlawBOT.Common +{ + public class Exceptions + { + public static async Task Process(CommandErrorEventArgs e, EventId eventId) + { + switch (e.Exception) + { + case CommandNotFoundException _: + await BotServices.SendEmbedAsync(e.Context, e.Exception.Message, EmbedType.Missing) + .ConfigureAwait(false); + break; + + case InvalidOperationException _: + await BotServices.SendEmbedAsync(e.Context, e.Exception.Message, EmbedType.Warning) + .ConfigureAwait(false); + break; + + case ChecksFailedException cfe: + 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).ConfigureAwait(false); + break; + + case RequireUserPermissionsAttribute perms: + await BotServices.SendEmbedAsync(e.Context, + $"- You do not have sufficient permissions ({perms.Permissions.ToPermissionString()})!", + EmbedType.Error).ConfigureAwait(false); + break; + + case RequireBotPermissionsAttribute perms: + await BotServices.SendEmbedAsync(e.Context, + $"- I do not have sufficient permissions ({perms.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) + .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).ConfigureAwait(false); + break; + + default: + await BotServices.SendEmbedAsync(e.Context, + "Unknown check triggered. Please notify the developer using the command *.bot report*", + EmbedType.Error).ConfigureAwait(false); + break; + } + + break; + + case ArgumentNullException _: + case ArgumentException _: + await BotServices.SendEmbedAsync(e.Context, + $"Invalid or missing parameters. For help, use command `.help {e.Command?.QualifiedName}`", + EmbedType.Warning); + break; + + case UnauthorizedException _: + await BotServices.SendEmbedAsync(e.Context, "One of us does not have the required permissions.", EmbedType.Warning); + break; + + case NullReferenceException _: + case InvalidDataException _: + e.Context.Client.Logger.LogWarning(eventId, e.Exception, + $"[{e.Context.Guild.Name} : {e.Context.Channel.Name}] {e.Context.User.Username} executed the command '{e.Command?.QualifiedName ?? ""}' but it threw an error: "); + await BotServices.SendEmbedAsync(e.Context, e.Exception.Message, EmbedType.Error); + break; + + default: + e.Context.Client.Logger.LogError(eventId, + $"[{e.Exception.GetType()}] Unhandled Exception. {e.Exception.Message}"); + break; + } + } + } +} \ No newline at end of file diff --git a/src/FlawBOT.Core/Common/HelpFormatter.cs b/src/FlawBOT/Common/HelpFormatter.cs similarity index 86% rename from src/FlawBOT.Core/Common/HelpFormatter.cs rename to src/FlawBOT/Common/HelpFormatter.cs index bbcbac95..08be1d63 100644 --- a/src/FlawBOT.Core/Common/HelpFormatter.cs +++ b/src/FlawBOT/Common/HelpFormatter.cs @@ -24,7 +24,8 @@ public HelpFormatter(CommandContext ctx) : base(ctx) public override CommandHelpMessage Build() { - var desc = $"Listing all commands and groups. Use {Formatter.InlineCode(".help ")} for more details."; + var desc = + $"Listing all commands and groups. Use {Formatter.InlineCode(".help ")} for more details."; if (!string.IsNullOrWhiteSpace(_name)) { _output.WithTitle(_name); @@ -47,21 +48,22 @@ public override BaseHelpFormatter WithCommand(Command cmd) foreach (var arg in overload.Arguments) { args.Append(Formatter.InlineCode($"[{CommandsNext.GetUserFriendlyTypeName(arg.Type)}]")); - args.Append(" "); + args.Append(' '); args.Append(arg.Description ?? "No description provided."); if (arg.IsOptional) { args.Append(" (def: ") .Append(Formatter.InlineCode(arg.DefaultValue is null ? "None" - : arg.DefaultValue.ToString())).Append(")"); + : arg.DefaultValue.ToString())).Append(')'); args.Append(" (optional)"); } args.AppendLine(); } - _output.AddField($"{(cmd.Overloads.Count > 1 ? $"Overload #{overload.Priority}" : "Arguments")}", args.ToString() ?? "No arguments."); + _output.AddField($"{(cmd.Overloads.Count > 1 ? $"Overload #{overload.Priority}" : "Arguments")}", + args.ToString() ?? "No arguments."); } if (cmd.Aliases?.Any() ?? false) @@ -73,7 +75,8 @@ public override BaseHelpFormatter WithSubcommands(IEnumerable subcomman { var enumerable = subcommands.ToList(); if (enumerable.Any()) - _output.AddField(_name is null ? "Commands" : "Subcommands", string.Join(", ", enumerable.Select(c => Formatter.InlineCode(c.Name)))); + _output.AddField(_name is null ? "Commands" : "Subcommands", + string.Join(", ", enumerable.Select(c => Formatter.InlineCode(c.Name)))); return this; } } diff --git a/src/FlawBOT/Common/MusicPlayer.cs b/src/FlawBOT/Common/MusicPlayer.cs new file mode 100644 index 00000000..d479b972 --- /dev/null +++ b/src/FlawBOT/Common/MusicPlayer.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using DSharpPlus.Entities; +using DSharpPlus.Lavalink; +using DSharpPlus.Lavalink.EventArgs; +using FlawBOT.Models; + +namespace FlawBOT.Services +{ + public sealed class MusicPlayer + { + public MusicPlayer(LavalinkService lavalink) + { + LavaLink = lavalink; + QueueLock = new SemaphoreSlim(1, 1); + QueueList = new List(); + } + + private LavalinkService LavaLink { get; } + private SemaphoreSlim QueueLock { get; } + private List QueueList { get; } + + public bool IsPlaying { get; private set; } + + public int Volume { get; private set; } = 100; + + public MusicData NowPlaying { get; private set; } + + public DiscordChannel Channel => Player?.Channel; + + public DiscordChannel CommandChannel { get; set; } + + private LavalinkGuildConnection Player { get; set; } + + public async Task PlayAsync() + { + if (Player == null || !Player.IsConnected) return; + + if (NowPlaying.Track?.TrackString == null) + await PlayHandlerAsync(); + } + + public async Task StopAsync() + { + if (Player == null || !Player.IsConnected) return; + + QueueList.Clear(); + NowPlaying = default; + await Player.StopAsync(); + } + + public async Task PauseAsync() + { + if (Player == null || !Player.IsConnected) return; + + IsPlaying = false; + await Player.PauseAsync(); + } + + public async Task ResumeAsync() + { + if (Player == null || !Player.IsConnected) return; + + IsPlaying = true; + await Player.ResumeAsync(); + } + + public async Task SetVolumeAsync(int volume) + { + if (Player == null || !Player.IsConnected) return; + + await Player.SetVolumeAsync(volume); + Volume = volume; + } + + public async Task RestartAsync() + { + if (Player == null || !Player.IsConnected || NowPlaying.Track.TrackString == null) return; + + await QueueLock.WaitAsync(); + QueueList.Insert(0, NowPlaying); + await Player.StopAsync(); + QueueLock.Release(); + } + + public void Enqueue(MusicData item) + { + lock (QueueList) + { + if (QueueList.Count == 1 || IsPlaying) + { + Player.StopAsync(); + QueueList.Clear(); + } + + if (!QueueList.Any()) + QueueList.Add(item); + } + } + + public MusicData? Dequeue() + { + lock (QueueList) + { + if (QueueList.Count == 0) return null; + + var item = QueueList[0]; + QueueList.RemoveAt(0); + return item; + } + } + + public async Task CreatePlayerAsync(DiscordChannel channel) + { + if (Player != null && Player.IsConnected) return; + + Player = await LavaLink.Node.ConnectAsync(channel); + + if (Volume != 100) + await Player.SetVolumeAsync(Volume); + Player.PlaybackFinished += Player_PlaybackFinished; + } + + public async Task DestroyPlayerAsync() + { + if (Player == null) return; + + if (Player.IsConnected) + await Player.DisconnectAsync(); + + Player = null; + } + + public TimeSpan GetCurrentPosition() + { + return NowPlaying.Track.TrackString == null ? TimeSpan.Zero : Player.CurrentState.PlaybackPosition; + } + + private async Task Player_PlaybackFinished(LavalinkGuildConnection con, TrackFinishEventArgs e) + { + await Task.Delay(500); + IsPlaying = false; + await PlayHandlerAsync(); + } + + private async Task PlayHandlerAsync() + { + var song = Dequeue(); + if (song == null) + { + NowPlaying = default; + return; + } + + var item = song.Value; + NowPlaying = item; + IsPlaying = true; + await Player.PlayAsync(item.Track); + } + } +} \ No newline at end of file diff --git a/src/FlawBOT/Common/SharedData.cs b/src/FlawBOT/Common/SharedData.cs new file mode 100644 index 00000000..deec2efc --- /dev/null +++ b/src/FlawBOT/Common/SharedData.cs @@ -0,0 +1,39 @@ +using System; +using System.Reflection; +using DSharpPlus.Entities; +using FlawBOT.Models; +using FlawBOT.Properties; + +namespace FlawBOT.Common +{ + public static class SharedData + { + public static string Name { get; } = "FlawBOT"; + public static string Version { get; } = Assembly.GetExecutingAssembly().GetName().Version?.ToString(); + public static string GitHubLink { get; } = Resources.URL_BOT_GitHub; + public static string InviteLink { get; } = Resources.URL_BOT_Invite; + public static DiscordColor DefaultColor { get; } = new DiscordColor("#00FF7F"); + public static DateTime ProcessStarted { get; set; } + public static TokenData Tokens { get; set; } = new TokenData(); + public static int ShardCount { get; } = 1; + } + + public enum EmbedType + { + Default, + Warning, + Missing, + Error + } + + public enum UserStateChange + { + Ban, + Deafen, + Kick, + Mute, + Unban, + Undeafen, + Unmute + } +} \ No newline at end of file diff --git a/src/FlawBOT/FlawBOT.cs b/src/FlawBOT/FlawBOT.cs new file mode 100644 index 00000000..7ebab6f0 --- /dev/null +++ b/src/FlawBOT/FlawBOT.cs @@ -0,0 +1,216 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using DSharpPlus; +using DSharpPlus.CommandsNext; +using DSharpPlus.Entities; +using DSharpPlus.EventArgs; +using DSharpPlus.Interactivity; +using DSharpPlus.Interactivity.Enums; +using DSharpPlus.Interactivity.Extensions; +using DSharpPlus.Lavalink; +using DSharpPlus.VoiceNext; +using Emzi0767; +using FlawBOT.Common; +using FlawBOT.Modules; +using FlawBOT.Services; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace FlawBOT +{ + internal sealed class FlawBot + { + public FlawBot(int shardId = 0) + { + // Setup Client + Client = new DiscordClient(new DiscordConfiguration + { + Token = SharedData.Tokens.DiscordToken, + TokenType = TokenType.Bot, + AutoReconnect = true, + ReconnectIndefinitely = true, + MinimumLogLevel = LogLevel.Information, + GatewayCompressionLevel = GatewayCompressionLevel.Stream, + LargeThreshold = 250, + MessageCacheSize = 2048, + LogTimestampFormat = "yyyy-MM-dd HH:mm:ss zzz", + ShardId = shardId, + ShardCount = SharedData.ShardCount + }); + Client.Ready += Client_Ready; + Client.GuildAvailable += Client_GuildAvailable; + Client.ClientErrored += Client_ClientErrored; + Client.SocketErrored += Client_SocketErrored; + Client.VoiceStateUpdated += Client_VoiceStateUpdated; + + // Setup Services + Services = new ServiceCollection() + .AddTransient() + .AddSingleton() + .AddSingleton(new LavalinkService(Client)) + .AddSingleton(new YoutubeService()) + .AddSingleton(this) + .BuildServiceProvider(true); + + // Setup Commands + Commands = Client.UseCommandsNext(new CommandsNextConfiguration + { + PrefixResolver = PrefixResolverAsync, // Set the command prefix that will be used by the bot + EnableDms = false, // Set the boolean for responding to direct messages + EnableMentionPrefix = true, // Set the boolean for mentioning the bot as a command prefix + CaseSensitive = false, + IgnoreExtraArguments = true, + EnableDefaultHelp = false, + Services = Services + }); + Commands.CommandExecuted += Command_Executed; + Commands.CommandErrored += Command_Errored; + Commands.SetHelpFormatter(); + 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.RegisterCommands(); + Commands.RegisterCommands(); + Commands.RegisterCommands(); + Commands.RegisterCommands(); + Commands.RegisterCommands(); + Commands.RegisterCommands(); + Commands.RegisterCommands(); + Commands.RegisterCommands(); + Commands.RegisterCommands(); + Commands.RegisterCommands(); + Commands.RegisterCommands(); + Commands.RegisterCommands(); + + // Setup Interactivity + Interactivity = Client.UseInteractivity(new InteractivityConfiguration + { + PaginationBehaviour = PaginationBehaviour.Ignore, + Timeout = TimeSpan.FromSeconds(30) + }); + + // Setup Voice + Voice = Client.UseVoiceNext(new VoiceNextConfiguration + { + AudioFormat = AudioFormat.Default, + EnableIncoming = true + }); + + // Setup Lavalink + Lavalink = Client.UseLavalink(); + + // Start the uptime counter + Console.Title = SharedData.Name + "-" + SharedData.Version; + SharedData.ProcessStarted = DateTime.Now; + } + + private IServiceProvider Services { get; } + private static EventId EventId { get; } = new EventId(1000, SharedData.Name); + private DiscordClient Client { get; } + private CommandsNextExtension Commands { get; } + private InteractivityExtension Interactivity { get; } + private VoiceNextExtension Voice { get; } + private LavalinkExtension Lavalink { get; } + + public async Task RunAsync() + { + // Update any other services that are being used. + Client.Logger.LogInformation(EventId, "Loading..."); + //await SteamService.UpdateSteamAppListAsync().ConfigureAwait(false); + //await TeamFortressService.UpdateTf2SchemaAsync().ConfigureAwait(false); + //await PokemonService.UpdatePokemonListAsync().ConfigureAwait(false); + + // Set the initial activity and connect the bot to Discord + var act = new DiscordActivity("Night of Fire", ActivityType.ListeningTo); + await Client.ConnectAsync(act, UserStatus.DoNotDisturb).ConfigureAwait(false); + } + + public async Task StopAsync() + { + await Client.DisconnectAsync().ConfigureAwait(false); + } + + private static Task Client_Ready(DiscordClient sender, ReadyEventArgs e) + { + sender.Logger.LogInformation(EventId, $"{SharedData.Name}, version: {SharedData.Version}"); + return Task.CompletedTask; + } + + private Task Client_GuildAvailable(DiscordClient sender, GuildCreateEventArgs e) + { + sender.Logger.LogInformation(EventId, $"Connected to server: {e.Guild.Name}"); + return Task.CompletedTask; + } + + private static Task Client_ClientErrored(DiscordClient sender, ClientErrorEventArgs e) + { + sender.Logger.LogError(EventId, $"[{e.Exception.GetType()}] Client Exception. {e.Exception.Message}"); + return Task.CompletedTask; + } + + private Task Client_SocketErrored(DiscordClient sender, SocketErrorEventArgs e) + { + var ex = e.Exception; + while (ex is AggregateException) + ex = ex.InnerException; + + sender.Logger.LogCritical(EventId, $"Socket threw an exception {ex.GetType()}: {ex.Message}"); + return Task.CompletedTask; + } + + private async Task Client_VoiceStateUpdated(DiscordClient sender, VoiceStateUpdateEventArgs e) + { + var musicData = await Services.GetService().GetOrCreateDataAsync(e.Guild); + if (e.After.Channel == null && e.User == Client.CurrentUser) + { + await musicData.StopAsync(); + await musicData.DestroyPlayerAsync(); + return; + } + + if (e.User == Client.CurrentUser) return; + var channel = musicData.Channel; + if (channel == null || channel != e.Before.Channel) return; + + var users = channel.Users; + if (musicData.IsPlaying && !users.Any(x => !x.IsBot)) + { + sender.Logger.LogInformation(EventId, $"All users left voice in {e.Guild.Name}, pausing playback..."); + await musicData.PauseAsync(); + + if (musicData.CommandChannel != null) + await musicData.CommandChannel.SendMessageAsync( + "All users left the channel, playback paused. You can resume it by joining the channel and using the `resume` command.") + .ConfigureAwait(false); + } + } + + private static Task Command_Executed(CommandsNextExtension sender, CommandExecutionEventArgs e) + { + e.Context.Client.Logger.LogInformation(EventId, + $"[{e.Context.Guild.Name} : {e.Context.Channel.Name}] {e.Context.User.Username} executed the command '{e.Command.QualifiedName}'"); + return Task.CompletedTask; + } + + private static async Task Command_Errored(CommandsNextExtension sender, CommandErrorEventArgs e) + { + await Exceptions.Process(e, EventId); + } + + private static Task PrefixResolverAsync(DiscordMessage m) + { + return Task.FromResult(m.GetStringPrefixLength(SharedData.Tokens.CommandPrefix)); + } + } +} \ No newline at end of file diff --git a/src/FlawBOT/FlawBOT.csproj b/src/FlawBOT/FlawBOT.csproj new file mode 100644 index 00000000..87ba1fca --- /dev/null +++ b/src/FlawBOT/FlawBOT.csproj @@ -0,0 +1,85 @@ + + + + Exe + net5.0 + favicon.ico + FlawBOT.Program + false + 3.0.0 + + 9.0 + 3.0.0.0 + 3.0.0.0 + FlawBOT + FlawBOT + + Multipurpose Discord bot written in C# using DSharpPlus. + https://github.com/CriticalFlaw/FlawBOT + + CriticalFlaw + FlawBOT + Igor Nikitin + en-CA + + + + false + AnyCPU + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + + + + ..\FlawBOT.Framework\Resources\PokemonTcgSdk.dll + + + + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + \ No newline at end of file diff --git a/src/FlawBOT/Models/AmiiboData.cs b/src/FlawBOT/Models/AmiiboData.cs new file mode 100644 index 00000000..dd3c0806 --- /dev/null +++ b/src/FlawBOT/Models/AmiiboData.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace FlawBOT.Models +{ + public class AmiiboData + { + [JsonProperty("amiibo")] public List Amiibo { get; set; } + } + + public class Amiibo + { + [JsonProperty("amiiboSeries")] public string AmiiboSeries { get; set; } + + [JsonProperty("gameSeries")] public string GameSeries { get; set; } + + [JsonProperty("image")] public string Image { get; set; } + + [JsonProperty("name")] public string Name { get; set; } + + [JsonProperty("release")] public Release ReleaseDate { get; set; } + } + + public class Release + { + [JsonProperty("au")] public string Australian { get; set; } + + [JsonProperty("eu")] public string European { get; set; } + + [JsonProperty("jp")] public string Japanese { get; set; } + + [JsonProperty("na")] public string American { get; set; } + } +} \ No newline at end of file diff --git a/src/FlawBOT/Models/DictionaryData.cs b/src/FlawBOT/Models/DictionaryData.cs new file mode 100644 index 00000000..85b30854 --- /dev/null +++ b/src/FlawBOT/Models/DictionaryData.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace FlawBOT.Models +{ + public class DictionaryData + { + [JsonProperty("result_type")] public string ResultType { get; set; } + + [JsonProperty("list")] public List List { get; set; } + } + + public class DictionaryDataList + { + [JsonProperty("definition")] public string Definition { get; set; } + + [JsonProperty("author")] public string Author { get; set; } + + [JsonProperty("permalink")] public string Permalink { get; set; } + + [JsonProperty("example")] public string Example { get; set; } + + [JsonProperty("thumbs_up")] public int ThumbsUp { get; set; } + + [JsonProperty("thumbs_down")] public int ThumbsDown { get; set; } + } +} \ No newline at end of file diff --git a/src/FlawBOT/Models/MiscData.cs b/src/FlawBOT/Models/MiscData.cs new file mode 100644 index 00000000..f5460251 --- /dev/null +++ b/src/FlawBOT/Models/MiscData.cs @@ -0,0 +1,11 @@ +using Newtonsoft.Json; + +namespace FlawBOT.Models +{ + public class DogData + { + [JsonProperty("status")] public string Status { get; set; } + + [JsonProperty("message")] public string Message { get; set; } + } +} \ No newline at end of file diff --git a/src/FlawBOT/Models/MusicData.cs b/src/FlawBOT/Models/MusicData.cs new file mode 100644 index 00000000..eaabb2f0 --- /dev/null +++ b/src/FlawBOT/Models/MusicData.cs @@ -0,0 +1,52 @@ +using DSharpPlus.Entities; +using DSharpPlus.Lavalink; +using Newtonsoft.Json; + +namespace FlawBOT.Models +{ + public struct MusicData + { + [JsonIgnore] public LavalinkTrack Track { get; } + + [JsonIgnore] public DiscordMember Requester { get; } + + public MusicData(LavalinkTrack track, DiscordMember requester) + { + Track = track; + Requester = requester; + } + } + + public struct YouTubeData + { + public string Title { get; } + public string Author { get; } + public string Id { get; } + + public YouTubeData(string title, string author, string id) + { + Title = title; + Author = author; + Id = id; + } + } + + internal struct YouTubeResponse + { + [JsonProperty("id")] public ResponseId Id { get; private set; } + + [JsonProperty("snippet")] public ResponseSnippet Snippet { get; private set; } + + public struct ResponseId + { + [JsonProperty("videoId")] public string VideoId { get; private set; } + } + + public struct ResponseSnippet + { + [JsonProperty("title")] public string Title { get; private set; } + + [JsonProperty("channelTitle")] public string Author { get; private set; } + } + } +} \ No newline at end of file diff --git a/src/FlawBOT/Models/NASAData.cs b/src/FlawBOT/Models/NASAData.cs new file mode 100644 index 00000000..8cccbf13 --- /dev/null +++ b/src/FlawBOT/Models/NASAData.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace FlawBOT.Models +{ + public class NasaData + { + [JsonProperty("copyright")] public string Copyright { get; set; } + + [JsonProperty("date")] public string Date { get; set; } + + [JsonProperty("explanation")] public string Description { get; set; } + + [JsonProperty("hdurl")] public string ImageHd { get; set; } + + [JsonProperty("title")] public string Title { get; set; } + + [JsonProperty("url")] public string ImageSd { get; set; } + } +} \ No newline at end of file diff --git a/src/FlawBOT/Models/NewsData.cs b/src/FlawBOT/Models/NewsData.cs new file mode 100644 index 00000000..cf2c80ac --- /dev/null +++ b/src/FlawBOT/Models/NewsData.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace FlawBOT.Models +{ + public class NewsData + { + [JsonProperty("status")] public string Status { get; set; } + + [JsonProperty("articles")] public List
Articles { get; set; } + } + + public class Article + { + [JsonProperty("author")] public string Author { get; set; } + + [JsonProperty("title")] public string Title { get; set; } + + [JsonProperty("description")] public string Description { get; set; } + + [JsonProperty("url")] public string Url { get; set; } + + [JsonProperty("urlToImage")] public string UrlImage { get; set; } + + [JsonProperty("publishedAt")] public DateTime PublishDate { get; set; } + + [JsonProperty("content")] public string Content { get; set; } + } +} \ No newline at end of file diff --git a/src/FlawBOT/Models/SimpsonsData.cs b/src/FlawBOT/Models/SimpsonsData.cs new file mode 100644 index 00000000..a094c0a6 --- /dev/null +++ b/src/FlawBOT/Models/SimpsonsData.cs @@ -0,0 +1,37 @@ +using Newtonsoft.Json; + +namespace FlawBOT.Models +{ + public class SimpsonsData + { + [JsonProperty("Episode")] public Episode Episode { get; set; } + + [JsonProperty("Frame")] public Frame Frame { get; set; } + } + + public class Episode + { + [JsonProperty("Id")] public int Id { get; set; } + + [JsonProperty("Key")] public string Key { get; set; } + + [JsonProperty("Title")] public string Title { get; set; } + + [JsonProperty("Director")] public string Director { get; set; } + + [JsonProperty("Writer")] public string Writer { get; set; } + + [JsonProperty("OriginalAirDate")] public string OriginalAirDate { get; set; } + + [JsonProperty("WikiLink")] public string WikiLink { get; set; } + } + + public class Frame + { + [JsonProperty("Id")] public int Id { get; set; } + + [JsonProperty("Episode")] public string Episode { get; set; } + + [JsonProperty("Timestamp")] public int Timestamp { get; set; } + } +} \ No newline at end of file diff --git a/src/FlawBOT/Models/SpeedrunData.cs b/src/FlawBOT/Models/SpeedrunData.cs new file mode 100644 index 00000000..4d6d7cdc --- /dev/null +++ b/src/FlawBOT/Models/SpeedrunData.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace FlawBOT.Models +{ + #region GAME + + public class SpeedrunGame + { + [JsonProperty("data")] public List Data { get; set; } + } + + public class Data + { + [JsonProperty("id")] public string Id { get; set; } + + [JsonProperty("names")] public Names Names { get; set; } + + [JsonProperty("abbreviation")] public string Abbreviation { get; set; } + + [JsonProperty("weblink")] public string WebLink { get; set; } + + [JsonProperty("release-date")] public string ReleaseDate { get; set; } + + [JsonProperty("platforms")] public List Platforms { get; set; } + + [JsonProperty("developers")] public List Developers { get; set; } + + [JsonProperty("publishers")] public List Publishers { get; set; } + + [JsonProperty("assets")] public Assets Assets { get; set; } + + [JsonProperty("links")] public List Links { get; set; } + + [JsonIgnore] + [JsonProperty("released")] + public int ReleaseYear { get; set; } + + [JsonIgnore] [JsonProperty("ruleset")] public int RuleSet { get; set; } + + [JsonIgnore] [JsonProperty("romhack")] public int RomHack { get; set; } + + [JsonIgnore] + [JsonProperty("gametypes")] + public int GameTypes { get; set; } + + [JsonIgnore] [JsonProperty("regions")] public int Regions { get; set; } + + [JsonIgnore] [JsonProperty("genres")] public int Genres { get; set; } + + [JsonIgnore] [JsonProperty("engines")] public int Engines { get; set; } + + [JsonIgnore] [JsonProperty("created")] public int Created { get; set; } + } + + public class Names + { + [JsonProperty("international")] public string International { get; set; } + + [JsonIgnore] + [JsonProperty("japanese")] + public string Japanese { get; set; } + + [JsonIgnore] [JsonProperty("twitch")] public string Twitch { get; set; } + } + + public class Assets + { + [JsonProperty("logo")] public Image Logo { get; set; } + + [JsonProperty("cover-small")] public Image CoverSmall { get; set; } + + [JsonProperty("cover-medium")] public Image CoverMedium { get; set; } + + [JsonProperty("cover-large")] public Image CoverLarge { get; set; } + + [JsonProperty("icon")] public Image Icon { get; set; } + + [JsonProperty("foreground")] public object Foreground { get; set; } + } + + public class Image + { + [JsonProperty("uri")] public string Url { get; set; } + + [JsonIgnore] [JsonProperty("width")] public int Width { get; set; } + + [JsonIgnore] [JsonProperty("height")] public int Height { get; set; } + } + + public class Link + { + [JsonProperty("rel")] public string Rel { get; set; } + + [JsonProperty("uri")] public string Url { get; set; } + } + + #endregion GAME + + #region CATEGORY + + public class SpeedrunCategory + { + [JsonProperty("data")] public List Data { get; set; } + } + + public class CategoryData + { + [JsonProperty("id")] public string Id { get; set; } + + [JsonProperty("name")] public string Name { get; set; } + + [JsonProperty("weblink")] public Uri Weblink { get; set; } + + [JsonIgnore] [JsonProperty("type")] public string Type { get; set; } + + [JsonProperty("rules")] public string Rules { get; set; } + + [JsonProperty("players")] public Players Players { get; set; } + + [JsonIgnore] + [JsonProperty("miscellaneous")] + public bool Miscellaneous { get; set; } + + [JsonProperty("links")] public List Links { get; set; } + } + + public class Players + { + [JsonProperty("type")] public string Type { get; set; } + + [JsonProperty("value")] public long Value { get; set; } + } + + #endregion CATEGORY + + #region EXTRA + + public class SpeedrunExtra + { + [JsonProperty("data")] public ExtraData Data { get; set; } + } + + public class ExtraData + { + [JsonProperty("id")] public string Id { get; set; } + + [JsonProperty("name")] public string Name { get; set; } + + [JsonIgnore] + [JsonProperty("released")] + public int? Released { get; set; } + + [JsonProperty("links")] public List Links { get; set; } + } + + public enum SpeedrunExtras + { + Platforms, + Genres, + Developers, + Publishers + } + + #endregion EXTRA +} \ No newline at end of file diff --git a/src/FlawBOT/Models/TokenData.cs b/src/FlawBOT/Models/TokenData.cs new file mode 100644 index 00000000..aebd7eaf --- /dev/null +++ b/src/FlawBOT/Models/TokenData.cs @@ -0,0 +1,38 @@ +using Newtonsoft.Json; + +namespace FlawBOT.Models +{ + public class TokenData + { + [JsonProperty("prefix")] public string CommandPrefix { get; set; } + + [JsonProperty("discord")] public string DiscordToken { get; set; } + + [JsonProperty("steam", NullValueHandling = NullValueHandling.Ignore)] + public string SteamToken { get; set; } + + [JsonProperty("imgur", NullValueHandling = NullValueHandling.Ignore)] + public string ImgurToken { get; set; } + + [JsonProperty("omdb", NullValueHandling = NullValueHandling.Ignore)] + public string OmdbToken { get; set; } + + [JsonProperty("twitch", NullValueHandling = NullValueHandling.Ignore)] + public string TwitchToken { get; set; } + + [JsonProperty("nasa", NullValueHandling = NullValueHandling.Ignore)] + public string NasaToken { get; set; } + + [JsonProperty("teamworktf", NullValueHandling = NullValueHandling.Ignore)] + public string TeamworkToken { get; set; } + + [JsonProperty("news", NullValueHandling = NullValueHandling.Ignore)] + public string NewsToken { get; set; } + + [JsonProperty("weather", NullValueHandling = NullValueHandling.Ignore)] + public string WeatherToken { get; set; } + + [JsonProperty("youtube", NullValueHandling = NullValueHandling.Ignore)] + public string YouTubeToken { get; set; } + } +} \ No newline at end of file diff --git a/src/FlawBOT/Models/WorldData.cs b/src/FlawBOT/Models/WorldData.cs new file mode 100644 index 00000000..c386dec8 --- /dev/null +++ b/src/FlawBOT/Models/WorldData.cs @@ -0,0 +1,158 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace FlawBOT.Models +{ + #region IP + + public class IpStack + { + [JsonProperty("ip")] public string Ip { get; set; } + + [JsonProperty("type")] public string Type { get; set; } + + [JsonProperty("continent_name")] public string Continent { get; set; } + + [JsonProperty("country_name")] public string Country { get; set; } + + [JsonProperty("region_name")] public string Region { get; set; } + + [JsonProperty("city")] public string City { get; set; } + + [JsonProperty("zip")] public string ZipCode { get; set; } + + [JsonProperty("latitude")] public double Latitude { get; set; } + + [JsonProperty("longitude")] public double Longitude { get; set; } + + [JsonProperty("continent_code")] + [JsonIgnore] + private string ContinentCode { get; set; } + + [JsonProperty("country_code")] + [JsonIgnore] + private string CountryCode { get; set; } + + [JsonProperty("region_code")] + [JsonIgnore] + private string RegionCode { get; set; } + + [JsonProperty("location")] + [JsonIgnore] + private string Location { get; set; } + } + + #endregion IP + + #region WEATHER + + public class WeatherData + { + [JsonProperty("request", NullValueHandling = NullValueHandling.Ignore)] + public Request Request { get; set; } + + [JsonProperty("location", NullValueHandling = NullValueHandling.Ignore)] + public Location Location { get; set; } + + [JsonProperty("current", NullValueHandling = NullValueHandling.Ignore)] + public Current Current { get; set; } + } + + public class Request + { + [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] + public string Type { get; set; } + + [JsonProperty("query", NullValueHandling = NullValueHandling.Ignore)] + public string Query { get; set; } + + [JsonProperty("language", NullValueHandling = NullValueHandling.Ignore)] + public string Language { get; set; } + + [JsonProperty("unit", NullValueHandling = NullValueHandling.Ignore)] + public string Unit { get; set; } + } + + public class Location + { + [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] + public string Name { get; set; } + + [JsonProperty("country", NullValueHandling = NullValueHandling.Ignore)] + public string Country { get; set; } + + [JsonProperty("region", NullValueHandling = NullValueHandling.Ignore)] + public string Region { get; set; } + + [JsonProperty("lat", NullValueHandling = NullValueHandling.Ignore)] + public string Latitude { get; set; } + + [JsonProperty("lon", NullValueHandling = NullValueHandling.Ignore)] + public string Longitude { get; set; } + + [JsonProperty("timezone_id", NullValueHandling = NullValueHandling.Ignore)] + public string Timezone { get; set; } + + [JsonProperty("localtime", NullValueHandling = NullValueHandling.Ignore)] + public string LocalTime { get; set; } + + [JsonProperty("localtime_epoch", NullValueHandling = NullValueHandling.Ignore)] + public int LocalTime_epoch { get; set; } + + [JsonProperty("utc_offset", NullValueHandling = NullValueHandling.Ignore)] + public string UTC_Offset { get; set; } + } + + public class Current + { + [JsonProperty("observation_time", NullValueHandling = NullValueHandling.Ignore)] + public string ObservationTime { get; set; } + + [JsonProperty("temperature", NullValueHandling = NullValueHandling.Ignore)] + public int Temperature { get; set; } + + [JsonProperty("weather_code", NullValueHandling = NullValueHandling.Ignore)] + public int Code { get; set; } + + [JsonProperty("weather_icons", NullValueHandling = NullValueHandling.Ignore)] + public List Icons { get; set; } + + [JsonProperty("weather_descriptions", NullValueHandling = NullValueHandling.Ignore)] + public List Descriptions { get; set; } + + [JsonProperty("wind_speed", NullValueHandling = NullValueHandling.Ignore)] + public int Wind_Speed { get; set; } + + [JsonProperty("wind_degree", NullValueHandling = NullValueHandling.Ignore)] + public int Wind_Degree { get; set; } + + [JsonProperty("wind_dir", NullValueHandling = NullValueHandling.Ignore)] + public string Wind_Direction { get; set; } + + [JsonProperty("pressure", NullValueHandling = NullValueHandling.Ignore)] + public int Pressure { get; set; } + + [JsonProperty("precip", NullValueHandling = NullValueHandling.Ignore)] + public int Precipitation { get; set; } + + [JsonProperty("humidity", NullValueHandling = NullValueHandling.Ignore)] + public int Humidity { get; set; } + + [JsonProperty("cloudcover", NullValueHandling = NullValueHandling.Ignore)] + public int CloudCover { get; set; } + + [JsonProperty("feelslike", NullValueHandling = NullValueHandling.Ignore)] + public int FeelsLike { get; set; } + + [JsonProperty("uv_index", NullValueHandling = NullValueHandling.Ignore)] + public int UV_Index { get; set; } + + [JsonProperty("visibility", NullValueHandling = NullValueHandling.Ignore)] + public int Visibility { get; set; } + + [JsonProperty("is_day", NullValueHandling = NullValueHandling.Ignore)] + public string IsDay { get; set; } + } + + #endregion WEATHER +} \ No newline at end of file diff --git a/src/FlawBOT/Modules/Bot/BotModule.cs b/src/FlawBOT/Modules/Bot/BotModule.cs new file mode 100644 index 00000000..46751101 --- /dev/null +++ b/src/FlawBOT/Modules/Bot/BotModule.cs @@ -0,0 +1,277 @@ +using System; +using System.Threading.Tasks; +using DSharpPlus; +using DSharpPlus.CommandsNext; +using DSharpPlus.CommandsNext.Attributes; +using DSharpPlus.Entities; +using FlawBOT.Common; +using FlawBOT.Properties; +using FlawBOT.Services; + +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", "about")] + [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) **|** [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 LeaveServer(CommandContext ctx) + { + await ctx.RespondAsync($"Are you sure you want {SharedData.Name} to leave this server?") + .ConfigureAwait(false); + var message = await ctx + .RespondAsync(Resources.INFO_RESPOND) + .ConfigureAwait(false); + var interactivity = await BotServices.GetUserInteractivity(ctx, "yes", 10).ConfigureAwait(false); + if (interactivity.Result is null) + { + await message.ModifyAsync("~~" + message.Content + "~~ " + Resources.INFO_REQ_TIMEOUT) + .ConfigureAwait(false); + return; + } + + 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 BotServices.SendEmbedAsync(ctx, $":ping_pong: Pong! Ping: **{ctx.Client.Ping}**ms") + .ConfigureAwait(false); + } + + #endregion COMMAND_PING + + #region COMMAND_REPORT + + [Hidden, Command("report"), 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_LENGTH).ConfigureAwait(false); + return; + } + + 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(Resources.INFO_RESPOND) + .ConfigureAwait(false); + var interactivity = await BotServices.GetUserInteractivity(ctx, "yes", 10).ConfigureAwait(false); + if (interactivity.Result is null) + { + await message.ModifyAsync("~~" + message.Content + "~~ " + Resources.INFO_REQ_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 ctx.RespondAsync("Thank You! Your report has been submitted.").ConfigureAwait(false); + } + } + + #endregion COMMAND_REPORT + + #region COMMAND_SAY + + [Hidden, Command("say"), 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_TTS + + [Command("tts"), Aliases("talk")] + [Description("Make FlawBOT repeat a message in text-to-speech")] + [RequirePermissions(Permissions.SendTtsMessages)] + public async Task SayTts(CommandContext ctx, + [Description("Message for the bot to convert to speech"), RemainingText] + string message) + { + if (string.IsNullOrWhiteSpace(message)) + await ctx.RespondAsync(":thinking:").ConfigureAwait(false); + else + await ctx.RespondAsync(Formatter.BlockCode(Formatter.Strip(message)), true).ConfigureAwait(false); + } + + #endregion COMMAND_TTS + + #region COMMAND_UPTIME + + [Command("uptime"), Aliases("time")] + [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, Hidden, Command("activity"), 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().ConfigureAwait(false); + return; + } + + var game = new DiscordActivity(activity); + await ctx.Client.UpdateStatusAsync(game).ConfigureAwait(false); + await ctx.RespondAsync($"{SharedData.Name} activity has been changed to Playing {game.Name}").ConfigureAwait(false); + } + + #endregion COMMAND_ACTIVITY + + #region COMMAND_AVATAR + + [RequireOwner, Hidden, Command("avatar"), Aliases("setavatar", "pfp", "photo")] + [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 ctx.RespondAsync($"{SharedData.Name} avatar has been updated!").ConfigureAwait(false); + } + + #endregion COMMAND_AVATAR + + #region COMMAND_STATUS + + [RequireOwner, Hidden, Command("status"), Aliases("setstatus", "state")] + [Description("Set FlawBOT's status")] + public async Task SetBotStatus(CommandContext ctx, + [Description("Activity Status. Online, Idle, DND or Offline"), RemainingText] + string 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_USERNAME + + [RequireOwner, Hidden, Command("username"), Aliases("setusername", "name", "setname", "nickname", "nick")] + [Description("Set FlawBOT's username")] + public async Task SetBotUsername(CommandContext ctx, + [Description("New bot username"), RemainingText] + string name) + { + var oldName = ctx.Client.CurrentUser.Username; + var newName = string.IsNullOrWhiteSpace(name) ? SharedData.Name : name; + await ctx.Client.UpdateCurrentUserAsync(newName).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, $"{oldName}'s username has been changed to {newName}") + .ConfigureAwait(false); + } + + #endregion COMMAND_USERNAME + + #endregion OWNERS-ONLY + } +} \ No newline at end of file diff --git a/src/FlawBOT/Modules/Bot/MusicModule.cs b/src/FlawBOT/Modules/Bot/MusicModule.cs new file mode 100644 index 00000000..8496cb8a --- /dev/null +++ b/src/FlawBOT/Modules/Bot/MusicModule.cs @@ -0,0 +1,194 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using DSharpPlus; +using DSharpPlus.CommandsNext; +using DSharpPlus.CommandsNext.Attributes; +using DSharpPlus.Lavalink; +using FlawBOT.Common; +using FlawBOT.Models; +using FlawBOT.Properties; +using FlawBOT.Services; + +namespace FlawBOT.Modules +{ + public class MusicModule : BaseCommandModule + { + public MusicModule(MusicService service, YoutubeService youTube) + { + Service = service; + YouTube = youTube; + } + + private MusicService Service { get; } + private MusicPlayer Player { get; set; } + private YoutubeService YouTube { get; } + + #region COMMAND_STOP + + [Command("stop")] + [Description("Stop audio playback and leave the voice channel.")] + public async Task StopSong(CommandContext ctx) + { + await Player.StopAsync(); + await Player.DestroyPlayerAsync(); + await ctx.RespondAsync(":stop_button: Stopping Playback...").ConfigureAwait(false); + } + + #endregion COMMAND_STOP + + #region COMMAND_PAUSE + + [Command("pause")] + [Description("Pause audio playback.")] + public async Task PauseSong(CommandContext ctx) + { + await Player.PauseAsync(); + await ctx.RespondAsync(":pause_button: Pausing Playback...").ConfigureAwait(false); + } + + #endregion COMMAND_PAUSE + + #region COMMAND_RESUME + + [Command("resume"), Aliases("unpause")] + [Description("Resume audio playback.")] + public async Task ResumeAsync(CommandContext ctx) + { + await Player.ResumeAsync(); + await ctx.RespondAsync(":play_pause: Resuming Playback...").ConfigureAwait(false); + } + + #endregion COMMAND_RESUME + + #region COMMAND_VOLUME + + [Command("volume"), Aliases("v")] + [Description("Set audio playback volume.")] + public async Task SetVolume(CommandContext ctx, + [Description("Audio volume. Can be set to 0-150 (default 100).")] + int volume = 100) + { + if (volume < 0 || volume > 150) + { + await ctx.RespondAsync(":warning: Volume must be greater than 0, and less than or equal to 150.") + .ConfigureAwait(false); + return; + } + + await Player.SetVolumeAsync(volume); + await ctx.RespondAsync($":speaker: Volume set to {volume}%.").ConfigureAwait(false); + } + + #endregion COMMAND_VOLUME + + #region COMMAND_RESTART + + [Command("restart"), Aliases("replay")] + [Description("Restarts the playback of the current track.")] + public async Task RestartSong(CommandContext ctx) + { + var track = Player.NowPlaying; + await Player.RestartAsync(); + await ctx.RespondAsync( + $":play_pause: Restarting {Formatter.Bold(Formatter.Sanitize(track.Track.Title))} by {Formatter.Bold(Formatter.Sanitize(track.Track.Author))}...") + .ConfigureAwait(false); + } + + #endregion COMMAND_RESTART + + #region COMMAND_NOWPLAYING + + [Command("nowplaying"), Aliases("np")] + [Description("Displays information about currently-played track.")] + public async Task NowPlaying(CommandContext ctx) + { + var track = Player.NowPlaying; + if (Player.NowPlaying.Track?.TrackString == null) + await ctx.RespondAsync("Currently not playing anything...").ConfigureAwait(false); + else + await ctx.RespondAsync( + $":musical_note: Now playing: {Formatter.Bold(Formatter.Sanitize(track.Track.Title))} by {Formatter.Bold(Formatter.Sanitize(track.Track.Author))} [{MusicService.ToDurationString(Player.GetCurrentPosition())}/{MusicService.ToDurationString(Player.NowPlaying.Track.Length)}] requested by {Formatter.Bold(Formatter.Sanitize(Player.NowPlaying.Requester.DisplayName))}.") + .ConfigureAwait(false); + } + + #endregion COMMAND_NOWPLAYING + + #region OVERLOAD_VALIDATION + + public override async Task BeforeExecutionAsync(CommandContext ctx) + { + // Check that the user is in a voice channel + var channel = ctx.Member.VoiceState?.Channel; + if (channel == null) + { + await ctx.RespondAsync("You need to be in a voice channel.").ConfigureAwait(false); + return; + } + + // Check that the user in the same voice channel + var userState = ctx.Guild.CurrentMember?.VoiceState?.Channel; + if (userState != null && channel != userState) + { + await ctx.RespondAsync("You need to be in the same voice channel.").ConfigureAwait(false); + return; + } + + // Connect the music play to the voice channel + Player = await Service.GetOrCreateDataAsync(ctx.Guild); + Player.CommandChannel = ctx.Channel; + await base.BeforeExecutionAsync(ctx); + } + + #endregion OVERLOAD_VALIDATION + + #region COMMAND_PLAY + + [Priority(1)] + [Command("play"), Aliases("p")] + [Description("Play audio from provided URL or search by specified query.")] + public async Task PlaySong(CommandContext ctx, + [Description("URL from which to play audio")] + Uri uri) + { + var trackLoad = await Service.GetTracksAsync(uri); + await Play(ctx, trackLoad); + } + + [Priority(0)] + [Command("play")] + public async Task PlaySong(CommandContext ctx, [RemainingText] string query) + { + var results = await YouTube.GetMusicDataAsync(query); + if (!results.Any()) + { + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_COMMON, EmbedType.Missing) + .ConfigureAwait(false); + return; + } + + var trackLoad = await Service.GetTracksAsync(new Uri($"https://youtu.be/{results.FirstOrDefault().Id}")); + await Play(ctx, trackLoad); + } + + public async Task Play(CommandContext ctx, LavalinkLoadResult results) + { + var audio = results.Tracks.FirstOrDefault(); + if (results.LoadResultType == LavalinkLoadResultType.LoadFailed || audio is null) + { + await ctx.RespondAsync(":mag: No tracks were found at specified link.").ConfigureAwait(false); + return; + } + + Player.Enqueue(new MusicData(audio, ctx.Member)); + await Player.CreatePlayerAsync(ctx.Member.VoiceState.Channel); + await Player.PlayAsync(); + + await ctx.RespondAsync( + $":play_pause: Now playing: {Formatter.Bold(Formatter.Sanitize(audio.Title))} by {Formatter.Bold(Formatter.Sanitize(audio.Author))}.") + .ConfigureAwait(false); + } + + #endregion COMMAND_PLAY + } +} \ No newline at end of file diff --git a/src/FlawBOT/Modules/Games/PokemonModule.cs b/src/FlawBOT/Modules/Games/PokemonModule.cs new file mode 100644 index 00000000..04395637 --- /dev/null +++ b/src/FlawBOT/Modules/Games/PokemonModule.cs @@ -0,0 +1,69 @@ +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DSharpPlus.CommandsNext; +using DSharpPlus.CommandsNext.Attributes; +using DSharpPlus.Entities; +using FlawBOT.Common; +using FlawBOT.Properties; +using FlawBOT.Services; + +namespace FlawBOT.Modules +{ + [Cooldown(3, 5, CooldownBucketType.Channel)] + public class PokemonModule : BaseCommandModule + { + #region COMMAND_POKEMON + + [Command("pokemon"), Aliases("poke", "pk")] + [Description("Retrieve a Pokémon card")] + public async Task Pokemon(CommandContext ctx, + [Description("Name of the Pokémon"), RemainingText] + string query) + { + var results = await PokemonService.GetPokemonCardsAsync(query).ConfigureAwait(false); + if (results.Cards.Count == 0) + { + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_COMMON, EmbedType.Missing) + .ConfigureAwait(false); + return; + } + + foreach (var dex in results.Cards) + { + var card = PokemonService.GetExactPokemon(dex.Id); + var output = new DiscordEmbedBuilder() + .WithTitle(card.Name + $" (#{card.NationalPokedexNumber})") + .AddField("Series", card.Series ?? "Unknown", true) + .AddField("Rarity", card.Rarity ?? "Unknown", true) + .AddField("HP", card.Hp ?? "Unknown", true) + .AddField("Ability", card.Ability != null ? card.Ability.Name : "Unknown", true) + .WithImageUrl(card.ImageUrlHiRes ?? card.ImageUrl) + .WithFooter(!string.Equals(card.Id, results.Cards.Last().Id) + ? "Type 'next' within 10 seconds for the next Pokémon" + : "This is the last found Pokémon on the list.") + .WithColor(DiscordColor.Gold); + + var types = new StringBuilder(); + if (card.Types != null) + foreach (var type in card.Types) + types.Append(type); + output.AddField("Types", types.ToString() ?? "Unknown", true); + + var weaknesses = new StringBuilder(); + foreach (var weakness in card.Weaknesses) + weaknesses.Append(weakness.Type); + output.AddField("Weaknesses", weaknesses.ToString() ?? "Unknown", true); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); + + if (results.Cards.Count == 1) continue; + var interactivity = await BotServices.GetUserInteractivity(ctx, "next", 10).ConfigureAwait(false); + if (interactivity.Result is null) break; + if (!string.Equals(card.Id, results.Cards.Last().Id)) + await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); + } + } + + #endregion COMMAND_POKEMON + } +} \ No newline at end of file diff --git a/src/FlawBOT/Modules/Games/SpeedrunModule.cs b/src/FlawBOT/Modules/Games/SpeedrunModule.cs new file mode 100644 index 00000000..93a4b65c --- /dev/null +++ b/src/FlawBOT/Modules/Games/SpeedrunModule.cs @@ -0,0 +1,72 @@ +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DSharpPlus.CommandsNext; +using DSharpPlus.CommandsNext.Attributes; +using DSharpPlus.Entities; +using FlawBOT.Common; +using FlawBOT.Models; +using FlawBOT.Properties; +using FlawBOT.Services; + +namespace FlawBOT.Modules +{ + [Cooldown(3, 5, CooldownBucketType.Channel)] + public class SpeedrunModule : BaseCommandModule + { + #region COMMAND_SPEEDRUN + + [Command("speedrun"), Aliases("game", "run")] + [Description("Retrieve a game from Speedrun.com")] + public async Task Speedrun(CommandContext ctx, + [Description("Game to search on Speedrun.com"), RemainingText] + string query) + { + if (string.IsNullOrWhiteSpace(query)) return; + var results = SpeedrunService.GetSpeedrunGameAsync(query).Result; + if (results is null || results.Data.Count == 0) + { + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_COMMON, EmbedType.Missing) + .ConfigureAwait(false); + return; + } + + foreach (var game in results.Data) + { + var output = new DiscordEmbedBuilder() + .WithTitle(game.Names.International) + .AddField("Developers", + SpeedrunService.GetSpeedrunExtraAsync(game.Developers, SpeedrunExtras.Developers).Result ?? + "Unknown", true) + .AddField("Publishers", + SpeedrunService.GetSpeedrunExtraAsync(game.Publishers, SpeedrunExtras.Publishers).Result ?? + "Unknown", true) + .AddField("Release Date", game.ReleaseDate ?? "Unknown", true) + .AddField("Platforms", + SpeedrunService.GetSpeedrunExtraAsync(game.Platforms, SpeedrunExtras.Platforms).Result ?? + "Unknown") + .WithFooter($"ID: {game.Id} - Abbreviation: {game.Abbreviation}") + .WithThumbnail(game.Assets.CoverLarge.Url ?? game.Assets.Icon.Url) + .WithUrl(game.WebLink) + .WithColor(new DiscordColor("#0F7A4D")); + + var link = game.Links.First(x => x.Rel == "categories").Url; + var categories = SpeedrunService.GetSpeedrunCategoryAsync(link).Result; + var category = new StringBuilder(); + if (categories != null || categories.Data.Count > 0) + foreach (var x in categories.Data) + category.Append($"[{x.Name}]({x.Weblink}) **|** "); + output.AddField("Categories", category.ToString() ?? "Unknown"); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); + + if (results.Data.Count == 1) continue; + var interactivity = await BotServices.GetUserInteractivity(ctx, "next", 10).ConfigureAwait(false); + if (interactivity.Result is null) break; + if (!game.Equals(results.Data.Last())) + await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); + } + } + + #endregion COMMAND_SPEEDRUN + } +} \ No newline at end of file diff --git a/src/FlawBOT/Modules/Games/SteamModule.cs b/src/FlawBOT/Modules/Games/SteamModule.cs new file mode 100644 index 00000000..fd36c846 --- /dev/null +++ b/src/FlawBOT/Modules/Games/SteamModule.cs @@ -0,0 +1,140 @@ +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using DSharpPlus.CommandsNext; +using DSharpPlus.CommandsNext.Attributes; +using DSharpPlus.Entities; +using FlawBOT.Common; +using FlawBOT.Properties; +using FlawBOT.Services; +using Steam.Models.SteamCommunity; +using UserStatus = Steam.Models.SteamCommunity.UserStatus; + +namespace FlawBOT.Modules +{ + [Group("steam")] + [Description("Commands finding Steam games and users")] + [Cooldown(3, 5, CooldownBucketType.Channel)] + public class SteamModule : BaseCommandModule + { + #region COMMAND_GAME + + [Command("game")] + [Description("Retrieve Steam game information")] + public async Task SteamGame(CommandContext ctx, + [Description("Game to find on Steam"), RemainingText] + string query = "Team Fortress 2") + { + try + { + var app = SteamService.GetSteamAppAsync(query).Result; + if (app is null) + { + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_COMMON, EmbedType.Missing) + .ConfigureAwait(false); + return; + } + + 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.FirstOrDefault() ?? "Unknown", true) + .AddField("Publisher", app.Publishers.FirstOrDefault() ?? "Unknown", true) + .AddField("Price", app.IsFree ? "Free" : app.PriceOverview.FinalFormatted ?? "Unknown", true) + .AddField("Metacritic", app.Metacritic != null ? app.Metacritic.Score.ToString() : "Unknown", true) + .WithThumbnail(app.HeaderImage) + .WithUrl(string.Format(Resources.URL_Steam_App, app.SteamAppId)) + .WithFooter("App ID: " + app.SteamAppId) + .WithColor(new DiscordColor("#1B2838")); + + var genres = new StringBuilder(); + foreach (var genre in app.Genres.Take(3)) + genres.Append(genre.Description).Append(!genre.Equals(app.Genres.Last()) ? ", " : string.Empty); + output.AddField("Genres", 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 + + #region COMMAND_USER + + [Command("user"), Aliases("player")] + [Description("Retrieve Steam user information")] + public async Task SteamUser(CommandContext ctx, + [Description("User to find on Steam"), RemainingText] + string query) + { + if (string.IsNullOrWhiteSpace(query)) return; + var profile = SteamService.GetSteamProfileAsync(query).Result; + if (profile is null) + { + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_COMMON, EmbedType.Missing) + .ConfigureAwait(false); + } + else if (profile.Data.ProfileVisibility == ProfileVisibility.Private) + { + await BotServices.SendEmbedAsync(ctx, "This profile is private...", EmbedType.Warning) + .ConfigureAwait(false); + } + else + { + var output = new DiscordEmbedBuilder() + .WithTitle(profile.Data.Nickname) + .AddField("Status", profile.Data.UserStatus.ToString(), true) + .AddField("Country", + $":flag_{profile.Data.CountryCode.ToLowerInvariant()}: {profile.Data.CountryCode}", true) + .WithUrl(profile.Data.ProfileUrl) + .WithThumbnail(profile.Data.AvatarFullUrl) + .WithColor(new DiscordColor("#1B2838")) + .WithFooter("Steam ID: " + profile.Data.SteamId); + + if (profile.Data.UserStatus == UserStatus.Offline) + output.AddField("Last seen", + profile.Data.LastLoggedOffDate.ToUniversalTime().ToString(CultureInfo.CurrentCulture)); + + if (profile.Data.PlayingGameId != null) + { + output.AddField("Now Playing", $"`{profile.Data.PlayingGameName}`", true); + output.WithColor(new DiscordColor("#79A14D")); + } + + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); + } + } + + #endregion COMMAND_USER + + #region COMMAND_CONNECT + + [Command("connect"), Aliases("link")] + [Description("Format a game connection string into a link")] + public async Task SteamLink(CommandContext ctx, + [Description("Connection string"), RemainingText] + string link) + { + 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}")) + .ConfigureAwait(false); + else + await BotServices.SendEmbedAsync(ctx, Resources.ERR_INVALID_IP_GAME, EmbedType.Warning) + .ConfigureAwait(false); + } + + #endregion COMMAND_CONNECT + } +} \ No newline at end of file diff --git a/src/FlawBOT/Modules/Games/TeamFortressModule.cs b/src/FlawBOT/Modules/Games/TeamFortressModule.cs new file mode 100644 index 00000000..246a1d4d --- /dev/null +++ b/src/FlawBOT/Modules/Games/TeamFortressModule.cs @@ -0,0 +1,341 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using DSharpPlus.CommandsNext; +using DSharpPlus.CommandsNext.Attributes; +using DSharpPlus.Entities; +using FlawBOT.Common; +using FlawBOT.Properties; +using FlawBOT.Services; + +namespace FlawBOT.Modules +{ + [Group("tf2")] + [Description("Commands related to Team Fortress 2")] + [Cooldown(3, 5, CooldownBucketType.Channel)] + public class TeamFortressModule : BaseCommandModule + { + #region COMMAND_SCHEMA + + [Command("item"), Aliases("schema", "hat")] + [Description("Retrieve an item from the latest TF2 item schema")] + public async Task Tf2Schema(CommandContext ctx, + [Description("Item to find in the TF2 schema"), RemainingText] + string query = "The Scattergun") + { + var item = TeamFortressService.GetSchemaItem(query); + if (item is null) + { + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_COMMON, EmbedType.Missing) + .ConfigureAwait(false); + return; + } + + var textInfo = new CultureInfo("en-US", false).TextInfo; + var output = new DiscordEmbedBuilder() + .WithTitle(item.ItemName) + .WithDescription(item.ItemDescription ?? string.Empty) + .AddField("Item Slot:", textInfo.ToTitleCase(item.ItemSlot) ?? "Unknown", true) + .AddField("Giftable:", item.Capabilities.CanGiftWrap == true ? "Yes" : "No", true) + .AddField("Nameable:", item.Capabilities.Nameable ? "Yes" : "No", true) + .WithThumbnail(item.ImageUrlLarge ?? item.ImageUrl) + .WithUrl(string.Format(Resources.URL_TF2Wiki, item.ItemName.Replace(' ', '_'))) + .WithFooter("ID: " + item.DefIndex) + .WithColor(new DiscordColor("#E7B53B")); + + if (item.UsedByClasses != null) + { + var classes = new StringBuilder(); + foreach (var className in item.UsedByClasses) + classes.Append(className) + .Append(!className.Equals(item.UsedByClasses.Last()) ? ", " : string.Empty); + output.AddField("Used by:", classes.ToString() ?? "Unknown"); + } + else + { + output.AddField("Used by:", "All-Classes"); + } + + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); + } + + #endregion COMMAND_SCHEMA + + #region COMMAND_MAP + + [Command("map"), Aliases("maps")] + [Description("Retrieve map information from teamwork.tf")] + public async Task Tf2Map(CommandContext ctx, + [Description("Normalized map name, like pl_upward")] + string query) + { + if (string.IsNullOrWhiteSpace(query)) return; + var results = await TeamFortressService.GetMapStatsAsync(query.ToLowerInvariant()).ConfigureAwait(false); + if (results is null) + { + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_COMMON, EmbedType.Missing) + .ConfigureAwait(false); + return; + } + + _ = double.TryParse(results.AllTimeAvgPlayers, out var avgPlayers); + var output = new DiscordEmbedBuilder() + .WithTitle(results.MapName) + .AddField("Highest Server Count", results.HighestServers.ToString() ?? "Unknown", true) + .AddField("Highest Player Count", results.HighestPlayers.ToString() ?? "Unknown", true) + .AddField("Avg. Players", Math.Round(avgPlayers, 2).ToString(CultureInfo.InvariantCulture) ?? "Unknown", + true) + .WithFooter("Statistics retrieved from teamwork.tf - refreshed every 5 minutes") + .WithImageUrl(results.Thumbnail) + .WithUrl(string.Format(Resources.URL_TF2Wiki, results.MapName)) + .WithColor(new DiscordColor("#E7B53B")); + + if (results.RelatedMaps.Count > 0) + { + var maps = new StringBuilder(); + foreach (var map in results.RelatedMaps.Take(4)) + maps.Append(map + "\n"); + output.AddField("Related Map(s)", maps.ToString(), true); + } + + if (results.ExtraInfo != null) + { + var links = new StringBuilder(); + if (results.ExtraInfo.SteamWorkshopUrl != null) + links.Append($"[Steam Workshop]({results.ExtraInfo.SteamWorkshopUrl}) **|**"); + if (results.ExtraInfo.Tf2MapsUrl != null) + links.Append($"[TF2Maps]({results.ExtraInfo.Tf2MapsUrl}) **|**"); + if (results.ExtraInfo.GameBananaUrl != null) + links.Append($"[GameBanana]({results.ExtraInfo.GameBananaUrl}"); + output.AddField("Links", links.ToString(), true); + } + + if (results.GameModes.Count > 0) + { + var desc = TeamFortressService.GetGameModeInfoAsync(results.GameModes.FirstOrDefault()).Result; + output.WithDescription(desc.Title + " - " + desc.Description); + output.WithColor(new DiscordColor($"#{desc.Color}")); + } + + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); + } + + #endregion COMMAND_MAP + + #region COMMAND_NEWS + + [Command("news")] + [Description("Retrieve the latest news article from teamwork.tf")] + public async Task Tf2News(CommandContext ctx, + [Description("Page number from which to retrieve the news")] + int query = 0) + { + var results = await TeamFortressService.GetNewsArticlesAsync(query).ConfigureAwait(false); + if (results is null || results.Count == 0) + { + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_COMMON, EmbedType.Missing) + .ConfigureAwait(false); + return; + } + + while (results.Count > 0) + { + var output = new DiscordEmbedBuilder() + .WithFooter("Type 'next' within 10 seconds for the next five posts.") + .WithColor(new DiscordColor("#E7B53B")); + + foreach (var result in results.Take(5)) + { + output.AddField(result.CreatedAt.Date.ToString(), + $"{result.Provider ?? result.Type}: [{result.Title}]({result.Link.AbsoluteUri})"); + results.Remove(result); + } + + var message = await ctx.RespondAsync("Latest news articles from teamwork.tf", embed: output) + .ConfigureAwait(false); + + if (results.Count == 5) continue; + var interactivity = await BotServices.GetUserInteractivity(ctx, "next", 10).ConfigureAwait(false); + if (interactivity.Result is null) break; + await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); + await BotServices.RemoveMessage(message).ConfigureAwait(false); + } + } + + #endregion COMMAND_NEWS + + #region COMMAND_CREATORS + + [Command("creator"), Aliases("creators", "youtuber")] + [Description("Retrieve a community creator profile from teamwork.tf")] + public async Task Tf2Creators(CommandContext ctx, + [Description("Name of the community creator to find"), RemainingText] + string query) + { + if (string.IsNullOrWhiteSpace(query)) return; + var steamId = SteamService.GetSteamUserId(query).Result; + if (steamId is null) + { + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_COMMON, EmbedType.Missing) + .ConfigureAwait(false); + return; + } + + var results = await TeamFortressService.GetContentCreatorAsync(steamId.Data).ConfigureAwait(false); + if (results.Count == 0) + { + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_COMMON, EmbedType.Missing) + .ConfigureAwait(false); + return; + } + + foreach (var creator in results) + { + var user = results.FirstOrDefault(); + var output = new DiscordEmbedBuilder() + .WithTitle(user?.Name) + .WithDescription("Main Class: " + user?.Main?.ToString().ToUpper()) + .WithThumbnail(user?.ThumbnailUrl) + .WithUrl(user?.Link) + .WithColor(new DiscordColor("#E7B53B")) + .WithFooter(!creator.Equals(results.Last()) + ? "Type 'next' within 10 seconds for the next creator" + : "Data retrieved from teamwork.tf"); + + var links = new StringBuilder(); + if (creator.DiscordGroup != null) + links.Append($"[Discord]({Resources.URL_Discord}{creator.DiscordGroup}) **|** "); + if (!string.IsNullOrWhiteSpace(creator.Steam)) + links.Append($"[Steam]({Resources.URL_Steam_User}{creator.Steam}) **|** "); + if (creator.SteamGroup != null) + links.Append($"[Steam Group]({Resources.URL_Steam_Group}{creator.SteamGroup}) **|** "); + if (creator.Twitch != null) + links.Append($"[Twitch]({Resources.URL_Twitch}{creator.Twitch}) **|** "); + if (!string.IsNullOrWhiteSpace(creator.Twitter)) + links.Append($"[Twitter]({Resources.URL_Twitter}{creator.Twitter}) **|** "); + if (!string.IsNullOrWhiteSpace(creator.Youtube)) + links.Append($"[YouTube]({string.Format(Resources.URL_YouTube_Channel, creator.Youtube)})"); + output.AddField("Links", links.ToString(), true); + var message = await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); + + if (results.Count == 1) continue; + var interactivity = await BotServices.GetUserInteractivity(ctx, "next", 10).ConfigureAwait(false); + if (interactivity.Result is null) break; + await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); + if (!creator.Equals(results.Last())) + await BotServices.RemoveMessage(message).ConfigureAwait(false); + } + } + + #endregion COMMAND_CREATORS + + #region COMMAND_SERVERS + + [Command("server"), Aliases("servers")] + [Description("Retrieve a list of servers with given game-mode")] + public async Task Tf2ServerByMode(CommandContext ctx, + [Description("Name of the game-mode, like payload"), RemainingText] + string query) + { + if (string.IsNullOrWhiteSpace(query)) return; + var results = await TeamFortressService.GetServersByGameModeAsync(query.Trim().Replace(' ', '-')) + .ConfigureAwait(false); + if (results is null) + { + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_COMMON, EmbedType.Missing) + .ConfigureAwait(false); + return; + } + + foreach (var server in results.OrderBy(_ => new Random().Next()).ToList()) + { + var output = new DiscordEmbedBuilder() + .WithTitle(server.Name) + .WithDescription("steam://connect/" + server.Ip + ":" + server.Port) + .AddField("Provider", server.Provider != null ? server.Provider.Name : "Unknown", true) + .AddField("Player Count", + (server.Players.ToString() ?? "Unknown") + "/" + (server.MaxPlayers.ToString() ?? "Unknown"), + true) + .AddField("Password Lock", server.HasPassword ? "Yes" : "No", true) + .AddField("Random Crits", server.HasRandomCrits == true ? "Yes" : "No", true) + .AddField("Instant Respawn", server.HasNoRespawnTime ? "Yes" : "No", true) + .AddField("All Talk", server.HasAllTalk ? "Yes" : "No", true) + .AddField("Current Map", server.MapName ?? "Unknown", true) + .AddField("Next Map", server.MapNameNext ?? "Unknown", true) + .WithFooter("Type 'next' within 10 seconds for the next server") + .WithColor(new DiscordColor("#E7B53B")); + + var thumbnailUrl = await TeamFortressService.GetMapThumbnailAsync(server.MapName).ConfigureAwait(false); + output.WithImageUrl(thumbnailUrl.Name); + + var message = await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); + + if (results.Count == 1) continue; + var interactivity = await BotServices.GetUserInteractivity(ctx, "next", 10).ConfigureAwait(false); + if (interactivity.Result is null) break; + if (!server.Equals(results.Last())) + await BotServices.RemoveMessage(message).ConfigureAwait(false); + await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); + } + } + + [Command("find"), Aliases("ip", "banner")] + [Description("Retrieve a game server with given ip address")] + public async Task Tf2ServerByIp(CommandContext ctx, + [Description("Game server IP address, like 164.132.233.16"), RemainingText] + string ip) + { + if (string.IsNullOrWhiteSpace(ip) || !IPAddress.TryParse(ip, out var address)) + { + await BotServices.SendEmbedAsync(ctx, Resources.ERR_INVALID_IP, EmbedType.Missing) + .ConfigureAwait(false); + return; + } + + await ctx.RespondAsync(string.Format(Resources.URL_Steam_Connect, address)).ConfigureAwait(false); + await ctx.RespondAsync(TeamFortressService.GetServerInfo(address.ToString())).ConfigureAwait(false); + } + + [Command("list"), Aliases("serverList", "server-list", "custom")] + [Description("Retrieve a curated list of servers")] + public async Task Tf2ServerList(CommandContext ctx) + { + var results = await TeamFortressService.GetCustomServerListsAsync().ConfigureAwait(false); + results = results.OrderBy(_ => new Random().Next()).ToList(); + while (results.Count > 0) + { + var output = new DiscordEmbedBuilder() + .WithFooter("Type 'next' within 10 seconds for the next set of server lists") + .WithColor(new DiscordColor("#E7B53B")); + + foreach (var list in results.Take(4)) + { + var desc = Regex.Replace( + list.DescriptionLarge.Length <= 400 + ? list.DescriptionLarge + : list.DescriptionLarge.Substring(0, 200) + "...", "<[^>]*>", ""); + output.AddField($"Created By: {list.Creator.Name ?? "Unknown"} \t Subscribers: {list.Subscribed}", + $"[{list.Name}]({Resources.URL_TeamworkTF + list.Id}) - {desc}"); + + results.Remove(list); + } + + var message = await ctx + .RespondAsync("Community-Curated Server Lists from teamwork.tf", embed: output.Build()) + .ConfigureAwait(false); + + if (results.Count == 4) continue; + var interactivity = await BotServices.GetUserInteractivity(ctx, "next", 10).ConfigureAwait(false); + if (interactivity.Result is null) break; + await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); + await BotServices.RemoveMessage(message).ConfigureAwait(false); + } + } + + #endregion COMMAND_SERVERS + } +} \ No newline at end of file diff --git a/src/FlawBOT.Core/Modules/Misc/MathModule.cs b/src/FlawBOT/Modules/Misc/MathModule.cs similarity index 66% rename from src/FlawBOT.Core/Modules/Misc/MathModule.cs rename to src/FlawBOT/Modules/Misc/MathModule.cs index fed1b726..b81ef0bf 100644 --- a/src/FlawBOT.Core/Modules/Misc/MathModule.cs +++ b/src/FlawBOT/Modules/Misc/MathModule.cs @@ -3,9 +3,9 @@ using DSharpPlus.CommandsNext; using DSharpPlus.CommandsNext.Attributes; using DSharpPlus.Entities; -using FlawBOT.Core.Properties; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Services; +using FlawBOT.Common; +using FlawBOT.Properties; +using FlawBOT.Services; namespace FlawBOT.Modules { @@ -14,8 +14,7 @@ public class MathModule : BaseCommandModule { #region COMMAND_MATH - [Command("math")] - [Aliases("calculate")] + [Command("math"), Aliases("calculate")] [Description("Perform a basic math operation")] public async Task Math(CommandContext ctx, [Description("First operand")] double num1, @@ -24,31 +23,14 @@ public async Task Math(CommandContext ctx, { try { - double result; - switch (operation) + var result = operation switch { - default: //case "+": - result = num1 + num2; - break; - - case "-": - result = num1 - num2; - break; - - case "*": - case "x": - result = num1 * num2; - break; - - case "/": - result = num1 / num2; - break; - - case "%": - result = num1 % num2; - break; - } - + "-" => num1 - num2, + "*" or "x" => num1 * num2, + "/" => num1 / num2, + "%" => num1 % num2, + _ => num1 + num2 + }; var output = new DiscordEmbedBuilder() .WithDescription($":1234: The result is {result:#,##0.00}") .WithColor(DiscordColor.CornflowerBlue); @@ -65,8 +47,7 @@ await BotServices.SendEmbedAsync(ctx, Resources.ERR_MATH_EQUATION, EmbedType.War #region COMMAND_SUM - [Command("sum")] - [Aliases("total")] + [Command("sum"), Aliases("total")] [Description("Calculate the sum of all inputted values")] public async Task Sum(CommandContext ctx, [Description("Numbers to sum up")] params int[] args) diff --git a/src/FlawBOT/Modules/Misc/MiscModule.cs b/src/FlawBOT/Modules/Misc/MiscModule.cs new file mode 100644 index 00000000..e1870a1c --- /dev/null +++ b/src/FlawBOT/Modules/Misc/MiscModule.cs @@ -0,0 +1,124 @@ +using System; +using System.Threading.Tasks; +using DSharpPlus; +using DSharpPlus.CommandsNext; +using DSharpPlus.CommandsNext.Attributes; +using DSharpPlus.Entities; +using FlawBOT.Common; +using FlawBOT.Properties; +using FlawBOT.Services; +using Newtonsoft.Json.Linq; + +namespace FlawBOT.Modules +{ + [Cooldown(3, 5, CooldownBucketType.Channel)] + public class MiscModule : BaseCommandModule + { + #region COMMAND_8BALL + + [Command("ask"), Aliases("8b", "8ball", "ball", "8")] + [Description("Ask an 8-ball a question")] + public Task EightBall(CommandContext ctx, + [Description("Question to ask the 8-Ball"), RemainingText] + string question = "") + { + if (string.IsNullOrWhiteSpace(question)) return Task.CompletedTask; + var output = new DiscordEmbedBuilder() + .WithDescription(":8ball: " + MiscService.GetRandomAnswer() + " (" + ctx.User.Mention + ")") + .WithColor(DiscordColor.Black); + return ctx.RespondAsync(embed: output.Build()); + } + + #endregion COMMAND_8BALL + + #region COMMAND_CAT + + [Command("cat"), Aliases("meow", "catfact", "randomcat")] + [Description("Retrieve a random cat fact")] + public async Task GetCat(CommandContext ctx) + { + var results = MiscService.GetCatFactAsync().Result; + var output = new DiscordEmbedBuilder() + .WithFooter($"Fact: {JObject.Parse(results)["fact"]}") + .WithColor(DiscordColor.Orange); + + var image = MiscService.GetCatPhotoAsync().Result; + if (!string.IsNullOrWhiteSpace(image)) + output.WithImageUrl(image); + + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); + } + + #endregion COMMAND_CAT + + #region COMMAND_COINFLIP + + [Command("coinflip"), Aliases("coin", "flip")] + [Description("Flip a coin")] + public Task CoinFlip(CommandContext ctx) + { + var random = new Random(); + var output = new DiscordEmbedBuilder() + .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()); + } + + #endregion COMMAND_COINFLIP + + #region COMMAND_DICEROLL + + [Command("diceroll"), 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.Username + " rolled a die and got " + + Formatter.Bold(random.Next(1, 7).ToString())) + .WithColor(SharedData.DefaultColor); + return ctx.RespondAsync(embed: output.Build()); + } + + #endregion COMMAND_DICEROLL + + #region COMMAND_DOG + + [Command("randomdog"), Aliases("woof", "dog", "bark")] + [Description("Retrieve a random dog photo")] + public async Task GetDog(CommandContext ctx) + { + var results = MiscService.GetDogPhotoAsync().Result; + if (results.Status != "success") + { + await BotServices.SendEmbedAsync(ctx, Resources.ERR_API_CONNECTION, EmbedType.Warning).ConfigureAwait(false); + return; + } + + var output = new DiscordEmbedBuilder() + .WithImageUrl(results.Message) + .WithColor(DiscordColor.Brown); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); + } + + #endregion COMMAND_DOG + + #region COMMAND_HELLO + + [Command("hello"), Aliases("hi", "howdy")] + [Description("Welcome another user to the server")] + 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).ConfigureAwait(false); + else + await ctx.RespondAsync(":wave: Welcome " + member.Mention + " to " + ctx.Guild.Name + + ". Enjoy your stay!").ConfigureAwait(false); + } + + #endregion COMMAND_HELLO + } +} \ No newline at end of file diff --git a/src/FlawBOT/Modules/Misc/PollModule.cs b/src/FlawBOT/Modules/Misc/PollModule.cs new file mode 100644 index 00000000..a6f6455e --- /dev/null +++ b/src/FlawBOT/Modules/Misc/PollModule.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using DSharpPlus.CommandsNext; +using DSharpPlus.CommandsNext.Attributes; +using DSharpPlus.Entities; +using DSharpPlus.Interactivity.Enums; +using DSharpPlus.Interactivity.Extensions; +using FlawBOT.Common; +using FlawBOT.Properties; +using FlawBOT.Services; + +namespace FlawBOT.Modules +{ + [Cooldown(3, 5, CooldownBucketType.Channel)] + public class PollModule : BaseCommandModule + { + #region COMMAND_POLL + + [Command("poll"), Aliases("vote")] + [Description("Run a Yay or Nay poll in the current channel")] + public async Task Poll(CommandContext ctx, + [Description("Question to be polled"), RemainingText] + string question) + { + if (string.IsNullOrWhiteSpace(question)) + { + await BotServices.SendEmbedAsync(ctx, Resources.ERR_POLL_QUESTION, EmbedType.Warning) + .ConfigureAwait(false); + return; + } + + // Build the poll question, duration and options. + question = ctx.User.Mention + " asked: " + question; + var interactivity = ctx.Client.GetInteractivity(); + var pollOptions = new List + { + DiscordEmoji.FromName(ctx.Client, ":thumbsup:"), + DiscordEmoji.FromName(ctx.Client, ":thumbsdown:") + }; + var duration = new TimeSpan(0, 3, 10); + var message = await ctx + .RespondAsync(embed: new DiscordEmbedBuilder() + .WithDescription(question + $"\nThis poll ends in {duration.Minutes} minutes.").Build()) + .ConfigureAwait(false); + var results = await interactivity.DoPollAsync(message, pollOptions, PollBehaviour.DeleteEmojis, duration) + .ConfigureAwait(false); + + // Removed the initial poll and return the calculated results + await BotServices.RemoveMessage(message).ConfigureAwait(false); + var output = new DiscordEmbedBuilder() + .WithDescription(question) + .WithFooter("The voting has ended."); + foreach (var vote in results) + output.AddField(vote.Emoji.Name, vote.Voted.Count.ToString(), true); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); + } + + #endregion COMMAND_POLL + } +} \ No newline at end of file diff --git a/src/FlawBOT/Modules/Search/AmiiboModule.cs b/src/FlawBOT/Modules/Search/AmiiboModule.cs new file mode 100644 index 00000000..5ce33048 --- /dev/null +++ b/src/FlawBOT/Modules/Search/AmiiboModule.cs @@ -0,0 +1,60 @@ +using System.Linq; +using System.Threading.Tasks; +using DSharpPlus.CommandsNext; +using DSharpPlus.CommandsNext.Attributes; +using DSharpPlus.Entities; +using FlawBOT.Common; +using FlawBOT.Properties; +using FlawBOT.Services; + +namespace FlawBOT.Modules +{ + [Cooldown(3, 5, CooldownBucketType.Channel)] + public class AmiiboModule : BaseCommandModule + { + #region COMMAND_AMIIBO + + [Command("amiibo"), Aliases("amib")] + [Description("Retrieve Amiibo figurine information")] + public async Task GetAmiibo(CommandContext ctx, + [Description("Name of the Amiibo figurine"), RemainingText] + string query) + { + if (string.IsNullOrWhiteSpace(query)) return; + var results = await AmiiboService.GetAmiiboDataAsync(query).ConfigureAwait(false); + if (results is null) + { + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_COMMON, EmbedType.Missing) + .ConfigureAwait(false); + return; + } + + foreach (var amiibo in results.Amiibo) + { + var output = new DiscordEmbedBuilder() + .WithTitle(amiibo.Name) + .AddField("Amiibo Series", amiibo.AmiiboSeries, true) + .AddField("Game Series", amiibo.GameSeries, true) + .AddField(":flag_us: Release:", amiibo.ReleaseDate.American, true) + .AddField(":flag_jp: Release:", amiibo.ReleaseDate.Japanese, true) + .AddField(":flag_eu: Release:", amiibo.ReleaseDate.European, true) + .AddField(":flag_au: Release:", amiibo.ReleaseDate.Australian, true) + .WithImageUrl(amiibo.Image) + .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()).ConfigureAwait(false); + + if (results.Amiibo.Count == 1) continue; + var interactivity = await BotServices.GetUserInteractivity(ctx, "next", 10).ConfigureAwait(false); + if (interactivity.Result is null) break; + await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); + if (!amiibo.Equals(results.Amiibo.Last())) + await BotServices.RemoveMessage(message).ConfigureAwait(false); + } + } + + #endregion COMMAND_AMIIBO + } +} \ No newline at end of file diff --git a/src/FlawBOT/Modules/Search/DictionaryModule.cs b/src/FlawBOT/Modules/Search/DictionaryModule.cs new file mode 100644 index 00000000..4a1fd080 --- /dev/null +++ b/src/FlawBOT/Modules/Search/DictionaryModule.cs @@ -0,0 +1,64 @@ +using System.Linq; +using System.Threading.Tasks; +using DSharpPlus; +using DSharpPlus.CommandsNext; +using DSharpPlus.CommandsNext.Attributes; +using DSharpPlus.Entities; +using FlawBOT.Common; +using FlawBOT.Properties; +using FlawBOT.Services; + +namespace FlawBOT.Modules +{ + [Cooldown(3, 5, CooldownBucketType.Channel)] + public class DictionaryModule : BaseCommandModule + { + #region COMMAND_DICTIONARY + + [Command("dictionary"), Aliases("define", "def", "dic")] + [Description("Retrieve an Urban Dictionary definition of a word or phrase")] + public async Task UrbanDictionary(CommandContext ctx, + [Description("Query to pass to Urban Dictionary"), RemainingText] + string query) + { + if (string.IsNullOrWhiteSpace(query)) return; + var results = await DictionaryService.GetDictionaryDefinitionAsync(query).ConfigureAwait(false); + if (results.ResultType == "no_results" || results.List.Count == 0) + { + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_COMMON, EmbedType.Missing) + .ConfigureAwait(false); + return; + } + + foreach (var definition in results.List) + { + var output = new DiscordEmbedBuilder() + .WithTitle("Urban Dictionary definition for " + Formatter.Bold(query)) + .WithDescription(!string.IsNullOrWhiteSpace(definition.Author) + ? "Submitted by: " + definition.Author + : string.Empty) + .AddField("Definition", 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(!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()).ConfigureAwait(false); + + if (results.List.Count == 1) continue; + var interactivity = await BotServices.GetUserInteractivity(ctx, "next", 10).ConfigureAwait(false); + if (interactivity.Result is null) break; + await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); + if (!definition.Equals(results.List.Last())) + await BotServices.RemoveMessage(message).ConfigureAwait(false); + } + } + + #endregion COMMAND_DICTIONARY + } +} \ No newline at end of file diff --git a/src/FlawBOT.Core/Modules/Search/ImgurModule.cs b/src/FlawBOT/Modules/Search/ImgurModule.cs similarity index 55% rename from src/FlawBOT.Core/Modules/Search/ImgurModule.cs rename to src/FlawBOT/Modules/Search/ImgurModule.cs index 37d070ba..bd457f0b 100644 --- a/src/FlawBOT.Core/Modules/Search/ImgurModule.cs +++ b/src/FlawBOT/Modules/Search/ImgurModule.cs @@ -1,10 +1,11 @@ using System.Threading.Tasks; +using DSharpPlus; using DSharpPlus.CommandsNext; using DSharpPlus.CommandsNext.Attributes; using DSharpPlus.Entities; -using FlawBOT.Core.Properties; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Services; +using FlawBOT.Common; +using FlawBOT.Properties; +using FlawBOT.Services; using Imgur.API.Models.Impl; namespace FlawBOT.Modules @@ -14,29 +15,31 @@ public class ImgurModule : BaseCommandModule { #region COMMAND_IMGUR - [Command("imgur")] - [Aliases("image")] + [Command("imgur"), Aliases("image")] [Description("Retrieve an image from Imgur")] public async Task Imgur(CommandContext ctx, - [Description("Search query to pass to Imgur")] [RemainingText] string query) + [Description("Search query to pass to Imgur"), RemainingText] + string query) { var results = ImgurService.GetImgurGalleryAsync(query).Result; + var output = new DiscordEmbedBuilder().WithColor(new DiscordColor("#89C623")); + switch (results) { case GalleryImage image: - var output = new DiscordEmbedBuilder() - .WithImageUrl(image.Link) - .WithFooter(image.Title ?? string.Empty) - .WithColor(new DiscordColor("#85BF25")); + output.WithDescription(image.Title ?? "Search results for " + Formatter.Bold(query) + " on Imgur"); + output.WithImageUrl(image.Link); await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); break; case GalleryAlbum album: - await ctx.RespondAsync(album.Link).ConfigureAwait(false); + output.WithDescription(album.Title ?? "Search results for " + Formatter.Bold(query) + " on Imgur"); + output.WithImageUrl(album.Link); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); break; default: - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing) + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_COMMON, EmbedType.Missing) .ConfigureAwait(false); break; } diff --git a/src/FlawBOT.Core/Modules/Search/NASAModule.cs b/src/FlawBOT/Modules/Search/NASAModule.cs similarity index 52% rename from src/FlawBOT.Core/Modules/Search/NASAModule.cs rename to src/FlawBOT/Modules/Search/NASAModule.cs index a6f99e89..dc9ec769 100644 --- a/src/FlawBOT.Core/Modules/Search/NASAModule.cs +++ b/src/FlawBOT/Modules/Search/NASAModule.cs @@ -2,9 +2,9 @@ using DSharpPlus.CommandsNext; using DSharpPlus.CommandsNext.Attributes; using DSharpPlus.Entities; -using FlawBOT.Core.Properties; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Services; +using FlawBOT.Common; +using FlawBOT.Properties; +using FlawBOT.Services; namespace FlawBOT.Modules { @@ -13,25 +13,23 @@ public class NasaModule : BaseCommandModule { #region COMMAND_NASA - [Command("nasa")] - [Aliases("apod", "space")] + [Command("nasa"), Aliases("apod", "space")] [Description("Retrieve NASA's Astronomy Picture of the Day")] public async Task Nasa(CommandContext ctx) { var results = await NasaService.GetNasaImageAsync().ConfigureAwait(false); if (results is null) { - await BotServices.SendEmbedAsync(ctx, Resources.ERR_NASA_API, EmbedType.Missing).ConfigureAwait(false); - } - else - { - var output = new DiscordEmbedBuilder() - .WithDescription(results.Title) - .WithImageUrl(results.ImageHd ?? results.ImageSd) - .WithFooter(results.Description) - .WithColor(new DiscordColor("#0B3D91")); - await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, Resources.ERR_API_CONNECTION, EmbedType.Missing).ConfigureAwait(false); + return; } + + var output = new DiscordEmbedBuilder() + .WithDescription(results.Title) + .WithImageUrl(results.ImageHd ?? results.ImageSd) + .WithFooter(results.Description) + .WithColor(new DiscordColor("#0B3D91")); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } #endregion COMMAND_NASA diff --git a/src/FlawBOT/Modules/Search/NewsModule.cs b/src/FlawBOT/Modules/Search/NewsModule.cs new file mode 100644 index 00000000..8ed8716f --- /dev/null +++ b/src/FlawBOT/Modules/Search/NewsModule.cs @@ -0,0 +1,59 @@ +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using DSharpPlus.CommandsNext; +using DSharpPlus.CommandsNext.Attributes; +using DSharpPlus.Entities; +using FlawBOT.Common; +using FlawBOT.Properties; +using FlawBOT.Services; + +namespace FlawBOT.Modules +{ + [Cooldown(3, 5, CooldownBucketType.Channel)] + public class NewsModule : BaseCommandModule + { + #region COMMAND_NEWS + + [Command("news")] + [Description("Retrieve the latest news articles from NewsAPI.org")] + public async Task News(CommandContext ctx, + [Description("Article topic to find on Google News"), RemainingText] + string query) + { + var results = await NewsService.GetNewsDataAsync(query).ConfigureAwait(false); + if (results.Status != "ok") + { + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_COMMON, EmbedType.Missing) + .ConfigureAwait(false); + return; + } + + while (results.Articles.Count > 0) + { + var output = new DiscordEmbedBuilder() + .WithFooter("Type 'next' within 10 seconds for the next five articles.") + .WithColor(new DiscordColor("#253B80")); + + foreach (var result in results.Articles.Take(5)) + { + output.AddField(result.PublishDate.ToString(CultureInfo.InvariantCulture), + $"[{result.Title}]({result.Url})"); + results.Articles.Remove(result); + } + + var message = await ctx + .RespondAsync("Latest Google News articles from News API", embed: output.Build()) + .ConfigureAwait(false); + + if (results.Articles.Count == 5) continue; + var interactivity = await BotServices.GetUserInteractivity(ctx, "next", 10).ConfigureAwait(false); + if (interactivity.Result is null) break; + await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); + await BotServices.RemoveMessage(message).ConfigureAwait(false); + } + } + + #endregion COMMAND_NEWS + } +} \ No newline at end of file diff --git a/src/FlawBOT/Modules/Search/OMDBModule.cs b/src/FlawBOT/Modules/Search/OMDBModule.cs new file mode 100644 index 00000000..babb6f8b --- /dev/null +++ b/src/FlawBOT/Modules/Search/OMDBModule.cs @@ -0,0 +1,64 @@ +using System.Linq; +using System.Threading.Tasks; +using DSharpPlus.CommandsNext; +using DSharpPlus.CommandsNext.Attributes; +using DSharpPlus.Entities; +using FlawBOT.Common; +using FlawBOT.Properties; +using FlawBOT.Services; + +namespace FlawBOT.Modules +{ + [Cooldown(3, 5, CooldownBucketType.Channel)] + public class OmdbModule : BaseCommandModule + { + #region COMMAND_OMDB + + [Command("omdb"), Aliases("imdb", "movie")] + [Description("Retrieve a movie or TV show from OMDB")] + public async Task Omdb(CommandContext ctx, + [Description("Movie or TV show to find on OMDB"), RemainingText] + string query) + { + if (string.IsNullOrWhiteSpace(query)) return; + var results = OmdbService.GetMovieListAsync(query.Replace(" ", "+")).Result; + if (!results.Search.Any()) + { + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_COMMON, EmbedType.Missing) + .ConfigureAwait(false); + return; + } + + foreach (var title in results.Search) + { + var movie = OmdbService.GetMovieDataAsync(title.Title.Replace(" ", "+")).Result; + var output = new DiscordEmbedBuilder() + .WithTitle(movie.Title) + .WithDescription(movie.Plot.Length < 500 ? movie.Plot : movie.Plot.Take(500) + "...") + .AddField("Released", movie.Released, true) + .AddField("Runtime", movie.Runtime, true) + .AddField("Genre", movie.Genre, true) + .AddField("Rating", movie.Rated, true) + .AddField("IMDb Rating", movie.IMDbRating, true) + .AddField("Box Office", movie.BoxOffice, true) + .AddField("Directors", movie.Director) + .AddField("Actors", movie.Actors) + .WithFooter(!movie.Title.Equals(results.Search.Last().Title) + ? "Type 'next' within 10 seconds for the next movie" + : "This is the last found movie on OMDB.") + .WithColor(DiscordColor.Goldenrod); + if (movie.Poster != "N/A") output.WithImageUrl(movie.Poster); + var message = await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); + + if (results.Search.Length == 1) continue; + var interactivity = await BotServices.GetUserInteractivity(ctx, "next", 10).ConfigureAwait(false); + if (interactivity.Result is null) break; + await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); + if (!movie.Title.Equals(results.Search.Last().Title)) + await BotServices.RemoveMessage(message).ConfigureAwait(false); + } + } + + #endregion COMMAND_OMDB + } +} \ No newline at end of file diff --git a/src/FlawBOT.Core/Modules/Search/RedditModule.cs b/src/FlawBOT/Modules/Search/RedditModule.cs similarity index 76% rename from src/FlawBOT.Core/Modules/Search/RedditModule.cs rename to src/FlawBOT/Modules/Search/RedditModule.cs index 9b484565..5055db60 100644 --- a/src/FlawBOT.Core/Modules/Search/RedditModule.cs +++ b/src/FlawBOT/Modules/Search/RedditModule.cs @@ -3,9 +3,9 @@ using DSharpPlus.CommandsNext; using DSharpPlus.CommandsNext.Attributes; using DSharpPlus.Entities; -using FlawBOT.Core.Properties; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Services; +using FlawBOT.Common; +using FlawBOT.Properties; +using FlawBOT.Services; namespace FlawBOT.Modules { @@ -18,7 +18,7 @@ public class RedditModule : BaseCommandModule [Command("hot")] [Description("Get hottest posts for a subreddit.")] - public Task HowPost(CommandContext ctx, [Description("Subreddit.")] string query) + public Task HotPost(CommandContext ctx, [Description("Subreddit.")] string query) { return RedditPost(ctx, query, RedditCategory.Hot); } @@ -38,15 +38,18 @@ public Task TopPost(CommandContext ctx, [Description("Subreddit.")] string query return RedditPost(ctx, query, RedditCategory.Top); } - private async Task RedditPost(CommandContext ctx, string query, RedditCategory category) + private static async Task RedditPost(CommandContext ctx, string query, RedditCategory category) { - if (!BotServices.CheckUserInput(query)) return; + if (string.IsNullOrWhiteSpace(query)) return; var results = RedditService.GetResults(query, category); if (results is null || results.Count == 0) - await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Missing) + { + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_COMMON, EmbedType.Missing) .ConfigureAwait(false); + return; + } - while (results != null && results.Count > 0) + while (results.Count > 0) { var output = new DiscordEmbedBuilder() .WithFooter("Type 'next' within 10 seconds for the next five posts.") @@ -54,11 +57,12 @@ await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_GENERIC, EmbedType.Mis foreach (var result in results.Take(5)) { - output.AddField(result.Authors[0].Name, $"[{(result.Title.Text.Length < 500 ? result.Title.Text : result.Title.Text.Take(500) + "...")}]({result.Links.First().Uri})"); + output.AddField(result.Authors.FirstOrDefault()?.Name, + $"[{(result.Title.Text.Length < 500 ? result.Title.Text : result.Title.Text.Take(500) + "...")}]({result.Links.First().Uri})"); results.Remove(result); } - var message = await ctx.RespondAsync("Search results for r/" + query, embed: output) + var message = await ctx.RespondAsync("Search results for r/" + query + " on Reddit", embed: output) .ConfigureAwait(false); if (results.Count == 5) continue; diff --git a/src/FlawBOT.Core/Modules/Search/SimpsonsModule.cs b/src/FlawBOT/Modules/Search/SimpsonsModule.cs similarity index 61% rename from src/FlawBOT.Core/Modules/Search/SimpsonsModule.cs rename to src/FlawBOT/Modules/Search/SimpsonsModule.cs index a2f85ec5..3a0cdfa8 100644 --- a/src/FlawBOT.Core/Modules/Search/SimpsonsModule.cs +++ b/src/FlawBOT/Modules/Search/SimpsonsModule.cs @@ -2,8 +2,8 @@ using DSharpPlus.CommandsNext; using DSharpPlus.CommandsNext.Attributes; using DSharpPlus.Entities; -using FlawBOT.Core.Properties; -using FlawBOT.Framework.Services; +using FlawBOT.Properties; +using FlawBOT.Services; namespace FlawBOT.Modules { @@ -12,8 +12,7 @@ public class SimpsonsModule : BaseCommandModule { #region COMMAND_SIMPSONS - [Command("simpsons")] - [Aliases("caramba")] + [Command("simpsons"), Aliases("caramba")] [Description("Retrieve a random Simpsons screenshot and episode")] public async Task Simpsons(CommandContext ctx) { @@ -26,33 +25,32 @@ public async Task Simpsons(CommandContext ctx) #region COMMAND_SIMPSONS_GIF - [Command("simpsonsgif")] - [Aliases("doh")] + [Command("simpsonsgif"), Aliases("doh")] [Description("Retrieve a random Simpsons gif")] public async Task SimpsonsGif(CommandContext ctx, - [Description("Inputting anything will add episode information")] [RemainingText] string input) + [Description("Inputting anything will add episode information"), RemainingText] + string input) { var output = await SimpsonsService.GetSimpsonsGifAsync(SimpsonsService.SiteRoot.Frinkiac) .ConfigureAwait(false); if (string.IsNullOrWhiteSpace(input)) { await ctx.RespondAsync(output).ConfigureAwait(false); + return; } - else // Include episode information if any kind of parameter is inputted - { - var results = await SimpsonsService.GetSimpsonsDataAsync(SimpsonsService.SiteRoot.Frinkiac) - .ConfigureAwait(false); - results.WithFooter(Resources.SIMPSONS_GIF_WARNING); - await ctx.RespondAsync(output, embed: results.Build()).ConfigureAwait(false); - } + + // Include episode information if any kind of parameter is inputted + var results = await SimpsonsService.GetSimpsonsDataAsync(SimpsonsService.SiteRoot.Frinkiac) + .ConfigureAwait(false); + results.WithFooter(Resources.INFO_GIF_LOADING); + await ctx.RespondAsync(output, embed: results.Build()).ConfigureAwait(false); } #endregion COMMAND_SIMPSONS_GIF #region COMMAND_FUTURAMA - [Command("futurama")] - [Aliases("bite")] + [Command("futurama"), Aliases("bite")] [Description("Retrieve a random Futurama screenshot and episode")] public async Task Futurama(CommandContext ctx) { @@ -66,36 +64,35 @@ public async Task Futurama(CommandContext ctx) #region COMMAND_FUTURAMA_GIF - [Command("futuramagif")] - [Aliases("neat")] + [Command("futuramagif"), Aliases("neat")] [Description("Retrieve a random Futurama gif")] public async Task FuturamaGif(CommandContext ctx, - [Description("Inputting anything will add episode information")] [RemainingText] string input) + [Description("Inputting anything will add episode information"), RemainingText] + string input) { var output = await SimpsonsService.GetSimpsonsGifAsync(SimpsonsService.SiteRoot.Morbotron) .ConfigureAwait(false); if (string.IsNullOrWhiteSpace(input)) { await ctx.RespondAsync(output).ConfigureAwait(false); + return; } - else // Include episode information if any kind of parameter is inputted - { - 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()).ConfigureAwait(false); - } + + // Include episode information if any kind of parameter is inputted + var results = await SimpsonsService.GetSimpsonsDataAsync(SimpsonsService.SiteRoot.Morbotron) + .ConfigureAwait(false); + results.WithFooter(Resources.INFO_GIF_LOADING); + results.WithColor(new DiscordColor("#69E398")); + await ctx.RespondAsync(output, embed: results.Build()).ConfigureAwait(false); } #endregion COMMAND_FUTURAMA_GIF #region COMMAND_RICKMORTY - [Command("rick")] - [Aliases("morty")] + [Command("rick"), Aliases("morty")] [Description("Retrieve a random Rick and Morty screenshot and episode")] - public async Task RickMorty(CommandContext ctx) + public async Task Morty(CommandContext ctx) { var results = await SimpsonsService.GetSimpsonsDataAsync(SimpsonsService.SiteRoot.MasterOfAllScience) .ConfigureAwait(false); @@ -107,26 +104,26 @@ public async Task RickMorty(CommandContext ctx) #region COMMAND_RICKMORTY_GIF - [Command("rickgif")] - [Aliases("mortygif")] + [Command("rickgif"), Aliases("mortygif")] [Description("Retrieve a random Rick and Morty gif")] - public async Task RickMortyGif(CommandContext ctx, - [Description("Inputting anything will add episode information")] [RemainingText] string input) + public async Task MortyGif(CommandContext ctx, + [Description("Inputting anything will add episode information"), RemainingText] + string input) { var output = await SimpsonsService.GetSimpsonsGifAsync(SimpsonsService.SiteRoot.MasterOfAllScience) .ConfigureAwait(false); if (string.IsNullOrWhiteSpace(input)) { await ctx.RespondAsync(output).ConfigureAwait(false); + return; } - else // Include episode information if any kind of parameter is inputted - { - 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()).ConfigureAwait(false); - } + + // Include episode information if any kind of parameter is inputted + var results = await SimpsonsService.GetSimpsonsDataAsync(SimpsonsService.SiteRoot.MasterOfAllScience) + .ConfigureAwait(false); + results.WithFooter(Resources.INFO_GIF_LOADING); + results.WithColor(new DiscordColor("#ABD5EC")); + await ctx.RespondAsync(output, embed: results.Build()).ConfigureAwait(false); } #endregion COMMAND_RICKMORTY_GIF diff --git a/src/FlawBOT/Modules/Search/TwitchModule.cs b/src/FlawBOT/Modules/Search/TwitchModule.cs new file mode 100644 index 00000000..b2da67e4 --- /dev/null +++ b/src/FlawBOT/Modules/Search/TwitchModule.cs @@ -0,0 +1,61 @@ +using System.Linq; +using System.Threading.Tasks; +using DSharpPlus.CommandsNext; +using DSharpPlus.CommandsNext.Attributes; +using DSharpPlus.Entities; +using FlawBOT.Common; +using FlawBOT.Properties; +using FlawBOT.Services; + +namespace FlawBOT.Modules +{ + [Cooldown(3, 5, CooldownBucketType.Channel)] + public class TwitchModule : BaseCommandModule + { + #region COMMAND_TWITCH + + [Command("twitch"), Aliases("stream")] + [Description("Retrieve Twitch stream information")] + public async Task Twitch(CommandContext ctx, + [Description("Channel to find on Twitch"), RemainingText] + string query) + { + if (string.IsNullOrWhiteSpace(query)) return; + var results = await TwitchService.GetTwitchDataAsync(query).ConfigureAwait(false); + if (results.Total == 0) + { + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_TWITCH, EmbedType.Missing) + .ConfigureAwait(false); + return; + } + + foreach (var streamer in results.Streams) + { + var output = new DiscordEmbedBuilder() + .WithTitle(streamer.Channel.DisplayName) + .WithDescription("[LIVE] Now Playing: " + streamer.Channel.Game) + .AddField("Broadcaster", streamer.Channel.BroadcasterType.ToUpperInvariant(), true) + .AddField("Viewers", streamer.Viewers.ToString(), true) + .AddField("Followers", streamer.Channel.Followers.ToString(), true) + .AddField("Status", streamer.Channel.Status) + .WithThumbnail(streamer.Channel.Logo) + .WithImageUrl(streamer.Preview.Large) + .WithUrl(streamer.Channel.Url) + .WithFooter(!streamer.Id.Equals(results.Streams.Last().Id) + ? "Type 'next' within 10 seconds for the next streamer" + : "This is the last found streamer on the list.") + .WithColor(new DiscordColor("#6441A5")); + var message = await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); + + if (results.Total == 1) continue; + var interactivity = await BotServices.GetUserInteractivity(ctx, "next", 10).ConfigureAwait(false); + if (interactivity.Result is null) break; + await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); + if (!streamer.Id.Equals(results.Streams.Last().Id)) + await BotServices.RemoveMessage(message).ConfigureAwait(false); + } + } + + #endregion COMMAND_TWITCH + } +} \ No newline at end of file diff --git a/src/FlawBOT/Modules/Search/WikipediaModule.cs b/src/FlawBOT/Modules/Search/WikipediaModule.cs new file mode 100644 index 00000000..cbfef423 --- /dev/null +++ b/src/FlawBOT/Modules/Search/WikipediaModule.cs @@ -0,0 +1,53 @@ +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using DSharpPlus; +using DSharpPlus.CommandsNext; +using DSharpPlus.CommandsNext.Attributes; +using DSharpPlus.Entities; +using FlawBOT.Common; +using FlawBOT.Properties; +using FlawBOT.Services; + +namespace FlawBOT.Modules +{ + [Cooldown(3, 5, CooldownBucketType.Channel)] + public class WikipediaModule : BaseCommandModule + { + #region COMMAND_WIKIPEDIA + + [Command("wiki"), Aliases("wikipedia")] + [Description("Search Wikipedia for a given query")] + public async Task Wikipedia(CommandContext ctx, + [Description("Query to search on Wikipedia"), RemainingText] + string query) + { + if (string.IsNullOrWhiteSpace(query)) return; + var results = WikipediaService.GetWikipediaDataAsync(query); + if (results.Error != null || results.Search.Count == 0) + { + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_WIKIPEDIA, EmbedType.Missing) + .ConfigureAwait(false); + return; + } + + var output = new DiscordEmbedBuilder() + .WithFooter("Articles retrieved using WikipediaNET") + .WithColor(new DiscordColor("#E7B53B")); + + foreach (var result in results.Search) + { + var desc = Regex.Replace( + result.Snippet.Length <= 300 + ? result.Snippet + : result.Snippet.Substring(0, 150) + "...", "<[^>]*>", "") ?? "Article has not content."; + + output.AddField(result.Title, $"[[Link]({result.Url.AbsoluteUri})] {desc}"); + } + + await ctx.RespondAsync("Search results for " + Formatter.Bold(query) + " on Wikipedia", embed: output) + .ConfigureAwait(false); + } + + #endregion COMMAND_WIKIPEDIA + } +} \ No newline at end of file diff --git a/src/FlawBOT/Modules/Search/WorldModule.cs b/src/FlawBOT/Modules/Search/WorldModule.cs new file mode 100644 index 00000000..398dffe8 --- /dev/null +++ b/src/FlawBOT/Modules/Search/WorldModule.cs @@ -0,0 +1,80 @@ +using System; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using DSharpPlus.CommandsNext; +using DSharpPlus.CommandsNext.Attributes; +using DSharpPlus.Entities; +using FlawBOT.Common; +using FlawBOT.Properties; +using FlawBOT.Services; + +namespace FlawBOT.Modules +{ + [Cooldown(3, 5, CooldownBucketType.Channel)] + public class WorldModule : BaseCommandModule + { + #region COMMAND_IP + + [Command("ip"), Aliases("ipstack", "track")] + [Description("Retrieve IP address geolocation information")] + public async Task IpTrack(CommandContext ctx, + [Description("IP Address")] string address) + { + if (string.IsNullOrWhiteSpace(address) || !IPAddress.TryParse(address, out var ip)) + { + await BotServices.SendEmbedAsync(ctx, Resources.ERR_INVALID_IP, EmbedType.Missing) + .ConfigureAwait(false); + return; + } + + var results = WorldService.GetIpLocationAsync(ip).Result; + if (results.Type == null) + { + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_LOCATION, EmbedType.Warning) + .ConfigureAwait(false); + return; + } + + var output = new DiscordEmbedBuilder() + .WithTitle($"{results.City}, {results.Region}, {results.Country}") + .WithDescription($"Coordinates: {results.Latitude}°N, {results.Longitude}°W") + .WithUrl(string.Format(Resources.URL_Google_Maps, results.Latitude, results.Longitude)) + .WithColor(new DiscordColor("#4d2f63")); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); + } + + #endregion COMMAND_IP + + #region COMMAND_WEATHER + + [Command("weather"), Aliases("time", "clock")] + [Description("Retrieve the time and weather for specified location")] + public async Task Weather(CommandContext ctx, + [Description("Location from which to retrieve data"), RemainingText] + string query) + { + if (string.IsNullOrWhiteSpace(query)) return; + var results = await WorldService.GetWeatherDataAsync(query).ConfigureAwait(false); + if (results is null) + { + await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_LOCATION, EmbedType.Missing) + .ConfigureAwait(false); + return; + } + + Func format = WorldService.CelsiusToFahrenheit; + var output = new DiscordEmbedBuilder() + .WithDescription("Weather in " + results.Location.Name + ", " + results.Location.Country) + .AddField(":partly_sunny: Currently", results.Current.Descriptions.FirstOrDefault(), true) + .AddField(":thermometer: Temperature", + $"{results.Current.Temperature:F1}°C / {format(results.Current.Temperature):F1}°F", true) + .AddField(":droplet: Humidity", $"{results.Current.Humidity}%", true) + .AddField(":clock1: Local Time", results.Location.LocalTime, true) + .WithColor(SharedData.DefaultColor); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); + } + } + + #endregion COMMAND_WEATHER +} \ No newline at end of file diff --git a/src/FlawBOT/Modules/Search/YoutubeModule.cs b/src/FlawBOT/Modules/Search/YoutubeModule.cs new file mode 100644 index 00000000..0f3487e9 --- /dev/null +++ b/src/FlawBOT/Modules/Search/YoutubeModule.cs @@ -0,0 +1,78 @@ +using System.Threading.Tasks; +using DSharpPlus; +using DSharpPlus.CommandsNext; +using DSharpPlus.CommandsNext.Attributes; +using FlawBOT.Services; + +namespace FlawBOT.Modules +{ + [Group("youtube")] + [Aliases("yt")] + [Description("Commands for finding YouTube videos, channels and playlists")] + [Cooldown(3, 5, CooldownBucketType.Channel)] + public class YouTubeModule : BaseCommandModule + { + #region COMMAND_CHANNEL + + [Command("channel"), Aliases("channels", "chn")] + [Description("Retrieve a list of YouTube channel given a query")] + public async Task YtChannel(CommandContext ctx, + [Description("Channels to find on YouTube"), RemainingText] + string query) + { + if (string.IsNullOrWhiteSpace(query)) return; + var results = await new YoutubeService().GetEmbeddedResults(query, 5, "channel").ConfigureAwait(false); + await ctx.RespondAsync("Search results for " + Formatter.Bold(query) + " on YouTube", embed: results) + .ConfigureAwait(false); + } + + #endregion COMMAND_CHANNEL + + #region COMMAND_PLAYLIST + + [Command("playlist"), Aliases("playlists", "list")] + [Description("Retrieve a list of YouTube playlists given a query")] + public async Task YtPlaylist(CommandContext ctx, + [Description("Playlist to find on YouTube"), RemainingText] + string query) + { + if (string.IsNullOrWhiteSpace(query)) return; + var results = await new YoutubeService().GetEmbeddedResults(query, 5, "playlist").ConfigureAwait(false); + await ctx.RespondAsync("Search results for " + Formatter.Bold(query) + " on YouTube", embed: results) + .ConfigureAwait(false); + } + + #endregion COMMAND_PLAYLIST + + #region COMMAND_SEARCH + + [Command("search"), Aliases("find", "watch")] + [Description("Retrieve the first YouTube search result given a query")] + public async Task YtVideo(CommandContext ctx, + [Description("First result video to find on YouTube"), RemainingText] + string query) + { + if (string.IsNullOrWhiteSpace(query)) return; + var results = await new YoutubeService().GetFirstVideoResultAsync(query).ConfigureAwait(false); + await ctx.RespondAsync(results).ConfigureAwait(false); + } + + #endregion COMMAND_SEARCH + + #region COMMAND_VIDEO + + [Command("video"), Aliases("videos", "vid")] + [Description("Retrieve a list of YouTube videos given a query")] + public async Task YtSearch(CommandContext ctx, + [Description("Video to find on YouTube"), RemainingText] + string query) + { + if (string.IsNullOrWhiteSpace(query)) return; + var results = await new YoutubeService().GetEmbeddedResults(query, 5, "video").ConfigureAwait(false); + await ctx.RespondAsync("Search results for " + Formatter.Bold(query) + " on YouTube", embed: results) + .ConfigureAwait(false); + } + + #endregion COMMAND_VIDEO + } +} \ No newline at end of file diff --git a/src/FlawBOT.Core/Modules/Server/ChannelModule.cs b/src/FlawBOT/Modules/Server/ChannelModule.cs similarity index 56% rename from src/FlawBOT.Core/Modules/Server/ChannelModule.cs rename to src/FlawBOT/Modules/Server/ChannelModule.cs index 82ea5725..3f42a6ac 100644 --- a/src/FlawBOT.Core/Modules/Server/ChannelModule.cs +++ b/src/FlawBOT/Modules/Server/ChannelModule.cs @@ -8,102 +8,95 @@ using DSharpPlus.CommandsNext.Attributes; using DSharpPlus.Entities; using FlawBOT.Common; -using FlawBOT.Core.Properties; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Services; +using FlawBOT.Properties; +using FlawBOT.Services; namespace FlawBOT.Modules { - [Group("channel")] - [Aliases("chn", "ch", "c")] + [Group("channel"), Aliases("chn", "ch", "c")] [Description("Commands for controlling channels")] [Cooldown(3, 5, CooldownBucketType.Channel)] public class ChannelModule : BaseCommandModule { #region COMMAND_CATEGORY - [Command("category")] - [Aliases("createcategory", "newcategory", "ct")] + [Command("category"), Aliases("createcategory", "newcategory", "ct")] [Description("Create a new channel category")] [RequirePermissions(Permissions.ManageChannels)] - public async Task CreateChannelCategory(CommandContext ctx, - [Description("New category name")] [RemainingText] string name) + public async Task CreateCategory(CommandContext ctx, + [Description("New category name"), RemainingText] + string name) { if (!BotServices.CheckChannelName(name)) { await BotServices.SendEmbedAsync(ctx, Resources.ERR_CHANNEL_NAME, EmbedType.Warning) .ConfigureAwait(false); + return; } - else - { - var category = await ctx.Guild.CreateChannelCategoryAsync(name.Trim()).ConfigureAwait(false); - await BotServices.SendEmbedAsync(ctx, "Successfully created category " + Formatter.Bold(category.Name), EmbedType.Good).ConfigureAwait(false); - } + + var category = await ctx.Guild.CreateChannelCategoryAsync(name.Trim()).ConfigureAwait(false); + await ctx.RespondAsync("Successfully created category " + Formatter.Bold(category.Name)).ConfigureAwait(false); } #endregion COMMAND_CATEGORY #region CHANNEL_CLEAN - [Command("clean")] - [Aliases("clear")] + [Command("clean"), Aliases("clear")] [Description("Remove channel messages")] [RequirePermissions(Permissions.ManageMessages)] - public async Task Clean(CommandContext ctx, - [Description("Number of message to remove from the current channel")] int limit = 2) + public async Task CleanChannel(CommandContext ctx, + [Description("Number of message to remove from the current channel")] + int limit = 2) { 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).ConfigureAwait(false); + await ctx.RespondAsync(Formatter.Bold(messages.Count.ToString()) + " message(s) removed from #" + ctx.Channel.Name).ConfigureAwait(false); } #endregion CHANNEL_CLEAN #region COMMAND_DELETE - [Command("delete")] - [Aliases("remove")] + [Command("delete"), Aliases("remove")] [Description("Delete a channel. If a channel isn't specified, the current one will be deleted")] [RequirePermissions(Permissions.ManageChannels)] - public async Task RemoveTextChannel(CommandContext ctx, - [Description("Channel to delete")] [RemainingText] DiscordChannel channel = null) + public async Task DeleteChannel(CommandContext ctx, + [Description("Channel to delete"), RemainingText] + DiscordChannel channel = null) { // Set the current channel for deletion if one isn't provided by the user - channel = channel ?? ctx.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.") + .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 BotServices.GetUserInteractivity(ctx, "yes", 10).ConfigureAwait(false); if (interactivity.Result is null) { - await ctx.RespondAsync(Resources.REQUEST_TIMEOUT).ConfigureAwait(false); - } - else - { - 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); + await ctx.RespondAsync(Resources.INFO_REQ_TIMEOUT).ConfigureAwait(false); + return; } + + await BotServices.RemoveMessage(interactivity.Result).ConfigureAwait(false); + await BotServices.RemoveMessage(prompt).ConfigureAwait(false); + await ctx.RespondAsync("Successfully deleted " + Formatter.Bold(channel.Name)).ConfigureAwait(false); + await channel.DeleteAsync().ConfigureAwait(false); } #endregion COMMAND_DELETE #region COMMAND_INFO - [Command("info")] - [Aliases("i")] + [Command("info"), Aliases("i")] [Description("Print channel information. If a channel isn't specified, the current one will be used")] public Task GetChannel(CommandContext ctx, - [Description("Channel to retrieve information from")] [RemainingText] DiscordChannel channel = null) + [Description("Channel to retrieve information from"), RemainingText] + DiscordChannel channel = null) { // Set the current channel for viewing if one isn't provided by the user - channel = channel ?? ctx.Channel; + channel ??= ctx.Channel; // Check that the user has the permission in the channel to view its information if (!ctx.Member.PermissionsIn(channel).HasPermission(Permissions.AccessChannels)) @@ -111,9 +104,8 @@ public Task GetChannel(CommandContext ctx, // Create the base embed message var output = new DiscordEmbedBuilder() - .WithTitle(channel.Name) - .WithDescription("ID: " + channel.Id) - .AddField("Topic", channel.Topic ?? string.Empty) + .WithTitle(channel.Name + " (" + channel.Id + ")") + .WithDescription("Topic: " + (channel.IsCategory ? "N/A" : channel.Topic ?? string.Empty)) .AddField("Type", channel.Type.ToString(), true) .AddField("Private", channel.IsPrivate ? "Yes" : "No", true) .AddField("NSFW", channel.IsNSFW ? "Yes" : "No", true) @@ -126,13 +118,14 @@ public Task GetChannel(CommandContext ctx, { case ChannelType.Voice: output.AddField("Bitrate", channel.Bitrate.ToString(), true); - output.AddField("User limit", channel.UserLimit > 0 ? channel.UserLimit.ToString() : "No limit.", true); + output.AddField("User limit", channel.UserLimit > 0 ? channel.UserLimit.ToString() : "No limit.", + true); break; case ChannelType.Category: var channels = new StringBuilder(); foreach (var chn in channel.Children) - channels.Append($"[`{chn.Name}`]"); + channels.Append($"[`{chn.Name}`]"); output.AddField("Channels", channels.Length > 0 ? channels.ToString() : "None", true); break; } @@ -148,129 +141,121 @@ public Task GetChannel(CommandContext ctx, [Description("Remove server user's channel messages")] [RequirePermissions(Permissions.ManageMessages)] public async Task Purge(CommandContext ctx, - [Description("Server user whose messages will be purged")] DiscordMember member, - [Description("Number of messages to purge")] [RemainingText] int limit = 0) + [Description("Server user whose messages will be purged")] + DiscordMember member, + [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)).ConfigureAwait(false); - await BotServices.SendEmbedAsync(ctx, - $"Purged **{limit}** messages by {member.Username}#{member.Discriminator} (ID:{member.Id})", - EmbedType.Good).ConfigureAwait(false); + await ctx.RespondAsync($"Purged **{limit}** messages by {member.Username}#{member.Discriminator} (ID:{member.Id})").ConfigureAwait(false); } #endregion CHANNEL_PURGE #region COMMAND_RENAME - [Command("rename")] - [Aliases("setname")] + [Command("rename"), Aliases("setname")] [Description("Rename a channel. If a channel isn't specified, the current one will be used")] [RequirePermissions(Permissions.ManageChannels)] public async Task SetChannelName(CommandContext ctx, [Description("Channel to rename")] DiscordChannel channel, - [Description("New channel name")] [RemainingText] string name) + [Description("New channel name"), RemainingText] + string name) { if (!BotServices.CheckChannelName(name)) { await BotServices.SendEmbedAsync(ctx, Resources.ERR_CHANNEL_NAME, EmbedType.Warning) .ConfigureAwait(false); + return; } - else - { - var oldName = channel.Name; - await channel.ModifyAsync(m => m.Name = name.Trim().Replace(" ", "-")).ConfigureAwait(false); - await BotServices.SendEmbedAsync(ctx, - "Successfully renamed the channel " + Formatter.Bold(oldName) + " to " + Formatter.Bold(name), - EmbedType.Good).ConfigureAwait(false); - } + + var oldName = channel.Name; + await channel.ModifyAsync(m => m.Name = name.Trim().Replace(" ", "-")).ConfigureAwait(false); + await ctx.RespondAsync("Successfully renamed the channel " + Formatter.Bold(oldName) + " to " + Formatter.Bold(name)).ConfigureAwait(false); } #endregion COMMAND_RENAME #region COMMAND_TEXT - [Command("text")] - [Aliases("createtext", "newtext", "ctc")] + [Command("text"), Aliases("createtext", "newtext", "ctc")] [Description("Create a new text channel")] [RequirePermissions(Permissions.ManageChannels)] - public async Task CreateTextChannel(CommandContext ctx, - [Description("New text channel name")] [RemainingText] string name = "") + public async Task CreateText(CommandContext ctx, + [Description("New text channel name"), RemainingText] + string name = "") { if (!BotServices.CheckChannelName(name)) { await BotServices.SendEmbedAsync(ctx, Resources.ERR_CHANNEL_NAME, EmbedType.Warning) .ConfigureAwait(false); + return; } - else if (ctx.Guild.Channels.Any(chn => string.Compare(name, chn.Value.Name, StringComparison.OrdinalIgnoreCase) == 0)) + + if (ctx.Guild.Channels.Any(chn => string.Equals(name, chn.Value.Name, StringComparison.OrdinalIgnoreCase))) { await BotServices.SendEmbedAsync(ctx, Resources.ERR_CHANNEL_EXISTS, EmbedType.Warning) .ConfigureAwait(false); + return; } - else - { - 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); - } + + var channel = await ctx.Guild.CreateTextChannelAsync(name.Trim().Replace(" ", "-")) + .ConfigureAwait(false); + await ctx.RespondAsync("Successfully created the text channel " + Formatter.Bold(channel.Name)).ConfigureAwait(false); } #endregion COMMAND_TEXT #region COMMAND_TOPIC - [Command("topic")] - [Aliases("settopic", "st")] + [Command("topic"), Aliases("settopic", "st")] [Description("Set current channel's topic")] [RequirePermissions(Permissions.ManageChannels)] public async Task SetChannelTopic(CommandContext ctx, - [Description("New channel topic")] [RemainingText] string topic = "") + [Description("New channel topic"), RemainingText] + string topic = "") { if (topic.Length > 1024) { await BotServices.SendEmbedAsync(ctx, Resources.ERR_CHANNEL_TOPIC, EmbedType.Warning) .ConfigureAwait(false); + return; } - else - { - 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) - .ConfigureAwait(false); - } + + await ctx.Channel.ModifyAsync(chn => chn.Topic = topic).ConfigureAwait(false); + if (!string.IsNullOrWhiteSpace(topic)) + await ctx.RespondAsync("Successfully changed the channel topic to " + Formatter.Bold(topic)).ConfigureAwait(false); } #endregion COMMAND_TOPIC #region COMMAND_VOICE - [Command("voice")] - [Aliases("createvoice", "newvoice", "cvc")] + [Command("voice"), Aliases("createvoice", "newvoice", "cvc")] [Description("Create a new voice channel")] [RequirePermissions(Permissions.ManageChannels)] - public async Task CreateVoiceChannel(CommandContext ctx, - [Description("New voice channel name")] [RemainingText] string name = "") + public async Task CreateVoice(CommandContext ctx, + [Description("New voice channel name"), RemainingText] + string name = "") { if (!BotServices.CheckChannelName(name)) { await BotServices.SendEmbedAsync(ctx, Resources.ERR_CHANNEL_NAME, EmbedType.Warning) .ConfigureAwait(false); + return; } - else if (ctx.Guild.Channels.Any(chn => string.Compare(name, chn.Value.Name, StringComparison.OrdinalIgnoreCase) == 0)) + + if (ctx.Guild.Channels.Any(chn => string.Equals(name, chn.Value.Name, StringComparison.OrdinalIgnoreCase))) { await BotServices.SendEmbedAsync(ctx, Resources.ERR_CHANNEL_EXISTS, EmbedType.Warning) .ConfigureAwait(false); + return; } - else - { - var channel = await ctx.Guild.CreateVoiceChannelAsync(name.Trim().Replace(" ", "-")) - .ConfigureAwait(false); - await BotServices.SendEmbedAsync(ctx, - "Successfully created the voice channel " + Formatter.Bold(channel.Name), EmbedType.Good) - .ConfigureAwait(false); - } + + var channel = await ctx.Guild.CreateVoiceChannelAsync(name.Trim().Replace(" ", "-")) + .ConfigureAwait(false); + await ctx.RespondAsync("Successfully created the voice channel " + Formatter.Bold(channel.Name)).ConfigureAwait(false); } #endregion COMMAND_VOICE diff --git a/src/FlawBOT.Core/Modules/Server/EmojiModule.cs b/src/FlawBOT/Modules/Server/EmojiModule.cs similarity index 59% rename from src/FlawBOT.Core/Modules/Server/EmojiModule.cs rename to src/FlawBOT/Modules/Server/EmojiModule.cs index 3e2650f8..5bc6af2f 100644 --- a/src/FlawBOT.Core/Modules/Server/EmojiModule.cs +++ b/src/FlawBOT/Modules/Server/EmojiModule.cs @@ -8,54 +8,59 @@ using DSharpPlus.CommandsNext.Attributes; using DSharpPlus.Entities; using DSharpPlus.Exceptions; -using FlawBOT.Core.Properties; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Services; +using FlawBOT.Common; +using FlawBOT.Properties; +using FlawBOT.Services; namespace FlawBOT.Modules { - [Group("emoji")] - [Aliases("emojis", "em", "e")] + [Group("emoji"), Aliases("emojis", "em", "e")] [Description("Commands for managing server emojis")] [Cooldown(3, 5, CooldownBucketType.Channel)] public class EmojiModule : BaseCommandModule { #region COMMAND_ADD - [Command("add")] - [Aliases("addnew", "create")] + [Command("add"), Aliases("addnew", "create")] [Description("Add a new server emoji through URL or as an attachment.")] [RequirePermissions(Permissions.ManageEmojis)] - public async Task AddAsync(CommandContext ctx, - [Description("Name for the emoji.")] string query, - [Description("Image URL.")] Uri url = null) + public async Task CreateEmoji(CommandContext ctx, + [Description("Image URL.")] Uri url, + [Description("Name for the emoji."), RemainingText] string name) { try { - if (string.IsNullOrWhiteSpace(query) || query.Length < 2 || query.Length > 50) - 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)) + { + if (!ctx.Message.Attachments.Any() || + !Uri.TryCreate(ctx.Message.Attachments[0].Url, UriKind.Absolute, out url)) await BotServices.SendEmbedAsync(ctx, Resources.ERR_EMOJI_IMAGE, EmbedType.Warning) .ConfigureAwait(false); + return; + } + + if (string.IsNullOrWhiteSpace(name) || name.Length < 2 || name.Length > 50) + { + await BotServices.SendEmbedAsync(ctx, Resources.ERR_EMOJI_NAME, EmbedType.Warning) + .ConfigureAwait(false); + return; + } - var handler = new HttpClientHandler {AllowAutoRedirect = 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).ConfigureAwait(false)) - using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) + await using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) { if (stream.Length >= 256000) - 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); + { + await BotServices.SendEmbedAsync(ctx, Resources.ERR_EMOJI_SIZE, EmbedType.Warning).ConfigureAwait(false); + return; + } + var emoji = await ctx.Guild.CreateEmojiAsync(name, stream).ConfigureAwait(false); + await ctx.RespondAsync("Created the emoji " + Formatter.Bold(emoji.Name)).ConfigureAwait(false); } } catch @@ -68,20 +73,18 @@ await BotServices #region COMMAND_DELETE - [Command("delete")] - [Aliases("remove", "rm", "del")] + [Command("delete"), Aliases("remove", "rm", "del")] [Description("Remove an existing server emoji. Note: Bots can only delete emojis they created.")] [RequirePermissions(Permissions.ManageEmojis)] - public async Task DeleteAsync(CommandContext ctx, - [Description("Server emoji to delete.")] DiscordEmoji query) + public async Task DeleteEmoji(CommandContext ctx, + [Description("Server emoji to delete.")] + DiscordEmoji query) { try { 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); + await ctx.RespondAsync("Deleted the emoji " + Formatter.Bold(emoji.Name)).ConfigureAwait(false); } catch (NotFoundException) { @@ -94,11 +97,10 @@ await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_EMOJI, EmbedType.Missi #region COMMAND_EDIT - [Command("modify")] - [Aliases("e", "edit", "rename")] + [Command("modify"), Aliases("e", "edit", "rename")] [Description("Edit the name of an existing server emoji.")] [RequirePermissions(Permissions.ManageEmojis)] - public async Task ModifyAsync(CommandContext ctx, + public async Task EditEmoji(CommandContext ctx, [Description("Emoji to rename.")] DiscordEmoji query, [Description("New name.")] string name) { @@ -108,14 +110,12 @@ public async Task ModifyAsync(CommandContext ctx, { await BotServices.SendEmbedAsync(ctx, Resources.ERR_EMOJI_NAME, EmbedType.Warning) .ConfigureAwait(false); + return; } - else - { - var emoji = await ctx.Guild.GetEmojiAsync(query.Id).ConfigureAwait(false); - emoji = await ctx.Guild.ModifyEmojiAsync(emoji, name).ConfigureAwait(false); - await BotServices.SendEmbedAsync(ctx, "Successfully renamed emoji to " + Formatter.Bold(emoji.Name), - EmbedType.Good).ConfigureAwait(false); - } + + var emoji = await ctx.Guild.GetEmojiAsync(query.Id).ConfigureAwait(false); + emoji = await ctx.Guild.ModifyEmojiAsync(emoji, name).ConfigureAwait(false); + await ctx.RespondAsync("Successfully renamed emoji to " + Formatter.Bold(emoji.Name)).ConfigureAwait(false); } catch (NotFoundException) { @@ -128,20 +128,17 @@ await BotServices.SendEmbedAsync(ctx, Resources.NOT_FOUND_EMOJI, EmbedType.Missi #region COMMAND_INFO - [Command("info")] - [Aliases("i")] + [Command("info"), Aliases("i")] [Description("Retrieve server emoji information.")] - public async Task GetEmoji(CommandContext ctx, - [Description("Server emoji information to retrieve.")] DiscordEmoji query) + public async Task GetEmojiInfo(CommandContext ctx, + [Description("Server emoji.")] + DiscordEmoji query) { var emoji = await ctx.Guild.GetEmojiAsync(query.Id).ConfigureAwait(false); var output = new DiscordEmbedBuilder() - .WithTitle(emoji.Name) - .WithDescription("Created By" + (emoji.User is null ? "" : emoji.User.Username)) - .AddField("Server", emoji.Guild.Name, true) - .AddField("Creation Date", emoji.CreationTimestamp.ToString(), true) + .WithDescription(emoji.Name + " (" + emoji.Guild.Name + ")") + .AddField("Created by", (emoji.User is null ? "" : emoji.User.Username) + " on " + emoji.CreationTimestamp.Date) .WithColor(DiscordColor.PhthaloBlue) - .WithUrl(emoji.Url) .WithThumbnail(emoji.Url); await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } @@ -150,17 +147,16 @@ public async Task GetEmoji(CommandContext ctx, #region COMMAND_LIST - [Command("list")] - [Aliases("print", "l", "ls", "all")] - [Description("Retrieve list of server emojis.")] + [Command("list"), Aliases("print", "l", "ls", "all")] + [Description("Retrieve the list of server emojis.")] public async Task GetEmojiList(CommandContext ctx) { var emojiList = new StringBuilder(); - foreach (var emoji in ctx.Guild.Emojis.Values.OrderBy(e => e.Name)) + foreach (var emoji in ctx.Guild.Emojis.Values) emojiList.Append(emoji.Name).Append(!emoji.Equals(ctx.Guild.Emojis.Last().Value) ? ", " : string.Empty); var output = new DiscordEmbedBuilder() - .WithTitle("Emojis available for " + ctx.Guild.Name) + .WithTitle(ctx.Guild.Name + " Emoji List") .WithDescription(emojiList.ToString()) .WithThumbnail(ctx.Guild.IconUrl) .WithColor(DiscordColor.PhthaloBlue); diff --git a/src/FlawBOT/Modules/Server/RoleModule.cs b/src/FlawBOT/Modules/Server/RoleModule.cs new file mode 100644 index 00000000..bf9a5a21 --- /dev/null +++ b/src/FlawBOT/Modules/Server/RoleModule.cs @@ -0,0 +1,284 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using DSharpPlus; +using DSharpPlus.CommandsNext; +using DSharpPlus.CommandsNext.Attributes; +using DSharpPlus.Entities; +using FlawBOT.Common; +using FlawBOT.Properties; +using FlawBOT.Services; + +namespace FlawBOT.Modules +{ + [Group("role"), Aliases("roles", "rl")] + [Description("Commands for controlling server roles")] + [Cooldown(3, 5, CooldownBucketType.Guild)] + public class RoleModule : BaseCommandModule + { + #region COMMAND_COLOR + + [Command("color"), Aliases("clr", "setcolor")] + [Description("Set the role color")] + [RequirePermissions(Permissions.ManageRoles)] + public async Task ColorRole(CommandContext ctx, + [Description("HEX color code to set for the role")] + DiscordColor color, + [Description("Server role to recolor"), RemainingText] + DiscordRole role) + { + 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, "Invalid color code. Please enter a HEX color code like #E7B53B", + EmbedType.Warning).ConfigureAwait(false); + return; + } + + await role.ModifyAsync(color: color).ConfigureAwait(false); + 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()).ConfigureAwait(false); + } + + #endregion COMMAND_COLOR + + #region COMMAND_CREATE + + [Command("create"), Aliases("new")] + [Description("Create a server role")] + [RequirePermissions(Permissions.ManageRoles)] + 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).ConfigureAwait(false); + return; + } + + await ctx.Guild.CreateRoleAsync(role).ConfigureAwait(false); + await ctx.RespondAsync("Successfully created the server role " + Formatter.Bold(role)).ConfigureAwait(false); + } + + #endregion COMMAND_CREATE + + #region COMMAND_DELETE + + [Command("delete"), Aliases("remove")] + [Description("Delete a server role")] + [RequirePermissions(Permissions.ManageRoles)] + 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) + .ConfigureAwait(false); + return; + } + + await role.DeleteAsync().ConfigureAwait(false); + await ctx.RespondAsync("Successfully removed the server role " + Formatter.Bold(role.Name)).ConfigureAwait(false); + } + + #endregion COMMAND_DELETE + + #region COMMAND_INFO + + [Command("info"), Aliases("i")] + [Description("Retrieve role information")] + 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) + .ConfigureAwait(false); + return; + } + + var output = new DiscordEmbedBuilder() + .WithTitle(role.Name) + .WithDescription("ID: " + role.Id) + .AddField("Creation Date", role.CreationTimestamp.DateTime.ToString(CultureInfo.InvariantCulture), + true) + .AddField("Hoisted", role.IsHoisted ? "Yes" : "No", true) + .AddField("Mentionable", role.IsMentionable ? "Yes" : "No", true) + .AddField("Permissions", role.Permissions.ToPermissionString()) + .WithThumbnail(ctx.Guild.IconUrl) + .WithFooter($"{ctx.Guild.Name} / #{ctx.Channel.Name} / {DateTime.Now}") + .WithColor(role.Color); + await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); + } + + #endregion COMMAND_INFO + + #region COMMAND_INROLE + + [Command("inrole")] + [Description("Retrieve a list of users in a given role")] + 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) + .ConfigureAwait(false); + return; + } + + var userCount = 0; + var usersList = new StringBuilder(); + var users = (await ctx.Guild.GetAllMembersAsync().ConfigureAwait(false)).ToArray(); + foreach (var user in users) + if (user.Roles.Contains(role)) + { + userCount++; + if (user.Equals(users.Last())) + usersList.Append(user.DisplayName); + else + usersList.Append(user.DisplayName).Append(", "); + } + + if (usersList.Length == 0) + 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}") + .ConfigureAwait(false); + } + + #endregion COMMAND_INROLE + + #region COMMAND_MENTION + + [Command("mention")] + [Description("Toggle whether this role can be mentioned by others")] + [RequirePermissions(Permissions.ManageRoles)] + public async Task MentionRole(CommandContext ctx, + [Description("Server role to toggle"), RemainingText] + DiscordRole role) + { + if (role is null) return; + if (role.IsMentionable) + { + 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).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, Formatter.Bold(role.Name) + " is now **mentionable**") + .ConfigureAwait(false); + } + } + + #endregion COMMAND_MENTION + + #region COMMAND_REVOKE + + [Command("revoke")] + [Description("Remove a role from server user")] + [RequirePermissions(Permissions.ManageRoles)] + public async Task RevokeRole(CommandContext ctx, + [Description("Server user to get revoked")] + DiscordMember member, + [Description("Server role to revoke from user"), RemainingText] + DiscordRole role) + { + if (role != null) + { + member ??= ctx.Member; + await member.RevokeRoleAsync(role).ConfigureAwait(false); + await ctx.RespondAsync(Formatter.Bold(member.DisplayName) + " has been removed from the role " + Formatter.Bold(role.Name)).ConfigureAwait(false); + } + } + + #endregion COMMAND_REVOKE + + #region COMMAND_REVOKE_ALL + + [Command("revokeall")] + [Description("Remove all role from server user")] + [RequirePermissions(Permissions.ManageRoles)] + public async Task RevokeAllRoles(CommandContext ctx, + [Description("Server user to get revoked")] + DiscordMember member) + { + if (!member.Roles.Any()) + { + await BotServices.SendEmbedAsync(ctx, Resources.ERR_ROLE_NONE, EmbedType.Warning).ConfigureAwait(false); + return; + } + + 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) + .ConfigureAwait(false); + return; + } + + await member.ReplaceRolesAsync(Enumerable.Empty()).ConfigureAwait(false); + await ctx.RespondAsync("Removed all roles from " + Formatter.Bold(member.DisplayName)).ConfigureAwait(false); + } + + #endregion COMMAND_REVOKE_ALL + + #region COMMAND_ASSIGN + + [Command("setrole"), Aliases("addrole", "sr")] + [Description("Assign a role to server user")] + [RequirePermissions(Permissions.ManageRoles)] + public async Task SetUserRole(CommandContext ctx, + [Description("Server user to get role assigned")] + DiscordMember member, + [Description("Server role to assign to the user"), RemainingText] + DiscordRole role) + { + member ??= ctx.Member; + await member.GrantRoleAsync(role).ConfigureAwait(false); + await ctx.RespondAsync(member.DisplayName + " been granted the role " + Formatter.Bold(role.Name)).ConfigureAwait(false); + } + + #endregion COMMAND_ASSIGN + + #region COMMAND_SHOW + + [Command("show"), Aliases("display", "hide")] + [Description("Toggle whether this role is seen or not")] + [RequirePermissions(Permissions.ManageRoles)] + public async Task SidebarRole(CommandContext ctx, + [Description("Server role to toggle"), RemainingText] + DiscordRole role) + { + if (role is null) return; + + if (role.IsHoisted) + { + 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).ConfigureAwait(false); + await BotServices.SendEmbedAsync(ctx, Formatter.Bold(role.Name) + " is now **displayed**") + .ConfigureAwait(false); + } + } + + #endregion COMMAND_SHOW + } +} \ No newline at end of file diff --git a/src/FlawBOT.Core/Modules/Server/ServerModule.cs b/src/FlawBOT/Modules/Server/ServerModule.cs similarity index 76% rename from src/FlawBOT.Core/Modules/Server/ServerModule.cs rename to src/FlawBOT/Modules/Server/ServerModule.cs index 98149998..e6f4a8d9 100644 --- a/src/FlawBOT.Core/Modules/Server/ServerModule.cs +++ b/src/FlawBOT/Modules/Server/ServerModule.cs @@ -7,33 +7,30 @@ using DSharpPlus.CommandsNext; using DSharpPlus.CommandsNext.Attributes; using DSharpPlus.Entities; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Services; +using FlawBOT.Common; +using FlawBOT.Services; namespace FlawBOT.Modules { - [Group("server")] - [Aliases("guild")] + [Group("server"), Aliases("guild")] [Description("Commands for controlling server")] [Cooldown(3, 5, CooldownBucketType.Guild)] public class ServerModule : BaseCommandModule { #region COMMAND_AVATAR - [Command("avatar")] - [Aliases("setavatar")] + [Command("avatar"), Aliases("setavatar")] [Description("Set server avatar")] [RequireUserPermissions(Permissions.ManageGuild)] public async Task SetServerAvatar(CommandContext ctx, - [Description("Image URL. Must be in jpg, png or img format.")] string query) + [Description("Image URL. Must be in jpg, png or img format.")] + string query) { try { var stream = BotServices.CheckImageInput(ctx, query).Result; 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); + await ctx.RespondAsync(ctx.Guild.Name + " server avatar has been updated!").ConfigureAwait(false); } catch { @@ -47,16 +44,17 @@ await BotServices #region COMMAND_INFO - [Command("info")] - [Aliases("i")] + [Command("info"), Aliases("i")] [Description("Retrieve server information")] public async Task GetServer(CommandContext ctx) { var output = new DiscordEmbedBuilder() - .WithAuthor($"Owner: {ctx.Guild.Owner.Username}#{ctx.Guild.Owner.Discriminator}", iconUrl: ctx.Guild.Owner.AvatarUrl ?? string.Empty) + .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) - .AddField("Created on", ctx.Guild.CreationTimestamp.DateTime.ToString(CultureInfo.InvariantCulture), true) + .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) .AddField("Authentication", ctx.Guild.MfaLevel.ToString(), true) @@ -86,9 +84,10 @@ public async Task GetServer(CommandContext ctx) [Command("invite")] [Description("Retrieve an instant invite link to the server")] - public async Task InviteAsync(CommandContext ctx) + public async Task Invite(CommandContext ctx) { - await ctx.RespondAsync("Instant Invite to " + Formatter.Bold(ctx.Guild.Name) + ":https://discord.gg/" + ctx.Channel.CreateInviteAsync().Result.Code).ConfigureAwait(false); + await ctx.RespondAsync("Instant Invite to " + Formatter.Bold(ctx.Guild.Name) + ":https://discord.gg/" + + ctx.Channel.CreateInviteAsync().Result.Code).ConfigureAwait(false); } #endregion COMMAND_INVITE @@ -98,8 +97,9 @@ public async Task InviteAsync(CommandContext ctx) [Command("prune")] [Description("Prune inactive server members")] [RequirePermissions(Permissions.DeafenMembers)] - public async Task PruneUsers(CommandContext ctx, - [Description("Number of days the user had to be inactive to get pruned")] int days = 7) + public async Task Prune(CommandContext ctx, + [Description("Number of days the user had to be inactive to get pruned")] + int days = 7) { if (days < 1 || days > 30) await BotServices.SendEmbedAsync(ctx, "Number of days must be between 1 and 30", EmbedType.Warning) @@ -112,7 +112,8 @@ await BotServices.SendEmbedAsync(ctx, "Number of days must be between 1 and 30", } var prompt = await ctx - .RespondAsync($"Pruning will remove {Formatter.Bold(count.ToString())} member(s).\nRespond with **yes** to continue.") + .RespondAsync( + $"Pruning will remove {Formatter.Bold(count.ToString())} member(s).\nRespond with **yes** to continue.") .ConfigureAwait(false); var interactivity = await BotServices.GetUserInteractivity(ctx, "yes", 10).ConfigureAwait(false); if (interactivity.Result is null) return; @@ -125,38 +126,35 @@ await BotServices.SendEmbedAsync(ctx, "Number of days must be between 1 and 30", #region COMMAND_RENAME - [Command("rename")] - [Aliases("setname")] + [Command("rename"), Aliases("setname")] [Description("Set server name")] [RequireUserPermissions(Permissions.ManageGuild)] public async Task SetServerName(CommandContext ctx, - [Description("New server name")] [RemainingText] string name = "") + [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) .ConfigureAwait(false); + return; } - else - { - 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); - } + + await ctx.Guild.ModifyAsync(srv => srv.Name = name).ConfigureAwait(false); + await ctx.RespondAsync("Server name has been changed to " + Formatter.Bold(name)).ConfigureAwait(false); } #endregion COMMAND_RENAME #region COMMAND_WARN - [Command("warn")] - [Aliases("scold")] + [Command("warn"), Aliases("scold")] [Description("Direct message user with a warning")] public async Task Warn(CommandContext ctx, [Description("Server user to warn")] DiscordMember member, - [Description("Warning message")] [RemainingText] string reason = null) + [Description("Warning message"), RemainingText] + string reason = null) { var output = new DiscordEmbedBuilder() .WithTitle("Warning received!") @@ -167,18 +165,17 @@ public async Task Warn(CommandContext ctx, .WithTimestamp(DateTime.Now) .WithColor(DiscordColor.Red); 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) .ConfigureAwait(false); + return; } - else - { - await dm.SendMessageAsync(embed: output.Build()).ConfigureAwait(false); - await BotServices.SendEmbedAsync(ctx, "Successfully sent a warning to " + Formatter.Bold(member.Username), EmbedType.Good) - .ConfigureAwait(false); - } + + await dm.SendMessageAsync(embed: output.Build()).ConfigureAwait(false); + await ctx.RespondAsync("Successfully sent a warning to " + Formatter.Bold(member.Username)).ConfigureAwait(false); } #endregion COMMAND_WARN diff --git a/src/FlawBOT.Core/Modules/Server/UserModule.cs b/src/FlawBOT/Modules/Server/UserModule.cs similarity index 67% rename from src/FlawBOT.Core/Modules/Server/UserModule.cs rename to src/FlawBOT/Modules/Server/UserModule.cs index a7140a12..1dc5dfc5 100644 --- a/src/FlawBOT.Core/Modules/Server/UserModule.cs +++ b/src/FlawBOT/Modules/Server/UserModule.cs @@ -6,29 +6,27 @@ using DSharpPlus.CommandsNext; using DSharpPlus.CommandsNext.Attributes; using DSharpPlus.Entities; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Services; +using FlawBOT.Common; +using FlawBOT.Services; namespace FlawBOT.Modules { - [Group("user")] - [Aliases("users", "u", "usr")] + [Group("user"), Aliases("users", "u", "usr")] [Description("Commands for controlling server users")] [Cooldown(3, 5, CooldownBucketType.Channel)] public class UserModule : BaseCommandModule { #region COMMAND_AVATAR - [Command("avatar")] - [Aliases("getavatar", "image", "pfp")] + [Command("avatar"), Aliases("getavatar", "image", "pfp")] [Description("Retrieve server user's profile picture")] public async Task GetAvatar(CommandContext ctx, - [Description("Server user whose profile picture to retrieve")] [RemainingText] DiscordMember member) + [Description("Server user whose profile picture to retrieve"), RemainingText] + DiscordMember member) { - member = member ?? ctx.Member; + member ??= ctx.Member; var output = new DiscordEmbedBuilder() .WithImageUrl(member.AvatarUrl) - .WithUrl("https://images.google.com/searchbyimage?image_url=" + member.AvatarUrl) // UNUSED .WithColor(DiscordColor.Lilac); await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } @@ -42,63 +40,65 @@ public async Task GetAvatar(CommandContext ctx, [RequirePermissions(Permissions.BanMembers)] public async Task Ban(CommandContext ctx, [Description("Server user to ban")] DiscordMember member, - [Description("Reason for the ban")] [RemainingText] string reason = null) + [Description("Reason for the ban"), RemainingText] + string reason = null) { if (ctx.Member.Id == member.Id) { await ctx.RespondAsync("You cannot ban yourself.").ConfigureAwait(false); + return; } - else - { - 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."); - } + + 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.").ConfigureAwait(false); } #endregion COMMAND_BAN #region COMMAND_DEAFEN - [Command("deafen")] - [Aliases("deaf")] + [Command("deafen"), Aliases("deaf")] [Description("Deafen server user")] [RequirePermissions(Permissions.DeafenMembers)] - public async Task Deafen(CommandContext ctx, + public async Task DeafenUser(CommandContext ctx, [Description("Server user to deafen")] DiscordMember member, - [Description("Reason for the deafen")] [RemainingText] string reason = null) + [Description("Reason for the deafen"), RemainingText] + string reason = null) { if (member.IsDeafened) { await ctx.RespondAsync($"{member.DisplayName}#{member.Discriminator} is already **deafened**.") .ConfigureAwait(false); + return; } - else - { - 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."); - } + + 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.").ConfigureAwait(false); } #endregion COMMAND_DEAFEN #region COMMAND_INFO - [Command("info")] - [Aliases("i")] + [Command("info"), Aliases("i")] [Description("Retrieve user information")] public async Task GetUser(CommandContext ctx, - [Description("Server user whose information to retrieve")] [RemainingText] DiscordMember member) + [Description("Server user whose information to retrieve"), RemainingText] + DiscordMember member) { - member = member ?? ctx.Member; + member ??= ctx.Member; var roles = new StringBuilder(); var permsobj = member.PermissionsIn(ctx.Channel); var perms = permsobj.ToPermissionString(); var output = new DiscordEmbedBuilder() .WithTitle($"@{member.Username}#{member.Discriminator}") .WithDescription("ID: " + member.Id) - .AddField("Registered on", member.CreationTimestamp.DateTime.ToString(CultureInfo.InvariantCulture), true) + .AddField("Registered on", member.CreationTimestamp.DateTime.ToString(CultureInfo.InvariantCulture), + true) .AddField("Joined on", member.JoinedAt.DateTime.ToString(CultureInfo.InvariantCulture), true) .AddField("Nickname", member.Nickname ?? "None", true) .AddField("Muted?", member.IsMuted ? "Yes" : "No", true) @@ -125,85 +125,86 @@ public async Task GetUser(CommandContext ctx, #region COMMAND_KICK - [Command("kick")] - [Aliases("remove")] + [Command("kick"), Aliases("remove")] [Description("Kick server user")] [RequirePermissions(Permissions.KickMembers)] public async Task Kick(CommandContext ctx, [Description("Server user to kick")] DiscordMember member, - [Description("Reason for the kick")] [RemainingText] string reason = null) + [Description("Reason for the kick"), RemainingText] + string reason = null) { if (ctx.Member.Id == member.Id) { await ctx.RespondAsync("You cannot kick yourself.").ConfigureAwait(false); + return; } - else - { - await member.RemoveAsync(reason).ConfigureAwait(false); - await BotServices.RemoveMessage(ctx.Message).ConfigureAwait(false); - await BotServices.SendUserStateChangeAsync(ctx, UserStateChange.Kick, member, reason ?? "No reason provided."); - } + + await member.RemoveAsync(reason).ConfigureAwait(false); + await BotServices.RemoveMessage(ctx.Message).ConfigureAwait(false); + await BotServices.SendUserStateChangeAsync(ctx, UserStateChange.Kick, member, + reason ?? "No reason provided.").ConfigureAwait(false); } #endregion COMMAND_KICK #region COMMAND_MUTE - [Command("mute")] - [Aliases("silence")] + [Command("mute"), Aliases("silence")] [Description("Mute server user")] [RequirePermissions(Permissions.MuteMembers)] public async Task Mute(CommandContext ctx, [Description("Server user to mute")] DiscordMember member, - [Description("Reason for the mute")] [RemainingText] string reason = null) + [Description("Reason for the mute"), RemainingText] + string reason = null) { if (member.IsMuted) { await ctx.RespondAsync($"{member.DisplayName}#{member.Discriminator} is already **muted**.") .ConfigureAwait(false); + return; } - else - { - 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."); - } + + 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.").ConfigureAwait(false); } #endregion COMMAND_MUTE #region COMMAND_NICKNAME - [Command("nickname")] - [Aliases("setnick", "nick")] + [Command("nickname"), Aliases("setnick", "nick")] [Description("Set server user's nickname")] [RequireUserPermissions(Permissions.ChangeNickname)] public async Task SetUserName(CommandContext ctx, - [Description("Server user to nickname")] DiscordMember member, - [Description("The new nickname")] [RemainingText] string name = null) + [Description("Server user to nickname")] + DiscordMember member, + [Description("The new nickname"), RemainingText] + string name = null) { - member = member ?? ctx.Member; + member ??= ctx.Member; var nickname = member.DisplayName; 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); + await ctx.RespondAsync(response).ConfigureAwait(false); } #endregion COMMAND_NICKNAME #region COMMAND_PERMS - [Command("perms")] - [Aliases("prm")] + [Command("perms"), Aliases("prm")] [Description("Retrieve server user's permissions")] - public async Task ListServerPermissions(CommandContext ctx, - [Description("Server user whose permissions to retrieve")] DiscordMember member = null, + public async Task GetPermissionsList(CommandContext ctx, + [Description("Server user whose permissions to retrieve")] + DiscordMember member = null, [Description("Server channel")] DiscordChannel channel = null) { - member = member ?? ctx.Member; - channel = channel ?? ctx.Channel; + member ??= ctx.Member; + channel ??= ctx.Channel; var perms = Formatter.Bold(member.DisplayName) + " cannot access channel " + Formatter.Bold(channel.Name); if (member.PermissionsIn(channel).HasPermission(Permissions.AccessChannels)) perms = member.PermissionsIn(channel).ToPermissionString(); @@ -221,9 +222,11 @@ public async Task ListServerPermissions(CommandContext ctx, [Command("unban")] [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("Reason for the deafen")] [RemainingText] string reason = null) + public async Task Unban(CommandContext ctx, + [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, reason ?? "No reason provided.").ConfigureAwait(false); @@ -235,17 +238,19 @@ public async Task Remove(CommandContext ctx, #region COMMAND_UNDEAFEN - [Command("undeafen")] - [Aliases("undeaf")] + [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("Reason for the deafen")] [RemainingText] string reason = null) + [Description("Server user to undeafen")] + DiscordMember member, + [Description("Reason for the deafen"), RemainingText] + string reason = null) { 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"); + await BotServices.SendUserStateChangeAsync(ctx, UserStateChange.Undeafen, member, + reason ?? "No reason provided").ConfigureAwait(false); } #endregion COMMAND_UNDEAFEN @@ -256,12 +261,14 @@ 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("Reason for the deafen")] [RemainingText] string reason = null) + [Description("Server user to unmute")] DiscordMember member, + [Description("Reason for the deafen"), RemainingText] + string reason = null) { 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"); + await BotServices.SendUserStateChangeAsync(ctx, UserStateChange.Unmute, member, + reason ?? "No reason provided").ConfigureAwait(false); } #endregion COMMAND_UNMUTE diff --git a/src/FlawBOT/NuGet.Config b/src/FlawBOT/NuGet.Config new file mode 100644 index 00000000..502291dd --- /dev/null +++ b/src/FlawBOT/NuGet.Config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/FlawBOT.Core/Program.cs b/src/FlawBOT/Program.cs similarity index 56% rename from src/FlawBOT.Core/Program.cs rename to src/FlawBOT/Program.cs index 8fd6f943..25359750 100644 --- a/src/FlawBOT.Core/Program.cs +++ b/src/FlawBOT/Program.cs @@ -2,38 +2,38 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using FlawBOT.Core.Properties; -using FlawBOT.Framework.Services; +using FlawBOT.Common; +using FlawBOT.Properties; +using FlawBOT.Services; namespace FlawBOT { internal sealed class Program { - private static List Shards { get; } = new List(); - public static CancellationTokenSource CancelTokenSource { get; } = new CancellationTokenSource(); - private static CancellationToken CancelToken => CancelTokenSource.Token; + private static List Shards { get; } = new List(); + private static CancellationTokenSource CancelTokenSource { get; } = new CancellationTokenSource(); - public static void Main(string[] args) => RunBotAsync(args).ConfigureAwait(false).GetAwaiter().GetResult(); + public static void Main(string[] args) + { + RunBotAsync(args).ConfigureAwait(false).GetAwaiter().GetResult(); + } - public static async Task RunBotAsync(string[] args) + private static async Task RunBotAsync(string[] args) { try { - // TO-DO: Remove? - //ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; - - // Set a command for cancelling the bot process + // Set a command for canceling the bot process Console.CancelKeyPress += ConsoleOnCancelKeyPress; // Load the bot configuration var service = new BotServices(); - if (!service.LoadBotConfiguration()) return; + if (!service.LoadConfiguration()) return; // Generate a list of shards var botList = new List(); - for (var i = 0; i < 1; i++) // TO-DO: Replace 1 with ShardCount + for (var i = 0; i < SharedData.ShardCount; i++) { - var client = new FlawBOT(i); + var client = new FlawBot(i); Shards.Add(client); botList.Add(client.RunAsync()); await Task.Delay(7500).ConfigureAwait(false); @@ -45,9 +45,9 @@ public static async Task RunBotAsync(string[] args) } catch (Exception ex) { - Console.WriteLine(Resources.ERR_EXCEPTION, ex.GetType(), ex.Message); + Console.WriteLine(Resources.ERR_EXCEPTION, ex.Message); if (!(ex.InnerException is null)) - Console.WriteLine(Resources.ERR_EXCEPTION_INNER, ex.InnerException.GetType(), ex.InnerException.Message); + Console.WriteLine(Resources.ERR_EXCEPTION_INNER, ex.InnerException.Message); Console.ReadKey(); } diff --git a/src/FlawBOT/Properties/Resources.Designer.cs b/src/FlawBOT/Properties/Resources.Designer.cs new file mode 100644 index 00000000..afccc160 --- /dev/null +++ b/src/FlawBOT/Properties/Resources.Designer.cs @@ -0,0 +1,639 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace FlawBOT.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FlawBOT.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Unable to reach the API. Please try again later.. + /// + internal static string ERR_API_CONNECTION { + get { + return ResourceManager.GetString("ERR_API_CONNECTION", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A channel with the same name already exists.. + /// + internal static string ERR_CHANNEL_EXISTS { + get { + return ResourceManager.GetString("ERR_CHANNEL_EXISTS", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Channel name cannot be blank or over a 100 characters long.. + /// + internal static string ERR_CHANNEL_NAME { + get { + return ResourceManager.GetString("ERR_CHANNEL_NAME", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Channel topic must be less than 1024 characters long.. + /// + internal static string ERR_CHANNEL_TOPIC { + get { + return ResourceManager.GetString("ERR_CHANNEL_TOPIC", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to create the server emoji. Either the HTTP request failed or Discord prevented the operation from completing.. + /// + internal static string ERR_EMOJI_ADD { + get { + return ResourceManager.GetString("ERR_EMOJI_ADD", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The provided URL must point to an image file or an attachment.. + /// + internal static string ERR_EMOJI_IMAGE { + get { + return ResourceManager.GetString("ERR_EMOJI_IMAGE", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Emoji name is invalid or does not meet the length requirement of 2-50 characters.. + /// + internal static string ERR_EMOJI_NAME { + get { + return ResourceManager.GetString("ERR_EMOJI_NAME", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to create the server emoji. Image file size is too large.. + /// + internal static string ERR_EMOJI_SIZE { + get { + return ResourceManager.GetString("ERR_EMOJI_SIZE", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Exception occurred: {0}. + /// + internal static string ERR_EXCEPTION { + get { + return ResourceManager.GetString("ERR_EXCEPTION", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Inner exception: {0}. + /// + internal static string ERR_EXCEPTION_INNER { + get { + return ResourceManager.GetString("ERR_EXCEPTION_INNER", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid IP address, please follow the format: `174.116.36.120`. + /// + internal static string ERR_INVALID_IP { + get { + return ResourceManager.GetString("ERR_INVALID_IP", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid connection info, please follow the format: `174.116.36.120:27015; password hello`. + /// + internal static string ERR_INVALID_IP_GAME { + get { + return ResourceManager.GetString("ERR_INVALID_IP_GAME", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error calculating the equation, make sure your values are integers and the operation is valid.. + /// + internal static string ERR_MATH_EQUATION { + get { + return ResourceManager.GetString("ERR_MATH_EQUATION", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error updating the Pokémon card list: {0}. + /// + internal static string ERR_POKEMON_LIST { + get { + return ResourceManager.GetString("ERR_POKEMON_LIST", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Please provide a poll question.. + /// + internal static string ERR_POLL_QUESTION { + get { + return ResourceManager.GetString("ERR_POLL_QUESTION", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Please provide an existing category.. + /// + internal static string ERR_REDDIT_UNKNOWN { + get { + return ResourceManager.GetString("ERR_REDDIT_UNKNOWN", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Please provide more information on the issue (50 characters minimum).. + /// + internal static string ERR_REPORT_LENGTH { + get { + return ResourceManager.GetString("ERR_REPORT_LENGTH", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Please provide an existing server role.. + /// + internal static string ERR_ROLE_EXISTING { + get { + return ResourceManager.GetString("ERR_ROLE_EXISTING", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Role name cannot be blank.. + /// + internal static string ERR_ROLE_NAME { + get { + return ResourceManager.GetString("ERR_ROLE_NAME", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This user doesn't have any roles.. + /// + internal static string ERR_ROLE_NONE { + get { + return ResourceManager.GetString("ERR_ROLE_NONE", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You are unauthorized to remove roles from this user.. + /// + internal static string ERR_ROLE_NOT_ALLOWED { + get { + return ResourceManager.GetString("ERR_ROLE_NOT_ALLOWED", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error updating Steam games list: {0}. + /// + internal static string ERR_STEAM_LIST { + get { + return ResourceManager.GetString("ERR_STEAM_LIST", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error updating TF2 item schema: {0}. + /// + internal static string ERR_TF2_LIST { + get { + return ResourceManager.GetString("ERR_TF2_LIST", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Note: First time gifs take a few minutes to properly generate.. + /// + internal static string INFO_GIF_LOADING { + get { + return ResourceManager.GetString("INFO_GIF_LOADING", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Request timed out.... + /// + internal static string INFO_REQ_TIMEOUT { + get { + return ResourceManager.GetString("INFO_REQ_TIMEOUT", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Respond with **yes** to proceed or wait 10 seconds to cancel this operation.. + /// + internal static string INFO_RESPOND { + get { + return ResourceManager.GetString("INFO_RESPOND", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shutting Down.... + /// + internal static string INFO_SHUTDOWN { + get { + return ResourceManager.GetString("INFO_SHUTDOWN", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No results found.. + /// + internal static string NOT_FOUND_COMMON { + get { + return ResourceManager.GetString("NOT_FOUND_COMMON", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Emoji not found in the server list.. + /// + internal static string NOT_FOUND_EMOJI { + get { + return ResourceManager.GetString("NOT_FOUND_EMOJI", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Location not found.. + /// + internal static string NOT_FOUND_LOCATION { + get { + return ResourceManager.GetString("NOT_FOUND_LOCATION", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Twitch channel is offline or was not found.. + /// + internal static string NOT_FOUND_TWITCH { + get { + return ResourceManager.GetString("NOT_FOUND_TWITCH", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Wikipedia page not found.. + /// + internal static string NOT_FOUND_WIKIPEDIA { + get { + return ResourceManager.GetString("NOT_FOUND_WIKIPEDIA", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to https://www.amiiboapi.com/api/amiibo/?name={0}. + /// + internal static string URL_Amiibo { + get { + return ResourceManager.GetString("URL_Amiibo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to https://github.com/CriticalFlaw/FlawBOT/. + /// + internal static string URL_BOT_GitHub { + get { + return ResourceManager.GetString("URL_BOT_GitHub", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to https://discordapp.com/oauth2/authorize?client_id=339833029013012483&scope=bot&permissions=66186303. + /// + internal static string URL_BOT_Invite { + get { + return ResourceManager.GetString("URL_BOT_Invite", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to https://catfact.ninja/fact. + /// + internal static string URL_CatFacts { + get { + return ResourceManager.GetString("URL_CatFacts", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to http://aws.random.cat/meow. + /// + internal static string URL_CatPhoto { + get { + return ResourceManager.GetString("URL_CatPhoto", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to http://api.urbandictionary.com/v0/define?term={0}. + /// + internal static string URL_Dictionary { + get { + return ResourceManager.GetString("URL_Dictionary", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to https://discord.gg/. + /// + internal static string URL_Discord { + get { + return ResourceManager.GetString("URL_Discord", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to https://dog.ceo/api/breeds/image/random. + /// + internal static string URL_DogPhoto { + get { + return ResourceManager.GetString("URL_DogPhoto", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to https://www.google.ca/maps/@{0},{1},14z. + /// + internal static string URL_Google_Maps { + get { + return ResourceManager.GetString("URL_Google_Maps", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An image URL ending with .img, .png or .jpg is required!. + /// + internal static string URL_INVALID_IMG { + get { + return ResourceManager.GetString("URL_INVALID_IMG", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to http://api.ipstack.com/{0}?access_key=2e3dea495fa72127ed0f03ccb0b141b9&format=1. + /// + internal static string URL_IPStack { + get { + return ResourceManager.GetString("URL_IPStack", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to https://api.nasa.gov/planetary/apod?api_key={0}. + /// + internal static string URL_NASA { + get { + return ResourceManager.GetString("URL_NASA", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to https://newsapi.org/v2/everything?sources=google-news&q={0}&apiKey={1}. + /// + internal static string URL_News { + get { + return ResourceManager.GetString("URL_News", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to https://www.reddit.com/r/{0}/{1}.rss. + /// + internal static string URL_Reddit { + get { + return ResourceManager.GetString("URL_Reddit", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to https://{0}.com/api/frames/{1}/{2}/3000/4000. + /// + internal static string URL_Simpsons_Frames { + get { + return ResourceManager.GetString("URL_Simpsons_Frames", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to https://{0}.com/img/{1}/{2}.jpg. + /// + internal static string URL_Simpsons_Image { + get { + return ResourceManager.GetString("URL_Simpsons_Image", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to https://{0}.com/api/random. + /// + internal static string URL_Simpsons_Random { + get { + return ResourceManager.GetString("URL_Simpsons_Random", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to https://{0}.com/gif/{1}/{2}/{3}.gif. + /// + internal static string URL_Simpsons_Result { + get { + return ResourceManager.GetString("URL_Simpsons_Result", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to https://www.speedrun.com/api/v1/games?name={0}. + /// + internal static string URL_Speedrun { + get { + return ResourceManager.GetString("URL_Speedrun", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to https://www.speedrun.com/api/v1/{0}/{1}. + /// + internal static string URL_Speedrun_Extras { + get { + return ResourceManager.GetString("URL_Speedrun_Extras", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to http://store.steampowered.com/app/{0}. + /// + internal static string URL_Steam_App { + get { + return ResourceManager.GetString("URL_Steam_App", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to steam://connect/{0}:27015. + /// + internal static string URL_Steam_Connect { + get { + return ResourceManager.GetString("URL_Steam_Connect", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to https://steamcommunity.com/groups/. + /// + internal static string URL_Steam_Group { + get { + return ResourceManager.GetString("URL_Steam_Group", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to https://steamcommunity.com/id/. + /// + internal static string URL_Steam_User { + get { + return ResourceManager.GetString("URL_Steam_User", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to https://teamwork.tf/community/customserverlist/. + /// + internal static string URL_TeamworkTF { + get { + return ResourceManager.GetString("URL_TeamworkTF", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to https://wiki.teamfortress.com/wiki/{0}. + /// + internal static string URL_TF2Wiki { + get { + return ResourceManager.GetString("URL_TF2Wiki", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to https://www.twitch.tv/. + /// + internal static string URL_Twitch { + get { + return ResourceManager.GetString("URL_Twitch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to https://twitter.com/. + /// + internal static string URL_Twitter { + get { + return ResourceManager.GetString("URL_Twitter", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to http://api.weatherstack.com//current?access_key={0}&query={1}. + /// + internal static string URL_Weather { + get { + return ResourceManager.GetString("URL_Weather", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to https://www.youtube.com/channel/{0}. + /// + internal static string URL_YouTube_Channel { + get { + return ResourceManager.GetString("URL_YouTube_Channel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to https://www.youtube.com/playlist?list={0}. + /// + internal static string URL_YouTube_Playlist { + get { + return ResourceManager.GetString("URL_YouTube_Playlist", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to https://www.youtube.com/watch?v={0}. + /// + internal static string URL_YouTube_Video { + get { + return ResourceManager.GetString("URL_YouTube_Video", resourceCulture); + } + } + } +} diff --git a/src/FlawBOT.Core/Properties/Resources.resx b/src/FlawBOT/Properties/Resources.resx similarity index 59% rename from src/FlawBOT.Core/Properties/Resources.resx rename to src/FlawBOT/Properties/Resources.resx index deef5198..f5b5f33d 100644 --- a/src/FlawBOT.Core/Properties/Resources.resx +++ b/src/FlawBOT/Properties/Resources.resx @@ -118,42 +118,36 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Channel with the same name already exists. + A channel with the same name already exists. - The name cannot be blank or over 100 characters. + Channel name cannot be blank or over a 100 characters long. Channel topic must be less than 1024 characters long. - - Invalid color code. Please enter a HEX color code like #E7B53B. - - - Unable to reach the Dog.CEO API. + + Unable to reach the API. Please try again later. Unable to create the server emoji. Either the HTTP request failed or Discord prevented the operation from completing. - Please provide a URL pointing to an emoji image or attach an image. + The provided URL must point to an image file or an attachment. - Server emoji name is invalid. + Emoji name is invalid or does not meet the length requirement of 2-50 characters. - Unable to create the server emoji. Either the image size was too large. + Unable to create the server emoji. Image file size is too large. - Error calculating math equation, make sure your values are integers and the operation is valid! - - - Unable to reach the NASA API. + Error calculating the equation, make sure your values are integers and the operation is valid. Please provide a poll question. - + Please provide more information on the issue (50 characters minimum). @@ -168,66 +162,156 @@ You are unauthorized to remove roles from this user. - - Invalid connection info, follow the format: 123.345.56.789:000; password hello + + Invalid connection info, please follow the format: `174.116.36.120:27015; password hello` Emoji not found in the server list. - + No results found. Location not found. - - Reddit post not found. - - - Subreddit not found. - Twitch channel is offline or was not found. Wikipedia page not found. - + Request timed out... - + Note: First time gifs take a few minutes to properly generate. - + https://discord.gg/ - + https://steamcommunity.com/groups/ - + https://steamcommunity.com/id/ - + https://teamwork.tf/community/customserverlist/ - + https://www.twitch.tv/ - - https://twitter.com/ - - - https://www.youtube.com/channel/ - - Exception occurred: {0} : -{1} + Exception occurred: {0} - Inner exception: {0} : -{1} + Inner exception: {0} - Shutting Down + Shutting Down... + + + Respond with **yes** to proceed or wait 10 seconds to cancel this operation. + + + https://wiki.teamfortress.com/wiki/{0} + + + https://github.com/CriticalFlaw/FlawBOT/ + + + https://discordapp.com/oauth2/authorize?client_id=339833029013012483&scope=bot&permissions=66186303 + + + http://store.steampowered.com/app/{0} + + + Invalid IP address, please follow the format: `174.116.36.120` + + + https://www.google.ca/maps/@{0},{1},14z + 0 = longitude, 1 = latitude + + + steam://connect/{0}:27015 + + + https://www.amiiboapi.com/api/amiibo/?name={0} + + + https://catfact.ninja/fact + + + http://aws.random.cat/meow + + + http://api.urbandictionary.com/v0/define?term={0} + + + https://dog.ceo/api/breeds/image/random + + + http://api.ipstack.com/{0}?access_key=2e3dea495fa72127ed0f03ccb0b141b9&format=1 + + + https://api.nasa.gov/planetary/apod?api_key={0} + + + https://newsapi.org/v2/everything?sources=google-news&q={0}&apiKey={1} + + + https://www.speedrun.com/api/v1/games?name={0} + + + https://www.speedrun.com/api/v1/{0}/{1} + + + http://api.weatherstack.com//current?access_key={0}&query={1} + + + Error updating the Pokémon card list: {0} + + + Please provide an existing category. + + + Error updating Steam games list: {0} + + + Error updating TF2 item schema: {0} + + + https://{0}.com/api/frames/{1}/{2}/3000/4000 + 0 = site, 1 = episode, 2 = timestamp + + + https://{0}.com/api/random + 0 = site + + + https://{0}.com/gif/{1}/{2}/{3}.gif + 0 = site, 1 = episode, 2 = start, 3 = end + + + https://www.reddit.com/r/{0}/{1}.rss + 0 = query, 1 = category + + + https://www.youtube.com/channel/{0} + + + https://www.youtube.com/playlist?list={0} + + + https://www.youtube.com/watch?v={0} + + + https://twitter.com/ + + + An image URL ending with .img, .png or .jpg is required! + + + https://{0}.com/img/{1}/{2}.jpg \ No newline at end of file diff --git a/src/FlawBOT.Framework/Services/Server/BotService.cs b/src/FlawBOT/Services/Bot/BotService.cs similarity index 74% rename from src/FlawBOT.Framework/Services/Server/BotService.cs rename to src/FlawBOT/Services/Bot/BotService.cs index fb3960e2..3c0b2d15 100644 --- a/src/FlawBOT.Framework/Services/Server/BotService.cs +++ b/src/FlawBOT/Services/Bot/BotService.cs @@ -6,10 +6,13 @@ using DSharpPlus.CommandsNext; using DSharpPlus.Entities; using DSharpPlus.Interactivity; -using FlawBOT.Framework.Models; +using DSharpPlus.Interactivity.Extensions; +using FlawBOT.Common; +using FlawBOT.Models; +using FlawBOT.Properties; using Newtonsoft.Json; -namespace FlawBOT.Framework.Services +namespace FlawBOT.Services { public class BotServices { @@ -19,10 +22,6 @@ public static async Task SendEmbedAsync(CommandContext ctx, string message, Embe DiscordColor color; switch (type) { - case EmbedType.Good: - color = DiscordColor.Green; - break; - case EmbedType.Warning: prefix = ":warning: "; color = DiscordColor.Yellow; @@ -49,29 +48,28 @@ public static async Task SendEmbedAsync(CommandContext ctx, string message, Embe await ctx.RespondAsync(embed: output.Build()).ConfigureAwait(false); } - public static async Task SendUserStateChangeAsync(CommandContext ctx, UserStateChange state, DiscordMember user, string reason) + 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}") + .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) - { - return !string.IsNullOrWhiteSpace(input); - } - public static bool CheckChannelName(string input) { return !string.IsNullOrWhiteSpace(input) && input.Length <= 100; } - public static async Task> GetUserInteractivity(CommandContext ctx, string keyword, int seconds) + public static async Task> GetUserInteractivity(CommandContext ctx, + string keyword, int seconds) { return await ctx.Client.GetInteractivity() .WaitForMessageAsync( - m => m.Channel.Id == ctx.Channel.Id && string.Equals(m.Content, keyword, StringComparison.InvariantCultureIgnoreCase), + m => m.Channel.Id == ctx.Channel.Id && + string.Equals(m.Content, keyword, StringComparison.InvariantCultureIgnoreCase), TimeSpan.FromSeconds(seconds)).ConfigureAwait(false); } @@ -91,8 +89,10 @@ public static async Task CheckImageInput(CommandContext ctx, strin var stream = new MemoryStream(); if (!Uri.TryCreate(input, UriKind.Absolute, out _) && (!input.EndsWith(".img") || !input.EndsWith(".png") || !input.EndsWith(".jpg"))) - await SendEmbedAsync(ctx, "An image URL ending with .img, .png or .jpg is required!", EmbedType.Warning) + { + await SendEmbedAsync(ctx, Resources.URL_INVALID_IMG, EmbedType.Warning) .ConfigureAwait(false); + } else { using var client = new WebClient(); @@ -104,13 +104,11 @@ await SendEmbedAsync(ctx, "An image URL ending with .img, .png or .jpg is requir return stream; } - public bool LoadBotConfiguration() + public bool LoadConfiguration() { if (!File.Exists("config.json")) return false; - // TO-DO: Generate a config.json file if one does not already exist. - var json = new StreamReader(File.OpenRead("config.json"), new UTF8Encoding(false)).ReadToEnd(); - TokenHandler.Tokens = JsonConvert.DeserializeObject(json); + SharedData.Tokens = JsonConvert.DeserializeObject(json); return true; } } diff --git a/src/FlawBOT.Framework/Models/Server/BotHandlers.cs b/src/FlawBOT/Services/Bot/HttpHandler.cs similarity index 64% rename from src/FlawBOT.Framework/Models/Server/BotHandlers.cs rename to src/FlawBOT/Services/Bot/HttpHandler.cs index 2c236c8a..d22133cf 100644 --- a/src/FlawBOT.Framework/Models/Server/BotHandlers.cs +++ b/src/FlawBOT/Services/Bot/HttpHandler.cs @@ -1,15 +1,10 @@ using System.Net.Http; -namespace FlawBOT.Framework.Models +namespace FlawBOT.Services { public abstract class HttpHandler { private static readonly HttpClientHandler Handler = new HttpClientHandler {AllowAutoRedirect = false}; protected static readonly HttpClient Http = new HttpClient(Handler, true); } - - public class TokenHandler - { - public static TokenData Tokens { get; set; } = new TokenData(); - } } \ No newline at end of file diff --git a/src/FlawBOT/Services/Bot/LavalinkService.cs b/src/FlawBOT/Services/Bot/LavalinkService.cs new file mode 100644 index 00000000..3fcaaf79 --- /dev/null +++ b/src/FlawBOT/Services/Bot/LavalinkService.cs @@ -0,0 +1,73 @@ +using System; +using System.Threading.Tasks; +using DSharpPlus; +using DSharpPlus.EventArgs; +using DSharpPlus.Lavalink; +using DSharpPlus.Lavalink.EventArgs; +using DSharpPlus.Net; +using Emzi0767.Utilities; +using FlawBOT.Common; +using Microsoft.Extensions.Logging; + +namespace FlawBOT.Services +{ + public sealed class LavalinkService + { + private readonly AsyncEvent _trackException; + + public LavalinkService(DiscordClient client) + { + Discord = client; + Discord.Ready += Client_Ready; + _trackException = + new AsyncEvent("FLAWBOT_LAVALINK_TRACK_EXCEPTION", + TimeSpan.Zero, EventExceptionHandler); + } + + private DiscordClient Discord { get; } + + public static EventId LogEvent { get; } = new EventId(1001, SharedData.Name); + + public LavalinkNodeConnection Node { get; private set; } + + private Task Client_Ready(DiscordClient sender, ReadyEventArgs e) + { + if (Node == null) + _ = Task.Run(async () => + { + var lava = sender.GetLavalink(); + Node = await lava.ConnectAsync(new LavalinkConfiguration + { + Password = "youshallnotpass", + SocketEndpoint = new ConnectionEndpoint("127.0.0.1", 2333), + RestEndpoint = new ConnectionEndpoint("127.0.0.1", 2333) + }); + + Node.TrackException += LavalinkNode_TrackException; + }); + + return Task.CompletedTask; + } + + private async Task LavalinkNode_TrackException(LavalinkGuildConnection con, TrackExceptionEventArgs e) + { + await _trackException.InvokeAsync(con, e); + } + + public event AsyncEventHandler TrackExceptionThrown + { + add => _trackException.Register(value); + remove => _trackException.Unregister(value); + } + + private void EventExceptionHandler( + AsyncEvent asyncEvent, + Exception exception, + AsyncEventHandler handler, + LavalinkGuildConnection sender, + TrackExceptionEventArgs eventArgs) + { + Discord.Logger.LogError(LogEvent, exception, "Exception occurred during audio playback."); + } + } +} \ No newline at end of file diff --git a/src/FlawBOT/Services/Bot/MusicService.cs b/src/FlawBOT/Services/Bot/MusicService.cs new file mode 100644 index 00000000..5e626dd9 --- /dev/null +++ b/src/FlawBOT/Services/Bot/MusicService.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Concurrent; +using System.Threading.Tasks; +using DSharpPlus; +using DSharpPlus.Entities; +using DSharpPlus.Lavalink; +using DSharpPlus.Lavalink.EventArgs; + +namespace FlawBOT.Services +{ + public sealed class MusicService + { + public MusicService(LavalinkService lavalink) + { + Lavalink = lavalink; + MusicData = new ConcurrentDictionary(); + Lavalink.TrackExceptionThrown += Lavalink_TrackExceptionThrown; + } + + private LavalinkService Lavalink { get; } + + private ConcurrentDictionary MusicData { get; } + + public async Task GetOrCreateDataAsync(DiscordGuild server) + { + if (MusicData.TryGetValue(server.Id, out var player)) + return player; + + return MusicData.AddOrUpdate(server.Id, new MusicPlayer(Lavalink), (k, v) => v); + } + + public Task GetTracksAsync(Uri uri) + { + return Lavalink.Node.Rest.GetTracksAsync(uri); + } + + private async Task Lavalink_TrackExceptionThrown(LavalinkGuildConnection con, TrackExceptionEventArgs e) + { + if (e.Player?.Guild == null) return; + + if (!MusicData.TryGetValue(e.Player.Guild.Id, out var gmd)) return; + + await gmd.CommandChannel.SendMessageAsync( + $"A problem occurred while playing {Formatter.Bold(Formatter.Sanitize(e.Track.Title))} by {Formatter.Bold(Formatter.Sanitize(e.Track.Author))}:\n{e.Error}"); + } + + public static string ToDurationString(TimeSpan ts) + { + return ts.ToString(ts.TotalHours >= 1 ? @"h\:mm\:ss" : @"m\:ss"); + } + } +} \ No newline at end of file diff --git a/src/FlawBOT.Framework/Services/Games/PokemonService.cs b/src/FlawBOT/Services/Games/PokemonService.cs similarity index 53% rename from src/FlawBOT.Framework/Services/Games/PokemonService.cs rename to src/FlawBOT/Services/Games/PokemonService.cs index 29fb8881..52ed6e9a 100644 --- a/src/FlawBOT.Framework/Services/Games/PokemonService.cs +++ b/src/FlawBOT/Services/Games/PokemonService.cs @@ -2,47 +2,52 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Properties; -using Newtonsoft.Json; +using FlawBOT.Properties; using PokemonTcgSdk; using PokemonTcgSdk.Models; -using Card = PokemonTcgSdk.Card; -namespace FlawBOT.Framework.Services +namespace FlawBOT.Services { public class PokemonService : HttpHandler { private static List PokemonList { get; set; } = new List(); - public static async Task GetPokemonCardsAsync(string query = "") + /// + /// Call the Pokémon TCG API for a set of cards. + /// + public static async Task GetPokemonCardsAsync(string query = "") { + // If the user did not provide a search query, pick at random. query ??= GetRandomPokemon(); - var results = await Http - .GetStringAsync(Resources.API_PokemonTCG + "?name=" + query.ToLowerInvariant().Trim()) - .ConfigureAwait(false); - return JsonConvert.DeserializeObject(results); + return await Card.GetAsync(new Dictionary {{"name", query}}); } + /// + /// Return a Pokémon card based on the Id provided. + /// public static PokemonCard GetExactPokemon(string cardId) { return Card.Find(cardId).Card; } + /// + /// Pick a random Pokémon from the list. + /// private static string GetRandomPokemon() { - var random = new Random(); - return PokemonList[random.Next(0, PokemonList.Count)]; + return PokemonList[new Random().Next(0, PokemonList.Count)]; } + /// + /// Update the list of Pokémon cards. + /// public static async Task UpdatePokemonListAsync() { try { - var list = await Http.GetStringAsync(Resources.API_Pokemon).ConfigureAwait(false); - var results = JsonConvert.DeserializeObject(list).Results; PokemonList.Clear(); - foreach (var pokemon in results.Where(pokemon => !string.IsNullOrWhiteSpace(pokemon.Name))) + var cards = await Card.AllAsync(); + foreach (var pokemon in cards) PokemonList.Add(pokemon.Name); PokemonList = PokemonList.Distinct().ToList(); return true; diff --git a/src/FlawBOT.Framework/Services/Games/SpeedrunService.cs b/src/FlawBOT/Services/Games/SpeedrunService.cs similarity index 86% rename from src/FlawBOT.Framework/Services/Games/SpeedrunService.cs rename to src/FlawBOT/Services/Games/SpeedrunService.cs index 6ddcd1d8..5fbacbc8 100644 --- a/src/FlawBOT.Framework/Services/Games/SpeedrunService.cs +++ b/src/FlawBOT/Services/Games/SpeedrunService.cs @@ -3,11 +3,11 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Properties; +using FlawBOT.Models; +using FlawBOT.Properties; using Newtonsoft.Json; -namespace FlawBOT.Framework.Services +namespace FlawBOT.Services { public class SpeedrunService : HttpHandler { @@ -20,7 +20,7 @@ public static async Task GetSpeedrunGameAsync(string query) try { var results = await Http - .GetStringAsync(Resources.API_Speedrun + "games?name=" + Uri.EscapeUriString(query.Trim())) + .GetStringAsync(string.Format(Resources.URL_Speedrun, Uri.EscapeUriString(query.Trim()))) .ConfigureAwait(false); return JsonConvert.DeserializeObject(results); } @@ -55,7 +55,7 @@ public static async Task GetSpeedrunGameIdAsync(string query) try { var results = await Http - .GetStringAsync(Resources.API_Speedrun + "games?name=" + Uri.EscapeUriString(query.Trim())) + .GetStringAsync(string.Format(Resources.URL_Speedrun, Uri.EscapeUriString(query.Trim()))) .ConfigureAwait(false); return JsonConvert.DeserializeObject(results).Data.First().Id; } @@ -79,7 +79,8 @@ public static async Task GetSpeedrunExtraAsync(List queryList, S foreach (var query in queryList.Take(3)) { var output = await Http - .GetStringAsync(Resources.API_Speedrun + search.ToString().ToLowerInvariant() + "/" + query) + .GetStringAsync(string.Format(Resources.URL_Speedrun_Extras, + search.ToString().ToLowerInvariant(), query)) .ConfigureAwait(false); var name = JsonConvert.DeserializeObject(output).Data.Name; results.Append(name).Append(!query.Equals(queryList.Take(3).Last()) ? ", " : string.Empty); diff --git a/src/FlawBOT/Services/Games/SteamService.cs b/src/FlawBOT/Services/Games/SteamService.cs new file mode 100644 index 00000000..7f3186aa --- /dev/null +++ b/src/FlawBOT/Services/Games/SteamService.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using FlawBOT.Common; +using FlawBOT.Properties; +using Microsoft.Extensions.Options; +using Steam.Models; +using Steam.Models.SteamCommunity; +using Steam.Models.SteamStore; +using SteamWebAPI2.Interfaces; +using SteamWebAPI2.Utilities; + +namespace FlawBOT.Services +{ + public class SteamService : HttpHandler + { + private static SteamWebInterfaceFactory _steamInterface; + + private static ISteamWebResponse> SteamAppList { get; set; } = + new SteamWebResponse>(); + + #region STORE + + /// + /// Call the Steam API for data on a given game title. + /// + public static async Task GetSteamAppAsync(string query) + { + try + { + var appId = SteamAppList.Data + .First(n => string.Equals(n.Name, query, StringComparison.InvariantCultureIgnoreCase)).AppId; + var factoryOptions = new SteamWebInterfaceFactoryOptions + { + SteamWebApiKey = SharedData.Tokens.SteamToken + }; + return await new SteamWebInterfaceFactory(Options.Create(factoryOptions)).CreateSteamStoreInterface() + .GetStoreAppDetailsAsync(appId).ConfigureAwait(false); + } + catch + { + return null; + } + } + + public static async Task UpdateSteamAppListAsync() + { + try + { + _steamInterface = new SteamWebInterfaceFactory(SharedData.Tokens.SteamToken); + SteamAppList = await _steamInterface.CreateSteamWebInterface(new HttpClient()) + .GetAppListAsync(); + return true; + } + catch (Exception ex) + { + Console.WriteLine(Resources.ERR_STEAM_LIST, ex.Message); + return false; + } + } + + #endregion STORE + + #region USERS + + /// + /// Call the Steam API for summary data on a given user. + /// + public static async Task> GetSteamProfileAsync(string query) + { + try + { + _steamInterface = new SteamWebInterfaceFactory(SharedData.Tokens.SteamToken); + var steam = _steamInterface.CreateSteamWebInterface(new HttpClient()); + var userId = ulong.TryParse(query, out var steamId) ? steamId : 0; + if (userId != 0) return await steam.GetPlayerSummaryAsync(userId).ConfigureAwait(false); + var data = GetSteamUserId(query).Result; + if (data is null) return null; + return await steam.GetPlayerSummaryAsync(data.Data).ConfigureAwait(false); + } + catch + { + return null; + } + } + + /// + /// Call the Steam API for the id of a given user. + /// + public static async Task> GetSteamUserId(string query) + { + try + { + var steam = _steamInterface.CreateSteamWebInterface(new HttpClient()); + return await steam.ResolveVanityUrlAsync(query.Replace(" ", "")).ConfigureAwait(false) ?? null; + } + catch + { + return null; + } + } + + #endregion USERS + } +} \ No newline at end of file diff --git a/src/FlawBOT/Services/Games/TeamFortressService.cs b/src/FlawBOT/Services/Games/TeamFortressService.cs new file mode 100644 index 00000000..930aca91 --- /dev/null +++ b/src/FlawBOT/Services/Games/TeamFortressService.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using FlawBOT.Common; +using FlawBOT.Properties; +using SteamWebAPI2.Interfaces; +using SteamWebAPI2.Models.GameEconomy; +using SteamWebAPI2.Utilities; +using TeamworkTF.Sharp; + +namespace FlawBOT.Services +{ + public class TeamFortressService : HttpHandler + { + private static SteamWebInterfaceFactory _steamInterface; + private static List ItemSchemaList { get; } = new List(); + + #region NEWS + + public static async Task> GetNewsArticlesAsync(int page = 0, string provider = "") + { + List results; + if (page > 0) + results = await new TeamworkClient(SharedData.Tokens.TeamworkToken).GetNewsByPageAsync(page) + .ConfigureAwait(false); + else if (provider != string.Empty) + results = await new TeamworkClient(SharedData.Tokens.TeamworkToken).GetNewsByProviderAsync(provider) + .ConfigureAwait(false); + else + results = await new TeamworkClient(SharedData.Tokens.TeamworkToken).GetNewsOverviewAsync() + .ConfigureAwait(false); + return results.Where(x => x.Type != "tf2-notification").ToList(); + } + + #endregion NEWS + + #region CREATORS + + public static async Task> GetContentCreatorAsync(ulong query) + { + return await new TeamworkClient(SharedData.Tokens.TeamworkToken).GetYouTubeCreatorAsync(query.ToString()) + .ConfigureAwait(false); + } + + #endregion CREATORS + + #region SCHEMA + + public static SchemaItem GetSchemaItem(string query) + { + return ItemSchemaList.Find(n => n.ItemName.Contains(query, StringComparison.InvariantCultureIgnoreCase)); + } + + public static async Task UpdateTf2SchemaAsync() + { + try + { + _steamInterface = new SteamWebInterfaceFactory(SharedData.Tokens.SteamToken); + var steam = _steamInterface.CreateSteamWebInterface(AppId.TeamFortress2, new HttpClient()); + var games = await steam.GetSchemaItemsForTF2Async().ConfigureAwait(false); + ItemSchemaList.Clear(); + foreach (var game in games.Data.Result.Items) + if (!string.IsNullOrWhiteSpace(game.Name)) + ItemSchemaList.Add(game); + return true; + } + catch (Exception ex) + { + Console.WriteLine(Resources.ERR_TF2_LIST, ex.Message); + return false; + } + } + + #endregion SCHEMA + + #region SERVERS + + public static async Task GetGameModeInfoAsync(string query) + { + return await new TeamworkClient(SharedData.Tokens.TeamworkToken).GetGameModeAsync(query) + .ConfigureAwait(false); + } + + public static async Task> GetServersByGameModeAsync(string query) + { + return await new TeamworkClient(SharedData.Tokens.TeamworkToken).GetServerListByGameModeAsync(query) + .ConfigureAwait(false); + } + + public static async Task> GetCustomServerListsAsync() + { + return await new TeamworkClient(SharedData.Tokens.TeamworkToken).GetServerListsAsync() + .ConfigureAwait(false); + } + + public static string GetServerInfo(string address) + { + return new TeamworkClient(SharedData.Tokens.TeamworkToken).GetServerBanner(address); + } + + #endregion SERVERS + + #region MAPS + + public static async Task GetMapStatsAsync(string query) + { + var map = new TeamworkClient(SharedData.Tokens.TeamworkToken).GetMapsBySearchAsync(query).Result + .FirstOrDefault()?.Name; + return await new TeamworkClient(SharedData.Tokens.TeamworkToken).GetMapStatsAsync(map) + .ConfigureAwait(false); + } + + public static async Task GetMapThumbnailAsync(string query) + { + return await new TeamworkClient(SharedData.Tokens.TeamworkToken).GetMapThumbnailAsync(query) + .ConfigureAwait(false); + } + + #endregion MAPS + } +} \ No newline at end of file diff --git a/src/FlawBOT.Framework/Services/Misc/MiscService.cs b/src/FlawBOT/Services/Misc/MiscService.cs similarity index 73% rename from src/FlawBOT.Framework/Services/Misc/MiscService.cs rename to src/FlawBOT/Services/Misc/MiscService.cs index 170c1322..7bb2af8e 100644 --- a/src/FlawBOT.Framework/Services/Misc/MiscService.cs +++ b/src/FlawBOT/Services/Misc/MiscService.cs @@ -1,14 +1,14 @@ using System; using System.Collections.Immutable; using System.Threading.Tasks; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Properties; +using FlawBOT.Models; +using FlawBOT.Properties; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace FlawBOT.Framework.Services +namespace FlawBOT.Services { - public static class EightBallService + public class MiscService : HttpHandler { private static ImmutableArray _answers = new[] { @@ -39,27 +39,21 @@ public static string GetRandomAnswer() var random = new Random(); return _answers[random.Next(_answers.Length)]; } - } - public class CatService : HttpHandler - { public static async Task GetCatFactAsync() { - return await Http.GetStringAsync(Resources.API_CatFacts).ConfigureAwait(false); + return await Http.GetStringAsync(Resources.URL_CatFacts).ConfigureAwait(false); } public static async Task GetCatPhotoAsync() { - var results = await Http.GetStringAsync(Resources.API_CatPhoto).ConfigureAwait(false); + var results = await Http.GetStringAsync(Resources.URL_CatPhoto).ConfigureAwait(false); return JObject.Parse(results)["file"]?.ToString(); } - } - public class DogService : HttpHandler - { public static async Task GetDogPhotoAsync() { - var results = await Http.GetStringAsync(Resources.API_DogPhoto).ConfigureAwait(false); + var results = await Http.GetStringAsync(Resources.URL_DogPhoto).ConfigureAwait(false); return JsonConvert.DeserializeObject(results); } } diff --git a/src/FlawBOT.Framework/Services/Search/AmiiboService.cs b/src/FlawBOT/Services/Search/AmiiboService.cs similarity index 67% rename from src/FlawBOT.Framework/Services/Search/AmiiboService.cs rename to src/FlawBOT/Services/Search/AmiiboService.cs index 69c58908..d6b0ae5c 100644 --- a/src/FlawBOT.Framework/Services/Search/AmiiboService.cs +++ b/src/FlawBOT/Services/Search/AmiiboService.cs @@ -1,9 +1,9 @@ using System.Threading.Tasks; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Properties; +using FlawBOT.Models; +using FlawBOT.Properties; using Newtonsoft.Json; -namespace FlawBOT.Framework.Services +namespace FlawBOT.Services { public class AmiiboService : HttpHandler { @@ -11,7 +11,7 @@ public static async Task GetAmiiboDataAsync(string query) { try { - var results = await Http.GetStringAsync(Resources.API_Amiibo + "?name=" + query.ToLowerInvariant()) + var results = await Http.GetStringAsync(string.Format(Resources.URL_Amiibo, query.ToLowerInvariant())) .ConfigureAwait(false); return JsonConvert.DeserializeObject(results); } diff --git a/src/FlawBOT.Framework/Services/Search/DictionaryService.cs b/src/FlawBOT/Services/Search/DictionaryService.cs similarity index 65% rename from src/FlawBOT.Framework/Services/Search/DictionaryService.cs rename to src/FlawBOT/Services/Search/DictionaryService.cs index 50892cca..325e619e 100644 --- a/src/FlawBOT.Framework/Services/Search/DictionaryService.cs +++ b/src/FlawBOT/Services/Search/DictionaryService.cs @@ -1,17 +1,17 @@ using System.Net; using System.Threading.Tasks; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Properties; +using FlawBOT.Models; +using FlawBOT.Properties; using Newtonsoft.Json; -namespace FlawBOT.Framework.Services +namespace FlawBOT.Services { public class DictionaryService : HttpHandler { public static async Task GetDictionaryDefinitionAsync(string query) { var results = await Http - .GetStringAsync(Resources.API_Dictionary + "?term=" + WebUtility.UrlEncode(query.Trim())) + .GetStringAsync(string.Format(Resources.URL_Dictionary, WebUtility.UrlEncode(query.Trim()))) .ConfigureAwait(false); return JsonConvert.DeserializeObject(results); } diff --git a/src/FlawBOT.Framework/Services/Search/ImgurService.cs b/src/FlawBOT/Services/Search/ImgurService.cs similarity index 75% rename from src/FlawBOT.Framework/Services/Search/ImgurService.cs rename to src/FlawBOT/Services/Search/ImgurService.cs index 55446994..41db88a9 100644 --- a/src/FlawBOT.Framework/Services/Search/ImgurService.cs +++ b/src/FlawBOT/Services/Search/ImgurService.cs @@ -1,20 +1,21 @@ using System; using System.Linq; using System.Threading.Tasks; -using FlawBOT.Framework.Models; +using FlawBOT.Common; using Imgur.API.Authentication.Impl; using Imgur.API.Endpoints.Impl; using Imgur.API.Enums; using Imgur.API.Models; -namespace FlawBOT.Framework.Services +namespace FlawBOT.Services { public static class ImgurService { - public static async Task GetImgurGalleryAsync(string query, GallerySortOrder order = GallerySortOrder.Top, TimeWindow time = TimeWindow.All) + public static async Task GetImgurGalleryAsync(string query, + GallerySortOrder order = GallerySortOrder.Top, TimeWindow time = TimeWindow.All) { var random = new Random(); - var imgur = new ImgurClient(TokenHandler.Tokens.ImgurToken); + var imgur = new ImgurClient(SharedData.Tokens.ImgurToken); var endpoint = new GalleryEndpoint(imgur); var gallery = string.IsNullOrWhiteSpace(query) ? (await endpoint.GetRandomGalleryAsync().ConfigureAwait(false)).ToList() diff --git a/src/FlawBOT.Framework/Services/Search/NASAService.cs b/src/FlawBOT/Services/Search/NASAService.cs similarity index 57% rename from src/FlawBOT.Framework/Services/Search/NASAService.cs rename to src/FlawBOT/Services/Search/NASAService.cs index b659b94d..b73b1ea9 100644 --- a/src/FlawBOT.Framework/Services/Search/NASAService.cs +++ b/src/FlawBOT/Services/Search/NASAService.cs @@ -1,15 +1,16 @@ using System.Threading.Tasks; -using FlawBOT.Framework.Models; -using FlawBOT.Framework.Properties; +using FlawBOT.Common; +using FlawBOT.Models; +using FlawBOT.Properties; using Newtonsoft.Json; -namespace FlawBOT.Framework.Services +namespace FlawBOT.Services { public class NasaService : HttpHandler { public static async Task GetNasaImageAsync() { - var results = await Http.GetStringAsync(Resources.API_NASA + "?api_key=" + TokenHandler.Tokens.NasaToken) + var results = await Http.GetStringAsync(string.Format(Resources.URL_NASA, SharedData.Tokens.NasaToken)) .ConfigureAwait(false); return JsonConvert.DeserializeObject(results); } diff --git a/src/FlawBOT/Services/Search/NewsService.cs b/src/FlawBOT/Services/Search/NewsService.cs new file mode 100644 index 00000000..093c3613 --- /dev/null +++ b/src/FlawBOT/Services/Search/NewsService.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; +using FlawBOT.Common; +using FlawBOT.Models; +using FlawBOT.Properties; +using Newtonsoft.Json; + +namespace FlawBOT.Services +{ + public class NewsService : HttpHandler + { + public static async Task GetNewsDataAsync(string query = "") + { + var results = await Http + .GetStringAsync(string.Format(Resources.URL_News, query, SharedData.Tokens.NewsToken)) + .ConfigureAwait(false); + return JsonConvert.DeserializeObject(results); + } + } +} \ No newline at end of file diff --git a/src/FlawBOT/Services/Search/OMDBService.cs b/src/FlawBOT/Services/Search/OMDBService.cs new file mode 100644 index 00000000..dd5b7f4a --- /dev/null +++ b/src/FlawBOT/Services/Search/OMDBService.cs @@ -0,0 +1,22 @@ +using System.Threading.Tasks; +using FlawBOT.Common; +using OMDbSharp; +using OMDbSharp.Objects; + +namespace FlawBOT.Services +{ + public static class OmdbService + { + public static async Task GetMovieDataAsync(string query) + { + return await new OMDbClient(SharedData.Tokens.OmdbToken, false) + .GetItemByTitle(query.ToLowerInvariant().Replace("&", "%26")).ConfigureAwait(false); + } + + public static async Task GetMovieListAsync(string query) + { + return await new OMDbClient(SharedData.Tokens.OmdbToken, false) + .GetItemList(query.ToLowerInvariant().Replace("&", "%26")).ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/src/FlawBOT.Framework/Services/Search/RedditService.cs b/src/FlawBOT/Services/Search/RedditService.cs similarity index 81% rename from src/FlawBOT.Framework/Services/Search/RedditService.cs rename to src/FlawBOT/Services/Search/RedditService.cs index 2b79da14..6265e421 100644 --- a/src/FlawBOT.Framework/Services/Search/RedditService.cs +++ b/src/FlawBOT/Services/Search/RedditService.cs @@ -3,9 +3,9 @@ using System.Linq; using System.ServiceModel.Syndication; using System.Xml; -using FlawBOT.Framework.Properties; +using FlawBOT.Properties; -namespace FlawBOT.Framework.Services +namespace FlawBOT.Services { public static class RedditService { @@ -13,7 +13,7 @@ public static List GetResults(string query, RedditCategory cate { try { - query = $"https://www.reddit.com/r/{query.ToLowerInvariant()}/{GetPostCategory(category)}.rss"; + query = string.Format(Resources.URL_Reddit, query.ToLowerInvariant(), GetPostCategory(category)); using var reader = XmlReader.Create(query); return SyndicationFeed.Load(reader).Items?.ToList(); } @@ -30,7 +30,7 @@ private static string GetPostCategory(this RedditCategory category) RedditCategory.Hot => "hot", RedditCategory.New => "new", RedditCategory.Top => "top", - _ => throw new ArgumentException(Resources.ERR_REDDIT_UNKNOWN_CAT, nameof(category)) + _ => throw new ArgumentException(Resources.ERR_REDDIT_UNKNOWN, nameof(category)) }; } } diff --git a/src/FlawBOT.Framework/Services/Search/SimpsonsService.cs b/src/FlawBOT/Services/Search/SimpsonsService.cs similarity index 50% rename from src/FlawBOT.Framework/Services/Search/SimpsonsService.cs rename to src/FlawBOT/Services/Search/SimpsonsService.cs index 48ef18b0..f6d31119 100644 --- a/src/FlawBOT.Framework/Services/Search/SimpsonsService.cs +++ b/src/FlawBOT/Services/Search/SimpsonsService.cs @@ -1,10 +1,12 @@ using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using DSharpPlus.Entities; -using FlawBOT.Framework.Models; +using FlawBOT.Models; +using FlawBOT.Properties; using Newtonsoft.Json; -namespace FlawBOT.Framework.Services +namespace FlawBOT.Services { public class SimpsonsService : HttpHandler { @@ -17,33 +19,39 @@ public enum SiteRoot public static async Task GetSimpsonsDataAsync(SiteRoot site) { - var output = await Http.GetStringAsync($"https://{site}.com/api/random").ConfigureAwait(false); + var output = await Http.GetStringAsync(string.Format(Resources.URL_Simpsons_Random, site)) + .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").ConfigureAwait(false); + var result = await Http.GetStringAsync(string.Format(Resources.URL_Simpsons_Random, site)) + .ConfigureAwait(false); var content = JsonConvert.DeserializeObject(result); var framesResult = await Http - .GetStringAsync($"https://{site}.com/api/frames/{content.Episode.Key}/{content.Frame.Timestamp}/3000/4000") + .GetStringAsync(string.Format(Resources.URL_Simpsons_Frames, site, content.Episode.Key, + content.Frame.Timestamp)) .ConfigureAwait(false); var frames = JsonConvert.DeserializeObject>(framesResult); - var start = frames[0].Timestamp; - var end = frames[^1].Timestamp; - return $"https://{site}.com/gif/{content.Episode.Key}/{start}/{end}.gif"; + var start = frames.FirstOrDefault()?.Timestamp; + var end = frames.LastOrDefault()?.Timestamp; + return string.Format(Resources.URL_Simpsons_Result, site, content.Episode.Key, start, end); } private static DiscordEmbedBuilder EmbedSimpsonsEpisode(SimpsonsData data, SiteRoot site) { var output = new DiscordEmbedBuilder() .WithTitle(data.Episode.Title) - .AddField("Season/Episode", data.Episode.Key, true) - .AddField("Writer", !string.IsNullOrWhiteSpace(data.Episode.Writer) ? data.Episode.Writer : "Unknown",true) - .AddField("Director", !string.IsNullOrWhiteSpace(data.Episode.Director) ? data.Episode.Director : "Unknown", true) - .WithFooter("Original Air Date: " + data.Episode.OriginalAirDate) - .WithImageUrl($"https://{site}.com/img/{data.Frame.Episode}/{data.Frame.Timestamp}.jpg") + .AddField("Original Air Date", data.Episode.OriginalAirDate, true) + .AddField("Writer", !string.IsNullOrWhiteSpace(data.Episode.Writer) ? data.Episode.Writer : "Unknown", + true) + .AddField("Director", + !string.IsNullOrWhiteSpace(data.Episode.Director) ? data.Episode.Director : "Unknown", true) + .WithImageUrl(string.Format(Resources.URL_Simpsons_Image, site, data.Frame.Episode, + data.Frame.Timestamp)) + .WithFooter(data.Episode.Key) .WithColor(new DiscordColor("#FFBB22")) .WithUrl(data.Episode.WikiLink); return output; diff --git a/src/FlawBOT/Services/Search/TwitchService.cs b/src/FlawBOT/Services/Search/TwitchService.cs new file mode 100644 index 00000000..90a6b494 --- /dev/null +++ b/src/FlawBOT/Services/Search/TwitchService.cs @@ -0,0 +1,17 @@ +using System.Threading.Tasks; +using FlawBOT.Common; +using TwitchLib.Api; +using TwitchLib.Api.V5.Models.Search; + +namespace FlawBOT.Services +{ + public class TwitchService : HttpHandler + { + public static async Task GetTwitchDataAsync(string query) + { + var service = new TwitchAPI(); + service.Settings.ClientId = SharedData.Tokens.TwitchToken; + return await service.V5.Search.SearchStreamsAsync(query).ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/src/FlawBOT/Services/Search/WikipediaService.cs b/src/FlawBOT/Services/Search/WikipediaService.cs new file mode 100644 index 00000000..4037c54c --- /dev/null +++ b/src/FlawBOT/Services/Search/WikipediaService.cs @@ -0,0 +1,14 @@ +using WikipediaNet; +using WikipediaNet.Objects; + +namespace FlawBOT.Services +{ + public class WikipediaService : HttpHandler + { + public static QueryResult GetWikipediaDataAsync(string query) + { + var wikipedia = new Wikipedia {Limit = 5}; + return wikipedia.Search(query); + } + } +} \ No newline at end of file diff --git a/src/FlawBOT/Services/Search/WorldService.cs b/src/FlawBOT/Services/Search/WorldService.cs new file mode 100644 index 00000000..a6788f71 --- /dev/null +++ b/src/FlawBOT/Services/Search/WorldService.cs @@ -0,0 +1,39 @@ +using System.Net; +using System.Threading.Tasks; +using FlawBOT.Common; +using FlawBOT.Models; +using FlawBOT.Properties; +using Newtonsoft.Json; + +namespace FlawBOT.Services +{ + public class WorldService : HttpHandler + { + public static async Task GetIpLocationAsync(IPAddress query) + { + var result = await Http.GetStringAsync(string.Format(Resources.URL_IPStack, query)) + .ConfigureAwait(false); + return JsonConvert.DeserializeObject(result); + } + + public static async Task GetWeatherDataAsync(string query) + { + try + { + var results = await Http + .GetStringAsync(string.Format(Resources.URL_Weather, SharedData.Tokens.WeatherToken, query)) + .ConfigureAwait(false); + return JsonConvert.DeserializeObject(results); + } + catch + { + return null; + } + } + + public static double CelsiusToFahrenheit(double cel) + { + return cel * 1.8f + 32; + } + } +} \ No newline at end of file diff --git a/src/FlawBOT.Framework/Services/Search/YouTubeService.cs b/src/FlawBOT/Services/Search/YouTubeService.cs similarity index 50% rename from src/FlawBOT.Framework/Services/Search/YouTubeService.cs rename to src/FlawBOT/Services/Search/YouTubeService.cs index f45caada..b8034887 100644 --- a/src/FlawBOT.Framework/Services/Search/YouTubeService.cs +++ b/src/FlawBOT/Services/Search/YouTubeService.cs @@ -1,22 +1,29 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Net; +using System.Text; using System.Threading.Tasks; using DSharpPlus.Entities; -using FlawBOT.Framework.Models; +using FlawBOT.Common; +using FlawBOT.Models; +using FlawBOT.Properties; using Google.Apis.Services; using Google.Apis.YouTube.v3; using Google.Apis.YouTube.v3.Data; +using Newtonsoft.Json.Linq; -namespace FlawBOT.Framework.Services +namespace FlawBOT.Services { - public class YoutubeService + public class YoutubeService : HttpHandler { public YoutubeService() { YouTube = new YouTubeService(new BaseClientService.Initializer { - ApiKey = TokenHandler.Tokens.GoogleToken, - ApplicationName = "FlawBOT" + ApiKey = SharedData.Tokens.YouTubeToken, + ApplicationName = SharedData.Name }); } @@ -25,8 +32,8 @@ public YoutubeService() public async Task GetFirstVideoResultAsync(string query) { var results = await GetResultsAsync(query, 1, "video").ConfigureAwait(false); - if (results is null || results.Count == 0) return ":warning: No results found!"; - return "https://www.youtube.com/watch?v=" + results[0].Id.VideoId; + if (results is null || results.Count == 0) return Resources.NOT_FOUND_COMMON; + return string.Format(Resources.URL_YouTube_Video, results.FirstOrDefault()?.Id.VideoId); } public async Task GetEmbeddedResults(string query, int amount, string type = null) @@ -35,7 +42,7 @@ public async Task GetEmbeddedResults(string query, int amount, str if (results is null || results.Count == 0) return new DiscordEmbedBuilder { - Description = ":warning: No results found!", + Description = Resources.NOT_FOUND_COMMON, Color = DiscordColor.Red }; results = results.Count > 25 ? results.Take(25).ToList() : results; @@ -44,15 +51,18 @@ public async Task GetEmbeddedResults(string query, int amount, str switch (result.Id.Kind) { case "youtube#video": - output.AddField(result.Snippet.Title, "https://www.youtube.com/watch?v=" + result.Id.VideoId); + output.AddField(result.Snippet.Title, + string.Format(Resources.URL_YouTube_Video, result.Id.VideoId)); break; case "youtube#channel": - output.AddField(result.Snippet.Title, "https://www.youtube.com/channel/" + result.Id.ChannelId); + output.AddField(result.Snippet.Title, + string.Format(Resources.URL_YouTube_Channel, result.Id.ChannelId)); break; case "youtube#playlist": - output.AddField(result.Snippet.Title, "https://www.youtube.com/playlist?list=" + result.Id.PlaylistId); + output.AddField(result.Snippet.Title, + string.Format(Resources.URL_YouTube_Playlist, result.Id.PlaylistId)); break; default: @@ -74,5 +84,26 @@ private async Task> GetResultsAsync(string query, int amount, videos.AddRange(searchListResponse.Items); return videos; } + + public async Task> GetMusicDataAsync(string term) + { + var uri = new Uri( + $"https://www.googleapis.com/youtube/v3/search?part=snippet&maxResults=5&type=video&fields=items(id(videoId),snippet(title,channelTitle))&key={YouTube.ApiKey}&q={WebUtility.UrlEncode(term)}"); + + var json = "{}"; + Http.BaseAddress = new Uri("https://www.googleapis.com/youtube/v3/search"); + Http.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", SharedData.Name); + using (var req = await Http.GetAsync(uri)) + using (var res = await req.Content.ReadAsStreamAsync()) + using (var sr = new StreamReader(res, Encoding.UTF8)) + { + json = await sr.ReadToEndAsync(); + } + + var jsonData = JObject.Parse(json); + var data = jsonData["items"].ToObject>(); + + return data.Select(x => new YouTubeData(x.Snippet.Title, x.Snippet.Author, x.Id.VideoId)); + } } } \ No newline at end of file diff --git a/src/FlawBOT/application.yml b/src/FlawBOT/application.yml new file mode 100644 index 00000000..9a3dd7a5 --- /dev/null +++ b/src/FlawBOT/application.yml @@ -0,0 +1,44 @@ +server: # REST and WS server + port: 2333 + address: 127.0.0.1 +spring: + main: + banner-mode: log +lavalink: + server: + password: "youshallnotpass" + sources: + youtube: true + bandcamp: true + soundcloud: true + twitch: true + vimeo: true + mixer: true + http: true + local: false + bufferDurationMs: 400 + youtubePlaylistLoadLimit: 6 # Number of pages at 100 each + youtubeSearchEnabled: true + soundcloudSearchEnabled: true + gc-warnings: true + +metrics: + prometheus: + enabled: false + endpoint: /metrics + +sentry: + dsn: "" +# tags: +# some_key: some_value +# another_key: another_value + +logging: + file: + max-history: 30 + max-size: 1GB + path: ./logs/ + + level: + root: INFO + lavalink: INFO \ No newline at end of file diff --git a/src/FlawBOT.Core/config.json b/src/FlawBOT/config.json similarity index 73% rename from src/FlawBOT.Core/config.json rename to src/FlawBOT/config.json index fc95eec1..6d24d314 100644 --- a/src/FlawBOT.Core/config.json +++ b/src/FlawBOT/config.json @@ -1,12 +1,13 @@ { "prefix": ".", "discord": null, - "google": null, "steam": null, "imgur": null, "omdb": null, "twitch": null, "nasa": null, "teamworktf": null, - "news": null + "news": null, + "weather": null, + "youtube": null } \ No newline at end of file diff --git a/src/FlawBOT.Core/icon.ico b/src/FlawBOT/favicon.ico similarity index 100% rename from src/FlawBOT.Core/icon.ico rename to src/FlawBOT/favicon.ico