mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 17:28:27 -04:00
Compare commits
203 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
2797e56666 | ||
|
11f27ab67a | ||
|
c5f8bf1891 | ||
|
66b57482bd | ||
|
dd9d914943 | ||
|
8b0b1af1f3 | ||
|
bdff19c718 | ||
|
eec4a91743 | ||
|
42caefbd1e | ||
|
3edf435fa9 | ||
|
32da43ad46 | ||
|
3f2d1e128e | ||
|
352ced1553 | ||
|
17a3fb2c52 | ||
|
2235729158 | ||
|
3e4d5d07e5 | ||
|
1857d65b77 | ||
|
38da171675 | ||
|
df076be15c | ||
|
190f030b93 | ||
|
411b873c84 | ||
|
50346fc5b4 | ||
|
e32a65bca4 | ||
|
ffff539545 | ||
|
4b3b6e37a9 | ||
|
620bcae4bb | ||
|
ab0fd44b46 | ||
|
b61f499f91 | ||
|
53d365db3a | ||
|
140c4f7fd6 | ||
|
5627a3b172 | ||
|
4795fa98a0 | ||
|
93453ba522 | ||
|
c6a9108474 | ||
|
c3ba805acf | ||
|
c0ce22a6b7 | ||
|
22183501fe | ||
|
2fbdab3235 | ||
|
804d3f79fd | ||
|
fb119cca4c | ||
|
31af5ea8c2 | ||
|
e1776d6093 | ||
|
33dd4bbf0e | ||
|
af343ac1f0 | ||
|
065807c180 | ||
|
9cd24feccc | ||
|
a2d1506915 | ||
|
54a32a5770 | ||
|
5b9abeb0b2 | ||
|
accfb2d1ac | ||
|
71d383c4db | ||
|
197ee9f5ff | ||
|
d51d159962 | ||
|
89b0eabd41 | ||
|
8d932d546a | ||
|
9ea3460e3d | ||
|
7bd4db60a8 | ||
|
42e1f35df2 | ||
|
179784da3e | ||
|
9ed0c870d1 | ||
|
77e288ee54 | ||
|
58adaa9110 | ||
|
d3a73945e7 | ||
|
caca407abd | ||
|
4fd7b2d8cd | ||
|
eaea6e3c54 | ||
|
0bb68c7723 | ||
|
52b2c0910c | ||
|
9a4bb7bff9 | ||
|
ab5450a125 | ||
|
bcce32423c | ||
|
c42d529016 | ||
|
cbea5077be | ||
|
cdc2cc1439 | ||
|
87819f21bf | ||
|
d1be56fbc1 | ||
|
14016a761d | ||
|
12a64c4c4d | ||
|
d922120f58 | ||
|
8e8e349e65 | ||
|
ccdf0fc077 | ||
|
8c66bcb1e1 | ||
|
77fb47183f | ||
|
d275dc36b2 | ||
|
7bff20cc70 | ||
|
29f5dcc359 | ||
|
14f2851072 | ||
|
a2b25f8246 | ||
|
a38951b5ad | ||
|
4c1b911cb7 | ||
|
6c9f231453 | ||
|
83daf3c30f | ||
|
9be8140d4d | ||
|
96c9b699aa | ||
|
3c0768a372 | ||
|
58b22e3d9e | ||
|
0474551e2f | ||
|
c85bdec396 | ||
|
5fa39eaa9f | ||
|
8eaaa35c7a | ||
|
1c24f95efa | ||
|
fcc49dbbdb | ||
|
3a317590b4 | ||
|
36c013fbb5 | ||
|
a9ec8049f6 | ||
|
ced0d97e3a | ||
|
24d0f57dc3 | ||
|
514ecd6be8 | ||
|
02eb6e172b | ||
|
d22c579875 | ||
|
f70e49fc6a | ||
|
8b410561f9 | ||
|
3c79fd1d6f | ||
|
e9f1a9b1dd | ||
|
3c293ae6db | ||
|
a5d9e7de66 | ||
|
4d9e48cd41 | ||
|
b7ead22e09 | ||
|
9f219cddbb | ||
|
cf9792f24a | ||
|
0187dd57ac | ||
|
2d2e54e31e | ||
|
cb7c5e48fd | ||
|
771c2745dc | ||
|
59c0f2f4b3 | ||
|
219ca39cd1 | ||
|
1e6d0806d7 | ||
|
71f1e43272 | ||
|
8499e1da70 | ||
|
a2ea806bed | ||
|
732b5dfeed | ||
|
d4dcdc761a | ||
|
57996ba290 | ||
|
4b29b3a239 | ||
|
54ac955395 | ||
|
f4fa298866 | ||
|
b2fafc964f | ||
|
22b452e449 | ||
|
fda385a5e4 | ||
|
c28f7cfa07 | ||
|
0a029a7847 | ||
|
c050ce2123 | ||
|
27613410dd | ||
|
1513008b4b | ||
|
bf97cffd84 | ||
|
e37d1c46db | ||
|
06c20c6fa4 | ||
|
aa518d60a5 | ||
|
d55ce7accc | ||
|
502c5cec07 | ||
|
ee5c13607b | ||
|
5a681a5194 | ||
|
68395372f0 | ||
|
c8e01bd158 | ||
|
1d57191700 | ||
|
02c7ded457 | ||
|
12c483d222 | ||
|
c80898a7bf | ||
|
aae2805785 | ||
|
fc3695d090 | ||
|
428429ff44 | ||
|
dc344caec6 | ||
|
2a4d55f81d | ||
|
d090aa23ee | ||
|
65062306c6 | ||
|
9ae3b66fc2 | ||
|
c4ba43ec6d | ||
|
1141791ce5 | ||
|
49f1ef7db0 | ||
|
a70c35e101 | ||
|
717543f6c2 | ||
|
b61b1dbfaa | ||
|
92365fd22d | ||
|
24a4745193 | ||
|
1af75fd813 | ||
|
18160164eb | ||
|
2fd7d97025 | ||
|
6ada15049d | ||
|
0ebc40b95c | ||
|
02de25a931 | ||
|
0b395e9176 | ||
|
4532f992cd | ||
|
34201f0558 | ||
|
d2f4d63183 | ||
|
b41c014869 | ||
|
d348347762 | ||
|
db7cf3d757 | ||
|
83ea046d5f | ||
|
d5c94424e9 | ||
|
ff95b3d00f | ||
|
4b5fa3bb04 | ||
|
52c9ec670d | ||
|
1ac472c676 | ||
|
39f01a3164 | ||
|
486916944b | ||
|
68e96cd1bb | ||
|
5a647d100a | ||
|
a562a571e2 | ||
|
12146ad2da | ||
|
453ac3efd2 | ||
|
c9e89e1911 | ||
|
611817f78a | ||
|
2e66137f3e |
@@ -9,6 +9,7 @@
|
||||
!src/NadekoBot.Generators/**
|
||||
# Use Ayu stuff
|
||||
!src/ayu/**
|
||||
!docker-entrypoint.sh
|
||||
|
||||
# ignore bin and obj folders in projects
|
||||
src/**/bin/*
|
||||
|
@@ -95,4 +95,31 @@ upload-windows-updater-release:
|
||||
- sed -i "s/_VERSION_/$CI_COMMIT_TAG/g" releases-v3.json
|
||||
- aws --version
|
||||
- aws --endpoint-url $AWS_SERVICE_URL s3api put-object --bucket "$AWS_BUCKET_NAME" --key "dl/bot/$INSTALLER_FILE_NAME" --acl public-read --body "$INSTALLER_OUTPUT_DIR/$INSTALLER_FILE_NAME"
|
||||
- aws --endpoint-url $AWS_SERVICE_URL s3api put-object --bucket "$AWS_BUCKET_NAME" --key "dl/bot/releases-v3.json" --acl public-read --body "releases-v3.json"
|
||||
- aws --endpoint-url $AWS_SERVICE_URL s3api put-object --bucket "$AWS_BUCKET_NAME" --key "dl/bot/releases-v3.json" --acl public-read --body "releases-v3.json"
|
||||
|
||||
docker-build:
|
||||
# Use the official docker image.
|
||||
image: docker:latest
|
||||
stage: build
|
||||
services:
|
||||
- docker:dind
|
||||
before_script:
|
||||
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
|
||||
# Default branch leaves tag empty (= latest tag)
|
||||
# All other branches are tagged with the escaped branch name (commit ref slug)
|
||||
script:
|
||||
- |
|
||||
if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then
|
||||
tag=""
|
||||
echo "Running on default branch '$CI_DEFAULT_BRANCH': tag = 'latest'"
|
||||
else
|
||||
tag=":$CI_COMMIT_REF_SLUG"
|
||||
echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
|
||||
fi
|
||||
- docker build --pull -t "$CI_REGISTRY_IMAGE${tag}" .
|
||||
- docker push "$CI_REGISTRY_IMAGE${tag}"
|
||||
# Run this job in a branch where a Dockerfile exists
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH
|
||||
exists:
|
||||
- Dockerfile
|
||||
|
124
CHANGELOG.md
124
CHANGELOG.md
@@ -2,17 +2,138 @@
|
||||
|
||||
Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
|
||||
|
||||
## Unreleased
|
||||
## [3.0.13] - 14.04.2021
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `.greetdm` causing ratelimits during raids
|
||||
- Fixed `.gelbooru`
|
||||
|
||||
## [3.0.12] - 06.01.2021
|
||||
|
||||
### Fixed
|
||||
- `.smch` Fixed
|
||||
- `.trans` command will now work properly with capitilized language names
|
||||
- Ban message color with plain text fixed
|
||||
- Fixed some grpc coordinator bugs
|
||||
- Fixed a string in `.xpex`
|
||||
- Google version of .img will now have safe search enabled
|
||||
- Fixed a small bug in `.hangman`
|
||||
|
||||
## [3.0.11] - 17.12.2021
|
||||
|
||||
### Added
|
||||
- `.remindl` and `.remindrm` commands now supports optional 'server' parameter for Administrators which allows them to delete any reminder created on the server
|
||||
- Added slots.currencyFontColor to gambling.yml
|
||||
- Added `.qexport` and `.qimport` commands which allow you to export and import quotes just like `.crsexport`
|
||||
- Added `.showembed <msgid>` and `.showembed #channel <msgid>` which will show you embed json from the specified message
|
||||
|
||||
### Changed
|
||||
- `.at` and `.atl` commands reworked
|
||||
- Persist restarts
|
||||
- Will now only translate non-commands
|
||||
- You can switch between `.at del` and `.at` without clearing the user language registrations
|
||||
- Disabling `.at` will clear all user language registrations on that channel
|
||||
- Users can't register languages if the `.at` is not enabled
|
||||
- Looks much nicer
|
||||
- Bot will now reply to user messages with a translation if `del` is disabled
|
||||
- Bot will make an embed with original and translated text with user avatar and name if `del` is enabled
|
||||
- If the bot is unable to delete messages while having `del` enabled, it will reset back to the no-del behavior for the current session
|
||||
|
||||
### Fixed
|
||||
- `.crypto` now supports top 5000 coins
|
||||
|
||||
## [3.0.10] - 01.12.2021
|
||||
|
||||
### Changed
|
||||
- `.warn` now supports weighted warnings
|
||||
- `.warnlog` will now show current amount and total amount of warnings
|
||||
|
||||
### Fixed
|
||||
- `.xprewsreset` now has correct permissions
|
||||
|
||||
### Removed
|
||||
- Removed slot.numbers from `images.yml` as they're no longer used
|
||||
|
||||
## [3.0.9] - 21.11.2021
|
||||
|
||||
### Changed
|
||||
- `.ea` will now use an image attachments if you omit imageUrl
|
||||
|
||||
### Added
|
||||
- Added `.emojiadd` with 3 overloads
|
||||
- `.ea :customEmoji:` which copies another server's emoji
|
||||
- `.ea newName :customEmoji:` which copies emoji under a different name
|
||||
- `.ea emojiName <imagelink.png>` which creates a new emoji from the specified image
|
||||
- Patreon Access and Refresh Tokens should now be automatically updated once a month as long as the user has provided the necessary credentials in creds.yml file:
|
||||
- `Patreon.ClientId`
|
||||
- `Patreon.RefreshToken` (will also get updated once a month but needs an initial value)
|
||||
- `Patreon.ClientSecret`
|
||||
- `Patreon.CampaignId`
|
||||
|
||||
### Fixed
|
||||
- Fixed an error that would show up in the console when a club image couldn't be drawn in certain circumstances
|
||||
|
||||
## [3.0.8] - 03.11.2021
|
||||
|
||||
### Added
|
||||
- Created VotesApi project nad re-worked vote rewards handling
|
||||
- Updated votes entries in creds.yml with explanations on how to set up vote links
|
||||
|
||||
### Fixed
|
||||
- Fixed adding currency to users who don't exist in the database
|
||||
- Memory used by the bot is now correct (thanks to kotz)
|
||||
- Ban/kick will no longer fail due to too long reasons
|
||||
- Fixed some fields not preserving inline after string replacements
|
||||
|
||||
### Changed
|
||||
- `images.json` moved to `images.yml`
|
||||
- Links will use the new cdn url
|
||||
- Heads and Tails images will be updated if you haven't changed them already
|
||||
- `.slot` redesigned (and updated entries in `images.yml`)
|
||||
- Reduced required permissions for .qdel (thanks to tbodt)
|
||||
|
||||
## [3.0.7] - 05.10.2021
|
||||
|
||||
### Added
|
||||
- `.streamsclear` re-added. It will remove all followed streams on the server.
|
||||
- `.gifts` now have 3 new ✂️ Haircut 🧻 ToiletPaper and 🥀 WiltedRose which **reduce** waifu's value
|
||||
- They are called negative gifts
|
||||
- They show up at the end of the `.gifts` page and are marked with a broken heart
|
||||
- They have a separate multiplier (`waifu.multi.negative_gift_effect` default 0.5, changeable via `.config gambling` or `data/gambling.yml`)
|
||||
- When gifted, the waifu's price will be reduced by the `price * multiplier`
|
||||
- Negative gifts don't show up in `.waifuinfo` nor is the record of them kept in the database
|
||||
|
||||
### Fixed
|
||||
- Fixed `%users%` and `%shard.usercount%` placeholders not showing correct values
|
||||
|
||||
## [3.0.6] - 27.09.2021
|
||||
|
||||
### Added
|
||||
|
||||
- .logignore now supports ignoring users and channels. Use without parameters to see the ignore list
|
||||
|
||||
### Changed
|
||||
|
||||
- Hangman rewrite
|
||||
- Hangman categories are now held in separate .yml files in data/hangman/XYZ.yml where XYZ is the category name
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed an exception which caused repeater queue to break
|
||||
- Fixed url field not working in embeds
|
||||
|
||||
## [3.0.5] - 20.09.2021
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed images not automatically reloading on startup if the keys don't exist
|
||||
- Fixed `.logserver` - it should no longer throw an exception if you had no logsettings previously
|
||||
|
||||
## [3.0.4] - 16.09.2021
|
||||
|
||||
### Added
|
||||
|
||||
- Fully translated to Brazilian Portuguese 🎉
|
||||
- Added `%server.boosters%` and `%server.boost_level%` placeholders
|
||||
- Added `DmHelpTextKeywords` to `data/bot.yml`
|
||||
@@ -20,6 +141,7 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.
|
||||
- If no keywords are specified, bot will reply to every DM (like before)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Possible fix for `.repeat` bug
|
||||
- Slight adjustment for repeater logic
|
||||
- Timer should no longer increase on some repeaters
|
||||
|
36
Dockerfile
36
Dockerfile
@@ -1,4 +1,4 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
|
||||
FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim AS build
|
||||
WORKDIR /source
|
||||
|
||||
COPY src/NadekoBot/*.csproj src/NadekoBot/
|
||||
@@ -9,14 +9,34 @@ RUN dotnet restore src/NadekoBot/
|
||||
|
||||
COPY . .
|
||||
WORKDIR /source/src/NadekoBot
|
||||
RUN dotnet --version
|
||||
RUN dotnet publish -c Release -o /app --no-restore
|
||||
RUN set -xe; \
|
||||
dotnet --version; \
|
||||
dotnet publish -c Release -o /app --no-restore; \
|
||||
mv /app/data /app/data_init; \
|
||||
rm -Rf libopus* libsodium* opus.* runtimes/win* runtimes/osx* runtimes/linux-arm* runtimes/linux-mips*; \
|
||||
find /app -type f -exec chmod -x {} \; ;\
|
||||
chmod +x /app/NadekoBot
|
||||
|
||||
# final stage/image
|
||||
FROM mcr.microsoft.com/dotnet/runtime:5.0
|
||||
FROM mcr.microsoft.com/dotnet/runtime:5.0-buster-slim
|
||||
WORKDIR /app
|
||||
|
||||
RUN set -xe; \
|
||||
useradd -m nadeko; \
|
||||
apt-get update; \
|
||||
apt-get install -y libopus0 libsodium23 libsqlite3-0 curl ffmpeg python3 python3-pip sudo; \
|
||||
update-alternatives --install /usr/bin/python python /usr/bin/python3.7 1; \
|
||||
echo 'Defaults>nadeko env_keep+="ASPNETCORE_* DOTNET_* NadekoBot_* shard_id total_shards TZ"' > /etc/sudoers.d/nadeko; \
|
||||
pip3 install --upgrade youtube-dl; \
|
||||
apt-get remove -y python3-pip; \
|
||||
chmod +x /usr/local/bin/youtube-dl
|
||||
|
||||
COPY --from=build /app ./
|
||||
COPY docker-entrypoint.sh /usr/local/sbin
|
||||
|
||||
ENV shard_id=0
|
||||
ENV total_shards=1
|
||||
WORKDIR /app
|
||||
COPY --from=build /app ./
|
||||
VOLUME [ "app/data", "app/creds.yml", "app/creds_example.yml" ]
|
||||
ENTRYPOINT dotnet NadekoBot.dll "$shard_id" "$total_shards"
|
||||
|
||||
VOLUME [ "app/data" ]
|
||||
ENTRYPOINT [ "/usr/local/sbin/docker-entrypoint.sh" ]
|
||||
CMD dotnet NadekoBot.dll "$shard_id" "$total_shards"
|
@@ -25,6 +25,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.Coordinator", "sr
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.Generators", "src\NadekoBot.Generators\NadekoBot.Generators.csproj", "{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.VotesApi", "src\NadekoBot.VotesApi\NadekoBot.VotesApi.csproj", "{3BC82CFE-BEE7-451F-986B-17EDD1570C4F}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -62,6 +64,12 @@ Global
|
||||
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3BC82CFE-BEE7-451F-986B-17EDD1570C4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3BC82CFE-BEE7-451F-986B-17EDD1570C4F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3BC82CFE-BEE7-451F-986B-17EDD1570C4F}.GlobalNadeko|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3BC82CFE-BEE7-451F-986B-17EDD1570C4F}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3BC82CFE-BEE7-451F-986B-17EDD1570C4F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3BC82CFE-BEE7-451F-986B-17EDD1570C4F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -73,6 +81,7 @@ Global
|
||||
{DB448DD4-C97F-40E9-8BD3-F605FF1FF833} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
||||
{AE9B7F8C-81D7-4401-83A3-643B38258374} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
||||
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
||||
{3BC82CFE-BEE7-451F-986B-17EDD1570C4F} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {5F3F555C-855F-4BE8-B526-D062D3E8ACA4}
|
||||
|
20
docker-entrypoint.sh
Executable file
20
docker-entrypoint.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
set -e;
|
||||
|
||||
data_init=/app/data_init
|
||||
data=/app/data
|
||||
|
||||
# populate /app/data if empty
|
||||
for i in $(ls $data_init)
|
||||
do
|
||||
if [ ! -e "$data/$i" ]; then
|
||||
[ -f "$data_init/$i" ] && cp "$data_init/$i" "$data/$i"
|
||||
[ -d "$data_init/$i" ] && cp -r "$data_init/$i" "$data/$i"
|
||||
fi
|
||||
done
|
||||
|
||||
# fix folder permissions
|
||||
chown -R nadeko:nadeko "$data"
|
||||
|
||||
# drop to regular user and launch command
|
||||
exec sudo -u nadeko "$@"
|
@@ -13,8 +13,13 @@ This document aims to guide you through the process of creating a Discord accoun
|
||||
- Click on the `Add a Bot` button and confirm that you do want to add a bot to this app.
|
||||
- **Optional:** Add bot's avatar and description.
|
||||
- Copy your Token to `creds.yml` as shown above.
|
||||
- Scroll down to the `Privileged Gateway Intents` section and enable both intents.
|
||||
These are required for a number of features to function properly, and should both be on.
|
||||
- Scroll down to the `Privileged Gateway Intents` section
|
||||
- Enabled the following:
|
||||
- PRESENCE INTENT
|
||||
- SERVER MEMBERS INTENT
|
||||
- MESSAGE CONTENT INTENT
|
||||
|
||||
These are required for a number of features to function properly, and all should be on.
|
||||
|
||||
##### Getting Owner ID*(s)*:
|
||||
|
||||
@@ -32,7 +37,7 @@ For a single owner, it should look like this:
|
||||
- 105635576866156544
|
||||
```
|
||||
|
||||
For multiple owners, it should look like this (pay attention to the commas, the last ID should **never** have a comma next to it):
|
||||
For multiple owners, it should look like this:
|
||||
|
||||
```yml
|
||||
OwnerIds:
|
||||
@@ -56,4 +61,4 @@ For multiple owners, it should look like this (pay attention to the commas, the
|
||||
|
||||
That's it! You may now go back to the installation guide you were following before 🎉
|
||||
|
||||
[DiscordApp]: https://discordapp.com/developers/applications/me
|
||||
[DiscordApp]: https://discordapp.com/developers/applications/me
|
||||
|
@@ -4,6 +4,19 @@
|
||||
|
||||
#### [Linux migration instructions](../migration-guide/#linux)
|
||||
|
||||
#### Operating System Compatibility
|
||||
|
||||
It is recommended that you use **Ubuntu 20.04**, as there have been nearly no problems with it. Also, **32-bit systems are incompatible**.
|
||||
|
||||
##### Compatible operating systems:
|
||||
|
||||
- Ubuntu: 16.04, 18.04, 20.04, 21.04, 21.10
|
||||
- Mint: 19, 20
|
||||
- Debian: 9, 10
|
||||
- CentOS: 7
|
||||
- openSUSE
|
||||
- Fedora: 33, 34, 35
|
||||
|
||||
## Linux From Source
|
||||
|
||||
Open Terminal (if you're on an installation with a window manager) and navigate to the location where you want to install the bot (for example `cd ~`)
|
||||
@@ -13,11 +26,11 @@ Open Terminal (if you're on an installation with a window manager) and navigate
|
||||
1. Download and run the **new** installer script `cd ~ && wget -N https://gitlab.com/Kwoth/nadeko-bash-installer/-/raw/master/linuxAIO.sh && bash linuxAIO.sh`
|
||||
2. Install prerequisites (type `1` and press enter)
|
||||
3. Download the bot (type `2` and press enter)
|
||||
4. Exit the installer in order to set up your `creds.yml`
|
||||
4. Exit the installer (type `5` and press enter)
|
||||
5. Copy the creds.yml template `cp nadekobot/output/creds_example.yml nadekobot/output/creds.yml`
|
||||
6. Open `nadekobot/output/creds.yml` with your favorite text editor. We will use nano here
|
||||
- `nano nadekobot/output/creds.yml`
|
||||
7. [Enter your bot's token](../../creds-guide)
|
||||
7. [Click here to follow creds guide](../../creds-guide)
|
||||
- After you're done, you can close nano (and save the file) by inputting, in order
|
||||
- `CTRL` + `X`
|
||||
- `Y`
|
||||
@@ -34,6 +47,8 @@ Open Terminal (if you're on an installation with a window manager) and navigate
|
||||
|
||||
## Linux Release
|
||||
|
||||
⚠ IF YOU ARE FOLLOWING THE GUIDE ABOVE, IGNORE THIS SECTION ⚠
|
||||
|
||||
##### Installation Instructions
|
||||
|
||||
1. Download the latest release from <https://gitlab.com/Kwoth/nadekobot/-/releases>
|
||||
@@ -48,8 +63,8 @@ Open Terminal (if you're on an installation with a window manager) and navigate
|
||||
5. Copy the creds.yml template
|
||||
- `cp creds_example.yml creds.yml`
|
||||
6. Open `creds.yml` with your favorite text editor. We will use nano here
|
||||
- `nano nadekobot/output/creds.yml`
|
||||
8. [Enter your bot's token](#creds-guide)
|
||||
- `nano creds.yml`
|
||||
8. [Click here to follow creds guide](../../creds-guide)
|
||||
- After you're done, you can close nano (and save the file) by inputting, in order
|
||||
- `CTRL` + `X`
|
||||
- `Y`
|
||||
@@ -134,6 +149,8 @@ Compared to using tmux, this method requires a little bit more work to set up, b
|
||||
echo "[Unit]
|
||||
Description=NadekoBot service
|
||||
After=network.target
|
||||
StartLimitIntervalSec=60
|
||||
StartLimitBurst=2
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
@@ -144,10 +161,11 @@ Compared to using tmux, this method requires a little bit more work to set up, b
|
||||
# source code.
|
||||
#ExecStartPre=/usr/bin/dotnet build ../src/NadekoBot/NadekoBot.csproj -c Release -o output/
|
||||
ExecStart=/usr/bin/dotnet NadekoBot.dll
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
StandardOutput=syslog
|
||||
StandardError=syslog
|
||||
SyslogIdentifier=NadekoBot
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target" | sudo tee /etc/systemd/system/nadeko.service
|
||||
@@ -172,12 +190,16 @@ This method is similar to the one above, but requires one extra step, with the a
|
||||
echo "[Unit]
|
||||
Description=NadekoBot service
|
||||
After=network.target
|
||||
StartLimitIntervalSec=60
|
||||
StartLimitBurst=2
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=$USER
|
||||
WorkingDirectory=$PWD
|
||||
WorkingDirectory=$_WORKING_DIR
|
||||
ExecStart=/bin/bash NadekoRun.sh
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
StandardOutput=syslog
|
||||
StandardError=syslog
|
||||
SyslogIdentifier=NadekoBot
|
||||
@@ -191,14 +213,14 @@ This method is similar to the one above, but requires one extra step, with the a
|
||||
4. Use the following command to create a script that will be used to start Nadeko:
|
||||
|
||||
```bash
|
||||
echo "#\!/bin/bash
|
||||
|
||||
echo \"\"
|
||||
echo \"Running NadekoBot in the background with auto restart\"
|
||||
{
|
||||
echo '#!/bin/bash'
|
||||
echo ""
|
||||
echo "echo \"Running NadekoBot in the background with auto restart\"
|
||||
youtube-dl -U
|
||||
|
||||
# If you want Nadeko to be compiled prior to every startup, uncomment the lines
|
||||
# below. Note that it's not neccessary unless you are personally modifying the
|
||||
# below. Note that it's not necessary unless you are personally modifying the
|
||||
# source code.
|
||||
#echo \"Compiling NadekoBot...\"
|
||||
#cd \"$PWD\"/nadekobot
|
||||
@@ -207,23 +229,64 @@ This method is similar to the one above, but requires one extra step, with the a
|
||||
echo \"Starting NadekoBot...\"
|
||||
|
||||
while true; do
|
||||
{
|
||||
cd \"$PWD\"/nadekobot/output
|
||||
dotnet NadekoBot.dll
|
||||
## If a non-zero exit code is produced, exit this script.
|
||||
} || {
|
||||
error_code=\"\$?\"
|
||||
if [[ -d $PWD/nadekobot/output ]]; then
|
||||
cd $PWD/nadekobot/output || {
|
||||
echo \"Failed to change working directory to $PWD/nadekobot/output\" >&2
|
||||
echo \"Ensure that the working directory inside of '/etc/systemd/system/nadeko.service' is correct\"
|
||||
echo \"Exiting...\"
|
||||
exit 1
|
||||
}
|
||||
else
|
||||
echo \"$PWD/nadekobot/output doesn't exist\"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dotnet NadekoBot.dll || {
|
||||
echo \"An error occurred when trying to start NadekBot\"
|
||||
echo \"EXIT CODE: \$?\"
|
||||
exit \"\$error_code\"
|
||||
echo \"Exiting...\"
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
||||
echo \"Waiting for 5 seconds...\"
|
||||
sleep 5
|
||||
youtube-dl -U
|
||||
echo \"Restarting NadekoBot...\"
|
||||
done
|
||||
|
||||
echo \"Stopping NadekoBot...\"" > NadekoRun.sh
|
||||
echo \"Stopping NadekoBot...\""
|
||||
} > NadekoRun.sh
|
||||
```
|
||||
|
||||
5. Start Nadeko:
|
||||
- `sudo systemctl start nadeko.service && sudo systemctl enable nadeko.service`
|
||||
- `sudo systemctl start nadeko.service && sudo systemctl enable nadeko.service`
|
||||
|
||||
### Setting up Nadeko on a Linux VPS (Digital Ocean Droplet)
|
||||
|
||||
If you want Nadeko to play music for you 24/7 without having to hosting it on your PC and want to keep it cheap, reliable and convenient as possible, you can try Nadeko on Linux Digital Ocean Droplet using the link [DigitalOcean](http://m.do.co/c/46b4d3d44795/) (by using this link, you will get **$10 credit** and also support Nadeko)
|
||||
|
||||
**Setting up NadekoBot**
|
||||
Assuming you have followed the link above to setup an account and a Droplet with a 64-bit operational system on Digital Ocean and got the `IP address and root password (in your e-mail)` to login, it's time to get started.
|
||||
|
||||
**This section is only relevant to those who want to host Nadeko on DigitalOcean. Go through this whole section before setting the bot up.**
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
- Download [PuTTY](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html)
|
||||
- Download [WinSCP](https://winscp.net/eng/download.php) *(optional)*
|
||||
- [Create and invite the bot](../../creds-guide).
|
||||
|
||||
#### Starting up
|
||||
|
||||
- **Open PuTTY** and paste or enter your `IP address` and then click **Open**.
|
||||
If you entered your Droplets IP address correctly, it should show **login as:** in a newly opened window.
|
||||
- Now for **login as:**, type `root` and press enter.
|
||||
- It should then ask for a password. Type the `root password` you have received in your e-mail address, then press Enter.
|
||||
|
||||
If you are running your droplet for the first time, it will most likely ask you to change your root password. To do that, copy the **password you've received by e-mail** and paste it on PuTTY.
|
||||
|
||||
- To paste, just right-click the window (it won't show any changes on the screen), then press Enter.
|
||||
- Type a **new password** somewhere, copy and paste it on PuTTY. Press Enter then paste it again.
|
||||
|
||||
**Save the new password somewhere safe.**
|
||||
|
||||
After that, your droplet should be ready for use. [Follow the guide from the beginning](#linux-from-source) to set Nadeko up on your newly created VPS.
|
||||
|
@@ -15,9 +15,9 @@
|
||||
|
||||
## Linux
|
||||
|
||||
1. In order to migrate a bot hosted on **Linux**, first update your current version to the latest 2.x version using the 2.x installer, run the bot, and make sure it works. Then:
|
||||
- Run the **old** installer with `cd ~ && wget -N https://github.com/Kwoth/NadekoBot-BashScript/raw/1.9/linuxAIO.sh && bash linuxAIO.sh`
|
||||
- Run option **1** again
|
||||
1. In order to migrate a bot hosted on **Linux**, first **BACKUP** then update your current version to the latest **2.x** version using the **2.x** installer, run the bot, and make sure it works. Then:
|
||||
- Run the **old** installer with `cd ~ && wget -N https://gitlab.com/hokutochen/becausekwoth/-/raw/main/linuxAIO.sh && bash linuxAIO.sh`
|
||||
- Run option **2**
|
||||
- You **MUST** Run the bot now to ensure database is ready for migration
|
||||
- Type `.stats` and ensure the version is `2.46.5` or later
|
||||
- Stop the bot
|
||||
|
@@ -2,13 +2,32 @@
|
||||
|
||||
Open Terminal (if you don't know how to, click on the magnifying glass on the top right corner of your screen and type **Terminal** on the window that pops up) and navigate to the location where you want to install the bot (for example `cd ~`)
|
||||
|
||||
##### Installing Homebrew and wget
|
||||
##### Installing Homebrew, wget and dotnet
|
||||
|
||||
###### Homebrew/wget
|
||||
*Skip this step if you already have homebrew installed*
|
||||
- Copy and paste this command, then press Enter:
|
||||
- `/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"`
|
||||
- Install wget
|
||||
- `brew install wget`
|
||||
- `brew install wget`
|
||||
|
||||
###### Dotnet
|
||||
- Download [.net5 SDK](https://dotnet.microsoft.com/download/dotnet/5.0)
|
||||
- Open the `.pkg` file you've downloaded and install it.
|
||||
- Run this command in Terminal. There might be output. If there is, disregard it. (copy-paste the entire block)
|
||||
```bash
|
||||
sudo mkdir /usr/local/bin
|
||||
|
||||
sudo mkdir /usr/local/lib
|
||||
```
|
||||
- Run this command in Terminal. There won't be any output. (copy-paste the entire block):
|
||||
```bash
|
||||
sudo ln -s /usr/local/share/dotnet/dotnet /usr/local/bin
|
||||
|
||||
sudo ln -s /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/
|
||||
|
||||
sudo ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/
|
||||
```
|
||||
|
||||
##### Installation Instructions
|
||||
|
||||
@@ -37,6 +56,8 @@ Open Terminal (if you don't know how to, click on the magnifying glass on the to
|
||||
|
||||
## MacOS Manual Release installation instructions
|
||||
|
||||
⚠ IF YOU ARE FOLLOWING THE GUIDE ABOVE, IGNORE THIS SECTION ⚠
|
||||
|
||||
##### Installation Instructions
|
||||
|
||||
1. Download the latest release from <https://gitlab.com/Kwoth/nadekobot/-/releases>
|
||||
@@ -101,4 +122,4 @@ rm -r nadekobot-old/data/strings && \
|
||||
cp -RT nadekobot-old/data/ nadekobot/data/ && \
|
||||
cp nadekobot-old/creds.yml nadekobot/ && \
|
||||
cd nadekobot && chmod +x NadekoBot
|
||||
```
|
||||
```
|
||||
|
@@ -12,16 +12,16 @@
|
||||
| [Setup](#setup) |
|
||||
| [Starting the Bot](#starting-the-bot) |
|
||||
| [Updating Nadeko](#updating-nadeko) |
|
||||
| [Manually Installing the Prerequisites from the Updater](#if-the-updater-fails-to-install-the-prerequisites-for-any-reason) |
|
||||
| [Manually Installing the Prerequisites from the Updater](#music-prerequisites) |
|
||||
|
||||
*Note: If you want to make changes to Nadeko's source code, please follow the [From Source][SourceGuide] guide instead.*
|
||||
*Note: If you want to make changes to Nadeko's source code, please follow the [From Source](#windows-from-source) guide instead.*
|
||||
|
||||
*If you have Windows 7 or a 32-bit system, please refer to the [From Source][SourceGuide] guide.*
|
||||
*If you have Windows 7 or a 32-bit system, please refer to the [From Source](#windows-from-source)) guide.*
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
- Windows 8 or later (64-bit)
|
||||
- [Create a Discord Bot application and invite the bot to your server](../../creds-guide.md)
|
||||
- [Create a Discord Bot application and invite the bot to your server](../creds-guide.md)
|
||||
|
||||
**Optional**
|
||||
|
||||
@@ -32,12 +32,13 @@
|
||||
|
||||
- Download and run the [NadekoBot v3 Updater][Updater].
|
||||
- Click on the + at the top left to create a new bot.
|
||||

|
||||

|
||||
- Give your bot a name and then click **`Go to setup`** at the lower right.
|
||||

|
||||

|
||||
- Click on **`DOWNLOAD`** at the lower right
|
||||

|
||||

|
||||
- Click on **`Install`** next to **`Redis`**.
|
||||
- Note: If Redis fails to install, install Redis manually here: [Redis Installer](https://github.com/MicrosoftArchive/redis/releases/tag/win-3.0.504) Download and run the **`.msi`** file.
|
||||
- If you will use the music module, click on **`Install`** next to **`FFMPEG`** and **`Youtube-DL`**.
|
||||
- If any dependencies fail to install, you can temporarily disable your Windows Defender/AV until you install them. If you don't want to, then read [the last section of this guide](#Manual-Prerequisite-Installation).
|
||||
- When installation is finished, click on **`CREDS`** to the left of **`RUN`** at the lower right.
|
||||
@@ -68,6 +69,8 @@ You can still install them manually:
|
||||
|
||||
### Windows From Source
|
||||
|
||||
⚠ IF YOU ARE FOLLOWING THE GUIDE ABOVE, IGNORE THIS SECTION ⚠
|
||||
|
||||
##### Prerequisites
|
||||
|
||||
**Install these before proceeding or your bot will not work!**
|
||||
@@ -82,11 +85,12 @@ Open PowerShell (press windows button on your keyboard and type powershell, it s
|
||||
1. `git clone https://gitlab.com/kwoth/nadekobot -b v3 --depth 1`
|
||||
2. `cd nadekobot`
|
||||
3. `dotnet publish -c Release -o output/ src/NadekoBot/`
|
||||
4. `cd output && cp creds_example.yml creds.yml`
|
||||
5. Open `creds.yml` with your favorite text editor (Please don't use Notepad or WordPad. You can use Notepad++, VSCode, Atom, Sublime, or something similar)
|
||||
6. [Enter your bot's token](#creds-guide)
|
||||
7. Run the bot `dotnet NadekoBot.dll`
|
||||
8. 🎉
|
||||
4. `cd output`
|
||||
5. `cp creds_example.yml creds.yml`
|
||||
6. Open `creds.yml` with your favorite text editor (Please don't use Notepad or WordPad. You can use Notepad++, VSCode, Atom, Sublime, or something similar)
|
||||
7. [Enter your bot's token](#creds-guide)
|
||||
8. Run the bot `dotnet NadekoBot.dll`
|
||||
9. 🎉
|
||||
|
||||
##### Update Instructions
|
||||
|
||||
@@ -128,8 +132,6 @@ In order to use music commands, you need ffmpeg and youtube-dl installed.
|
||||
[Redis]: https://github.com/MicrosoftArchive/redis/releases/download/win-3.0.504/Redis-x64-3.0.504.msi
|
||||
[Visual C++ 2010 (x86)]: https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x86.exe
|
||||
[Visual C++ 2017 (x64)]: https://aka.ms/vs/15/release/vc_redist.x64.exe
|
||||
[SourceGuide]: ../from-source
|
||||
[ffmpeg-32bit]: https://cdn.nadeko.bot/dl/ffmpeg-32.zip
|
||||
[ffmpeg-64bit]: https://cdn.nadeko.bot/dl/ffmpeg-64.zip
|
||||
[youtube-dl]: https://yt-dl.org/downloads/latest/youtube-dl.exe
|
||||
|
||||
|
@@ -8,8 +8,14 @@ This part is completely optional, **however it's necessary for music and a few o
|
||||
- Go to [Google Console][Google Console] and log in.
|
||||
- Create a new project (name does not matter).
|
||||
- Once the project is created, go into `Library`
|
||||
- Under the `YouTube APIs` section, enable `YouTube Data API`
|
||||
- On the left tab, access `Credentials`,
|
||||
- Under the `YouTube APIs` section
|
||||
- Select `YouTube Data API v3`,
|
||||
- Click enable.
|
||||
- Search for `Custom Search API`
|
||||
- Select `Custom Search API`,
|
||||
- Click enable.
|
||||
- Open up the `Navigation menu` on the top right with the three lines.
|
||||
- select `APIs & Services`, then select `Credentials`,
|
||||
- Click `Create Credentials` button,
|
||||
- Click on `API Key`
|
||||
- A new window will appear with your `Google API key`
|
||||
@@ -18,7 +24,7 @@ This part is completely optional, **however it's necessary for music and a few o
|
||||
- Open up `creds.yml` and look for `GoogleAPIKey`, paste your API key after the `:`.
|
||||
- It should look like this:
|
||||
```yml
|
||||
GoogleApiKey: "AIzaSyDSci1sdlWQOWNVj1vlXxxxxxbk0oWMEzM"
|
||||
GoogleApiKey: AIzaSyDSci1sdlWQOWNVj1vlXxxxxxbk0oWMEzM
|
||||
```
|
||||
- **MashapeKey**
|
||||
- Required for Hearthstone cards.
|
||||
@@ -69,6 +75,7 @@ For Windows (Updater), add this to your `creds.yml`
|
||||
```yml
|
||||
RestartCommand:
|
||||
Cmd: "NadekoBot.exe"
|
||||
args: "{0}"
|
||||
```
|
||||
|
||||
For Windows (Source), Linux or OSX, add this to your `creds.yml`
|
||||
@@ -92,11 +99,10 @@ version: 1
|
||||
token: 'MTE5Nzc3MDIxMzE5NTc3NjEw.VlhNCw.BuqJFyzdIUAK1PRf1eK1Cu89Jew'
|
||||
# List of Ids of the users who have bot owner permissions
|
||||
# **DO NOT ADD PEOPLE YOU DON'T TRUST**
|
||||
ownerIds: [
|
||||
105635123466156544,
|
||||
145521851676884992,
|
||||
341420590009417729
|
||||
]
|
||||
ownerIds:
|
||||
- 105635123466156544
|
||||
- 145521851676884992
|
||||
- 341420590009417729
|
||||
# The number of shards that the bot will running on.
|
||||
# Leave at 1 if you don't know what you're doing.
|
||||
totalShards: 1
|
||||
@@ -156,8 +162,8 @@ osuApiKey: 4c8c8fdffdsfdsfsdfsfa33f3f3140a7d93320d6
|
||||
# cmd: dotnet
|
||||
# args: "NadekoBot.dll -- {0}"
|
||||
# Windows default
|
||||
# cmd: NadekoBot.exe
|
||||
# args: {0}
|
||||
# cmd: "NadekoBot.exe"
|
||||
# args: "{0}"
|
||||
restartCommand:
|
||||
cmd:
|
||||
args:
|
||||
|
@@ -9,9 +9,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Grpc.AspNetCore" Version="2.38.0" />
|
||||
<PackageReference Include="Grpc.AspNetCore" Version="2.41.0" />
|
||||
<PackageReference Include="Serilog" Version="2.10.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
|
||||
<PackageReference Include="YamlDotNet" Version="11.2.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@@ -160,18 +160,24 @@ namespace NadekoBot.Coordinator
|
||||
private void StartShard(int shardId)
|
||||
{
|
||||
var status = _shardStatuses[shardId];
|
||||
if (status.Process is {HasExited: false} p)
|
||||
try
|
||||
{
|
||||
try
|
||||
if (status.Process is { HasExited: false } p)
|
||||
{
|
||||
p.Kill(true);
|
||||
try
|
||||
{
|
||||
p.Kill(true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
status.Process?.Dispose();
|
||||
status.Process?.Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
var proc = StartShardProcess(shardId);
|
||||
_shardStatuses[shardId] = status with
|
||||
|
@@ -8,7 +8,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
25
src/NadekoBot.VotesApi/.dockerignore
Normal file
25
src/NadekoBot.VotesApi/.dockerignore
Normal file
@@ -0,0 +1,25 @@
|
||||
**/.dockerignore
|
||||
**/.env
|
||||
**/.git
|
||||
**/.gitignore
|
||||
**/.project
|
||||
**/.settings
|
||||
**/.toolstarget
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/.idea
|
||||
**/*.*proj.user
|
||||
**/*.dbmdl
|
||||
**/*.jfm
|
||||
**/azds.yaml
|
||||
**/bin
|
||||
**/charts
|
||||
**/docker-compose*
|
||||
**/Dockerfile*
|
||||
**/node_modules
|
||||
**/npm-debug.log
|
||||
**/obj
|
||||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
LICENSE
|
||||
README.md
|
1
src/NadekoBot.VotesApi/.gitignore
vendored
Normal file
1
src/NadekoBot.VotesApi/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
store/
|
44
src/NadekoBot.VotesApi/Common/AuthHandler.cs
Normal file
44
src/NadekoBot.VotesApi/Common/AuthHandler.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NadekoBot.VotesApi.Controllers;
|
||||
|
||||
namespace NadekoBot.VotesApi
|
||||
{
|
||||
public class AuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
|
||||
{
|
||||
public const string SchemeName = "AUTHORIZATION_SCHEME";
|
||||
public const string DiscordsClaim = "DISCORDS_CLAIM";
|
||||
public const string TopggClaim = "TOPGG_CLAIM";
|
||||
|
||||
private readonly IConfiguration _conf;
|
||||
|
||||
public AuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||
ILoggerFactory logger,
|
||||
UrlEncoder encoder,
|
||||
ISystemClock clock,
|
||||
IConfiguration conf)
|
||||
: base(options, logger, encoder, clock)
|
||||
{
|
||||
_conf = conf;
|
||||
}
|
||||
|
||||
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
var claims = new List<Claim>();
|
||||
|
||||
if (_conf[ConfKeys.DISCORDS_KEY].Trim() == Request.Headers["Authorization"].ToString().Trim())
|
||||
claims.Add(new(DiscordsClaim, "true"));
|
||||
|
||||
if (_conf[ConfKeys.TOPGG_KEY] == Request.Headers["Authorization"].ToString().Trim())
|
||||
claims.Add(new Claim(TopggClaim, "true"));
|
||||
|
||||
return Task.FromResult(AuthenticateResult.Success(new(new(new ClaimsIdentity(claims)), SchemeName)));
|
||||
}
|
||||
}
|
||||
}
|
8
src/NadekoBot.VotesApi/Common/ConfKeys.cs
Normal file
8
src/NadekoBot.VotesApi/Common/ConfKeys.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace NadekoBot.VotesApi
|
||||
{
|
||||
public static class ConfKeys
|
||||
{
|
||||
public const string DISCORDS_KEY = "DiscordsKey";
|
||||
public const string TOPGG_KEY = "TopGGKey";
|
||||
}
|
||||
}
|
26
src/NadekoBot.VotesApi/Common/DiscordsVoteWebhookModel.cs
Normal file
26
src/NadekoBot.VotesApi/Common/DiscordsVoteWebhookModel.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace NadekoBot.VotesApi
|
||||
{
|
||||
public class DiscordsVoteWebhookModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The ID of the user who voted
|
||||
/// </summary>
|
||||
public string User { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the bot which recieved the vote
|
||||
/// </summary>
|
||||
public string Bot { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Contains totalVotes, votesMonth, votes24, hasVoted - a list of IDs of users who have voted this month, and
|
||||
/// Voted24 - a list of IDs of users who have voted today
|
||||
/// </summary>
|
||||
public string Votes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of event, whether it is a vote event or test event
|
||||
/// </summary>
|
||||
public string Type { get; set; }
|
||||
}
|
||||
}
|
8
src/NadekoBot.VotesApi/Common/Policies.cs
Normal file
8
src/NadekoBot.VotesApi/Common/Policies.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace NadekoBot.VotesApi
|
||||
{
|
||||
public static class Policies
|
||||
{
|
||||
public const string DiscordsAuth = "DiscordsAuth";
|
||||
public const string TopggAuth = "TopggAuth";
|
||||
}
|
||||
}
|
30
src/NadekoBot.VotesApi/Common/TopggVoteWebhookModel.cs
Normal file
30
src/NadekoBot.VotesApi/Common/TopggVoteWebhookModel.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
namespace NadekoBot.VotesApi
|
||||
{
|
||||
public class TopggVoteWebhookModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Discord ID of the bot that received a vote.
|
||||
/// </summary>
|
||||
public string Bot { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Discord ID of the user who voted.
|
||||
/// </summary>
|
||||
public string User { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of the vote (should always be "upvote" except when using the test button it's "test").
|
||||
/// </summary>
|
||||
public string Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the weekend multiplier is in effect, meaning users votes count as two.
|
||||
/// </summary>
|
||||
public bool Weekend { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Query string params found on the /bot/:ID/vote page. Example: ?a=1&b=2.
|
||||
/// </summary>
|
||||
public string Query { get; set; }
|
||||
}
|
||||
}
|
33
src/NadekoBot.VotesApi/Controllers/DiscordsController.cs
Normal file
33
src/NadekoBot.VotesApi/Controllers/DiscordsController.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NadekoBot.VotesApi.Services;
|
||||
|
||||
namespace NadekoBot.VotesApi.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("[controller]")]
|
||||
public class DiscordsController : ControllerBase
|
||||
{
|
||||
private readonly ILogger<TopGgController> _logger;
|
||||
private readonly IVotesCache _cache;
|
||||
|
||||
public DiscordsController(ILogger<TopGgController> logger, IVotesCache cache)
|
||||
{
|
||||
_logger = logger;
|
||||
_cache = cache;
|
||||
}
|
||||
|
||||
[HttpGet("new")]
|
||||
[Authorize(Policy = Policies.DiscordsAuth)]
|
||||
public async Task<IEnumerable<Vote>> New()
|
||||
{
|
||||
var votes = await _cache.GetNewDiscordsVotesAsync();
|
||||
if(votes.Count > 0)
|
||||
_logger.LogInformation("Sending {NewDiscordsVotes} new discords votes.", votes.Count);
|
||||
return votes;
|
||||
}
|
||||
}
|
||||
}
|
34
src/NadekoBot.VotesApi/Controllers/TopGgController.cs
Normal file
34
src/NadekoBot.VotesApi/Controllers/TopGgController.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NadekoBot.VotesApi.Services;
|
||||
|
||||
namespace NadekoBot.VotesApi.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("[controller]")]
|
||||
public class TopGgController : ControllerBase
|
||||
{
|
||||
private readonly ILogger<TopGgController> _logger;
|
||||
private readonly IVotesCache _cache;
|
||||
|
||||
public TopGgController(ILogger<TopGgController> logger, IVotesCache cache)
|
||||
{
|
||||
_logger = logger;
|
||||
_cache = cache;
|
||||
}
|
||||
|
||||
[HttpGet("new")]
|
||||
[Authorize(Policy = Policies.TopggAuth)]
|
||||
public async Task<IEnumerable<Vote>> New()
|
||||
{
|
||||
var votes = await _cache.GetNewTopGgVotesAsync();
|
||||
if(votes.Count > 0)
|
||||
_logger.LogInformation("Sending {NewTopggVotes} new topgg votes.", votes.Count);
|
||||
|
||||
return votes;
|
||||
}
|
||||
}
|
||||
}
|
52
src/NadekoBot.VotesApi/Controllers/WebhookController.cs
Normal file
52
src/NadekoBot.VotesApi/Controllers/WebhookController.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NadekoBot.VotesApi.Services;
|
||||
|
||||
namespace NadekoBot.VotesApi.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
public class WebhookController : ControllerBase
|
||||
{
|
||||
private readonly ILogger<WebhookController> _logger;
|
||||
private readonly IVotesCache _votesCache;
|
||||
private readonly IConfiguration _conf;
|
||||
|
||||
public WebhookController(ILogger<WebhookController> logger, IVotesCache votesCache, IConfiguration conf)
|
||||
{
|
||||
_logger = logger;
|
||||
_votesCache = votesCache;
|
||||
_conf = conf;
|
||||
}
|
||||
|
||||
[HttpPost("/discordswebhook")]
|
||||
[Authorize(Policy = Policies.DiscordsAuth)]
|
||||
public async Task<IActionResult> DiscordsWebhook([FromBody]DiscordsVoteWebhookModel data)
|
||||
{
|
||||
|
||||
_logger.LogInformation("User {UserId} has voted for Bot {BotId} on {Platform}",
|
||||
data.User,
|
||||
data.Bot,
|
||||
"discords.com");
|
||||
|
||||
await _votesCache.AddNewDiscordsVote(data.User);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpPost("/topggwebhook")]
|
||||
[Authorize(Policy = Policies.TopggAuth)]
|
||||
public async Task<IActionResult> TopggWebhook([FromBody] TopggVoteWebhookModel data)
|
||||
{
|
||||
_logger.LogInformation("User {UserId} has voted for Bot {BotId} on {Platform}",
|
||||
data.User,
|
||||
data.Bot,
|
||||
"top.gg");
|
||||
|
||||
await _votesCache.AddNewTopggVote(data.User);
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
}
|
20
src/NadekoBot.VotesApi/Dockerfile
Normal file
20
src/NadekoBot.VotesApi/Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
|
||||
WORKDIR /app
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
|
||||
WORKDIR /src
|
||||
COPY ["src/NadekoBot.VotesApi/NadekoBot.VotesApi.csproj", "NadekoBot.VotesApi/"]
|
||||
RUN dotnet restore "src/NadekoBot.VotesApi/NadekoBot.VotesApi.csproj"
|
||||
COPY . .
|
||||
WORKDIR "/src/NadekoBot.VotesApi"
|
||||
RUN dotnet build "NadekoBot.VotesApi.csproj" -c Release -o /app/build
|
||||
|
||||
FROM build AS publish
|
||||
RUN dotnet publish "NadekoBot.VotesApi.csproj" -c Release -o /app/publish
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
ENTRYPOINT ["dotnet", "NadekoBot.VotesApi.dll"]
|
13
src/NadekoBot.VotesApi/NadekoBot.VotesApi.csproj
Normal file
13
src/NadekoBot.VotesApi/NadekoBot.VotesApi.csproj
Normal file
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MorseCode.ITask" Version="2.0.3" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
23
src/NadekoBot.VotesApi/Program.cs
Normal file
23
src/NadekoBot.VotesApi/Program.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace NadekoBot.VotesApi
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CreateHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
|
||||
}
|
||||
}
|
31
src/NadekoBot.VotesApi/Properties/launchSettings.json
Normal file
31
src/NadekoBot.VotesApi/Properties/launchSettings.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:16451",
|
||||
"sslPort": 44323
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"NadekoBot.VotesApi": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": "true",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
46
src/NadekoBot.VotesApi/README.md
Normal file
46
src/NadekoBot.VotesApi/README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
## Votes Api
|
||||
|
||||
This api is used if you want your bot to be able to reward users who vote for it on discords.com or top.gg
|
||||
|
||||
#### [GET] `/discords/new`
|
||||
Get the discords votes received after previous call to this endpoint.
|
||||
Input full url of this endpoint in your creds.yml file under Discords url field.
|
||||
For example "https://api.my.cool.bot/discords/new"
|
||||
#### [GET] `/topgg/new`
|
||||
Get the topgg votes received after previous call to this endpoint.
|
||||
Input full url of this endpoint in your creds.yml file under Topgg url field.
|
||||
For example "https://api.my.cool.bot/topgg/new"
|
||||
|
||||
#### [POST] `/discordswebhook`
|
||||
Input this endpoint as the webhook on discords.com bot edit page
|
||||
model: https://docs.botsfordiscord.com/methods/receiving-votes
|
||||
For example "https://api.my.cool.bot/topggwebhook"
|
||||
#### [POST] `/topggwebhook`
|
||||
Input this endpoint as the webhook https://top.gg/bot/:your-bot-id/webhooks (replace :your-bot-id with your bot's id)
|
||||
model: https://docs.top.gg/resources/webhooks/#schema
|
||||
For example "https://api.my.cool.bot/discordswebhook"
|
||||
|
||||
Input your super-secret header value in appsettings.json's DiscordsKey and TopGGKey fields
|
||||
They must match your DiscordsKey and TopGG key respectively, as well as your secrets in the discords.com and top.gg webhook setup pages
|
||||
|
||||
Full Example:
|
||||
|
||||
⚠ Change TopggKey and DiscordsKey to a secure long string
|
||||
⚠ You can use https://www.random.org/strings/?num=1&len=20&digits=on&upperalpha=on&loweralpha=on&unique=on&format=html&rnd=new to generate it
|
||||
|
||||
`creds.yml`
|
||||
```yml
|
||||
votes:
|
||||
TopggServiceUrl: "https://api.my.cool.bot/topgg"
|
||||
TopggKey: "my_topgg_key"
|
||||
DiscordsServiceUrl: "https://api.my.cool.bot/discords"
|
||||
DiscordsKey: "my_discords_key"
|
||||
```
|
||||
|
||||
`appsettings.json`
|
||||
```json
|
||||
...
|
||||
"DiscordsKey": "my_discords_key",
|
||||
"TopGGKey": "my_topgg_key",
|
||||
...
|
||||
```
|
105
src/NadekoBot.VotesApi/Services/FileVotesCache.cs
Normal file
105
src/NadekoBot.VotesApi/Services/FileVotesCache.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using MorseCode.ITask;
|
||||
|
||||
namespace NadekoBot.VotesApi.Services
|
||||
{
|
||||
public class FileVotesCache : IVotesCache
|
||||
{
|
||||
private const string statsFile = "store/stats.json";
|
||||
private const string topggFile = "store/topgg.json";
|
||||
private const string discordsFile = "store/discords.json";
|
||||
|
||||
private readonly SemaphoreSlim locker = new SemaphoreSlim(1, 1);
|
||||
|
||||
public FileVotesCache()
|
||||
{
|
||||
if (!Directory.Exists("store"))
|
||||
Directory.CreateDirectory("store");
|
||||
|
||||
if(!File.Exists(topggFile))
|
||||
File.WriteAllText(topggFile, "[]");
|
||||
|
||||
if(!File.Exists(discordsFile))
|
||||
File.WriteAllText(discordsFile, "[]");
|
||||
}
|
||||
|
||||
public ITask AddNewTopggVote(string userId)
|
||||
{
|
||||
return AddNewVote(topggFile, userId);
|
||||
}
|
||||
|
||||
public ITask AddNewDiscordsVote(string userId)
|
||||
{
|
||||
return AddNewVote(discordsFile, userId);
|
||||
}
|
||||
|
||||
private async ITask AddNewVote(string file, string userId)
|
||||
{
|
||||
await locker.WaitAsync();
|
||||
try
|
||||
{
|
||||
var votes = await GetVotesAsync(file);
|
||||
votes.Add(userId);
|
||||
await File.WriteAllTextAsync(file , JsonSerializer.Serialize(votes));
|
||||
}
|
||||
finally
|
||||
{
|
||||
locker.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async ITask<IList<Vote>> GetNewTopGgVotesAsync()
|
||||
{
|
||||
var votes = await EvictTopggVotes();
|
||||
return votes;
|
||||
}
|
||||
|
||||
public async ITask<IList<Vote>> GetNewDiscordsVotesAsync()
|
||||
{
|
||||
var votes = await EvictDiscordsVotes();
|
||||
return votes;
|
||||
}
|
||||
|
||||
private ITask<List<Vote>> EvictTopggVotes()
|
||||
=> EvictVotes(topggFile);
|
||||
|
||||
private ITask<List<Vote>> EvictDiscordsVotes()
|
||||
=> EvictVotes(discordsFile);
|
||||
|
||||
private async ITask<List<Vote>> EvictVotes(string file)
|
||||
{
|
||||
await locker.WaitAsync();
|
||||
try
|
||||
{
|
||||
|
||||
var ids = await GetVotesAsync(file);
|
||||
await File.WriteAllTextAsync(file, "[]");
|
||||
|
||||
return ids?
|
||||
.Select(x => (Ok: ulong.TryParse(x, out var r), Id: r))
|
||||
.Where(x => x.Ok)
|
||||
.Select(x => new Vote
|
||||
{
|
||||
UserId = x.Id
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
finally
|
||||
{
|
||||
locker.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async ITask<IList<string>> GetVotesAsync(string file)
|
||||
{
|
||||
await using var fs = File.Open(file, FileMode.Open);
|
||||
var votes = await JsonSerializer.DeserializeAsync<List<string>>(fs);
|
||||
return votes;
|
||||
}
|
||||
}
|
||||
}
|
13
src/NadekoBot.VotesApi/Services/IVotesCache.cs
Normal file
13
src/NadekoBot.VotesApi/Services/IVotesCache.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using MorseCode.ITask;
|
||||
|
||||
namespace NadekoBot.VotesApi.Services
|
||||
{
|
||||
public interface IVotesCache
|
||||
{
|
||||
ITask<IList<Vote>> GetNewTopGgVotesAsync();
|
||||
ITask<IList<Vote>> GetNewDiscordsVotesAsync();
|
||||
ITask AddNewTopggVote(string userId);
|
||||
ITask AddNewDiscordsVote(string userId);
|
||||
}
|
||||
}
|
69
src/NadekoBot.VotesApi/Startup.cs
Normal file
69
src/NadekoBot.VotesApi/Startup.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using NadekoBot.VotesApi.Services;
|
||||
|
||||
namespace NadekoBot.VotesApi
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddControllers();
|
||||
services.AddSingleton<IVotesCache, FileVotesCache>();
|
||||
services.AddSwaggerGen(c =>
|
||||
{
|
||||
c.SwaggerDoc("v1", new OpenApiInfo { Title = "NadekoBot.VotesApi", Version = "v1" });
|
||||
});
|
||||
|
||||
services
|
||||
.AddAuthentication(opts =>
|
||||
{
|
||||
opts.DefaultScheme = AuthHandler.SchemeName;
|
||||
opts.AddScheme<AuthHandler>(AuthHandler.SchemeName, AuthHandler.SchemeName);
|
||||
});
|
||||
|
||||
services
|
||||
.AddAuthorization(opts =>
|
||||
{
|
||||
opts.DefaultPolicy = new AuthorizationPolicyBuilder(AuthHandler.SchemeName)
|
||||
.RequireAssertion(x => false)
|
||||
.Build();
|
||||
opts.AddPolicy(Policies.DiscordsAuth, policy => policy.RequireClaim(AuthHandler.DiscordsClaim));
|
||||
opts.AddPolicy(Policies.TopggAuth, policy => policy.RequireClaim(AuthHandler.TopggClaim));
|
||||
});
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "NadekoBot.VotesApi v1"));
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
|
||||
}
|
||||
}
|
||||
}
|
9
src/NadekoBot.VotesApi/WeatherForecast.cs
Normal file
9
src/NadekoBot.VotesApi/WeatherForecast.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace NadekoBot.VotesApi
|
||||
{
|
||||
public class Vote
|
||||
{
|
||||
public ulong UserId { get; set; }
|
||||
}
|
||||
}
|
9
src/NadekoBot.VotesApi/appsettings.Development.json
Normal file
9
src/NadekoBot.VotesApi/appsettings.Development.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
}
|
||||
}
|
12
src/NadekoBot.VotesApi/appsettings.json
Normal file
12
src/NadekoBot.VotesApi/appsettings.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
"DiscordsKey": "my_discords_key",
|
||||
"TopGGKey": "my_topgg_key",
|
||||
"AllowedHosts": "*"
|
||||
}
|
@@ -19,6 +19,7 @@ using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Common.Configs;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.Administration.Services;
|
||||
using NadekoBot.Modules.Searches;
|
||||
using Serilog;
|
||||
|
||||
namespace NadekoBot
|
||||
@@ -28,7 +29,7 @@ namespace NadekoBot
|
||||
private readonly IBotCredentials _creds;
|
||||
private readonly CommandService _commandService;
|
||||
private readonly DbService _db;
|
||||
private readonly BotCredsProvider _credsProvider;
|
||||
private readonly IBotCredsProvider _credsProvider;
|
||||
|
||||
public event Func<GuildConfig, Task> JoinedGuild = delegate { return Task.CompletedTask; };
|
||||
|
||||
@@ -95,8 +96,8 @@ namespace NadekoBot
|
||||
}
|
||||
|
||||
var svcs = new ServiceCollection()
|
||||
.AddTransient<IBotCredentials>(_ => _creds) // bot creds
|
||||
.AddSingleton(_credsProvider)
|
||||
.AddTransient<IBotCredentials>(_ => _credsProvider.GetCreds()) // bot creds
|
||||
.AddSingleton<IBotCredsProvider>(_credsProvider)
|
||||
.AddSingleton(_db) // database
|
||||
.AddRedis(_creds.RedisOptions) // redis
|
||||
.AddSingleton(Client) // discord socket client
|
||||
@@ -105,7 +106,7 @@ namespace NadekoBot
|
||||
.AddSingleton<ISeria, JsonSeria>()
|
||||
.AddSingleton<IPubSub, RedisPubSub>()
|
||||
.AddSingleton<IConfigSeria, YamlSeria>()
|
||||
.AddBotStringsServices()
|
||||
.AddBotStringsServices(_creds.TotalShards)
|
||||
.AddConfigServices()
|
||||
.AddConfigMigrators()
|
||||
.AddMemoryCache()
|
||||
@@ -145,7 +146,8 @@ namespace NadekoBot
|
||||
|
||||
svcs.Scan(scan => scan
|
||||
.FromAssemblyOf<IReadyExecutor>()
|
||||
.AddClasses(classes => classes.AssignableToAny(
|
||||
.AddClasses(classes => classes
|
||||
.AssignableToAny(
|
||||
// services
|
||||
typeof(INService),
|
||||
|
||||
|
@@ -12,7 +12,7 @@ namespace NadekoBot.Common.Attributes
|
||||
{
|
||||
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo executingCommand, IServiceProvider services)
|
||||
{
|
||||
var creds = services.GetRequiredService<BotCredsProvider>().GetCreds();
|
||||
var creds = services.GetRequiredService<IBotCredsProvider>().GetCreds();
|
||||
|
||||
return Task.FromResult((creds.IsOwner(context.User) || context.Client.CurrentUser.Id == context.User.Id ? PreconditionResult.FromSuccess() : PreconditionResult.FromError("Not owner")));
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@ namespace NadekoBot.Common
|
||||
OwnerIds = new List<ulong>();
|
||||
TotalShards = 1;
|
||||
GoogleApiKey = string.Empty;
|
||||
Votes = new(string.Empty, string.Empty);
|
||||
Votes = new(string.Empty, string.Empty, string.Empty, string.Empty);
|
||||
Patreon = new(string.Empty, string.Empty, string.Empty, string.Empty);
|
||||
BotListToken = string.Empty;
|
||||
CleverbotApiKey = string.Empty;
|
||||
@@ -73,16 +73,6 @@ go to https://www.patreon.com/portal -> my clients -> create client")]
|
||||
Change only if you've changed the coordinator address or port.")]
|
||||
public string CoordinatorUrl { get; set; }
|
||||
|
||||
[YamlIgnore]
|
||||
public string PatreonCampaignId => Patreon?.CampaignId;
|
||||
[YamlIgnore]
|
||||
public string PatreonAccessToken => Patreon?.AccessToken;
|
||||
|
||||
[YamlIgnore]
|
||||
public string VotesUrl => Votes?.Url;
|
||||
[YamlIgnore]
|
||||
public string VotesToken => Votes.Key;
|
||||
|
||||
[Comment(@"Api key obtained on https://rapidapi.com (go to MyApps -> Add New App -> Enter Name -> Application key)")]
|
||||
public string RapidApiKey { get; set; }
|
||||
|
||||
@@ -126,11 +116,9 @@ Windows default
|
||||
// todo fixup patreon
|
||||
public sealed record PatreonSettings
|
||||
{
|
||||
[Comment(@"Access token. You have to manually update this 1st of each month by refreshing the token on https://patreon.com/portal")]
|
||||
public string ClientId { get; set; }
|
||||
public string AccessToken { get; set; }
|
||||
[Comment(@"Unused atm")]
|
||||
public string RefreshToken { get; set; }
|
||||
[Comment(@"Unused atm")]
|
||||
public string ClientSecret { get; set; }
|
||||
|
||||
[Comment(@"Campaign ID of your patreon page. Go to your patreon page (make sure you're logged in) and type ""prompt('Campaign ID', window.patreon.bootstrap.creator.data.id);"" in the console. (ctrl + shift + i)")]
|
||||
@@ -143,19 +131,44 @@ Windows default
|
||||
ClientSecret = clientSecret;
|
||||
CampaignId = campaignId;
|
||||
}
|
||||
|
||||
public PatreonSettings()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record VotesSettings
|
||||
{
|
||||
[Comment(@"")]
|
||||
public string Url { get; set; }
|
||||
[Comment(@"")]
|
||||
public string Key { get; set; }
|
||||
[Comment(@"top.gg votes service url
|
||||
This is the url of your instance of the NadekoBot.Votes api
|
||||
Example: https://votes.my.cool.bot.com")]
|
||||
public string TopggServiceUrl { get; set; }
|
||||
|
||||
[Comment(@"Authorization header value sent to the TopGG service url with each request
|
||||
This should be equivalent to the TopggKey in your NadekoBot.Votes api appsettings.json file")]
|
||||
public string TopggKey { get; set; }
|
||||
|
||||
[Comment(@"discords.com votes service url
|
||||
This is the url of your instance of the NadekoBot.Votes api
|
||||
Example: https://votes.my.cool.bot.com")]
|
||||
public string DiscordsServiceUrl { get; set; }
|
||||
|
||||
[Comment(@"Authorization header value sent to the Discords service url with each request
|
||||
This should be equivalent to the DiscordsKey in your NadekoBot.Votes api appsettings.json file")]
|
||||
public string DiscordsKey { get; set; }
|
||||
|
||||
public VotesSettings(string url, string key)
|
||||
public VotesSettings()
|
||||
{
|
||||
Url = url;
|
||||
Key = key;
|
||||
|
||||
}
|
||||
|
||||
public VotesSettings(string topggServiceUrl, string topggKey, string discordsServiceUrl, string discordsKey)
|
||||
{
|
||||
TopggServiceUrl = topggServiceUrl;
|
||||
TopggKey = topggKey;
|
||||
DiscordsServiceUrl = discordsServiceUrl;
|
||||
DiscordsKey = discordsKey;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -19,6 +19,9 @@ namespace NadekoBot.Common
|
||||
/// <returns>Task representing download state</returns>
|
||||
public async Task EnsureUsersDownloadedAsync(IGuild guild)
|
||||
{
|
||||
#if GLOBAL_NADEKO
|
||||
return;
|
||||
#endif
|
||||
await downloadUsersSemaphore.WaitAsync();
|
||||
try
|
||||
{
|
||||
|
@@ -14,11 +14,15 @@ namespace NadekoBot.Extensions
|
||||
{
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddBotStringsServices(this IServiceCollection services)
|
||||
=> services
|
||||
.AddSingleton<IStringsSource, LocalFileStringsSource>()
|
||||
.AddSingleton<IBotStringsProvider, LocalBotStringsProvider>()
|
||||
.AddSingleton<IBotStrings, BotStrings>();
|
||||
public static IServiceCollection AddBotStringsServices(this IServiceCollection services, int totalShards)
|
||||
=> totalShards <= 1
|
||||
? services
|
||||
.AddSingleton<IStringsSource, LocalFileStringsSource>()
|
||||
.AddSingleton<IBotStringsProvider, LocalBotStringsProvider>()
|
||||
.AddSingleton<IBotStrings, BotStrings>()
|
||||
: services.AddSingleton<IStringsSource, LocalFileStringsSource>()
|
||||
.AddSingleton<IBotStringsProvider, RedisBotStringsProvider>()
|
||||
.AddSingleton<IBotStrings, BotStrings>();
|
||||
|
||||
public static IServiceCollection AddConfigServices(this IServiceCollection services)
|
||||
{
|
||||
|
@@ -12,16 +12,14 @@ namespace NadekoBot
|
||||
string GoogleApiKey { get; }
|
||||
ICollection<ulong> OwnerIds { get; }
|
||||
string RapidApiKey { get; }
|
||||
string PatreonAccessToken { get; }
|
||||
|
||||
Creds.DbOptions Db { get; }
|
||||
string OsuApiKey { get; }
|
||||
int TotalShards { get; }
|
||||
string PatreonCampaignId { get; }
|
||||
Creds.PatreonSettings Patreon { get; }
|
||||
string CleverbotApiKey { get; }
|
||||
RestartConfig RestartCommand { get; }
|
||||
string VotesUrl { get; }
|
||||
string VotesToken { get; }
|
||||
Creds.VotesSettings Votes { get; }
|
||||
string BotListToken { get; }
|
||||
string RedisOptions { get; }
|
||||
string LocationIqApiKey { get; }
|
||||
|
@@ -1,10 +1,12 @@
|
||||
using System;
|
||||
using NadekoBot.Common.Yml;
|
||||
|
||||
namespace NadekoBot.Common
|
||||
{
|
||||
public class ImageUrls
|
||||
{
|
||||
public int Version { get; set; } = 2;
|
||||
[Comment("DO NOT CHANGE")]
|
||||
public int Version { get; set; } = 3;
|
||||
|
||||
public CoinData Coins { get; set; }
|
||||
public Uri[] Currency { get; set; }
|
||||
@@ -25,7 +27,6 @@ namespace NadekoBot.Common
|
||||
public class SlotData
|
||||
{
|
||||
public Uri[] Emojis { get; set; }
|
||||
public Uri[] Numbers { get; set; }
|
||||
public Uri Bg { get; set; }
|
||||
}
|
||||
|
||||
|
49
src/NadekoBot/Common/OldImageUrls.cs
Normal file
49
src/NadekoBot/Common/OldImageUrls.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
|
||||
namespace NadekoBot.Common
|
||||
{
|
||||
public class OldImageUrls
|
||||
{
|
||||
public int Version { get; set; } = 2;
|
||||
|
||||
public CoinData Coins { get; set; }
|
||||
public Uri[] Currency { get; set; }
|
||||
public Uri[] Dice { get; set; }
|
||||
public RategirlData Rategirl { get; set; }
|
||||
public XpData Xp { get; set; }
|
||||
|
||||
//new
|
||||
public RipData Rip { get; set; }
|
||||
public SlotData Slots { get; set; }
|
||||
|
||||
public class RipData
|
||||
{
|
||||
public Uri Bg { get; set; }
|
||||
public Uri Overlay { get; set; }
|
||||
}
|
||||
|
||||
public class SlotData
|
||||
{
|
||||
public Uri[] Emojis { get; set; }
|
||||
public Uri[] Numbers { get; set; }
|
||||
public Uri Bg { get; set; }
|
||||
}
|
||||
|
||||
public class CoinData
|
||||
{
|
||||
public Uri[] Heads { get; set; }
|
||||
public Uri[] Tails { get; set; }
|
||||
}
|
||||
|
||||
public class RategirlData
|
||||
{
|
||||
public Uri Matrix { get; set; }
|
||||
public Uri Dot { get; set; }
|
||||
}
|
||||
|
||||
public class XpData
|
||||
{
|
||||
public Uri Bg { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@@ -177,13 +177,13 @@ namespace NadekoBot.Common.Replacements
|
||||
/*OBSOLETE*/
|
||||
_reps.TryAdd("%servers%", () => c.Guilds.Count.ToString());
|
||||
#if !GLOBAL_NADEKO
|
||||
_reps.TryAdd("%users%", () => c.Guilds.Sum(s => s.Users.Count).ToString());
|
||||
_reps.TryAdd("%users%", () => c.Guilds.Sum(g => g.MemberCount).ToString());
|
||||
#endif
|
||||
|
||||
/*NEW*/
|
||||
_reps.TryAdd("%shard.servercount%", () => c.Guilds.Count.ToString());
|
||||
#if !GLOBAL_NADEKO
|
||||
_reps.TryAdd("%shard.usercount%", () => c.Guilds.Sum(s => s.Users.Count).ToString());
|
||||
_reps.TryAdd("%shard.usercount%", () => c.Guilds.Sum(g => g.MemberCount).ToString());
|
||||
#endif
|
||||
_reps.TryAdd("%shard.id%", () => c.ShardId.ToString());
|
||||
return this;
|
||||
|
@@ -53,6 +53,7 @@ namespace NadekoBot.Common.Replacements
|
||||
newEmbedData.Title = Replace(embedData.Title);
|
||||
newEmbedData.Thumbnail = Replace(embedData.Thumbnail);
|
||||
newEmbedData.Image = Replace(embedData.Image);
|
||||
newEmbedData.Url = Replace(embedData.Url);
|
||||
if (embedData.Author != null)
|
||||
{
|
||||
newEmbedData.Author = new SmartTextEmbedAuthor();
|
||||
@@ -68,6 +69,7 @@ namespace NadekoBot.Common.Replacements
|
||||
var newF = new SmartTextEmbedField();
|
||||
newF.Name = Replace(f.Name);
|
||||
newF.Value = Replace(f.Value);
|
||||
newF.Inline = f.Inline;
|
||||
fields.Add(newF);
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Discord;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Services;
|
||||
@@ -29,6 +30,47 @@ namespace NadekoBot
|
||||
(Footer != null && (!string.IsNullOrWhiteSpace(Footer.Text) || !string.IsNullOrWhiteSpace(Footer.IconUrl))) ||
|
||||
(Fields != null && Fields.Length > 0);
|
||||
|
||||
public static SmartEmbedText FromEmbed(IEmbed eb, string plainText = null)
|
||||
{
|
||||
var set = new SmartEmbedText();
|
||||
|
||||
set.PlainText = plainText;
|
||||
set.Title = eb.Title;
|
||||
set.Description = eb.Description;
|
||||
set.Url = eb.Url;
|
||||
set.Thumbnail = eb.Thumbnail?.Url;
|
||||
set.Image = eb.Image?.Url;
|
||||
set.Author = eb.Author is EmbedAuthor ea
|
||||
? new()
|
||||
{
|
||||
Name = ea.Name,
|
||||
Url = ea.Url,
|
||||
IconUrl = ea.IconUrl
|
||||
}
|
||||
: null;
|
||||
set.Footer = eb.Footer is EmbedFooter ef
|
||||
? new()
|
||||
{
|
||||
Text = ef.Text,
|
||||
IconUrl = ef.IconUrl
|
||||
}
|
||||
: null;
|
||||
|
||||
if (eb.Fields.Length > 0)
|
||||
set.Fields = eb
|
||||
.Fields
|
||||
.Select(field => new SmartTextEmbedField()
|
||||
{
|
||||
Inline = field.Inline,
|
||||
Name = field.Name,
|
||||
Value = field.Value,
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
set.Color = eb.Color?.RawValue ?? 0;
|
||||
return set;
|
||||
}
|
||||
|
||||
public EmbedBuilder GetEmbed()
|
||||
{
|
||||
var embed = new EmbedBuilder()
|
||||
|
17
src/NadekoBot/Common/TypeReaders/EmoteTypeReader.cs
Normal file
17
src/NadekoBot/Common/TypeReaders/EmoteTypeReader.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
|
||||
namespace NadekoBot.Common.TypeReaders
|
||||
{
|
||||
public sealed class EmoteTypeReader : NadekoTypeReader<Emote>
|
||||
{
|
||||
public override Task<TypeReaderResult> ReadAsync(ICommandContext ctx, string input)
|
||||
{
|
||||
if (!Emote.TryParse(input, out var emote))
|
||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Input is not a valid emote"));
|
||||
|
||||
return Task.FromResult(TypeReaderResult.FromSuccess(emote));
|
||||
}
|
||||
}
|
||||
}
|
@@ -20,6 +20,7 @@ namespace NadekoBot.Common.Yml
|
||||
.WithTypeConverter(new Rgba32Converter())
|
||||
.WithTypeConverter(new CultureInfoConverter())
|
||||
.WithTypeConverter(new UriConverter())
|
||||
.IgnoreUnmatchedProperties()
|
||||
.Build();
|
||||
}
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
using NadekoBot.Db.Models;
|
||||
using System;
|
||||
using NadekoBot.Db.Models;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Discord;
|
||||
@@ -138,14 +139,15 @@ WHERE UserId={userId};");
|
||||
// just update the amount, there is no new user data
|
||||
if (!updatedUserData)
|
||||
{
|
||||
ctx.Database.ExecuteSqlInterpolated($@"
|
||||
var rows = ctx.Database.ExecuteSqlInterpolated($@"
|
||||
UPDATE OR IGNORE DiscordUser
|
||||
SET CurrencyAmount=CurrencyAmount+{amount}
|
||||
WHERE UserId={userId};
|
||||
|
||||
INSERT OR IGNORE INTO DiscordUser (UserId, Username, Discriminator, AvatarId, CurrencyAmount)
|
||||
VALUES ({userId}, {name}, {discrim}, {avatarId}, {amount});
|
||||
INSERT OR IGNORE INTO DiscordUser (UserId, Username, Discriminator, AvatarId, CurrencyAmount, TotalXp)
|
||||
VALUES ({userId}, {name}, {discrim}, {avatarId}, {amount}, 0);
|
||||
");
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -157,8 +159,8 @@ SET CurrencyAmount=CurrencyAmount+{amount},
|
||||
AvatarId={avatarId}
|
||||
WHERE UserId={userId};
|
||||
|
||||
INSERT OR IGNORE INTO DiscordUser (UserId, Username, Discriminator, AvatarId, CurrencyAmount)
|
||||
VALUES ({userId}, {name}, {discrim}, {avatarId}, {amount});
|
||||
INSERT OR IGNORE INTO DiscordUser (UserId, Username, Discriminator, AvatarId, CurrencyAmount, TotalXp)
|
||||
VALUES ({userId}, {name}, {discrim}, {avatarId}, {amount}, 0);
|
||||
");
|
||||
}
|
||||
return true;
|
||||
@@ -167,7 +169,7 @@ VALUES ({userId}, {name}, {discrim}, {avatarId}, {amount});
|
||||
public static decimal GetTotalCurrency(this DbSet<DiscordUser> users)
|
||||
{
|
||||
return users
|
||||
.Sum(x => x.CurrencyAmount);
|
||||
.Sum((Func<DiscordUser, decimal>)(x => x.CurrencyAmount));
|
||||
}
|
||||
|
||||
public static decimal GetTopOnePercentCurrency(this DbSet<DiscordUser> users, ulong botId)
|
||||
|
@@ -48,12 +48,12 @@ namespace NadekoBot.Db
|
||||
|
||||
private static IQueryable<GuildConfig> IncludeEverything(this DbSet<GuildConfig> configs)
|
||||
{
|
||||
// todo split query
|
||||
return configs
|
||||
.AsQueryable()
|
||||
.Include(gc => gc.CommandCooldowns)
|
||||
.Include(gc => gc.FollowedStreams)
|
||||
.Include(gc => gc.StreamRole)
|
||||
.Include(gc => gc.NsfwBlacklistedTags)
|
||||
.Include(gc => gc.XpSettings)
|
||||
.ThenInclude(x => x.ExclusionList)
|
||||
.Include(gc => gc.DelMsgOnCmdChannels)
|
||||
@@ -117,7 +117,7 @@ namespace NadekoBot.Db
|
||||
{
|
||||
var logSetting = ctx.LogSettings
|
||||
.AsQueryable()
|
||||
.Include(x => x.IgnoredChannels)
|
||||
.Include(x => x.LogIgnores)
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.FirstOrDefault();
|
||||
|
||||
|
@@ -9,6 +9,11 @@ namespace NadekoBot.Db
|
||||
{
|
||||
public static class QuoteExtensions
|
||||
{
|
||||
public static IEnumerable<Quote> GetForGuild(this DbSet<Quote> quotes, ulong guildId)
|
||||
{
|
||||
return quotes.AsQueryable().Where(x => x.GuildId == guildId);
|
||||
}
|
||||
|
||||
public static IEnumerable<Quote> GetGroup(this DbSet<Quote> quotes, ulong guildId, int page, OrderType order)
|
||||
{
|
||||
var q = quotes.AsQueryable().Where(x => x.GuildId == guildId);
|
||||
|
@@ -18,5 +18,12 @@ namespace NadekoBot.Db
|
||||
.OrderBy(x => x.DateAdded)
|
||||
.Skip(page * 10)
|
||||
.Take(10);
|
||||
|
||||
public static IEnumerable<Reminder> RemindersForServer(this DbSet<Reminder> reminders, ulong serverId, int page)
|
||||
=> reminders.AsQueryable()
|
||||
.Where(x => x.ServerId == serverId)
|
||||
.OrderBy(x => x.DateAdded)
|
||||
.Skip(page * 10)
|
||||
.Take(10);
|
||||
}
|
||||
}
|
||||
|
12
src/NadekoBot/Db/Models/AutoTranslateChannel.cs
Normal file
12
src/NadekoBot/Db/Models/AutoTranslateChannel.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NadekoBot.Services.Database.Models
|
||||
{
|
||||
public class AutoTranslateChannel : DbEntity
|
||||
{
|
||||
public ulong GuildId { get; set; }
|
||||
public ulong ChannelId { get; set; }
|
||||
public bool AutoDelete { get; set; }
|
||||
public IList<AutoTranslateUser> Users { get; set; } = new List<AutoTranslateUser>();
|
||||
}
|
||||
}
|
11
src/NadekoBot/Db/Models/AutoTranslateUser.cs
Normal file
11
src/NadekoBot/Db/Models/AutoTranslateUser.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace NadekoBot.Services.Database.Models
|
||||
{
|
||||
public class AutoTranslateUser : DbEntity
|
||||
{
|
||||
public int ChannelId { get; set; }
|
||||
public AutoTranslateChannel Channel { get; set; }
|
||||
public ulong UserId { get; set; }
|
||||
public string Source { get; set; }
|
||||
public string Target { get; set; }
|
||||
}
|
||||
}
|
@@ -91,7 +91,6 @@ namespace NadekoBot.Services.Database.Models
|
||||
public bool WarningsInitialized { get; set; }
|
||||
public HashSet<SlowmodeIgnoredUser> SlowmodeIgnoredUsers { get; set; }
|
||||
public HashSet<SlowmodeIgnoredRole> SlowmodeIgnoredRoles { get; set; }
|
||||
public HashSet<NsfwBlacklitedTag> NsfwBlacklistedTags { get; set; } = new HashSet<NsfwBlacklitedTag>();
|
||||
|
||||
public List<ShopEntry> ShopEntries { get; set; }
|
||||
public ulong? GameVoiceChannel { get; set; } = null;
|
||||
|
@@ -1,8 +0,0 @@
|
||||
namespace NadekoBot.Services.Database.Models
|
||||
{
|
||||
public class IgnoredLogChannel : DbEntity
|
||||
{
|
||||
public LogSetting LogSetting { get; set; }
|
||||
public ulong ChannelId { get; set; }
|
||||
}
|
||||
}
|
16
src/NadekoBot/Db/Models/IgnoredLogItem.cs
Normal file
16
src/NadekoBot/Db/Models/IgnoredLogItem.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace NadekoBot.Services.Database.Models
|
||||
{
|
||||
public class IgnoredLogItem : DbEntity
|
||||
{
|
||||
public int LogSettingId { get; set; }
|
||||
public LogSetting LogSetting { get; set; }
|
||||
public ulong LogItemId { get; set; }
|
||||
public IgnoredItemType ItemType { get; set; }
|
||||
}
|
||||
|
||||
public enum IgnoredItemType
|
||||
{
|
||||
Channel,
|
||||
User,
|
||||
}
|
||||
}
|
@@ -4,8 +4,7 @@ namespace NadekoBot.Services.Database.Models
|
||||
{
|
||||
public class LogSetting : DbEntity
|
||||
{
|
||||
public HashSet<IgnoredLogChannel> IgnoredChannels { get; set; } = new HashSet<IgnoredLogChannel>();
|
||||
public HashSet<IgnoredVoicePresenceChannel> IgnoredVoicePresenceChannelIds { get; set; } = new HashSet<IgnoredVoicePresenceChannel>();
|
||||
public List<IgnoredLogItem> LogIgnores { get; set; } = new List<IgnoredLogItem>();
|
||||
|
||||
public ulong GuildId { get; set; }
|
||||
public ulong? LogOtherId { get; set; }
|
||||
|
16
src/NadekoBot/Db/Models/NsfwBlacklistedTag.cs
Normal file
16
src/NadekoBot/Db/Models/NsfwBlacklistedTag.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace NadekoBot.Services.Database.Models
|
||||
{
|
||||
public class NsfwBlacklistedTag : DbEntity
|
||||
{
|
||||
public ulong GuildId { get; set; }
|
||||
public string Tag { get; set; }
|
||||
|
||||
public override int GetHashCode()
|
||||
=> Tag.GetHashCode(StringComparison.InvariantCulture);
|
||||
|
||||
public override bool Equals(object obj)
|
||||
=> obj is NsfwBlacklistedTag x && x.Tag == Tag;
|
||||
}
|
||||
}
|
@@ -1,21 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace NadekoBot.Services.Database.Models
|
||||
{
|
||||
public class NsfwBlacklitedTag : DbEntity
|
||||
{
|
||||
public string Tag { get; set; }
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Tag.GetHashCode(StringComparison.InvariantCulture);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is NsfwBlacklitedTag x
|
||||
? x.Tag == Tag
|
||||
: false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -8,5 +8,6 @@
|
||||
public bool Forgiven { get; set; }
|
||||
public string ForgivenBy { get; set; }
|
||||
public string Moderator { get; set; }
|
||||
public long Weight { get; set; }
|
||||
}
|
||||
}
|
||||
|
@@ -42,8 +42,8 @@ namespace NadekoBot.Services.Database
|
||||
|
||||
//logging
|
||||
public DbSet<LogSetting> LogSettings { get; set; }
|
||||
public DbSet<IgnoredLogChannel> IgnoredLogChannels { get; set; }
|
||||
public DbSet<IgnoredVoicePresenceChannel> IgnoredVoicePresenceCHannels { get; set; }
|
||||
public DbSet<IgnoredLogItem> IgnoredLogChannels { get; set; }
|
||||
|
||||
public DbSet<RotatingPlayingStatus> RotatingStatus { get; set; }
|
||||
public DbSet<BlacklistEntry> Blacklist { get; set; }
|
||||
@@ -59,6 +59,9 @@ namespace NadekoBot.Services.Database
|
||||
public DbSet<Poll> Poll { get; set; }
|
||||
public DbSet<WaifuInfo> WaifuInfo { get; set; }
|
||||
public DbSet<ImageOnlyChannel> ImageOnlyChannels { get; set; }
|
||||
public DbSet<NsfwBlacklistedTag> NsfwBlacklistedTags { get; set; }
|
||||
public DbSet<AutoTranslateChannel> AutoTranslateChannels { get; set; }
|
||||
public DbSet<AutoTranslateUser> AutoTranslateUsers { get; set; }
|
||||
|
||||
public NadekoContext(DbContextOptions<NadekoContext> options) : base(options)
|
||||
{
|
||||
@@ -195,10 +198,16 @@ namespace NadekoBot.Services.Database
|
||||
#endregion
|
||||
|
||||
#region Warnings
|
||||
var warn = modelBuilder.Entity<Warning>();
|
||||
warn.HasIndex(x => x.GuildId);
|
||||
warn.HasIndex(x => x.UserId);
|
||||
warn.HasIndex(x => x.DateAdded);
|
||||
|
||||
modelBuilder.Entity<Warning>(warn =>
|
||||
{
|
||||
warn.HasIndex(x => x.GuildId);
|
||||
warn.HasIndex(x => x.UserId);
|
||||
warn.HasIndex(x => x.DateAdded);
|
||||
warn.Property(x => x.Weight)
|
||||
.HasDefaultValue(1);
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
#region PatreonRewards
|
||||
@@ -342,12 +351,40 @@ namespace NadekoBot.Services.Database
|
||||
modelBuilder.Entity<LogSetting>(ls => ls
|
||||
.HasIndex(x => x.GuildId)
|
||||
.IsUnique());
|
||||
|
||||
modelBuilder.Entity<LogSetting>(ls => ls
|
||||
.HasMany(x => x.LogIgnores)
|
||||
.WithOne(x => x.LogSetting)
|
||||
.OnDelete(DeleteBehavior.Cascade));
|
||||
|
||||
modelBuilder.Entity<IgnoredLogItem>(ili => ili
|
||||
.HasIndex(x => new { x.LogSettingId, x.LogItemId, x.ItemType })
|
||||
.IsUnique());
|
||||
|
||||
#endregion
|
||||
|
||||
modelBuilder.Entity<ImageOnlyChannel>(ioc => ioc
|
||||
.HasIndex(x => x.ChannelId)
|
||||
.IsUnique());
|
||||
|
||||
modelBuilder.Entity<NsfwBlacklistedTag>(nbt => nbt
|
||||
.HasIndex(x => x.GuildId)
|
||||
.IsUnique(false));
|
||||
|
||||
var atch = modelBuilder.Entity<AutoTranslateChannel>();
|
||||
atch.HasIndex(x => x.GuildId)
|
||||
.IsUnique(false);
|
||||
|
||||
atch.HasIndex(x => x.ChannelId)
|
||||
.IsUnique();
|
||||
|
||||
atch
|
||||
.HasMany(x => x.Users)
|
||||
.WithOne(x => x.Channel)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<AutoTranslateUser>(atu => atu
|
||||
.HasAlternateKey(x => new { x.ChannelId, x.UserId }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -17,6 +17,12 @@ namespace NadekoBot.Migrations
|
||||
migrationBuilder.Sql("DELETE FROM FilterChannelId WHERE GuildConfigId NOT IN (SELECT Id from GuildConfigs)");
|
||||
migrationBuilder.Sql("DELETE FROM CommandCooldown WHERE GuildConfigId NOT IN (SELECT Id from GuildConfigs)");
|
||||
|
||||
// fix for users who edited their waifuinfo table manually and are unable to update
|
||||
migrationBuilder.Sql("DELETE FROM WaifuInfo where WaifuId not in (SELECT Id from DiscordUser);");
|
||||
|
||||
// fix for users who deleted clubs manually and are unable to update now
|
||||
migrationBuilder.Sql("UPDATE DiscordUser SET ClubId = null WHERE ClubId is not null and ClubId not in (SELECT Id from Clubs);");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ChannelCreated",
|
||||
table: "LogSettings");
|
||||
|
2657
src/NadekoBot/Migrations/20210921204645_logignore-user-channel.Designer.cs
generated
Normal file
2657
src/NadekoBot/Migrations/20210921204645_logignore-user-channel.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,97 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
public partial class logignoreuserchannel : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql("DELETE FROM IgnoredLogChannels WHERE LogSettingId is NULL");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_IgnoredLogChannels_LogSettings_LogSettingId",
|
||||
table: "IgnoredLogChannels");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_IgnoredLogChannels_LogSettingId",
|
||||
table: "IgnoredLogChannels");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "ChannelId",
|
||||
table: "IgnoredLogChannels",
|
||||
newName: "LogItemId");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "LogSettingId",
|
||||
table: "IgnoredLogChannels",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "INTEGER",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "ItemType",
|
||||
table: "IgnoredLogChannels",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_IgnoredLogChannels_LogSettingId_LogItemId_ItemType",
|
||||
table: "IgnoredLogChannels",
|
||||
columns: new[] { "LogSettingId", "LogItemId", "ItemType" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_IgnoredLogChannels_LogSettings_LogSettingId",
|
||||
table: "IgnoredLogChannels",
|
||||
column: "LogSettingId",
|
||||
principalTable: "LogSettings",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_IgnoredLogChannels_LogSettings_LogSettingId",
|
||||
table: "IgnoredLogChannels");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_IgnoredLogChannels_LogSettingId_LogItemId_ItemType",
|
||||
table: "IgnoredLogChannels");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ItemType",
|
||||
table: "IgnoredLogChannels");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "LogItemId",
|
||||
table: "IgnoredLogChannels",
|
||||
newName: "ChannelId");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "LogSettingId",
|
||||
table: "IgnoredLogChannels",
|
||||
type: "INTEGER",
|
||||
nullable: true,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "INTEGER");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_IgnoredLogChannels_LogSettingId",
|
||||
table: "IgnoredLogChannels",
|
||||
column: "LogSettingId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_IgnoredLogChannels_LogSettings_LogSettingId",
|
||||
table: "IgnoredLogChannels",
|
||||
column: "LogSettingId",
|
||||
principalTable: "LogSettings",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
}
|
||||
}
|
||||
}
|
2648
src/NadekoBot/Migrations/20211015232708_nsfw-blacklist-tags.Designer.cs
generated
Normal file
2648
src/NadekoBot/Migrations/20211015232708_nsfw-blacklist-tags.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
public partial class nsfwblacklisttags : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "NsfwBlacklistedTags",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
Tag = table.Column<string>(type: "TEXT", nullable: true),
|
||||
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_NsfwBlacklistedTags", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_NsfwBlacklistedTags_GuildId",
|
||||
table: "NsfwBlacklistedTags",
|
||||
column: "GuildId");
|
||||
|
||||
migrationBuilder.Sql(@"INSERT INTO NsfwBlacklistedTags(Id, GuildId, Tag, DateAdded)
|
||||
SELECT
|
||||
Id,
|
||||
(SELECT GuildId From GuildConfigs WHERE Id=GuildConfigId),
|
||||
Tag,
|
||||
DateAdded
|
||||
FROM NsfwBlacklitedTag
|
||||
WHERE GuildConfigId in (SELECT Id from GuildConfigs);");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "NsfwBlacklitedTag");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "NsfwBlacklistedTags");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "NsfwBlacklitedTag",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||
GuildConfigId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||
Tag = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_NsfwBlacklitedTag", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_NsfwBlacklitedTag_GuildConfigs_GuildConfigId",
|
||||
column: x => x.GuildConfigId,
|
||||
principalTable: "GuildConfigs",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_NsfwBlacklitedTag_GuildConfigId",
|
||||
table: "NsfwBlacklitedTag",
|
||||
column: "GuildConfigId");
|
||||
}
|
||||
}
|
||||
}
|
2653
src/NadekoBot/Migrations/20211121002508_weighted-warnings.Designer.cs
generated
Normal file
2653
src/NadekoBot/Migrations/20211121002508_weighted-warnings.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
24
src/NadekoBot/Migrations/20211121002508_weighted-warnings.cs
Normal file
24
src/NadekoBot/Migrations/20211121002508_weighted-warnings.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
public partial class weightedwarnings : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "Weight",
|
||||
table: "Warnings",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 1);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Weight",
|
||||
table: "Warnings");
|
||||
}
|
||||
}
|
||||
}
|
2725
src/NadekoBot/Migrations/20211213145407_atl-rework.Designer.cs
generated
Normal file
2725
src/NadekoBot/Migrations/20211213145407_atl-rework.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
71
src/NadekoBot/Migrations/20211213145407_atl-rework.cs
Normal file
71
src/NadekoBot/Migrations/20211213145407_atl-rework.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
public partial class atlrework : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AutoTranslateChannels",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
AutoDelete = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AutoTranslateChannels", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AutoTranslateUsers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
ChannelId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
UserId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
Source = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Target = table.Column<string>(type: "TEXT", nullable: true),
|
||||
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AutoTranslateUsers", x => x.Id);
|
||||
table.UniqueConstraint("AK_AutoTranslateUsers_ChannelId_UserId", x => new { x.ChannelId, x.UserId });
|
||||
table.ForeignKey(
|
||||
name: "FK_AutoTranslateUsers_AutoTranslateChannels_ChannelId",
|
||||
column: x => x.ChannelId,
|
||||
principalTable: "AutoTranslateChannels",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AutoTranslateChannels_ChannelId",
|
||||
table: "AutoTranslateChannels",
|
||||
column: "ChannelId",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AutoTranslateChannels_GuildId",
|
||||
table: "AutoTranslateChannels",
|
||||
column: "GuildId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "AutoTranslateUsers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AutoTranslateChannels");
|
||||
}
|
||||
}
|
||||
}
|
@@ -187,29 +187,6 @@ namespace NadekoBot.Migrations
|
||||
b.ToTable("FollowedStream");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.ImageOnlyChannel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("ChannelId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<ulong>("GuildId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChannelId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ImageOnlyChannels");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiAltSetting", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -363,6 +340,62 @@ namespace NadekoBot.Migrations
|
||||
b.ToTable("AutoCommands");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateChannel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("AutoDelete")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("ChannelId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<ulong>("GuildId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChannelId")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("GuildId");
|
||||
|
||||
b.ToTable("AutoTranslateChannels");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateUser", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ChannelId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Source")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Target")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<ulong>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasAlternateKey("ChannelId", "UserId");
|
||||
|
||||
b.ToTable("AutoTranslateUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.BanTemplate", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -870,24 +903,28 @@ namespace NadekoBot.Migrations
|
||||
b.ToTable("GuildConfigs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b =>
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogItem", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("ChannelId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("LogSettingId")
|
||||
b.Property<int>("ItemType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("LogItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("LogSettingId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("LogSettingId");
|
||||
b.HasIndex("LogSettingId", "LogItemId", "ItemType")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("IgnoredLogChannels");
|
||||
});
|
||||
@@ -914,6 +951,29 @@ namespace NadekoBot.Migrations
|
||||
b.ToTable("IgnoredVoicePresenceCHannels");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ImageOnlyChannel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("ChannelId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<ulong>("GuildId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChannelId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ImageOnlyChannels");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -1058,7 +1118,7 @@ namespace NadekoBot.Migrations
|
||||
b.ToTable("MutedUserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b =>
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklistedTag", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -1067,7 +1127,7 @@ namespace NadekoBot.Migrations
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("GuildConfigId")
|
||||
b.Property<ulong>("GuildId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Tag")
|
||||
@@ -1075,9 +1135,9 @@ namespace NadekoBot.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("GuildConfigId");
|
||||
b.HasIndex("GuildId");
|
||||
|
||||
b.ToTable("NsfwBlacklitedTag");
|
||||
b.ToTable("NsfwBlacklistedTags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b =>
|
||||
@@ -1963,6 +2023,11 @@ namespace NadekoBot.Migrations
|
||||
b.Property<ulong>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Weight")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(1);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DateAdded");
|
||||
@@ -2185,6 +2250,17 @@ namespace NadekoBot.Migrations
|
||||
b.Navigation("GuildConfig");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateUser", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.AutoTranslateChannel", "Channel")
|
||||
.WithMany("Users")
|
||||
.HasForeignKey("ChannelId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Channel");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null)
|
||||
@@ -2269,11 +2345,13 @@ namespace NadekoBot.Migrations
|
||||
b.Navigation("GuildConfig");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b =>
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogItem", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting")
|
||||
.WithMany("IgnoredChannels")
|
||||
.HasForeignKey("LogSettingId");
|
||||
.WithMany("LogIgnores")
|
||||
.HasForeignKey("LogSettingId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("LogSetting");
|
||||
});
|
||||
@@ -2281,7 +2359,7 @@ namespace NadekoBot.Migrations
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting")
|
||||
.WithMany("IgnoredVoicePresenceChannelIds")
|
||||
.WithMany()
|
||||
.HasForeignKey("LogSettingId");
|
||||
|
||||
b.Navigation("LogSetting");
|
||||
@@ -2294,13 +2372,6 @@ namespace NadekoBot.Migrations
|
||||
.HasForeignKey("GuildConfigId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null)
|
||||
.WithMany("NsfwBlacklistedTags")
|
||||
.HasForeignKey("GuildConfigId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null)
|
||||
@@ -2537,6 +2608,11 @@ namespace NadekoBot.Migrations
|
||||
b.Navigation("IgnoredChannels");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateChannel", b =>
|
||||
{
|
||||
b.Navigation("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b =>
|
||||
{
|
||||
b.Navigation("AntiAltSetting");
|
||||
@@ -2567,8 +2643,6 @@ namespace NadekoBot.Migrations
|
||||
|
||||
b.Navigation("MutedUsers");
|
||||
|
||||
b.Navigation("NsfwBlacklistedTags");
|
||||
|
||||
b.Navigation("Permissions");
|
||||
|
||||
b.Navigation("ReactionRoleMessages");
|
||||
@@ -2598,9 +2672,7 @@ namespace NadekoBot.Migrations
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b =>
|
||||
{
|
||||
b.Navigation("IgnoredChannels");
|
||||
|
||||
b.Navigation("IgnoredVoicePresenceChannelIds");
|
||||
b.Navigation("LogIgnores");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b =>
|
||||
|
@@ -16,6 +16,7 @@ namespace NadekoBot.Modules.Administration
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
public async Task AutoAssignRole([Leftover] IRole role)
|
||||
{
|
||||
var guser = (IGuildUser) ctx.User;
|
||||
@@ -47,6 +48,7 @@ namespace NadekoBot.Modules.Administration
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
public async Task AutoAssignRole()
|
||||
{
|
||||
if (!_service.TryGetRoles(ctx.Guild.Id, out var roles))
|
||||
|
@@ -7,6 +7,7 @@ using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Administration.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -18,12 +19,6 @@ namespace NadekoBot.Modules.Administration
|
||||
[NoPublicBot]
|
||||
public class LogCommands : NadekoSubmodule<ILogCommandService>
|
||||
{
|
||||
public enum EnableDisable
|
||||
{
|
||||
Enable,
|
||||
Disable
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
@@ -43,14 +38,51 @@ namespace NadekoBot.Modules.Administration
|
||||
[OwnerOnly]
|
||||
public async Task LogIgnore()
|
||||
{
|
||||
var channel = (ITextChannel)ctx.Channel;
|
||||
var settings = _service.GetGuildLogSettings(ctx.Guild.Id);
|
||||
|
||||
var removed = _service.LogIgnore(ctx.Guild.Id, ctx.Channel.Id);
|
||||
var chs = settings?.LogIgnores.Where(x => x.ItemType == IgnoredItemType.Channel).ToList()
|
||||
?? new List<IgnoredLogItem>();
|
||||
var usrs = settings?.LogIgnores.Where(x => x.ItemType == IgnoredItemType.User).ToList()
|
||||
?? new List<IgnoredLogItem>();
|
||||
|
||||
var eb = _eb.Create(ctx)
|
||||
.WithOkColor()
|
||||
.AddField(GetText(strs.log_ignored_channels),
|
||||
chs.Count == 0 ? "-" : string.Join('\n', chs.Select(x => $"{x.LogItemId} | <#{x.LogItemId}>")))
|
||||
.AddField(GetText(strs.log_ignored_users),
|
||||
usrs.Count == 0 ? "-" : string.Join('\n', usrs.Select(x => $"{x.LogItemId} | <@{x.LogItemId}>")));
|
||||
|
||||
await ctx.Channel.EmbedAsync(eb);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[OwnerOnly]
|
||||
public async Task LogIgnore([Leftover]ITextChannel target)
|
||||
{
|
||||
target ??= (ITextChannel)ctx.Channel;
|
||||
|
||||
var removed = _service.LogIgnore(ctx.Guild.Id, target.Id, IgnoredItemType.Channel);
|
||||
|
||||
if (!removed)
|
||||
await ReplyConfirmLocalizedAsync(strs.log_ignore(Format.Bold(channel.Mention + "(" + channel.Id + ")"))).ConfigureAwait(false);
|
||||
await ReplyConfirmLocalizedAsync(strs.log_ignore_chan(Format.Bold(target.Mention + "(" + target.Id + ")"))).ConfigureAwait(false);
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.log_not_ignore(Format.Bold(channel.Mention + "(" + channel.Id + ")"))).ConfigureAwait(false);
|
||||
await ReplyConfirmLocalizedAsync(strs.log_not_ignore_chan(Format.Bold(target.Mention + "(" + target.Id + ")"))).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[OwnerOnly]
|
||||
public async Task LogIgnore([Leftover]IUser target)
|
||||
{
|
||||
var removed = _service.LogIgnore(ctx.Guild.Id, target.Id, IgnoredItemType.User);
|
||||
|
||||
if (!removed)
|
||||
await ReplyConfirmLocalizedAsync(strs.log_ignore_user(Format.Bold(target.Mention + "(" + target.Id + ")"))).ConfigureAwait(false);
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.log_not_ignore_user(Format.Bold(target.Mention + "(" + target.Id + ")"))).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
@@ -318,7 +318,7 @@ namespace NadekoBot.Modules.Administration
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task Die()
|
||||
public async Task Die(bool graceful = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -329,7 +329,7 @@ namespace NadekoBot.Modules.Administration
|
||||
// ignored
|
||||
}
|
||||
await Task.Delay(2000).ConfigureAwait(false);
|
||||
_coord.Die();
|
||||
_coord.Die(graceful);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
@@ -506,6 +506,14 @@ namespace NadekoBot.Modules.Administration
|
||||
_strings.Reload();
|
||||
await ReplyConfirmLocalizedAsync(strs.bot_strings_reloaded).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task CoordReload()
|
||||
{
|
||||
await _coord.Reload();
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
|
||||
private static UserStatus SettableUserStatusToUserStatus(SettableUserStatus sus)
|
||||
{
|
||||
|
@@ -258,8 +258,7 @@ namespace NadekoBot.Modules.Administration
|
||||
{
|
||||
user = user ?? (IGuildUser) ctx.User;
|
||||
|
||||
var channel = await user.GetOrCreateDMChannelAsync();
|
||||
var success = await _service.GreetDmTest(channel, user);
|
||||
var success = await _service.GreetDmTest(user);
|
||||
if (success)
|
||||
await ctx.OkAsync();
|
||||
else
|
||||
|
@@ -6,6 +6,8 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using NadekoBot.Common.Collections;
|
||||
@@ -21,7 +23,7 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
void AddDeleteIgnore(ulong xId);
|
||||
Task LogServer(ulong guildId, ulong channelId, bool actionValue);
|
||||
bool LogIgnore(ulong guildId, ulong channelId);
|
||||
bool LogIgnore(ulong guildId, ulong itemId, IgnoredItemType itemType);
|
||||
LogSetting GetGuildLogSettings(ulong guildId);
|
||||
bool Log(ulong guildId, ulong? channelId, LogType type);
|
||||
}
|
||||
@@ -37,7 +39,7 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public bool LogIgnore(ulong guildId, ulong channelId)
|
||||
public bool LogIgnore(ulong guildId, ulong itemId, IgnoredItemType itemType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -97,7 +99,7 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
.AsQueryable()
|
||||
.AsNoTracking()
|
||||
.Where(x => guildIds.Contains(x.GuildId))
|
||||
.Include(ls => ls.IgnoredChannels)
|
||||
.Include(ls => ls.LogIgnores)
|
||||
.ToList();
|
||||
|
||||
GuildLogSettings = configs
|
||||
@@ -165,21 +167,23 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
_ignoreMessageIds.Add(messageId);
|
||||
}
|
||||
|
||||
public bool LogIgnore(ulong gid, ulong cid)
|
||||
public bool LogIgnore(ulong gid, ulong itemId, IgnoredItemType itemType)
|
||||
{
|
||||
int removed = 0;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var logSetting = uow.LogSettingsFor(gid);
|
||||
removed = logSetting.IgnoredChannels.RemoveWhere(ilc => ilc.ChannelId == cid);
|
||||
removed = logSetting.LogIgnores
|
||||
.RemoveAll(x => x.ItemType == itemType && itemId == x.LogItemId);
|
||||
|
||||
if (removed == 0)
|
||||
{
|
||||
var toAdd = new IgnoredLogChannel {ChannelId = cid};
|
||||
logSetting.IgnoredChannels.Add(toAdd);
|
||||
var toAdd = new IgnoredLogItem { LogItemId = itemId, ItemType = itemType};
|
||||
logSetting.LogIgnores.Add(toAdd);
|
||||
}
|
||||
|
||||
GuildLogSettings.AddOrUpdate(gid, logSetting, (_, _) => logSetting);
|
||||
uow.SaveChanges();
|
||||
GuildLogSettings.AddOrUpdate(gid, logSetting, (_, _) => logSetting);
|
||||
}
|
||||
|
||||
return removed > 0;
|
||||
@@ -580,7 +584,8 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!GuildLogSettings.TryGetValue(before.Guild.Id, out LogSetting logSetting))
|
||||
if (!GuildLogSettings.TryGetValue(before.Guild.Id, out LogSetting logSetting)
|
||||
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == after.Id && ilc.ItemType == IgnoredItemType.User))
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
@@ -682,7 +687,7 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
|
||||
if (!GuildLogSettings.TryGetValue(before.Guild.Id, out LogSetting logSetting)
|
||||
|| (logSetting.ChannelUpdatedId is null)
|
||||
|| logSetting.IgnoredChannels.Any(ilc => ilc.ChannelId == after.Id))
|
||||
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == after.Id && ilc.ItemType == IgnoredItemType.Channel))
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
@@ -733,7 +738,7 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
|
||||
if (!GuildLogSettings.TryGetValue(ch.Guild.Id, out LogSetting logSetting)
|
||||
|| (logSetting.ChannelDestroyedId is null)
|
||||
|| logSetting.IgnoredChannels.Any(ilc => ilc.ChannelId == ch.Id))
|
||||
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == ch.Id && ilc.ItemType == IgnoredItemType.Channel))
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
@@ -772,7 +777,7 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
return;
|
||||
|
||||
if (!GuildLogSettings.TryGetValue(ch.Guild.Id, out LogSetting logSetting)
|
||||
|| (logSetting.ChannelCreatedId is null))
|
||||
|| logSetting.ChannelCreatedId is null)
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
@@ -817,7 +822,8 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
return;
|
||||
|
||||
if (!GuildLogSettings.TryGetValue(usr.Guild.Id, out LogSetting logSetting)
|
||||
|| (logSetting.LogVoicePresenceId is null))
|
||||
|| (logSetting.LogVoicePresenceId is null)
|
||||
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == iusr.Id && ilc.ItemType == IgnoredItemType.User))
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
@@ -862,49 +868,6 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
//private Task _client_UserPresenceUpdated(Optional<SocketGuild> optGuild, SocketUser usr, SocketPresence before, SocketPresence after)
|
||||
//{
|
||||
// var _ = Task.Run(async () =>
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// var guild = optGuild.GetValueOrDefault() ?? (usr as SocketGuildUser)?.Guild;
|
||||
|
||||
// if (guild is null)
|
||||
// return;
|
||||
|
||||
// if (!GuildLogSettings.TryGetValue(guild.Id, out LogSetting logSetting)
|
||||
// || (logSetting.LogUserPresenceId is null)
|
||||
// || before.Status == after.Status)
|
||||
// return;
|
||||
|
||||
// ITextChannel logChannel;
|
||||
// if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserPresence)) is null)
|
||||
// return;
|
||||
// string str = "";
|
||||
// if (before.Status != after.Status)
|
||||
// str = "🎭" + Format.Code(PrettyCurrentTime(g)) +
|
||||
// GetText(logChannel.Guild, strs.user_status_change(,
|
||||
// "👤" + Format.Bold(usr.Username),
|
||||
// Format.Bold(after.Status.ToString()));
|
||||
|
||||
// //if (before.Game?.Name != after.Game?.Name)
|
||||
// //{
|
||||
// // if (str != "")
|
||||
// // str += "\n";
|
||||
// // str += $"👾`{prettyCurrentTime}`👤__**{usr.Username}**__ is now playing **{after.Game?.Name}**.";
|
||||
// //}
|
||||
|
||||
// PresenceUpdates.AddOrUpdate(logChannel, new List<string>() { str }, (id, list) => { list.Add(str); return list; });
|
||||
// }
|
||||
// catch
|
||||
// {
|
||||
// // ignored
|
||||
// }
|
||||
// });
|
||||
// return Task.CompletedTask;
|
||||
//}
|
||||
|
||||
private Task _client_UserLeft(IGuildUser usr)
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
@@ -912,7 +875,8 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
try
|
||||
{
|
||||
if (!GuildLogSettings.TryGetValue(usr.Guild.Id, out LogSetting logSetting)
|
||||
|| (logSetting.UserLeftId is null))
|
||||
|| (logSetting.UserLeftId is null)
|
||||
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == usr.Id && ilc.ItemType == IgnoredItemType.User))
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
@@ -987,7 +951,8 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
try
|
||||
{
|
||||
if (!GuildLogSettings.TryGetValue(guild.Id, out LogSetting logSetting)
|
||||
|| (logSetting.UserUnbannedId is null))
|
||||
|| (logSetting.UserUnbannedId is null)
|
||||
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == usr.Id && ilc.ItemType == IgnoredItemType.User))
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
@@ -1021,7 +986,8 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
try
|
||||
{
|
||||
if (!GuildLogSettings.TryGetValue(guild.Id, out LogSetting logSetting)
|
||||
|| (logSetting.UserBannedId is null))
|
||||
|| (logSetting.UserBannedId is null)
|
||||
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == usr.Id && ilc.ItemType == IgnoredItemType.User))
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
@@ -1069,7 +1035,7 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
|
||||
if (!GuildLogSettings.TryGetValue(channel.Guild.Id, out LogSetting logSetting)
|
||||
|| (logSetting.MessageDeletedId is null)
|
||||
|| logSetting.IgnoredChannels.Any(ilc => ilc.ChannelId == channel.Id))
|
||||
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == channel.Id && ilc.ItemType == IgnoredItemType.Channel))
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
@@ -1127,7 +1093,7 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
|
||||
if (!GuildLogSettings.TryGetValue(channel.Guild.Id, out LogSetting logSetting)
|
||||
|| (logSetting.MessageUpdatedId is null)
|
||||
|| logSetting.IgnoredChannels.Any(ilc => ilc.ChannelId == channel.Id))
|
||||
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == channel.Id && ilc.ItemType == IgnoredItemType.Channel))
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
|
@@ -12,6 +12,8 @@ using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using NadekoBot.Db;
|
||||
using Serilog;
|
||||
using System.Threading;
|
||||
using System;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
@@ -21,6 +23,11 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly ConcurrentDictionary<ulong, IndexedCollection<ReactionRoleMessage>> _models;
|
||||
|
||||
/// <summary>
|
||||
/// Contains the (Message ID, User ID) of reaction roles that are currently being processed.
|
||||
/// </summary>
|
||||
private readonly ConcurrentHashSet<(ulong, ulong)> _reacting = new();
|
||||
|
||||
public RoleCommandsService(DiscordSocketClient client, DbService db,
|
||||
Bot bot)
|
||||
{
|
||||
@@ -38,75 +45,58 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
|
||||
private Task _client_ReactionAdded(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel chan, SocketReaction reaction)
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
if (!reaction.User.IsSpecified ||
|
||||
reaction.User.Value.IsBot ||
|
||||
reaction.User.Value is not SocketGuildUser gusr ||
|
||||
chan is not SocketGuildChannel gch ||
|
||||
!_models.TryGetValue(gch.Guild.Id, out var confs))
|
||||
return;
|
||||
|
||||
var conf = confs.FirstOrDefault(x => x.MessageId == msg.Id);
|
||||
|
||||
if (conf is null)
|
||||
return;
|
||||
|
||||
// compare emote names for backwards compatibility :facepalm:
|
||||
var reactionRole = conf.ReactionRoles.FirstOrDefault(x => x.EmoteName == reaction.Emote.Name || x.EmoteName == reaction.Emote.ToString());
|
||||
|
||||
if (reactionRole != null)
|
||||
{
|
||||
if (!reaction.User.IsSpecified ||
|
||||
reaction.User.Value.IsBot ||
|
||||
!(reaction.User.Value is SocketGuildUser gusr))
|
||||
return;
|
||||
|
||||
if (!(chan is SocketGuildChannel gch))
|
||||
return;
|
||||
|
||||
if (!_models.TryGetValue(gch.Guild.Id, out var confs))
|
||||
return;
|
||||
|
||||
var conf = confs.FirstOrDefault(x => x.MessageId == msg.Id);
|
||||
|
||||
if (conf is null)
|
||||
return;
|
||||
|
||||
// compare emote names for backwards compatibility :facepalm:
|
||||
var reactionRole = conf.ReactionRoles.FirstOrDefault(x => x.EmoteName == reaction.Emote.Name || x.EmoteName == reaction.Emote.ToString());
|
||||
if (reactionRole != null)
|
||||
if (!conf.Exclusive)
|
||||
{
|
||||
if (conf.Exclusive)
|
||||
{
|
||||
var roleIds = conf.ReactionRoles.Select(x => x.RoleId)
|
||||
.Where(x => x != reactionRole.RoleId)
|
||||
.Select(x => gusr.Guild.GetRole(x))
|
||||
.Where(x => x != null);
|
||||
|
||||
var __ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
//if the role is exclusive,
|
||||
// remove all other reactions user added to the message
|
||||
var dl = await msg.GetOrDownloadAsync().ConfigureAwait(false);
|
||||
foreach (var r in dl.Reactions)
|
||||
{
|
||||
if (r.Key.Name == reaction.Emote.Name)
|
||||
continue;
|
||||
try { await dl.RemoveReactionAsync(r.Key, gusr).ConfigureAwait(false); } catch { }
|
||||
await Task.Delay(100).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
await gusr.RemoveRolesAsync(roleIds).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var toAdd = gusr.Guild.GetRole(reactionRole.RoleId);
|
||||
if (toAdd != null && !gusr.Roles.Contains(toAdd))
|
||||
{
|
||||
await gusr.AddRolesAsync(new[] { toAdd }).ConfigureAwait(false);
|
||||
}
|
||||
await AddReactionRoleAsync(gusr, reactionRole);
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
// If same (message, user) are being processed in an exclusive rero, quit
|
||||
if (!_reacting.Add((msg.Id, reaction.UserId)))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var dl = await msg.GetOrDownloadAsync().ConfigureAwait(false);
|
||||
await dl.RemoveReactionAsync(reaction.Emote, dl.Author,
|
||||
new RequestOptions()
|
||||
{
|
||||
RetryMode = RetryMode.RetryRatelimit | RetryMode.Retry502
|
||||
}).ConfigureAwait(false);
|
||||
Log.Warning("User {0} is adding unrelated reactions to the reaction roles message.", dl.Author);
|
||||
var removeExclusiveTask = RemoveExclusiveReactionRoleAsync(msg, gusr, reaction, conf, reactionRole, CancellationToken.None);
|
||||
var addRoleTask = AddReactionRoleAsync(gusr, reactionRole);
|
||||
|
||||
await Task.WhenAll(removeExclusiveTask, addRoleTask).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Free (message/user) for another exclusive rero
|
||||
_reacting.TryRemove((msg.Id, reaction.UserId));
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
else
|
||||
{
|
||||
var dl = await msg.GetOrDownloadAsync().ConfigureAwait(false);
|
||||
await dl.RemoveReactionAsync(reaction.Emote, dl.Author,
|
||||
new RequestOptions()
|
||||
{
|
||||
RetryMode = RetryMode.RetryRatelimit | RetryMode.Retry502
|
||||
}).ConfigureAwait(false);
|
||||
Log.Warning("User {0} is adding unrelated reactions to the reaction roles message.", dl.Author);
|
||||
}
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
@@ -114,16 +104,16 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
|
||||
private Task _client_ReactionRemoved(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel chan, SocketReaction reaction)
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!reaction.User.IsSpecified ||
|
||||
reaction.User.Value.IsBot ||
|
||||
!(reaction.User.Value is SocketGuildUser gusr))
|
||||
reaction.User.Value is not SocketGuildUser gusr)
|
||||
return;
|
||||
|
||||
if (!(chan is SocketGuildChannel gch))
|
||||
if (chan is not SocketGuildChannel gch)
|
||||
return;
|
||||
|
||||
if (!_models.TryGetValue(gch.Guild.Id, out var confs))
|
||||
@@ -193,5 +183,71 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
uow.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a reaction role to the specified user.
|
||||
/// </summary>
|
||||
/// <param name="user">A Discord guild user.</param>
|
||||
/// <param name="dbRero">The database settings of this reaction role.</param>
|
||||
private Task AddReactionRoleAsync(SocketGuildUser user, ReactionRole dbRero)
|
||||
{
|
||||
var toAdd = user.Guild.GetRole(dbRero.RoleId);
|
||||
|
||||
return (toAdd != null && !user.Roles.Contains(toAdd))
|
||||
? user.AddRoleAsync(toAdd)
|
||||
: Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the exclusive reaction roles and reactions from the specified user.
|
||||
/// </summary>
|
||||
/// <param name="reactionMessage">The Discord message that contains the reaction roles.</param>
|
||||
/// <param name="user">A Discord guild user.</param>
|
||||
/// <param name="reaction">The Discord reaction of the user.</param>
|
||||
/// <param name="dbReroMsg">The database entry of the reaction role message.</param>
|
||||
/// <param name="dbRero">The database settings of this reaction role.</param>
|
||||
/// <param name="cToken">A cancellation token to cancel the operation.</param>
|
||||
/// <exception cref="OperationCanceledException">Occurs when the operation is cancelled before it began.</exception>
|
||||
/// <exception cref="TaskCanceledException">Occurs when the operation is cancelled while it's still executing.</exception>
|
||||
private Task RemoveExclusiveReactionRoleAsync(Cacheable<IUserMessage, ulong> reactionMessage, SocketGuildUser user, SocketReaction reaction, ReactionRoleMessage dbReroMsg, ReactionRole dbRero, CancellationToken cToken = default)
|
||||
{
|
||||
cToken.ThrowIfCancellationRequested();
|
||||
|
||||
var roleIds = dbReroMsg.ReactionRoles.Select(x => x.RoleId)
|
||||
.Where(x => x != dbRero.RoleId)
|
||||
.Select(x => user.Guild.GetRole(x))
|
||||
.Where(x => x != null);
|
||||
|
||||
var removeReactionsTask = RemoveOldReactionsAsync(reactionMessage, user, reaction, cToken);
|
||||
|
||||
var removeRolesTask = user.RemoveRolesAsync(roleIds);
|
||||
|
||||
return Task.WhenAll(removeReactionsTask, removeRolesTask);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes old reactions from an exclusive reaction role.
|
||||
/// </summary>
|
||||
/// <param name="reactionMessage">The Discord message that contains the reaction roles.</param>
|
||||
/// <param name="user">A Discord guild user.</param>
|
||||
/// <param name="reaction">The Discord reaction of the user.</param>
|
||||
/// <param name="cToken">A cancellation token to cancel the operation.</param>
|
||||
/// <exception cref="OperationCanceledException">Occurs when the operation is cancelled before it began.</exception>
|
||||
/// <exception cref="TaskCanceledException">Occurs when the operation is cancelled while it's still executing.</exception>
|
||||
private async Task RemoveOldReactionsAsync(Cacheable<IUserMessage, ulong> reactionMessage, SocketGuildUser user, SocketReaction reaction, CancellationToken cToken = default)
|
||||
{
|
||||
cToken.ThrowIfCancellationRequested();
|
||||
|
||||
//if the role is exclusive,
|
||||
// remove all other reactions user added to the message
|
||||
var dl = await reactionMessage.GetOrDownloadAsync().ConfigureAwait(false);
|
||||
foreach (var r in dl.Reactions)
|
||||
{
|
||||
if (r.Key.Name == reaction.Emote.Name)
|
||||
continue;
|
||||
try { await dl.RemoveReactionAsync(r.Key, user).ConfigureAwait(false); } catch { }
|
||||
await Task.Delay(100, cToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -41,8 +41,11 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
}, null, TimeSpan.FromSeconds(0), TimeSpan.FromHours(12));
|
||||
}
|
||||
|
||||
public async Task<WarningPunishment> Warn(IGuild guild, ulong userId, IUser mod, string reason)
|
||||
public async Task<WarningPunishment> Warn(IGuild guild, ulong userId, IUser mod, int weight, string reason)
|
||||
{
|
||||
if (weight <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(weight));
|
||||
|
||||
var modName = mod.ToString();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(reason))
|
||||
@@ -57,29 +60,32 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
Forgiven = false,
|
||||
Reason = reason,
|
||||
Moderator = modName,
|
||||
Weight = weight,
|
||||
};
|
||||
|
||||
int warnings = 1;
|
||||
long previousCount;
|
||||
List<WarningPunishment> ps;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
ps = uow.GuildConfigsForId(guildId, set => set.Include(x => x.WarnPunishments))
|
||||
.WarnPunishments;
|
||||
|
||||
warnings += uow
|
||||
.Warnings
|
||||
.ForId(guildId, userId)
|
||||
.Where(w => !w.Forgiven && w.UserId == userId)
|
||||
.Count();
|
||||
previousCount = uow.Warnings.ForId(guildId, userId)
|
||||
.Where(w => !w.Forgiven && w.UserId == userId)
|
||||
.Sum(x => x.Weight);
|
||||
|
||||
uow.Warnings.Add(warn);
|
||||
|
||||
uow.SaveChanges();
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
var p = ps.FirstOrDefault(x => x.Count == warnings);
|
||||
var totalCount = previousCount + weight;
|
||||
var p = ps.Where(x => x.Count > previousCount && x.Count <= totalCount)
|
||||
.OrderByDescending(x => x.Count)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (p != null)
|
||||
|
||||
if (p is not null)
|
||||
{
|
||||
var user = await guild.GetUserAsync(userId).ConfigureAwait(false);
|
||||
if (user is null)
|
||||
@@ -95,6 +101,10 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
public async Task ApplyPunishment(IGuild guild, IGuildUser user, IUser mod, PunishmentAction p, int minutes,
|
||||
ulong? roleId, string reason)
|
||||
{
|
||||
|
||||
if (!await CheckPermission(guild, p))
|
||||
return;
|
||||
|
||||
switch (p)
|
||||
{
|
||||
case PunishmentAction.Mute:
|
||||
@@ -167,6 +177,40 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to prevent the bot from hitting 403's when it needs to
|
||||
/// apply punishments with insufficient permissions
|
||||
/// </summary>
|
||||
/// <param name="guild">Guild the punishment is applied in</param>
|
||||
/// <param name="punish">Punishment to apply</param>
|
||||
/// <returns>Whether the bot has sufficient permissions</returns>
|
||||
private async Task<bool> CheckPermission(IGuild guild, PunishmentAction punish)
|
||||
{
|
||||
|
||||
var botUser = await guild.GetCurrentUserAsync();
|
||||
switch (punish)
|
||||
{
|
||||
case PunishmentAction.Mute:
|
||||
return botUser.GuildPermissions.MuteMembers && botUser.GuildPermissions.ManageRoles;
|
||||
case PunishmentAction.Kick:
|
||||
return botUser.GuildPermissions.KickMembers;
|
||||
case PunishmentAction.Ban:
|
||||
return botUser.GuildPermissions.BanMembers;
|
||||
case PunishmentAction.Softban:
|
||||
return botUser.GuildPermissions.BanMembers; // ban + unban
|
||||
case PunishmentAction.RemoveRoles:
|
||||
return botUser.GuildPermissions.ManageRoles;
|
||||
case PunishmentAction.ChatMute:
|
||||
return botUser.GuildPermissions.ManageRoles; // adds nadeko-mute role
|
||||
case PunishmentAction.VoiceMute:
|
||||
return botUser.GuildPermissions.MuteMembers;
|
||||
case PunishmentAction.AddRole:
|
||||
return botUser.GuildPermissions.ManageRoles;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CheckAllWarnExpiresAsync()
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
@@ -457,7 +501,9 @@ WHERE GuildId={guildId}
|
||||
{
|
||||
template = JsonConvert.SerializeObject(new
|
||||
{
|
||||
color = _bcs.Data.Color.Error,
|
||||
//To get the decimal version of the color that's expected, take the packed value of the Rgba32
|
||||
//and bitshift it to the right by 8 bits, thereby dropping the "a" and getting a reprensentation of the RGB value
|
||||
color = _bcs.Data.Color.Error.PackedValue >> 8,
|
||||
description = defaultMessage
|
||||
});
|
||||
}
|
||||
@@ -472,7 +518,9 @@ WHERE GuildId={guildId}
|
||||
{
|
||||
template = JsonConvert.SerializeObject(new
|
||||
{
|
||||
color = _bcs.Data.Color.Error,
|
||||
//To get the decimal version of the color that's expected, take the packed value of the Rgba32
|
||||
//and bitshift it to the right by 8 bits, thereby dropping the "a" and getting a reprensentation of the RGB value
|
||||
color = _bcs.Data.Color.Error.PackedValue >> 8,
|
||||
description = template
|
||||
});
|
||||
}
|
||||
|
@@ -54,8 +54,17 @@ namespace NadekoBot.Modules.Administration
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
public async Task Warn(IGuildUser user, [Leftover] string reason = null)
|
||||
public Task Warn(IGuildUser user, [Leftover] string reason = null)
|
||||
=> Warn(1, user, reason);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
public async Task Warn(int weight, IGuildUser user, [Leftover] string reason = null)
|
||||
{
|
||||
if (weight <= 0)
|
||||
return;
|
||||
|
||||
if (!await CheckRoleHierarchy(user))
|
||||
return;
|
||||
|
||||
@@ -76,7 +85,7 @@ namespace NadekoBot.Modules.Administration
|
||||
WarningPunishment punishment;
|
||||
try
|
||||
{
|
||||
punishment = await _service.Warn(ctx.Guild, user.Id, ctx.User, reason).ConfigureAwait(false);
|
||||
punishment = await _service.Warn(ctx.Guild, user.Id, ctx.User, weight, reason).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -230,19 +239,29 @@ namespace NadekoBot.Modules.Administration
|
||||
}
|
||||
else
|
||||
{
|
||||
var descText = GetText(strs.warn_count(
|
||||
Format.Bold(warnings.Where(x => !x.Forgiven).Sum(x => x.Weight).ToString()),
|
||||
Format.Bold(warnings.Sum(x => x.Weight).ToString())));
|
||||
|
||||
embed.WithDescription(descText);
|
||||
|
||||
var i = page * 9;
|
||||
foreach (var w in warnings)
|
||||
{
|
||||
i++;
|
||||
var name = GetText(strs.warned_on_by(
|
||||
w.DateAdded.Value.ToString("dd.MM.yyy"),
|
||||
w.DateAdded.Value.ToString("HH:mm"),
|
||||
w.DateAdded?.ToString("dd.MM.yyy"),
|
||||
w.DateAdded?.ToString("HH:mm"),
|
||||
w.Moderator));
|
||||
|
||||
if (w.Forgiven)
|
||||
name = $"{Format.Strikethrough(name)} {GetText(strs.warn_cleared_by(w.ForgivenBy))}";
|
||||
|
||||
embed.AddField($"#`{i}` " + name, w.Reason.TrimTo(1020));
|
||||
|
||||
embed.AddField($"#`{i}` " + name,
|
||||
Format.Code(GetText(strs.warn_weight(w.Weight))) +
|
||||
'\n' +
|
||||
w.Reason.TrimTo(1000));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,7 +469,7 @@ namespace NadekoBot.Modules.Administration
|
||||
}
|
||||
}
|
||||
|
||||
await _mute.TimedBan(ctx.Guild, user, time.Time, ctx.User.ToString() + " | " + msg).ConfigureAwait(false);
|
||||
await _mute.TimedBan(ctx.Guild, user, time.Time, (ctx.User.ToString() + " | " + msg).TrimTo(512)).ConfigureAwait(false);
|
||||
var toSend = _eb.Create().WithOkColor()
|
||||
.WithTitle("⛔️ " + GetText(strs.banned_user))
|
||||
.AddField(GetText(strs.username), user.ToString(), true)
|
||||
@@ -476,7 +495,7 @@ namespace NadekoBot.Modules.Administration
|
||||
var user = await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(ctx.Guild.Id, userId);
|
||||
if (user is null)
|
||||
{
|
||||
await ctx.Guild.AddBanAsync(userId, 7, ctx.User.ToString() + " | " + msg);
|
||||
await ctx.Guild.AddBanAsync(userId, 7, (ctx.User.ToString() + " | " + msg).TrimTo(512));
|
||||
|
||||
await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
|
||||
.WithTitle("⛔️ " + GetText(strs.banned_user))
|
||||
@@ -516,7 +535,7 @@ namespace NadekoBot.Modules.Administration
|
||||
dmFailed = true;
|
||||
}
|
||||
|
||||
await ctx.Guild.AddBanAsync(user, 7, ctx.User.ToString() + " | " + msg).ConfigureAwait(false);
|
||||
await ctx.Guild.AddBanAsync(user, 7, (ctx.User.ToString() + " | " + msg).TrimTo(512)).ConfigureAwait(false);
|
||||
|
||||
var toSend = _eb.Create().WithOkColor()
|
||||
.WithTitle("⛔️ " + GetText(strs.banned_user))
|
||||
@@ -692,7 +711,7 @@ namespace NadekoBot.Modules.Administration
|
||||
dmFailed = true;
|
||||
}
|
||||
|
||||
await ctx.Guild.AddBanAsync(user, 7, "Softban | " + ctx.User.ToString() + " | " + msg).ConfigureAwait(false);
|
||||
await ctx.Guild.AddBanAsync(user, 7, ("Softban | " + ctx.User.ToString() + " | " + msg).TrimTo(512)).ConfigureAwait(false);
|
||||
try { await ctx.Guild.RemoveBanAsync(user).ConfigureAwait(false); }
|
||||
catch { await ctx.Guild.RemoveBanAsync(user).ConfigureAwait(false); }
|
||||
|
||||
@@ -749,7 +768,7 @@ namespace NadekoBot.Modules.Administration
|
||||
dmFailed = true;
|
||||
}
|
||||
|
||||
await user.KickAsync(ctx.User.ToString() + " | " + msg).ConfigureAwait(false);
|
||||
await user.KickAsync((ctx.User.ToString() + " | " + msg).TrimTo(512)).ConfigureAwait(false);
|
||||
|
||||
var toSend = _eb.Create().WithOkColor()
|
||||
.WithTitle(GetText(strs.kicked_user))
|
||||
@@ -776,23 +795,32 @@ namespace NadekoBot.Modules.Administration
|
||||
return;
|
||||
|
||||
var missing = new List<string>();
|
||||
var banning = new HashSet<IGuildUser>();
|
||||
var banning = new HashSet<IUser>();
|
||||
|
||||
await ctx.Channel.TriggerTypingAsync();
|
||||
foreach (var userStr in userStrings)
|
||||
{
|
||||
if (ulong.TryParse(userStr, out var userId))
|
||||
{
|
||||
var user = await ctx.Guild.GetUserAsync(userId) ??
|
||||
IUser user = await ctx.Guild.GetUserAsync(userId) ??
|
||||
await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(ctx.Guild.Id, userId);
|
||||
|
||||
if (user is null)
|
||||
{
|
||||
missing.Add(userStr);
|
||||
continue;
|
||||
// if IGuildUser is null, try to get IUser
|
||||
user = await ((DiscordSocketClient)Context.Client).Rest.GetUserAsync(userId);
|
||||
|
||||
// only add to missing if *still* null
|
||||
if (user is null)
|
||||
{
|
||||
missing.Add(userStr);
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!await CheckRoleHierarchy(user))
|
||||
//Hierachy checks only if the user is in the guild
|
||||
if (user is IGuildUser gu && !await CheckRoleHierarchy(gu))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -820,7 +848,7 @@ namespace NadekoBot.Modules.Administration
|
||||
{
|
||||
try
|
||||
{
|
||||
await toBan.BanAsync(7);
|
||||
await ctx.Guild.AddBanAsync(toBan.Id, 7, $"{ctx.User} | Massban");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@@ -149,7 +149,7 @@ namespace NadekoBot.Modules.CustomReactions
|
||||
await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
|
||||
.WithDescription($"#{id}")
|
||||
.AddField(GetText(strs.trigger), found.Trigger.TrimTo(1024))
|
||||
.AddField(GetText(strs.response), (found.Response + "\n```css\n" + found.Response).TrimTo(1020) + "```")
|
||||
.AddField(GetText(strs.response), found.Response.TrimTo(1000).Replace("](", "]\\("))
|
||||
).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -310,7 +310,7 @@ namespace NadekoBot.Modules.CustomReactions
|
||||
_ = ctx.Channel.TriggerTypingAsync();
|
||||
|
||||
var serialized = _service.ExportCrs(ctx.Guild?.Id);
|
||||
using var stream = await serialized.ToStream();
|
||||
await using var stream = await serialized.ToStream();
|
||||
await ctx.Channel.SendFileAsync(stream, "crs-export.yml", text: null);
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,8 @@ using System.Collections.Generic;
|
||||
using Cloneable;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Yml;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using YamlDotNet.Serialization;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common
|
||||
{
|
||||
@@ -19,10 +21,11 @@ namespace NadekoBot.Modules.Gambling.Common
|
||||
Generation = new GenerationConfig();
|
||||
Timely = new TimelyConfig();
|
||||
Decay = new DecayConfig();
|
||||
Slots = new SlotsConfig();
|
||||
}
|
||||
|
||||
[Comment(@"DO NOT CHANGE")]
|
||||
public int Version { get; set; } = 1;
|
||||
public int Version { get; set; } = 2;
|
||||
|
||||
[Comment(@"Currency settings")]
|
||||
public CurrencyConfig Currency { get; set; }
|
||||
@@ -59,6 +62,13 @@ Set 0 for unlimited")]
|
||||
[Comment(@"Amount of currency selfhosters will get PER pledged dollar CENT.
|
||||
1 = 100 currency per $. Used almost exclusively on public nadeko.")]
|
||||
public decimal PatreonCurrencyPerCent { get; set; } = 1;
|
||||
|
||||
[Comment(@"Currency reward per vote.
|
||||
This will work only if you've set up VotesApi and correct credentials for topgg and/or discords voting")]
|
||||
public long VoteReward { get; set; } = 100;
|
||||
|
||||
[Comment(@"Slot config")]
|
||||
public SlotsConfig Slots { get; set; }
|
||||
}
|
||||
|
||||
public class CurrencyConfig
|
||||
@@ -179,7 +189,8 @@ default is 0.02, which is 2%")]
|
||||
|
||||
public MultipliersData Multipliers { get; set; } = new MultipliersData();
|
||||
|
||||
[Comment(@"List of items available for gifting.")]
|
||||
[Comment(@"List of items available for gifting.
|
||||
If negative is true, gift will instead reduce waifu value.")]
|
||||
public List<WaifuItemModel> Items { get; set; } = new List<WaifuItemModel>();
|
||||
|
||||
public WaifuConfig()
|
||||
@@ -260,6 +271,17 @@ Default 1 (meaning no effect)")]
|
||||
Default 0.95 (meaning 95%)
|
||||
Example: If a waifu is worth 1000, and she receives a gift worth 100, her new value will be 1095)")]
|
||||
public decimal GiftEffect { get; set; } = 0.95M;
|
||||
|
||||
[Comment(@"What percentage of the value of the gift will a waifu lose when she's gifted a gift marked as 'negative'.
|
||||
Default 0.5 (meaning 50%)
|
||||
Example: If a waifu is worth 1000, and she receives a negative gift worth 100, her new value will be 950)")]
|
||||
public decimal NegativeGiftEffect { get; set; } = 0.50M;
|
||||
}
|
||||
|
||||
public sealed partial class SlotsConfig
|
||||
{
|
||||
[Comment(@"Hex value of the color which the numbers on the slot image will have.")]
|
||||
public Rgba32 CurrencyFontColor { get; set; } = SixLabors.ImageSharp.Color.Red;
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
@@ -268,19 +290,24 @@ Example: If a waifu is worth 1000, and she receives a gift worth 100, her new va
|
||||
public string ItemEmoji { get; set; }
|
||||
public int Price { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
[YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)]
|
||||
public bool Negative { get; set; }
|
||||
|
||||
public WaifuItemModel()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public WaifuItemModel(string itemEmoji, int price, string name)
|
||||
public WaifuItemModel(string itemEmoji, int price, string name, bool negative = false)
|
||||
{
|
||||
ItemEmoji = itemEmoji;
|
||||
Price = price;
|
||||
Name = name;
|
||||
Negative = negative;
|
||||
}
|
||||
|
||||
|
||||
public override string ToString() => Name;
|
||||
}
|
||||
|
||||
|
8
src/NadekoBot/Modules/Gambling/Common/GamblingError.cs
Normal file
8
src/NadekoBot/Modules/Gambling/Common/GamblingError.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
{
|
||||
public enum GamblingError
|
||||
{
|
||||
None,
|
||||
NotEnough
|
||||
}
|
||||
}
|
44
src/NadekoBot/Modules/Gambling/Common/Slot/SlotGame.cs
Normal file
44
src/NadekoBot/Modules/Gambling/Common/Slot/SlotGame.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NadekoBot.Common;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common.Slot
|
||||
{
|
||||
public class SlotGame
|
||||
{
|
||||
public class Result
|
||||
{
|
||||
public float Multiplier { get; }
|
||||
public int[] Rolls { get; }
|
||||
|
||||
public Result(float multiplier, int[] rolls)
|
||||
{
|
||||
Multiplier = multiplier;
|
||||
Rolls = rolls;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Random _rng = new NadekoRandom();
|
||||
|
||||
public SlotGame()
|
||||
{
|
||||
}
|
||||
|
||||
public Result Spin()
|
||||
{
|
||||
var rolls = new int[] { _rng.Next(0, 6), _rng.Next(0, 6), _rng.Next(0, 6) };
|
||||
var multi = 0;
|
||||
|
||||
if (rolls.All(x => x == 5))
|
||||
multi = 30;
|
||||
else if (rolls.All(x => x == rolls[0]))
|
||||
multi = 10;
|
||||
else if (rolls.Count(x => x == 5) == 2)
|
||||
multi = 4;
|
||||
else if (rolls.Any(x => x == 5))
|
||||
multi = 1;
|
||||
|
||||
return new Result(multi, rolls);
|
||||
}
|
||||
}
|
||||
}
|
12
src/NadekoBot/Modules/Gambling/Common/SlotResponse.cs
Normal file
12
src/NadekoBot/Modules/Gambling/Common/SlotResponse.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
{
|
||||
public class SlotResponse
|
||||
{
|
||||
public float Multiplier { get; set; }
|
||||
public long Won { get; set; }
|
||||
public List<int> Rolls { get; set; } = new List<int>();
|
||||
public GamblingError Error { get; set; }
|
||||
}
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
namespace NadekoBot.Modules.Gambling.Common.Waifu
|
||||
{
|
||||
public struct WaifuProfileTitle
|
||||
{
|
||||
public int Count { get; }
|
||||
public string Title { get; }
|
||||
|
||||
public WaifuProfileTitle(int count, string title)
|
||||
{
|
||||
Count = count;
|
||||
Title = title;
|
||||
}
|
||||
}
|
||||
}
|
@@ -17,11 +17,6 @@ namespace NadekoBot.Modules.Gambling
|
||||
[Group]
|
||||
public class CurrencyEventsCommands : GamblingSubmodule<CurrencyEventsService>
|
||||
{
|
||||
public enum OtherEvent
|
||||
{
|
||||
BotListUpvoters
|
||||
}
|
||||
|
||||
public CurrencyEventsCommands(GamblingConfigService gamblingConf) : base(gamblingConf)
|
||||
{
|
||||
}
|
||||
@@ -37,41 +32,36 @@ namespace NadekoBot.Modules.Gambling
|
||||
ctx.Channel.Id,
|
||||
ev,
|
||||
opts,
|
||||
GetEmbed
|
||||
).ConfigureAwait(false))
|
||||
GetEmbed))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.start_event_fail).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private IEmbedBuilder GetEmbed(CurrencyEvent.Type type, EventOptions opts, long currentPot)
|
||||
{
|
||||
switch (type)
|
||||
return type switch
|
||||
{
|
||||
case CurrencyEvent.Type.Reaction:
|
||||
return _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.event_title(type.ToString())))
|
||||
.WithDescription(GetReactionDescription(opts.Amount, currentPot))
|
||||
.WithFooter(GetText(strs.event_duration_footer(opts.Hours)));
|
||||
case CurrencyEvent.Type.GameStatus:
|
||||
return _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.event_title(type.ToString())))
|
||||
.WithDescription(GetGameStatusDescription(opts.Amount, currentPot))
|
||||
.WithFooter(GetText(strs.event_duration_footer(opts.Hours)));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
throw new ArgumentOutOfRangeException(nameof(type));
|
||||
CurrencyEvent.Type.Reaction => _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.event_title(type.ToString())))
|
||||
.WithDescription(GetReactionDescription(opts.Amount, currentPot))
|
||||
.WithFooter(GetText(strs.event_duration_footer(opts.Hours))),
|
||||
CurrencyEvent.Type.GameStatus => _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.event_title(type.ToString())))
|
||||
.WithDescription(GetGameStatusDescription(opts.Amount, currentPot))
|
||||
.WithFooter(GetText(strs.event_duration_footer(opts.Hours))),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type))
|
||||
};
|
||||
}
|
||||
|
||||
private string GetReactionDescription(long amount, long potSize)
|
||||
{
|
||||
string potSizeStr = Format.Bold(potSize == 0
|
||||
var potSizeStr = Format.Bold(potSize == 0
|
||||
? "∞" + CurrencySign
|
||||
: potSize.ToString() + CurrencySign);
|
||||
: potSize + CurrencySign);
|
||||
|
||||
return GetText(strs.new_reaction_event(
|
||||
CurrencySign,
|
||||
Format.Bold(amount + CurrencySign),
|
||||
@@ -80,9 +70,10 @@ namespace NadekoBot.Modules.Gambling
|
||||
|
||||
private string GetGameStatusDescription(long amount, long potSize)
|
||||
{
|
||||
string potSizeStr = Format.Bold(potSize == 0
|
||||
var potSizeStr = Format.Bold(potSize == 0
|
||||
? "∞" + CurrencySign
|
||||
: potSize.ToString() + CurrencySign);
|
||||
: potSize + CurrencySign);
|
||||
|
||||
return GetText(strs.new_gamestatus_event(
|
||||
CurrencySign,
|
||||
Format.Bold(amount + CurrencySign),
|
||||
|
@@ -22,15 +22,12 @@ namespace NadekoBot.Modules.Gambling
|
||||
{
|
||||
private readonly IImageCache _images;
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly DbService _db;
|
||||
private static readonly NadekoRandom rng = new NadekoRandom();
|
||||
|
||||
public FlipCoinCommands(IDataCache data, ICurrencyService cs, DbService db,
|
||||
GamblingConfigService gss) : base(gss)
|
||||
public FlipCoinCommands(IDataCache data, ICurrencyService cs, GamblingConfigService gss) : base(gss)
|
||||
{
|
||||
_images = data.LocalImages;
|
||||
_cs = cs;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
@@ -66,11 +66,11 @@ namespace NadekoBot.Modules.Gambling
|
||||
}
|
||||
var embed = _eb.Create()
|
||||
.WithTitle(GetText(strs.economy_state))
|
||||
.AddField(GetText(strs.currency_owned), ((BigInteger)(ec.Cash - ec.Bot)) + CurrencySign)
|
||||
.AddField(GetText(strs.currency_owned), ((BigInteger)(ec.Cash - ec.Bot)).ToString("N", _enUsCulture) + CurrencySign)
|
||||
.AddField(GetText(strs.currency_one_percent), (onePercent * 100).ToString("F2") + "%")
|
||||
.AddField(GetText(strs.currency_planted), ((BigInteger)ec.Planted) + CurrencySign)
|
||||
.AddField(GetText(strs.owned_waifus_total), ((BigInteger)ec.Waifus) + CurrencySign)
|
||||
.AddField(GetText(strs.bot_currency), ec.Bot + CurrencySign)
|
||||
.AddField(GetText(strs.bot_currency), ec.Bot.ToString("N", _enUsCulture) + CurrencySign)
|
||||
.AddField(GetText(strs.total), ((BigInteger)(ec.Cash + ec.Planted + ec.Waifus)).ToString("N", _enUsCulture) + CurrencySign)
|
||||
.WithOkColor();
|
||||
// ec.Cash already contains ec.Bot as it's the total of all values in the CurrencyAmount column of the DiscordUser table
|
||||
@@ -247,25 +247,33 @@ namespace NadekoBot.Modules.Gambling
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
[Priority(0)]
|
||||
public Task Award(ShmartNumber amount, IGuildUser usr, [Leftover] string msg) =>
|
||||
public Task Award(long amount, IGuildUser usr, [Leftover] string msg) =>
|
||||
Award(amount, usr.Id, msg);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
[Priority(1)]
|
||||
public Task Award(ShmartNumber amount, [Leftover] IGuildUser usr) =>
|
||||
public Task Award(long amount, [Leftover] IGuildUser usr) =>
|
||||
Award(amount, usr.Id);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[OwnerOnly]
|
||||
[Priority(2)]
|
||||
public async Task Award(ShmartNumber amount, ulong usrId, [Leftover] string msg = null)
|
||||
public async Task Award(long amount, ulong usrId, [Leftover] string msg = null)
|
||||
{
|
||||
if (amount <= 0)
|
||||
return;
|
||||
|
||||
await _cs.AddAsync(usrId,
|
||||
var usr = await ((DiscordSocketClient)Context.Client).Rest.GetUserAsync(usrId);
|
||||
|
||||
if(usr is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.user_not_found).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await _cs.AddAsync(usr,
|
||||
$"Awarded by bot owner. ({ctx.User.Username}/{ctx.User.Id}) {(msg ?? "")}",
|
||||
amount,
|
||||
gamble: (ctx.Client.CurrentUser.Id != usrId)).ConfigureAwait(false);
|
||||
@@ -275,8 +283,8 @@ namespace NadekoBot.Modules.Gambling
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
[Priority(2)]
|
||||
public async Task Award(ShmartNumber amount, [Leftover] IRole role)
|
||||
[Priority(3)]
|
||||
public async Task Award(long amount, [Leftover] IRole role)
|
||||
{
|
||||
var users = (await ctx.Guild.GetUsersAsync().ConfigureAwait(false))
|
||||
.Where(u => u.GetRoles().Contains(role))
|
||||
@@ -284,7 +292,7 @@ namespace NadekoBot.Modules.Gambling
|
||||
|
||||
await _cs.AddBulkAsync(users.Select(x => x.Id),
|
||||
users.Select(x => $"Awarded by bot owner to **{role.Name}** role. ({ctx.User.Username}/{ctx.User.Id})"),
|
||||
users.Select(x => amount.Value),
|
||||
users.Select(x => amount),
|
||||
gamble: true)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
@@ -298,13 +306,13 @@ namespace NadekoBot.Modules.Gambling
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
[Priority(0)]
|
||||
public async Task Take(ShmartNumber amount, [Leftover] IRole role)
|
||||
public async Task Take(long amount, [Leftover] IRole role)
|
||||
{
|
||||
var users = (await role.GetMembersAsync()).ToList();
|
||||
|
||||
await _cs.RemoveBulkAsync(users.Select(x => x.Id),
|
||||
users.Select(x => $"Taken by bot owner from **{role.Name}** role. ({ctx.User.Username}/{ctx.User.Id})"),
|
||||
users.Select(x => amount.Value),
|
||||
users.Select(x => amount),
|
||||
gamble: true)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
@@ -318,7 +326,7 @@ namespace NadekoBot.Modules.Gambling
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
[Priority(1)]
|
||||
public async Task Take(ShmartNumber amount, [Leftover] IGuildUser user)
|
||||
public async Task Take(long amount, [Leftover] IGuildUser user)
|
||||
{
|
||||
if (amount <= 0)
|
||||
return;
|
||||
@@ -333,7 +341,7 @@ namespace NadekoBot.Modules.Gambling
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task Take(ShmartNumber amount, [Leftover] ulong usrId)
|
||||
public async Task Take(long amount, [Leftover] ulong usrId)
|
||||
{
|
||||
if (amount <= 0)
|
||||
return;
|
||||
|
@@ -8,10 +8,11 @@ using NadekoBot.Modules.Gambling.Services;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Common;
|
||||
|
||||
namespace NadekoBot.Modules.Games
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
{
|
||||
public partial class Games
|
||||
public partial class Gambling
|
||||
{
|
||||
[Group]
|
||||
public class PlantPickCommands : GamblingSubmodule<PlantPickService>
|
||||
@@ -53,7 +54,7 @@ namespace NadekoBot.Modules.Games
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Plant(int amount = 1, string pass = null)
|
||||
public async Task Plant(ShmartNumber amount, string pass = null)
|
||||
{
|
||||
if (amount < 1)
|
||||
return;
|
||||
@@ -63,18 +64,17 @@ namespace NadekoBot.Modules.Games
|
||||
return;
|
||||
}
|
||||
|
||||
var success = await _service.PlantAsync(ctx.Guild.Id, ctx.Channel, ctx.User.Id, ctx.User.ToString(), amount, pass);
|
||||
if (!success)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough( CurrencySign));
|
||||
return;
|
||||
}
|
||||
|
||||
if (((SocketGuild)ctx.Guild).CurrentUser.GuildPermissions.ManageMessages)
|
||||
{
|
||||
logService.AddDeleteIgnore(ctx.Message.Id);
|
||||
await ctx.Message.DeleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var success = await _service.PlantAsync(ctx.Guild.Id, ctx.Channel, ctx.User.Id, ctx.User.ToString(), amount, pass);
|
||||
if (!success)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough( CurrencySign));
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
@@ -8,8 +8,6 @@ using System.Threading.Tasks;
|
||||
using System;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using System.Net.Http;
|
||||
using Newtonsoft.Json;
|
||||
using System.Linq;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using Serilog;
|
||||
|
||||
@@ -17,76 +15,22 @@ namespace NadekoBot.Modules.Gambling.Services
|
||||
{
|
||||
public class CurrencyEventsService : INService
|
||||
{
|
||||
public class VoteModel
|
||||
{
|
||||
public ulong User { get; set; }
|
||||
public long Date { get; set; }
|
||||
}
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly IBotCredentials _creds;
|
||||
private readonly IHttpClientFactory _http;
|
||||
private readonly GamblingConfigService _configService;
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, ICurrencyEvent> _events =
|
||||
new ConcurrentDictionary<ulong, ICurrencyEvent>();
|
||||
|
||||
public CurrencyEventsService(DiscordSocketClient client,
|
||||
IBotCredentials creds, ICurrencyService cs,
|
||||
IHttpClientFactory http, GamblingConfigService configService)
|
||||
|
||||
public CurrencyEventsService(
|
||||
DiscordSocketClient client,
|
||||
ICurrencyService cs,
|
||||
GamblingConfigService configService)
|
||||
{
|
||||
_client = client;
|
||||
_cs = cs;
|
||||
_creds = creds;
|
||||
_http = http;
|
||||
_configService = configService;
|
||||
|
||||
if (_client.ShardId == 0)
|
||||
{
|
||||
Task t = BotlistUpvoteLoop();
|
||||
}
|
||||
}
|
||||
|
||||
// todo future use votes api directly?
|
||||
private async Task BotlistUpvoteLoop()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_creds.VotesUrl))
|
||||
return;
|
||||
|
||||
while (true)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromHours(1)).ConfigureAwait(false);
|
||||
await TriggerVoteCheck().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TriggerVoteCheck()
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var req = new HttpRequestMessage(HttpMethod.Get, _creds.VotesUrl))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(_creds.VotesToken))
|
||||
req.Headers.Add("Authorization", _creds.VotesToken);
|
||||
using (var http = _http.CreateClient())
|
||||
using (var res = await http.SendAsync(req).ConfigureAwait(false))
|
||||
{
|
||||
if (!res.IsSuccessStatusCode)
|
||||
{
|
||||
Log.Warning("Botlist API not reached.");
|
||||
return;
|
||||
}
|
||||
var resStr = await res.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
var ids = JsonConvert.DeserializeObject<VoteModel[]>(resStr)
|
||||
.Select(x => x.User)
|
||||
.Distinct();
|
||||
await _cs.AddBulkAsync(ids, ids.Select(x => "Voted - <https://discordbots.org/bot/nadeko/vote>"), ids.Select(x => 10L), true).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error in TriggerVoteCheck");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> TryCreateEventAsync(ulong guildId, ulong channelId, CurrencyEvent.Type type,
|
||||
@@ -127,6 +71,7 @@ namespace NadekoBot.Modules.Gambling.Services
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
@@ -136,4 +81,4 @@ namespace NadekoBot.Modules.Gambling.Services
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,7 +1,11 @@
|
||||
using NadekoBot.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Configs;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Services
|
||||
{
|
||||
@@ -34,9 +38,48 @@ namespace NadekoBot.Modules.Gambling.Services
|
||||
AddParsedProp("waifu.multi.divorce_value", gs => gs.Waifu.Multipliers.DivorceNewValue, decimal.TryParse, ConfigPrinters.ToString, val => val > 0);
|
||||
AddParsedProp("waifu.multi.all_gifts", gs => gs.Waifu.Multipliers.AllGiftPrices, decimal.TryParse, ConfigPrinters.ToString, val => val > 0);
|
||||
AddParsedProp("waifu.multi.gift_effect", gs => gs.Waifu.Multipliers.GiftEffect, decimal.TryParse, ConfigPrinters.ToString, val => val >= 0);
|
||||
AddParsedProp("waifu.multi.negative_gift_effect", gs => gs.Waifu.Multipliers.NegativeGiftEffect, decimal.TryParse, ConfigPrinters.ToString, val => val >= 0);
|
||||
AddParsedProp("decay.percent", gs => gs.Decay.Percent, decimal.TryParse, ConfigPrinters.ToString, val => val >= 0 && val <= 1);
|
||||
AddParsedProp("decay.maxdecay", gs => gs.Decay.MaxDecay, int.TryParse, ConfigPrinters.ToString, val => val >= 0);
|
||||
AddParsedProp("decay.threshold", gs => gs.Decay.MinThreshold, int.TryParse, ConfigPrinters.ToString, val => val >= 0);
|
||||
|
||||
Migrate();
|
||||
}
|
||||
|
||||
private readonly IEnumerable<WaifuItemModel> antiGiftSeed = new[]
|
||||
{
|
||||
new WaifuItemModel("🥀", 100, "WiltedRose", true),
|
||||
new WaifuItemModel("✂️", 1000, "Haircut", true),
|
||||
new WaifuItemModel("🧻", 10000, "ToiletPaper", true),
|
||||
};
|
||||
|
||||
public void Migrate()
|
||||
{
|
||||
if (_data.Version < 2)
|
||||
{
|
||||
ModifyConfig(c =>
|
||||
{
|
||||
c.Waifu.Items = c.Waifu.Items.Concat(antiGiftSeed).ToList();
|
||||
c.Version = 2;
|
||||
});
|
||||
}
|
||||
|
||||
if (_data.Version < 3)
|
||||
{
|
||||
ModifyConfig(c =>
|
||||
{
|
||||
c.Version = 3;
|
||||
c.VoteReward = 100;
|
||||
});
|
||||
}
|
||||
|
||||
if (_data.Version < 4)
|
||||
{
|
||||
ModifyConfig(c =>
|
||||
{
|
||||
c.Version = 4;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,12 +6,14 @@ using NadekoBot.Modules.Gambling.Common.WheelOfFortune;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.Gambling.Common.Slot;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using Serilog;
|
||||
|
||||
@@ -82,6 +84,41 @@ WHERE CurrencyAmount > {config.Decay.MinThreshold} AND UserId!={_client.CurrentU
|
||||
}, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SlotResponse> SlotAsync(ulong userId, long amount)
|
||||
{
|
||||
var takeRes = await _cs.RemoveAsync(userId, "Slot Machine", amount, true);
|
||||
|
||||
if (!takeRes)
|
||||
{
|
||||
return new SlotResponse
|
||||
{
|
||||
Error = GamblingError.NotEnough
|
||||
};
|
||||
}
|
||||
|
||||
var game = new SlotGame();
|
||||
var result = game.Spin();
|
||||
long won = 0;
|
||||
|
||||
if (result.Multiplier > 0)
|
||||
{
|
||||
won = (long)(result.Multiplier * amount);
|
||||
|
||||
await _cs.AddAsync(userId, $"Slot Machine x{result.Multiplier}", won, true);
|
||||
}
|
||||
|
||||
var toReturn = new SlotResponse
|
||||
{
|
||||
Multiplier = result.Multiplier,
|
||||
Won = won,
|
||||
};
|
||||
|
||||
toReturn.Rolls.AddRange(result.Rolls);
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
|
||||
public struct EconomyResult
|
||||
{
|
||||
|
122
src/NadekoBot/Modules/Gambling/Services/VoteRewardService.cs
Normal file
122
src/NadekoBot/Modules/Gambling/Services/VoteRewardService.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Services;
|
||||
using Discord.WebSocket;
|
||||
using Serilog;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Services
|
||||
{
|
||||
public class VoteModel
|
||||
{
|
||||
[JsonPropertyName("userId")]
|
||||
public ulong UserId { get; set; }
|
||||
}
|
||||
|
||||
public class VoteRewardService : INService, IReadyExecutor
|
||||
{
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IBotCredentials _creds;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ICurrencyService _currencyService;
|
||||
private readonly GamblingConfigService _gamb;
|
||||
private HttpClient _http;
|
||||
|
||||
public VoteRewardService(
|
||||
DiscordSocketClient client,
|
||||
IBotCredentials creds,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
ICurrencyService currencyService,
|
||||
GamblingConfigService gamb)
|
||||
{
|
||||
_client = client;
|
||||
_creds = creds;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_currencyService = currencyService;
|
||||
_gamb = gamb;
|
||||
}
|
||||
|
||||
public async Task OnReadyAsync()
|
||||
{
|
||||
if (_client.ShardId != 0)
|
||||
return;
|
||||
|
||||
_http = new HttpClient(new HttpClientHandler()
|
||||
{
|
||||
AllowAutoRedirect = false,
|
||||
ServerCertificateCustomValidationCallback = delegate { return true; }
|
||||
});
|
||||
|
||||
while (true)
|
||||
{
|
||||
await Task.Delay(30000);
|
||||
|
||||
var topggKey = _creds.Votes?.TopggKey;
|
||||
var topggServiceUrl = _creds.Votes?.TopggServiceUrl;
|
||||
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(topggKey)
|
||||
&& !string.IsNullOrWhiteSpace(topggServiceUrl))
|
||||
{
|
||||
_http.DefaultRequestHeaders.Authorization = new(topggKey);
|
||||
var uri = new Uri(new(topggServiceUrl), "topgg/new");
|
||||
var res = await _http.GetStringAsync(uri);
|
||||
var data = JsonSerializer.Deserialize<List<VoteModel>>(res);
|
||||
|
||||
if (data is { Count: > 0 })
|
||||
{
|
||||
var ids = data.Select(x => x.UserId).ToList();
|
||||
|
||||
await _currencyService.AddBulkAsync(ids,
|
||||
data.Select(_ => "top.gg vote reward"),
|
||||
data.Select(x => _gamb.Data.VoteReward),
|
||||
true);
|
||||
|
||||
Log.Information("Rewarding {Count} top.gg voters", ids.Count());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Critical error loading top.gg vote rewards.");
|
||||
}
|
||||
|
||||
var discordsKey = _creds.Votes?.DiscordsKey;
|
||||
var discordsServiceUrl = _creds.Votes?.DiscordsServiceUrl;
|
||||
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(discordsKey)
|
||||
&& !string.IsNullOrWhiteSpace(discordsServiceUrl))
|
||||
{
|
||||
_http.DefaultRequestHeaders.Authorization = new(discordsKey);
|
||||
var res = await _http.GetStringAsync(new Uri(new(discordsServiceUrl), "discords/new"));
|
||||
var data = JsonSerializer.Deserialize<List<VoteModel>>(res);
|
||||
|
||||
if (data is { Count: > 0 })
|
||||
{
|
||||
var ids = data.Select(x => x.UserId).ToList();
|
||||
|
||||
await _currencyService.AddBulkAsync(ids,
|
||||
data.Select(_ => "discords.com vote reward"),
|
||||
data.Select(x => _gamb.Data.VoteReward),
|
||||
true);
|
||||
|
||||
Log.Information("Rewarding {Count} discords.com voters", ids.Count());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Critical error loading discords.com vote rewards.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -394,19 +394,28 @@ namespace NadekoBot.Modules.Gambling.Services
|
||||
});
|
||||
}
|
||||
|
||||
w.Items.Add(new WaifuItem()
|
||||
if (!itemObj.Negative)
|
||||
{
|
||||
Name = itemObj.Name.ToLowerInvariant(),
|
||||
ItemEmoji = itemObj.ItemEmoji,
|
||||
});
|
||||
|
||||
if (w.Claimer?.UserId == from.Id)
|
||||
{
|
||||
w.Price += (int) (itemObj.Price * _gss.Data.Waifu.Multipliers.GiftEffect);
|
||||
w.Items.Add(new WaifuItem()
|
||||
{
|
||||
Name = itemObj.Name.ToLowerInvariant(),
|
||||
ItemEmoji = itemObj.ItemEmoji,
|
||||
});
|
||||
|
||||
if (w.Claimer?.UserId == from.Id)
|
||||
{
|
||||
w.Price += (int)(itemObj.Price * _gss.Data.Waifu.Multipliers.GiftEffect);
|
||||
}
|
||||
else
|
||||
{
|
||||
w.Price += itemObj.Price / 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
w.Price += itemObj.Price / 2;
|
||||
w.Price -= (int)(itemObj.Price * _gss.Data.Waifu.Multipliers.NegativeGiftEffect);
|
||||
if (w.Price < 1)
|
||||
w.Price = 1;
|
||||
}
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
@@ -512,7 +521,7 @@ namespace NadekoBot.Modules.Gambling.Services
|
||||
{
|
||||
var conf = _gss.Data;
|
||||
return conf.Waifu.Items
|
||||
.Select(x => new WaifuItemModel(x.ItemEmoji, (int)(x.Price * conf.Waifu.Multipliers.AllGiftPrices), x.Name))
|
||||
.Select(x => new WaifuItemModel(x.ItemEmoji, (int)(x.Price * conf.Waifu.Multipliers.AllGiftPrices), x.Name, x.Negative))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
@@ -225,6 +225,9 @@ namespace NadekoBot.Modules.Gambling
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
public async Task ShopAdd(Role _, int price, [Leftover] IRole role)
|
||||
{
|
||||
if (price < 1)
|
||||
return;
|
||||
|
||||
var entry = new ShopEntry()
|
||||
{
|
||||
Name = "-",
|
||||
@@ -252,8 +255,11 @@ namespace NadekoBot.Modules.Gambling
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task ShopAdd(List _, int price, [Leftover]string name)
|
||||
public async Task ShopAdd(List _, int price, [Leftover] string name)
|
||||
{
|
||||
if (price < 1)
|
||||
return;
|
||||
|
||||
var entry = new ShopEntry()
|
||||
{
|
||||
Name = name.TrimTo(100),
|
||||
@@ -266,13 +272,14 @@ namespace NadekoBot.Modules.Gambling
|
||||
{
|
||||
var entries = new IndexedCollection<ShopEntry>(uow.GuildConfigsForId(ctx.Guild.Id,
|
||||
set => set.Include(x => x.ShopEntries)
|
||||
.ThenInclude(x => x.Items)).ShopEntries)
|
||||
.ThenInclude(x => x.Items)).ShopEntries)
|
||||
{
|
||||
entry
|
||||
};
|
||||
uow.GuildConfigsForId(ctx.Guild.Id, set => set).ShopEntries = entries;
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
await ctx.Channel.EmbedAsync(EntryToEmbed(entry)
|
||||
.WithTitle(GetText(strs.shop_item_add))).ConfigureAwait(false);
|
||||
}
|
||||
|
@@ -9,12 +9,17 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Db.Models;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Services;
|
||||
using SixLabors.Fonts;
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Drawing.Processing;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Color = SixLabors.ImageSharp.Color;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
{
|
||||
@@ -33,12 +38,16 @@ namespace NadekoBot.Modules.Gambling
|
||||
//thanks to judge for helping me with this
|
||||
|
||||
private readonly IImageCache _images;
|
||||
private readonly ICurrencyService _cs;
|
||||
private FontProvider _fonts;
|
||||
private readonly DbService _db;
|
||||
|
||||
public SlotCommands(IDataCache data, ICurrencyService cs, GamblingConfigService gamb) : base(gamb)
|
||||
public SlotCommands(IDataCache data,
|
||||
FontProvider fonts, DbService db,
|
||||
GamblingConfigService gamb) : base(gamb)
|
||||
{
|
||||
_images = data.LocalImages;
|
||||
_cs = cs;
|
||||
_fonts = fonts;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
public sealed class SlotMachine
|
||||
@@ -140,92 +149,119 @@ namespace NadekoBot.Modules.Gambling
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Slot(ShmartNumber amount)
|
||||
{
|
||||
if (!_runningUsers.Add(ctx.User.Id))
|
||||
if (!_runningUsers.Add(ctx.User.Id))
|
||||
return;
|
||||
try
|
||||
{
|
||||
if (!await CheckBetMandatory(amount).ConfigureAwait(false))
|
||||
return;
|
||||
const int maxAmount = 9999;
|
||||
if (amount > maxAmount)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.max_bet_limit(maxAmount + CurrencySign));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!await CheckBetMandatory(amount).ConfigureAwait(false))
|
||||
return;
|
||||
|
||||
await ctx.Channel.TriggerTypingAsync().ConfigureAwait(false);
|
||||
|
||||
if (!await _cs.RemoveAsync(ctx.User, "Slot Machine", amount, false, gamble: true).ConfigureAwait(false))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
return;
|
||||
}
|
||||
Interlocked.Add(ref _totalBet, amount.Value);
|
||||
using (var bgImage = Image.Load(_images.SlotBackground))
|
||||
{
|
||||
var result = SlotMachine.Pull();
|
||||
int[] numbers = result.Numbers;
|
||||
var result = await _service.SlotAsync(ctx.User.Id, amount);
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
using (var randomImage = Image.Load(_images.SlotEmojis[numbers[i]]))
|
||||
{
|
||||
bgImage.Mutate(x => x.DrawImage(randomImage, new Point(95 + 142 * i, 330), new GraphicsOptions()));
|
||||
}
|
||||
}
|
||||
if (result.Error != GamblingError.None)
|
||||
{
|
||||
if (result.Error == GamblingError.NotEnough)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
}
|
||||
|
||||
var won = amount * result.Multiplier;
|
||||
var printWon = won;
|
||||
var n = 0;
|
||||
do
|
||||
{
|
||||
var digit = (int)(printWon % 10);
|
||||
using (var img = Image.Load(_images.SlotNumbers[digit]))
|
||||
{
|
||||
bgImage.Mutate(x => x.DrawImage(img, new Point(230 - n * 16, 462), new GraphicsOptions()));
|
||||
}
|
||||
n++;
|
||||
} while ((printWon /= 10) != 0);
|
||||
return;
|
||||
}
|
||||
|
||||
var printAmount = amount;
|
||||
n = 0;
|
||||
do
|
||||
{
|
||||
var digit = (int)(printAmount % 10);
|
||||
using (var img = Image.Load(_images.SlotNumbers[digit]))
|
||||
{
|
||||
bgImage.Mutate(x => x.DrawImage(img, new Point(395 - n * 16, 462), new GraphicsOptions()));
|
||||
}
|
||||
n++;
|
||||
} while ((printAmount /= 10) != 0);
|
||||
Interlocked.Add(ref _totalBet, amount);
|
||||
Interlocked.Add(ref _totalPaidOut, result.Won);
|
||||
|
||||
var msg = GetText(strs.better_luck);
|
||||
if (result.Multiplier != 0)
|
||||
{
|
||||
await _cs.AddAsync(ctx.User, $"Slot Machine x{result.Multiplier}", amount * result.Multiplier, false, gamble: true).ConfigureAwait(false);
|
||||
Interlocked.Add(ref _totalPaidOut, amount * result.Multiplier);
|
||||
if (result.Multiplier == 1)
|
||||
msg = GetText(strs.slot_single(CurrencySign, 1));
|
||||
else if (result.Multiplier == 4)
|
||||
msg = GetText(strs.slot_two(CurrencySign, 4));
|
||||
else if (result.Multiplier == 10)
|
||||
msg = GetText(strs.slot_three(10));
|
||||
else if (result.Multiplier == 30)
|
||||
msg = GetText(strs.slot_jackpot(30));
|
||||
}
|
||||
long ownedAmount;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
ownedAmount = uow.Set<DiscordUser>()
|
||||
.FirstOrDefault(x => x.UserId == ctx.User.Id)
|
||||
?.CurrencyAmount ?? 0;
|
||||
}
|
||||
|
||||
using (var imgStream = bgImage.ToStream())
|
||||
{
|
||||
await ctx.Channel.SendFileAsync(imgStream, "result.png", ctx.User.Mention + " " + msg + $"\n`{GetText(strs.slot_bet)}:`{amount} `{GetText(strs.won)}:` {amount * result.Multiplier}{CurrencySign}").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
_runningUsers.Remove(ctx.User.Id);
|
||||
});
|
||||
}
|
||||
using (var bgImage = Image.Load<Rgba32>(_images.SlotBackground, out var format))
|
||||
{
|
||||
var numbers = new int[3];
|
||||
result.Rolls.CopyTo(numbers, 0);
|
||||
|
||||
Color fontColor = _config.Slots.CurrencyFontColor;
|
||||
|
||||
bgImage.Mutate(x => x.DrawText(new TextGraphicsOptions
|
||||
{
|
||||
TextOptions = new TextOptions()
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
WrapTextWidth = 140,
|
||||
}
|
||||
}, result.Won.ToString(), _fonts.DottyFont.CreateFont(65), fontColor,
|
||||
new PointF(227, 92)));
|
||||
|
||||
var bottomFont = _fonts.DottyFont.CreateFont(50);
|
||||
|
||||
bgImage.Mutate(x => x.DrawText(new TextGraphicsOptions
|
||||
{
|
||||
TextOptions = new TextOptions()
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
WrapTextWidth = 135,
|
||||
}
|
||||
}, amount.ToString(), bottomFont, fontColor,
|
||||
new PointF(129, 472)));
|
||||
|
||||
bgImage.Mutate(x => x.DrawText(new TextGraphicsOptions
|
||||
{
|
||||
TextOptions = new TextOptions()
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
WrapTextWidth = 135,
|
||||
}
|
||||
}, ownedAmount.ToString(), bottomFont, fontColor,
|
||||
new PointF(325, 472)));
|
||||
//sw.PrintLap("drew red text");
|
||||
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
using (var img = Image.Load(_images.SlotEmojis[numbers[i]]))
|
||||
{
|
||||
bgImage.Mutate(x => x.DrawImage(img, new Point(148 + 105 * i, 217), 1f));
|
||||
}
|
||||
}
|
||||
|
||||
var msg = GetText(strs.better_luck);
|
||||
if (result.Multiplier > 0)
|
||||
{
|
||||
if (result.Multiplier == 1f)
|
||||
msg = GetText(strs.slot_single(CurrencySign, 1));
|
||||
else if (result.Multiplier == 4f)
|
||||
msg = GetText(strs.slot_two(CurrencySign, 4));
|
||||
else if (result.Multiplier == 10f)
|
||||
msg = GetText(strs.slot_three(10));
|
||||
else if (result.Multiplier == 30f)
|
||||
msg = GetText(strs.slot_jackpot(30));
|
||||
}
|
||||
|
||||
using (var imgStream = bgImage.ToStream())
|
||||
{
|
||||
await ctx.Channel.SendFileAsync(imgStream,
|
||||
filename: "result.png",
|
||||
text: Format.Bold(ctx.User.ToString()) + " " + msg).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
_runningUsers.Remove(ctx.User.Id);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -317,10 +317,14 @@ namespace NadekoBot.Modules.Gambling
|
||||
.WithOkColor();
|
||||
|
||||
waifuItems
|
||||
.OrderBy(x => x.Price)
|
||||
.OrderBy(x => x.Negative)
|
||||
.ThenBy(x => x.Price)
|
||||
.Skip(9 * cur)
|
||||
.Take(9)
|
||||
.ForEach(x => embed.AddField($"{x.ItemEmoji} {x.Name}", x.Price, true));
|
||||
.ForEach(x => embed
|
||||
.AddField($"{(!x.Negative ? string.Empty : "\\💔")} {x.ItemEmoji} {x.Name}",
|
||||
Format.Bold(x.Price.ToString()) + _config.Currency.Sign,
|
||||
true));
|
||||
|
||||
return embed;
|
||||
}, waifuItems.Count, 9);
|
||||
|
@@ -2,6 +2,7 @@
|
||||
using Discord.Commands;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Db;
|
||||
@@ -23,6 +24,7 @@ namespace NadekoBot.Modules.Games
|
||||
_db = db;
|
||||
}
|
||||
|
||||
[NoPublicBot]
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user