diff --git a/.deploy.yml b/.deploy.yml index cf0e485..5fb394d 100644 --- a/.deploy.yml +++ b/.deploy.yml @@ -7,13 +7,13 @@ ansible_python_interpreter: /usr/bin/python3 tasks: - name: Login to Gitlab Docker Registry - docker_login: + 'community.docker.docker_login': registry_url: "{{ lookup('env', 'CI_REGISTRY') }}" username: "{{ lookup('env', 'CI_REGISTRY_USER') }}" password: "{{ lookup('env', 'CI_REGISTRY_PASSWORD') }}" reauthorize: yes - name: Replace Prod Container - docker_container: + 'community.docker.docker_container': name: GeekbotProd image: "{{ lookup('env', 'IMAGE_TAG') }}" recreate: yes @@ -34,5 +34,5 @@ GEEKBOT_SENTRY: "{{ lookup('env', 'GEEKBOT_SENTRY') }}" GEEKBOT_DB_REDSHIFT_COMPAT: "true" - name: Cleanup Old Container - docker_prune: + 'community.docker.docker_prune': images: yes diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c4d8b15..a774f5e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,12 +5,12 @@ stages: - ops variables: - VERSION: 4.3.0-$CI_COMMIT_SHORT_SHA + VERSION: 4.4.0-V$CI_COMMIT_SHORT_SHA IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG Build: stage: build - image: mcr.microsoft.com/dotnet/sdk:5.0 + image: mcr.microsoft.com/dotnet/sdk:6.0 artifacts: expire_in: 1h paths: @@ -18,7 +18,7 @@ Build: script: - dotnet restore - dotnet test tests - - dotnet publish --version-suffix $VERSION -r linux-x64 -c Release -o ./app ./src/Bot/ + - dotnet publish --version-suffix "$VERSION" -r linux-x64 -c Release -p:DebugType=embedded --no-self-contained -o ./app ./src/Startup/ Package: stage: docker @@ -34,7 +34,7 @@ Package: Deploy: stage: deploy - image: ansible/ansible-runner + image: quay.io/ansible/ansible-runner:stable-2.12-latest only: - master variables: @@ -46,6 +46,7 @@ Deploy: - chmod -R 600 /root/.ssh - ssh-keyscan -p 65432 $PROD_IP > /root/.ssh/known_hosts script: + - ansible-galaxy collection install -r ansible-requirements.yml - ansible-playbook -i $PROD_IP, .deploy.yml Sentry: diff --git a/Dockerfile b/Dockerfile index 245e06b..39529ff 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/aspnet:5.0 +FROM mcr.microsoft.com/dotnet/aspnet:6.0 COPY ./app /app/ diff --git a/Geekbot.net.sln b/Geekbot.net.sln index 4a5b912..f33d887 100644 --- a/Geekbot.net.sln +++ b/Geekbot.net.sln @@ -11,6 +11,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Web", "src\Web\Web.csproj", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bot", "src\Bot\Bot.csproj", "{DBF79896-9F7F-443D-B336-155E276DFF16}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Commands", "src\Commands\Commands.csproj", "{7C771DFE-912A-4276-B0A6-047E09603F1E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Interactions", "src\Interactions\Interactions.csproj", "{FF6859D9-C539-4910-BE1E-9ECFED2F46FA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Startup", "src\Startup\Startup.csproj", "{A691B018-4B19-4A7A-A0F6-DBB17641254F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -33,6 +39,18 @@ Global {DBF79896-9F7F-443D-B336-155E276DFF16}.Debug|Any CPU.Build.0 = Debug|Any CPU {DBF79896-9F7F-443D-B336-155E276DFF16}.Release|Any CPU.ActiveCfg = Release|Any CPU {DBF79896-9F7F-443D-B336-155E276DFF16}.Release|Any CPU.Build.0 = Release|Any CPU + {7C771DFE-912A-4276-B0A6-047E09603F1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C771DFE-912A-4276-B0A6-047E09603F1E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C771DFE-912A-4276-B0A6-047E09603F1E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7C771DFE-912A-4276-B0A6-047E09603F1E}.Release|Any CPU.Build.0 = Release|Any CPU + {FF6859D9-C539-4910-BE1E-9ECFED2F46FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FF6859D9-C539-4910-BE1E-9ECFED2F46FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FF6859D9-C539-4910-BE1E-9ECFED2F46FA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FF6859D9-C539-4910-BE1E-9ECFED2F46FA}.Release|Any CPU.Build.0 = Release|Any CPU + {A691B018-4B19-4A7A-A0F6-DBB17641254F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A691B018-4B19-4A7A-A0F6-DBB17641254F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A691B018-4B19-4A7A-A0F6-DBB17641254F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A691B018-4B19-4A7A-A0F6-DBB17641254F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ansible-requirements.yml b/ansible-requirements.yml new file mode 100644 index 0000000..90d8fb4 --- /dev/null +++ b/ansible-requirements.yml @@ -0,0 +1,3 @@ +collections: + - name: community.docker + version: 2.7.0 \ No newline at end of file diff --git a/src/Bot/Bot.csproj b/src/Bot/Bot.csproj index 4cd5b39..2219b32 100644 --- a/src/Bot/Bot.csproj +++ b/src/Bot/Bot.csproj @@ -1,34 +1,25 @@ - Exe - net5.0 - win-x64;linux-x64 - derp.ico + net6.0 $(VersionSuffix) Geekbot.Bot - Geekbot - $(VersionSuffix) + Geekbot.Bot + $(VersionSuffix) 0.0.0-DEV - Pizza and Coffee Studios - Pizza and Coffee Studios - A Discord bot - https://github.com/pizzaandcoffee/Geekbot.net NU1701 - git - https://geekbot.pizzaandcoffee.rocks - - - true + enable + True + Library + - - + + - - + @@ -36,113 +27,7 @@ + - - - - - ResXFileCodeGenerator - Ship.Designer.cs - - - ResXFileCodeGenerator - Rank.Designer.cs - - - ResXFileCodeGenerator - Karma.Designer.cs - - - ResXFileCodeGenerator - Internal.Designer.cs - - - ResXFileCodeGenerator - Cookies.Designer.cs - - - ResXFileCodeGenerator - Roll.Designer.cs - - - ResXFileCodeGenerator - Choose.Designer.cs - - - ResXFileCodeGenerator - Admin.Designer.cs - - - ResXFileCodeGenerator - Quote.Designer.cs - - - ResXFileCodeGenerator - Role.Designer.cs - - - ResXFileCodeGenerator - Stats.Designer.cs - - - - - True - True - ship.resx - - - True - True - Rank.resx - - - Ship.resx - - - True - True - Karma.resx - - - True - True - Internal.resx - - - True - True - Cookies.resx - - - True - True - Roll.resx - - - True - True - Choose.resx - - - True - True - Admin.resx - - - True - True - Quote.resx - - - True - True - Role.resx - - - True - True - Stats.resx - diff --git a/src/Bot/BotStartup.cs b/src/Bot/BotStartup.cs new file mode 100644 index 0000000..afb1a8a --- /dev/null +++ b/src/Bot/BotStartup.cs @@ -0,0 +1,127 @@ +using System.Reflection; +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using Geekbot.Bot.Handlers; +using Geekbot.Core; +using Geekbot.Core.Database; +using Geekbot.Core.GlobalSettings; +using Geekbot.Core.GuildSettingsManager; +using Geekbot.Core.Logger; +using Geekbot.Core.Logger.Adapters; +using Geekbot.Core.ReactionListener; +using Geekbot.Core.UserRepository; +using Microsoft.Extensions.DependencyInjection; + +namespace Geekbot.Bot; + +public class BotStartup +{ + private readonly IServiceCollection _serviceCollection; + private readonly GeekbotLogger _logger; + private readonly RunParameters _runParameters; + private readonly IGlobalSettings _globalSettings; + private DiscordSocketClient _client; + + public BotStartup(IServiceCollection serviceCollection, GeekbotLogger logger, RunParameters runParameters, IGlobalSettings globalSettings) + { + _serviceCollection = serviceCollection; + _logger = logger; + _runParameters = runParameters; + _globalSettings = globalSettings; + } + + public async Task Start() + { + _logger.Information(LogSource.Geekbot, "Connecting to Discord"); + SetupDiscordClient(); + await Login(); + await _client.SetGameAsync(_globalSettings.GetKey("Game")); + _logger.Information(LogSource.Geekbot, $"Now Connected as {_client.CurrentUser.Username} to {_client.Guilds.Count} Servers"); + + _logger.Information(LogSource.Geekbot, "Registering Gateway Handlers"); + await RegisterHandlers(); + + _logger.Information(LogSource.Geekbot, "Done and ready for use"); + await Task.Delay(-1); + } + + private void SetupDiscordClient() + { + _client = new DiscordSocketClient(new DiscordSocketConfig + { + GatewayIntents = GatewayIntents.DirectMessageReactions | + GatewayIntents.DirectMessages | + GatewayIntents.GuildMessageReactions | + GatewayIntents.GuildMessages | + GatewayIntents.GuildWebhooks | + GatewayIntents.GuildIntegrations | + GatewayIntents.GuildEmojis | + GatewayIntents.GuildBans | + GatewayIntents.Guilds | + GatewayIntents.GuildMembers, + LogLevel = LogSeverity.Verbose, + MessageCacheSize = 1000, + }); + + var discordLogger = new DiscordLogger(_logger); + _client.Log += discordLogger.Log; + } + + private async Task Login() + { + try + { + var token = await GetToken(); + await _client.LoginAsync(TokenType.Bot, token); + await _client.StartAsync(); + while (!_client.ConnectionState.Equals(ConnectionState.Connected)) await Task.Delay(25); + } + catch (Exception e) + { + _logger.Error(LogSource.Geekbot, "Could not connect to Discord", e); + Environment.Exit(GeekbotExitCode.CouldNotLogin.GetHashCode()); + } + } + + private async Task GetToken() + { + var token = _runParameters.Token ?? _globalSettings.GetKey("DiscordToken"); + if (string.IsNullOrEmpty(token)) + { + Console.Write("Your bot Token: "); + var newToken = Console.ReadLine(); + await _globalSettings.SetKey("DiscordToken", newToken); + await _globalSettings.SetKey("Game", "Ping Pong"); + token = newToken; + } + + return token; + } + + private async Task RegisterHandlers() + { + var applicationInfo = await _client.GetApplicationInfoAsync(); + + _serviceCollection.AddSingleton(_client); + var serviceProvider = _serviceCollection.BuildServiceProvider(); + + var commands = new CommandService(); + await commands.AddModulesAsync(Assembly.GetAssembly(typeof(BotStartup)), serviceProvider); + + var commandHandler = new CommandHandler(_client, _logger, serviceProvider, commands, applicationInfo, serviceProvider.GetService()); + var userHandler = new UserHandler(serviceProvider.GetService(), _logger, serviceProvider.GetService(), _client); + var reactionHandler = new ReactionHandler(serviceProvider.GetService()); + var statsHandler = new StatsHandler(_logger, serviceProvider.GetService()); + var messageDeletedHandler = new MessageDeletedHandler(serviceProvider.GetService(), _logger, _client); + + _client.MessageReceived += commandHandler.RunCommand; + _client.MessageDeleted += messageDeletedHandler.HandleMessageDeleted; + _client.UserJoined += userHandler.Joined; + _client.UserUpdated += userHandler.Updated; + _client.UserLeft += userHandler.Left; + _client.ReactionAdded += reactionHandler.Added; + _client.ReactionRemoved += reactionHandler.Removed; + if (!_runParameters.InMemory) _client.MessageReceived += statsHandler.UpdateStats; + } +} \ No newline at end of file diff --git a/src/Core/CommandPreconditions/DisableInDirectMessageAttribute.cs b/src/Bot/CommandPreconditions/DisableInDirectMessageAttribute.cs similarity index 93% rename from src/Core/CommandPreconditions/DisableInDirectMessageAttribute.cs rename to src/Bot/CommandPreconditions/DisableInDirectMessageAttribute.cs index 835e0b3..b75f4a1 100644 --- a/src/Core/CommandPreconditions/DisableInDirectMessageAttribute.cs +++ b/src/Bot/CommandPreconditions/DisableInDirectMessageAttribute.cs @@ -2,7 +2,7 @@ using System; using System.Threading.Tasks; using Discord.Commands; -namespace Geekbot.Core.CommandPreconditions +namespace Geekbot.Bot.CommandPreconditions { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] public class DisableInDirectMessageAttribute : PreconditionAttribute diff --git a/src/Bot/Commands/Admin/Admin.cs b/src/Bot/Commands/Admin/Admin.cs index 60ba8ac..43fb3c4 100644 --- a/src/Bot/Commands/Admin/Admin.cs +++ b/src/Bot/Commands/Admin/Admin.cs @@ -9,11 +9,12 @@ using System.Threading.Tasks; using Discord; using Discord.Commands; using Discord.WebSocket; +using Geekbot.Bot.CommandPreconditions; using Geekbot.Core; -using Geekbot.Core.CommandPreconditions; using Geekbot.Core.ErrorHandling; using Geekbot.Core.Extensions; using Geekbot.Core.GuildSettingsManager; +using Localization = Geekbot.Core.Localization; namespace Geekbot.Bot.Commands.Admin { diff --git a/src/Bot/Commands/Admin/Mod.cs b/src/Bot/Commands/Admin/Mod.cs deleted file mode 100644 index b642680..0000000 --- a/src/Bot/Commands/Admin/Mod.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Threading.Tasks; -using Discord; -using Discord.Commands; -using Geekbot.Core.CommandPreconditions; -using Geekbot.Core.ErrorHandling; - -namespace Geekbot.Bot.Commands.Admin -{ - [Group("mod")] - [RequireUserPermission(GuildPermission.KickMembers)] - [RequireUserPermission(GuildPermission.ManageMessages)] - [RequireUserPermission(GuildPermission.ManageRoles)] - [DisableInDirectMessage] - public class Mod : ModuleBase - { - private readonly IErrorHandler _errorHandler; - - public Mod(IErrorHandler errorHandler) - { - _errorHandler = errorHandler; - } - - [Command("namehistory", RunMode = RunMode.Async)] - [Summary("See past usernames of an user")] - public async Task UsernameHistory([Summary("@someone")] IUser user) - { - try - { - await Context.Channel.SendMessageAsync("This command has been removed due to low usage and excessively high database usage"); - } - catch (Exception e) - { - await _errorHandler.HandleCommandException(e, Context); - } - } - } -} \ No newline at end of file diff --git a/src/Bot/Commands/Admin/Role.cs b/src/Bot/Commands/Admin/Role.cs index efec7bd..fb8ef68 100644 --- a/src/Bot/Commands/Admin/Role.cs +++ b/src/Bot/Commands/Admin/Role.cs @@ -6,14 +6,15 @@ using System.Threading.Tasks; using Discord; using Discord.Commands; using Discord.Net; +using Geekbot.Bot.CommandPreconditions; using Geekbot.Core; -using Geekbot.Core.CommandPreconditions; using Geekbot.Core.Database; using Geekbot.Core.Database.Models; using Geekbot.Core.ErrorHandling; using Geekbot.Core.Extensions; using Geekbot.Core.GuildSettingsManager; using Geekbot.Core.ReactionListener; +using Localization = Geekbot.Core.Localization; namespace Geekbot.Bot.Commands.Admin { diff --git a/src/Bot/Commands/Games/Pokedex.cs b/src/Bot/Commands/Games/Pokedex.cs index 426761c..325ee8c 100644 --- a/src/Bot/Commands/Games/Pokedex.cs +++ b/src/Bot/Commands/Games/Pokedex.cs @@ -3,13 +3,14 @@ using System.Linq; using System.Threading.Tasks; using Discord; using Discord.Commands; +using Geekbot.Core; using Geekbot.Core.ErrorHandling; using Geekbot.Core.Extensions; using PokeAPI; namespace Geekbot.Bot.Commands.Games { - public class Pokedex : ModuleBase + public class Pokedex : TransactionModuleBase { private readonly IErrorHandler _errorHandler; diff --git a/src/Bot/Commands/Games/Roll/Roll.cs b/src/Bot/Commands/Games/Roll/Roll.cs index 6ef9322..e9ed9e9 100644 --- a/src/Bot/Commands/Games/Roll/Roll.cs +++ b/src/Bot/Commands/Games/Roll/Roll.cs @@ -1,16 +1,13 @@ using System; -using System.Linq; using System.Threading.Tasks; using Discord.Commands; -using Geekbot.Bot.Utils; using Geekbot.Core; using Geekbot.Core.Database; -using Geekbot.Core.Database.Models; using Geekbot.Core.ErrorHandling; -using Geekbot.Core.Extensions; using Geekbot.Core.GuildSettingsManager; using Geekbot.Core.KvInMemoryStore; using Geekbot.Core.RandomNumberGenerator; +using Sentry; namespace Geekbot.Bot.Commands.Games.Roll { @@ -34,63 +31,20 @@ namespace Geekbot.Bot.Commands.Games.Roll { try { - var number = _randomNumberGenerator.Next(1, 100); - int.TryParse(stuff, out var guess); - if (guess <= 100 && guess > 0) - { - var kvKey = $"{Context?.Guild?.Id ?? 0}:{Context.User.Id}:RollsPrevious"; - - var prevRoll = _kvInMemoryStore.Get(kvKey); - - if (prevRoll?.LastGuess == guess && prevRoll?.GuessedOn.AddDays(1) > DateTime.Now) - { - await ReplyAsync(string.Format( - Localization.Roll.NoPrevGuess, - Context.Message.Author.Mention, - DateLocalization.FormatDateTimeAsRemaining(prevRoll.GuessedOn.AddDays(1)))); - return; - } - - _kvInMemoryStore.Set(kvKey, new RollTimeout {LastGuess = guess, GuessedOn = DateTime.Now}); - - await ReplyAsync(string.Format(Localization.Roll.Rolled, Context.Message.Author.Mention, number, guess)); - if (guess == number) - { - await ReplyAsync(string.Format(Localization.Roll.Gratz, Context.Message.Author)); - var user = await GetUser(Context.User.Id); - user.Rolls += 1; - _database.Rolls.Update(user); - await _database.SaveChangesAsync(); - } - } - else - { - await ReplyAsync(string.Format(Localization.Roll.RolledNoGuess, Context.Message.Author.Mention, number)); - } + var res = await new Geekbot.Commands.Roll.Roll(_kvInMemoryStore, _database, _randomNumberGenerator) + .RunFromGateway( + Context.Guild.Id, + Context.User.Id, + Context.User.Username, + stuff ?? "0" + ); + await ReplyAsync(res); } catch (Exception e) { await ErrorHandler.HandleCommandException(e, Context); + Transaction.Status = SpanStatus.InternalError; } } - - private async Task GetUser(ulong userId) - { - var user = _database.Rolls.FirstOrDefault(u => u.GuildId.Equals(Context.Guild.Id.AsLong()) && u.UserId.Equals(userId.AsLong())) ?? await CreateNewRow(userId); - return user; - } - - private async Task CreateNewRow(ulong userId) - { - var user = new RollsModel() - { - GuildId = Context.Guild.Id.AsLong(), - UserId = userId.AsLong(), - Rolls = 0 - }; - var newUser = _database.Rolls.Add(user).Entity; - await _database.SaveChangesAsync(); - return newUser; - } } } \ No newline at end of file diff --git a/src/Bot/Commands/Integrations/LolMmr/LolMmr.cs b/src/Bot/Commands/Integrations/LolMmr/LolMmr.cs index 4e75034..511d7b8 100644 --- a/src/Bot/Commands/Integrations/LolMmr/LolMmr.cs +++ b/src/Bot/Commands/Integrations/LolMmr/LolMmr.cs @@ -10,7 +10,7 @@ using Geekbot.Core.ErrorHandling; namespace Geekbot.Bot.Commands.Integrations.LolMmr { - public class LolMmr : ModuleBase + public class LolMmr : TransactionModuleBase { private readonly IErrorHandler _errorHandler; @@ -46,9 +46,9 @@ namespace Geekbot.Bot.Commands.Integrations.LolMmr var sb = new StringBuilder(); sb.AppendLine($"**MMR for {summonerName}**"); - sb.AppendLine($"Normal: {data.Normal.Avg}"); - sb.AppendLine($"Ranked: {data.Ranked.Avg}"); - sb.AppendLine($"ARAM: {data.ARAM.Avg}"); + sb.AppendLine($"Normal: {data.Normal?.Avg ?? 0}"); + sb.AppendLine($"Ranked: {data.Ranked?.Avg ?? 0}"); + sb.AppendLine($"ARAM: {data.ARAM?.Avg ?? 0}"); await Context.Channel.SendMessageAsync(sb.ToString()); } diff --git a/src/Bot/Commands/Integrations/LolMmr/LolMmrDto.cs b/src/Bot/Commands/Integrations/LolMmr/LolMmrDto.cs index 51d4c85..233bcfc 100644 --- a/src/Bot/Commands/Integrations/LolMmr/LolMmrDto.cs +++ b/src/Bot/Commands/Integrations/LolMmr/LolMmrDto.cs @@ -1,9 +1,16 @@ +using System.Text.Json.Serialization; + namespace Geekbot.Bot.Commands.Integrations.LolMmr { public class LolMmrDto { + [JsonPropertyName("ranked")] public LolMrrInfoDto Ranked { get; set; } + + [JsonPropertyName("normal")] public LolMrrInfoDto Normal { get; set; } + + [JsonPropertyName("aram")] public LolMrrInfoDto ARAM { get; set; } } } \ No newline at end of file diff --git a/src/Bot/Commands/Integrations/LolMmr/LolMrrInfoDto.cs b/src/Bot/Commands/Integrations/LolMmr/LolMrrInfoDto.cs index 18b096a..fbcc49a 100644 --- a/src/Bot/Commands/Integrations/LolMmr/LolMrrInfoDto.cs +++ b/src/Bot/Commands/Integrations/LolMmr/LolMrrInfoDto.cs @@ -1,10 +1,10 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Geekbot.Bot.Commands.Integrations.LolMmr { public class LolMrrInfoDto { - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] - public decimal Avg { get; set; } = 0; + [JsonPropertyName("avg")] + public decimal? Avg { get; set; } } } \ No newline at end of file diff --git a/src/Bot/Commands/Integrations/MagicTheGathering.cs b/src/Bot/Commands/Integrations/MagicTheGathering.cs index b179d6e..359b41e 100644 --- a/src/Bot/Commands/Integrations/MagicTheGathering.cs +++ b/src/Bot/Commands/Integrations/MagicTheGathering.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using Discord; using Discord.Commands; +using Geekbot.Core; using Geekbot.Core.Converters; using Geekbot.Core.ErrorHandling; using Geekbot.Core.Extensions; @@ -11,7 +12,7 @@ using MtgApiManager.Lib.Service; namespace Geekbot.Bot.Commands.Integrations { - public class MagicTheGathering : ModuleBase + public class MagicTheGathering : TransactionModuleBase { private readonly IErrorHandler _errorHandler; private readonly IMtgManaConverter _manaConverter; diff --git a/src/Bot/Commands/Integrations/UbranDictionary/UrbanDictListItemDto.cs b/src/Bot/Commands/Integrations/UbranDictionary/UrbanDictListItemDto.cs deleted file mode 100644 index 0aee35f..0000000 --- a/src/Bot/Commands/Integrations/UbranDictionary/UrbanDictListItemDto.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Geekbot.Bot.Commands.Integrations.UbranDictionary -{ - internal class UrbanListItemDto - { - public string Definition { get; set; } - public string Permalink { get; set; } - public string ThumbsUp { get; set; } - public string Word { get; set; } - public string Example { get; set; } - public string ThumbsDown { get; set; } - } -} \ No newline at end of file diff --git a/src/Bot/Commands/Integrations/UbranDictionary/UrbanDictResponseDto.cs b/src/Bot/Commands/Integrations/UbranDictionary/UrbanDictResponseDto.cs deleted file mode 100644 index 1846601..0000000 --- a/src/Bot/Commands/Integrations/UbranDictionary/UrbanDictResponseDto.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; - -namespace Geekbot.Bot.Commands.Integrations.UbranDictionary -{ - internal class UrbanResponseDto - { - public string[] Tags { get; set; } - public List List { get; set; } - } -} \ No newline at end of file diff --git a/src/Bot/Commands/Integrations/UbranDictionary/UrbanDictionary.cs b/src/Bot/Commands/Integrations/UbranDictionary/UrbanDictionary.cs deleted file mode 100644 index 72bc8cd..0000000 --- a/src/Bot/Commands/Integrations/UbranDictionary/UrbanDictionary.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Discord; -using Discord.Commands; -using Geekbot.Core; -using Geekbot.Core.ErrorHandling; -using Geekbot.Core.Extensions; - -namespace Geekbot.Bot.Commands.Integrations.UbranDictionary -{ - public class UrbanDictionary : ModuleBase - { - private readonly IErrorHandler _errorHandler; - - public UrbanDictionary(IErrorHandler errorHandler) - { - _errorHandler = errorHandler; - } - - [Command("urban", RunMode = RunMode.Async)] - [Summary("Lookup something on urban dictionary")] - public async Task UrbanDefine([Remainder] [Summary("word")] string word) - { - try - { - var definitions = await HttpAbstractions.Get(new Uri($"https://api.urbandictionary.com/v0/define?term={word}")); - if (definitions.List.Count == 0) - { - await ReplyAsync("That word hasn't been defined..."); - return; - } - - var definition = definitions.List.First(e => !string.IsNullOrWhiteSpace(e.Example)); - - var eb = new EmbedBuilder(); - eb.WithAuthor(new EmbedAuthorBuilder - { - Name = definition.Word, - Url = definition.Permalink - }); - eb.WithColor(new Color(239, 255, 0)); - - static string ShortenIfToLong(string str, int maxLength) => str.Length > maxLength ? $"{str.Substring(0, maxLength - 5)}[...]" : str; - - if (!string.IsNullOrEmpty(definition.Definition)) eb.Description = ShortenIfToLong(definition.Definition, 1800); - if (!string.IsNullOrEmpty(definition.Example)) eb.AddField("Example", ShortenIfToLong(definition.Example, 1024)); - if (!string.IsNullOrEmpty(definition.ThumbsUp)) eb.AddInlineField("Upvotes", definition.ThumbsUp); - if (!string.IsNullOrEmpty(definition.ThumbsDown)) eb.AddInlineField("Downvotes", definition.ThumbsDown); - if (definitions.Tags?.Length > 0) eb.AddField("Tags", string.Join(", ", definitions.Tags)); - - await ReplyAsync("", false, eb.Build()); - } - catch (Exception e) - { - await _errorHandler.HandleCommandException(e, Context); - } - } - } -} \ No newline at end of file diff --git a/src/Bot/Commands/Integrations/UrbanDictionary.cs b/src/Bot/Commands/Integrations/UrbanDictionary.cs new file mode 100644 index 0000000..44fe868 --- /dev/null +++ b/src/Bot/Commands/Integrations/UrbanDictionary.cs @@ -0,0 +1,39 @@ +using System; +using System.Threading.Tasks; +using Discord.Commands; +using Geekbot.Core; +using Geekbot.Core.ErrorHandling; + +namespace Geekbot.Bot.Commands.Integrations +{ + public class UrbanDictionary : TransactionModuleBase + { + private readonly IErrorHandler _errorHandler; + + public UrbanDictionary(IErrorHandler errorHandler) + { + _errorHandler = errorHandler; + } + + [Command("urban", RunMode = RunMode.Async)] + [Summary("Lookup something on urban dictionary")] + public async Task UrbanDefine([Remainder] [Summary("word")] string word) + { + try + { + var eb = await Geekbot.Commands.UrbanDictionary.UrbanDictionary.Run(word); + if (eb == null) + { + await ReplyAsync("That word hasn't been defined..."); + return; + } + + await ReplyAsync(string.Empty, false, eb.ToDiscordNetEmbed().Build()); + } + catch (Exception e) + { + await _errorHandler.HandleCommandException(e, Context); + } + } + } +} \ No newline at end of file diff --git a/src/Bot/Commands/Integrations/Wikipedia.cs b/src/Bot/Commands/Integrations/Wikipedia.cs index 709f974..82f42a0 100644 --- a/src/Bot/Commands/Integrations/Wikipedia.cs +++ b/src/Bot/Commands/Integrations/Wikipedia.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading.Tasks; using Discord; using Discord.Commands; +using Geekbot.Core; using Geekbot.Core.Database; using Geekbot.Core.ErrorHandling; using Geekbot.Core.Extensions; @@ -14,7 +15,7 @@ using HtmlAgilityPack; namespace Geekbot.Bot.Commands.Integrations { - public class Wikipedia : ModuleBase + public class Wikipedia : TransactionModuleBase { private readonly IErrorHandler _errorHandler; private readonly IWikipediaClient _wikipediaClient; diff --git a/src/Bot/Commands/Integrations/Youtube.cs b/src/Bot/Commands/Integrations/Youtube.cs index 60ebdaa..50e9519 100644 --- a/src/Bot/Commands/Integrations/Youtube.cs +++ b/src/Bot/Commands/Integrations/Youtube.cs @@ -1,58 +1,59 @@ -using System; -using System.Threading.Tasks; -using Discord.Commands; -using Geekbot.Core.ErrorHandling; -using Geekbot.Core.GlobalSettings; -using Google.Apis.Services; -using Google.Apis.YouTube.v3; +using Discord.Commands; +using Geekbot.Core; +// using Geekbot.Core.ErrorHandling; +// using Geekbot.Core.GlobalSettings; +// using Google.Apis.Services; +// using Google.Apis.YouTube.v3; namespace Geekbot.Bot.Commands.Integrations { - public class Youtube : ModuleBase + public class Youtube : TransactionModuleBase { - private readonly IGlobalSettings _globalSettings; - private readonly IErrorHandler _errorHandler; + // private readonly IGlobalSettings _globalSettings; + // private readonly IErrorHandler _errorHandler; - public Youtube(IGlobalSettings globalSettings, IErrorHandler errorHandler) - { - _globalSettings = globalSettings; - _errorHandler = errorHandler; - } + // public Youtube(IGlobalSettings globalSettings, IErrorHandler errorHandler) + // { + // _globalSettings = globalSettings; + // _errorHandler = errorHandler; + // } [Command("yt", RunMode = RunMode.Async)] [Summary("Search for something on youtube.")] public async Task Yt([Remainder] [Summary("title")] string searchQuery) { - var key = _globalSettings.GetKey("YoutubeKey"); - if (string.IsNullOrEmpty(key)) - { - await ReplyAsync("No youtube key set, please tell my senpai to set one"); - return; - } - - try - { - var youtubeService = new YouTubeService(new BaseClientService.Initializer - { - ApiKey = key, - ApplicationName = GetType().ToString() - }); - - var searchListRequest = youtubeService.Search.List("snippet"); - searchListRequest.Q = searchQuery; - searchListRequest.MaxResults = 2; - - var searchListResponse = await searchListRequest.ExecuteAsync(); - - var result = searchListResponse.Items[0]; - - await ReplyAsync( - $"\"{result.Snippet.Title}\" from \"{result.Snippet.ChannelTitle}\" https://youtu.be/{result.Id.VideoId}"); - } - catch (Exception e) - { - await _errorHandler.HandleCommandException(e, Context); - } + await ReplyAsync("The youtube command is temporarily disabled"); + + // var key = _globalSettings.GetKey("YoutubeKey"); + // if (string.IsNullOrEmpty(key)) + // { + // await ReplyAsync("No youtube key set, please tell my senpai to set one"); + // return; + // } + // + // try + // { + // var youtubeService = new YouTubeService(new BaseClientService.Initializer + // { + // ApiKey = key, + // ApplicationName = GetType().ToString() + // }); + // + // var searchListRequest = youtubeService.Search.List("snippet"); + // searchListRequest.Q = searchQuery; + // searchListRequest.MaxResults = 2; + // + // var searchListResponse = await searchListRequest.ExecuteAsync(); + // + // var result = searchListResponse.Items[0]; + // + // await ReplyAsync( + // $"\"{result.Snippet.Title}\" from \"{result.Snippet.ChannelTitle}\" https://youtu.be/{result.Id.VideoId}"); + // } + // catch (Exception e) + // { + // await _errorHandler.HandleCommandException(e, Context); + // } } } } \ No newline at end of file diff --git a/src/Bot/Commands/Randomness/BenedictCumberbatchNameGenerator.cs b/src/Bot/Commands/Randomness/BenedictCumberbatchNameGenerator.cs index 995c921..23187bd 100644 --- a/src/Bot/Commands/Randomness/BenedictCumberbatchNameGenerator.cs +++ b/src/Bot/Commands/Randomness/BenedictCumberbatchNameGenerator.cs @@ -2,12 +2,13 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using Discord.Commands; +using Geekbot.Core; using Geekbot.Core.ErrorHandling; using Geekbot.Core.RandomNumberGenerator; namespace Geekbot.Bot.Commands.Randomness { - public class BenedictCumberbatchNameGenerator : ModuleBase + public class BenedictCumberbatchNameGenerator : TransactionModuleBase { private readonly IErrorHandler _errorHandler; private readonly IRandomNumberGenerator _randomNumberGenerator; diff --git a/src/Bot/Commands/Randomness/Cat/Cat.cs b/src/Bot/Commands/Randomness/Cat/Cat.cs index 7bd7e57..1198113 100644 --- a/src/Bot/Commands/Randomness/Cat/Cat.cs +++ b/src/Bot/Commands/Randomness/Cat/Cat.cs @@ -7,7 +7,7 @@ using Geekbot.Core.ErrorHandling; namespace Geekbot.Bot.Commands.Randomness.Cat { - public class Cat : ModuleBase + public class Cat : TransactionModuleBase { private readonly IErrorHandler _errorHandler; diff --git a/src/Bot/Commands/Randomness/Cat/CatResponseDto.cs b/src/Bot/Commands/Randomness/Cat/CatResponseDto.cs index febb66f..523613b 100644 --- a/src/Bot/Commands/Randomness/Cat/CatResponseDto.cs +++ b/src/Bot/Commands/Randomness/Cat/CatResponseDto.cs @@ -1,7 +1,10 @@ -namespace Geekbot.Bot.Commands.Randomness.Cat +using System.Text.Json.Serialization; + +namespace Geekbot.Bot.Commands.Randomness.Cat { internal class CatResponseDto { + [JsonPropertyName("file")] public string File { get; set; } } } \ No newline at end of file diff --git a/src/Bot/Commands/Randomness/Chuck/ChuckNorrisJokeResponseDto.cs b/src/Bot/Commands/Randomness/Chuck/ChuckNorrisJokeResponseDto.cs index 99d9493..7c0aefa 100644 --- a/src/Bot/Commands/Randomness/Chuck/ChuckNorrisJokeResponseDto.cs +++ b/src/Bot/Commands/Randomness/Chuck/ChuckNorrisJokeResponseDto.cs @@ -1,7 +1,10 @@ -namespace Geekbot.Bot.Commands.Randomness.Chuck +using System.Text.Json.Serialization; + +namespace Geekbot.Bot.Commands.Randomness.Chuck { internal class ChuckNorrisJokeResponseDto { + [JsonPropertyName("value")] public string Value { get; set; } } } \ No newline at end of file diff --git a/src/Bot/Commands/Randomness/Chuck/ChuckNorrisJokes.cs b/src/Bot/Commands/Randomness/Chuck/ChuckNorrisJokes.cs index be328b4..e2b1f16 100644 --- a/src/Bot/Commands/Randomness/Chuck/ChuckNorrisJokes.cs +++ b/src/Bot/Commands/Randomness/Chuck/ChuckNorrisJokes.cs @@ -7,7 +7,7 @@ using Geekbot.Core.ErrorHandling; namespace Geekbot.Bot.Commands.Randomness.Chuck { - public class ChuckNorrisJokes : ModuleBase + public class ChuckNorrisJokes : TransactionModuleBase { private readonly IErrorHandler _errorHandler; diff --git a/src/Bot/Commands/Randomness/Dad/DadJokeResponseDto.cs b/src/Bot/Commands/Randomness/Dad/DadJokeResponseDto.cs index 2d72bdd..012f9e9 100644 --- a/src/Bot/Commands/Randomness/Dad/DadJokeResponseDto.cs +++ b/src/Bot/Commands/Randomness/Dad/DadJokeResponseDto.cs @@ -1,7 +1,10 @@ -namespace Geekbot.Bot.Commands.Randomness.Dad +using System.Text.Json.Serialization; + +namespace Geekbot.Bot.Commands.Randomness.Dad { internal class DadJokeResponseDto { + [JsonPropertyName("joke")] public string Joke { get; set; } } } \ No newline at end of file diff --git a/src/Bot/Commands/Randomness/Dad/DadJokes.cs b/src/Bot/Commands/Randomness/Dad/DadJokes.cs index 136650c..67f9679 100644 --- a/src/Bot/Commands/Randomness/Dad/DadJokes.cs +++ b/src/Bot/Commands/Randomness/Dad/DadJokes.cs @@ -6,7 +6,7 @@ using Geekbot.Core.ErrorHandling; namespace Geekbot.Bot.Commands.Randomness.Dad { - public class DadJokes : ModuleBase + public class DadJokes : TransactionModuleBase { private readonly IErrorHandler _errorHandler; diff --git a/src/Bot/Commands/Randomness/Dog/Dog.cs b/src/Bot/Commands/Randomness/Dog/Dog.cs index 051dbf3..39e57c7 100644 --- a/src/Bot/Commands/Randomness/Dog/Dog.cs +++ b/src/Bot/Commands/Randomness/Dog/Dog.cs @@ -7,7 +7,7 @@ using Geekbot.Core.ErrorHandling; namespace Geekbot.Bot.Commands.Randomness.Dog { - public class Dog : ModuleBase + public class Dog : TransactionModuleBase { private readonly IErrorHandler _errorHandler; diff --git a/src/Bot/Commands/Randomness/Dog/DogResponseDto.cs b/src/Bot/Commands/Randomness/Dog/DogResponseDto.cs index 473c1ce..9f0dfce 100644 --- a/src/Bot/Commands/Randomness/Dog/DogResponseDto.cs +++ b/src/Bot/Commands/Randomness/Dog/DogResponseDto.cs @@ -1,7 +1,10 @@ -namespace Geekbot.Bot.Commands.Randomness.Dog +using System.Text.Json.Serialization; + +namespace Geekbot.Bot.Commands.Randomness.Dog { internal class DogResponseDto { + [JsonPropertyName("url")] public string Url { get; set; } } } \ No newline at end of file diff --git a/src/Bot/Commands/Randomness/EightBall.cs b/src/Bot/Commands/Randomness/EightBall.cs index e1a3a71..b5f0c3a 100644 --- a/src/Bot/Commands/Randomness/EightBall.cs +++ b/src/Bot/Commands/Randomness/EightBall.cs @@ -1,18 +1,19 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Threading.Tasks; using Discord.Commands; +using Geekbot.Core; using Geekbot.Core.ErrorHandling; +using Geekbot.Core.GuildSettingsManager; +using Localization = Geekbot.Core.Localization; namespace Geekbot.Bot.Commands.Randomness { - public class EightBall : ModuleBase + public class EightBall : GeekbotCommandBase { - private readonly IErrorHandler _errorHandler; - - public EightBall(IErrorHandler errorHandler) + public EightBall(IErrorHandler errorHandler, IGuildSettingsManager guildSettingsManager) : base(errorHandler, guildSettingsManager) { - _errorHandler = errorHandler; } [Command("8ball", RunMode = RunMode.Async)] @@ -21,36 +22,19 @@ namespace Geekbot.Bot.Commands.Randomness { try { - var replies = new List + var enumerator = Localization.EightBall.ResourceManager.GetResourceSet(CultureInfo.CurrentUICulture, true, true).GetEnumerator(); + var replies = new List(); + while (enumerator.MoveNext()) { - "It is certain", - "It is decidedly so", - "Without a doubt", - "Yes, definitely", - "You may rely on it", - "As I see it, yes", - "Most likely", - "Outlook good", - "Yes", - "Signs point to yes", - "Reply hazy try again", - "Ask again later", - "Better not tell you now", - "Cannot predict now", - "Concentrate and ask again", - "Don't count on it", - "My reply is no", - "My sources say no", - "Outlook not so good", - "Very doubtful" - }; - + replies.Add(enumerator.Value?.ToString()); + } + var answer = new Random().Next(replies.Count); await ReplyAsync(replies[answer]); } catch (Exception e) { - await _errorHandler.HandleCommandException(e, Context); + await ErrorHandler.HandleCommandException(e, Context); } } } diff --git a/src/Bot/Commands/Randomness/Fortune.cs b/src/Bot/Commands/Randomness/Fortune.cs index cc31536..1157603 100644 --- a/src/Bot/Commands/Randomness/Fortune.cs +++ b/src/Bot/Commands/Randomness/Fortune.cs @@ -1,10 +1,11 @@ using System.Threading.Tasks; using Discord.Commands; +using Geekbot.Core; using Geekbot.Core.Media; namespace Geekbot.Bot.Commands.Randomness { - public class Fortune : ModuleBase + public class Fortune : TransactionModuleBase { private readonly IFortunesProvider _fortunes; diff --git a/src/Bot/Commands/Randomness/Gdq.cs b/src/Bot/Commands/Randomness/Gdq.cs deleted file mode 100644 index c6d9fa8..0000000 --- a/src/Bot/Commands/Randomness/Gdq.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Net; -using System.Threading.Tasks; -using Discord.Commands; -using Geekbot.Core.ErrorHandling; - -namespace Geekbot.Bot.Commands.Randomness -{ - public class Gdq : ModuleBase - { - private readonly IErrorHandler _errorHandler; - - public Gdq(IErrorHandler errorHandler) - { - _errorHandler = errorHandler; - } - - [Command("gdq", RunMode = RunMode.Async)] - [Summary("Get a quote from the GDQ donation generator.")] - public async Task GetQuote() - { - try - { - using var client = new WebClient(); - var url = new Uri("http://taskinoz.com/gdq/api/"); - var response = client.DownloadString(url); - - await ReplyAsync(response); - } - catch (Exception e) - { - await _errorHandler.HandleCommandException(e, Context); - } - } - } -} \ No newline at end of file diff --git a/src/Bot/Commands/Randomness/Greetings/GreetingBaseDto.cs b/src/Bot/Commands/Randomness/Greetings/GreetingBaseDto.cs deleted file mode 100644 index ae0274b..0000000 --- a/src/Bot/Commands/Randomness/Greetings/GreetingBaseDto.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Geekbot.Bot.Commands.Randomness.Greetings -{ - public class GreetingBaseDto - { - public string Language { get; set; } - public string LanguageNative { get; set; } - public string LanguageCode { get; set; } - public string Script { get; set; } - public GreetingDto Primary { get; set; } - } -} \ No newline at end of file diff --git a/src/Bot/Commands/Randomness/Greetings/GreetingDto.cs b/src/Bot/Commands/Randomness/Greetings/GreetingDto.cs deleted file mode 100644 index 679e544..0000000 --- a/src/Bot/Commands/Randomness/Greetings/GreetingDto.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Geekbot.Bot.Commands.Randomness.Greetings -{ - public class GreetingDto - { - public string Text { get; set; } - public string Dialect { get; set; } - public string Romanization { get; set; } - public string[] Use { get; set; } - } -} \ No newline at end of file diff --git a/src/Bot/Commands/Randomness/Greetings/Greetings.cs b/src/Bot/Commands/Randomness/Greetings/Greetings.cs deleted file mode 100644 index cd69010..0000000 --- a/src/Bot/Commands/Randomness/Greetings/Greetings.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Threading.Tasks; -using Discord; -using Discord.Commands; -using Geekbot.Core; -using Geekbot.Core.ErrorHandling; -using Geekbot.Core.Extensions; - -namespace Geekbot.Bot.Commands.Randomness.Greetings -{ - public class Greetings : ModuleBase - { - private readonly IErrorHandler _errorHandler; - - public Greetings(IErrorHandler errorHandler) - { - _errorHandler = errorHandler; - } - - [Command("hello", RunMode = RunMode.Async)] - [Alias("greeting", "hi", "hallo")] - [Summary("Say hello to the bot and get a reply in a random language")] - public async Task GetGreeting() - { - try - { - var greeting = await HttpAbstractions.Get(new Uri("https://api.greetings.dev/v1/greeting")); - - var eb = new EmbedBuilder(); - eb.Title = greeting.Primary.Text; - eb.AddInlineField("Language", greeting.Language); - - if (greeting.Primary.Dialect != null) - { - eb.AddInlineField("Dialect", greeting.Primary.Dialect); - } - - if (greeting.Primary.Romanization != null) - { - eb.AddInlineField("Roman", greeting.Primary.Romanization); - } - - await ReplyAsync(string.Empty, false, eb.Build()); - } - catch (Exception e) - { - await _errorHandler.HandleCommandException(e, Context); - } - } - } -} \ No newline at end of file diff --git a/src/Bot/Commands/Randomness/Kanye/Kanye.cs b/src/Bot/Commands/Randomness/Kanye/Kanye.cs index 6ad67cb..e5d2e95 100644 --- a/src/Bot/Commands/Randomness/Kanye/Kanye.cs +++ b/src/Bot/Commands/Randomness/Kanye/Kanye.cs @@ -6,7 +6,7 @@ using Geekbot.Core.ErrorHandling; namespace Geekbot.Bot.Commands.Randomness.Kanye { - public class Kanye : ModuleBase + public class Kanye : TransactionModuleBase { private readonly IErrorHandler _errorHandler; diff --git a/src/Bot/Commands/Randomness/Kanye/KanyeResponseDto.cs b/src/Bot/Commands/Randomness/Kanye/KanyeResponseDto.cs index 8ca248e..ab8c06f 100644 --- a/src/Bot/Commands/Randomness/Kanye/KanyeResponseDto.cs +++ b/src/Bot/Commands/Randomness/Kanye/KanyeResponseDto.cs @@ -1,8 +1,10 @@ +using System.Text.Json.Serialization; + namespace Geekbot.Bot.Commands.Randomness.Kanye { public class KanyeResponseDto { - public string Id { get; set; } + [JsonPropertyName("quote")] public string Quote { get; set; } } } \ No newline at end of file diff --git a/src/Bot/Commands/Randomness/RandomAnimals.cs b/src/Bot/Commands/Randomness/RandomAnimals.cs index b9c1bdd..5493485 100644 --- a/src/Bot/Commands/Randomness/RandomAnimals.cs +++ b/src/Bot/Commands/Randomness/RandomAnimals.cs @@ -1,11 +1,12 @@ using System.Threading.Tasks; using Discord; using Discord.Commands; +using Geekbot.Core; using Geekbot.Core.Media; namespace Geekbot.Bot.Commands.Randomness { - public class RandomAnimals : ModuleBase + public class RandomAnimals : TransactionModuleBase { private readonly IMediaProvider _mediaProvider; diff --git a/src/Bot/Commands/Randomness/Ship.cs b/src/Bot/Commands/Randomness/Ship.cs index f48713e..55e55c5 100644 --- a/src/Bot/Commands/Randomness/Ship.cs +++ b/src/Bot/Commands/Randomness/Ship.cs @@ -10,6 +10,7 @@ using Geekbot.Core.ErrorHandling; using Geekbot.Core.Extensions; using Geekbot.Core.GuildSettingsManager; using Geekbot.Core.RandomNumberGenerator; +using Localization = Geekbot.Core.Localization; namespace Geekbot.Bot.Commands.Randomness { diff --git a/src/Bot/Commands/Randomness/Slap.cs b/src/Bot/Commands/Randomness/Slap.cs index b512e73..c99c325 100644 --- a/src/Bot/Commands/Randomness/Slap.cs +++ b/src/Bot/Commands/Randomness/Slap.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using Discord; using Discord.Commands; +using Geekbot.Core; using Geekbot.Core.Database; using Geekbot.Core.Database.Models; using Geekbot.Core.ErrorHandling; @@ -11,7 +12,7 @@ using Geekbot.Core.Extensions; namespace Geekbot.Bot.Commands.Randomness { - public class Slap : ModuleBase + public class Slap : TransactionModuleBase { private readonly IErrorHandler _errorHandler; private readonly DatabaseContext _database; @@ -76,10 +77,14 @@ namespace Geekbot.Bot.Commands.Randomness "teapot", "candle", "dictionary", - "powerless banhammer" + "powerless banhammer", + "piece of low fat mozzarella", + // For some reason it never picks the last one + // Adding this workaround, because i'm to lazy to actually fix it at the time of writing this + "padding" }; - await ReplyAsync($"{Context.User.Username} slapped {user.Username} with a {things[new Random().Next(things.Count - 1)]}"); + await ReplyAsync($"{Context.User.Username} slapped {user.Username} with a {things[new Random().Next(0, things.Count - 1)]}"); await UpdateRecieved(user.Id); await UpdateGiven(Context.User.Id); @@ -127,4 +132,4 @@ namespace Geekbot.Bot.Commands.Randomness e.UserId.Equals(userId.AsLong())); } } -} \ No newline at end of file +} diff --git a/src/Bot/Commands/Rpg/Cookies.cs b/src/Bot/Commands/Rpg/Cookies.cs index a51d652..66d845f 100644 --- a/src/Bot/Commands/Rpg/Cookies.cs +++ b/src/Bot/Commands/Rpg/Cookies.cs @@ -3,15 +3,15 @@ using System.Linq; using System.Threading.Tasks; using Discord; using Discord.Commands; -using Geekbot.Bot.Utils; +using Geekbot.Bot.CommandPreconditions; using Geekbot.Core; -using Geekbot.Core.CommandPreconditions; using Geekbot.Core.Database; using Geekbot.Core.Database.Models; using Geekbot.Core.ErrorHandling; using Geekbot.Core.Extensions; using Geekbot.Core.GuildSettingsManager; using Geekbot.Core.RandomNumberGenerator; +using Localization = Geekbot.Core.Localization; namespace Geekbot.Bot.Commands.Rpg { @@ -37,14 +37,16 @@ namespace Geekbot.Bot.Commands.Rpg try { var actor = await GetUser(Context.User.Id); - if (actor.LastPayout.Value.AddDays(1).Date > DateTime.Now.Date) + var timeoutDays = 1; + if (actor.LastPayout?.AddDays(timeoutDays) > DateTime.Now.ToUniversalTime()) { - var formattedWaitTime = DateLocalization.FormatDateTimeAsRemaining(DateTimeOffset.Now.AddDays(1).Date); + var remaining = actor.LastPayout.Value.AddDays(timeoutDays) - DateTimeOffset.Now.ToUniversalTime(); + var formattedWaitTime = DateLocalization.FormatDateTimeAsRemaining(remaining); await ReplyAsync(string.Format(Localization.Cookies.WaitForMoreCookies, formattedWaitTime)); return; } actor.Cookies += 10; - actor.LastPayout = DateTimeOffset.Now; + actor.LastPayout = DateTimeOffset.Now.ToUniversalTime(); await SetUser(actor); await ReplyAsync(string.Format(Localization.Cookies.GetCookies, 10, actor.Cookies)); @@ -78,6 +80,12 @@ namespace Geekbot.Bot.Commands.Rpg { var giver = await GetUser(Context.User.Id); + if (amount < 1) + { + await ReplyAsync(Localization.Cookies.CantTakeCookies); + return; + } + if (giver.Cookies < amount) { await ReplyAsync(Localization.Cookies.NotEnoughToGive); @@ -146,7 +154,7 @@ namespace Geekbot.Bot.Commands.Rpg GuildId = Context.Guild.Id.AsLong(), UserId = userId.AsLong(), Cookies = 0, - LastPayout = DateTimeOffset.MinValue + LastPayout = DateTimeOffset.MinValue.ToUniversalTime() }; var newUser = _database.Cookies.Add(user).Entity; await _database.SaveChangesAsync(); diff --git a/src/Bot/Commands/User/GuildInfo.cs b/src/Bot/Commands/User/GuildInfo.cs index c596186..c063d89 100644 --- a/src/Bot/Commands/User/GuildInfo.cs +++ b/src/Bot/Commands/User/GuildInfo.cs @@ -3,7 +3,8 @@ using System.Linq; using System.Threading.Tasks; using Discord; using Discord.Commands; -using Geekbot.Core.CommandPreconditions; +using Geekbot.Bot.CommandPreconditions; +using Geekbot.Core; using Geekbot.Core.Database; using Geekbot.Core.ErrorHandling; using Geekbot.Core.Extensions; @@ -11,7 +12,7 @@ using Geekbot.Core.Levels; namespace Geekbot.Bot.Commands.User { - public class GuildInfo : ModuleBase + public class GuildInfo : TransactionModuleBase { private readonly IErrorHandler _errorHandler; private readonly DatabaseContext _database; diff --git a/src/Bot/Commands/User/Karma.cs b/src/Bot/Commands/User/Karma.cs index 4778bce..469c371 100644 --- a/src/Bot/Commands/User/Karma.cs +++ b/src/Bot/Commands/User/Karma.cs @@ -1,13 +1,11 @@ using System; -using System.Linq; using System.Threading.Tasks; using Discord; using Discord.Commands; -using Geekbot.Bot.Utils; +using Geekbot.Bot.CommandPreconditions; +using Geekbot.Commands.Karma; using Geekbot.Core; -using Geekbot.Core.CommandPreconditions; using Geekbot.Core.Database; -using Geekbot.Core.Database.Models; using Geekbot.Core.ErrorHandling; using Geekbot.Core.Extensions; using Geekbot.Core.GuildSettingsManager; @@ -28,122 +26,51 @@ namespace Geekbot.Bot.Commands.User [Summary("Increase Someones Karma")] public async Task Good([Summary("@someone")] IUser user) { - try - { - var actor = await GetUser(Context.User.Id); - if (user.Id == Context.User.Id) - { - await ReplyAsync(string.Format(Localization.Karma.CannotChangeOwnUp, Context.User.Username)); - } - else if (TimeoutFinished(actor.TimeOut)) - { - var formatedWaitTime = DateLocalization.FormatDateTimeAsRemaining(actor.TimeOut.AddMinutes(3)); - await ReplyAsync(string.Format(Localization.Karma.WaitUntill, Context.User.Username, formatedWaitTime)); - } - else - { - var target = await GetUser(user.Id); - target.Karma += 1; - SetUser(target); - - actor.TimeOut = DateTimeOffset.Now; - SetUser(actor); - - await _database.SaveChangesAsync(); - - var eb = new EmbedBuilder(); - eb.WithAuthor(new EmbedAuthorBuilder() - .WithIconUrl(user.GetAvatarUrl()) - .WithName(user.Username)); - - eb.WithColor(new Color(138, 219, 146)); - eb.Title = Localization.Karma.Increased; - eb.AddInlineField(Localization.Karma.By, Context.User.Username); - eb.AddInlineField(Localization.Karma.Amount, "+1"); - eb.AddInlineField(Localization.Karma.Current, target.Karma); - await ReplyAsync("", false, eb.Build()); - } - } - catch (Exception e) - { - await ErrorHandler.HandleCommandException(e, Context); - } + await ChangeKarma(user, KarmaChange.Up); } [Command("bad", RunMode = RunMode.Async)] [Summary("Decrease Someones Karma")] public async Task Bad([Summary("@someone")] IUser user) + { + await ChangeKarma(user, KarmaChange.Down); + } + + [Command("neutral", RunMode = RunMode.Async)] + [Summary("Do nothing to someones Karma")] + public async Task Neutral([Summary("@someone")] IUser user) + { + await ChangeKarma(user, KarmaChange.Same); + } + + private async Task ChangeKarma(IUser user, KarmaChange change) { try { - var actor = await GetUser(Context.User.Id); - if (user.Id == Context.User.Id) + var author = new Interactions.Resolved.User() { - await ReplyAsync(string.Format(Localization.Karma.CannotChangeOwnDown, Context.User.Username)); - } - else if (TimeoutFinished(actor.TimeOut)) + Id = Context.User.Id.ToString(), + Username = Context.User.Username, + Discriminator = Context.User.Discriminator, + Avatar = Context.User.AvatarId, + }; + var targetUser = new Interactions.Resolved.User() { - var formatedWaitTime = DateLocalization.FormatDateTimeAsRemaining(actor.TimeOut.AddMinutes(3)); - await ReplyAsync(string.Format(Localization.Karma.WaitUntill, Context.User.Username, formatedWaitTime)); - } - else - { - var target = await GetUser(user.Id); - target.Karma -= 1; - SetUser(target); - - actor.TimeOut = DateTimeOffset.Now; - SetUser(actor); + Id = user.Id.ToString(), + Username = user.Username, + Discriminator = user.Discriminator, + Avatar = user.AvatarId, + }; + + var karma = new Geekbot.Commands.Karma.Karma(_database, Context.Guild.Id.AsLong()); + var res = await karma.ChangeKarma(author, targetUser, change); - await _database.SaveChangesAsync(); - - var eb = new EmbedBuilder(); - eb.WithAuthor(new EmbedAuthorBuilder() - .WithIconUrl(user.GetAvatarUrl()) - .WithName(user.Username)); - - eb.WithColor(new Color(138, 219, 146)); - eb.Title = Localization.Karma.Decreased; - eb.AddInlineField(Localization.Karma.By, Context.User.Username); - eb.AddInlineField(Localization.Karma.Amount, "-1"); - eb.AddInlineField(Localization.Karma.Current, target.Karma); - await ReplyAsync("", false, eb.Build()); - } + await ReplyAsync(string.Empty, false, res.ToDiscordNetEmbed().Build()); } catch (Exception e) { await ErrorHandler.HandleCommandException(e, Context); } } - - private bool TimeoutFinished(DateTimeOffset lastKarma) - { - return lastKarma.AddMinutes(3) > DateTimeOffset.Now; - } - - private async Task GetUser(ulong userId) - { - var user = _database.Karma.FirstOrDefault(u =>u.GuildId.Equals(Context.Guild.Id.AsLong()) && u.UserId.Equals(userId.AsLong())) ?? await CreateNewRow(userId); - return user; - } - - private void SetUser(KarmaModel user) - { - _database.Karma.Update(user); - } - - private async Task CreateNewRow(ulong userId) - { - var user = new KarmaModel() - { - GuildId = Context.Guild.Id.AsLong(), - UserId = userId.AsLong(), - Karma = 0, - TimeOut = DateTimeOffset.MinValue - }; - var newUser = _database.Karma.Add(user).Entity; - await _database.SaveChangesAsync(); - return newUser; - } } } \ No newline at end of file diff --git a/src/Bot/Commands/User/Rank.cs b/src/Bot/Commands/User/Rank.cs new file mode 100644 index 0000000..639469f --- /dev/null +++ b/src/Bot/Commands/User/Rank.cs @@ -0,0 +1,43 @@ +using Discord.Commands; +using Geekbot.Bot.CommandPreconditions; +using Geekbot.Core; +using Geekbot.Core.Database; +using Geekbot.Core.ErrorHandling; +using Geekbot.Core.GuildSettingsManager; +using Geekbot.Core.Highscores; + +namespace Geekbot.Bot.Commands.User +{ + public class Rank : GeekbotCommandBase + { + private readonly IHighscoreManager _highscoreManager; + private readonly DatabaseContext _database; + + public Rank(DatabaseContext database, IErrorHandler errorHandler, IHighscoreManager highscoreManager, IGuildSettingsManager guildSettingsManager) + : base(errorHandler, guildSettingsManager) + { + _database = database; + _highscoreManager = highscoreManager; + } + + [Command("rank", RunMode = RunMode.Async)] + [Summary("Get the highscore for various stats like message count, karma, correctly guessed roles, etc...")] + [DisableInDirectMessage] + public async Task RankCmd( + [Summary("type")] string typeUnformated = "messages", + [Summary("amount")] int amount = 10, + [Summary("season")] string season = null) + { + try + { + var res = new Geekbot.Commands.Rank(_database, _highscoreManager) + .Run(typeUnformated, amount, season, Context.Guild.Id, Context.Guild.Name); + await ReplyAsync(res); + } + catch (Exception e) + { + await ErrorHandler.HandleCommandException(e, Context); + } + } + } +} \ No newline at end of file diff --git a/src/Bot/Commands/User/Ranking/Rank.cs b/src/Bot/Commands/User/Ranking/Rank.cs deleted file mode 100644 index 20749a8..0000000 --- a/src/Bot/Commands/User/Ranking/Rank.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Discord.Commands; -using Geekbot.Core; -using Geekbot.Core.CommandPreconditions; -using Geekbot.Core.Converters; -using Geekbot.Core.Database; -using Geekbot.Core.ErrorHandling; -using Geekbot.Core.Extensions; -using Geekbot.Core.GuildSettingsManager; -using Geekbot.Core.Highscores; - -namespace Geekbot.Bot.Commands.User.Ranking -{ - public class Rank : GeekbotCommandBase - { - private readonly IEmojiConverter _emojiConverter; - private readonly IHighscoreManager _highscoreManager; - private readonly DatabaseContext _database; - - public Rank(DatabaseContext database, IErrorHandler errorHandler, IEmojiConverter emojiConverter, IHighscoreManager highscoreManager, IGuildSettingsManager guildSettingsManager) - : base(errorHandler, guildSettingsManager) - { - _database = database; - _emojiConverter = emojiConverter; - _highscoreManager = highscoreManager; - } - - [Command("rank", RunMode = RunMode.Async)] - [Summary("get user top 10 in messages or karma")] - [DisableInDirectMessage] - public async Task RankCmd([Summary("type")] string typeUnformated = "messages", [Summary("amount")] int amount = 10) - { - try - { - HighscoreTypes type; - try - { - type = Enum.Parse(typeUnformated, true); - if (!Enum.IsDefined(typeof(HighscoreTypes), type)) throw new Exception(); - } - catch - { - await ReplyAsync(Localization.Rank.InvalidType); - return; - } - - var replyBuilder = new StringBuilder(); - if (amount > 20) - { - await ReplyAsync(Localization.Rank.LimitingTo20Warning); - amount = 20; - } - - var guildId = Context.Guild.Id; - Dictionary highscoreUsers; - try - { - highscoreUsers = _highscoreManager.GetHighscoresWithUserData(type, guildId, amount); - } - catch (HighscoreListEmptyException) - { - await ReplyAsync(string.Format(Localization.Rank.NoTypeFoundForServer, type)); - return; - } - - var guildMessages = 0; - if (type == HighscoreTypes.messages) - { - guildMessages = _database.Messages - .Where(e => e.GuildId.Equals(Context.Guild.Id.AsLong())) - .Select(e => e.MessageCount) - .Sum(); - } - - var failedToRetrieveUser = highscoreUsers.Any(e => string.IsNullOrEmpty(e.Key.Username)); - - if (failedToRetrieveUser) replyBuilder.AppendLine(Localization.Rank.FailedToResolveAllUsernames).AppendLine(); - - replyBuilder.AppendLine(string.Format(Localization.Rank.HighscoresFor, type.ToString().CapitalizeFirst(), Context.Guild.Name)); - - var highscorePlace = 1; - foreach (var (user, value) in highscoreUsers) - { - replyBuilder.Append(highscorePlace < 11 - ? $"{_emojiConverter.NumberToEmoji(highscorePlace)} " - : $"`{highscorePlace}.` "); - - replyBuilder.Append(user.Username != null - ? $"**{user.Username}#{user.Discriminator}**" - : $"**{user.Id}**"); - - replyBuilder.Append(type == HighscoreTypes.messages - ? $" - {value} {type} - {Math.Round((double) (100 * value) / guildMessages, 2)}%\n" - : $" - {value} {type}\n"); - - highscorePlace++; - } - - await ReplyAsync(replyBuilder.ToString()); - } - catch (Exception e) - { - await ErrorHandler.HandleCommandException(e, Context); - } - } - } -} \ No newline at end of file diff --git a/src/Bot/Commands/User/Stats.cs b/src/Bot/Commands/User/Stats.cs index a2a54a2..61505c0 100644 --- a/src/Bot/Commands/User/Stats.cs +++ b/src/Bot/Commands/User/Stats.cs @@ -3,13 +3,14 @@ using System.Linq; using System.Threading.Tasks; using Discord; using Discord.Commands; +using Geekbot.Bot.CommandPreconditions; using Geekbot.Core; -using Geekbot.Core.CommandPreconditions; using Geekbot.Core.Database; using Geekbot.Core.ErrorHandling; using Geekbot.Core.Extensions; using Geekbot.Core.GuildSettingsManager; using Geekbot.Core.Levels; +using Localization = Geekbot.Core.Localization; namespace Geekbot.Bot.Commands.User { @@ -54,6 +55,8 @@ namespace Geekbot.Bot.Commands.User ?.FirstOrDefault(e => e.GuildId.Equals(Context.Guild.Id.AsLong()) && e.UserId.Equals(userInfo.Id.AsLong())) ?.Cookies ?? 0; + var quotes = _database.Quotes.Count(e => e.GuildId.Equals(Context.Guild.Id.AsLong()) && e.UserId.Equals(userInfo.Id.AsLong())); + var eb = new EmbedBuilder(); eb.WithAuthor(new EmbedAuthorBuilder() .WithIconUrl(userInfo.GetAvatarUrl()) @@ -68,9 +71,9 @@ namespace Geekbot.Bot.Commands.User e.UserId.Equals(userInfo.Id.AsLong())); eb.AddInlineField(Localization.Stats.OnDiscordSince, - $"{createdAt.Day}.{createdAt.Month}.{createdAt.Year} ({age} days)") + $"{createdAt.Day}.{createdAt.Month}.{createdAt.Year} ({age} {Localization.Stats.Days})") .AddInlineField(Localization.Stats.JoinedServer, - $"{joinedAt.Day}.{joinedAt.Month}.{joinedAt.Year} ({joinedDayAgo} days)") + $"{joinedAt.Day}.{joinedAt.Month}.{joinedAt.Year} ({joinedDayAgo} {Localization.Stats.Days})") .AddInlineField(Localization.Stats.Karma, karma?.Karma ?? 0) .AddInlineField(Localization.Stats.Level, level) .AddInlineField(Localization.Stats.MessagesSent, messages) @@ -78,6 +81,7 @@ namespace Geekbot.Bot.Commands.User if (correctRolls != null) eb.AddInlineField(Localization.Stats.GuessedRolls, correctRolls.Rolls); if (cookies > 0) eb.AddInlineField(Localization.Stats.Cookies, cookies); + if (quotes > 0) eb.AddInlineField(Localization.Stats.Quotes, quotes); await ReplyAsync("", false, eb.Build()); } diff --git a/src/Bot/Commands/Utils/AvatarGetter.cs b/src/Bot/Commands/Utils/AvatarGetter.cs index 6585a75..458eec8 100644 --- a/src/Bot/Commands/Utils/AvatarGetter.cs +++ b/src/Bot/Commands/Utils/AvatarGetter.cs @@ -2,11 +2,12 @@ using System.Threading.Tasks; using Discord; using Discord.Commands; +using Geekbot.Core; using Geekbot.Core.ErrorHandling; namespace Geekbot.Bot.Commands.Utils { - public class AvatarGetter : ModuleBase + public class AvatarGetter : TransactionModuleBase { private readonly IErrorHandler _errorHandler; @@ -21,8 +22,8 @@ namespace Geekbot.Bot.Commands.Utils { try { - if (user == null) user = Context.User; - var url = user.GetAvatarUrl().Replace("128", "1024"); + user ??= Context.User; + var url = user.GetAvatarUrl(ImageFormat.Auto, 1024); await ReplyAsync(url); } catch (Exception e) diff --git a/src/Bot/Commands/Utils/Changelog/Changelog.cs b/src/Bot/Commands/Utils/Changelog/Changelog.cs index 92d5ddd..989ac0d 100644 --- a/src/Bot/Commands/Utils/Changelog/Changelog.cs +++ b/src/Bot/Commands/Utils/Changelog/Changelog.cs @@ -11,7 +11,7 @@ using Geekbot.Core.ErrorHandling; namespace Geekbot.Bot.Commands.Utils.Changelog { - public class Changelog : ModuleBase + public class Changelog : TransactionModuleBase { private readonly DiscordSocketClient _client; private readonly IErrorHandler _errorHandler; diff --git a/src/Bot/Commands/Utils/Changelog/CommitAuthorDto.cs b/src/Bot/Commands/Utils/Changelog/CommitAuthorDto.cs index 7cabece..19d93eb 100644 --- a/src/Bot/Commands/Utils/Changelog/CommitAuthorDto.cs +++ b/src/Bot/Commands/Utils/Changelog/CommitAuthorDto.cs @@ -1,11 +1,17 @@ using System; +using System.Text.Json.Serialization; namespace Geekbot.Bot.Commands.Utils.Changelog { public class CommitAuthorDto { + [JsonPropertyName("name")] public string Name { get; set; } + + [JsonPropertyName("email")] public string Email { get; set; } + + [JsonPropertyName("date")] public DateTimeOffset Date { get; set; } } } \ No newline at end of file diff --git a/src/Bot/Commands/Utils/Changelog/CommitDto.cs b/src/Bot/Commands/Utils/Changelog/CommitDto.cs index a534730..e67d08c 100644 --- a/src/Bot/Commands/Utils/Changelog/CommitDto.cs +++ b/src/Bot/Commands/Utils/Changelog/CommitDto.cs @@ -1,7 +1,10 @@ -namespace Geekbot.Bot.Commands.Utils.Changelog +using System.Text.Json.Serialization; + +namespace Geekbot.Bot.Commands.Utils.Changelog { public class CommitDto { + [JsonPropertyName("commit")] public CommitInfoDto Commit { get; set; } } } \ No newline at end of file diff --git a/src/Bot/Commands/Utils/Changelog/CommitInfoDto.cs b/src/Bot/Commands/Utils/Changelog/CommitInfoDto.cs index d6f806e..592da9e 100644 --- a/src/Bot/Commands/Utils/Changelog/CommitInfoDto.cs +++ b/src/Bot/Commands/Utils/Changelog/CommitInfoDto.cs @@ -1,8 +1,13 @@ -namespace Geekbot.Bot.Commands.Utils.Changelog +using System.Text.Json.Serialization; + +namespace Geekbot.Bot.Commands.Utils.Changelog { public class CommitInfoDto { + [JsonPropertyName("author")] public CommitAuthorDto Author { get; set; } + + [JsonPropertyName("message")] public string Message { get; set; } } } \ No newline at end of file diff --git a/src/Bot/Commands/Utils/Choose.cs b/src/Bot/Commands/Utils/Choose.cs index 731bee6..450433d 100644 --- a/src/Bot/Commands/Utils/Choose.cs +++ b/src/Bot/Commands/Utils/Choose.cs @@ -1,9 +1,8 @@ -using System; -using System.Threading.Tasks; -using Discord.Commands; +using Discord.Commands; using Geekbot.Core; using Geekbot.Core.ErrorHandling; using Geekbot.Core.GuildSettingsManager; +using Localization = Geekbot.Core.Localization; namespace Geekbot.Bot.Commands.Utils { @@ -14,7 +13,7 @@ namespace Geekbot.Bot.Commands.Utils } [Command("choose", RunMode = RunMode.Async)] - [Summary("Let the bot choose for you, seperate options with a semicolon.")] + [Summary("Let the bot choose for you, separate options with a semicolon.")] public async Task Command([Remainder] [Summary("option1;option2")] string choices) { diff --git a/src/Bot/Commands/Utils/Corona/CoronaStats.cs b/src/Bot/Commands/Utils/Corona/CoronaStats.cs deleted file mode 100644 index 3f9f3ea..0000000 --- a/src/Bot/Commands/Utils/Corona/CoronaStats.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Threading.Tasks; -using Discord; -using Discord.Commands; -using Geekbot.Core; -using Geekbot.Core.ErrorHandling; -using Geekbot.Core.Extensions; - -namespace Geekbot.Bot.Commands.Utils.Corona -{ - public class CoronaStats : ModuleBase - { - private readonly IErrorHandler _errorHandler; - - public CoronaStats(IErrorHandler errorHandler) - { - _errorHandler = errorHandler; - } - - [Command("corona", RunMode = RunMode.Async)] - [Summary("Get the latest worldwide corona statistics")] - public async Task Summary() - { - try - { - var summary = await HttpAbstractions.Get(new Uri("https://api.covid19api.com/world/total")); - var activeCases = summary.TotalConfirmed - (summary.TotalRecovered + summary.TotalDeaths); - - string CalculatePercentage(decimal i) => (i / summary.TotalConfirmed).ToString("#0.##%"); - var activePercent = CalculatePercentage(activeCases); - var recoveredPercentage = CalculatePercentage(summary.TotalRecovered); - var deathsPercentage = CalculatePercentage(summary.TotalDeaths); - - var numberFormat = "#,#"; - var totalFormatted = summary.TotalConfirmed.ToString(numberFormat); - var activeFormatted = activeCases.ToString(numberFormat); - var recoveredFormatted = summary.TotalRecovered.ToString(numberFormat); - var deathsFormatted = summary.TotalDeaths.ToString(numberFormat); - - var eb = new EmbedBuilder - { - Author = new EmbedAuthorBuilder - { - Name = "Confirmed Corona Cases", - IconUrl = "https://www.redcross.org/content/dam/icons/disasters/virus/Virus-1000x1000-R-Pl.png" - }, - Footer = new EmbedFooterBuilder - { - Text = "Source: covid19api.com", - }, - Color = Color.Red - }; - eb.AddField("Total", totalFormatted); - eb.AddInlineField("Active", $"{activeFormatted} ({activePercent})"); - eb.AddInlineField("Recovered", $"{recoveredFormatted} ({recoveredPercentage})"); - eb.AddInlineField("Deaths", $"{deathsFormatted} ({deathsPercentage})"); - - await Context.Channel.SendMessageAsync(String.Empty, false, eb.Build()); - } - catch (Exception e) - { - await _errorHandler.HandleCommandException(e, Context); - } - } - } -} \ No newline at end of file diff --git a/src/Bot/Commands/Utils/Corona/CoronaSummaryDto.cs b/src/Bot/Commands/Utils/Corona/CoronaSummaryDto.cs deleted file mode 100644 index 3f6a820..0000000 --- a/src/Bot/Commands/Utils/Corona/CoronaSummaryDto.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Geekbot.Bot.Commands.Utils.Corona -{ - public class CoronaSummaryDto - { - public decimal TotalConfirmed { get; set; } - public decimal TotalDeaths { get; set; } - public decimal TotalRecovered { get; set; } - } -} \ No newline at end of file diff --git a/src/Bot/Commands/Utils/Dice.cs b/src/Bot/Commands/Utils/Dice.cs index 0668493..c57001f 100644 --- a/src/Bot/Commands/Utils/Dice.cs +++ b/src/Bot/Commands/Utils/Dice.cs @@ -3,12 +3,13 @@ using System.Collections.Generic; using System.Text; using System.Threading.Tasks; using Discord.Commands; +using Geekbot.Core; using Geekbot.Core.DiceParser; using Geekbot.Core.ErrorHandling; namespace Geekbot.Bot.Commands.Utils { - public class Dice : ModuleBase + public class Dice : TransactionModuleBase { private readonly IErrorHandler _errorHandler; private readonly IDiceParser _diceParser; diff --git a/src/Bot/Commands/Utils/Emojify.cs b/src/Bot/Commands/Utils/Emojify.cs index 8bb880e..a513710 100644 --- a/src/Bot/Commands/Utils/Emojify.cs +++ b/src/Bot/Commands/Utils/Emojify.cs @@ -1,20 +1,17 @@ -using System; -using System.Threading.Tasks; -using Discord.Commands; +using Discord.Commands; +using Geekbot.Core; using Geekbot.Core.Converters; using Geekbot.Core.ErrorHandling; namespace Geekbot.Bot.Commands.Utils { - public class Emojify : ModuleBase + public class Emojify : TransactionModuleBase { - private readonly IEmojiConverter _emojiConverter; private readonly IErrorHandler _errorHandler; - public Emojify(IErrorHandler errorHandler, IEmojiConverter emojiConverter) + public Emojify(IErrorHandler errorHandler) { _errorHandler = errorHandler; - _emojiConverter = emojiConverter; } [Command("emojify", RunMode = RunMode.Async)] @@ -23,7 +20,7 @@ namespace Geekbot.Bot.Commands.Utils { try { - var emojis = _emojiConverter.TextToEmoji(text); + var emojis = EmojiConverter.TextToEmoji(text); if (emojis.Length > 1999) { await ReplyAsync("I can't take that much at once!"); diff --git a/src/Bot/Commands/Utils/Help.cs b/src/Bot/Commands/Utils/Help.cs index 58630fe..7aa9aff 100644 --- a/src/Bot/Commands/Utils/Help.cs +++ b/src/Bot/Commands/Utils/Help.cs @@ -3,11 +3,12 @@ using System.Text; using System.Threading.Tasks; using Discord; using Discord.Commands; +using Geekbot.Core; using Geekbot.Core.ErrorHandling; namespace Geekbot.Bot.Commands.Utils { - public class Help : ModuleBase + public class Help : TransactionModuleBase { private readonly IErrorHandler _errorHandler; @@ -26,7 +27,7 @@ namespace Geekbot.Bot.Commands.Utils sb.AppendLine("For a list of all commands, please visit the following page"); sb.AppendLine("https://geekbot.pizzaandcoffee.rocks/commands"); - var dm = await Context.User.GetOrCreateDMChannelAsync(); + var dm = await Context.User.CreateDMChannelAsync(RequestOptions.Default); await dm.SendMessageAsync(sb.ToString()); await Context.Message.AddReactionAsync(new Emoji("✅")); } diff --git a/src/Bot/Commands/Utils/Info.cs b/src/Bot/Commands/Utils/Info.cs index 663dd07..912528d 100644 --- a/src/Bot/Commands/Utils/Info.cs +++ b/src/Bot/Commands/Utils/Info.cs @@ -11,7 +11,7 @@ using Geekbot.Core.Extensions; namespace Geekbot.Bot.Commands.Utils { - public class Info : ModuleBase + public class Info : TransactionModuleBase { private readonly DiscordSocketClient _client; private readonly CommandService _commands; diff --git a/src/Bot/Commands/Utils/Lmgtfy.cs b/src/Bot/Commands/Utils/Lmgtfy.cs index 6063cf9..76fa6fa 100644 --- a/src/Bot/Commands/Utils/Lmgtfy.cs +++ b/src/Bot/Commands/Utils/Lmgtfy.cs @@ -2,11 +2,12 @@ using System; using System.Threading.Tasks; using System.Web; using Discord.Commands; +using Geekbot.Core; using Geekbot.Core.ErrorHandling; namespace Geekbot.Bot.Commands.Utils { - public class Lmgtfy : ModuleBase + public class Lmgtfy : TransactionModuleBase { private readonly IErrorHandler _errorHandler; diff --git a/src/Bot/Commands/Utils/Ping.cs b/src/Bot/Commands/Utils/Ping.cs index d4faa53..ee751cd 100644 --- a/src/Bot/Commands/Utils/Ping.cs +++ b/src/Bot/Commands/Utils/Ping.cs @@ -1,9 +1,10 @@ using System.Threading.Tasks; using Discord.Commands; +using Geekbot.Core; namespace Geekbot.Bot.Commands.Utils { - public class Ping : ModuleBase + public class Ping : TransactionModuleBase { [Command("👀", RunMode = RunMode.Async)] [Summary("Look at the bot.")] diff --git a/src/Bot/Commands/Utils/Quote/Quote.cs b/src/Bot/Commands/Utils/Quote/Quote.cs index 0b197cd..243e5dd 100644 --- a/src/Bot/Commands/Utils/Quote/Quote.cs +++ b/src/Bot/Commands/Utils/Quote/Quote.cs @@ -4,8 +4,8 @@ using System.Text; using System.Threading.Tasks; using Discord; using Discord.Commands; +using Geekbot.Bot.CommandPreconditions; using Geekbot.Core; -using Geekbot.Core.CommandPreconditions; using Geekbot.Core.Database; using Geekbot.Core.Database.Models; using Geekbot.Core.ErrorHandling; @@ -13,6 +13,11 @@ using Geekbot.Core.Extensions; using Geekbot.Core.GuildSettingsManager; using Geekbot.Core.Polyfills; using Geekbot.Core.RandomNumberGenerator; +using Geekbot.Core.UserRepository; +using Microsoft.EntityFrameworkCore; +using Sentry; +using Constants = Geekbot.Core.Constants; +using Localization = Geekbot.Core.Localization; namespace Geekbot.Bot.Commands.Utils.Quote { @@ -22,13 +27,15 @@ namespace Geekbot.Bot.Commands.Utils.Quote { private readonly DatabaseContext _database; private readonly IRandomNumberGenerator _randomNumberGenerator; + private readonly IUserRepository _userRepository; private readonly bool _isDev; - public Quote(IErrorHandler errorHandler, DatabaseContext database, IRandomNumberGenerator randomNumberGenerator, IGuildSettingsManager guildSettingsManager) + public Quote(IErrorHandler errorHandler, DatabaseContext database, IRandomNumberGenerator randomNumberGenerator, IGuildSettingsManager guildSettingsManager, IUserRepository userRepository) : base(errorHandler, guildSettingsManager) { _database = database; _randomNumberGenerator = randomNumberGenerator; + _userRepository = userRepository; // to remove restrictions when developing _isDev = Constants.BotVersion() == "0.0.0-DEV"; } @@ -39,23 +46,26 @@ namespace Geekbot.Bot.Commands.Utils.Quote { try { - var totalQuotes = await _database.Quotes.CountAsync(e => e.GuildId.Equals(Context.Guild.Id.AsLong())); - - if (totalQuotes == 0) + var getQuoteFromDbSpan = Transaction.StartChild("GetQuoteFromDB"); + var quote = _database.Quotes.FromSqlInterpolated($"select * from \"Quotes\" where \"GuildId\" = {Context.Guild.Id} order by random() limit 1"); + getQuoteFromDbSpan.Finish(); + + if (!quote.Any()) { await ReplyAsync(Localization.Quote.NoQuotesFound); + Transaction.Status = SpanStatus.NotFound; return; } - var random = _randomNumberGenerator.Next(0, totalQuotes - 1); - var quote = _database.Quotes.Where(e => e.GuildId.Equals(Context.Guild.Id.AsLong())).Skip(random).Take(1); - + var buildQuoteEmbedSpan = Transaction.StartChild("BuildQuoteEmbed"); var embed = QuoteBuilder(quote.FirstOrDefault()); + buildQuoteEmbedSpan.Finish(); await ReplyAsync("", false, embed.Build()); } catch (Exception e) { await ErrorHandler.HandleCommandException(e, Context, "Whoops, seems like the quote was to edgy to return"); + Transaction.Status = SpanStatus.InternalError; } } @@ -74,22 +84,6 @@ namespace Geekbot.Bot.Commands.Utils.Quote { await QuoteFromMention(user, false); } - - [Command("add")] - [Alias("save")] - [Summary("Add a quote from a message id")] - public async Task AddQuote([Summary("message-ID")] ulong messageId) - { - await QuoteFromMessageId(messageId, true); - } - - [Command("make")] - [Alias("preview")] - [Summary("Preview a quote from a message id")] - public async Task ReturnSpecifiedQuote([Summary("message-ID")] ulong messageId) - { - await QuoteFromMessageId(messageId, false); - } [Command("add")] [Alias("save")] @@ -160,8 +154,8 @@ namespace Geekbot.Bot.Commands.Utils.Quote .Where(row => row.GuildId == Context.Guild.Id.AsLong()) .GroupBy(row => row.UserId) .Select(row => new { userId = row.Key, amount = row.Count()}) - .OrderBy(row => row.amount) - .Last(); + .OrderByDescending(row => row.amount) + .First(); var mostQuotedPersonUser = Context.Client.GetUserAsync(mostQuotedPerson.userId.AsUlong()).Result ?? new UserPolyfillDto {Username = "Unknown User"}; var quotesByYear = _database.Quotes @@ -208,21 +202,7 @@ namespace Geekbot.Bot.Commands.Utils.Quote } - private async Task QuoteFromMessageId(ulong messageId, bool saveToDb) - { - try - { - var message = await Context.Channel.GetMessageAsync(messageId); - - await ProcessQuote(message, saveToDb, true); - } - catch (Exception e) - { - await ErrorHandler.HandleCommandException(e, Context, "I couldn't find a message with that id :disappointed:"); - } - } - - private async Task QuoteFromMessageLink(string messageLink, bool saveToDb) + private async Task QuoteFromMessageLink(string messageLink, bool saveToDb) { try { @@ -253,7 +233,7 @@ namespace Geekbot.Bot.Commands.Utils.Quote } } - private async Task ProcessQuote(IMessage message, bool saveToDb, bool showMessageIdWarning = false) + private async Task ProcessQuote(IMessage message, bool saveToDb) { if (message.Author.Id == Context.Message.Author.Id && saveToDb && !_isDev) { @@ -278,27 +258,38 @@ namespace Geekbot.Bot.Commands.Utils.Quote var sb = new StringBuilder(); if (saveToDb) sb.AppendLine(Localization.Quote.QuoteAdded); - if (showMessageIdWarning) sb.AppendLine(Localization.Quote.MessageIdDeprecation); await ReplyAsync(sb.ToString(), false, embed.Build()); } private EmbedBuilder QuoteBuilder(QuoteModel quote) { - var user = Context.Client.GetUserAsync(quote.UserId.AsUlong()).Result ?? new UserPolyfillDto { Username = "Unknown User" }; + var getEmbedUserSpan = Transaction.StartChild("GetEmbedUser"); + var user = Context.Client.GetUserAsync(quote.UserId.AsUlong()).Result; + if (user == null) + { + var getEmbedUserFromRepoSpan = Transaction.StartChild("GetEmbedUserFromRepo"); + var fallbackUserFromRepo = _userRepository.Get(quote.UserId.AsUlong()); + user = new UserPolyfillDto() + { + Username = fallbackUserFromRepo?.Username ?? "Unknown User", + AvatarUrl = fallbackUserFromRepo?.AvatarUrl + }; + getEmbedUserFromRepoSpan.Finish(); + } + getEmbedUserSpan.Finish(); + + var embedBuilderSpan = Transaction.StartChild("EmbedBuilder"); var eb = new EmbedBuilder(); eb.WithColor(new Color(143, 167, 232)); - if (quote.InternalId == 0) - { - eb.Title = $"{user.Username} @ {quote.Time.Day}.{quote.Time.Month}.{quote.Time.Year}"; - } - else - { - eb.Title = $"#{quote.InternalId} | {user.Username} @ {quote.Time.Day}.{quote.Time.Month}.{quote.Time.Year}"; - } + eb.Title = quote.InternalId == 0 + ? $"{user.Username} @ {quote.Time.Day}.{quote.Time.Month}.{quote.Time.Year}" + : $"#{quote.InternalId} | {user.Username} @ {quote.Time.Day}.{quote.Time.Month}.{quote.Time.Year}"; eb.Description = quote.Quote; eb.ThumbnailUrl = user.GetAvatarUrl(); if (quote.Image != null) eb.ImageUrl = quote.Image; + embedBuilderSpan.Finish(); + return eb; } @@ -322,7 +313,7 @@ namespace Geekbot.Bot.Commands.Utils.Quote InternalId = internalId, GuildId = Context.Guild.Id.AsLong(), UserId = message.Author.Id.AsLong(), - Time = message.Timestamp.DateTime, + Time = message.Timestamp.DateTime.ToUniversalTime(), Quote = message.Content, Image = image }; diff --git a/src/Bot/Handlers/CommandHandler.cs b/src/Bot/Handlers/CommandHandler.cs index 52e9a52..19d4edd 100644 --- a/src/Bot/Handlers/CommandHandler.cs +++ b/src/Bot/Handlers/CommandHandler.cs @@ -21,12 +21,10 @@ namespace Geekbot.Bot.Handlers private readonly RestApplication _applicationInfo; private readonly IGuildSettingsManager _guildSettingsManager; private readonly List _ignoredServers; - private readonly DatabaseContext _database; - public CommandHandler(DatabaseContext database, IDiscordClient client, IGeekbotLogger logger, IServiceProvider servicesProvider, CommandService commands, RestApplication applicationInfo, + public CommandHandler(IDiscordClient client, IGeekbotLogger logger, IServiceProvider servicesProvider, CommandService commands, RestApplication applicationInfo, IGuildSettingsManager guildSettingsManager) { - _database = database; _client = client; _logger = logger; _servicesProvider = servicesProvider; @@ -39,7 +37,7 @@ namespace Geekbot.Bot.Handlers _ignoredServers = new List { 228623803201224704, // SwitzerLAN - 169844523181015040, // EEvent + // 169844523181015040, // EEvent 248531441548263425, // MYI 110373943822540800 // Discord Bots }; diff --git a/src/Bot/Handlers/MessageDeletedHandler.cs b/src/Bot/Handlers/MessageDeletedHandler.cs index d0377f7..b8ffe5c 100644 --- a/src/Bot/Handlers/MessageDeletedHandler.cs +++ b/src/Bot/Handlers/MessageDeletedHandler.cs @@ -23,11 +23,11 @@ namespace Geekbot.Bot.Handlers _client = client; } - public async Task HandleMessageDeleted(Cacheable message, ISocketMessageChannel channel) + public async Task HandleMessageDeleted(Cacheable message, Cacheable cacheableMessageChannel) { try { - var guildSocketData = ((IGuildChannel) channel).Guild; + var guildSocketData = ((IGuildChannel) cacheableMessageChannel.Value).Guild; var guild = _database.GuildSettings.FirstOrDefault(g => g.GuildId.Equals(guildSocketData.Id.AsLong())); if ((guild?.ShowDelete ?? false) && guild?.ModChannel != 0) { @@ -35,7 +35,7 @@ namespace Geekbot.Bot.Handlers var sb = new StringBuilder(); if (message.Value != null) { - sb.AppendLine($"The following message from {message.Value.Author.Username}#{message.Value.Author.Discriminator} was deleted in <#{channel.Id}>"); + sb.AppendLine($"The following message from {message.Value.Author.Username}#{message.Value.Author.Discriminator} was deleted in <#{cacheableMessageChannel.Id}>"); sb.AppendLine(message.Value.Content); } else diff --git a/src/Bot/Handlers/ReactionHandler.cs b/src/Bot/Handlers/ReactionHandler.cs index 66af3e8..816e125 100644 --- a/src/Bot/Handlers/ReactionHandler.cs +++ b/src/Bot/Handlers/ReactionHandler.cs @@ -14,19 +14,19 @@ namespace Geekbot.Bot.Handlers _reactionListener = reactionListener; } - public Task Added(Cacheable cacheable, ISocketMessageChannel socketMessageChannel, SocketReaction reaction) + public Task Added(Cacheable cacheableUserMessage, Cacheable cacheableMessageChannel, SocketReaction reaction) { if (reaction.User.Value.IsBot) return Task.CompletedTask; if (!_reactionListener.IsListener(reaction.MessageId)) return Task.CompletedTask; - _reactionListener.GiveRole(socketMessageChannel, reaction); + _reactionListener.GiveRole(cacheableMessageChannel.Value, reaction); return Task.CompletedTask; } - public Task Removed(Cacheable cacheable, ISocketMessageChannel socketMessageChannel, SocketReaction reaction) + public Task Removed(Cacheable cacheableUserMessage, Cacheable cacheableMessageChannel, SocketReaction reaction) { if (reaction.User.Value.IsBot) return Task.CompletedTask; if (!_reactionListener.IsListener(reaction.MessageId)) return Task.CompletedTask; - _reactionListener.RemoveRole(socketMessageChannel, reaction); + _reactionListener.RemoveRole(cacheableMessageChannel.Value, reaction); return Task.CompletedTask; } } diff --git a/src/Bot/Handlers/StatsHandler.cs b/src/Bot/Handlers/StatsHandler.cs index 197bbb8..b089515 100644 --- a/src/Bot/Handlers/StatsHandler.cs +++ b/src/Bot/Handlers/StatsHandler.cs @@ -4,6 +4,7 @@ using Discord.WebSocket; using Geekbot.Core.Database; using Geekbot.Core.Database.Models; using Geekbot.Core.Extensions; +using Geekbot.Core.Highscores; using Geekbot.Core.Logger; using Microsoft.EntityFrameworkCore; @@ -13,11 +14,26 @@ namespace Geekbot.Bot.Handlers { private readonly IGeekbotLogger _logger; private readonly DatabaseContext _database; + private string _season; public StatsHandler(IGeekbotLogger logger, DatabaseContext database) { _logger = logger; _database = database; + _season = SeasonsUtils.GetCurrentSeason(); + + var timer = new System.Timers.Timer() + { + Enabled = true, + AutoReset = true, + Interval = TimeSpan.FromMinutes(5).TotalMilliseconds + }; + timer.Elapsed += (sender, args) => + { + var current = SeasonsUtils.GetCurrentSeason(); + if (current == _season) return; + _season = SeasonsUtils.GetCurrentSeason(); + }; } public async Task UpdateStats(SocketMessage message) @@ -33,22 +49,16 @@ namespace Geekbot.Bot.Handlers var channel = (SocketGuildChannel) message.Channel; - var rowId = await _database.Database.ExecuteSqlRawAsync( - "UPDATE \"Messages\" SET \"MessageCount\" = \"MessageCount\" + 1 WHERE \"GuildId\" = {0} AND \"UserId\" = {1}", - channel.Guild.Id.AsLong(), - message.Author.Id.AsLong() - ); - - if (rowId == 0) + // ignore the discord bots server + // ToDo: create a clean solution for this... + if (channel.Guild.Id == 110373943822540800) { - await _database.Messages.AddAsync(new MessagesModel - { - UserId = message.Author.Id.AsLong(), - GuildId = channel.Guild.Id.AsLong(), - MessageCount = 1 - }); - await _database.SaveChangesAsync(); + return; } + + await UpdateTotalTable(message, channel); + await UpdateSeasonsTable(message, channel); + if (message.Author.IsBot) return; _logger.Information(LogSource.Message, message.Content, SimpleConextConverter.ConvertSocketMessage(message)); @@ -58,5 +68,47 @@ namespace Geekbot.Bot.Handlers _logger.Error(LogSource.Message, "Could not process message stats", e); } } + + private async Task UpdateTotalTable(SocketMessage message, SocketGuildChannel channel) + { + var rowId = await _database.Database.ExecuteSqlRawAsync( + "UPDATE \"Messages\" SET \"MessageCount\" = \"MessageCount\" + 1 WHERE \"GuildId\" = {0} AND \"UserId\" = {1}", + channel.Guild.Id.AsLong(), + message.Author.Id.AsLong() + ); + + if (rowId == 0) + { + await _database.Messages.AddAsync(new MessagesModel + { + UserId = message.Author.Id.AsLong(), + GuildId = channel.Guild.Id.AsLong(), + MessageCount = 1 + }); + await _database.SaveChangesAsync(); + } + } + + private async Task UpdateSeasonsTable(SocketMessage message, SocketGuildChannel channel) + { + var rowId = await _database.Database.ExecuteSqlRawAsync( + "UPDATE \"MessagesSeasons\" SET \"MessageCount\" = \"MessageCount\" + 1 WHERE \"GuildId\" = {0} AND \"UserId\" = {1} AND \"Season\" = {2}", + channel.Guild.Id.AsLong(), + message.Author.Id.AsLong(), + _season + ); + + if (rowId == 0) + { + await _database.MessagesSeasons.AddAsync(new MessageSeasonsModel() + { + UserId = message.Author.Id.AsLong(), + GuildId = channel.Guild.Id.AsLong(), + Season = _season, + MessageCount = 1 + }); + await _database.SaveChangesAsync(); + } + } } } \ No newline at end of file diff --git a/src/Bot/Handlers/UserHandler.cs b/src/Bot/Handlers/UserHandler.cs index a22d0e1..1f58131 100644 --- a/src/Bot/Handlers/UserHandler.cs +++ b/src/Bot/Handlers/UserHandler.cs @@ -74,15 +74,15 @@ namespace Geekbot.Bot.Handlers await _userRepository.Update(newUser); } - public async Task Left(SocketGuildUser user) + public async Task Left(SocketGuild socketGuild, SocketUser socketUser) { try { - var guild = _database.GuildSettings.FirstOrDefault(g => g.GuildId.Equals(user.Guild.Id.AsLong())); + var guild = _database.GuildSettings.FirstOrDefault(g => g.GuildId.Equals(socketGuild.Id.AsLong())); if (guild?.ShowLeave ?? false) { var modChannelSocket = (ISocketMessageChannel) await _client.GetChannelAsync(guild.ModChannel.AsUlong()); - await modChannelSocket.SendMessageAsync($"{user.Username}#{user.Discriminator} left the server"); + await modChannelSocket.SendMessageAsync($"{socketUser.Username}#{socketUser.Discriminator} left the server"); } } catch (Exception e) @@ -90,7 +90,7 @@ namespace Geekbot.Bot.Handlers _logger.Error(LogSource.Geekbot, "Failed to send leave message", e); } - _logger.Information(LogSource.Geekbot, $"{user.Username} ({user.Id}) joined {user.Guild.Name} ({user.Guild.Id})"); + _logger.Information(LogSource.Geekbot, $"{socketUser.Username} ({socketUser.Id}) joined {socketGuild.Name} ({socketGuild.Id})"); } } } \ No newline at end of file diff --git a/src/Bot/Program.cs b/src/Bot/Program.cs deleted file mode 100644 index dee6ec3..0000000 --- a/src/Bot/Program.cs +++ /dev/null @@ -1,228 +0,0 @@ -using System; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using CommandLine; -using Discord; -using Discord.Commands; -using Discord.WebSocket; -using Geekbot.Bot.Handlers; -using Geekbot.Core; -using Geekbot.Core.Converters; -using Geekbot.Core.Database; -using Geekbot.Core.DiceParser; -using Geekbot.Core.ErrorHandling; -using Geekbot.Core.GlobalSettings; -using Geekbot.Core.GuildSettingsManager; -using Geekbot.Core.Highscores; -using Geekbot.Core.KvInMemoryStore; -using Geekbot.Core.Levels; -using Geekbot.Core.Logger; -using Geekbot.Core.Media; -using Geekbot.Core.RandomNumberGenerator; -using Geekbot.Core.ReactionListener; -using Geekbot.Core.UserRepository; -using Geekbot.Core.WikipediaClient; -using Geekbot.Web; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; - -namespace Geekbot.Bot -{ - internal class Program - { - private DiscordSocketClient _client; - private CommandService _commands; - private DatabaseInitializer _databaseInitializer; - private IGlobalSettings _globalSettings; - private IServiceProvider _servicesProvider; - private GeekbotLogger _logger; - private IUserRepository _userRepository; - private RunParameters _runParameters; - private IReactionListener _reactionListener; - private IGuildSettingsManager _guildSettingsManager; - - private static async Task Main(string[] args) - { - RunParameters runParameters = null; - Parser.Default.ParseArguments(args) - .WithParsed(e => runParameters = e) - .WithNotParsed(_ => Environment.Exit(GeekbotExitCode.InvalidArguments.GetHashCode())); - - var logo = new StringBuilder(); - logo.AppendLine(@" ____ _____ _____ _ ______ ___ _____"); - logo.AppendLine(@" / ___| ____| ____| |/ / __ ) / _ \\_ _|"); - logo.AppendLine(@"| | _| _| | _| | ' /| _ \| | | || |"); - logo.AppendLine(@"| |_| | |___| |___| . \| |_) | |_| || |"); - logo.AppendLine(@" \____|_____|_____|_|\_\____/ \___/ |_|"); - logo.AppendLine($"Version {Constants.BotVersion()} ".PadRight(41, '=')); - Console.WriteLine(logo.ToString()); - var logger = new GeekbotLogger(runParameters); - logger.Information(LogSource.Geekbot, "Starting..."); - try - { - await new Program().Start(runParameters, logger); - } - catch (Exception e) - { - logger.Error(LogSource.Geekbot, "RIP", e); - } - } - - private async Task Start(RunParameters runParameters, GeekbotLogger logger) - { - _logger = logger; - _runParameters = runParameters; - - logger.Information(LogSource.Geekbot, "Connecting to Database"); - var database = ConnectToDatabase(); - _globalSettings = new GlobalSettings(database); - - logger.Information(LogSource.Geekbot, "Connecting to Discord"); - SetupDiscordClient(); - await Login(); - _logger.Information(LogSource.Geekbot, $"Now Connected as {_client.CurrentUser.Username} to {_client.Guilds.Count} Servers"); - await _client.SetGameAsync(_globalSettings.GetKey("Game")); - - _logger.Information(LogSource.Geekbot, "Loading Dependencies and Handlers"); - RegisterDependencies(); - await RegisterHandlers(); - - _logger.Information(LogSource.Api, "Starting Web API"); - StartWebApi(); - - _logger.Information(LogSource.Geekbot, "Done and ready for use"); - - await Task.Delay(-1); - } - - private async Task Login() - { - try - { - var token = await GetToken(); - await _client.LoginAsync(TokenType.Bot, token); - await _client.StartAsync(); - while (!_client.ConnectionState.Equals(ConnectionState.Connected)) await Task.Delay(25); - } - catch (Exception e) - { - _logger.Error(LogSource.Geekbot, "Could not connect to Discord", e); - Environment.Exit(GeekbotExitCode.CouldNotLogin.GetHashCode()); - } - } - - private DatabaseContext ConnectToDatabase() - { - _databaseInitializer = new DatabaseInitializer(_runParameters, _logger); - var database = _databaseInitializer.Initialize(); - database.Database.EnsureCreated(); - if(!_runParameters.InMemory) database.Database.Migrate(); - - return database; - } - - private async Task GetToken() - { - var token = _runParameters.Token ?? _globalSettings.GetKey("DiscordToken"); - if (string.IsNullOrEmpty(token)) - { - Console.Write("Your bot Token: "); - var newToken = Console.ReadLine(); - await _globalSettings.SetKey("DiscordToken", newToken); - await _globalSettings.SetKey("Game", "Ping Pong"); - token = newToken; - } - - return token; - } - - private void SetupDiscordClient() - { - _client = new DiscordSocketClient(new DiscordSocketConfig - { - LogLevel = LogSeverity.Verbose, - MessageCacheSize = 1000, - ExclusiveBulkDelete = true - }); - - var discordLogger = new DiscordLogger(_logger); - _client.Log += discordLogger.Log; - } - - private void RegisterDependencies() - { - var services = new ServiceCollection(); - - _userRepository = new UserRepository(_databaseInitializer.Initialize(), _logger); - _reactionListener = new ReactionListener(_databaseInitializer.Initialize()); - _guildSettingsManager = new GuildSettingsManager(_databaseInitializer.Initialize()); - var fortunes = new FortunesProvider(_logger); - var levelCalc = new LevelCalc(); - var emojiConverter = new EmojiConverter(); - var mtgManaConverter = new MtgManaConverter(); - var wikipediaClient = new WikipediaClient(); - var randomNumberGenerator = new RandomNumberGenerator(_globalSettings); - var mediaProvider = new MediaProvider(_logger, randomNumberGenerator); - var kvMemoryStore = new KvInInMemoryStore(); - var errorHandler = new ErrorHandler(_logger, _runParameters, () => Localization.Internal.SomethingWentWrong); - var diceParser = new DiceParser(randomNumberGenerator); - - services.AddSingleton(_userRepository); - services.AddSingleton(_logger); - services.AddSingleton(levelCalc); - services.AddSingleton(emojiConverter); - services.AddSingleton(fortunes); - services.AddSingleton(mediaProvider); - services.AddSingleton(mtgManaConverter); - services.AddSingleton(wikipediaClient); - services.AddSingleton(randomNumberGenerator); - services.AddSingleton(kvMemoryStore); - services.AddSingleton(_globalSettings); - services.AddSingleton(errorHandler); - services.AddSingleton(diceParser); - services.AddSingleton(_reactionListener); - services.AddSingleton(_guildSettingsManager); - services.AddSingleton(_client); - services.AddTransient(e => new HighscoreManager(_databaseInitializer.Initialize(), _userRepository)); - services.AddTransient(e => _databaseInitializer.Initialize()); - - _servicesProvider = services.BuildServiceProvider(); - } - - private async Task RegisterHandlers() - { - var applicationInfo = await _client.GetApplicationInfoAsync(); - - _commands = new CommandService(); - await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _servicesProvider); - - var commandHandler = new CommandHandler(_databaseInitializer.Initialize(), _client, _logger, _servicesProvider, _commands, applicationInfo, _guildSettingsManager); - var userHandler = new UserHandler(_userRepository, _logger, _databaseInitializer.Initialize(), _client); - var reactionHandler = new ReactionHandler(_reactionListener); - var statsHandler = new StatsHandler(_logger, _databaseInitializer.Initialize()); - var messageDeletedHandler = new MessageDeletedHandler(_databaseInitializer.Initialize(), _logger, _client); - - _client.MessageReceived += commandHandler.RunCommand; - _client.MessageDeleted += messageDeletedHandler.HandleMessageDeleted; - _client.UserJoined += userHandler.Joined; - _client.UserUpdated += userHandler.Updated; - _client.UserLeft += userHandler.Left; - _client.ReactionAdded += reactionHandler.Added; - _client.ReactionRemoved += reactionHandler.Removed; - if (!_runParameters.InMemory) _client.MessageReceived += statsHandler.UpdateStats; - } - - private void StartWebApi() - { - if (_runParameters.DisableApi) - { - _logger.Warning(LogSource.Api, "Web API is disabled"); - return; - } - - var highscoreManager = new HighscoreManager(_databaseInitializer.Initialize(), _userRepository); - WebApiStartup.StartWebApi(_logger, _runParameters, _commands, _databaseInitializer.Initialize(), _client, _globalSettings, highscoreManager); - } - } -} \ No newline at end of file diff --git a/src/Commands/Commands.csproj b/src/Commands/Commands.csproj new file mode 100644 index 0000000..7f0bd8f --- /dev/null +++ b/src/Commands/Commands.csproj @@ -0,0 +1,23 @@ + + + + net6.0 + $(VersionSuffix) + $(VersionSuffix) + 0.0.0-DEV + Geekbot.Commands + Geekbot.Commands + NU1701 + CS8618 + enable + enable + True + Library + + + + + + + + diff --git a/src/Commands/Karma/Karma.cs b/src/Commands/Karma/Karma.cs new file mode 100644 index 0000000..026af2f --- /dev/null +++ b/src/Commands/Karma/Karma.cs @@ -0,0 +1,103 @@ +using System.Drawing; +using Geekbot.Core; +using Geekbot.Core.Database; +using Geekbot.Core.Database.Models; +using Geekbot.Interactions.Embed; +using Geekbot.Interactions.Resolved; +using Localization = Geekbot.Core.Localization; + +namespace Geekbot.Commands.Karma; + +public class Karma +{ + private readonly DatabaseContext _database; + private readonly long _guildId; + + public Karma(DatabaseContext database, long guildId) + { + _database = database; + _guildId = guildId; + } + + public async Task ChangeKarma(User author, User targetUser, KarmaChange change) + { + // Get the user + var authorRecord = await GetUser(long.Parse(author.Id)); + + // Check if the user can change karma + if (targetUser.Id == author.Id) + { + var message = change switch + { + KarmaChange.Up => Localization.Karma.CannotChangeOwnUp, + KarmaChange.Same => Localization.Karma.CannotChangeOwnSame, + KarmaChange.Down => Localization.Karma.CannotChangeOwnDown, + _ => throw new ArgumentOutOfRangeException(nameof(change), change, null) + }; + return Embed.ErrorEmbed(string.Format(message, author.Username)); + } + + var timeoutMinutes = 3; + if (authorRecord.TimeOut.AddMinutes(timeoutMinutes) > DateTimeOffset.Now.ToUniversalTime()) + { + var remaining = authorRecord.TimeOut.AddMinutes(timeoutMinutes) - DateTimeOffset.Now.ToUniversalTime(); + var formatedWaitTime = DateLocalization.FormatDateTimeAsRemaining(remaining); + return Embed.ErrorEmbed(string.Format(Localization.Karma.WaitUntill, author.Username, formatedWaitTime)); + } + + // Get the values for the change direction + var (title, amount) = change switch + { + KarmaChange.Up => (Localization.Karma.Increased, 1), + KarmaChange.Same => (Localization.Karma.Neutral, 0), + KarmaChange.Down => (Localization.Karma.Decreased, -1), + _ => throw new ArgumentOutOfRangeException(nameof(change), change, null) + }; + + // Change it + var targetUserRecord = await GetUser(long.Parse(targetUser.Id)); + targetUserRecord.Karma += amount; + _database.Karma.Update(targetUserRecord); + + authorRecord.TimeOut = DateTimeOffset.Now.ToUniversalTime(); + _database.Karma.Update(authorRecord); + + await _database.SaveChangesAsync(); + + // Respond + var eb = new Embed() + { + Author = new () + { + Name = targetUser.Username, + IconUrl = targetUser.GetAvatarUrl() + }, + Title = title, + }; + eb.SetColor(Color.PaleGreen); + eb.AddInlineField(Localization.Karma.By, author.Username); + eb.AddInlineField(Localization.Karma.Amount, amount.ToString()); + eb.AddInlineField(Localization.Karma.Current, targetUserRecord.Karma.ToString()); + return eb; + } + + private async Task GetUser(long userId) + { + var user = _database.Karma.FirstOrDefault(u => u.GuildId.Equals(_guildId) && u.UserId.Equals(userId)) ?? await CreateNewRow(userId); + return user; + } + + private async Task CreateNewRow(long userId) + { + var user = new KarmaModel() + { + GuildId = _guildId, + UserId = userId, + Karma = 0, + TimeOut = DateTimeOffset.MinValue.ToUniversalTime() + }; + var newUser = _database.Karma.Add(user).Entity; + await _database.SaveChangesAsync(); + return newUser; + } +} \ No newline at end of file diff --git a/src/Commands/Karma/KarmaChange.cs b/src/Commands/Karma/KarmaChange.cs new file mode 100644 index 0000000..96cbe88 --- /dev/null +++ b/src/Commands/Karma/KarmaChange.cs @@ -0,0 +1,8 @@ +namespace Geekbot.Commands.Karma; + +public enum KarmaChange +{ + Up, + Same, + Down +} diff --git a/src/Commands/Rank.cs b/src/Commands/Rank.cs new file mode 100644 index 0000000..6de2d83 --- /dev/null +++ b/src/Commands/Rank.cs @@ -0,0 +1,102 @@ +using System.Text; +using Geekbot.Core.Converters; +using Geekbot.Core.Database; +using Geekbot.Core.Extensions; +using Geekbot.Core.Highscores; +using Localization = Geekbot.Core.Localization; + +namespace Geekbot.Commands +{ + public class Rank + { + private readonly DatabaseContext _database; + private readonly IHighscoreManager _highscoreManager; + + public Rank(DatabaseContext database, IHighscoreManager highscoreManager) + { + _database = database; + _highscoreManager = highscoreManager; + } + + public string Run(string typeUnformated, int amount, string season, ulong guildId, string guildName) + { + HighscoreTypes type; + try + { + type = Enum.Parse(typeUnformated, true); + if (!Enum.IsDefined(typeof(HighscoreTypes), type)) throw new Exception(); + } + catch + { + return Localization.Rank.InvalidType; + } + + var replyBuilder = new StringBuilder(); + if (amount > 20) + { + replyBuilder.AppendLine(Localization.Rank.LimitingTo20Warning); + amount = 20; + } + + Dictionary highscoreUsers; + try + { + highscoreUsers = _highscoreManager.GetHighscoresWithUserData(type, guildId, amount, season); + } + catch (HighscoreListEmptyException) + { + return string.Format(Core.Localization.Rank.NoTypeFoundForServer, type); + } + + var guildMessages = 0; + if (type == HighscoreTypes.messages) + { + guildMessages = _database.Messages + .Where(e => e.GuildId.Equals(guildId.AsLong())) + .Select(e => e.MessageCount) + .Sum(); + } + + var failedToRetrieveUser = highscoreUsers.Any(e => string.IsNullOrEmpty(e.Key.Username)); + + if (failedToRetrieveUser) replyBuilder.AppendLine(Core.Localization.Rank.FailedToResolveAllUsernames).AppendLine(); + + if (type == HighscoreTypes.seasons) + { + if (string.IsNullOrEmpty(season)) + { + season = SeasonsUtils.GetCurrentSeason(); + } + + replyBuilder.AppendLine(string.Format(Core.Localization.Rank.HighscoresFor, $"{type.ToString().CapitalizeFirst()} ({season})", guildName)); + } + else + { + replyBuilder.AppendLine(string.Format(Core.Localization.Rank.HighscoresFor, type.ToString().CapitalizeFirst(), guildName)); + } + + var highscorePlace = 1; + foreach (var (user, value) in highscoreUsers) + { + replyBuilder.Append(highscorePlace < 11 + ? $"{EmojiConverter.NumberToEmoji(highscorePlace)} " + : $"`{highscorePlace}.` "); + + replyBuilder.Append(user.Username != null + ? $"**{user.Username}#{user.Discriminator}**" + : $"**{user.Id}**"); + + replyBuilder.Append(type switch + { + HighscoreTypes.messages => $" - {value} {HighscoreTypes.messages} - {Math.Round((double)(100 * value) / guildMessages, 2)}%\n", + HighscoreTypes.seasons => $" - {value} {HighscoreTypes.messages}\n", + _ => $" - {value} {type}\n" + }); + + highscorePlace++; + } + + return replyBuilder.ToString(); + } + } +} \ No newline at end of file diff --git a/src/Commands/Roll/Roll.cs b/src/Commands/Roll/Roll.cs new file mode 100644 index 0000000..a431bd9 --- /dev/null +++ b/src/Commands/Roll/Roll.cs @@ -0,0 +1,91 @@ +using Geekbot.Core; +using Geekbot.Core.Database; +using Geekbot.Core.Database.Models; +using Geekbot.Core.Extensions; +using Geekbot.Core.KvInMemoryStore; +using Geekbot.Core.RandomNumberGenerator; + +namespace Geekbot.Commands.Roll +{ + public class Roll + { + private readonly IKvInMemoryStore _kvInMemoryStore; + private readonly DatabaseContext _database; + private readonly IRandomNumberGenerator _randomNumberGenerator; + + public Roll(IKvInMemoryStore kvInMemoryStore, DatabaseContext database, IRandomNumberGenerator randomNumberGenerator) + { + _kvInMemoryStore = kvInMemoryStore; + _database = database; + _randomNumberGenerator = randomNumberGenerator; + } + + public async Task RunFromGateway(ulong guildId, ulong userId, string userName, string unparsedGuess) + { + int.TryParse(unparsedGuess, out var guess); + return await this.Run(guildId.AsLong(), userId.AsLong(), userName, guess); + } + + public async Task RunFromInteraction(string guildId, string userId, string userName, int guess) + { + return await this.Run(long.Parse(guildId), long.Parse(userId), userName, guess); + } + + private async Task Run(long guildId, long userId, string userName, int guess) + { + var number = _randomNumberGenerator.Next(1, 100); + + if (guess <= 100 && guess > 0) + { + var kvKey = $"{guildId}:{userId}:RollsPrevious"; + var prevRoll = _kvInMemoryStore.Get(kvKey); + + if (prevRoll?.LastGuess == guess && prevRoll?.GuessedOn.AddDays(1) > DateTime.Now) + { + return string.Format( + Core.Localization.Roll.NoPrevGuess, + $"<@{userId}>", + DateLocalization.FormatDateTimeAsRemaining(prevRoll.GuessedOn.AddDays(1))); + } + + _kvInMemoryStore.Set(kvKey, new RollTimeout { LastGuess = guess, GuessedOn = DateTime.Now }); + + var answer = string.Format(Core.Localization.Roll.Rolled, $"<@{userId}>", number, guess); + + if (guess == number) + { + var user = await GetUser(guildId, userId); + user.Rolls += 1; + _database.Rolls.Update(user); + await _database.SaveChangesAsync(); + answer += string.Format(($"\n{Core.Localization.Roll.Gratz}"), userName); + } + + return answer; + } + else + { + return string.Format(Core.Localization.Roll.RolledNoGuess, $"<@{userId}>", number); + } + } + + private async Task GetUser(long guildId, long userId) + { + var user = _database.Rolls.FirstOrDefault(u => u.GuildId.Equals(guildId) && u.UserId.Equals(userId)) ?? await CreateNewRow(guildId, userId); + return user; + } + + private async Task CreateNewRow(long guildId, long userId) + { + var user = new RollsModel() + { + GuildId = guildId, + UserId = userId, + Rolls = 0 + }; + var newUser = _database.Rolls.Add(user).Entity; + await _database.SaveChangesAsync(); + return newUser; + } + } +} \ No newline at end of file diff --git a/src/Bot/Commands/Games/Roll/RollTimeout.cs b/src/Commands/Roll/RollTimeout.cs similarity index 57% rename from src/Bot/Commands/Games/Roll/RollTimeout.cs rename to src/Commands/Roll/RollTimeout.cs index c53101a..d296c45 100644 --- a/src/Bot/Commands/Games/Roll/RollTimeout.cs +++ b/src/Commands/Roll/RollTimeout.cs @@ -1,10 +1,10 @@ -using System; - -namespace Geekbot.Bot.Commands.Games.Roll -{ - public class RollTimeout - { - public int LastGuess { get; set; } - public DateTime GuessedOn { get; set; } - } +using System; + +namespace Geekbot.Commands.Roll +{ + public record RollTimeout + { + public int LastGuess { get; set; } + public DateTime GuessedOn { get; set; } + } } \ No newline at end of file diff --git a/src/Commands/UrbanDictionary/UrbanDictionary.cs b/src/Commands/UrbanDictionary/UrbanDictionary.cs new file mode 100644 index 0000000..9862463 --- /dev/null +++ b/src/Commands/UrbanDictionary/UrbanDictionary.cs @@ -0,0 +1,37 @@ +using System.Drawing; +using Geekbot.Core; +using Geekbot.Interactions.Embed; + +namespace Geekbot.Commands.UrbanDictionary; + +public class UrbanDictionary +{ + public static async Task Run(string term) + { + var definitions = await HttpAbstractions.Get(new Uri($"https://api.urbandictionary.com/v0/define?term={term}")); + + if (definitions.List.Count == 0) + { + return null; + } + + var definition = definitions.List.First(e => !string.IsNullOrWhiteSpace(e.Example)); + + static string ShortenIfToLong(string str, int maxLength) => str.Length > maxLength ? $"{str[..(maxLength - 5)]}[...]" : str; + + var eb = new Embed(); + eb.Author = new() + { + Name = definition.Word, + Url = definition.Permalink + }; + eb.SetColor(Color.Gold); + + if (!string.IsNullOrEmpty(definition.Definition)) eb.Description = ShortenIfToLong(definition.Definition, 1800); + if (!string.IsNullOrEmpty(definition.Example)) eb.AddField("Example", ShortenIfToLong(definition.Example, 1024)); + if (definition.ThumbsUp != 0) eb.AddInlineField("Upvotes", definition.ThumbsUp.ToString()); + if (definition.ThumbsDown != 0) eb.AddInlineField("Downvotes", definition.ThumbsDown.ToString()); + + return eb; + } +} \ No newline at end of file diff --git a/src/Commands/UrbanDictionary/UrbanDictionaryListItem.cs b/src/Commands/UrbanDictionary/UrbanDictionaryListItem.cs new file mode 100644 index 0000000..822ca8a --- /dev/null +++ b/src/Commands/UrbanDictionary/UrbanDictionaryListItem.cs @@ -0,0 +1,24 @@ +using System.Text.Json.Serialization; + +namespace Geekbot.Commands.UrbanDictionary; + +public record UrbanDictionaryListItem +{ + [JsonPropertyName("definition")] + public string Definition { get; set; } + + [JsonPropertyName("permalink")] + public string Permalink { get; set; } + + [JsonPropertyName("thumbs_up")] + public int ThumbsUp { get; set; } + + [JsonPropertyName("word")] + public string Word { get; set; } + + [JsonPropertyName("example")] + public string Example { get; set; } + + [JsonPropertyName("thumbs_down")] + public int ThumbsDown { get; set; } +} diff --git a/src/Commands/UrbanDictionary/UrbanDictionaryResponse.cs b/src/Commands/UrbanDictionary/UrbanDictionaryResponse.cs new file mode 100644 index 0000000..42861c3 --- /dev/null +++ b/src/Commands/UrbanDictionary/UrbanDictionaryResponse.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace Geekbot.Commands.UrbanDictionary; + +public struct UrbanDictionaryResponse +{ + [JsonPropertyName("tags")] + public string[] Tags { get; set; } + + [JsonPropertyName("list")] + public List List { get; set; } +} \ No newline at end of file diff --git a/src/Core/BotCommandLookup/CommandInfo.cs b/src/Core/BotCommandLookup/CommandInfo.cs new file mode 100644 index 0000000..c5793ac --- /dev/null +++ b/src/Core/BotCommandLookup/CommandInfo.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Geekbot.Core.BotCommandLookup; + +public struct CommandInfo +{ + public string Name { get; set; } + public Dictionary Parameters { get; set; } + public List Aliases { get; set; } + public string Summary { get; set; } +} \ No newline at end of file diff --git a/src/Core/BotCommandLookup/CommandLookup.cs b/src/Core/BotCommandLookup/CommandLookup.cs new file mode 100644 index 0000000..62369c6 --- /dev/null +++ b/src/Core/BotCommandLookup/CommandLookup.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Discord.Commands; + +namespace Geekbot.Core.BotCommandLookup; + +public class CommandLookup +{ + private readonly Assembly _assembly; + + public CommandLookup(Assembly assembly) + { + _assembly = assembly; + } + + public List GetCommands() + { + var commands = SearchCommands(_assembly); + var result = new List(); + commands.ForEach(x => GetCommandDefinition(ref result, x)); + + return result; + } + + private List SearchCommands(Assembly assembly) + { + bool IsLoadableModule(TypeInfo info) => info.DeclaredMethods.Any(x => x.GetCustomAttribute() != null || x.GetCustomAttribute() != null); + return assembly + .DefinedTypes + .Where(typeInfo => typeInfo.IsPublic || typeInfo.IsNestedPublic) + .Where(IsLoadableModule) + .ToList(); + } + + private void GetCommandDefinition(ref List commandInfos, TypeInfo commandType) + { + var methods = commandType + .GetMethods() + .Where(x => x.GetCustomAttribute() != null) + .ToList(); + + var commandGroup = (commandType.GetCustomAttributes().FirstOrDefault(attr => attr is GroupAttribute) as GroupAttribute)?.Prefix; + + foreach (var command in methods) + { + var commandInfo = new CommandInfo() + { + Parameters = new Dictionary(), + }; + + foreach (var attr in command.GetCustomAttributes()) + { + + switch (attr) + { + case SummaryAttribute name: + commandInfo.Summary = name.Text; + break; + case CommandAttribute name: + commandInfo.Name = string.IsNullOrEmpty(commandGroup) ? name.Text : $"{commandGroup} {name.Text}"; + break; + case AliasAttribute name: + commandInfo.Aliases = name.Aliases.ToList() ?? new List(); + break; + } + } + + foreach (var param in command.GetParameters()) + { + var paramName = param.Name ?? string.Empty; + var paramInfo = new ParameterInfo() + { + Summary = param.GetCustomAttribute()?.Text ?? string.Empty, + Type = param.ParameterType.Name, + DefaultValue = param.DefaultValue?.ToString() + }; + commandInfo.Parameters.Add(paramName, paramInfo); + } + + if (!string.IsNullOrEmpty(commandInfo.Name)) + { + commandInfos.Add(commandInfo); + } + } + } +} \ No newline at end of file diff --git a/src/Core/BotCommandLookup/ParameterInfo.cs b/src/Core/BotCommandLookup/ParameterInfo.cs new file mode 100644 index 0000000..afdfd50 --- /dev/null +++ b/src/Core/BotCommandLookup/ParameterInfo.cs @@ -0,0 +1,8 @@ +namespace Geekbot.Core.BotCommandLookup; + +public struct ParameterInfo +{ + public string Summary { get; set; } + public string Type { get; set; } + public string DefaultValue { get; set; } +} \ No newline at end of file diff --git a/src/Core/Converters/EmojiConverter.cs b/src/Core/Converters/EmojiConverter.cs index 327aab8..d25415f 100644 --- a/src/Core/Converters/EmojiConverter.cs +++ b/src/Core/Converters/EmojiConverter.cs @@ -3,88 +3,133 @@ using System.Text; namespace Geekbot.Core.Converters { - public class EmojiConverter : IEmojiConverter + public static class EmojiConverter { - public string NumberToEmoji(int number) + private static readonly string[] NumberEmojiMap = + { + ":zero:", + ":one:", + ":two:", + ":three:", + ":four:", + ":five:", + ":six:", + ":seven:", + ":eight:", + ":nine:" + }; + + public static string NumberToEmoji(int number) { if (number == 10) { return "🔟"; } - var emojiMap = new[] - { - ":zero:", - ":one:", - ":two:", - ":three:", - ":four:", - ":five:", - ":six:", - ":seven:", - ":eight:", - ":nine:" - }; + var numbers = number.ToString().ToCharArray(); var returnString = new StringBuilder(); foreach (var n in numbers) { - returnString.Append(emojiMap[int.Parse(n.ToString())]); + returnString.Append(NumberEmojiMap[int.Parse(n.ToString())]); } return returnString.ToString(); } - public string TextToEmoji(string text) + private static readonly Hashtable TextEmojiMap = new Hashtable + { + ['A'] = ":regional_indicator_a: ", + ['B'] = ":b: ", + ['C'] = ":regional_indicator_c: ", + ['D'] = ":regional_indicator_d: ", + ['E'] = ":regional_indicator_e: ", + ['F'] = ":regional_indicator_f: ", + ['G'] = ":regional_indicator_g: ", + ['H'] = ":regional_indicator_h: ", + ['I'] = ":regional_indicator_i: ", + ['J'] = ":regional_indicator_j: ", + ['K'] = ":regional_indicator_k: ", + ['L'] = ":regional_indicator_l: ", + ['M'] = ":regional_indicator_m: ", + ['N'] = ":regional_indicator_n: ", + ['O'] = ":regional_indicator_o: ", + ['P'] = ":regional_indicator_p: ", + ['Q'] = ":regional_indicator_q: ", + ['R'] = ":regional_indicator_r: ", + ['S'] = ":regional_indicator_s: ", + ['T'] = ":regional_indicator_t: ", + ['U'] = ":regional_indicator_u: ", + ['V'] = ":regional_indicator_v: ", + ['W'] = ":regional_indicator_w: ", + ['X'] = ":regional_indicator_x: ", + ['Y'] = ":regional_indicator_y: ", + ['Z'] = ":regional_indicator_z: ", + ['!'] = ":exclamation: ", + ['?'] = ":question: ", + ['#'] = ":hash: ", + ['*'] = ":star2: ", + ['+'] = ":heavy_plus_sign: ", + ['0'] = ":zero: ", + ['1'] = ":one: ", + ['2'] = ":two: ", + ['3'] = ":three: ", + ['4'] = ":four: ", + ['5'] = ":five: ", + ['6'] = ":six: ", + ['7'] = ":seven: ", + ['8'] = ":eight: ", + ['9'] = ":nine: ", + [' '] = " " + }; + + public static string TextToEmoji(string text) { - var emojiMap = new Hashtable - { - ['A'] = ":regional_indicator_a: ", - ['B'] = ":b: ", - ['C'] = ":regional_indicator_c: ", - ['D'] = ":regional_indicator_d: ", - ['E'] = ":regional_indicator_e: ", - ['F'] = ":regional_indicator_f: ", - ['G'] = ":regional_indicator_g: ", - ['H'] = ":regional_indicator_h: ", - ['I'] = ":regional_indicator_i: ", - ['J'] = ":regional_indicator_j: ", - ['K'] = ":regional_indicator_k: ", - ['L'] = ":regional_indicator_l: ", - ['M'] = ":regional_indicator_m: ", - ['N'] = ":regional_indicator_n: ", - ['O'] = ":regional_indicator_o: ", - ['P'] = ":regional_indicator_p: ", - ['Q'] = ":regional_indicator_q: ", - ['R'] = ":regional_indicator_r: ", - ['S'] = ":regional_indicator_s: ", - ['T'] = ":regional_indicator_t: ", - ['U'] = ":regional_indicator_u: ", - ['V'] = ":regional_indicator_v: ", - ['W'] = ":regional_indicator_w: ", - ['X'] = ":regional_indicator_x: ", - ['Y'] = ":regional_indicator_y: ", - ['Z'] = ":regional_indicator_z: ", - ['!'] = ":exclamation: ", - ['?'] = ":question: ", - ['#'] = ":hash: ", - ['*'] = ":star2: ", - ['+'] = ":heavy_plus_sign: ", - ['0'] = ":zero: ", - ['1'] = ":one: ", - ['2'] = ":two: ", - ['3'] = ":three: ", - ['4'] = ":four: ", - ['5'] = ":five: ", - ['6'] = ":six: ", - ['7'] = ":seven: ", - ['8'] = ":eight: ", - ['9'] = ":nine: ", - [' '] = " " - }; var letters = text.ToUpper().ToCharArray(); var returnString = new StringBuilder(); foreach (var n in letters) { - var emoji = emojiMap[n] ?? n; + var emoji = TextEmojiMap[n] ?? n; + returnString.Append(emoji); + } + return returnString.ToString(); + } + + private static readonly Hashtable RegionalIndicatorMap = new Hashtable() + { + ['A'] = new Rune(0x1F1E6), + ['B'] = new Rune(0x1F1E7), + ['C'] = new Rune(0x1F1E8), + ['D'] = new Rune(0x1F1E9), + ['E'] = new Rune(0x1F1EA), + ['F'] = new Rune(0x1F1EB), + ['G'] = new Rune(0x1F1EC), + ['H'] = new Rune(0x1F1ED), + ['I'] = new Rune(0x1F1EE), + ['J'] = new Rune(0x1F1EF), + ['K'] = new Rune(0x1F1F0), + ['L'] = new Rune(0x1F1F1), + ['M'] = new Rune(0x1F1F2), + ['N'] = new Rune(0x1F1F3), + ['O'] = new Rune(0x1F1F4), + ['P'] = new Rune(0x1F1F5), + ['Q'] = new Rune(0x1F1F6), + ['R'] = new Rune(0x1F1F7), + ['S'] = new Rune(0x1F1F8), + ['T'] = new Rune(0x1F1F9), + ['U'] = new Rune(0x1F1FA), + ['V'] = new Rune(0x1F1FB), + ['W'] = new Rune(0x1F1FC), + ['X'] = new Rune(0x1F1FD), + ['Y'] = new Rune(0x1F1FE), + ['Z'] = new Rune(0x1F1FF) + }; + + public static string CountryCodeToEmoji(string countryCode) + { + var letters = countryCode.ToUpper().ToCharArray(); + var returnString = new StringBuilder(); + foreach (var n in letters) + { + var emoji = RegionalIndicatorMap[n]; returnString.Append(emoji); } return returnString.ToString(); diff --git a/src/Core/Converters/IEmojiConverter.cs b/src/Core/Converters/IEmojiConverter.cs deleted file mode 100644 index 79ca0a7..0000000 --- a/src/Core/Converters/IEmojiConverter.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Geekbot.Core.Converters -{ - public interface IEmojiConverter - { - string NumberToEmoji(int number); - string TextToEmoji(string text); - } -} \ No newline at end of file diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index c05d75c..2cd8dbc 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -1,33 +1,94 @@ - net5.0 + net6.0 $(VersionSuffix) $(VersionSuffix) 0.0.0-DEV Geekbot.Core Geekbot.Core NU1701 + True + Library + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + - - - - - - - - - - - - - - - - - + + ResXFileCodeGenerator + Admin.Designer.cs + + + ResXFileCodeGenerator + Choose.Designer.cs + + + ResXFileCodeGenerator + Cookies.Designer.cs + + + ResXFileCodeGenerator + Corona.Designer.cs + + + ResXFileCodeGenerator + EightBall.Designer.cs + + + ResXFileCodeGenerator + Internal.Designer.cs + + + ResXFileCodeGenerator + Karma.Designer.cs + + + ResXFileCodeGenerator + Quote.Designer.cs + + + ResXFileCodeGenerator + Rank.Designer.cs + + + ResXFileCodeGenerator + Role.Designer.cs + + + ResXFileCodeGenerator + Roll.Designer.cs + + + ResXFileCodeGenerator + Ship.Designer.cs + + + ResXFileCodeGenerator + Stats.Designer.cs + diff --git a/src/Core/Database/DatabaseContext.cs b/src/Core/Database/DatabaseContext.cs index 93d46d3..14a1275 100644 --- a/src/Core/Database/DatabaseContext.cs +++ b/src/Core/Database/DatabaseContext.cs @@ -11,6 +11,7 @@ namespace Geekbot.Core.Database public DbSet Karma { get; set; } public DbSet Ships { get; set; } public DbSet Rolls { get; set; } + public DbSet MessagesSeasons { get; set; } public DbSet Messages { get; set; } public DbSet Slaps { get; set; } public DbSet Globals { get; set; } diff --git a/src/Core/Database/LoggingAdapter/NpgsqlLoggingAdapter.cs b/src/Core/Database/LoggingAdapter/NpgsqlLoggingAdapter.cs index 29acb93..8a46c0d 100644 --- a/src/Core/Database/LoggingAdapter/NpgsqlLoggingAdapter.cs +++ b/src/Core/Database/LoggingAdapter/NpgsqlLoggingAdapter.cs @@ -1,8 +1,7 @@ using System; using Geekbot.Core.Logger; -using Microsoft.Extensions.Logging; using Npgsql.Logging; -using Serilog.Events; +using LogLevel = NLog.LogLevel; namespace Geekbot.Core.Database.LoggingAdapter { @@ -22,7 +21,7 @@ namespace Geekbot.Core.Database.LoggingAdapter public override bool IsEnabled(NpgsqlLogLevel level) { - return (_runParameters.DbLogging && _geekbotLogger.GetLogger().IsEnabled(ToGeekbotLogLevel(level))); + return (_runParameters.DbLogging && _geekbotLogger.GetNLogger().IsEnabled(ToGeekbotLogLevel(level))); } public override void Log(NpgsqlLogLevel level, int connectorId, string msg, Exception exception = null) @@ -52,16 +51,16 @@ namespace Geekbot.Core.Database.LoggingAdapter } } - private static LogEventLevel ToGeekbotLogLevel(NpgsqlLogLevel level) + private static LogLevel ToGeekbotLogLevel(NpgsqlLogLevel level) { return level switch { - NpgsqlLogLevel.Trace => LogEventLevel.Verbose, - NpgsqlLogLevel.Debug => LogEventLevel.Debug, - NpgsqlLogLevel.Info => LogEventLevel.Information, - NpgsqlLogLevel.Warn => LogEventLevel.Warning, - NpgsqlLogLevel.Error => LogEventLevel.Error, - NpgsqlLogLevel.Fatal => LogEventLevel.Fatal, + NpgsqlLogLevel.Trace => LogLevel.Trace, + NpgsqlLogLevel.Debug => LogLevel.Debug, + NpgsqlLogLevel.Info => LogLevel.Info, + NpgsqlLogLevel.Warn => LogLevel.Warn, + NpgsqlLogLevel.Error => LogLevel.Error, + NpgsqlLogLevel.Fatal => LogLevel.Fatal, _ => throw new ArgumentOutOfRangeException(nameof(level)) }; } diff --git a/src/Core/Database/Models/MessageSeasonsModel.cs b/src/Core/Database/Models/MessageSeasonsModel.cs new file mode 100644 index 0000000..5a4252c --- /dev/null +++ b/src/Core/Database/Models/MessageSeasonsModel.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; + +namespace Geekbot.Core.Database.Models +{ + public class MessageSeasonsModel + { + [Key] + public int Id { get; set; } + + [Required] + public long GuildId { get; set; } + + [Required] + public long UserId { get; set; } + + [Required] + public string Season { get; set; } + + public int MessageCount { get; set; } + } +} \ No newline at end of file diff --git a/src/Bot/Utils/DateLocalization.cs b/src/Core/DateLocalization.cs similarity index 86% rename from src/Bot/Utils/DateLocalization.cs rename to src/Core/DateLocalization.cs index eea40fb..39b447f 100644 --- a/src/Bot/Utils/DateLocalization.cs +++ b/src/Core/DateLocalization.cs @@ -1,13 +1,17 @@ using System; using System.Text; -namespace Geekbot.Bot.Utils +namespace Geekbot.Core { public class DateLocalization { public static string FormatDateTimeAsRemaining(DateTimeOffset dateTime) { - var remaining = dateTime - DateTimeOffset.Now; + return FormatDateTimeAsRemaining(dateTime - DateTimeOffset.Now); + } + + public static string FormatDateTimeAsRemaining(TimeSpan remaining) + { const string formattable = "{0} {1}"; var sb = new StringBuilder(); diff --git a/src/Core/DiceParser/SingleDie.cs b/src/Core/DiceParser/SingleDie.cs index 4e0f474..7c1ae41 100644 --- a/src/Core/DiceParser/SingleDie.cs +++ b/src/Core/DiceParser/SingleDie.cs @@ -63,9 +63,9 @@ namespace Geekbot.Core.DiceParser throw new DiceException("Die must have at least 2 sides") { DiceName = DiceName }; } - if (Sides > 144) + if (Sides > 145) { - throw new DiceException("Die can not have more than 144 sides") { DiceName = DiceName }; + throw new DiceException("Die can not have more than 145 sides") { DiceName = DiceName }; } } } diff --git a/src/Core/ErrorHandling/ErrorHandler.cs b/src/Core/ErrorHandling/ErrorHandler.cs index 60d97e9..0b55bd8 100644 --- a/src/Core/ErrorHandling/ErrorHandler.cs +++ b/src/Core/ErrorHandling/ErrorHandler.cs @@ -2,8 +2,7 @@ using System.Threading.Tasks; using Discord.Commands; using Geekbot.Core.Logger; -using SharpRaven; -using SharpRaven.Data; +using Sentry; using Exception = System.Exception; namespace Geekbot.Core.ErrorHandling @@ -12,7 +11,6 @@ namespace Geekbot.Core.ErrorHandling { private readonly IGeekbotLogger _logger; private readonly Func _getDefaultErrorText; - private readonly IRavenClient _raven; private readonly bool _errorsInChat; public ErrorHandler(IGeekbotLogger logger, RunParameters runParameters, Func getDefaultErrorText) @@ -20,17 +18,6 @@ namespace Geekbot.Core.ErrorHandling _logger = logger; _getDefaultErrorText = getDefaultErrorText; _errorsInChat = runParameters.ExposeErrors; - - var sentryDsn = runParameters.SentryEndpoint; - if (!string.IsNullOrEmpty(sentryDsn)) - { - _raven = new RavenClient(sentryDsn) { Release = Constants.BotVersion(), Environment = "Production" }; - _logger.Information(LogSource.Geekbot, $"Command Errors will be logged to Sentry: {sentryDsn}"); - } - else - { - _raven = null; - } } public async Task HandleCommandException(Exception e, ICommandContext context, string errorMessage = "def") @@ -83,18 +70,19 @@ namespace Geekbot.Core.ErrorHandling private void ReportExternal(Exception e, MessageDto errorObj) { - if (_raven == null) return; + if (!SentrySdk.IsEnabled) return; + var sentryEvent = new SentryEvent(e) { - Tags = - { - ["discord_server"] = errorObj.Guild.Name, - ["discord_user"] = errorObj.User.Name - }, Message = errorObj.Message.Content, - Extra = errorObj }; - _raven.Capture(sentryEvent); + sentryEvent.SetTag("discord_server", errorObj.Guild.Name); + sentryEvent.SetExtra("Channel", errorObj.Channel); + sentryEvent.SetExtra("Guild", errorObj.Guild); + sentryEvent.SetExtra("Message", errorObj.Message); + sentryEvent.SetExtra("User", errorObj.User); + + SentrySdk.CaptureEvent(sentryEvent); } } } \ No newline at end of file diff --git a/src/Core/GeekbotCommandBase.cs b/src/Core/GeekbotCommandBase.cs index 43ced95..801c53c 100644 --- a/src/Core/GeekbotCommandBase.cs +++ b/src/Core/GeekbotCommandBase.cs @@ -7,7 +7,7 @@ using Geekbot.Core.GuildSettingsManager; namespace Geekbot.Core { - public class GeekbotCommandBase : ModuleBase + public class GeekbotCommandBase : TransactionModuleBase { protected readonly IGuildSettingsManager GuildSettingsManager; protected GuildSettingsModel GuildSettings; @@ -22,9 +22,14 @@ namespace Geekbot.Core protected override void BeforeExecute(CommandInfo command) { base.BeforeExecute(command); + + var setupSpan = Transaction.StartChild("Setup"); + GuildSettings = GuildSettingsManager.GetSettings(Context?.Guild?.Id ?? 0); var language = GuildSettings.Language; - Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(language == "CHDE" ? "de-ch" : language); + Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(language); + + setupSpan.Finish(); } } } \ No newline at end of file diff --git a/src/Core/Highscores/HighscoreManager.cs b/src/Core/Highscores/HighscoreManager.cs index ac8580b..c839b39 100644 --- a/src/Core/Highscores/HighscoreManager.cs +++ b/src/Core/Highscores/HighscoreManager.cs @@ -18,7 +18,7 @@ namespace Geekbot.Core.Highscores } - public Dictionary GetHighscoresWithUserData(HighscoreTypes type, ulong guildId, int amount) + public Dictionary GetHighscoresWithUserData(HighscoreTypes type, ulong guildId, int amount, string season = null) { var list = type switch { @@ -26,6 +26,8 @@ namespace Geekbot.Core.Highscores HighscoreTypes.karma => GetKarmaList(guildId, amount), HighscoreTypes.rolls => GetRollsList(guildId, amount), HighscoreTypes.cookies => GetCookiesList(guildId, amount), + HighscoreTypes.seasons => GetMessageSeasonList(guildId, amount, season), + HighscoreTypes.quotes => GetQuotesList(guildId, amount), _ => new Dictionary() }; @@ -75,6 +77,19 @@ namespace Geekbot.Core.Highscores .ToDictionary(key => key.UserId.AsUlong(), key => key.MessageCount); } + public Dictionary GetMessageSeasonList(ulong guildId, int amount, string season) + { + if (string.IsNullOrEmpty(season)) + { + season = SeasonsUtils.GetCurrentSeason(); + } + return _database.MessagesSeasons + .Where(k => k.GuildId.Equals(guildId.AsLong()) && k.Season.Equals(season)) + .OrderByDescending(o => o.MessageCount) + .Take(amount) + .ToDictionary(key => key.UserId.AsUlong(), key => key.MessageCount); + } + public Dictionary GetKarmaList(ulong guildId, int amount) { return _database.Karma @@ -101,5 +116,16 @@ namespace Geekbot.Core.Highscores .Take(amount) .ToDictionary(key => key.UserId.AsUlong(), key => key.Cookies); } + + private Dictionary GetQuotesList(ulong guildId, int amount) + { + return _database.Quotes + .Where(row => row.GuildId == guildId.AsLong()) + .GroupBy(row => row.UserId) + .Select(row => new { userId = row.Key, amount = row.Count()}) + .OrderByDescending(row => row.amount) + .Take(amount) + .ToDictionary(key => key.userId.AsUlong(), key => key.amount); + } } } \ No newline at end of file diff --git a/src/Core/Highscores/HighscoreTypes.cs b/src/Core/Highscores/HighscoreTypes.cs index b577642..3aa396e 100644 --- a/src/Core/Highscores/HighscoreTypes.cs +++ b/src/Core/Highscores/HighscoreTypes.cs @@ -5,6 +5,8 @@ messages, karma, rolls, - cookies + cookies, + seasons, + quotes } } \ No newline at end of file diff --git a/src/Core/Highscores/IHighscoreManager.cs b/src/Core/Highscores/IHighscoreManager.cs index 83ba2da..9c2d6f0 100644 --- a/src/Core/Highscores/IHighscoreManager.cs +++ b/src/Core/Highscores/IHighscoreManager.cs @@ -4,8 +4,9 @@ namespace Geekbot.Core.Highscores { public interface IHighscoreManager { - Dictionary GetHighscoresWithUserData(HighscoreTypes type, ulong guildId, int amount); + Dictionary GetHighscoresWithUserData(HighscoreTypes type, ulong guildId, int amount, string season = null); Dictionary GetMessageList(ulong guildId, int amount); + Dictionary GetMessageSeasonList(ulong guildId, int amount, string season); Dictionary GetKarmaList(ulong guildId, int amount); Dictionary GetRollsList(ulong guildId, int amount); } diff --git a/src/Core/Highscores/SeasonsUtils.cs b/src/Core/Highscores/SeasonsUtils.cs new file mode 100644 index 0000000..d2a8e3e --- /dev/null +++ b/src/Core/Highscores/SeasonsUtils.cs @@ -0,0 +1,16 @@ +using System; +using System.Globalization; + +namespace Geekbot.Core.Highscores +{ + public class SeasonsUtils + { + public static string GetCurrentSeason() + { + var now = DateTime.Now; + var year = (now.Year - 2000).ToString(CultureInfo.InvariantCulture); + var quarter = Math.Ceiling(now.Month / 3.0).ToString(CultureInfo.InvariantCulture); + return $"{year}Q{quarter}"; + } + } +} \ No newline at end of file diff --git a/src/Core/HttpAbstractions.cs b/src/Core/HttpAbstractions.cs index 4fa287b..629de74 100644 --- a/src/Core/HttpAbstractions.cs +++ b/src/Core/HttpAbstractions.cs @@ -1,8 +1,12 @@ using System; +using System.Linq; +using System.Net; using System.Net.Http; using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; using System.Threading.Tasks; -using Newtonsoft.Json; namespace Geekbot.Core { @@ -22,21 +26,164 @@ namespace Geekbot.Core return client; } - public static async Task Get(Uri location, HttpClient httpClient = null, bool disposeClient = true) + public static async Task Get(Uri location, HttpClient httpClient = null, bool disposeClient = true, int maxRetries = 3) { httpClient ??= CreateDefaultClient(); httpClient.BaseAddress = location; - var response = await httpClient.GetAsync(location.PathAndQuery); - response.EnsureSuccessStatusCode(); - var stringResponse = await response.Content.ReadAsStringAsync(); - - if (disposeClient) + HttpResponseMessage response; + try { - httpClient.Dispose(); + response = await Execute(() => httpClient.GetAsync(location.PathAndQuery), maxRetries); + } + finally + { + if (disposeClient) + { + httpClient.Dispose(); + } } - return JsonConvert.DeserializeObject(stringResponse); + var stringResponse = await response.Content.ReadAsStringAsync(); + return JsonSerializer.Deserialize(stringResponse); + } + + public static async Task Post(Uri location, object data, HttpClient httpClient = null, bool disposeClient = true, int maxRetries = 3) + { + httpClient ??= CreateDefaultClient(); + httpClient.BaseAddress = location; + + var content = new StringContent( + JsonSerializer.Serialize(data, new JsonSerializerOptions() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }), + Encoding.UTF8, + "application/json" + ); + + HttpResponseMessage response; + try + { + response = await Execute(() => httpClient.PostAsync(location, content), maxRetries); + } + finally + { + if (disposeClient) + { + httpClient.Dispose(); + } + } + + var stringResponse = await response.Content.ReadAsStringAsync(); + return JsonSerializer.Deserialize(stringResponse); + } + + public static async Task Post(Uri location, object data, HttpClient httpClient = null, bool disposeClient = true, int maxRetries = 3) + { + httpClient ??= CreateDefaultClient(); + httpClient.BaseAddress = location; + + var content = new StringContent( + JsonSerializer.Serialize(data, new JsonSerializerOptions() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }), + Encoding.UTF8, + "application/json" + ); + + try + { + await Execute(() => httpClient.PostAsync(location, content), maxRetries); + } + finally + { + if (disposeClient) + { + httpClient.Dispose(); + } + } + } + + public static async Task Patch(Uri location, object data, HttpClient httpClient = null, bool disposeClient = true, int maxRetries = 3) + { + httpClient ??= CreateDefaultClient(); + httpClient.BaseAddress = location; + + var content = new StringContent( + JsonSerializer.Serialize(data, new JsonSerializerOptions() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }), + Encoding.UTF8, + "application/json" + ); + + try + { + await Execute(() => httpClient.PatchAsync(location, content), maxRetries); + } + finally + { + if (disposeClient) + { + httpClient.Dispose(); + } + } + } + + public static async Task Delete(Uri location, HttpClient httpClient = null, bool disposeClient = true, int maxRetries = 3) + { + httpClient ??= CreateDefaultClient(); + httpClient.BaseAddress = location; + + try + { + await Execute(() => httpClient.DeleteAsync(location), maxRetries); + } + finally + { + if (disposeClient) + { + httpClient.Dispose(); + } + } + } + + private static async Task Execute(Func> request, int maxRetries) + { + var attempt = 0; + while (true) + { + var response = await request(); + if (!response.IsSuccessStatusCode) + { + if (attempt >= maxRetries) + { + throw new HttpRequestException($"Request failed after {attempt} attempts"); + } + + if (response.Headers.Contains("Retry-After")) + { + var retryAfter = response.Headers.GetValues("Retry-After").First(); + if (retryAfter.Contains(':')) + { + var duration = DateTimeOffset.Parse(retryAfter).ToUniversalTime() - DateTimeOffset.Now.ToUniversalTime(); + await Task.Delay(duration); + } + else + { + await Task.Delay(int.Parse(retryAfter) * 1000); + } + } + else if (response.StatusCode is HttpStatusCode.BadGateway or HttpStatusCode.ServiceUnavailable or HttpStatusCode.GatewayTimeout) + { + await Task.Delay(TimeSpan.FromSeconds(Math.Ceiling(attempt * 1.5))); + } + else + { + response.EnsureSuccessStatusCode(); + } + + attempt++; + } + else + { + return response; + } + } } } } \ No newline at end of file diff --git a/src/Bot/Localization/Admin.Designer.cs b/src/Core/Localization/Admin.Designer.cs similarity index 83% rename from src/Bot/Localization/Admin.Designer.cs rename to src/Core/Localization/Admin.Designer.cs index c55adf1..460d471 100644 --- a/src/Bot/Localization/Admin.Designer.cs +++ b/src/Core/Localization/Admin.Designer.cs @@ -8,10 +8,7 @@ // //------------------------------------------------------------------------------ -namespace Geekbot.Bot.Localization { - using System; - - +namespace Geekbot.Core.Localization { /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -22,24 +19,24 @@ namespace Geekbot.Bot.Localization { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Admin { + public class Admin { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Admin() { + public Admin() { } /// /// 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 { + public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Geekbot.Bot.Localization.Admin", typeof(Admin).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Geekbot.Core.Localization.Admin", typeof(Admin).Assembly); resourceMan = temp; } return resourceMan; @@ -51,7 +48,7 @@ namespace Geekbot.Bot.Localization { /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + public static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -63,7 +60,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to I'm talking english. /// - internal static string GetLanguage { + public static string GetLanguage { get { return ResourceManager.GetString("GetLanguage", resourceCulture); } @@ -72,7 +69,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to I will reply in english from now on. /// - internal static string NewLanguageSet { + public static string NewLanguageSet { get { return ResourceManager.GetString("NewLanguageSet", resourceCulture); } diff --git a/src/Bot/Localization/Admin.de-ch.resx b/src/Core/Localization/Admin.de-ch.resx similarity index 100% rename from src/Bot/Localization/Admin.de-ch.resx rename to src/Core/Localization/Admin.de-ch.resx diff --git a/src/Bot/Localization/Admin.resx b/src/Core/Localization/Admin.resx similarity index 100% rename from src/Bot/Localization/Admin.resx rename to src/Core/Localization/Admin.resx diff --git a/src/Bot/Localization/Choose.Designer.cs b/src/Core/Localization/Choose.Designer.cs similarity index 84% rename from src/Bot/Localization/Choose.Designer.cs rename to src/Core/Localization/Choose.Designer.cs index 1319140..91ef136 100644 --- a/src/Bot/Localization/Choose.Designer.cs +++ b/src/Core/Localization/Choose.Designer.cs @@ -8,10 +8,7 @@ // //------------------------------------------------------------------------------ -namespace Geekbot.Bot.Localization { - using System; - - +namespace Geekbot.Core.Localization { /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -22,24 +19,24 @@ namespace Geekbot.Bot.Localization { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Choose { + public class Choose { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Choose() { + public Choose() { } /// /// 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 { + public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Geekbot.Bot.Localization.Choose", typeof(Choose).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Geekbot.Core.Localization.Choose", typeof(Choose).Assembly); resourceMan = temp; } return resourceMan; @@ -51,7 +48,7 @@ namespace Geekbot.Bot.Localization { /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + public static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -63,7 +60,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to I Choose **{0}**. /// - internal static string Choice { + public static string Choice { get { return ResourceManager.GetString("Choice", resourceCulture); } diff --git a/src/Bot/Localization/Choose.de-ch.resx b/src/Core/Localization/Choose.de-ch.resx similarity index 100% rename from src/Bot/Localization/Choose.de-ch.resx rename to src/Core/Localization/Choose.de-ch.resx diff --git a/src/Bot/Localization/Choose.resx b/src/Core/Localization/Choose.resx similarity index 100% rename from src/Bot/Localization/Choose.resx rename to src/Core/Localization/Choose.resx diff --git a/src/Core/Localization/Cookies.Designer.cs b/src/Core/Localization/Cookies.Designer.cs new file mode 100644 index 0000000..7a3442c --- /dev/null +++ b/src/Core/Localization/Cookies.Designer.cs @@ -0,0 +1,96 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Geekbot.Core.Localization { + using System; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Cookies { + + private static System.Resources.ResourceManager resourceMan; + + private static System.Globalization.CultureInfo resourceCulture; + + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + public Cookies() { + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + public static System.Resources.ResourceManager ResourceManager { + get { + if (object.Equals(null, resourceMan)) { + System.Resources.ResourceManager temp = new System.Resources.ResourceManager("Geekbot.Core.Localization.Cookies", typeof(Cookies).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + public static System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + public static string GetCookies { + get { + return ResourceManager.GetString("GetCookies", resourceCulture); + } + } + + public static string WaitForMoreCookies { + get { + return ResourceManager.GetString("WaitForMoreCookies", resourceCulture); + } + } + + public static string InYourJar { + get { + return ResourceManager.GetString("InYourJar", resourceCulture); + } + } + + public static string Given { + get { + return ResourceManager.GetString("Given", resourceCulture); + } + } + + public static string NotEnoughToGive { + get { + return ResourceManager.GetString("NotEnoughToGive", resourceCulture); + } + } + + public static string NotEnoughCookiesToEat { + get { + return ResourceManager.GetString("NotEnoughCookiesToEat", resourceCulture); + } + } + + public static string AteCookies { + get { + return ResourceManager.GetString("AteCookies", resourceCulture); + } + } + + public static string CantTakeCookies { + get { + return ResourceManager.GetString("CantTakeCookies", resourceCulture); + } + } + } +} diff --git a/src/Bot/Localization/Cookies.de-ch.resx b/src/Core/Localization/Cookies.de-ch.resx similarity index 89% rename from src/Bot/Localization/Cookies.de-ch.resx rename to src/Core/Localization/Cookies.de-ch.resx index b53b588..3652224 100644 --- a/src/Bot/Localization/Cookies.de-ch.resx +++ b/src/Core/Localization/Cookies.de-ch.resx @@ -32,4 +32,7 @@ Du hesch {0} guetzli gesse und hesch jezt no {1} übrig + + :police_officer: Du chasch nid guetzli vo anderne chlaue... + \ No newline at end of file diff --git a/src/Bot/Localization/Cookies.resx b/src/Core/Localization/Cookies.resx similarity index 91% rename from src/Bot/Localization/Cookies.resx rename to src/Core/Localization/Cookies.resx index 53207fa..a359c5c 100644 --- a/src/Bot/Localization/Cookies.resx +++ b/src/Core/Localization/Cookies.resx @@ -39,4 +39,7 @@ You ate {0} cookies, you've only got {1} cookies left + + You can't take someone else's cookies + \ No newline at end of file diff --git a/src/Bot/Localization/Cookies.Designer.cs b/src/Core/Localization/Corona.Designer.cs similarity index 57% rename from src/Bot/Localization/Cookies.Designer.cs rename to src/Core/Localization/Corona.Designer.cs index 5f2528f..a19bbcd 100644 --- a/src/Bot/Localization/Cookies.Designer.cs +++ b/src/Core/Localization/Corona.Designer.cs @@ -8,10 +8,7 @@ // //------------------------------------------------------------------------------ -namespace Geekbot.Bot.Localization { - using System; - - +namespace Geekbot.Core.Localization { /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -22,24 +19,24 @@ namespace Geekbot.Bot.Localization { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Cookies { + public class Corona { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Cookies() { + public Corona() { } /// /// 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 { + public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Geekbot.Bot.Localization.Cookies", typeof(Cookies).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Geekbot.Core.Localization.Corona", typeof(Corona).Assembly); resourceMan = temp; } return resourceMan; @@ -51,7 +48,7 @@ namespace Geekbot.Bot.Localization { /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + public static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -61,65 +58,56 @@ namespace Geekbot.Bot.Localization { } /// - /// Looks up a localized string similar to You ate {0} cookies, you've only got {1} cookies left. + /// Looks up a localized string similar to Active. /// - internal static string AteCookies { + public static string Active { get { - return ResourceManager.GetString("AteCookies", resourceCulture); + return ResourceManager.GetString("Active", resourceCulture); } } /// - /// Looks up a localized string similar to You got {0} cookies, there are now {1} cookies in you cookie jar. + /// Looks up a localized string similar to Confirmed Corona Cases. /// - internal static string GetCookies { + public static string ConfirmedCases { get { - return ResourceManager.GetString("GetCookies", resourceCulture); + return ResourceManager.GetString("ConfirmedCases", resourceCulture); } } /// - /// Looks up a localized string similar to You gave {0} cookies to {1}. + /// Looks up a localized string similar to Deaths. /// - internal static string Given { + public static string Deaths { get { - return ResourceManager.GetString("Given", resourceCulture); + return ResourceManager.GetString("Deaths", resourceCulture); } } /// - /// Looks up a localized string similar to There are {0} cookies in you cookie jar. + /// Looks up a localized string similar to Recovered. /// - internal static string InYourJar { + public static string Recovered { get { - return ResourceManager.GetString("InYourJar", resourceCulture); + return ResourceManager.GetString("Recovered", resourceCulture); } } /// - /// Looks up a localized string similar to Your cookie jar looks almost empty, you should probably not eat a cookie. + /// Looks up a localized string similar to Source. /// - internal static string NotEnoughCookiesToEat { + public static string Source { get { - return ResourceManager.GetString("NotEnoughCookiesToEat", resourceCulture); + return ResourceManager.GetString("Source", resourceCulture); } } /// - /// Looks up a localized string similar to You don't have enough cookies. + /// Looks up a localized string similar to Total. /// - internal static string NotEnoughToGive { + public static string Total { get { - return ResourceManager.GetString("NotEnoughToGive", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to You already got cookies today, you can have more cookies in {0}. - /// - internal static string WaitForMoreCookies { - get { - return ResourceManager.GetString("WaitForMoreCookies", resourceCulture); + return ResourceManager.GetString("Total", resourceCulture); } } } diff --git a/src/Core/Localization/Corona.de-ch.resx b/src/Core/Localization/Corona.de-ch.resx new file mode 100644 index 0000000..3c4180c --- /dev/null +++ b/src/Core/Localization/Corona.de-ch.resx @@ -0,0 +1,32 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Bstätigti Corona Fallzahle + + + Total + + + Aktiv + + + Erholt + + + Gstorbe + + + Quelle + + \ No newline at end of file diff --git a/src/Core/Localization/Corona.resx b/src/Core/Localization/Corona.resx new file mode 100644 index 0000000..44bf85e --- /dev/null +++ b/src/Core/Localization/Corona.resx @@ -0,0 +1,39 @@ + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Confirmed Corona Cases + + + Total + + + Active + + + Recovered + + + Deaths + + + Source + + \ No newline at end of file diff --git a/src/Core/Localization/EightBall.Designer.cs b/src/Core/Localization/EightBall.Designer.cs new file mode 100644 index 0000000..eee2bc5 --- /dev/null +++ b/src/Core/Localization/EightBall.Designer.cs @@ -0,0 +1,240 @@ +//------------------------------------------------------------------------------ +// +// 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 Geekbot.Core.Localization { + /// + /// 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", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class EightBall { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + public EightBall() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Geekbot.Core.Localization.EightBall", typeof(EightBall).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)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to As I see it, yes. + /// + public static string AsISeeItYes { + get { + return ResourceManager.GetString("AsISeeItYes", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Ask again later. + /// + public static string AskAgainLater { + get { + return ResourceManager.GetString("AskAgainLater", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Better not tell you now. + /// + public static string BetterNotTellYouNow { + get { + return ResourceManager.GetString("BetterNotTellYouNow", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot predict now. + /// + public static string CannotPredictNow { + get { + return ResourceManager.GetString("CannotPredictNow", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Concentrate and ask again. + /// + public static string ConcentrateAndAskAgain { + get { + return ResourceManager.GetString("ConcentrateAndAskAgain", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Don't count on it. + /// + public static string DontCountOnIt { + get { + return ResourceManager.GetString("DontCountOnIt", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to It is certain. + /// + public static string ItIsCertain { + get { + return ResourceManager.GetString("ItIsCertain", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to It is decidedly so. + /// + public static string ItIsDecidedlySo { + get { + return ResourceManager.GetString("ItIsDecidedlySo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Most likely. + /// + public static string MostLikely { + get { + return ResourceManager.GetString("MostLikely", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to My reply is no. + /// + public static string MyReplyIsNo { + get { + return ResourceManager.GetString("MyReplyIsNo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to My sources say no. + /// + public static string MySourcesSayNo { + get { + return ResourceManager.GetString("MySourcesSayNo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Outlook good. + /// + public static string OutlookGood { + get { + return ResourceManager.GetString("OutlookGood", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Outlook not so good. + /// + public static string OutlookNotSoGood { + get { + return ResourceManager.GetString("OutlookNotSoGood", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Reply hazy try again. + /// + public static string ReplyHazyTryAgain { + get { + return ResourceManager.GetString("ReplyHazyTryAgain", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Signs point to yes. + /// + public static string SignsPointToYes { + get { + return ResourceManager.GetString("SignsPointToYes", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Very doubtful. + /// + public static string VeryDoubtful { + get { + return ResourceManager.GetString("VeryDoubtful", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Without a doubt. + /// + public static string WithoutADoubt { + get { + return ResourceManager.GetString("WithoutADoubt", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Yes. + /// + public static string Yes { + get { + return ResourceManager.GetString("Yes", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Yes, definitely. + /// + public static string YesDefinitely { + get { + return ResourceManager.GetString("YesDefinitely", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You may rely on it. + /// + public static string YouMayRelyOnIt { + get { + return ResourceManager.GetString("YouMayRelyOnIt", resourceCulture); + } + } + } +} diff --git a/src/Core/Localization/EightBall.de-ch.resx b/src/Core/Localization/EightBall.de-ch.resx new file mode 100644 index 0000000..89d7a60 --- /dev/null +++ b/src/Core/Localization/EightBall.de-ch.resx @@ -0,0 +1,94 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Es isch sicher + + + + So isch es entschiede worde + + + + Ohni zwifel + + + + Ja, absolut + + + + Chasch davo usgoh + + + + Wie ich es gsehn, ja + + + + Sehr waschinli + + + + Ussicht isch guet + + + + Ja + + + + Ahzeiche zeigend uf ja + + + + Antwort isch verschwumme, versuechs nomol + + + + Frög spöter nomol + + + + Segs dir jetzt besser nid + + + + Im mommnet chani das nid vorussege + + + + Konzentrier di und frog nomol + + + + Zähl nid druf + + + + Mini antwort isch nei + + + + Mini quellene seged nei + + + + Ussicht isch ned so guet + + + + Sehr froglich + + + \ No newline at end of file diff --git a/src/Core/Localization/EightBall.resx b/src/Core/Localization/EightBall.resx new file mode 100644 index 0000000..6eba369 --- /dev/null +++ b/src/Core/Localization/EightBall.resx @@ -0,0 +1,200 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + It is certain + + + + It is decidedly so + + + + Without a doubt + + + + Yes, definitely + + + + You may rely on it + + + + As I see it, yes + + + + Most likely + + + + Outlook good + + + + Yes + + + + Signs point to yes + + + + Reply hazy try again + + + + Ask again later + + + + Better not tell you now + + + + Cannot predict now + + + + Concentrate and ask again + + + + Don't count on it + + + + My reply is no + + + + My sources say no + + + + Outlook not so good + + + + Very doubtful + + + \ No newline at end of file diff --git a/src/Bot/Localization/Internal.Designer.cs b/src/Core/Localization/Internal.Designer.cs similarity index 83% rename from src/Bot/Localization/Internal.Designer.cs rename to src/Core/Localization/Internal.Designer.cs index e7c18af..967b26e 100644 --- a/src/Bot/Localization/Internal.Designer.cs +++ b/src/Core/Localization/Internal.Designer.cs @@ -8,10 +8,7 @@ // //------------------------------------------------------------------------------ -namespace Geekbot.Bot.Localization { - using System; - - +namespace Geekbot.Core.Localization { /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -22,24 +19,24 @@ namespace Geekbot.Bot.Localization { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Internal { + public class Internal { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Internal() { + public Internal() { } /// /// 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 { + public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Geekbot.Bot.Localization.Internal", typeof(Internal).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Geekbot.Core.Localization.Internal", typeof(Internal).Assembly); resourceMan = temp; } return resourceMan; @@ -51,7 +48,7 @@ namespace Geekbot.Bot.Localization { /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + public static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -63,7 +60,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to and. /// - internal static string And { + public static string And { get { return ResourceManager.GetString("And", resourceCulture); } @@ -72,7 +69,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to day|days. /// - internal static string Days { + public static string Days { get { return ResourceManager.GetString("Days", resourceCulture); } @@ -81,7 +78,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to hour|hours. /// - internal static string Hours { + public static string Hours { get { return ResourceManager.GetString("Hours", resourceCulture); } @@ -90,7 +87,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to Seems like i don't have enough permission to that :confused:. /// - internal static string Http403 { + public static string Http403 { get { return ResourceManager.GetString("Http403", resourceCulture); } @@ -99,7 +96,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to minute|minutes. /// - internal static string Minutes { + public static string Minutes { get { return ResourceManager.GetString("Minutes", resourceCulture); } @@ -108,7 +105,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to second|seconds. /// - internal static string Seconds { + public static string Seconds { get { return ResourceManager.GetString("Seconds", resourceCulture); } @@ -117,7 +114,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to Something went wrong :confused:. /// - internal static string SomethingWentWrong { + public static string SomethingWentWrong { get { return ResourceManager.GetString("SomethingWentWrong", resourceCulture); } diff --git a/src/Bot/Localization/Internal.de-ch.resx b/src/Core/Localization/Internal.de-ch.resx similarity index 100% rename from src/Bot/Localization/Internal.de-ch.resx rename to src/Core/Localization/Internal.de-ch.resx diff --git a/src/Bot/Localization/Internal.resx b/src/Core/Localization/Internal.resx similarity index 100% rename from src/Bot/Localization/Internal.resx rename to src/Core/Localization/Internal.resx diff --git a/src/Bot/Localization/Karma.Designer.cs b/src/Core/Localization/Karma.Designer.cs similarity index 75% rename from src/Bot/Localization/Karma.Designer.cs rename to src/Core/Localization/Karma.Designer.cs index 4191fa2..b3e8325 100644 --- a/src/Bot/Localization/Karma.Designer.cs +++ b/src/Core/Localization/Karma.Designer.cs @@ -8,10 +8,7 @@ // //------------------------------------------------------------------------------ -namespace Geekbot.Bot.Localization { - using System; - - +namespace Geekbot.Core.Localization { /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -22,24 +19,24 @@ namespace Geekbot.Bot.Localization { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Karma { + public class Karma { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Karma() { + public Karma() { } /// /// 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 { + public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Geekbot.Bot.Localization.Karma", typeof(Karma).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Geekbot.Core.Localization.Karma", typeof(Karma).Assembly); resourceMan = temp; } return resourceMan; @@ -51,7 +48,7 @@ namespace Geekbot.Bot.Localization { /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + public static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -63,7 +60,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to Amount. /// - internal static string Amount { + public static string Amount { get { return ResourceManager.GetString("Amount", resourceCulture); } @@ -72,7 +69,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to By. /// - internal static string By { + public static string By { get { return ResourceManager.GetString("By", resourceCulture); } @@ -81,16 +78,25 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to Sorry {0}, but you can't lower your own karma. /// - internal static string CannotChangeOwnDown { + public static string CannotChangeOwnDown { get { return ResourceManager.GetString("CannotChangeOwnDown", resourceCulture); } } + /// + /// Looks up a localized string similar to Sorry {0}, but you can't give yourself neutral karma. + /// + public static string CannotChangeOwnSame { + get { + return ResourceManager.GetString("CannotChangeOwnSame", resourceCulture); + } + } + /// /// Looks up a localized string similar to Sorry {0}, but you can't give yourself karma. /// - internal static string CannotChangeOwnUp { + public static string CannotChangeOwnUp { get { return ResourceManager.GetString("CannotChangeOwnUp", resourceCulture); } @@ -99,7 +105,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to Current. /// - internal static string Current { + public static string Current { get { return ResourceManager.GetString("Current", resourceCulture); } @@ -108,7 +114,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to Karma lowered. /// - internal static string Decreased { + public static string Decreased { get { return ResourceManager.GetString("Decreased", resourceCulture); } @@ -117,16 +123,25 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to Gained Karma. /// - internal static string Increased { + public static string Increased { get { return ResourceManager.GetString("Increased", resourceCulture); } } + /// + /// Looks up a localized string similar to Neutral Karma. + /// + public static string Neutral { + get { + return ResourceManager.GetString("Neutral", resourceCulture); + } + } + /// /// Looks up a localized string similar to Sorry {0}, but you have to wait {1} before you can give karma again.... /// - internal static string WaitUntill { + public static string WaitUntill { get { return ResourceManager.GetString("WaitUntill", resourceCulture); } diff --git a/src/Bot/Localization/Karma.de-ch.resx b/src/Core/Localization/Karma.de-ch.resx similarity index 83% rename from src/Bot/Localization/Karma.de-ch.resx rename to src/Core/Localization/Karma.de-ch.resx index 5605f8b..5805a27 100644 --- a/src/Bot/Localization/Karma.de-ch.resx +++ b/src/Core/Localization/Karma.de-ch.resx @@ -35,4 +35,10 @@ Karma gsenkt + + Neutral Karma + + + Sorry {0}, aber du chasch dr selber kei neutrals karma geh + \ No newline at end of file diff --git a/src/Bot/Localization/Karma.resx b/src/Core/Localization/Karma.resx similarity index 85% rename from src/Bot/Localization/Karma.resx rename to src/Core/Localization/Karma.resx index 3a8fe5a..a16e14a 100644 --- a/src/Bot/Localization/Karma.resx +++ b/src/Core/Localization/Karma.resx @@ -42,4 +42,10 @@ Karma lowered + + Neutral Karma + + + Sorry {0}, but you can't give yourself neutral karma + \ No newline at end of file diff --git a/src/Bot/Localization/Quote.Designer.cs b/src/Core/Localization/Quote.Designer.cs similarity index 78% rename from src/Bot/Localization/Quote.Designer.cs rename to src/Core/Localization/Quote.Designer.cs index d0926ae..9146971 100644 --- a/src/Bot/Localization/Quote.Designer.cs +++ b/src/Core/Localization/Quote.Designer.cs @@ -8,10 +8,7 @@ // //------------------------------------------------------------------------------ -namespace Geekbot.Bot.Localization { - using System; - - +namespace Geekbot.Core.Localization { /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -22,24 +19,24 @@ namespace Geekbot.Bot.Localization { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Quote { + public class Quote { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Quote() { + public Quote() { } /// /// 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 { + public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Geekbot.Bot.Localization.Quote", typeof(Quote).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Geekbot.Core.Localization.Quote", typeof(Quote).Assembly); resourceMan = temp; } return resourceMan; @@ -51,7 +48,7 @@ namespace Geekbot.Bot.Localization { /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + public static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -63,7 +60,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to You can't save quotes by a bot.... /// - internal static string CannotQuoteBots { + public static string CannotQuoteBots { get { return ResourceManager.GetString("CannotQuoteBots", resourceCulture); } @@ -72,25 +69,16 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to You can't save your own quotes.... /// - internal static string CannotSaveOwnQuotes { + public static string CannotSaveOwnQuotes { get { return ResourceManager.GetString("CannotSaveOwnQuotes", resourceCulture); } } - /// - /// Looks up a localized string similar to :warning: Creating quotes by message ID is deprecated in favour of message links and will be removed on 1 December 2020. - /// - internal static string MessageIdDeprecation { - get { - return ResourceManager.GetString("MessageIdDeprecation", resourceCulture); - } - } - /// /// Looks up a localized string similar to Most quoted person. /// - internal static string MostQuotesPerson { + public static string MostQuotesPerson { get { return ResourceManager.GetString("MostQuotesPerson", resourceCulture); } @@ -99,7 +87,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to This server doesn't seem to have any quotes yet. You can add a quote with `!quote save @user` or `!quote save <messageId>`. /// - internal static string NoQuotesFound { + public static string NoQuotesFound { get { return ResourceManager.GetString("NoQuotesFound", resourceCulture); } @@ -108,7 +96,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to That is not a valid message link. /// - internal static string NotAValidMessageLink { + public static string NotAValidMessageLink { get { return ResourceManager.GetString("NotAValidMessageLink", resourceCulture); } @@ -117,7 +105,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to I couldn't find a quote with that ID :disappointed:. /// - internal static string NotFoundWithId { + public static string NotFoundWithId { get { return ResourceManager.GetString("NotFoundWithId", resourceCulture); } @@ -126,7 +114,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to You can only quote messages from the same server. /// - internal static string OnlyQuoteFromSameServer { + public static string OnlyQuoteFromSameServer { get { return ResourceManager.GetString("OnlyQuoteFromSameServer", resourceCulture); } @@ -135,7 +123,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to **Quote Added**. /// - internal static string QuoteAdded { + public static string QuoteAdded { get { return ResourceManager.GetString("QuoteAdded", resourceCulture); } @@ -144,7 +132,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to Quote Stats. /// - internal static string QuoteStats { + public static string QuoteStats { get { return ResourceManager.GetString("QuoteStats", resourceCulture); } @@ -153,7 +141,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to **Removed #{0}**. /// - internal static string Removed { + public static string Removed { get { return ResourceManager.GetString("Removed", resourceCulture); } @@ -162,7 +150,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to Total. /// - internal static string TotalQuotes { + public static string TotalQuotes { get { return ResourceManager.GetString("TotalQuotes", resourceCulture); } diff --git a/src/Bot/Localization/Quote.de-ch.resx b/src/Core/Localization/Quote.de-ch.resx similarity index 88% rename from src/Bot/Localization/Quote.de-ch.resx rename to src/Core/Localization/Quote.de-ch.resx index c7e3b8b..99fd959 100644 --- a/src/Bot/Localization/Quote.de-ch.resx +++ b/src/Core/Localization/Quote.de-ch.resx @@ -44,7 +44,4 @@ Du chasch numme nachrichte vom gliche server quote - - :warning: Es mache vo quotes mit message-IDs isch zgunste vo message-links veraltet und wird am 1. dezember 2020 entfernt - \ No newline at end of file diff --git a/src/Bot/Localization/Quote.resx b/src/Core/Localization/Quote.resx similarity index 89% rename from src/Bot/Localization/Quote.resx rename to src/Core/Localization/Quote.resx index 215f0ea..b51d79c 100644 --- a/src/Bot/Localization/Quote.resx +++ b/src/Core/Localization/Quote.resx @@ -51,7 +51,4 @@ You can only quote messages from the same server - - :warning: Creating quotes by message ID is deprecated in favour of message links and will be removed on 1 December 2020 - \ No newline at end of file diff --git a/src/Bot/Localization/Rank.Designer.cs b/src/Core/Localization/Rank.Designer.cs similarity index 83% rename from src/Bot/Localization/Rank.Designer.cs rename to src/Core/Localization/Rank.Designer.cs index 23f5e16..1439a8c 100644 --- a/src/Bot/Localization/Rank.Designer.cs +++ b/src/Core/Localization/Rank.Designer.cs @@ -8,10 +8,7 @@ // //------------------------------------------------------------------------------ -namespace Geekbot.Bot.Localization { - using System; - - +namespace Geekbot.Core.Localization { /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -22,24 +19,24 @@ namespace Geekbot.Bot.Localization { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Rank { + public class Rank { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Rank() { + public Rank() { } /// /// 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 { + public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Geekbot.Bot.Localization.Rank", typeof(Rank).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Geekbot.Core.Localization.Rank", typeof(Rank).Assembly); resourceMan = temp; } return resourceMan; @@ -51,7 +48,7 @@ namespace Geekbot.Bot.Localization { /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + public static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -63,7 +60,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to :warning: I couldn't find all usernames. Maybe they left the server?. /// - internal static string FailedToResolveAllUsernames { + public static string FailedToResolveAllUsernames { get { return ResourceManager.GetString("FailedToResolveAllUsernames", resourceCulture); } @@ -72,16 +69,16 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to :bar_chart: **{0} Highscore for {1}**. /// - internal static string HighscoresFor { + public static string HighscoresFor { get { return ResourceManager.GetString("HighscoresFor", resourceCulture); } } /// - /// Looks up a localized string similar to Valid types are '`messages`' '`karma`', '`rolls`' and '`cookies`'. + /// Looks up a localized string similar to Valid types are '`messages`' '`karma`', '`rolls`', '`cookies`', '`seasons`' and '`quotes`'. /// - internal static string InvalidType { + public static string InvalidType { get { return ResourceManager.GetString("InvalidType", resourceCulture); } @@ -90,7 +87,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to :warning: Limiting to 20. /// - internal static string LimitingTo20Warning { + public static string LimitingTo20Warning { get { return ResourceManager.GetString("LimitingTo20Warning", resourceCulture); } @@ -99,7 +96,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to No {0} found on this server. /// - internal static string NoTypeFoundForServer { + public static string NoTypeFoundForServer { get { return ResourceManager.GetString("NoTypeFoundForServer", resourceCulture); } diff --git a/src/Bot/Localization/Rank.de-ch.resx b/src/Core/Localization/Rank.de-ch.resx similarity index 93% rename from src/Bot/Localization/Rank.de-ch.resx rename to src/Core/Localization/Rank.de-ch.resx index 0b22fe4..743b8cd 100644 --- a/src/Bot/Localization/Rank.de-ch.resx +++ b/src/Core/Localization/Rank.de-ch.resx @@ -24,6 +24,6 @@ :bar_chart: **{0} Highscore für {1}** - Gültigi paramenter sind '`messages`' '`karma`', '`rolls`' und '`cookies` + Gültigi paramenter sind '`messages`' '`karma`', '`rolls`', '`cookies`', '`seasons`' und '`quotes`' \ No newline at end of file diff --git a/src/Bot/Localization/Rank.resx b/src/Core/Localization/Rank.resx similarity index 94% rename from src/Bot/Localization/Rank.resx rename to src/Core/Localization/Rank.resx index 9598cf8..606a34e 100644 --- a/src/Bot/Localization/Rank.resx +++ b/src/Core/Localization/Rank.resx @@ -19,7 +19,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Valid types are '`messages`' '`karma`', '`rolls`' and '`cookies`' + Valid types are '`messages`' '`karma`', '`rolls`', '`cookies`', '`seasons`' and '`quotes`' :warning: Limiting to 20 diff --git a/src/Bot/Localization/Role.Designer.cs b/src/Core/Localization/Role.Designer.cs similarity index 83% rename from src/Bot/Localization/Role.Designer.cs rename to src/Core/Localization/Role.Designer.cs index 9128e3d..fc48852 100644 --- a/src/Bot/Localization/Role.Designer.cs +++ b/src/Core/Localization/Role.Designer.cs @@ -8,10 +8,7 @@ // //------------------------------------------------------------------------------ -namespace Geekbot.Bot.Localization { - using System; - - +namespace Geekbot.Core.Localization { /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -22,24 +19,24 @@ namespace Geekbot.Bot.Localization { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Role { + public class Role { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Role() { + public Role() { } /// /// 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 { + public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Geekbot.Bot.Localization.Role", typeof(Role).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Geekbot.Core.Localization.Role", typeof(Role).Assembly); resourceMan = temp; } return resourceMan; @@ -51,7 +48,7 @@ namespace Geekbot.Bot.Localization { /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + public static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -63,7 +60,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to Added {0} to the whitelist. /// - internal static string AddedRoleToWhitelist { + public static string AddedRoleToWhitelist { get { return ResourceManager.GetString("AddedRoleToWhitelist", resourceCulture); } @@ -72,7 +69,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to Added you to {0}. /// - internal static string AddedUserFromRole { + public static string AddedUserFromRole { get { return ResourceManager.GetString("AddedUserFromRole", resourceCulture); } @@ -81,7 +78,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to You cannot add that role to self service because it contains one or more dangerous permissions. /// - internal static string CannotAddDangerousRole { + public static string CannotAddDangerousRole { get { return ResourceManager.GetString("CannotAddDangerousRole", resourceCulture); } @@ -90,7 +87,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to You can't add a role that is managed by discord. /// - internal static string CannotAddManagedRole { + public static string CannotAddManagedRole { get { return ResourceManager.GetString("CannotAddManagedRole", resourceCulture); } @@ -99,7 +96,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to **Self Service Roles on {0}**. /// - internal static string ListHeader { + public static string ListHeader { get { return ResourceManager.GetString("ListHeader", resourceCulture); } @@ -108,7 +105,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to To get a role, use `!role [name]`. /// - internal static string ListInstruction { + public static string ListInstruction { get { return ResourceManager.GetString("ListInstruction", resourceCulture); } @@ -117,7 +114,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to There are no roles configured for this server. /// - internal static string NoRolesConfigured { + public static string NoRolesConfigured { get { return ResourceManager.GetString("NoRolesConfigured", resourceCulture); } @@ -126,7 +123,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to Removed {0} from the whitelist. /// - internal static string RemovedRoleFromWhitelist { + public static string RemovedRoleFromWhitelist { get { return ResourceManager.GetString("RemovedRoleFromWhitelist", resourceCulture); } @@ -135,7 +132,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to Removed you from {0}. /// - internal static string RemovedUserFromRole { + public static string RemovedUserFromRole { get { return ResourceManager.GetString("RemovedUserFromRole", resourceCulture); } @@ -144,7 +141,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to That role doesn't exist or is not on the whitelist. /// - internal static string RoleNotFound { + public static string RoleNotFound { get { return ResourceManager.GetString("RoleNotFound", resourceCulture); } diff --git a/src/Bot/Localization/Role.de-ch.resx b/src/Core/Localization/Role.de-ch.resx similarity index 100% rename from src/Bot/Localization/Role.de-ch.resx rename to src/Core/Localization/Role.de-ch.resx diff --git a/src/Bot/Localization/Role.resx b/src/Core/Localization/Role.resx similarity index 100% rename from src/Bot/Localization/Role.resx rename to src/Core/Localization/Role.resx diff --git a/src/Bot/Localization/Roll.Designer.cs b/src/Core/Localization/Roll.Designer.cs similarity index 84% rename from src/Bot/Localization/Roll.Designer.cs rename to src/Core/Localization/Roll.Designer.cs index fda0536..bceb55d 100644 --- a/src/Bot/Localization/Roll.Designer.cs +++ b/src/Core/Localization/Roll.Designer.cs @@ -8,10 +8,7 @@ // //------------------------------------------------------------------------------ -namespace Geekbot.Bot.Localization { - using System; - - +namespace Geekbot.Core.Localization { /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -22,24 +19,24 @@ namespace Geekbot.Bot.Localization { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Roll { + public class Roll { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Roll() { + public Roll() { } /// /// 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 { + public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Geekbot.Bot.Localization.Roll", typeof(Roll).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Geekbot.Core.Localization.Roll", typeof(Roll).Assembly); resourceMan = temp; } return resourceMan; @@ -51,7 +48,7 @@ namespace Geekbot.Bot.Localization { /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + public static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -63,7 +60,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to Congratulations {0}, your guess was correct!. /// - internal static string Gratz { + public static string Gratz { get { return ResourceManager.GetString("Gratz", resourceCulture); } @@ -72,7 +69,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to :red_circle: {0}, you can't guess the same number again, guess another number or wait {1}. /// - internal static string NoPrevGuess { + public static string NoPrevGuess { get { return ResourceManager.GetString("NoPrevGuess", resourceCulture); } @@ -81,7 +78,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to {0}, you rolled {1}, your guess was {2}. /// - internal static string Rolled { + public static string Rolled { get { return ResourceManager.GetString("Rolled", resourceCulture); } @@ -90,7 +87,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to {0}, you rolled {1}. /// - internal static string RolledNoGuess { + public static string RolledNoGuess { get { return ResourceManager.GetString("RolledNoGuess", resourceCulture); } diff --git a/src/Bot/Localization/Roll.de-ch.resx b/src/Core/Localization/Roll.de-ch.resx similarity index 100% rename from src/Bot/Localization/Roll.de-ch.resx rename to src/Core/Localization/Roll.de-ch.resx diff --git a/src/Bot/Localization/Roll.resx b/src/Core/Localization/Roll.resx similarity index 100% rename from src/Bot/Localization/Roll.resx rename to src/Core/Localization/Roll.resx diff --git a/src/Bot/Localization/Ship.Designer.cs b/src/Core/Localization/Ship.Designer.cs similarity index 83% rename from src/Bot/Localization/Ship.Designer.cs rename to src/Core/Localization/Ship.Designer.cs index d959693..2d917b6 100644 --- a/src/Bot/Localization/Ship.Designer.cs +++ b/src/Core/Localization/Ship.Designer.cs @@ -8,10 +8,7 @@ // //------------------------------------------------------------------------------ -namespace Geekbot.Bot.Localization { - using System; - - +namespace Geekbot.Core.Localization { /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -22,24 +19,24 @@ namespace Geekbot.Bot.Localization { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Ship { + public class Ship { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Ship() { + public Ship() { } /// /// 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 { + public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Geekbot.Bot.Localization.Ship", typeof(Ship).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Geekbot.Core.Localization.Ship", typeof(Ship).Assembly); resourceMan = temp; } return resourceMan; @@ -51,7 +48,7 @@ namespace Geekbot.Bot.Localization { /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + public static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -63,7 +60,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to Almost a match. /// - internal static string CouldWork { + public static string CouldWork { get { return ResourceManager.GetString("CouldWork", resourceCulture); } @@ -72,7 +69,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to It's a match. /// - internal static string ItsAMatch { + public static string ItsAMatch { get { return ResourceManager.GetString("ItsAMatch", resourceCulture); } @@ -81,7 +78,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to Matchmaking. /// - internal static string Matchmaking { + public static string Matchmaking { get { return ResourceManager.GetString("Matchmaking", resourceCulture); } @@ -90,7 +87,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to Not going happen. /// - internal static string NotGoingToHappen { + public static string NotGoingToHappen { get { return ResourceManager.GetString("NotGoingToHappen", resourceCulture); } @@ -99,7 +96,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to Not such a good idea. /// - internal static string NotSuchAGoodIdea { + public static string NotSuchAGoodIdea { get { return ResourceManager.GetString("NotSuchAGoodIdea", resourceCulture); } @@ -108,7 +105,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to There might be a chance. /// - internal static string ThereMightBeAChance { + public static string ThereMightBeAChance { get { return ResourceManager.GetString("ThereMightBeAChance", resourceCulture); } diff --git a/src/Bot/Localization/Ship.de-ch.resx b/src/Core/Localization/Ship.de-ch.resx similarity index 100% rename from src/Bot/Localization/Ship.de-ch.resx rename to src/Core/Localization/Ship.de-ch.resx diff --git a/src/Bot/Localization/Ship.resx b/src/Core/Localization/Ship.resx similarity index 100% rename from src/Bot/Localization/Ship.resx rename to src/Core/Localization/Ship.resx diff --git a/src/Bot/Localization/Stats.Designer.cs b/src/Core/Localization/Stats.Designer.cs similarity index 75% rename from src/Bot/Localization/Stats.Designer.cs rename to src/Core/Localization/Stats.Designer.cs index d05f937..5f9b96c 100644 --- a/src/Bot/Localization/Stats.Designer.cs +++ b/src/Core/Localization/Stats.Designer.cs @@ -8,10 +8,7 @@ // //------------------------------------------------------------------------------ -namespace Geekbot.Bot.Localization { - using System; - - +namespace Geekbot.Core.Localization { /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -22,24 +19,24 @@ namespace Geekbot.Bot.Localization { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Stats { + public class Stats { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Stats() { + public Stats() { } /// /// 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 { + public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Geekbot.Bot.Localization.Stats", typeof(Stats).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Geekbot.Core.Localization.Stats", typeof(Stats).Assembly); resourceMan = temp; } return resourceMan; @@ -51,7 +48,7 @@ namespace Geekbot.Bot.Localization { /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + public static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -63,16 +60,25 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to Cookies. /// - internal static string Cookies { + public static string Cookies { get { return ResourceManager.GetString("Cookies", resourceCulture); } } + /// + /// Looks up a localized string similar to Days. + /// + public static string Days { + get { + return ResourceManager.GetString("Days", resourceCulture); + } + } + /// /// Looks up a localized string similar to Guessed Rolls. /// - internal static string GuessedRolls { + public static string GuessedRolls { get { return ResourceManager.GetString("GuessedRolls", resourceCulture); } @@ -81,7 +87,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to Joined Server. /// - internal static string JoinedServer { + public static string JoinedServer { get { return ResourceManager.GetString("JoinedServer", resourceCulture); } @@ -90,7 +96,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to Karma. /// - internal static string Karma { + public static string Karma { get { return ResourceManager.GetString("Karma", resourceCulture); } @@ -99,7 +105,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to Level. /// - internal static string Level { + public static string Level { get { return ResourceManager.GetString("Level", resourceCulture); } @@ -108,7 +114,7 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to Messages Sent. /// - internal static string MessagesSent { + public static string MessagesSent { get { return ResourceManager.GetString("MessagesSent", resourceCulture); } @@ -117,16 +123,25 @@ namespace Geekbot.Bot.Localization { /// /// Looks up a localized string similar to On Discord Since. /// - internal static string OnDiscordSince { + public static string OnDiscordSince { get { return ResourceManager.GetString("OnDiscordSince", resourceCulture); } } + /// + /// Looks up a localized string similar to Quotes. + /// + public static string Quotes { + get { + return ResourceManager.GetString("Quotes", resourceCulture); + } + } + /// /// Looks up a localized string similar to Server Total. /// - internal static string ServerTotal { + public static string ServerTotal { get { return ResourceManager.GetString("ServerTotal", resourceCulture); } diff --git a/src/Bot/Localization/Stats.de-ch.resx b/src/Core/Localization/Stats.de-ch.resx similarity index 86% rename from src/Bot/Localization/Stats.de-ch.resx rename to src/Core/Localization/Stats.de-ch.resx index ab44a2e..0af0477 100644 --- a/src/Bot/Localization/Stats.de-ch.resx +++ b/src/Core/Localization/Stats.de-ch.resx @@ -35,4 +35,10 @@ Guetzli + + Täg + + + Quotes + \ No newline at end of file diff --git a/src/Bot/Localization/Stats.resx b/src/Core/Localization/Stats.resx similarity index 88% rename from src/Bot/Localization/Stats.resx rename to src/Core/Localization/Stats.resx index 3b8303a..6eb3a92 100644 --- a/src/Bot/Localization/Stats.resx +++ b/src/Core/Localization/Stats.resx @@ -42,4 +42,10 @@ Cookies + + Days + + + Quotes + \ No newline at end of file diff --git a/src/Core/Logger/DiscordLogger.cs b/src/Core/Logger/Adapters/DiscordLogger.cs similarity index 94% rename from src/Core/Logger/DiscordLogger.cs rename to src/Core/Logger/Adapters/DiscordLogger.cs index f6fb95a..e9bb02e 100644 --- a/src/Core/Logger/DiscordLogger.cs +++ b/src/Core/Logger/Adapters/DiscordLogger.cs @@ -2,7 +2,7 @@ using System.Threading.Tasks; using Discord; -namespace Geekbot.Core.Logger +namespace Geekbot.Core.Logger.Adapters { public class DiscordLogger : IDiscordLogger { diff --git a/src/Core/Logger/Adapters/ILoggerAdapter.cs b/src/Core/Logger/Adapters/ILoggerAdapter.cs new file mode 100644 index 0000000..90e7bf3 --- /dev/null +++ b/src/Core/Logger/Adapters/ILoggerAdapter.cs @@ -0,0 +1,70 @@ +using System; +using Microsoft.Extensions.Logging; + +namespace Geekbot.Core.Logger.Adapters; + +public class ILoggerAdapter : ILogger +{ + private readonly string _categoryName; + private readonly LogSource _logSource; + private readonly IGeekbotLogger _geekbotLogger; + public ILoggerAdapter(string categoryName, LogSource logSource, IGeekbotLogger geekbotLogger) + { + _categoryName = categoryName; + _logSource = logSource; + _geekbotLogger = geekbotLogger; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + switch (logLevel) + { + case LogLevel.Trace: + _geekbotLogger.Trace(_logSource, $"{eventId.Id} - {_categoryName} - {state}"); + break; + case LogLevel.Debug: + _geekbotLogger.Debug(_logSource, $"{eventId.Id} - {_categoryName} - {state}"); + break; + case LogLevel.Information: + _geekbotLogger.Information(_logSource, $"{eventId.Id} - {_categoryName} - {state}"); + break; + case LogLevel.Warning: + _geekbotLogger.Warning(_logSource, $"{eventId.Id} - {_categoryName} - {state}", exception); + break; + case LogLevel.Error: + case LogLevel.Critical: + _geekbotLogger.Error(_logSource, $"{eventId.Id} - {_categoryName} - {state}", exception); + break; + case LogLevel.None: + break; + default: + throw new ArgumentOutOfRangeException(nameof(logLevel)); + } + } + + public bool IsEnabled(LogLevel logLevel) + { + return _geekbotLogger.GetNLogger().IsEnabled(ToGeekbotLogLevel(logLevel)); + // return !_geekbotLogger.LogAsJson() && _geekbotLogger.GetNLogger().IsEnabled(ToGeekbotLogLevel(logLevel)); + } + + public IDisposable BeginScope(TState state) + { + return null; + } + + private static NLog.LogLevel ToGeekbotLogLevel(LogLevel level) + { + return level switch + { + LogLevel.Trace => NLog.LogLevel.Trace, + LogLevel.Debug => NLog.LogLevel.Debug, + LogLevel.Information => NLog.LogLevel.Info, + LogLevel.Warning => NLog.LogLevel.Warn, + LogLevel.Error => NLog.LogLevel.Error, + LogLevel.Critical => NLog.LogLevel.Fatal, + LogLevel.None => NLog.LogLevel.Off, + _ => throw new ArgumentOutOfRangeException(nameof(level)) + }; + } +} \ No newline at end of file diff --git a/src/Core/Logger/Adapters/ILoggerProviderProvider.cs b/src/Core/Logger/Adapters/ILoggerProviderProvider.cs new file mode 100644 index 0000000..bc31a24 --- /dev/null +++ b/src/Core/Logger/Adapters/ILoggerProviderProvider.cs @@ -0,0 +1,33 @@ +using System.Collections.Concurrent; +using Microsoft.Extensions.Logging; + +namespace Geekbot.Core.Logger.Adapters; + +public class ILoggerProviderProvider : ILoggerProvider, ILoggerFactory +{ + private readonly IGeekbotLogger _geekbotLogger; + private readonly LogSource _logSource; + + private readonly ConcurrentDictionary _loggers = new(); + + public ILoggerProviderProvider(IGeekbotLogger geekbotLogger, LogSource logSource) + { + _geekbotLogger = geekbotLogger; + _logSource = logSource; + } + + public void Dispose() + { + _loggers.Clear(); + } + + public ILogger CreateLogger(string categoryName) + { + return _loggers.GetOrAdd(categoryName, name => new ILoggerAdapter(categoryName, _logSource, _geekbotLogger)); + } + + public void AddProvider(ILoggerProvider provider) + { + throw new System.NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/Core/Logger/ExceptionDto.cs b/src/Core/Logger/ExceptionDto.cs new file mode 100644 index 0000000..bd67d90 --- /dev/null +++ b/src/Core/Logger/ExceptionDto.cs @@ -0,0 +1,19 @@ +using System; + +namespace Geekbot.Core.Logger; + +public struct ExceptionDto +{ + public string Message { get; init; } + + public string InnerException { get; init; } + + public string Source { get; init; } + + public ExceptionDto(Exception exception) + { + Message = exception.Message; + InnerException = string.IsNullOrEmpty(exception?.InnerException?.ToString()) ? exception?.StackTrace : exception?.InnerException?.ToString(); + Source = exception.Source; + } +}; \ No newline at end of file diff --git a/src/Core/Logger/GeekbotLogger.cs b/src/Core/Logger/GeekbotLogger.cs index d9b7752..83a6d49 100644 --- a/src/Core/Logger/GeekbotLogger.cs +++ b/src/Core/Logger/GeekbotLogger.cs @@ -1,46 +1,47 @@ using System; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Geekbot.Core.Logger { public class GeekbotLogger : IGeekbotLogger { private readonly bool _logAsJson; - private readonly Serilog.ILogger _logger; - private readonly JsonSerializerSettings _serializerSettings; + private readonly NLog.Logger _logger; + private readonly JsonSerializerOptions _serializerSettings; public GeekbotLogger(RunParameters runParameters) { - _logAsJson = !string.IsNullOrEmpty(runParameters.DatadogApiKey) || runParameters.LogJson; - _logger = LoggerFactory.CreateLogger(runParameters); - _serializerSettings = new JsonSerializerSettings + _logAsJson = !string.IsNullOrEmpty(runParameters.SumologicEndpoint) || runParameters.LogJson; + _logger = LoggerFactory.CreateNLog(runParameters); + _serializerSettings = new JsonSerializerOptions { - ReferenceLoopHandling = ReferenceLoopHandling.Serialize, - NullValueHandling = NullValueHandling.Include + ReferenceHandler = ReferenceHandler.IgnoreCycles, + DefaultIgnoreCondition = JsonIgnoreCondition.Never, }; Information(LogSource.Geekbot, "Using GeekbotLogger"); } public void Trace(LogSource source, string message, object extra = null) - => _logger.Verbose(CreateLogString("Trace", source, message, null, extra)); + => _logger.Trace(CreateLogString("Trace", source, message, null, extra)); public void Debug(LogSource source, string message, object extra = null) => _logger.Debug(CreateLogString("Debug", source, message, null, extra)); public void Information(LogSource source, string message, object extra = null) - => _logger.Information(CreateLogString("Information", source, message, null, extra)); + => _logger.Info(CreateLogString("Information", source, message, null, extra)); public void Warning(LogSource source, string message, Exception stackTrace = null, object extra = null) - => _logger.Warning(CreateLogString("Warning", source, message, stackTrace, extra)); + => _logger.Warn(CreateLogString("Warning", source, message, stackTrace, extra)); public void Error(LogSource source, string message, Exception stackTrace, object extra = null) => _logger.Error(stackTrace, CreateLogString("Error", source, message, stackTrace, extra)); - public Serilog.ILogger GetLogger() => _logger; + public NLog.Logger GetNLogger() => _logger; public bool LogAsJson() => _logAsJson; - private string CreateLogString(string type, LogSource source, string message, Exception stackTrace = null, object extra = null) + private string CreateLogString(string type, LogSource source, string message, Exception exception = null, object extra = null) { if (_logAsJson) { @@ -50,10 +51,10 @@ namespace Geekbot.Core.Logger Type = type, Source = source, Message = message, - StackTrace = stackTrace, + StackTrace = exception != null ? new ExceptionDto(exception) : null, Extra = extra }; - return JsonConvert.SerializeObject(logObject, Formatting.None, _serializerSettings); + return JsonSerializer.Serialize(logObject, _serializerSettings); } if (source != LogSource.Message) return $"[{source}] - {message}"; diff --git a/src/Core/Logger/IGeekbotLogger.cs b/src/Core/Logger/IGeekbotLogger.cs index 3359d10..1363629 100644 --- a/src/Core/Logger/IGeekbotLogger.cs +++ b/src/Core/Logger/IGeekbotLogger.cs @@ -9,7 +9,7 @@ namespace Geekbot.Core.Logger void Information(LogSource source, string message, object extra = null); void Warning(LogSource source, string message, Exception stackTrace = null, object extra = null); void Error(LogSource source, string message, Exception stackTrace, object extra = null); - Serilog.ILogger GetLogger(); + NLog.Logger GetNLogger(); bool LogAsJson(); } } \ No newline at end of file diff --git a/src/Core/Logger/LogDto.cs b/src/Core/Logger/LogDto.cs index 9db0c65..ee6a3e5 100644 --- a/src/Core/Logger/LogDto.cs +++ b/src/Core/Logger/LogDto.cs @@ -1,14 +1,20 @@ using System; +using System.Text.Json.Serialization; -namespace Geekbot.Core.Logger +namespace Geekbot.Core.Logger; + +public struct GeekbotLoggerObject { - public class GeekbotLoggerObject - { - public DateTime Timestamp { get; set; } - public string Type { get; set; } - public LogSource Source { get; set; } - public string Message { get; set; } - public Exception StackTrace { get; set; } - public object Extra { get; set; } - } -} \ No newline at end of file + public DateTime Timestamp { get; set; } + + public string Type { get; set; } + + public LogSource Source { get; set; } + + public string Message { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public ExceptionDto? StackTrace { get; set; } + + public object Extra { get; set; } +} diff --git a/src/Core/Logger/LogSource.cs b/src/Core/Logger/LogSource.cs index 4518327..d2baff5 100644 --- a/src/Core/Logger/LogSource.cs +++ b/src/Core/Logger/LogSource.cs @@ -1,9 +1,8 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Text.Json.Serialization; namespace Geekbot.Core.Logger { - [JsonConverter(typeof(StringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum LogSource { Geekbot, @@ -17,6 +16,7 @@ namespace Geekbot.Core.Logger Api, Migration, HighscoreManager, + Interaction, Other } } \ No newline at end of file diff --git a/src/Core/Logger/LoggerFactory.cs b/src/Core/Logger/LoggerFactory.cs index d44c331..bf3d926 100644 --- a/src/Core/Logger/LoggerFactory.cs +++ b/src/Core/Logger/LoggerFactory.cs @@ -1,104 +1,64 @@ using System; -using Serilog; -using Serilog.Events; -using Serilog.Sinks.Datadog.Logs; +using System.Text; +using NLog; +using NLog.Config; +using NLog.Targets; +using SumoLogic.Logging.NLog; namespace Geekbot.Core.Logger { public class LoggerFactory { - public static ILogger CreateLogger(RunParameters runParameters) + public static NLog.Logger CreateNLog(RunParameters runParameters) { - var logger = new LoggerConfiguration(); - var minLevel = runParameters.Verbose ? LogEventLevel.Verbose : LogEventLevel.Information; - if (!string.IsNullOrEmpty(runParameters.DatadogApiKey)) + var config = new LoggingConfiguration(); + var minLevel = runParameters.Verbose ? LogLevel.Trace : LogLevel.Info; + + if (!string.IsNullOrEmpty(runParameters.SumologicEndpoint)) { - // 2nd error logger - var errorLogger = new LoggerConfiguration().WriteTo.Console().CreateLogger(); - errorLogger.Information("Enabling Datadog Logger"); - - logger.WriteTo.DatadogLogs( - apiKey: runParameters.DatadogApiKey, - source: "GeekbotLogger", - service: "Geekbot", - host: Environment.MachineName, - configuration: new DatadogConfiguration() - { - Url = "https://http-intake.logs.datadoghq.eu", - Port = 443, - UseSSL = true, - UseTCP = false - }, - logLevel: minLevel, - exceptionHandler: exception => - { - var cannotSendLogEventException = exception as CannotSendLogEventException; - errorLogger.Error(cannotSendLogEventException, "Datadog Error"); - } + Console.WriteLine("Logging Geekbot Logs to Sumologic"); + config.LoggingRules.Add( + new LoggingRule("*", minLevel, LogLevel.Fatal, + new SumoLogicTarget() + { + Url = runParameters.SumologicEndpoint, + SourceName = "GeekbotLogger", + Layout = "${message}", + UseConsoleLog = false, + OptimizeBufferReuse = true, + Name = "Geekbot" + }) ); } else if (runParameters.LogJson) { - logger.WriteTo.Console(restrictedToMinimumLevel: minLevel, outputTemplate: "{Message:lj}{NewLine}"); + config.LoggingRules.Add( + new LoggingRule("*", minLevel, LogLevel.Fatal, + new ConsoleTarget + { + Name = "Console", + Encoding = Encoding.UTF8, + Layout = "${message}" + } + ) + ); } else { - logger.WriteTo.Console(restrictedToMinimumLevel: minLevel); + config.LoggingRules.Add( + new LoggingRule("*", minLevel, LogLevel.Fatal, + new ColoredConsoleTarget + { + Name = "Console", + Encoding = Encoding.UTF8, + Layout = "[${longdate} ${level:format=FirstCharacter}] ${message} ${exception:format=toString}" + } + ) + ); } - return logger.CreateLogger(); + var loggerConfig = new LogFactory {Configuration = config}; + return loggerConfig.GetCurrentClassLogger(); } - - // public static NLog.Logger CreateNLog(RunParameters runParameters) - // { - // var config = new LoggingConfiguration(); - // var minLevel = runParameters.Verbose ? LogLevel.Trace : LogLevel.Info; - // - // if (!string.IsNullOrEmpty(runParameters.SumologicEndpoint)) - // { - // Console.WriteLine("Logging Geekbot Logs to Sumologic"); - // config.LoggingRules.Add( - // new LoggingRule("*", minLevel, LogLevel.Fatal, - // new SumoLogicTarget() - // { - // Url = runParameters.SumologicEndpoint, - // SourceName = "GeekbotLogger", - // Layout = "${message}", - // UseConsoleLog = false, - // OptimizeBufferReuse = true, - // Name = "Geekbot" - // }) - // ); - // } - // else if (runParameters.LogJson) - // { - // config.LoggingRules.Add( - // new LoggingRule("*", minLevel, LogLevel.Fatal, - // new ConsoleTarget - // { - // Name = "Console", - // Encoding = Encoding.UTF8, - // Layout = "${message}" - // } - // ) - // ); - // } - // else - // { - // config.LoggingRules.Add( - // new LoggingRule("*", minLevel, LogLevel.Fatal, - // new ColoredConsoleTarget - // { - // Name = "Console", - // Encoding = Encoding.UTF8, - // Layout = "[${longdate} ${level:format=FirstCharacter}] ${message} ${exception:format=toString}" - // } - // ) - // ); - // } - // - // var loggerConfig = new LogFactory {Configuration = config}; - // return loggerConfig.GetCurrentClassLogger(); - // } } } \ No newline at end of file diff --git a/src/Core/Polyfills/UserPolyfillDto.cs b/src/Core/Polyfills/UserPolyfillDto.cs index 3a8f43f..3907e05 100644 --- a/src/Core/Polyfills/UserPolyfillDto.cs +++ b/src/Core/Polyfills/UserPolyfillDto.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Threading.Tasks; using Discord; @@ -12,17 +13,29 @@ namespace Geekbot.Core.Polyfills public string Mention { get; set; } public IActivity Activity { get; } public UserStatus Status { get; set; } + IReadOnlyCollection IPresence.ActiveClients => ActiveClients; + + IReadOnlyCollection IPresence.Activities => Activities; + public IImmutableSet ActiveClients { get; } + public IImmutableList Activities { get; } + public Task CreateDMChannelAsync(RequestOptions options = null) + { + throw new NotImplementedException(); + } + public string AvatarId { get; set; } + public string AvatarUrl { get; set; } public string Discriminator { get; set; } public ushort DiscriminatorValue { get; set; } public bool IsBot { get; set; } public bool IsWebhook { get; set; } public string Username { get; set; } - + public UserProperties? PublicFlags { get; } + public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) { - return "https://discordapp.com/assets/6debd47ed13483642cf09e832ed0bc1b.png"; + return AvatarUrl ?? "https://discordapp.com/assets/6debd47ed13483642cf09e832ed0bc1b.png"; } public string GetDefaultAvatarUrl() diff --git a/src/Core/RandomNumberGenerator/RandomNumberGenerator.cs b/src/Core/RandomNumberGenerator/RandomNumberGenerator.cs index db990a7..7460677 100644 --- a/src/Core/RandomNumberGenerator/RandomNumberGenerator.cs +++ b/src/Core/RandomNumberGenerator/RandomNumberGenerator.cs @@ -1,26 +1,14 @@ using System; -using System.Security.Cryptography; -using Anemonis.RandomOrg; -using Geekbot.Core.GlobalSettings; namespace Geekbot.Core.RandomNumberGenerator { public class RandomNumberGenerator : IRandomNumberGenerator { - private readonly RNGCryptoServiceProvider csp; - private readonly bool _canUseRandomOrg; - private readonly RandomOrgClient _randomOrgClient; + private readonly System.Security.Cryptography.RandomNumberGenerator rng; - public RandomNumberGenerator(IGlobalSettings globalSettings) + public RandomNumberGenerator() { - csp = new RNGCryptoServiceProvider(); - - var randomOrgApiKey = globalSettings.GetKey("RandomOrgApiKey"); - if (!string.IsNullOrEmpty(randomOrgApiKey)) - { - _canUseRandomOrg = true; - _randomOrgClient = new RandomOrgClient(randomOrgApiKey); - } + rng = System.Security.Cryptography.RandomNumberGenerator.Create(); } public int Next(int minValue, int maxInclusiveValue) @@ -32,33 +20,12 @@ namespace Geekbot.Core.RandomNumberGenerator if (minValue >= maxInclusiveValue) { - throw new ArgumentOutOfRangeException("minValue must be lower than maxExclusiveValue"); - } - - if (_canUseRandomOrg) - { - try - { - return GetFromRandomOrg(minValue, maxInclusiveValue); - } - catch - { - // ignore - } + throw new ArgumentOutOfRangeException("minValue", "must be lower than maxExclusiveValue"); } return GetFromCrypto(minValue, maxInclusiveValue); } - private int GetFromRandomOrg(int minValue, int maxInclusiveValue) - { - return _randomOrgClient - .GenerateIntegersAsync(1, minValue, maxInclusiveValue, false) - .Result - .Random - .Data[0]; - } - private int GetFromCrypto(int minValue, int maxInclusiveValue) { var maxExclusiveValue = maxInclusiveValue + 1; @@ -84,7 +51,7 @@ namespace Geekbot.Core.RandomNumberGenerator private byte[] GenerateRandomBytes(int bytesNumber) { var buffer = new byte[bytesNumber]; - csp.GetBytes(buffer); + rng.GetBytes(buffer); return buffer; } } diff --git a/src/Core/ReactionListener/IReactionListener.cs b/src/Core/ReactionListener/IReactionListener.cs index 4909d68..c22d792 100644 --- a/src/Core/ReactionListener/IReactionListener.cs +++ b/src/Core/ReactionListener/IReactionListener.cs @@ -8,8 +8,8 @@ namespace Geekbot.Core.ReactionListener { bool IsListener(ulong id); Task AddRoleToListener(ulong messageId, ulong guildId, string emoji, IRole role); - void RemoveRole(ISocketMessageChannel channel, SocketReaction reaction); - void GiveRole(ISocketMessageChannel message, SocketReaction reaction); + void RemoveRole(IMessageChannel channel, SocketReaction reaction); + void GiveRole(IMessageChannel message, SocketReaction reaction); IEmote ConvertStringToEmote(string emoji); } } \ No newline at end of file diff --git a/src/Core/ReactionListener/ReactionListener.cs b/src/Core/ReactionListener/ReactionListener.cs index 84367c9..dcb0d5b 100644 --- a/src/Core/ReactionListener/ReactionListener.cs +++ b/src/Core/ReactionListener/ReactionListener.cs @@ -1,22 +1,29 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Threading.Tasks; using Discord; using Discord.WebSocket; using Geekbot.Core.Database; using Geekbot.Core.Database.Models; using Geekbot.Core.Extensions; +using Geekbot.Core.Logger; +using Sentry; namespace Geekbot.Core.ReactionListener { public class ReactionListener : IReactionListener { private readonly DatabaseContext _database; + + private readonly IGeekbotLogger _logger; + // private Dictionary> _listener; - public ReactionListener(DatabaseContext database) + public ReactionListener(DatabaseContext database, IGeekbotLogger logger) { _database = database; + _logger = logger; LoadListeners(); } @@ -60,20 +67,59 @@ namespace Geekbot.Core.ReactionListener _listener[messageId].Add(emote, role.Id); } - public async void RemoveRole(ISocketMessageChannel channel, SocketReaction reaction) + public async void RemoveRole(IMessageChannel channel, SocketReaction reaction) { - var roleId = _listener[reaction.MessageId][reaction.Emote]; + _listener.TryGetValue(reaction.MessageId, out var registeredReactions); + if (registeredReactions == null) return; + if (!registeredReactions.ContainsKey(reaction.Emote)) return; + var roleId = registeredReactions[reaction.Emote]; var guild = (SocketGuildChannel) channel; - var role = guild.Guild.GetRole(roleId); - await ((IGuildUser) reaction.User.Value).RemoveRoleAsync(role); + + try + { + var role = guild.Guild.GetRole(roleId); + await ((IGuildUser) reaction.User.Value).RemoveRoleAsync(role); + } + catch (Exception error) + { + HandleDeletedRole(error, guild, reaction, roleId); + } } - public async void GiveRole(ISocketMessageChannel channel, SocketReaction reaction) + public async void GiveRole(IMessageChannel channel, SocketReaction reaction) { - var roleId = _listener[reaction.MessageId][reaction.Emote]; + _listener.TryGetValue(reaction.MessageId, out var registeredReactions); + if (registeredReactions == null) return; + if (!registeredReactions.ContainsKey(reaction.Emote)) return; + var roleId = registeredReactions[reaction.Emote]; var guild = (SocketGuildChannel) channel; - var role = guild.Guild.GetRole(roleId); - await ((IGuildUser) reaction.User.Value).AddRoleAsync(role); + + try + { + + var role = guild.Guild.GetRole(roleId); + await ((IGuildUser) reaction.User.Value).AddRoleAsync(role); + } + catch (Exception error) + { + HandleDeletedRole(error, guild, reaction, roleId); + } + } + + private void HandleDeletedRole(Exception error, SocketGuildChannel guild, SocketReaction reaction, ulong roleId) + { + _logger.Warning(LogSource.Interaction, "Failed to get or assign role in reaction listener", error); + + if (!SentrySdk.IsEnabled) return; + var sentryEvent = new SentryEvent(error) + { + Message = "Failed to get or assign role in reaction listener" + }; + sentryEvent.SetTag("discord_server", guild.Id.ToString()); + sentryEvent.SetExtra("Message", reaction.MessageId.ToString()); + sentryEvent.SetExtra("User", roleId.ToString()); + + SentrySdk.CaptureEvent(sentryEvent); } public IEmote ConvertStringToEmote(string emoji) diff --git a/src/Core/RunParameters.cs b/src/Core/RunParameters.cs index 00ff5f2..a886a98 100644 --- a/src/Core/RunParameters.cs +++ b/src/Core/RunParameters.cs @@ -20,6 +20,9 @@ namespace Geekbot.Core [Option('e', "expose-errors", HelpText = "Shows internal errors in the chat (default: false) (env: EXPOSE_ERRORS)")] public bool ExposeErrors { get; set; } = ParamFallback("EXPOSE_ERRORS", false); + + [Option("disable-gateway", HelpText = "Disables the Discord Gateway (default: false) (env: GATEWAY_DISABLE)")] + public bool DisableGateway { get; set; } = ParamFallback("GATEWAY_DISABLE", false); /************************************ * Database * @@ -74,15 +77,12 @@ namespace Geekbot.Core * Intergrations * ************************************/ - // [Option("sumologic", HelpText = "Sumologic endpoint for logging (default: null) (env: SUMOLOGIC)")] - // public string SumologicEndpoint { get; set; } = ParamFallback("SUMOLOGIC"); + [Option("sumologic", HelpText = "Sumologic endpoint for logging (default: null) (env: SUMOLOGIC)")] + public string SumologicEndpoint { get; set; } = ParamFallback("SUMOLOGIC"); [Option("sentry", HelpText = "Sentry endpoint for error reporting (default: null) (env: SENTRY)")] public string SentryEndpoint { get; set; } = ParamFallback("SENTRY"); - [Option("datadog-api-key", HelpText = "Datadog API Key (default: null) (env: DATADOG_API_KEY)")] - public string DatadogApiKey { get; set; } = ParamFallback("DATADOG_API_KEY"); - /************************************ * Helper Functions * ************************************/ diff --git a/src/Core/TransactionModuleBase.cs b/src/Core/TransactionModuleBase.cs new file mode 100644 index 0000000..cbe5206 --- /dev/null +++ b/src/Core/TransactionModuleBase.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Discord; +using Discord.Commands; +using Sentry; + +namespace Geekbot.Core +{ + public class TransactionModuleBase : ModuleBase + { + protected ITransaction Transaction; + + protected override void BeforeExecute(CommandInfo command) + { + base.BeforeExecute(command); + + // Transaction Setup + Transaction = SentrySdk.StartTransaction(new Transaction(command.Name, "Exec")); + Transaction.SetTags(new [] + { + new KeyValuePair("Guild", Context.Guild.Name), + }); + Transaction.User = new User() + { + Id = Context.User.Id.ToString(), + Username = Context.User.Username, + }; + Transaction.Status = SpanStatus.Ok; + } + + protected override void AfterExecute(CommandInfo command) + { + base.AfterExecute(command); + Transaction.Finish(); + } + + protected Task ReplyAsync(string message = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null) + { + var replySpan = Transaction.StartChild("Reply"); + var msg = base.ReplyAsync(message, isTTS, embed, options, allowedMentions, messageReference); + replySpan.Finish(); + return msg; + } + } +} \ No newline at end of file diff --git a/src/Core/UserRepository/UserRepository.cs b/src/Core/UserRepository/UserRepository.cs index 31fe2e2..6b436b1 100644 --- a/src/Core/UserRepository/UserRepository.cs +++ b/src/Core/UserRepository/UserRepository.cs @@ -35,7 +35,7 @@ namespace Geekbot.Core.UserRepository savedUser.Discriminator = user.Discriminator; savedUser.AvatarUrl = user.GetAvatarUrl() ?? ""; savedUser.IsBot = user.IsBot; - savedUser.Joined = user.CreatedAt; + savedUser.Joined = user.CreatedAt.ToUniversalTime(); if (isNew) { diff --git a/src/Core/WikipediaClient/Page/PageApiUrls.cs b/src/Core/WikipediaClient/Page/PageApiUrls.cs index 6e3a1b6..59d3258 100644 --- a/src/Core/WikipediaClient/Page/PageApiUrls.cs +++ b/src/Core/WikipediaClient/Page/PageApiUrls.cs @@ -1,14 +1,26 @@ using System; +using System.Text.Json.Serialization; namespace Geekbot.Core.WikipediaClient.Page { public class PageApiUrls { + [JsonPropertyName("summary")] public Uri Summary { get; set; } + + [JsonPropertyName("metadata")] public Uri Metadata { get; set; } + + [JsonPropertyName("references")] public Uri References { get; set; } + + [JsonPropertyName("media")] public Uri Media { get; set; } + + [JsonPropertyName("edit_html")] public Uri EditHtml { get; set; } + + [JsonPropertyName("talk_page_html")] public Uri TalkPageHtml { get; set; } } } \ No newline at end of file diff --git a/src/Core/WikipediaClient/Page/PageContentUrlCollection.cs b/src/Core/WikipediaClient/Page/PageContentUrlCollection.cs index 3b567a7..39bbc0c 100644 --- a/src/Core/WikipediaClient/Page/PageContentUrlCollection.cs +++ b/src/Core/WikipediaClient/Page/PageContentUrlCollection.cs @@ -1,8 +1,13 @@ -namespace Geekbot.Core.WikipediaClient.Page +using System.Text.Json.Serialization; + +namespace Geekbot.Core.WikipediaClient.Page { public class PageContentUrlCollection { + [JsonPropertyName("desktop")] public PageContentUrls Desktop { get; set; } + + [JsonPropertyName("mobile")] public PageContentUrls Mobile { get; set; } } } \ No newline at end of file diff --git a/src/Core/WikipediaClient/Page/PageContentUrls.cs b/src/Core/WikipediaClient/Page/PageContentUrls.cs index ca30b43..8da17c4 100644 --- a/src/Core/WikipediaClient/Page/PageContentUrls.cs +++ b/src/Core/WikipediaClient/Page/PageContentUrls.cs @@ -1,12 +1,20 @@ using System; +using System.Text.Json.Serialization; namespace Geekbot.Core.WikipediaClient.Page { public class PageContentUrls { + [JsonPropertyName("page")] public Uri Page { get; set; } + + [JsonPropertyName("revisions")] public Uri Revisions { get; set; } + + [JsonPropertyName("edit")] public Uri Edit { get; set; } + + [JsonPropertyName("talk")] public Uri Talk { get; set; } } } \ No newline at end of file diff --git a/src/Core/WikipediaClient/Page/PageCoordinates.cs b/src/Core/WikipediaClient/Page/PageCoordinates.cs index 52c31a4..8537325 100644 --- a/src/Core/WikipediaClient/Page/PageCoordinates.cs +++ b/src/Core/WikipediaClient/Page/PageCoordinates.cs @@ -1,8 +1,13 @@ -namespace Geekbot.Core.WikipediaClient.Page +using System.Text.Json.Serialization; + +namespace Geekbot.Core.WikipediaClient.Page { public class PageCoordinates { + [JsonPropertyName("lat")] public float Lat { get; set; } + + [JsonPropertyName("lon")] public float Lon { get; set; } } } \ No newline at end of file diff --git a/src/Core/WikipediaClient/Page/PageImage.cs b/src/Core/WikipediaClient/Page/PageImage.cs index 8c4fb82..0d0429a 100644 --- a/src/Core/WikipediaClient/Page/PageImage.cs +++ b/src/Core/WikipediaClient/Page/PageImage.cs @@ -1,11 +1,17 @@ using System; +using System.Text.Json.Serialization; namespace Geekbot.Core.WikipediaClient.Page { public class PageImage { + [JsonPropertyName("source")] public Uri Source { get; set; } + + [JsonPropertyName("width")] public int Width { get; set; } + + [JsonPropertyName("height")] public int Height { get; set; } } diff --git a/src/Core/WikipediaClient/Page/PageNamespace.cs b/src/Core/WikipediaClient/Page/PageNamespace.cs index 0691ac3..29eaba8 100644 --- a/src/Core/WikipediaClient/Page/PageNamespace.cs +++ b/src/Core/WikipediaClient/Page/PageNamespace.cs @@ -1,8 +1,13 @@ -namespace Geekbot.Core.WikipediaClient.Page +using System.Text.Json.Serialization; + +namespace Geekbot.Core.WikipediaClient.Page { public class PageNamespace { + [JsonPropertyName("id")] public ulong Id { get; set; } + + [JsonPropertyName("text")] public string Text { get; set; } } } \ No newline at end of file diff --git a/src/Core/WikipediaClient/Page/PagePreview.cs b/src/Core/WikipediaClient/Page/PagePreview.cs index 1c1749d..b87a05e 100644 --- a/src/Core/WikipediaClient/Page/PagePreview.cs +++ b/src/Core/WikipediaClient/Page/PagePreview.cs @@ -1,67 +1,65 @@ using System; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Text.Json.Serialization; namespace Geekbot.Core.WikipediaClient.Page { public class PagePreview { - [JsonProperty("type")] - [JsonConverter(typeof(StringEnumConverter))] + [JsonPropertyName("type")] public PageTypes Type { get; set; } - [JsonProperty("title")] + [JsonPropertyName("title")] public string Title { get; set; } - [JsonProperty("displaytitle")] + [JsonPropertyName("displaytitle")] public string Displaytitle { get; set; } - [JsonProperty("namespace")] + [JsonPropertyName("namespace")] public PageNamespace Namespace { get; set; } - [JsonProperty("titles")] + [JsonPropertyName("titles")] public PageTitles Titles { get; set; } - [JsonProperty("pageid")] + [JsonPropertyName("pageid")] public ulong Pageid { get; set; } - [JsonProperty("thumbnail")] + [JsonPropertyName("thumbnail")] public PageImage Thumbnail { get; set; } - [JsonProperty("originalimage")] + [JsonPropertyName("originalimage")] public PageImage Originalimage { get; set; } - [JsonProperty("lang")] + [JsonPropertyName("lang")] public string Lang { get; set; } - [JsonProperty("dir")] + [JsonPropertyName("dir")] public string Dir { get; set; } - [JsonProperty("revision")] - public ulong Revision { get; set; } + [JsonPropertyName("revision")] + public string Revision { get; set; } - [JsonProperty("tid")] + [JsonPropertyName("tid")] public string Tid { get; set; } - [JsonProperty("timestamp")] + [JsonPropertyName("timestamp")] public DateTimeOffset Timestamp { get; set; } - [JsonProperty("description")] + [JsonPropertyName("description")] public string Description { get; set; } - [JsonProperty("coordinates")] + [JsonPropertyName("coordinates")] public PageCoordinates Coordinates { get; set; } - [JsonProperty("content_urls")] + [JsonPropertyName("content_urls")] public PageContentUrlCollection ContentUrls { get; set; } - [JsonProperty("api_urls")] + [JsonPropertyName("api_urls")] public PageApiUrls ApiUrls { get; set; } - [JsonProperty("extract")] + [JsonPropertyName("extract")] public string Extract { get; set; } - [JsonProperty("extract_html")] + [JsonPropertyName("extract_html")] public string ExtractHtml { get; set; } } } \ No newline at end of file diff --git a/src/Core/WikipediaClient/Page/PageTitles.cs b/src/Core/WikipediaClient/Page/PageTitles.cs index ad83b27..f550c38 100644 --- a/src/Core/WikipediaClient/Page/PageTitles.cs +++ b/src/Core/WikipediaClient/Page/PageTitles.cs @@ -1,10 +1,16 @@ -namespace Geekbot.Core.WikipediaClient.Page +using System.Text.Json.Serialization; + +namespace Geekbot.Core.WikipediaClient.Page { public class PageTitles { + [JsonPropertyName("Canonical")] public string Canonical { get; set; } - public string Normalized { get; set; } - public string Display { get; set; } + [JsonPropertyName("Normalized")] + public string Normalized { get; set; } + + [JsonPropertyName("Display")] + public string Display { get; set; } } } \ No newline at end of file diff --git a/src/Core/WikipediaClient/Page/PageTypes.cs b/src/Core/WikipediaClient/Page/PageTypes.cs index 8bc9f64..9ad748a 100644 --- a/src/Core/WikipediaClient/Page/PageTypes.cs +++ b/src/Core/WikipediaClient/Page/PageTypes.cs @@ -1,7 +1,9 @@ using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Geekbot.Core.WikipediaClient.Page { + [JsonConverter(typeof(JsonStringEnumConverter))] public enum PageTypes { [EnumMember(Value = "standard")] diff --git a/src/Core/WikipediaClient/WikipediaClient.cs b/src/Core/WikipediaClient/WikipediaClient.cs index f577d92..cf13277 100644 --- a/src/Core/WikipediaClient/WikipediaClient.cs +++ b/src/Core/WikipediaClient/WikipediaClient.cs @@ -1,7 +1,7 @@ using System.Net.Http; +using System.Text.Json; using System.Threading.Tasks; using Geekbot.Core.WikipediaClient.Page; -using Newtonsoft.Json; namespace Geekbot.Core.WikipediaClient { @@ -19,7 +19,7 @@ namespace Geekbot.Core.WikipediaClient response.EnsureSuccessStatusCode(); var stringResponse = await response.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject(stringResponse); + return JsonSerializer.Deserialize(stringResponse); } } } \ No newline at end of file diff --git a/src/Interactions/ApplicationCommand/Command.cs b/src/Interactions/ApplicationCommand/Command.cs new file mode 100644 index 0000000..37a0ffd --- /dev/null +++ b/src/Interactions/ApplicationCommand/Command.cs @@ -0,0 +1,72 @@ +using System.Text.Json.Serialization; + +namespace Geekbot.Interactions.ApplicationCommand +{ + /// + public record Command + { + /// + /// unique id of the command + /// + [JsonPropertyName("id")] + public string? Id { get; set; } + + /// + /// the type of command, defaults 1 if not set + /// + [JsonPropertyName("type")] + public CommandType Type { get; set; } + + /// + /// unique id of the parent application + /// + [JsonPropertyName("application_id")] + public string? ApplicationId { get; set; } + + /// + /// guild id of the command, if not global + /// + [JsonPropertyName("guild_id")] + public string? GuildId { get; set; } + + /// + /// 1-32 character name + /// + /// + /// CHAT_INPUT command names and command option names must match the following regex ^[\w-]{1,32}$ with the unicode flag set. If there is a lowercase variant of any letters used, you must use those. + /// Characters with no lowercase variants and/or uncased letters are still allowed. USER and MESSAGE commands may be mixed case and can include spaces. + /// + [JsonPropertyName("name")] + public string Name { get; set; } + + /// + /// 1-100 character description for CHAT_INPUT commands, empty string for USER and MESSAGE commands + /// + /// + /// Exclusive: CHAT_INPUT + /// + [JsonPropertyName("description")] + public string? Description { get; set; } + + /// + /// the parameters for the command, max 25 + /// + /// + /// Exclusive: CHAT_INPUT + /// + [JsonPropertyName("options")] + public List