Compare commits

..

265 commits

Author SHA1 Message Date
4b62dd99aa
Revert "Carefree dependency updates"
This reverts commit d9e29f8b95.
2022-07-26 20:56:41 +02:00
d9e29f8b95
Carefree dependency updates 2022-07-26 02:16:19 +02:00
dc800d144d
Move the asp logger to core and and make it a generic ilogger adapter 2022-07-26 02:01:44 +02:00
a3e10b15c1
Prevent users from giving others negative cookies 2022-07-25 15:41:44 +02:00
2946ed523e
Geekbot version 4.4 2022-07-22 19:47:46 +02:00
b0d603e518
Update discord.net to v3 2022-07-22 19:46:32 +02:00
be06870892
Update libsodium 2022-07-22 18:56:26 +02:00
94bdc1081b
Temporarily disable the youtube command 2022-07-22 18:33:56 +02:00
29f44c34bc
Handle possible error with non-existing role properly in the reaction listener 2022-07-22 18:26:48 +02:00
245e88726a
Remove the !hello/greeting command, the api is no longer available 2022-07-22 18:00:54 +02:00
15e1d10839
remove the !corona command 2022-07-22 18:00:12 +02:00
c1b5a4d449
Upgrade to ansible 2.12 in an attempt to fix the deployment 2022-07-19 16:53:35 +02:00
fdd23ad00f
Fix some unchecked access in the giveRole function in the reactionListener that would crash the bot if an expected value wasn't around 2022-07-19 16:22:46 +02:00
7cc9fc92d9
Comment out the "Cleanup Old Container" step from the ansible script 2022-05-22 18:16:54 +02:00
5b40b7b2e7
Downgrade to ansible runner 1.4.7 2022-05-22 18:05:53 +02:00
eefd8452cd
Use ansible-runner docker container from quay.io, thank you red hat for removing it from docker hub... 2022-05-22 17:25:01 +02:00
a3623ccddd
Temporarily don't ignore commands coming from the eevent guild (169844523181015040) 2022-05-22 17:11:09 +02:00
b30c048bac
Fix a bug where users could modify their own karma, because the user check was an object reference check rather than a user-id check 2022-04-15 10:33:09 +01:00
193a651495
Move the error embed from /karma into the embad class as a static method so it can be used by other commands as well 2021-12-27 21:58:49 +08:00
3fa8fac867
Remove forgotten platform target specifier from Startup.csproj 2021-12-27 21:55:44 +08:00
2fe8e2fa4f
Add some additional logging to highlight startup parameters 2021-12-18 17:12:10 +01:00
bdaf16f53f
Remove PlatfromTarget from the csproj files so that the project can run on arm64 2021-12-18 17:09:48 +01:00
0e5785e3a1
Rework the output of the /choose command to show all the choices that the user has entered; which are somewhat lost with / commands, as others can't see what the user entered without an additional mouseclick 2021-11-16 00:40:19 +01:00
d03525d363
Port the choose command to / commands 2021-11-14 23:44:02 +01:00
17cb5951ee
Allow Interaction Commands to run for up to 15 minutes by replying to discord immediately and handling the command in a separate thread 2021-11-14 03:39:36 +01:00
699a93200b
Port the emojify command to / commands 2021-11-14 01:18:36 +01:00
1b396a529c
Make sure that interaction commands without changes are not being patched unnecessarily 2021-11-14 01:16:50 +01:00
0f7f936492
Add explicit nullability for some fields of the Interaction Command record 2021-11-14 01:14:26 +01:00
bcc2742e81
Add retry logic for Post, Patch and Delete to the HttpAbstractions 2021-11-14 01:12:32 +01:00
5d6e5cf2ad
Make the EmojiConverter static 2021-11-13 16:26:14 +01:00
df6672305d
Downgrade Entity Framework from 6-rc2 to 5.0.12 2021-11-10 00:48:42 +01:00
c2c30846fb
Ensure the logger doesn't fail to log based on some stacktrace field during json serialization 2021-11-10 00:48:42 +01:00
e13cf9d830
Remove the single file build flag because it seems to break localization 2021-11-09 18:46:10 +01:00
09af445436
Make sure that the /v1/command endpoint actually ignores the parameter default value during json serialization if its null, instead of making it an empty string 2021-11-09 01:44:31 +01:00
4d97201319
Set parameter default value to null instead of an empty string in the bot command lookup 2021-11-09 01:25:42 +01:00
9cfac1ad38
Remove the runtime identifier from the interactions project 2021-11-09 01:25:32 +01:00
9beef55979
Read the bot commands on startup and provide them to the /v1/commands endpoint 2021-11-09 00:53:09 +01:00
4f4e16d674
Add a way to lookup all bot commands in the bot assembly without using the commandService provided by discord.net 2021-11-09 00:51:37 +01:00
ae1b28ff77
Decouple the WebApi and the Bot and move the startup code into a new project 2021-11-09 00:49:46 +01:00
ee31e66e75
Fix the datetime issue when writing to the database for !cookie and the karma commands as well 2021-11-08 00:15:25 +01:00
c9af82015b
Fix !quote, use ToUniversalTime() on the timestamp saved to the database when saving a new quote 2021-11-08 00:02:12 +01:00
7d4a81dcde
Make sure all DateTimeOffsets that are written to the DB are in UTC 2021-11-07 02:52:35 +01:00
1a1d1406ec
Ignore compiler warning CS8618 in the Commands project 2021-11-07 00:50:53 +01:00
65d84c0ba6
Move CommandPreconditions into Geekbot.Bot 2021-11-07 00:38:57 +01:00
a460041c52
Move all interaction classes to its own project 2021-11-07 00:36:20 +01:00
54cbb00880
Upgrade EntityFramework to .net6-rc2 2021-11-07 00:32:32 +01:00
d0bc5810a9
Cleanup all interaction commands for .net6 2021-11-07 00:16:11 +01:00
47299dd1de
Refactor the ASP Logger to take advantage of new .net6 features 2021-11-07 00:15:15 +01:00
e01a066920
Refactor WebApi Controllers to take advantage of new .net6 features 2021-11-07 00:08:08 +01:00
6b3a3a9ec2
Rewrite the WebApi startup to take advantage of new .net6 features 2021-11-07 00:02:26 +01:00
4395d9e9dd
Port the karma commands to / commands 2021-11-06 19:08:42 +01:00
6d39c2d33f
Add primitive GetAvatarUrl function to a resolved interaction user 2021-11-06 19:07:28 +01:00
31f12a4110
Add command type level to interaction command dictionary 2021-11-06 18:20:16 +01:00
eb648b94d9
Make Embed Types Deserializable 2021-11-06 18:18:09 +01:00
fe1063167f
Set default embed color to DimGray 2021-11-06 18:17:44 +01:00
866c28b76b
Move embed building logic for !urban into the shared command code 2021-11-06 16:53:37 +01:00
10b29cce8a
Add function to convert Geekbot Embeds to Discord.Net Embeds for easier code sharing 2021-11-06 16:52:49 +01:00
34f15402b4
Port !urban to a / command 2021-11-06 16:23:50 +01:00
ea17ce2866
Add helper classes to simplify embed creation for interactions 2021-11-06 16:17:22 +01:00
c15a66255f
Fail if a user tries to execute a non-existing command, instead of letting them know that the command doesn't exist 2021-11-05 17:46:08 +01:00
e74aeb1403
Add simple response function to the InteractionBase to reduce the InteractionResponse copying 2021-11-05 17:45:11 +01:00
5a520ff567
Pass interaction data into all InteractionBase hooks 2021-11-02 21:57:01 +01:00
01df35b12b
Capture interaction exceptions in sentry 2021-11-02 21:56:40 +01:00
44ae2eeaf6
Update sentry SDK to v3.11.0 2021-11-02 21:54:47 +01:00
8c2eabfd21
Fully remove the dependency on Newtonsoft.Json 2021-11-01 01:27:04 +01:00
cf0cd743b8
Get rid of Newtonsoft.Json everywhere but the HTTP abstractions 2021-11-01 01:04:20 +01:00
7b06965f14
Switch from Newtonsoft.Json to System.Text.Json in the logger 2021-11-01 00:34:50 +01:00
6f94de5a14
Reduce ulong to long casts in the roll command 2021-11-01 00:16:42 +01:00
616ac5e430
Use structs instead of enums for interaction option names 2021-10-31 23:50:48 +01:00
913ea23732
Remove OptionChoice type inheritors in favour of just supporting strings as values 2021-10-31 23:23:02 +01:00
e20faa43e1
Port rank for slash commands 2021-10-31 23:21:15 +01:00
772557978b
Set guild language when executing interaction 2021-10-31 22:33:31 +01:00
177c773451
Remove the discord socket client dependency from the webapi 2021-10-31 20:22:42 +01:00
29a2e5c4a2
Don't go into an infinite await when stopping the app if the webapi is running 2021-10-31 20:18:27 +01:00
78c139293f
Move the roll command logic into the shared commands project 2021-10-31 20:16:56 +01:00
89ea6df6e2
Move Localizations into core 2021-10-31 20:15:08 +01:00
29e22acbc0
Move the RollTimeout record to the shared commands project 2021-10-30 15:48:28 +02:00
dd941f5f94
Add new project to share command logic between gateway and slash commands 2021-10-30 15:47:54 +02:00
588c93b87d
Check the users previous roll in the /roll command 2021-10-30 14:47:56 +02:00
a1893c7414
Add DI support for interaction commands 2021-10-30 14:46:23 +02:00
24749d9009
Add and use interaction command hooks for BeforeExecute, AfterExecute, OnException and GetExceptionResponse 2021-10-30 14:43:57 +02:00
9a2bf84a05
Add applicationId/guildId mapping to the interaction registrar 2021-09-20 02:23:44 +02:00
d2b9daac57
Add roll interaction 2021-09-20 02:14:25 +02:00
d17ca4c556
Pass entire Interaction object to interaction commands, instead of just the data 2021-09-20 02:12:57 +02:00
aaea8d0540
Add user property to the interaction member object 2021-09-20 02:11:53 +02:00
d975594d21
Add mention property to the interaction user object 2021-09-20 02:11:16 +02:00
2de6381f9d
Change type of Value in InteractionOption to JsonElement because it's unknown at deserialization time what type it will be 2021-09-20 02:10:28 +02:00
65bb7f6cac
Creat initial interaction command framework 2021-09-20 01:31:24 +02:00
60547140ea
Create all interaction models 2021-09-20 01:28:26 +02:00
209887e237
Add sentry support to the webapi 2021-09-19 16:19:40 +02:00
d81fb2a3d9
Add initial interaction support 2021-09-19 16:11:06 +02:00
85d06b76e0
Add --disable-gateway parameter to the run parameters to stop the bot from connecting to discord. Useful when working on the web-api 2021-09-19 16:06:11 +02:00
447c6d8042
Remove a database call from !quote by delegating the randomness to the database. 2021-09-19 00:58:00 +02:00
1b9d8732d5
Add tracing to the !quote embed builder 2021-09-19 00:57:10 +02:00
954c6c2be3
Remove Guild ID from the tracing tags 2021-09-17 15:55:13 +02:00
3d117aebe1
Split reply and quote embed building trace apart in !quote 2021-09-17 15:12:22 +02:00
0a9099a6d2
Move Sentry Init priority between the discord connection and dependency registration 2021-09-17 14:53:24 +02:00
d16828077d
Set Transaction Status to OK by default 2021-09-17 14:33:55 +02:00
a1f8d033c2
Use the TransactionModuleBase for all commands that haven't implemented GeekbotCommandBase 2021-09-17 14:31:24 +02:00
f02c30e660
Remove the mod command class 2021-09-17 14:30:50 +02:00
833a8a0dd8
Split Transactions from the GeekbotCommandBase 2021-09-17 14:27:46 +02:00
d708525a2f
Add traces to the !quote command 2021-09-17 14:07:19 +02:00
aa826f962d
Add traces to the !roll command 2021-09-17 14:06:34 +02:00
3299ac4eab
Add generic sentry tracing to the main command module 2021-09-17 14:06:10 +02:00
1f518e980c
Move Sentry SDK init to the main init process 2021-09-17 14:04:30 +02:00
5c507b026c
Remove random.org integration 2021-09-17 14:03:35 +02:00
989057a0b0
Migrate from RavenSharp to the SentrySDK 2021-09-17 12:22:26 +02:00
f19ddb30b2
Replace RNGCryptoServiceProvider with System.Security.Cryptography.RandomNumberGenerator 2021-09-17 11:23:20 +02:00
e712403dd9
Upgrade Sumologic, jikan and HtmlAgilityPack 2021-09-17 11:21:42 +02:00
18ece35ffe
Remove System.Timers.Timer ambiguity for .net6-preview7 2021-08-11 23:08:00 +02:00
f22956368b
Remove !gdq 2021-08-11 18:01:19 +02:00
9a55d8447f
Upgrade discord.net to version 2.4.0 2021-08-11 18:01:19 +02:00
90668b6aac
Add padding at the end of things for the !slap command 2021-07-10 00:49:03 +02:00
8d037c786e
Reenable MSBuildEnableWorkloadResolver 2021-07-10 00:41:22 +02:00
86068ecc44
Disable self contained publishing for the test, web and bot dlls 2021-07-10 00:05:59 +02:00
8fcc629106
Disable MSBuildEnableWorkloadResolver in gitlab ci 2021-07-09 20:39:04 +02:00
611b179d62
Add a piece of low fat mozzarella to the !slap command 2021-07-09 20:21:17 +02:00
5a50ba5820
Add total quotes of a user to !stats 2021-04-20 22:57:36 +02:00
8bd8efa66a
Remove the !evergiven command 2021-04-07 22:49:13 +02:00
153ce3dca4
Translate !8ball 2021-03-29 19:02:31 +02:00
5b99ee951b
Evergiven is free once again 2021-03-29 17:45:43 +02:00
9ad39058ac
Update the !evergiven command to directly reflect what istheshipstillstuck.com says 2021-03-29 11:39:50 +02:00
41e0a9f8d7
Add !evergiven to see if the ship is still stuck in the suez canal 2021-03-26 00:11:15 +01:00
49870b6b91
Stop using single-file deployments due to missing locale data 2021-03-20 04:17:14 +01:00
52fe5bdec1
Switch back to a debian container, alpine is missing locale info 2021-03-20 04:06:10 +01:00
f25c9250ec
Switch to an alpine container and single file, self-contained deployments 2021-03-19 01:10:22 +01:00
1c64328587
Upgrade to .net6 preview 2021-03-19 00:21:35 +01:00
d1d57ba714
Refactor karma commands 2021-03-18 23:53:56 +01:00
c77b501b6c
Fix message sent when user is trying give themselves !neutral karma 2021-03-18 12:27:26 +01:00
6c142f41d3
Upgrade discord.net 2021-03-18 12:23:18 +01:00
c1b8394e1b
Add a !neutral command for karma, it does nothing. 2021-03-18 12:06:41 +01:00
eddd005d34
Add translations for !corona 2021-01-25 01:40:51 +01:00
644d877e29
Show country flag when !corona has a country code parameter 2021-01-25 00:49:18 +01:00
bbb9b89422
Add Support for emoji flags in the emoji converter 2021-01-25 00:48:42 +01:00
4fd62e9184
Make sure that the build version suffix is not a number 2021-01-24 23:06:47 +01:00
0434335239
Fix !corona by changing data source to covid19-api.org, added a country code parameter as well 2021-01-24 22:15:15 +01:00
21303bfca8
Remove code in the stats handler that checks if 2021 has started 2021-01-01 18:06:40 +01:00
e495e2df17
Use the correct unit when listing messages in a season 2021-01-01 02:48:28 +01:00
d477a4b056
Update the translations for !rank with the new rank types 2020-12-30 23:34:22 +01:00
8bdf2e9681
Add support for !rank quote 2020-12-30 23:33:53 +01:00
17f62d7607
Ignore the discord bots server when updating stats 2020-12-30 23:23:40 +01:00
01f0d2f43b
Check every 5 minutes if it's 2021 instead of every hour in the stats handler 2020-12-30 23:19:15 +01:00
29bb8035fe
add !rank seasons to the rank command 2020-12-30 23:17:00 +01:00
29c0def713
Start counting messages per quarter starting 1 january 2021 2020-12-29 22:33:19 +01:00
7e792bd782
Fallback to user repo when retrieving a user via the discord gateway fails or times out 2020-12-29 17:12:03 +01:00
714b0008bc
Allow a die to have 145 sides 2020-12-11 22:39:05 +01:00
97d479adc4 Merge branch 'quote-with-id-deprecation' into 'master'
Remove the ability to create quotes from message ids

See merge request dbgit/open/geekbot!19
2020-12-03 09:55:49 +00:00
baf09e2f38
Remove the ability to create quotes from message ids 2020-11-25 15:21:55 +01:00
09dbeb9766
Fix a stupid bug with the !avatar command 2020-11-24 21:40:33 +01:00
4c3b7044ce
Don't count 1 to many sides in the !dice command 2020-11-02 18:33:05 +01:00
2fb815bc97
Don't use embeds for !dog 2020-11-02 18:01:57 +01:00
ffab56d4a5
Delete original message from user after using !emojify 2020-10-23 21:46:59 +02:00
fe51dfe540
Revert last commit 2020-10-09 17:25:53 +02:00
28a5b9322e
Allow Rune#0007 to bypass the ignored servers restriction 2020-10-09 17:10:49 +02:00
41795aa13f
Small wording tweaks to the !anime and !manga commands. They also no long need their description html decoded. 2020-09-24 18:29:21 +02:00
ed7748833a
Format dates in the !anime and !manga commands with the correct culture info 2020-09-24 12:49:55 +02:00
7ef0b6a319
Swap the my anime list wrapper for the !anime and !manga commands 2020-09-24 12:21:21 +02:00
216188f61f
Add a deprecation warning for quoting by message ID 2020-09-23 16:46:13 +02:00
58bd4d17d0
Optimize the !quote database query 2020-09-23 16:28:50 +02:00
ae9b9caeb9
Add random.org for random number generation. 2020-09-23 16:05:43 +02:00
b743539c74
Upgrade to .net5 rc1 and fix all breaking changes in the web api since .net core 2.2 2020-09-22 13:06:57 +02:00
482a74839a
Fix some swiss german grammar (never heard of it either) in the !quote command 2020-09-22 12:24:12 +02:00
cbe2aa23c9
Make sure that examples in the !urban command are not longer than 1024 characters. 2020-09-15 23:25:40 +02:00
d3c284102b
Add an api endpoint to shutdown the bot from the browser 2020-09-15 00:14:27 +02:00
f71349b378
Reduce logging by making the user repo update message a debug log message 2020-09-08 20:20:09 +02:00
81373b7614
Make the ErrorHandler more resilient in case an exception is thrown during the error handling 2020-08-31 21:50:01 +02:00
ce1153a0e2
Upgrade Entity Framework to preview 8 2020-08-31 19:29:03 +02:00
8246c7a862
When json logging is enabled, log it to the console without color or additional timestamp, also log the messages with the correct log level. Remove the logs folder. 2020-08-31 18:46:00 +02:00
546b5450e7
Deal with MTG Gatherer downtime 2020-08-26 23:28:03 +02:00
3c4a5c638b
Upgrade Microsoft.Extensions.* to .NET5 preview 8 2020-08-26 23:15:57 +02:00
a78e92d230
Remove YamlDotNet 2020-08-15 00:40:59 +02:00
79fb7dece6 Merge branch 'rework-localization' into 'master'
Rework Localization

