Compare commits
1 commit
master
...
context-ha
Author | SHA1 | Date | |
---|---|---|---|
70d92706e2 |
360 changed files with 5393 additions and 14204 deletions
38
.deploy.yml
38
.deploy.yml
|
@ -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
|
16
.gitignore
vendored
16
.gitignore
vendored
|
@ -1,10 +1,12 @@
|
|||
/*/**/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.sln.DotSettings.user
|
||||
app
|
||||
Geekbot.net/Logs/*
|
||||
!/Geekbot.net/Logs/.keep
|
||||
|
|
|
@ -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
|
||||
before_script:
|
||||
- set -e
|
||||
- set -u
|
||||
- set -o pipefail
|
||||
|
||||
Build:
|
||||
build:
|
||||
stage: build
|
||||
image: mcr.microsoft.com/dotnet/sdk:6.0
|
||||
image: microsoft/dotnet:2.0.3-sdk-stretch
|
||||
variables:
|
||||
NUGET_PACKAGES: "${CI_PROJECT_DIR}/.nugetcache"
|
||||
cache:
|
||||
paths:
|
||||
- .nugetcache
|
||||
artifacts:
|
||||
expire_in: 1h
|
||||
paths:
|
||||
- app
|
||||
- Geekbot.net/Binaries/
|
||||
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/
|
||||
- dotnet test Tests
|
||||
- dotnet publish --configuration Release -o Binaries ./
|
||||
|
||||
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:
|
||||
deploy:
|
||||
stage: deploy
|
||||
image: quay.io/ansible/ansible-runner:stable-2.12-latest
|
||||
image: instrumentisto/rsync-ssh
|
||||
only:
|
||||
- master
|
||||
variables:
|
||||
ANSIBLE_NOCOWS: 1
|
||||
dependencies:
|
||||
- build
|
||||
environment:
|
||||
name: Production
|
||||
url: https://discordapp.com/oauth2/authorize?client_id=171249478546882561&scope=bot&permissions=1416834054
|
||||
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
|
||||
- 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:
|
||||
- ansible-galaxy collection install -r ansible-requirements.yml
|
||||
- ansible-playbook -i $PROD_IP, .deploy.yml
|
||||
- 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"
|
||||
|
||||
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
|
||||
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
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
FROM mcr.microsoft.com/dotnet/aspnet:6.0
|
||||
|
||||
COPY ./app /app/
|
||||
|
||||
EXPOSE 12995/tcp
|
||||
WORKDIR /app
|
||||
ENTRYPOINT ./Geekbot
|
|
@ -3,19 +3,9 @@ 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}"
|
||||
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}") = "Tests", "Tests\Tests.csproj", "{4CAF5F02-EFFE-4FDA-BD44-EEADDBA9600E}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
@ -23,34 +13,14 @@ 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
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
|
||||
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LIMIT/@EntryValue">200</s:Int64>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpRenamePlacementToArrangementMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
159
Geekbot.net/Commands/Admin.cs
Normal file
159
Geekbot.net/Commands/Admin.cs
Normal file
|
@ -0,0 +1,159 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using Geekbot.net.Lib;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
[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("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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,12 +2,11 @@
|
|||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Geekbot.Core;
|
||||
using Geekbot.Core.ErrorHandling;
|
||||
using Geekbot.net.Lib;
|
||||
|
||||
namespace Geekbot.Bot.Commands.Utils
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
public class AvatarGetter : TransactionModuleBase
|
||||
public class AvatarGetter : ModuleBase
|
||||
{
|
||||
private readonly IErrorHandler _errorHandler;
|
||||
|
||||
|
@ -17,18 +16,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);
|
||||
}
|
||||
}
|
||||
}
|
71
Geekbot.net/Commands/BattleTag.cs
Normal file
71
Geekbot.net/Commands/BattleTag.cs
Normal file
|
@ -0,0 +1,71 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
using Geekbot.net.Lib;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
[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;
|
||||
if (splited[1].Length == 4 || splited[1].Length == 5) return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
58
Geekbot.net/Commands/Cat.cs
Normal file
58
Geekbot.net/Commands/Cat.cs
Normal file
|
@ -0,0 +1,58 @@
|
|||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Geekbot.net.Lib;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
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("http://random.cat");
|
||||
var response = await client.GetAsync("/meow.php");
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var stringResponse = await response.Content.ReadAsStringAsync();
|
||||
var catFile = JsonConvert.DeserializeObject<CatResponse>(stringResponse);
|
||||
var eb = new EmbedBuilder();
|
||||
eb.ImageUrl = catFile.file;
|
||||
await ReplyAsync("", false, eb.Build());
|
||||
}
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
await ReplyAsync($"Seems like the dog cought the cat (error occured)\r\n{e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_errorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
|
||||
private class CatResponse
|
||||
{
|
||||
public string file { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
89
Geekbot.net/Commands/Changelog.cs
Normal file
89
Geekbot.net/Commands/Changelog.cs
Normal file
|
@ -0,0 +1,89 @@
|
|||
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 Newtonsoft.Json;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
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<List<Commit>>(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);
|
||||
}
|
||||
}
|
||||
|
||||
private class Commit
|
||||
{
|
||||
public string sha { get; set; }
|
||||
public CommitInfo commit { get; set; }
|
||||
public Uri html_url { get; set; }
|
||||
}
|
||||
|
||||
private class CommitInfo
|
||||
{
|
||||
public commitAuthor author { get; set; }
|
||||
public string message { get; set; }
|
||||
}
|
||||
|
||||
private class commitAuthor
|
||||
{
|
||||
public string name { get; set; }
|
||||
public string email { get; set; }
|
||||
public DateTimeOffset date { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
74
Geekbot.net/Commands/CheckEm.cs
Normal file
74
Geekbot.net/Commands/CheckEm.cs
Normal file
|
@ -0,0 +1,74 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
using Geekbot.net.Lib;
|
||||
using Geekbot.net.Lib.Media;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
public class CheckEm : ModuleBase
|
||||
{
|
||||
private readonly IMediaProvider _checkEmImages;
|
||||
private readonly IErrorHandler _errorHandler;
|
||||
private readonly Random _rnd;
|
||||
|
||||
public CheckEm(Random RandomClient, IMediaProvider mediaProvider, IErrorHandler errorHandler)
|
||||
{
|
||||
_rnd = RandomClient;
|
||||
_checkEmImages = mediaProvider;
|
||||
_errorHandler = errorHandler;
|
||||
}
|
||||
|
||||
[Command("checkem", RunMode = RunMode.Async)]
|
||||
[Remarks(CommandCategories.Randomness)]
|
||||
[Summary("Check for dubs")]
|
||||
public async Task MuhDubs()
|
||||
{
|
||||
try
|
||||
{
|
||||
var number = _rnd.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<int>();
|
||||
while (num > 0)
|
||||
{
|
||||
listOfInts.Add(num % 10);
|
||||
num = num / 10;
|
||||
}
|
||||
|
||||
listOfInts.Reverse();
|
||||
return listOfInts.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
40
Geekbot.net/Commands/Choose.cs
Normal file
40
Geekbot.net/Commands/Choose.cs
Normal file
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
using Geekbot.net.Lib;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
public class Choose : ModuleBase
|
||||
{
|
||||
private readonly IErrorHandler _errorHandler;
|
||||
private readonly Random _rnd;
|
||||
private readonly ITranslationHandler _translation;
|
||||
|
||||
public Choose(Random RandomClient, IErrorHandler errorHandler, ITranslationHandler translation)
|
||||
{
|
||||
_rnd = RandomClient;
|
||||
_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 = _rnd.Next(choicesArray.Length);
|
||||
await ReplyAsync(string.Format(transDict["Choice"], choicesArray[choice]));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_errorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
97
Geekbot.net/Commands/ContextHandler.cs
Normal file
97
Geekbot.net/Commands/ContextHandler.cs
Normal file
|
@ -0,0 +1,97 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
public class ContextHandler : IContextHandler
|
||||
{
|
||||
private Dictionary<ulong, ContextStore> _store;
|
||||
private readonly IDiscordClient _client;
|
||||
private readonly CommandService _commandService;
|
||||
private readonly IServiceProvider _servicesProvider;
|
||||
|
||||
public ContextHandler(IDiscordClient client, CommandService commandService, IServiceProvider servicesProvider)
|
||||
{
|
||||
_store = new Dictionary<ulong, ContextStore>();
|
||||
_commandService = commandService;
|
||||
_servicesProvider = servicesProvider;
|
||||
_client = client;
|
||||
}
|
||||
|
||||
public ContextReference HasContext(IUserMessage message)
|
||||
{
|
||||
if (_store.ContainsKey(message.Author.Id)) return ContextReference.User;
|
||||
|
||||
if (_store.ContainsKey(message.Channel.Id)) return ContextReference.Channel;
|
||||
|
||||
return ContextReference.None;
|
||||
}
|
||||
|
||||
public void SaveContext(ContextReference type, ICommandContext context, string commandName)
|
||||
{
|
||||
var contextStore = new ContextStore()
|
||||
{
|
||||
CommandName = commandName
|
||||
};
|
||||
var id = GetId(type, context.Message);
|
||||
_store.Add(id, contextStore);
|
||||
}
|
||||
|
||||
public async void ExecuteOnContext(ContextReference type, IUserMessage message)
|
||||
{
|
||||
var id = GetId(type, message);
|
||||
var obj = _store[id];
|
||||
var context = new CommandContext(_client, message);
|
||||
var rest = await _commandService.ExecuteAsync(context, $"{obj.CommandSearch} {context.Message?.Content}", _servicesProvider);
|
||||
if (!rest.IsSuccess)
|
||||
{
|
||||
await context.Channel.SendMessageAsync(rest.ErrorReason);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearContext(ulong id)
|
||||
{
|
||||
_store.Remove(id);
|
||||
}
|
||||
|
||||
private ulong GetId(ContextReference type, IUserMessage message)
|
||||
{
|
||||
if (type == ContextReference.Channel)
|
||||
{
|
||||
return message.Author.Id;
|
||||
}
|
||||
|
||||
if (type == ContextReference.User)
|
||||
{
|
||||
return message.Author.Id;
|
||||
}
|
||||
|
||||
throw new Exception("No Context Object Found");
|
||||
}
|
||||
|
||||
private class ContextStore
|
||||
{
|
||||
public string CommandName { get; set; }
|
||||
public string CommandSearch => $"{CommandName} ctx";
|
||||
}
|
||||
}
|
||||
|
||||
public enum ContextReference
|
||||
{
|
||||
User,
|
||||
Channel,
|
||||
None
|
||||
}
|
||||
|
||||
public interface IContextHandler
|
||||
{
|
||||
ContextReference HasContext(IUserMessage message);
|
||||
void SaveContext(ContextReference type, ICommandContext context, string commandName);
|
||||
void ExecuteOnContext(ContextReference type, IUserMessage message);
|
||||
void ClearContext(ulong id);
|
||||
}
|
||||
}
|
130
Geekbot.net/Commands/Dice.cs
Normal file
130
Geekbot.net/Commands/Dice.cs
Normal file
|
@ -0,0 +1,130 @@
|
|||
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
|
||||
{
|
||||
public class Dice : ModuleBase
|
||||
{
|
||||
private readonly Random _rnd;
|
||||
|
||||
public Dice(Random RandomClient)
|
||||
{
|
||||
_rnd = RandomClient;
|
||||
}
|
||||
|
||||
[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<DiceTypeDto>();
|
||||
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 > 120))
|
||||
{
|
||||
await ReplyAsync("A dice can't have more than 120 sides");
|
||||
return;
|
||||
}
|
||||
|
||||
var rep = new StringBuilder();
|
||||
rep.AppendLine($":game_die: {Context.User.Mention}");
|
||||
rep.Append("**Result:** ");
|
||||
var resultStrings = new List<string>();
|
||||
var total = 0;
|
||||
var extraText = "";
|
||||
foreach (var dice in dices)
|
||||
{
|
||||
var results = new List<int>();
|
||||
for (var i = 0; i < dice.times; i++)
|
||||
{
|
||||
var roll = _rnd.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();
|
||||
}
|
||||
}
|
||||
|
||||
internal class DiceTypeDto
|
||||
{
|
||||
public string diceType { get; set; }
|
||||
public int times { get; set; }
|
||||
public int sides { get; set; }
|
||||
public int mod { get; set; }
|
||||
}
|
||||
}
|
58
Geekbot.net/Commands/Dog.cs
Normal file
58
Geekbot.net/Commands/Dog.cs
Normal file
|
@ -0,0 +1,58 @@
|
|||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Geekbot.net.Lib;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
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<DogResponse>(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);
|
||||
}
|
||||
}
|
||||
|
||||
private class DogResponse
|
||||
{
|
||||
public string url { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
60
Geekbot.net/Commands/EightBall.cs
Normal file
60
Geekbot.net/Commands/EightBall.cs
Normal file
|
@ -0,0 +1,60 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
using Geekbot.net.Lib;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
public class EightBall : ModuleBase
|
||||
{
|
||||
private readonly IErrorHandler _errorHandler;
|
||||
private readonly Random _rnd;
|
||||
|
||||
public EightBall(Random RandomClient, IErrorHandler errorHandler)
|
||||
{
|
||||
_rnd = RandomClient;
|
||||
_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<string>
|
||||
{
|
||||
"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 = _rnd.Next(replies.Count);
|
||||
await ReplyAsync(replies[answer]);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_errorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +1,29 @@
|
|||
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;
|
||||
|
||||
namespace Geekbot.Bot.Commands.Utils
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
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 +32,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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
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()
|
||||
{
|
41
Geekbot.net/Commands/Gdq.cs
Normal file
41
Geekbot.net/Commands/Gdq.cs
Normal file
|
@ -0,0 +1,41 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Geekbot.net.Lib;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
102
Geekbot.net/Commands/Google.cs
Normal file
102
Geekbot.net/Commands/Google.cs
Normal file
|
@ -0,0 +1,102 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.Net;
|
||||
using Geekbot.net.Lib;
|
||||
using Newtonsoft.Json;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
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 = Utf8Json.JsonSerializer.Deserialize<GoogleKGApiResponse>(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.detailedDescription?.url)) eb.WithUrl(data.detailedDescription.url);
|
||||
if(!string.IsNullOrEmpty(data.detailedDescription?.articleBody)) eb.AddField("Details", data.detailedDescription.articleBody);
|
||||
if(!string.IsNullOrEmpty(data.image?.contentUrl)) eb.WithThumbnailUrl(data.image.contentUrl);
|
||||
|
||||
await ReplyAsync("", false, eb.Build());
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_errorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
|
||||
public class GoogleKGApiResponse
|
||||
{
|
||||
public List<GoogleKGApiElement> itemListElement { get; set; }
|
||||
|
||||
public class GoogleKGApiElement
|
||||
{
|
||||
public GoogleKGApiResult result { get; set; }
|
||||
public double resultScore { get; set; }
|
||||
}
|
||||
|
||||
public class GoogleKGApiResult
|
||||
{
|
||||
public string name { get; set; }
|
||||
public string description { get; set; }
|
||||
public GoogleKGApiImage image { get; set; }
|
||||
public GoogleKGApiDetailed detailedDescription { get; set; }
|
||||
}
|
||||
|
||||
public class GoogleKGApiImage
|
||||
{
|
||||
public string contentUrl { get; set; }
|
||||
public string url { get; set; }
|
||||
}
|
||||
|
||||
public class GoogleKGApiDetailed
|
||||
{
|
||||
public string articleBody { get; set; }
|
||||
public string url { get; set; }
|
||||
public string license { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,32 +3,28 @@ 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 StackExchange.Redis;
|
||||
|
||||
namespace Geekbot.Bot.Commands.User
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
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()
|
||||
public async Task getInfo()
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -41,10 +37,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,8 +48,15 @@ namespace Geekbot.Bot.Commands.User
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await _errorHandler.HandleCommandException(e, Context);
|
||||
_errorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
|
||||
public static string FirstCharToUpper(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
throw new ArgumentException("ARGH!");
|
||||
return input.First().ToString().ToUpper() + input.Substring(1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +1,12 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Geekbot.Core;
|
||||
using Geekbot.Core.ErrorHandling;
|
||||
using Geekbot.net.Lib;
|
||||
|
||||
namespace Geekbot.Bot.Commands.Utils
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
public class Help : TransactionModuleBase
|
||||
public class Help : ModuleBase
|
||||
{
|
||||
private readonly IErrorHandler _errorHandler;
|
||||
|
||||
|
@ -18,6 +16,7 @@ namespace Geekbot.Bot.Commands.Utils
|
|||
}
|
||||
|
||||
[Command("help", RunMode = RunMode.Async)]
|
||||
[Remarks(CommandCategories.Helpers)]
|
||||
[Summary("List all Commands")]
|
||||
public async Task GetHelp()
|
||||
{
|
||||
|
@ -27,13 +26,12 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,26 +5,28 @@ 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 StackExchange.Redis;
|
||||
|
||||
namespace Geekbot.Bot.Commands.Utils
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
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 +34,31 @@ namespace Geekbot.Bot.Commands.Utils
|
|||
{
|
||||
var eb = new EmbedBuilder();
|
||||
|
||||
var appInfo = await _client.GetApplicationInfoAsync();
|
||||
|
||||
eb.WithAuthor(new EmbedAuthorBuilder()
|
||||
.WithIconUrl(appInfo.IconUrl)
|
||||
.WithName($"{Constants.Name} V{Constants.BotVersion()}"));
|
||||
.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 +69,7 @@ namespace Geekbot.Bot.Commands.Utils
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await _errorHandler.HandleCommandException(e, Context);
|
||||
_errorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
}
|
129
Geekbot.net/Commands/Karma.cs
Normal file
129
Geekbot.net/Commands/Karma.cs
Normal file
|
@ -0,0 +1,129 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Geekbot.net.Lib;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (string.IsNullOrEmpty(dateTimeOffsetString))
|
||||
return DateTimeOffset.Now.Subtract(new TimeSpan(7, 18, 0, 0));
|
||||
return 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";
|
||||
}
|
||||
}
|
||||
}
|
88
Geekbot.net/Commands/MagicTheGathering.cs
Normal file
88
Geekbot.net/Commands/MagicTheGathering.cs
Normal file
|
@ -0,0 +1,88 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Geekbot.net.Lib;
|
||||
using MtgApiManager.Lib.Service;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
public class Magicthegathering : ModuleBase
|
||||
{
|
||||
private readonly IErrorHandler _errorHandler;
|
||||
|
||||
public Magicthegathering(IErrorHandler errorHandler)
|
||||
{
|
||||
_errorHandler = errorHandler;
|
||||
}
|
||||
|
||||
[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", 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", 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<string> colors)
|
||||
{
|
||||
var color = colors.FirstOrDefault();
|
||||
switch (color)
|
||||
{
|
||||
case "Black":
|
||||
return new Color(177, 171, 170);
|
||||
case "White":
|
||||
return new Color(255, 252, 214);
|
||||
case "Blue":
|
||||
return new Color(156, 189, 204);
|
||||
case "Red":
|
||||
return new Color(204, 156, 140);
|
||||
case "Green":
|
||||
return new Color(147, 181, 159);
|
||||
default:
|
||||
return new Color(255, 252, 214);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
87
Geekbot.net/Commands/Mod.cs
Normal file
87
Geekbot.net/Commands/Mod.cs
Normal file
|
@ -0,0 +1,87 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using Geekbot.net.Lib;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
[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 to give {user.Username} that role");
|
||||
}
|
||||
}
|
||||
|
||||
[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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
131
Geekbot.net/Commands/Overwatch.cs
Normal file
131
Geekbot.net/Commands/Overwatch.cs
Normal file
|
@ -0,0 +1,131 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Geekbot.net.Lib;
|
||||
using OverwatchAPI;
|
||||
using OverwatchAPI.Config;
|
||||
using Serilog;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
[Group("ow")]
|
||||
public class Overwatch : ModuleBase
|
||||
{
|
||||
private readonly IErrorHandler _errorHandler;
|
||||
private readonly IUserRepository _userRepository;
|
||||
|
||||
public Overwatch(IErrorHandler errorHandler, IDatabase redis, 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<EmbedBuilder> createProfile(string battletag)
|
||||
{
|
||||
var owConfig = new OverwatchConfig.Builder().WithRegions(Region.Eu).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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
117
Geekbot.net/Commands/Owner.cs
Normal file
117
Geekbot.net/Commands/Owner.cs
Normal file
|
@ -0,0 +1,117 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using Geekbot.net.Lib;
|
||||
using Serilog;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
[Group("owner")]
|
||||
[RequireUserPermission(GuildPermission.Administrator)]
|
||||
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)
|
||||
{
|
||||
var botOwner = Context.Guild.GetUserAsync(ulong.Parse(_redis.StringGet("botOwner"))).Result;
|
||||
if (!Context.User.Id.ToString().Equals(botOwner.Id.ToString()))
|
||||
{
|
||||
await ReplyAsync(
|
||||
$"Sorry, only the botowner can do this ({botOwner.Username}#{botOwner.Discriminator})");
|
||||
return;
|
||||
}
|
||||
|
||||
_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)
|
||||
{
|
||||
var botOwner = Context.Guild.GetUserAsync(ulong.Parse(_redis.StringGet("botOwner"))).Result;
|
||||
if (!Context.User.Id.ToString().Equals(botOwner.Id.ToString()))
|
||||
{
|
||||
await ReplyAsync(
|
||||
$"Sorry, only the botowner can do this ({botOwner.Username}#{botOwner.Discriminator})");
|
||||
return;
|
||||
}
|
||||
|
||||
_redis.StringSet("Game", key);
|
||||
await _client.SetGameAsync(key);
|
||||
_logger.Information("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()
|
||||
{
|
||||
try
|
||||
{
|
||||
var botOwner = Context.Guild.GetUserAsync(ulong.Parse(_redis.StringGet("botOwner"))).Result;
|
||||
if (!Context.User.Id.ToString().Equals(botOwner.Id.ToString()))
|
||||
{
|
||||
await ReplyAsync(
|
||||
$"Sorry, only the botowner can do this ({botOwner.Username}#{botOwner.Discriminator})");
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
await ReplyAsync(
|
||||
$"Sorry, only the botowner can do this");
|
||||
return;
|
||||
}
|
||||
|
||||
var success = 0;
|
||||
var failed = 0;
|
||||
try
|
||||
{
|
||||
_logger.Warning("UserRepository", "Populating User Repositry");
|
||||
await ReplyAsync("Starting Population of User Repository");
|
||||
foreach (var guild in _client.Guilds)
|
||||
{
|
||||
_logger.Information("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("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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
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!");
|
|
@ -3,14 +3,12 @@ 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 PokeAPI;
|
||||
|
||||
namespace Geekbot.Bot.Commands.Games
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
public class Pokedex : TransactionModuleBase
|
||||
public class Pokedex : ModuleBase
|
||||
{
|
||||
private readonly IErrorHandler _errorHandler;
|
||||
|
||||
|
@ -20,8 +18,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
|
||||
{
|
||||
|
@ -37,38 +36,38 @@ namespace Geekbot.Bot.Commands.Games
|
|||
return;
|
||||
}
|
||||
|
||||
var embed = await PokemonEmbedBuilder(pokemon);
|
||||
var embed = await pokemonEmbedBuilder(pokemon);
|
||||
await ReplyAsync("", false, embed.Build());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await _errorHandler.HandleCommandException(e, Context);
|
||||
_errorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<EmbedBuilder> PokemonEmbedBuilder(Pokemon pokemon)
|
||||
private async Task<EmbedBuilder> pokemonEmbedBuilder(Pokemon pokemon)
|
||||
{
|
||||
var eb = new EmbedBuilder();
|
||||
var species = await DataFetcher.GetApiObject<PokemonSpecies>(pokemon.ID);
|
||||
eb.Title = $"#{pokemon.ID} {ToUpper(pokemon.Name)}";
|
||||
eb.Title = $"#{pokemon.ID} {toUpper(pokemon.Name)}";
|
||||
eb.Description = species.FlavorTexts[1].FlavorText;
|
||||
eb.ThumbnailUrl = pokemon.Sprites.FrontMale ?? pokemon.Sprites.FrontFemale;
|
||||
eb.AddInlineField(GetSingularOrPlural(pokemon.Types.Length, "Type"),
|
||||
string.Join(", ", pokemon.Types.Select(t => ToUpper(t.Type.Name))));
|
||||
eb.AddInlineField(GetSingularOrPlural(pokemon.Abilities.Length, "Ability"),
|
||||
string.Join(", ", pokemon.Abilities.Select(t => ToUpper(t.Ability.Name))));
|
||||
eb.AddInlineField(getSingularOrPlural(pokemon.Types.Length, "Type"),
|
||||
string.Join(", ", pokemon.Types.Select(t => toUpper(t.Type.Name))));
|
||||
eb.AddInlineField(getSingularOrPlural(pokemon.Abilities.Length, "Ability"),
|
||||
string.Join(", ", pokemon.Abilities.Select(t => toUpper(t.Ability.Name))));
|
||||
eb.AddInlineField("Height", pokemon.Height);
|
||||
eb.AddInlineField("Weight", pokemon.Mass);
|
||||
return eb;
|
||||
}
|
||||
|
||||
private string GetSingularOrPlural(int lenght, string word)
|
||||
private string getSingularOrPlural(int lenght, string word)
|
||||
{
|
||||
if (lenght == 1) return word;
|
||||
return word.EndsWith("y") ? $"{word.Remove(word.Length - 1)}ies" : $"{word}s";
|
||||
}
|
||||
|
||||
private string ToUpper(string s)
|
||||
private string toUpper(string s)
|
||||
{
|
||||
if (string.IsNullOrEmpty(s)) return string.Empty;
|
||||
return char.ToUpper(s[0]) + s.Substring(1);
|
191
Geekbot.net/Commands/Poll.cs
Normal file
191
Geekbot.net/Commands/Poll.cs
Normal file
|
@ -0,0 +1,191 @@
|
|||
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 Newtonsoft.Json;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
[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 PollData
|
||||
{
|
||||
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 PollData GetCurrentPoll()
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentPoll = _redis.HashGet($"{Context.Guild.Id}:Polls", Context.Channel.Id);
|
||||
return JsonConvert.DeserializeObject<PollData>(currentPoll.ToString());
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new PollData();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<PollResult>> getPollResults(PollData poll)
|
||||
{
|
||||
var message = (IUserMessage) await Context.Channel.GetMessageAsync(poll.MessageId);
|
||||
var results = new List<PollResult>();
|
||||
foreach (var r in message.Reactions)
|
||||
try
|
||||
{
|
||||
var option = int.Parse(r.Key.Name.ToCharArray()[0].ToString());
|
||||
var result = new PollResult
|
||||
{
|
||||
Option = poll.Options[option - 1],
|
||||
VoteCount = r.Value.ReactionCount
|
||||
};
|
||||
results.Add(result);
|
||||
}
|
||||
catch {}
|
||||
|
||||
results.Sort((x, y) => y.VoteCount.CompareTo(x.VoteCount));
|
||||
return results;
|
||||
}
|
||||
|
||||
private class PollData
|
||||
{
|
||||
public ulong Creator { get; set; }
|
||||
public ulong MessageId { get; set; }
|
||||
public bool IsFinshed { get; set; }
|
||||
public string Question { get; set; }
|
||||
public List<string> Options { get; set; }
|
||||
}
|
||||
|
||||
private class PollResult
|
||||
{
|
||||
public string Option { get; set; }
|
||||
public int VoteCount { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
231
Geekbot.net/Commands/Quote.cs
Normal file
231
Geekbot.net/Commands/Quote.cs
Normal file
|
@ -0,0 +1,231 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Geekbot.net.Lib;
|
||||
using Newtonsoft.Json;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
[Group("quote")]
|
||||
public class Quote : ModuleBase
|
||||
{
|
||||
private readonly IErrorHandler _errorHandler;
|
||||
private readonly IDatabase _redis;
|
||||
|
||||
public Quote(IDatabase redis, IErrorHandler errorHandler, Random random)
|
||||
{
|
||||
_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");
|
||||
var randomNumber = new Random().Next(randomQuotes.Length - 1);
|
||||
var randomQuote = randomQuotes[randomNumber];
|
||||
var quote = JsonConvert.DeserializeObject<QuoteObject>(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);
|
||||
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);
|
||||
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<QuoteObject>(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<IMessage> getLastMessageByUser(IUser user)
|
||||
{
|
||||
var list = Context.Channel.GetMessagesAsync().Flatten();
|
||||
await list;
|
||||
return list.Result
|
||||
.First(msg => msg.Author.Id == user.Id
|
||||
&& msg.Embeds.Count == 0
|
||||
&& msg.Id != Context.Message.Id
|
||||
&& !msg.Content.ToLower().StartsWith("!"));
|
||||
}
|
||||
|
||||
private EmbedBuilder quoteBuilder(QuoteObject quote, int id = 0)
|
||||
{
|
||||
var user = Context.Client.GetUserAsync(quote.userId).Result;
|
||||
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 QuoteObject createQuoteObject(IMessage message)
|
||||
{
|
||||
string image;
|
||||
try
|
||||
{
|
||||
image = message.Attachments.First().Url;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
image = null;
|
||||
}
|
||||
|
||||
return new QuoteObject
|
||||
{
|
||||
userId = message.Author.Id,
|
||||
time = message.Timestamp.DateTime,
|
||||
quote = message.Content,
|
||||
image = image
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class QuoteObject
|
||||
{
|
||||
public ulong userId { get; set; }
|
||||
public string quote { get; set; }
|
||||
public DateTime time { get; set; }
|
||||
public string image { get; set; }
|
||||
}
|
||||
}
|
58
Geekbot.net/Commands/RandomAnimals.cs
Normal file
58
Geekbot.net/Commands/RandomAnimals.cs
Normal file
|
@ -0,0 +1,58 @@
|
|||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
using Geekbot.net.Lib;
|
||||
using Geekbot.net.Lib.Media;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
public class RandomAnimals : ModuleBase
|
||||
{
|
||||
private readonly IMediaProvider _mediaProvider;
|
||||
|
||||
public RandomAnimals(IMediaProvider mediaProvider)
|
||||
{
|
||||
_mediaProvider = mediaProvider;
|
||||
}
|
||||
|
||||
[Command("panda", RunMode = RunMode.Async)]
|
||||
[Remarks(CommandCategories.Randomness)]
|
||||
[Summary("Get a random panda image")]
|
||||
public async Task panda()
|
||||
{
|
||||
await ReplyAsync(_mediaProvider.getPanda());
|
||||
}
|
||||
|
||||
[Command("croissant", RunMode = RunMode.Async)]
|
||||
[Alias("gipfeli")]
|
||||
[Remarks(CommandCategories.Randomness)]
|
||||
[Summary("Get a random croissant image")]
|
||||
public async Task croissant()
|
||||
{
|
||||
await ReplyAsync(_mediaProvider.getCrossant());
|
||||
}
|
||||
|
||||
[Command("pumpkin", RunMode = RunMode.Async)]
|
||||
[Remarks(CommandCategories.Randomness)]
|
||||
[Summary("Get a random pumpkin image")]
|
||||
public async Task pumpkin()
|
||||
{
|
||||
await ReplyAsync(_mediaProvider.getPumpkin());
|
||||
}
|
||||
|
||||
[Command("squirrel", RunMode = RunMode.Async)]
|
||||
[Remarks(CommandCategories.Randomness)]
|
||||
[Summary("Get a random squirrel image")]
|
||||
public async Task squirrel()
|
||||
{
|
||||
await ReplyAsync(_mediaProvider.getSquirrel());
|
||||
}
|
||||
|
||||
[Command("turtle", RunMode = RunMode.Async)]
|
||||
[Remarks(CommandCategories.Randomness)]
|
||||
[Summary("Get a random turtle image")]
|
||||
public async Task turtle()
|
||||
{
|
||||
await ReplyAsync(_mediaProvider.getTurtle());
|
||||
}
|
||||
}
|
||||
}
|
145
Geekbot.net/Commands/Rank.cs
Normal file
145
Geekbot.net/Commands/Rank.cs
Normal file
|
@ -0,0 +1,145 @@
|
|||
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 Serilog;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
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}");
|
||||
var sortedList = messageList.OrderByDescending(e => e.Value).ToList();
|
||||
var guildMessages = (int) sortedList.First().Value;
|
||||
sortedList.Remove(sortedList.Single(e => e.Name.ToString().Equals(_client.CurrentUser.Id.ToString())));
|
||||
if (type == "Messages") sortedList.RemoveAt(0);
|
||||
|
||||
var highscoreUsers = new Dictionary<RankUserPolyfill, int>();
|
||||
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 RankUserPolyfill
|
||||
{
|
||||
Username = guildUser.Username,
|
||||
Discriminator = guildUser.Discriminator
|
||||
}, (int) user.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
highscoreUsers.Add(new RankUserPolyfill
|
||||
{
|
||||
Id = user.Name
|
||||
}, (int) user.Value);
|
||||
failedToRetrieveUser = true;
|
||||
}
|
||||
|
||||
listLimiter++;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Warning("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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class RankUserPolyfill
|
||||
{
|
||||
public string Username { get; set; }
|
||||
public string Discriminator { get; set; }
|
||||
public string Id { get; set; }
|
||||
}
|
||||
}
|
146
Geekbot.net/Commands/Role.cs
Normal file
146
Geekbot.net/Commands/Role.cs
Normal file
|
@ -0,0 +1,146 @@
|
|||
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 StackExchange.Redis;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
[Group("role")]
|
||||
public class Role : ModuleBase
|
||||
{
|
||||
private readonly IErrorHandler _errorHandler;
|
||||
private readonly IDatabase _redis;
|
||||
|
||||
public Role(IErrorHandler errorHandler, IDatabase redis)
|
||||
{
|
||||
_errorHandler = errorHandler;
|
||||
_redis = redis;
|
||||
}
|
||||
|
||||
[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.Administrator)]
|
||||
[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.Administrator)]
|
||||
[Command("remove", RunMode = RunMode.Async)]
|
||||
[Remarks(CommandCategories.Admin)]
|
||||
[Summary("Remove a role from the whitelist.")]
|
||||
public async Task removeRole([Summary("roleNickname")] string roleName)
|
||||
{
|
||||
try
|
||||
{
|
||||
_redis.HashDelete($"{Context.Guild.Id}:RoleWhitelist", roleName);
|
||||
await ReplyAsync($"Removed {roleName} from the whitelist");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_errorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
64
Geekbot.net/Commands/Roll.cs
Normal file
64
Geekbot.net/Commands/Roll.cs
Normal file
|
@ -0,0 +1,64 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
using Geekbot.net.Lib;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
public class Roll : ModuleBase
|
||||
{
|
||||
private readonly IErrorHandler _errorHandler;
|
||||
private readonly IDatabase _redis;
|
||||
private readonly Random _rnd;
|
||||
private readonly ITranslationHandler _translation;
|
||||
|
||||
public Roll(IDatabase redis, Random RandomClient, IErrorHandler errorHandler, ITranslationHandler translation)
|
||||
{
|
||||
_redis = redis;
|
||||
_rnd = RandomClient;
|
||||
_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 = _rnd.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}:RollsPrevious", Context.Message.Author.Id);
|
||||
if (!prevRoll.IsNullOrEmpty && prevRoll.ToString() == guess.ToString())
|
||||
{
|
||||
await ReplyAsync(string.Format(transDict["NoPrevGuess"], Context.Message.Author.Mention));
|
||||
return;
|
||||
}
|
||||
|
||||
_redis.HashSet($"{Context.Guild.Id}:RollsPrevious",
|
||||
new[] {new HashEntry(Context.Message.Author.Id, guess)});
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
66
Geekbot.net/Commands/Say.cs
Normal file
66
Geekbot.net/Commands/Say.cs
Normal file
|
@ -0,0 +1,66 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Geekbot.net.Lib;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
[Group("say")]
|
||||
public class Say : ModuleBase
|
||||
{
|
||||
private readonly IErrorHandler _errorHandler;
|
||||
private readonly IContextHandler _contextHandler;
|
||||
|
||||
public Say(IErrorHandler errorHandler, IContextHandler contextHandler)
|
||||
{
|
||||
_errorHandler = errorHandler;
|
||||
_contextHandler = contextHandler;
|
||||
}
|
||||
|
||||
[RequireUserPermission(GuildPermission.Administrator)]
|
||||
[Command("record", RunMode = RunMode.Async)]
|
||||
[Remarks(CommandCategories.Admin)]
|
||||
[Summary("Say Something.")]
|
||||
public async Task recordEcho()
|
||||
{
|
||||
try
|
||||
{
|
||||
_contextHandler.SaveContext(ContextReference.User, Context, "say");
|
||||
await ReplyAsync("Recording...");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_errorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
|
||||
[RequireUserPermission(GuildPermission.Administrator)]
|
||||
[Command("ctx", RunMode = RunMode.Async)]
|
||||
[Remarks(CommandCategories.Admin)]
|
||||
[Summary("Say Something.")]
|
||||
public async Task recordEcho([Remainder] [Summary("What?")] string echo)
|
||||
{
|
||||
Console.WriteLine("actually got here...");
|
||||
_contextHandler.ClearContext(Context.User.Id);
|
||||
await Context.Channel.SendMessageAsync(echo);
|
||||
}
|
||||
|
||||
[RequireUserPermission(GuildPermission.Administrator)]
|
||||
[Command("ss", 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
94
Geekbot.net/Commands/Ship.cs
Normal file
94
Geekbot.net/Commands/Ship.cs
Normal file
|
@ -0,0 +1,94 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Geekbot.net.Lib;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
public class Ship : ModuleBase
|
||||
{
|
||||
private readonly IErrorHandler _errorHandler;
|
||||
private readonly IDatabase _redis;
|
||||
private readonly Random _rnd;
|
||||
|
||||
public Ship(IDatabase redis, Random randomClient, IErrorHandler errorHandler)
|
||||
{
|
||||
_redis = redis;
|
||||
_rnd = randomClient;
|
||||
_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 = _rnd.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";
|
||||
if (rate >= 80)
|
||||
return "It's a match";
|
||||
return "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;
|
||||
}
|
||||
}
|
||||
}
|
72
Geekbot.net/Commands/Slap.cs
Normal file
72
Geekbot.net/Commands/Slap.cs
Normal file
|
@ -0,0 +1,72 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Geekbot.net.Lib;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
public class Slap : ModuleBase
|
||||
{
|
||||
private readonly IErrorHandler _errorHandler;
|
||||
private readonly Random _random;
|
||||
private readonly IDatabase _redis;
|
||||
|
||||
public Slap(IErrorHandler errorHandler, Random random, IDatabase redis)
|
||||
{
|
||||
_errorHandler = errorHandler;
|
||||
_random = random;
|
||||
_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<string>()
|
||||
{
|
||||
"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"
|
||||
};
|
||||
|
||||
_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[_random.Next(things.Count - 1)]}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_errorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
72
Geekbot.net/Commands/Stats.cs
Normal file
72
Geekbot.net/Commands/Stats.cs
Normal file
|
@ -0,0 +1,72 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Geekbot.net.Lib;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
87
Geekbot.net/Commands/UrbanDictionary.cs
Normal file
87
Geekbot.net/Commands/UrbanDictionary.cs
Normal file
|
@ -0,0 +1,87 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Geekbot.net.Lib;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
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<UrbanResponse>(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));
|
||||
eb.Description = definition.definition;
|
||||
eb.AddField("Example", definition.example ?? "(no example given...)");
|
||||
eb.AddInlineField("Upvotes", definition.thumbs_up);
|
||||
eb.AddInlineField("Downvotes", definition.thumbs_down);
|
||||
if (definitions.tags.Length > 0) eb.AddField("Tags", string.Join(", ", definitions.tags));
|
||||
|
||||
await ReplyAsync("", false, eb.Build());
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_errorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
|
||||
private class UrbanResponse
|
||||
{
|
||||
public string[] tags { get; set; }
|
||||
public string result_type { get; set; }
|
||||
public List<UrbanListItem> list { get; set; }
|
||||
}
|
||||
|
||||
private class UrbanListItem
|
||||
{
|
||||
public string definition { get; set; }
|
||||
public string permalink { get; set; }
|
||||
public string thumbs_up { get; set; }
|
||||
public string author { get; set; }
|
||||
public string word { get; set; }
|
||||
public string defid { get; set; }
|
||||
public string current_vote { get; set; }
|
||||
public string example { get; set; }
|
||||
public string thumbs_down { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
102
Geekbot.net/Commands/Voice.cs
Normal file
102
Geekbot.net/Commands/Voice.cs
Normal file
|
@ -0,0 +1,102 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Geekbot.net.Lib;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
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(
|
||||
"User must be in a voice channel, or a voice channel must be passed as an argument.");
|
||||
return;
|
||||
}
|
||||
|
||||
// For the next step with transmitting audio, you would want to pass this Audio Client in to a service.
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
59
Geekbot.net/Commands/Youtube.cs
Normal file
59
Geekbot.net/Commands/Youtube.cs
Normal file
|
@ -0,0 +1,59 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
using Geekbot.net.Lib;
|
||||
using Google.Apis.Services;
|
||||
using Google.Apis.YouTube.v3;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
119
Geekbot.net/Commands/mal.cs
Normal file
119
Geekbot.net/Commands/mal.cs
Normal file
|
@ -0,0 +1,119 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Geekbot.net.Lib;
|
||||
|
||||
namespace Geekbot.net.Commands
|
||||
{
|
||||
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("<br />", "")
|
||||
.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("<br />", "")
|
||||
.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
73
Geekbot.net/Geekbot.net.csproj
Executable file
73
Geekbot.net/Geekbot.net.csproj
Executable file
|
@ -0,0 +1,73 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<ApplicationIcon>derp.ico</ApplicationIcon>
|
||||
<Version>1.1.0</Version>
|
||||
<Company>Pizza and Coffee Studios</Company>
|
||||
<Authors>Pizza and Coffee Studios</Authors>
|
||||
<Description>A Discord bot</Description>
|
||||
<RepositoryUrl>https://github.com/pizzaandcoffee/Geekbot.net</RepositoryUrl>
|
||||
<NoWarn>NU1701</NoWarn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Discord.Net">
|
||||
<Version>1.0.2</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Google.Apis.YouTube.v3" Version="1.29.1.991" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="2.0.0" />
|
||||
<PackageReference Include="MtgApiManager.Lib" Version="1.0.0.2" />
|
||||
<PackageReference Include="MyAnimeListSharp" Version="1.3.4" />
|
||||
<PackageReference Include="Nancy" Version="2.0.0-clinteastwood" />
|
||||
<PackageReference Include="Nancy.Hosting.Self" Version="2.0.0-clinteastwood" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
|
||||
<PackageReference Include="Overwatch.Net" Version="3.0.0" />
|
||||
<PackageReference Include="PokeApi.NET" Version="1.1.0" />
|
||||
<PackageReference Include="Serilog" Version="2.6.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1-dev-00757" />
|
||||
<PackageReference Include="Serilog.Sinks.Literate" Version="3.0.1-dev-00044" />
|
||||
<PackageReference Include="Serilog.Sinks.RollingFile" Version="3.3.1-dev-00771" />
|
||||
<PackageReference Include="Serilog.Sinks.SumoLogic" Version="2.3.0" />
|
||||
<PackageReference Include="SharpRaven" Version="2.2.0" />
|
||||
<PackageReference Include="StackExchange.Redis">
|
||||
<Version>1.2.6</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.3" />
|
||||
<PackageReference Include="System.Runtime.Serialization.Formatters" Version="4.3.0" />
|
||||
<PackageReference Include="System.Runtime.Serialization.Json">
|
||||
<Version>4.3.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Runtime.Serialization.Primitives">
|
||||
<Version>4.3.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Utf8Json" Version="1.3.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Storage\checkEmPics">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Storage\croissant">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Storage\fortunes">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Storage\pandas">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Storage\pumpkin">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Storage\squirrel">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Storage\Translations.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Storage\turtles">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
196
Geekbot.net/Handlers.cs
Normal file
196
Geekbot.net/Handlers.cs
Normal file
|
@ -0,0 +1,196 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using Geekbot.net.Commands;
|
||||
using Geekbot.net.Lib;
|
||||
using Serilog;
|
||||
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 IContextHandler _contextHandler;
|
||||
|
||||
public Handlers(IDiscordClient client, IGeekbotLogger logger, IDatabase redis, IServiceProvider servicesProvider, CommandService commands, IUserRepository userRepository, IContextHandler contextHandler)
|
||||
{
|
||||
_client = client;
|
||||
_logger = logger;
|
||||
_redis = redis;
|
||||
_servicesProvider = servicesProvider;
|
||||
_commands = commands;
|
||||
_userRepository = userRepository;
|
||||
_contextHandler = contextHandler;
|
||||
}
|
||||
|
||||
//
|
||||
// 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.Equals("ping") || lowCaseMsg.StartsWith("ping "))
|
||||
{
|
||||
message.Channel.SendMessageAsync("pong");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
if (lowCaseMsg.StartsWith("hui"))
|
||||
{
|
||||
message.Channel.SendMessageAsync("hui!!!");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var contextType = _contextHandler.HasContext(message);
|
||||
if (contextType != ContextReference.None)
|
||||
{
|
||||
_contextHandler.ExecuteOnContext(contextType, message);
|
||||
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);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error("Geekbot", "Failed to run commands", e);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public Task UpdateStats(SocketMessage message)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (message == null) return Task.CompletedTask;
|
||||
if (message.Channel.Name.StartsWith('@'))
|
||||
{
|
||||
_logger.Information("Message", "DM-Channel - {message.Channel.Name} - {message.Content}");
|
||||
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("Message", message.Content, SimpleConextConverter.ConvertSocketMessage(message));
|
||||
// _logger.Information($"[Message] {channel.Guild.Name} ({channel.Guild.Id}) - {message.Channel} ({message.Channel.Id}) - {message.Author.Username}#{message.Author.Discriminator} ({message.Author.Id}) - {message.Content}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error("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("Geekbot", $"{user.Username} ({user.Id}) joined {user.Guild.Name} ({user.Guild.Id})");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error("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("Geekbot", "Failed to send leave message", e);
|
||||
}
|
||||
_logger.Information("Geekbot", $"{user.Username} ({user.Id}) joined {user.Guild.Name} ({user.Guild.Id})");
|
||||
}
|
||||
|
||||
//
|
||||
// Message Stuff
|
||||
//
|
||||
|
||||
public async Task MessageDeleted(Cacheable<IMessage, ulong> 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("Geekbot", "Failed to send delete message...", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
106
Geekbot.net/Lib/AudioClientCache.cs
Normal file
106
Geekbot.net/Lib/AudioClientCache.cs
Normal file
|
@ -0,0 +1,106 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using Discord.Audio;
|
||||
using Discord.Net;
|
||||
|
||||
namespace Geekbot.net.Lib
|
||||
{
|
||||
public class AudioUtils : IAudioUtils
|
||||
{
|
||||
private string _tempFolderPath;
|
||||
private Dictionary<ulong, IAudioClient> _audioClients;
|
||||
|
||||
public AudioUtils()
|
||||
{
|
||||
_audioClients = new Dictionary<ulong, IAudioClient>();
|
||||
_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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
15
Geekbot.net/Lib/CommandCategories.cs
Normal file
15
Geekbot.net/Lib/CommandCategories.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
namespace Geekbot.net.Lib
|
||||
{
|
||||
public 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";
|
||||
}
|
||||
}
|
9
Geekbot.net/Lib/Constants.cs
Normal file
9
Geekbot.net/Lib/Constants.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Geekbot.net.Lib
|
||||
{
|
||||
public class Constants
|
||||
{
|
||||
public const string Name = "Geekbot";
|
||||
public const double BotVersion = 3.5;
|
||||
public const double ApiVersion = 1;
|
||||
}
|
||||
}
|
99
Geekbot.net/Lib/EmojiConverter.cs
Normal file
99
Geekbot.net/Lib/EmojiConverter.cs
Normal file
|
@ -0,0 +1,99 @@
|
|||
using System.Collections;
|
||||
using System.Text;
|
||||
|
||||
namespace Geekbot.net.Lib
|
||||
{
|
||||
public class EmojiConverter : IEmojiConverter
|
||||
{
|
||||
public string numberToEmoji(int number)
|
||||
{
|
||||
if (number == 10)
|
||||
{
|
||||
return "🔟";
|
||||
}
|
||||
var emojiMap = new string[]
|
||||
{
|
||||
":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();
|
||||
}
|
||||
}
|
||||
|
||||
public interface IEmojiConverter
|
||||
{
|
||||
string numberToEmoji(int number);
|
||||
string textToEmoji(string text);
|
||||
}
|
||||
}
|
92
Geekbot.net/Lib/ErrorHandler.cs
Normal file
92
Geekbot.net/Lib/ErrorHandler.cs
Normal file
|
@ -0,0 +1,92 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using System.Security.Principal;
|
||||
using Discord.Commands;
|
||||
using Discord.Net;
|
||||
using Nancy.Extensions;
|
||||
using Serilog;
|
||||
using SharpRaven;
|
||||
using SharpRaven.Data;
|
||||
using SharpRaven.Utilities;
|
||||
using Utf8Json;
|
||||
|
||||
namespace Geekbot.net.Lib
|
||||
{
|
||||
public class ErrorHandler : IErrorHandler
|
||||
{
|
||||
private readonly IGeekbotLogger _logger;
|
||||
private readonly ITranslationHandler _translation;
|
||||
private readonly IRavenClient _raven;
|
||||
|
||||
public ErrorHandler(IGeekbotLogger logger, ITranslationHandler translation)
|
||||
{
|
||||
_logger = logger;
|
||||
_translation = translation;
|
||||
|
||||
var sentryDsn = Environment.GetEnvironmentVariable("SENTRY");
|
||||
if (!string.IsNullOrEmpty(sentryDsn))
|
||||
{
|
||||
_raven = new RavenClient(sentryDsn);
|
||||
_logger.Information("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);
|
||||
_logger.Error("Geekbot", "An error ocured", e, errorObj);
|
||||
if (!string.IsNullOrEmpty(errorMessage))
|
||||
{
|
||||
Context.Channel.SendMessageAsync(errorString);
|
||||
}
|
||||
|
||||
if (e.Message.Contains("50013")) return;
|
||||
if (e.Message.Contains("50007")) return;
|
||||
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)
|
||||
{
|
||||
_logger.Error("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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public interface IErrorHandler
|
||||
{
|
||||
void HandleCommandException(Exception e, ICommandContext Context, string errorMessage = "def");
|
||||
void HandleHttpException(HttpException e, ICommandContext Context);
|
||||
}
|
||||
}
|
80
Geekbot.net/Lib/GeekbotLogger.cs
Normal file
80
Geekbot.net/Lib/GeekbotLogger.cs
Normal file
|
@ -0,0 +1,80 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using Utf8Json;
|
||||
using Utf8Json.Formatters;
|
||||
using Utf8Json.Resolvers;
|
||||
|
||||
namespace Geekbot.net.Lib
|
||||
{
|
||||
public class GeekbotLogger : IGeekbotLogger
|
||||
{
|
||||
private readonly ILogger _serilog;
|
||||
public GeekbotLogger()
|
||||
{
|
||||
_serilog = LoggerFactory.createLogger();
|
||||
//JsonSerializer.SetDefaultResolver(StandardResolver.AllowPrivateExcludeNullSnakeCase);
|
||||
Information("Geekbot", "Using GeekbotLogger");
|
||||
}
|
||||
|
||||
public void Debug(string source, string message, object extra = null)
|
||||
{
|
||||
HandleLogObject("Debug", source, message, null, extra);
|
||||
}
|
||||
|
||||
public void Information(string source, string message, object extra = null)
|
||||
{
|
||||
HandleLogObject("Information", source, message, null, extra);
|
||||
}
|
||||
|
||||
public void Warning(string source, string message, Exception stackTrace = null, object extra = null)
|
||||
{
|
||||
HandleLogObject("Warning", source, message, stackTrace, extra);
|
||||
}
|
||||
|
||||
public void Error(string source, string message, Exception stackTrace, object extra = null)
|
||||
{
|
||||
HandleLogObject("Error", source, message, stackTrace, extra);
|
||||
}
|
||||
|
||||
private Task HandleLogObject(string type, string source, string message, Exception stackTrace = null, object extra = null)
|
||||
{
|
||||
var logJson = CreateLogObject(type, source, message, null, extra);
|
||||
// fuck serilog
|
||||
_serilog.Information(logJson + "}");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private string CreateLogObject(string type, string source, string message, Exception stackTrace = null, object extra = null)
|
||||
{
|
||||
var logObject = new GeekbotLoggerObject()
|
||||
{
|
||||
Timestamp = DateTime.Now,
|
||||
Type = type,
|
||||
Source = source,
|
||||
Message = message,
|
||||
StackTrace = stackTrace,
|
||||
Extra = extra
|
||||
};
|
||||
return JsonSerializer.ToJsonString(logObject);
|
||||
}
|
||||
}
|
||||
|
||||
public class GeekbotLoggerObject
|
||||
{
|
||||
public DateTime Timestamp { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string Source { get; set; }
|
||||
public string Message { get; set; }
|
||||
public Exception StackTrace { get; set; }
|
||||
public object Extra { get; set; }
|
||||
}
|
||||
|
||||
public interface IGeekbotLogger
|
||||
{
|
||||
void Debug(string source, string message, object extra = null);
|
||||
void Information(string source, string message, object extra = null);
|
||||
void Warning(string source, string message, Exception stackTrace = null, object extra = null);
|
||||
void Error(string source, string message, Exception stackTrace, object extra = null);
|
||||
}
|
||||
}
|
|
@ -2,11 +2,11 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Geekbot.Core.Levels
|
||||
namespace Geekbot.net.Lib
|
||||
{
|
||||
public class LevelCalc : ILevelCalc
|
||||
{
|
||||
private readonly int[] _levels;
|
||||
private int[] _levels;
|
||||
|
||||
public LevelCalc()
|
||||
{
|
||||
|
@ -20,9 +20,20 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
public interface ILevelCalc
|
||||
{
|
||||
int GetLevel(int experience);
|
||||
}
|
||||
}
|
28
Geekbot.net/Lib/LoggerFactory.cs
Normal file
28
Geekbot.net/Lib/LoggerFactory.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using Serilog;
|
||||
using Serilog.Formatting.Json;
|
||||
using Serilog.Sinks.SumoLogic;
|
||||
|
||||
namespace Geekbot.net.Lib
|
||||
{
|
||||
public class LoggerFactory
|
||||
{
|
||||
public static ILogger createLogger()
|
||||
{
|
||||
var loggerCreation = new LoggerConfiguration();
|
||||
var template = "{Message}{NewLine}";
|
||||
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("GEEKBOT_SUMO")))
|
||||
{
|
||||
Console.WriteLine("Logging Geekbot Logs to Sumologic");
|
||||
loggerCreation.WriteTo.SumoLogic(Environment.GetEnvironmentVariable("GEEKBOT_SUMO"),
|
||||
outputTemplate: template);
|
||||
}
|
||||
else
|
||||
{
|
||||
loggerCreation.WriteTo.LiterateConsole(outputTemplate: template);
|
||||
loggerCreation.WriteTo.RollingFile("Logs/geekbot-{Date}.txt", shared: true, outputTemplate: template);
|
||||
}
|
||||
return loggerCreation.CreateLogger();
|
||||
}
|
||||
}
|
||||
}
|
78
Geekbot.net/Lib/MalClient.cs
Normal file
78
Geekbot.net/Lib/MalClient.cs
Normal file
|
@ -0,0 +1,78 @@
|
|||
using System.Threading.Tasks;
|
||||
using MyAnimeListSharp.Auth;
|
||||
using MyAnimeListSharp.Core;
|
||||
using MyAnimeListSharp.Facade.Async;
|
||||
using Serilog;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace Geekbot.net.Lib
|
||||
{
|
||||
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("Geekbot", "Logged in to MAL");
|
||||
return true;
|
||||
}
|
||||
_logger.Debug("Geekbot", "No MAL Credentials Set!");
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
public bool isLoggedIn()
|
||||
{
|
||||
return _credentials != null;
|
||||
}
|
||||
|
||||
public async Task<AnimeEntry> getAnime(string query)
|
||||
{
|
||||
var response = await _animeSearch.SearchDeserializedAsync(query);
|
||||
return response.Entries.Count == 0 ? null : response.Entries[0];
|
||||
}
|
||||
|
||||
public async Task<MangaEntry> getManga(string query)
|
||||
{
|
||||
var response = await _mangaSearch.SearchDeserializedAsync(query);
|
||||
return response.Entries.Count == 0 ? null : response.Entries[0];
|
||||
}
|
||||
}
|
||||
|
||||
public interface IMalClient
|
||||
{
|
||||
bool reloadClient();
|
||||
bool isLoggedIn();
|
||||
Task<AnimeEntry> getAnime(string query);
|
||||
Task<MangaEntry> getManga(string query);
|
||||
}
|
||||
}
|
40
Geekbot.net/Lib/Media/FortunesProvider.cs
Normal file
40
Geekbot.net/Lib/Media/FortunesProvider.cs
Normal file
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Serilog;
|
||||
|
||||
namespace Geekbot.net.Lib.Media
|
||||
{
|
||||
internal class FortunesProvider : IFortunesProvider
|
||||
{
|
||||
private readonly string[] fortuneArray;
|
||||
private readonly Random rnd;
|
||||
private readonly int totalFortunes;
|
||||
|
||||
public FortunesProvider(Random rnd, IGeekbotLogger logger)
|
||||
{
|
||||
var path = Path.GetFullPath("./Storage/fortunes");
|
||||
if (File.Exists(path))
|
||||
{
|
||||
var rawFortunes = File.ReadAllText(path);
|
||||
fortuneArray = rawFortunes.Split("%");
|
||||
totalFortunes = fortuneArray.Length;
|
||||
this.rnd = rnd;
|
||||
logger.Debug("Geekbot", "Loaded {totalFortunes} Fortunes");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Information("Geekbot", $"Fortunes File not found at {path}");
|
||||
}
|
||||
}
|
||||
|
||||
public string GetRandomFortune()
|
||||
{
|
||||
return fortuneArray[rnd.Next(0, totalFortunes)];
|
||||
}
|
||||
}
|
||||
|
||||
public interface IFortunesProvider
|
||||
{
|
||||
string GetRandomFortune();
|
||||
}
|
||||
}
|
117
Geekbot.net/Lib/Media/MediaProvider.cs
Normal file
117
Geekbot.net/Lib/Media/MediaProvider.cs
Normal file
|
@ -0,0 +1,117 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
|
||||
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;
|
||||
|
||||
public MediaProvider(Random rnd, IGeekbotLogger logger)
|
||||
{
|
||||
_random = rnd;
|
||||
_logger = logger;
|
||||
|
||||
logger.Information("Geekbot", "Loading Media Files");
|
||||
|
||||
LoadCheckem();
|
||||
LoadPandas();
|
||||
BakeCroissants();
|
||||
LoadSquirrels();
|
||||
LoadPumpkins();
|
||||
LoadTurtles();
|
||||
}
|
||||
|
||||
private void LoadCheckem()
|
||||
{
|
||||
var rawLinks = File.ReadAllText(Path.GetFullPath("./Storage/checkEmPics"));
|
||||
_checkemImages = rawLinks.Split("\n");
|
||||
_logger.Debug("Geekbot", $"Loaded {_checkemImages.Length} CheckEm Images");
|
||||
}
|
||||
|
||||
private void LoadPandas()
|
||||
{
|
||||
var rawLinks = File.ReadAllText(Path.GetFullPath("./Storage/pandas"));
|
||||
_pandaImages = rawLinks.Split("\n");
|
||||
_logger.Debug("Geekbot", $"Loaded {_pandaImages.Length} Panda Images");
|
||||
}
|
||||
|
||||
private void BakeCroissants()
|
||||
{
|
||||
var rawLinks = File.ReadAllText(Path.GetFullPath("./Storage/croissant"));
|
||||
_croissantImages = rawLinks.Split("\n");
|
||||
_logger.Debug("Geekbot", $"Loaded {_croissantImages.Length} Croissant Images");
|
||||
}
|
||||
|
||||
private void LoadSquirrels()
|
||||
{
|
||||
var rawLinks = File.ReadAllText(Path.GetFullPath("./Storage/squirrel"));
|
||||
_squirrelImages = rawLinks.Split("\n");
|
||||
_logger.Debug("Geekbot", $"Loaded {_squirrelImages.Length} Squirrel Images");
|
||||
}
|
||||
|
||||
private void LoadPumpkins()
|
||||
{
|
||||
var rawLinks = File.ReadAllText(Path.GetFullPath("./Storage/pumpkin"));
|
||||
_pumpkinImages = rawLinks.Split("\n");
|
||||
_logger.Debug("Geekbot", $"Loaded {_pumpkinImages.Length} Pumpkin Images");
|
||||
}
|
||||
|
||||
private void LoadTurtles()
|
||||
{
|
||||
var rawLinks = File.ReadAllText(Path.GetFullPath("./Storage/turtles"));
|
||||
_turtlesImages = rawLinks.Split("\n");
|
||||
_logger.Debug("Geekbot", $"Loaded {_turtlesImages.Length} Turtle 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 interface IMediaProvider
|
||||
{
|
||||
string getCheckem();
|
||||
string getPanda();
|
||||
string getCrossant();
|
||||
string getSquirrel();
|
||||
string getPumpkin();
|
||||
string getTurtle();
|
||||
}
|
||||
}
|
97
Geekbot.net/Lib/SimpleConextConverter.cs
Normal file
97
Geekbot.net/Lib/SimpleConextConverter.cs
Normal file
|
@ -0,0 +1,97 @@
|
|||
using System;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
|
||||
namespace Geekbot.net.Lib
|
||||
{
|
||||
public class SimpleConextConverter
|
||||
{
|
||||
public static MessageDto ConvertContext(ICommandContext context)
|
||||
{
|
||||
return new MessageDto()
|
||||
{
|
||||
Message = new MessageDto.MessageContent()
|
||||
{
|
||||
Content = context.Message.Content,
|
||||
Id = context.Message.Id.ToString(),
|
||||
Attachments = context.Message.Attachments.Count,
|
||||
ChannelMentions = context.Message.MentionedChannelIds.Count,
|
||||
UserMentions = context.Message.MentionedUserIds.Count,
|
||||
RoleMentions = context.Message.MentionedRoleIds.Count
|
||||
},
|
||||
User = new MessageDto.IdAndName()
|
||||
{
|
||||
Id = context.User.Id.ToString(),
|
||||
Name = $"{context.User.Username}#{context.User.Discriminator}"
|
||||
},
|
||||
Guild = new MessageDto.IdAndName()
|
||||
{
|
||||
Id = context.Guild.Id.ToString(),
|
||||
Name = context.Guild.Name
|
||||
},
|
||||
Channel = new MessageDto.IdAndName()
|
||||
{
|
||||
Id = context.Channel.Id.ToString(),
|
||||
Name = context.Channel.Name
|
||||
}
|
||||
};
|
||||
}
|
||||
public static MessageDto ConvertSocketMessage(SocketMessage message)
|
||||
{
|
||||
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,
|
||||
UserMentions = message.MentionedUsers.Count,
|
||||
RoleMentions = message.MentionedRoles.Count
|
||||
},
|
||||
User = new MessageDto.IdAndName()
|
||||
{
|
||||
Id = message.Author.Id.ToString(),
|
||||
Name = $"{message.Author.Username}#{message.Author.Discriminator}"
|
||||
},
|
||||
Guild = new MessageDto.IdAndName()
|
||||
{
|
||||
Id = channel.Guild.Id.ToString(),
|
||||
Name = channel.Guild.Name
|
||||
},
|
||||
Channel = new MessageDto.IdAndName()
|
||||
{
|
||||
Id = channel.Id.ToString(),
|
||||
Name = channel.Name
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public class MessageDto
|
||||
{
|
||||
public MessageContent Message { get; set; }
|
||||
public IdAndName User { get; set; }
|
||||
public IdAndName Guild { get; set; }
|
||||
public IdAndName Channel { get; set; }
|
||||
|
||||
public class MessageContent
|
||||
{
|
||||
public string Content { get; set; }
|
||||
public string Id { get; set; }
|
||||
public int Attachments { get; set; }
|
||||
public int ChannelMentions { get; set; }
|
||||
public int UserMentions { get; set; }
|
||||
public int RoleMentions { get; set; }
|
||||
}
|
||||
|
||||
public class IdAndName
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
164
Geekbot.net/Lib/TranslationHandler.cs
Normal file
164
Geekbot.net/Lib/TranslationHandler.cs
Normal file
|
@ -0,0 +1,164 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using Serilog;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace Geekbot.net.Lib
|
||||
{
|
||||
public class TranslationHandler : ITranslationHandler
|
||||
{
|
||||
private readonly IGeekbotLogger _logger;
|
||||
private readonly IDatabase _redis;
|
||||
private Dictionary<string, Dictionary<string, Dictionary<string, string>>> _translations;
|
||||
private Dictionary<ulong, string> _serverLanguages;
|
||||
private List<string> _supportedLanguages;
|
||||
|
||||
public TranslationHandler(IReadOnlyCollection<SocketGuild> clientGuilds, IDatabase redis, IGeekbotLogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_redis = redis;
|
||||
_logger.Information("Geekbot", "Loading Translations");
|
||||
LoadTranslations();
|
||||
LoadServerLanguages(clientGuilds);
|
||||
}
|
||||
|
||||
private void LoadTranslations()
|
||||
{
|
||||
try
|
||||
{
|
||||
var translationFile = File.ReadAllText(Path.GetFullPath("./Storage/Translations.json"));
|
||||
var rawTranslations = Utf8Json.JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, Dictionary<string, string>>>>(translationFile);
|
||||
var sortedPerLanguage = new Dictionary<string, Dictionary<string, Dictionary<string, string>>>();
|
||||
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<string, Dictionary<string, string>>();
|
||||
var strDict = new Dictionary<string, string>();
|
||||
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<string, string>();
|
||||
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<string>();
|
||||
foreach (var lang in sortedPerLanguage)
|
||||
{
|
||||
_supportedLanguages.Add(lang.Key);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error("Geekbot", "Failed to load Translations", e);
|
||||
Environment.Exit(110);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadServerLanguages(IReadOnlyCollection<SocketGuild> clientGuilds)
|
||||
{
|
||||
_serverLanguages = new Dictionary<ulong, string>();
|
||||
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("Geekbot", $"No translation found for {command} - {stringName}");
|
||||
}
|
||||
return translation;
|
||||
}
|
||||
|
||||
public Dictionary<string, string> 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("Geekbot", "lol nope", e);
|
||||
return new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<string, string> GetDict(ICommandContext context, string command)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _translations[_serverLanguages[context.Guild.Id]][command];
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error("Geekbot", "lol nope", e);
|
||||
return new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
|
||||
public bool SetLanguage(ulong guildId, string language)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_supportedLanguages.Contains(language)) return false;
|
||||
_redis.HashSet($"{guildId}:Settings", new HashEntry[]{ new HashEntry("Language", language), });
|
||||
_serverLanguages[guildId] = language;
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error("Geekbot", "Error while changing language", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> GetSupportedLanguages()
|
||||
{
|
||||
return _supportedLanguages;
|
||||
}
|
||||
}
|
||||
|
||||
public interface ITranslationHandler
|
||||
{
|
||||
string GetString(ulong guildId, string command, string stringName);
|
||||
Dictionary<string, string> GetDict(ICommandContext context);
|
||||
Dictionary<string, string> GetDict(ICommandContext context, string command);
|
||||
bool SetLanguage(ulong guildId, string language);
|
||||
List<string> GetSupportedLanguages();
|
||||
}
|
||||
}
|
146
Geekbot.net/Lib/UserRepository.cs
Normal file
146
Geekbot.net/Lib/UserRepository.cs
Normal file
|
@ -0,0 +1,146 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.WebSocket;
|
||||
using Serilog;
|
||||
using StackExchange.Redis;
|
||||
using Utf8Json;
|
||||
|
||||
namespace Geekbot.net.Lib
|
||||
{
|
||||
public class UserRepository : IUserRepository
|
||||
{
|
||||
private readonly IDatabase _redis;
|
||||
private readonly IGeekbotLogger _logger;
|
||||
public UserRepository(IDatabase redis, IGeekbotLogger logger)
|
||||
{
|
||||
_redis = redis;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Task<bool> 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<string>();
|
||||
if (!savedUser.UsedNames.Contains(user.Username))
|
||||
{
|
||||
savedUser.UsedNames.Add(user.Username);
|
||||
}
|
||||
Store(savedUser);
|
||||
|
||||
_logger.Information("UserRepository", "Updated User", savedUser);
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Warning("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 HashEntry[]
|
||||
{
|
||||
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 (int 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<List<string>>(a.Value.ToString()) ?? new List<string>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return dto;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Warning("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 HashEntry[]
|
||||
{
|
||||
new HashEntry(setting, value)
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
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<string> UsedNames { get; set; }
|
||||
}
|
||||
|
||||
public interface IUserRepository
|
||||
{
|
||||
Task<bool> Update(SocketUser user);
|
||||
UserRepositoryUser Get(ulong userId);
|
||||
string getUserSetting(ulong userId, string setting);
|
||||
bool saveUserSetting(ulong userId, string setting, string value);
|
||||
}
|
||||
}
|
0
Geekbot.net/Logs/.keep
Executable file
0
Geekbot.net/Logs/.keep
Executable file
243
Geekbot.net/Program.cs
Executable file
243
Geekbot.net/Program.cs
Executable file
|
@ -0,0 +1,243 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using Geekbot.net.Commands;
|
||||
using Geekbot.net.Lib;
|
||||
using Geekbot.net.Lib.Media;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Nancy.Hosting.Self;
|
||||
using Serilog;
|
||||
using StackExchange.Redis;
|
||||
|
||||
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 IGeekbotLogger logger;
|
||||
private IUserRepository userRepository;
|
||||
private string[] args;
|
||||
private bool firstStart = false;
|
||||
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
var logo = new StringBuilder();
|
||||
logo.AppendLine(@" ____ _____ _____ _ ______ ___ _____");
|
||||
logo.AppendLine(@" / ___| ____| ____| |/ / __ ) / _ \\_ _|");
|
||||
logo.AppendLine(@"| | _| _| | _| | ' /| _ \| | | || |");
|
||||
logo.AppendLine(@"| |_| | |___| |___| . \| |_) | |_| || |");
|
||||
logo.AppendLine(@" \____|_____|_____|_|\_\____/ \___/ |_|");
|
||||
logo.AppendLine("=========================================");
|
||||
Console.WriteLine(logo.ToString());
|
||||
var logger = new GeekbotLogger();
|
||||
logger.Information("Geekbot", "Starting...");
|
||||
try
|
||||
{
|
||||
new Program().MainAsync(args, logger).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Geekbot", "RIP", e);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task MainAsync(string[] args, IGeekbotLogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.args = args;
|
||||
logger.Information("Geekbot", "Initing Stuff");
|
||||
|
||||
client = new DiscordSocketClient(new DiscordSocketConfig
|
||||
{
|
||||
LogLevel = LogSeverity.Verbose,
|
||||
MessageCacheSize = 1000
|
||||
});
|
||||
client.Log += DiscordLogger;
|
||||
commands = new CommandService();
|
||||
|
||||
try
|
||||
{
|
||||
var redisMultiplexer = ConnectionMultiplexer.Connect("127.0.0.1:6379");
|
||||
redis = redisMultiplexer.GetDatabase(6);
|
||||
logger.Information("Redis", $"Connected to db {redis.Database}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Redis", "Redis Connection Failed", e);
|
||||
Environment.Exit(102);
|
||||
}
|
||||
|
||||
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 randomClient = new Random();
|
||||
var fortunes = new FortunesProvider(randomClient, logger);
|
||||
var mediaProvider = new MediaProvider(randomClient, logger);
|
||||
var malClient = new MalClient(redis, logger);
|
||||
var levelCalc = new LevelCalc();
|
||||
var emojiConverter = new EmojiConverter();
|
||||
var audioUtils = new AudioUtils();
|
||||
|
||||
services.AddSingleton(redis);
|
||||
services.AddSingleton<IGeekbotLogger>(logger);
|
||||
services.AddSingleton<IUserRepository>(userRepository);
|
||||
services.AddSingleton<ILevelCalc>(levelCalc);
|
||||
services.AddSingleton<IEmojiConverter>(emojiConverter);
|
||||
services.AddSingleton<IAudioUtils>(audioUtils);
|
||||
services.AddSingleton(randomClient);
|
||||
services.AddSingleton<IFortunesProvider>(fortunes);
|
||||
services.AddSingleton<IMediaProvider>(mediaProvider);
|
||||
services.AddSingleton<IMalClient>(malClient);
|
||||
|
||||
logger.Information("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("Geekbot", $"Now Connected as {client.CurrentUser.Username} to {client.Guilds.Count} Servers");
|
||||
|
||||
logger.Information("Geekbot", "Registering Stuff");
|
||||
var translationHandler = new TranslationHandler(client.Guilds, redis, logger);
|
||||
var errorHandler = new ErrorHandler(logger, translationHandler);
|
||||
await commands.AddModulesAsync(Assembly.GetEntryAssembly());
|
||||
var contextHandler = new ContextHandler(client, commands, servicesProvider);
|
||||
services.AddSingleton(commands);
|
||||
services.AddSingleton<IContextHandler>(contextHandler);
|
||||
services.AddSingleton<IErrorHandler>(errorHandler);
|
||||
services.AddSingleton<ITranslationHandler>(translationHandler);
|
||||
services.AddSingleton<DiscordSocketClient>(client);
|
||||
servicesProvider = services.BuildServiceProvider();
|
||||
|
||||
var handlers = new Handlers(client, logger, redis, servicesProvider, commands, userRepository, contextHandler);
|
||||
|
||||
client.MessageReceived += handlers.RunCommand;
|
||||
client.MessageReceived += handlers.UpdateStats;
|
||||
client.MessageDeleted += handlers.MessageDeleted;
|
||||
client.UserJoined += handlers.UserJoined;
|
||||
client.UserUpdated += handlers.UserUpdated;
|
||||
client.UserLeft += handlers.UserLeft;
|
||||
|
||||
if (firstStart || args.Contains("--reset"))
|
||||
{
|
||||
logger.Information("Geekbot", "Finishing setup");
|
||||
await FinishSetup();
|
||||
logger.Information("Geekbot", "Setup finished");
|
||||
}
|
||||
if (!args.Contains("--disable-api"))
|
||||
{
|
||||
startWebApi();
|
||||
}
|
||||
|
||||
logger.Information("Geekbot", "Done and ready for use");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Discord", "Could not connect...", e);
|
||||
Environment.Exit(103);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> isConnected()
|
||||
{
|
||||
while (!client.ConnectionState.Equals(ConnectionState.Connected))
|
||||
await Task.Delay(25);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void startWebApi()
|
||||
{
|
||||
logger.Information("API", "Starting Webserver");
|
||||
var webApiUrl = new Uri("http://localhost:12995");
|
||||
new NancyHost(webApiUrl).Start();
|
||||
logger.Information("API", $"Webserver now running on {webApiUrl}");
|
||||
}
|
||||
|
||||
private async Task<Task> FinishSetup()
|
||||
{
|
||||
var appInfo = await client.GetApplicationInfoAsync();
|
||||
logger.Information("Setup", $"Just a moment while i setup everything {appInfo.Owner.Username}");
|
||||
try
|
||||
{
|
||||
redis.StringSet("botOwner", appInfo.Owner.Id);
|
||||
var req = HttpWebRequest.Create(appInfo.IconUrl);
|
||||
using (var stream = req.GetResponse().GetResponseStream())
|
||||
{
|
||||
await client.CurrentUser.ModifyAsync(User =>
|
||||
{
|
||||
User.Avatar = new Image(stream);
|
||||
User.Username = appInfo.Name.ToString();
|
||||
});
|
||||
}
|
||||
logger.Information("Setup", "Everything done, enjoy!");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Warning("Setup", "Oha, it seems like something went wrong while running the setup, geekbot will work never the less though", e);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task DiscordLogger(LogMessage message)
|
||||
{
|
||||
var logMessage = $"[{message.Source}] {message.Message}";
|
||||
switch (message.Severity)
|
||||
{
|
||||
case LogSeverity.Verbose:
|
||||
case LogSeverity.Debug:
|
||||
logger.Debug(message.Source, message.Message);
|
||||
break;
|
||||
case LogSeverity.Info:
|
||||
logger.Information(message.Source, message.Message);
|
||||
break;
|
||||
case LogSeverity.Critical:
|
||||
case LogSeverity.Error:
|
||||
case LogSeverity.Warning:
|
||||
if (logMessage.Contains("VOICE_STATE_UPDATE")) break;
|
||||
logger.Error(message.Source, message.Message, message.Exception);
|
||||
break;
|
||||
default:
|
||||
logger.Information(message.Source, $"{logMessage} --- {message.Severity}");
|
||||
break;
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
100
Geekbot.net/Storage/Translations.json
Normal file
100
Geekbot.net/Storage/Translations.json
Normal file
|
@ -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",
|
||||
"CHDE": ":red_circle: {0}, du chasch nid nomol es gliche rate"
|
||||
}
|
||||
}
|
||||
}
|
122
Geekbot.net/Storage/checkEmPics
Normal file
122
Geekbot.net/Storage/checkEmPics
Normal file
|
@ -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
|
17
Geekbot.net/Storage/croissant
Normal file
17
Geekbot.net/Storage/croissant
Normal file
|
@ -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
|
|
@ -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
|
23
Geekbot.net/Storage/pumpkin
Normal file
23
Geekbot.net/Storage/pumpkin
Normal file
|
@ -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
|
45
Geekbot.net/Storage/squirrel
Normal file
45
Geekbot.net/Storage/squirrel
Normal file
|
@ -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
|
|
@ -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
|
77
Geekbot.net/WebApi/HelpController.cs
Normal file
77
Geekbot.net/WebApi/HelpController.cs
Normal file
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Nancy;
|
||||
using Geekbot.net.Lib;
|
||||
|
||||
namespace Geekbot.net.WebApi
|
||||
{
|
||||
public class HelpController : NancyModule
|
||||
{
|
||||
public HelpController()
|
||||
{
|
||||
Get("/v1/commands", args =>
|
||||
{
|
||||
var commands = getCommands().Result;
|
||||
|
||||
var commandList = new List<CommandDto>();
|
||||
foreach (var cmd in commands.Commands)
|
||||
{
|
||||
var cmdParamsObj = new List<CommandParamDto>();
|
||||
foreach (var cmdParam in cmd.Parameters)
|
||||
{
|
||||
var singleParamObj = new CommandParamDto()
|
||||
{
|
||||
Summary = cmdParam.Summary,
|
||||
Default = cmdParam?.DefaultValue?.ToString() ?? null,
|
||||
Type = cmdParam?.Type?.ToString()
|
||||
};
|
||||
cmdParamsObj.Add(singleParamObj);
|
||||
}
|
||||
|
||||
var param = string.Join(", !", cmd.Aliases);
|
||||
var cmdObj = new CommandDto()
|
||||
{
|
||||
Name = cmd.Name,
|
||||
Summary = cmd.Summary,
|
||||
Category = cmd.Remarks ?? CommandCategories.Uncategorized,
|
||||
IsAdminCommand = (param.Contains("admin")),
|
||||
Aliases = cmd.Aliases.ToArray(),
|
||||
Params = cmdParamsObj
|
||||
};
|
||||
commandList.Add(cmdObj);
|
||||
}
|
||||
return Response.AsJson(commandList);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<CommandService> getCommands()
|
||||
{
|
||||
var commands = new CommandService();
|
||||
await commands.AddModulesAsync(Assembly.GetEntryAssembly());
|
||||
return commands;
|
||||
}
|
||||
}
|
||||
|
||||
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<CommandParamDto> Params { get; set; }
|
||||
}
|
||||
|
||||
public class CommandParamDto
|
||||
{
|
||||
public string Summary { get; set; }
|
||||
public string Default { get; set; }
|
||||
public string Type { get; set; }
|
||||
}
|
||||
}
|
29
Geekbot.net/WebApi/StatusController.cs
Normal file
29
Geekbot.net/WebApi/StatusController.cs
Normal file
|
@ -0,0 +1,29 @@
|
|||
using Nancy;
|
||||
using Geekbot.net.Lib;
|
||||
|
||||
namespace Geekbot.net.WebApi
|
||||
{
|
||||
public class StatusController : NancyModule
|
||||
{
|
||||
public StatusController()
|
||||
{
|
||||
Get("/", args =>
|
||||
{
|
||||
var responseBody = new ApiStatusDto()
|
||||
{
|
||||
GeekbotVersion = Constants.BotVersion.ToString(),
|
||||
ApiVersion = Constants.ApiVersion.ToString(),
|
||||
Status = "Online"
|
||||
};
|
||||
return Response.AsJson(responseBody);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class ApiStatusDto
|
||||
{
|
||||
public string GeekbotVersion { get; set; }
|
||||
public string ApiVersion { get; set; }
|
||||
public string Status { get; set; }
|
||||
}
|
||||
}
|
23
Geekbot.net/WebApi/WebConfig.cs
Normal file
23
Geekbot.net/WebApi/WebConfig.cs
Normal file
|
@ -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());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 361 KiB After Width: | Height: | Size: 361 KiB |
75
Tests/Lib/EmojiConverter.test.cs
Normal file
75
Tests/Lib/EmojiConverter.test.cs
Normal file
|
@ -0,0 +1,75 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Geekbot.net.Lib;
|
||||
using Xunit;
|
||||
|
||||
namespace Tests.Lib
|
||||
{
|
||||
public class EmojiConverter_test
|
||||
{
|
||||
public static IEnumerable<object[]> NumberToEmojiTestData
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new object[]
|
||||
{
|
||||
2,
|
||||
":two:"
|
||||
};
|
||||
|
||||
yield return new object[]
|
||||
{
|
||||
10,
|
||||
"🔟"
|
||||
};
|
||||
|
||||
yield return new object[]
|
||||
{
|
||||
15,
|
||||
":one::five:"
|
||||
};
|
||||
|
||||
yield return new object[]
|
||||
{
|
||||
null,
|
||||
":zero:"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Theory, MemberData(nameof(NumberToEmojiTestData))]
|
||||
public async Task NumberToEmoji(int number, string expectedResult)
|
||||
{
|
||||
var emojiConverter = new EmojiConverter();
|
||||
var result = emojiConverter.numberToEmoji(number);
|
||||
Assert.Equal(result, expectedResult);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> textToEmojiTestData
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new object[]
|
||||
{
|
||||
"test",
|
||||
":regional_indicator_t::regional_indicator_e::regional_indicator_s::regional_indicator_t:"
|
||||
};
|
||||
yield return new object[]
|
||||
{
|
||||
"Best3+?",
|
||||
":b::regional_indicator_e::regional_indicator_s::regional_indicator_t::three::heavy_plus_sign::question:"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Theory, MemberData(nameof(textToEmojiTestData))]
|
||||
public async Task TextToEmoji(string text, string expectedResult)
|
||||
{
|
||||
var emojiConverter = new EmojiConverter();
|
||||
var result = emojiConverter.textToEmoji(text);
|
||||
Assert.Equal(result, expectedResult);
|
||||
}
|
||||
}
|
||||
}
|
49
Tests/Lib/LevelCalc.test.cs
Normal file
49
Tests/Lib/LevelCalc.test.cs
Normal file
|
@ -0,0 +1,49 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Geekbot.net.Lib;
|
||||
using Xunit;
|
||||
|
||||
namespace Tests.Lib
|
||||
{
|
||||
public class LevelCalc_test
|
||||
{
|
||||
public static IEnumerable<object[]> LevelCalcTestData
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new object[]
|
||||
{
|
||||
500,
|
||||
13
|
||||
};
|
||||
|
||||
yield return new object[]
|
||||
{
|
||||
41659,
|
||||
55
|
||||
};
|
||||
|
||||
yield return new object[]
|
||||
{
|
||||
0,
|
||||
1
|
||||
};
|
||||
|
||||
yield return new object[]
|
||||
{
|
||||
4000000,
|
||||
101
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Theory, MemberData(nameof(LevelCalcTestData))]
|
||||
public async Task GetLevel(int messages, int expectedResult)
|
||||
{
|
||||
var levelCalc = new LevelCalc();
|
||||
var result = levelCalc.GetLevel(messages);
|
||||
Assert.Equal(result, expectedResult);
|
||||
}
|
||||
}
|
||||
}
|
16
Tests/Tests.csproj
Normal file
16
Tests/Tests.csproj
Normal file
|
@ -0,0 +1,16 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<NoWarn>NU1701</NoWarn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
|
||||
<PackageReference Include="xunit" Version="2.3.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
|
||||
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Geekbot.net\Geekbot.net.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -1,3 +0,0 @@
|
|||
collections:
|
||||
- name: community.docker
|
||||
version: 2.7.0
|
27
readme.md
27
readme.md
|
@ -1,28 +1,37 @@
|
|||
[![pipeline status](https://gitlab.com/dbgit/open/geekbot/badges/master/pipeline.svg)](https://gitlab.com/dbgit/open/geekbot/commits/master)
|
||||
[![pipeline status](https://git.boerlage.me/open/Geekbot.net/badges/master/pipeline.svg)](https://git.boerlage.me/open/Geekbot.net/commits/master)
|
||||
|
||||
# [Geekbot.net](https://geekbot.pizzaandcoffee.rocks/)
|
||||
|
||||
A General Purpose Discord Bot written in C#
|
||||
A General Purpose Discord Bot written in DotNet Core.
|
||||
|
||||
You can invite Geekbot to your server with [this link](https://discordapp.com/oauth2/authorize?client_id=171249478546882561&scope=bot&permissions=1416834054)
|
||||
|
||||
## Technologies
|
||||
|
||||
* .NET 5
|
||||
* PostgreSQL
|
||||
* DotNet Core 2
|
||||
* Redis
|
||||
* Discord.net
|
||||
* ffmpeg
|
||||
|
||||
## Running
|
||||
|
||||
You can start geekbot with: `dotnet run`
|
||||
Make sure redis is running
|
||||
|
||||
On your first run geekbot will ask for your bot token.
|
||||
Run these commands
|
||||
|
||||
You might need to pass some additional configuration (e.g. database credentials), these can be passed as commandline arguments or environment variables.
|
||||
* `dotnet restore`
|
||||
* `dotnet run`
|
||||
|
||||
For a list of commandline arguments and environment variables use `dotnet run -- -h`
|
||||
On your first run geekbot will ask for your bot token, everything else is taken care of.
|
||||
|
||||
All Environment Variables must be prefixed with `GEEKBOT_`
|
||||
### Launch Parameters
|
||||
|
||||
| Parameter | Description |
|
||||
| --- | --- |
|
||||
| `--verbose` | Show more log information |
|
||||
| `--disable-api` | Disables the webapi on startup |
|
||||
| `--reset` | Resets certain parts of the bot |
|
||||
| `--migrate` | Migrates the database from V3.1 to the new format from V3.2<br> (make sure to backup before running this) |
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<VersionSuffix>$(VersionSuffix)</VersionSuffix>
|
||||
<RootNamespace>Geekbot.Bot</RootNamespace>
|
||||
<AssemblyName>Geekbot.Bot</AssemblyName>
|
||||
<Version Condition=" '$(VersionSuffix)' != '' ">$(VersionSuffix)</Version>
|
||||
<Version Condition=" '$(VersionSuffix)' == '' ">0.0.0-DEV</Version>
|
||||
<NoWarn>NU1701</NoWarn>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommandLineParser" Version="2.8.0" />
|
||||
<PackageReference Include="Google.Apis.YouTube.v3" Version="1.45.0.1929" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.36" />
|
||||
<PackageReference Include="JikanDotNet" Version="1.6.0" />
|
||||
<PackageReference Include="MtgApiManager.Lib" Version="1.2.2" />
|
||||
<PackageReference Include="PokeApi.NET" Version="1.1.2" />
|
||||
<PackageReference Include="Sentry" Version="3.11.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Storage\*">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Commands\Commands.csproj" />
|
||||
<ProjectReference Include="..\Core\Core.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -1,127 +0,0 @@
|
|||
using System.Reflection;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using Geekbot.Bot.Handlers;
|
||||
using Geekbot.Core;
|
||||
using Geekbot.Core.Database;
|
||||
using Geekbot.Core.GlobalSettings;
|
||||
using Geekbot.Core.GuildSettingsManager;
|
||||
using Geekbot.Core.Logger;
|
||||
using Geekbot.Core.Logger.Adapters;
|
||||
using Geekbot.Core.ReactionListener;
|
||||
using Geekbot.Core.UserRepository;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Geekbot.Bot;
|
||||
|
||||
public class BotStartup
|
||||
{
|
||||
private readonly IServiceCollection _serviceCollection;
|
||||
private readonly GeekbotLogger _logger;
|
||||
private readonly RunParameters _runParameters;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private DiscordSocketClient _client;
|
||||
|
||||
public BotStartup(IServiceCollection serviceCollection, GeekbotLogger logger, RunParameters runParameters, IGlobalSettings globalSettings)
|
||||
{
|
||||
_serviceCollection = serviceCollection;
|
||||
_logger = logger;
|
||||
_runParameters = runParameters;
|
||||
_globalSettings = globalSettings;
|
||||
}
|
||||
|
||||
public async Task Start()
|
||||
{
|
||||
_logger.Information(LogSource.Geekbot, "Connecting to Discord");
|
||||
SetupDiscordClient();
|
||||
await Login();
|
||||
await _client.SetGameAsync(_globalSettings.GetKey("Game"));
|
||||
_logger.Information(LogSource.Geekbot, $"Now Connected as {_client.CurrentUser.Username} to {_client.Guilds.Count} Servers");
|
||||
|
||||
_logger.Information(LogSource.Geekbot, "Registering Gateway Handlers");
|
||||
await RegisterHandlers();
|
||||
|
||||
_logger.Information(LogSource.Geekbot, "Done and ready for use");
|
||||
await Task.Delay(-1);
|
||||
}
|
||||
|
||||
private void SetupDiscordClient()
|
||||
{
|
||||
_client = new DiscordSocketClient(new DiscordSocketConfig
|
||||
{
|
||||
GatewayIntents = GatewayIntents.DirectMessageReactions |
|
||||
GatewayIntents.DirectMessages |
|
||||
GatewayIntents.GuildMessageReactions |
|
||||
GatewayIntents.GuildMessages |
|
||||
GatewayIntents.GuildWebhooks |
|
||||
GatewayIntents.GuildIntegrations |
|
||||
GatewayIntents.GuildEmojis |
|
||||
GatewayIntents.GuildBans |
|
||||
GatewayIntents.Guilds |
|
||||
GatewayIntents.GuildMembers,
|
||||
LogLevel = LogSeverity.Verbose,
|
||||
MessageCacheSize = 1000,
|
||||
});
|
||||
|
||||
var discordLogger = new DiscordLogger(_logger);
|
||||
_client.Log += discordLogger.Log;
|
||||
}
|
||||
|
||||
private async Task Login()
|
||||
{
|
||||
try
|
||||
{
|
||||
var token = await GetToken();
|
||||
await _client.LoginAsync(TokenType.Bot, token);
|
||||
await _client.StartAsync();
|
||||
while (!_client.ConnectionState.Equals(ConnectionState.Connected)) await Task.Delay(25);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(LogSource.Geekbot, "Could not connect to Discord", e);
|
||||
Environment.Exit(GeekbotExitCode.CouldNotLogin.GetHashCode());
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> GetToken()
|
||||
{
|
||||
var token = _runParameters.Token ?? _globalSettings.GetKey("DiscordToken");
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
Console.Write("Your bot Token: ");
|
||||
var newToken = Console.ReadLine();
|
||||
await _globalSettings.SetKey("DiscordToken", newToken);
|
||||
await _globalSettings.SetKey("Game", "Ping Pong");
|
||||
token = newToken;
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
private async Task RegisterHandlers()
|
||||
{
|
||||
var applicationInfo = await _client.GetApplicationInfoAsync();
|
||||
|
||||
_serviceCollection.AddSingleton<DiscordSocketClient>(_client);
|
||||
var serviceProvider = _serviceCollection.BuildServiceProvider();
|
||||
|
||||
var commands = new CommandService();
|
||||
await commands.AddModulesAsync(Assembly.GetAssembly(typeof(BotStartup)), serviceProvider);
|
||||
|
||||
var commandHandler = new CommandHandler(_client, _logger, serviceProvider, commands, applicationInfo, serviceProvider.GetService<IGuildSettingsManager>());
|
||||
var userHandler = new UserHandler(serviceProvider.GetService<IUserRepository>(), _logger, serviceProvider.GetService<DatabaseContext>(), _client);
|
||||
var reactionHandler = new ReactionHandler(serviceProvider.GetService<IReactionListener>());
|
||||
var statsHandler = new StatsHandler(_logger, serviceProvider.GetService<DatabaseContext>());
|
||||
var messageDeletedHandler = new MessageDeletedHandler(serviceProvider.GetService<DatabaseContext>(), _logger, _client);
|
||||
|
||||
_client.MessageReceived += commandHandler.RunCommand;
|
||||
_client.MessageDeleted += messageDeletedHandler.HandleMessageDeleted;
|
||||
_client.UserJoined += userHandler.Joined;
|
||||
_client.UserUpdated += userHandler.Updated;
|
||||
_client.UserLeft += userHandler.Left;
|
||||
_client.ReactionAdded += reactionHandler.Added;
|
||||
_client.ReactionRemoved += reactionHandler.Removed;
|
||||
if (!_runParameters.InMemory) _client.MessageReceived += statsHandler.UpdateStats;
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
|
||||
namespace Geekbot.Bot.CommandPreconditions
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
|
||||
public class DisableInDirectMessageAttribute : PreconditionAttribute
|
||||
{
|
||||
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
|
||||
{
|
||||
var result = context.Guild.Id != 0 ? PreconditionResult.FromSuccess() : PreconditionResult.FromError("Command unavailable in Direct Messaging");
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,253 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Resources;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using Geekbot.Bot.CommandPreconditions;
|
||||
using Geekbot.Core;
|
||||
using Geekbot.Core.ErrorHandling;
|
||||
using Geekbot.Core.Extensions;
|
||||
using Geekbot.Core.GuildSettingsManager;
|
||||
using Localization = Geekbot.Core.Localization;
|
||||
|
||||
namespace Geekbot.Bot.Commands.Admin
|
||||
{
|
||||
[Group("admin")]
|
||||
[RequireUserPermission(GuildPermission.Administrator)]
|
||||
[DisableInDirectMessage]
|
||||
public class Admin : GeekbotCommandBase
|
||||
{
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
public Admin(DiscordSocketClient client, IErrorHandler errorHandler, IGuildSettingsManager guildSettingsManager) : base(errorHandler, guildSettingsManager)
|
||||
{
|
||||
_client = client;
|
||||
}
|
||||
|
||||
[Command("welcome", RunMode = RunMode.Async)]
|
||||
[Summary("Set a Welcome Message (use '$user' to mention the new joined user).")]
|
||||
public async Task SetWelcomeMessage([Remainder, Summary("message")] string welcomeMessage)
|
||||
{
|
||||
GuildSettings.WelcomeMessage = welcomeMessage;
|
||||
await GuildSettingsManager.UpdateSettings(GuildSettings);
|
||||
|
||||
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("welcomechannel", RunMode = RunMode.Async)]
|
||||
[Summary("Set a channel for the welcome messages (by default it uses the top most channel)")]
|
||||
public async Task SelectWelcomeChannel([Summary("#Channel")] ISocketMessageChannel channel)
|
||||
{
|
||||
try
|
||||
{
|
||||
var m = await channel.SendMessageAsync("...");
|
||||
|
||||
GuildSettings.WelcomeChannel = channel.Id.AsLong();
|
||||
await GuildSettingsManager.UpdateSettings(GuildSettings);
|
||||
|
||||
await m.DeleteAsync();
|
||||
|
||||
await ReplyAsync("Successfully saved the welcome channel");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ErrorHandler.HandleCommandException(e, Context, "That channel doesn't seem to exist or i don't have write permissions");
|
||||
}
|
||||
}
|
||||
|
||||
[Command("modchannel", RunMode = RunMode.Async)]
|
||||
[Summary("Set a channel for moderation purposes")]
|
||||
public async Task SelectModChannel([Summary("#Channel")] ISocketMessageChannel channel)
|
||||
{
|
||||
try
|
||||
{
|
||||
var m = await channel.SendMessageAsync("verifying...");
|
||||
|
||||
GuildSettings.ModChannel = channel.Id.AsLong();
|
||||
await GuildSettingsManager.UpdateSettings(GuildSettings);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("Successfully saved mod channel, you can now do the following");
|
||||
sb.AppendLine("- `!admin showleave` - send message to mod channel when someone leaves");
|
||||
sb.AppendLine("- `!admin showdel` - send message to mod channel when someone deletes a message");
|
||||
await m.ModifyAsync(e => e.Content = sb.ToString());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ErrorHandler.HandleCommandException(e, Context, "That channel doesn't seem to exist or i don't have write permissions");
|
||||
}
|
||||
}
|
||||
|
||||
[Command("showleave", RunMode = RunMode.Async)]
|
||||
[Summary("Toggle - notify modchannel when someone leaves")]
|
||||
public async Task ShowLeave()
|
||||
{
|
||||
try
|
||||
{
|
||||
var modChannel = await GetModChannel(GuildSettings.ModChannel.AsUlong());
|
||||
if (modChannel == null) return;
|
||||
|
||||
GuildSettings.ShowLeave = !GuildSettings.ShowLeave;
|
||||
await GuildSettingsManager.UpdateSettings(GuildSettings);
|
||||
await modChannel.SendMessageAsync(GuildSettings.ShowLeave
|
||||
? "Saved - now sending messages here when someone leaves"
|
||||
: "Saved - stopping sending messages here when someone leaves"
|
||||
);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ErrorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
|
||||
[Command("showdel", RunMode = RunMode.Async)]
|
||||
[Summary("Toggle - notify modchannel when someone deletes a message")]
|
||||
public async Task ShowDelete()
|
||||
{
|
||||
try
|
||||
{
|
||||
var modChannel = await GetModChannel(GuildSettings.ModChannel.AsUlong());
|
||||
if (modChannel == null) return;
|
||||
|
||||
GuildSettings.ShowDelete = !GuildSettings.ShowDelete;
|
||||
await GuildSettingsManager.UpdateSettings(GuildSettings);
|
||||
await modChannel.SendMessageAsync(GuildSettings.ShowDelete
|
||||
? "Saved - now sending messages here when someone deletes a message"
|
||||
: "Saved - stopping sending messages here when someone deletes a message"
|
||||
);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ErrorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
|
||||
[Command("setlang", RunMode = RunMode.Async)]
|
||||
[Summary("Change the bots language")]
|
||||
public async Task SetLanguage([Summary("language")] string language)
|
||||
{
|
||||
try
|
||||
{
|
||||
var availableLanguages = new List<string>();
|
||||
availableLanguages.Add("en-GB"); // default
|
||||
availableLanguages.AddRange(GetAvailableCultures().Select(culture => culture.Name));
|
||||
if (availableLanguages.Contains(language))
|
||||
{
|
||||
GuildSettings.Language = language;
|
||||
await GuildSettingsManager.UpdateSettings(GuildSettings);
|
||||
|
||||
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(language.ToLower() == "chde" ? "de-CH" : language);
|
||||
|
||||
await ReplyAsync(Localization.Admin.NewLanguageSet);
|
||||
return;
|
||||
}
|
||||
|
||||
await ReplyAsync($"That doesn't seem to be a supported language\nSupported Languages are {string.Join(", ", availableLanguages)}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ErrorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
|
||||
[Command("wiki", RunMode = RunMode.Async)]
|
||||
[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();
|
||||
GuildSettings.WikiLang = language;
|
||||
await GuildSettingsManager.UpdateSettings(GuildSettings);
|
||||
|
||||
await ReplyAsync($"Now using the {language} wikipedia");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ErrorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
|
||||
[Command("ping", RunMode = RunMode.Async)]
|
||||
[Summary("Enable the ping reply.")]
|
||||
public async Task TogglePing()
|
||||
{
|
||||
try
|
||||
{
|
||||
// var guild = _guildSettingsManager.GetSettings(Context.Guild.Id);
|
||||
GuildSettings.Ping = !GuildSettings.Ping;
|
||||
await GuildSettingsManager.UpdateSettings(GuildSettings);
|
||||
await ReplyAsync(GuildSettings.Ping ? "i will reply to ping now" : "No more pongs...");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ErrorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
|
||||
[Command("hui", RunMode = RunMode.Async)]
|
||||
[Summary("Enable the ping reply.")]
|
||||
public async Task ToggleHui()
|
||||
{
|
||||
try
|
||||
{
|
||||
// var guild = _guildSettingsManager.GetSettings(Context.Guild.Id);
|
||||
GuildSettings.Hui = !GuildSettings.Hui;
|
||||
await GuildSettingsManager.UpdateSettings(GuildSettings);
|
||||
await ReplyAsync(GuildSettings.Hui ? "i will reply to hui now" : "No more hui's...");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ErrorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<ISocketMessageChannel> GetModChannel(ulong channelId)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (channelId == ulong.MinValue) throw new Exception();
|
||||
var modChannel = (ISocketMessageChannel) _client.GetChannel(channelId);
|
||||
if (modChannel == null) throw new Exception();
|
||||
return modChannel;
|
||||
}
|
||||
catch
|
||||
{
|
||||
await ReplyAsync("Modchannel doesn't seem to exist, please set one with `!admin modchannel [channelId]`");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<CultureInfo> GetAvailableCultures()
|
||||
{
|
||||
var result = new List<CultureInfo>();
|
||||
var rm = new ResourceManager(typeof(Localization.Admin));
|
||||
var cultures = CultureInfo.GetCultures(CultureTypes.AllCultures);
|
||||
foreach (var culture in cultures)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (culture.Equals(CultureInfo.InvariantCulture)) continue; //do not use "==", won't work
|
||||
|
||||
var rs = rm.GetResourceSet(culture, true, false);
|
||||
if (rs != null)
|
||||
{
|
||||
result.Add(culture);
|
||||
}
|
||||
}
|
||||
catch (CultureNotFoundException)
|
||||
{
|
||||
//NOP
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,127 +0,0 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using Geekbot.Core;
|
||||
using Geekbot.Core.ErrorHandling;
|
||||
using Geekbot.Core.GlobalSettings;
|
||||
using Geekbot.Core.GuildSettingsManager;
|
||||
using Geekbot.Core.Logger;
|
||||
using Geekbot.Core.UserRepository;
|
||||
|
||||
namespace Geekbot.Bot.Commands.Admin.Owner
|
||||
{
|
||||
[Group("owner")]
|
||||
[RequireOwner]
|
||||
public class Owner : GeekbotCommandBase
|
||||
{
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private readonly IGeekbotLogger _logger;
|
||||
private readonly IUserRepository _userRepository;
|
||||
|
||||
public Owner(DiscordSocketClient client, IGeekbotLogger logger, IUserRepository userRepositry, IErrorHandler errorHandler, IGlobalSettings globalSettings,
|
||||
IGuildSettingsManager guildSettingsManager) : base(errorHandler, guildSettingsManager)
|
||||
{
|
||||
_client = client;
|
||||
_logger = logger;
|
||||
_userRepository = userRepositry;
|
||||
_globalSettings = globalSettings;
|
||||
}
|
||||
|
||||
[Command("youtubekey", RunMode = RunMode.Async)]
|
||||
[Summary("Set the youtube api key")]
|
||||
public async Task SetYoutubeKey([Summary("API-Key")] string key)
|
||||
{
|
||||
await _globalSettings.SetKey("YoutubeKey", key);
|
||||
await ReplyAsync("Apikey has been set");
|
||||
}
|
||||
|
||||
[Command("game", RunMode = RunMode.Async)]
|
||||
[Summary("Set the game that the bot is playing")]
|
||||
public async Task SetGame([Remainder] [Summary("Game")] string key)
|
||||
{
|
||||
await _globalSettings.SetKey("Game", key);
|
||||
await _client.SetGameAsync(key);
|
||||
_logger.Information(LogSource.Geekbot, $"Changed game to {key}");
|
||||
await ReplyAsync($"Now Playing {key}");
|
||||
}
|
||||
|
||||
[Command("popuserrepo", RunMode = RunMode.Async)]
|
||||
[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)
|
||||
{
|
||||
await ErrorHandler.HandleCommandException(e, Context,
|
||||
"Couldn't complete User Repository, see console for more info");
|
||||
}
|
||||
}
|
||||
|
||||
[Command("refreshuser", RunMode = RunMode.Async)]
|
||||
[Summary("Refresh a user in the user cache")]
|
||||
public async Task PopUserRepoCommand([Summary("@someone")] IUser user)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _userRepository.Update(user as SocketUser);
|
||||
await ReplyAsync($"Refreshed: {user.Username}#{user.Discriminator}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ErrorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
|
||||
[Command("refreshuser", RunMode = RunMode.Async)]
|
||||
[Summary("Refresh a user in the user cache")]
|
||||
public async Task PopUserRepoCommand([Summary("user-id")] ulong userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = _client.GetUser(userId);
|
||||
await _userRepository.Update(user);
|
||||
await ReplyAsync($"Refreshed: {user.Username}#{user.Discriminator}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ErrorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
|
||||
[Command("error", RunMode = RunMode.Async)]
|
||||
[Summary("Throw an error un purpose")]
|
||||
public async Task PurposefulError()
|
||||
{
|
||||
try
|
||||
{
|
||||
throw new Exception("Error Generated by !owner error");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ErrorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,197 +0,0 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.Net;
|
||||
using Geekbot.Bot.CommandPreconditions;
|
||||
using Geekbot.Core;
|
||||
using Geekbot.Core.Database;
|
||||
using Geekbot.Core.Database.Models;
|
||||
using Geekbot.Core.ErrorHandling;
|
||||
using Geekbot.Core.Extensions;
|
||||
using Geekbot.Core.GuildSettingsManager;
|
||||
using Geekbot.Core.ReactionListener;
|
||||
using Localization = Geekbot.Core.Localization;
|
||||
|
||||
namespace Geekbot.Bot.Commands.Admin
|
||||
{
|
||||
[Group("role")]
|
||||
[DisableInDirectMessage]
|
||||
public class Role : GeekbotCommandBase
|
||||
{
|
||||
private readonly DatabaseContext _database;
|
||||
private readonly IReactionListener _reactionListener;
|
||||
|
||||
public Role(DatabaseContext database, IErrorHandler errorHandler, IReactionListener reactionListener, IGuildSettingsManager guildSettingsManager) : base(errorHandler, guildSettingsManager)
|
||||
{
|
||||
_database = database;
|
||||
_reactionListener = reactionListener;
|
||||
}
|
||||
|
||||
[Command(RunMode = RunMode.Async)]
|
||||
[Summary("Get a list of all available roles.")]
|
||||
public async Task GetAllRoles()
|
||||
{
|
||||
try
|
||||
{
|
||||
var roles = _database.RoleSelfService.Where(g => g.GuildId.Equals(Context.Guild.Id.AsLong())).ToList();
|
||||
if (roles.Count == 0)
|
||||
{
|
||||
await ReplyAsync(Localization.Role.NoRolesConfigured);
|
||||
return;
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine(string.Format(Localization.Role.ListHeader, Context.Guild.Name));
|
||||
sb.AppendLine(Localization.Role.ListInstruction);
|
||||
foreach (var role in roles) sb.AppendLine($"- {role.WhiteListName}");
|
||||
await ReplyAsync(sb.ToString());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ErrorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
|
||||
[Command(RunMode = RunMode.Async)]
|
||||
[Summary("Get a role by mentioning it.")]
|
||||
public async Task GiveRole([Summary("role-nickname")] string roleNameRaw)
|
||||
{
|
||||
try
|
||||
{
|
||||
var roleName = roleNameRaw.ToLower();
|
||||
var roleFromDb = _database.RoleSelfService.FirstOrDefault(e =>
|
||||
e.GuildId.Equals(Context.Guild.Id.AsLong()) && e.WhiteListName.Equals(roleName));
|
||||
if (roleFromDb != null)
|
||||
{
|
||||
var guildUser = (IGuildUser) Context.User;
|
||||
var role = Context.Guild.Roles.First(r => r.Id == roleFromDb.RoleId.AsUlong());
|
||||
if (role == null)
|
||||
{
|
||||
await ReplyAsync(Localization.Role.RoleNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
if (guildUser.RoleIds.Contains(role.Id))
|
||||
{
|
||||
await guildUser.RemoveRoleAsync(role);
|
||||
await ReplyAsync(string.Format(Localization.Role.RemovedUserFromRole, role.Name));
|
||||
return;
|
||||
}
|
||||
|
||||
await guildUser.AddRoleAsync(role);
|
||||
await ReplyAsync(string.Format(Localization.Role.AddedUserFromRole, role.Name));
|
||||
return;
|
||||
}
|
||||
|
||||
await ReplyAsync(Localization.Role.RoleNotFound);
|
||||
}
|
||||
catch (HttpException e)
|
||||
{
|
||||
if (e.HttpCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
await ReplyAsync(Localization.Internal.Http403);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ErrorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ErrorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
|
||||
[RequireUserPermission(GuildPermission.ManageRoles)]
|
||||
[Command("add", RunMode = RunMode.Async)]
|
||||
[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(Localization.Role.CannotAddManagedRole);
|
||||
return;
|
||||
}
|
||||
|
||||
if (role.Permissions.ManageRoles
|
||||
|| role.Permissions.Administrator
|
||||
|| role.Permissions.ManageGuild
|
||||
|| role.Permissions.BanMembers
|
||||
|| role.Permissions.KickMembers)
|
||||
{
|
||||
await ReplyAsync(Localization.Role.CannotAddDangerousRole);
|
||||
return;
|
||||
}
|
||||
|
||||
_database.RoleSelfService.Add(new RoleSelfServiceModel
|
||||
{
|
||||
GuildId = Context.Guild.Id.AsLong(),
|
||||
RoleId = role.Id.AsLong(),
|
||||
WhiteListName = roleName
|
||||
});
|
||||
await _database.SaveChangesAsync();
|
||||
await ReplyAsync(string.Format(Localization.Role.AddedRoleToWhitelist, role.Name));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ErrorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
|
||||
[RequireUserPermission(GuildPermission.ManageRoles)]
|
||||
[Command("remove", RunMode = RunMode.Async)]
|
||||
[Summary("Remove a role from the whitelist.")]
|
||||
public async Task RemoveRole([Summary("role-nickname")] string roleName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var roleFromDb = _database.RoleSelfService.FirstOrDefault(e =>
|
||||
e.GuildId.Equals(Context.Guild.Id.AsLong()) && e.WhiteListName.Equals(roleName));
|
||||
if (roleFromDb != null)
|
||||
{
|
||||
_database.RoleSelfService.Remove(roleFromDb);
|
||||
await _database.SaveChangesAsync();
|
||||
await ReplyAsync(string.Format(Localization.Role.RemovedRoleFromWhitelist, roleName));
|
||||
return;
|
||||
}
|
||||
|
||||
await ReplyAsync(Localization.Role.RoleNotFound);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ErrorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
|
||||
[RequireUserPermission(GuildPermission.ManageRoles)]
|
||||
[Summary("Give a role by clicking on an emoji")]
|
||||
[Command("listen", RunMode = RunMode.Async)]
|
||||
public async Task AddListener([Summary("message-ID")] string messageIdStr, [Summary("Emoji")] string emoji, [Summary("@role")] IRole role)
|
||||
{
|
||||
try
|
||||
{
|
||||
var messageId = ulong.Parse(messageIdStr);
|
||||
var message = (IUserMessage) await Context.Channel.GetMessageAsync(messageId);
|
||||
var emote = _reactionListener.ConvertStringToEmote(emoji);
|
||||
|
||||
await message.AddReactionAsync(emote);
|
||||
await _reactionListener.AddRoleToListener(messageId, Context.Guild.Id, emoji, role);
|
||||
await Context.Message.DeleteAsync();
|
||||
}
|
||||
catch (HttpException)
|
||||
{
|
||||
await Context.Channel.SendMessageAsync("Custom emojis from other servers are not supported");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ErrorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
using Geekbot.Core;
|
||||
using Geekbot.Core.Database;
|
||||
using Geekbot.Core.ErrorHandling;
|
||||
using Geekbot.Core.GuildSettingsManager;
|
||||
using Geekbot.Core.KvInMemoryStore;
|
||||
using Geekbot.Core.RandomNumberGenerator;
|
||||
using Sentry;
|
||||
|
||||
namespace Geekbot.Bot.Commands.Games.Roll
|
||||
{
|
||||
public class Roll : GeekbotCommandBase
|
||||
{
|
||||
private readonly IKvInMemoryStore _kvInMemoryStore;
|
||||
private readonly DatabaseContext _database;
|
||||
private readonly IRandomNumberGenerator _randomNumberGenerator;
|
||||
|
||||
public Roll(IKvInMemoryStore kvInMemoryStore, IErrorHandler errorHandler, DatabaseContext database, IRandomNumberGenerator randomNumberGenerator, IGuildSettingsManager guildSettingsManager)
|
||||
: base(errorHandler, guildSettingsManager)
|
||||
{
|
||||
_kvInMemoryStore = kvInMemoryStore;
|
||||
_database = database;
|
||||
_randomNumberGenerator = randomNumberGenerator;
|
||||
}
|
||||
|
||||
[Command("roll", RunMode = RunMode.Async)]
|
||||
[Summary("Guess which number the bot will roll (1-100")]
|
||||
public async Task RollCommand([Remainder] [Summary("guess")] string stuff = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var res = await new Geekbot.Commands.Roll.Roll(_kvInMemoryStore, _database, _randomNumberGenerator)
|
||||
.RunFromGateway(
|
||||
Context.Guild.Id,
|
||||
Context.User.Id,
|
||||
Context.User.Username,
|
||||
stuff ?? "0"
|
||||
);
|
||||
await ReplyAsync(res);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ErrorHandler.HandleCommandException(e, Context);
|
||||
Transaction.Status = SpanStatus.InternalError;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Discord.Commands;
|
||||
using Geekbot.Core;
|
||||
using Geekbot.Core.ErrorHandling;
|
||||
|
||||
namespace Geekbot.Bot.Commands.Integrations.LolMmr
|
||||
{
|
||||
public class LolMmr : TransactionModuleBase
|
||||
{
|
||||
private readonly IErrorHandler _errorHandler;
|
||||
|
||||
public LolMmr(IErrorHandler errorHandler)
|
||||
{
|
||||
_errorHandler = errorHandler;
|
||||
}
|
||||
|
||||
[Command("mmr", RunMode = RunMode.Async)]
|
||||
[Summary("Get the League of Legends MMR for a specified summoner")]
|
||||
public async Task GetMmr([Remainder] [Summary("summoner")] string summonerName)
|
||||
{
|
||||
try
|
||||
{
|
||||
LolMmrDto data;
|
||||
try
|
||||
{
|
||||
var name = HttpUtility.UrlEncode(summonerName.ToLower());
|
||||
var httpClient = HttpAbstractions.CreateDefaultClient();
|
||||
// setting the user agent in accordance with the whatismymmr.com api rules
|
||||
httpClient.DefaultRequestHeaders.Remove("User-Agent");
|
||||
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "Linux:rocks.pizzaandcoffee.geekbot:v0.0.0");
|
||||
data = await HttpAbstractions.Get<LolMmrDto>(new Uri($"https://euw.whatismymmr.com/api/v1/summoner?name={name}"), httpClient);
|
||||
}
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
if (e.StatusCode != HttpStatusCode.NotFound) throw e;
|
||||
|
||||
await Context.Channel.SendMessageAsync("Player not found");
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"**MMR for {summonerName}**");
|
||||
sb.AppendLine($"Normal: {data.Normal?.Avg ?? 0}");
|
||||
sb.AppendLine($"Ranked: {data.Ranked?.Avg ?? 0}");
|
||||
sb.AppendLine($"ARAM: {data.ARAM?.Avg ?? 0}");
|
||||
|
||||
await Context.Channel.SendMessageAsync(sb.ToString());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await _errorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Geekbot.Bot.Commands.Integrations.LolMmr
|
||||
{
|
||||
public class LolMmrDto
|
||||
{
|
||||
[JsonPropertyName("ranked")]
|
||||
public LolMrrInfoDto Ranked { get; set; }
|
||||
|
||||
[JsonPropertyName("normal")]
|
||||
public LolMrrInfoDto Normal { get; set; }
|
||||
|
||||
[JsonPropertyName("aram")]
|
||||
public LolMrrInfoDto ARAM { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Geekbot.Bot.Commands.Integrations.LolMmr
|
||||
{
|
||||
public class LolMrrInfoDto
|
||||
{
|
||||
[JsonPropertyName("avg")]
|
||||
public decimal? Avg { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Geekbot.Core;
|
||||
using Geekbot.Core.Converters;
|
||||
using Geekbot.Core.ErrorHandling;
|
||||
using Geekbot.Core.Extensions;
|
||||
using MtgApiManager.Lib.Service;
|
||||
|
||||
namespace Geekbot.Bot.Commands.Integrations
|
||||
{
|
||||
public class MagicTheGathering : TransactionModuleBase
|
||||
{
|
||||
private readonly IErrorHandler _errorHandler;
|
||||
private readonly IMtgManaConverter _manaConverter;
|
||||
|
||||
public MagicTheGathering(IErrorHandler errorHandler, IMtgManaConverter manaConverter)
|
||||
{
|
||||
_errorHandler = errorHandler;
|
||||
_manaConverter = manaConverter;
|
||||
}
|
||||
|
||||
[Command("mtg", RunMode = RunMode.Async)]
|
||||
[Summary("Find a Magic The Gathering Card.")]
|
||||
public async Task GetCard([Remainder] [Summary("card-name")] string cardName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var message = await Context.Channel.SendMessageAsync($":mag: Looking up \"{cardName}\", please wait...");
|
||||
|
||||
var service = new CardService();
|
||||
var result = service
|
||||
.Where(x => x.Name, cardName)
|
||||
// fewer cards less risk of deserialization problems, don't need more than one anyways...
|
||||
.Where(x => x.PageSize, 1);
|
||||
|
||||
var cards = await result.AllAsync();
|
||||
if (!cards.IsSuccess)
|
||||
{
|
||||
await message.ModifyAsync(properties => properties.Content = $":warning: The Gatherer reacted in an unexpected way: {cards.Exception.Message}");
|
||||
return;
|
||||
}
|
||||
|
||||
var card = cards.Value.FirstOrDefault();
|
||||
|
||||
if (card == null)
|
||||
{
|
||||
await message.ModifyAsync(properties => properties.Content = ":red_circle: I couldn't find a card with that name...");
|
||||
return;
|
||||
}
|
||||
|
||||
var eb = new EmbedBuilder
|
||||
{
|
||||
Title = card.Name,
|
||||
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 && card.Legalities.Count > 0)
|
||||
eb.AddField("Legality", string.Join(", ", card.Legalities.Select(e => e.Format)));
|
||||
|
||||
await message.ModifyAsync(properties =>
|
||||
{
|
||||
properties.Content = string.Empty;
|
||||
properties.Embed = eb.Build();
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await _errorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
|
||||
private Color GetColor(IEnumerable<string> colors)
|
||||
{
|
||||
var color = colors.FirstOrDefault();
|
||||
return color switch
|
||||
{
|
||||
"Black" => new Color(203, 194, 191),
|
||||
"White" => new Color(255, 251, 213),
|
||||
"Blue" => new Color(170, 224, 250),
|
||||
"Red" => new Color(250, 170, 143),
|
||||
"Green" => new Color(155, 211, 174),
|
||||
_ => new Color(204, 194, 212)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Geekbot.Core;
|
||||
using Geekbot.Core.ErrorHandling;
|
||||
using Geekbot.Core.Extensions;
|
||||
using Geekbot.Core.GuildSettingsManager;
|
||||
using JikanDotNet;
|
||||
|
||||
namespace Geekbot.Bot.Commands.Integrations
|
||||
{
|
||||
public class Mal : GeekbotCommandBase
|
||||
{
|
||||
private readonly IJikan _client;
|
||||
|
||||
public Mal(IErrorHandler errorHandler, IGuildSettingsManager guildSettingsManager) : base(errorHandler, guildSettingsManager)
|
||||
{
|
||||
_client = new Jikan();
|
||||
}
|
||||
|
||||
[Command("anime", RunMode = RunMode.Async)]
|
||||
[Summary("Show Info about an Anime.")]
|
||||
public async Task SearchAnime([Remainder] [Summary("anime-name")] string animeName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var results = await _client.SearchAnime(animeName);
|
||||
var anime = results.Results.FirstOrDefault();
|
||||
if (anime != null)
|
||||
{
|
||||
var eb = new EmbedBuilder
|
||||
{
|
||||
Title = anime.Title,
|
||||
Description = anime.Description,
|
||||
ImageUrl = anime.ImageURL
|
||||
};
|
||||
|
||||
eb.AddInlineField("Premiere", FormatDate(anime.StartDate))
|
||||
.AddInlineField("Ended", anime.Airing ? "-" : FormatDate(anime.EndDate))
|
||||
.AddInlineField("Episodes", anime.Episodes)
|
||||
.AddInlineField("MAL Score", anime.Score)
|
||||
.AddInlineField("Type", anime.Type)
|
||||
.AddField("MAL Link", $"https://myanimelist.net/anime/{anime.MalId}");
|
||||
|
||||
await ReplyAsync("", false, eb.Build());
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyAsync("No anime found with that name...");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ErrorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
|
||||
[Command("manga", RunMode = RunMode.Async)]
|
||||
[Summary("Show Info about a Manga.")]
|
||||
public async Task SearchManga([Remainder] [Summary("manga-name")] string mangaName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var results = await _client.SearchManga(mangaName);
|
||||
var manga = results.Results.FirstOrDefault();
|
||||
if (manga != null)
|
||||
{
|
||||
var eb = new EmbedBuilder
|
||||
{
|
||||
Title = manga.Title,
|
||||
Description = manga.Description,
|
||||
ImageUrl = manga.ImageURL
|
||||
};
|
||||
|
||||
eb.AddInlineField("Premiere", FormatDate(manga.StartDate))
|
||||
.AddInlineField("Ended", manga.Publishing ? "-" : FormatDate(manga.EndDate))
|
||||
.AddInlineField("Volumes", manga.Volumes)
|
||||
.AddInlineField("Chapters", manga.Chapters)
|
||||
.AddInlineField("MAL Score", manga.Score)
|
||||
.AddField("MAL Link", $"https://myanimelist.net/manga/{manga.MalId}");
|
||||
|
||||
await ReplyAsync("", false, eb.Build());
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyAsync("No manga found with that name...");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ErrorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
|
||||
private string FormatDate(DateTime? dateTime)
|
||||
{
|
||||
if (!dateTime.HasValue)
|
||||
{
|
||||
return DateTime.MinValue.ToString("d", Thread.CurrentThread.CurrentUICulture);
|
||||
}
|
||||
|
||||
return dateTime.Value.ToString("d", Thread.CurrentThread.CurrentUICulture);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
using Geekbot.Core;
|
||||
using Geekbot.Core.ErrorHandling;
|
||||
|
||||
namespace Geekbot.Bot.Commands.Integrations
|
||||
{
|
||||
public class UrbanDictionary : TransactionModuleBase
|
||||
{
|
||||
private readonly IErrorHandler _errorHandler;
|
||||
|
||||
public UrbanDictionary(IErrorHandler errorHandler)
|
||||
{
|
||||
_errorHandler = errorHandler;
|
||||
}
|
||||
|
||||
[Command("urban", RunMode = RunMode.Async)]
|
||||
[Summary("Lookup something on urban dictionary")]
|
||||
public async Task UrbanDefine([Remainder] [Summary("word")] string word)
|
||||
{
|
||||
try
|
||||
{
|
||||
var eb = await Geekbot.Commands.UrbanDictionary.UrbanDictionary.Run(word);
|
||||
if (eb == null)
|
||||
{
|
||||
await ReplyAsync("That word hasn't been defined...");
|
||||
return;
|
||||
}
|
||||
|
||||
await ReplyAsync(string.Empty, false, eb.ToDiscordNetEmbed().Build());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await _errorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
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 HtmlAgilityPack;
|
||||
|
||||
namespace Geekbot.Bot.Commands.Integrations
|
||||
{
|
||||
public class Wikipedia : TransactionModuleBase
|
||||
{
|
||||
private readonly IErrorHandler _errorHandler;
|
||||
private readonly IWikipediaClient _wikipediaClient;
|
||||
private readonly DatabaseContext _database;
|
||||
|
||||
public Wikipedia(IErrorHandler errorHandler, IWikipediaClient wikipediaClient, DatabaseContext database)
|
||||
{
|
||||
_errorHandler = errorHandler;
|
||||
_wikipediaClient = wikipediaClient;
|
||||
_database = database;
|
||||
}
|
||||
|
||||
[Command("wiki", RunMode = RunMode.Async)]
|
||||
[Summary("Get an article from wikipedia.")]
|
||||
public async Task GetPreview([Remainder] [Summary("article")] string articleName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var wikiLang = _database.GuildSettings.FirstOrDefault(g => g.GuildId.Equals(Context.Guild.Id.AsLong()))?.WikiLang;
|
||||
if (string.IsNullOrEmpty(wikiLang))
|
||||
{
|
||||
wikiLang = "en";
|
||||
}
|
||||
var article = await _wikipediaClient.GetPreview(articleName.Replace(" ", "_"), wikiLang);
|
||||
|
||||
if (article.Type != PageTypes.Standard)
|
||||
{
|
||||
switch (article.Type)
|
||||
{
|
||||
case PageTypes.Disambiguation:
|
||||
await ReplyAsync($"**__Disambiguation__**\r\n{DisambiguationExtractor(article.ExtractHtml)}");
|
||||
break;
|
||||
case PageTypes.MainPage:
|
||||
await ReplyAsync("The main page is not supported");
|
||||
break;
|
||||
case PageTypes.NoExtract:
|
||||
await ReplyAsync($"This page has no summary, here is the link: {article.ContentUrls.Desktop.Page}");
|
||||
break;
|
||||
case PageTypes.Standard:
|
||||
break;
|
||||
default:
|
||||
await ReplyAsync($"This page type is currently not supported, here is the link: {article.ContentUrls.Desktop.Page}");
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var eb = new EmbedBuilder
|
||||
{
|
||||
Title = article.Title,
|
||||
Description = article.Description,
|
||||
ImageUrl = article.Thumbnail?.Source.ToString(),
|
||||
Url = article.ContentUrls.Desktop.Page.ToString(),
|
||||
Color = new Color(246,246,246),
|
||||
Timestamp = article.Timestamp,
|
||||
Footer = new EmbedFooterBuilder
|
||||
{
|
||||
Text = "Last Edit",
|
||||
IconUrl = "http://icons.iconarchive.com/icons/sykonist/popular-sites/256/Wikipedia-icon.png"
|
||||
}
|
||||
};
|
||||
|
||||
eb.AddField("Description", article.Extract);
|
||||
if (article.Coordinates != null) eb.AddField("Coordinates", $"{article.Coordinates.Lat} Lat {article.Coordinates.Lon} Lon");
|
||||
await ReplyAsync("", false, eb.Build());
|
||||
}
|
||||
catch (HttpRequestException)
|
||||
{
|
||||
await ReplyAsync("I couldn't find that article");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await _errorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
|
||||
private string DisambiguationExtractor(string extractHtml)
|
||||
{
|
||||
var doc = new HtmlDocument();
|
||||
doc.LoadHtml(extractHtml);
|
||||
var nodes = doc.DocumentNode.SelectNodes("//li");
|
||||
if (nodes == null) return "(List is to long to show)";
|
||||
var sb = new StringBuilder();
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
var split = node.InnerText.Split(',');
|
||||
var title = split.First();
|
||||
var desc = string.Join(",", split.Skip(1));
|
||||
sb.AppendLine($"• **{title}** -{desc}");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
using Discord.Commands;
|
||||
using Geekbot.Core;
|
||||
// using Geekbot.Core.ErrorHandling;
|
||||
// using Geekbot.Core.GlobalSettings;
|
||||
// using Google.Apis.Services;
|
||||
// using Google.Apis.YouTube.v3;
|
||||
|
||||
namespace Geekbot.Bot.Commands.Integrations
|
||||
{
|
||||
public class Youtube : TransactionModuleBase
|
||||
{
|
||||
// private readonly IGlobalSettings _globalSettings;
|
||||
// private readonly IErrorHandler _errorHandler;
|
||||
|
||||
// public Youtube(IGlobalSettings globalSettings, IErrorHandler errorHandler)
|
||||
// {
|
||||
// _globalSettings = globalSettings;
|
||||
// _errorHandler = errorHandler;
|
||||
// }
|
||||
|
||||
[Command("yt", RunMode = RunMode.Async)]
|
||||
[Summary("Search for something on youtube.")]
|
||||
public async Task Yt([Remainder] [Summary("title")] string searchQuery)
|
||||
{
|
||||
await ReplyAsync("The youtube command is temporarily disabled");
|
||||
|
||||
// var key = _globalSettings.GetKey("YoutubeKey");
|
||||
// if (string.IsNullOrEmpty(key))
|
||||
// {
|
||||
// await ReplyAsync("No youtube key set, please tell my senpai to set one");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// try
|
||||
// {
|
||||
// var youtubeService = new YouTubeService(new BaseClientService.Initializer
|
||||
// {
|
||||
// ApiKey = key,
|
||||
// ApplicationName = GetType().ToString()
|
||||
// });
|
||||
//
|
||||
// var searchListRequest = youtubeService.Search.List("snippet");
|
||||
// searchListRequest.Q = searchQuery;
|
||||
// searchListRequest.MaxResults = 2;
|
||||
//
|
||||
// var searchListResponse = await searchListRequest.ExecuteAsync();
|
||||
//
|
||||
// var result = searchListResponse.Items[0];
|
||||
//
|
||||
// await ReplyAsync(
|
||||
// $"\"{result.Snippet.Title}\" from \"{result.Snippet.ChannelTitle}\" https://youtu.be/{result.Id.VideoId}");
|
||||
// }
|
||||
// catch (Exception e)
|
||||
// {
|
||||
// await _errorHandler.HandleCommandException(e, Context);
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
using Geekbot.Core;
|
||||
using Geekbot.Core.ErrorHandling;
|
||||
using Geekbot.Core.RandomNumberGenerator;
|
||||
|
||||
namespace Geekbot.Bot.Commands.Randomness
|
||||
{
|
||||
public class BenedictCumberbatchNameGenerator : TransactionModuleBase
|
||||
{
|
||||
private readonly IErrorHandler _errorHandler;
|
||||
private readonly IRandomNumberGenerator _randomNumberGenerator;
|
||||
|
||||
public BenedictCumberbatchNameGenerator(IErrorHandler errorHandler, IRandomNumberGenerator randomNumberGenerator)
|
||||
{
|
||||
_errorHandler = errorHandler;
|
||||
_randomNumberGenerator = randomNumberGenerator;
|
||||
}
|
||||
|
||||
[Command("bdcb", RunMode = RunMode.Async)]
|
||||
[Summary("Benedict Cumberbatch Name Generator")]
|
||||
public async Task GetQuote()
|
||||
{
|
||||
try
|
||||
{
|
||||
var firstnameList = new List<string>
|
||||
{
|
||||
"Bumblebee", "Bandersnatch", "Broccoli", "Rinkydink", "Bombadil", "Boilerdang", "Bandicoot", "Fragglerock", "Muffintop", "Congleton", "Blubberdick", "Buffalo", "Benadryl",
|
||||
"Butterfree", "Burberry", "Whippersnatch", "Buttermilk", "Beezlebub", "Budapest", "Boilerdang", "Blubberwhale", "Bumberstump", "Bulbasaur", "Cogglesnatch", "Liverswort",
|
||||
"Bodybuild", "Johnnycash", "Bendydick", "Burgerking", "Bonaparte", "Bunsenburner", "Billiardball", "Bukkake", "Baseballmitt", "Blubberbutt", "Baseballbat", "Rumblesack",
|
||||
"Barister", "Danglerack", "Rinkydink", "Bombadil", "Honkytonk", "Billyray", "Bumbleshack", "Snorkeldink", "Beetlejuice", "Bedlington", "Bandicoot", "Boobytrap", "Blenderdick",
|
||||
"Bentobox", "Pallettown", "Wimbledon", "Buttercup", "Blasphemy", "Syphilis", "Snorkeldink", "Brandenburg", "Barbituate", "Snozzlebert", "Tiddleywomp", "Bouillabaisse",
|
||||
"Wellington", "Benetton", "Bendandsnap", "Timothy", "Brewery", "Bentobox", "Brandybuck", "Benjamin", "Buckminster", "Bourgeoisie", "Bakery", "Oscarbait", "Buckyball",
|
||||
"Bourgeoisie", "Burlington", "Buckingham", "Barnoldswick", "Bumblesniff", "Butercup", "Bubblebath", "Fiddlestick", "Bulbasaur", "Bumblebee", "Bettyboop", "Botany", "Cadbury",
|
||||
"Brendadirk", "Buckingham", "Barnabus", "Barnacle", "Billybong", "Botany", "Benddadick", "Benderchick"
|
||||
};
|
||||
|
||||
var lastnameList = new List<string>
|
||||
{
|
||||
"Coddleswort", "Crumplesack", "Curdlesnoot", "Calldispatch", "Humperdinck", "Rivendell", "Cuttlefish", "Lingerie", "Vegemite", "Ampersand", "Cumberbund", "Candycrush",
|
||||
"Clombyclomp", "Cragglethatch", "Nottinghill", "Cabbagepatch", "Camouflage", "Creamsicle", "Curdlemilk", "Upperclass", "Frumblesnatch", "Crumplehorn", "Talisman", "Candlestick",
|
||||
"Chesterfield", "Bumbersplat", "Scratchnsniff", "Snugglesnatch", "Charizard", "Carrotstick", "Cumbercooch", "Crackerjack", "Crucifix", "Cuckatoo", "Cockletit", "Collywog",
|
||||
"Capncrunch", "Covergirl", "Cumbersnatch", "Countryside", "Coggleswort", "Splishnsplash", "Copperwire", "Animorph", "Curdledmilk", "Cheddarcheese", "Cottagecheese", "Crumplehorn",
|
||||
"Snickersbar", "Banglesnatch", "Stinkyrash", "Cameltoe", "Chickenbroth", "Concubine", "Candygram", "Moldyspore", "Chuckecheese", "Cankersore", "Crimpysnitch", "Wafflesmack",
|
||||
"Chowderpants", "Toodlesnoot", "Clavichord", "Cuckooclock", "Oxfordshire", "Cumbersome", "Chickenstrips", "Battleship", "Commonwealth", "Cunningsnatch", "Custardbath",
|
||||
"Kryptonite", "Curdlesnoot", "Cummerbund", "Coochyrash", "Crackerdong", "Crackerdong", "Curdledong", "Crackersprout", "Crumplebutt", "Colonist", "Coochierash", "Anglerfish",
|
||||
"Cumbersniff", "Charmander", "Scratch-n-sniff", "Cumberbitch", "Pumpkinpatch", "Cramplesnutch", "Lumberjack", "Bonaparte", "Cul-de-sac", "Cankersore", "Cucumbercatch", "Contradict"
|
||||
};
|
||||
|
||||
var lastname = lastnameList[_randomNumberGenerator.Next(0, lastnameList.Count - 1)];
|
||||
var firstname = firstnameList[_randomNumberGenerator.Next(0, firstnameList.Count - 1)];
|
||||
|
||||
await ReplyAsync($"{firstname} {lastname}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await _errorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Geekbot.Core;
|
||||
using Geekbot.Core.ErrorHandling;
|
||||
|
||||
namespace Geekbot.Bot.Commands.Randomness.Cat
|
||||
{
|
||||
public class Cat : TransactionModuleBase
|
||||
{
|
||||
private readonly IErrorHandler _errorHandler;
|
||||
|
||||
public Cat(IErrorHandler errorHandler)
|
||||
{
|
||||
_errorHandler = errorHandler;
|
||||
}
|
||||
|
||||
[Command("cat", RunMode = RunMode.Async)]
|
||||
[Summary("Return a random image of a cat.")]
|
||||
public async Task Say()
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await HttpAbstractions.Get<CatResponseDto>(new Uri("https://aws.random.cat/meow"));
|
||||
var eb = new EmbedBuilder
|
||||
{
|
||||
ImageUrl = response.File
|
||||
};
|
||||
await ReplyAsync("", false, eb.Build());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await _errorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Geekbot.Bot.Commands.Randomness.Cat
|
||||
{
|
||||
internal class CatResponseDto
|
||||
{
|
||||
[JsonPropertyName("file")]
|
||||
public string File { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Geekbot.Bot.Commands.Randomness.Chuck
|
||||
{
|
||||
internal class ChuckNorrisJokeResponseDto
|
||||
{
|
||||
[JsonPropertyName("value")]
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
using Geekbot.Core;
|
||||
using Geekbot.Core.ErrorHandling;
|
||||
|
||||
namespace Geekbot.Bot.Commands.Randomness.Chuck
|
||||
{
|
||||
public class ChuckNorrisJokes : TransactionModuleBase
|
||||
{
|
||||
private readonly IErrorHandler _errorHandler;
|
||||
|
||||
public ChuckNorrisJokes(IErrorHandler errorHandler)
|
||||
{
|
||||
_errorHandler = errorHandler;
|
||||
}
|
||||
|
||||
[Command("chuck", RunMode = RunMode.Async)]
|
||||
[Summary("A random chuck norris joke")]
|
||||
public async Task Say()
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await HttpAbstractions.Get<ChuckNorrisJokeResponseDto>(new Uri("https://api.chucknorris.io/jokes/random"));
|
||||
await ReplyAsync(response.Value);
|
||||
}
|
||||
catch (HttpRequestException)
|
||||
{
|
||||
await ReplyAsync("Api down...");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await _errorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue