Compare commits

..

149 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
247 changed files with 5983 additions and 2128 deletions

View file

@ -7,13 +7,13 @@
ansible_python_interpreter: /usr/bin/python3
tasks:
- name: Login to Gitlab Docker Registry
docker_login:
'community.docker.docker_login':
registry_url: "{{ lookup('env', 'CI_REGISTRY') }}"
username: "{{ lookup('env', 'CI_REGISTRY_USER') }}"
password: "{{ lookup('env', 'CI_REGISTRY_PASSWORD') }}"
reauthorize: yes
- name: Replace Prod Container
docker_container:
'community.docker.docker_container':
name: GeekbotProd
image: "{{ lookup('env', 'IMAGE_TAG') }}"
recreate: yes
@ -34,5 +34,5 @@
GEEKBOT_SENTRY: "{{ lookup('env', 'GEEKBOT_SENTRY') }}"
GEEKBOT_DB_REDSHIFT_COMPAT: "true"
- name: Cleanup Old Container
docker_prune:
'community.docker.docker_prune':
images: yes

View file

@ -5,12 +5,12 @@ stages:
- ops
variables:
VERSION: 4.3.0-$CI_COMMIT_SHORT_SHA
VERSION: 4.4.0-V$CI_COMMIT_SHORT_SHA
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
Build:
stage: build
image: mcr.microsoft.com/dotnet/sdk:5.0
image: mcr.microsoft.com/dotnet/sdk:6.0
artifacts:
expire_in: 1h
paths:
@ -18,7 +18,7 @@ Build:
script:
- dotnet restore
- dotnet test tests
- dotnet publish --version-suffix $VERSION -r linux-x64 -c Release -o ./app ./src/Bot/
- dotnet publish --version-suffix "$VERSION" -r linux-x64 -c Release -p:DebugType=embedded --no-self-contained -o ./app ./src/Startup/
Package:
stage: docker
@ -34,7 +34,7 @@ Package:
Deploy:
stage: deploy
image: ansible/ansible-runner
image: quay.io/ansible/ansible-runner:stable-2.12-latest
only:
- master
variables:
@ -46,6 +46,7 @@ Deploy:
- chmod -R 600 /root/.ssh
- ssh-keyscan -p 65432 $PROD_IP > /root/.ssh/known_hosts
script:
- ansible-galaxy collection install -r ansible-requirements.yml
- ansible-playbook -i $PROD_IP, .deploy.yml
Sentry:

View file

@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/aspnet:5.0
FROM mcr.microsoft.com/dotnet/aspnet:6.0
COPY ./app /app/

View file

@ -11,6 +11,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Web", "src\Web\Web.csproj",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bot", "src\Bot\Bot.csproj", "{DBF79896-9F7F-443D-B336-155E276DFF16}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Commands", "src\Commands\Commands.csproj", "{7C771DFE-912A-4276-B0A6-047E09603F1E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Interactions", "src\Interactions\Interactions.csproj", "{FF6859D9-C539-4910-BE1E-9ECFED2F46FA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Startup", "src\Startup\Startup.csproj", "{A691B018-4B19-4A7A-A0F6-DBB17641254F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -33,6 +39,18 @@ Global
{DBF79896-9F7F-443D-B336-155E276DFF16}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DBF79896-9F7F-443D-B336-155E276DFF16}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DBF79896-9F7F-443D-B336-155E276DFF16}.Release|Any CPU.Build.0 = Release|Any CPU
{7C771DFE-912A-4276-B0A6-047E09603F1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7C771DFE-912A-4276-B0A6-047E09603F1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7C771DFE-912A-4276-B0A6-047E09603F1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7C771DFE-912A-4276-B0A6-047E09603F1E}.Release|Any CPU.Build.0 = Release|Any CPU
{FF6859D9-C539-4910-BE1E-9ECFED2F46FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FF6859D9-C539-4910-BE1E-9ECFED2F46FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FF6859D9-C539-4910-BE1E-9ECFED2F46FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FF6859D9-C539-4910-BE1E-9ECFED2F46FA}.Release|Any CPU.Build.0 = Release|Any CPU
{A691B018-4B19-4A7A-A0F6-DBB17641254F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A691B018-4B19-4A7A-A0F6-DBB17641254F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A691B018-4B19-4A7A-A0F6-DBB17641254F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A691B018-4B19-4A7A-A0F6-DBB17641254F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

3
ansible-requirements.yml Normal file
View file

@ -0,0 +1,3 @@
collections:
- name: community.docker
version: 2.7.0

View file

@ -1,34 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers>
<ApplicationIcon>derp.ico</ApplicationIcon>
<TargetFramework>net6.0</TargetFramework>
<VersionSuffix>$(VersionSuffix)</VersionSuffix>
<RootNamespace>Geekbot.Bot</RootNamespace>
<AssemblyName>Geekbot</AssemblyName>
<Version Condition=" '$(VersionSuffix)' != '' ">$(VersionSuffix)</Version>
<AssemblyName>Geekbot.Bot</AssemblyName>
<Version Condition=" '$(VersionSuffix)' != '' ">$(VersionSuffix)</Version>
<Version Condition=" '$(VersionSuffix)' == '' ">0.0.0-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>
<ImplicitUsings>enable</ImplicitUsings>
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<OutputType>Library</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="Google.Apis.YouTube.v3" Version="1.45.0.1929" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.24" />
<PackageReference Include="JikanDotNet" Version="1.5.1" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.36" />
<PackageReference Include="JikanDotNet" Version="1.6.0" />
<PackageReference Include="MtgApiManager.Lib" Version="1.2.2" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="PokeApi.NET" Version="1.1.2" />
<PackageReference Include="SharpRaven" Version="2.4.0" />
<PackageReference Include="Sentry" Version="3.11.0" />
</ItemGroup>
<ItemGroup>
<Content Include="Storage\*">
@ -36,113 +27,7 @@
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Commands\Commands.csproj" />
<ProjectReference Include="..\Core\Core.csproj" />
<ProjectReference Include="..\Web\Web.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Localization\Ship.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Ship.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Localization\Rank.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Rank.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Localization\Karma.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Karma.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Localization\Internal.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Internal.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Localization\Cookies.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Cookies.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Localization\Roll.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Roll.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Localization\Choose.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Choose.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Localization\Admin.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Admin.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Localization\Quote.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Quote.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Localization\Role.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Role.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Localization\Stats.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Stats.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Compile Update="Localization\Ship.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>ship.resx</DependentUpon>
</Compile>
<Compile Update="Localization\Rank.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Rank.resx</DependentUpon>
</Compile>
<Compile Update="Localization\Ship.Designer.cs">
<DependentUpon>Ship.resx</DependentUpon>
</Compile>
<Compile Update="Localization\Karma.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Karma.resx</DependentUpon>
</Compile>
<Compile Update="Localization\Internal.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Internal.resx</DependentUpon>
</Compile>
<Compile Update="Localization\Cookies.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Cookies.resx</DependentUpon>
</Compile>
<Compile Update="Localization\Roll.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Roll.resx</DependentUpon>
</Compile>
<Compile Update="Localization\Choose.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Choose.resx</DependentUpon>
</Compile>
<Compile Update="Localization\Admin.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Admin.resx</DependentUpon>
</Compile>
<Compile Update="Localization\Quote.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Quote.resx</DependentUpon>
</Compile>
<Compile Update="Localization\Role.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Role.resx</DependentUpon>
</Compile>
<Compile Update="Localization\Stats.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Stats.resx</DependentUpon>
</Compile>
</ItemGroup>
</Project>

127
src/Bot/BotStartup.cs Normal file
View file

@ -0,0 +1,127 @@
using System.Reflection;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Geekbot.Bot.Handlers;
using Geekbot.Core;
using Geekbot.Core.Database;
using Geekbot.Core.GlobalSettings;
using Geekbot.Core.GuildSettingsManager;
using Geekbot.Core.Logger;
using Geekbot.Core.Logger.Adapters;
using Geekbot.Core.ReactionListener;
using Geekbot.Core.UserRepository;
using Microsoft.Extensions.DependencyInjection;
namespace Geekbot.Bot;
public class BotStartup
{
private readonly IServiceCollection _serviceCollection;
private readonly GeekbotLogger _logger;
private readonly RunParameters _runParameters;
private readonly IGlobalSettings _globalSettings;
private DiscordSocketClient _client;
public BotStartup(IServiceCollection serviceCollection, GeekbotLogger logger, RunParameters runParameters, IGlobalSettings globalSettings)
{
_serviceCollection = serviceCollection;
_logger = logger;
_runParameters = runParameters;
_globalSettings = globalSettings;
}
public async Task Start()
{
_logger.Information(LogSource.Geekbot, "Connecting to Discord");
SetupDiscordClient();
await Login();
await _client.SetGameAsync(_globalSettings.GetKey("Game"));
_logger.Information(LogSource.Geekbot, $"Now Connected as {_client.CurrentUser.Username} to {_client.Guilds.Count} Servers");
_logger.Information(LogSource.Geekbot, "Registering Gateway Handlers");
await RegisterHandlers();
_logger.Information(LogSource.Geekbot, "Done and ready for use");
await Task.Delay(-1);
}
private void SetupDiscordClient()
{
_client = new DiscordSocketClient(new DiscordSocketConfig
{
GatewayIntents = GatewayIntents.DirectMessageReactions |
GatewayIntents.DirectMessages |
GatewayIntents.GuildMessageReactions |
GatewayIntents.GuildMessages |
GatewayIntents.GuildWebhooks |
GatewayIntents.GuildIntegrations |
GatewayIntents.GuildEmojis |
GatewayIntents.GuildBans |
GatewayIntents.Guilds |
GatewayIntents.GuildMembers,
LogLevel = LogSeverity.Verbose,
MessageCacheSize = 1000,
});
var discordLogger = new DiscordLogger(_logger);
_client.Log += discordLogger.Log;
}
private async Task Login()
{
try
{
var token = await GetToken();
await _client.LoginAsync(TokenType.Bot, token);
await _client.StartAsync();
while (!_client.ConnectionState.Equals(ConnectionState.Connected)) await Task.Delay(25);
}
catch (Exception e)
{
_logger.Error(LogSource.Geekbot, "Could not connect to Discord", e);
Environment.Exit(GeekbotExitCode.CouldNotLogin.GetHashCode());
}
}
private async Task<string> GetToken()
{
var token = _runParameters.Token ?? _globalSettings.GetKey("DiscordToken");
if (string.IsNullOrEmpty(token))
{
Console.Write("Your bot Token: ");
var newToken = Console.ReadLine();
await _globalSettings.SetKey("DiscordToken", newToken);
await _globalSettings.SetKey("Game", "Ping Pong");
token = newToken;
}
return token;
}
private async Task RegisterHandlers()
{
var applicationInfo = await _client.GetApplicationInfoAsync();
_serviceCollection.AddSingleton<DiscordSocketClient>(_client);
var serviceProvider = _serviceCollection.BuildServiceProvider();
var commands = new CommandService();
await commands.AddModulesAsync(Assembly.GetAssembly(typeof(BotStartup)), serviceProvider);
var commandHandler = new CommandHandler(_client, _logger, serviceProvider, commands, applicationInfo, serviceProvider.GetService<IGuildSettingsManager>());
var userHandler = new UserHandler(serviceProvider.GetService<IUserRepository>(), _logger, serviceProvider.GetService<DatabaseContext>(), _client);
var reactionHandler = new ReactionHandler(serviceProvider.GetService<IReactionListener>());
var statsHandler = new StatsHandler(_logger, serviceProvider.GetService<DatabaseContext>());
var messageDeletedHandler = new MessageDeletedHandler(serviceProvider.GetService<DatabaseContext>(), _logger, _client);
_client.MessageReceived += commandHandler.RunCommand;
_client.MessageDeleted += messageDeletedHandler.HandleMessageDeleted;
_client.UserJoined += userHandler.Joined;
_client.UserUpdated += userHandler.Updated;
_client.UserLeft += userHandler.Left;
_client.ReactionAdded += reactionHandler.Added;
_client.ReactionRemoved += reactionHandler.Removed;
if (!_runParameters.InMemory) _client.MessageReceived += statsHandler.UpdateStats;
}
}

View file

@ -2,7 +2,7 @@ using System;
using System.Threading.Tasks;
using Discord.Commands;
namespace Geekbot.Core.CommandPreconditions
namespace Geekbot.Bot.CommandPreconditions
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class DisableInDirectMessageAttribute : PreconditionAttribute

View file

@ -9,11 +9,12 @@ using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Geekbot.Bot.CommandPreconditions;
using Geekbot.Core;
using Geekbot.Core.CommandPreconditions;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
using Geekbot.Core.GuildSettingsManager;
using Localization = Geekbot.Core.Localization;
namespace Geekbot.Bot.Commands.Admin
{

View file

@ -1,38 +0,0 @@
using System;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core.CommandPreconditions;
using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Admin
{
[Group("mod")]
[RequireUserPermission(GuildPermission.KickMembers)]
[RequireUserPermission(GuildPermission.ManageMessages)]
[RequireUserPermission(GuildPermission.ManageRoles)]
[DisableInDirectMessage]
public class Mod : ModuleBase
{
private readonly IErrorHandler _errorHandler;
public Mod(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
}
[Command("namehistory", RunMode = RunMode.Async)]
[Summary("See past usernames of an user")]
public async Task UsernameHistory([Summary("@someone")] IUser user)
{
try
{
await Context.Channel.SendMessageAsync("This command has been removed due to low usage and excessively high database usage");
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -6,14 +6,15 @@ using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.Net;
using Geekbot.Bot.CommandPreconditions;
using Geekbot.Core;
using Geekbot.Core.CommandPreconditions;
using Geekbot.Core.Database;
using Geekbot.Core.Database.Models;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
using Geekbot.Core.GuildSettingsManager;
using Geekbot.Core.ReactionListener;
using Localization = Geekbot.Core.Localization;
namespace Geekbot.Bot.Commands.Admin
{

View file

@ -3,13 +3,14 @@ using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
using PokeAPI;
namespace Geekbot.Bot.Commands.Games
{
public class Pokedex : ModuleBase
public class Pokedex : TransactionModuleBase
{
private readonly IErrorHandler _errorHandler;

View file

@ -1,16 +1,13 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.Bot.Utils;
using Geekbot.Core;
using Geekbot.Core.Database;
using Geekbot.Core.Database.Models;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
using Geekbot.Core.GuildSettingsManager;
using Geekbot.Core.KvInMemoryStore;
using Geekbot.Core.RandomNumberGenerator;
using Sentry;
namespace Geekbot.Bot.Commands.Games.Roll
{
@ -34,63 +31,20 @@ namespace Geekbot.Bot.Commands.Games.Roll
{
try
{
var number = _randomNumberGenerator.Next(1, 100);
int.TryParse(stuff, out var guess);
if (guess <= 100 && guess > 0)
{
var kvKey = $"{Context?.Guild?.Id ?? 0}:{Context.User.Id}:RollsPrevious";
var prevRoll = _kvInMemoryStore.Get<RollTimeout>(kvKey);
if (prevRoll?.LastGuess == guess && prevRoll?.GuessedOn.AddDays(1) > DateTime.Now)
{
await ReplyAsync(string.Format(
Localization.Roll.NoPrevGuess,
Context.Message.Author.Mention,
DateLocalization.FormatDateTimeAsRemaining(prevRoll.GuessedOn.AddDays(1))));
return;
}
_kvInMemoryStore.Set(kvKey, new RollTimeout {LastGuess = guess, GuessedOn = DateTime.Now});
await ReplyAsync(string.Format(Localization.Roll.Rolled, Context.Message.Author.Mention, number, guess));
if (guess == number)
{
await ReplyAsync(string.Format(Localization.Roll.Gratz, Context.Message.Author));
var user = await GetUser(Context.User.Id);
user.Rolls += 1;
_database.Rolls.Update(user);
await _database.SaveChangesAsync();
}
}
else
{
await ReplyAsync(string.Format(Localization.Roll.RolledNoGuess, Context.Message.Author.Mention, number));
}
var res = await new Geekbot.Commands.Roll.Roll(_kvInMemoryStore, _database, _randomNumberGenerator)
.RunFromGateway(
Context.Guild.Id,
Context.User.Id,
Context.User.Username,
stuff ?? "0"
);
await ReplyAsync(res);
}
catch (Exception e)
{
await ErrorHandler.HandleCommandException(e, Context);
Transaction.Status = SpanStatus.InternalError;
}
}
private async Task<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

@ -10,7 +10,7 @@ using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Integrations.LolMmr
{
public class LolMmr : ModuleBase
public class LolMmr : TransactionModuleBase
{
private readonly IErrorHandler _errorHandler;
@ -46,9 +46,9 @@ namespace Geekbot.Bot.Commands.Integrations.LolMmr
var sb = new StringBuilder();
sb.AppendLine($"**MMR for {summonerName}**");
sb.AppendLine($"Normal: {data.Normal.Avg}");
sb.AppendLine($"Ranked: {data.Ranked.Avg}");
sb.AppendLine($"ARAM: {data.ARAM.Avg}");
sb.AppendLine($"Normal: {data.Normal?.Avg ?? 0}");
sb.AppendLine($"Ranked: {data.Ranked?.Avg ?? 0}");
sb.AppendLine($"ARAM: {data.ARAM?.Avg ?? 0}");
await Context.Channel.SendMessageAsync(sb.ToString());
}

View file

@ -1,9 +1,16 @@
using System.Text.Json.Serialization;
namespace Geekbot.Bot.Commands.Integrations.LolMmr
{
public class LolMmrDto
{
[JsonPropertyName("ranked")]
public LolMrrInfoDto Ranked { get; set; }
[JsonPropertyName("normal")]
public LolMrrInfoDto Normal { get; set; }
[JsonPropertyName("aram")]
public LolMrrInfoDto ARAM { get; set; }
}
}

View file

@ -1,10 +1,10 @@
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Geekbot.Bot.Commands.Integrations.LolMmr
{
public class LolMrrInfoDto
{
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public decimal Avg { get; set; } = 0;
[JsonPropertyName("avg")]
public decimal? Avg { get; set; }
}
}

View file

@ -4,6 +4,7 @@ using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core;
using Geekbot.Core.Converters;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
@ -11,7 +12,7 @@ using MtgApiManager.Lib.Service;
namespace Geekbot.Bot.Commands.Integrations
{
public class MagicTheGathering : ModuleBase
public class MagicTheGathering : TransactionModuleBase
{
private readonly IErrorHandler _errorHandler;
private readonly IMtgManaConverter _manaConverter;

View file

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

View file

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

View file

@ -1,60 +0,0 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
namespace Geekbot.Bot.Commands.Integrations.UbranDictionary
{
public class UrbanDictionary : ModuleBase
{
private readonly IErrorHandler _errorHandler;
public UrbanDictionary(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
}
[Command("urban", RunMode = RunMode.Async)]
[Summary("Lookup something on urban dictionary")]
public async Task UrbanDefine([Remainder] [Summary("word")] string word)
{
try
{
var definitions = await HttpAbstractions.Get<UrbanResponseDto>(new Uri($"https://api.urbandictionary.com/v0/define?term={word}"));
if (definitions.List.Count == 0)
{
await ReplyAsync("That word hasn't been defined...");
return;
}
var definition = definitions.List.First(e => !string.IsNullOrWhiteSpace(e.Example));
var eb = new EmbedBuilder();
eb.WithAuthor(new EmbedAuthorBuilder
{
Name = definition.Word,
Url = definition.Permalink
});
eb.WithColor(new Color(239, 255, 0));
static string ShortenIfToLong(string str, int maxLength) => str.Length > maxLength ? $"{str.Substring(0, maxLength - 5)}[...]" : str;
if (!string.IsNullOrEmpty(definition.Definition)) eb.Description = ShortenIfToLong(definition.Definition, 1800);
if (!string.IsNullOrEmpty(definition.Example)) eb.AddField("Example", ShortenIfToLong(definition.Example, 1024));
if (!string.IsNullOrEmpty(definition.ThumbsUp)) eb.AddInlineField("Upvotes", definition.ThumbsUp);
if (!string.IsNullOrEmpty(definition.ThumbsDown)) eb.AddInlineField("Downvotes", definition.ThumbsDown);
if (definitions.Tags?.Length > 0) eb.AddField("Tags", string.Join(", ", definitions.Tags));
await ReplyAsync("", false, eb.Build());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,39 @@
using System;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.Core;
using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Integrations
{
public class UrbanDictionary : TransactionModuleBase
{
private readonly IErrorHandler _errorHandler;
public UrbanDictionary(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
}
[Command("urban", RunMode = RunMode.Async)]
[Summary("Lookup something on urban dictionary")]
public async Task UrbanDefine([Remainder] [Summary("word")] string word)
{
try
{
var eb = await Geekbot.Commands.UrbanDictionary.UrbanDictionary.Run(word);
if (eb == null)
{
await ReplyAsync("That word hasn't been defined...");
return;
}
await ReplyAsync(string.Empty, false, eb.ToDiscordNetEmbed().Build());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -5,6 +5,7 @@ using System.Text;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core;
using Geekbot.Core.Database;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
@ -14,7 +15,7 @@ using HtmlAgilityPack;
namespace Geekbot.Bot.Commands.Integrations
{
public class Wikipedia : ModuleBase
public class Wikipedia : TransactionModuleBase
{
private readonly IErrorHandler _errorHandler;
private readonly IWikipediaClient _wikipediaClient;

View file

@ -1,58 +1,59 @@
using System;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.GlobalSettings;
using Google.Apis.Services;
using Google.Apis.YouTube.v3;
using Discord.Commands;
using Geekbot.Core;
// using Geekbot.Core.ErrorHandling;
// using Geekbot.Core.GlobalSettings;
// using Google.Apis.Services;
// using Google.Apis.YouTube.v3;
namespace Geekbot.Bot.Commands.Integrations
{
public class Youtube : ModuleBase
public class Youtube : TransactionModuleBase
{
private readonly IGlobalSettings _globalSettings;
private readonly IErrorHandler _errorHandler;
// private readonly IGlobalSettings _globalSettings;
// private readonly IErrorHandler _errorHandler;
public Youtube(IGlobalSettings globalSettings, IErrorHandler errorHandler)
{
_globalSettings = globalSettings;
_errorHandler = errorHandler;
}
// public Youtube(IGlobalSettings globalSettings, IErrorHandler errorHandler)
// {
// _globalSettings = globalSettings;
// _errorHandler = errorHandler;
// }
[Command("yt", RunMode = RunMode.Async)]
[Summary("Search for something on youtube.")]
public async Task Yt([Remainder] [Summary("title")] string searchQuery)
{
var key = _globalSettings.GetKey("YoutubeKey");
if (string.IsNullOrEmpty(key))
{
await ReplyAsync("No youtube key set, please tell my senpai to set one");
return;
}
await ReplyAsync("The youtube command is temporarily disabled");
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);
}
// 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

@ -2,12 +2,13 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.Core;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.RandomNumberGenerator;
namespace Geekbot.Bot.Commands.Randomness
{
public class BenedictCumberbatchNameGenerator : ModuleBase
public class BenedictCumberbatchNameGenerator : TransactionModuleBase
{
private readonly IErrorHandler _errorHandler;
private readonly IRandomNumberGenerator _randomNumberGenerator;

View file

@ -7,7 +7,7 @@ using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Randomness.Cat
{
public class Cat : ModuleBase
public class Cat : TransactionModuleBase
{
private readonly IErrorHandler _errorHandler;

View file

@ -1,7 +1,10 @@
namespace Geekbot.Bot.Commands.Randomness.Cat
using System.Text.Json.Serialization;
namespace Geekbot.Bot.Commands.Randomness.Cat
{
internal class CatResponseDto
{
[JsonPropertyName("file")]
public string File { get; set; }
}
}

View file

@ -1,7 +1,10 @@
namespace Geekbot.Bot.Commands.Randomness.Chuck
using System.Text.Json.Serialization;
namespace Geekbot.Bot.Commands.Randomness.Chuck
{
internal class ChuckNorrisJokeResponseDto
{
[JsonPropertyName("value")]
public string Value { get; set; }
}
}

View file

@ -7,7 +7,7 @@ using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Randomness.Chuck
{
public class ChuckNorrisJokes : ModuleBase
public class ChuckNorrisJokes : TransactionModuleBase
{
private readonly IErrorHandler _errorHandler;

View file

@ -1,7 +1,10 @@
namespace Geekbot.Bot.Commands.Randomness.Dad
using System.Text.Json.Serialization;
namespace Geekbot.Bot.Commands.Randomness.Dad
{
internal class DadJokeResponseDto
{
[JsonPropertyName("joke")]
public string Joke { get; set; }
}
}

View file

@ -6,7 +6,7 @@ using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Randomness.Dad
{
public class DadJokes : ModuleBase
public class DadJokes : TransactionModuleBase
{
private readonly IErrorHandler _errorHandler;

View file

@ -7,7 +7,7 @@ using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Randomness.Dog
{
public class Dog : ModuleBase
public class Dog : TransactionModuleBase
{
private readonly IErrorHandler _errorHandler;

View file

@ -1,7 +1,10 @@
namespace Geekbot.Bot.Commands.Randomness.Dog
using System.Text.Json.Serialization;
namespace Geekbot.Bot.Commands.Randomness.Dog
{
internal class DogResponseDto
{
[JsonPropertyName("url")]
public string Url { get; set; }
}
}

View file

@ -1,18 +1,19 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.Core;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.GuildSettingsManager;
using Localization = Geekbot.Core.Localization;
namespace Geekbot.Bot.Commands.Randomness
{
public class EightBall : ModuleBase
public class EightBall : GeekbotCommandBase
{
private readonly IErrorHandler _errorHandler;
public EightBall(IErrorHandler errorHandler)
public EightBall(IErrorHandler errorHandler, IGuildSettingsManager guildSettingsManager) : base(errorHandler, guildSettingsManager)
{
_errorHandler = errorHandler;
}
[Command("8ball", RunMode = RunMode.Async)]
@ -21,36 +22,19 @@ namespace Geekbot.Bot.Commands.Randomness
{
try
{
var replies = new List<string>
var enumerator = Localization.EightBall.ResourceManager.GetResourceSet(CultureInfo.CurrentUICulture, true, true).GetEnumerator();
var replies = new List<string>();
while (enumerator.MoveNext())
{
"It is certain",
"It is decidedly so",
"Without a doubt",
"Yes, definitely",
"You may rely on it",
"As I see it, yes",
"Most likely",
"Outlook good",
"Yes",
"Signs point to yes",
"Reply hazy try again",
"Ask again later",
"Better not tell you now",
"Cannot predict now",
"Concentrate and ask again",
"Don't count on it",
"My reply is no",
"My sources say no",
"Outlook not so good",
"Very doubtful"
};
replies.Add(enumerator.Value?.ToString());
}
var answer = new Random().Next(replies.Count);
await ReplyAsync(replies[answer]);
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
await ErrorHandler.HandleCommandException(e, Context);
}
}
}

View file

@ -1,10 +1,11 @@
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.Core;
using Geekbot.Core.Media;
namespace Geekbot.Bot.Commands.Randomness
{
public class Fortune : ModuleBase
public class Fortune : TransactionModuleBase
{
private readonly IFortunesProvider _fortunes;

View file

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

View file

@ -1,11 +0,0 @@
namespace Geekbot.Bot.Commands.Randomness.Greetings
{
public class GreetingBaseDto
{
public string Language { get; set; }
public string LanguageNative { get; set; }
public string LanguageCode { get; set; }
public string Script { get; set; }
public GreetingDto Primary { get; set; }
}
}

View file

@ -1,10 +0,0 @@
namespace Geekbot.Bot.Commands.Randomness.Greetings
{
public class GreetingDto
{
public string Text { get; set; }
public string Dialect { get; set; }
public string Romanization { get; set; }
public string[] Use { get; set; }
}
}

View file

@ -1,51 +0,0 @@
using System;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
namespace Geekbot.Bot.Commands.Randomness.Greetings
{
public class Greetings : ModuleBase
{
private readonly IErrorHandler _errorHandler;
public Greetings(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
}
[Command("hello", RunMode = RunMode.Async)]
[Alias("greeting", "hi", "hallo")]
[Summary("Say hello to the bot and get a reply in a random language")]
public async Task GetGreeting()
{
try
{
var greeting = await HttpAbstractions.Get<GreetingBaseDto>(new Uri("https://api.greetings.dev/v1/greeting"));
var eb = new EmbedBuilder();
eb.Title = greeting.Primary.Text;
eb.AddInlineField("Language", greeting.Language);
if (greeting.Primary.Dialect != null)
{
eb.AddInlineField("Dialect", greeting.Primary.Dialect);
}
if (greeting.Primary.Romanization != null)
{
eb.AddInlineField("Roman", greeting.Primary.Romanization);
}
await ReplyAsync(string.Empty, false, eb.Build());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -6,7 +6,7 @@ using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Randomness.Kanye
{
public class Kanye : ModuleBase
public class Kanye : TransactionModuleBase
{
private readonly IErrorHandler _errorHandler;

View file

@ -1,8 +1,10 @@
using System.Text.Json.Serialization;
namespace Geekbot.Bot.Commands.Randomness.Kanye
{
public class KanyeResponseDto
{
public string Id { get; set; }
[JsonPropertyName("quote")]
public string Quote { get; set; }
}
}

View file

@ -1,11 +1,12 @@
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core;
using Geekbot.Core.Media;
namespace Geekbot.Bot.Commands.Randomness
{
public class RandomAnimals : ModuleBase
public class RandomAnimals : TransactionModuleBase
{
private readonly IMediaProvider _mediaProvider;

View file

@ -10,6 +10,7 @@ using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
using Geekbot.Core.GuildSettingsManager;
using Geekbot.Core.RandomNumberGenerator;
using Localization = Geekbot.Core.Localization;
namespace Geekbot.Bot.Commands.Randomness
{

View file

@ -4,6 +4,7 @@ using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core;
using Geekbot.Core.Database;
using Geekbot.Core.Database.Models;
using Geekbot.Core.ErrorHandling;
@ -11,7 +12,7 @@ using Geekbot.Core.Extensions;
namespace Geekbot.Bot.Commands.Randomness
{
public class Slap : ModuleBase
public class Slap : TransactionModuleBase
{
private readonly IErrorHandler _errorHandler;
private readonly DatabaseContext _database;
@ -76,10 +77,14 @@ namespace Geekbot.Bot.Commands.Randomness
"teapot",
"candle",
"dictionary",
"powerless banhammer"
"powerless banhammer",
"piece of low fat mozzarella",
// For some reason it never picks the last one
// Adding this workaround, because i'm to lazy to actually fix it at the time of writing this
"padding"
};
await ReplyAsync($"{Context.User.Username} slapped {user.Username} with a {things[new Random().Next(things.Count - 1)]}");
await ReplyAsync($"{Context.User.Username} slapped {user.Username} with a {things[new Random().Next(0, things.Count - 1)]}");
await UpdateRecieved(user.Id);
await UpdateGiven(Context.User.Id);

View file

@ -3,15 +3,15 @@ using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Bot.Utils;
using Geekbot.Bot.CommandPreconditions;
using Geekbot.Core;
using Geekbot.Core.CommandPreconditions;
using Geekbot.Core.Database;
using Geekbot.Core.Database.Models;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
using Geekbot.Core.GuildSettingsManager;
using Geekbot.Core.RandomNumberGenerator;
using Localization = Geekbot.Core.Localization;
namespace Geekbot.Bot.Commands.Rpg
{
@ -37,14 +37,16 @@ namespace Geekbot.Bot.Commands.Rpg
try
{
var actor = await GetUser(Context.User.Id);
if (actor.LastPayout.Value.AddDays(1).Date > DateTime.Now.Date)
var timeoutDays = 1;
if (actor.LastPayout?.AddDays(timeoutDays) > DateTime.Now.ToUniversalTime())
{
var formattedWaitTime = DateLocalization.FormatDateTimeAsRemaining(DateTimeOffset.Now.AddDays(1).Date);
var remaining = actor.LastPayout.Value.AddDays(timeoutDays) - DateTimeOffset.Now.ToUniversalTime();
var formattedWaitTime = DateLocalization.FormatDateTimeAsRemaining(remaining);
await ReplyAsync(string.Format(Localization.Cookies.WaitForMoreCookies, formattedWaitTime));
return;
}
actor.Cookies += 10;
actor.LastPayout = DateTimeOffset.Now;
actor.LastPayout = DateTimeOffset.Now.ToUniversalTime();
await SetUser(actor);
await ReplyAsync(string.Format(Localization.Cookies.GetCookies, 10, actor.Cookies));
@ -78,6 +80,12 @@ namespace Geekbot.Bot.Commands.Rpg
{
var giver = await GetUser(Context.User.Id);
if (amount < 1)
{
await ReplyAsync(Localization.Cookies.CantTakeCookies);
return;
}
if (giver.Cookies < amount)
{
await ReplyAsync(Localization.Cookies.NotEnoughToGive);
@ -146,7 +154,7 @@ namespace Geekbot.Bot.Commands.Rpg
GuildId = Context.Guild.Id.AsLong(),
UserId = userId.AsLong(),
Cookies = 0,
LastPayout = DateTimeOffset.MinValue
LastPayout = DateTimeOffset.MinValue.ToUniversalTime()
};
var newUser = _database.Cookies.Add(user).Entity;
await _database.SaveChangesAsync();

View file

@ -3,7 +3,8 @@ using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core.CommandPreconditions;
using Geekbot.Bot.CommandPreconditions;
using Geekbot.Core;
using Geekbot.Core.Database;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
@ -11,7 +12,7 @@ using Geekbot.Core.Levels;
namespace Geekbot.Bot.Commands.User
{
public class GuildInfo : ModuleBase
public class GuildInfo : TransactionModuleBase
{
private readonly IErrorHandler _errorHandler;
private readonly DatabaseContext _database;

View file

@ -1,13 +1,11 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Bot.Utils;
using Geekbot.Bot.CommandPreconditions;
using Geekbot.Commands.Karma;
using Geekbot.Core;
using Geekbot.Core.CommandPreconditions;
using Geekbot.Core.Database;
using Geekbot.Core.Database.Models;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
using Geekbot.Core.GuildSettingsManager;
@ -28,122 +26,51 @@ namespace Geekbot.Bot.Commands.User
[Summary("Increase Someones Karma")]
public async Task Good([Summary("@someone")] IUser user)
{
try
{
var actor = await GetUser(Context.User.Id);
if (user.Id == Context.User.Id)
{
await ReplyAsync(string.Format(Localization.Karma.CannotChangeOwnUp, Context.User.Username));
}
else if (TimeoutFinished(actor.TimeOut))
{
var formatedWaitTime = DateLocalization.FormatDateTimeAsRemaining(actor.TimeOut.AddMinutes(3));
await ReplyAsync(string.Format(Localization.Karma.WaitUntill, Context.User.Username, formatedWaitTime));
}
else
{
var target = await GetUser(user.Id);
target.Karma += 1;
SetUser(target);
actor.TimeOut = DateTimeOffset.Now;
SetUser(actor);
await _database.SaveChangesAsync();
var eb = new EmbedBuilder();
eb.WithAuthor(new EmbedAuthorBuilder()
.WithIconUrl(user.GetAvatarUrl())
.WithName(user.Username));
eb.WithColor(new Color(138, 219, 146));
eb.Title = Localization.Karma.Increased;
eb.AddInlineField(Localization.Karma.By, Context.User.Username);
eb.AddInlineField(Localization.Karma.Amount, "+1");
eb.AddInlineField(Localization.Karma.Current, target.Karma);
await ReplyAsync("", false, eb.Build());
}
}
catch (Exception e)
{
await ErrorHandler.HandleCommandException(e, Context);
}
await ChangeKarma(user, KarmaChange.Up);
}
[Command("bad", RunMode = RunMode.Async)]
[Summary("Decrease Someones Karma")]
public async Task Bad([Summary("@someone")] IUser user)
{
await ChangeKarma(user, KarmaChange.Down);
}
[Command("neutral", RunMode = RunMode.Async)]
[Summary("Do nothing to someones Karma")]
public async Task Neutral([Summary("@someone")] IUser user)
{
await ChangeKarma(user, KarmaChange.Same);
}
private async Task ChangeKarma(IUser user, KarmaChange change)
{
try
{
var actor = await GetUser(Context.User.Id);
if (user.Id == Context.User.Id)
var author = new Interactions.Resolved.User()
{
await ReplyAsync(string.Format(Localization.Karma.CannotChangeOwnDown, Context.User.Username));
}
else if (TimeoutFinished(actor.TimeOut))
Id = Context.User.Id.ToString(),
Username = Context.User.Username,
Discriminator = Context.User.Discriminator,
Avatar = Context.User.AvatarId,
};
var targetUser = new Interactions.Resolved.User()
{
var formatedWaitTime = DateLocalization.FormatDateTimeAsRemaining(actor.TimeOut.AddMinutes(3));
await ReplyAsync(string.Format(Localization.Karma.WaitUntill, Context.User.Username, formatedWaitTime));
}
else
{
var target = await GetUser(user.Id);
target.Karma -= 1;
SetUser(target);
Id = user.Id.ToString(),
Username = user.Username,
Discriminator = user.Discriminator,
Avatar = user.AvatarId,
};
actor.TimeOut = DateTimeOffset.Now;
SetUser(actor);
var karma = new Geekbot.Commands.Karma.Karma(_database, Context.Guild.Id.AsLong());
var res = await karma.ChangeKarma(author, targetUser, change);
await _database.SaveChangesAsync();
var eb = new EmbedBuilder();
eb.WithAuthor(new EmbedAuthorBuilder()
.WithIconUrl(user.GetAvatarUrl())
.WithName(user.Username));
eb.WithColor(new Color(138, 219, 146));
eb.Title = Localization.Karma.Decreased;
eb.AddInlineField(Localization.Karma.By, Context.User.Username);
eb.AddInlineField(Localization.Karma.Amount, "-1");
eb.AddInlineField(Localization.Karma.Current, target.Karma);
await ReplyAsync("", false, eb.Build());
}
await ReplyAsync(string.Empty, false, res.ToDiscordNetEmbed().Build());
}
catch (Exception e)
{
await ErrorHandler.HandleCommandException(e, Context);
}
}
private bool TimeoutFinished(DateTimeOffset lastKarma)
{
return lastKarma.AddMinutes(3) > DateTimeOffset.Now;
}
private async Task<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

@ -0,0 +1,43 @@
using Discord.Commands;
using Geekbot.Bot.CommandPreconditions;
using Geekbot.Core;
using Geekbot.Core.Database;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.GuildSettingsManager;
using Geekbot.Core.Highscores;
namespace Geekbot.Bot.Commands.User
{
public class Rank : GeekbotCommandBase
{
private readonly IHighscoreManager _highscoreManager;
private readonly DatabaseContext _database;
public Rank(DatabaseContext database, IErrorHandler errorHandler, IHighscoreManager highscoreManager, IGuildSettingsManager guildSettingsManager)
: base(errorHandler, guildSettingsManager)
{
_database = database;
_highscoreManager = highscoreManager;
}
[Command("rank", RunMode = RunMode.Async)]
[Summary("Get the highscore for various stats like message count, karma, correctly guessed roles, etc...")]
[DisableInDirectMessage]
public async Task RankCmd(
[Summary("type")] string typeUnformated = "messages",
[Summary("amount")] int amount = 10,
[Summary("season")] string season = null)
{
try
{
var res = new Geekbot.Commands.Rank(_database, _highscoreManager)
.Run(typeUnformated, amount, season, Context.Guild.Id, Context.Guild.Name);
await ReplyAsync(res);
}
catch (Exception e)
{
await ErrorHandler.HandleCommandException(e, Context);
}
}
}
}

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.Core;
using Geekbot.Core.CommandPreconditions;
using Geekbot.Core.Converters;
using Geekbot.Core.Database;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
using Geekbot.Core.GuildSettingsManager;
using Geekbot.Core.Highscores;
namespace Geekbot.Bot.Commands.User.Ranking
{
public class Rank : GeekbotCommandBase
{
private readonly IEmojiConverter _emojiConverter;
private readonly IHighscoreManager _highscoreManager;
private readonly DatabaseContext _database;
public Rank(DatabaseContext database, IErrorHandler errorHandler, IEmojiConverter emojiConverter, IHighscoreManager highscoreManager, IGuildSettingsManager guildSettingsManager)
: base(errorHandler, guildSettingsManager)
{
_database = database;
_emojiConverter = emojiConverter;
_highscoreManager = highscoreManager;
}
[Command("rank", RunMode = RunMode.Async)]
[Summary("get user top 10 in messages or karma")]
[DisableInDirectMessage]
public async Task RankCmd([Summary("type")] string typeUnformated = "messages", [Summary("amount")] int amount = 10)
{
try
{
HighscoreTypes type;
try
{
type = Enum.Parse<HighscoreTypes>(typeUnformated, true);
if (!Enum.IsDefined(typeof(HighscoreTypes), type)) throw new Exception();
}
catch
{
await ReplyAsync(Localization.Rank.InvalidType);
return;
}
var replyBuilder = new StringBuilder();
if (amount > 20)
{
await ReplyAsync(Localization.Rank.LimitingTo20Warning);
amount = 20;
}
var guildId = Context.Guild.Id;
Dictionary<HighscoreUserDto, int> highscoreUsers;
try
{
highscoreUsers = _highscoreManager.GetHighscoresWithUserData(type, guildId, amount);
}
catch (HighscoreListEmptyException)
{
await ReplyAsync(string.Format(Localization.Rank.NoTypeFoundForServer, type));
return;
}
var guildMessages = 0;
if (type == HighscoreTypes.messages)
{
guildMessages = _database.Messages
.Where(e => e.GuildId.Equals(Context.Guild.Id.AsLong()))
.Select(e => e.MessageCount)
.Sum();
}
var failedToRetrieveUser = highscoreUsers.Any(e => string.IsNullOrEmpty(e.Key.Username));
if (failedToRetrieveUser) replyBuilder.AppendLine(Localization.Rank.FailedToResolveAllUsernames).AppendLine();
replyBuilder.AppendLine(string.Format(Localization.Rank.HighscoresFor, type.ToString().CapitalizeFirst(), Context.Guild.Name));
var highscorePlace = 1;
foreach (var (user, value) in highscoreUsers)
{
replyBuilder.Append(highscorePlace < 11
? $"{_emojiConverter.NumberToEmoji(highscorePlace)} "
: $"`{highscorePlace}.` ");
replyBuilder.Append(user.Username != null
? $"**{user.Username}#{user.Discriminator}**"
: $"**{user.Id}**");
replyBuilder.Append(type == HighscoreTypes.messages
? $" - {value} {type} - {Math.Round((double) (100 * value) / guildMessages, 2)}%\n"
: $" - {value} {type}\n");
highscorePlace++;
}
await ReplyAsync(replyBuilder.ToString());
}
catch (Exception e)
{
await ErrorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -3,13 +3,14 @@ using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Bot.CommandPreconditions;
using Geekbot.Core;
using Geekbot.Core.CommandPreconditions;
using Geekbot.Core.Database;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
using Geekbot.Core.GuildSettingsManager;
using Geekbot.Core.Levels;
using Localization = Geekbot.Core.Localization;
namespace Geekbot.Bot.Commands.User
{
@ -54,6 +55,8 @@ namespace Geekbot.Bot.Commands.User
?.FirstOrDefault(e => e.GuildId.Equals(Context.Guild.Id.AsLong()) && e.UserId.Equals(userInfo.Id.AsLong()))
?.Cookies ?? 0;
var quotes = _database.Quotes.Count(e => e.GuildId.Equals(Context.Guild.Id.AsLong()) && e.UserId.Equals(userInfo.Id.AsLong()));
var eb = new EmbedBuilder();
eb.WithAuthor(new EmbedAuthorBuilder()
.WithIconUrl(userInfo.GetAvatarUrl())
@ -68,9 +71,9 @@ namespace Geekbot.Bot.Commands.User
e.UserId.Equals(userInfo.Id.AsLong()));
eb.AddInlineField(Localization.Stats.OnDiscordSince,
$"{createdAt.Day}.{createdAt.Month}.{createdAt.Year} ({age} days)")
$"{createdAt.Day}.{createdAt.Month}.{createdAt.Year} ({age} {Localization.Stats.Days})")
.AddInlineField(Localization.Stats.JoinedServer,
$"{joinedAt.Day}.{joinedAt.Month}.{joinedAt.Year} ({joinedDayAgo} days)")
$"{joinedAt.Day}.{joinedAt.Month}.{joinedAt.Year} ({joinedDayAgo} {Localization.Stats.Days})")
.AddInlineField(Localization.Stats.Karma, karma?.Karma ?? 0)
.AddInlineField(Localization.Stats.Level, level)
.AddInlineField(Localization.Stats.MessagesSent, messages)
@ -78,6 +81,7 @@ namespace Geekbot.Bot.Commands.User
if (correctRolls != null) eb.AddInlineField(Localization.Stats.GuessedRolls, correctRolls.Rolls);
if (cookies > 0) eb.AddInlineField(Localization.Stats.Cookies, cookies);
if (quotes > 0) eb.AddInlineField(Localization.Stats.Quotes, quotes);
await ReplyAsync("", false, eb.Build());
}

View file

@ -2,11 +2,12 @@
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core;
using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Utils
{
public class AvatarGetter : ModuleBase
public class AvatarGetter : TransactionModuleBase
{
private readonly IErrorHandler _errorHandler;
@ -21,8 +22,8 @@ namespace Geekbot.Bot.Commands.Utils
{
try
{
if (user == null) user = Context.User;
var url = user.GetAvatarUrl().Replace("128", "1024");
user ??= Context.User;
var url = user.GetAvatarUrl(ImageFormat.Auto, 1024);
await ReplyAsync(url);
}
catch (Exception e)

View file

@ -11,7 +11,7 @@ using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Utils.Changelog
{
public class Changelog : ModuleBase
public class Changelog : TransactionModuleBase
{
private readonly DiscordSocketClient _client;
private readonly IErrorHandler _errorHandler;

View file

@ -1,11 +1,17 @@
using System;
using System.Text.Json.Serialization;
namespace Geekbot.Bot.Commands.Utils.Changelog
{
public class CommitAuthorDto
{
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("email")]
public string Email { get; set; }
[JsonPropertyName("date")]
public DateTimeOffset Date { get; set; }
}
}

View file

@ -1,7 +1,10 @@
namespace Geekbot.Bot.Commands.Utils.Changelog
using System.Text.Json.Serialization;
namespace Geekbot.Bot.Commands.Utils.Changelog
{
public class CommitDto
{
[JsonPropertyName("commit")]
public CommitInfoDto Commit { get; set; }
}
}

View file

@ -1,8 +1,13 @@
namespace Geekbot.Bot.Commands.Utils.Changelog
using System.Text.Json.Serialization;
namespace Geekbot.Bot.Commands.Utils.Changelog
{
public class CommitInfoDto
{
[JsonPropertyName("author")]
public CommitAuthorDto Author { get; set; }
[JsonPropertyName("message")]
public string Message { get; set; }
}
}

View file

@ -1,9 +1,8 @@
using System;
using System.Threading.Tasks;
using Discord.Commands;
using Discord.Commands;
using Geekbot.Core;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.GuildSettingsManager;
using Localization = Geekbot.Core.Localization;
namespace Geekbot.Bot.Commands.Utils
{
@ -14,7 +13,7 @@ namespace Geekbot.Bot.Commands.Utils
}
[Command("choose", RunMode = RunMode.Async)]
[Summary("Let the bot choose for you, seperate options with a semicolon.")]
[Summary("Let the bot choose for you, separate options with a semicolon.")]
public async Task Command([Remainder] [Summary("option1;option2")]
string choices)
{

View file

@ -1,66 +0,0 @@
using System;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
namespace Geekbot.Bot.Commands.Utils.Corona
{
public class CoronaStats : ModuleBase
{
private readonly IErrorHandler _errorHandler;
public CoronaStats(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
}
[Command("corona", RunMode = RunMode.Async)]
[Summary("Get the latest worldwide corona statistics")]
public async Task Summary()
{
try
{
var summary = await HttpAbstractions.Get<CoronaSummaryDto>(new Uri("https://api.covid19api.com/world/total"));
var activeCases = summary.TotalConfirmed - (summary.TotalRecovered + summary.TotalDeaths);
string CalculatePercentage(decimal i) => (i / summary.TotalConfirmed).ToString("#0.##%");
var activePercent = CalculatePercentage(activeCases);
var recoveredPercentage = CalculatePercentage(summary.TotalRecovered);
var deathsPercentage = CalculatePercentage(summary.TotalDeaths);
var numberFormat = "#,#";
var totalFormatted = summary.TotalConfirmed.ToString(numberFormat);
var activeFormatted = activeCases.ToString(numberFormat);
var recoveredFormatted = summary.TotalRecovered.ToString(numberFormat);
var deathsFormatted = summary.TotalDeaths.ToString(numberFormat);
var eb = new EmbedBuilder
{
Author = new EmbedAuthorBuilder
{
Name = "Confirmed Corona Cases",
IconUrl = "https://www.redcross.org/content/dam/icons/disasters/virus/Virus-1000x1000-R-Pl.png"
},
Footer = new EmbedFooterBuilder
{
Text = "Source: covid19api.com",
},
Color = Color.Red
};
eb.AddField("Total", totalFormatted);
eb.AddInlineField("Active", $"{activeFormatted} ({activePercent})");
eb.AddInlineField("Recovered", $"{recoveredFormatted} ({recoveredPercentage})");
eb.AddInlineField("Deaths", $"{deathsFormatted} ({deathsPercentage})");
await Context.Channel.SendMessageAsync(String.Empty, false, eb.Build());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -1,9 +0,0 @@
namespace Geekbot.Bot.Commands.Utils.Corona
{
public class CoronaSummaryDto
{
public decimal TotalConfirmed { get; set; }
public decimal TotalDeaths { get; set; }
public decimal TotalRecovered { get; set; }
}
}

View file

@ -3,12 +3,13 @@ using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.Core;
using Geekbot.Core.DiceParser;
using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Utils
{
public class Dice : ModuleBase
public class Dice : TransactionModuleBase
{
private readonly IErrorHandler _errorHandler;
private readonly IDiceParser _diceParser;

View file

@ -1,20 +1,17 @@
using System;
using System.Threading.Tasks;
using Discord.Commands;
using Discord.Commands;
using Geekbot.Core;
using Geekbot.Core.Converters;
using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Utils
{
public class Emojify : ModuleBase
public class Emojify : TransactionModuleBase
{
private readonly IEmojiConverter _emojiConverter;
private readonly IErrorHandler _errorHandler;
public Emojify(IErrorHandler errorHandler, IEmojiConverter emojiConverter)
public Emojify(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
_emojiConverter = emojiConverter;
}
[Command("emojify", RunMode = RunMode.Async)]
@ -23,7 +20,7 @@ namespace Geekbot.Bot.Commands.Utils
{
try
{
var emojis = _emojiConverter.TextToEmoji(text);
var emojis = EmojiConverter.TextToEmoji(text);
if (emojis.Length > 1999)
{
await ReplyAsync("I can't take that much at once!");

View file

@ -3,11 +3,12 @@ using System.Text;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core;
using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Utils
{
public class Help : ModuleBase
public class Help : TransactionModuleBase
{
private readonly IErrorHandler _errorHandler;
@ -26,7 +27,7 @@ namespace Geekbot.Bot.Commands.Utils
sb.AppendLine("For a list of all commands, please visit the following page");
sb.AppendLine("https://geekbot.pizzaandcoffee.rocks/commands");
var dm = await Context.User.GetOrCreateDMChannelAsync();
var dm = await Context.User.CreateDMChannelAsync(RequestOptions.Default);
await dm.SendMessageAsync(sb.ToString());
await Context.Message.AddReactionAsync(new Emoji("✅"));
}

View file

@ -11,7 +11,7 @@ using Geekbot.Core.Extensions;
namespace Geekbot.Bot.Commands.Utils
{
public class Info : ModuleBase
public class Info : TransactionModuleBase
{
private readonly DiscordSocketClient _client;
private readonly CommandService _commands;

View file

@ -2,11 +2,12 @@ using System;
using System.Threading.Tasks;
using System.Web;
using Discord.Commands;
using Geekbot.Core;
using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Utils
{
public class Lmgtfy : ModuleBase
public class Lmgtfy : TransactionModuleBase
{
private readonly IErrorHandler _errorHandler;

View file

@ -1,9 +1,10 @@
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.Core;
namespace Geekbot.Bot.Commands.Utils
{
public class Ping : ModuleBase
public class Ping : TransactionModuleBase
{
[Command("👀", RunMode = RunMode.Async)]
[Summary("Look at the bot.")]

View file

@ -4,8 +4,8 @@ using System.Text;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Bot.CommandPreconditions;
using Geekbot.Core;
using Geekbot.Core.CommandPreconditions;
using Geekbot.Core.Database;
using Geekbot.Core.Database.Models;
using Geekbot.Core.ErrorHandling;
@ -13,6 +13,11 @@ using Geekbot.Core.Extensions;
using Geekbot.Core.GuildSettingsManager;
using Geekbot.Core.Polyfills;
using Geekbot.Core.RandomNumberGenerator;
using Geekbot.Core.UserRepository;
using Microsoft.EntityFrameworkCore;
using Sentry;
using Constants = Geekbot.Core.Constants;
using Localization = Geekbot.Core.Localization;
namespace Geekbot.Bot.Commands.Utils.Quote
{
@ -22,13 +27,15 @@ namespace Geekbot.Bot.Commands.Utils.Quote
{
private readonly DatabaseContext _database;
private readonly IRandomNumberGenerator _randomNumberGenerator;
private readonly IUserRepository _userRepository;
private readonly bool _isDev;
public Quote(IErrorHandler errorHandler, DatabaseContext database, IRandomNumberGenerator randomNumberGenerator, IGuildSettingsManager guildSettingsManager)
public Quote(IErrorHandler errorHandler, DatabaseContext database, IRandomNumberGenerator randomNumberGenerator, IGuildSettingsManager guildSettingsManager, IUserRepository userRepository)
: base(errorHandler, guildSettingsManager)
{
_database = database;
_randomNumberGenerator = randomNumberGenerator;
_userRepository = userRepository;
// to remove restrictions when developing
_isDev = Constants.BotVersion() == "0.0.0-DEV";
}
@ -39,23 +46,26 @@ namespace Geekbot.Bot.Commands.Utils.Quote
{
try
{
var totalQuotes = await _database.Quotes.CountAsync(e => e.GuildId.Equals(Context.Guild.Id.AsLong()));
var getQuoteFromDbSpan = Transaction.StartChild("GetQuoteFromDB");
var quote = _database.Quotes.FromSqlInterpolated($"select * from \"Quotes\" where \"GuildId\" = {Context.Guild.Id} order by random() limit 1");
getQuoteFromDbSpan.Finish();
if (totalQuotes == 0)
if (!quote.Any())
{
await ReplyAsync(Localization.Quote.NoQuotesFound);
Transaction.Status = SpanStatus.NotFound;
return;
}
var random = _randomNumberGenerator.Next(0, totalQuotes - 1);
var quote = _database.Quotes.Where(e => e.GuildId.Equals(Context.Guild.Id.AsLong())).Skip(random).Take(1);
var buildQuoteEmbedSpan = Transaction.StartChild("BuildQuoteEmbed");
var embed = QuoteBuilder(quote.FirstOrDefault());
buildQuoteEmbedSpan.Finish();
await ReplyAsync("", false, embed.Build());
}
catch (Exception e)
{
await ErrorHandler.HandleCommandException(e, Context, "Whoops, seems like the quote was to edgy to return");
Transaction.Status = SpanStatus.InternalError;
}
}
@ -75,22 +85,6 @@ namespace Geekbot.Bot.Commands.Utils.Quote
await QuoteFromMention(user, false);
}
[Command("add")]
[Alias("save")]
[Summary("Add a quote from a message id")]
public async Task AddQuote([Summary("message-ID")] ulong messageId)
{
await QuoteFromMessageId(messageId, true);
}
[Command("make")]
[Alias("preview")]
[Summary("Preview a quote from a message id")]
public async Task ReturnSpecifiedQuote([Summary("message-ID")] ulong messageId)
{
await QuoteFromMessageId(messageId, false);
}
[Command("add")]
[Alias("save")]
[Summary("Add a quote from a message link")]
@ -160,8 +154,8 @@ namespace Geekbot.Bot.Commands.Utils.Quote
.Where(row => row.GuildId == Context.Guild.Id.AsLong())
.GroupBy(row => row.UserId)
.Select(row => new { userId = row.Key, amount = row.Count()})
.OrderBy(row => row.amount)
.Last();
.OrderByDescending(row => row.amount)
.First();
var mostQuotedPersonUser = Context.Client.GetUserAsync(mostQuotedPerson.userId.AsUlong()).Result ?? new UserPolyfillDto {Username = "Unknown User"};
var quotesByYear = _database.Quotes
@ -208,21 +202,7 @@ namespace Geekbot.Bot.Commands.Utils.Quote
}
private async Task QuoteFromMessageId(ulong messageId, bool saveToDb)
{
try
{
var message = await Context.Channel.GetMessageAsync(messageId);
await ProcessQuote(message, saveToDb, true);
}
catch (Exception e)
{
await ErrorHandler.HandleCommandException(e, Context, "I couldn't find a message with that id :disappointed:");
}
}
private async Task QuoteFromMessageLink(string messageLink, bool saveToDb)
private async Task QuoteFromMessageLink(string messageLink, bool saveToDb)
{
try
{
@ -253,7 +233,7 @@ namespace Geekbot.Bot.Commands.Utils.Quote
}
}
private async Task ProcessQuote(IMessage message, bool saveToDb, bool showMessageIdWarning = false)
private async Task ProcessQuote(IMessage message, bool saveToDb)
{
if (message.Author.Id == Context.Message.Author.Id && saveToDb && !_isDev)
{
@ -278,27 +258,38 @@ namespace Geekbot.Bot.Commands.Utils.Quote
var sb = new StringBuilder();
if (saveToDb) sb.AppendLine(Localization.Quote.QuoteAdded);
if (showMessageIdWarning) sb.AppendLine(Localization.Quote.MessageIdDeprecation);
await ReplyAsync(sb.ToString(), false, embed.Build());
}
private EmbedBuilder QuoteBuilder(QuoteModel quote)
{
var user = Context.Client.GetUserAsync(quote.UserId.AsUlong()).Result ?? new UserPolyfillDto { Username = "Unknown User" };
var getEmbedUserSpan = Transaction.StartChild("GetEmbedUser");
var user = Context.Client.GetUserAsync(quote.UserId.AsUlong()).Result;
if (user == null)
{
var getEmbedUserFromRepoSpan = Transaction.StartChild("GetEmbedUserFromRepo");
var fallbackUserFromRepo = _userRepository.Get(quote.UserId.AsUlong());
user = new UserPolyfillDto()
{
Username = fallbackUserFromRepo?.Username ?? "Unknown User",
AvatarUrl = fallbackUserFromRepo?.AvatarUrl
};
getEmbedUserFromRepoSpan.Finish();
}
getEmbedUserSpan.Finish();
var embedBuilderSpan = Transaction.StartChild("EmbedBuilder");
var eb = new EmbedBuilder();
eb.WithColor(new Color(143, 167, 232));
if (quote.InternalId == 0)
{
eb.Title = $"{user.Username} @ {quote.Time.Day}.{quote.Time.Month}.{quote.Time.Year}";
}
else
{
eb.Title = $"#{quote.InternalId} | {user.Username} @ {quote.Time.Day}.{quote.Time.Month}.{quote.Time.Year}";
}
eb.Title = quote.InternalId == 0
? $"{user.Username} @ {quote.Time.Day}.{quote.Time.Month}.{quote.Time.Year}"
: $"#{quote.InternalId} | {user.Username} @ {quote.Time.Day}.{quote.Time.Month}.{quote.Time.Year}";
eb.Description = quote.Quote;
eb.ThumbnailUrl = user.GetAvatarUrl();
if (quote.Image != null) eb.ImageUrl = quote.Image;
embedBuilderSpan.Finish();
return eb;
}
@ -322,7 +313,7 @@ namespace Geekbot.Bot.Commands.Utils.Quote
InternalId = internalId,
GuildId = Context.Guild.Id.AsLong(),
UserId = message.Author.Id.AsLong(),
Time = message.Timestamp.DateTime,
Time = message.Timestamp.DateTime.ToUniversalTime(),
Quote = message.Content,
Image = image
};

View file

@ -21,12 +21,10 @@ namespace Geekbot.Bot.Handlers
private readonly RestApplication _applicationInfo;
private readonly IGuildSettingsManager _guildSettingsManager;
private readonly List<ulong> _ignoredServers;
private readonly DatabaseContext _database;
public CommandHandler(DatabaseContext database, IDiscordClient client, IGeekbotLogger logger, IServiceProvider servicesProvider, CommandService commands, RestApplication applicationInfo,
public CommandHandler(IDiscordClient client, IGeekbotLogger logger, IServiceProvider servicesProvider, CommandService commands, RestApplication applicationInfo,
IGuildSettingsManager guildSettingsManager)
{
_database = database;
_client = client;
_logger = logger;
_servicesProvider = servicesProvider;
@ -39,7 +37,7 @@ namespace Geekbot.Bot.Handlers
_ignoredServers = new List<ulong>
{
228623803201224704, // SwitzerLAN
169844523181015040, // EEvent
// 169844523181015040, // EEvent
248531441548263425, // MYI
110373943822540800 // Discord Bots
};

View file

@ -23,11 +23,11 @@ namespace Geekbot.Bot.Handlers
_client = client;
}
public async Task HandleMessageDeleted(Cacheable<IMessage, ulong> message, ISocketMessageChannel channel)
public async Task HandleMessageDeleted(Cacheable<IMessage, ulong> message, Cacheable<IMessageChannel, ulong> cacheableMessageChannel)
{
try
{
var guildSocketData = ((IGuildChannel) channel).Guild;
var guildSocketData = ((IGuildChannel) cacheableMessageChannel.Value).Guild;
var guild = _database.GuildSettings.FirstOrDefault(g => g.GuildId.Equals(guildSocketData.Id.AsLong()));
if ((guild?.ShowDelete ?? false) && guild?.ModChannel != 0)
{
@ -35,7 +35,7 @@ namespace Geekbot.Bot.Handlers
var sb = new StringBuilder();
if (message.Value != null)
{
sb.AppendLine($"The following message from {message.Value.Author.Username}#{message.Value.Author.Discriminator} was deleted in <#{channel.Id}>");
sb.AppendLine($"The following message from {message.Value.Author.Username}#{message.Value.Author.Discriminator} was deleted in <#{cacheableMessageChannel.Id}>");
sb.AppendLine(message.Value.Content);
}
else

View file

@ -14,19 +14,19 @@ namespace Geekbot.Bot.Handlers
_reactionListener = reactionListener;
}
public Task Added(Cacheable<IUserMessage, ulong> cacheable, ISocketMessageChannel socketMessageChannel, SocketReaction reaction)
public Task Added(Cacheable<IUserMessage, ulong> cacheableUserMessage, Cacheable<IMessageChannel, ulong> cacheableMessageChannel, SocketReaction reaction)
{
if (reaction.User.Value.IsBot) return Task.CompletedTask;
if (!_reactionListener.IsListener(reaction.MessageId)) return Task.CompletedTask;
_reactionListener.GiveRole(socketMessageChannel, reaction);
_reactionListener.GiveRole(cacheableMessageChannel.Value, reaction);
return Task.CompletedTask;
}
public Task Removed(Cacheable<IUserMessage, ulong> cacheable, ISocketMessageChannel socketMessageChannel, SocketReaction reaction)
public Task Removed(Cacheable<IUserMessage, ulong> cacheableUserMessage, Cacheable<IMessageChannel, ulong> cacheableMessageChannel, SocketReaction reaction)
{
if (reaction.User.Value.IsBot) return Task.CompletedTask;
if (!_reactionListener.IsListener(reaction.MessageId)) return Task.CompletedTask;
_reactionListener.RemoveRole(socketMessageChannel, reaction);
_reactionListener.RemoveRole(cacheableMessageChannel.Value, reaction);
return Task.CompletedTask;
}
}

View file

@ -4,6 +4,7 @@ using Discord.WebSocket;
using Geekbot.Core.Database;
using Geekbot.Core.Database.Models;
using Geekbot.Core.Extensions;
using Geekbot.Core.Highscores;
using Geekbot.Core.Logger;
using Microsoft.EntityFrameworkCore;
@ -13,11 +14,26 @@ namespace Geekbot.Bot.Handlers
{
private readonly IGeekbotLogger _logger;
private readonly DatabaseContext _database;
private string _season;
public StatsHandler(IGeekbotLogger logger, DatabaseContext database)
{
_logger = logger;
_database = database;
_season = SeasonsUtils.GetCurrentSeason();
var timer = new System.Timers.Timer()
{
Enabled = true,
AutoReset = true,
Interval = TimeSpan.FromMinutes(5).TotalMilliseconds
};
timer.Elapsed += (sender, args) =>
{
var current = SeasonsUtils.GetCurrentSeason();
if (current == _season) return;
_season = SeasonsUtils.GetCurrentSeason();
};
}
public async Task UpdateStats(SocketMessage message)
@ -33,23 +49,17 @@ namespace Geekbot.Bot.Handlers
var channel = (SocketGuildChannel) message.Channel;
var rowId = await _database.Database.ExecuteSqlRawAsync(
"UPDATE \"Messages\" SET \"MessageCount\" = \"MessageCount\" + 1 WHERE \"GuildId\" = {0} AND \"UserId\" = {1}",
channel.Guild.Id.AsLong(),
message.Author.Id.AsLong()
);
if (rowId == 0)
// ignore the discord bots server
// ToDo: create a clean solution for this...
if (channel.Guild.Id == 110373943822540800)
{
await _database.Messages.AddAsync(new MessagesModel
{
UserId = message.Author.Id.AsLong(),
GuildId = channel.Guild.Id.AsLong(),
MessageCount = 1
});
await _database.SaveChangesAsync();
return;
}
await UpdateTotalTable(message, channel);
await UpdateSeasonsTable(message, channel);
if (message.Author.IsBot) return;
_logger.Information(LogSource.Message, message.Content, SimpleConextConverter.ConvertSocketMessage(message));
}
@ -58,5 +68,47 @@ namespace Geekbot.Bot.Handlers
_logger.Error(LogSource.Message, "Could not process message stats", e);
}
}
private async Task UpdateTotalTable(SocketMessage message, SocketGuildChannel channel)
{
var rowId = await _database.Database.ExecuteSqlRawAsync(
"UPDATE \"Messages\" SET \"MessageCount\" = \"MessageCount\" + 1 WHERE \"GuildId\" = {0} AND \"UserId\" = {1}",
channel.Guild.Id.AsLong(),
message.Author.Id.AsLong()
);
if (rowId == 0)
{
await _database.Messages.AddAsync(new MessagesModel
{
UserId = message.Author.Id.AsLong(),
GuildId = channel.Guild.Id.AsLong(),
MessageCount = 1
});
await _database.SaveChangesAsync();
}
}
private async Task UpdateSeasonsTable(SocketMessage message, SocketGuildChannel channel)
{
var rowId = await _database.Database.ExecuteSqlRawAsync(
"UPDATE \"MessagesSeasons\" SET \"MessageCount\" = \"MessageCount\" + 1 WHERE \"GuildId\" = {0} AND \"UserId\" = {1} AND \"Season\" = {2}",
channel.Guild.Id.AsLong(),
message.Author.Id.AsLong(),
_season
);
if (rowId == 0)
{
await _database.MessagesSeasons.AddAsync(new MessageSeasonsModel()
{
UserId = message.Author.Id.AsLong(),
GuildId = channel.Guild.Id.AsLong(),
Season = _season,
MessageCount = 1
});
await _database.SaveChangesAsync();
}
}
}
}

View file

@ -74,15 +74,15 @@ namespace Geekbot.Bot.Handlers
await _userRepository.Update(newUser);
}
public async Task Left(SocketGuildUser user)
public async Task Left(SocketGuild socketGuild, SocketUser socketUser)
{
try
{
var guild = _database.GuildSettings.FirstOrDefault(g => g.GuildId.Equals(user.Guild.Id.AsLong()));
var guild = _database.GuildSettings.FirstOrDefault(g => g.GuildId.Equals(socketGuild.Id.AsLong()));
if (guild?.ShowLeave ?? false)
{
var modChannelSocket = (ISocketMessageChannel) await _client.GetChannelAsync(guild.ModChannel.AsUlong());
await modChannelSocket.SendMessageAsync($"{user.Username}#{user.Discriminator} left the server");
await modChannelSocket.SendMessageAsync($"{socketUser.Username}#{socketUser.Discriminator} left the server");
}
}
catch (Exception e)
@ -90,7 +90,7 @@ namespace Geekbot.Bot.Handlers
_logger.Error(LogSource.Geekbot, "Failed to send leave message", e);
}
_logger.Information(LogSource.Geekbot, $"{user.Username} ({user.Id}) joined {user.Guild.Name} ({user.Guild.Id})");
_logger.Information(LogSource.Geekbot, $"{socketUser.Username} ({socketUser.Id}) joined {socketGuild.Name} ({socketGuild.Id})");
}
}
}

View file

@ -1,228 +0,0 @@
using System;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using CommandLine;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Geekbot.Bot.Handlers;
using Geekbot.Core;
using Geekbot.Core.Converters;
using Geekbot.Core.Database;
using Geekbot.Core.DiceParser;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.GlobalSettings;
using Geekbot.Core.GuildSettingsManager;
using Geekbot.Core.Highscores;
using Geekbot.Core.KvInMemoryStore;
using Geekbot.Core.Levels;
using Geekbot.Core.Logger;
using Geekbot.Core.Media;
using Geekbot.Core.RandomNumberGenerator;
using Geekbot.Core.ReactionListener;
using Geekbot.Core.UserRepository;
using Geekbot.Core.WikipediaClient;
using Geekbot.Web;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace Geekbot.Bot
{
internal class Program
{
private DiscordSocketClient _client;
private CommandService _commands;
private DatabaseInitializer _databaseInitializer;
private IGlobalSettings _globalSettings;
private IServiceProvider _servicesProvider;
private GeekbotLogger _logger;
private IUserRepository _userRepository;
private RunParameters _runParameters;
private IReactionListener _reactionListener;
private IGuildSettingsManager _guildSettingsManager;
private static async Task Main(string[] args)
{
RunParameters runParameters = null;
Parser.Default.ParseArguments<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 logger = new GeekbotLogger(runParameters);
logger.Information(LogSource.Geekbot, "Starting...");
try
{
await new Program().Start(runParameters, logger);
}
catch (Exception e)
{
logger.Error(LogSource.Geekbot, "RIP", e);
}
}
private async Task Start(RunParameters runParameters, GeekbotLogger logger)
{
_logger = logger;
_runParameters = runParameters;
logger.Information(LogSource.Geekbot, "Connecting to Database");
var database = ConnectToDatabase();
_globalSettings = new GlobalSettings(database);
logger.Information(LogSource.Geekbot, "Connecting to Discord");
SetupDiscordClient();
await Login();
_logger.Information(LogSource.Geekbot, $"Now Connected as {_client.CurrentUser.Username} to {_client.Guilds.Count} Servers");
await _client.SetGameAsync(_globalSettings.GetKey("Game"));
_logger.Information(LogSource.Geekbot, "Loading Dependencies and Handlers");
RegisterDependencies();
await RegisterHandlers();
_logger.Information(LogSource.Api, "Starting Web API");
StartWebApi();
_logger.Information(LogSource.Geekbot, "Done and ready for use");
await Task.Delay(-1);
}
private async Task Login()
{
try
{
var token = await GetToken();
await _client.LoginAsync(TokenType.Bot, token);
await _client.StartAsync();
while (!_client.ConnectionState.Equals(ConnectionState.Connected)) await Task.Delay(25);
}
catch (Exception e)
{
_logger.Error(LogSource.Geekbot, "Could not connect to Discord", e);
Environment.Exit(GeekbotExitCode.CouldNotLogin.GetHashCode());
}
}
private DatabaseContext ConnectToDatabase()
{
_databaseInitializer = new DatabaseInitializer(_runParameters, _logger);
var database = _databaseInitializer.Initialize();
database.Database.EnsureCreated();
if(!_runParameters.InMemory) database.Database.Migrate();
return database;
}
private async Task<string> GetToken()
{
var token = _runParameters.Token ?? _globalSettings.GetKey("DiscordToken");
if (string.IsNullOrEmpty(token))
{
Console.Write("Your bot Token: ");
var newToken = Console.ReadLine();
await _globalSettings.SetKey("DiscordToken", newToken);
await _globalSettings.SetKey("Game", "Ping Pong");
token = newToken;
}
return token;
}
private void SetupDiscordClient()
{
_client = new DiscordSocketClient(new DiscordSocketConfig
{
LogLevel = LogSeverity.Verbose,
MessageCacheSize = 1000,
ExclusiveBulkDelete = true
});
var discordLogger = new DiscordLogger(_logger);
_client.Log += discordLogger.Log;
}
private void RegisterDependencies()
{
var services = new ServiceCollection();
_userRepository = new UserRepository(_databaseInitializer.Initialize(), _logger);
_reactionListener = new ReactionListener(_databaseInitializer.Initialize());
_guildSettingsManager = new GuildSettingsManager(_databaseInitializer.Initialize());
var fortunes = new FortunesProvider(_logger);
var levelCalc = new LevelCalc();
var emojiConverter = new EmojiConverter();
var mtgManaConverter = new MtgManaConverter();
var wikipediaClient = new WikipediaClient();
var randomNumberGenerator = new RandomNumberGenerator(_globalSettings);
var mediaProvider = new MediaProvider(_logger, randomNumberGenerator);
var kvMemoryStore = new KvInInMemoryStore();
var errorHandler = new ErrorHandler(_logger, _runParameters, () => Localization.Internal.SomethingWentWrong);
var diceParser = new DiceParser(randomNumberGenerator);
services.AddSingleton(_userRepository);
services.AddSingleton<IGeekbotLogger>(_logger);
services.AddSingleton<ILevelCalc>(levelCalc);
services.AddSingleton<IEmojiConverter>(emojiConverter);
services.AddSingleton<IFortunesProvider>(fortunes);
services.AddSingleton<IMediaProvider>(mediaProvider);
services.AddSingleton<IMtgManaConverter>(mtgManaConverter);
services.AddSingleton<IWikipediaClient>(wikipediaClient);
services.AddSingleton<IRandomNumberGenerator>(randomNumberGenerator);
services.AddSingleton<IKvInMemoryStore>(kvMemoryStore);
services.AddSingleton<IGlobalSettings>(_globalSettings);
services.AddSingleton<IErrorHandler>(errorHandler);
services.AddSingleton<IDiceParser>(diceParser);
services.AddSingleton<IReactionListener>(_reactionListener);
services.AddSingleton<IGuildSettingsManager>(_guildSettingsManager);
services.AddSingleton(_client);
services.AddTransient<IHighscoreManager>(e => new HighscoreManager(_databaseInitializer.Initialize(), _userRepository));
services.AddTransient(e => _databaseInitializer.Initialize());
_servicesProvider = services.BuildServiceProvider();
}
private async Task RegisterHandlers()
{
var applicationInfo = await _client.GetApplicationInfoAsync();
_commands = new CommandService();
await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _servicesProvider);
var commandHandler = new CommandHandler(_databaseInitializer.Initialize(), _client, _logger, _servicesProvider, _commands, applicationInfo, _guildSettingsManager);
var userHandler = new UserHandler(_userRepository, _logger, _databaseInitializer.Initialize(), _client);
var reactionHandler = new ReactionHandler(_reactionListener);
var statsHandler = new StatsHandler(_logger, _databaseInitializer.Initialize());
var messageDeletedHandler = new MessageDeletedHandler(_databaseInitializer.Initialize(), _logger, _client);
_client.MessageReceived += commandHandler.RunCommand;
_client.MessageDeleted += messageDeletedHandler.HandleMessageDeleted;
_client.UserJoined += userHandler.Joined;
_client.UserUpdated += userHandler.Updated;
_client.UserLeft += userHandler.Left;
_client.ReactionAdded += reactionHandler.Added;
_client.ReactionRemoved += reactionHandler.Removed;
if (!_runParameters.InMemory) _client.MessageReceived += statsHandler.UpdateStats;
}
private void StartWebApi()
{
if (_runParameters.DisableApi)
{
_logger.Warning(LogSource.Api, "Web API is disabled");
return;
}
var highscoreManager = new HighscoreManager(_databaseInitializer.Initialize(), _userRepository);
WebApiStartup.StartWebApi(_logger, _runParameters, _commands, _databaseInitializer.Initialize(), _client, _globalSettings, highscoreManager);
}
}
}

View file

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<VersionSuffix>$(VersionSuffix)</VersionSuffix>
<Version Condition=" '$(VersionSuffix)' != '' ">$(VersionSuffix)</Version>
<Version Condition=" '$(VersionSuffix)' == '' ">0.0.0-DEV</Version>
<RootNamespace>Geekbot.Commands</RootNamespace>
<AssemblyName>Geekbot.Commands</AssemblyName>
<NoWarn>NU1701</NoWarn>
<NoWarn>CS8618</NoWarn>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<OutputType>Library</OutputType>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Core\Core.csproj" />
<ProjectReference Include="..\Interactions\Interactions.csproj" />
</ItemGroup>
</Project>

103
src/Commands/Karma/Karma.cs Normal file
View file

@ -0,0 +1,103 @@
using System.Drawing;
using Geekbot.Core;
using Geekbot.Core.Database;
using Geekbot.Core.Database.Models;
using Geekbot.Interactions.Embed;
using Geekbot.Interactions.Resolved;
using Localization = Geekbot.Core.Localization;
namespace Geekbot.Commands.Karma;
public class Karma
{
private readonly DatabaseContext _database;
private readonly long _guildId;
public Karma(DatabaseContext database, long guildId)
{
_database = database;
_guildId = guildId;
}
public async Task<Embed> ChangeKarma(User author, User targetUser, KarmaChange change)
{
// Get the user
var authorRecord = await GetUser(long.Parse(author.Id));
// Check if the user can change karma
if (targetUser.Id == author.Id)
{
var message = change switch
{
KarmaChange.Up => Localization.Karma.CannotChangeOwnUp,
KarmaChange.Same => Localization.Karma.CannotChangeOwnSame,
KarmaChange.Down => Localization.Karma.CannotChangeOwnDown,
_ => throw new ArgumentOutOfRangeException(nameof(change), change, null)
};
return Embed.ErrorEmbed(string.Format(message, author.Username));
}
var timeoutMinutes = 3;
if (authorRecord.TimeOut.AddMinutes(timeoutMinutes) > DateTimeOffset.Now.ToUniversalTime())
{
var remaining = authorRecord.TimeOut.AddMinutes(timeoutMinutes) - DateTimeOffset.Now.ToUniversalTime();
var formatedWaitTime = DateLocalization.FormatDateTimeAsRemaining(remaining);
return Embed.ErrorEmbed(string.Format(Localization.Karma.WaitUntill, author.Username, formatedWaitTime));
}
// Get the values for the change direction
var (title, amount) = change switch
{
KarmaChange.Up => (Localization.Karma.Increased, 1),
KarmaChange.Same => (Localization.Karma.Neutral, 0),
KarmaChange.Down => (Localization.Karma.Decreased, -1),
_ => throw new ArgumentOutOfRangeException(nameof(change), change, null)
};
// Change it
var targetUserRecord = await GetUser(long.Parse(targetUser.Id));
targetUserRecord.Karma += amount;
_database.Karma.Update(targetUserRecord);
authorRecord.TimeOut = DateTimeOffset.Now.ToUniversalTime();
_database.Karma.Update(authorRecord);
await _database.SaveChangesAsync();
// Respond
var eb = new Embed()
{
Author = new ()
{
Name = targetUser.Username,
IconUrl = targetUser.GetAvatarUrl()
},
Title = title,
};
eb.SetColor(Color.PaleGreen);
eb.AddInlineField(Localization.Karma.By, author.Username);
eb.AddInlineField(Localization.Karma.Amount, amount.ToString());
eb.AddInlineField(Localization.Karma.Current, targetUserRecord.Karma.ToString());
return eb;
}
private async Task<KarmaModel> GetUser(long userId)
{
var user = _database.Karma.FirstOrDefault(u => u.GuildId.Equals(_guildId) && u.UserId.Equals(userId)) ?? await CreateNewRow(userId);
return user;
}
private async Task<KarmaModel> CreateNewRow(long userId)
{
var user = new KarmaModel()
{
GuildId = _guildId,
UserId = userId,
Karma = 0,
TimeOut = DateTimeOffset.MinValue.ToUniversalTime()
};
var newUser = _database.Karma.Add(user).Entity;
await _database.SaveChangesAsync();
return newUser;
}
}

View file

@ -0,0 +1,8 @@
namespace Geekbot.Commands.Karma;
public enum KarmaChange
{
Up,
Same,
Down
}

102
src/Commands/Rank.cs Normal file
View file

@ -0,0 +1,102 @@
using System.Text;
using Geekbot.Core.Converters;
using Geekbot.Core.Database;
using Geekbot.Core.Extensions;
using Geekbot.Core.Highscores;
using Localization = Geekbot.Core.Localization;
namespace Geekbot.Commands
{
public class Rank
{
private readonly DatabaseContext _database;
private readonly IHighscoreManager _highscoreManager;
public Rank(DatabaseContext database, IHighscoreManager highscoreManager)
{
_database = database;
_highscoreManager = highscoreManager;
}
public string Run(string typeUnformated, int amount, string season, ulong guildId, string guildName)
{
HighscoreTypes type;
try
{
type = Enum.Parse<HighscoreTypes>(typeUnformated, true);
if (!Enum.IsDefined(typeof(HighscoreTypes), type)) throw new Exception();
}
catch
{
return Localization.Rank.InvalidType;
}
var replyBuilder = new StringBuilder();
if (amount > 20)
{
replyBuilder.AppendLine(Localization.Rank.LimitingTo20Warning);
amount = 20;
}
Dictionary<HighscoreUserDto, int> highscoreUsers;
try
{
highscoreUsers = _highscoreManager.GetHighscoresWithUserData(type, guildId, amount, season);
}
catch (HighscoreListEmptyException)
{
return string.Format(Core.Localization.Rank.NoTypeFoundForServer, type);
}
var guildMessages = 0;
if (type == HighscoreTypes.messages)
{
guildMessages = _database.Messages
.Where(e => e.GuildId.Equals(guildId.AsLong()))
.Select(e => e.MessageCount)
.Sum();
}
var failedToRetrieveUser = highscoreUsers.Any(e => string.IsNullOrEmpty(e.Key.Username));
if (failedToRetrieveUser) replyBuilder.AppendLine(Core.Localization.Rank.FailedToResolveAllUsernames).AppendLine();
if (type == HighscoreTypes.seasons)
{
if (string.IsNullOrEmpty(season))
{
season = SeasonsUtils.GetCurrentSeason();
}
replyBuilder.AppendLine(string.Format(Core.Localization.Rank.HighscoresFor, $"{type.ToString().CapitalizeFirst()} ({season})", guildName));
}
else
{
replyBuilder.AppendLine(string.Format(Core.Localization.Rank.HighscoresFor, type.ToString().CapitalizeFirst(), guildName));
}
var highscorePlace = 1;
foreach (var (user, value) in highscoreUsers)
{
replyBuilder.Append(highscorePlace < 11
? $"{EmojiConverter.NumberToEmoji(highscorePlace)} "
: $"`{highscorePlace}.` ");
replyBuilder.Append(user.Username != null
? $"**{user.Username}#{user.Discriminator}**"
: $"**{user.Id}**");
replyBuilder.Append(type switch
{
HighscoreTypes.messages => $" - {value} {HighscoreTypes.messages} - {Math.Round((double)(100 * value) / guildMessages, 2)}%\n",
HighscoreTypes.seasons => $" - {value} {HighscoreTypes.messages}\n",
_ => $" - {value} {type}\n"
});
highscorePlace++;
}
return replyBuilder.ToString();
}
}
}

91
src/Commands/Roll/Roll.cs Normal file
View file

@ -0,0 +1,91 @@
using Geekbot.Core;
using Geekbot.Core.Database;
using Geekbot.Core.Database.Models;
using Geekbot.Core.Extensions;
using Geekbot.Core.KvInMemoryStore;
using Geekbot.Core.RandomNumberGenerator;
namespace Geekbot.Commands.Roll
{
public class Roll
{
private readonly IKvInMemoryStore _kvInMemoryStore;
private readonly DatabaseContext _database;
private readonly IRandomNumberGenerator _randomNumberGenerator;
public Roll(IKvInMemoryStore kvInMemoryStore, DatabaseContext database, IRandomNumberGenerator randomNumberGenerator)
{
_kvInMemoryStore = kvInMemoryStore;
_database = database;
_randomNumberGenerator = randomNumberGenerator;
}
public async Task<string> RunFromGateway(ulong guildId, ulong userId, string userName, string unparsedGuess)
{
int.TryParse(unparsedGuess, out var guess);
return await this.Run(guildId.AsLong(), userId.AsLong(), userName, guess);
}
public async Task<string> RunFromInteraction(string guildId, string userId, string userName, int guess)
{
return await this.Run(long.Parse(guildId), long.Parse(userId), userName, guess);
}
private async Task<string> Run(long guildId, long userId, string userName, int guess)
{
var number = _randomNumberGenerator.Next(1, 100);
if (guess <= 100 && guess > 0)
{
var kvKey = $"{guildId}:{userId}:RollsPrevious";
var prevRoll = _kvInMemoryStore.Get<RollTimeout>(kvKey);
if (prevRoll?.LastGuess == guess && prevRoll?.GuessedOn.AddDays(1) > DateTime.Now)
{
return string.Format(
Core.Localization.Roll.NoPrevGuess,
$"<@{userId}>",
DateLocalization.FormatDateTimeAsRemaining(prevRoll.GuessedOn.AddDays(1)));
}
_kvInMemoryStore.Set(kvKey, new RollTimeout { LastGuess = guess, GuessedOn = DateTime.Now });
var answer = string.Format(Core.Localization.Roll.Rolled, $"<@{userId}>", number, guess);
if (guess == number)
{
var user = await GetUser(guildId, userId);
user.Rolls += 1;
_database.Rolls.Update(user);
await _database.SaveChangesAsync();
answer += string.Format(($"\n{Core.Localization.Roll.Gratz}"), userName);
}
return answer;
}
else
{
return string.Format(Core.Localization.Roll.RolledNoGuess, $"<@{userId}>", number);
}
}
private async Task<RollsModel> GetUser(long guildId, long userId)
{
var user = _database.Rolls.FirstOrDefault(u => u.GuildId.Equals(guildId) && u.UserId.Equals(userId)) ?? await CreateNewRow(guildId, userId);
return user;
}
private async Task<RollsModel> CreateNewRow(long guildId, long userId)
{
var user = new RollsModel()
{
GuildId = guildId,
UserId = userId,
Rolls = 0
};
var newUser = _database.Rolls.Add(user).Entity;
await _database.SaveChangesAsync();
return newUser;
}
}
}

View file

@ -1,8 +1,8 @@
using System;
namespace Geekbot.Bot.Commands.Games.Roll
namespace Geekbot.Commands.Roll
{
public class RollTimeout
public record RollTimeout
{
public int LastGuess { get; set; }
public DateTime GuessedOn { get; set; }

View file

@ -0,0 +1,37 @@
using System.Drawing;
using Geekbot.Core;
using Geekbot.Interactions.Embed;
namespace Geekbot.Commands.UrbanDictionary;
public class UrbanDictionary
{
public static async Task<Embed?> Run(string term)
{
var definitions = await HttpAbstractions.Get<UrbanDictionaryResponse>(new Uri($"https://api.urbandictionary.com/v0/define?term={term}"));
if (definitions.List.Count == 0)
{
return null;
}
var definition = definitions.List.First(e => !string.IsNullOrWhiteSpace(e.Example));
static string ShortenIfToLong(string str, int maxLength) => str.Length > maxLength ? $"{str[..(maxLength - 5)]}[...]" : str;
var eb = new Embed();
eb.Author = new()
{
Name = definition.Word,
Url = definition.Permalink
};
eb.SetColor(Color.Gold);
if (!string.IsNullOrEmpty(definition.Definition)) eb.Description = ShortenIfToLong(definition.Definition, 1800);
if (!string.IsNullOrEmpty(definition.Example)) eb.AddField("Example", ShortenIfToLong(definition.Example, 1024));
if (definition.ThumbsUp != 0) eb.AddInlineField("Upvotes", definition.ThumbsUp.ToString());
if (definition.ThumbsDown != 0) eb.AddInlineField("Downvotes", definition.ThumbsDown.ToString());
return eb;
}
}

View file

@ -0,0 +1,24 @@
using System.Text.Json.Serialization;
namespace Geekbot.Commands.UrbanDictionary;
public record UrbanDictionaryListItem
{
[JsonPropertyName("definition")]
public string Definition { get; set; }
[JsonPropertyName("permalink")]
public string Permalink { get; set; }
[JsonPropertyName("thumbs_up")]
public int ThumbsUp { get; set; }
[JsonPropertyName("word")]
public string Word { get; set; }
[JsonPropertyName("example")]
public string Example { get; set; }
[JsonPropertyName("thumbs_down")]
public int ThumbsDown { get; set; }
}

View file

@ -0,0 +1,12 @@
using System.Text.Json.Serialization;
namespace Geekbot.Commands.UrbanDictionary;
public struct UrbanDictionaryResponse
{
[JsonPropertyName("tags")]
public string[] Tags { get; set; }
[JsonPropertyName("list")]
public List<UrbanDictionaryListItem?> List { get; set; }
}

View file

@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace Geekbot.Core.BotCommandLookup;
public struct CommandInfo
{
public string Name { get; set; }
public Dictionary<string, ParameterInfo> Parameters { get; set; }
public List<string> Aliases { get; set; }
public string Summary { get; set; }
}

View file

@ -0,0 +1,87 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Discord.Commands;
namespace Geekbot.Core.BotCommandLookup;
public class CommandLookup
{
private readonly Assembly _assembly;
public CommandLookup(Assembly assembly)
{
_assembly = assembly;
}
public List<CommandInfo> GetCommands()
{
var commands = SearchCommands(_assembly);
var result = new List<CommandInfo>();
commands.ForEach(x => GetCommandDefinition(ref result, x));
return result;
}
private List<TypeInfo> SearchCommands(Assembly assembly)
{
bool IsLoadableModule(TypeInfo info) => info.DeclaredMethods.Any(x => x.GetCustomAttribute<CommandAttribute>() != null || x.GetCustomAttribute<GroupAttribute>() != null);
return assembly
.DefinedTypes
.Where(typeInfo => typeInfo.IsPublic || typeInfo.IsNestedPublic)
.Where(IsLoadableModule)
.ToList();
}
private void GetCommandDefinition(ref List<CommandInfo> commandInfos, TypeInfo commandType)
{
var methods = commandType
.GetMethods()
.Where(x => x.GetCustomAttribute<CommandAttribute>() != null)
.ToList();
var commandGroup = (commandType.GetCustomAttributes().FirstOrDefault(attr => attr is GroupAttribute) as GroupAttribute)?.Prefix;
foreach (var command in methods)
{
var commandInfo = new CommandInfo()
{
Parameters = new Dictionary<string, ParameterInfo>(),
};
foreach (var attr in command.GetCustomAttributes())
{
switch (attr)
{
case SummaryAttribute name:
commandInfo.Summary = name.Text;
break;
case CommandAttribute name:
commandInfo.Name = string.IsNullOrEmpty(commandGroup) ? name.Text : $"{commandGroup} {name.Text}";
break;
case AliasAttribute name:
commandInfo.Aliases = name.Aliases.ToList() ?? new List<string>();
break;
}
}
foreach (var param in command.GetParameters())
{
var paramName = param.Name ?? string.Empty;
var paramInfo = new ParameterInfo()
{
Summary = param.GetCustomAttribute<SummaryAttribute>()?.Text ?? string.Empty,
Type = param.ParameterType.Name,
DefaultValue = param.DefaultValue?.ToString()
};
commandInfo.Parameters.Add(paramName, paramInfo);
}
if (!string.IsNullOrEmpty(commandInfo.Name))
{
commandInfos.Add(commandInfo);
}
}
}
}

View file

@ -0,0 +1,8 @@
namespace Geekbot.Core.BotCommandLookup;
public struct ParameterInfo
{
public string Summary { get; set; }
public string Type { get; set; }
public string DefaultValue { get; set; }
}

View file

@ -3,88 +3,133 @@ using System.Text;
namespace Geekbot.Core.Converters
{
public class EmojiConverter : IEmojiConverter
public static class EmojiConverter
{
public string NumberToEmoji(int number)
private static readonly string[] NumberEmojiMap =
{
":zero:",
":one:",
":two:",
":three:",
":four:",
":five:",
":six:",
":seven:",
":eight:",
":nine:"
};
public static string NumberToEmoji(int number)
{
if (number == 10)
{
return "🔟";
}
var emojiMap = new[]
{
":zero:",
":one:",
":two:",
":three:",
":four:",
":five:",
":six:",
":seven:",
":eight:",
":nine:"
};
var numbers = number.ToString().ToCharArray();
var returnString = new StringBuilder();
foreach (var n in numbers)
{
returnString.Append(emojiMap[int.Parse(n.ToString())]);
returnString.Append(NumberEmojiMap[int.Parse(n.ToString())]);
}
return returnString.ToString();
}
public string TextToEmoji(string text)
private static readonly Hashtable TextEmojiMap = new Hashtable
{
['A'] = ":regional_indicator_a: ",
['B'] = ":b: ",
['C'] = ":regional_indicator_c: ",
['D'] = ":regional_indicator_d: ",
['E'] = ":regional_indicator_e: ",
['F'] = ":regional_indicator_f: ",
['G'] = ":regional_indicator_g: ",
['H'] = ":regional_indicator_h: ",
['I'] = ":regional_indicator_i: ",
['J'] = ":regional_indicator_j: ",
['K'] = ":regional_indicator_k: ",
['L'] = ":regional_indicator_l: ",
['M'] = ":regional_indicator_m: ",
['N'] = ":regional_indicator_n: ",
['O'] = ":regional_indicator_o: ",
['P'] = ":regional_indicator_p: ",
['Q'] = ":regional_indicator_q: ",
['R'] = ":regional_indicator_r: ",
['S'] = ":regional_indicator_s: ",
['T'] = ":regional_indicator_t: ",
['U'] = ":regional_indicator_u: ",
['V'] = ":regional_indicator_v: ",
['W'] = ":regional_indicator_w: ",
['X'] = ":regional_indicator_x: ",
['Y'] = ":regional_indicator_y: ",
['Z'] = ":regional_indicator_z: ",
['!'] = ":exclamation: ",
['?'] = ":question: ",
['#'] = ":hash: ",
['*'] = ":star2: ",
['+'] = ":heavy_plus_sign: ",
['0'] = ":zero: ",
['1'] = ":one: ",
['2'] = ":two: ",
['3'] = ":three: ",
['4'] = ":four: ",
['5'] = ":five: ",
['6'] = ":six: ",
['7'] = ":seven: ",
['8'] = ":eight: ",
['9'] = ":nine: ",
[' '] = ""
};
public static string TextToEmoji(string text)
{
var emojiMap = new Hashtable
{
['A'] = ":regional_indicator_a: ",
['B'] = ":b: ",
['C'] = ":regional_indicator_c: ",
['D'] = ":regional_indicator_d: ",
['E'] = ":regional_indicator_e: ",
['F'] = ":regional_indicator_f: ",
['G'] = ":regional_indicator_g: ",
['H'] = ":regional_indicator_h: ",
['I'] = ":regional_indicator_i: ",
['J'] = ":regional_indicator_j: ",
['K'] = ":regional_indicator_k: ",
['L'] = ":regional_indicator_l: ",
['M'] = ":regional_indicator_m: ",
['N'] = ":regional_indicator_n: ",
['O'] = ":regional_indicator_o: ",
['P'] = ":regional_indicator_p: ",
['Q'] = ":regional_indicator_q: ",
['R'] = ":regional_indicator_r: ",
['S'] = ":regional_indicator_s: ",
['T'] = ":regional_indicator_t: ",
['U'] = ":regional_indicator_u: ",
['V'] = ":regional_indicator_v: ",
['W'] = ":regional_indicator_w: ",
['X'] = ":regional_indicator_x: ",
['Y'] = ":regional_indicator_y: ",
['Z'] = ":regional_indicator_z: ",
['!'] = ":exclamation: ",
['?'] = ":question: ",
['#'] = ":hash: ",
['*'] = ":star2: ",
['+'] = ":heavy_plus_sign: ",
['0'] = ":zero: ",
['1'] = ":one: ",
['2'] = ":two: ",
['3'] = ":three: ",
['4'] = ":four: ",
['5'] = ":five: ",
['6'] = ":six: ",
['7'] = ":seven: ",
['8'] = ":eight: ",
['9'] = ":nine: ",
[' '] = ""
};
var letters = text.ToUpper().ToCharArray();
var returnString = new StringBuilder();
foreach (var n in letters)
{
var emoji = emojiMap[n] ?? n;
var emoji = TextEmojiMap[n] ?? n;
returnString.Append(emoji);
}
return returnString.ToString();
}
private static readonly Hashtable RegionalIndicatorMap = new Hashtable()
{
['A'] = new Rune(0x1F1E6),
['B'] = new Rune(0x1F1E7),
['C'] = new Rune(0x1F1E8),
['D'] = new Rune(0x1F1E9),
['E'] = new Rune(0x1F1EA),
['F'] = new Rune(0x1F1EB),
['G'] = new Rune(0x1F1EC),
['H'] = new Rune(0x1F1ED),
['I'] = new Rune(0x1F1EE),
['J'] = new Rune(0x1F1EF),
['K'] = new Rune(0x1F1F0),
['L'] = new Rune(0x1F1F1),
['M'] = new Rune(0x1F1F2),
['N'] = new Rune(0x1F1F3),
['O'] = new Rune(0x1F1F4),
['P'] = new Rune(0x1F1F5),
['Q'] = new Rune(0x1F1F6),
['R'] = new Rune(0x1F1F7),
['S'] = new Rune(0x1F1F8),
['T'] = new Rune(0x1F1F9),
['U'] = new Rune(0x1F1FA),
['V'] = new Rune(0x1F1FB),
['W'] = new Rune(0x1F1FC),
['X'] = new Rune(0x1F1FD),
['Y'] = new Rune(0x1F1FE),
['Z'] = new Rune(0x1F1FF)
};
public static string CountryCodeToEmoji(string countryCode)
{
var letters = countryCode.ToUpper().ToCharArray();
var returnString = new StringBuilder();
foreach (var n in letters)
{
var emoji = RegionalIndicatorMap[n];
returnString.Append(emoji);
}
return returnString.ToString();

View file

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

View file

@ -1,33 +1,94 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<VersionSuffix>$(VersionSuffix)</VersionSuffix>
<Version Condition=" '$(VersionSuffix)' != '' ">$(VersionSuffix)</Version>
<Version Condition=" '$(VersionSuffix)' == '' ">0.0.0-DEV</Version>
<RootNamespace>Geekbot.Core</RootNamespace>
<AssemblyName>Geekbot.Core</AssemblyName>
<NoWarn>NU1701</NoWarn>
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<OutputType>Library</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Anemonis.RandomOrg" Version="1.14.0" />
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="Discord.Net" Version="2.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.0-rc.1.*" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.0-rc.1.*" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="5.0.0-rc.1.*" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.0-rc.1.*" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.0-rc.1.*" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="5.0.0-rc.1.*" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0-rc.1.*" />
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0-rc.1.*" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="5.0.0-rc1" />
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.Datadog.Logs" Version="0.3.3" />
<PackageReference Include="SharpRaven" Version="2.4.0" />
<PackageReference Include="Discord.Net" Version="3.7.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.12" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.12">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="5.0.12" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.12" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.12">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="5.0.12" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.12" />
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.12" />
<PackageReference Include="NLog" Version="4.7.2" />
<PackageReference Include="NLog.Config" Version="4.7.2" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="5.0.10" />
<PackageReference Include="Sentry" Version="3.11.0" />
<PackageReference Include="SumoLogic.Logging.NLog" Version="1.0.1.4" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Localization\Admin.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Admin.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Localization\Choose.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Choose.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Localization\Cookies.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Cookies.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Localization\Corona.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Corona.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Localization\EightBall.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>EightBall.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Localization\Internal.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Internal.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Localization\Karma.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Karma.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Localization\Quote.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Quote.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Localization\Rank.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Rank.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Localization\Role.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Role.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Localization\Roll.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Roll.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Localization\Ship.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Ship.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Localization\Stats.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Stats.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

View file

@ -11,6 +11,7 @@ namespace Geekbot.Core.Database
public DbSet<KarmaModel> Karma { get; set; }
public DbSet<ShipsModel> Ships { get; set; }
public DbSet<RollsModel> Rolls { get; set; }
public DbSet<MessageSeasonsModel> MessagesSeasons { get; set; }
public DbSet<MessagesModel> Messages { get; set; }
public DbSet<SlapsModel> Slaps { get; set; }
public DbSet<GlobalsModel> Globals { get; set; }

View file

@ -1,8 +1,7 @@
using System;
using Geekbot.Core.Logger;
using Microsoft.Extensions.Logging;
using Npgsql.Logging;
using Serilog.Events;
using LogLevel = NLog.LogLevel;
namespace Geekbot.Core.Database.LoggingAdapter
{
@ -22,7 +21,7 @@ namespace Geekbot.Core.Database.LoggingAdapter
public override bool IsEnabled(NpgsqlLogLevel level)
{
return (_runParameters.DbLogging && _geekbotLogger.GetLogger().IsEnabled(ToGeekbotLogLevel(level)));
return (_runParameters.DbLogging && _geekbotLogger.GetNLogger().IsEnabled(ToGeekbotLogLevel(level)));
}
public override void Log(NpgsqlLogLevel level, int connectorId, string msg, Exception exception = null)
@ -52,16 +51,16 @@ namespace Geekbot.Core.Database.LoggingAdapter
}
}
private static LogEventLevel ToGeekbotLogLevel(NpgsqlLogLevel level)
private static LogLevel ToGeekbotLogLevel(NpgsqlLogLevel level)
{
return level switch
{
NpgsqlLogLevel.Trace => LogEventLevel.Verbose,
NpgsqlLogLevel.Debug => LogEventLevel.Debug,
NpgsqlLogLevel.Info => LogEventLevel.Information,
NpgsqlLogLevel.Warn => LogEventLevel.Warning,
NpgsqlLogLevel.Error => LogEventLevel.Error,
NpgsqlLogLevel.Fatal => LogEventLevel.Fatal,
NpgsqlLogLevel.Trace => LogLevel.Trace,
NpgsqlLogLevel.Debug => LogLevel.Debug,
NpgsqlLogLevel.Info => LogLevel.Info,
NpgsqlLogLevel.Warn => LogLevel.Warn,
NpgsqlLogLevel.Error => LogLevel.Error,
NpgsqlLogLevel.Fatal => LogLevel.Fatal,
_ => throw new ArgumentOutOfRangeException(nameof(level))
};
}

View file

@ -0,0 +1,21 @@
using System.ComponentModel.DataAnnotations;
namespace Geekbot.Core.Database.Models
{
public class MessageSeasonsModel
{
[Key]
public int Id { get; set; }
[Required]
public long GuildId { get; set; }
[Required]
public long UserId { get; set; }
[Required]
public string Season { get; set; }
public int MessageCount { get; set; }
}
}

View file

@ -1,13 +1,17 @@
using System;
using System.Text;
namespace Geekbot.Bot.Utils
namespace Geekbot.Core
{
public class DateLocalization
{
public static string FormatDateTimeAsRemaining(DateTimeOffset dateTime)
{
var remaining = dateTime - DateTimeOffset.Now;
return FormatDateTimeAsRemaining(dateTime - DateTimeOffset.Now);
}
public static string FormatDateTimeAsRemaining(TimeSpan remaining)
{
const string formattable = "{0} {1}";
var sb = new StringBuilder();

View file

@ -63,9 +63,9 @@ namespace Geekbot.Core.DiceParser
throw new DiceException("Die must have at least 2 sides") { DiceName = DiceName };
}
if (Sides > 144)
if (Sides > 145)
{
throw new DiceException("Die can not have more than 144 sides") { DiceName = DiceName };
throw new DiceException("Die can not have more than 145 sides") { DiceName = DiceName };
}
}
}

View file

@ -2,8 +2,7 @@
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.Core.Logger;
using SharpRaven;
using SharpRaven.Data;
using Sentry;
using Exception = System.Exception;
namespace Geekbot.Core.ErrorHandling
@ -12,7 +11,6 @@ namespace Geekbot.Core.ErrorHandling
{
private readonly IGeekbotLogger _logger;
private readonly Func<string> _getDefaultErrorText;
private readonly IRavenClient _raven;
private readonly bool _errorsInChat;
public ErrorHandler(IGeekbotLogger logger, RunParameters runParameters, Func<string> getDefaultErrorText)
@ -20,17 +18,6 @@ namespace Geekbot.Core.ErrorHandling
_logger = logger;
_getDefaultErrorText = getDefaultErrorText;
_errorsInChat = runParameters.ExposeErrors;
var sentryDsn = runParameters.SentryEndpoint;
if (!string.IsNullOrEmpty(sentryDsn))
{
_raven = new RavenClient(sentryDsn) { Release = Constants.BotVersion(), Environment = "Production" };
_logger.Information(LogSource.Geekbot, $"Command Errors will be logged to Sentry: {sentryDsn}");
}
else
{
_raven = null;
}
}
public async Task HandleCommandException(Exception e, ICommandContext context, string errorMessage = "def")
@ -83,18 +70,19 @@ namespace Geekbot.Core.ErrorHandling
private void ReportExternal(Exception e, MessageDto errorObj)
{
if (_raven == null) return;
if (!SentrySdk.IsEnabled) return;
var sentryEvent = new SentryEvent(e)
{
Tags =
{
["discord_server"] = errorObj.Guild.Name,
["discord_user"] = errorObj.User.Name
},
Message = errorObj.Message.Content,
Extra = errorObj
};
_raven.Capture(sentryEvent);
sentryEvent.SetTag("discord_server", errorObj.Guild.Name);
sentryEvent.SetExtra("Channel", errorObj.Channel);
sentryEvent.SetExtra("Guild", errorObj.Guild);
sentryEvent.SetExtra("Message", errorObj.Message);
sentryEvent.SetExtra("User", errorObj.User);
SentrySdk.CaptureEvent(sentryEvent);
}
}
}

View file

@ -7,7 +7,7 @@ using Geekbot.Core.GuildSettingsManager;
namespace Geekbot.Core
{
public class GeekbotCommandBase : ModuleBase<ICommandContext>
public class GeekbotCommandBase : TransactionModuleBase
{
protected readonly IGuildSettingsManager GuildSettingsManager;
protected GuildSettingsModel GuildSettings;
@ -22,9 +22,14 @@ namespace Geekbot.Core
protected override void BeforeExecute(CommandInfo command)
{
base.BeforeExecute(command);
var setupSpan = Transaction.StartChild("Setup");
GuildSettings = GuildSettingsManager.GetSettings(Context?.Guild?.Id ?? 0);
var language = GuildSettings.Language;
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(language == "CHDE" ? "de-ch" : language);
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(language);
setupSpan.Finish();
}
}
}

View file

@ -18,7 +18,7 @@ namespace Geekbot.Core.Highscores
}
public Dictionary<HighscoreUserDto, int> GetHighscoresWithUserData(HighscoreTypes type, ulong guildId, int amount)
public Dictionary<HighscoreUserDto, int> GetHighscoresWithUserData(HighscoreTypes type, ulong guildId, int amount, string season = null)
{
var list = type switch
{
@ -26,6 +26,8 @@ namespace Geekbot.Core.Highscores
HighscoreTypes.karma => GetKarmaList(guildId, amount),
HighscoreTypes.rolls => GetRollsList(guildId, amount),
HighscoreTypes.cookies => GetCookiesList(guildId, amount),
HighscoreTypes.seasons => GetMessageSeasonList(guildId, amount, season),
HighscoreTypes.quotes => GetQuotesList(guildId, amount),
_ => new Dictionary<ulong, int>()
};
@ -75,6 +77,19 @@ namespace Geekbot.Core.Highscores
.ToDictionary(key => key.UserId.AsUlong(), key => key.MessageCount);
}
public Dictionary<ulong, int> GetMessageSeasonList(ulong guildId, int amount, string season)
{
if (string.IsNullOrEmpty(season))
{
season = SeasonsUtils.GetCurrentSeason();
}
return _database.MessagesSeasons
.Where(k => k.GuildId.Equals(guildId.AsLong()) && k.Season.Equals(season))
.OrderByDescending(o => o.MessageCount)
.Take(amount)
.ToDictionary(key => key.UserId.AsUlong(), key => key.MessageCount);
}
public Dictionary<ulong, int> GetKarmaList(ulong guildId, int amount)
{
return _database.Karma
@ -101,5 +116,16 @@ namespace Geekbot.Core.Highscores
.Take(amount)
.ToDictionary(key => key.UserId.AsUlong(), key => key.Cookies);
}
private Dictionary<ulong, int> GetQuotesList(ulong guildId, int amount)
{
return _database.Quotes
.Where(row => row.GuildId == guildId.AsLong())
.GroupBy(row => row.UserId)
.Select(row => new { userId = row.Key, amount = row.Count()})
.OrderByDescending(row => row.amount)
.Take(amount)
.ToDictionary(key => key.userId.AsUlong(), key => key.amount);
}
}
}

View file

@ -5,6 +5,8 @@
messages,
karma,
rolls,
cookies
cookies,
seasons,
quotes
}
}

View file

@ -4,8 +4,9 @@ namespace Geekbot.Core.Highscores
{
public interface IHighscoreManager
{
Dictionary<HighscoreUserDto, int> GetHighscoresWithUserData(HighscoreTypes type, ulong guildId, int amount);
Dictionary<HighscoreUserDto, int> GetHighscoresWithUserData(HighscoreTypes type, ulong guildId, int amount, string season = null);
Dictionary<ulong, int> GetMessageList(ulong guildId, int amount);
Dictionary<ulong, int> GetMessageSeasonList(ulong guildId, int amount, string season);
Dictionary<ulong, int> GetKarmaList(ulong guildId, int amount);
Dictionary<ulong, int> GetRollsList(ulong guildId, int amount);
}

View file

@ -0,0 +1,16 @@
using System;
using System.Globalization;
namespace Geekbot.Core.Highscores
{
public class SeasonsUtils
{
public static string GetCurrentSeason()
{
var now = DateTime.Now;
var year = (now.Year - 2000).ToString(CultureInfo.InvariantCulture);
var quarter = Math.Ceiling(now.Month / 3.0).ToString(CultureInfo.InvariantCulture);
return $"{year}Q{quarter}";
}
}
}

View file

@ -1,8 +1,12 @@
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace Geekbot.Core
{
@ -22,21 +26,164 @@ namespace Geekbot.Core
return client;
}
public static async Task<T> Get<T>(Uri location, HttpClient httpClient = null, bool disposeClient = true)
public static async Task<TResponse> Get<TResponse>(Uri location, HttpClient httpClient = null, bool disposeClient = true, int maxRetries = 3)
{
httpClient ??= CreateDefaultClient();
httpClient.BaseAddress = location;
var response = await httpClient.GetAsync(location.PathAndQuery);
response.EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync();
if (disposeClient)
HttpResponseMessage response;
try
{
httpClient.Dispose();
response = await Execute(() => httpClient.GetAsync(location.PathAndQuery), maxRetries);
}
finally
{
if (disposeClient)
{
httpClient.Dispose();
}
}
return JsonConvert.DeserializeObject<T>(stringResponse);
var stringResponse = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<TResponse>(stringResponse);
}
public static async Task<TResponse> Post<TResponse>(Uri location, object data, HttpClient httpClient = null, bool disposeClient = true, int maxRetries = 3)
{
httpClient ??= CreateDefaultClient();
httpClient.BaseAddress = location;
var content = new StringContent(
JsonSerializer.Serialize(data, new JsonSerializerOptions() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }),
Encoding.UTF8,
"application/json"
);
HttpResponseMessage response;
try
{
response = await Execute(() => httpClient.PostAsync(location, content), maxRetries);
}
finally
{
if (disposeClient)
{
httpClient.Dispose();
}
}
var stringResponse = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<TResponse>(stringResponse);
}
public static async Task Post(Uri location, object data, HttpClient httpClient = null, bool disposeClient = true, int maxRetries = 3)
{
httpClient ??= CreateDefaultClient();
httpClient.BaseAddress = location;
var content = new StringContent(
JsonSerializer.Serialize(data, new JsonSerializerOptions() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }),
Encoding.UTF8,
"application/json"
);
try
{
await Execute(() => httpClient.PostAsync(location, content), maxRetries);
}
finally
{
if (disposeClient)
{
httpClient.Dispose();
}
}
}
public static async Task Patch(Uri location, object data, HttpClient httpClient = null, bool disposeClient = true, int maxRetries = 3)
{
httpClient ??= CreateDefaultClient();
httpClient.BaseAddress = location;
var content = new StringContent(
JsonSerializer.Serialize(data, new JsonSerializerOptions() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }),
Encoding.UTF8,
"application/json"
);
try
{
await Execute(() => httpClient.PatchAsync(location, content), maxRetries);
}
finally
{
if (disposeClient)
{
httpClient.Dispose();
}
}
}
public static async Task Delete(Uri location, HttpClient httpClient = null, bool disposeClient = true, int maxRetries = 3)
{
httpClient ??= CreateDefaultClient();
httpClient.BaseAddress = location;
try
{
await Execute(() => httpClient.DeleteAsync(location), maxRetries);
}
finally
{
if (disposeClient)
{
httpClient.Dispose();
}
}
}
private static async Task<HttpResponseMessage> Execute(Func<Task<HttpResponseMessage>> request, int maxRetries)
{
var attempt = 0;
while (true)
{
var response = await request();
if (!response.IsSuccessStatusCode)
{
if (attempt >= maxRetries)
{
throw new HttpRequestException($"Request failed after {attempt} attempts");
}
if (response.Headers.Contains("Retry-After"))
{
var retryAfter = response.Headers.GetValues("Retry-After").First();
if (retryAfter.Contains(':'))
{
var duration = DateTimeOffset.Parse(retryAfter).ToUniversalTime() - DateTimeOffset.Now.ToUniversalTime();
await Task.Delay(duration);
}
else
{
await Task.Delay(int.Parse(retryAfter) * 1000);
}
}
else if (response.StatusCode is HttpStatusCode.BadGateway or HttpStatusCode.ServiceUnavailable or HttpStatusCode.GatewayTimeout)
{
await Task.Delay(TimeSpan.FromSeconds(Math.Ceiling(attempt * 1.5)));
}
else
{
response.EnsureSuccessStatusCode();
}
attempt++;
}
else
{
return response;
}
}
}
}
}

View file

@ -8,10 +8,7 @@
// </auto-generated>
//------------------------------------------------------------------------------
namespace Geekbot.Bot.Localization {
using System;
namespace Geekbot.Core.Localization {
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
@ -22,24 +19,24 @@ namespace Geekbot.Bot.Localization {
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Admin {
public class Admin {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Admin() {
public Admin() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Geekbot.Bot.Localization.Admin", typeof(Admin).Assembly);
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Geekbot.Core.Localization.Admin", typeof(Admin).Assembly);
resourceMan = temp;
}
return resourceMan;
@ -51,7 +48,7 @@ namespace Geekbot.Bot.Localization {
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
@ -63,7 +60,7 @@ namespace Geekbot.Bot.Localization {
/// <summary>
/// Looks up a localized string similar to I&apos;m talking english.
/// </summary>
internal static string GetLanguage {
public static string GetLanguage {
get {
return ResourceManager.GetString("GetLanguage", resourceCulture);
}
@ -72,7 +69,7 @@ namespace Geekbot.Bot.Localization {
/// <summary>
/// Looks up a localized string similar to I will reply in english from now on.
/// </summary>
internal static string NewLanguageSet {
public static string NewLanguageSet {
get {
return ResourceManager.GetString("NewLanguageSet", resourceCulture);
}

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