See merge request dbgit/open/geekbot!18
2020-08-14 21:47:44 +00:00
60e36daaec
Translate !stats 2020-08-14 23:34:02 +02:00
33829e91bc
Delete the TranslationHandler and the old translations file. Refactor GeekbotCommandBase to get the server language from guild settings. Create DateLocalization to create a localized relative time remaining string. 2020-08-14 23:15:11 +02:00
078c884df7
Convert Role command to new localization method 2020-08-14 18:11:52 +02:00
90af781c7b
Start Using resource files (.resx) for translations. Create GeekbotCommandBase to reduce command boilerplate. Convert admin, choose, cookies, karma, quote, rank, roll and ship to the new localization method. 2020-08-14 03:30:54 +02:00
12388fd7d0
Curate Media Files 2020-08-14 00:12:14 +02:00
187fd6a04f
Remove code duplication from !quote 2020-08-13 22:46:53 +02:00
726ee77ed4
Fix bug where message links from discord stable would be considered invalid 2020-08-13 20:04:54 +02:00
ad086a5e0c
Make it possible to create quotes from message links 2020-08-13 19:51:56 +02:00
7942308059
Alias "!quote save" with "!quote add" 2020-08-13 18:22:09 +02:00
c893e45004 Merge branch 'TheGreatSplit' into 'master'
The Great Split

See merge request dbgit/open/geekbot!17
2020-08-13 15:59:58 +00:00
d68ce459ef
Fix CI Pipeline: tests should be executed from the tests folder (with a lowercase t) 2020-08-13 17:47:28 +02:00
bd117e2595
Fix Translations file path 2020-08-13 17:42:33 +02:00
97ad54df9e
Rename the folder Tests to tests 2020-08-13 17:23:03 +02:00
61ce14a61d
Change .gitlab-ci and dockerfile to fit the new project structure 2020-08-08 22:33:02 +02:00
fc0af492ad
Split Geekbot.net into src/Bot, src/Core, and src/Web 2020-08-08 22:24:01 +02:00
7b6dd2d2f9
Rename the clients namescape to MalClient, because that was the only thing in there, while other clients got their own namespaces 2020-08-08 21:17:02 +02:00
c22d0cf941
Merge WikipediaApi into the main code base 2020-08-08 21:15:17 +02:00
3813290f89
Simplefy version number during in CI pipeline 2020-08-08 20:55:14 +02:00
9003d6249e
Add !mmr to get League of Legends MMR numbers 2020-07-28 22:14:59 +02:00
913b4a5f10
Change the HttpAbstractions so that the caller can provide its own HttpClient instead of creating parameters for every HttpClient Option 2020-07-28 22:14:22 +02:00
4659f793f5
Add !lmgtfy 2020-07-22 14:32:47 +02:00
fff2324232
Fix build warnings in MediaProvider.cs and TranslationHandler.cs 2020-07-22 14:17:08 +02:00
77e912501d
Upgrade to .NET5 preview 7 2020-07-22 14:09:26 +02:00
4cd7ac1d79
Fix broken DM Channel support 2020-07-15 17:10:50 +02:00
5e9cb8a4c1
Make main async 2020-07-15 03:11:36 +02:00
efed2f7120
Add the !corona command 2020-07-15 03:09:43 +02:00
ba0d116f3e
Add an abstraction for http calls 2020-07-15 02:52:13 +02:00
0589b8e91b
Update EntityFrameworkCore and microsoft.extensions.* to .net5 preview 6 2020-07-10 22:32:42 +02:00
c94d73736d
Use greetings.dev instead of hardcoded greetings 2020-06-30 17:54:01 +02:00
580a514ce5
Rework the media provider 2020-06-25 15:12:41 +02:00
cc22774729
Make sure a die can actually land on its highest number 2020-06-21 21:12:34 +02:00
859db4ebdd
Add some unit tests for the dice parser 2020-06-21 15:49:18 +02:00
acd1cee16c
Reformat !dice help 2020-06-21 03:43:11 +02:00
6d44960867
Rewrite the !dice command from scratch 2020-06-21 03:33:05 +02:00
d7e313c9fa
Update readme 2020-06-20 03:43:02 +02:00
279a8975c9
Expose web api on port 12995 again 2020-06-20 03:42:41 +02:00
194bfd3d3b
remove traces of the docker branch from the ci file 2020-06-20 03:27:08 +02:00
8f41999015
Expose the web api directly on port 80 2020-06-20 03:25:28 +02:00
619f63067c
Remove the ability the lookup username history for moderators 2020-06-20 03:23:27 +02:00
56f788878a
Fix Sumologic environment variable in docker 2020-06-20 03:17:17 +02:00
d9f8e9a80e
Add redshift (and DigitalOcean Managed DB) compatibility and start using a string building to create the sql connection string 2020-06-20 03:05:51 +02:00
a4b914d576
Use Ansible for deployment 2020-06-20 02:51:31 +02:00
3213e10b88
Add Sumologic and Sentry to the run parameters 2020-06-20 00:20:00 +02:00
f23b8099f1
Try to create a docker image 2020-06-19 22:20:05 +02:00
e0f17d00ea
Upgrade to .NET 5 2020-06-19 22:19:26 +02:00
a0b1ec44f6
Make it possible to connect to the database with ssl enabled 2020-06-19 21:46:19 +02:00
4655424fb0
Remove prometheus-net from the dependencies 2020-06-19 12:40:17 +02:00
9cc944fcc1
Use a single message for !mtg and show a searching message when the command starts executing 2020-06-19 12:28:22 +02:00
e564e80849
Copy every file in storage by default instead of listing every single one 2020-06-19 12:15:17 +02:00
76bb645394
Inline MTG Emoji converter 2020-06-19 12:08:28 +02:00
1ea851be22
Release Geekbot 4.2 2020-06-19 04:13:26 +02:00
fb676e8918
Add GuildSettingsManager to centrally manage guild settings 2020-06-19 04:10:26 +02:00
83dc2c8e49
Attempt to improve code quality with a fearless refactor
- Completely refactor Program.cs
- Split Handlers into separate files
- Split up the command handler into multiple functions
2020-06-19 03:34:37 +02:00
f7908c2a0c
- Remove accidental leftover from the GreetingProvider
- Add 2 additional aliases for !hello
- Change Romanization to Roman in the !hello embed
2020-06-19 00:10:43 +02:00
baea4528e1
Merge branch 'master' of gitlab.com:dbgit/open/geekbot 2020-06-18 19:19:12 +02:00
12199f4ad4
Add greetings in 299 languages 2020-06-18 19:19:02 +02:00
0898e9b6bd
Add the timeout in !roll back 2020-06-14 17:27:07 +02:00
8018d5e750
Allow configuration to be passed as environment variables 2020-06-01 15:22:41 +02:00
d91c21e607
update readme 2020-06-01 02:20:27 +02:00
6692b3bc77
Don't show a quote id when using '!quote make' 2020-06-01 02:17:15 +02:00
3108a68407
Dependency Upgrades 2020-06-01 02:16:29 +02:00
ff968a490f
Cleanup some dead code and database models 2020-06-01 01:13:45 +02:00
520633c590
Remove all redis leftovers 2020-06-01 01:02:36 +02:00
46fee88f03
Add a migration script for role listeners 2020-06-01 00:52:56 +02:00
33b17b373f
Remove all dependencies on redis 2020-05-30 17:02:17 +02:00
2e501008df
Remove !checkem 2020-05-21 15:55:04 +02:00
656393cc7b
'!quote stats' bug fixes 2020-05-12 00:04:20 +02:00
5cf1248bf0
Add !quote stats (v1) 2020-05-11 23:44:15 +02:00
c031d2bfb4
Remove Prometheus 2020-05-07 13:11:00 +02:00
fc5ff87c8f
Only the ManageMessages is now required to delete a quote 2020-04-19 13:52:44 +02:00
569715f124
fix prometheus metric server startup 2020-04-18 00:05:09 +02:00
ee548390a5
Add Prometheus with 1 metric 2020-04-17 23:48:50 +02:00
2a616f8c5d
Revert "Re-use the database connection everywhere"
This reverts commit 77b3d612f2.
2020-04-06 15:51:28 +02:00
77b3d612f2
Re-use the database connection everywhere 2020-04-06 15:35:28 +02:00
f12bdcf4cd
Translate the ship command 2020-04-04 23:05:18 +02:00
3bd7274d68
Remove some unnecessary text from the dice command 2020-04-04 21:21:15 +02:00
3568b61f38
Use the new Csharp 8 features (pattern matching and using assignments) and cleanup some insignificant resparper complaints 2020-02-08 15:58:17 +01:00
21f813d342
Remove redundant code 2020-02-08 15:42:42 +01:00
dd9cf3c5d7
Upgrade to dotnet core 3.1 2020-02-08 15:32:58 +01:00
cd04549834
Fix deployment to accomodate dotnet core 3 changes 2019-10-26 22:20:22 +02:00
20c75019f9
Upgrade to dotnet core 3 and update dependencies 2019-10-26 21:57:50 +02:00
8dd914e012
Properly log errors when adding a role emoji 2019-10-11 01:08:59 +02:00
b7b4a600cd
Fix message when a role has been added to the whitelist 2019-10-11 00:55:55 +02:00
19df65fc76
Fix out of bounds error in the urban dict. command 2019-09-19 21:27:34 +02:00
309f06370b
Cut urban dictionary word definitions at the 1800 character mark 2019-09-19 13:42:48 +02:00
f1387f824e
Remove the google command 2019-09-19 13:40:02 +02:00
143722eccf
Stop using redis for counting messages 2019-08-05 09:18:16 +02:00
1bfd7c7a12
Ignore the discord bots server aswell 2019-08-05 09:14:58 +02:00
fe4a78b743
Allow failure on sentry during deployment 2019-07-30 00:47:32 +02:00
7a250f6642
Ignore commands on certain serves 2019-07-30 00:42:08 +02:00
ac43d087b1
Add the !cookie alias and allow yaml alliases in the translations file 2019-07-21 13:04:41 +02:00
8fadff4092
Cookies timeout is now at midnight instead of every 24h 2019-06-04 23:00:16 +02:00
288c976674 rename bdcc to bdcb 2019-05-28 20:32:15 +02:00
401 changed files with 11740 additions and 5619 deletions

38
.deploy.yml Normal file
View file

@ -0,0 +1,38 @@
---
- 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

18
.gitignore vendored
View file

@ -1,16 +1,10 @@
Geekbot.net/bin
Geekbot.net/obj
Geekbot.net/tmp/
Tests/bin
Tests/obj
Backup/
/*/**/bin
/*/**/obj
src/Bot/tmp/
src/Bot/Logs/*
!/src/Bot/Logs/.keep
.vs/
UpgradeLog.htm
.idea
.vscode
Geekbot.net/Logs/*
!/Geekbot.net/Logs/.keep
Geekbot.net.sln.DotSettings.user
Geekbot.net/temp/
WikipediaApi/bin/
WikipediaApi/obj/
app

View file

@ -1,65 +1,69 @@
stages:
- build
- ops
- docker
- deploy
- ops
before_script:
- set -e
- set -u
- set -o pipefail
variables:
VERSION: 4.4.0-V$CI_COMMIT_SHORT_SHA
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
build:
Build:
stage: build
image: mcr.microsoft.com/dotnet/core/sdk:2.2
image: mcr.microsoft.com/dotnet/sdk:6.0
artifacts:
expire_in: 1h
paths:
- Geekbot.net/Binaries/
- app
script:
- dotnet restore -s https://api.nuget.org/v3/index.json -s https://www.myget.org/F/discord-net/api/v3/index.json
- dotnet test Tests
- dotnet publish --version-suffix ${CI_COMMIT_SHA:0:8} --configuration Release -o Binaries ./
- 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/
sentry:
Package:
stage: docker
image: docker
only:
- master
services:
- docker:stable-dind
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $IMAGE_TAG .
- docker push $IMAGE_TAG
Deploy:
stage: deploy
image: quay.io/ansible/ansible-runner:stable-2.12-latest
only:
- master
variables:
ANSIBLE_NOCOWS: 1
before_script:
- mkdir /root/.ssh
- cp $SSH_PRIVATE_KEY /root/.ssh/id_ed25519
- cp $SSH_PUBLIC_KEY /root/.ssh/id_ed25519.pub
- chmod -R 600 /root/.ssh
- ssh-keyscan -p 65432 $PROD_IP > /root/.ssh/known_hosts
script:
- ansible-galaxy collection install -r ansible-requirements.yml
- ansible-playbook -i $PROD_IP, .deploy.yml
Sentry:
stage: ops
image: getsentry/sentry-cli
allow_failure: true
only:
- master
dependencies:
- build
script:
- sentry-cli releases new -p geekbot 4.1.0-${CI_COMMIT_SHA:0:8}
- sentry-cli releases set-commits --auto 4.1.0-${CI_COMMIT_SHA:0:8}
- sentry-cli releases deploys 4.1.0-${CI_COMMIT_SHA:0:8} new -e Production
- sentry-cli releases new -p geekbot $VERSION
- sentry-cli releases set-commits --auto $VERSION
- sentry-cli releases deploys $VERSION new -e Production
deploy:
stage: deploy
Github Mirror:
stage: ops
image: runebaas/rsync-ssh-git
only:
- master
dependencies:
- build
- sentry
environment:
name: Production
url: https://discordapp.com/oauth2/authorize?client_id=171249478546882561&scope=bot&permissions=1416834054
before_script:
- eval $(ssh-agent -s)
- mkdir -p ~/.ssh
- '[[ -f /.dockerenv ]] && echo -e "Host *\n StrictHostKeyChecking no" > ~/.ssh/config'
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
- chmod 700 ~/.ssh
script:
- rsync -rav -e "ssh -p 65432" ./Geekbot.net/Binaries/* geekbot@$DEPIP:$DEPPATH
- ssh -p 65432 geekbot@$DEPIP "sudo systemctl restart geekbot.service"
mirror:
stage: deploy
image: runebaas/rsync-ssh-git
only:
- master
dependencies:
- build
- sentry
script:
- git push https://runebaas:$TOKEN@github.com/pizzaandcoffee/Geekbot.net.git origin/master:master -f

View file

@ -1,7 +1,7 @@
FROM microsoft/dotnet:2.1-aspnetcore-runtime
FROM mcr.microsoft.com/dotnet/aspnet:6.0
COPY Geekbot.net/Binaries /app/
COPY ./app /app/
EXPOSE 12995/tcp
WORKDIR /app
ENTRYPOINT ./run.sh
ENTRYPOINT ./Geekbot

View file

@ -3,11 +3,19 @@ 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}") = "Geekbot.net", "Geekbot.net/Geekbot.net.csproj", "{FDCB3D92-E7B5-47BB-A9B5-CFAEFA57CDB4}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "tests\Tests.csproj", "{4CAF5F02-EFFE-4FDA-BD44-EEADDBA9600E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{4CAF5F02-EFFE-4FDA-BD44-EEADDBA9600E}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "src\Core\Core.csproj", "{47671723-52A9-4668-BBC5-2BA76AE3B288}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WikipediaApi", "WikipediaApi\WikipediaApi.csproj", "{1084D499-EF94-4834-9E6A-B2AD81B60078}"
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}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -15,18 +23,34 @@ 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
{1084D499-EF94-4834-9E6A-B2AD81B60078}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1084D499-EF94-4834-9E6A-B2AD81B60078}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1084D499-EF94-4834-9E6A-B2AD81B60078}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1084D499-EF94-4834-9E6A-B2AD81B60078}.Release|Any CPU.Build.0 = Release|Any CPU
{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

View file

@ -1,268 +0,0 @@
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Geekbot.net.Database;
using Geekbot.net.Database.Models;
using Geekbot.net.Lib;
using Geekbot.net.Lib.CommandPreconditions;
using Geekbot.net.Lib.ErrorHandling;
using Geekbot.net.Lib.Extensions;
using Geekbot.net.Lib.Localization;
namespace Geekbot.net.Commands.Admin
{
[Group("admin")]
[RequireUserPermission(GuildPermission.Administrator)]
[DisableInDirectMessage]
public class Admin : GeekbotBase
{
private readonly DiscordSocketClient _client;
private readonly IErrorHandler _errorHandler;
private readonly DatabaseContext _database;
private readonly ITranslationHandler _translation;
public Admin(DatabaseContext database, DiscordSocketClient client, IErrorHandler errorHandler,
ITranslationHandler translationHandler)
{
_database = database;
_client = client;
_errorHandler = errorHandler;
_translation = translationHandler;
}
[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)
{
var guild = await GetGuildSettings(Context.Guild.Id);
guild.WelcomeMessage = welcomeMessage;
_database.GuildSettings.Update(guild);
await _database.SaveChangesAsync();
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("...");
var guild = await GetGuildSettings(Context.Guild.Id);
guild.WelcomeChannel = channel.Id.AsLong();
_database.GuildSettings.Update(guild);
await _database.SaveChangesAsync();
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...");
var guild = await GetGuildSettings(Context.Guild.Id);
guild.ModChannel = channel.Id.AsLong();
_database.GuildSettings.Update(guild);
await _database.SaveChangesAsync();
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 guild = await GetGuildSettings(Context.Guild.Id);
var modChannel = await GetModChannel(guild.ModChannel.AsUlong());
if (modChannel == null) return;
guild.ShowLeave = !guild.ShowLeave;
_database.GuildSettings.Update(guild);
await _database.SaveChangesAsync();
await modChannel.SendMessageAsync(guild.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 guild = await GetGuildSettings(Context.Guild.Id);
var modChannel = await GetModChannel(guild.ModChannel.AsUlong());
if (modChannel == null) return;
guild.ShowDelete = !guild.ShowDelete;
_database.GuildSettings.Update(guild);
await _database.SaveChangesAsync();
await modChannel.SendMessageAsync(guild.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 languageRaw)
{
try
{
var language = languageRaw.ToUpper();
var success = await _translation.SetLanguage(Context.Guild.Id, language);
if (success)
{
var guild = await GetGuildSettings(Context.Guild.Id);
guild.Language = language;
_database.GuildSettings.Update(guild);
await _database.SaveChangesAsync();
var transContext = await _translation.GetGuildContext(Context);
await ReplyAsync(transContext.GetString("NewLanguageSet"));
return;
}
await ReplyAsync(
$"That doesn't seem to be a supported language\r\nSupported Languages are {string.Join(", ", _translation.SupportedLanguages)}");
}
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();
var guild = await GetGuildSettings(Context.Guild.Id);
guild.WikiLang = language;
_database.GuildSettings.Update(guild);
await _database.SaveChangesAsync();
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 = await GetGuildSettings(Context.Guild.Id);
guild.Ping = !guild.Ping;
_database.GuildSettings.Update(guild);
await _database.SaveChangesAsync();
await ReplyAsync(guild.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 = await GetGuildSettings(Context.Guild.Id);
guild.Hui = !guild.Hui;
_database.GuildSettings.Update(guild);
await _database.SaveChangesAsync();
await ReplyAsync(guild.Hui ? "i will reply to hui now" : "No more hui's...");
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
private async Task<GuildSettingsModel> GetGuildSettings(ulong guildId)
{
var guild = _database.GuildSettings.FirstOrDefault(g => g.GuildId.Equals(guildId.AsLong()));
if (guild != null) return guild;
Console.WriteLine("Adding non-exist Guild Settings to database");
_database.GuildSettings.Add(new GuildSettingsModel()
{
GuildId = guildId.AsLong(),
Hui = false,
Ping = false,
Language = "EN",
ShowDelete = false,
ShowLeave = false,
WikiLang = "en"
});
await _database.SaveChangesAsync();
return _database.GuildSettings.FirstOrDefault(g => g.GuildId.Equals(guildId.AsLong()));
}
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;
}
}
}
}

View file

@ -1,58 +0,0 @@
using System;
using System.Text;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Geekbot.net.Lib;
using Geekbot.net.Lib.CommandPreconditions;
using Geekbot.net.Lib.ErrorHandling;
using Geekbot.net.Lib.UserRepository;
namespace Geekbot.net.Commands.Admin
{
[Group("mod")]
[RequireUserPermission(GuildPermission.KickMembers)]
[RequireUserPermission(GuildPermission.ManageMessages)]
[RequireUserPermission(GuildPermission.ManageRoles)]
[DisableInDirectMessage]
public class Mod : GeekbotBase
{
private readonly DiscordSocketClient _client;
private readonly IErrorHandler _errorHandler;
private readonly IUserRepository _userRepository;
public Mod(IUserRepository userRepositry, IErrorHandler errorHandler, DiscordSocketClient client)
{
_userRepository = userRepositry;
_errorHandler = errorHandler;
_client = client;
}
[Command("namehistory", RunMode = RunMode.Async)]
[Summary("See past usernames of an user")]
public async Task UsernameHistory([Summary("@someone")] IUser user)
{
try
{
var userRepo = _userRepository.Get(user.Id);
if (userRepo != null && userRepo.UsedNames != null)
{
var sb = new StringBuilder();
sb.AppendLine($":bust_in_silhouette: {user.Username} has been known as:");
foreach (var name in userRepo.UsedNames) sb.AppendLine($"- `{name.Name}`");
await ReplyAsync(sb.ToString());
}
else
{
await ReplyAsync($"No name changes found for {user.Username}");
}
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context,
$"I don't have enough permissions do that");
}
}
}
}

View file

@ -1,96 +0,0 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.net.Database;
using Geekbot.net.Database.Models;
using Geekbot.net.Lib;
using Geekbot.net.Lib.AlmostRedis;
using Geekbot.net.Lib.ErrorHandling;
using Geekbot.net.Lib.Extensions;
using Geekbot.net.Lib.Localization;
using Geekbot.net.Lib.RandomNumberGenerator;
using StackExchange.Redis;
namespace Geekbot.net.Commands.Games
{
public class Roll : GeekbotBase
{
private readonly IErrorHandler _errorHandler;
private readonly IAlmostRedis _redis;
private readonly DatabaseContext _database;
private readonly IRandomNumberGenerator _randomNumberGenerator;
public Roll(IAlmostRedis redis, IErrorHandler errorHandler, DatabaseContext database, IRandomNumberGenerator randomNumberGenerator)
{
_redis = redis;
_database = database;
_randomNumberGenerator = randomNumberGenerator;
_errorHandler = errorHandler;
}
[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 number = _randomNumberGenerator.Next(1, 100);
var guess = 1000;
int.TryParse(stuff, out guess);
if (guess <= 100 && guess > 0)
{
var prevRoll = _redis.Db.HashGet($"{Context.Guild.Id}:RollsPrevious2", Context.Message.Author.Id).ToString()?.Split('|');
if (prevRoll?.Length == 2)
{
if (prevRoll[0] == guess.ToString() && DateTime.Parse(prevRoll[1]) > DateTime.Now.AddDays(-1))
{
await ReplyAsync(Context.Translations.GetString("NoPrevGuess", Context.Message.Author.Mention));
return;
}
}
_redis.Db.HashSet($"{Context.Guild.Id}:RollsPrevious2", new[] {new HashEntry(Context.Message.Author.Id, $"{guess}|{DateTime.Now}")});
await ReplyAsync(Context.Translations.GetString("Rolled", Context.Message.Author.Mention, number, guess));
if (guess == number)
{
await ReplyAsync(Context.Translations.GetString("Gratz", Context.Message.Author));
_redis.Db.HashIncrement($"{Context.Guild.Id}:Rolls", Context.User.Id.ToString());
var user = await GetUser(Context.User.Id);
user.Rolls += 1;
_database.Rolls.Update(user);
await _database.SaveChangesAsync();
}
}
else
{
await ReplyAsync(Context.Translations.GetString("RolledNoGuess", Context.Message.Author.Mention, number));
}
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
private async Task<RollsModel> GetUser(ulong userId)
{
var user = _database.Rolls.FirstOrDefault(u =>u.GuildId.Equals(Context.Guild.Id.AsLong()) && u.UserId.Equals(userId.AsLong())) ?? await CreateNewRow(userId);
return user;
}
private async Task<RollsModel> CreateNewRow(ulong userId)
{
var user = new RollsModel()
{
GuildId = Context.Guild.Id.AsLong(),
UserId = userId.AsLong(),
Rolls = 0
};
var newUser = _database.Rolls.Add(user).Entity;
await _database.SaveChangesAsync();
return newUser;
}
}
}

View file

@ -1,69 +0,0 @@
using System;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.net.Lib;
using Geekbot.net.Lib.ErrorHandling;
using Geekbot.net.Lib.GlobalSettings;
using Newtonsoft.Json;
namespace Geekbot.net.Commands.Integrations.Google
{
public class Google : GeekbotBase
{
private readonly IErrorHandler _errorHandler;
private readonly IGlobalSettings _globalSettings;
public Google(IErrorHandler errorHandler, IGlobalSettings globalSettings)
{
_errorHandler = errorHandler;
_globalSettings = globalSettings;
}
[Command("google", RunMode = RunMode.Async)]
[Summary("Google Something.")]
public async Task AskGoogle([Remainder, Summary("search-text")] string searchText)
{
try
{
using (var client = new WebClient())
{
var apiKey = _globalSettings.GetKey("GoogleGraphKey");
if (string.IsNullOrEmpty(apiKey))
{
await ReplyAsync("No Google API key has been set, please contact my owner");
return;
}
var url = new Uri($"https://kgsearch.googleapis.com/v1/entities:search?languages=en&limit=1&query={searchText}&key={apiKey}");
var responseString = client.DownloadString(url);
var response = JsonConvert.DeserializeObject<GoogleKgApiResponseDto>(responseString);
if (!response.ItemListElement.Any())
{
await ReplyAsync("No results were found...");
return;
}
var data = response.ItemListElement.First().Result;
var eb = new EmbedBuilder
{
Title = data.Name
};
if(!string.IsNullOrEmpty(data.Description)) eb.WithDescription(data.Description);
if(!string.IsNullOrEmpty(data.DetailedDtoDescription?.Url)) eb.WithUrl(data.DetailedDtoDescription.Url);
if(!string.IsNullOrEmpty(data.DetailedDtoDescription?.ArticleBody)) eb.AddField("Details", data.DetailedDtoDescription.ArticleBody);
if(!string.IsNullOrEmpty(data.Image?.ContentUrl)) eb.WithThumbnailUrl(data.Image.ContentUrl);
await ReplyAsync("", false, eb.Build());
}
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -1,9 +0,0 @@
namespace Geekbot.net.Commands.Integrations.Google
{
public class GoogleKgApiDetailedDto
{
public string ArticleBody { get; set; }
public string Url { get; set; }
public string License { get; set; }
}
}

View file

@ -1,8 +0,0 @@
namespace Geekbot.net.Commands.Integrations.Google
{
public class GoogleKgApiElementDto
{
public GoogleKgApiResultDto Result { get; set; }
public double ResultScore { get; set; }
}
}

View file

@ -1,8 +0,0 @@
namespace Geekbot.net.Commands.Integrations.Google
{
public class GoogleKgApiImageDto
{
public string ContentUrl { get; set; }
public string Url { get; set; }
}
}

View file

@ -1,9 +0,0 @@
using System.Collections.Generic;
namespace Geekbot.net.Commands.Integrations.Google
{
public class GoogleKgApiResponseDto
{
public List<GoogleKgApiElementDto> ItemListElement { get; set; }
}
}

View file

@ -1,10 +0,0 @@
namespace Geekbot.net.Commands.Integrations.Google
{
public class GoogleKgApiResultDto
{
public string Name { get; set; }
public string Description { get; set; }
public GoogleKgApiImageDto Image { get; set; }
public GoogleKgApiDetailedDto DetailedDtoDescription { get; set; }
}
}

View file

@ -1,129 +0,0 @@
using System;
using System.Threading.Tasks;
using System.Web;
using System.Xml;
using Discord;
using Discord.Commands;
using Geekbot.net.Lib;
using Geekbot.net.Lib.Clients;
using Geekbot.net.Lib.ErrorHandling;
using Geekbot.net.Lib.Extensions;
namespace Geekbot.net.Commands.Integrations
{
public class Mal : GeekbotBase
{
private readonly IErrorHandler _errorHandler;
private readonly IMalClient _malClient;
public Mal(IMalClient malClient, IErrorHandler errorHandler)
{
_malClient = malClient;
_errorHandler = errorHandler;
}
[Command("anime", RunMode = RunMode.Async)]
[Summary("Show Info about an Anime.")]
public async Task SearchAnime([Remainder] [Summary("anime-name")] 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 (XmlException e)
{
await _errorHandler.HandleCommandException(e, Context, "The MyAnimeList.net API refused to answer");
}
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
{
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 (XmlException e)
{
await _errorHandler.HandleCommandException(e, Context, "The MyAnimeList.net API refused to answer");
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -1,12 +0,0 @@
namespace Geekbot.net.Commands.Integrations.UbranDictionary
{
internal class UrbanListItemDto
{
public string Definition { get; set; }
public string Permalink { get; set; }
public string ThumbsUp { get; set; }
public string Word { get; set; }
public string Example { get; set; }
public string ThumbsDown { get; set; }
}
}

View file

@ -1,10 +0,0 @@
using System.Collections.Generic;
namespace Geekbot.net.Commands.Integrations.UbranDictionary
{
internal class UrbanResponseDto
{
public string[] Tags { get; set; }
public List<UrbanListItemDto> List { get; set; }
}
}

View file

@ -1,67 +0,0 @@
using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.net.Lib;
using Geekbot.net.Lib.ErrorHandling;
using Geekbot.net.Lib.Extensions;
using Newtonsoft.Json;
namespace Geekbot.net.Commands.Integrations.UbranDictionary
{
public class UrbanDictionary : GeekbotBase
{
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
{
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<UrbanResponseDto>(stringResponse);
if (definitions.List.Count == 0)
{
await ReplyAsync("That word hasn't been defined...");
return;
}
var definition = definitions.List.First(e => !string.IsNullOrWhiteSpace(e.Example));
var eb = new EmbedBuilder();
eb.WithAuthor(new EmbedAuthorBuilder
{
Name = definition.Word,
Url = definition.Permalink
});
eb.WithColor(new Color(239, 255, 0));
if (!string.IsNullOrEmpty(definition.Definition)) eb.Description = definition.Definition;
if (!string.IsNullOrEmpty(definition.Example)) eb.AddField("Example", definition.Example ?? "(no example given...)");
if (!string.IsNullOrEmpty(definition.ThumbsUp)) eb.AddInlineField("Upvotes", definition.ThumbsUp);
if (!string.IsNullOrEmpty(definition.ThumbsDown)) eb.AddInlineField("Downvotes", definition.ThumbsDown);
if (definitions.Tags?.Length > 0) eb.AddField("Tags", string.Join(", ", definitions.Tags));
await ReplyAsync("", false, eb.Build());
}
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -1,59 +0,0 @@
using System;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.net.Lib;
using Geekbot.net.Lib.ErrorHandling;
using Geekbot.net.Lib.GlobalSettings;
using Google.Apis.Services;
using Google.Apis.YouTube.v3;
namespace Geekbot.net.Commands.Integrations
{
public class Youtube : GeekbotBase
{
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)
{
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);
}
}
}
}

View file

@ -1,53 +0,0 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.net.Lib;
using Geekbot.net.Lib.ErrorHandling;
using Newtonsoft.Json;
namespace Geekbot.net.Commands.Randomness.Cat
{
public class Cat : GeekbotBase
{
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
{
using (var client = new HttpClient())
{
try
{
client.BaseAddress = new Uri("https://aws.random.cat");
var response = await client.GetAsync("/meow");
response.EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync();
var catFile = JsonConvert.DeserializeObject<CatResponseDto>(stringResponse);
var eb = new EmbedBuilder();
eb.ImageUrl = catFile.File;
await ReplyAsync("", false, eb.Build());
}
catch
{
await ReplyAsync("Seems like the dog cought the cat (error occured)");
}
}
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -1,7 +0,0 @@
namespace Geekbot.net.Commands.Randomness.Cat
{
internal class CatResponseDto
{
public string File { get; set; }
}
}

View file

@ -1,72 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.net.Lib;
using Geekbot.net.Lib.ErrorHandling;
using Geekbot.net.Lib.Media;
namespace Geekbot.net.Commands.Randomness
{
public class CheckEm : GeekbotBase
{
private readonly IMediaProvider _checkEmImages;
private readonly IErrorHandler _errorHandler;
public CheckEm(IMediaProvider mediaProvider, IErrorHandler errorHandler)
{
_checkEmImages = mediaProvider;
_errorHandler = errorHandler;
}
[Command("checkem", RunMode = RunMode.Async)]
[Summary("Check for dubs")]
public async Task MuhDubs()
{
try
{
var number = new Random().Next(10000000, 99999999);
var dubtriqua = "";
var ns = GetIntArray(number);
if (ns[7] == ns[6])
{
dubtriqua = "DUBS";
if (ns[6] == ns[5])
{
dubtriqua = "TRIPS";
if (ns[5] == ns[4])
dubtriqua = "QUADS";
}
}
var sb = new StringBuilder();
sb.AppendLine($"Check em {Context.User.Mention}");
sb.AppendLine($"**{number}**");
if (!string.IsNullOrEmpty(dubtriqua))
sb.AppendLine($":tada: {dubtriqua} :tada:");
sb.AppendLine(_checkEmImages.GetCheckem());
await ReplyAsync(sb.ToString());
}
catch (Exception e)
{
await _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();
}
}
}

View file

@ -1,7 +0,0 @@
namespace Geekbot.net.Commands.Randomness.Chuck
{
internal class ChuckNorrisJokeResponseDto
{
public string Value { get; set; }
}
}

View file

@ -1,52 +0,0 @@
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.net.Lib;
using Geekbot.net.Lib.ErrorHandling;
using Newtonsoft.Json;
namespace Geekbot.net.Commands.Randomness.Chuck
{
public class ChuckNorrisJokes : GeekbotBase
{
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
{
using (var client = new HttpClient())
{
try
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
var response = await client.GetAsync("https://api.chucknorris.io/jokes/random");
response.EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync();
var data = JsonConvert.DeserializeObject<ChuckNorrisJokeResponseDto>(stringResponse);
await ReplyAsync(data.Value);
}
catch (HttpRequestException)
{
await ReplyAsync("Api down...");
}
}
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -1,7 +0,0 @@
namespace Geekbot.net.Commands.Randomness.Dad
{
internal class DadJokeResponseDto
{
public string Joke { get; set; }
}
}

View file

@ -1,52 +0,0 @@
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.net.Lib;
using Geekbot.net.Lib.ErrorHandling;
using Newtonsoft.Json;
namespace Geekbot.net.Commands.Randomness.Dad
{
public class DadJokes : GeekbotBase
{
private readonly IErrorHandler _errorHandler;
public DadJokes(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
}
[Command("dad", RunMode = RunMode.Async)]
[Summary("A random dad joke")]
public async Task Say()
{
try
{
using (var client = new HttpClient())
{
try
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
var response = await client.GetAsync("https://icanhazdadjoke.com/");
response.EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync();
var data = JsonConvert.DeserializeObject<DadJokeResponseDto>(stringResponse);
await ReplyAsync(data.Joke);
}
catch (HttpRequestException)
{
await ReplyAsync("Api down...");
}
}
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -1,53 +0,0 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.net.Lib;
using Geekbot.net.Lib.ErrorHandling;
using Newtonsoft.Json;
namespace Geekbot.net.Commands.Randomness.Dog
{
public class Dog : GeekbotBase
{
private readonly IErrorHandler _errorHandler;
public Dog(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
}
[Command("dog", RunMode = RunMode.Async)]
[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<DogResponseDto>(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)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -1,7 +0,0 @@
namespace Geekbot.net.Commands.Randomness.Dog
{
internal class DogResponseDto
{
public string Url { get; set; }
}
}

View file

@ -1,58 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.net.Lib;
using Geekbot.net.Lib.ErrorHandling;
namespace Geekbot.net.Commands.Randomness
{
public class EightBall : GeekbotBase
{
private readonly IErrorHandler _errorHandler;
public EightBall(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
}
[Command("8ball", RunMode = RunMode.Async)]
[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 = new Random().Next(replies.Count);
await ReplyAsync(replies[answer]);
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -1,39 +0,0 @@
using System;
using System.Net;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.net.Lib;
using Geekbot.net.Lib.ErrorHandling;
namespace Geekbot.net.Commands.Randomness
{
public class Gdq : GeekbotBase
{
private readonly IErrorHandler _errorHandler;
public Gdq(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
}
[Command("gdq", RunMode = RunMode.Async)]
[Summary("Get a quote from the GDQ donation generator.")]
public async Task GetQuote()
{
try
{
using (var client = new WebClient())
{
var url = new Uri("http://taskinoz.com/gdq/api/");
var response = client.DownloadString(url);
await ReplyAsync(response);
}
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -1,54 +0,0 @@
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.net.Commands.Randomness.Dad;
using Geekbot.net.Lib;
using Geekbot.net.Lib.ErrorHandling;
using Microsoft.AspNetCore.Hosting.Internal;
using Newtonsoft.Json;
namespace Geekbot.net.Commands.Randomness.Kanye
{
public class Kanye : GeekbotBase
{
private readonly IErrorHandler _errorHandler;
public Kanye(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
}
[Command("kanye", RunMode = RunMode.Async)]
[Summary("A random kayne west quote")]
public async Task Say()
{
try
{
using (var client = new HttpClient())
{
try
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
var response = await client.GetAsync("https://api.kanye.rest/");
response.EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync();
var data = JsonConvert.DeserializeObject<KanyeResponseDto>(stringResponse);
await ReplyAsync(data.Quote);
}
catch (HttpRequestException)
{
await ReplyAsync("Api down...");
}
}
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -1,8 +0,0 @@
namespace Geekbot.net.Commands.Randomness.Kanye
{
public class KanyeResponseDto
{
public string Id { get; set; }
public string Quote { get; set; }
}
}

View file

@ -1,149 +0,0 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.net.Database;
using Geekbot.net.Database.Models;
using Geekbot.net.Lib;
using Geekbot.net.Lib.CommandPreconditions;
using Geekbot.net.Lib.ErrorHandling;
using Geekbot.net.Lib.Extensions;
namespace Geekbot.net.Commands.User
{
[DisableInDirectMessage]
public class Karma : GeekbotBase
{
private readonly IErrorHandler _errorHandler;
private readonly DatabaseContext _database;
public Karma(DatabaseContext database, IErrorHandler errorHandler)
{
_database = database;
_errorHandler = errorHandler;
}
[Command("good", RunMode = RunMode.Async)]
[Summary("Increase Someones Karma")]
public async Task Good([Summary("@someone")] IUser user)
{
try
{
var actor = await GetUser(Context.User.Id);
if (user.Id == Context.User.Id)
{
await ReplyAsync(Context.Translations.GetString("CannotChangeOwn", Context.User.Username));
}
else if (TimeoutFinished(actor.TimeOut))
{
var formatedWaitTime = Context.Translations.FormatDateTimeAsRemaining(actor.TimeOut.AddMinutes(3));
await ReplyAsync(Context.Translations.GetString("WaitUntill", Context.User.Username, formatedWaitTime));
}
else
{
var target = await GetUser(user.Id);
target.Karma = target.Karma + 1;
SetUser(target);
actor.TimeOut = DateTimeOffset.Now;
SetUser(actor);
await _database.SaveChangesAsync();
var eb = new EmbedBuilder();
eb.WithAuthor(new EmbedAuthorBuilder()
.WithIconUrl(user.GetAvatarUrl())
.WithName(user.Username));
eb.WithColor(new Color(138, 219, 146));
eb.Title = Context.Translations.GetString("Increased");
eb.AddInlineField(Context.Translations.GetString("By"), Context.User.Username);
eb.AddInlineField(Context.Translations.GetString("Amount"), "+1");
eb.AddInlineField(Context.Translations.GetString("Current"), target.Karma);
await ReplyAsync("", false, eb.Build());
}
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
[Command("bad", RunMode = RunMode.Async)]
[Summary("Decrease Someones Karma")]
public async Task Bad([Summary("@someone")] IUser user)
{
try
{
var actor = await GetUser(Context.User.Id);
if (user.Id == Context.User.Id)
{
await ReplyAsync(Context.Translations.GetString("CannotChangeOwn", Context.User.Username));
}
else if (TimeoutFinished(actor.TimeOut))
{
var formatedWaitTime = Context.Translations.FormatDateTimeAsRemaining(actor.TimeOut.AddMinutes(3));
await ReplyAsync(Context.Translations.GetString("WaitUntill", Context.User.Username, formatedWaitTime));
}
else
{
var target = await GetUser(user.Id);
target.Karma = target.Karma - 1;
SetUser(target);
actor.TimeOut = DateTimeOffset.Now;
SetUser(actor);
await _database.SaveChangesAsync();
var eb = new EmbedBuilder();
eb.WithAuthor(new EmbedAuthorBuilder()
.WithIconUrl(user.GetAvatarUrl())
.WithName(user.Username));
eb.WithColor(new Color(138, 219, 146));
eb.Title = Context.Translations.GetString("Decreased");
eb.AddInlineField(Context.Translations.GetString("By"), Context.User.Username);
eb.AddInlineField(Context.Translations.GetString("Amount"), "-1");
eb.AddInlineField(Context.Translations.GetString("Current"), target.Karma);
await ReplyAsync("", false, eb.Build());
}
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
private bool TimeoutFinished(DateTimeOffset lastKarma)
{
return lastKarma.AddMinutes(3) > DateTimeOffset.Now;
}
private async Task<KarmaModel> GetUser(ulong userId)
{
var user = _database.Karma.FirstOrDefault(u =>u.GuildId.Equals(Context.Guild.Id.AsLong()) && u.UserId.Equals(userId.AsLong())) ?? await CreateNewRow(userId);
return user;
}
private void SetUser(KarmaModel user)
{
_database.Karma.Update(user);
}
private async Task<KarmaModel> CreateNewRow(ulong userId)
{
var user = new KarmaModel()
{
GuildId = Context.Guild.Id.AsLong(),
UserId = userId.AsLong(),
Karma = 0,
TimeOut = DateTimeOffset.MinValue
};
var newUser = _database.Karma.Add(user).Entity;
await _database.SaveChangesAsync();
return newUser;
}
}
}

View file

@ -1,111 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.net.Database;
using Geekbot.net.Lib;
using Geekbot.net.Lib.CommandPreconditions;
using Geekbot.net.Lib.Converters;
using Geekbot.net.Lib.ErrorHandling;
using Geekbot.net.Lib.Extensions;
using Geekbot.net.Lib.Highscores;
using Geekbot.net.Lib.Localization;
using Geekbot.net.Lib.UserRepository;
namespace Geekbot.net.Commands.User.Ranking
{
public class Rank : GeekbotBase
{
private readonly IEmojiConverter _emojiConverter;
private readonly IHighscoreManager _highscoreManager;
private readonly IErrorHandler _errorHandler;
private readonly DatabaseContext _database;
public Rank(DatabaseContext database, IErrorHandler errorHandler, IEmojiConverter emojiConverter, IHighscoreManager highscoreManager)
{
_database = database;
_errorHandler = errorHandler;
_emojiConverter = emojiConverter;
_highscoreManager = highscoreManager;
}
[Command("rank", RunMode = RunMode.Async)]
[Summary("get user top 10 in messages or karma")]
[DisableInDirectMessage]
public async Task RankCmd([Summary("type")] string typeUnformated = "messages", [Summary("amount")] int amount = 10)
{
try
{
HighscoreTypes type;
try
{
type = Enum.Parse<HighscoreTypes>(typeUnformated, true);
if (!Enum.IsDefined(typeof(HighscoreTypes), type)) throw new Exception();
}
catch
{
await ReplyAsync(Context.Translations.GetString("InvalidType"));
return;
}
var replyBuilder = new StringBuilder();
if (amount > 20)
{
await ReplyAsync(Context.Translations.GetString("LimitingTo20Warning"));
amount = 20;
}
var guildId = Context.Guild.Id;
Dictionary<HighscoreUserDto, int> highscoreUsers;
try
{
highscoreUsers = _highscoreManager.GetHighscoresWithUserData(type, guildId, amount);
}
catch (HighscoreListEmptyException)
{
await ReplyAsync(Context.Translations.GetString("NoTypeFoundForServer", type));
return;
}
int guildMessages = 0;
if (type == HighscoreTypes.messages)
{
guildMessages = _database.Messages
.Where(e => e.GuildId.Equals(Context.Guild.Id.AsLong()))
.Select(e => e.MessageCount)
.Sum();
}
var failedToRetrieveUser = highscoreUsers.Any(e => string.IsNullOrEmpty(e.Key.Username));
if (failedToRetrieveUser) replyBuilder.AppendLine(Context.Translations.GetString("FailedToResolveAllUsernames"));
replyBuilder.AppendLine(Context.Translations.GetString("HighscoresFor", type.ToString().CapitalizeFirst(), 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}**");
replyBuilder.Append(type == HighscoreTypes.messages
? $" - {user.Value} {type} - {Math.Round((double) (100 * user.Value) / guildMessages, digits: 2)}%\n"
: $" - {user.Value} {type}\n");
highscorePlace++;
}
await ReplyAsync(replyBuilder.ToString());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -1,68 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Geekbot.net.Lib;
using Geekbot.net.Lib.ErrorHandling;
using Newtonsoft.Json;
namespace Geekbot.net.Commands.Utils.Changelog
{
public class Changelog : GeekbotBase
{
private readonly DiscordSocketClient _client;
private readonly IErrorHandler _errorHandler;
public Changelog(IErrorHandler errorHandler, DiscordSocketClient client)
{
_errorHandler = errorHandler;
_client = client;
}
[Command("changelog", RunMode = RunMode.Async)]
[Summary("Show the latest 10 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<CommitDto>>(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)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -1,11 +0,0 @@
using System;
namespace Geekbot.net.Commands.Utils.Changelog
{
public class CommitAuthorDto
{
public string Name { get; set; }
public string Email { get; set; }
public DateTimeOffset Date { get; set; }
}
}

View file

@ -1,7 +0,0 @@
namespace Geekbot.net.Commands.Utils.Changelog
{
public class CommitDto
{
public CommitInfoDto Commit { get; set; }
}
}

View file

@ -1,8 +0,0 @@
namespace Geekbot.net.Commands.Utils.Changelog
{
public class CommitInfoDto
{
public CommitAuthorDto Author { get; set; }
public string Message { get; set; }
}
}

View file

@ -1,36 +0,0 @@
using System;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.net.Lib;
using Geekbot.net.Lib.ErrorHandling;
using Geekbot.net.Lib.Localization;
namespace Geekbot.net.Commands.Utils
{
public class Choose : GeekbotBase
{
private readonly IErrorHandler _errorHandler;
public Choose(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
}
[Command("choose", RunMode = RunMode.Async)]
[Summary("Let the bot choose for you, seperate options with a semicolon.")]
public async Task Command([Remainder] [Summary("option1;option2")]
string choices)
{
try
{
var choicesArray = choices.Split(';');
var choice = new Random().Next(choicesArray.Length);
await ReplyAsync(Context.Translations.GetString("Choice", choicesArray[choice].Trim()));
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -1,122 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.net.Lib;
using Geekbot.net.Lib.RandomNumberGenerator;
namespace Geekbot.net.Commands.Utils.Dice
{
public class Dice : GeekbotBase
{
private readonly IRandomNumberGenerator _randomNumberGenerator;
public Dice(IRandomNumberGenerator randomNumberGenerator)
{
_randomNumberGenerator = randomNumberGenerator;
}
[Command("dice", RunMode = RunMode.Async)]
[Summary("Roll a dice.")]
public async Task RollCommand([Remainder] [Summary("dice-type")] 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 > 144))
{
await ReplyAsync("A dice can't have more than 144 sides");
return;
}
var rep = new StringBuilder();
rep.AppendLine($":game_die: {Context.User.Mention}");
rep.Append("**Result:** ");
var resultStrings = new List<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 = _randomNumberGenerator.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();
}
}
}

View file

@ -1,10 +0,0 @@
namespace Geekbot.net.Commands.Utils.Dice
{
internal class DiceTypeDto
{
public string DiceType { get; set; }
public int Times { get; set; }
public int Sides { get; set; }
public int Mod { get; set; }
}
}

View file

@ -1,249 +0,0 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.net.Database;
using Geekbot.net.Database.Models;
using Geekbot.net.Lib;
using Geekbot.net.Lib.CommandPreconditions;
using Geekbot.net.Lib.ErrorHandling;
using Geekbot.net.Lib.Extensions;
using Geekbot.net.Lib.Localization;
using Geekbot.net.Lib.Polyfills;
using Geekbot.net.Lib.RandomNumberGenerator;
namespace Geekbot.net.Commands.Utils.Quote
{
[Group("quote")]
[DisableInDirectMessage]
public class Quote : GeekbotBase
{
private readonly IErrorHandler _errorHandler;
private readonly DatabaseContext _database;
private readonly IRandomNumberGenerator _randomNumberGenerator;
public Quote(IErrorHandler errorHandler, DatabaseContext database, IRandomNumberGenerator randomNumberGenerator)
{
_errorHandler = errorHandler;
_database = database;
_randomNumberGenerator = randomNumberGenerator;
}
[Command]
[Summary("Return a random quoute from the database")]
public async Task GetRandomQuote()
{
try
{
var s = _database.Quotes.Where(e => e.GuildId.Equals(Context.Guild.Id.AsLong())).ToList();
if (!s.Any())
{
await ReplyAsync(Context.Translations.GetString("NoQuotesFound"));
return;
}
var random = _randomNumberGenerator.Next(0, s.Count());
var quote = s[random];
var embed = QuoteBuilder(quote);
await ReplyAsync("", false, embed.Build());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context, "Whoops, seems like the quote was to edgy to return");
}
}
[Command("save")]
[Summary("Save a quote from the last sent message by @user")]
public async Task SaveQuote([Summary("@someone")] IUser user)
{
try
{
if (user.Id == Context.Message.Author.Id)
{
await ReplyAsync(Context.Translations.GetString("CannotSaveOwnQuotes"));
return;
}
if (user.IsBot)
{
await ReplyAsync(Context.Translations.GetString("CannotQuoteBots"));
return;
}
var lastMessage = await GetLastMessageByUser(user);
if (lastMessage == null) return;
var quote = CreateQuoteObject(lastMessage);
_database.Quotes.Add(quote);
await _database.SaveChangesAsync();
var embed = QuoteBuilder(quote);
await ReplyAsync(Context.Translations.GetString("QuoteAdded"), false, embed.Build());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context,
"I counldn't find a quote from that user :disappointed:");
}
}
[Command("save")]
[Summary("Save a quote from a message id")]
public async Task SaveQuote([Summary("message-ID")] ulong messageId)
{
try
{
var message = await Context.Channel.GetMessageAsync(messageId);
if (message.Author.Id == Context.Message.Author.Id)
{
await ReplyAsync(Context.Translations.GetString("CannotSaveOwnQuotes"));
return;
}
if (message.Author.IsBot)
{
await ReplyAsync(Context.Translations.GetString("CannotQuoteBots"));
return;
}
var quote = CreateQuoteObject(message);
_database.Quotes.Add(quote);
await _database.SaveChangesAsync();
var embed = QuoteBuilder(quote);
await ReplyAsync(Context.Translations.GetString("QuoteAdded"), false, embed.Build());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context,
"I couldn't find a message with that id :disappointed:");
}
}
[Command("make")]
[Summary("Create a quote from the last sent message by @user")]
public async Task ReturnSpecifiedQuote([Summary("@someone")] IUser user)
{
try
{
var lastMessage = await GetLastMessageByUser(user);
if (lastMessage == null) return;
var quote = CreateQuoteObject(lastMessage);
var embed = QuoteBuilder(quote);
await ReplyAsync("", false, embed.Build());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context,
"I counldn't find a quote from that user :disappointed:");
}
}
[Command("make")]
[Summary("Create a quote from a message id")]
public async Task ReturnSpecifiedQuote([Summary("message-ID")] 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)
{
await _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)]
[Summary("Remove a quote (required mod permissions)")]
public async Task RemoveQuote([Summary("quote-ID")] int id)
{
try
{
var quote = _database.Quotes.Where(e => e.GuildId == Context.Guild.Id.AsLong() && e.InternalId == id)?.FirstOrDefault();
if (quote != null)
{
_database.Quotes.Remove(quote);
await _database.SaveChangesAsync();
var embed = QuoteBuilder(quote);
await ReplyAsync(Context.Translations.GetString("Removed", id), false, embed.Build());
}
else
{
await ReplyAsync(Context.Translations.GetString("NotFoundWithId"));
}
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context, "I couldn't find a quote with that id :disappointed:");
}
}
private async Task<IMessage> GetLastMessageByUser(IUser user)
{
try
{
var list = Context.Channel.GetMessagesAsync().Flatten();
return await list.FirstOrDefault(msg =>
msg.Author.Id == user.Id &&
msg.Embeds.Count == 0 &&
msg.Id != Context.Message.Id &&
!msg.Content.ToLower().StartsWith("!"));
}
catch
{
await ReplyAsync($"No quoteable message have been sent by {user.Username} in this channel");
return null;
}
}
private EmbedBuilder QuoteBuilder(QuoteModel quote)
{
var user = Context.Client.GetUserAsync(quote.UserId.AsUlong()).Result ?? new UserPolyfillDto { Username = "Unknown User" };
var eb = new EmbedBuilder();
eb.WithColor(new Color(143, 167, 232));
eb.Title = $"#{quote.InternalId} | {user.Username} @ {quote.Time.Day}.{quote.Time.Month}.{quote.Time.Year}";
eb.Description = quote.Quote;
eb.ThumbnailUrl = user.GetAvatarUrl();
if (quote.Image != null) eb.ImageUrl = quote.Image;
return eb;
}
private QuoteModel CreateQuoteObject(IMessage message)
{
string image;
try
{
image = message.Attachments.First().Url;
}
catch (Exception)
{
image = null;
}
var last = _database.Quotes.Where(e => e.GuildId.Equals(Context.Guild.Id.AsLong()))
.OrderByDescending(e => e.InternalId).FirstOrDefault();
int internalId = 1;
if (last != null) internalId = last.InternalId + 1;
return new QuoteModel()
{
InternalId = internalId,
GuildId = Context.Guild.Id.AsLong(),
UserId = message.Author.Id.AsLong(),
Time = message.Timestamp.DateTime,
Quote = message.Content,
Image = image
};
}
}
}

View file

@ -1,24 +0,0 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace Geekbot.net.Database.Models
{
public class GuildsModel
{
[Key]
public int Id { get; set; }
[Required]
public long GuildId { get; set; }
[Required]
public string Name { get; set; }
[Required]
public long Owner { get; set; }
public string IconUrl { get; set; }
public DateTimeOffset CreatedAt { get; set; }
}
}

View file

@ -1,27 +0,0 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Geekbot.net.Database.Models
{
public class PollModel
{
[Key]
public int Id { get; set; }
[Required]
public long GuildId { get; set; }
[Required]
public long ChannelId { get; set; }
public string Question { get; set; }
public long Creator { get; set; }
public long MessageId { get; set; }
public List<PollQuestionModel> Options { get; set; }
public bool IsFinshed { get; set; }
}
}

View file

@ -1,16 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace Geekbot.net.Database.Models
{
public class PollQuestionModel
{
[Key]
public int Id { get; set; }
public int OptionId { get; set; }
public string OptionText { get; set; }
public int Votes { get; set; }
}
}

View file

@ -1,15 +0,0 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace Geekbot.net.Database.Models
{
public class UserUsedNamesModel
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public DateTimeOffset FirstSeen { get; set; }
}
}

View file

@ -1,16 +0,0 @@
namespace Geekbot.net.Database
{
public class SqlConnectionString
{
public string Host { get; set; }
public string Port { get; set; }
public string Database { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public override string ToString()
{
return $"Server={Host};Port={Port};Database={Database};Uid={Username};Pwd={Password};";
}
}
}

View file

@ -1,104 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>
<RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers>
<ApplicationIcon>derp.ico</ApplicationIcon>
<Version>4.1.0</Version>
<VersionSuffix>$(VersionSuffix)</VersionSuffix>
<Version Condition=" '$(VersionSuffix)' != '' ">$(Version)-$(VersionSuffix)</Version>
<Version Condition=" '$(VersionSuffix)' == '' ">$(Version)-DEV</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>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://geekbot.pizzaandcoffee.rocks</PackageProjectUrl>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<Optimize>true</Optimize>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.4.3" />
<PackageReference Include="Discord.Net">
<Version>2.1.0</Version>
</PackageReference>
<PackageReference Include="Google.Apis.YouTube.v3" Version="1.38.0.1488" />
<PackageReference Include="HtmlAgilityPack" Version="1.9.1" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.Redis" Version="0.4.0" />
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Cors" Version="2.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.2.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="2.2.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="2.2.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.2.2" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="2.2.0" />
<PackageReference Include="MtgApiManager.Lib" Version="1.2.1" />
<PackageReference Include="MyAnimeListSharp" Version="1.3.4" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="NLog" Version="4.5.11" />
<PackageReference Include="NLog.Config" Version="4.5.11" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="2.2.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.Design" Version="2.0.0-preview1" />
<PackageReference Include="PokeApi.NET" Version="1.1.1" />
<PackageReference Include="SharpRaven" Version="2.4.0" />
<PackageReference Include="SumoLogic.Logging.NLog" Version="1.0.1.1" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<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.7" />
<PackageReference Include="YamlDotNet" Version="6.0.0" />
</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\turtles">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Storage\pinguins">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Storage\foxes">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Lib\Converters\MtgManaEmojis.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Storage\dab">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Lib\Localization\Translations.yml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WikipediaApi\WikipediaApi.csproj" />
</ItemGroup>
</Project>

View file

@ -1,268 +0,0 @@
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.Rest;
using Discord.WebSocket;
using Geekbot.net.Database;
using Geekbot.net.Database.Models;
using Geekbot.net.Lib.AlmostRedis;
using Geekbot.net.Lib.Context;
using Geekbot.net.Lib.Extensions;
using Geekbot.net.Lib.Localization;
using Geekbot.net.Lib.Logger;
using Geekbot.net.Lib.ReactionListener;
using Geekbot.net.Lib.UserRepository;
using Microsoft.EntityFrameworkCore;
namespace Geekbot.net
{
public class Handlers
{
private readonly DatabaseContext _database;
private readonly IDiscordClient _client;
private readonly IGeekbotLogger _logger;
private readonly IAlmostRedis _redis;
private readonly IServiceProvider _servicesProvider;
private readonly CommandService _commands;
private readonly IUserRepository _userRepository;
private readonly IReactionListener _reactionListener;
private readonly ITranslationHandler _translationHandler;
private readonly DatabaseContext _messageCounterDatabaseContext;
public Handlers(DatabaseInitializer databaseInitializer, IDiscordClient client, IGeekbotLogger logger, IAlmostRedis redis,
IServiceProvider servicesProvider, CommandService commands, IUserRepository userRepository,
IReactionListener reactionListener, ITranslationHandler translationHandler)
{
_database = databaseInitializer.Initialize();
_messageCounterDatabaseContext = databaseInitializer.Initialize();
_client = client;
_logger = logger;
_redis = redis;
_servicesProvider = servicesProvider;
_commands = commands;
_userRepository = userRepository;
_reactionListener = reactionListener;
_translationHandler = translationHandler;
}
//
// Incoming Messages
//
public Task RunCommand(SocketMessage messageParam)
{
try
{
if (!(messageParam is SocketUserMessage message)) return Task.CompletedTask;
if (message.Author.IsBot) return Task.CompletedTask;
var argPos = 0;
var lowCaseMsg = message.ToString().ToLower();
if (lowCaseMsg.StartsWith("hui"))
{
var hasPing = _database.GuildSettings.FirstOrDefault(guild => guild.GuildId.Equals(((SocketGuildChannel) message.Channel).Guild.Id.AsLong()))?.Hui ?? false;
if (hasPing)
{
message.Channel.SendMessageAsync("hui!!!");
return Task.CompletedTask;
}
}
if (lowCaseMsg.StartsWith("ping ") || lowCaseMsg.Equals("ping"))
{
var hasPing = _database.GuildSettings.FirstOrDefault(guild => guild.GuildId.Equals(((SocketGuildChannel) message.Channel).Guild.Id.AsLong()))?.Ping ?? false;
if (hasPing)
{
message.Channel.SendMessageAsync("pong");
return Task.CompletedTask;
}
}
if (!(message.HasCharPrefix('!', ref argPos) ||
message.HasMentionPrefix(_client.CurrentUser, ref argPos))) return Task.CompletedTask;
var context = new GeekbotContext(_client, message, _translationHandler);
var commandExec = _commands.ExecuteAsync(context, argPos, _servicesProvider);
_logger.Information(LogSource.Command,
context.Message.Content.Split(" ")[0].Replace("!", ""),
SimpleConextConverter.ConvertContext(context));
return Task.CompletedTask;
}
catch (Exception e)
{
_logger.Error(LogSource.Geekbot, "Failed to Process Message", e);
return Task.CompletedTask;
}
}
public async Task UpdateStats(SocketMessage message)
{
try
{
if (message == null) return;
if (message.Channel.Name.StartsWith('@'))
{
_logger.Information(LogSource.Message, $"[DM-Channel] {message.Content}", SimpleConextConverter.ConvertSocketMessage(message, true));
return;
}
var channel = (SocketGuildChannel) message.Channel;
// just testing, redis will remain the source of truth for now
var rowId = await _messageCounterDatabaseContext.Database.ExecuteSqlCommandAsync(
"UPDATE \"Messages\" SET \"MessageCount\" = \"MessageCount\" + 1 WHERE \"GuildId\" = {0} AND \"UserId\" = {1}",
channel.Guild.Id.AsLong(),
message.Author.Id.AsLong()
);
if (rowId == 0)
{
_messageCounterDatabaseContext.Messages.Add(new MessagesModel
{
UserId = message.Author.Id.AsLong(),
GuildId = channel.Guild.Id.AsLong(),
MessageCount = 1
});
_messageCounterDatabaseContext.SaveChanges();
}
await _redis.Db.HashIncrementAsync($"{channel.Guild.Id}:Messages", message.Author.Id.ToString());
await _redis.Db.HashIncrementAsync($"{channel.Guild.Id}:Messages", 0.ToString());
if (message.Author.IsBot) return;
_logger.Information(LogSource.Message, message.Content, SimpleConextConverter.ConvertSocketMessage(message));
}
catch (Exception e)
{
_logger.Error(LogSource.Message, "Could not process message stats", e);
}
}
//
// User Stuff
//
public async Task UserJoined(SocketGuildUser user)
{
try
{
var userRepoUpdate = _userRepository.Update(user);
_logger.Information(LogSource.Geekbot, $"{user.Username} ({user.Id}) joined {user.Guild.Name} ({user.Guild.Id})");
if (!user.IsBot)
{
var guildSettings = _database.GuildSettings.FirstOrDefault(guild => guild.GuildId == user.Guild.Id.AsLong());
var message = guildSettings?.WelcomeMessage;
if (string.IsNullOrEmpty(message)) return;
message = message.Replace("$user", user.Mention);
var fallbackSender = new Func<Task<RestUserMessage>>(() => user.Guild.DefaultChannel.SendMessageAsync(message));
if (guildSettings.WelcomeChannel != 0)
{
try
{
var target = await _client.GetChannelAsync(guildSettings.WelcomeChannel.AsUlong());
var channel = target as ISocketMessageChannel;
await channel.SendMessageAsync(message);
}
catch (Exception e)
{
_logger.Error(LogSource.Geekbot, "Failed to send welcome message to user defined welcome channel", e);
await fallbackSender();
}
}
else
{
await fallbackSender();
}
}
await userRepoUpdate;
}
catch (Exception e)
{
_logger.Error(LogSource.Geekbot, "Failed to send welcome message", e);
}
}
public async Task UserUpdated(SocketUser oldUser, SocketUser newUser)
{
await _userRepository.Update(newUser);
}
public async Task UserLeft(SocketGuildUser user)
{
try
{
var guild = _database.GuildSettings.FirstOrDefault(g =>
g.GuildId.Equals(user.Guild.Id.AsLong()));
if (guild?.ShowLeave ?? false)
{
var modChannelSocket = (ISocketMessageChannel) await _client.GetChannelAsync(guild.ModChannel.AsUlong());
await modChannelSocket.SendMessageAsync($"{user.Username}#{user.Discriminator} left the server");
}
}
catch (Exception e)
{
_logger.Error(LogSource.Geekbot, "Failed to send leave message", e);
}
_logger.Information(LogSource.Geekbot, $"{user.Username} ({user.Id}) joined {user.Guild.Name} ({user.Guild.Id})");
}
//
// Message Stuff
//
public async Task MessageDeleted(Cacheable<IMessage, ulong> message, ISocketMessageChannel channel)
{
try
{
var guildSocketData = ((IGuildChannel) channel).Guild;
var guild = _database.GuildSettings.FirstOrDefault(g => g.GuildId.Equals(guildSocketData.Id.AsLong()));
if ((guild?.ShowDelete ?? false) && guild?.ModChannel != 0)
{
var modChannelSocket = (ISocketMessageChannel) await _client.GetChannelAsync(guild.ModChannel.AsUlong());
var sb = new StringBuilder();
if (message.Value != null)
{
sb.AppendLine($"The following message from {message.Value.Author.Username}#{message.Value.Author.Discriminator} was deleted in <#{channel.Id}>");
sb.AppendLine(message.Value.Content);
}
else
{
sb.AppendLine("Someone deleted a message, the message was not cached...");
}
await modChannelSocket.SendMessageAsync(sb.ToString());
}
}
catch (Exception e)
{
_logger.Error(LogSource.Geekbot, "Failed to send delete message...", e);
}
}
//
// Reactions
//
public Task ReactionAdded(Cacheable<IUserMessage, ulong> cacheable, ISocketMessageChannel socketMessageChannel, SocketReaction reaction)
{
if (reaction.User.Value.IsBot) return Task.CompletedTask;
if (!_reactionListener.IsListener(reaction.MessageId)) return Task.CompletedTask;
_reactionListener.GiveRole(socketMessageChannel, reaction);
return Task.CompletedTask;
}
public Task ReactionRemoved(Cacheable<IUserMessage, ulong> cacheable, ISocketMessageChannel socketMessageChannel, SocketReaction reaction)
{
if (reaction.User.Value.IsBot) return Task.CompletedTask;
if (!_reactionListener.IsListener(reaction.MessageId)) return Task.CompletedTask;
_reactionListener.RemoveRole(socketMessageChannel, reaction);
return Task.CompletedTask;
}
}
}

View file

@ -1,35 +0,0 @@
using System.Collections.Generic;
using Geekbot.net.Lib.Logger;
using StackExchange.Redis;
namespace Geekbot.net.Lib.AlmostRedis
{
// if anyone ever sees this, please come up with a better fucking name, i'd appriciate it
public class AlmostRedis : IAlmostRedis
{
private readonly GeekbotLogger _logger;
private readonly RunParameters _runParameters;
public AlmostRedis(GeekbotLogger logger, RunParameters runParameters)
{
_logger = logger;
_runParameters = runParameters;
}
public void Connect()
{
Connection = ConnectionMultiplexer.Connect($"{_runParameters.RedisHost}:{_runParameters.RedisPort}");
Db = Connection.GetDatabase(int.Parse(_runParameters.RedisDatabase));
_logger.Information(LogSource.Redis, $"Connected to Redis on {Connection.Configuration} at {Db.Database}");
}
public IDatabase Db { get; private set; }
public ConnectionMultiplexer Connection { get; private set; }
public IEnumerable<RedisKey> GetAllKeys()
{
return Connection.GetServer($"{_runParameters.RedisHost}:{_runParameters.RedisPort}").Keys(int.Parse(_runParameters.RedisDatabase));
}
}
}

View file

@ -1,13 +0,0 @@
using System.Collections.Generic;
using StackExchange.Redis;
namespace Geekbot.net.Lib.AlmostRedis
{
public interface IAlmostRedis
{
void Connect();
IDatabase Db { get; }
ConnectionMultiplexer Connection { get; }
IEnumerable<RedisKey> GetAllKeys();
}
}

View file

@ -1,12 +0,0 @@
using System.Threading.Tasks;
using MyAnimeListSharp.Core;
namespace Geekbot.net.Lib.Clients
{
public interface IMalClient
{
bool IsLoggedIn();
Task<AnimeEntry> GetAnime(string query);
Task<MangaEntry> GetManga(string query);
}
}

View file

@ -1,63 +0,0 @@
using System.Threading.Tasks;
using Geekbot.net.Lib.GlobalSettings;
using Geekbot.net.Lib.Logger;
using MyAnimeListSharp.Auth;
using MyAnimeListSharp.Core;
using MyAnimeListSharp.Facade.Async;
namespace Geekbot.net.Lib.Clients
{
public class MalClient : IMalClient
{
private readonly IGlobalSettings _globalSettings;
private readonly IGeekbotLogger _logger;
private ICredentialContext _credentials;
private AnimeSearchMethodsAsync _animeSearch;
private MangaSearchMethodsAsync _mangaSearch;
public MalClient(IGlobalSettings globalSettings, IGeekbotLogger logger)
{
_globalSettings = globalSettings;
_logger = logger;
ReloadClient();
}
public bool ReloadClient()
{
var malCredentials = _globalSettings.GetKey("MalCredentials");
if (!string.IsNullOrEmpty(malCredentials))
{
var credSplit = malCredentials.Split('|');
_credentials = new CredentialContext()
{
UserName = credSplit[0],
Password = credSplit[1]
};
_animeSearch = new AnimeSearchMethodsAsync(_credentials);
_mangaSearch = new MangaSearchMethodsAsync(_credentials);
_logger.Debug(LogSource.Geekbot, "Logged in to MAL");
return true;
}
_logger.Debug(LogSource.Geekbot, "No MAL Credentials Set!");
return false;
}
public bool IsLoggedIn()
{
return _credentials != null;
}
public async Task<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];
}
}
}

View file

@ -1,56 +0,0 @@
using Discord;
using Geekbot.net.Lib.Localization;
namespace Geekbot.net.Lib.Context
{
/// <summary> The context of a command which may contain the client, user, guild, channel, and message. </summary>
public class GeekbotContext : IGeekbotContext
{
/// <inheritdoc />
public IDiscordClient Client { get; }
/// <inheritdoc />
public IGuild Guild { get; }
/// <inheritdoc />
public IMessageChannel Channel { get; }
/// <inheritdoc />
public IUser User { get; }
/// <inheritdoc />
public IUserMessage Message { get; }
/// <inheritdoc />
public IGuildUser GuildUser { get; }
/// <inheritdoc />
public TranslationGuildContext Translations { get; }
/// <summary> Indicates whether the channel that the command is executed in is a private channel. </summary>
public bool IsPrivate
{
get
{
return this.Channel is IPrivateChannel;
}
}
/// <summary>
/// Initializes a new <see cref="T:Discord.Commands.CommandContext" /> class with the provided client and message.
/// </summary>
/// <param name="client">The underlying client.</param>
/// <param name="msg">The underlying message.</param>
/// <param name="translationHandler">the translation handler</param>
public GeekbotContext(IDiscordClient client, IUserMessage msg, ITranslationHandler translationHandler)
{
this.Client = client;
this.Guild = (msg.Channel as IGuildChannel)?.Guild;
this.Channel = msg.Channel;
this.User = msg.Author;
this.GuildUser = msg.Author as IGuildUser;
this.Message = msg;
this.Translations = translationHandler.GetGuildContext(this.Guild, this.Message).Result;
}
}
}

View file

@ -1,19 +0,0 @@
using Discord;
using Discord.Commands;
using Geekbot.net.Lib.Localization;
namespace Geekbot.net.Lib.Context
{
public interface IGeekbotContext : ICommandContext
{
/// <summary>
/// Gets the <see cref="T:Discord:IGuildUser"/> who executed the command.
/// </summary>
IGuildUser GuildUser { get; }
/// <summary>
/// Gets the <see cref="T:Geekbot:net:Lib:Localization:TranslationGuildContext"/> containing the necessary tools for command localization.
/// </summary>
TranslationGuildContext Translations { get; }
}
}

View file

@ -1,93 +0,0 @@
using System.Collections;
using System.Text;
namespace Geekbot.net.Lib.Converters
{
public class EmojiConverter : IEmojiConverter
{
public string NumberToEmoji(int number)
{
if (number == 10)
{
return "🔟";
}
var emojiMap = new[]
{
":zero:",
":one:",
":two:",
":three:",
":four:",
":five:",
":six:",
":seven:",
":eight:",
":nine:"
};
var numbers = number.ToString().ToCharArray();
var returnString = new StringBuilder();
foreach (var n in numbers)
{
returnString.Append(emojiMap[int.Parse(n.ToString())]);
}
return returnString.ToString();
}
public string TextToEmoji(string text)
{
var emojiMap = new Hashtable
{
['A'] = ":regional_indicator_a: ",
['B'] = ":b: ",
['C'] = ":regional_indicator_c: ",
['D'] = ":regional_indicator_d: ",
['E'] = ":regional_indicator_e: ",
['F'] = ":regional_indicator_f: ",
['G'] = ":regional_indicator_g: ",
['H'] = ":regional_indicator_h: ",
['I'] = ":regional_indicator_i: ",
['J'] = ":regional_indicator_j: ",
['K'] = ":regional_indicator_k: ",
['L'] = ":regional_indicator_l: ",
['M'] = ":regional_indicator_m: ",
['N'] = ":regional_indicator_n: ",
['O'] = ":regional_indicator_o: ",
['P'] = ":regional_indicator_p: ",
['Q'] = ":regional_indicator_q: ",
['R'] = ":regional_indicator_r: ",
['S'] = ":regional_indicator_s: ",
['T'] = ":regional_indicator_t: ",
['U'] = ":regional_indicator_u: ",
['V'] = ":regional_indicator_v: ",
['W'] = ":regional_indicator_w: ",
['X'] = ":regional_indicator_x: ",
['Y'] = ":regional_indicator_y: ",
['Z'] = ":regional_indicator_z: ",
['!'] = ":exclamation: ",
['?'] = ":question: ",
['#'] = ":hash: ",
['*'] = ":star2: ",
['+'] = ":heavy_plus_sign: ",
['0'] = ":zero: ",
['1'] = ":one: ",
['2'] = ":two: ",
['3'] = ":three: ",
['4'] = ":four: ",
['5'] = ":five: ",
['6'] = ":six: ",
['7'] = ":seven: ",
['8'] = ":eight: ",
['9'] = ":nine: ",
[' '] = ""
};
var letters = text.ToUpper().ToCharArray();
var returnString = new StringBuilder();
foreach (var n in letters)
{
var emoji = emojiMap[n] ?? n;
returnString.Append(emoji);
}
return returnString.ToString();
}
}
}

View file

@ -1,8 +0,0 @@
namespace Geekbot.net.Lib.Converters
{
public interface IEmojiConverter
{
string NumberToEmoji(int number);
string TextToEmoji(string text);
}
}

View file

@ -1,33 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using Utf8Json;
namespace Geekbot.net.Lib.Converters
{
public class MtgManaConverter : IMtgManaConverter
{
private Dictionary<string, string> _manaDict;
public MtgManaConverter()
{
// these emotes can be found at https://discord.gg/bz8HyA7
var mtgEmojis = File.ReadAllText(Path.GetFullPath("./Lib/Converters/MtgManaEmojis.json"));
_manaDict = JsonSerializer.Deserialize<Dictionary<string, string>>(mtgEmojis);
}
public string ConvertMana(string mana)
{
var rgx = Regex.Matches(mana, @"(\{(.*?)\})");
foreach (Match manaTypes in rgx)
{
var m = _manaDict.GetValueOrDefault(manaTypes.Value);
if (!string.IsNullOrEmpty(m))
{
mana = mana.Replace(manaTypes.Value, m);
}
}
return mana;
}
}
}

View file

@ -1,50 +0,0 @@
{
"{0}": "<:mtg_0:415216130043412482>",
"{1}": "<:mtg_1:415216130253389835>",
"{2}": "<:mtg_2:415216130031091713>",
"{3}": "<:mtg_3:415216130467037194>",
"{4}": "<:mtg_4:415216130026635295>",
"{5}": "<:mtg_5:415216130492203008>",
"{6}": "<:mtg_6:415216130458779658>",
"{7}": "<:mtg_7:415216130190475265>",
"{8}": "<:mtg_8:415216130517630986>",
"{9}": "<:mtg_9:415216130500722689>",
"{10": "<:mtg_10:415216130450391051>",
"{11}": "<:mtg_11:415216130811101185>",
"{12}": "<:mtg_12:415216130525888532>",
"{13}": "<:mtg_13:415216130517631000>",
"{14}": "<:mtg_14:415216130165178370>",
"{15}": "<:mtg_15:415216130576089108>",
"{16}": "<:mtg_16:415216130358247425>",
"{17}": "<:mtg_17:415216130601517056>",
"{18}": "<:mtg_18:415216130462842891>",
"{19}": "<:mtg_19:415216130614099988>",
"{20}": "<:mtg_20:415216130656043038>",
"{W}": "<:mtg_white:415216131515744256>",
"{U}": "<:mtg_blue:415216130521694209>",
"{B}": "<:mtg_black:415216130873884683>",
"{R}": "<:mtg_red:415216131322806272>",
"{G}": "<:mtg_green:415216131180331009>",
"{S}": "<:mtg_s:415216131293446144>",
"{T}": "<:mtg_tap:415258392727257088>",
"{C}": "<:mtg_colorless:415216130706374666>",
"{2/W}": "<:mtg_2w:415216130446065664>",
"{2/U}": "<:mtg_2u:415216130429550592>",
"{2/B}": "<:mtg_2b:415216130160984065>",
"{2/R}": "<:mtg_2r:415216130454716436>",
"{2/G}": "<:mtg_2g:415216130420899840>",
"{W/U}": "<:mtg_wu:415216130970484736>",
"{W/B}": "<:mtg_wb:415216131222011914>",
"{U/R}": "<:mtg_ur:415216130962096128>",
"{U/B}": "<:mtg_ub:415216130865758218>",
"{R/W}": "<:mtg_rw:415216130878210057>",
"{G/W}": "<:mtg_gw:415216130567962646>",
"{G/U}": "<:mtg_gu:415216130739666945>",
"{B/R}": "<:mtg_br:415216130580283394>",
"{B/G}": "<:mtg_bg:415216130781609994>",
"{U/P}": "<:mtg_up:415216130861432842>",
"{R/P}": "<:mtg_rp:415216130597322783>",
"{G/P}": "<:mtg_gp:415216130760769546>",
"{W/P}": "<:mtg_wp:415216131541041172>",
"{B/P}": "<:mtg_bp:415216130664169482>"
}

View file

@ -1,17 +0,0 @@
using System;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
namespace Geekbot.net.Lib.Extensions
{
public static class DbSetExtensions
{
public static EntityEntry<T> AddIfNotExists<T>(this DbSet<T> dbSet, T entity, Expression<Func<T, bool>> predicate = null) where T : class, new()
{
var exists = predicate != null ? dbSet.Any(predicate) : dbSet.Any();
return !exists ? dbSet.Add(entity) : null;
}
}
}

View file

@ -1,9 +0,0 @@
using Discord.Commands;
using Geekbot.net.Lib.Context;
namespace Geekbot.net.Lib
{
public abstract class GeekbotBase : ModuleBase<GeekbotContext>
{
}
}

View file

@ -1,10 +0,0 @@
namespace Geekbot.net.Lib.Highscores
{
public enum HighscoreTypes
{
messages,
karma,
rolls,
cookies
}
}

View file

@ -1,18 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
namespace Geekbot.net.Lib.Localization
{
public interface ITranslationHandler
{
Task<string> GetString(ulong guildId, string command, string stringName);
string GetString(string language, string command, string stringName);
Task<Dictionary<string, string>> GetDict(ICommandContext context, string command);
Task<TranslationGuildContext> GetGuildContext(ICommandContext context);
Task<TranslationGuildContext> GetGuildContext(IGuild guild, IUserMessage message);
Task<bool> SetLanguage(ulong guildId, string language);
List<string> SupportedLanguages { get; }
}
}

View file

@ -1,91 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Geekbot.net.Lib.Localization
{
public class TranslationGuildContext
{
public ITranslationHandler TranslationHandler { get; }
public string Language { get; }
public Dictionary<string, string> Dict { get; }
public TranslationGuildContext(ITranslationHandler translationHandler, string language, Dictionary<string, string> dict)
{
TranslationHandler = translationHandler;
Language = language;
Dict = dict;
}
public string GetString(string stringToFormat, params object[] args)
{
return string.Format(Dict[stringToFormat] ?? "", args);
}
public string FormatDateTimeAsRemaining(DateTimeOffset dateTime)
{
var remaining = dateTime - DateTimeOffset.Now;
const string formattable = "{0} {1}";
var sb = new StringBuilder();
if (remaining.Days > 0)
{
var s = GetTimeString(TimeTypes.Days);
sb.AppendFormat(formattable, remaining.Days, GetSingOrPlur(remaining.Days, s));
}
if (remaining.Hours > 0)
{
if (sb.Length > 0) sb.Append(", ");
var s = GetTimeString(TimeTypes.Hours);
sb.AppendFormat(formattable, remaining.Hours, GetSingOrPlur(remaining.Hours, s));
}
if (remaining.Minutes > 0)
{
if (sb.Length > 0) sb.Append(", ");
var s = GetTimeString(TimeTypes.Minutes);
sb.AppendFormat(formattable, remaining.Minutes, GetSingOrPlur(remaining.Minutes, s));
}
if (remaining.Seconds > 0)
{
if (sb.Length > 0)
{
var and = TranslationHandler.GetString(Language, "dateTime", "And");
sb.AppendFormat(" {0} ", and);
}
var s = GetTimeString(TimeTypes.Seconds);
sb.AppendFormat(formattable, remaining.Seconds, GetSingOrPlur(remaining.Seconds, s));
}
return sb.ToString().Trim();
}
public Task<bool> SetLanguage(ulong guildId, string language)
{
return TranslationHandler.SetLanguage(guildId, language);
}
private string GetTimeString(TimeTypes type)
{
return TranslationHandler.GetString(Language, "dateTime", type.ToString());
}
private string GetSingOrPlur(int number, string rawString)
{
var versions = rawString.Split('|');
return number == 1 ? versions[0] : versions[1];
}
private enum TimeTypes
{
Days,
Hours,
Minutes,
Seconds
}
}
}

View file

@ -1,219 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.net.Database;
using Geekbot.net.Database.Models;
using Geekbot.net.Lib.Extensions;
using Geekbot.net.Lib.Logger;
using YamlDotNet.Serialization;
namespace Geekbot.net.Lib.Localization
{
public class TranslationHandler : ITranslationHandler
{
private readonly DatabaseContext _database;
private readonly IGeekbotLogger _logger;
private readonly Dictionary<ulong, string> _serverLanguages;
private Dictionary<string, Dictionary<string, Dictionary<string, string>>> _translations;
public TranslationHandler(DatabaseContext database, IGeekbotLogger logger)
{
_database = database;
_logger = logger;
_logger.Information(LogSource.Geekbot, "Loading Translations");
LoadTranslations();
_serverLanguages = new Dictionary<ulong, string>();
}
private void LoadTranslations()
{
try
{
// Read the file
var translationFile = File.ReadAllText(Path.GetFullPath("./Lib/Localization/Translations.yml"));
// Deserialize
var input = new StringReader(translationFile);
var deserializer = new DeserializerBuilder().Build();
var rawTranslations = deserializer.Deserialize<Dictionary<string, Dictionary<string, Dictionary<string, string>>>>(input);
// Sort
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>
{
{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>
{
{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;
// Find Languages
SupportedLanguages = new List<string>();
foreach (var lang in sortedPerLanguage)
{
SupportedLanguages.Add(lang.Key);
}
}
catch (Exception e)
{
_logger.Error(LogSource.Geekbot, "Failed to load Translations", e);
Environment.Exit(GeekbotExitCode.TranslationsFailed.GetHashCode());
}
}
private async Task<string> GetServerLanguage(ulong guildId)
{
try
{
string lang;
try
{
lang = _serverLanguages[guildId];
if (!string.IsNullOrEmpty(lang))
{
return lang;
}
throw new Exception();
}
catch
{
lang = (await GetGuild(guildId)).Language ?? "EN";
_serverLanguages[guildId] = lang;
return lang;
}
}
catch (Exception e)
{
_logger.Error(LogSource.Geekbot, "Could not get guild language", e);
return "EN";
}
}
public async Task<string> GetString(ulong guildId, string command, string stringName)
{
var serverLang = await GetServerLanguage(guildId);
return GetString(serverLang, command, stringName);
}
public string GetString(string language, string command, string stringName)
{
var translation = _translations[language][command][stringName];
if (!string.IsNullOrWhiteSpace(translation)) return translation;
translation = _translations[command][stringName]["EN"];
if (string.IsNullOrWhiteSpace(translation))
{
_logger.Warning(LogSource.Geekbot, $"No translation found for {command} - {stringName}");
}
return translation;
}
private Task<Dictionary<string, string>> GetDict(ICommandContext context)
{
return GetDict(context.Guild, context.Message);
}
private async Task<Dictionary<string, string>> GetDict(IGuild guild, IUserMessage message)
{
try
{
var command = message.Content.Split(' ').First().TrimStart('!').ToLower();
var serverLanguage = await GetServerLanguage(guild?.Id ?? 0);
return _translations[serverLanguage][command];
}
catch (Exception e)
{
_logger.Error(LogSource.Geekbot, "No translations for command found", e);
return new Dictionary<string, string>();
}
}
public async Task<TranslationGuildContext> GetGuildContext(ICommandContext context)
{
var dict = await GetDict(context);
var language = await GetServerLanguage(context.Guild?.Id ?? 0);
return new TranslationGuildContext(this, language, dict);
}
public async Task<TranslationGuildContext> GetGuildContext(IGuild guild, IUserMessage message)
{
var dict = await GetDict(guild, message);
var language = await GetServerLanguage(guild?.Id ?? 0);
return new TranslationGuildContext(this, language, dict);
}
public async Task<Dictionary<string, string>> GetDict(ICommandContext context, string command)
{
try
{
var serverLanguage = await GetServerLanguage(context.Guild?.Id ?? 0);
return _translations[serverLanguage][command];
}
catch (Exception e)
{
_logger.Error(LogSource.Geekbot, "No translations for command found", e);
return new Dictionary<string, string>();
}
}
public async Task<bool> SetLanguage(ulong guildId, string language)
{
try
{
if (!SupportedLanguages.Contains(language)) return false;
var guild = await GetGuild(guildId);
guild.Language = language;
_database.GuildSettings.Update(guild);
_serverLanguages[guildId] = language;
return true;
}
catch (Exception e)
{
_logger.Error(LogSource.Geekbot, "Error while changing language", e);
return false;
}
}
public List<string> SupportedLanguages { get; private set; }
private async Task<GuildSettingsModel> GetGuild(ulong guildId)
{
var guild = _database.GuildSettings.FirstOrDefault(g => g.GuildId.Equals(guildId.AsLong()));
if (guild != null) return guild;
_database.GuildSettings.Add(new GuildSettingsModel
{
GuildId = guildId.AsLong()
});
await _database.SaveChangesAsync();
return _database.GuildSettings.FirstOrDefault(g => g.GuildId.Equals(guildId.AsLong()));
}
}
}

View file

@ -1,175 +0,0 @@
---
dateTime:
Days:
EN: "day|days"
CHDE: "tag|täg"
Hours:
EN: "hour|hours"
CHDE: "stund|stunde"
Minutes:
EN: "minute|minutes"
CHDE: "minute|minute"
Seconds:
EN: "second|seconds"
CHDE: "sekunde|sekunde"
And:
EN: "and"
CHDE: "und"
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"
cookies:
GetCookies:
EN: "You got {0} cookies, there are now {1} cookies in you cookie jar"
CHDE: "Du häsch {0} guetzli becho, du häsch jetzt {1} guetzli ih dr büchse"
WaitForMoreCookies:
EN: "You already got cookies in the last 24 hours, you can have more cookies in {0}"
CHDE: "Du hesch scho guetzli becho ih de letzti 24 stund, du chasch meh ha in {0}"
InYourJar:
EN: "There are {0} cookies in you cookie jar"
CHDE: "Es hät {0} guetzli ih dineri büchs"
Given:
EN: "You gave {0} cookies to {1}"
CHDE: "Du hesch {1} {0} guetzli geh"
NotEnoughToGive:
EN: "You don't have enough cookies"
CHDE: "Du hesch nid gnueg guetzli"
NotEnoughCookiesToEat:
EN: "Your cookie jar looks almost empty, you should probably not eat a cookie"
CHDE: "Du hesch chuum no guetzli ih dineri büchs, du sötsch warschinli keini esse"
AteCookies:
EN: "You ate {0} cookies, you've only got {1} cookies left"
CHDE: "Du hesch {0} guetzli gesse und hesch jezt no {1} übrig"
role:
NoRolesConfigured:
EN: "There are no roles configured for this server"
CHDE: "Es sind kei rolle für dä server konfiguriert"
ListHeader:
EN: "**Self Service Roles on {0}**"
CHDE: "**Self Service Rollene uf {0}**"
ListInstruction:
EN: "To get a role, use `!role [name]`"
CHDE: "Zum ä rolle becho, schriib `!role [name]`"
RoleNotFound:
EN: "That role doesn't exist or is not on the whitelist"
CHDE: "Die rolle gids nid or isch nid uf dr whitelist"
RemovedUserFromRole:
EN: "Removed you from {0}"
CHDE: "Han di entfernt vo {0}"
AddedUserFromRole:
EN: "Added you to {0}"
CHDE: "Han di hinzue gfüegt zu {0}"
CannotAddManagedRole:
EN: "You can't add a role that is managed by discord"
CHDE: "Du chasch kei rolle hinzuefüge wo verwalted wird vo discord"
CannotAddDangerousRole:
EN: "You cannot add that role to self service because it contains one or more dangerous permissions"
CHDE: "Du chasch die rolle nid hinzuefüge will er ein oder mehreri gföhrlichi berechtigunge het"
AddedRoleToWhitelist:
EN: "Added {0} to the whitelist"
CHDE: "{0} isch zur whitelist hinzuegfüegt"
RemovedRoleFromWhitelist:
EN: "Removed {0} from the whitelist"
CHDE: "{0} isch vo dr whitelist glöscht"
quote:
NoQuotesFound:
EN: "This server doesn't seem to have any quotes yet. You can add a quote with `!quote save @user` or `!quote save <messageId>`"
CHDE: "Dä server het no kei quotes. Du chasch quotes hinzuefüege mit `!quote save @user` oder `!quote save <messageId>`"
CannotSaveOwnQuotes:
EN: "You can't save your own quotes..."
CHDE: "Du chasch kei quotes vo dir selber speichere..."
CannotQuoteBots:
EN: "You can't save quotes by a bot..."
CHDE: "Du chasch kei quotes vomne bot speichere..."
QuoteAdded:
EN: "**Quote Added**"
CHDE: "**Quote hinzugfüegt**"
Removed:
EN: "**Removed #{0}**"
CHDE: "**#{0} glöscht**"
NotFoundWithId:
EN: "I couldn't find a quote with that ID :disappointed:"
CHDE: "Ich chan kei quote finde mit därri ID :disappointed:"
rank:
InvalidType:
EN: "Valid types are '`messages`' '`karma`', '`rolls`' and '`cookies`'"
CHDE: "Gültigi paramenter sind '`messages`' '`karma`', '`rolls`' und '`cookies`'"
LimitingTo20Warning:
EN: ":warning: Limiting to 20\n"
CHDE: ":warning: Limitiert uf 20\n"
NoTypeFoundForServer:
EN: "No {0} found on this server"
CHDE: "Kei {0} gfunde für dä server"
FailedToResolveAllUsernames:
EN: ":warning: I couldn't find all usernames. Maybe they left the server?\n"
CHDE: ":warning: Ich han nid alli benutzername gfunde. villiicht hend sie de server verlah?\n"
HighscoresFor:
EN: ":bar_chart: **{0} Highscore for {1}**"
CHDE: ":bar_chart: **{0} Highscore für {1}**"

View file

@ -1,84 +0,0 @@
using System;
using Newtonsoft.Json;
namespace Geekbot.net.Lib.Logger
{
public class GeekbotLogger : IGeekbotLogger
{
private readonly bool _logAsJson;
private readonly NLog.Logger _logger;
private readonly JsonSerializerSettings _serializerSettings;
public GeekbotLogger(RunParameters runParameters, bool sumologicActive)
{
_logAsJson = sumologicActive || runParameters.LogJson;
_logger = LoggerFactory.CreateNLog(runParameters, sumologicActive);
_serializerSettings = new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
NullValueHandling = NullValueHandling.Include
};
Information(LogSource.Geekbot, "Using GeekbotLogger");
}
public void Trace(LogSource source, string message, object extra = null)
{
_logger.Trace(CreateLogString("Trace", source, message, null, extra));
}
public void Debug(LogSource source, string message, object extra = null)
{
if (_logAsJson) _logger.Info(CreateLogString("Debug", source, message, null, extra));
else _logger.Debug(CreateLogString("Debug", source, message, null, extra));
}
public void Information(LogSource source, string message, object extra = null)
{
_logger.Info(CreateLogString("Information", source, message, null, extra));
}
public void Warning(LogSource source, string message, Exception stackTrace = null, object extra = null)
{
if (_logAsJson) _logger.Info(CreateLogString("Warning", source, message, stackTrace, extra));
else _logger.Warn(CreateLogString("Warning", source, message, stackTrace, extra));
}
public void Error(LogSource source, string message, Exception stackTrace, object extra = null)
{
if (_logAsJson) _logger.Info(CreateLogString("Error", source, message, stackTrace, extra));
else _logger.Error(stackTrace, CreateLogString("Error", source, message, stackTrace, extra));
}
public NLog.Logger GetNLogger()
{
return _logger;
}
public bool LogAsJson()
{
return _logAsJson;
}
private string CreateLogString(string type, LogSource source, string message, Exception stackTrace = null, object extra = null)
{
if (_logAsJson)
{
var logObject = new GeekbotLoggerObject
{
Timestamp = DateTime.Now,
Type = type,
Source = source,
Message = message,
StackTrace = stackTrace,
Extra = extra
};
return JsonConvert.SerializeObject(logObject, Formatting.None, _serializerSettings);
}
if (source != LogSource.Message) return $"[{source}] - {message}";
var m = (MessageDto) extra;
return $"[{source}] - [{m?.Guild.Name} - {m?.Channel.Name}] {m?.User.Name}: {m?.Message.Content}";
}
}
}

View file

@ -1,14 +0,0 @@
using System;
namespace Geekbot.net.Lib.Logger
{
public class GeekbotLoggerObject
{
public DateTime Timestamp { get; set; }
public string Type { get; set; }
public LogSource Source { get; set; }
public string Message { get; set; }
public Exception StackTrace { get; set; }
public object Extra { get; set; }
}
}

View file

@ -1,15 +0,0 @@
namespace Geekbot.net.Lib.Media
{
public interface IMediaProvider
{
string GetCheckem();
string GetPanda();
string GetCrossant();
string GetSquirrel();
string GetPumpkin();
string GetTurtle();
string GetPinguin();
string GetFox();
string GetDab();
}
}

View file

@ -1,147 +0,0 @@
using System;
using System.IO;
using Geekbot.net.Lib.Logger;
namespace Geekbot.net.Lib.Media
{
public class MediaProvider : IMediaProvider
{
private readonly Random _random;
private readonly IGeekbotLogger _logger;
private string[] _checkemImages;
private string[] _pandaImages;
private string[] _croissantImages;
private string[] _squirrelImages;
private string[] _pumpkinImages;
private string[] _turtlesImages;
private string[] _pinguinImages;
private string[] _foxImages;
private string[] _dabImages;
public MediaProvider(IGeekbotLogger logger)
{
_random = new Random();
_logger = logger;
logger.Information(LogSource.Geekbot, "Loading Media Files");
LoadCheckem();
LoadPandas();
BakeCroissants();
LoadSquirrels();
LoadPumpkins();
LoadTurtles();
LoadPinguins();
LoadFoxes();
LoadDab();
}
private void LoadCheckem()
{
var rawLinks = File.ReadAllText(Path.GetFullPath("./Storage/checkEmPics"));
_checkemImages = rawLinks.Split("\n");
_logger.Trace(LogSource.Geekbot, $"Loaded {_checkemImages.Length} CheckEm Images");
}
private void LoadPandas()
{
var rawLinks = File.ReadAllText(Path.GetFullPath("./Storage/pandas"));
_pandaImages = rawLinks.Split("\n");
_logger.Trace(LogSource.Geekbot, $"Loaded {_pandaImages.Length} Panda Images");
}
private void BakeCroissants()
{
var rawLinks = File.ReadAllText(Path.GetFullPath("./Storage/croissant"));
_croissantImages = rawLinks.Split("\n");
_logger.Trace(LogSource.Geekbot, $"Loaded {_croissantImages.Length} Croissant Images");
}
private void LoadSquirrels()
{
var rawLinks = File.ReadAllText(Path.GetFullPath("./Storage/squirrel"));
_squirrelImages = rawLinks.Split("\n");
_logger.Trace(LogSource.Geekbot, $"Loaded {_squirrelImages.Length} Squirrel Images");
}
private void LoadPumpkins()
{
var rawLinks = File.ReadAllText(Path.GetFullPath("./Storage/pumpkin"));
_pumpkinImages = rawLinks.Split("\n");
_logger.Trace(LogSource.Geekbot, $"Loaded {_pumpkinImages.Length} Pumpkin Images");
}
private void LoadTurtles()
{
var rawLinks = File.ReadAllText(Path.GetFullPath("./Storage/turtles"));
_turtlesImages = rawLinks.Split("\n");
_logger.Trace(LogSource.Geekbot, $"Loaded {_turtlesImages.Length} Turtle Images");
}
private void LoadPinguins()
{
var rawLinks = File.ReadAllText(Path.GetFullPath("./Storage/pinguins"));
_pinguinImages = rawLinks.Split("\n");
_logger.Trace(LogSource.Geekbot, $"Loaded {_pinguinImages.Length} Pinguin Images");
}
private void LoadFoxes()
{
var rawLinks = File.ReadAllText(Path.GetFullPath("./Storage/foxes"));
_foxImages = rawLinks.Split("\n");
_logger.Trace(LogSource.Geekbot, $"Loaded {_foxImages.Length} Foxes Images");
}
private void LoadDab()
{
var rawLinks = File.ReadAllText(Path.GetFullPath("./Storage/dab"));
_dabImages = rawLinks.Split("\n");
_logger.Trace(LogSource.Geekbot, $"Loaded {_dabImages.Length} Dab Images");
}
public string GetCheckem()
{
return _checkemImages[_random.Next(0, _checkemImages.Length)];
}
public string GetPanda()
{
return _pandaImages[_random.Next(0, _pandaImages.Length)];
}
public string GetCrossant()
{
return _croissantImages[_random.Next(0, _croissantImages.Length)];
}
public string GetSquirrel()
{
return _squirrelImages[_random.Next(0, _squirrelImages.Length)];
}
public string GetPumpkin()
{
return _pumpkinImages[_random.Next(0, _pumpkinImages.Length)];
}
public string GetTurtle()
{
return _turtlesImages[_random.Next(0, _turtlesImages.Length)];
}
public string GetPinguin()
{
return _pinguinImages[_random.Next(0, _pinguinImages.Length)];
}
public string GetFox()
{
return _foxImages[_random.Next(0, _foxImages.Length)];
}
public string GetDab()
{
return _dabImages[_random.Next(0, _dabImages.Length)];
}
}
}

View file

@ -1,46 +0,0 @@
using System;
using System.Security.Cryptography;
namespace Geekbot.net.Lib.RandomNumberGenerator
{
public class RandomNumberGenerator : IRandomNumberGenerator
{
readonly RNGCryptoServiceProvider csp;
public RandomNumberGenerator()
{
csp = new RNGCryptoServiceProvider();
}
public int Next(int minValue, int maxExclusiveValue)
{
if (minValue >= maxExclusiveValue)
{
throw new ArgumentOutOfRangeException("minValue must be lower than maxExclusiveValue");
}
var diff = (long)maxExclusiveValue - minValue;
var upperBound = uint.MaxValue / diff * diff;
uint ui;
do
{
ui = GetRandomUInt();
} while (ui >= upperBound);
return (int)(minValue + (ui % diff));
}
private uint GetRandomUInt()
{
var randomBytes = GenerateRandomBytes(sizeof(uint));
return BitConverter.ToUInt32(randomBytes, 0);
}
private byte[] GenerateRandomBytes(int bytesNumber)
{
var buffer = new byte[bytesNumber];
csp.GetBytes(buffer);
return buffer;
}
}
}

View file

@ -1,14 +0,0 @@
using System.Threading.Tasks;
using Discord;
using Discord.WebSocket;
namespace Geekbot.net.Lib.ReactionListener
{
public interface IReactionListener
{
bool IsListener(ulong id);
Task AddRoleToListener(string messageId, IEmote emoji, IRole role);
void RemoveRole(ISocketMessageChannel channel, SocketReaction reaction);
void GiveRole(ISocketMessageChannel message, SocketReaction reaction);
}
}

View file

@ -1,90 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.WebSocket;
using StackExchange.Redis;
namespace Geekbot.net.Lib.ReactionListener
{
public class ReactionListener : IReactionListener
{
private readonly IDatabase _redis;
private Dictionary<string, Dictionary<IEmote, ulong>> _listener;
public ReactionListener(IDatabase redis)
{
_redis = redis;
LoadListeners();
}
private Task LoadListeners()
{
var ids = _redis.SetMembers("MessageIds");
_listener = new Dictionary<string, Dictionary<IEmote, ulong>>();
foreach (var id in ids)
{
var reactions = _redis.HashGetAll($"Messages:{id}");
var messageId = id;
var emojiDict = new Dictionary<IEmote, ulong>();
foreach (var r in reactions)
{
IEmote emote;
if (!r.Name.ToString().StartsWith('<'))
{
var emo = new Emoji(r.Name);
emote = emo;
}
else
{
emote = Emote.Parse(r.Name);
}
emojiDict.Add(emote, ulong.Parse(r.Value));
}
_listener.Add(messageId, emojiDict);
}
return Task.CompletedTask;
}
public bool IsListener(ulong id)
{
return _listener.ContainsKey(id.ToString());
}
public Task AddRoleToListener(string messageId, IEmote emoji, IRole role)
{
if (_redis.SetMembers("MessageIds").All(e => e.ToString() != messageId))
{
_redis.SetAdd("MessageIds", messageId);
}
_redis.HashSet($"Messages:{messageId}", new[] {new HashEntry(emoji.ToString(), role.Id.ToString())});
_redis.SetAdd("MessageIds", messageId);
if (_listener.ContainsKey(messageId))
{
_listener[messageId].Add(emoji, role.Id);
return Task.CompletedTask;
}
var dict = new Dictionary<IEmote, ulong>();
dict.Add(emoji, role.Id);
_listener.Add(messageId, dict);
return Task.CompletedTask;
}
public async void RemoveRole(ISocketMessageChannel channel, SocketReaction reaction)
{
var roleId = _listener[reaction.MessageId.ToString()][reaction.Emote];
var guild = (SocketGuildChannel) channel;
var role = guild.Guild.GetRole(roleId);
await ((IGuildUser) reaction.User.Value).RemoveRoleAsync(role);
}
public async void GiveRole(ISocketMessageChannel channel, SocketReaction reaction)
{
var roleId = _listener[reaction.MessageId.ToString()][reaction.Emote];
var guild = (SocketGuildChannel) channel;
var role = guild.Guild.GetRole(roleId);
await ((IGuildUser) reaction.User.Value).AddRoleAsync(role);
}
}
}

View file

@ -1,76 +0,0 @@
using CommandLine;
namespace Geekbot.net.Lib
{
public class RunParameters
{
/************************************
* General *
************************************/
[Option('V', "verbose", Default = false, HelpText = "Logs everything.")]
public bool Verbose { get; set; }
[Option('j', "log-json", Default = false, HelpText = "Logger outputs json")]
public bool LogJson { get; set; }
[Option('a', "disable-api", Default = false, HelpText = "Disables the web api")]
public bool DisableApi { get; set; }
[Option('e', "expose-errors", Default = false, HelpText = "Shows internal errors in the chat")]
public bool ExposeErrors { get; set; }
[Option("token", Default = null, HelpText = "Set a new bot token")]
public string Token { get; set; }
/************************************
* Database *
************************************/
[Option("in-memory", Default = false, HelpText = "Uses the in-memory database instead of postgresql")]
public bool InMemory { get; set; }
// Postresql connection
[Option("database", Default = "geekbot", HelpText = "Select a postgresql database")]
public string DbDatabase { get; set; }
[Option("db-host", Default = "localhost", HelpText = "Set a postgresql host (e.g. 127.0.0.1)")]
public string DbHost { get; set; }
[Option("db-port", Default = "5432", HelpText = "Set a postgresql host (e.g. 5432)")]
public string DbPort { get; set; }
[Option("db-user", Default = "geekbot", HelpText = "Set a postgresql user")]
public string DbUser { get; set; }
[Option("db-password", Default = "", HelpText = "Set a posgresql password")]
public string DbPassword { get; set; }
// Logging
[Option("db-logging", Default = false, HelpText = "Enable database logging")]
public bool DbLogging { get; set; }
/************************************
* Redis *
************************************/
[Option("redis-host", Default = "127.0.0.1", HelpText = "Set a redis host")]
public string RedisHost { get; set; }
[Option("redis-port", Default = "6379", HelpText = "Set a redis port")]
public string RedisPort { get; set; }
[Option("redis-database", Default = "6", HelpText = "Select a redis database (1-15)")]
public string RedisDatabase { get; set; }
/************************************
* WebApi *
************************************/
[Option("api-host", Default = "localhost", HelpText = "Host on which the WebApi listens")]
public string ApiHost { get; set; }
[Option("api-port", Default = "12995", HelpText = "Port on which the WebApi listens")]
public string ApiPort { get; set; }
}
}

View file

View file

@ -1,213 +0,0 @@
using System;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using CommandLine;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Geekbot.net.Database;
using Geekbot.net.Lib;
using Geekbot.net.Lib.AlmostRedis;
using Geekbot.net.Lib.Clients;
using Geekbot.net.Lib.Converters;
using Geekbot.net.Lib.ErrorHandling;
using Geekbot.net.Lib.GlobalSettings;
using Geekbot.net.Lib.Highscores;
using Geekbot.net.Lib.Levels;
using Geekbot.net.Lib.Localization;
using Geekbot.net.Lib.Logger;
using Geekbot.net.Lib.Media;
using Geekbot.net.Lib.RandomNumberGenerator;
using Geekbot.net.Lib.ReactionListener;
using Geekbot.net.Lib.UserRepository;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using WikipediaApi;
namespace Geekbot.net
{
internal class Program
{
private DiscordSocketClient _client;
private CommandService _commands;
private DatabaseInitializer _databaseInitializer;
private IGlobalSettings _globalSettings;
private IServiceCollection _services;
private IServiceProvider _servicesProvider;
private string _token;
private GeekbotLogger _logger;
private IUserRepository _userRepository;
private RunParameters _runParameters;
private IAlmostRedis _redis;
private static void Main(string[] args)
{
RunParameters runParameters = null;
Parser.Default.ParseArguments<RunParameters>(args)
.WithParsed(e => runParameters = e)
.WithNotParsed(_ => Environment.Exit(GeekbotExitCode.InvalidArguments.GetHashCode()));
var logo = new StringBuilder();
logo.AppendLine(@" ____ _____ _____ _ ______ ___ _____");
logo.AppendLine(@" / ___| ____| ____| |/ / __ ) / _ \\_ _|");
logo.AppendLine(@"| | _| _| | _| | ' /| _ \| | | || |");
logo.AppendLine(@"| |_| | |___| |___| . \| |_) | |_| || |");
logo.AppendLine(@" \____|_____|_____|_|\_\____/ \___/ |_|");
logo.AppendLine($"Version {Constants.BotVersion()} ".PadRight(41, '='));
Console.WriteLine(logo.ToString());
var sumologicActive = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("GEEKBOT_SUMO"));
var logger = new GeekbotLogger(runParameters, sumologicActive);
logger.Information(LogSource.Geekbot, "Starting...");
try
{
new Program().MainAsync(runParameters, logger).GetAwaiter().GetResult();
}
catch (Exception e)
{
logger.Error(LogSource.Geekbot, "RIP", e);
}
}
private async Task MainAsync(RunParameters runParameters, GeekbotLogger logger)
{
_logger = logger;
_runParameters = runParameters;
logger.Information(LogSource.Geekbot, "Initing Stuff");
var discordLogger = new DiscordLogger(logger);
_client = new DiscordSocketClient(new DiscordSocketConfig
{
LogLevel = LogSeverity.Verbose,
MessageCacheSize = 1000,
ExclusiveBulkDelete = true
});
_client.Log += discordLogger.Log;
_commands = new CommandService();
_databaseInitializer = new DatabaseInitializer(runParameters, logger);
var database = _databaseInitializer.Initialize();
database.Database.EnsureCreated();
if(!_runParameters.InMemory) database.Database.Migrate();
_globalSettings = new GlobalSettings(database);
try
{
_redis = new AlmostRedis(logger, runParameters);
_redis.Connect();
}
catch (Exception e)
{
logger.Error(LogSource.Redis, "Redis Connection Failed", e);
Environment.Exit(GeekbotExitCode.RedisConnectionFailed.GetHashCode());
}
_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;
}
_services = new ServiceCollection();
_userRepository = new UserRepository(_databaseInitializer.Initialize(), logger);
var fortunes = new FortunesProvider(logger);
var mediaProvider = new MediaProvider(logger);
var malClient = new MalClient(_globalSettings, logger);
var levelCalc = new LevelCalc();
var emojiConverter = new EmojiConverter();
var mtgManaConverter = new MtgManaConverter();
var wikipediaClient = new WikipediaClient();
var randomNumberGenerator = new RandomNumberGenerator();
_services.AddSingleton<IAlmostRedis>(_redis);
_services.AddSingleton<IUserRepository>(_userRepository);
_services.AddSingleton<IGeekbotLogger>(logger);
_services.AddSingleton<ILevelCalc>(levelCalc);
_services.AddSingleton<IEmojiConverter>(emojiConverter);
_services.AddSingleton<IFortunesProvider>(fortunes);
_services.AddSingleton<IMediaProvider>(mediaProvider);
_services.AddSingleton<IMalClient>(malClient);
_services.AddSingleton<IMtgManaConverter>(mtgManaConverter);
_services.AddSingleton<IWikipediaClient>(wikipediaClient);
_services.AddSingleton<IRandomNumberGenerator>(randomNumberGenerator);
_services.AddSingleton<IGlobalSettings>(_globalSettings);
_services.AddTransient<IHighscoreManager>((e) => new HighscoreManager(_databaseInitializer.Initialize(), _userRepository));
_services.AddTransient<DatabaseContext>((e) => _databaseInitializer.Initialize());
logger.Information(LogSource.Geekbot, "Connecting to Discord");
await Login();
await Task.Delay(-1);
}
private async Task Login()
{
try
{
await _client.LoginAsync(TokenType.Bot, _token);
await _client.StartAsync();
var isConneted = await IsConnected();
if (isConneted)
{
await _client.SetGameAsync(_globalSettings.GetKey("Game"));
_logger.Information(LogSource.Geekbot, $"Now Connected as {_client.CurrentUser.Username} to {_client.Guilds.Count} Servers");
_logger.Information(LogSource.Geekbot, "Registering Stuff");
var translationHandler = new TranslationHandler(_databaseInitializer.Initialize(), _logger);
var errorHandler = new ErrorHandler(_logger, translationHandler, _globalSettings, _runParameters.ExposeErrors);
var reactionListener = new ReactionListener(_redis.Db);
_services.AddSingleton<IErrorHandler>(errorHandler);
_services.AddSingleton<ITranslationHandler>(translationHandler);
_services.AddSingleton(_client);
_services.AddSingleton<IReactionListener>(reactionListener);
_servicesProvider = _services.BuildServiceProvider();
await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _servicesProvider);
var handlers = new Handlers(_databaseInitializer, _client, _logger, _redis, _servicesProvider, _commands, _userRepository, reactionListener, translationHandler);
_client.MessageReceived += handlers.RunCommand;
_client.MessageDeleted += handlers.MessageDeleted;
_client.UserJoined += handlers.UserJoined;
_client.UserUpdated += handlers.UserUpdated;
_client.UserLeft += handlers.UserLeft;
_client.ReactionAdded += handlers.ReactionAdded;
_client.ReactionRemoved += handlers.ReactionRemoved;
if (!_runParameters.InMemory) _client.MessageReceived += handlers.UpdateStats;
var webserver = _runParameters.DisableApi ? Task.Delay(10) : StartWebApi();
_logger.Information(LogSource.Geekbot, "Done and ready for use");
await webserver;
}
}
catch (Exception e)
{
_logger.Error(LogSource.Geekbot, "Could not connect to Discord", e);
Environment.Exit(GeekbotExitCode.CouldNotLogin.GetHashCode());
}
}
private async Task<bool> IsConnected()
{
while (!_client.ConnectionState.Equals(ConnectionState.Connected))
await Task.Delay(25);
return true;
}
private Task StartWebApi()
{
_logger.Information(LogSource.Api, "Starting Webserver");
var highscoreManager = new HighscoreManager(_databaseInitializer.Initialize(), _userRepository);
WebApi.WebApiStartup.StartWebApi(_logger, _runParameters, _commands, _databaseInitializer.Initialize(), _client, _globalSettings, highscoreManager);
return Task.CompletedTask;
}
}
}

View file

@ -1,122 +0,0 @@
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

View file

@ -1,17 +0,0 @@
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

View file

@ -1,29 +0,0 @@
https://i.ytimg.com/vi/qF6OOGuT_hI/maxresdefault.jpg
https://www.hd-wallpapersdownload.com/script/bulk-upload/desktop-funny-fox-wallpaper.jpg
http://moziru.com/images/drawn-fox-funny-18.jpg
https://static.tumblr.com/bb34d8f163098ad1daafcffbdbb03975/rk23uap/Nwwp0rmi2/tumblr_static_tumblr_static__640.jpg
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQoHUFOnZ3wJ2kT1skNdztFXXSvpU8bEoGS1alNZiuyLXvGJhcY
http://childrenstorytales.com/wp-content/uploads/2011/03/how-to-draw-a-red-fox-in-the-snow.jpg
https://www.popsci.com/sites/popsci.com/files/styles/1000_1x_/public/import/2013/images/2013/09/redfoxyawn.jpg?itok=yRkSVe8T
https://hdqwalls.com/wallpapers/wild-fox-art.jpg
https://ae01.alicdn.com/kf/HTB1Q9dpLpXXXXbhXpXXq6xXFXXXl/new-cute-fox-toy-lifelike-soft-long-yellow-fox-doll-gift-about-73cm.jpg_640x640.jpg
https://i.imgur.com/ktK9yXX.jpg
https://res.cloudinary.com/teepublic/image/private/s--yTx2ncFA--/t_Preview/b_rgb:c8e0ec,c_limit,f_auto,h_313,q_90,w_313/v1506478249/production/designs/1932607_0
http://4.bp.blogspot.com/-Hz-o_KYj3Xk/Vlm2mwbztjI/AAAAAAAA8Ss/jbH5ovjmC9A/s1600/ScreenShot5502.jpg
https://i.pinimg.com/originals/1e/d5/2f/1ed52f70873a95ac02fa074e48edfb71.jpg
https://i.imgur.com/2vCrtap.jpg
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSfukWGu_IBaDeJOMBqOhVAwsDfqEPw0BFpCn5_-Iyr_xjd7zi9
https://cdn.pixabay.com/photo/2017/01/31/18/36/animal-2026297_960_720.png
https://i.pinimg.com/originals/e2/63/67/e26367a0844633b2a697b0a9d69e8cc9.jpg
https://i.ebayimg.com/images/g/BvkAAOSwqxdTqrip/s-l300.jpg
https://res.cloudinary.com/teepublic/image/private/s--1R53bger--/t_Preview/b_rgb:eae0c7,c_limit,f_jpg,h_630,q_90,w_630/v1481013120/production/designs/914528_1.jpg
https://i.pinimg.com/originals/97/fe/69/97fe698462afde7b4209ccefeecbce71.jpg
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcT6G0ch6g-wG1TuDJ6BbkOFelMNnkgFXC6CxOw7qSNjoFkx-BCe
https://wallpaperscraft.com/image/fox_forest_grass_117190_540x960.jpg
https://image.freepik.com/free-vector/cartoon-flat-illustration-funny-cute-fox_6317-1174.jpg
https://orig00.deviantart.net/2feb/f/2013/137/a/f/fox_and_curious_squirrel_by_tamarar-d65ju8d.jpg
https://res.cloudinary.com/teepublic/image/private/s--dICeNmBx--/t_Preview/b_rgb:6e2229,c_limit,f_jpg,h_630,q_90,w_630/v1505243196/production/designs/1890493_1.jpg
https://vignette.wikia.nocookie.net/puppyinmypocketfanon/images/4/49/L-Baby-Fox.jpg/revision/latest?cb=20130421001806
http://7-themes.com/data_images/out/69/7009194-fox-puppy.jpg
http://www.tehcute.com/pics/201401/little-fox-big.jpg
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR6QXB1APLdUsyzO39kPvhnC9cOvcwzEtsxown9QjWilWppia2mwg

View file

@ -1,13 +0,0 @@
https://i.ytimg.com/vi/Qr6sULJnu2o/maxresdefault.jpg
https://www.apex-expeditions.com/wp-content/uploads/2015/08/newzealandSlider_Macquarie_ElephantSealKingPenguins_GRiehle_1366x601.jpg
https://www.birdlife.org/sites/default/files/styles/1600/public/slide.jpg?itok=HRhQfA1S
http://experimentexchange.com/wp-content/uploads/2016/07/penguins-fact.jpg
http://images.mentalfloss.com/sites/default/files/styles/mf_image_16x9/public/istock-511366776.jpg?itok=cWhdWNZ8&resize=1100x619
https://www.thevaporplace.ch/media/catalog/product/cache/1/thumbnail/800x800/9df78eab33525d08d6e5fb8d27136e95/a/t/atopack_penguin-15.jpg
https://www.superfastbusiness.com/wp-content/uploads/2015/10/real-time-penguin-algorithm-featured.jpg
http://www.antarctica.gov.au/__data/assets/image/0011/147737/varieties/antarctic.jpg
https://vignette.wikia.nocookie.net/robloxcreepypasta/images/1/11/AAEAAQAAAAAAAAdkAAAAJDc3YzkyYjJhLTYyZjctNDY2Mi04M2VjLTg4NjY4ZjgwYzRmNg.png/revision/latest?cb=20180207200526
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR3xV0lhpZuhT8Nmm6LaITsppZ7VfWcWXuyu2cPHrlv_dt_M92K5g
http://goboiano.com/wp-content/uploads/2017/04/Penguin-Kemeno-Friends-Waifu.jpg
https://cdn.yoast.com/app/uploads/2015/10/Penguins_1200x628.png
https://images.justwatch.com/backdrop/8611153/s1440/pingu

View file

@ -1,23 +0,0 @@
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

View file

@ -1,45 +0,0 @@
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

View file

@ -1,7 +0,0 @@
namespace Geekbot.net.WebApi
{
public class ApiError
{
public string Message { get; set; }
}
}

View file

@ -1,55 +0,0 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Discord.WebSocket;
using Geekbot.net.Lib.GlobalSettings;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
namespace Geekbot.net.WebApi.Controllers.Callback
{
public class CallbackController : Controller
{
private readonly DiscordSocketClient _client;
private readonly IGlobalSettings _globalSettings;
public CallbackController(DiscordSocketClient client, IGlobalSettings globalSettings)
{
_client = client;
_globalSettings = globalSettings;
}
[Route("/callback")]
public async Task<IActionResult> DoCallback([FromQuery] string code)
{
var token = "";
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("https://discordapp.com");
var appInfo = await _client.GetApplicationInfoAsync();
var accessToken = _globalSettings.GetKey("OAuthToken");
var callbackUrl = _globalSettings.GetKey("OAuthCallbackUrl");
var form = new Dictionary<string, string>();
form.Add("client_id", appInfo.Id.ToString());
form.Add("client_secret", accessToken);
form.Add("grant_type", "authorization_code");
form.Add("code", code);
form.Add("scope", "identify email guilds");
form.Add("redirect_uri", callbackUrl);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));
var result = await client.PostAsync("/api/oauth2/token", new FormUrlEncodedContent(form));
result.EnsureSuccessStatusCode();
var stringResponse = await result.Content.ReadAsStringAsync();
var responseData = JsonConvert.DeserializeObject<CallbackTokenResponseDto>(stringResponse);
token = responseData.access_token;
}
return new RedirectResult($"https://geekbot.pizzaandcoffee.rocks/login?token={token}", false);
}
}
}

View file

@ -1,11 +0,0 @@
namespace Geekbot.net.WebApi.Controllers.Callback
{
public class CallbackTokenResponseDto
{
public string access_token { get; set; }
public string token_type { get; set; }
public int expires_in { get; set; }
public string refresh_token { get; set; }
public string scope { get; set; }
}
}

View file

@ -1,41 +0,0 @@
using System.Linq;
using Discord.Commands;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
namespace Geekbot.net.WebApi.Controllers.Commands
{
[EnableCors("AllowSpecificOrigin")]
public class CommandController : Controller
{
private readonly CommandService _commands;
public CommandController(CommandService commands)
{
_commands = commands;
}
[Route("/v1/commands")]
public IActionResult GetCommands()
{
var commandList = (from cmd in _commands.Commands
let cmdParamsObj = cmd.Parameters.Select(cmdParam => new CommandParamDto
{
Summary = cmdParam.Summary,
Default = cmdParam.DefaultValue?.ToString(),
Type = cmdParam.Type?.ToString()
})
.ToList()
let param = string.Join(", !", cmd.Aliases)
select new CommandDto
{
Name = cmd.Name,
Summary = cmd.Summary,
IsAdminCommand = param.Contains("admin") || param.Contains("owner"),
Aliases = cmd.Aliases.ToList(),
Params = cmdParamsObj
}).ToList();
return Ok(commandList.FindAll(e => !e.Aliases[0].StartsWith("owner")));
}
}
}

View file

@ -1,14 +0,0 @@
using System;
using System.Collections.Generic;
namespace Geekbot.net.WebApi.Controllers.Commands
{
public class CommandDto
{
public string Name { get; set; }
public string Summary { get; set; }
public bool IsAdminCommand { get; set; }
public List<string> Aliases { get; set; }
public List<CommandParamDto> Params { get; set; }
}
}

View file

@ -1,9 +0,0 @@
namespace Geekbot.net.WebApi.Controllers.Commands
{
public class CommandParamDto
{
public string Summary { get; set; }
public string Default { get; set; }
public string Type { get; set; }
}
}

View file

@ -1,56 +0,0 @@
using System.Collections.Generic;
using Geekbot.net.Lib.Highscores;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
namespace Geekbot.net.WebApi.Controllers.Highscores
{
[EnableCors("AllowSpecificOrigin")]
public class HighscoreController : Controller
{
private readonly IHighscoreManager _highscoreManager;
public HighscoreController(IHighscoreManager highscoreManager)
{
_highscoreManager = highscoreManager;
}
[HttpPost]
[Route("/v1/highscore")]
public IActionResult GetHighscores([FromBody] HighscoreControllerPostBodyDto body)
{
if (!ModelState.IsValid || body == null)
{
var error = new SerializableError(ModelState);
return BadRequest(error);
}
Dictionary<HighscoreUserDto, int> list;
try
{
list = _highscoreManager.GetHighscoresWithUserData(body.Type, body.GuildId, body.Amount);
}
catch (HighscoreListEmptyException)
{
return NotFound(new ApiError
{
Message = $"No {body.Type} found on this server"
});
}
var response = new List<HighscoreControllerReponseBody>();
var counter = 1;
foreach (var item in list)
{
response.Add(new HighscoreControllerReponseBody
{
count = item.Value,
rank = counter,
user = item.Key
});
counter++;
}
return Ok(response);
}
}
}

View file

@ -1,16 +0,0 @@
using System.ComponentModel.DataAnnotations;
using Geekbot.net.Lib.Highscores;
namespace Geekbot.net.WebApi.Controllers.Highscores
{
public class HighscoreControllerPostBodyDto
{
[Required]
public ulong GuildId { get; set; }
public HighscoreTypes Type { get; set; } = HighscoreTypes.messages;
[Range(1, 150)]
public int Amount { get; set; } = 50;
}
}

View file

@ -1,11 +0,0 @@
using Geekbot.net.Lib.Highscores;
namespace Geekbot.net.WebApi.Controllers.Highscores
{
public class HighscoreControllerReponseBody
{
public int rank { get; set; }
public HighscoreUserDto user { get; set; }
public int count { get; set; }
}
}

View file

@ -1,9 +0,0 @@
namespace Geekbot.net.WebApi.Controllers.Status
{
public class ApiStatusDto
{
public string GeekbotVersion { get; set; }
public string ApiVersion { get; set; }
public string Status { get; set; }
}
}

View file

@ -1,23 +0,0 @@
using System.Globalization;
using Geekbot.net.Lib;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
namespace Geekbot.net.WebApi.Controllers.Status
{
[EnableCors("AllowSpecificOrigin")]
public class StatusController : Controller
{
[Route("/")]
public IActionResult GetCommands()
{
var responseBody = new ApiStatusDto
{
GeekbotVersion = Constants.BotVersion(),
ApiVersion = Constants.ApiVersion.ToString(CultureInfo.InvariantCulture),
Status = "Online"
};
return Ok(responseBody);
}
}
}

View file

@ -1,28 +0,0 @@
using System.Collections.Concurrent;
using Geekbot.net.Lib.Logger;
using Microsoft.Extensions.Logging;
namespace Geekbot.net.WebApi.Logging
{
public class AspLogProvider : ILoggerProvider
{
private readonly IGeekbotLogger _geekbotLogger;
private readonly ConcurrentDictionary<string, AspLogger> _loggers = new ConcurrentDictionary<string, AspLogger>();
public AspLogProvider(IGeekbotLogger geekbotLogger)
{
_geekbotLogger = geekbotLogger;
}
public void Dispose()
{
_loggers.Clear();
}
public ILogger CreateLogger(string categoryName)
{
return _loggers.GetOrAdd(categoryName, name => new AspLogger(categoryName, _geekbotLogger));
}
}
}

View file

@ -1,79 +0,0 @@
using System;
using Geekbot.net.Lib.Logger;
using Microsoft.Extensions.Logging;
namespace Geekbot.net.WebApi.Logging
{
public class AspLogger : ILogger
{
private readonly string _categoryName;
private readonly IGeekbotLogger _geekbotLogger;
public AspLogger(string categoryName, IGeekbotLogger geekbotLogger)
{
geekbotLogger.Trace(LogSource.Api, $"Adding {categoryName}");
_categoryName = categoryName;
_geekbotLogger = geekbotLogger;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
switch (logLevel)
{
case LogLevel.Trace:
_geekbotLogger.Trace(LogSource.Api, $"{eventId.Id} - {_categoryName} - {state}");
break;
case LogLevel.Debug:
_geekbotLogger.Debug(LogSource.Api, $"{eventId.Id} - {_categoryName} - {state}");
break;
case LogLevel.Information:
_geekbotLogger.Information(LogSource.Api, $"{eventId.Id} - {_categoryName} - {state}");
break;
case LogLevel.Warning:
_geekbotLogger.Warning(LogSource.Api, $"{eventId.Id} - {_categoryName} - {state}", exception);
break;
case LogLevel.Error:
case LogLevel.Critical:
_geekbotLogger.Error(LogSource.Api, $"{eventId.Id} - {_categoryName} - {state}", exception);
break;
case LogLevel.None:
break;
default:
throw new ArgumentOutOfRangeException(nameof(logLevel));
}
}
public bool IsEnabled(LogLevel logLevel)
{
return !_geekbotLogger.LogAsJson() && _geekbotLogger.GetNLogger().IsEnabled(ToGeekbotLogLevel(logLevel));
}
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
private static NLog.LogLevel ToGeekbotLogLevel(LogLevel level)
{
switch (level)
{
case LogLevel.Trace:
return NLog.LogLevel.Trace;
case LogLevel.Debug:
return NLog.LogLevel.Debug;
case LogLevel.Information:
return NLog.LogLevel.Info;
case LogLevel.Warning:
return NLog.LogLevel.Warn;
case LogLevel.Error:
return NLog.LogLevel.Error;
case LogLevel.Critical:
return NLog.LogLevel.Fatal;
case LogLevel.None:
return NLog.LogLevel.Off;
default:
throw new ArgumentOutOfRangeException(nameof(level));
}
}
}
}

View file

@ -1,58 +0,0 @@
using System.Net;
using System.Reflection;
using Discord.Commands;
using Discord.WebSocket;
using Geekbot.net.Database;
using Geekbot.net.Lib;
using Geekbot.net.Lib.GlobalSettings;
using Geekbot.net.Lib.Highscores;
using Geekbot.net.Lib.Logger;
using Geekbot.net.WebApi.Logging;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Geekbot.net.WebApi
{
public static class WebApiStartup
{
public static void StartWebApi(IGeekbotLogger logger, RunParameters runParameters, CommandService commandService,
DatabaseContext databaseContext, DiscordSocketClient client, IGlobalSettings globalSettings, IHighscoreManager highscoreManager)
{
WebHost.CreateDefaultBuilder()
.UseKestrel(options =>
{
options.Listen(IPAddress.Any, int.Parse(runParameters.ApiPort));
})
.ConfigureServices(services =>
{
services.AddMvc();
services.AddSingleton<CommandService>(commandService);
services.AddSingleton<DatabaseContext>(databaseContext);
services.AddSingleton<DiscordSocketClient>(client);
services.AddSingleton<IGlobalSettings>(globalSettings);
services.AddSingleton<IHighscoreManager>(highscoreManager);
services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigin",
builder => builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
});
})
.Configure(app =>
{
app.UseMvc();
app.UseCors(builder => builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod().Build());
})
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.SetMinimumLevel(LogLevel.Debug);
logging.AddProvider(new AspLogProvider(logger));
})
.UseSetting(WebHostDefaults.ApplicationKey, typeof(Program).GetTypeInfo().Assembly.FullName)
.Build().Run();
}
}
}

View file

@ -1,71 +0,0 @@
using System;
using System.Collections.Generic;
using Geekbot.net.Lib.Localization;
using Moq;
using Xunit;
namespace Tests.Lib.Localization
{
public class TranslationGuildContext_test
{
public class FormatDateTimeAsRemainingTestDto
{
public DateTimeOffset DateTime { get; set; }
public string Expected { get; set; }
}
public static TestData<FormatDateTimeAsRemainingTestDto> FormatDateTimeAsRemainingData =>
new TestData<FormatDateTimeAsRemainingTestDto>
{
{
"Wait for days",
new FormatDateTimeAsRemainingTestDto
{
DateTime = DateTimeOffset.Now.AddDays(5),
Expected = "4 days, 23 hours, 59 minutes and 59 seconds"
}
},
{
"Wait for minutes",
new FormatDateTimeAsRemainingTestDto
{
DateTime = DateTimeOffset.Now.AddMinutes(5),
Expected = "4 minutes and 59 seconds"
}
},
{
"Wait for seconds",
new FormatDateTimeAsRemainingTestDto
{
DateTime = DateTimeOffset.Now.AddSeconds(5),
Expected = "4 seconds"
}
}
};
[Theory, MemberData(nameof(FormatDateTimeAsRemainingData))]
public void FormatDateTimeAsRemaining(string testName, FormatDateTimeAsRemainingTestDto testData)
{
var translationHandlerMock = new Mock<ITranslationHandler>(MockBehavior.Loose);
translationHandlerMock
.Setup(thm => thm.GetString("EN", "dateTime", "Days"))
.Returns("day|days");
translationHandlerMock
.Setup(thm => thm.GetString("EN", "dateTime", "Hours"))
.Returns("hour|hours");
translationHandlerMock
.Setup(thm => thm.GetString("EN", "dateTime", "Minutes"))
.Returns("minute|minutes");
translationHandlerMock
.Setup(thm => thm.GetString("EN", "dateTime", "Seconds"))
.Returns("second|seconds");
translationHandlerMock
.Setup(thm => thm.GetString("EN", "dateTime", "And"))
.Returns("and");
var context = new TranslationGuildContext(translationHandlerMock.Object, "EN", new Dictionary<string, string>());
var result = context.FormatDateTimeAsRemaining(testData.DateTime);
Assert.Equal(result, testData.Expected);
}
}
}

View file

@ -1,44 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FluentAssertions;
using Xunit;
using YamlDotNet.Serialization;
namespace Tests.Lib.Localization
{
public class Translations_test
{
[Fact]
public void TranslationsYamlIsValid()
{
// Read the file
var translationFile = File.ReadAllText(Path.GetFullPath("./../../../../Geekbot.net/Lib/Localization/Translations.yml"));
// Deserialize
var input = new StringReader(translationFile);
var deserializer = new DeserializerBuilder().Build();
var rawTranslations = deserializer.Deserialize<Dictionary<string, Dictionary<string, Dictionary<string, string>>>>(input);
// These languages must be supported
var supportedLanguages = new List<string>
{
"EN",
"CHDE"
};
// Iterate every single key to make sure it's populated
foreach (var command in rawTranslations)
{
foreach (var str in command.Value)
{
str.Value.Select(e => e.Key).ToList().Should().BeEquivalentTo(supportedLanguages, str.Key);
foreach (var lang in str.Value)
{
lang.Value.Should().NotBeNullOrEmpty($"{command.Key} / {str.Key} / {lang.Key}");
}
}
}
}
}
}

View file

@ -1,14 +0,0 @@
using System;
namespace WikipediaApi.Page
{
public class PageApiUrls
{
public Uri Summary { get; set; }
public Uri Metadata { get; set; }
public Uri References { get; set; }
public Uri Media { get; set; }
public Uri EditHtml { get; set; }
public Uri TalkPageHtml { get; set; }
}
}

View file

@ -1,8 +0,0 @@
namespace WikipediaApi.Page
{
public class PageContentUrlCollection
{
public PageContentUrls Desktop { get; set; }
public PageContentUrls Mobile { get; set; }
}
}

Some files were not shown because too many files have changed in this diff Show more