diff --git a/.deploy.yml b/.deploy.yml
deleted file mode 100644
index 5fb394d..0000000
--- a/.deploy.yml
+++ /dev/null
@@ -1,38 +0,0 @@
----
-- name: Geekbot Deploy
- hosts: all
- remote_user: geekbot
- vars:
- ansible_port: 65432
- ansible_python_interpreter: /usr/bin/python3
- tasks:
- - name: Login to Gitlab Docker Registry
- '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
- 'community.docker.docker_container':
- name: GeekbotProd
- image: "{{ lookup('env', 'IMAGE_TAG') }}"
- recreate: yes
- pull: yes
- restart_policy: always
- keep_volumes: no
- ports:
- - "12995:12995"
- env:
- GEEKBOT_DB_HOST: "{{ lookup('env', 'GEEKBOT_DB_HOST') }}"
- GEEKBOT_DB_USER: "{{ lookup('env', 'GEEKBOT_DB_USER') }}"
- GEEKBOT_DB_PASSWORD: "{{ lookup('env', 'GEEKBOT_DB_PASSWORD') }}"
- GEEKBOT_DB_PORT: "{{ lookup('env', 'GEEKBOT_DB_PORT') }}"
- GEEKBOT_DB_DATABASE: "{{ lookup('env', 'GEEKBOT_DB_DATABASE') }}"
- GEEKBOT_DB_REQUIRE_SSL: "true"
- GEEKBOT_DB_TRUST_CERT: "true"
- GEEKBOT_SUMOLOGIC: "{{ lookup('env', 'GEEKBOT_SUMOLOCIG') }}"
- GEEKBOT_SENTRY: "{{ lookup('env', 'GEEKBOT_SENTRY') }}"
- GEEKBOT_DB_REDSHIFT_COMPAT: "true"
- - name: Cleanup Old Container
- 'community.docker.docker_prune':
- images: yes
diff --git a/.gitignore b/.gitignore
index 066c4b4..fe7e3d4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,10 +1,16 @@
-/*/**/bin
-/*/**/obj
-src/Bot/tmp/
-src/Bot/Logs/*
-!/src/Bot/Logs/.keep
+Geekbot.net/bin
+Geekbot.net/obj
+Geekbot.net/tmp/
+Tests/bin
+Tests/obj
+Backup/
.vs/
+UpgradeLog.htm
.idea
.vscode
+Geekbot.net/Logs/*
+!/Geekbot.net/Logs/.keep
Geekbot.net.sln.DotSettings.user
-app
+Geekbot.net/temp/
+WikipediaApi/bin/
+WikipediaApi/obj/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index a774f5e..98eb9ba 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,69 +1,54 @@
-stages:
- - build
- - docker
- - deploy
- - ops
-
-variables:
- 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:6.0
- artifacts:
- expire_in: 1h
- paths:
- - app
- script:
- - dotnet restore
- - dotnet test tests
- - dotnet publish --version-suffix "$VERSION" -r linux-x64 -c Release -p:DebugType=embedded --no-self-contained -o ./app ./src/Startup/
-
-Package:
- stage: docker
- image: docker
- only:
- - master
- services:
- - docker:stable-dind
- script:
- - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- - docker build -t $IMAGE_TAG .
- - docker push $IMAGE_TAG
-
-Deploy:
- stage: deploy
- image: quay.io/ansible/ansible-runner:stable-2.12-latest
- only:
- - master
- variables:
- ANSIBLE_NOCOWS: 1
- before_script:
- - mkdir /root/.ssh
- - cp $SSH_PRIVATE_KEY /root/.ssh/id_ed25519
- - cp $SSH_PUBLIC_KEY /root/.ssh/id_ed25519.pub
- - 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:
- stage: ops
- image: getsentry/sentry-cli
- allow_failure: true
- only:
- - master
- script:
- - sentry-cli releases new -p geekbot $VERSION
- - sentry-cli releases set-commits --auto $VERSION
- - sentry-cli releases deploys $VERSION new -e Production
-
-Github Mirror:
- stage: ops
- image: runebaas/rsync-ssh-git
- only:
- - master
- script:
- - git push https://runebaas:$TOKEN@github.com/pizzaandcoffee/Geekbot.net.git origin/master:master -f
+stages:
+ - build
+ - deploy
+
+before_script:
+ - set -e
+ - set -u
+ - set -o pipefail
+
+build:
+ stage: build
+ image: microsoft/dotnet:2.0.3-sdk-stretch
+ variables:
+ NUGET_PACKAGES: "${CI_PROJECT_DIR}/.nugetcache"
+ cache:
+ paths:
+ - .nugetcache
+ artifacts:
+ expire_in: 1h
+ paths:
+ - Geekbot.net/Binaries/
+ script:
+ - dotnet restore
+ - dotnet test Tests
+ - dotnet publish --version-suffix ${CI_COMMIT_SHA:0:8} --configuration Release -o Binaries ./
+
+deploy:
+ stage: deploy
+ image: instrumentisto/rsync-ssh
+ only:
+ - master
+ dependencies:
+ - build
+ environment:
+ name: Production
+ url: https://discordapp.com/oauth2/authorize?client_id=171249478546882561&scope=bot&permissions=1416834054
+ before_script:
+ - eval $(ssh-agent -s)
+ - mkdir -p ~/.ssh
+ - '[[ -f /.dockerenv ]] && echo -e "Host *\n StrictHostKeyChecking no" > ~/.ssh/config'
+ - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
+ - chmod 700 ~/.ssh
+ script:
+ - rsync -rav -e "ssh -p 65432" ./Geekbot.net/Binaries/* www-data@31.220.42.224:$DEPPATH
+ - ssh -p 65432 www-data@31.220.42.224 "sudo systemctl restart geekbot.service"
+
+mirror:
+ stage: deploy
+ image: bravissimolabs/alpine-git:latest
+ only:
+ - master
+ script:
+ - git push https://runebaas:$TOKEN@github.com/pizzaandcoffee/Geekbot.net.git origin/master:master -f
+
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index 39529ff..0000000
--- a/Dockerfile
+++ /dev/null
@@ -1,7 +0,0 @@
-FROM mcr.microsoft.com/dotnet/aspnet:6.0
-
-COPY ./app /app/
-
-EXPOSE 12995/tcp
-WORKDIR /app
-ENTRYPOINT ./Geekbot
diff --git a/Geekbot.net.sln b/Geekbot.net.sln
index f33d887..b542f25 100644
--- a/Geekbot.net.sln
+++ b/Geekbot.net.sln
@@ -3,19 +3,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
VisualStudioVersion = 12.0.0.0
MinimumVisualStudioVersion = 10.0.0.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "tests\Tests.csproj", "{4CAF5F02-EFFE-4FDA-BD44-EEADDBA9600E}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Geekbot.net", "Geekbot.net/Geekbot.net.csproj", "{FDCB3D92-E7B5-47BB-A9B5-CFAEFA57CDB4}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "src\Core\Core.csproj", "{47671723-52A9-4668-BBC5-2BA76AE3B288}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{4CAF5F02-EFFE-4FDA-BD44-EEADDBA9600E}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Web", "src\Web\Web.csproj", "{0A63D5DC-6325-4F53-8ED2-9843239B76CC}"
-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}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WikipediaApi", "WikipediaApi\WikipediaApi.csproj", "{1084D499-EF94-4834-9E6A-B2AD81B60078}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -23,34 +15,18 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {FDCB3D92-E7B5-47BB-A9B5-CFAEFA57CDB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FDCB3D92-E7B5-47BB-A9B5-CFAEFA57CDB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FDCB3D92-E7B5-47BB-A9B5-CFAEFA57CDB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FDCB3D92-E7B5-47BB-A9B5-CFAEFA57CDB4}.Release|Any CPU.Build.0 = Release|Any CPU
{4CAF5F02-EFFE-4FDA-BD44-EEADDBA9600E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4CAF5F02-EFFE-4FDA-BD44-EEADDBA9600E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4CAF5F02-EFFE-4FDA-BD44-EEADDBA9600E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4CAF5F02-EFFE-4FDA-BD44-EEADDBA9600E}.Release|Any CPU.Build.0 = Release|Any CPU
- {47671723-52A9-4668-BBC5-2BA76AE3B288}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {47671723-52A9-4668-BBC5-2BA76AE3B288}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {47671723-52A9-4668-BBC5-2BA76AE3B288}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {47671723-52A9-4668-BBC5-2BA76AE3B288}.Release|Any CPU.Build.0 = Release|Any CPU
- {0A63D5DC-6325-4F53-8ED2-9843239B76CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {0A63D5DC-6325-4F53-8ED2-9843239B76CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {0A63D5DC-6325-4F53-8ED2-9843239B76CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {0A63D5DC-6325-4F53-8ED2-9843239B76CC}.Release|Any CPU.Build.0 = Release|Any CPU
- {DBF79896-9F7F-443D-B336-155E276DFF16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {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
+ {1084D499-EF94-4834-9E6A-B2AD81B60078}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1084D499-EF94-4834-9E6A-B2AD81B60078}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1084D499-EF94-4834-9E6A-B2AD81B60078}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1084D499-EF94-4834-9E6A-B2AD81B60078}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Geekbot.net.sln.DotSettings b/Geekbot.net.sln.DotSettings
deleted file mode 100644
index 6d439b8..0000000
--- a/Geekbot.net.sln.DotSettings
+++ /dev/null
@@ -1,12 +0,0 @@
-
- NEVER
- 200
- True
- True
- True
- True
- True
- True
- True
- True
- True
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Admin/Admin.cs b/Geekbot.net/Commands/Admin/Admin.cs
new file mode 100644
index 0000000..7d30689
--- /dev/null
+++ b/Geekbot.net/Commands/Admin/Admin.cs
@@ -0,0 +1,196 @@
+using System;
+using System.Text;
+using System.Threading.Tasks;
+using Discord;
+using Discord.Commands;
+using Discord.WebSocket;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+using Geekbot.net.Lib.Localization;
+using StackExchange.Redis;
+
+namespace Geekbot.net.Commands.Admin
+{
+ [Group("admin")]
+ [RequireUserPermission(GuildPermission.Administrator)]
+ public class Admin : ModuleBase
+ {
+ private readonly DiscordSocketClient _client;
+ private readonly IErrorHandler _errorHandler;
+ private readonly IDatabase _redis;
+ private readonly ITranslationHandler _translation;
+
+ public Admin(IDatabase redis, DiscordSocketClient client, IErrorHandler errorHandler,
+ ITranslationHandler translationHandler)
+ {
+ _redis = redis;
+ _client = client;
+ _errorHandler = errorHandler;
+ _translation = translationHandler;
+ }
+
+ [Command("welcome", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Admin)]
+ [Summary("Set a Welcome Message (use '$user' to mention the new joined user).")]
+ public async Task SetWelcomeMessage([Remainder] [Summary("message")] string welcomeMessage)
+ {
+ _redis.HashSet($"{Context.Guild.Id}:Settings", new[] {new HashEntry("WelcomeMsg", welcomeMessage)});
+ var formatedMessage = welcomeMessage.Replace("$user", Context.User.Mention);
+ await ReplyAsync($"Welcome message has been changed\r\nHere is an example of how it would look:\r\n{formatedMessage}");
+ }
+
+ [Command("modchannel", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Admin)]
+ [Summary("Set a channel for moderation purposes")]
+ public async Task SelectModChannel([Summary("#Channel")] ISocketMessageChannel channel)
+ {
+ try
+ {
+ var sb = new StringBuilder();
+ sb.AppendLine("Successfully saved mod channel, you can now do the following");
+ sb.AppendLine("- `!admin showleave true` - send message to mod channel when someone leaves");
+ sb.AppendLine("- `!admin showdel true` - send message to mod channel when someone deletes a message");
+ await channel.SendMessageAsync(sb.ToString());
+ _redis.HashSet($"{Context.Guild.Id}:Settings",
+ new[] {new HashEntry("ModChannel", channel.Id.ToString())});
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context, "That channel doesn't seem to be valid");
+ }
+ }
+
+ [Command("showleave", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Admin)]
+ [Summary("Notify modchannel when someone leaves")]
+ public async Task ShowLeave([Summary("true/false")] bool enabled)
+ {
+ var modChannelId = ulong.Parse(_redis.HashGet($"{Context.Guild.Id}:Settings", "ModChannel"));
+ try
+ {
+ var modChannel = (ISocketMessageChannel) _client.GetChannel(modChannelId);
+ if (enabled)
+ {
+ await modChannel.SendMessageAsync("Saved - now sending messages here when someone leaves");
+ _redis.HashSet($"{Context.Guild.Id}:Settings", new[] {new HashEntry("ShowLeave", true)});
+ }
+ else
+ {
+ await modChannel.SendMessageAsync("Saved - stopping sending messages here when someone leaves");
+ _redis.HashSet($"{Context.Guild.Id}:Settings", new[] {new HashEntry("ShowLeave", false)});
+ }
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context,
+ "Modchannel doesn't seem to exist, please set one with `!admin modchannel [channelId]`");
+ }
+ }
+
+ [Command("showdel", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Admin)]
+ [Summary("Notify modchannel when someone deletes a message")]
+ public async Task ShowDelete([Summary("true/false")] bool enabled)
+ {
+ var modChannelId = ulong.Parse(_redis.HashGet($"{Context.Guild.Id}:Settings", "ModChannel"));
+ try
+ {
+ var modChannel = (ISocketMessageChannel) _client.GetChannel(modChannelId);
+ if (enabled)
+ {
+ await modChannel.SendMessageAsync(
+ "Saved - now sending messages here when someone deletes a message");
+ _redis.HashSet($"{Context.Guild.Id}:Settings", new[] {new HashEntry("ShowDelete", true)});
+ }
+ else
+ {
+ await modChannel.SendMessageAsync(
+ "Saved - stopping sending messages here when someone deletes a message");
+ _redis.HashSet($"{Context.Guild.Id}:Settings", new[] {new HashEntry("ShowDelete", false)});
+ }
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context,
+ "Modchannel doesn't seem to exist, please set one with `!admin modchannel [channelId]`");
+ }
+ }
+
+ [Command("setlang", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Admin)]
+ [Summary("Change the bots language")]
+ public async Task SetLanguage([Summary("language")] string languageRaw)
+ {
+ try
+ {
+ var language = languageRaw.ToUpper();
+ var success = _translation.SetLanguage(Context.Guild.Id, language);
+ if (success)
+ {
+ var trans = _translation.GetDict(Context);
+ await ReplyAsync(trans["NewLanguageSet"]);
+ return;
+ }
+
+ await ReplyAsync(
+ $"That doesn't seem to be a supported language\r\nSupported Languages are {string.Join(", ", _translation.GetSupportedLanguages())}");
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+
+ [Command("wiki", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Admin)]
+ [Summary("Change the wikipedia instance (use lang code in xx.wikipedia.org)")]
+ public async Task SetWikiLanguage([Summary("language")] string languageRaw)
+ {
+ try
+ {
+ var language = languageRaw.ToLower();
+ _redis.HashSet($"{Context.Guild.Id}:Settings", new[] {new HashEntry("WikiLang", language) });
+
+ await ReplyAsync($"Now using the {language} wikipedia");
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+
+ [Command("lang", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Admin)]
+ [Summary("Change the bots language")]
+ public async Task GetLanguage()
+ {
+ try
+ {
+ var trans = _translation.GetDict(Context);
+ await ReplyAsync(trans["GetLanguage"]);
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+
+ [Command("ping", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Admin)]
+ [Summary("Enable the ping reply.")]
+ public async Task TogglePing()
+ {
+ try
+ {
+ bool.TryParse(_redis.HashGet($"{Context.Guild.Id}:Settings", "ping"), out var current);
+ _redis.HashSet($"{Context.Guild.Id}:Settings", new[] {new HashEntry("ping", current ? "false" : "true") });
+ await ReplyAsync(!current ? "i will reply to ping now" : "No more pongs...");
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Admin/Mod.cs b/Geekbot.net/Commands/Admin/Mod.cs
new file mode 100644
index 0000000..7551a47
--- /dev/null
+++ b/Geekbot.net/Commands/Admin/Mod.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Text;
+using System.Threading.Tasks;
+using Discord;
+using Discord.Commands;
+using Discord.WebSocket;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+using Geekbot.net.Lib.Extensions;
+using Geekbot.net.Lib.UserRepository;
+using StackExchange.Redis;
+
+namespace Geekbot.net.Commands.Admin
+{
+ [Group("mod")]
+ [RequireUserPermission(GuildPermission.KickMembers)]
+ [RequireUserPermission(GuildPermission.ManageMessages)]
+ [RequireUserPermission(GuildPermission.ManageRoles)]
+ public class Mod : ModuleBase
+ {
+ private readonly DiscordSocketClient _client;
+ private readonly IErrorHandler _errorHandler;
+ private readonly IDatabase _redis;
+ private readonly IUserRepository _userRepository;
+
+ public Mod(IUserRepository userRepositry, IErrorHandler errorHandler, IDatabase redis,
+ DiscordSocketClient client)
+ {
+ _userRepository = userRepositry;
+ _errorHandler = errorHandler;
+ _redis = redis;
+ _client = client;
+ }
+
+ [Command("namehistory", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Admin)]
+ [Summary("See past usernames of an user")]
+ public async Task UsernameHistory([Summary("@user")] IUser user)
+ {
+ try
+ {
+ var userRepo = _userRepository.Get(user.Id);
+ var sb = new StringBuilder();
+ sb.AppendLine($":bust_in_silhouette: {user.Username} has been known as:");
+ foreach (var name in userRepo.UsedNames) sb.AppendLine($"- `{name}`");
+ await ReplyAsync(sb.ToString());
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context,
+ $"I don't have enough permissions do that");
+ }
+ }
+
+ [Command("kick", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Admin)]
+ [Summary("Ban a user")]
+ public async Task Kick([Summary("@user")] IUser userNormal,
+ [Summary("reason")] [Remainder] string reason = "none")
+ {
+ try
+ {
+ var user = (IGuildUser) userNormal;
+ if (reason == "none") reason = "No reason provided";
+ await user.GetOrCreateDMChannelAsync().Result.SendMessageAsync(
+ $"You have been kicked from {Context.Guild.Name} for the following reason: \"{reason}\"");
+ await user.KickAsync();
+ try
+ {
+ var modChannelId = ulong.Parse(_redis.HashGet($"{Context.Guild.Id}:Settings", "ModChannel"));
+ var modChannel = (ISocketMessageChannel) _client.GetChannel(modChannelId);
+ var eb = new EmbedBuilder();
+ eb.Title = ":x: User Kicked";
+ eb.AddInlineField("User", user.Username);
+ eb.AddInlineField("By Mod", Context.User.Username);
+ eb.AddField("Reason", reason);
+ await modChannel.SendMessageAsync("", false, eb.Build());
+ }
+ catch
+ {
+ await ReplyAsync($"{user.Username} was kicked for the following reason: \"{reason}\"");
+ }
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context, "I don't have enough permissions to kick someone");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Admin/Owner.cs b/Geekbot.net/Commands/Admin/Owner.cs
new file mode 100644
index 0000000..2d097e0
--- /dev/null
+++ b/Geekbot.net/Commands/Admin/Owner.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Threading.Tasks;
+using Discord;
+using Discord.Commands;
+using Discord.WebSocket;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+using Geekbot.net.Lib.Logger;
+using Geekbot.net.Lib.UserRepository;
+using StackExchange.Redis;
+
+namespace Geekbot.net.Commands.Admin
+{
+ [Group("owner")]
+ [RequireOwner]
+ public class Owner : ModuleBase
+ {
+ private readonly DiscordSocketClient _client;
+ private readonly IErrorHandler _errorHandler;
+ private readonly IGeekbotLogger _logger;
+ private readonly IDatabase _redis;
+ private readonly IUserRepository _userRepository;
+
+ public Owner(IDatabase redis, DiscordSocketClient client, IGeekbotLogger logger, IUserRepository userRepositry, IErrorHandler errorHandler)
+ {
+ _redis = redis;
+ _client = client;
+ _logger = logger;
+ _userRepository = userRepositry;
+ _errorHandler = errorHandler;
+ }
+
+ [Command("youtubekey", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Admin)]
+ [Summary("Set the youtube api key")]
+ public async Task SetYoutubeKey([Summary("API Key")] string key)
+ {
+ _redis.StringSet("youtubeKey", key);
+ await ReplyAsync("Apikey has been set");
+ }
+
+ [Command("game", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Admin)]
+ [Summary("Set the game that the bot is playing")]
+ public async Task SetGame([Remainder] [Summary("Game")] string key)
+ {
+ _redis.StringSet("Game", key);
+ await _client.SetGameAsync(key);
+ _logger.Information(LogSource.Geekbot, $"Changed game to {key}");
+ await ReplyAsync($"Now Playing {key}");
+ }
+
+ [Command("popuserrepo", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Admin)]
+ [Summary("Populate user cache")]
+ public async Task PopUserRepoCommand()
+ {
+ var success = 0;
+ var failed = 0;
+ try
+ {
+ _logger.Warning(LogSource.UserRepository, "Populating User Repositry");
+ await ReplyAsync("Starting Population of User Repository");
+ foreach (var guild in _client.Guilds)
+ {
+ _logger.Information(LogSource.UserRepository, $"Populating users from {guild.Name}");
+ foreach (var user in guild.Users)
+ {
+ var succeded = await _userRepository.Update(user);
+ var inc = succeded ? success++ : failed++;
+ }
+ }
+
+ _logger.Warning(LogSource.UserRepository, "Finished Updating User Repositry");
+ await ReplyAsync(
+ $"Successfully Populated User Repository with {success} Users in {_client.Guilds.Count} Guilds (Failed: {failed})");
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context,
+ "Couldn't complete User Repository, see console for more info");
+ }
+ }
+
+ [Command("error", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Admin)]
+ [Summary("Throw an error un purpose")]
+ public void PurposefulError()
+ {
+ var e = new Exception("Error Generated by !owner error");
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Admin/Role.cs b/Geekbot.net/Commands/Admin/Role.cs
new file mode 100644
index 0000000..24d19dd
--- /dev/null
+++ b/Geekbot.net/Commands/Admin/Role.cs
@@ -0,0 +1,193 @@
+using System;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Discord;
+using Discord.Commands;
+using Discord.Net;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+using Geekbot.net.Lib.ReactionListener;
+using StackExchange.Redis;
+
+namespace Geekbot.net.Commands.Admin
+{
+ [Group("role")]
+ public class Role : ModuleBase
+ {
+ private readonly IErrorHandler _errorHandler;
+ private readonly IDatabase _redis;
+ private readonly IReactionListener _reactionListener;
+
+ public Role(IErrorHandler errorHandler, IDatabase redis, IReactionListener reactionListener)
+ {
+ _errorHandler = errorHandler;
+ _redis = redis;
+ _reactionListener = reactionListener;
+ }
+
+ [Command(RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Helpers)]
+ [Summary("Get a list of all available roles.")]
+ public async Task GetAllRoles()
+ {
+ try
+ {
+ var roles = _redis.HashGetAll($"{Context.Guild.Id}:RoleWhitelist");
+ if (roles.Length == 0)
+ {
+ await ReplyAsync("There are no roles configured for this server");
+ return;
+ }
+
+ var sb = new StringBuilder();
+ sb.AppendLine($"**Self Service Roles on {Context.Guild.Name}**");
+ sb.AppendLine("To get a role, use `!role name`");
+ foreach (var role in roles) sb.AppendLine($"- {role.Name}");
+ await ReplyAsync(sb.ToString());
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+
+ [Command(RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Helpers)]
+ [Summary("Get a role by mentioning it.")]
+ public async Task GiveRole([Summary("roleNickname")] string roleNameRaw)
+ {
+ try
+ {
+ var roleName = roleNameRaw.ToLower();
+ if (_redis.HashExists($"{Context.Guild.Id}:RoleWhitelist", roleName))
+ {
+ var guildUser = (IGuildUser) Context.User;
+ var roleId = ulong.Parse(_redis.HashGet($"{Context.Guild.Id}:RoleWhitelist", roleName));
+ var role = Context.Guild.Roles.First(r => r.Id == roleId);
+ if (role == null)
+ {
+ await ReplyAsync("That role doesn't seem to exist");
+ return;
+ }
+
+ if (guildUser.RoleIds.Contains(roleId))
+ {
+ await guildUser.RemoveRoleAsync(role);
+ await ReplyAsync($"Removed you from {role.Name}");
+ return;
+ }
+
+ await guildUser.AddRoleAsync(role);
+ await ReplyAsync($"Added you to {role.Name}");
+ return;
+ }
+
+ await ReplyAsync("That role doesn't seem to exist");
+ }
+ catch (HttpException e)
+ {
+ _errorHandler.HandleHttpException(e, Context);
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+
+ [RequireUserPermission(GuildPermission.ManageRoles)]
+ [Command("add", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Admin)]
+ [Summary("Add a role to the whitelist.")]
+ public async Task AddRole([Summary("@role")] IRole role, [Summary("alias")] string roleName)
+ {
+ try
+ {
+ if (role.IsManaged)
+ {
+ await ReplyAsync("You can't add a role that is managed by discord");
+ return;
+ }
+
+ if (role.Permissions.ManageRoles
+ || role.Permissions.Administrator
+ || role.Permissions.ManageGuild
+ || role.Permissions.BanMembers
+ || role.Permissions.KickMembers)
+ {
+ await ReplyAsync(
+ "Woah, i don't think you want to add that role to self service as it contains some dangerous permissions");
+ return;
+ }
+
+ _redis.HashSet($"{Context.Guild.Id}:RoleWhitelist",
+ new[] {new HashEntry(roleName.ToLower(), role.Id.ToString())});
+ await ReplyAsync($"Added {role.Name} to the whitelist");
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+
+ [RequireUserPermission(GuildPermission.ManageRoles)]
+ [Command("remove", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Admin)]
+ [Summary("Remove a role from the whitelist.")]
+ public async Task RemoveRole([Summary("roleNickname")] string roleName)
+ {
+ try
+ {
+
+ var success = _redis.HashDelete($"{Context.Guild.Id}:RoleWhitelist", roleName.ToLower());
+ if (success)
+ {
+ await ReplyAsync($"Removed {roleName} from the whitelist");
+ return;
+ }
+
+ await ReplyAsync("There is not whitelisted role with that name...");
+
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+
+ [RequireUserPermission(GuildPermission.ManageRoles)]
+ [Remarks(CommandCategories.Admin)]
+ [Summary("Give a role by clicking on an emoji")]
+ [Command("listen", RunMode = RunMode.Async)]
+ public async Task AddListener([Summary("messageID")] string messageId, [Summary("Emoji")] string emoji, [Summary("@role")] IRole role)
+ {
+ try
+ {
+ var message = (IUserMessage) await Context.Channel.GetMessageAsync(ulong.Parse(messageId));
+ IEmote emote;
+ if (!emoji.StartsWith('<'))
+ {
+ var emo = new Emoji(emoji);
+ emote = emo;
+ }
+ else
+ {
+ emote = Emote.Parse(emoji);
+ }
+ await message.AddReactionAsync(emote);
+ await _reactionListener.AddRoleToListener(messageId, emote, role);
+ await Context.Message.DeleteAsync();
+ }
+ catch (HttpException e)
+ {
+ await Context.Channel.SendMessageAsync("Custom emojis from other servers are not supported");
+ Console.WriteLine(e);
+ }
+ catch (Exception e)
+ {
+ await Context.Channel.SendMessageAsync("Something went wrong... please try again on a new message");
+ Console.WriteLine(e);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Admin/Say.cs b/Geekbot.net/Commands/Admin/Say.cs
new file mode 100644
index 0000000..97c7a0f
--- /dev/null
+++ b/Geekbot.net/Commands/Admin/Say.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Threading.Tasks;
+using Discord;
+using Discord.Commands;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+
+namespace Geekbot.net.Commands.Admin
+{
+ public class Say : ModuleBase
+ {
+ private readonly IErrorHandler _errorHandler;
+
+ public Say(IErrorHandler errorHandler)
+ {
+ _errorHandler = errorHandler;
+ }
+
+ [RequireUserPermission(GuildPermission.Administrator)]
+ [Command("say", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Admin)]
+ [Summary("Say Something.")]
+ public async Task Echo([Remainder] [Summary("What?")] string echo)
+ {
+ try
+ {
+ await Context.Message.DeleteAsync();
+ await ReplyAsync(echo);
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Audio/Voice.cs b/Geekbot.net/Commands/Audio/Voice.cs
new file mode 100644
index 0000000..410d8e2
--- /dev/null
+++ b/Geekbot.net/Commands/Audio/Voice.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Threading.Tasks;
+using Discord;
+using Discord.Commands;
+using Geekbot.net.Lib.Audio;
+using Geekbot.net.Lib.ErrorHandling;
+
+namespace Geekbot.net.Commands.Audio
+{
+ public class Voice : ModuleBase
+ {
+ private readonly IAudioUtils _audioUtils;
+ private readonly IErrorHandler _errorHandler;
+
+ public Voice(IErrorHandler errorHandler, IAudioUtils audioUtils)
+ {
+ _errorHandler = errorHandler;
+ _audioUtils = audioUtils;
+ }
+
+// [Command("join")]
+ public async Task JoinChannel()
+ {
+ try
+ {
+ // Get the audio channel
+ var channel = (Context.User as IGuildUser)?.VoiceChannel;
+ if (channel == null)
+ {
+ await Context.Channel.SendMessageAsync("You must be in a voice channel.");
+ return;
+ }
+
+ var audioClient = await channel.ConnectAsync();
+ _audioUtils.StoreAudioClient(Context.Guild.Id, audioClient);
+ await ReplyAsync($"Connected to {channel.Name}");
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+
+// [Command("disconnect")]
+ public async Task DisconnectChannel()
+ {
+ try
+ {
+ var audioClient = _audioUtils.GetAudioClient(Context.Guild.Id);
+ if (audioClient == null)
+ {
+ await Context.Channel.SendMessageAsync("I'm not in a voice channel at the moment");
+ return;
+ }
+
+ await audioClient.StopAsync();
+ await ReplyAsync("Disconnected from channel!");
+ _audioUtils.Cleanup(Context.Guild.Id);
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ _audioUtils.Cleanup(Context.Guild.Id);
+ }
+ }
+
+// [Command("ytplay")]
+ public async Task ytplay(string url)
+ {
+ try
+ {
+ if (!url.Contains("youtube"))
+ {
+ await ReplyAsync("I can only play youtube videos");
+ return;
+ }
+ var audioClient = _audioUtils.GetAudioClient(Context.Guild.Id);
+ if (audioClient == null)
+ {
+ await ReplyAsync("I'm not in a voice channel at the moment");
+ return;
+ }
+
+ var message = await Context.Channel.SendMessageAsync("Just a second, i'm still a bit slow at this");
+ var ffmpeg = _audioUtils.CreateStreamFromYoutube(url, Context.Guild.Id);
+ var output = ffmpeg.StandardOutput.BaseStream;
+ await message.ModifyAsync(msg => msg.Content = "**Playing!** Please note that this feature is experimental");
+ var discord = audioClient.CreatePCMStream(Discord.Audio.AudioApplication.Mixed);
+ await output.CopyToAsync(discord);
+ await discord.FlushAsync();
+ _audioUtils.Cleanup(Context.Guild.Id);
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ _audioUtils.Cleanup(Context.Guild.Id);
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Games/BattleTag.cs b/Geekbot.net/Commands/Games/BattleTag.cs
new file mode 100644
index 0000000..88cc0d8
--- /dev/null
+++ b/Geekbot.net/Commands/Games/BattleTag.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Threading.Tasks;
+using Discord.Commands;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+using Geekbot.net.Lib.UserRepository;
+
+namespace Geekbot.net.Commands.Games
+{
+ [Group("battletag")]
+ public class BattleTag : ModuleBase
+ {
+ private readonly IErrorHandler _errorHandler;
+ private readonly IUserRepository _userRepository;
+
+ public BattleTag(IErrorHandler errorHandler, IUserRepository userRepository)
+ {
+ _errorHandler = errorHandler;
+ _userRepository = userRepository;
+ }
+
+ [Command(RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Games)]
+ [Summary("Get your battletag")]
+ public async Task BattleTagCmd()
+ {
+ try
+ {
+ var tag = _userRepository.GetUserSetting(Context.User.Id, "BattleTag");
+ if (!string.IsNullOrEmpty(tag))
+ await ReplyAsync($"Your BattleTag is {tag}");
+ else
+ await ReplyAsync("You haven't set your BattleTag, set it with `!battletag user#1234`");
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+
+ [Command(RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Games)]
+ [Summary("Save your battletag")]
+ public async Task BattleTagCmd([Summary("Battletag")] string tag)
+ {
+ try
+ {
+ if (IsValidTag(tag))
+ {
+ _userRepository.SaveUserSetting(Context.User.Id, "BattleTag", tag);
+ await ReplyAsync("Saved!");
+ }
+ else
+ {
+ await ReplyAsync("That doesn't seem to be a valid battletag");
+ }
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+
+ public static bool IsValidTag(string tag)
+ {
+ var splited = tag.Split("#");
+ if (splited.Length != 2) return false;
+ if (!int.TryParse(splited[1], out var discriminator)) return false;
+ return splited[1].Length == 4 || splited[1].Length == 5;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Games/Overwatch.cs b/Geekbot.net/Commands/Games/Overwatch.cs
new file mode 100644
index 0000000..bda75b3
--- /dev/null
+++ b/Geekbot.net/Commands/Games/Overwatch.cs
@@ -0,0 +1,132 @@
+using System;
+using System.Threading.Tasks;
+using Discord;
+using Discord.Commands;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+using Geekbot.net.Lib.Extensions;
+using Geekbot.net.Lib.UserRepository;
+using OverwatchAPI;
+using OverwatchAPI.Config;
+
+namespace Geekbot.net.Commands.Games
+{
+ [Group("ow")]
+ public class Overwatch : ModuleBase
+ {
+ private readonly IErrorHandler _errorHandler;
+ private readonly IUserRepository _userRepository;
+
+ public Overwatch(IErrorHandler errorHandler, IUserRepository userRepository)
+ {
+ _errorHandler = errorHandler;
+ _userRepository = userRepository;
+ }
+
+ [Command("profile", RunMode = RunMode.Async)]
+ [Summary("Get someones overwatch profile. EU on PC only. Default battletag is your own (if set).")]
+ [Remarks(CommandCategories.Games)]
+ public async Task OwProfile()
+ {
+ try
+ {
+ var tag = _userRepository.GetUserSetting(Context.User.Id, "BattleTag");
+ if (string.IsNullOrEmpty(tag))
+ {
+ await ReplyAsync("You have no battle Tag saved, use `!battletag`");
+ return;
+ }
+
+ var profile = await CreateProfile(tag);
+ if (profile == null)
+ {
+ await ReplyAsync("That player doesn't seem to exist");
+ return;
+ }
+
+ await ReplyAsync("", false, profile.Build());
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+
+ [Command("profile", RunMode = RunMode.Async)]
+ [Summary("Get someones overwatch profile. EU on PC only. Default battletag is your own (if set).")]
+ [Remarks(CommandCategories.Games)]
+ public async Task OwProfile([Summary("BattleTag")] string tag)
+ {
+ try
+ {
+ if (!BattleTag.IsValidTag(tag))
+ {
+ await ReplyAsync("That doesn't seem to be a valid battletag...");
+ return;
+ }
+
+ var profile = await CreateProfile(tag);
+ if (profile == null)
+ {
+ await ReplyAsync("That player doesn't seem to exist");
+ return;
+ }
+
+ await ReplyAsync("", false, profile.Build());
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+
+ [Command("profile", RunMode = RunMode.Async)]
+ [Summary("Get someones overwatch profile. EU on PC only.")]
+ [Remarks(CommandCategories.Games)]
+ public async Task OwProfile([Summary("@someone")] IUser user)
+ {
+ try
+ {
+ var tag = _userRepository.GetUserSetting(user.Id, "BattleTag");
+ if (string.IsNullOrEmpty(tag))
+ {
+ await ReplyAsync("This user didn't set a battletag");
+ return;
+ }
+
+ var profile = await CreateProfile(tag);
+ if (profile == null)
+ {
+ await ReplyAsync("That player doesn't seem to exist");
+ return;
+ }
+
+ await ReplyAsync("", false, profile.Build());
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+
+ private async Task CreateProfile(string battletag)
+ {
+ var owConfig = new OverwatchConfig.Builder().WithPlatforms(Platform.Pc);
+ using (var owClient = new OverwatchClient(owConfig))
+ {
+ var player = await owClient.GetPlayerAsync(battletag);
+ if (player.Username == null) return null;
+ var eb = new EmbedBuilder();
+ eb.WithAuthor(new EmbedAuthorBuilder()
+ .WithIconUrl(player.ProfilePortraitUrl)
+ .WithName(player.Username));
+ eb.Url = player.ProfileUrl;
+ eb.AddInlineField("Level", player.PlayerLevel);
+ eb.AddInlineField("Current Rank",
+ player.CompetitiveRank > 0 ? player.CompetitiveRank.ToString() : "Unranked");
+
+ return eb;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Bot/Commands/Games/Pokedex.cs b/Geekbot.net/Commands/Games/Pokedex.cs
similarity index 84%
rename from src/Bot/Commands/Games/Pokedex.cs
rename to Geekbot.net/Commands/Games/Pokedex.cs
index 325ee8c..780c564 100644
--- a/src/Bot/Commands/Games/Pokedex.cs
+++ b/Geekbot.net/Commands/Games/Pokedex.cs
@@ -3,14 +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 Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+using Geekbot.net.Lib.Extensions;
using PokeAPI;
-namespace Geekbot.Bot.Commands.Games
+namespace Geekbot.net.Commands.Games
{
- public class Pokedex : TransactionModuleBase
+ public class Pokedex : ModuleBase
{
private readonly IErrorHandler _errorHandler;
@@ -20,8 +20,9 @@ namespace Geekbot.Bot.Commands.Games
}
[Command("pokedex", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Helpers)]
[Summary("A Pokedex Tool")]
- public async Task GetPokemon([Summary("pokemon-name")] string pokemonName)
+ public async Task GetPokemon([Summary("pokemonName")] string pokemonName)
{
try
{
@@ -42,7 +43,7 @@ namespace Geekbot.Bot.Commands.Games
}
catch (Exception e)
{
- await _errorHandler.HandleCommandException(e, Context);
+ _errorHandler.HandleCommandException(e, Context);
}
}
diff --git a/Geekbot.net/Commands/Games/Roll.cs b/Geekbot.net/Commands/Games/Roll.cs
new file mode 100644
index 0000000..e8a89c0
--- /dev/null
+++ b/Geekbot.net/Commands/Games/Roll.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Threading.Tasks;
+using Discord.Commands;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+using Geekbot.net.Lib.Localization;
+using StackExchange.Redis;
+
+namespace Geekbot.net.Commands.Games
+{
+ public class Roll : ModuleBase
+ {
+ private readonly IErrorHandler _errorHandler;
+ private readonly IDatabase _redis;
+ private readonly ITranslationHandler _translation;
+
+ public Roll(IDatabase redis, IErrorHandler errorHandler, ITranslationHandler translation)
+ {
+ _redis = redis;
+ _translation = translation;
+ _errorHandler = errorHandler;
+ }
+
+ [Command("roll", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Fun)]
+ [Summary("Guess which number the bot will roll (1-100")]
+ public async Task RollCommand([Remainder] [Summary("guess")] string stuff = "noGuess")
+ {
+ try
+ {
+ var number = new Random().Next(1, 100);
+ var guess = 1000;
+ int.TryParse(stuff, out guess);
+ var transDict = _translation.GetDict(Context);
+ if (guess <= 100 && guess > 0)
+ {
+ var prevRoll = _redis.HashGet($"{Context.Guild.Id}:RollsPrevious2", Context.Message.Author.Id).ToString()?.Split('|');
+ if (prevRoll?.Length == 2)
+ {
+ if (prevRoll[0] == guess.ToString() && DateTime.Parse(prevRoll[1]) > DateTime.Now.AddDays(-1))
+ {
+ await ReplyAsync(string.Format(transDict["NoPrevGuess"], Context.Message.Author.Mention));
+ return;
+ }
+ }
+
+ _redis.HashSet($"{Context.Guild.Id}:RollsPrevious2",
+ new[] {new HashEntry(Context.Message.Author.Id, $"{guess}|{DateTime.Now}")});
+ await ReplyAsync(string.Format(transDict["Rolled"], Context.Message.Author.Mention, number, guess));
+ if (guess == number)
+ {
+ await ReplyAsync(string.Format(transDict["Gratz"], Context.Message.Author));
+ _redis.HashIncrement($"{Context.Guild.Id}:Rolls", Context.User.Id.ToString());
+ }
+ }
+ else
+ {
+ await ReplyAsync(string.Format(transDict["RolledNoGuess"], Context.Message.Author.Mention, number));
+ }
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Integrations/Google/Google.cs b/Geekbot.net/Commands/Integrations/Google/Google.cs
new file mode 100644
index 0000000..7bd3a56
--- /dev/null
+++ b/Geekbot.net/Commands/Integrations/Google/Google.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Linq;
+using System.Net;
+using System.Threading.Tasks;
+using Discord;
+using Discord.Commands;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+using Newtonsoft.Json;
+using StackExchange.Redis;
+
+namespace Geekbot.net.Commands.Integrations.Google
+{
+ public class Google : ModuleBase
+ {
+ private readonly IErrorHandler _errorHandler;
+ private readonly IDatabase _redis;
+
+ public Google(IErrorHandler errorHandler, IDatabase redis)
+ {
+ _errorHandler = errorHandler;
+ _redis = redis;
+ }
+
+ [Command("google", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Helpers)]
+ [Summary("Google Something.")]
+ public async Task AskGoogle([Remainder, Summary("SearchText")] string searchText)
+ {
+ try
+ {
+ using (var client = new WebClient())
+ {
+ var apiKey = _redis.StringGet("googleGraphKey");
+ if (!apiKey.HasValue)
+ {
+ await ReplyAsync("No Google API key has been set, please contact my owner");
+ return;
+ }
+
+ var url = new Uri($"https://kgsearch.googleapis.com/v1/entities:search?languages=en&limit=1&query={searchText}&key={apiKey}");
+ var responseString = client.DownloadString(url);
+ var response = JsonConvert.DeserializeObject(responseString);
+
+ if (!response.ItemListElement.Any())
+ {
+ await ReplyAsync("No results were found...");
+ return;
+ }
+
+ var data = response.ItemListElement.First().Result;
+ var eb = new EmbedBuilder();
+ eb.Title = data.Name;
+ if(!string.IsNullOrEmpty(data.Description)) eb.WithDescription(data.Description);
+ if(!string.IsNullOrEmpty(data.DetailedDtoDescription?.Url)) eb.WithUrl(data.DetailedDtoDescription.Url);
+ if(!string.IsNullOrEmpty(data.DetailedDtoDescription?.ArticleBody)) eb.AddField("Details", data.DetailedDtoDescription.ArticleBody);
+ if(!string.IsNullOrEmpty(data.Image?.ContentUrl)) eb.WithThumbnailUrl(data.Image.ContentUrl);
+
+ await ReplyAsync("", false, eb.Build());
+ }
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Integrations/Google/GoogleKgApiDetailedDto.cs b/Geekbot.net/Commands/Integrations/Google/GoogleKgApiDetailedDto.cs
new file mode 100644
index 0000000..031d1e7
--- /dev/null
+++ b/Geekbot.net/Commands/Integrations/Google/GoogleKgApiDetailedDto.cs
@@ -0,0 +1,9 @@
+namespace Geekbot.net.Commands.Integrations.Google
+{
+ public class GoogleKgApiDetailedDto
+ {
+ public string ArticleBody { get; set; }
+ public string Url { get; set; }
+ public string License { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Integrations/Google/GoogleKgApiElementDto.cs b/Geekbot.net/Commands/Integrations/Google/GoogleKgApiElementDto.cs
new file mode 100644
index 0000000..a48b184
--- /dev/null
+++ b/Geekbot.net/Commands/Integrations/Google/GoogleKgApiElementDto.cs
@@ -0,0 +1,8 @@
+namespace Geekbot.net.Commands.Integrations.Google
+{
+ public class GoogleKgApiElementDto
+ {
+ public GoogleKgApiResultDto Result { get; set; }
+ public double ResultScore { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Integrations/Google/GoogleKgApiImageDto.cs b/Geekbot.net/Commands/Integrations/Google/GoogleKgApiImageDto.cs
new file mode 100644
index 0000000..fe7cdaa
--- /dev/null
+++ b/Geekbot.net/Commands/Integrations/Google/GoogleKgApiImageDto.cs
@@ -0,0 +1,8 @@
+namespace Geekbot.net.Commands.Integrations.Google
+{
+ public class GoogleKgApiImageDto
+ {
+ public string ContentUrl { get; set; }
+ public string Url { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Integrations/Google/GoogleKgApiResponseDto.cs b/Geekbot.net/Commands/Integrations/Google/GoogleKgApiResponseDto.cs
new file mode 100644
index 0000000..af337db
--- /dev/null
+++ b/Geekbot.net/Commands/Integrations/Google/GoogleKgApiResponseDto.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace Geekbot.net.Commands.Integrations.Google
+{
+ public class GoogleKgApiResponseDto
+ {
+ public List ItemListElement { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Integrations/Google/GoogleKgApiResultDto.cs b/Geekbot.net/Commands/Integrations/Google/GoogleKgApiResultDto.cs
new file mode 100644
index 0000000..465f1d7
--- /dev/null
+++ b/Geekbot.net/Commands/Integrations/Google/GoogleKgApiResultDto.cs
@@ -0,0 +1,10 @@
+namespace Geekbot.net.Commands.Integrations.Google
+{
+ public class GoogleKgApiResultDto
+ {
+ public string Name { get; set; }
+ public string Description { get; set; }
+ public GoogleKgApiImageDto Image { get; set; }
+ public GoogleKgApiDetailedDto DetailedDtoDescription { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Integrations/MagicTheGathering.cs b/Geekbot.net/Commands/Integrations/MagicTheGathering.cs
new file mode 100644
index 0000000..8ecefd2
--- /dev/null
+++ b/Geekbot.net/Commands/Integrations/MagicTheGathering.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Discord;
+using Discord.Commands;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.Converters;
+using Geekbot.net.Lib.ErrorHandling;
+using Geekbot.net.Lib.Extensions;
+using MtgApiManager.Lib.Service;
+
+namespace Geekbot.net.Commands.Integrations
+{
+ public class Magicthegathering : ModuleBase
+ {
+ private readonly IErrorHandler _errorHandler;
+ private readonly IMtgManaConverter _manaConverter;
+
+ public Magicthegathering(IErrorHandler errorHandler, IMtgManaConverter manaConverter)
+ {
+ _errorHandler = errorHandler;
+ _manaConverter = manaConverter;
+ }
+
+ [Command("mtg", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Games)]
+ [Summary("Find a Magic The Gathering Card.")]
+ public async Task GetCard([Remainder] [Summary("name")] string cardName)
+ {
+ try
+ {
+ var service = new CardService();
+ var result = service.Where(x => x.Name, cardName);
+
+ var card = result.All().Value.FirstOrDefault();
+ if (card == null)
+ {
+ await ReplyAsync("I couldn't find that card...");
+ return;
+ }
+
+ var eb = new EmbedBuilder();
+ eb.Title = card.Name;
+ eb.Description = card.Type;
+
+ if (card.Colors != null) eb.WithColor(GetColor(card.Colors));
+
+ if (card.ImageUrl != null) eb.ImageUrl = card.ImageUrl.ToString();
+
+ if (!string.IsNullOrEmpty(card.Text)) eb.AddField("Text", _manaConverter.ConvertMana(card.Text));
+
+ if (!string.IsNullOrEmpty(card.Flavor)) eb.AddField("Flavor", card.Flavor);
+ if (!string.IsNullOrEmpty(card.SetName)) eb.AddInlineField("Set", card.SetName);
+ if (!string.IsNullOrEmpty(card.Power)) eb.AddInlineField("Power", card.Power);
+ if (!string.IsNullOrEmpty(card.Loyalty)) eb.AddInlineField("Loyality", card.Loyalty);
+ if (!string.IsNullOrEmpty(card.Toughness)) eb.AddInlineField("Thoughness", card.Toughness);
+
+ if (!string.IsNullOrEmpty(card.ManaCost)) eb.AddInlineField("Cost", _manaConverter.ConvertMana(card.ManaCost));
+ if (!string.IsNullOrEmpty(card.Rarity)) eb.AddInlineField("Rarity", card.Rarity);
+
+ if (card.Legalities != null)
+ eb.AddField("Legality", string.Join(", ", card.Legalities.Select(e => e.Format)));
+
+ await ReplyAsync("", false, eb.Build());
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+
+ private Color GetColor(IEnumerable colors)
+ {
+ var color = colors.FirstOrDefault();
+ switch (color)
+ {
+ case "Black":
+ return new Color(203, 194, 191);
+ case "White":
+ return new Color(255, 251, 213);
+ case "Blue":
+ return new Color(170, 224, 250);
+ case "Red":
+ return new Color(250, 170, 143);
+ case "Green":
+ return new Color(155, 211, 174);
+ default:
+ return new Color(204, 194, 212);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Integrations/Mal.cs b/Geekbot.net/Commands/Integrations/Mal.cs
new file mode 100644
index 0000000..eee5782
--- /dev/null
+++ b/Geekbot.net/Commands/Integrations/Mal.cs
@@ -0,0 +1,122 @@
+using System;
+using System.Threading.Tasks;
+using System.Web;
+using Discord;
+using Discord.Commands;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.Clients;
+using Geekbot.net.Lib.ErrorHandling;
+using Geekbot.net.Lib.Extensions;
+
+namespace Geekbot.net.Commands.Integrations
+{
+ public class Mal : ModuleBase
+ {
+ private readonly IErrorHandler _errorHandler;
+ private readonly IMalClient _malClient;
+
+ public Mal(IMalClient malClient, IErrorHandler errorHandler)
+ {
+ _malClient = malClient;
+ _errorHandler = errorHandler;
+ }
+
+ [Command("anime", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Helpers)]
+ [Summary("Show Info about an Anime.")]
+ public async Task SearchAnime([Remainder] [Summary("AnimeName")] string animeName)
+ {
+ try
+ {
+ if (_malClient.IsLoggedIn())
+ {
+ var anime = await _malClient.GetAnime(animeName);
+ if (anime != null)
+ {
+ var eb = new EmbedBuilder();
+
+ var description = HttpUtility.HtmlDecode(anime.Synopsis)
+ .Replace("
", "")
+ .Replace("[i]", "*")
+ .Replace("[/i]", "*");
+
+ eb.Title = anime.Title;
+ eb.Description = description;
+ eb.ImageUrl = anime.Image;
+ eb.AddInlineField("Premiered", $"{anime.StartDate}");
+ eb.AddInlineField("Ended", anime.EndDate == "0000-00-00" ? "???" : anime.EndDate);
+ eb.AddInlineField("Status", anime.Status);
+ eb.AddInlineField("Episodes", anime.Episodes);
+ eb.AddInlineField("MAL Score", anime.Score);
+ eb.AddInlineField("Type", anime.Type);
+ eb.AddField("MAL Link", $"https://myanimelist.net/anime/{anime.Id}");
+
+ await ReplyAsync("", false, eb.Build());
+ }
+ else
+ {
+ await ReplyAsync("No anime found with that name...");
+ }
+ }
+ else
+ {
+ await ReplyAsync(
+ "Unfortunally i'm not connected to MyAnimeList.net, please tell my senpai to connect me");
+ }
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+
+ [Command("manga", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Helpers)]
+ [Summary("Show Info about a Manga.")]
+ public async Task SearchManga([Remainder] [Summary("MangaName")] string mangaName)
+ {
+ try
+ {
+ if (_malClient.IsLoggedIn())
+ {
+ var manga = await _malClient.GetManga(mangaName);
+ if (manga != null)
+ {
+ var eb = new EmbedBuilder();
+
+ var description = HttpUtility.HtmlDecode(manga.Synopsis)
+ .Replace("
", "")
+ .Replace("[i]", "*")
+ .Replace("[/i]", "*");
+
+ eb.Title = manga.Title;
+ eb.Description = description;
+ eb.ImageUrl = manga.Image;
+ eb.AddInlineField("Premiered", $"{manga.StartDate}");
+ eb.AddInlineField("Ended", manga.EndDate == "0000-00-00" ? "???" : manga.EndDate);
+ eb.AddInlineField("Status", manga.Status);
+ eb.AddInlineField("Volumes", manga.Volumes);
+ eb.AddInlineField("Chapters", manga.Chapters);
+ eb.AddInlineField("MAL Score", manga.Score);
+ eb.AddField("MAL Link", $"https://myanimelist.net/manga/{manga.Id}");
+
+ await ReplyAsync("", false, eb.Build());
+ }
+ else
+ {
+ await ReplyAsync("No manga found with that name...");
+ }
+ }
+ else
+ {
+ await ReplyAsync(
+ "Unfortunally i'm not connected to MyAnimeList.net, please tell my senpai to connect me");
+ }
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Integrations/UbranDictionary/UrbanDictListItemDto.cs b/Geekbot.net/Commands/Integrations/UbranDictionary/UrbanDictListItemDto.cs
new file mode 100644
index 0000000..e98885b
--- /dev/null
+++ b/Geekbot.net/Commands/Integrations/UbranDictionary/UrbanDictListItemDto.cs
@@ -0,0 +1,12 @@
+namespace Geekbot.net.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/Geekbot.net/Commands/Integrations/UbranDictionary/UrbanDictResponseDto.cs b/Geekbot.net/Commands/Integrations/UbranDictionary/UrbanDictResponseDto.cs
new file mode 100644
index 0000000..2c3e014
--- /dev/null
+++ b/Geekbot.net/Commands/Integrations/UbranDictionary/UrbanDictResponseDto.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+
+namespace Geekbot.net.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/Geekbot.net/Commands/Integrations/UbranDictionary/UrbanDictionary.cs b/Geekbot.net/Commands/Integrations/UbranDictionary/UrbanDictionary.cs
new file mode 100644
index 0000000..9d99885
--- /dev/null
+++ b/Geekbot.net/Commands/Integrations/UbranDictionary/UrbanDictionary.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Linq;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Discord;
+using Discord.Commands;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+using Geekbot.net.Lib.Extensions;
+using Newtonsoft.Json;
+
+namespace Geekbot.net.Commands.Integrations.UbranDictionary
+{
+ public class UrbanDictionary : ModuleBase
+ {
+ private readonly IErrorHandler _errorHandler;
+
+ public UrbanDictionary(IErrorHandler errorHandler)
+ {
+ _errorHandler = errorHandler;
+ }
+
+ [Command("urban", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Helpers)]
+ [Summary("Lookup something on urban dictionary")]
+ public async Task UrbanDefine([Remainder] [Summary("word")] string word)
+ {
+ try
+ {
+ using (var client = new HttpClient())
+ {
+ client.BaseAddress = new Uri("https://api.urbandictionary.com");
+ var response = await client.GetAsync($"/v0/define?term={word}");
+ response.EnsureSuccessStatusCode();
+
+ var stringResponse = await response.Content.ReadAsStringAsync();
+ var definitions = JsonConvert.DeserializeObject(stringResponse);
+ 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));
+ if (!string.IsNullOrEmpty(definition.Definition)) eb.Description = definition.Definition;
+ if (!string.IsNullOrEmpty(definition.Example)) eb.AddField("Example", definition.Example ?? "(no example given...)");
+ 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)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Bot/Commands/Integrations/Wikipedia.cs b/Geekbot.net/Commands/Integrations/Wikipedia.cs
similarity index 82%
rename from src/Bot/Commands/Integrations/Wikipedia.cs
rename to Geekbot.net/Commands/Integrations/Wikipedia.cs
index 82f42a0..68589f6 100644
--- a/src/Bot/Commands/Integrations/Wikipedia.cs
+++ b/Geekbot.net/Commands/Integrations/Wikipedia.cs
@@ -5,36 +5,36 @@ 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;
-using Geekbot.Core.WikipediaClient;
-using Geekbot.Core.WikipediaClient.Page;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
using HtmlAgilityPack;
+using StackExchange.Redis;
+using WikipediaApi;
+using WikipediaApi.Page;
-namespace Geekbot.Bot.Commands.Integrations
+namespace Geekbot.net.Commands.Integrations
{
- public class Wikipedia : TransactionModuleBase
+ public class Wikipedia : ModuleBase
{
private readonly IErrorHandler _errorHandler;
private readonly IWikipediaClient _wikipediaClient;
- private readonly DatabaseContext _database;
+ private readonly IDatabase _redis;
- public Wikipedia(IErrorHandler errorHandler, IWikipediaClient wikipediaClient, DatabaseContext database)
+ public Wikipedia(IErrorHandler errorHandler, IWikipediaClient wikipediaClient, IDatabase redis)
{
_errorHandler = errorHandler;
_wikipediaClient = wikipediaClient;
- _database = database;
+ _redis = redis;
}
[Command("wiki", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Helpers)]
[Summary("Get an article from wikipedia.")]
- public async Task GetPreview([Remainder] [Summary("article")] string articleName)
+ public async Task GetPreview([Remainder] [Summary("Article")] string articleName)
{
try
{
- var wikiLang = _database.GuildSettings.FirstOrDefault(g => g.GuildId.Equals(Context.Guild.Id.AsLong()))?.WikiLang;
+ var wikiLang = _redis.HashGet($"{Context.Guild.Id}:Settings", "WikiLang").ToString();
if (string.IsNullOrEmpty(wikiLang))
{
wikiLang = "en";
@@ -88,7 +88,7 @@ namespace Geekbot.Bot.Commands.Integrations
}
catch (Exception e)
{
- await _errorHandler.HandleCommandException(e, Context);
+ _errorHandler.HandleCommandException(e, Context);
}
}
diff --git a/Geekbot.net/Commands/Integrations/Youtube.cs b/Geekbot.net/Commands/Integrations/Youtube.cs
new file mode 100644
index 0000000..f534bcd
--- /dev/null
+++ b/Geekbot.net/Commands/Integrations/Youtube.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Threading.Tasks;
+using Discord.Commands;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+using Google.Apis.Services;
+using Google.Apis.YouTube.v3;
+using StackExchange.Redis;
+
+namespace Geekbot.net.Commands.Integrations
+{
+ public class Youtube : ModuleBase
+ {
+ private readonly IErrorHandler _errorHandler;
+ private readonly IDatabase _redis;
+
+ public Youtube(IDatabase redis, IErrorHandler errorHandler)
+ {
+ _redis = redis;
+ _errorHandler = errorHandler;
+ }
+
+ [Command("yt", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Helpers)]
+ [Summary("Search for something on youtube.")]
+ public async Task Yt([Remainder] [Summary("Title")] string searchQuery)
+ {
+ var key = _redis.StringGet("youtubeKey");
+ if (key.IsNullOrEmpty)
+ {
+ await ReplyAsync("No youtube key set, please tell my senpai to set one");
+ return;
+ }
+
+ try
+ {
+ var youtubeService = new YouTubeService(new BaseClientService.Initializer
+ {
+ ApiKey = key.ToString(),
+ 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)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Randomness/Cat/Cat.cs b/Geekbot.net/Commands/Randomness/Cat/Cat.cs
new file mode 100644
index 0000000..88a9ce5
--- /dev/null
+++ b/Geekbot.net/Commands/Randomness/Cat/Cat.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Discord;
+using Discord.Commands;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+using Newtonsoft.Json;
+
+namespace Geekbot.net.Commands.Randomness.Cat
+{
+ public class Cat : ModuleBase
+ {
+ private readonly IErrorHandler _errorHandler;
+
+ public Cat(IErrorHandler errorHandler)
+ {
+ _errorHandler = errorHandler;
+ }
+
+ [Command("cat", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Randomness)]
+ [Summary("Return a random image of a cat.")]
+ public async Task Say()
+ {
+ try
+ {
+ using (var client = new HttpClient())
+ {
+ try
+ {
+ client.BaseAddress = new Uri("https://aws.random.cat");
+ var response = await client.GetAsync("/meow");
+ response.EnsureSuccessStatusCode();
+
+ var stringResponse = await response.Content.ReadAsStringAsync();
+ var catFile = JsonConvert.DeserializeObject(stringResponse);
+ var eb = new EmbedBuilder();
+ eb.ImageUrl = catFile.File;
+ await ReplyAsync("", false, eb.Build());
+ }
+ catch
+ {
+ await ReplyAsync("Seems like the dog cought the cat (error occured)");
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Randomness/Cat/CatResponseDto.cs b/Geekbot.net/Commands/Randomness/Cat/CatResponseDto.cs
new file mode 100644
index 0000000..05ebf2b
--- /dev/null
+++ b/Geekbot.net/Commands/Randomness/Cat/CatResponseDto.cs
@@ -0,0 +1,7 @@
+namespace Geekbot.net.Commands.Randomness.Cat
+{
+ internal class CatResponseDto
+ {
+ public string File { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Randomness/CheckEm.cs b/Geekbot.net/Commands/Randomness/CheckEm.cs
new file mode 100644
index 0000000..fc962cc
--- /dev/null
+++ b/Geekbot.net/Commands/Randomness/CheckEm.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using Discord.Commands;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+using Geekbot.net.Lib.Media;
+
+namespace Geekbot.net.Commands.Randomness
+{
+ public class CheckEm : ModuleBase
+ {
+ private readonly IMediaProvider _checkEmImages;
+ private readonly IErrorHandler _errorHandler;
+
+ public CheckEm(IMediaProvider mediaProvider, IErrorHandler errorHandler)
+ {
+ _checkEmImages = mediaProvider;
+ _errorHandler = errorHandler;
+ }
+
+ [Command("checkem", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Randomness)]
+ [Summary("Check for dubs")]
+ public async Task MuhDubs()
+ {
+ try
+ {
+ var number = new Random().Next(10000000, 99999999);
+ var dubtriqua = "";
+
+ var ns = GetIntArray(number);
+ if (ns[7] == ns[6])
+ {
+ dubtriqua = "DUBS";
+ if (ns[6] == ns[5])
+ {
+ dubtriqua = "TRIPS";
+ if (ns[5] == ns[4])
+ dubtriqua = "QUADS";
+ }
+ }
+
+ var sb = new StringBuilder();
+ sb.AppendLine($"Check em {Context.User.Mention}");
+ sb.AppendLine($"**{number}**");
+ if (!string.IsNullOrEmpty(dubtriqua))
+ sb.AppendLine($":tada: {dubtriqua} :tada:");
+ sb.AppendLine(_checkEmImages.GetCheckem());
+
+ await ReplyAsync(sb.ToString());
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+
+ private int[] GetIntArray(int num)
+ {
+ var listOfInts = new List();
+ while (num > 0)
+ {
+ listOfInts.Add(num % 10);
+ num = num / 10;
+ }
+
+ listOfInts.Reverse();
+ return listOfInts.ToArray();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Randomness/Chuck/ChuckNorrisJokeResponseDto.cs b/Geekbot.net/Commands/Randomness/Chuck/ChuckNorrisJokeResponseDto.cs
new file mode 100644
index 0000000..8d513b8
--- /dev/null
+++ b/Geekbot.net/Commands/Randomness/Chuck/ChuckNorrisJokeResponseDto.cs
@@ -0,0 +1,7 @@
+namespace Geekbot.net.Commands.Randomness.Chuck
+{
+ internal class ChuckNorrisJokeResponseDto
+ {
+ public string Value { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Randomness/Chuck/ChuckNorrisJokes.cs b/Geekbot.net/Commands/Randomness/Chuck/ChuckNorrisJokes.cs
new file mode 100644
index 0000000..6ff2255
--- /dev/null
+++ b/Geekbot.net/Commands/Randomness/Chuck/ChuckNorrisJokes.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Threading.Tasks;
+using Discord.Commands;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+using Newtonsoft.Json;
+
+namespace Geekbot.net.Commands.Randomness.Chuck
+{
+ public class ChuckNorrisJokes : ModuleBase
+ {
+ private readonly IErrorHandler _errorHandler;
+
+ public ChuckNorrisJokes(IErrorHandler errorHandler)
+ {
+ _errorHandler = errorHandler;
+ }
+
+ [Command("chuck", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Randomness)]
+ [Summary("A random chuck norris joke")]
+ public async Task Say()
+ {
+ try
+ {
+ using (var client = new HttpClient())
+ {
+ try
+ {
+ client.DefaultRequestHeaders.Accept.Clear();
+ client.DefaultRequestHeaders.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
+ var response = await client.GetAsync("https://api.chucknorris.io/jokes/random");
+ response.EnsureSuccessStatusCode();
+
+ var stringResponse = await response.Content.ReadAsStringAsync();
+ var data = JsonConvert.DeserializeObject(stringResponse);
+ await ReplyAsync(data.Value);
+ }
+ catch (HttpRequestException)
+ {
+ await ReplyAsync("Api down...");
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Randomness/Dad/DadJokeResponseDto.cs b/Geekbot.net/Commands/Randomness/Dad/DadJokeResponseDto.cs
new file mode 100644
index 0000000..262eee9
--- /dev/null
+++ b/Geekbot.net/Commands/Randomness/Dad/DadJokeResponseDto.cs
@@ -0,0 +1,7 @@
+namespace Geekbot.net.Commands.Randomness.Dad
+{
+ internal class DadJokeResponseDto
+ {
+ public string Joke { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Randomness/Dad/DadJokes.cs b/Geekbot.net/Commands/Randomness/Dad/DadJokes.cs
new file mode 100644
index 0000000..e35798c
--- /dev/null
+++ b/Geekbot.net/Commands/Randomness/Dad/DadJokes.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Threading.Tasks;
+using Discord.Commands;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+using Newtonsoft.Json;
+
+namespace Geekbot.net.Commands.Randomness.Dad
+{
+ public class DadJokes : ModuleBase
+ {
+ private readonly IErrorHandler _errorHandler;
+
+ public DadJokes(IErrorHandler errorHandler)
+ {
+ _errorHandler = errorHandler;
+ }
+
+ [Command("dad", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Randomness)]
+ [Summary("A random dad joke")]
+ public async Task Say()
+ {
+ try
+ {
+ using (var client = new HttpClient())
+ {
+ try
+ {
+ client.DefaultRequestHeaders.Accept.Clear();
+ client.DefaultRequestHeaders.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
+ var response = await client.GetAsync("https://icanhazdadjoke.com/");
+ response.EnsureSuccessStatusCode();
+
+ var stringResponse = await response.Content.ReadAsStringAsync();
+ var data = JsonConvert.DeserializeObject(stringResponse);
+ await ReplyAsync(data.Joke);
+ }
+ catch (HttpRequestException)
+ {
+ await ReplyAsync("Api down...");
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Randomness/Dog/Dog.cs b/Geekbot.net/Commands/Randomness/Dog/Dog.cs
new file mode 100644
index 0000000..08c1a9b
--- /dev/null
+++ b/Geekbot.net/Commands/Randomness/Dog/Dog.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Discord;
+using Discord.Commands;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+using Newtonsoft.Json;
+
+namespace Geekbot.net.Commands.Randomness.Dog
+{
+ public class Dog : ModuleBase
+ {
+ private readonly IErrorHandler _errorHandler;
+
+ public Dog(IErrorHandler errorHandler)
+ {
+ _errorHandler = errorHandler;
+ }
+
+ [Command("dog", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Randomness)]
+ [Summary("Return a random image of a dog.")]
+ public async Task Say()
+ {
+ try
+ {
+ using (var client = new HttpClient())
+ {
+ try
+ {
+ client.BaseAddress = new Uri("http://random.dog");
+ var response = await client.GetAsync("/woof.json");
+ response.EnsureSuccessStatusCode();
+
+ var stringResponse = await response.Content.ReadAsStringAsync();
+ var dogFile = JsonConvert.DeserializeObject(stringResponse);
+ var eb = new EmbedBuilder();
+ eb.ImageUrl = dogFile.Url;
+ await ReplyAsync("", false, eb.Build());
+ }
+ catch (HttpRequestException e)
+ {
+ await ReplyAsync($"Seems like the dog got lost (error occured)\r\n{e.Message}");
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Randomness/Dog/DogResponseDto.cs b/Geekbot.net/Commands/Randomness/Dog/DogResponseDto.cs
new file mode 100644
index 0000000..1fc1a82
--- /dev/null
+++ b/Geekbot.net/Commands/Randomness/Dog/DogResponseDto.cs
@@ -0,0 +1,7 @@
+namespace Geekbot.net.Commands.Randomness.Dog
+{
+ internal class DogResponseDto
+ {
+ public string Url { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Randomness/EightBall.cs b/Geekbot.net/Commands/Randomness/EightBall.cs
new file mode 100644
index 0000000..79ba6d1
--- /dev/null
+++ b/Geekbot.net/Commands/Randomness/EightBall.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Discord.Commands;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+
+namespace Geekbot.net.Commands.Randomness
+{
+ public class EightBall : ModuleBase
+ {
+ private readonly IErrorHandler _errorHandler;
+
+ public EightBall(IErrorHandler errorHandler)
+ {
+ _errorHandler = errorHandler;
+ }
+
+ [Command("8ball", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Randomness)]
+ [Summary("Ask 8Ball a Question.")]
+ public async Task Ball([Remainder] [Summary("Question")] string echo)
+ {
+ try
+ {
+ var replies = new List
+ {
+ "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"
+ };
+
+ var answer = new Random().Next(replies.Count);
+ await ReplyAsync(replies[answer]);
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Bot/Commands/Randomness/Fortune.cs b/Geekbot.net/Commands/Randomness/Fortune.cs
similarity index 68%
rename from src/Bot/Commands/Randomness/Fortune.cs
rename to Geekbot.net/Commands/Randomness/Fortune.cs
index 1157603..abc9ce5 100644
--- a/src/Bot/Commands/Randomness/Fortune.cs
+++ b/Geekbot.net/Commands/Randomness/Fortune.cs
@@ -1,11 +1,11 @@
using System.Threading.Tasks;
using Discord.Commands;
-using Geekbot.Core;
-using Geekbot.Core.Media;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.Media;
-namespace Geekbot.Bot.Commands.Randomness
+namespace Geekbot.net.Commands.Randomness
{
- public class Fortune : TransactionModuleBase
+ public class Fortune : ModuleBase
{
private readonly IFortunesProvider _fortunes;
@@ -15,6 +15,7 @@ namespace Geekbot.Bot.Commands.Randomness
}
[Command("fortune", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Randomness)]
[Summary("Get a random fortune")]
public async Task GetAFortune()
{
diff --git a/Geekbot.net/Commands/Randomness/Gdq.cs b/Geekbot.net/Commands/Randomness/Gdq.cs
new file mode 100644
index 0000000..3e17451
--- /dev/null
+++ b/Geekbot.net/Commands/Randomness/Gdq.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Net;
+using System.Threading.Tasks;
+using Discord.Commands;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+
+namespace Geekbot.net.Commands.Randomness
+{
+ public class Gdq : ModuleBase
+ {
+ private readonly IErrorHandler _errorHandler;
+
+ public Gdq(IErrorHandler errorHandler)
+ {
+ _errorHandler = errorHandler;
+ }
+
+ [Command("gdq", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Games)]
+ [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)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Bot/Commands/Randomness/RandomAnimals.cs b/Geekbot.net/Commands/Randomness/RandomAnimals.cs
similarity index 52%
rename from src/Bot/Commands/Randomness/RandomAnimals.cs
rename to Geekbot.net/Commands/Randomness/RandomAnimals.cs
index 5493485..0e3b81d 100644
--- a/src/Bot/Commands/Randomness/RandomAnimals.cs
+++ b/Geekbot.net/Commands/Randomness/RandomAnimals.cs
@@ -1,12 +1,12 @@
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
-using Geekbot.Core;
-using Geekbot.Core.Media;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.Media;
-namespace Geekbot.Bot.Commands.Randomness
+namespace Geekbot.net.Commands.Randomness
{
- public class RandomAnimals : TransactionModuleBase
+ public class RandomAnimals : ModuleBase
{
private readonly IMediaProvider _mediaProvider;
@@ -16,64 +16,64 @@ namespace Geekbot.Bot.Commands.Randomness
}
[Command("panda", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Randomness)]
[Summary("Get a random panda image")]
public async Task Panda()
{
- await ReplyAsync("", false, Eb(_mediaProvider.GetMedia(MediaType.Panda)));
+ await ReplyAsync("", false, Eb(_mediaProvider.GetPanda()));
}
[Command("croissant", RunMode = RunMode.Async)]
[Alias("gipfeli")]
+ [Remarks(CommandCategories.Randomness)]
[Summary("Get a random croissant image")]
public async Task Croissant()
{
- await ReplyAsync("", false, Eb(_mediaProvider.GetMedia(MediaType.Croissant)));
+ await ReplyAsync("", false, Eb(_mediaProvider.GetCrossant()));
}
[Command("pumpkin", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Randomness)]
[Summary("Get a random pumpkin image")]
public async Task Pumpkin()
{
- await ReplyAsync("", false, Eb(_mediaProvider.GetMedia(MediaType.Pumpkin)));
+ await ReplyAsync("", false, Eb(_mediaProvider.GetPumpkin()));
}
[Command("squirrel", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Randomness)]
[Summary("Get a random squirrel image")]
public async Task Squirrel()
{
- await ReplyAsync("", false, Eb(_mediaProvider.GetMedia(MediaType.Squirrel)));
+ await ReplyAsync("", false, Eb(_mediaProvider.GetSquirrel()));
}
[Command("turtle", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Randomness)]
[Summary("Get a random turtle image")]
public async Task Turtle()
{
- await ReplyAsync("", false, Eb(_mediaProvider.GetMedia(MediaType.Turtle)));
+ await ReplyAsync("", false, Eb(_mediaProvider.GetTurtle()));
}
- [Command("penguin", RunMode = RunMode.Async)]
- [Alias("pengu")]
- [Summary("Get a random penguin image")]
- public async Task Penguin()
+ [Command("pinguin", RunMode = RunMode.Async)]
+ [Alias("pingu")]
+ [Remarks(CommandCategories.Randomness)]
+ [Summary("Get a random pinguin image")]
+ public async Task Pinguin()
{
- await ReplyAsync("", false, Eb(_mediaProvider.GetMedia(MediaType.Penguin)));
+ await ReplyAsync("", false, Eb(_mediaProvider.GetPinguin()));
}
[Command("fox", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Randomness)]
[Summary("Get a random fox image")]
public async Task Fox()
{
- await ReplyAsync("", false, Eb(_mediaProvider.GetMedia(MediaType.Fox)));
- }
-
- [Command("dab", RunMode = RunMode.Async)]
- [Summary("Get a random dab image")]
- public async Task Dab()
- {
- await ReplyAsync("", false, Eb(_mediaProvider.GetMedia(MediaType.Dab)));
+ await ReplyAsync("", false, Eb(_mediaProvider.GetFox()));
}
- private static Embed Eb(string image)
+ private Embed Eb(string image)
{
return new EmbedBuilder {ImageUrl = image}.Build();
}
diff --git a/Geekbot.net/Commands/Randomness/Ship.cs b/Geekbot.net/Commands/Randomness/Ship.cs
new file mode 100644
index 0000000..ffa18b4
--- /dev/null
+++ b/Geekbot.net/Commands/Randomness/Ship.cs
@@ -0,0 +1,91 @@
+using System;
+using System.Threading.Tasks;
+using Discord;
+using Discord.Commands;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+using StackExchange.Redis;
+
+namespace Geekbot.net.Commands.Randomness
+{
+ public class Ship : ModuleBase
+ {
+ private readonly IErrorHandler _errorHandler;
+ private readonly IDatabase _redis;
+
+ public Ship(IDatabase redis, IErrorHandler errorHandler)
+ {
+ _redis = redis;
+ _errorHandler = errorHandler;
+ }
+
+ [Command("Ship", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Fun)]
+ [Summary("Ask the Shipping meter")]
+ public async Task Command([Summary("@User1")] IUser user1, [Summary("@User2")] IUser user2)
+ {
+ try
+ {
+ var dbstring = "";
+ if (user1.Id > user2.Id)
+ dbstring = $"{user1.Id}-{user2.Id}";
+ else
+ dbstring = $"{user2.Id}-{user1.Id}";
+
+ var dbval = _redis.HashGet($"{Context.Guild.Id}:Ships", dbstring);
+ var shippingRate = 0;
+ if (dbval.IsNullOrEmpty)
+ {
+ shippingRate = new Random().Next(1, 100);
+ _redis.HashSet($"{Context.Guild.Id}:Ships", dbstring, shippingRate);
+ }
+ else
+ {
+ shippingRate = int.Parse(dbval.ToString());
+ }
+
+ var reply = ":heartpulse: **Matchmaking** :heartpulse:\r\n";
+ reply = reply + $":two_hearts: {user1.Mention} :heart: {user2.Mention} :two_hearts:\r\n";
+ reply = reply + $"0% [{BlockCounter(shippingRate)}] 100% - {DeterminateSuccess(shippingRate)}";
+ await ReplyAsync(reply);
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+
+ private string DeterminateSuccess(int rate)
+ {
+ if (rate < 20)
+ return "Not gonna happen";
+ if (rate >= 20 && rate < 40)
+ return "Not such a good idea";
+ if (rate >= 40 && rate < 60)
+ return "There might be a chance";
+ if (rate >= 60 && rate < 80)
+ return "Almost a match, but could work";
+ return rate >= 80 ? "It's a match" : "a";
+ }
+
+ private string BlockCounter(int rate)
+ {
+ var amount = Math.Floor(decimal.Floor(rate / 10));
+ Console.WriteLine(amount);
+ var blocks = "";
+ for (var i = 1; i <= 10; i++)
+ if (i <= amount)
+ {
+ blocks = blocks + ":white_medium_small_square:";
+ if (i == amount)
+ blocks = blocks + $" {rate}% ";
+ }
+ else
+ {
+ blocks = blocks + ":black_medium_small_square:";
+ }
+
+ return blocks;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Randomness/Slap.cs b/Geekbot.net/Commands/Randomness/Slap.cs
new file mode 100644
index 0000000..129e89d
--- /dev/null
+++ b/Geekbot.net/Commands/Randomness/Slap.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Discord;
+using Discord.Commands;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+using StackExchange.Redis;
+
+namespace Geekbot.net.Commands.Randomness
+{
+ public class Slap : ModuleBase
+ {
+ private readonly IErrorHandler _errorHandler;
+ private readonly IDatabase _redis;
+
+ public Slap(IErrorHandler errorHandler, IDatabase redis)
+ {
+ _errorHandler = errorHandler;
+ _redis = redis;
+ }
+
+ [Command("slap", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Fun)]
+ [Summary("slap someone")]
+ public async Task Slapper([Summary("@user")] IUser user)
+ {
+ try
+ {
+ if (user.Id == Context.User.Id)
+ {
+ await ReplyAsync("Why would you slap yourself?");
+ return;
+ }
+
+ var things = new List
+ {
+ "thing",
+ "rubber chicken",
+ "leek stick",
+ "large trout",
+ "flat hand",
+ "strip of bacon",
+ "feather",
+ "piece of pizza",
+ "moldy banana",
+ "sharp retort",
+ "printed version of wikipedia",
+ "panda paw",
+ "spiked sledgehammer",
+ "monstertruck",
+ "dirty toilet brush",
+ "sleeping seagull",
+ "sunflower",
+ "mousepad",
+ "lolipop",
+ "bottle of rum",
+ "cheese slice",
+ "critical 1",
+ "natural 20",
+ "mjölnir (aka mewmew)",
+ "kamehameha",
+ "copy of Twilight",
+ "med pack (get ready for the end boss)",
+ "derp",
+ "condom (used)",
+ "gremlin fed after midnight",
+ "wet baguette",
+ "exploding kitten",
+ "shiny piece of shit",
+ "mismatched pair of socks",
+ "horcrux",
+ "tuna",
+ "suggestion",
+ "teapot",
+ "candle",
+ "dictionary",
+ "powerless banhammer"
+ };
+
+ _redis.HashIncrement($"{Context.Guild.Id}:SlapsRecieved", user.Id.ToString());
+ _redis.HashIncrement($"{Context.Guild.Id}:SlapsGiven", Context.User.Id.ToString());
+
+ await ReplyAsync($"{Context.User.Username} slapped {user.Username} with a {things[new Random().Next(things.Count - 1)]}");
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Bot/Commands/User/GuildInfo.cs b/Geekbot.net/Commands/User/GuildInfo.cs
similarity index 59%
rename from src/Bot/Commands/User/GuildInfo.cs
rename to Geekbot.net/Commands/User/GuildInfo.cs
index c063d89..18618d7 100644
--- a/src/Bot/Commands/User/GuildInfo.cs
+++ b/Geekbot.net/Commands/User/GuildInfo.cs
@@ -3,31 +3,30 @@ using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
-using Geekbot.Bot.CommandPreconditions;
-using Geekbot.Core;
-using Geekbot.Core.Database;
-using Geekbot.Core.ErrorHandling;
-using Geekbot.Core.Extensions;
-using Geekbot.Core.Levels;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+using Geekbot.net.Lib.Extensions;
+using Geekbot.net.Lib.Levels;
+using StackExchange.Redis;
-namespace Geekbot.Bot.Commands.User
+namespace Geekbot.net.Commands.User
{
- public class GuildInfo : TransactionModuleBase
+ public class GuildInfo : ModuleBase
{
private readonly IErrorHandler _errorHandler;
- private readonly DatabaseContext _database;
private readonly ILevelCalc _levelCalc;
+ private readonly IDatabase _redis;
- public GuildInfo(DatabaseContext database, ILevelCalc levelCalc, IErrorHandler errorHandler)
+ public GuildInfo(IDatabase redis, ILevelCalc levelCalc, IErrorHandler errorHandler)
{
- _database = database;
+ _redis = redis;
_levelCalc = levelCalc;
_errorHandler = errorHandler;
}
[Command("serverstats", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Statistics)]
[Summary("Show some info about the bot.")]
- [DisableInDirectMessage]
public async Task GetInfo()
{
try
@@ -41,10 +40,8 @@ namespace Geekbot.Bot.Commands.User
var created = Context.Guild.CreatedAt;
var age = Math.Floor((DateTime.Now - created).TotalDays);
- var messages = _database.Messages
- .Where(e => e.GuildId == Context.Guild.Id.AsLong())
- .Sum(e => e.MessageCount);
- var level = _levelCalc.GetLevel(messages);
+ var messages = _redis.HashGet($"{Context.Guild.Id}:Messages", 0.ToString());
+ var level = _levelCalc.GetLevel((int) messages);
eb.AddField("Server Age", $"{created.Day}/{created.Month}/{created.Year} ({age} days)");
eb.AddInlineField("Level", level)
@@ -54,7 +51,7 @@ namespace Geekbot.Bot.Commands.User
}
catch (Exception e)
{
- await _errorHandler.HandleCommandException(e, Context);
+ _errorHandler.HandleCommandException(e, Context);
}
}
}
diff --git a/Geekbot.net/Commands/User/Karma.cs b/Geekbot.net/Commands/User/Karma.cs
new file mode 100644
index 0000000..6290dc4
--- /dev/null
+++ b/Geekbot.net/Commands/User/Karma.cs
@@ -0,0 +1,130 @@
+using System;
+using System.Threading.Tasks;
+using Discord;
+using Discord.Commands;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+using Geekbot.net.Lib.Extensions;
+using Geekbot.net.Lib.Localization;
+using StackExchange.Redis;
+
+namespace Geekbot.net.Commands.User
+{
+ public class Karma : ModuleBase
+ {
+ private readonly IErrorHandler _errorHandler;
+ private readonly IDatabase _redis;
+ private readonly ITranslationHandler _translation;
+
+ public Karma(IDatabase redis, IErrorHandler errorHandler, ITranslationHandler translation)
+ {
+ _redis = redis;
+ _errorHandler = errorHandler;
+ _translation = translation;
+ }
+
+ [Command("good", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Karma)]
+ [Summary("Increase Someones Karma")]
+ public async Task Good([Summary("@someone")] IUser user)
+ {
+ try
+ {
+ var transDict = _translation.GetDict(Context);
+ var lastKarmaFromRedis = _redis.HashGet($"{Context.Guild.Id}:KarmaTimeout", Context.User.Id.ToString());
+ var lastKarma = ConvertToDateTimeOffset(lastKarmaFromRedis.ToString());
+ if (user.Id == Context.User.Id)
+ {
+ await ReplyAsync(string.Format(transDict["CannotChangeOwn"], Context.User.Username));
+ }
+ else if (TimeoutFinished(lastKarma))
+ {
+ await ReplyAsync(string.Format(transDict["WaitUntill"], Context.User.Username,
+ GetTimeLeft(lastKarma)));
+ }
+ else
+ {
+ var newKarma = _redis.HashIncrement($"{Context.Guild.Id}:Karma", user.Id.ToString());
+ _redis.HashSet($"{Context.Guild.Id}:KarmaTimeout",
+ new[] {new HashEntry(Context.User.Id.ToString(), DateTimeOffset.Now.ToString("u"))});
+
+ var eb = new EmbedBuilder();
+ eb.WithAuthor(new EmbedAuthorBuilder()
+ .WithIconUrl(user.GetAvatarUrl())
+ .WithName(user.Username));
+
+ eb.WithColor(new Color(138, 219, 146));
+ eb.Title = transDict["Increased"];
+ eb.AddInlineField(transDict["By"], Context.User.Username);
+ eb.AddInlineField(transDict["Amount"], "+1");
+ eb.AddInlineField(transDict["Current"], newKarma);
+ await ReplyAsync("", false, eb.Build());
+ }
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+
+ [Command("bad", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Karma)]
+ [Summary("Decrease Someones Karma")]
+ public async Task Bad([Summary("@someone")] IUser user)
+ {
+ try
+ {
+ var transDict = _translation.GetDict(Context);
+ var lastKarmaFromRedis = _redis.HashGet($"{Context.Guild.Id}:KarmaTimeout", Context.User.Id.ToString());
+ var lastKarma = ConvertToDateTimeOffset(lastKarmaFromRedis.ToString());
+ if (user.Id == Context.User.Id)
+ {
+ await ReplyAsync(string.Format(transDict["CannotChangeOwn"], Context.User.Username));
+ }
+ else if (TimeoutFinished(lastKarma))
+ {
+ await ReplyAsync(string.Format(transDict["WaitUntill"], Context.User.Username,
+ GetTimeLeft(lastKarma)));
+ }
+ else
+ {
+ var newKarma = _redis.HashDecrement($"{Context.Guild.Id}:Karma", user.Id.ToString());
+ _redis.HashSet($"{Context.Guild.Id}:KarmaTimeout",
+ new[] {new HashEntry(Context.User.Id.ToString(), DateTimeOffset.Now.ToString())});
+
+ var eb = new EmbedBuilder();
+ eb.WithAuthor(new EmbedAuthorBuilder()
+ .WithIconUrl(user.GetAvatarUrl())
+ .WithName(user.Username));
+
+ eb.WithColor(new Color(138, 219, 146));
+ eb.Title = transDict["Decreased"];
+ eb.AddInlineField(transDict["By"], Context.User.Username);
+ eb.AddInlineField(transDict["Amount"], "-1");
+ eb.AddInlineField(transDict["Current"], newKarma);
+ await ReplyAsync("", false, eb.Build());
+ }
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+
+ private DateTimeOffset ConvertToDateTimeOffset(string dateTimeOffsetString)
+ {
+ return string.IsNullOrEmpty(dateTimeOffsetString) ? DateTimeOffset.Now.Subtract(new TimeSpan(7, 18, 0, 0)) : DateTimeOffset.Parse(dateTimeOffsetString);
+ }
+
+ private bool TimeoutFinished(DateTimeOffset lastKarma)
+ {
+ return lastKarma.AddMinutes(3) > DateTimeOffset.Now;
+ }
+
+ private string GetTimeLeft(DateTimeOffset lastKarma)
+ {
+ var dt = lastKarma.AddMinutes(3).Subtract(DateTimeOffset.Now);
+ return $"{dt.Minutes} Minutes and {dt.Seconds} Seconds";
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/User/Ranking/Rank.cs b/Geekbot.net/Commands/User/Ranking/Rank.cs
new file mode 100644
index 0000000..08d4d3b
--- /dev/null
+++ b/Geekbot.net/Commands/User/Ranking/Rank.cs
@@ -0,0 +1,149 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Discord.Commands;
+using Discord.WebSocket;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.Converters;
+using Geekbot.net.Lib.ErrorHandling;
+using Geekbot.net.Lib.Logger;
+using Geekbot.net.Lib.UserRepository;
+using StackExchange.Redis;
+
+namespace Geekbot.net.Commands.User.Ranking
+{
+ public class Rank : ModuleBase
+ {
+ private readonly IEmojiConverter _emojiConverter;
+ private readonly IErrorHandler _errorHandler;
+ private readonly IGeekbotLogger _logger;
+ private readonly IDatabase _redis;
+ private readonly IUserRepository _userRepository;
+ private readonly DiscordSocketClient _client;
+
+ public Rank(IDatabase redis, IErrorHandler errorHandler, IGeekbotLogger logger, IUserRepository userRepository,
+ IEmojiConverter emojiConverter, DiscordSocketClient client)
+ {
+ _redis = redis;
+ _errorHandler = errorHandler;
+ _logger = logger;
+ _userRepository = userRepository;
+ _emojiConverter = emojiConverter;
+ _client = client;
+ }
+
+ [Command("rank", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Statistics)]
+ [Summary("get user top 10 in messages or karma")]
+ public async Task RankCmd([Summary("type")] string typeUnformated = "messages", [Summary("amount")] int amount = 10)
+ {
+ try
+ {
+ var type = typeUnformated.ToCharArray().First().ToString().ToUpper() + typeUnformated.Substring(1);
+
+ if (!type.Equals("Messages") && !type.Equals("Karma") && !type.Equals("Rolls"))
+ {
+ await ReplyAsync("Valid types are '`messages`' '`karma`', '`rolls`'");
+ return;
+ }
+
+ var replyBuilder = new StringBuilder();
+
+ if (amount > 20)
+ {
+ replyBuilder.AppendLine(":warning: Limiting to 20");
+ amount = 20;
+ }
+
+ var messageList = _redis.HashGetAll($"{Context.Guild.Id}:{type}");
+ if (messageList.Length == 0)
+ {
+ await ReplyAsync($"No {type.ToLowerInvariant()} found on this server");
+ return;
+ }
+ var sortedList = messageList.OrderByDescending(e => e.Value).ToList();
+ var guildMessages = (int) sortedList.First().Value;
+ var theBot = sortedList.FirstOrDefault(e => e.Name.ToString().Equals(_client.CurrentUser.Id.ToString()));
+ if (!string.IsNullOrEmpty(theBot.Name))
+ {
+ sortedList.Remove(theBot);
+ }
+ if (type == "Messages") sortedList.RemoveAt(0);
+
+ var highscoreUsers = new Dictionary();
+ var listLimiter = 1;
+ var failedToRetrieveUser = false;
+ foreach (var user in sortedList)
+ {
+ if (listLimiter > amount) break;
+ try
+ {
+ var guildUser = _userRepository.Get((ulong) user.Name);
+ if (guildUser.Username != null)
+ {
+ highscoreUsers.Add(new RankUserPolyfillDto
+ {
+ Username = guildUser.Username,
+ Discriminator = guildUser.Discriminator
+ }, (int) user.Value);
+ }
+ else
+ {
+ highscoreUsers.Add(new RankUserPolyfillDto
+ {
+ Id = user.Name
+ }, (int) user.Value);
+ failedToRetrieveUser = true;
+ }
+
+ listLimiter++;
+ }
+ catch (Exception e)
+ {
+ _logger.Warning(LogSource.Geekbot, $"Could not retrieve user {user.Name}", e);
+ }
+ }
+
+ if (failedToRetrieveUser) replyBuilder.AppendLine(":warning: Couldn't get all userdata\n");
+ replyBuilder.AppendLine($":bar_chart: **{type} Highscore for {Context.Guild.Name}**");
+ var highscorePlace = 1;
+ foreach (var user in highscoreUsers)
+ {
+ replyBuilder.Append(highscorePlace < 11
+ ? $"{_emojiConverter.NumberToEmoji(highscorePlace)} "
+ : $"`{highscorePlace}.` ");
+
+ replyBuilder.Append(user.Key.Username != null
+ ? $"**{user.Key.Username}#{user.Key.Discriminator}**"
+ : $"**{user.Key.Id}**");
+
+ switch (type)
+ {
+ case "Messages":
+ var percent = Math.Round((double) (100 * user.Value) / guildMessages, 2);
+ replyBuilder.Append($" - {percent}% of total - {user.Value} messages");
+ break;
+ case "Karma":
+ replyBuilder.Append($" - {user.Value} Karma");
+ break;
+ case "Rolls":
+ replyBuilder.Append($" - {user.Value} Guessed");
+ break;
+ }
+
+ replyBuilder.Append("\n");
+
+ highscorePlace++;
+ }
+
+ await ReplyAsync(replyBuilder.ToString());
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Core/Highscores/HighscoreUserDto.cs b/Geekbot.net/Commands/User/Ranking/RankUserPolyfillDto.cs
similarity index 54%
rename from src/Core/Highscores/HighscoreUserDto.cs
rename to Geekbot.net/Commands/User/Ranking/RankUserPolyfillDto.cs
index 58e1897..625c326 100644
--- a/src/Core/Highscores/HighscoreUserDto.cs
+++ b/Geekbot.net/Commands/User/Ranking/RankUserPolyfillDto.cs
@@ -1,9 +1,8 @@
-namespace Geekbot.Core.Highscores
+namespace Geekbot.net.Commands.User.Ranking
{
- public class HighscoreUserDto
+ internal class RankUserPolyfillDto
{
public string Username { get; set; }
- public string Avatar { get; set; }
public string Discriminator { get; set; }
public string Id { get; set; }
}
diff --git a/Geekbot.net/Commands/User/Stats.cs b/Geekbot.net/Commands/User/Stats.cs
new file mode 100644
index 0000000..476d643
--- /dev/null
+++ b/Geekbot.net/Commands/User/Stats.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Threading.Tasks;
+using Discord;
+using Discord.Commands;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+using Geekbot.net.Lib.Extensions;
+using Geekbot.net.Lib.Levels;
+using StackExchange.Redis;
+
+namespace Geekbot.net.Commands.User
+{
+ public class Stats : ModuleBase
+ {
+ private readonly IErrorHandler _errorHandler;
+ private readonly ILevelCalc _levelCalc;
+ private readonly IDatabase _redis;
+
+ public Stats(IDatabase redis, IErrorHandler errorHandler, ILevelCalc levelCalc)
+ {
+ _redis = redis;
+ _errorHandler = errorHandler;
+ _levelCalc = levelCalc;
+ }
+
+ [Command("stats", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Statistics)]
+ [Summary("Get information about this user")]
+ public async Task User([Summary("@someone")] IUser user = null)
+ {
+ try
+ {
+ var userInfo = user ?? Context.Message.Author;
+ var userGuildInfo = (IGuildUser) userInfo;
+ var createdAt = userInfo.CreatedAt;
+ var joinedAt = userGuildInfo.JoinedAt.Value;
+ var age = Math.Floor((DateTime.Now - createdAt).TotalDays);
+ var joinedDayAgo = Math.Floor((DateTime.Now - joinedAt).TotalDays);
+
+ var messages = (int) _redis.HashGet($"{Context.Guild.Id}:Messages", userInfo.Id.ToString());
+ var guildMessages = (int) _redis.HashGet($"{Context.Guild.Id}:Messages", 0.ToString());
+ var level = _levelCalc.GetLevel(messages);
+
+ var percent = Math.Round((double) (100 * messages) / guildMessages, 2);
+
+ var eb = new EmbedBuilder();
+ eb.WithAuthor(new EmbedAuthorBuilder()
+ .WithIconUrl(userInfo.GetAvatarUrl())
+ .WithName(userInfo.Username));
+ eb.WithColor(new Color(221, 255, 119));
+
+ var karma = _redis.HashGet($"{Context.Guild.Id}:Karma", userInfo.Id.ToString());
+ var correctRolls = _redis.HashGet($"{Context.Guild.Id}:Rolls", userInfo.Id.ToString());
+
+ eb.AddInlineField("Discordian Since",
+ $"{createdAt.Day}.{createdAt.Month}.{createdAt.Year} ({age} days)")
+ .AddInlineField("Joined Server",
+ $"{joinedAt.Day}.{joinedAt.Month}.{joinedAt.Year} ({joinedDayAgo} days)")
+ .AddInlineField("Karma", karma.ToString() ?? "0")
+ .AddInlineField("Level", level)
+ .AddInlineField("Messages Sent", messages)
+ .AddInlineField("Server Total", $"{percent}%");
+
+ if (!correctRolls.IsNullOrEmpty)
+ eb.AddInlineField("Guessed Rolls", correctRolls);
+
+ await ReplyAsync("", false, eb.Build());
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Bot/Commands/Utils/AvatarGetter.cs b/Geekbot.net/Commands/Utils/AvatarGetter.cs
similarity index 51%
rename from src/Bot/Commands/Utils/AvatarGetter.cs
rename to Geekbot.net/Commands/Utils/AvatarGetter.cs
index 458eec8..d42c779 100644
--- a/src/Bot/Commands/Utils/AvatarGetter.cs
+++ b/Geekbot.net/Commands/Utils/AvatarGetter.cs
@@ -2,12 +2,12 @@
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
-using Geekbot.Core;
-using Geekbot.Core.ErrorHandling;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
-namespace Geekbot.Bot.Commands.Utils
+namespace Geekbot.net.Commands.Utils
{
- public class AvatarGetter : TransactionModuleBase
+ public class AvatarGetter : ModuleBase
{
private readonly IErrorHandler _errorHandler;
@@ -17,18 +17,19 @@ namespace Geekbot.Bot.Commands.Utils
}
[Command("avatar", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Helpers)]
[Summary("Get someones avatar")]
- public async Task GetAvatar([Remainder, Summary("@someone")] IUser user = null)
+ public async Task GetAvatar([Remainder] [Summary("user")] IUser user = null)
{
try
{
- user ??= Context.User;
- var url = user.GetAvatarUrl(ImageFormat.Auto, 1024);
+ if (user == null) user = Context.User;
+ var url = user.GetAvatarUrl().Replace("128", "1024");
await ReplyAsync(url);
}
catch (Exception e)
{
- await _errorHandler.HandleCommandException(e, Context);
+ _errorHandler.HandleCommandException(e, Context);
}
}
}
diff --git a/Geekbot.net/Commands/Utils/Changelog/Changelog.cs b/Geekbot.net/Commands/Utils/Changelog/Changelog.cs
new file mode 100644
index 0000000..f1752f7
--- /dev/null
+++ b/Geekbot.net/Commands/Utils/Changelog/Changelog.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+using Discord;
+using Discord.Commands;
+using Discord.WebSocket;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+using Newtonsoft.Json;
+
+namespace Geekbot.net.Commands.Utils.Changelog
+{
+ public class Changelog : ModuleBase
+ {
+ private readonly DiscordSocketClient _client;
+ private readonly IErrorHandler _errorHandler;
+
+ public Changelog(IErrorHandler errorHandler, DiscordSocketClient client)
+ {
+ _errorHandler = errorHandler;
+ _client = client;
+ }
+
+ [Command("changelog", RunMode = RunMode.Async)]
+ [Alias("updates")]
+ [Remarks(CommandCategories.Helpers)]
+ [Summary("Show the latest 5 updates")]
+ public async Task GetChangelog()
+ {
+ try
+ {
+ using (var client = new HttpClient())
+ {
+ client.BaseAddress = new Uri("https://api.github.com");
+ client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent",
+ "http://developer.github.com/v3/#user-agent-required");
+ var response = await client.GetAsync("/repos/pizzaandcoffee/geekbot.net/commits");
+ response.EnsureSuccessStatusCode();
+
+ var stringResponse = await response.Content.ReadAsStringAsync();
+ var commits = JsonConvert.DeserializeObject>(stringResponse);
+ var eb = new EmbedBuilder();
+ eb.WithColor(new Color(143, 165, 102));
+ eb.WithAuthor(new EmbedAuthorBuilder
+ {
+ IconUrl = _client.CurrentUser.GetAvatarUrl(),
+ Name = "Latest Updates",
+ Url = "https://geekbot.pizzaandcoffee.rocks/updates"
+ });
+ var sb = new StringBuilder();
+ foreach (var commit in commits.Take(10))
+ sb.AppendLine($"- {commit.Commit.Message} ({commit.Commit.Author.Date:yyyy-MM-dd})");
+ eb.Description = sb.ToString();
+ eb.WithFooter(new EmbedFooterBuilder
+ {
+ Text = $"List generated from github commits on {DateTime.Now:yyyy-MM-dd}"
+ });
+ await ReplyAsync("", false, eb.Build());
+ }
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Utils/Changelog/CommitAuthorDto.cs b/Geekbot.net/Commands/Utils/Changelog/CommitAuthorDto.cs
new file mode 100644
index 0000000..8debd77
--- /dev/null
+++ b/Geekbot.net/Commands/Utils/Changelog/CommitAuthorDto.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Geekbot.net.Commands.Utils.Changelog
+{
+ public class CommitAuthorDto
+ {
+ public string Name { get; set; }
+ public string Email { get; set; }
+ public DateTimeOffset Date { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Utils/Changelog/CommitDto.cs b/Geekbot.net/Commands/Utils/Changelog/CommitDto.cs
new file mode 100644
index 0000000..3379697
--- /dev/null
+++ b/Geekbot.net/Commands/Utils/Changelog/CommitDto.cs
@@ -0,0 +1,7 @@
+namespace Geekbot.net.Commands.Utils.Changelog
+{
+ public class CommitDto
+ {
+ public CommitInfoDto Commit { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Utils/Changelog/CommitInfoDto.cs b/Geekbot.net/Commands/Utils/Changelog/CommitInfoDto.cs
new file mode 100644
index 0000000..9008343
--- /dev/null
+++ b/Geekbot.net/Commands/Utils/Changelog/CommitInfoDto.cs
@@ -0,0 +1,8 @@
+namespace Geekbot.net.Commands.Utils.Changelog
+{
+ public class CommitInfoDto
+ {
+ public CommitAuthorDto Author { get; set; }
+ public string Message { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Utils/Choose.cs b/Geekbot.net/Commands/Utils/Choose.cs
new file mode 100644
index 0000000..4d54654
--- /dev/null
+++ b/Geekbot.net/Commands/Utils/Choose.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Threading.Tasks;
+using Discord.Commands;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+using Geekbot.net.Lib.Localization;
+
+namespace Geekbot.net.Commands.Utils
+{
+ public class Choose : ModuleBase
+ {
+ private readonly IErrorHandler _errorHandler;
+ private readonly ITranslationHandler _translation;
+
+ public Choose(IErrorHandler errorHandler, ITranslationHandler translation)
+ {
+ _errorHandler = errorHandler;
+ _translation = translation;
+ }
+
+ [Command("choose", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Helpers)]
+ [Summary("Let the bot choose for you, seperate options with a semicolon.")]
+ public async Task Command([Remainder] [Summary("option1;option2")]
+ string choices)
+ {
+ try
+ {
+ var transDict = _translation.GetDict(Context);
+ var choicesArray = choices.Split(';');
+ var choice = new Random().Next(choicesArray.Length);
+ await ReplyAsync(string.Format(transDict["Choice"], choicesArray[choice]));
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Utils/Dice/Dice.cs b/Geekbot.net/Commands/Utils/Dice/Dice.cs
new file mode 100644
index 0000000..9c169c7
--- /dev/null
+++ b/Geekbot.net/Commands/Utils/Dice/Dice.cs
@@ -0,0 +1,115 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Discord.Commands;
+using Geekbot.net.Lib;
+
+namespace Geekbot.net.Commands.Utils.Dice
+{
+ public class Dice : ModuleBase
+ {
+ [Command("dice", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Randomness)]
+ [Summary("Roll a dice.")]
+ public async Task RollCommand([Remainder] [Summary("diceType")] string diceType = "1d20")
+ {
+ var splitedDices = diceType.Split("+");
+ var dices = new List();
+ var mod = 0;
+ foreach (var i in splitedDices)
+ {
+ var dice = ToDice(i);
+ if (dice.Sides != 0 && dice.Times != 0)
+ {
+ dices.Add(dice);
+ }
+ else if (dice.Mod != 0)
+ {
+ if (mod != 0)
+ {
+ await ReplyAsync("You can only have one mod");
+ return;
+ }
+
+ mod = dice.Mod;
+ }
+ }
+
+ if (!dices.Any())
+ {
+ await ReplyAsync(
+ "That is not a valid dice, examples are: 1d20, 1d6, 2d6, 1d6+2, 1d6+2d8+1d20+6, etc...");
+ return;
+ }
+
+
+ if (dices.Any(d => d.Times > 20))
+ {
+ await ReplyAsync("You can't throw more than 20 dices");
+ return;
+ }
+
+ if (dices.Any(d => d.Sides > 144))
+ {
+ await ReplyAsync("A dice can't have more than 144 sides");
+ return;
+ }
+
+ var rep = new StringBuilder();
+ rep.AppendLine($":game_die: {Context.User.Mention}");
+ rep.Append("**Result:** ");
+ var resultStrings = new List();
+ var total = 0;
+ var extraText = "";
+ foreach (var dice in dices)
+ {
+ var results = new List();
+ for (var i = 0; i < dice.Times; i++)
+ {
+ var roll = new Random().Next(1, dice.Sides);
+ total += roll;
+ results.Add(roll);
+ if (roll == dice.Sides) extraText = "**Critical Hit!**";
+ if (roll == 1) extraText = "**Critical Fail!**";
+ }
+
+ resultStrings.Add($"{dice.DiceType} ({string.Join(",", results)})");
+ }
+
+ rep.Append(string.Join(" + ", resultStrings));
+ if (mod != 0)
+ {
+ rep.Append($" + {mod}");
+ total += mod;
+ }
+
+ rep.AppendLine();
+ rep.AppendLine($"**Total:** {total}");
+ if (extraText != "") rep.AppendLine(extraText);
+ await ReplyAsync(rep.ToString());
+ }
+
+ private DiceTypeDto ToDice(string dice)
+ {
+ var diceParts = dice.Split('d');
+ if (diceParts.Length == 2
+ && int.TryParse(diceParts[0], out var times)
+ && int.TryParse(diceParts[1], out var max))
+ return new DiceTypeDto
+ {
+ DiceType = dice,
+ Times = times,
+ Sides = max
+ };
+ if (dice.Length == 1
+ && int.TryParse(diceParts[0], out var mod))
+ return new DiceTypeDto
+ {
+ Mod = mod
+ };
+ return new DiceTypeDto();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Utils/Dice/DiceTypeDto.cs b/Geekbot.net/Commands/Utils/Dice/DiceTypeDto.cs
new file mode 100644
index 0000000..5c54792
--- /dev/null
+++ b/Geekbot.net/Commands/Utils/Dice/DiceTypeDto.cs
@@ -0,0 +1,10 @@
+namespace Geekbot.net.Commands.Utils.Dice
+{
+ internal class DiceTypeDto
+ {
+ public string DiceType { get; set; }
+ public int Times { get; set; }
+ public int Sides { get; set; }
+ public int Mod { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Bot/Commands/Utils/Emojify.cs b/Geekbot.net/Commands/Utils/Emojify.cs
similarity index 51%
rename from src/Bot/Commands/Utils/Emojify.cs
rename to Geekbot.net/Commands/Utils/Emojify.cs
index a513710..57363c2 100644
--- a/src/Bot/Commands/Utils/Emojify.cs
+++ b/Geekbot.net/Commands/Utils/Emojify.cs
@@ -1,26 +1,31 @@
-using Discord.Commands;
-using Geekbot.Core;
-using Geekbot.Core.Converters;
-using Geekbot.Core.ErrorHandling;
+using System;
+using System.Threading.Tasks;
+using Discord.Commands;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.Converters;
+using Geekbot.net.Lib.ErrorHandling;
-namespace Geekbot.Bot.Commands.Utils
+namespace Geekbot.net.Commands.Utils
{
- public class Emojify : TransactionModuleBase
+ public class Emojify : ModuleBase
{
+ private readonly IEmojiConverter _emojiConverter;
private readonly IErrorHandler _errorHandler;
- public Emojify(IErrorHandler errorHandler)
+ public Emojify(IErrorHandler errorHandler, IEmojiConverter emojiConverter)
{
_errorHandler = errorHandler;
+ _emojiConverter = emojiConverter;
}
[Command("emojify", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Helpers)]
[Summary("Emojify text")]
public async Task Dflt([Remainder] [Summary("text")] string text)
{
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!");
@@ -29,18 +34,10 @@ namespace Geekbot.Bot.Commands.Utils
await ReplyAsync($"{Context.User.Username}#{Context.User.Discriminator} said:");
await ReplyAsync(emojis);
- try
- {
- await Context.Message.DeleteAsync();
- }
- catch
- {
- // bot may not have enough permission, doesn't matter if it fails
- }
}
catch (Exception e)
{
- await _errorHandler.HandleCommandException(e, Context);
+ _errorHandler.HandleCommandException(e, Context);
}
}
}
diff --git a/src/Bot/Commands/Utils/Help.cs b/Geekbot.net/Commands/Utils/Help.cs
similarity index 73%
rename from src/Bot/Commands/Utils/Help.cs
rename to Geekbot.net/Commands/Utils/Help.cs
index 7aa9aff..be9a747 100644
--- a/src/Bot/Commands/Utils/Help.cs
+++ b/Geekbot.net/Commands/Utils/Help.cs
@@ -3,12 +3,12 @@ using System.Text;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
-using Geekbot.Core;
-using Geekbot.Core.ErrorHandling;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
-namespace Geekbot.Bot.Commands.Utils
+namespace Geekbot.net.Commands.Utils
{
- public class Help : TransactionModuleBase
+ public class Help : ModuleBase
{
private readonly IErrorHandler _errorHandler;
@@ -18,6 +18,7 @@ namespace Geekbot.Bot.Commands.Utils
}
[Command("help", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Helpers)]
[Summary("List all Commands")]
public async Task GetHelp()
{
@@ -27,13 +28,13 @@ 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.CreateDMChannelAsync(RequestOptions.Default);
+ var dm = await Context.User.GetOrCreateDMChannelAsync();
await dm.SendMessageAsync(sb.ToString());
await Context.Message.AddReactionAsync(new Emoji("✅"));
}
catch (Exception e)
{
- await _errorHandler.HandleCommandException(e, Context);
+ _errorHandler.HandleCommandException(e, Context);
}
}
}
diff --git a/src/Bot/Commands/Utils/Info.cs b/Geekbot.net/Commands/Utils/Info.cs
similarity index 65%
rename from src/Bot/Commands/Utils/Info.cs
rename to Geekbot.net/Commands/Utils/Info.cs
index 912528d..467b9d3 100644
--- a/src/Bot/Commands/Utils/Info.cs
+++ b/Geekbot.net/Commands/Utils/Info.cs
@@ -5,26 +5,30 @@ using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
-using Geekbot.Core;
-using Geekbot.Core.ErrorHandling;
-using Geekbot.Core.Extensions;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+using Geekbot.net.Lib.Extensions;
+using StackExchange.Redis;
-namespace Geekbot.Bot.Commands.Utils
+namespace Geekbot.net.Commands.Utils
{
- public class Info : TransactionModuleBase
+ public class Info : ModuleBase
{
private readonly DiscordSocketClient _client;
private readonly CommandService _commands;
private readonly IErrorHandler _errorHandler;
+ private readonly IDatabase _redis;
- public Info(IErrorHandler errorHandler, DiscordSocketClient client, CommandService commands)
+ public Info(IDatabase redis, IErrorHandler errorHandler, DiscordSocketClient client, CommandService commands)
{
+ _redis = redis;
_errorHandler = errorHandler;
_client = client;
_commands = commands;
}
[Command("info", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Helpers)]
[Summary("Get Information about the bot")]
public async Task BotInfo()
{
@@ -32,30 +36,31 @@ namespace Geekbot.Bot.Commands.Utils
{
var eb = new EmbedBuilder();
- var appInfo = await _client.GetApplicationInfoAsync();
-
eb.WithAuthor(new EmbedAuthorBuilder()
- .WithIconUrl(appInfo.IconUrl)
+ .WithIconUrl(_client.CurrentUser.GetAvatarUrl())
.WithName($"{Constants.Name} V{Constants.BotVersion()}"));
+ var botOwner = await Context.Guild.GetUserAsync(ulong.Parse(_redis.StringGet("botOwner")));
var uptime = DateTime.Now.Subtract(Process.GetCurrentProcess().StartTime);
eb.AddInlineField("Bot Name", _client.CurrentUser.Username);
- eb.AddInlineField("Bot Owner", $"{appInfo.Owner.Username}#{appInfo.Owner.Discriminator}");
- eb.AddInlineField("Library", $"Discord.NET {Constants.LibraryVersion()}");
+ eb.AddInlineField("Bot Owner", $"{botOwner.Username}#{botOwner.Discriminator}");
+ eb.AddInlineField("Library", "Discord.NET V1.0.2");
eb.AddInlineField("Uptime", $"{uptime.Days}D {uptime.Hours}H {uptime.Minutes}M {uptime.Seconds}S");
eb.AddInlineField("Servers", Context.Client.GetGuildsAsync().Result.Count);
eb.AddInlineField("Total Commands", _commands.Commands.Count());
+
eb.AddField("Website", "https://geekbot.pizzaandcoffee.rocks/");
await ReplyAsync("", false, eb.Build());
}
catch (Exception e)
{
- await _errorHandler.HandleCommandException(e, Context);
+ _errorHandler.HandleCommandException(e, Context);
}
}
[Command("uptime", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Helpers)]
[Summary("Get the Bot Uptime")]
public async Task BotUptime()
{
@@ -66,7 +71,7 @@ namespace Geekbot.Bot.Commands.Utils
}
catch (Exception e)
{
- await _errorHandler.HandleCommandException(e, Context);
+ _errorHandler.HandleCommandException(e, Context);
}
}
}
diff --git a/src/Bot/Commands/Utils/Ping.cs b/Geekbot.net/Commands/Utils/Ping.cs
similarity index 67%
rename from src/Bot/Commands/Utils/Ping.cs
rename to Geekbot.net/Commands/Utils/Ping.cs
index ee751cd..226e9a3 100644
--- a/src/Bot/Commands/Utils/Ping.cs
+++ b/Geekbot.net/Commands/Utils/Ping.cs
@@ -1,13 +1,14 @@
using System.Threading.Tasks;
using Discord.Commands;
-using Geekbot.Core;
+using Geekbot.net.Lib;
-namespace Geekbot.Bot.Commands.Utils
+namespace Geekbot.net.Commands.Utils
{
- public class Ping : TransactionModuleBase
+ public class Ping : ModuleBase
{
[Command("👀", RunMode = RunMode.Async)]
[Summary("Look at the bot.")]
+ [Remarks(CommandCategories.Fun)]
public async Task Eyes()
{
await ReplyAsync("S... Stop looking at me... baka!");
diff --git a/Geekbot.net/Commands/Utils/Poll/Poll.cs b/Geekbot.net/Commands/Utils/Poll/Poll.cs
new file mode 100644
index 0000000..b32a7c9
--- /dev/null
+++ b/Geekbot.net/Commands/Utils/Poll/Poll.cs
@@ -0,0 +1,180 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Discord;
+using Discord.Commands;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.Converters;
+using Geekbot.net.Lib.ErrorHandling;
+using Geekbot.net.Lib.Extensions;
+using Geekbot.net.Lib.UserRepository;
+using Newtonsoft.Json;
+using StackExchange.Redis;
+
+namespace Geekbot.net.Commands.Utils.Poll
+{
+ [Group("poll")]
+ public class Poll : ModuleBase
+ {
+ private readonly IEmojiConverter _emojiConverter;
+ private readonly IErrorHandler _errorHandler;
+ private readonly IDatabase _redis;
+ private readonly IUserRepository _userRepository;
+
+ public Poll(IErrorHandler errorHandler, IDatabase redis, IEmojiConverter emojiConverter,
+ IUserRepository userRepository)
+ {
+ _errorHandler = errorHandler;
+ _redis = redis;
+ _emojiConverter = emojiConverter;
+ _userRepository = userRepository;
+ }
+
+ [Command(RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Helpers)]
+ [Summary("Check status of the current poll")]
+ public async Task Dflt()
+ {
+ try
+ {
+ var currentPoll = GetCurrentPoll();
+ if (currentPoll.Question == null || currentPoll.IsFinshed)
+ {
+ await ReplyAsync(
+ "There is no poll in this channel ongoing at the moment\r\nYou can create one with `!poll create question;option1;option2;option3`");
+ return;
+ }
+
+ await ReplyAsync("There is a poll running at the moment");
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+
+ [Command("create", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Helpers)]
+ [Summary("Create a poll")]
+ public async Task Create([Remainder] [Summary("question;option1;option2")]
+ string rawPollString)
+ {
+ try
+ {
+ var currentPoll = GetCurrentPoll();
+ if (currentPoll.Question != null && !currentPoll.IsFinshed)
+ {
+ await ReplyAsync("You have not finished you last poll yet. To finish it use `!poll end`");
+ return;
+ }
+
+ var pollList = rawPollString.Split(';').ToList();
+ if (pollList.Count <= 2)
+ {
+ await ReplyAsync(
+ "You need a question with atleast 2 options, a valid creation would look like this `question;option1;option2`");
+ return;
+ }
+
+ var eb = new EmbedBuilder();
+ eb.Title = $"Poll by {Context.User.Username}";
+ var question = pollList[0];
+ eb.Description = question;
+ pollList.RemoveAt(0);
+ var i = 1;
+ pollList.ForEach(option =>
+ {
+ eb.AddInlineField($"Option {_emojiConverter.NumberToEmoji(i)}", option);
+ i++;
+ });
+ var pollMessage = await ReplyAsync("", false, eb.Build());
+ i = 1;
+ pollList.ForEach(option =>
+ {
+ pollMessage.AddReactionAsync(new Emoji(_emojiConverter.NumberToEmoji(i)));
+ i++;
+ });
+ var poll = new PollDataDto
+ {
+ Creator = Context.User.Id,
+ MessageId = pollMessage.Id,
+ IsFinshed = false,
+ Question = question,
+ Options = pollList
+ };
+ var pollJson = JsonConvert.SerializeObject(poll);
+ _redis.HashSet($"{Context.Guild.Id}:Polls", new[] {new HashEntry(Context.Channel.Id, pollJson)});
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+
+ [Command("end", RunMode = RunMode.Async)]
+ [Remarks(CommandCategories.Helpers)]
+ [Summary("End the current poll")]
+ public async Task End()
+ {
+ try
+ {
+ var currentPoll = GetCurrentPoll();
+ if (currentPoll.Question == null || currentPoll.IsFinshed)
+ {
+ await ReplyAsync("There is no ongoing poll at the moment");
+ return;
+ }
+
+ var results = await GetPollResults(currentPoll);
+ var sb = new StringBuilder();
+ sb.AppendLine("**Poll Results**");
+ sb.AppendLine(currentPoll.Question);
+ foreach (var result in results) sb.AppendLine($"{result.VoteCount} - {result.Option}");
+ await ReplyAsync(sb.ToString());
+ currentPoll.IsFinshed = true;
+ var pollJson = JsonConvert.SerializeObject(currentPoll);
+ _redis.HashSet($"{Context.Guild.Id}:Polls", new[] {new HashEntry(Context.Channel.Id, pollJson)});
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context);
+ }
+ }
+
+ private PollDataDto GetCurrentPoll()
+ {
+ try
+ {
+ var currentPoll = _redis.HashGet($"{Context.Guild.Id}:Polls", Context.Channel.Id);
+ return JsonConvert.DeserializeObject(currentPoll.ToString());
+ }
+ catch
+ {
+ return new PollDataDto();
+ }
+ }
+
+ private async Task> GetPollResults(PollDataDto poll)
+ {
+ var message = (IUserMessage) await Context.Channel.GetMessageAsync(poll.MessageId);
+ var results = new List();
+ foreach (var r in message.Reactions)
+ try
+ {
+ var option = int.Parse(r.Key.Name.ToCharArray()[0].ToString());
+ var result = new PollResultDto
+ {
+ Option = poll.Options[option - 1],
+ VoteCount = r.Value.ReactionCount
+ };
+ results.Add(result);
+ }
+ catch {}
+
+ results.Sort((x, y) => y.VoteCount.CompareTo(x.VoteCount));
+ return results;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Utils/Poll/PollDataDto.cs b/Geekbot.net/Commands/Utils/Poll/PollDataDto.cs
new file mode 100644
index 0000000..3d21479
--- /dev/null
+++ b/Geekbot.net/Commands/Utils/Poll/PollDataDto.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+
+namespace Geekbot.net.Commands.Utils.Poll
+{
+ internal class PollDataDto
+ {
+ public ulong Creator { get; set; }
+ public ulong MessageId { get; set; }
+ public bool IsFinshed { get; set; }
+ public string Question { get; set; }
+ public List Options { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Utils/Poll/PollResultDto.cs b/Geekbot.net/Commands/Utils/Poll/PollResultDto.cs
new file mode 100644
index 0000000..b4f14a2
--- /dev/null
+++ b/Geekbot.net/Commands/Utils/Poll/PollResultDto.cs
@@ -0,0 +1,8 @@
+namespace Geekbot.net.Commands.Utils.Poll
+{
+ internal class PollResultDto
+ {
+ public string Option { get; set; }
+ public int VoteCount { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Commands/Utils/Quote/Quote.cs b/Geekbot.net/Commands/Utils/Quote/Quote.cs
new file mode 100644
index 0000000..84eec26
--- /dev/null
+++ b/Geekbot.net/Commands/Utils/Quote/Quote.cs
@@ -0,0 +1,239 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Discord;
+using Discord.Commands;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.ErrorHandling;
+using Geekbot.net.Lib.Polyfills;
+using Newtonsoft.Json;
+using StackExchange.Redis;
+
+namespace Geekbot.net.Commands.Utils.Quote
+{
+ [Group("quote")]
+ public class Quote : ModuleBase
+ {
+ private readonly IErrorHandler _errorHandler;
+ private readonly IDatabase _redis;
+
+ public Quote(IDatabase redis, IErrorHandler errorHandler)
+ {
+ _redis = redis;
+ _errorHandler = errorHandler;
+ }
+
+ [Command]
+ [Remarks(CommandCategories.Quotes)]
+ [Summary("Return a random quoute from the database")]
+ public async Task GetRandomQuote()
+ {
+ try
+ {
+ var randomQuotes = _redis.SetMembers($"{Context.Guild.Id}:Quotes");
+ if (!randomQuotes.Any())
+ {
+ await ReplyAsync("This server doesn't seem to have any quotes yet. You can add a quote with `!quote save @user` or `!quote save `");
+ return;
+ }
+ var randomNumber = new Random().Next(randomQuotes.Length - 1);
+ var randomQuote = randomQuotes[randomNumber];
+ var quote = JsonConvert.DeserializeObject(randomQuote);
+ var embed = QuoteBuilder(quote, randomNumber + 1);
+ await ReplyAsync("", false, embed.Build());
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context, "Whoops, seems like the quote was to edgy to return");
+ }
+ }
+
+ [Command("save")]
+ [Remarks(CommandCategories.Quotes)]
+ [Summary("Save a quote from the last sent message by @user")]
+ public async Task SaveQuote([Summary("@user")] IUser user)
+ {
+ try
+ {
+ if (user.Id == Context.Message.Author.Id)
+ {
+ await ReplyAsync("You can't save your own quotes...");
+ return;
+ }
+
+ if (user.IsBot)
+ {
+ await ReplyAsync("You can't save quotes by a bot...");
+ return;
+ }
+
+ var lastMessage = await GetLastMessageByUser(user);
+ if (lastMessage == null) return;
+ var quote = CreateQuoteObject(lastMessage);
+ var quoteStore = JsonConvert.SerializeObject(quote);
+ _redis.SetAdd($"{Context.Guild.Id}:Quotes", quoteStore);
+ var embed = QuoteBuilder(quote);
+ await ReplyAsync("**Quote Added**", false, embed.Build());
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context,
+ "I counldn't find a quote from that user :disappointed:");
+ }
+ }
+
+ [Command("save")]
+ [Remarks(CommandCategories.Quotes)]
+ [Summary("Save a quote from a message id")]
+ public async Task SaveQuote([Summary("messageId")] ulong messageId)
+ {
+ try
+ {
+ var message = await Context.Channel.GetMessageAsync(messageId);
+ if (message.Author.Id == Context.Message.Author.Id)
+ {
+ await ReplyAsync("You can't save your own quotes...");
+ return;
+ }
+
+ if (message.Author.IsBot)
+ {
+ await ReplyAsync("You can't save quotes by a bot...");
+ return;
+ }
+
+ var quote = CreateQuoteObject(message);
+ var quoteStore = JsonConvert.SerializeObject(quote);
+ _redis.SetAdd($"{Context.Guild.Id}:Quotes", quoteStore);
+ var embed = QuoteBuilder(quote);
+ await ReplyAsync("**Quote Added**", false, embed.Build());
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context,
+ "I couldn't find a message with that id :disappointed:");
+ }
+ }
+
+ [Command("make")]
+ [Remarks(CommandCategories.Quotes)]
+ [Summary("Create a quote from the last sent message by @user")]
+ public async Task ReturnSpecifiedQuote([Summary("@user")] IUser user)
+ {
+ try
+ {
+ var lastMessage = await GetLastMessageByUser(user);
+ if (lastMessage == null) return;
+ var quote = CreateQuoteObject(lastMessage);
+ var embed = QuoteBuilder(quote);
+ await ReplyAsync("", false, embed.Build());
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context,
+ "I counldn't find a quote from that user :disappointed:");
+ }
+ }
+
+ [Command("make")]
+ [Remarks(CommandCategories.Quotes)]
+ [Summary("Create a quote from a message id")]
+ public async Task ReturnSpecifiedQuote([Summary("messageId")] ulong messageId)
+ {
+ try
+ {
+ var message = await Context.Channel.GetMessageAsync(messageId);
+ var quote = CreateQuoteObject(message);
+ var embed = QuoteBuilder(quote);
+ await ReplyAsync("", false, embed.Build());
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context,
+ "I couldn't find a message with that id :disappointed:");
+ }
+ }
+
+ [Command("remove")]
+ [RequireUserPermission(GuildPermission.KickMembers)]
+ [RequireUserPermission(GuildPermission.ManageMessages)]
+ [RequireUserPermission(GuildPermission.ManageRoles)]
+ [Remarks(CommandCategories.Quotes)]
+ [Summary("Remove a quote (required mod permissions)")]
+ public async Task RemoveQuote([Summary("quoteId")] int id)
+ {
+ try
+ {
+ var quotes = _redis.SetMembers($"{Context.Guild.Id}:Quotes");
+ var success = _redis.SetRemove($"{Context.Guild.Id}:Quotes", quotes[id - 1]);
+ if (success)
+ {
+ var quote = JsonConvert.DeserializeObject(quotes[id - 1]);
+ var embed = QuoteBuilder(quote);
+ await ReplyAsync($"**Removed #{id}**", false, embed.Build());
+ }
+ else
+ {
+ await ReplyAsync("I couldn't find a quote with that id :disappointed:");
+ }
+ }
+ catch (Exception e)
+ {
+ _errorHandler.HandleCommandException(e, Context,
+ "I couldn't find a quote with that id :disappointed:");
+ }
+ }
+
+ private async Task GetLastMessageByUser(IUser user)
+ {
+ try
+ {
+ var list = Context.Channel.GetMessagesAsync().Flatten();
+ return await list.FirstOrDefault(msg =>
+ msg.Author.Id == user.Id &&
+ msg.Embeds.Count == 0 &&
+ msg.Id != Context.Message.Id &&
+ !msg.Content.ToLower().StartsWith("!"));
+ }
+ catch
+ {
+ await ReplyAsync($"No quoteable message have been sent by {user.Username} in this channel");
+ return null;
+ }
+ }
+
+ private EmbedBuilder QuoteBuilder(QuoteObjectDto quote, int id = 0)
+ {
+ var user = Context.Client.GetUserAsync(quote.UserId).Result ?? new UserPolyfillDto { Username = "Unknown User" };
+ var eb = new EmbedBuilder();
+ eb.WithColor(new Color(143, 167, 232));
+ eb.Title = id == 0 ? "" : $"#{id} | ";
+ eb.Title += $"{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;
+ return eb;
+ }
+
+ private QuoteObjectDto CreateQuoteObject(IMessage message)
+ {
+ string image;
+ try
+ {
+ image = message.Attachments.First().Url;
+ }
+ catch (Exception)
+ {
+ image = null;
+ }
+
+ return new QuoteObjectDto
+ {
+ UserId = message.Author.Id,
+ Time = message.Timestamp.DateTime,
+ Quote = message.Content,
+ Image = image
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Bot/Commands/Utils/Quote/QuoteObjectDto.cs b/Geekbot.net/Commands/Utils/Quote/QuoteObjectDto.cs
similarity index 81%
rename from src/Bot/Commands/Utils/Quote/QuoteObjectDto.cs
rename to Geekbot.net/Commands/Utils/Quote/QuoteObjectDto.cs
index 32b65cf..a37ff76 100644
--- a/src/Bot/Commands/Utils/Quote/QuoteObjectDto.cs
+++ b/Geekbot.net/Commands/Utils/Quote/QuoteObjectDto.cs
@@ -1,6 +1,6 @@
using System;
-namespace Geekbot.Bot.Commands.Utils.Quote
+namespace Geekbot.net.Commands.Utils.Quote
{
internal class QuoteObjectDto
{
diff --git a/Geekbot.net/Geekbot.net.csproj b/Geekbot.net/Geekbot.net.csproj
new file mode 100755
index 0000000..81f3a6c
--- /dev/null
+++ b/Geekbot.net/Geekbot.net.csproj
@@ -0,0 +1,87 @@
+
+
+ Exe
+ netcoreapp2.0
+ derp.ico
+ 3.7.0
+ $(VersionSuffix)
+ $(Version)-$(VersionSuffix)
+ Pizza and Coffee Studios
+ Pizza and Coffee Studios
+ A Discord bot
+ https://github.com/pizzaandcoffee/Geekbot.net
+ NU1701
+
+
+
+
+ 2.0.0-beta
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1.2.6
+
+
+
+
+
+ 4.3.0
+
+
+ 4.3.0
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ Always
+
+
+ Always
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Geekbot.net/Handlers.cs b/Geekbot.net/Handlers.cs
new file mode 100644
index 0000000..f248d5d
--- /dev/null
+++ b/Geekbot.net/Handlers.cs
@@ -0,0 +1,214 @@
+using System;
+using System.Text;
+using System.Threading.Tasks;
+using Discord;
+using Discord.Commands;
+using Discord.WebSocket;
+using Geekbot.net.Lib.Logger;
+using Geekbot.net.Lib.ReactionListener;
+using Geekbot.net.Lib.UserRepository;
+using StackExchange.Redis;
+
+namespace Geekbot.net
+{
+ public class Handlers
+ {
+ private readonly IDiscordClient _client;
+ private readonly IGeekbotLogger _logger;
+ private readonly IDatabase _redis;
+ private readonly IServiceProvider _servicesProvider;
+ private readonly CommandService _commands;
+ private readonly IUserRepository _userRepository;
+ private readonly IReactionListener _reactionListener;
+
+ public Handlers(IDiscordClient client, IGeekbotLogger logger, IDatabase redis, IServiceProvider servicesProvider, CommandService commands, IUserRepository userRepository, IReactionListener reactionListener)
+ {
+ _client = client;
+ _logger = logger;
+ _redis = redis;
+ _servicesProvider = servicesProvider;
+ _commands = commands;
+ _userRepository = userRepository;
+ _reactionListener = reactionListener;
+ }
+
+ //
+ // Incoming Messages
+ //
+
+ public Task RunCommand(SocketMessage messageParam)
+ {
+ try
+ {
+ if (!(messageParam is SocketUserMessage message)) return Task.CompletedTask;
+ if (message.Author.IsBot) return Task.CompletedTask;
+ var argPos = 0;
+ var lowCaseMsg = message.ToString().ToLower();
+ if (lowCaseMsg.StartsWith("hui"))
+ {
+ message.Channel.SendMessageAsync("hui!!!");
+ return Task.CompletedTask;
+ }
+ if (lowCaseMsg.StartsWith("ping ") || lowCaseMsg.Equals("ping"))
+ {
+ bool.TryParse(_redis.HashGet($"{((SocketGuildChannel) message.Channel).Guild.Id}:Settings", "ping"), out var allowPings);
+ if (allowPings)
+ {
+ message.Channel.SendMessageAsync("pong");
+ return Task.CompletedTask;
+ }
+ }
+ if (!(message.HasCharPrefix('!', ref argPos) ||
+ message.HasMentionPrefix(_client.CurrentUser, ref argPos))) return Task.CompletedTask;
+ var context = new CommandContext(_client, message);
+ var commandExec = _commands.ExecuteAsync(context, argPos, _servicesProvider);
+ _logger.Information(LogSource.Command,
+ context.Message.Content.Split(" ")[0].Replace("!", ""),
+ SimpleConextConverter.ConvertContext(context));
+ return Task.CompletedTask;
+ }
+ catch (Exception e)
+ {
+ _logger.Error(LogSource.Geekbot, "Failed to Process Message", e);
+ return Task.CompletedTask;
+ }
+ }
+
+ public Task UpdateStats(SocketMessage message)
+ {
+ try
+ {
+ if (message == null) return Task.CompletedTask;
+ if (message.Channel.Name.StartsWith('@'))
+ {
+ _logger.Information(LogSource.Message, $"[DM-Channel] {message.Content}", SimpleConextConverter.ConvertSocketMessage(message));
+ return Task.CompletedTask;
+ }
+ var channel = (SocketGuildChannel) message.Channel;
+
+ _redis.HashIncrementAsync($"{channel.Guild.Id}:Messages", message.Author.Id.ToString());
+ _redis.HashIncrementAsync($"{channel.Guild.Id}:Messages", 0.ToString());
+
+ if (message.Author.IsBot) return Task.CompletedTask;
+ _logger.Information(LogSource.Message, message.Content, SimpleConextConverter.ConvertSocketMessage(message));
+ }
+ catch (Exception e)
+ {
+ _logger.Error(LogSource.Message, "Could not process message stats", e);
+ }
+ return Task.CompletedTask;
+ }
+
+ //
+ // User Stuff
+ //
+
+ public Task UserJoined(SocketGuildUser user)
+ {
+ try
+ {
+ if (!user.IsBot)
+ {
+ var message = _redis.HashGet($"{user.Guild.Id}:Settings", "WelcomeMsg");
+ if (!message.IsNullOrEmpty)
+ {
+ message = message.ToString().Replace("$user", user.Mention);
+ user.Guild.DefaultChannel.SendMessageAsync(message);
+ }
+ }
+ _userRepository.Update(user);
+ _logger.Information(LogSource.Geekbot, $"{user.Username} ({user.Id}) joined {user.Guild.Name} ({user.Guild.Id})");
+ }
+ catch (Exception e)
+ {
+ _logger.Error(LogSource.Geekbot, "Failed to send welcome message", e);
+ }
+ return Task.CompletedTask;
+ }
+
+ public Task UserUpdated(SocketUser oldUser, SocketUser newUser)
+ {
+ _userRepository.Update(newUser);
+ return Task.CompletedTask;
+ }
+
+ public async Task UserLeft(SocketGuildUser user)
+ {
+ try
+ {
+ var sendLeftEnabled = _redis.HashGet($"{user.Guild.Id}:Settings", "ShowLeave");
+ if (sendLeftEnabled.ToString() == "1")
+ {
+ var modChannel = ulong.Parse(_redis.HashGet($"{user.Guild.Id}:Settings", "ModChannel"));
+ if (!string.IsNullOrEmpty(modChannel.ToString()))
+ {
+ var modChannelSocket = (ISocketMessageChannel) await _client.GetChannelAsync(modChannel);
+ await modChannelSocket.SendMessageAsync($"{user.Username}#{user.Discriminator} left the server");
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ _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})");
+ }
+
+ //
+ // Message Stuff
+ //
+
+ public async Task MessageDeleted(Cacheable message, ISocketMessageChannel channel)
+ {
+ try
+ {
+ var guild = ((IGuildChannel) channel).Guild;
+ var sendLeftEnabled = _redis.HashGet($"{guild.Id}:Settings", "ShowDelete");
+ if (sendLeftEnabled.ToString() == "1")
+ {
+ var modChannel = ulong.Parse(_redis.HashGet($"{guild.Id}:Settings", "ModChannel"));
+ if (!string.IsNullOrEmpty(modChannel.ToString()) && modChannel != channel.Id)
+ {
+ var modChannelSocket = (ISocketMessageChannel) await _client.GetChannelAsync(modChannel);
+ 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(message.Value.Content);
+ }
+ else
+ {
+ sb.AppendLine("Someone deleted a message, the message was not cached...");
+ }
+ await modChannelSocket.SendMessageAsync(sb.ToString());
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.Error(LogSource.Geekbot, "Failed to send delete message...", e);
+ }
+ }
+
+ //
+ // Reactions
+ //
+
+ public Task ReactionAdded(Cacheable cacheable, ISocketMessageChannel socketMessageChannel, SocketReaction reaction)
+ {
+ if (reaction.User.Value.IsBot) return Task.CompletedTask;
+ if (!_reactionListener.IsListener(reaction.MessageId)) return Task.CompletedTask;
+ _reactionListener.GiveRole(socketMessageChannel, reaction);
+ return Task.CompletedTask;
+ }
+
+ public Task ReactionRemoved(Cacheable cacheable, ISocketMessageChannel socketMessageChannel, SocketReaction reaction)
+ {
+ if (reaction.User.Value.IsBot) return Task.CompletedTask;
+ if (!_reactionListener.IsListener(reaction.MessageId)) return Task.CompletedTask;
+ _reactionListener.RemoveRole(socketMessageChannel, reaction);
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Geekbot.net/Lib/Audio/AudioUtils.cs b/Geekbot.net/Lib/Audio/AudioUtils.cs
new file mode 100644
index 0000000..024019d
--- /dev/null
+++ b/Geekbot.net/Lib/Audio/AudioUtils.cs
@@ -0,0 +1,96 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Net;
+using Discord.Audio;
+
+namespace Geekbot.net.Lib.Audio
+{
+ public class AudioUtils : IAudioUtils
+ {
+ private string _tempFolderPath;
+ private Dictionary _audioClients;
+
+ public AudioUtils()
+ {
+ _audioClients = new Dictionary();
+ _tempFolderPath = Path.GetFullPath("./tmp/");
+ if (Directory.Exists(_tempFolderPath))
+ {
+ Directory.Delete(_tempFolderPath, true);
+ }
+ Directory.CreateDirectory(_tempFolderPath);
+ }
+
+ public IAudioClient GetAudioClient(ulong guildId)
+ {
+ return _audioClients[guildId];
+ }
+
+ public void StoreAudioClient(ulong guildId, IAudioClient client)
+ {
+ _audioClients[guildId] = client;
+ }
+
+ public Process CreateStreamFromFile(string path)
+ {
+ var ffmpeg = new ProcessStartInfo
+ {
+ FileName = "ffmpeg",
+ Arguments = $"-i {path} -ac 2 -f s16le -ar 48000 pipe:1",
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ };
+ return Process.Start(ffmpeg);
+ }
+
+ public Process CreateStreamFromYoutube(string url, ulong guildId)
+ {
+ var ytdlMediaUrl = GetYoutubeMediaUrl(url);
+ DownloadMediaUrl(ytdlMediaUrl, guildId);
+ return CreateStreamFromFile($"{_tempFolderPath}{guildId}");
+ }
+
+ public void Cleanup(ulong guildId)
+ {
+ File.Delete($"{_tempFolderPath}{guildId}");
+ }
+
+ private string GetYoutubeMediaUrl(string url)
+ {
+ var ytdl = new ProcessStartInfo()
+ {
+ FileName = "youtube-dl",
+ Arguments = $"-f bestaudio -g {url}",
+ UseShellExecute = false,
+ RedirectStandardOutput = true
+ };
+ var output = Process.Start(ytdl).StandardOutput.ReadToEnd();
+ if (string.IsNullOrWhiteSpace(output))
+ {
+ throw new Exception("Could not get Youtube Media URL");
+ }
+ return output;
+ }
+
+ private void DownloadMediaUrl(string url, ulong guildId)
+ {
+ using (var web = new WebClient())
+ {
+ web.DownloadFile(url, $"{_tempFolderPath}{guildId}");
+ }
+// var ffmpeg = new ProcessStartInfo
+// {
+// FileName = "ffmpeg",
+// Arguments = $"-i \"{_tempFolderPath}{guildId}\" -c:a mp3 -b:a 256k {_tempFolderPath}{guildId}.mp3",
+// UseShellExecute = false,
+// RedirectStandardOutput = true,
+// };
+// Process.Start(ffmpeg).WaitForExit();
+// File.Delete($"{_tempFolderPath}{guildId}");
+ return;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Lib/Audio/IAudioUtils.cs b/Geekbot.net/Lib/Audio/IAudioUtils.cs
new file mode 100644
index 0000000..4af1293
--- /dev/null
+++ b/Geekbot.net/Lib/Audio/IAudioUtils.cs
@@ -0,0 +1,15 @@
+using System.Diagnostics;
+using Discord.Audio;
+
+namespace Geekbot.net.Lib.Audio
+{
+ public interface IAudioUtils
+ {
+ IAudioClient GetAudioClient(ulong guildId);
+ void StoreAudioClient(ulong guildId, IAudioClient client);
+ Process CreateStreamFromFile(string path);
+ Process CreateStreamFromYoutube(string url, ulong guildId);
+ void Cleanup(ulong guildId);
+
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Lib/Clients/IMalClient.cs b/Geekbot.net/Lib/Clients/IMalClient.cs
new file mode 100644
index 0000000..f59c511
--- /dev/null
+++ b/Geekbot.net/Lib/Clients/IMalClient.cs
@@ -0,0 +1,12 @@
+using System.Threading.Tasks;
+using MyAnimeListSharp.Core;
+
+namespace Geekbot.net.Lib.Clients
+{
+ public interface IMalClient
+ {
+ bool IsLoggedIn();
+ Task GetAnime(string query);
+ Task GetManga(string query);
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Lib/Clients/MalClient.cs b/Geekbot.net/Lib/Clients/MalClient.cs
new file mode 100644
index 0000000..95d7dbd
--- /dev/null
+++ b/Geekbot.net/Lib/Clients/MalClient.cs
@@ -0,0 +1,70 @@
+using System.Threading.Tasks;
+using Geekbot.net.Lib.Logger;
+using MyAnimeListSharp.Auth;
+using MyAnimeListSharp.Core;
+using MyAnimeListSharp.Facade.Async;
+using StackExchange.Redis;
+
+namespace Geekbot.net.Lib.Clients
+{
+ public class MalClient : IMalClient
+ {
+ private readonly IDatabase _redis;
+ private readonly IGeekbotLogger _logger;
+ private ICredentialContext _credentials;
+ private AnimeSearchMethodsAsync _animeSearch;
+ private MangaSearchMethodsAsync _mangaSearch;
+
+ public MalClient(IDatabase redis, IGeekbotLogger logger)
+ {
+ _redis = redis;
+ _logger = logger;
+ ReloadClient();
+ }
+
+ public bool ReloadClient()
+ {
+ var malCredentials = _redis.HashGetAll("malCredentials");
+ if (malCredentials.Length != 0)
+ {
+ _credentials = new CredentialContext();
+ foreach (var c in malCredentials)
+ {
+ switch (c.Name)
+ {
+ case "Username":
+ _credentials.UserName = c.Value;
+ break;
+ case "Password":
+ _credentials.Password = c.Value;
+ break;
+ }
+ }
+ _animeSearch = new AnimeSearchMethodsAsync(_credentials);
+ _mangaSearch = new MangaSearchMethodsAsync(_credentials);
+ _logger.Debug(LogSource.Geekbot, "Logged in to MAL");
+ return true;
+ }
+ _logger.Debug(LogSource.Geekbot, "No MAL Credentials Set!");
+ return false;
+
+ }
+
+ public bool IsLoggedIn()
+ {
+ return _credentials != null;
+ }
+
+ public async Task GetAnime(string query)
+ {
+ var response = await _animeSearch.SearchDeserializedAsync(query);
+ return response.Entries.Count == 0 ? null : response.Entries[0];
+ }
+
+ public async Task GetManga(string query)
+ {
+ var response = await _mangaSearch.SearchDeserializedAsync(query);
+ return response.Entries.Count == 0 ? null : response.Entries[0];
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Lib/CommandCategories.cs b/Geekbot.net/Lib/CommandCategories.cs
new file mode 100644
index 0000000..d67123b
--- /dev/null
+++ b/Geekbot.net/Lib/CommandCategories.cs
@@ -0,0 +1,15 @@
+namespace Geekbot.net.Lib
+{
+ public static class CommandCategories
+ {
+ public const string Randomness = "Randomness";
+ public const string Karma = "Karma";
+ public const string Quotes = "Quotes";
+ public const string Fun = "Fun";
+ public const string Statistics = "Statistics";
+ public const string Helpers = "Helpers";
+ public const string Games = "Games";
+ public const string Admin = "Admin";
+ public const string Uncategorized = "Uncategorized";
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Lib/Constants.cs b/Geekbot.net/Lib/Constants.cs
new file mode 100644
index 0000000..f80f182
--- /dev/null
+++ b/Geekbot.net/Lib/Constants.cs
@@ -0,0 +1,16 @@
+using System.Reflection;
+
+namespace Geekbot.net.Lib
+{
+ public static class Constants
+ {
+ public const string Name = "Geekbot";
+
+ public static string BotVersion()
+ {
+ return typeof(Program).Assembly.GetCustomAttribute().InformationalVersion;
+ }
+
+ public const double ApiVersion = 1;
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Lib/Converters/EmojiConverter.cs b/Geekbot.net/Lib/Converters/EmojiConverter.cs
new file mode 100644
index 0000000..06f1aa8
--- /dev/null
+++ b/Geekbot.net/Lib/Converters/EmojiConverter.cs
@@ -0,0 +1,93 @@
+using System.Collections;
+using System.Text;
+
+namespace Geekbot.net.Lib.Converters
+{
+ public class EmojiConverter : IEmojiConverter
+ {
+ public 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())]);
+ }
+ return returnString.ToString();
+ }
+
+ public 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;
+ returnString.Append(emoji);
+ }
+ return returnString.ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Lib/Converters/IEmojiConverter.cs b/Geekbot.net/Lib/Converters/IEmojiConverter.cs
new file mode 100644
index 0000000..b0f666f
--- /dev/null
+++ b/Geekbot.net/Lib/Converters/IEmojiConverter.cs
@@ -0,0 +1,8 @@
+namespace Geekbot.net.Lib.Converters
+{
+ public interface IEmojiConverter
+ {
+ string NumberToEmoji(int number);
+ string TextToEmoji(string text);
+ }
+}
\ No newline at end of file
diff --git a/src/Core/Converters/IMtgManaConverter.cs b/Geekbot.net/Lib/Converters/IMtgManaConverter.cs
similarity index 67%
rename from src/Core/Converters/IMtgManaConverter.cs
rename to Geekbot.net/Lib/Converters/IMtgManaConverter.cs
index 0dd3034..d558f09 100644
--- a/src/Core/Converters/IMtgManaConverter.cs
+++ b/Geekbot.net/Lib/Converters/IMtgManaConverter.cs
@@ -1,4 +1,4 @@
-namespace Geekbot.Core.Converters
+namespace Geekbot.net.Lib.Converters
{
public interface IMtgManaConverter
{
diff --git a/Geekbot.net/Lib/Converters/MtgManaConverter.cs b/Geekbot.net/Lib/Converters/MtgManaConverter.cs
new file mode 100644
index 0000000..c29e1e0
--- /dev/null
+++ b/Geekbot.net/Lib/Converters/MtgManaConverter.cs
@@ -0,0 +1,33 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Text.RegularExpressions;
+using Utf8Json;
+
+namespace Geekbot.net.Lib.Converters
+{
+ public class MtgManaConverter : IMtgManaConverter
+ {
+ private Dictionary _manaDict;
+
+ public MtgManaConverter()
+ {
+ // these emotes can be found at https://discord.gg/bz8HyA7
+ var mtgEmojis = File.ReadAllText(Path.GetFullPath("./Lib/Converters/MtgManaEmojis.json"));
+ _manaDict = JsonSerializer.Deserialize>(mtgEmojis);
+ }
+
+ public string ConvertMana(string mana)
+ {
+ var rgx = Regex.Matches(mana, @"(\{(.*?)\})");
+ foreach (Match manaTypes in rgx)
+ {
+ var m = _manaDict.GetValueOrDefault(manaTypes.Value);
+ if (!string.IsNullOrEmpty(m))
+ {
+ mana = mana.Replace(manaTypes.Value, m);
+ }
+ }
+ return mana;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Lib/Converters/MtgManaEmojis.json b/Geekbot.net/Lib/Converters/MtgManaEmojis.json
new file mode 100644
index 0000000..8ebe75b
--- /dev/null
+++ b/Geekbot.net/Lib/Converters/MtgManaEmojis.json
@@ -0,0 +1,50 @@
+{
+ "{0}": "<:mtg_0:415216130043412482>",
+ "{1}": "<:mtg_1:415216130253389835>",
+ "{2}": "<:mtg_2:415216130031091713>",
+ "{3}": "<:mtg_3:415216130467037194>",
+ "{4}": "<:mtg_4:415216130026635295>",
+ "{5}": "<:mtg_5:415216130492203008>",
+ "{6}": "<:mtg_6:415216130458779658>",
+ "{7}": "<:mtg_7:415216130190475265>",
+ "{8}": "<:mtg_8:415216130517630986>",
+ "{9}": "<:mtg_9:415216130500722689>",
+ "{10": "<:mtg_10:415216130450391051>",
+ "{11}": "<:mtg_11:415216130811101185>",
+ "{12}": "<:mtg_12:415216130525888532>",
+ "{13}": "<:mtg_13:415216130517631000>",
+ "{14}": "<:mtg_14:415216130165178370>",
+ "{15}": "<:mtg_15:415216130576089108>",
+ "{16}": "<:mtg_16:415216130358247425>",
+ "{17}": "<:mtg_17:415216130601517056>",
+ "{18}": "<:mtg_18:415216130462842891>",
+ "{19}": "<:mtg_19:415216130614099988>",
+ "{20}": "<:mtg_20:415216130656043038>",
+ "{W}": "<:mtg_white:415216131515744256>",
+ "{U}": "<:mtg_blue:415216130521694209>",
+ "{B}": "<:mtg_black:415216130873884683>",
+ "{R}": "<:mtg_red:415216131322806272>",
+ "{G}": "<:mtg_green:415216131180331009>",
+ "{S}": "<:mtg_s:415216131293446144>",
+ "{T}": "<:mtg_tap:415258392727257088>",
+ "{C}": "<:mtg_colorless:415216130706374666>",
+ "{2/W}": "<:mtg_2w:415216130446065664>",
+ "{2/U}": "<:mtg_2u:415216130429550592>",
+ "{2/B}": "<:mtg_2b:415216130160984065>",
+ "{2/R}": "<:mtg_2r:415216130454716436>",
+ "{2/G}": "<:mtg_2g:415216130420899840>",
+ "{W/U}": "<:mtg_wu:415216130970484736>",
+ "{W/B}": "<:mtg_wb:415216131222011914>",
+ "{U/R}": "<:mtg_ur:415216130962096128>",
+ "{U/B}": "<:mtg_ub:415216130865758218>",
+ "{R/W}": "<:mtg_rw:415216130878210057>",
+ "{G/W}": "<:mtg_gw:415216130567962646>",
+ "{G/U}": "<:mtg_gu:415216130739666945>",
+ "{B/R}": "<:mtg_br:415216130580283394>",
+ "{B/G}": "<:mtg_bg:415216130781609994>",
+ "{U/P}": "<:mtg_up:415216130861432842>",
+ "{R/P}": "<:mtg_rp:415216130597322783>",
+ "{G/P}": "<:mtg_gp:415216130760769546>",
+ "{W/P}": "<:mtg_wp:415216131541041172>",
+ "{B/P}": "<:mtg_bp:415216130664169482>"
+}
\ No newline at end of file
diff --git a/Geekbot.net/Lib/ErrorHandling/ErrorHandler.cs b/Geekbot.net/Lib/ErrorHandling/ErrorHandler.cs
new file mode 100644
index 0000000..99d1e8b
--- /dev/null
+++ b/Geekbot.net/Lib/ErrorHandling/ErrorHandler.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Net;
+using Discord.Commands;
+using Discord.Net;
+using Geekbot.net.Lib.Localization;
+using Geekbot.net.Lib.Logger;
+using SharpRaven;
+using SharpRaven.Data;
+
+namespace Geekbot.net.Lib.ErrorHandling
+{
+ public class ErrorHandler : IErrorHandler
+ {
+ private readonly IGeekbotLogger _logger;
+ private readonly ITranslationHandler _translation;
+ private readonly IRavenClient _raven;
+ private readonly bool _errorsInChat;
+
+ public ErrorHandler(IGeekbotLogger logger, ITranslationHandler translation, bool errorsInChat)
+ {
+ _logger = logger;
+ _translation = translation;
+ _errorsInChat = errorsInChat;
+
+ var sentryDsn = Environment.GetEnvironmentVariable("SENTRY");
+ if (!string.IsNullOrEmpty(sentryDsn))
+ {
+ _raven = new RavenClient(sentryDsn) { Release = Constants.BotVersion() };
+ _logger.Information(LogSource.Geekbot, $"Command Errors will be logged to Sentry: {sentryDsn}");
+ }
+ else
+ {
+ _raven = null;
+ }
+ }
+
+ public void HandleCommandException(Exception e, ICommandContext context, string errorMessage = "def")
+ {
+ try
+ {
+ var errorString = errorMessage == "def" ? _translation.GetString(context.Guild.Id, "errorHandler", "SomethingWentWrong") : errorMessage;
+ var errorObj = SimpleConextConverter.ConvertContext(context);
+ if (e.Message.Contains("50007")) return;
+ if (e.Message.Contains("50013")) return;
+ _logger.Error(LogSource.Geekbot, "An error ocured", e, errorObj);
+ if (!string.IsNullOrEmpty(errorMessage))
+ {
+ if (_errorsInChat)
+ {
+ var resStackTrace = string.IsNullOrEmpty(e.InnerException?.ToString()) ? e.StackTrace : e.InnerException?.ToString();
+ if (!string.IsNullOrEmpty(resStackTrace))
+ {
+ var maxLen = Math.Min(resStackTrace.Length, 1850);
+ context.Channel.SendMessageAsync($"{e.Message}\r\n```\r\n{resStackTrace.Substring(0, maxLen)}\r\n```");
+ }
+ else
+ {
+ context.Channel.SendMessageAsync(e.Message);
+ }
+ }
+ else
+ {
+ context.Channel.SendMessageAsync(errorString);
+ }
+
+ }
+
+ if (_raven == null) 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);
+ }
+ catch (Exception ex)
+ {
+ context.Channel.SendMessageAsync("Something went really really wrong here");
+ _logger.Error(LogSource.Geekbot, "Errorception", ex);
+ }
+ }
+
+ public async void HandleHttpException(HttpException e, ICommandContext context)
+ {
+ var errorStrings = _translation.GetDict(context, "httpErrors");
+ switch(e.HttpCode)
+ {
+ case HttpStatusCode.Forbidden:
+ await context.Channel.SendMessageAsync(errorStrings["403"]);
+ break;
+ }
+ }
+
+
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Lib/ErrorHandling/IErrorHandler.cs b/Geekbot.net/Lib/ErrorHandling/IErrorHandler.cs
new file mode 100644
index 0000000..f2d8dac
--- /dev/null
+++ b/Geekbot.net/Lib/ErrorHandling/IErrorHandler.cs
@@ -0,0 +1,12 @@
+using System;
+using Discord.Commands;
+using Discord.Net;
+
+namespace Geekbot.net.Lib.ErrorHandling
+{
+ public interface IErrorHandler
+ {
+ void HandleCommandException(Exception e, ICommandContext context, string errorMessage = "def");
+ void HandleHttpException(HttpException e, ICommandContext context);
+ }
+}
\ No newline at end of file
diff --git a/src/Core/Extensions/EmbedBuilderExtensions.cs b/Geekbot.net/Lib/Extensions/EmbedBuilderExtensions.cs
similarity index 82%
rename from src/Core/Extensions/EmbedBuilderExtensions.cs
rename to Geekbot.net/Lib/Extensions/EmbedBuilderExtensions.cs
index c546306..2a84622 100644
--- a/src/Core/Extensions/EmbedBuilderExtensions.cs
+++ b/Geekbot.net/Lib/Extensions/EmbedBuilderExtensions.cs
@@ -1,12 +1,12 @@
-using Discord;
-
-namespace Geekbot.Core.Extensions
-{
- public static class EmbedBuilderExtensions
- {
- public static EmbedBuilder AddInlineField(this EmbedBuilder builder, string name, object value)
- {
- return builder.AddField(new EmbedFieldBuilder().WithIsInline(true).WithName(name).WithValue(value));
- }
- }
-}
\ No newline at end of file
+using Discord;
+
+namespace Geekbot.net.Lib.Extensions
+{
+ public static class EmbedBuilderExtensions
+ {
+ public static EmbedBuilder AddInlineField(this EmbedBuilder builder, string name, object value)
+ {
+ return builder.AddField(new EmbedFieldBuilder().WithIsInline(true).WithName(name).WithValue(value));
+ }
+ }
+}
diff --git a/src/Core/GeekbotExitCode.cs b/Geekbot.net/Lib/GeekbotExitCode.cs
similarity index 57%
rename from src/Core/GeekbotExitCode.cs
rename to Geekbot.net/Lib/GeekbotExitCode.cs
index 51006e1..e9e1210 100644
--- a/src/Core/GeekbotExitCode.cs
+++ b/Geekbot.net/Lib/GeekbotExitCode.cs
@@ -1,6 +1,6 @@
-namespace Geekbot.Core
+namespace Geekbot.net.Lib
{
- public enum GeekbotExitCode
+ public enum GeekbotExitCode : int
{
// General
Clean = 0,
@@ -8,11 +8,9 @@
// Geekbot Internals
TranslationsFailed = 201,
- KilledByApiCall = 210,
// Dependent Services
- /* 301 not in use anymore (redis) */
- DatabaseConnectionFailed = 302,
+ RedisConnectionFailed = 301,
// Discord Related
CouldNotLogin = 401
diff --git a/Geekbot.net/Lib/Levels/ILevelCalc.cs b/Geekbot.net/Lib/Levels/ILevelCalc.cs
new file mode 100644
index 0000000..6353132
--- /dev/null
+++ b/Geekbot.net/Lib/Levels/ILevelCalc.cs
@@ -0,0 +1,7 @@
+namespace Geekbot.net.Lib.Levels
+{
+ public interface ILevelCalc
+ {
+ int GetLevel(int experience);
+ }
+}
\ No newline at end of file
diff --git a/src/Core/Levels/LevelCalc.cs b/Geekbot.net/Lib/Levels/LevelCalc.cs
similarity index 60%
rename from src/Core/Levels/LevelCalc.cs
rename to Geekbot.net/Lib/Levels/LevelCalc.cs
index 8203f97..9ecf803 100644
--- a/src/Core/Levels/LevelCalc.cs
+++ b/Geekbot.net/Lib/Levels/LevelCalc.cs
@@ -1,12 +1,11 @@
using System;
using System.Collections.Generic;
-using System.Linq;
-namespace Geekbot.Core.Levels
+namespace Geekbot.net.Lib.Levels
{
public class LevelCalc : ILevelCalc
{
- private readonly int[] _levels;
+ private int[] _levels;
public LevelCalc()
{
@@ -20,9 +19,15 @@ namespace Geekbot.Core.Levels
_levels = levels.ToArray();
}
- public int GetLevel(int? messages)
+ public int GetLevel(int messages)
{
- return 1 + _levels.TakeWhile(level => !(level > messages)).Count();
+ var returnVal = 1;
+ foreach (var level in _levels)
+ {
+ if (level > messages) break;
+ returnVal++;
+ }
+ return returnVal;
}
}
}
\ No newline at end of file
diff --git a/Geekbot.net/Lib/Localization/ITranslationHandler.cs b/Geekbot.net/Lib/Localization/ITranslationHandler.cs
new file mode 100644
index 0000000..3cd9549
--- /dev/null
+++ b/Geekbot.net/Lib/Localization/ITranslationHandler.cs
@@ -0,0 +1,14 @@
+using System.Collections.Generic;
+using Discord.Commands;
+
+namespace Geekbot.net.Lib.Localization
+{
+ public interface ITranslationHandler
+ {
+ string GetString(ulong guildId, string command, string stringName);
+ Dictionary GetDict(ICommandContext context);
+ Dictionary GetDict(ICommandContext context, string command);
+ bool SetLanguage(ulong guildId, string language);
+ List GetSupportedLanguages();
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Lib/Localization/TranslationHandler.cs b/Geekbot.net/Lib/Localization/TranslationHandler.cs
new file mode 100644
index 0000000..550759d
--- /dev/null
+++ b/Geekbot.net/Lib/Localization/TranslationHandler.cs
@@ -0,0 +1,156 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Discord.Commands;
+using Discord.WebSocket;
+using Geekbot.net.Lib.Logger;
+using StackExchange.Redis;
+using Utf8Json;
+
+namespace Geekbot.net.Lib.Localization
+{
+ public class TranslationHandler : ITranslationHandler
+ {
+ private readonly IGeekbotLogger _logger;
+ private readonly IDatabase _redis;
+ private Dictionary>> _translations;
+ private Dictionary _serverLanguages;
+ private List _supportedLanguages;
+
+ public TranslationHandler(IReadOnlyCollection clientGuilds, IDatabase redis, IGeekbotLogger logger)
+ {
+ _logger = logger;
+ _redis = redis;
+ _logger.Information(LogSource.Geekbot, "Loading Translations");
+ LoadTranslations();
+ LoadServerLanguages(clientGuilds);
+ }
+
+ private void LoadTranslations()
+ {
+ try
+ {
+ var translationFile = File.ReadAllText(Path.GetFullPath("./Lib/Localization/Translations.json"));
+ var rawTranslations = JsonSerializer.Deserialize>>>(translationFile);
+ var sortedPerLanguage = new Dictionary>>();
+ foreach (var command in rawTranslations)
+ {
+ foreach (var str in command.Value)
+ {
+ foreach (var lang in str.Value)
+ {
+ if (!sortedPerLanguage.ContainsKey(lang.Key))
+ {
+ var commandDict = new Dictionary>();
+ var strDict = new Dictionary();
+ strDict.Add(str.Key, lang.Value);
+ commandDict.Add(command.Key, strDict);
+ sortedPerLanguage.Add(lang.Key, commandDict);
+ }
+ if (!sortedPerLanguage[lang.Key].ContainsKey(command.Key))
+ {
+ var strDict = new Dictionary();
+ strDict.Add(str.Key, lang.Value);
+ sortedPerLanguage[lang.Key].Add(command.Key, strDict);
+ }
+ if (!sortedPerLanguage[lang.Key][command.Key].ContainsKey(str.Key))
+ {
+ sortedPerLanguage[lang.Key][command.Key].Add(str.Key, lang.Value);
+ }
+ }
+ }
+ }
+ _translations = sortedPerLanguage;
+
+ _supportedLanguages = new List();
+ foreach (var lang in sortedPerLanguage)
+ {
+ _supportedLanguages.Add(lang.Key);
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.Error(LogSource.Geekbot, "Failed to load Translations", e);
+ Environment.Exit(GeekbotExitCode.TranslationsFailed.GetHashCode());
+ }
+ }
+
+ private void LoadServerLanguages(IReadOnlyCollection clientGuilds)
+ {
+ _serverLanguages = new Dictionary();
+ foreach (var guild in clientGuilds)
+ {
+ var language = _redis.HashGet($"{guild.Id}:Settings", "Language");
+ if (string.IsNullOrEmpty(language) || !_supportedLanguages.Contains(language))
+ {
+ _serverLanguages[guild.Id] = "EN";
+ }
+ else
+ {
+ _serverLanguages[guild.Id] = language.ToString();
+ }
+ }
+ }
+
+ public string GetString(ulong guildId, string command, string stringName)
+ {
+ var translation = _translations[_serverLanguages[guildId]][command][stringName];
+ if (!string.IsNullOrWhiteSpace(translation)) return translation;
+ translation = _translations[command][stringName]["EN"];
+ if (string.IsNullOrWhiteSpace(translation))
+ {
+ _logger.Warning(LogSource.Geekbot, $"No translation found for {command} - {stringName}");
+ }
+ return translation;
+ }
+
+ public Dictionary GetDict(ICommandContext context)
+ {
+ try
+ {
+ var command = context.Message.Content.Split(' ').First().TrimStart('!').ToLower();
+ return _translations[_serverLanguages[context.Guild.Id]][command];
+ }
+ catch (Exception e)
+ {
+ _logger.Error(LogSource.Geekbot, "No translations for command found", e);
+ return new Dictionary();
+ }
+ }
+
+ public Dictionary GetDict(ICommandContext context, string command)
+ {
+ try
+ {
+ return _translations[_serverLanguages[context.Guild.Id]][command];
+ }
+ catch (Exception e)
+ {
+ _logger.Error(LogSource.Geekbot, "No translations for command found", e);
+ return new Dictionary();
+ }
+ }
+
+ public bool SetLanguage(ulong guildId, string language)
+ {
+ try
+ {
+ if (!_supportedLanguages.Contains(language)) return false;
+ _redis.HashSet($"{guildId}:Settings", new[]{ new HashEntry("Language", language) });
+ _serverLanguages[guildId] = language;
+ return true;
+ }
+ catch (Exception e)
+ {
+ _logger.Error(LogSource.Geekbot, "Error while changing language", e);
+ return false;
+ }
+ }
+
+ public List GetSupportedLanguages()
+ {
+ return _supportedLanguages;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Lib/Localization/Translations.json b/Geekbot.net/Lib/Localization/Translations.json
new file mode 100644
index 0000000..fab8b94
--- /dev/null
+++ b/Geekbot.net/Lib/Localization/Translations.json
@@ -0,0 +1,100 @@
+{
+ "admin": {
+ "NewLanguageSet": {
+ "EN": "I will reply in english from now on",
+ "CHDE": "I werd ab jetzt uf schwiizerdüütsch antworte, äuuä"
+ },
+ "GetLanguage": {
+ "EN": "I'm talking english",
+ "CHDE": "I red schwiizerdüütsch"
+ }
+ },
+ "errorHandler": {
+ "SomethingWentWrong": {
+ "EN": "Something went wrong :confused:",
+ "CHDE": "Öppis isch schief gange :confused:"
+ }
+ },
+ "httpErrors": {
+ "403": {
+ "EN": "Seems like i don't have enough permission to that :confused:",
+ "CHDE": "Gseht danach us das ich nid gnueg recht han zum das mache :confused:"
+ }
+ },
+ "choose": {
+ "Choice": {
+ "EN": "I Choose **{0}**",
+ "CHDE": "I nimme **{0}**"
+ }
+ },
+ "good": {
+ "CannotChangeOwn": {
+ "EN": "Sorry {0}, but you can't give yourself karma",
+ "CHDE": "Sorry {0}, aber du chasch dr selber kei karma geh"
+ },
+ "WaitUntill": {
+ "EN": "Sorry {0}, but you have to wait {1} before you can give karma again...",
+ "CHDE": "Sorry {0}, aber du musch no {1} warte bisch d wieder karma chasch geh..."
+ },
+ "Increased": {
+ "EN": "Karma gained",
+ "CHDE": "Karma becho"
+ },
+ "By": {
+ "EN": "By",
+ "CHDE": "Vo"
+ },
+ "Amount": {
+ "EN": "Amount",
+ "CHDE": "Mengi"
+ },
+ "Current": {
+ "EN": "Current",
+ "CHDE": "Jetzt"
+ }
+ },
+ "bad": {
+ "CannotChangeOwn": {
+ "EN": "Sorry {0}, but you can't lower your own karma",
+ "CHDE": "Sorry {0}, aber du chasch dr din eigete karma nid weg neh"
+ },
+ "WaitUntill": {
+ "EN": "Sorry {0}, but you have to wait {1} before you can lower karma again...",
+ "CHDE": "Sorry {0}, aber du musch no {1} warte bisch d wieder karma chasch senke..."
+ },
+ "Decreased": {
+ "EN": "Karma lowered",
+ "CHDE": "Karma gsenkt"
+ },
+ "By": {
+ "EN": "By",
+ "CHDE": "Vo"
+ },
+ "Amount": {
+ "EN": "Amount",
+ "CHDE": "Mengi"
+ },
+ "Current": {
+ "EN": "Current",
+ "CHDE": "Jetzt"
+ }
+ },
+ "roll": {
+ "Rolled": {
+ "EN": "{0}, you rolled {1}, your guess was {2}",
+ "CHDE": "{0}, du hesch {1} grollt und hesch {2} grate"
+ },
+ "Gratz": {
+ "EN": "Congratulations {0}, your guess was correct!",
+ "CHDE": "Gratuliere {0}, du hesch richtig grate!"
+ },
+ "RolledNoGuess": {
+ "EN": "{0}, you rolled {1}",
+ "CHDE": "{0}, du hesch {1} grollt"
+ },
+ "NoPrevGuess": {
+ "EN": ":red_circle: {0}, you can't guess the same number again within 24 hours",
+ "CHDE": ":red_circle: {0}, du chasch nid nomol es gliche rate innerhalb vo 24 stund"
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Core/Logger/Adapters/DiscordLogger.cs b/Geekbot.net/Lib/Logger/DiscordLogger.cs
similarity index 92%
rename from src/Core/Logger/Adapters/DiscordLogger.cs
rename to Geekbot.net/Lib/Logger/DiscordLogger.cs
index e9bb02e..2adcd73 100644
--- a/src/Core/Logger/Adapters/DiscordLogger.cs
+++ b/Geekbot.net/Lib/Logger/DiscordLogger.cs
@@ -1,8 +1,9 @@
using System;
using System.Threading.Tasks;
using Discord;
+using Geekbot.net.Commands.Randomness.Cat;
-namespace Geekbot.Core.Logger.Adapters
+namespace Geekbot.net.Lib.Logger
{
public class DiscordLogger : IDiscordLogger
{
diff --git a/Geekbot.net/Lib/Logger/GeekbotLogger.cs b/Geekbot.net/Lib/Logger/GeekbotLogger.cs
new file mode 100644
index 0000000..b2bb677
--- /dev/null
+++ b/Geekbot.net/Lib/Logger/GeekbotLogger.cs
@@ -0,0 +1,74 @@
+using System;
+using Newtonsoft.Json;
+
+namespace Geekbot.net.Lib.Logger
+{
+ public class GeekbotLogger : IGeekbotLogger
+ {
+ private readonly bool _logAsJson;
+ private readonly NLog.Logger _logger;
+ private readonly JsonSerializerSettings _serializerSettings;
+
+ public GeekbotLogger(RunParameters runParameters, bool sumologicActive)
+ {
+ _logAsJson = sumologicActive || runParameters.LogJson;
+ _logger = LoggerFactory.CreateNLog(runParameters, sumologicActive);
+ _serializerSettings = new JsonSerializerSettings
+ {
+ ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
+ NullValueHandling = NullValueHandling.Include
+ };
+ Information(LogSource.Geekbot, "Using GeekbotLogger");
+ }
+
+ public void Trace(LogSource source, string message, object extra = null)
+ {
+ _logger.Trace(CreateLogString("Trace", source, message, null, extra));
+ }
+
+ public void Debug(LogSource source, string message, object extra = null)
+ {
+ if (_logAsJson) _logger.Info(CreateLogString("Debug", source, message, null, extra));
+ else _logger.Debug(CreateLogString("Debug", source, message, null, extra));
+ }
+
+ public void Information(LogSource source, string message, object extra = null)
+ {
+ _logger.Info(CreateLogString("Information", source, message, null, extra));
+ }
+
+ public void Warning(LogSource source, string message, Exception stackTrace = null, object extra = null)
+ {
+ if (_logAsJson) _logger.Info(CreateLogString("Warning", source, message, stackTrace, extra));
+ else _logger.Warn(CreateLogString("Warning", source, message, stackTrace, extra));
+ }
+
+ public void Error(LogSource source, string message, Exception stackTrace, object extra = null)
+ {
+ if (_logAsJson) _logger.Info(CreateLogString("Error", source, message, stackTrace, extra));
+ else _logger.Error(stackTrace, CreateLogString("Error", source, message, stackTrace, extra));
+ }
+
+ private string CreateLogString(string type, LogSource source, string message, Exception stackTrace = null, object extra = null)
+ {
+ if (_logAsJson)
+ {
+ var logObject = new GeekbotLoggerObject
+ {
+ Timestamp = DateTime.Now,
+ Type = type,
+ Source = source,
+ Message = message,
+ StackTrace = stackTrace,
+ Extra = extra
+ };
+ return JsonConvert.SerializeObject(logObject, Formatting.None, _serializerSettings);
+ }
+
+ if (source != LogSource.Message) return $"[{source}] - {message}";
+
+ var m = (MessageDto) extra;
+ return $"[{source}] - [{m?.Guild.Name} - {m?.Channel.Name}] {m?.User.Name}: {m?.Message.Content}";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Core/Logger/IDiscordLogger.cs b/Geekbot.net/Lib/Logger/IDiscordLogger.cs
similarity index 76%
rename from src/Core/Logger/IDiscordLogger.cs
rename to Geekbot.net/Lib/Logger/IDiscordLogger.cs
index c127e0c..fdc8c7f 100644
--- a/src/Core/Logger/IDiscordLogger.cs
+++ b/Geekbot.net/Lib/Logger/IDiscordLogger.cs
@@ -1,7 +1,7 @@
using System.Threading.Tasks;
using Discord;
-namespace Geekbot.Core.Logger
+namespace Geekbot.net.Lib.Logger
{
public interface IDiscordLogger
{
diff --git a/src/Core/Logger/IGeekbotLogger.cs b/Geekbot.net/Lib/Logger/IGeekbotLogger.cs
similarity index 82%
rename from src/Core/Logger/IGeekbotLogger.cs
rename to Geekbot.net/Lib/Logger/IGeekbotLogger.cs
index 1363629..1f76cb0 100644
--- a/src/Core/Logger/IGeekbotLogger.cs
+++ b/Geekbot.net/Lib/Logger/IGeekbotLogger.cs
@@ -1,6 +1,6 @@
using System;
-namespace Geekbot.Core.Logger
+namespace Geekbot.net.Lib.Logger
{
public interface IGeekbotLogger
{
@@ -9,7 +9,5 @@ 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);
- NLog.Logger GetNLogger();
- bool LogAsJson();
}
}
\ No newline at end of file
diff --git a/Geekbot.net/Lib/Logger/LogDto.cs b/Geekbot.net/Lib/Logger/LogDto.cs
new file mode 100644
index 0000000..8ccfcdf
--- /dev/null
+++ b/Geekbot.net/Lib/Logger/LogDto.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Geekbot.net.Lib.Logger
+{
+ 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
diff --git a/Geekbot.net/Lib/Logger/LogSource.cs b/Geekbot.net/Lib/Logger/LogSource.cs
new file mode 100644
index 0000000..d9a8629
--- /dev/null
+++ b/Geekbot.net/Lib/Logger/LogSource.cs
@@ -0,0 +1,20 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+
+namespace Geekbot.net.Lib.Logger
+{
+ [JsonConverter(typeof(StringEnumConverter))]
+ public enum LogSource
+ {
+ Geekbot,
+ Rest,
+ Gateway,
+ Discord,
+ Redis,
+ Message,
+ UserRepository,
+ Command,
+ Api,
+ Other
+ }
+}
\ No newline at end of file
diff --git a/src/Core/Logger/LoggerFactory.cs b/Geekbot.net/Lib/Logger/LoggerFactory.cs
similarity index 51%
rename from src/Core/Logger/LoggerFactory.cs
rename to Geekbot.net/Lib/Logger/LoggerFactory.cs
index bf3d926..4095d00 100644
--- a/src/Core/Logger/LoggerFactory.cs
+++ b/Geekbot.net/Lib/Logger/LoggerFactory.cs
@@ -5,59 +5,62 @@ using NLog.Config;
using NLog.Targets;
using SumoLogic.Logging.NLog;
-namespace Geekbot.Core.Logger
+namespace Geekbot.net.Lib.Logger
{
public class LoggerFactory
{
- public static NLog.Logger CreateNLog(RunParameters runParameters)
+ public static NLog.Logger CreateNLog(RunParameters runParameters, bool sumologicActive)
{
var config = new LoggingConfiguration();
- var minLevel = runParameters.Verbose ? LogLevel.Trace : LogLevel.Info;
- if (!string.IsNullOrEmpty(runParameters.SumologicEndpoint))
+ if (sumologicActive)
{
Console.WriteLine("Logging Geekbot Logs to Sumologic");
config.LoggingRules.Add(
- new LoggingRule("*", minLevel, LogLevel.Fatal,
+ new LoggingRule("*", LogLevel.Debug, LogLevel.Fatal,
new SumoLogicTarget()
{
- Url = runParameters.SumologicEndpoint,
+ Url = Environment.GetEnvironmentVariable("GEEKBOT_SUMO"),
SourceName = "GeekbotLogger",
Layout = "${message}",
UseConsoleLog = false,
OptimizeBufferReuse = true,
- Name = "Geekbot"
+ Name = "Geekbot",
+ AppendException = false
})
- );
- }
- else if (runParameters.LogJson)
- {
- config.LoggingRules.Add(
- new LoggingRule("*", minLevel, LogLevel.Fatal,
- new ConsoleTarget
- {
- Name = "Console",
- Encoding = Encoding.UTF8,
- Layout = "${message}"
- }
- )
- );
+ );
}
else
{
+ var minLevel = runParameters.Verbose ? LogLevel.Trace : LogLevel.Info;
config.LoggingRules.Add(
- new LoggingRule("*", minLevel, LogLevel.Fatal,
+ new LoggingRule("*", LogLevel.Info, LogLevel.Fatal,
new ColoredConsoleTarget
{
Name = "Console",
Encoding = Encoding.UTF8,
Layout = "[${longdate} ${level:format=FirstCharacter}] ${message} ${exception:format=toString}"
- }
- )
- );
+ })
+ );
+
+ config.LoggingRules.Add(
+ new LoggingRule("*", minLevel, LogLevel.Fatal,
+ new FileTarget
+ {
+ Name = "File",
+ Layout = "[${longdate} ${level}] ${message}",
+ Encoding = Encoding.UTF8,
+ LineEnding = LineEndingMode.Default,
+ MaxArchiveFiles = 30,
+ ArchiveNumbering = ArchiveNumberingMode.Date,
+ ArchiveEvery = FileArchivePeriod.Day,
+ ArchiveFileName = "./Logs/Archive/{#####}.log",
+ FileName = "./Logs/Geekbot.log"
+ })
+ );
}
-
- var loggerConfig = new LogFactory {Configuration = config};
+
+ var loggerConfig = new LogFactory { Configuration = config };
return loggerConfig.GetCurrentClassLogger();
}
}
diff --git a/src/Core/Logger/MessageDto.cs b/Geekbot.net/Lib/Logger/MessageDto.cs
similarity index 92%
rename from src/Core/Logger/MessageDto.cs
rename to Geekbot.net/Lib/Logger/MessageDto.cs
index 039024e..011acf3 100644
--- a/src/Core/Logger/MessageDto.cs
+++ b/Geekbot.net/Lib/Logger/MessageDto.cs
@@ -1,4 +1,4 @@
-namespace Geekbot.Core.Logger
+namespace Geekbot.net.Lib.Logger
{
public class MessageDto
{
diff --git a/src/Core/Logger/SimpleConextConverter.cs b/Geekbot.net/Lib/Logger/SimpleConextConverter.cs
similarity index 74%
rename from src/Core/Logger/SimpleConextConverter.cs
rename to Geekbot.net/Lib/Logger/SimpleConextConverter.cs
index 23cee40..0845cc1 100644
--- a/src/Core/Logger/SimpleConextConverter.cs
+++ b/Geekbot.net/Lib/Logger/SimpleConextConverter.cs
@@ -1,7 +1,7 @@
using Discord.Commands;
using Discord.WebSocket;
-namespace Geekbot.Core.Logger
+namespace Geekbot.net.Lib.Logger
{
public class SimpleConextConverter
{
@@ -11,7 +11,7 @@ namespace Geekbot.Core.Logger
{
Message = new MessageDto.MessageContent
{
- Content = context.Message.Content, // Only when an error occurs, including for diagnostic reason
+ Content = context.Message.Content,
Id = context.Message.Id.ToString(),
Attachments = context.Message.Attachments.Count,
ChannelMentions = context.Message.MentionedChannelIds.Count,
@@ -25,23 +25,24 @@ namespace Geekbot.Core.Logger
},
Guild = new MessageDto.IdAndName
{
- Id = context.Guild?.Id.ToString(),
- Name = context.Guild?.Name
+ Id = context.Guild.Id.ToString(),
+ Name = context.Guild.Name
},
Channel = new MessageDto.IdAndName
{
- Id = context.Channel?.Id.ToString() ?? context.User.Id.ToString(),
- Name = context.Channel?.Name ?? "DM-Channel"
+ Id = context.Channel.Id.ToString(),
+ Name = context.Channel.Name
}
};
}
- public static MessageDto ConvertSocketMessage(SocketMessage message, bool isPrivate = false)
+ public static MessageDto ConvertSocketMessage(SocketMessage message)
{
- var channel = isPrivate ? null : (SocketGuildChannel) message.Channel;
+ var channel = (SocketGuildChannel) message.Channel;
return new MessageDto
{
Message = new MessageDto.MessageContent
{
+ Content = message.Content,
Id = message.Id.ToString(),
Attachments = message.Attachments.Count,
ChannelMentions = message.MentionedChannels.Count,
@@ -60,8 +61,8 @@ namespace Geekbot.Core.Logger
},
Channel = new MessageDto.IdAndName
{
- Id = channel?.Id.ToString() ?? message.Author.Id.ToString(),
- Name = channel?.Name ?? "DM-Channel"
+ Id = channel?.Id.ToString(),
+ Name = channel?.Name
}
};
}
diff --git a/src/Core/Media/FortunesProvider.cs b/Geekbot.net/Lib/Media/FortunesProvider.cs
similarity index 85%
rename from src/Core/Media/FortunesProvider.cs
rename to Geekbot.net/Lib/Media/FortunesProvider.cs
index 1b0d87a..56b5ed6 100644
--- a/src/Core/Media/FortunesProvider.cs
+++ b/Geekbot.net/Lib/Media/FortunesProvider.cs
@@ -1,10 +1,10 @@
using System;
using System.IO;
-using Geekbot.Core.Logger;
+using Geekbot.net.Lib.Logger;
-namespace Geekbot.Core.Media
+namespace Geekbot.net.Lib.Media
{
- public class FortunesProvider : IFortunesProvider
+ internal class FortunesProvider : IFortunesProvider
{
private readonly string[] _fortuneArray;
private readonly int _totalFortunes;
diff --git a/src/Core/Media/IFortunesProvider.cs b/Geekbot.net/Lib/Media/IFortunesProvider.cs
similarity index 68%
rename from src/Core/Media/IFortunesProvider.cs
rename to Geekbot.net/Lib/Media/IFortunesProvider.cs
index a4ef0d9..c82e45c 100644
--- a/src/Core/Media/IFortunesProvider.cs
+++ b/Geekbot.net/Lib/Media/IFortunesProvider.cs
@@ -1,4 +1,4 @@
-namespace Geekbot.Core.Media
+namespace Geekbot.net.Lib.Media
{
public interface IFortunesProvider
{
diff --git a/Geekbot.net/Lib/Media/IMediaProvider.cs b/Geekbot.net/Lib/Media/IMediaProvider.cs
new file mode 100644
index 0000000..79c6c98
--- /dev/null
+++ b/Geekbot.net/Lib/Media/IMediaProvider.cs
@@ -0,0 +1,14 @@
+namespace Geekbot.net.Lib.Media
+{
+ public interface IMediaProvider
+ {
+ string GetCheckem();
+ string GetPanda();
+ string GetCrossant();
+ string GetSquirrel();
+ string GetPumpkin();
+ string GetTurtle();
+ string GetPinguin();
+ string GetFox();
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Lib/Media/MediaProvider.cs b/Geekbot.net/Lib/Media/MediaProvider.cs
new file mode 100644
index 0000000..7f883ae
--- /dev/null
+++ b/Geekbot.net/Lib/Media/MediaProvider.cs
@@ -0,0 +1,133 @@
+using System;
+using System.IO;
+using Geekbot.net.Lib.Logger;
+
+namespace Geekbot.net.Lib.Media
+{
+ public class MediaProvider : IMediaProvider
+ {
+ private readonly Random _random;
+ private readonly IGeekbotLogger _logger;
+ private string[] _checkemImages;
+ private string[] _pandaImages;
+ private string[] _croissantImages;
+ private string[] _squirrelImages;
+ private string[] _pumpkinImages;
+ private string[] _turtlesImages;
+ private string[] _pinguinImages;
+ private string[] _foxImages;
+
+ public MediaProvider(IGeekbotLogger logger)
+ {
+ _random = new Random();
+ _logger = logger;
+
+ logger.Information(LogSource.Geekbot, "Loading Media Files");
+
+ LoadCheckem();
+ LoadPandas();
+ BakeCroissants();
+ LoadSquirrels();
+ LoadPumpkins();
+ LoadTurtles();
+ LoadPinguins();
+ LoadFoxes();
+ }
+
+ private void LoadCheckem()
+ {
+ var rawLinks = File.ReadAllText(Path.GetFullPath("./Storage/checkEmPics"));
+ _checkemImages = rawLinks.Split("\n");
+ _logger.Trace(LogSource.Geekbot, $"Loaded {_checkemImages.Length} CheckEm Images");
+ }
+
+ private void LoadPandas()
+ {
+ var rawLinks = File.ReadAllText(Path.GetFullPath("./Storage/pandas"));
+ _pandaImages = rawLinks.Split("\n");
+ _logger.Trace(LogSource.Geekbot, $"Loaded {_pandaImages.Length} Panda Images");
+ }
+
+ private void BakeCroissants()
+ {
+ var rawLinks = File.ReadAllText(Path.GetFullPath("./Storage/croissant"));
+ _croissantImages = rawLinks.Split("\n");
+ _logger.Trace(LogSource.Geekbot, $"Loaded {_croissantImages.Length} Croissant Images");
+ }
+
+ private void LoadSquirrels()
+ {
+ var rawLinks = File.ReadAllText(Path.GetFullPath("./Storage/squirrel"));
+ _squirrelImages = rawLinks.Split("\n");
+ _logger.Trace(LogSource.Geekbot, $"Loaded {_squirrelImages.Length} Squirrel Images");
+ }
+
+ private void LoadPumpkins()
+ {
+ var rawLinks = File.ReadAllText(Path.GetFullPath("./Storage/pumpkin"));
+ _pumpkinImages = rawLinks.Split("\n");
+ _logger.Trace(LogSource.Geekbot, $"Loaded {_pumpkinImages.Length} Pumpkin Images");
+ }
+
+ private void LoadTurtles()
+ {
+ var rawLinks = File.ReadAllText(Path.GetFullPath("./Storage/turtles"));
+ _turtlesImages = rawLinks.Split("\n");
+ _logger.Trace(LogSource.Geekbot, $"Loaded {_turtlesImages.Length} Turtle Images");
+ }
+
+ private void LoadPinguins()
+ {
+ var rawLinks = File.ReadAllText(Path.GetFullPath("./Storage/pinguins"));
+ _pinguinImages = rawLinks.Split("\n");
+ _logger.Trace(LogSource.Geekbot, $"Loaded {_pinguinImages.Length} Pinguin Images");
+ }
+
+ private void LoadFoxes()
+ {
+ var rawLinks = File.ReadAllText(Path.GetFullPath("./Storage/foxes"));
+ _foxImages = rawLinks.Split("\n");
+ _logger.Trace(LogSource.Geekbot, $"Loaded {_foxImages.Length} Foxes Images");
+ }
+
+ public string GetCheckem()
+ {
+ return _checkemImages[_random.Next(0, _checkemImages.Length)];
+ }
+
+ public string GetPanda()
+ {
+ return _pandaImages[_random.Next(0, _pandaImages.Length)];
+ }
+
+ public string GetCrossant()
+ {
+ return _croissantImages[_random.Next(0, _croissantImages.Length)];
+ }
+
+ public string GetSquirrel()
+ {
+ return _squirrelImages[_random.Next(0, _squirrelImages.Length)];
+ }
+
+ public string GetPumpkin()
+ {
+ return _pumpkinImages[_random.Next(0, _pumpkinImages.Length)];
+ }
+
+ public string GetTurtle()
+ {
+ return _turtlesImages[_random.Next(0, _turtlesImages.Length)];
+ }
+
+ public string GetPinguin()
+ {
+ return _pinguinImages[_random.Next(0, _pinguinImages.Length)];
+ }
+
+ public string GetFox()
+ {
+ return _foxImages[_random.Next(0, _foxImages.Length)];
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Lib/Polyfills/UserPolyfillDto.cs b/Geekbot.net/Lib/Polyfills/UserPolyfillDto.cs
new file mode 100644
index 0000000..568bdd7
--- /dev/null
+++ b/Geekbot.net/Lib/Polyfills/UserPolyfillDto.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Threading.Tasks;
+using Discord;
+
+namespace Geekbot.net.Lib.Polyfills
+{
+ internal class UserPolyfillDto : IUser
+ {
+ public ulong Id { get; set; }
+ public DateTimeOffset CreatedAt { get; set; }
+ public string Mention { get; set; }
+ public IActivity Activity { get; }
+ public UserStatus Status { get; set; }
+ public string AvatarId { 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 string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128)
+ {
+ return "https://discordapp.com/assets/6debd47ed13483642cf09e832ed0bc1b.png";
+ }
+
+ public Task GetOrCreateDMChannelAsync(RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Lib/ReactionListener/IReactionListener.cs b/Geekbot.net/Lib/ReactionListener/IReactionListener.cs
new file mode 100644
index 0000000..792a516
--- /dev/null
+++ b/Geekbot.net/Lib/ReactionListener/IReactionListener.cs
@@ -0,0 +1,14 @@
+using System.Threading.Tasks;
+using Discord;
+using Discord.WebSocket;
+
+namespace Geekbot.net.Lib.ReactionListener
+{
+ public interface IReactionListener
+ {
+ bool IsListener(ulong id);
+ Task AddRoleToListener(string messageId, IEmote emoji, IRole role);
+ void RemoveRole(ISocketMessageChannel channel, SocketReaction reaction);
+ void GiveRole(ISocketMessageChannel message, SocketReaction reaction);
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Lib/ReactionListener/ReactionListener.cs b/Geekbot.net/Lib/ReactionListener/ReactionListener.cs
new file mode 100644
index 0000000..950edd1
--- /dev/null
+++ b/Geekbot.net/Lib/ReactionListener/ReactionListener.cs
@@ -0,0 +1,90 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Discord;
+using Discord.WebSocket;
+using StackExchange.Redis;
+
+namespace Geekbot.net.Lib.ReactionListener
+{
+ public class ReactionListener : IReactionListener
+ {
+ private readonly IDatabase _database;
+ private Dictionary> _listener;
+
+ public ReactionListener(IDatabase database)
+ {
+ _database = database;
+ LoadListeners();
+ }
+
+ private Task LoadListeners()
+ {
+ var ids = _database.SetMembers("MessageIds");
+ _listener = new Dictionary>();
+ foreach (var id in ids)
+ {
+ var reactions = _database.HashGetAll($"Messages:{id}");
+ var messageId = id;
+ var emojiDict = new Dictionary();
+ foreach (var r in reactions)
+ {
+ IEmote emote;
+ if (!r.Name.ToString().StartsWith('<'))
+ {
+ var emo = new Emoji(r.Name);
+ emote = emo;
+ }
+ else
+ {
+ emote = Emote.Parse(r.Name);
+ }
+ emojiDict.Add(emote, ulong.Parse(r.Value));
+ }
+ _listener.Add(messageId, emojiDict);
+ }
+
+ return Task.CompletedTask;
+ }
+
+ public bool IsListener(ulong id)
+ {
+ return _listener.ContainsKey(id.ToString());
+ }
+
+ public Task AddRoleToListener(string messageId, IEmote emoji, IRole role)
+ {
+ if (_database.SetMembers("MessageIds").All(e => e.ToString() != messageId))
+ {
+ _database.SetAdd("MessageIds", messageId);
+ }
+ _database.HashSet($"Messages:{messageId}", new[] {new HashEntry(emoji.ToString(), role.Id.ToString())});
+ _database.SetAdd("MessageIds", messageId);
+ if (_listener.ContainsKey(messageId))
+ {
+ _listener[messageId].Add(emoji, role.Id);
+ return Task.CompletedTask;
+ }
+ var dict = new Dictionary();
+ dict.Add(emoji, role.Id);
+ _listener.Add(messageId, dict);
+ return Task.CompletedTask;
+ }
+
+ public async void RemoveRole(ISocketMessageChannel channel, SocketReaction reaction)
+ {
+ var roleId = _listener[reaction.MessageId.ToString()][reaction.Emote];
+ var guild = (SocketGuildChannel) channel;
+ var role = guild.Guild.GetRole(roleId);
+ await ((IGuildUser) reaction.User.Value).RemoveRoleAsync(role);
+ }
+
+ public async void GiveRole(ISocketMessageChannel channel, SocketReaction reaction)
+ {
+ var roleId = _listener[reaction.MessageId.ToString()][reaction.Emote];
+ var guild = (SocketGuildChannel) channel;
+ var role = guild.Guild.GetRole(roleId);
+ await ((IGuildUser) reaction.User.Value).AddRoleAsync(role);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Lib/RunParameters.cs b/Geekbot.net/Lib/RunParameters.cs
new file mode 100644
index 0000000..35c1a2a
--- /dev/null
+++ b/Geekbot.net/Lib/RunParameters.cs
@@ -0,0 +1,26 @@
+using System;
+using CommandLine;
+
+namespace Geekbot.net.Lib
+{
+ public class RunParameters
+ {
+ [Option('V', "verbose", Default = false, HelpText = "Prints all messages to standard output.")]
+ public bool Verbose { get; set; }
+
+ [Option('r', "reset", Default = false, HelpText = "Resets the bot")]
+ public bool Reset { get; set; }
+
+ [Option('j', "log-json", Default = false, HelpText = "Logs messages as json")]
+ public bool LogJson { get; set; }
+
+ [Option("disable-api", Default = false, HelpText = "Disables the web api")]
+ public bool DisableApi { get; set; }
+
+ [Option('e', "expose-errors", Default = false, HelpText = "Shows internal errors in the chat")]
+ public bool ExposeErrors { get; set; }
+
+ [Option("token", Default = null, HelpText = "Set a new bot token")]
+ public string Token { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Lib/UserRepository/IUserRepository.cs b/Geekbot.net/Lib/UserRepository/IUserRepository.cs
new file mode 100644
index 0000000..9466621
--- /dev/null
+++ b/Geekbot.net/Lib/UserRepository/IUserRepository.cs
@@ -0,0 +1,13 @@
+using System.Threading.Tasks;
+using Discord.WebSocket;
+
+namespace Geekbot.net.Lib.UserRepository
+{
+ public interface IUserRepository
+ {
+ Task Update(SocketUser user);
+ UserRepositoryUser Get(ulong userId);
+ string GetUserSetting(ulong userId, string setting);
+ bool SaveUserSetting(ulong userId, string setting, string value);
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Lib/UserRepository/UserRepository.cs b/Geekbot.net/Lib/UserRepository/UserRepository.cs
new file mode 100644
index 0000000..3937107
--- /dev/null
+++ b/Geekbot.net/Lib/UserRepository/UserRepository.cs
@@ -0,0 +1,125 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Discord.WebSocket;
+using Geekbot.net.Lib.Logger;
+using StackExchange.Redis;
+using Utf8Json;
+
+namespace Geekbot.net.Lib.UserRepository
+{
+ public class UserRepository : IUserRepository
+ {
+ private readonly IDatabase _redis;
+ private readonly IGeekbotLogger _logger;
+ public UserRepository(IDatabase redis, IGeekbotLogger logger)
+ {
+ _redis = redis;
+ _logger = logger;
+ }
+
+ public Task Update(SocketUser user)
+ {
+ try
+ {
+ var savedUser = Get(user.Id);
+ savedUser.Id = user.Id;
+ savedUser.Username = user.Username;
+ savedUser.Discriminator = user.Discriminator;
+ savedUser.AvatarUrl = user.GetAvatarUrl() ?? "0";
+ savedUser.IsBot = user.IsBot;
+ savedUser.Joined = user.CreatedAt;
+ if(savedUser.UsedNames == null) savedUser.UsedNames = new List();
+ if (!savedUser.UsedNames.Contains(user.Username))
+ {
+ savedUser.UsedNames.Add(user.Username);
+ }
+ Store(savedUser);
+
+ _logger.Information(LogSource.UserRepository, "Updated User", savedUser);
+ return Task.FromResult(true);
+ }
+ catch (Exception e)
+ {
+ _logger.Warning(LogSource.UserRepository, $"Failed to update user: {user.Username}#{user.Discriminator} ({user.Id})", e);
+ return Task.FromResult(false);
+ }
+ }
+
+ private void Store(UserRepositoryUser user)
+ {
+ _redis.HashSetAsync($"Users:{user.Id.ToString()}", new[]
+ {
+ new HashEntry("Id", user.Id.ToString()),
+ new HashEntry("Username", user.Username),
+ new HashEntry("Discriminator", user.Discriminator),
+ new HashEntry("AvatarUrl", user.AvatarUrl),
+ new HashEntry("IsBot", user.IsBot),
+ new HashEntry("Joined", user.Joined.ToString()),
+ new HashEntry("UsedNames", JsonSerializer.Serialize(user.UsedNames))
+ });
+ }
+
+ public UserRepositoryUser Get(ulong userId)
+ {
+ try
+ {
+ var user = _redis.HashGetAll($"Users:{userId.ToString()}");
+ for (var i = 1; i < 11; i++)
+ {
+ if (user.Length != 0) break;
+ user = _redis.HashGetAll($"Users:{(userId + (ulong) i).ToString()}");
+
+ }
+ var dto = new UserRepositoryUser();
+ foreach (var a in user.ToDictionary())
+ {
+ switch (a.Key)
+ {
+ case "Id":
+ dto.Id = ulong.Parse(a.Value);
+ break;
+ case "Username":
+ dto.Username = a.Value.ToString();
+ break;
+ case "Discriminator":
+ dto.Discriminator = a.Value.ToString();
+ break;
+ case "AvatarUrl":
+ dto.AvatarUrl = a.Value != "0" ? a.Value.ToString() : null;
+ break;
+ case "IsBot":
+ dto.IsBot = a.Value == 1;
+ break;
+ case "Joined":
+ dto.Joined = DateTimeOffset.Parse(a.Value.ToString());
+ break;
+ case "UsedNames":
+ dto.UsedNames = JsonSerializer.Deserialize>(a.Value.ToString()) ?? new List();
+ break;
+ }
+ }
+ return dto;
+ }
+ catch (Exception e)
+ {
+ _logger.Warning(LogSource.UserRepository, $"Failed to get {userId} from repository", e);
+ return new UserRepositoryUser();
+ }
+ }
+
+ public string GetUserSetting(ulong userId, string setting)
+ {
+ return _redis.HashGet($"Users:{userId}", setting);
+ }
+
+ public bool SaveUserSetting(ulong userId, string setting, string value)
+ {
+ _redis.HashSet($"Users:{userId}", new[]
+ {
+ new HashEntry(setting, value)
+ });
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Lib/UserRepository/UserRepositoryUserDto.cs b/Geekbot.net/Lib/UserRepository/UserRepositoryUserDto.cs
new file mode 100644
index 0000000..4af719b
--- /dev/null
+++ b/Geekbot.net/Lib/UserRepository/UserRepositoryUserDto.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+
+namespace Geekbot.net.Lib.UserRepository
+{
+ public class UserRepositoryUser
+ {
+ public ulong Id { get; set; }
+ public string Username { get; set; }
+ public string Discriminator { get; set; }
+ public string AvatarUrl { get; set; }
+ public bool IsBot { get; set; }
+ public DateTimeOffset Joined { get; set; }
+ public List UsedNames { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Logs/.keep b/Geekbot.net/Logs/.keep
new file mode 100755
index 0000000..e69de29
diff --git a/Geekbot.net/Program.cs b/Geekbot.net/Program.cs
new file mode 100755
index 0000000..3a35d19
--- /dev/null
+++ b/Geekbot.net/Program.cs
@@ -0,0 +1,224 @@
+using System;
+using System.Net;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using CommandLine;
+using Discord;
+using Discord.Commands;
+using Discord.WebSocket;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.Audio;
+using Geekbot.net.Lib.Clients;
+using Geekbot.net.Lib.Converters;
+using Geekbot.net.Lib.ErrorHandling;
+using Geekbot.net.Lib.Levels;
+using Geekbot.net.Lib.Localization;
+using Geekbot.net.Lib.Logger;
+using Geekbot.net.Lib.Media;
+using Geekbot.net.Lib.ReactionListener;
+using Geekbot.net.Lib.UserRepository;
+using Microsoft.Extensions.DependencyInjection;
+using Nancy.Hosting.Self;
+using StackExchange.Redis;
+using WikipediaApi;
+
+namespace Geekbot.net
+{
+ internal class Program
+ {
+ private DiscordSocketClient _client;
+ private CommandService _commands;
+ private IDatabase _redis;
+ private IServiceCollection _services;
+ private IServiceProvider _servicesProvider;
+ private RedisValue _token;
+ private GeekbotLogger _logger;
+ private IUserRepository _userRepository;
+ private bool _firstStart;
+ private RunParameters _runParameters;
+
+ private static void 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("=========================================");
+ Console.WriteLine(logo.ToString());
+ var sumologicActive = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("GEEKBOT_SUMO"));
+ var logger = new GeekbotLogger(runParameters, sumologicActive);
+ logger.Information(LogSource.Geekbot, "Starting...");
+ try
+ {
+ new Program().MainAsync(runParameters, logger).GetAwaiter().GetResult();
+ }
+ catch (Exception e)
+ {
+ logger.Error(LogSource.Geekbot, "RIP", e);
+ }
+ }
+
+ private async Task MainAsync(RunParameters runParameters, GeekbotLogger logger)
+ {
+ _logger = logger;
+ _runParameters = runParameters;
+ logger.Information(LogSource.Geekbot, "Initing Stuff");
+ var discordLogger = new DiscordLogger(logger);
+
+ _client = new DiscordSocketClient(new DiscordSocketConfig
+ {
+ LogLevel = LogSeverity.Verbose,
+ MessageCacheSize = 1000
+ });
+ _client.Log += discordLogger.Log;
+ _commands = new CommandService();
+
+ try
+ {
+ var redisMultiplexer = ConnectionMultiplexer.Connect("127.0.0.1:6379");
+ _redis = redisMultiplexer.GetDatabase(6);
+ logger.Information(LogSource.Redis, $"Connected to db {_redis.Database}");
+ }
+ catch (Exception e)
+ {
+ logger.Error(LogSource.Redis, "Redis Connection Failed", e);
+ Environment.Exit(GeekbotExitCode.RedisConnectionFailed.GetHashCode());
+ }
+
+ _token = runParameters.Token ?? _redis.StringGet("discordToken");
+ if (_token.IsNullOrEmpty)
+ {
+ Console.Write("Your bot Token: ");
+ var newToken = Console.ReadLine();
+ _redis.StringSet("discordToken", newToken);
+ _redis.StringSet("Game", "Ping Pong");
+ _token = newToken;
+ _firstStart = true;
+ }
+
+ _services = new ServiceCollection();
+
+ _userRepository = new UserRepository(_redis, logger);
+ var fortunes = new FortunesProvider(logger);
+ var mediaProvider = new MediaProvider(logger);
+ var malClient = new MalClient(_redis, logger);
+ var levelCalc = new LevelCalc();
+ var emojiConverter = new EmojiConverter();
+ var mtgManaConverter = new MtgManaConverter();
+ var wikipediaClient = new WikipediaClient();
+ var audioUtils = new AudioUtils();
+
+ _services.AddSingleton(_redis);
+ _services.AddSingleton(_userRepository);
+ _services.AddSingleton(logger);
+ _services.AddSingleton(levelCalc);
+ _services.AddSingleton(emojiConverter);
+ _services.AddSingleton(fortunes);
+ _services.AddSingleton(mediaProvider);
+ _services.AddSingleton(malClient);
+ _services.AddSingleton(mtgManaConverter);
+ _services.AddSingleton(wikipediaClient);
+ _services.AddSingleton(audioUtils);
+
+ logger.Information(LogSource.Geekbot, "Connecting to Discord");
+
+ await Login();
+
+ await Task.Delay(-1);
+ }
+
+ private async Task Login()
+ {
+ try
+ {
+ await _client.LoginAsync(TokenType.Bot, _token);
+ await _client.StartAsync();
+ var isConneted = await IsConnected();
+ if (isConneted)
+ {
+ await _client.SetGameAsync(_redis.StringGet("Game"));
+ _logger.Information(LogSource.Geekbot, $"Now Connected as {_client.CurrentUser.Username} to {_client.Guilds.Count} Servers");
+
+ _logger.Information(LogSource.Geekbot, "Registering Stuff");
+ var translationHandler = new TranslationHandler(_client.Guilds, _redis, _logger);
+ var errorHandler = new ErrorHandler(_logger, translationHandler, _runParameters.ExposeErrors);
+ var reactionListener = new ReactionListener(_redis);
+ await _commands.AddModulesAsync(Assembly.GetEntryAssembly());
+ _services.AddSingleton(_commands);
+ _services.AddSingleton(errorHandler);
+ _services.AddSingleton(translationHandler);
+ _services.AddSingleton(_client);
+ _services.AddSingleton(reactionListener);
+ _servicesProvider = _services.BuildServiceProvider();
+
+ var handlers = new Handlers(_client, _logger, _redis, _servicesProvider, _commands, _userRepository, reactionListener);
+
+ _client.MessageReceived += handlers.RunCommand;
+ _client.MessageReceived += handlers.UpdateStats;
+ _client.MessageDeleted += handlers.MessageDeleted;
+ _client.UserJoined += handlers.UserJoined;
+ _client.UserUpdated += handlers.UserUpdated;
+ _client.UserLeft += handlers.UserLeft;
+ _client.ReactionAdded += handlers.ReactionAdded;
+ _client.ReactionRemoved += handlers.ReactionRemoved;
+
+ if (_firstStart || _runParameters.Reset)
+ {
+ _logger.Information(LogSource.Geekbot, "Finishing setup");
+ await FinishSetup();
+ _logger.Information(LogSource.Geekbot, "Setup finished");
+ }
+ if (!_runParameters.DisableApi)
+ {
+ StartWebApi();
+ }
+
+ _logger.Information(LogSource.Geekbot, "Done and ready for use");
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.Error(LogSource.Geekbot, "Could not connect...", e);
+ Environment.Exit(GeekbotExitCode.CouldNotLogin.GetHashCode());
+ }
+ }
+
+ private async Task IsConnected()
+ {
+ while (!_client.ConnectionState.Equals(ConnectionState.Connected))
+ await Task.Delay(25);
+ return true;
+ }
+
+ private void StartWebApi()
+ {
+ _logger.Information(LogSource.Api, "Starting Webserver");
+ var webApiUrl = new Uri("http://localhost:12995");
+ new NancyHost(webApiUrl).Start();
+ _logger.Information(LogSource.Api, $"Webserver now running on {webApiUrl}");
+ }
+
+ private async Task FinishSetup()
+ {
+ try
+ {
+ // ToDo: Set bot avatar
+ var appInfo = await _client.GetApplicationInfoAsync();
+ _redis.StringSet("botOwner", appInfo.Owner.Id);
+ }
+ catch (Exception e)
+ {
+ _logger.Warning(LogSource.Geekbot, "Setup Failed, couldn't retrieve discord application data", e);
+ }
+ return Task.CompletedTask;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/Storage/checkEmPics b/Geekbot.net/Storage/checkEmPics
new file mode 100644
index 0000000..03be23e
--- /dev/null
+++ b/Geekbot.net/Storage/checkEmPics
@@ -0,0 +1,122 @@
+http://s19.postimg.org/pcq2kwzoj/4cb.png
+http://s19.postimg.org/cvetk0f4z/5_Dim_Dy6p.jpg
+http://s19.postimg.org/5hzfl1v37/1310151998600.jpg
+http://s19.postimg.org/53y3lgazn/1324181141954.jpg
+http://s19.postimg.org/724rjg3hf/1392512742365.png
+http://s19.postimg.org/3rgejkdk3/1393501296733.png
+http://s19.postimg.org/a6ffg8k9v/1401667341503.jpg
+http://s19.postimg.org/qiph5yylf/1419231572452.jpg
+http://s19.postimg.org/fqwi4m8ir/1427600681401.png
+http://s19.postimg.org/4c00zzw6b/1447813628974.png
+http://s19.postimg.org/uuio8puw3/b5_3q_ycaaavxtf.jpg
+http://s19.postimg.org/bghu913fn/check_em_by_boyboy99100_d57xp3y.png
+http://s19.postimg.org/s1pgooujn/l_Hkppjs.jpg
+http://s19.postimg.org/m08itft0j/checkem.jpg
+https://old.postimg.org/image/6vx33rb1b/
+https://old.postimg.org/image/wxiaz1mov/
+https://old.postimg.org/image/azqfizx27/
+https://old.postimg.org/image/6iy2kbiu7/
+https://old.postimg.org/image/k8slt45y7/
+https://old.postimg.org/image/t7ruxmplr/
+https://old.postimg.org/image/ssbzqvean/
+https://old.postimg.org/image/kbchfy9lr/
+https://old.postimg.org/image/dl0lk9btr/
+https://old.postimg.org/image/e5k80oufz/
+https://old.postimg.org/image/er005baqn/
+https://old.postimg.org/image/bfk2uzcin/
+https://old.postimg.org/image/556fp0jkv/
+https://old.postimg.org/image/i0efbryu7/
+https://old.postimg.org/image/943n7u87z/
+https://old.postimg.org/image/xn5op5cm7/
+https://old.postimg.org/image/3l5p4d0kf/
+https://old.postimg.org/image/5boq5ui3j/
+https://old.postimg.org/image/ru082bqcf/
+https://old.postimg.org/image/ytea1oqan/
+https://old.postimg.org/image/vu7dekgtb/
+https://old.postimg.org/image/hl7qwi2an/
+https://old.postimg.org/image/5aescfg9r/
+https://old.postimg.org/image/9gzmrrfvj/
+https://old.postimg.org/image/50bv6tr1b/
+https://old.postimg.org/image/afkl7silb/
+https://old.postimg.org/image/nrdsgzllr/
+https://old.postimg.org/image/s32e5zsin/
+https://old.postimg.org/image/5sej60v8f/
+https://old.postimg.org/image/lgfqctau7/
+https://old.postimg.org/image/tn7q4e0wv/
+https://old.postimg.org/image/8612arz1b/
+https://old.postimg.org/image/w5tf52mn3/
+https://old.postimg.org/image/zdxwi48wv/
+https://old.postimg.org/image/lphwghd0f/
+https://old.postimg.org/image/uzu0k0nq7/
+https://old.postimg.org/image/3vqzsxjbz/
+https://old.postimg.org/image/5d7uqqyov/
+https://old.postimg.org/image/dntnyku8v/
+https://old.postimg.org/image/dsxf891jz/
+https://old.postimg.org/image/3nyrioizj/
+https://old.postimg.org/image/6zx2bzaqn/
+https://old.postimg.org/image/wu6v1raqn/
+https://old.postimg.org/image/hb9f4n2fz/
+https://old.postimg.org/image/p7yhqm3a7/
+https://old.postimg.org/image/oelvxzx9b/
+https://old.postimg.org/image/vcq03xvdr/
+https://old.postimg.org/image/b08t1yqlb/
+https://old.postimg.org/image/6yrpwayan/
+https://old.postimg.org/image/btleukwm7/
+https://old.postimg.org/image/62ztuldzz/
+https://old.postimg.org/image/w3iq9pxr3/
+https://old.postimg.org/image/byp6493xb/
+https://old.postimg.org/image/xp2lf9xcv/
+https://old.postimg.org/image/j9p9u49pb/
+https://old.postimg.org/image/hvxmytafz/
+https://old.postimg.org/image/5eqzbnfa7/
+https://old.postimg.org/image/do2uq290f/
+https://old.postimg.org/image/54o261q1r/
+https://old.postimg.org/image/94qm4jr4v/
+https://old.postimg.org/image/lee88y0pr/
+https://old.postimg.org/image/bncb58cv3/
+https://old.postimg.org/image/5246j7me7/
+https://old.postimg.org/image/4uby8ym1r/
+https://old.postimg.org/image/qn996tj4v/
+https://old.postimg.org/image/c1dn4twyn/
+https://old.postimg.org/image/6rd9ra23j/
+https://lehcark14.files.wordpress.com/2008/08/botan16.jpg
+http://i.imgur.com/p9vALew.jpg
+http://i.imgur.com/4a9l2Rm.png
+http://i.imgur.com/RNtixMQ.jpg
+https://pbs.twimg.com/media/Cro9aIGUEAAkXCP.jpg
+http://s16.postimg.org/empvloimd/Check_em_Guts.png
+https://s18.postimg.io/qgbhe7u09/1424491645996.gif
+http://s19.postimg.org/hhemlt7xf/3eb.jpg
+http://s19.postimg.org/cwsg6vo83/8aa.png
+http://s19.postimg.org/rh9j1pj6r/28mohl4.png
+http://s19.postimg.org/zba4n3qzn/86d.jpg
+http://s19.postimg.org/cb3hart5v/2016_09_16_08_58_45.png
+http://s19.postimg.org/m9ofx92lf/bb1.jpg
+http://s19.postimg.org/maydqo4f7/e8b.jpg
+http://s19.postimg.org/yqzoy5n4z/fbe.png
+http://s19.postimg.org/xd822unvn/giphy.gif
+http://s19.postimg.org/c4udlf9er/l_TU3eup.jpg
+https://66.media.tumblr.com/cc893a0ee40d73d083da3df4bdaf45cc/tumblr_mx8psiFduG1t1g1k8o1_500.gif
+http://i.imgur.com/swbXHSy.gif
+http://img1.reactor.cc/pics/post/full/Anime-Touhou-Project-Yakumo-Yukari-%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F-1303807.jpeg
+http://i.imgur.com/ftGLHE0.png
+http://i.imgur.com/JELDhKQ.png
+http://imgur.com/yBJound
+http://i.imgur.com/f7gAVPJ.png
+http://i.imgur.com/HxWyo2Z.jpg
+http://i.imgur.com/8Eb9CxQ.png
+http://i.imgur.com/kOECcjz.png
+http://i.imgur.com/MJLu7oJ.jpg
+http://i.imgur.com/itG3rPM.jpg
+http://i.imgur.com/G83Go9t.jpg
+http://i.imgur.com/jI2dBnU.jpg
+http://i.imgur.com/FtALzg0.jpg
+http://i.imgur.com/GwZpJEv.gif
+http://i.imgur.com/TYGRD3B.gif
+http://i.imgur.com/P6TxLS3.png
+http://i.imgur.com/phTVTdn.jpg
+http://i.imgur.com/thhR6UE.jpg
+http://i.imgur.com/KbROufx.jpg
+http://i.imgur.com/sQqWbcm.jpg
+http://i.imgur.com/YYpis53.png
+http://i.imgur.com/kwaRd54.gif
\ No newline at end of file
diff --git a/Geekbot.net/Storage/croissant b/Geekbot.net/Storage/croissant
new file mode 100644
index 0000000..281b790
--- /dev/null
+++ b/Geekbot.net/Storage/croissant
@@ -0,0 +1,17 @@
+https://i2.wp.com/epicureandculture.com/wp-content/uploads/2014/12/shutterstock_172040546.jpg
+http://www.bakespace.com/images/large/5d79070cf21b2f33c3a1dd4336cb27d2.jpeg
+http://food.fnr.sndimg.com/content/dam/images/food/fullset/2015/5/7/1/SD1B43_croissants-recipe_s4x3.jpg.rend.hgtvcom.616.462.suffix/1431052139248.jpeg
+http://img.taste.com.au/u-Bwjfm_/taste/2016/11/mini-croissants-with-3-fillings-14692-1.jpeg
+https://media.newyorker.com/photos/590974702179605b11ad8096/16:9/w_1200,h_630,c_limit/Gopnik-TheMurkyMeaningsofStraightenedOutCroissants.jpg
+http://bt.static-redmouse.ch/sites/bielertagblatt.ch/files/styles/bt_article_showroom_landscape/hash/84/c9/84c9aed08415265911ec05c46d25d3ef.jpg?itok=hP5PnHaT
+https://www.dermann.at/wp-content/uploads/Schokocroissant_HPBild_1400x900px.jpeg
+https://www.bettybossi.ch/static/rezepte/x/bb_bkxx060101_0360a_x.jpg
+http://www.engel-beck.ch/uploads/pics/tete-de-moine-gipfel-.jpg
+https://storage.cpstatic.ch/storage/og_image/laugengipfel--425319.jpg
+https://www.backhaus-kutzer.de/fileadmin/templates/Resources/Public/img/produkte/suesses-gebaeck/Milchhoernchen.png
+https://www.kuechengoetter.de/uploads/media/1000x524/00/36390-vanillekipferl-0.jpg?v=1-0
+https://c1.staticflickr.com/3/2835/10874180753_2b2916e3ce_b.jpg
+http://www.mistercool.ch/wp-content/uploads/2017/02/Gipfel-mit-Cerealien-7168.png
+https://scontent-sea1-1.cdninstagram.com/t51.2885-15/s480x480/e35/c40.0.999.999/15099604_105396696611384_2866237281000226816_n.jpg?ig_cache_key=MTM4MzQxOTU1MDc5NjUxNzcwMA%3D%3D.2.c
+http://www.lecrobag.de/wp-content/uploads/2014/03/Wurst_2014_l.jpg
+https://www.thecookierookie.com/wp-content/uploads/2017/02/sheet-pan-chocolate-croissants-collage1.jpeg
\ No newline at end of file
diff --git a/src/Bot/Storage/fortunes b/Geekbot.net/Storage/fortunes
similarity index 100%
rename from src/Bot/Storage/fortunes
rename to Geekbot.net/Storage/fortunes
diff --git a/Geekbot.net/Storage/foxes b/Geekbot.net/Storage/foxes
new file mode 100644
index 0000000..020c1cf
--- /dev/null
+++ b/Geekbot.net/Storage/foxes
@@ -0,0 +1,29 @@
+https://i.ytimg.com/vi/qF6OOGuT_hI/maxresdefault.jpg
+https://www.hd-wallpapersdownload.com/script/bulk-upload/desktop-funny-fox-wallpaper.jpg
+http://moziru.com/images/drawn-fox-funny-18.jpg
+https://static.tumblr.com/bb34d8f163098ad1daafcffbdbb03975/rk23uap/Nwwp0rmi2/tumblr_static_tumblr_static__640.jpg
+https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQoHUFOnZ3wJ2kT1skNdztFXXSvpU8bEoGS1alNZiuyLXvGJhcY
+http://childrenstorytales.com/wp-content/uploads/2011/03/how-to-draw-a-red-fox-in-the-snow.jpg
+https://www.popsci.com/sites/popsci.com/files/styles/1000_1x_/public/import/2013/images/2013/09/redfoxyawn.jpg?itok=yRkSVe8T
+https://hdqwalls.com/wallpapers/wild-fox-art.jpg
+https://ae01.alicdn.com/kf/HTB1Q9dpLpXXXXbhXpXXq6xXFXXXl/new-cute-fox-toy-lifelike-soft-long-yellow-fox-doll-gift-about-73cm.jpg_640x640.jpg
+https://i.imgur.com/ktK9yXX.jpg
+https://res.cloudinary.com/teepublic/image/private/s--yTx2ncFA--/t_Preview/b_rgb:c8e0ec,c_limit,f_auto,h_313,q_90,w_313/v1506478249/production/designs/1932607_0
+http://4.bp.blogspot.com/-Hz-o_KYj3Xk/Vlm2mwbztjI/AAAAAAAA8Ss/jbH5ovjmC9A/s1600/ScreenShot5502.jpg
+https://i.pinimg.com/originals/1e/d5/2f/1ed52f70873a95ac02fa074e48edfb71.jpg
+https://i.imgur.com/2vCrtap.jpg
+https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSfukWGu_IBaDeJOMBqOhVAwsDfqEPw0BFpCn5_-Iyr_xjd7zi9
+https://cdn.pixabay.com/photo/2017/01/31/18/36/animal-2026297_960_720.png
+https://i.pinimg.com/originals/e2/63/67/e26367a0844633b2a697b0a9d69e8cc9.jpg
+https://i.ebayimg.com/images/g/BvkAAOSwqxdTqrip/s-l300.jpg
+https://res.cloudinary.com/teepublic/image/private/s--1R53bger--/t_Preview/b_rgb:eae0c7,c_limit,f_jpg,h_630,q_90,w_630/v1481013120/production/designs/914528_1.jpg
+https://i.pinimg.com/originals/97/fe/69/97fe698462afde7b4209ccefeecbce71.jpg
+https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcT6G0ch6g-wG1TuDJ6BbkOFelMNnkgFXC6CxOw7qSNjoFkx-BCe
+https://wallpaperscraft.com/image/fox_forest_grass_117190_540x960.jpg
+https://image.freepik.com/free-vector/cartoon-flat-illustration-funny-cute-fox_6317-1174.jpg
+https://orig00.deviantart.net/2feb/f/2013/137/a/f/fox_and_curious_squirrel_by_tamarar-d65ju8d.jpg
+https://res.cloudinary.com/teepublic/image/private/s--dICeNmBx--/t_Preview/b_rgb:6e2229,c_limit,f_jpg,h_630,q_90,w_630/v1505243196/production/designs/1890493_1.jpg
+https://vignette.wikia.nocookie.net/puppyinmypocketfanon/images/4/49/L-Baby-Fox.jpg/revision/latest?cb=20130421001806
+http://7-themes.com/data_images/out/69/7009194-fox-puppy.jpg
+http://www.tehcute.com/pics/201401/little-fox-big.jpg
+https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR6QXB1APLdUsyzO39kPvhnC9cOvcwzEtsxown9QjWilWppia2mwg
\ No newline at end of file
diff --git a/src/Bot/Storage/pandas b/Geekbot.net/Storage/pandas
similarity index 78%
rename from src/Bot/Storage/pandas
rename to Geekbot.net/Storage/pandas
index 6c6d725..9d5046c 100644
--- a/src/Bot/Storage/pandas
+++ b/Geekbot.net/Storage/pandas
@@ -4,9 +4,8 @@ https://nationalzoo.si.edu/sites/default/files/styles/slide_1400x700/public/supp
https://media4.s-nbcnews.com/j/newscms/2016_36/1685951/ss-160826-twip-05_8cf6d4cb83758449fd400c7c3d71aa1f.nbcnews-ux-2880-1000.jpg
https://ichef-1.bbci.co.uk/news/660/cpsprodpb/169F6/production/_91026629_gettyimages-519508400.jpg
https://cdn.history.com/sites/2/2017/03/GettyImages-157278376.jpg
+https://www.pandasinternational.org/wptemp/wp-content/uploads/2012/10/slider1.jpg
https://tctechcrunch2011.files.wordpress.com/2015/11/panda.jpg
http://www.nationalgeographic.com/content/dam/magazine/rights-exempt/2016/08/departments/panda-mania-12.jpg
http://animals.sandiegozoo.org/sites/default/files/2016-09/panda1_10.jpg
-http://kids.nationalgeographic.com/content/dam/kids/photos/animals/Mammals/A-G/giant-panda-eating.adapt.945.1.jpg
-https://static.independent.co.uk/s3fs-public/thumbnails/image/2015/10/08/15/Hong-Kong-pandas.jpg
-https://3sn4dm1qd6i72l8a4r2ig7fl-wpengine.netdna-ssl.com/wp-content/uploads/2016/11/panda_lunlun_ZA_2083-b.jpg
\ No newline at end of file
+http://kids.nationalgeographic.com/content/dam/kids/photos/animals/Mammals/A-G/giant-panda-eating.adapt.945.1.jpg
\ No newline at end of file
diff --git a/Geekbot.net/Storage/pinguins b/Geekbot.net/Storage/pinguins
new file mode 100644
index 0000000..631f9d0
--- /dev/null
+++ b/Geekbot.net/Storage/pinguins
@@ -0,0 +1,13 @@
+https://i.ytimg.com/vi/Qr6sULJnu2o/maxresdefault.jpg
+https://www.apex-expeditions.com/wp-content/uploads/2015/08/newzealandSlider_Macquarie_ElephantSealKingPenguins_GRiehle_1366x601.jpg
+https://www.birdlife.org/sites/default/files/styles/1600/public/slide.jpg?itok=HRhQfA1S
+http://experimentexchange.com/wp-content/uploads/2016/07/penguins-fact.jpg
+http://images.mentalfloss.com/sites/default/files/styles/mf_image_16x9/public/istock-511366776.jpg?itok=cWhdWNZ8&resize=1100x619
+https://www.thevaporplace.ch/media/catalog/product/cache/1/thumbnail/800x800/9df78eab33525d08d6e5fb8d27136e95/a/t/atopack_penguin-15.jpg
+https://www.superfastbusiness.com/wp-content/uploads/2015/10/real-time-penguin-algorithm-featured.jpg
+http://www.antarctica.gov.au/__data/assets/image/0011/147737/varieties/antarctic.jpg
+https://vignette.wikia.nocookie.net/robloxcreepypasta/images/1/11/AAEAAQAAAAAAAAdkAAAAJDc3YzkyYjJhLTYyZjctNDY2Mi04M2VjLTg4NjY4ZjgwYzRmNg.png/revision/latest?cb=20180207200526
+https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR3xV0lhpZuhT8Nmm6LaITsppZ7VfWcWXuyu2cPHrlv_dt_M92K5g
+http://goboiano.com/wp-content/uploads/2017/04/Penguin-Kemeno-Friends-Waifu.jpg
+https://cdn.yoast.com/app/uploads/2015/10/Penguins_1200x628.png
+https://images.justwatch.com/backdrop/8611153/s1440/pingu
\ No newline at end of file
diff --git a/Geekbot.net/Storage/pumpkin b/Geekbot.net/Storage/pumpkin
new file mode 100644
index 0000000..4b8e6f2
--- /dev/null
+++ b/Geekbot.net/Storage/pumpkin
@@ -0,0 +1,23 @@
+https://i.pinimg.com/736x/0a/a7/8a/0aa78af25e114836e1a42585fb7b09ed--funny-pumpkins-pumkin-carving.jpg
+http://wdy.h-cdn.co/assets/16/31/980x1470/gallery-1470321728-shot-two-021.jpg
+https://i.pinimg.com/736x/6c/62/bf/6c62bfa73a19ffd9fc6f2d720d5e9764--cool-pumpkin-carving-carving-pumpkins.jpg
+http://images6.fanpop.com/image/photos/38900000/Jack-o-Lantern-halloween-38991566-500-415.jpg
+http://ghk.h-cdn.co/assets/15/37/1441834730-pumpkin-carve-2.jpg
+http://diy.sndimg.com/content/dam/images/diy/fullset/2011/7/26/1/iStock-10761186_halloween-pumpkin-in-garden_s4x3.jpg.rend.hgtvcom.966.725.suffix/1420851319631.jpeg
+http://ghk.h-cdn.co/assets/cm/15/11/54ffe537af882-snail-pumpkin-de.jpg
+https://www.digsdigs.com/photos/2009/10/100-halloween-pumpkin-carving-ideas-12.jpg
+http://diy.sndimg.com/content/dam/images/diy/fullset/2010/6/4/0/CI-Kyle-Nishioka_big-teeth-Jack-O-Lantern_s4x3.jpg.rend.hgtvcom.966.725.suffix/1420699522718.jpeg
+https://twistedsifter.files.wordpress.com/2011/10/most-amazing-pumpkin-carving-ray-villafane-10.jpg?w=521&h=739
+https://i.pinimg.com/736x/09/c4/b1/09c4b187b266c1f65332294f66009944--funny-pumpkins-halloween-pumpkins.jpg
+http://www.evilmilk.com/pictures/The_Pumpkin_Man.jpg
+http://cache.lovethispic.com/uploaded_images/blogs/13-Funny-Pumpkin-Carvings-5773-9.JPG
+http://ihappyhalloweenpictures.com/wp-content/uploads/2016/10/funny-halloween-pumpkin.jpg
+http://www.smallhomelove.com/wp-content/uploads/2012/08/leg-eating-pumpkin.jpg
+https://cdn.shopify.com/s/files/1/0773/6789/articles/Halloween_Feature_8ff7a7c4-2cb3-4584-a85f-5d4d1e6ca26e.jpg?v=1476211360
+http://4vector.com/i/free-vector-pumpkin-boy-color-version-clip-art_107714_Pumpkin_Boy_Color_Version_clip_art_hight.png
+https://i.pinimg.com/736x/59/8a/0f/598a0fbf789631b76c1ffd4443194d8e--halloween-pumpkins-fall-halloween.jpg
+https://i.pinimg.com/originals/8f/86/f9/8f86f95457467872b371ba697d341961.jpg
+http://nerdist.com/wp-content/uploads/2015/08/taleshalloween1.jpg
+http://www.designbolts.com/wp-content/uploads/2014/09/Scary-Pumpkin_Grin_stencil-Ideas.jpg
+http://vignette2.wikia.nocookie.net/scoobydoo/images/7/75/Pumpkin_monsters_%28Witch%27s_Ghost%29.png/revision/latest?cb=20140520070213
+https://taholtorf.files.wordpress.com/2013/10/36307-1920x1280.jpg
diff --git a/Geekbot.net/Storage/squirrel b/Geekbot.net/Storage/squirrel
new file mode 100644
index 0000000..2216465
--- /dev/null
+++ b/Geekbot.net/Storage/squirrel
@@ -0,0 +1,45 @@
+http://orig14.deviantart.net/6016/f/2010/035/c/b/first_squirrel_assassin_by_shotokanteddy.jpg
+https://thumbs-prod.si-cdn.com/eoEYA_2Hau4795uKoecUZZgz-3w=/800x600/filters:no_upscale()/https://public-media.smithsonianmag.com/filer/52/f9/52f93262-c29b-4a4f-b031-0c7ad145ed5f/42-33051942.jpg
+http://images5.fanpop.com/image/photos/30700000/Squirrel-squirrels-30710732-400-300.jpg
+https://www.lovethegarden.com/sites/default/files/files/Red%20%26%20Grey%20Squirrel%20picture%20side%20by%20side-LR.jpg
+http://i.dailymail.co.uk/i/pix/2016/02/24/16/158F7E7C000005DC-3462228-image-a-65_1456331226865.jpg
+http://2.bp.blogspot.com/-egfnMhUb8tg/T_dAIu1m6cI/AAAAAAAAPPU/v4x9q4WqWl8/s640/cute-squirrel-hey-watcha-thinkin-about.jpg
+https://upload.wikimedia.org/wikipedia/commons/thumb/1/1c/Squirrel_posing.jpg/287px-Squirrel_posing.jpg
+https://i.pinimg.com/736x/51/db/9b/51db9bad4a87d445d321923c7d56b501--red-squirrel-animal-kingdom.jpg
+https://metrouk2.files.wordpress.com/2016/10/ad_223291521.jpg?w=620&h=949&crop=1
+http://www.redsquirrelsunited.org.uk/wp-content/uploads/2016/07/layer-slider.jpg
+http://images.mentalfloss.com/sites/default/files/squirrel-hero.jpg?resize=1100x740
+https://i.pinimg.com/736x/ce/9c/59/ce9c5990b193046400d98724595cdaf3--red-squirrel-chipmunks.jpg
+https://www.brooklynpaper.com/assets/photos/40/30/dtg-squirrel-attacks-prospect-park-patrons-2017-07-28-bk01_z.jpg
+http://www.freakingnews.com/pictures/16000/Squirrel-Shark-16467.jpg
+http://img09.deviantart.net/5c1c/i/2013/138/0/6/barbarian_squirel_by_coucoucmoa-d64r9m4.jpg
+https://i.pinimg.com/736x/b4/5c/0d/b45c0d00b1a57e9f84f27f13cb019001--baby-squirrel-red-squirrel.jpg
+https://i.pinimg.com/736x/0f/75/87/0f7587bb613ab524763afe8c9a532e5c--cute-squirrel-squirrels.jpg
+http://cdn.images.express.co.uk/img/dynamic/128/590x/Grey-squirrel-828838.jpg
+http://www.lovethispic.com/uploaded_images/79964-Squirrel-Smelling-A-Flower.jpg
+https://i.pinimg.com/736x/23/d5/f9/23d5f9868f7d76c79c49bef53ae08f7f--squirrel-funny-red-squirrel.jpg
+http://stories.barkpost.com/wp-content/uploads/2016/01/squirrel-3-copy.jpg
+https://i.ytimg.com/vi/pzUs0DdzK3Y/hqdefault.jpg
+https://www.askideas.com/media/41/I-Swear-It-Wasnt-Me-Funny-Squirrel-Meme-Picture-For-Facebook.jpg
+https://i.pinimg.com/736x/2d/54/d8/2d54d8d2a9b3ab9d3e78544b75afd88e--funny-animal-pictures-humorous-pictures.jpg
+http://www.funny-animalpictures.com/media/content/items/images/funnysquirrels0012_O.jpg
+http://funny-pics.co/wp-content/uploads/funny-squirrel-and-coffee-picture.jpg
+https://pbs.twimg.com/media/Bi4Ij6CIgAAgEdZ.jpg
+http://www.funnyjunksite.com/pictures/wp-content/uploads/2015/06/Funny-Superman-Squirrels.jpg
+https://i.pinimg.com/736x/bf/35/00/bf3500104f8394909d116259d1f0575e--funny-squirrel-squirrel-girl.jpg
+http://quotespill.com/wp-content/uploads/2017/07/Squirrel-Meme-Draw-me-like-one-of-your-french-squirrrels-min.jpg
+https://i.pinimg.com/736x/e2/16/bb/e216bba53f80fc8e0111d371e9850159--funny-squirrels-cute-squirrel.jpg
+https://i.pinimg.com/736x/52/43/c9/5243c93377245be1f686218c266d775c--funny-squirrel-baby-squirrel.jpg
+https://i.pinimg.com/736x/0c/be/1d/0cbe1da8ad2c0cf3882a806b6fd88965--cute-pictures-funny-animal-pictures.jpg
+https://i.pinimg.com/736x/e5/08/67/e508670aa00ca3c896eccb81c4f6e2a8--funny-squirrel-baby-squirrel.jpg
+https://i.pinimg.com/736x/1c/7d/4f/1c7d4f067a10066aad802ce5ac468d71--group-boards-a-squirrel.jpg
+http://funny-pics.co/wp-content/uploads/funny-squirrel-on-a-branch.jpg
+http://loldamn.com/wp-content/uploads/2016/06/funny-squirrel-playing-water-bending.jpg
+https://cdn.trendhunterstatic.com/thumbs/squirrel-photography.jpeg
+https://i.pinimg.com/736x/d6/42/12/d64212cc6221916db4173962bf6c131a--cute-squirrel-baby-squirrel.jpg
+https://i.pinimg.com/236x/10/13/58/101358f2afc2c7d6b6a668046e7b8382--funny-animal-pictures-funny-animals.jpg
+https://i.pinimg.com/736x/da/0d/fe/da0dfe93bb26887795f906e8fa97d68e--secret-squirrel-cute-squirrel.jpg
+http://2.bp.blogspot.com/-HLieBqEuQoM/UDkRmeyzB5I/AAAAAAAABHs/RtsEynn5t6Y/s1600/hd-squirrel-wallpaper-with-a-brown-squirrel-eating-watermelon-wallpapers-backgrounds-pictures-photos.jpg
+http://www.city-data.com/forum/members/brenda-starz-328928-albums-brenda-s-funny-squirrel-comment-pic-s-pic5075-punk-squirrels.jpg
+http://img15.deviantart.net/9c50/i/2011/213/c/9/just_taking_it_easy_by_lou_in_canada-d42do3d.jpg
+http://3.bp.blogspot.com/-AwsSk76R2Is/USQa3-dszKI/AAAAAAAABUQ/KF_F8HbtP1U/w1200-h630-p-k-no-nu/crazySquirrel.jpg
diff --git a/src/Bot/Storage/turtles b/Geekbot.net/Storage/turtles
similarity index 69%
rename from src/Bot/Storage/turtles
rename to Geekbot.net/Storage/turtles
index aa0fbcf..9dbbf72 100644
--- a/src/Bot/Storage/turtles
+++ b/Geekbot.net/Storage/turtles
@@ -1,20 +1,21 @@
https://i.guim.co.uk/img/media/6b9be13031738e642f93f9271f3592044726a9b1/0_0_2863_1610/2863.jpg?w=640&h=360&q=55&auto=format&usm=12&fit=max&s=85f3b33cc158b5aa120c143dae1916ed
http://cf.ltkcdn.net/small-pets/images/std/212089-676x450-Turtle-feeding-on-leaf.jpg
+https://static1.squarespace.com/static/5369465be4b0507a1fd05af0/53767a6be4b0ad0822345e52/57e40ba4893fc031e05a018f/1498243318058/solvin.jpg?format=1500w
https://c402277.ssl.cf1.rackcdn.com/photos/419/images/story_full_width/HI_287338Hero.jpg?1433950119
https://www.cdc.gov/salmonella/agbeni-08-17/images/turtle.jpg
https://cdn.arstechnica.net/wp-content/uploads/2017/08/GettyImages-524757168.jpg
http://pmdvod.nationalgeographic.com/NG_Video/595/319/4504517_098_05_TOS_thumbnail_640x360_636296259676.jpg
+http://cdn1.arkive.org/media/7D/7D46329A-6ED2-4F08-909E-7B596417994A/Presentation.Large/Big-headed-turtle-close-up.jpg
http://s7d2.scene7.com/is/image/PetSmart/ARTHMB-CleaningYourTortoiseOrTurtlesHabitat-20160818?$AR1104$
https://fthmb.tqn.com/9VGWzK_GWlvrjxtdFPX6EJxOq24=/960x0/filters:no_upscale()/133605352-56a2bce53df78cf7727960db.jpg
+https://i.imgur.com/46QmzgF.jpg
https://www.wildgratitude.com/wp-content/uploads/2015/07/turtle-spirit-animal1.jpg
http://www.backwaterreptiles.com/images/turtles/red-eared-slider-turtle-for-sale.jpg
+https://i.pinimg.com/736x/f1/f4/13/f1f413d6d07912be6080c08b186630ac--happy-turtle-funny-stuff.jpg
+http://www.dupageforest.org/uploadedImages/Content/District_News/Nature_Stories/2016/Snapping%20Turtle%20Scott%20Plantier%20STP4793.jpg
http://turtlebackzoo.com/wp-content/uploads/2016/07/exhibit-headers_0008_SOUTH-AMERICA-600x400.jpg
+https://i.ytimg.com/vi/_YfYHFM3Das/maxresdefault.jpg
https://i.pinimg.com/736x/dd/4e/7f/dd4e7f2f921ac28b1d5a59174d477131--cute-baby-sea-turtles-adorable-turtles.jpg
http://kids.nationalgeographic.com/content/dam/kids/photos/animals/Reptiles/A-G/green-sea-turtle-closeup-underwater.adapt.945.1.jpg
+https://i.ytimg.com/vi/p4Jj9QZFJvw/hqdefault.jpg
https://fthmb.tqn.com/nirxHkH3jBAe74ife6fJJu6k6q8=/2121x1414/filters:fill(auto,1)/Red-eared-sliders-GettyImages-617946009-58fae8835f9b581d59a5bab6.jpg
-http://assets.worldwildlife.org/photos/167/images/original/MID_225023-circle-hawksbill-turtle.jpg?1345565600
-https://seaturtles.org/wp-content/uploads/2013/11/GRN-honuAnitaWintner2.jpg
-https://images2.minutemediacdn.com/image/upload/c_crop,h_2549,w_4536,x_0,y_237/v1560186367/shape/mentalfloss/istock-687398754.jpg?itok=QsiF5yHP
-https://c402277.ssl.cf1.rackcdn.com/photos/13028/images/story_full_width/seaturtle_spring2017.jpg?1485359391
-https://i2.wp.com/rangerrick.org/wp-content/uploads/2018/03/Turtle-Tale-RR-Jr-June-July-2017.jpg?fit=1156%2C650&ssl=1
-https://boyslifeorg.files.wordpress.com/2019/07/greenseaturtle.jpg
\ No newline at end of file
diff --git a/Geekbot.net/WebApi/Help/CommandDto.cs b/Geekbot.net/WebApi/Help/CommandDto.cs
new file mode 100644
index 0000000..5921fa6
--- /dev/null
+++ b/Geekbot.net/WebApi/Help/CommandDto.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+
+namespace Geekbot.net.WebApi.Help
+{
+ public class CommandDto
+ {
+ public string Name { get; set; }
+ public string Category { get; set; }
+ public string Summary { get; set; }
+ public bool IsAdminCommand { get; set; }
+ public Array Aliases { get; set; }
+ public List Params { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/WebApi/Help/CommandParamDto.cs b/Geekbot.net/WebApi/Help/CommandParamDto.cs
new file mode 100644
index 0000000..f7ce0ea
--- /dev/null
+++ b/Geekbot.net/WebApi/Help/CommandParamDto.cs
@@ -0,0 +1,9 @@
+namespace Geekbot.net.WebApi.Help
+{
+ public class CommandParamDto
+ {
+ public string Summary { get; set; }
+ public string Default { get; set; }
+ public string Type { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/WebApi/Help/HelpController.cs b/Geekbot.net/WebApi/Help/HelpController.cs
new file mode 100644
index 0000000..d71d26f
--- /dev/null
+++ b/Geekbot.net/WebApi/Help/HelpController.cs
@@ -0,0 +1,48 @@
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+using Discord.Commands;
+using Geekbot.net.Lib;
+using Nancy;
+
+namespace Geekbot.net.WebApi.Help
+{
+ public class HelpController : NancyModule
+ {
+ public HelpController()
+ {
+ Get("/v1/commands", args =>
+ {
+ var commands = GetCommands().Result;
+
+ var commandList = (from cmd in commands.Commands
+ let cmdParamsObj = cmd.Parameters.Select(cmdParam => new CommandParamDto
+ {
+ Summary = cmdParam.Summary,
+ Default = cmdParam.DefaultValue?.ToString() ?? null,
+ Type = cmdParam.Type?.ToString()
+ })
+ .ToList()
+ let param = string.Join(", !", cmd.Aliases)
+ select new CommandDto
+ {
+ Name = cmd.Name,
+ Summary = cmd.Summary,
+ Category = cmd.Remarks ?? CommandCategories.Uncategorized,
+ IsAdminCommand = (param.Contains("admin")),
+ Aliases = cmd.Aliases.ToArray(),
+ Params = cmdParamsObj
+ }).ToList();
+ return Response.AsJson(commandList);
+
+ });
+ }
+
+ private async Task GetCommands()
+ {
+ var commands = new CommandService();
+ await commands.AddModulesAsync(Assembly.GetEntryAssembly());
+ return commands;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/WebApi/Status/ApiStatusDto.cs b/Geekbot.net/WebApi/Status/ApiStatusDto.cs
new file mode 100644
index 0000000..242bf58
--- /dev/null
+++ b/Geekbot.net/WebApi/Status/ApiStatusDto.cs
@@ -0,0 +1,9 @@
+namespace Geekbot.net.WebApi.Status
+{
+ public class ApiStatusDto
+ {
+ public string GeekbotVersion { get; set; }
+ public string ApiVersion { get; set; }
+ public string Status { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/WebApi/Status/StatusController.cs b/Geekbot.net/WebApi/Status/StatusController.cs
new file mode 100644
index 0000000..05196a2
--- /dev/null
+++ b/Geekbot.net/WebApi/Status/StatusController.cs
@@ -0,0 +1,22 @@
+using Geekbot.net.Lib;
+using Nancy;
+
+namespace Geekbot.net.WebApi.Status
+{
+ public class StatusController : NancyModule
+ {
+ public StatusController()
+ {
+ Get("/", args =>
+ {
+ var responseBody = new ApiStatusDto
+ {
+ GeekbotVersion = Constants.BotVersion(),
+ ApiVersion = Constants.ApiVersion.ToString(),
+ Status = "Online"
+ };
+ return Response.AsJson(responseBody);
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/Geekbot.net/WebApi/WebConfig.cs b/Geekbot.net/WebApi/WebConfig.cs
new file mode 100644
index 0000000..f0b9ca3
--- /dev/null
+++ b/Geekbot.net/WebApi/WebConfig.cs
@@ -0,0 +1,23 @@
+using System.Diagnostics;
+using Nancy;
+using Nancy.Bootstrapper;
+using Nancy.TinyIoc;
+
+namespace Geekbot.net.WebApi
+{
+ public class WebConfig : DefaultNancyBootstrapper
+ {
+ protected override void RequestStartup(TinyIoCContainer container, IPipelines pipelines, NancyContext context)
+ {
+
+ //CORS Enable
+ pipelines.AfterRequest.AddItemToEndOfPipeline(ctx =>
+ {
+ ctx.Response.WithHeader("Access-Control-Allow-Origin", "*")
+ .WithHeader("Access-Control-Allow-Methods", "GET")
+ .WithHeader("Access-Control-Allow-Headers", "Accept, Origin, Content-type")
+ .WithHeader("Last-Modified", Process.GetCurrentProcess().StartTime.ToString());
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Startup/derp.ico b/Geekbot.net/derp.ico
similarity index 100%
rename from src/Startup/derp.ico
rename to Geekbot.net/derp.ico
diff --git a/Tests/Lib/EmojiConverter.test.cs b/Tests/Lib/EmojiConverter.test.cs
new file mode 100644
index 0000000..aaaaa8f
--- /dev/null
+++ b/Tests/Lib/EmojiConverter.test.cs
@@ -0,0 +1,75 @@
+using System.Collections.Generic;
+using Geekbot.net.Lib;
+using Geekbot.net.Lib.Converters;
+using Xunit;
+
+namespace Tests.Lib
+{
+ public class EmojiConverterTest
+ {
+ public static IEnumerable