mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-12 10:18:27 -04:00
Compare commits
76 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
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/**
|
!src/NadekoBot.Generators/**
|
||||||
# Use Ayu stuff
|
# Use Ayu stuff
|
||||||
!src/ayu/**
|
!src/ayu/**
|
||||||
|
!docker-entrypoint.sh
|
||||||
|
|
||||||
# ignore bin and obj folders in projects
|
# ignore bin and obj folders in projects
|
||||||
src/**/bin/*
|
src/**/bin/*
|
||||||
|
@@ -96,3 +96,30 @@ upload-windows-updater-release:
|
|||||||
- aws --version
|
- 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/$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
|
||||||
|
83
CHANGELOG.md
83
CHANGELOG.md
@@ -2,17 +2,97 @@
|
|||||||
|
|
||||||
Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
|
Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
|
||||||
|
|
||||||
## Unreleased
|
## [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
|
## [3.0.5] - 20.09.2021
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed images not automatically reloading on startup if the keys don't exist
|
- 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
|
- Fixed `.logserver` - it should no longer throw an exception if you had no logsettings previously
|
||||||
|
|
||||||
## [3.0.4] - 16.09.2021
|
## [3.0.4] - 16.09.2021
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Fully translated to Brazilian Portuguese 🎉
|
- Fully translated to Brazilian Portuguese 🎉
|
||||||
- Added `%server.boosters%` and `%server.boost_level%` placeholders
|
- Added `%server.boosters%` and `%server.boost_level%` placeholders
|
||||||
- Added `DmHelpTextKeywords` to `data/bot.yml`
|
- Added `DmHelpTextKeywords` to `data/bot.yml`
|
||||||
@@ -20,6 +100,7 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.
|
|||||||
- If no keywords are specified, bot will reply to every DM (like before)
|
- If no keywords are specified, bot will reply to every DM (like before)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Possible fix for `.repeat` bug
|
- Possible fix for `.repeat` bug
|
||||||
- Slight adjustment for repeater logic
|
- Slight adjustment for repeater logic
|
||||||
- Timer should no longer increase on some repeaters
|
- 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
|
WORKDIR /source
|
||||||
|
|
||||||
COPY src/NadekoBot/*.csproj src/NadekoBot/
|
COPY src/NadekoBot/*.csproj src/NadekoBot/
|
||||||
@@ -9,14 +9,34 @@ RUN dotnet restore src/NadekoBot/
|
|||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
WORKDIR /source/src/NadekoBot
|
WORKDIR /source/src/NadekoBot
|
||||||
RUN dotnet --version
|
RUN set -xe; \
|
||||||
RUN dotnet publish -c Release -o /app --no-restore
|
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
|
# 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 shard_id=0
|
||||||
ENV total_shards=1
|
ENV total_shards=1
|
||||||
WORKDIR /app
|
|
||||||
COPY --from=build /app ./
|
VOLUME [ "app/data" ]
|
||||||
VOLUME [ "app/data", "app/creds.yml", "app/creds_example.yml" ]
|
ENTRYPOINT [ "/usr/local/sbin/docker-entrypoint.sh" ]
|
||||||
ENTRYPOINT dotnet NadekoBot.dll "$shard_id" "$total_shards"
|
CMD dotnet NadekoBot.dll "$shard_id" "$total_shards"
|
@@ -25,6 +25,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.Coordinator", "sr
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.Generators", "src\NadekoBot.Generators\NadekoBot.Generators.csproj", "{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.Generators", "src\NadekoBot.Generators\NadekoBot.Generators.csproj", "{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.VotesApi", "src\NadekoBot.VotesApi\NadekoBot.VotesApi.csproj", "{3BC82CFE-BEE7-451F-986B-17EDD1570C4F}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.Release|Any CPU.Build.0 = 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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -73,6 +81,7 @@ Global
|
|||||||
{DB448DD4-C97F-40E9-8BD3-F605FF1FF833} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
{DB448DD4-C97F-40E9-8BD3-F605FF1FF833} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
||||||
{AE9B7F8C-81D7-4401-83A3-643B38258374} = {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}
|
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
||||||
|
{3BC82CFE-BEE7-451F-986B-17EDD1570C4F} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {5F3F555C-855F-4BE8-B526-D062D3E8ACA4}
|
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 "$@"
|
@@ -134,6 +134,8 @@ Compared to using tmux, this method requires a little bit more work to set up, b
|
|||||||
echo "[Unit]
|
echo "[Unit]
|
||||||
Description=NadekoBot service
|
Description=NadekoBot service
|
||||||
After=network.target
|
After=network.target
|
||||||
|
StartLimitIntervalSec=60
|
||||||
|
StartLimitBurst=2
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
@@ -144,10 +146,11 @@ Compared to using tmux, this method requires a little bit more work to set up, b
|
|||||||
# source code.
|
# source code.
|
||||||
#ExecStartPre=/usr/bin/dotnet build ../src/NadekoBot/NadekoBot.csproj -c Release -o output/
|
#ExecStartPre=/usr/bin/dotnet build ../src/NadekoBot/NadekoBot.csproj -c Release -o output/
|
||||||
ExecStart=/usr/bin/dotnet NadekoBot.dll
|
ExecStart=/usr/bin/dotnet NadekoBot.dll
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
StandardOutput=syslog
|
StandardOutput=syslog
|
||||||
StandardError=syslog
|
StandardError=syslog
|
||||||
SyslogIdentifier=NadekoBot
|
SyslogIdentifier=NadekoBot
|
||||||
Restart=always
|
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target" | sudo tee /etc/systemd/system/nadeko.service
|
WantedBy=multi-user.target" | sudo tee /etc/systemd/system/nadeko.service
|
||||||
@@ -172,12 +175,16 @@ This method is similar to the one above, but requires one extra step, with the a
|
|||||||
echo "[Unit]
|
echo "[Unit]
|
||||||
Description=NadekoBot service
|
Description=NadekoBot service
|
||||||
After=network.target
|
After=network.target
|
||||||
|
StartLimitIntervalSec=60
|
||||||
|
StartLimitBurst=2
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
User=$USER
|
User=$USER
|
||||||
WorkingDirectory=$PWD
|
WorkingDirectory=$_WORKING_DIR
|
||||||
ExecStart=/bin/bash NadekoRun.sh
|
ExecStart=/bin/bash NadekoRun.sh
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
StandardOutput=syslog
|
StandardOutput=syslog
|
||||||
StandardError=syslog
|
StandardError=syslog
|
||||||
SyslogIdentifier=NadekoBot
|
SyslogIdentifier=NadekoBot
|
||||||
@@ -191,14 +198,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:
|
4. Use the following command to create a script that will be used to start Nadeko:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
echo "#\!/bin/bash
|
{
|
||||||
|
echo '#!/bin/bash'
|
||||||
echo \"\"
|
echo ""
|
||||||
echo \"Running NadekoBot in the background with auto restart\"
|
echo "echo \"Running NadekoBot in the background with auto restart\"
|
||||||
youtube-dl -U
|
youtube-dl -U
|
||||||
|
|
||||||
# If you want Nadeko to be compiled prior to every startup, uncomment the lines
|
# 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.
|
# source code.
|
||||||
#echo \"Compiling NadekoBot...\"
|
#echo \"Compiling NadekoBot...\"
|
||||||
#cd \"$PWD\"/nadekobot
|
#cd \"$PWD\"/nadekobot
|
||||||
@@ -207,22 +214,32 @@ This method is similar to the one above, but requires one extra step, with the a
|
|||||||
echo \"Starting NadekoBot...\"
|
echo \"Starting NadekoBot...\"
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
{
|
if [[ -d $PWD/nadekobot/output ]]; then
|
||||||
cd \"$PWD\"/nadekobot/output
|
cd $PWD/nadekobot/output || {
|
||||||
dotnet NadekoBot.dll
|
echo \"Failed to change working directory to $PWD/nadekobot/output\" >&2
|
||||||
## If a non-zero exit code is produced, exit this script.
|
echo \"Ensure that the working directory inside of '/etc/systemd/system/nadeko.service' is correct\"
|
||||||
} || {
|
echo \"Exiting...\"
|
||||||
error_code=\"\$?\"
|
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 \"An error occurred when trying to start NadekBot\"
|
||||||
echo \"EXIT CODE: \$?\"
|
echo \"Exiting...\"
|
||||||
exit \"\$error_code\"
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
echo \"Waiting for 5 seconds...\"
|
||||||
|
sleep 5
|
||||||
youtube-dl -U
|
youtube-dl -U
|
||||||
echo \"Restarting NadekoBot...\"
|
echo \"Restarting NadekoBot...\"
|
||||||
done
|
done
|
||||||
|
|
||||||
echo \"Stopping NadekoBot...\"" > NadekoRun.sh
|
echo \"Stopping NadekoBot...\""
|
||||||
|
} > NadekoRun.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Start Nadeko:
|
5. Start Nadeko:
|
||||||
|
@@ -21,7 +21,7 @@
|
|||||||
#### Prerequisites
|
#### Prerequisites
|
||||||
|
|
||||||
- Windows 8 or later (64-bit)
|
- 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**
|
**Optional**
|
||||||
|
|
||||||
@@ -32,12 +32,13 @@
|
|||||||
|
|
||||||
- Download and run the [NadekoBot v3 Updater][Updater].
|
- Download and run the [NadekoBot v3 Updater][Updater].
|
||||||
- Click on the + at the top left to create a new bot.
|
- 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.
|
- Give your bot a name and then click **`Go to setup`** at the lower right.
|
||||||

|

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

|

|
||||||
- Click on **`Install`** next to **`Redis`**.
|
- 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 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).
|
- 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.
|
- When installation is finished, click on **`CREDS`** to the left of **`RUN`** at the lower right.
|
||||||
|
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": "*"
|
||||||
|
}
|
@@ -28,7 +28,7 @@ namespace NadekoBot
|
|||||||
private readonly IBotCredentials _creds;
|
private readonly IBotCredentials _creds;
|
||||||
private readonly CommandService _commandService;
|
private readonly CommandService _commandService;
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
private readonly BotCredsProvider _credsProvider;
|
private readonly IBotCredsProvider _credsProvider;
|
||||||
|
|
||||||
public event Func<GuildConfig, Task> JoinedGuild = delegate { return Task.CompletedTask; };
|
public event Func<GuildConfig, Task> JoinedGuild = delegate { return Task.CompletedTask; };
|
||||||
|
|
||||||
@@ -95,8 +95,8 @@ namespace NadekoBot
|
|||||||
}
|
}
|
||||||
|
|
||||||
var svcs = new ServiceCollection()
|
var svcs = new ServiceCollection()
|
||||||
.AddTransient<IBotCredentials>(_ => _creds) // bot creds
|
.AddTransient<IBotCredentials>(_ => _credsProvider.GetCreds()) // bot creds
|
||||||
.AddSingleton(_credsProvider)
|
.AddSingleton<IBotCredsProvider>(_credsProvider)
|
||||||
.AddSingleton(_db) // database
|
.AddSingleton(_db) // database
|
||||||
.AddRedis(_creds.RedisOptions) // redis
|
.AddRedis(_creds.RedisOptions) // redis
|
||||||
.AddSingleton(Client) // discord socket client
|
.AddSingleton(Client) // discord socket client
|
||||||
@@ -145,7 +145,8 @@ namespace NadekoBot
|
|||||||
|
|
||||||
svcs.Scan(scan => scan
|
svcs.Scan(scan => scan
|
||||||
.FromAssemblyOf<IReadyExecutor>()
|
.FromAssemblyOf<IReadyExecutor>()
|
||||||
.AddClasses(classes => classes.AssignableToAny(
|
.AddClasses(classes => classes
|
||||||
|
.AssignableToAny(
|
||||||
// services
|
// services
|
||||||
typeof(INService),
|
typeof(INService),
|
||||||
|
|
||||||
|
@@ -12,7 +12,7 @@ namespace NadekoBot.Common.Attributes
|
|||||||
{
|
{
|
||||||
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo executingCommand, IServiceProvider services)
|
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")));
|
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>();
|
OwnerIds = new List<ulong>();
|
||||||
TotalShards = 1;
|
TotalShards = 1;
|
||||||
GoogleApiKey = string.Empty;
|
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);
|
Patreon = new(string.Empty, string.Empty, string.Empty, string.Empty);
|
||||||
BotListToken = string.Empty;
|
BotListToken = string.Empty;
|
||||||
CleverbotApiKey = 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.")]
|
Change only if you've changed the coordinator address or port.")]
|
||||||
public string CoordinatorUrl { get; set; }
|
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)")]
|
[Comment(@"Api key obtained on https://rapidapi.com (go to MyApps -> Add New App -> Enter Name -> Application key)")]
|
||||||
public string RapidApiKey { get; set; }
|
public string RapidApiKey { get; set; }
|
||||||
|
|
||||||
@@ -126,11 +116,9 @@ Windows default
|
|||||||
// todo fixup patreon
|
// todo fixup patreon
|
||||||
public sealed record PatreonSettings
|
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; }
|
public string AccessToken { get; set; }
|
||||||
[Comment(@"Unused atm")]
|
|
||||||
public string RefreshToken { get; set; }
|
public string RefreshToken { get; set; }
|
||||||
[Comment(@"Unused atm")]
|
|
||||||
public string ClientSecret { get; set; }
|
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)")]
|
[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;
|
ClientSecret = clientSecret;
|
||||||
CampaignId = campaignId;
|
CampaignId = campaignId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PatreonSettings()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed record VotesSettings
|
public sealed record VotesSettings
|
||||||
{
|
{
|
||||||
[Comment(@"")]
|
[Comment(@"top.gg votes service url
|
||||||
public string Url { get; set; }
|
This is the url of your instance of the NadekoBot.Votes api
|
||||||
[Comment(@"")]
|
Example: https://votes.my.cool.bot.com")]
|
||||||
public string Key { get; set; }
|
public string TopggServiceUrl { get; set; }
|
||||||
|
|
||||||
public VotesSettings(string url, string key)
|
[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()
|
||||||
{
|
{
|
||||||
Url = url;
|
|
||||||
Key = key;
|
}
|
||||||
|
|
||||||
|
public VotesSettings(string topggServiceUrl, string topggKey, string discordsServiceUrl, string discordsKey)
|
||||||
|
{
|
||||||
|
TopggServiceUrl = topggServiceUrl;
|
||||||
|
TopggKey = topggKey;
|
||||||
|
DiscordsServiceUrl = discordsServiceUrl;
|
||||||
|
DiscordsKey = discordsKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -12,16 +12,14 @@ namespace NadekoBot
|
|||||||
string GoogleApiKey { get; }
|
string GoogleApiKey { get; }
|
||||||
ICollection<ulong> OwnerIds { get; }
|
ICollection<ulong> OwnerIds { get; }
|
||||||
string RapidApiKey { get; }
|
string RapidApiKey { get; }
|
||||||
string PatreonAccessToken { get; }
|
|
||||||
|
|
||||||
Creds.DbOptions Db { get; }
|
Creds.DbOptions Db { get; }
|
||||||
string OsuApiKey { get; }
|
string OsuApiKey { get; }
|
||||||
int TotalShards { get; }
|
int TotalShards { get; }
|
||||||
string PatreonCampaignId { get; }
|
Creds.PatreonSettings Patreon { get; }
|
||||||
string CleverbotApiKey { get; }
|
string CleverbotApiKey { get; }
|
||||||
RestartConfig RestartCommand { get; }
|
RestartConfig RestartCommand { get; }
|
||||||
string VotesUrl { get; }
|
Creds.VotesSettings Votes { get; }
|
||||||
string VotesToken { get; }
|
|
||||||
string BotListToken { get; }
|
string BotListToken { get; }
|
||||||
string RedisOptions { get; }
|
string RedisOptions { get; }
|
||||||
string LocationIqApiKey { get; }
|
string LocationIqApiKey { get; }
|
||||||
|
@@ -1,10 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using NadekoBot.Common.Yml;
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common
|
||||||
{
|
{
|
||||||
public class ImageUrls
|
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 CoinData Coins { get; set; }
|
||||||
public Uri[] Currency { get; set; }
|
public Uri[] Currency { get; set; }
|
||||||
@@ -25,7 +27,6 @@ namespace NadekoBot.Common
|
|||||||
public class SlotData
|
public class SlotData
|
||||||
{
|
{
|
||||||
public Uri[] Emojis { get; set; }
|
public Uri[] Emojis { get; set; }
|
||||||
public Uri[] Numbers { get; set; }
|
|
||||||
public Uri Bg { 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*/
|
/*OBSOLETE*/
|
||||||
_reps.TryAdd("%servers%", () => c.Guilds.Count.ToString());
|
_reps.TryAdd("%servers%", () => c.Guilds.Count.ToString());
|
||||||
#if !GLOBAL_NADEKO
|
#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
|
#endif
|
||||||
|
|
||||||
/*NEW*/
|
/*NEW*/
|
||||||
_reps.TryAdd("%shard.servercount%", () => c.Guilds.Count.ToString());
|
_reps.TryAdd("%shard.servercount%", () => c.Guilds.Count.ToString());
|
||||||
#if !GLOBAL_NADEKO
|
#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
|
#endif
|
||||||
_reps.TryAdd("%shard.id%", () => c.ShardId.ToString());
|
_reps.TryAdd("%shard.id%", () => c.ShardId.ToString());
|
||||||
return this;
|
return this;
|
||||||
|
@@ -53,6 +53,7 @@ namespace NadekoBot.Common.Replacements
|
|||||||
newEmbedData.Title = Replace(embedData.Title);
|
newEmbedData.Title = Replace(embedData.Title);
|
||||||
newEmbedData.Thumbnail = Replace(embedData.Thumbnail);
|
newEmbedData.Thumbnail = Replace(embedData.Thumbnail);
|
||||||
newEmbedData.Image = Replace(embedData.Image);
|
newEmbedData.Image = Replace(embedData.Image);
|
||||||
|
newEmbedData.Url = Replace(embedData.Url);
|
||||||
if (embedData.Author != null)
|
if (embedData.Author != null)
|
||||||
{
|
{
|
||||||
newEmbedData.Author = new SmartTextEmbedAuthor();
|
newEmbedData.Author = new SmartTextEmbedAuthor();
|
||||||
@@ -68,6 +69,7 @@ namespace NadekoBot.Common.Replacements
|
|||||||
var newF = new SmartTextEmbedField();
|
var newF = new SmartTextEmbedField();
|
||||||
newF.Name = Replace(f.Name);
|
newF.Name = Replace(f.Name);
|
||||||
newF.Value = Replace(f.Value);
|
newF.Value = Replace(f.Value);
|
||||||
|
newF.Inline = f.Inline;
|
||||||
fields.Add(newF);
|
fields.Add(newF);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 Rgba32Converter())
|
||||||
.WithTypeConverter(new CultureInfoConverter())
|
.WithTypeConverter(new CultureInfoConverter())
|
||||||
.WithTypeConverter(new UriConverter())
|
.WithTypeConverter(new UriConverter())
|
||||||
|
.IgnoreUnmatchedProperties()
|
||||||
.Build();
|
.Build();
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,5 @@
|
|||||||
using NadekoBot.Db.Models;
|
using System;
|
||||||
|
using NadekoBot.Db.Models;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Discord;
|
using Discord;
|
||||||
@@ -138,14 +139,15 @@ WHERE UserId={userId};");
|
|||||||
// just update the amount, there is no new user data
|
// just update the amount, there is no new user data
|
||||||
if (!updatedUserData)
|
if (!updatedUserData)
|
||||||
{
|
{
|
||||||
ctx.Database.ExecuteSqlInterpolated($@"
|
var rows = ctx.Database.ExecuteSqlInterpolated($@"
|
||||||
UPDATE OR IGNORE DiscordUser
|
UPDATE OR IGNORE DiscordUser
|
||||||
SET CurrencyAmount=CurrencyAmount+{amount}
|
SET CurrencyAmount=CurrencyAmount+{amount}
|
||||||
WHERE UserId={userId};
|
WHERE UserId={userId};
|
||||||
|
|
||||||
INSERT OR IGNORE INTO DiscordUser (UserId, Username, Discriminator, AvatarId, CurrencyAmount)
|
INSERT OR IGNORE INTO DiscordUser (UserId, Username, Discriminator, AvatarId, CurrencyAmount, TotalXp)
|
||||||
VALUES ({userId}, {name}, {discrim}, {avatarId}, {amount});
|
VALUES ({userId}, {name}, {discrim}, {avatarId}, {amount}, 0);
|
||||||
");
|
");
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -157,8 +159,8 @@ SET CurrencyAmount=CurrencyAmount+{amount},
|
|||||||
AvatarId={avatarId}
|
AvatarId={avatarId}
|
||||||
WHERE UserId={userId};
|
WHERE UserId={userId};
|
||||||
|
|
||||||
INSERT OR IGNORE INTO DiscordUser (UserId, Username, Discriminator, AvatarId, CurrencyAmount)
|
INSERT OR IGNORE INTO DiscordUser (UserId, Username, Discriminator, AvatarId, CurrencyAmount, TotalXp)
|
||||||
VALUES ({userId}, {name}, {discrim}, {avatarId}, {amount});
|
VALUES ({userId}, {name}, {discrim}, {avatarId}, {amount}, 0);
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -167,7 +169,7 @@ VALUES ({userId}, {name}, {discrim}, {avatarId}, {amount});
|
|||||||
public static decimal GetTotalCurrency(this DbSet<DiscordUser> users)
|
public static decimal GetTotalCurrency(this DbSet<DiscordUser> users)
|
||||||
{
|
{
|
||||||
return users
|
return users
|
||||||
.Sum(x => x.CurrencyAmount);
|
.Sum((Func<DiscordUser, decimal>)(x => x.CurrencyAmount));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static decimal GetTopOnePercentCurrency(this DbSet<DiscordUser> users, ulong botId)
|
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)
|
private static IQueryable<GuildConfig> IncludeEverything(this DbSet<GuildConfig> configs)
|
||||||
{
|
{
|
||||||
|
// todo split query
|
||||||
return configs
|
return configs
|
||||||
.AsQueryable()
|
.AsQueryable()
|
||||||
.Include(gc => gc.CommandCooldowns)
|
.Include(gc => gc.CommandCooldowns)
|
||||||
.Include(gc => gc.FollowedStreams)
|
.Include(gc => gc.FollowedStreams)
|
||||||
.Include(gc => gc.StreamRole)
|
.Include(gc => gc.StreamRole)
|
||||||
.Include(gc => gc.NsfwBlacklistedTags)
|
|
||||||
.Include(gc => gc.XpSettings)
|
.Include(gc => gc.XpSettings)
|
||||||
.ThenInclude(x => x.ExclusionList)
|
.ThenInclude(x => x.ExclusionList)
|
||||||
.Include(gc => gc.DelMsgOnCmdChannels)
|
.Include(gc => gc.DelMsgOnCmdChannels)
|
||||||
@@ -117,7 +117,7 @@ namespace NadekoBot.Db
|
|||||||
{
|
{
|
||||||
var logSetting = ctx.LogSettings
|
var logSetting = ctx.LogSettings
|
||||||
.AsQueryable()
|
.AsQueryable()
|
||||||
.Include(x => x.IgnoredChannels)
|
.Include(x => x.LogIgnores)
|
||||||
.Where(x => x.GuildId == guildId)
|
.Where(x => x.GuildId == guildId)
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
@@ -91,7 +91,6 @@ namespace NadekoBot.Services.Database.Models
|
|||||||
public bool WarningsInitialized { get; set; }
|
public bool WarningsInitialized { get; set; }
|
||||||
public HashSet<SlowmodeIgnoredUser> SlowmodeIgnoredUsers { get; set; }
|
public HashSet<SlowmodeIgnoredUser> SlowmodeIgnoredUsers { get; set; }
|
||||||
public HashSet<SlowmodeIgnoredRole> SlowmodeIgnoredRoles { get; set; }
|
public HashSet<SlowmodeIgnoredRole> SlowmodeIgnoredRoles { get; set; }
|
||||||
public HashSet<NsfwBlacklitedTag> NsfwBlacklistedTags { get; set; } = new HashSet<NsfwBlacklitedTag>();
|
|
||||||
|
|
||||||
public List<ShopEntry> ShopEntries { get; set; }
|
public List<ShopEntry> ShopEntries { get; set; }
|
||||||
public ulong? GameVoiceChannel { get; set; } = null;
|
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 class LogSetting : DbEntity
|
||||||
{
|
{
|
||||||
public HashSet<IgnoredLogChannel> IgnoredChannels { get; set; } = new HashSet<IgnoredLogChannel>();
|
public List<IgnoredLogItem> LogIgnores { get; set; } = new List<IgnoredLogItem>();
|
||||||
public HashSet<IgnoredVoicePresenceChannel> IgnoredVoicePresenceChannelIds { get; set; } = new HashSet<IgnoredVoicePresenceChannel>();
|
|
||||||
|
|
||||||
public ulong GuildId { get; set; }
|
public ulong GuildId { get; set; }
|
||||||
public ulong? LogOtherId { 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 bool Forgiven { get; set; }
|
||||||
public string ForgivenBy { get; set; }
|
public string ForgivenBy { get; set; }
|
||||||
public string Moderator { get; set; }
|
public string Moderator { get; set; }
|
||||||
|
public int Weight { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -42,8 +42,8 @@ namespace NadekoBot.Services.Database
|
|||||||
|
|
||||||
//logging
|
//logging
|
||||||
public DbSet<LogSetting> LogSettings { get; set; }
|
public DbSet<LogSetting> LogSettings { get; set; }
|
||||||
public DbSet<IgnoredLogChannel> IgnoredLogChannels { get; set; }
|
|
||||||
public DbSet<IgnoredVoicePresenceChannel> IgnoredVoicePresenceCHannels { get; set; }
|
public DbSet<IgnoredVoicePresenceChannel> IgnoredVoicePresenceCHannels { get; set; }
|
||||||
|
public DbSet<IgnoredLogItem> IgnoredLogChannels { get; set; }
|
||||||
|
|
||||||
public DbSet<RotatingPlayingStatus> RotatingStatus { get; set; }
|
public DbSet<RotatingPlayingStatus> RotatingStatus { get; set; }
|
||||||
public DbSet<BlacklistEntry> Blacklist { get; set; }
|
public DbSet<BlacklistEntry> Blacklist { get; set; }
|
||||||
@@ -59,6 +59,7 @@ namespace NadekoBot.Services.Database
|
|||||||
public DbSet<Poll> Poll { get; set; }
|
public DbSet<Poll> Poll { get; set; }
|
||||||
public DbSet<WaifuInfo> WaifuInfo { get; set; }
|
public DbSet<WaifuInfo> WaifuInfo { get; set; }
|
||||||
public DbSet<ImageOnlyChannel> ImageOnlyChannels { get; set; }
|
public DbSet<ImageOnlyChannel> ImageOnlyChannels { get; set; }
|
||||||
|
public DbSet<NsfwBlacklistedTag> NsfwBlacklistedTags { get; set; }
|
||||||
|
|
||||||
public NadekoContext(DbContextOptions<NadekoContext> options) : base(options)
|
public NadekoContext(DbContextOptions<NadekoContext> options) : base(options)
|
||||||
{
|
{
|
||||||
@@ -195,10 +196,16 @@ namespace NadekoBot.Services.Database
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Warnings
|
#region Warnings
|
||||||
var warn = modelBuilder.Entity<Warning>();
|
|
||||||
|
modelBuilder.Entity<Warning>(warn =>
|
||||||
|
{
|
||||||
warn.HasIndex(x => x.GuildId);
|
warn.HasIndex(x => x.GuildId);
|
||||||
warn.HasIndex(x => x.UserId);
|
warn.HasIndex(x => x.UserId);
|
||||||
warn.HasIndex(x => x.DateAdded);
|
warn.HasIndex(x => x.DateAdded);
|
||||||
|
warn.Property(x => x.Weight)
|
||||||
|
.HasDefaultValue(1);
|
||||||
|
});
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region PatreonRewards
|
#region PatreonRewards
|
||||||
@@ -343,11 +350,24 @@ namespace NadekoBot.Services.Database
|
|||||||
.HasIndex(x => x.GuildId)
|
.HasIndex(x => x.GuildId)
|
||||||
.IsUnique());
|
.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
|
#endregion
|
||||||
|
|
||||||
modelBuilder.Entity<ImageOnlyChannel>(ioc => ioc
|
modelBuilder.Entity<ImageOnlyChannel>(ioc => ioc
|
||||||
.HasIndex(x => x.ChannelId)
|
.HasIndex(x => x.ChannelId)
|
||||||
.IsUnique());
|
.IsUnique());
|
||||||
|
|
||||||
|
modelBuilder.Entity<NsfwBlacklistedTag>(nbt => nbt
|
||||||
|
.HasIndex(x => x.GuildId)
|
||||||
|
.IsUnique(false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,6 +17,12 @@ namespace NadekoBot.Migrations
|
|||||||
migrationBuilder.Sql("DELETE FROM FilterChannelId WHERE GuildConfigId NOT IN (SELECT Id from GuildConfigs)");
|
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)");
|
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(
|
migrationBuilder.DropColumn(
|
||||||
name: "ChannelCreated",
|
name: "ChannelCreated",
|
||||||
table: "LogSettings");
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -187,29 +187,6 @@ namespace NadekoBot.Migrations
|
|||||||
b.ToTable("FollowedStream");
|
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 =>
|
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiAltSetting", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -870,24 +847,28 @@ namespace NadekoBot.Migrations
|
|||||||
b.ToTable("GuildConfigs");
|
b.ToTable("GuildConfigs");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b =>
|
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogItem", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<ulong>("ChannelId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<DateTime?>("DateAdded")
|
b.Property<DateTime?>("DateAdded")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<int?>("LogSettingId")
|
b.Property<int>("ItemType")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<ulong>("LogItemId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("LogSettingId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("LogSettingId");
|
b.HasIndex("LogSettingId", "LogItemId", "ItemType")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
b.ToTable("IgnoredLogChannels");
|
b.ToTable("IgnoredLogChannels");
|
||||||
});
|
});
|
||||||
@@ -914,6 +895,29 @@ namespace NadekoBot.Migrations
|
|||||||
b.ToTable("IgnoredVoicePresenceCHannels");
|
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 =>
|
modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -1058,7 +1062,7 @@ namespace NadekoBot.Migrations
|
|||||||
b.ToTable("MutedUserId");
|
b.ToTable("MutedUserId");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b =>
|
modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklistedTag", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -1067,7 +1071,7 @@ namespace NadekoBot.Migrations
|
|||||||
b.Property<DateTime?>("DateAdded")
|
b.Property<DateTime?>("DateAdded")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<int?>("GuildConfigId")
|
b.Property<ulong>("GuildId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<string>("Tag")
|
b.Property<string>("Tag")
|
||||||
@@ -1075,9 +1079,9 @@ namespace NadekoBot.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("GuildConfigId");
|
b.HasIndex("GuildId");
|
||||||
|
|
||||||
b.ToTable("NsfwBlacklitedTag");
|
b.ToTable("NsfwBlacklistedTags");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b =>
|
modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b =>
|
||||||
@@ -1963,6 +1967,11 @@ namespace NadekoBot.Migrations
|
|||||||
b.Property<ulong>("UserId")
|
b.Property<ulong>("UserId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Weight")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(1);
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("DateAdded");
|
b.HasIndex("DateAdded");
|
||||||
@@ -2269,11 +2278,13 @@ namespace NadekoBot.Migrations
|
|||||||
b.Navigation("GuildConfig");
|
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")
|
b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting")
|
||||||
.WithMany("IgnoredChannels")
|
.WithMany("LogIgnores")
|
||||||
.HasForeignKey("LogSettingId");
|
.HasForeignKey("LogSettingId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
b.Navigation("LogSetting");
|
b.Navigation("LogSetting");
|
||||||
});
|
});
|
||||||
@@ -2281,7 +2292,7 @@ namespace NadekoBot.Migrations
|
|||||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b =>
|
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting")
|
b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting")
|
||||||
.WithMany("IgnoredVoicePresenceChannelIds")
|
.WithMany()
|
||||||
.HasForeignKey("LogSettingId");
|
.HasForeignKey("LogSettingId");
|
||||||
|
|
||||||
b.Navigation("LogSetting");
|
b.Navigation("LogSetting");
|
||||||
@@ -2294,13 +2305,6 @@ namespace NadekoBot.Migrations
|
|||||||
.HasForeignKey("GuildConfigId");
|
.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 =>
|
modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null)
|
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null)
|
||||||
@@ -2567,8 +2571,6 @@ namespace NadekoBot.Migrations
|
|||||||
|
|
||||||
b.Navigation("MutedUsers");
|
b.Navigation("MutedUsers");
|
||||||
|
|
||||||
b.Navigation("NsfwBlacklistedTags");
|
|
||||||
|
|
||||||
b.Navigation("Permissions");
|
b.Navigation("Permissions");
|
||||||
|
|
||||||
b.Navigation("ReactionRoleMessages");
|
b.Navigation("ReactionRoleMessages");
|
||||||
@@ -2598,9 +2600,7 @@ namespace NadekoBot.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b =>
|
modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("IgnoredChannels");
|
b.Navigation("LogIgnores");
|
||||||
|
|
||||||
b.Navigation("IgnoredVoicePresenceChannelIds");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b =>
|
modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b =>
|
||||||
|
@@ -16,6 +16,7 @@ namespace NadekoBot.Modules.Administration
|
|||||||
[NadekoCommand, Aliases]
|
[NadekoCommand, Aliases]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.ManageRoles)]
|
[UserPerm(GuildPerm.ManageRoles)]
|
||||||
|
[BotPerm(GuildPerm.ManageRoles)]
|
||||||
public async Task AutoAssignRole([Leftover] IRole role)
|
public async Task AutoAssignRole([Leftover] IRole role)
|
||||||
{
|
{
|
||||||
var guser = (IGuildUser) ctx.User;
|
var guser = (IGuildUser) ctx.User;
|
||||||
@@ -47,6 +48,7 @@ namespace NadekoBot.Modules.Administration
|
|||||||
[NadekoCommand, Aliases]
|
[NadekoCommand, Aliases]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.ManageRoles)]
|
[UserPerm(GuildPerm.ManageRoles)]
|
||||||
|
[BotPerm(GuildPerm.ManageRoles)]
|
||||||
public async Task AutoAssignRole()
|
public async Task AutoAssignRole()
|
||||||
{
|
{
|
||||||
if (!_service.TryGetRoles(ctx.Guild.Id, out var roles))
|
if (!_service.TryGetRoles(ctx.Guild.Id, out var roles))
|
||||||
|
@@ -7,6 +7,7 @@ using NadekoBot.Services.Database.Models;
|
|||||||
using NadekoBot.Extensions;
|
using NadekoBot.Extensions;
|
||||||
using NadekoBot.Modules.Administration.Services;
|
using NadekoBot.Modules.Administration.Services;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -18,12 +19,6 @@ namespace NadekoBot.Modules.Administration
|
|||||||
[NoPublicBot]
|
[NoPublicBot]
|
||||||
public class LogCommands : NadekoSubmodule<ILogCommandService>
|
public class LogCommands : NadekoSubmodule<ILogCommandService>
|
||||||
{
|
{
|
||||||
public enum EnableDisable
|
|
||||||
{
|
|
||||||
Enable,
|
|
||||||
Disable
|
|
||||||
}
|
|
||||||
|
|
||||||
[NadekoCommand, Aliases]
|
[NadekoCommand, Aliases]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.Administrator)]
|
[UserPerm(GuildPerm.Administrator)]
|
||||||
@@ -43,14 +38,51 @@ namespace NadekoBot.Modules.Administration
|
|||||||
[OwnerOnly]
|
[OwnerOnly]
|
||||||
public async Task LogIgnore()
|
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)
|
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
|
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]
|
[NadekoCommand, Aliases]
|
||||||
|
@@ -6,6 +6,8 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Discord;
|
using Discord;
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
|
using LinqToDB;
|
||||||
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using NadekoBot.Common.Collections;
|
using NadekoBot.Common.Collections;
|
||||||
@@ -21,7 +23,7 @@ namespace NadekoBot.Modules.Administration.Services
|
|||||||
{
|
{
|
||||||
void AddDeleteIgnore(ulong xId);
|
void AddDeleteIgnore(ulong xId);
|
||||||
Task LogServer(ulong guildId, ulong channelId, bool actionValue);
|
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);
|
LogSetting GetGuildLogSettings(ulong guildId);
|
||||||
bool Log(ulong guildId, ulong? channelId, LogType type);
|
bool Log(ulong guildId, ulong? channelId, LogType type);
|
||||||
}
|
}
|
||||||
@@ -37,7 +39,7 @@ namespace NadekoBot.Modules.Administration.Services
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool LogIgnore(ulong guildId, ulong channelId)
|
public bool LogIgnore(ulong guildId, ulong itemId, IgnoredItemType itemType)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -97,7 +99,7 @@ namespace NadekoBot.Modules.Administration.Services
|
|||||||
.AsQueryable()
|
.AsQueryable()
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(x => guildIds.Contains(x.GuildId))
|
.Where(x => guildIds.Contains(x.GuildId))
|
||||||
.Include(ls => ls.IgnoredChannels)
|
.Include(ls => ls.LogIgnores)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
GuildLogSettings = configs
|
GuildLogSettings = configs
|
||||||
@@ -165,21 +167,23 @@ namespace NadekoBot.Modules.Administration.Services
|
|||||||
_ignoreMessageIds.Add(messageId);
|
_ignoreMessageIds.Add(messageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool LogIgnore(ulong gid, ulong cid)
|
public bool LogIgnore(ulong gid, ulong itemId, IgnoredItemType itemType)
|
||||||
{
|
{
|
||||||
int removed = 0;
|
int removed = 0;
|
||||||
using (var uow = _db.GetDbContext())
|
using (var uow = _db.GetDbContext())
|
||||||
{
|
{
|
||||||
var logSetting = uow.LogSettingsFor(gid);
|
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)
|
if (removed == 0)
|
||||||
{
|
{
|
||||||
var toAdd = new IgnoredLogChannel {ChannelId = cid};
|
var toAdd = new IgnoredLogItem { LogItemId = itemId, ItemType = itemType};
|
||||||
logSetting.IgnoredChannels.Add(toAdd);
|
logSetting.LogIgnores.Add(toAdd);
|
||||||
}
|
}
|
||||||
|
|
||||||
GuildLogSettings.AddOrUpdate(gid, logSetting, (_, _) => logSetting);
|
|
||||||
uow.SaveChanges();
|
uow.SaveChanges();
|
||||||
|
GuildLogSettings.AddOrUpdate(gid, logSetting, (_, _) => logSetting);
|
||||||
}
|
}
|
||||||
|
|
||||||
return removed > 0;
|
return removed > 0;
|
||||||
@@ -580,7 +584,8 @@ namespace NadekoBot.Modules.Administration.Services
|
|||||||
{
|
{
|
||||||
try
|
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;
|
return;
|
||||||
|
|
||||||
ITextChannel logChannel;
|
ITextChannel logChannel;
|
||||||
@@ -682,7 +687,7 @@ namespace NadekoBot.Modules.Administration.Services
|
|||||||
|
|
||||||
if (!GuildLogSettings.TryGetValue(before.Guild.Id, out LogSetting logSetting)
|
if (!GuildLogSettings.TryGetValue(before.Guild.Id, out LogSetting logSetting)
|
||||||
|| (logSetting.ChannelUpdatedId is null)
|
|| (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;
|
return;
|
||||||
|
|
||||||
ITextChannel logChannel;
|
ITextChannel logChannel;
|
||||||
@@ -733,7 +738,7 @@ namespace NadekoBot.Modules.Administration.Services
|
|||||||
|
|
||||||
if (!GuildLogSettings.TryGetValue(ch.Guild.Id, out LogSetting logSetting)
|
if (!GuildLogSettings.TryGetValue(ch.Guild.Id, out LogSetting logSetting)
|
||||||
|| (logSetting.ChannelDestroyedId is null)
|
|| (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;
|
return;
|
||||||
|
|
||||||
ITextChannel logChannel;
|
ITextChannel logChannel;
|
||||||
@@ -772,7 +777,7 @@ namespace NadekoBot.Modules.Administration.Services
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (!GuildLogSettings.TryGetValue(ch.Guild.Id, out LogSetting logSetting)
|
if (!GuildLogSettings.TryGetValue(ch.Guild.Id, out LogSetting logSetting)
|
||||||
|| (logSetting.ChannelCreatedId is null))
|
|| logSetting.ChannelCreatedId is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ITextChannel logChannel;
|
ITextChannel logChannel;
|
||||||
@@ -817,7 +822,8 @@ namespace NadekoBot.Modules.Administration.Services
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (!GuildLogSettings.TryGetValue(usr.Guild.Id, out LogSetting logSetting)
|
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;
|
return;
|
||||||
|
|
||||||
ITextChannel logChannel;
|
ITextChannel logChannel;
|
||||||
@@ -862,49 +868,6 @@ namespace NadekoBot.Modules.Administration.Services
|
|||||||
return Task.CompletedTask;
|
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)
|
private Task _client_UserLeft(IGuildUser usr)
|
||||||
{
|
{
|
||||||
var _ = Task.Run(async () =>
|
var _ = Task.Run(async () =>
|
||||||
@@ -912,7 +875,8 @@ namespace NadekoBot.Modules.Administration.Services
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!GuildLogSettings.TryGetValue(usr.Guild.Id, out LogSetting logSetting)
|
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;
|
return;
|
||||||
|
|
||||||
ITextChannel logChannel;
|
ITextChannel logChannel;
|
||||||
@@ -987,7 +951,8 @@ namespace NadekoBot.Modules.Administration.Services
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!GuildLogSettings.TryGetValue(guild.Id, out LogSetting logSetting)
|
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;
|
return;
|
||||||
|
|
||||||
ITextChannel logChannel;
|
ITextChannel logChannel;
|
||||||
@@ -1021,7 +986,8 @@ namespace NadekoBot.Modules.Administration.Services
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!GuildLogSettings.TryGetValue(guild.Id, out LogSetting logSetting)
|
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;
|
return;
|
||||||
|
|
||||||
ITextChannel logChannel;
|
ITextChannel logChannel;
|
||||||
@@ -1069,7 +1035,7 @@ namespace NadekoBot.Modules.Administration.Services
|
|||||||
|
|
||||||
if (!GuildLogSettings.TryGetValue(channel.Guild.Id, out LogSetting logSetting)
|
if (!GuildLogSettings.TryGetValue(channel.Guild.Id, out LogSetting logSetting)
|
||||||
|| (logSetting.MessageDeletedId is null)
|
|| (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;
|
return;
|
||||||
|
|
||||||
ITextChannel logChannel;
|
ITextChannel logChannel;
|
||||||
@@ -1127,7 +1093,7 @@ namespace NadekoBot.Modules.Administration.Services
|
|||||||
|
|
||||||
if (!GuildLogSettings.TryGetValue(channel.Guild.Id, out LogSetting logSetting)
|
if (!GuildLogSettings.TryGetValue(channel.Guild.Id, out LogSetting logSetting)
|
||||||
|| (logSetting.MessageUpdatedId is null)
|
|| (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;
|
return;
|
||||||
|
|
||||||
ITextChannel logChannel;
|
ITextChannel logChannel;
|
||||||
|
@@ -12,6 +12,8 @@ using LinqToDB;
|
|||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using NadekoBot.Db;
|
using NadekoBot.Db;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
using System.Threading;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Administration.Services
|
namespace NadekoBot.Modules.Administration.Services
|
||||||
{
|
{
|
||||||
@@ -21,6 +23,11 @@ namespace NadekoBot.Modules.Administration.Services
|
|||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
private readonly ConcurrentDictionary<ulong, IndexedCollection<ReactionRoleMessage>> _models;
|
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,
|
public RoleCommandsService(DiscordSocketClient client, DbService db,
|
||||||
Bot bot)
|
Bot bot)
|
||||||
{
|
{
|
||||||
@@ -38,19 +45,13 @@ namespace NadekoBot.Modules.Administration.Services
|
|||||||
|
|
||||||
private Task _client_ReactionAdded(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel chan, SocketReaction reaction)
|
private Task _client_ReactionAdded(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel chan, SocketReaction reaction)
|
||||||
{
|
{
|
||||||
var _ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
if (!reaction.User.IsSpecified ||
|
if (!reaction.User.IsSpecified ||
|
||||||
reaction.User.Value.IsBot ||
|
reaction.User.Value.IsBot ||
|
||||||
!(reaction.User.Value is SocketGuildUser gusr))
|
reaction.User.Value is not SocketGuildUser gusr ||
|
||||||
return;
|
chan is not SocketGuildChannel gch ||
|
||||||
|
!_models.TryGetValue(gch.Guild.Id, out var confs))
|
||||||
if (!(chan is SocketGuildChannel gch))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!_models.TryGetValue(gch.Guild.Id, out var confs))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var conf = confs.FirstOrDefault(x => x.MessageId == msg.Id);
|
var conf = confs.FirstOrDefault(x => x.MessageId == msg.Id);
|
||||||
@@ -60,39 +61,30 @@ namespace NadekoBot.Modules.Administration.Services
|
|||||||
|
|
||||||
// compare emote names for backwards compatibility :facepalm:
|
// compare emote names for backwards compatibility :facepalm:
|
||||||
var reactionRole = conf.ReactionRoles.FirstOrDefault(x => x.EmoteName == reaction.Emote.Name || x.EmoteName == reaction.Emote.ToString());
|
var reactionRole = conf.ReactionRoles.FirstOrDefault(x => x.EmoteName == reaction.Emote.Name || x.EmoteName == reaction.Emote.ToString());
|
||||||
|
|
||||||
if (reactionRole != null)
|
if (reactionRole != null)
|
||||||
{
|
{
|
||||||
if (conf.Exclusive)
|
if (!conf.Exclusive)
|
||||||
{
|
{
|
||||||
var roleIds = conf.ReactionRoles.Select(x => x.RoleId)
|
await AddReactionRoleAsync(gusr, reactionRole);
|
||||||
.Where(x => x != reactionRole.RoleId)
|
return;
|
||||||
.Select(x => gusr.Guild.GetRole(x))
|
}
|
||||||
.Where(x => x != null);
|
|
||||||
|
// If same (message, user) are being processed in an exclusive rero, quit
|
||||||
|
if (!_reacting.Add((msg.Id, reaction.UserId)))
|
||||||
|
return;
|
||||||
|
|
||||||
var __ = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
//if the role is exclusive,
|
var removeExclusiveTask = RemoveExclusiveReactionRoleAsync(msg, gusr, reaction, conf, reactionRole, CancellationToken.None);
|
||||||
// remove all other reactions user added to the message
|
var addRoleTask = AddReactionRoleAsync(gusr, reactionRole);
|
||||||
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);
|
await Task.WhenAll(removeExclusiveTask, addRoleTask).ConfigureAwait(false);
|
||||||
if (toAdd != null && !gusr.Roles.Contains(toAdd))
|
}
|
||||||
|
finally
|
||||||
{
|
{
|
||||||
await gusr.AddRolesAsync(new[] { toAdd }).ConfigureAwait(false);
|
// Free (message/user) for another exclusive rero
|
||||||
|
_reacting.TryRemove((msg.Id, reaction.UserId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -105,8 +97,6 @@ namespace NadekoBot.Modules.Administration.Services
|
|||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
Log.Warning("User {0} is adding unrelated reactions to the reaction roles message.", dl.Author);
|
Log.Warning("User {0} is adding unrelated reactions to the reaction roles message.", dl.Author);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
@@ -114,16 +104,16 @@ namespace NadekoBot.Modules.Administration.Services
|
|||||||
|
|
||||||
private Task _client_ReactionRemoved(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel chan, SocketReaction reaction)
|
private Task _client_ReactionRemoved(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel chan, SocketReaction reaction)
|
||||||
{
|
{
|
||||||
var _ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!reaction.User.IsSpecified ||
|
if (!reaction.User.IsSpecified ||
|
||||||
reaction.User.Value.IsBot ||
|
reaction.User.Value.IsBot ||
|
||||||
!(reaction.User.Value is SocketGuildUser gusr))
|
reaction.User.Value is not SocketGuildUser gusr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!(chan is SocketGuildChannel gch))
|
if (chan is not SocketGuildChannel gch)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!_models.TryGetValue(gch.Guild.Id, out var confs))
|
if (!_models.TryGetValue(gch.Guild.Id, out var confs))
|
||||||
@@ -193,5 +183,71 @@ namespace NadekoBot.Modules.Administration.Services
|
|||||||
uow.SaveChanges();
|
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));
|
}, 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();
|
var modName = mod.ToString();
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(reason))
|
if (string.IsNullOrWhiteSpace(reason))
|
||||||
@@ -57,6 +60,7 @@ namespace NadekoBot.Modules.Administration.Services
|
|||||||
Forgiven = false,
|
Forgiven = false,
|
||||||
Reason = reason,
|
Reason = reason,
|
||||||
Moderator = modName,
|
Moderator = modName,
|
||||||
|
Weight = weight,
|
||||||
};
|
};
|
||||||
|
|
||||||
int warnings = 1;
|
int warnings = 1;
|
||||||
@@ -70,7 +74,7 @@ namespace NadekoBot.Modules.Administration.Services
|
|||||||
.Warnings
|
.Warnings
|
||||||
.ForId(guildId, userId)
|
.ForId(guildId, userId)
|
||||||
.Where(w => !w.Forgiven && w.UserId == userId)
|
.Where(w => !w.Forgiven && w.UserId == userId)
|
||||||
.Count();
|
.Sum(x => x.Weight);
|
||||||
|
|
||||||
uow.Warnings.Add(warn);
|
uow.Warnings.Add(warn);
|
||||||
|
|
||||||
|
@@ -54,8 +54,17 @@ namespace NadekoBot.Modules.Administration
|
|||||||
[NadekoCommand, Aliases]
|
[NadekoCommand, Aliases]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.BanMembers)]
|
[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))
|
if (!await CheckRoleHierarchy(user))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -76,7 +85,7 @@ namespace NadekoBot.Modules.Administration
|
|||||||
WarningPunishment punishment;
|
WarningPunishment punishment;
|
||||||
try
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -230,19 +239,29 @@ namespace NadekoBot.Modules.Administration
|
|||||||
}
|
}
|
||||||
else
|
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;
|
var i = page * 9;
|
||||||
foreach (var w in warnings)
|
foreach (var w in warnings)
|
||||||
{
|
{
|
||||||
i++;
|
i++;
|
||||||
var name = GetText(strs.warned_on_by(
|
var name = GetText(strs.warned_on_by(
|
||||||
w.DateAdded.Value.ToString("dd.MM.yyy"),
|
w.DateAdded?.ToString("dd.MM.yyy"),
|
||||||
w.DateAdded.Value.ToString("HH:mm"),
|
w.DateAdded?.ToString("HH:mm"),
|
||||||
w.Moderator));
|
w.Moderator));
|
||||||
|
|
||||||
if (w.Forgiven)
|
if (w.Forgiven)
|
||||||
name = $"{Format.Strikethrough(name)} {GetText(strs.warn_cleared_by(w.ForgivenBy))}";
|
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()
|
var toSend = _eb.Create().WithOkColor()
|
||||||
.WithTitle("⛔️ " + GetText(strs.banned_user))
|
.WithTitle("⛔️ " + GetText(strs.banned_user))
|
||||||
.AddField(GetText(strs.username), user.ToString(), true)
|
.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);
|
var user = await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(ctx.Guild.Id, userId);
|
||||||
if (user is null)
|
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()
|
await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
|
||||||
.WithTitle("⛔️ " + GetText(strs.banned_user))
|
.WithTitle("⛔️ " + GetText(strs.banned_user))
|
||||||
@@ -516,7 +535,7 @@ namespace NadekoBot.Modules.Administration
|
|||||||
dmFailed = true;
|
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()
|
var toSend = _eb.Create().WithOkColor()
|
||||||
.WithTitle("⛔️ " + GetText(strs.banned_user))
|
.WithTitle("⛔️ " + GetText(strs.banned_user))
|
||||||
@@ -692,7 +711,7 @@ namespace NadekoBot.Modules.Administration
|
|||||||
dmFailed = true;
|
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); }
|
try { await ctx.Guild.RemoveBanAsync(user).ConfigureAwait(false); }
|
||||||
catch { 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;
|
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()
|
var toSend = _eb.Create().WithOkColor()
|
||||||
.WithTitle(GetText(strs.kicked_user))
|
.WithTitle(GetText(strs.kicked_user))
|
||||||
@@ -776,23 +795,32 @@ namespace NadekoBot.Modules.Administration
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var missing = new List<string>();
|
var missing = new List<string>();
|
||||||
var banning = new HashSet<IGuildUser>();
|
var banning = new HashSet<IUser>();
|
||||||
|
|
||||||
await ctx.Channel.TriggerTypingAsync();
|
await ctx.Channel.TriggerTypingAsync();
|
||||||
foreach (var userStr in userStrings)
|
foreach (var userStr in userStrings)
|
||||||
{
|
{
|
||||||
if (ulong.TryParse(userStr, out var userId))
|
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);
|
await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(ctx.Guild.Id, userId);
|
||||||
|
|
||||||
|
if (user is null)
|
||||||
|
{
|
||||||
|
// 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)
|
if (user is null)
|
||||||
{
|
{
|
||||||
missing.Add(userStr);
|
missing.Add(userStr);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!await CheckRoleHierarchy(user))
|
}
|
||||||
|
|
||||||
|
//Hierachy checks only if the user is in the guild
|
||||||
|
if (user is IGuildUser gu && !await CheckRoleHierarchy(gu))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -820,7 +848,7 @@ namespace NadekoBot.Modules.Administration
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await toBan.BanAsync(7);
|
await ctx.Guild.AddBanAsync(toBan.Id, 7, $"{ctx.User} | Massban");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@@ -149,7 +149,7 @@ namespace NadekoBot.Modules.CustomReactions
|
|||||||
await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
|
await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
|
||||||
.WithDescription($"#{id}")
|
.WithDescription($"#{id}")
|
||||||
.AddField(GetText(strs.trigger), found.Trigger.TrimTo(1024))
|
.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);
|
).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using Cloneable;
|
using Cloneable;
|
||||||
using NadekoBot.Common;
|
using NadekoBot.Common;
|
||||||
using NadekoBot.Common.Yml;
|
using NadekoBot.Common.Yml;
|
||||||
|
using YamlDotNet.Serialization;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Gambling.Common
|
namespace NadekoBot.Modules.Gambling.Common
|
||||||
{
|
{
|
||||||
@@ -22,7 +23,7 @@ namespace NadekoBot.Modules.Gambling.Common
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Comment(@"DO NOT CHANGE")]
|
[Comment(@"DO NOT CHANGE")]
|
||||||
public int Version { get; set; } = 1;
|
public int Version { get; set; } = 2;
|
||||||
|
|
||||||
[Comment(@"Currency settings")]
|
[Comment(@"Currency settings")]
|
||||||
public CurrencyConfig Currency { get; set; }
|
public CurrencyConfig Currency { get; set; }
|
||||||
@@ -59,6 +60,10 @@ Set 0 for unlimited")]
|
|||||||
[Comment(@"Amount of currency selfhosters will get PER pledged dollar CENT.
|
[Comment(@"Amount of currency selfhosters will get PER pledged dollar CENT.
|
||||||
1 = 100 currency per $. Used almost exclusively on public nadeko.")]
|
1 = 100 currency per $. Used almost exclusively on public nadeko.")]
|
||||||
public decimal PatreonCurrencyPerCent { get; set; } = 1;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CurrencyConfig
|
public class CurrencyConfig
|
||||||
@@ -179,7 +184,8 @@ default is 0.02, which is 2%")]
|
|||||||
|
|
||||||
public MultipliersData Multipliers { get; set; } = new MultipliersData();
|
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 List<WaifuItemModel> Items { get; set; } = new List<WaifuItemModel>();
|
||||||
|
|
||||||
public WaifuConfig()
|
public WaifuConfig()
|
||||||
@@ -260,6 +266,11 @@ Default 1 (meaning no effect)")]
|
|||||||
Default 0.95 (meaning 95%)
|
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)")]
|
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;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cloneable]
|
[Cloneable]
|
||||||
@@ -269,18 +280,23 @@ Example: If a waifu is worth 1000, and she receives a gift worth 100, her new va
|
|||||||
public int Price { get; set; }
|
public int Price { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
[YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)]
|
||||||
|
public bool Negative { get; set; }
|
||||||
|
|
||||||
public WaifuItemModel()
|
public WaifuItemModel()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public WaifuItemModel(string itemEmoji, int price, string name)
|
public WaifuItemModel(string itemEmoji, int price, string name, bool negative = false)
|
||||||
{
|
{
|
||||||
ItemEmoji = itemEmoji;
|
ItemEmoji = itemEmoji;
|
||||||
Price = price;
|
Price = price;
|
||||||
Name = name;
|
Name = name;
|
||||||
|
Negative = negative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public override string ToString() => Name;
|
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]
|
[Group]
|
||||||
public class CurrencyEventsCommands : GamblingSubmodule<CurrencyEventsService>
|
public class CurrencyEventsCommands : GamblingSubmodule<CurrencyEventsService>
|
||||||
{
|
{
|
||||||
public enum OtherEvent
|
|
||||||
{
|
|
||||||
BotListUpvoters
|
|
||||||
}
|
|
||||||
|
|
||||||
public CurrencyEventsCommands(GamblingConfigService gamblingConf) : base(gamblingConf)
|
public CurrencyEventsCommands(GamblingConfigService gamblingConf) : base(gamblingConf)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -37,41 +32,36 @@ namespace NadekoBot.Modules.Gambling
|
|||||||
ctx.Channel.Id,
|
ctx.Channel.Id,
|
||||||
ev,
|
ev,
|
||||||
opts,
|
opts,
|
||||||
GetEmbed
|
GetEmbed))
|
||||||
).ConfigureAwait(false))
|
|
||||||
{
|
{
|
||||||
await ReplyErrorLocalizedAsync(strs.start_event_fail).ConfigureAwait(false);
|
await ReplyErrorLocalizedAsync(strs.start_event_fail).ConfigureAwait(false);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEmbedBuilder GetEmbed(CurrencyEvent.Type type, EventOptions opts, long currentPot)
|
private IEmbedBuilder GetEmbed(CurrencyEvent.Type type, EventOptions opts, long currentPot)
|
||||||
{
|
{
|
||||||
switch (type)
|
return type switch
|
||||||
{
|
{
|
||||||
case CurrencyEvent.Type.Reaction:
|
CurrencyEvent.Type.Reaction => _eb.Create()
|
||||||
return _eb.Create()
|
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithTitle(GetText(strs.event_title(type.ToString())))
|
.WithTitle(GetText(strs.event_title(type.ToString())))
|
||||||
.WithDescription(GetReactionDescription(opts.Amount, currentPot))
|
.WithDescription(GetReactionDescription(opts.Amount, currentPot))
|
||||||
.WithFooter(GetText(strs.event_duration_footer(opts.Hours)));
|
.WithFooter(GetText(strs.event_duration_footer(opts.Hours))),
|
||||||
case CurrencyEvent.Type.GameStatus:
|
CurrencyEvent.Type.GameStatus => _eb.Create()
|
||||||
return _eb.Create()
|
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithTitle(GetText(strs.event_title(type.ToString())))
|
.WithTitle(GetText(strs.event_title(type.ToString())))
|
||||||
.WithDescription(GetGameStatusDescription(opts.Amount, currentPot))
|
.WithDescription(GetGameStatusDescription(opts.Amount, currentPot))
|
||||||
.WithFooter(GetText(strs.event_duration_footer(opts.Hours)));
|
.WithFooter(GetText(strs.event_duration_footer(opts.Hours))),
|
||||||
default:
|
_ => throw new ArgumentOutOfRangeException(nameof(type))
|
||||||
break;
|
};
|
||||||
}
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(type));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetReactionDescription(long amount, long potSize)
|
private string GetReactionDescription(long amount, long potSize)
|
||||||
{
|
{
|
||||||
string potSizeStr = Format.Bold(potSize == 0
|
var potSizeStr = Format.Bold(potSize == 0
|
||||||
? "∞" + CurrencySign
|
? "∞" + CurrencySign
|
||||||
: potSize.ToString() + CurrencySign);
|
: potSize + CurrencySign);
|
||||||
|
|
||||||
return GetText(strs.new_reaction_event(
|
return GetText(strs.new_reaction_event(
|
||||||
CurrencySign,
|
CurrencySign,
|
||||||
Format.Bold(amount + CurrencySign),
|
Format.Bold(amount + CurrencySign),
|
||||||
@@ -80,9 +70,10 @@ namespace NadekoBot.Modules.Gambling
|
|||||||
|
|
||||||
private string GetGameStatusDescription(long amount, long potSize)
|
private string GetGameStatusDescription(long amount, long potSize)
|
||||||
{
|
{
|
||||||
string potSizeStr = Format.Bold(potSize == 0
|
var potSizeStr = Format.Bold(potSize == 0
|
||||||
? "∞" + CurrencySign
|
? "∞" + CurrencySign
|
||||||
: potSize.ToString() + CurrencySign);
|
: potSize + CurrencySign);
|
||||||
|
|
||||||
return GetText(strs.new_gamestatus_event(
|
return GetText(strs.new_gamestatus_event(
|
||||||
CurrencySign,
|
CurrencySign,
|
||||||
Format.Bold(amount + CurrencySign),
|
Format.Bold(amount + CurrencySign),
|
||||||
|
@@ -22,15 +22,12 @@ namespace NadekoBot.Modules.Gambling
|
|||||||
{
|
{
|
||||||
private readonly IImageCache _images;
|
private readonly IImageCache _images;
|
||||||
private readonly ICurrencyService _cs;
|
private readonly ICurrencyService _cs;
|
||||||
private readonly DbService _db;
|
|
||||||
private static readonly NadekoRandom rng = new NadekoRandom();
|
private static readonly NadekoRandom rng = new NadekoRandom();
|
||||||
|
|
||||||
public FlipCoinCommands(IDataCache data, ICurrencyService cs, DbService db,
|
public FlipCoinCommands(IDataCache data, ICurrencyService cs, GamblingConfigService gss) : base(gss)
|
||||||
GamblingConfigService gss) : base(gss)
|
|
||||||
{
|
{
|
||||||
_images = data.LocalImages;
|
_images = data.LocalImages;
|
||||||
_cs = cs;
|
_cs = cs;
|
||||||
_db = db;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[NadekoCommand, Aliases]
|
[NadekoCommand, Aliases]
|
||||||
|
@@ -66,11 +66,11 @@ namespace NadekoBot.Modules.Gambling
|
|||||||
}
|
}
|
||||||
var embed = _eb.Create()
|
var embed = _eb.Create()
|
||||||
.WithTitle(GetText(strs.economy_state))
|
.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_one_percent), (onePercent * 100).ToString("F2") + "%")
|
||||||
.AddField(GetText(strs.currency_planted), ((BigInteger)ec.Planted) + CurrencySign)
|
.AddField(GetText(strs.currency_planted), ((BigInteger)ec.Planted) + CurrencySign)
|
||||||
.AddField(GetText(strs.owned_waifus_total), ((BigInteger)ec.Waifus) + 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)
|
.AddField(GetText(strs.total), ((BigInteger)(ec.Cash + ec.Planted + ec.Waifus)).ToString("N", _enUsCulture) + CurrencySign)
|
||||||
.WithOkColor();
|
.WithOkColor();
|
||||||
// ec.Cash already contains ec.Bot as it's the total of all values in the CurrencyAmount column of the DiscordUser table
|
// ec.Cash already contains ec.Bot as it's the total of all values in the CurrencyAmount column of the DiscordUser table
|
||||||
@@ -247,20 +247,20 @@ namespace NadekoBot.Modules.Gambling
|
|||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[OwnerOnly]
|
[OwnerOnly]
|
||||||
[Priority(0)]
|
[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);
|
Award(amount, usr.Id, msg);
|
||||||
|
|
||||||
[NadekoCommand, Aliases]
|
[NadekoCommand, Aliases]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[OwnerOnly]
|
[OwnerOnly]
|
||||||
[Priority(1)]
|
[Priority(1)]
|
||||||
public Task Award(ShmartNumber amount, [Leftover] IGuildUser usr) =>
|
public Task Award(long amount, [Leftover] IGuildUser usr) =>
|
||||||
Award(amount, usr.Id);
|
Award(amount, usr.Id);
|
||||||
|
|
||||||
[NadekoCommand, Aliases]
|
[NadekoCommand, Aliases]
|
||||||
[OwnerOnly]
|
[OwnerOnly]
|
||||||
[Priority(2)]
|
[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)
|
if (amount <= 0)
|
||||||
return;
|
return;
|
||||||
@@ -276,7 +276,7 @@ namespace NadekoBot.Modules.Gambling
|
|||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[OwnerOnly]
|
[OwnerOnly]
|
||||||
[Priority(2)]
|
[Priority(2)]
|
||||||
public async Task Award(ShmartNumber amount, [Leftover] IRole role)
|
public async Task Award(long amount, [Leftover] IRole role)
|
||||||
{
|
{
|
||||||
var users = (await ctx.Guild.GetUsersAsync().ConfigureAwait(false))
|
var users = (await ctx.Guild.GetUsersAsync().ConfigureAwait(false))
|
||||||
.Where(u => u.GetRoles().Contains(role))
|
.Where(u => u.GetRoles().Contains(role))
|
||||||
@@ -284,7 +284,7 @@ namespace NadekoBot.Modules.Gambling
|
|||||||
|
|
||||||
await _cs.AddBulkAsync(users.Select(x => x.Id),
|
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 => $"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)
|
gamble: true)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
@@ -298,13 +298,13 @@ namespace NadekoBot.Modules.Gambling
|
|||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[OwnerOnly]
|
[OwnerOnly]
|
||||||
[Priority(0)]
|
[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();
|
var users = (await role.GetMembersAsync()).ToList();
|
||||||
|
|
||||||
await _cs.RemoveBulkAsync(users.Select(x => x.Id),
|
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 => $"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)
|
gamble: true)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
@@ -318,7 +318,7 @@ namespace NadekoBot.Modules.Gambling
|
|||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[OwnerOnly]
|
[OwnerOnly]
|
||||||
[Priority(1)]
|
[Priority(1)]
|
||||||
public async Task Take(ShmartNumber amount, [Leftover] IGuildUser user)
|
public async Task Take(long amount, [Leftover] IGuildUser user)
|
||||||
{
|
{
|
||||||
if (amount <= 0)
|
if (amount <= 0)
|
||||||
return;
|
return;
|
||||||
@@ -333,7 +333,7 @@ namespace NadekoBot.Modules.Gambling
|
|||||||
|
|
||||||
[NadekoCommand, Aliases]
|
[NadekoCommand, Aliases]
|
||||||
[OwnerOnly]
|
[OwnerOnly]
|
||||||
public async Task Take(ShmartNumber amount, [Leftover] ulong usrId)
|
public async Task Take(long amount, [Leftover] ulong usrId)
|
||||||
{
|
{
|
||||||
if (amount <= 0)
|
if (amount <= 0)
|
||||||
return;
|
return;
|
||||||
|
@@ -8,10 +8,11 @@ using NadekoBot.Modules.Gambling.Services;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NadekoBot.Modules.Gambling.Common;
|
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]
|
[Group]
|
||||||
public class PlantPickCommands : GamblingSubmodule<PlantPickService>
|
public class PlantPickCommands : GamblingSubmodule<PlantPickService>
|
||||||
@@ -53,7 +54,7 @@ namespace NadekoBot.Modules.Games
|
|||||||
|
|
||||||
[NadekoCommand, Aliases]
|
[NadekoCommand, Aliases]
|
||||||
[RequireContext(ContextType.Guild)]
|
[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)
|
if (amount < 1)
|
||||||
return;
|
return;
|
||||||
@@ -63,18 +64,17 @@ namespace NadekoBot.Modules.Games
|
|||||||
return;
|
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)
|
if (((SocketGuild)ctx.Guild).CurrentUser.GuildPermissions.ManageMessages)
|
||||||
{
|
{
|
||||||
logService.AddDeleteIgnore(ctx.Message.Id);
|
logService.AddDeleteIgnore(ctx.Message.Id);
|
||||||
await ctx.Message.DeleteAsync().ConfigureAwait(false);
|
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]
|
[NadekoCommand, Aliases]
|
@@ -8,8 +8,6 @@ using System.Threading.Tasks;
|
|||||||
using System;
|
using System;
|
||||||
using NadekoBot.Services.Database.Models;
|
using NadekoBot.Services.Database.Models;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System.Linq;
|
|
||||||
using NadekoBot.Modules.Gambling.Services;
|
using NadekoBot.Modules.Gambling.Services;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
@@ -17,76 +15,22 @@ namespace NadekoBot.Modules.Gambling.Services
|
|||||||
{
|
{
|
||||||
public class CurrencyEventsService : INService
|
public class CurrencyEventsService : INService
|
||||||
{
|
{
|
||||||
public class VoteModel
|
|
||||||
{
|
|
||||||
public ulong User { get; set; }
|
|
||||||
public long Date { get; set; }
|
|
||||||
}
|
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
private readonly ICurrencyService _cs;
|
private readonly ICurrencyService _cs;
|
||||||
private readonly IBotCredentials _creds;
|
|
||||||
private readonly IHttpClientFactory _http;
|
|
||||||
private readonly GamblingConfigService _configService;
|
private readonly GamblingConfigService _configService;
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<ulong, ICurrencyEvent> _events =
|
private readonly ConcurrentDictionary<ulong, ICurrencyEvent> _events =
|
||||||
new ConcurrentDictionary<ulong, ICurrencyEvent>();
|
new ConcurrentDictionary<ulong, ICurrencyEvent>();
|
||||||
|
|
||||||
public CurrencyEventsService(DiscordSocketClient client,
|
|
||||||
IBotCredentials creds, ICurrencyService cs,
|
public CurrencyEventsService(
|
||||||
IHttpClientFactory http, GamblingConfigService configService)
|
DiscordSocketClient client,
|
||||||
|
ICurrencyService cs,
|
||||||
|
GamblingConfigService configService)
|
||||||
{
|
{
|
||||||
_client = client;
|
_client = client;
|
||||||
_cs = cs;
|
_cs = cs;
|
||||||
_creds = creds;
|
|
||||||
_http = http;
|
|
||||||
_configService = configService;
|
_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,
|
public async Task<bool> TryCreateEventAsync(ulong guildId, ulong channelId, CurrencyEvent.Type type,
|
||||||
@@ -127,6 +71,7 @@ namespace NadekoBot.Modules.Gambling.Services
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return added;
|
return added;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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.Common.Configs;
|
||||||
using NadekoBot.Modules.Gambling.Common;
|
using NadekoBot.Modules.Gambling.Common;
|
||||||
using NadekoBot.Services;
|
using NadekoBot.Services;
|
||||||
|
using NadekoBot.Services.Database.Models;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Gambling.Services
|
namespace NadekoBot.Modules.Gambling.Services
|
||||||
{
|
{
|
||||||
@@ -34,9 +38,39 @@ 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.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.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.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.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.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);
|
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.VoteReward = 100;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -6,12 +6,14 @@ using NadekoBot.Modules.Gambling.Common.WheelOfFortune;
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NadekoBot.Common;
|
using NadekoBot.Common;
|
||||||
using NadekoBot.Db;
|
using NadekoBot.Db;
|
||||||
|
using NadekoBot.Modules.Gambling.Common.Slot;
|
||||||
using NadekoBot.Modules.Gambling.Services;
|
using NadekoBot.Modules.Gambling.Services;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
@@ -83,6 +85,41 @@ WHERE CurrencyAmount > {config.Decay.MinThreshold} AND UserId!={_client.CurrentU
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
public struct EconomyResult
|
||||||
{
|
{
|
||||||
public decimal Cash { get; set; }
|
public decimal Cash { get; set; }
|
||||||
|
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,6 +394,8 @@ namespace NadekoBot.Modules.Gambling.Services
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!itemObj.Negative)
|
||||||
|
{
|
||||||
w.Items.Add(new WaifuItem()
|
w.Items.Add(new WaifuItem()
|
||||||
{
|
{
|
||||||
Name = itemObj.Name.ToLowerInvariant(),
|
Name = itemObj.Name.ToLowerInvariant(),
|
||||||
@@ -408,6 +410,13 @@ namespace NadekoBot.Modules.Gambling.Services
|
|||||||
{
|
{
|
||||||
w.Price += itemObj.Price / 2;
|
w.Price += itemObj.Price / 2;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
w.Price -= (int)(itemObj.Price * _gss.Data.Waifu.Multipliers.NegativeGiftEffect);
|
||||||
|
if (w.Price < 1)
|
||||||
|
w.Price = 1;
|
||||||
|
}
|
||||||
|
|
||||||
await uow.SaveChangesAsync();
|
await uow.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
@@ -512,7 +521,7 @@ namespace NadekoBot.Modules.Gambling.Services
|
|||||||
{
|
{
|
||||||
var conf = _gss.Data;
|
var conf = _gss.Data;
|
||||||
return conf.Waifu.Items
|
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();
|
.ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -225,6 +225,9 @@ namespace NadekoBot.Modules.Gambling
|
|||||||
[BotPerm(GuildPerm.ManageRoles)]
|
[BotPerm(GuildPerm.ManageRoles)]
|
||||||
public async Task ShopAdd(Role _, int price, [Leftover] IRole role)
|
public async Task ShopAdd(Role _, int price, [Leftover] IRole role)
|
||||||
{
|
{
|
||||||
|
if (price < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
var entry = new ShopEntry()
|
var entry = new ShopEntry()
|
||||||
{
|
{
|
||||||
Name = "-",
|
Name = "-",
|
||||||
@@ -254,6 +257,9 @@ namespace NadekoBot.Modules.Gambling
|
|||||||
[UserPerm(GuildPerm.Administrator)]
|
[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()
|
var entry = new ShopEntry()
|
||||||
{
|
{
|
||||||
Name = name.TrimTo(100),
|
Name = name.TrimTo(100),
|
||||||
@@ -273,6 +279,7 @@ namespace NadekoBot.Modules.Gambling
|
|||||||
uow.GuildConfigsForId(ctx.Guild.Id, set => set).ShopEntries = entries;
|
uow.GuildConfigsForId(ctx.Guild.Id, set => set).ShopEntries = entries;
|
||||||
uow.SaveChanges();
|
uow.SaveChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
await ctx.Channel.EmbedAsync(EntryToEmbed(entry)
|
await ctx.Channel.EmbedAsync(EntryToEmbed(entry)
|
||||||
.WithTitle(GetText(strs.shop_item_add))).ConfigureAwait(false);
|
.WithTitle(GetText(strs.shop_item_add))).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
@@ -9,12 +9,16 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NadekoBot.Common;
|
using NadekoBot.Common;
|
||||||
using NadekoBot.Common.Attributes;
|
using NadekoBot.Common.Attributes;
|
||||||
|
using NadekoBot.Db.Models;
|
||||||
using NadekoBot.Modules.Gambling.Services;
|
using NadekoBot.Modules.Gambling.Services;
|
||||||
using NadekoBot.Modules.Gambling.Common;
|
using NadekoBot.Modules.Gambling.Common;
|
||||||
using NadekoBot.Services;
|
using NadekoBot.Services;
|
||||||
|
using SixLabors.Fonts;
|
||||||
using Image = SixLabors.ImageSharp.Image;
|
using Image = SixLabors.ImageSharp.Image;
|
||||||
using SixLabors.ImageSharp.Processing;
|
using SixLabors.ImageSharp.Processing;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
|
using SixLabors.ImageSharp.Drawing.Processing;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Gambling
|
namespace NadekoBot.Modules.Gambling
|
||||||
{
|
{
|
||||||
@@ -33,12 +37,16 @@ namespace NadekoBot.Modules.Gambling
|
|||||||
//thanks to judge for helping me with this
|
//thanks to judge for helping me with this
|
||||||
|
|
||||||
private readonly IImageCache _images;
|
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;
|
_images = data.LocalImages;
|
||||||
_cs = cs;
|
_fonts = fonts;
|
||||||
|
_db = db;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class SlotMachine
|
public sealed class SlotMachine
|
||||||
@@ -142,79 +150,102 @@ namespace NadekoBot.Modules.Gambling
|
|||||||
{
|
{
|
||||||
if (!_runningUsers.Add(ctx.User.Id))
|
if (!_runningUsers.Add(ctx.User.Id))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!await CheckBetMandatory(amount).ConfigureAwait(false))
|
if (!await CheckBetMandatory(amount).ConfigureAwait(false))
|
||||||
return;
|
return;
|
||||||
const int maxAmount = 9999;
|
|
||||||
if (amount > maxAmount)
|
|
||||||
{
|
|
||||||
await ReplyErrorLocalizedAsync(strs.max_bet_limit(maxAmount + CurrencySign));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!await _cs.RemoveAsync(ctx.User, "Slot Machine", amount, false, gamble: true).ConfigureAwait(false))
|
await ctx.Channel.TriggerTypingAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
var result = await _service.SlotAsync(ctx.User.Id, amount);
|
||||||
|
|
||||||
|
if (result.Error != GamblingError.None)
|
||||||
|
{
|
||||||
|
if (result.Error == GamblingError.NotEnough)
|
||||||
{
|
{
|
||||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Interlocked.Add(ref _totalBet, amount.Value);
|
|
||||||
using (var bgImage = Image.Load(_images.SlotBackground))
|
|
||||||
{
|
|
||||||
var result = SlotMachine.Pull();
|
|
||||||
int[] numbers = result.Numbers;
|
|
||||||
|
|
||||||
for (int i = 0; i < 3; i++)
|
Interlocked.Add(ref _totalBet, amount);
|
||||||
|
Interlocked.Add(ref _totalPaidOut, result.Won);
|
||||||
|
|
||||||
|
long ownedAmount;
|
||||||
|
using (var uow = _db.GetDbContext())
|
||||||
{
|
{
|
||||||
using (var randomImage = Image.Load(_images.SlotEmojis[numbers[i]]))
|
ownedAmount = uow.Set<DiscordUser>()
|
||||||
{
|
.FirstOrDefault(x => x.UserId == ctx.User.Id)
|
||||||
bgImage.Mutate(x => x.DrawImage(randomImage, new Point(95 + 142 * i, 330), new GraphicsOptions()));
|
?.CurrencyAmount ?? 0;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var won = amount * result.Multiplier;
|
using (var bgImage = Image.Load<Rgba32>(_images.SlotBackground, out var format))
|
||||||
var printWon = won;
|
|
||||||
var n = 0;
|
|
||||||
do
|
|
||||||
{
|
{
|
||||||
var digit = (int)(printWon % 10);
|
var numbers = new int[3];
|
||||||
using (var img = Image.Load(_images.SlotNumbers[digit]))
|
result.Rolls.CopyTo(numbers, 0);
|
||||||
{
|
|
||||||
bgImage.Mutate(x => x.DrawImage(img, new Point(230 - n * 16, 462), new GraphicsOptions()));
|
|
||||||
}
|
|
||||||
n++;
|
|
||||||
} while ((printWon /= 10) != 0);
|
|
||||||
|
|
||||||
var printAmount = amount;
|
bgImage.Mutate(x => x.DrawText(new TextGraphicsOptions
|
||||||
n = 0;
|
|
||||||
do
|
|
||||||
{
|
{
|
||||||
var digit = (int)(printAmount % 10);
|
TextOptions = new TextOptions()
|
||||||
using (var img = Image.Load(_images.SlotNumbers[digit]))
|
|
||||||
{
|
{
|
||||||
bgImage.Mutate(x => x.DrawImage(img, new Point(395 - n * 16, 462), new GraphicsOptions()));
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
WrapTextWidth = 140,
|
||||||
|
}
|
||||||
|
}, result.Won.ToString(), _fonts.DottyFont.CreateFont(65), SixLabors.ImageSharp.Color.Red,
|
||||||
|
new PointF(227, 92)));
|
||||||
|
|
||||||
|
bgImage.Mutate(x => x.DrawText(new TextGraphicsOptions
|
||||||
|
{
|
||||||
|
TextOptions = new TextOptions()
|
||||||
|
{
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
WrapTextWidth = 135,
|
||||||
|
}
|
||||||
|
}, amount.ToString(), _fonts.DottyFont.CreateFont(50), SixLabors.ImageSharp.Color.Red,
|
||||||
|
new PointF(129, 472)));
|
||||||
|
|
||||||
|
bgImage.Mutate(x => x.DrawText(new TextGraphicsOptions
|
||||||
|
{
|
||||||
|
TextOptions = new TextOptions()
|
||||||
|
{
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
WrapTextWidth = 135,
|
||||||
|
}
|
||||||
|
}, ownedAmount.ToString(), _fonts.DottyFont.CreateFont(50), SixLabors.ImageSharp.Color.Red,
|
||||||
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
n++;
|
|
||||||
} while ((printAmount /= 10) != 0);
|
|
||||||
|
|
||||||
var msg = GetText(strs.better_luck);
|
var msg = GetText(strs.better_luck);
|
||||||
if (result.Multiplier != 0)
|
if (result.Multiplier > 0)
|
||||||
{
|
{
|
||||||
await _cs.AddAsync(ctx.User, $"Slot Machine x{result.Multiplier}", amount * result.Multiplier, false, gamble: true).ConfigureAwait(false);
|
if (result.Multiplier == 1f)
|
||||||
Interlocked.Add(ref _totalPaidOut, amount * result.Multiplier);
|
|
||||||
if (result.Multiplier == 1)
|
|
||||||
msg = GetText(strs.slot_single(CurrencySign, 1));
|
msg = GetText(strs.slot_single(CurrencySign, 1));
|
||||||
else if (result.Multiplier == 4)
|
else if (result.Multiplier == 4f)
|
||||||
msg = GetText(strs.slot_two(CurrencySign, 4));
|
msg = GetText(strs.slot_two(CurrencySign, 4));
|
||||||
else if (result.Multiplier == 10)
|
else if (result.Multiplier == 10f)
|
||||||
msg = GetText(strs.slot_three(10));
|
msg = GetText(strs.slot_three(10));
|
||||||
else if (result.Multiplier == 30)
|
else if (result.Multiplier == 30f)
|
||||||
msg = GetText(strs.slot_jackpot(30));
|
msg = GetText(strs.slot_jackpot(30));
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var imgStream = bgImage.ToStream())
|
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);
|
await ctx.Channel.SendFileAsync(imgStream,
|
||||||
|
filename: "result.png",
|
||||||
|
text: Format.Bold(ctx.User.ToString()) + " " + msg).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -317,10 +317,14 @@ namespace NadekoBot.Modules.Gambling
|
|||||||
.WithOkColor();
|
.WithOkColor();
|
||||||
|
|
||||||
waifuItems
|
waifuItems
|
||||||
.OrderBy(x => x.Price)
|
.OrderBy(x => x.Negative)
|
||||||
|
.ThenBy(x => x.Price)
|
||||||
.Skip(9 * cur)
|
.Skip(9 * cur)
|
||||||
.Take(9)
|
.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;
|
return embed;
|
||||||
}, waifuItems.Count, 9);
|
}, waifuItems.Count, 9);
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using NadekoBot.Common;
|
||||||
using NadekoBot.Common.Attributes;
|
using NadekoBot.Common.Attributes;
|
||||||
using NadekoBot.Services;
|
using NadekoBot.Services;
|
||||||
using NadekoBot.Db;
|
using NadekoBot.Db;
|
||||||
@@ -23,6 +24,7 @@ namespace NadekoBot.Modules.Games
|
|||||||
_db = db;
|
_db = db;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[NoPublicBot]
|
||||||
[NadekoCommand, Aliases]
|
[NadekoCommand, Aliases]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.ManageMessages)]
|
[UserPerm(GuildPerm.ManageMessages)]
|
||||||
|
@@ -0,0 +1,72 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using NadekoBot.Common;
|
||||||
|
using NadekoBot.Common.Yml;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Games.Hangman
|
||||||
|
{
|
||||||
|
public sealed class DefaultHangmanSource : IHangmanSource
|
||||||
|
{
|
||||||
|
private IReadOnlyDictionary<string, HangmanTerm[]> _terms = new Dictionary<string, HangmanTerm[]>();
|
||||||
|
private readonly Random _rng;
|
||||||
|
|
||||||
|
public DefaultHangmanSource()
|
||||||
|
{
|
||||||
|
_rng = new NadekoRandom();
|
||||||
|
Reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reload()
|
||||||
|
{
|
||||||
|
if (!Directory.Exists("data/hangman"))
|
||||||
|
{
|
||||||
|
Log.Error("Hangman game won't work. Folder 'data/hangman' is missing.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var qs = new Dictionary<string, HangmanTerm[]>();
|
||||||
|
foreach (var file in Directory.EnumerateFiles("data/hangman/", "*.yml"))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var data = Yaml.Deserializer.Deserialize<HangmanTerm[]>(File.ReadAllText(file));
|
||||||
|
qs[Path.GetFileNameWithoutExtension(file).ToLowerInvariant()] = data;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Loading {HangmanFile} failed.", file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_terms = qs;
|
||||||
|
|
||||||
|
Log.Information("Loaded {HangmanCategoryCount} hangman categories.", qs.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyCollection<string> GetCategories()
|
||||||
|
=> _terms.Keys.ToList();
|
||||||
|
|
||||||
|
public bool GetTerm(string? category, [NotNullWhen(true)] out HangmanTerm? term)
|
||||||
|
{
|
||||||
|
if (category is null)
|
||||||
|
{
|
||||||
|
var cats = GetCategories();
|
||||||
|
category = cats.ElementAt(_rng.Next(0, cats.Count));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_terms.TryGetValue(category, out var terms))
|
||||||
|
{
|
||||||
|
term = terms[_rng.Next(0, terms.Length)];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
term = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,19 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Games.Common.Hangman.Exceptions
|
|
||||||
{
|
|
||||||
public class TermNotFoundException : Exception
|
|
||||||
{
|
|
||||||
public TermNotFoundException() : base("Term of that type couldn't be found")
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public TermNotFoundException(string message) : base(message)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public TermNotFoundException(string message, Exception innerException) : base(message, innerException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,170 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using NadekoBot.Extensions;
|
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Games.Common.Hangman
|
|
||||||
{
|
|
||||||
public sealed class Hangman : IDisposable
|
|
||||||
{
|
|
||||||
public string TermType { get; }
|
|
||||||
public TermPool TermPool { get; }
|
|
||||||
public HangmanObject Term { get; }
|
|
||||||
|
|
||||||
public string ScrambledWord => "`" + String.Concat(Term.Word.Select(c =>
|
|
||||||
{
|
|
||||||
if (c == ' ')
|
|
||||||
return " \u2000";
|
|
||||||
if (!(char.IsLetter(c) || char.IsDigit(c)))
|
|
||||||
return $" {c}";
|
|
||||||
|
|
||||||
c = char.ToLowerInvariant(c);
|
|
||||||
return _previousGuesses.Contains(c) ? $" {c}" : " ◯";
|
|
||||||
})) + "`";
|
|
||||||
|
|
||||||
private Phase _currentPhase = Phase.Active;
|
|
||||||
public Phase CurrentPhase
|
|
||||||
{
|
|
||||||
get => _currentPhase;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value == Phase.Ended)
|
|
||||||
_endingCompletionSource.TrySetResult(true);
|
|
||||||
|
|
||||||
_currentPhase = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
|
|
||||||
|
|
||||||
private readonly HashSet<ulong> _recentUsers = new HashSet<ulong>();
|
|
||||||
|
|
||||||
public uint Errors { get; private set; } = 0;
|
|
||||||
public uint MaxErrors { get; } = 6;
|
|
||||||
|
|
||||||
public event Func<Hangman, string, ulong, Task> OnGameEnded = delegate { return Task.CompletedTask; };
|
|
||||||
public event Func<Hangman, string, char, Task> OnLetterAlreadyUsed = delegate { return Task.CompletedTask; };
|
|
||||||
public event Func<Hangman, string, char, Task> OnGuessFailed = delegate { return Task.CompletedTask; };
|
|
||||||
public event Func<Hangman, string, char, Task> OnGuessSucceeded = delegate { return Task.CompletedTask; };
|
|
||||||
|
|
||||||
private readonly HashSet<char> _previousGuesses = new HashSet<char>();
|
|
||||||
public ImmutableArray<char> PreviousGuesses => _previousGuesses.ToImmutableArray();
|
|
||||||
|
|
||||||
private readonly TaskCompletionSource<bool> _endingCompletionSource = new TaskCompletionSource<bool>();
|
|
||||||
|
|
||||||
public Task EndedTask => _endingCompletionSource.Task;
|
|
||||||
|
|
||||||
public Hangman(string type, TermPool tp = null)
|
|
||||||
{
|
|
||||||
this.TermType = type.Trim().ToLowerInvariant().ToTitleCase();
|
|
||||||
this.TermPool = tp ?? new TermPool();
|
|
||||||
this.Term = this.TermPool.GetTerm(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddError()
|
|
||||||
{
|
|
||||||
if (++Errors > MaxErrors)
|
|
||||||
{
|
|
||||||
var _ = OnGameEnded(this, null, 0);
|
|
||||||
CurrentPhase = Phase.Ended;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetHangman() => $@". ┌─────┐
|
|
||||||
.┃...............┋
|
|
||||||
.┃...............┋
|
|
||||||
.┃{(Errors > 0 ? ".............😲" : "")}
|
|
||||||
.┃{(Errors > 1 ? "............./" : "")} {(Errors > 2 ? "|" : "")} {(Errors > 3 ? "\\" : "")}
|
|
||||||
.┃{(Errors > 4 ? "............../" : "")} {(Errors > 5 ? "\\" : "")}
|
|
||||||
/-\";
|
|
||||||
|
|
||||||
public async Task Input(ulong userId, string userName, string input)
|
|
||||||
{
|
|
||||||
if (CurrentPhase == Phase.Ended)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(input))
|
|
||||||
return;
|
|
||||||
|
|
||||||
input = input.Trim().ToLowerInvariant();
|
|
||||||
|
|
||||||
await _locker.WaitAsync().ConfigureAwait(false);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (CurrentPhase == Phase.Ended)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (input.Length > 1) // tried to guess the whole word
|
|
||||||
{
|
|
||||||
if (input != Term.Word) // failed
|
|
||||||
return;
|
|
||||||
|
|
||||||
var _ = OnGameEnded?.Invoke(this, userName, userId);
|
|
||||||
CurrentPhase = Phase.Ended;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var ch = input[0];
|
|
||||||
|
|
||||||
if (!char.IsLetterOrDigit(ch))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!_recentUsers.Add(userId)) // don't let a single user spam guesses
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!_previousGuesses.Add(ch)) // that letter was already guessed
|
|
||||||
{
|
|
||||||
var _ = OnLetterAlreadyUsed?.Invoke(this, userName, ch);
|
|
||||||
}
|
|
||||||
else if (!Term.Word.Contains(ch)) // guessed letter doesn't exist
|
|
||||||
{
|
|
||||||
var _ = OnGuessFailed?.Invoke(this, userName, ch);
|
|
||||||
AddError();
|
|
||||||
}
|
|
||||||
else if (Term.Word.All(x => _previousGuesses.IsSupersetOf(Term.Word.ToLowerInvariant()
|
|
||||||
.Where(char.IsLetterOrDigit))))
|
|
||||||
{
|
|
||||||
var _ = OnGameEnded.Invoke(this, userName, userId); // if all letters are guessed
|
|
||||||
CurrentPhase = Phase.Ended;
|
|
||||||
}
|
|
||||||
else // guessed but not last letter
|
|
||||||
{
|
|
||||||
var _ = OnGuessSucceeded?.Invoke(this, userName, ch);
|
|
||||||
_recentUsers.Remove(userId); // he can guess again right away
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var clearSpam = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await Task.Delay(3000).ConfigureAwait(false); // remove the user from the spamlist after 5 seconds
|
|
||||||
_recentUsers.Remove(userId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
finally { _locker.Release(); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Stop()
|
|
||||||
{
|
|
||||||
await _locker.WaitAsync().ConfigureAwait(false);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
CurrentPhase = Phase.Ended;
|
|
||||||
}
|
|
||||||
finally { _locker.Release(); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
OnGameEnded = null;
|
|
||||||
OnGuessFailed = null;
|
|
||||||
OnGuessSucceeded = null;
|
|
||||||
OnLetterAlreadyUsed = null;
|
|
||||||
_previousGuesses.Clear();
|
|
||||||
_recentUsers.Clear();
|
|
||||||
// _locker.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
121
src/NadekoBot/Modules/Games/Common/Hangman/HangmanGame.cs
Normal file
121
src/NadekoBot/Modules/Games/Common/Hangman/HangmanGame.cs
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AngleSharp.Text;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Games.Hangman
|
||||||
|
{
|
||||||
|
public sealed class HangmanGame
|
||||||
|
{
|
||||||
|
public enum Phase { Running, Ended }
|
||||||
|
public enum GuessResult { NoAction, AlreadyTried, Incorrect, Guess, Win }
|
||||||
|
|
||||||
|
public record State(
|
||||||
|
int Errors,
|
||||||
|
Phase Phase,
|
||||||
|
string Word,
|
||||||
|
GuessResult GuessResult,
|
||||||
|
List<char> missedLetters,
|
||||||
|
string ImageUrl)
|
||||||
|
{
|
||||||
|
public bool Failed => Errors > 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Phase CurrentPhase { get; set; }
|
||||||
|
|
||||||
|
private readonly HashSet<char> _incorrect = new();
|
||||||
|
private readonly HashSet<char> _correct = new();
|
||||||
|
private readonly HashSet<char> _remaining = new();
|
||||||
|
|
||||||
|
private readonly string _word;
|
||||||
|
private readonly string _imageUrl;
|
||||||
|
|
||||||
|
public HangmanGame(HangmanTerm term)
|
||||||
|
{
|
||||||
|
_word = term.Word;
|
||||||
|
_imageUrl = term.ImageUrl;
|
||||||
|
|
||||||
|
_remaining = _word
|
||||||
|
.ToLowerInvariant()
|
||||||
|
.Where(x => x.IsLetter())
|
||||||
|
.Select(char.ToLowerInvariant)
|
||||||
|
.ToHashSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public State GetState(GuessResult guessResult = GuessResult.NoAction)
|
||||||
|
=> new State(_incorrect.Count,
|
||||||
|
CurrentPhase,
|
||||||
|
CurrentPhase == Phase.Ended
|
||||||
|
? _word
|
||||||
|
: GetScrambledWord(),
|
||||||
|
guessResult,
|
||||||
|
_incorrect.ToList(),
|
||||||
|
CurrentPhase == Phase.Ended
|
||||||
|
? _imageUrl
|
||||||
|
: string.Empty);
|
||||||
|
|
||||||
|
private string GetScrambledWord()
|
||||||
|
{
|
||||||
|
Span<char> output = stackalloc char[_word.Length * 2];
|
||||||
|
for (var i = 0; i < _word.Length; i++)
|
||||||
|
{
|
||||||
|
var ch = _word[i];
|
||||||
|
if (ch == ' ')
|
||||||
|
output[i*2] = ' ';
|
||||||
|
if (!ch.IsLetter() || !_remaining.Contains(char.ToLowerInvariant(ch)))
|
||||||
|
output[i*2] = ch;
|
||||||
|
else
|
||||||
|
output[i*2] = '_';
|
||||||
|
|
||||||
|
output[i * 2 + 1] = ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
return new(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
public State Guess(string guess)
|
||||||
|
{
|
||||||
|
if (CurrentPhase != Phase.Running)
|
||||||
|
return GetState(GuessResult.NoAction);
|
||||||
|
|
||||||
|
guess = guess.Trim();
|
||||||
|
if (guess.Length > 1)
|
||||||
|
{
|
||||||
|
if (guess.Equals(_word, StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
CurrentPhase = Phase.Ended;
|
||||||
|
return GetState(GuessResult.Win);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetState(GuessResult.NoAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
var charGuess = guess[0];
|
||||||
|
if (!char.IsLetter(charGuess))
|
||||||
|
return GetState(GuessResult.NoAction);
|
||||||
|
|
||||||
|
if (_incorrect.Contains(charGuess) || _correct.Contains(charGuess))
|
||||||
|
return GetState(GuessResult.AlreadyTried);
|
||||||
|
|
||||||
|
if (_remaining.Remove(charGuess))
|
||||||
|
{
|
||||||
|
if (_remaining.Count == 0)
|
||||||
|
{
|
||||||
|
CurrentPhase = Phase.Ended;
|
||||||
|
return GetState(GuessResult.Win);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetState(GuessResult.Guess);
|
||||||
|
}
|
||||||
|
|
||||||
|
_incorrect.Add(charGuess);
|
||||||
|
if (_incorrect.Count > 5)
|
||||||
|
{
|
||||||
|
CurrentPhase = Phase.Ended;
|
||||||
|
return GetState(GuessResult.Incorrect);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetState(GuessResult.Incorrect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,17 +0,0 @@
|
|||||||
using NadekoBot.Extensions;
|
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Games.Common.Hangman
|
|
||||||
{
|
|
||||||
public class HangmanObject
|
|
||||||
{
|
|
||||||
public string Word { get; set; }
|
|
||||||
public string ImageUrl { get; set; }
|
|
||||||
|
|
||||||
public string GetWord()
|
|
||||||
{
|
|
||||||
var term = Word.ToTitleCase();
|
|
||||||
|
|
||||||
return $"[{term}](https://en.wikipedia.org/wiki/{term.Replace(' ', '_')})";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
147
src/NadekoBot/Modules/Games/Common/Hangman/HangmanService.cs
Normal file
147
src/NadekoBot/Modules/Games/Common/Hangman/HangmanService.cs
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Discord;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
|
using NadekoBot.Extensions;
|
||||||
|
using NadekoBot.Modules.Games.Services;
|
||||||
|
using NadekoBot.Services;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Games.Hangman
|
||||||
|
{
|
||||||
|
public sealed class HangmanService : IHangmanService, ILateExecutor
|
||||||
|
{
|
||||||
|
private readonly ConcurrentDictionary<ulong, HangmanGame> _hangmanGames = new();
|
||||||
|
private readonly IHangmanSource _source;
|
||||||
|
private readonly IEmbedBuilderService _eb;
|
||||||
|
private readonly GamesConfigService _gcs;
|
||||||
|
private readonly ICurrencyService _cs;
|
||||||
|
private readonly IMemoryCache _cdCache;
|
||||||
|
private readonly object _locker = new();
|
||||||
|
|
||||||
|
public HangmanService(IHangmanSource source, IEmbedBuilderService eb, GamesConfigService gcs,
|
||||||
|
ICurrencyService cs, IMemoryCache cdCache)
|
||||||
|
{
|
||||||
|
_source = source;
|
||||||
|
_eb = eb;
|
||||||
|
_gcs = gcs;
|
||||||
|
_cs = cs;
|
||||||
|
_cdCache = cdCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool StartHangman(
|
||||||
|
ulong channelId,
|
||||||
|
string? category,
|
||||||
|
[NotNullWhen(true)] out HangmanGame.State? state)
|
||||||
|
{
|
||||||
|
state = null;
|
||||||
|
if (!_source.GetTerm(category, out var term))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
|
||||||
|
var game = new HangmanGame(term);
|
||||||
|
lock (_locker)
|
||||||
|
{
|
||||||
|
var hc = _hangmanGames.GetOrAdd(channelId, game);
|
||||||
|
if (hc == game)
|
||||||
|
{
|
||||||
|
state = hc.GetState();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask<bool> StopHangman(ulong channelId)
|
||||||
|
{
|
||||||
|
lock (_locker)
|
||||||
|
{
|
||||||
|
if (_hangmanGames.TryRemove(channelId, out var game))
|
||||||
|
{
|
||||||
|
return new(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyCollection<string> GetHangmanTypes()
|
||||||
|
=> _source.GetCategories();
|
||||||
|
|
||||||
|
public async Task LateExecute(IGuild guild, IUserMessage msg)
|
||||||
|
{
|
||||||
|
if (_hangmanGames.ContainsKey(msg.Channel.Id))
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(msg.Content))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_cdCache.TryGetValue(msg.Author.Id, out _))
|
||||||
|
return;
|
||||||
|
|
||||||
|
HangmanGame.State state;
|
||||||
|
long rew = 0;
|
||||||
|
lock (_locker)
|
||||||
|
{
|
||||||
|
if (!_hangmanGames.TryGetValue(msg.Channel.Id, out var game))
|
||||||
|
return;
|
||||||
|
|
||||||
|
state = game.Guess(msg.Content.ToLowerInvariant());
|
||||||
|
|
||||||
|
if (state.GuessResult == HangmanGame.GuessResult.NoAction)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (state.GuessResult == HangmanGame.GuessResult.Incorrect
|
||||||
|
|| state.GuessResult == HangmanGame.GuessResult.AlreadyTried)
|
||||||
|
{
|
||||||
|
_cdCache.Set(msg.Author.Id, string.Empty, new MemoryCacheEntryOptions()
|
||||||
|
{
|
||||||
|
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(3)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.Phase == HangmanGame.Phase.Ended)
|
||||||
|
if (_hangmanGames.TryRemove(msg.Channel.Id, out _))
|
||||||
|
rew = _gcs.Data.Hangman.CurrencyReward;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rew > 0)
|
||||||
|
await _cs.AddAsync(msg.Author, "hangman win", rew, gamble: true);
|
||||||
|
|
||||||
|
await SendState((ITextChannel)msg.Channel, msg.Author, msg.Content, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<IUserMessage> SendState(ITextChannel channel, IUser user, string content, HangmanGame.State state)
|
||||||
|
{
|
||||||
|
var embed = Games.HangmanCommands.GetEmbed(_eb, state);
|
||||||
|
if (state.GuessResult == HangmanGame.GuessResult.Guess)
|
||||||
|
embed.WithDescription($"{user} guessed the letter {content}!")
|
||||||
|
.WithOkColor();
|
||||||
|
else if (state.GuessResult == HangmanGame.GuessResult.Incorrect && state.Failed)
|
||||||
|
embed.WithDescription($"{user} Letter {content} doesn't exist! Game over!")
|
||||||
|
.WithErrorColor();
|
||||||
|
else if (state.GuessResult == HangmanGame.GuessResult.Incorrect)
|
||||||
|
embed.WithDescription($"{user} Letter {content} doesn't exist!")
|
||||||
|
.WithErrorColor();
|
||||||
|
else if (state.GuessResult == HangmanGame.GuessResult.AlreadyTried)
|
||||||
|
embed.WithDescription($"{user} Letter {content} has already been used.")
|
||||||
|
.WithPendingColor();
|
||||||
|
else if (state.GuessResult == HangmanGame.GuessResult.Win)
|
||||||
|
embed.WithDescription($"{user} won!")
|
||||||
|
.WithOkColor();
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(state.ImageUrl)
|
||||||
|
&& Uri.IsWellFormedUriString(state.ImageUrl, UriKind.Absolute))
|
||||||
|
{
|
||||||
|
embed.WithImageUrl(state.ImageUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
return channel.EmbedAsync(embed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,8 @@
|
|||||||
|
namespace NadekoBot.Modules.Games.Hangman
|
||||||
|
{
|
||||||
|
public sealed class HangmanTerm
|
||||||
|
{
|
||||||
|
public string Word { get; set; }
|
||||||
|
public string ImageUrl { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,14 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Games.Hangman
|
||||||
|
{
|
||||||
|
public interface IHangmanService
|
||||||
|
{
|
||||||
|
bool StartHangman(ulong channelId, string? category, [NotNullWhen(true)] out HangmanGame.State? hangmanController);
|
||||||
|
ValueTask<bool> StopHangman(ulong channelId);
|
||||||
|
IReadOnlyCollection<string> GetHangmanTypes();
|
||||||
|
}
|
||||||
|
}
|
14
src/NadekoBot/Modules/Games/Common/Hangman/IHangmanSource.cs
Normal file
14
src/NadekoBot/Modules/Games/Common/Hangman/IHangmanSource.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using NadekoBot.Services;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Games.Hangman
|
||||||
|
{
|
||||||
|
public interface IHangmanSource : INService
|
||||||
|
{
|
||||||
|
public IReadOnlyCollection<string> GetCategories();
|
||||||
|
public void Reload();
|
||||||
|
public bool GetTerm(string? category, [NotNullWhen(true)] out HangmanTerm? term);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,8 +0,0 @@
|
|||||||
namespace NadekoBot.Modules.Games.Common.Hangman
|
|
||||||
{
|
|
||||||
public enum Phase
|
|
||||||
{
|
|
||||||
Active,
|
|
||||||
Ended,
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,50 +0,0 @@
|
|||||||
using NadekoBot.Common;
|
|
||||||
using NadekoBot.Modules.Games.Common.Hangman.Exceptions;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Games.Common.Hangman
|
|
||||||
{
|
|
||||||
public class TermPool
|
|
||||||
{
|
|
||||||
const string termsPath = "data/hangman.json";
|
|
||||||
|
|
||||||
public IReadOnlyDictionary<string, HangmanObject[]> Data { get; } = new Dictionary<string, HangmanObject[]>();
|
|
||||||
public TermPool()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Data = JsonConvert.DeserializeObject<Dictionary<string, HangmanObject[]>>(File.ReadAllText(termsPath));
|
|
||||||
Data = Data.ToDictionary(
|
|
||||||
x => x.Key.ToLowerInvariant(),
|
|
||||||
x => x.Value);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Warning(ex, "Error loading Hangman Term pool");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public HangmanObject GetTerm(string type)
|
|
||||||
{
|
|
||||||
type = type?.Trim().ToLowerInvariant();
|
|
||||||
var rng = new NadekoRandom();
|
|
||||||
|
|
||||||
if (type == "random")
|
|
||||||
{
|
|
||||||
type = Data.Keys.ToArray()[rng.Next(0, Data.Keys.Count())];
|
|
||||||
}
|
|
||||||
if (!Data.TryGetValue(type, out var termTypes) || termTypes.Length == 0)
|
|
||||||
throw new TermNotFoundException();
|
|
||||||
|
|
||||||
var obj = termTypes[rng.Next(0, termTypes.Length)];
|
|
||||||
|
|
||||||
obj.Word = obj.Word.Trim().ToLowerInvariant();
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,14 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Games.Common.Hangman
|
|
||||||
{
|
|
||||||
[Flags]
|
|
||||||
public enum TermTypes
|
|
||||||
{
|
|
||||||
Countries = 0,
|
|
||||||
Movies = 1,
|
|
||||||
Animals = 2,
|
|
||||||
Things = 4,
|
|
||||||
Random = 8,
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,13 +1,10 @@
|
|||||||
|
#nullable enable
|
||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
using NadekoBot.Extensions;
|
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Discord;
|
using Discord;
|
||||||
using Discord.WebSocket;
|
|
||||||
using NadekoBot.Common.Attributes;
|
using NadekoBot.Common.Attributes;
|
||||||
using NadekoBot.Modules.Games.Common.Hangman;
|
using NadekoBot.Extensions;
|
||||||
using NadekoBot.Modules.Games.Services;
|
using NadekoBot.Modules.Games.Hangman;
|
||||||
using NadekoBot.Modules.Games.Common.Hangman.Exceptions;
|
|
||||||
using NadekoBot.Services;
|
using NadekoBot.Services;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Games
|
namespace NadekoBot.Modules.Games
|
||||||
@@ -15,136 +12,74 @@ namespace NadekoBot.Modules.Games
|
|||||||
public partial class Games
|
public partial class Games
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public class HangmanCommands : NadekoSubmodule<GamesService>
|
public class HangmanCommands : NadekoSubmodule<IHangmanService>
|
||||||
{
|
{
|
||||||
private readonly DiscordSocketClient _client;
|
|
||||||
private readonly ICurrencyService _cs;
|
|
||||||
private readonly GamesConfigService _gcs;
|
|
||||||
|
|
||||||
public HangmanCommands(DiscordSocketClient client, ICurrencyService cs, GamesConfigService gcs)
|
|
||||||
{
|
|
||||||
_client = client;
|
|
||||||
_cs = cs;
|
|
||||||
_gcs = gcs;
|
|
||||||
}
|
|
||||||
|
|
||||||
[NadekoCommand, Aliases]
|
[NadekoCommand, Aliases]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
public async Task Hangmanlist()
|
public async Task Hangmanlist()
|
||||||
{
|
{
|
||||||
await SendConfirmAsync(Format.Code(GetText(strs.hangman_types(Prefix)) + "\n" + string.Join("\n", _service.TermPool.Data.Keys)));
|
await SendConfirmAsync(
|
||||||
|
GetText(strs.hangman_types(Prefix)),
|
||||||
|
_service.GetHangmanTypes().JoinWith('\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Draw(HangmanGame.State state)
|
||||||
|
{
|
||||||
|
return $@". ┌─────┐
|
||||||
|
.┃...............┋
|
||||||
|
.┃...............┋
|
||||||
|
.┃{(state.Errors > 0 ? ".............😲" : "")}
|
||||||
|
.┃{(state.Errors > 1 ? "............./" : "")} {(state.Errors > 2 ? "|" : "")} {(state.Errors > 3 ? "\\" : "")}
|
||||||
|
.┃{(state.Errors > 4 ? "............../" : "")} {(state.Errors > 5 ? "\\" : "")}
|
||||||
|
/-\";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEmbedBuilder GetEmbed(IEmbedBuilderService eb, HangmanGame.State state)
|
||||||
|
{
|
||||||
|
if (state.Phase == HangmanGame.Phase.Running)
|
||||||
|
return eb.Create()
|
||||||
|
.WithOkColor()
|
||||||
|
.AddField("Hangman", Draw(state))
|
||||||
|
.AddField("Guess", Format.Code(state.Word))
|
||||||
|
.WithFooter(state.missedLetters.JoinWith(' '));
|
||||||
|
|
||||||
|
if (state.Phase == HangmanGame.Phase.Ended && state.Failed)
|
||||||
|
return eb.Create()
|
||||||
|
.WithErrorColor()
|
||||||
|
.AddField("Hangman", Draw(state))
|
||||||
|
.AddField("Guess", Format.Code(state.Word))
|
||||||
|
.WithFooter(state.missedLetters.JoinWith(' '));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return eb.Create()
|
||||||
|
.WithOkColor()
|
||||||
|
.AddField("Hangman", Draw(state))
|
||||||
|
.AddField("Guess", Format.Code(state.Word))
|
||||||
|
.WithFooter(state.missedLetters.JoinWith(' '));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[NadekoCommand, Aliases]
|
[NadekoCommand, Aliases]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
public async Task Hangman([Leftover]string type = "random")
|
public async Task Hangman([Leftover] string? type = null)
|
||||||
{
|
{
|
||||||
Hangman hm;
|
if (!_service.StartHangman(ctx.Channel.Id, type, out var hangman))
|
||||||
try
|
|
||||||
{
|
|
||||||
hm = new Hangman(type, _service.TermPool);
|
|
||||||
}
|
|
||||||
catch (TermNotFoundException)
|
|
||||||
{
|
{
|
||||||
|
await ReplyErrorLocalizedAsync(strs.hangman_running);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_service.HangmanGames.TryAdd(ctx.Channel.Id, hm))
|
var eb = GetEmbed(_eb, hangman);
|
||||||
{
|
eb.WithDescription(GetText(strs.hangman_game_started));
|
||||||
hm.Dispose();
|
await ctx.Channel.EmbedAsync(eb);
|
||||||
await ReplyErrorLocalizedAsync(strs.hangman_running).ConfigureAwait(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
hm.OnGameEnded += Hm_OnGameEnded;
|
|
||||||
hm.OnGuessFailed += Hm_OnGuessFailed;
|
|
||||||
hm.OnGuessSucceeded += Hm_OnGuessSucceeded;
|
|
||||||
hm.OnLetterAlreadyUsed += Hm_OnLetterAlreadyUsed;
|
|
||||||
_client.MessageReceived += _client_MessageReceived;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await SendConfirmAsync(GetText(strs.hangman_game_started) + $" ({hm.TermType})",
|
|
||||||
hm.ScrambledWord + "\n" + hm.GetHangman())
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
|
|
||||||
await hm.EndedTask.ConfigureAwait(false);
|
|
||||||
|
|
||||||
_client.MessageReceived -= _client_MessageReceived;
|
|
||||||
_service.HangmanGames.TryRemove(ctx.Channel.Id, out _);
|
|
||||||
hm.Dispose();
|
|
||||||
|
|
||||||
Task _client_MessageReceived(SocketMessage msg)
|
|
||||||
{
|
|
||||||
var _ = Task.Run(() =>
|
|
||||||
{
|
|
||||||
if (ctx.Channel.Id == msg.Channel.Id && !msg.Author.IsBot)
|
|
||||||
return hm.Input(msg.Author.Id, msg.Author.ToString(), msg.Content);
|
|
||||||
else
|
|
||||||
return Task.CompletedTask;
|
|
||||||
});
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Task Hm_OnGameEnded(Hangman game, string winner, ulong userId)
|
|
||||||
{
|
|
||||||
if (winner is null)
|
|
||||||
{
|
|
||||||
var loseEmbed = _eb.Create().WithTitle($"Hangman Game ({game.TermType}) - Ended")
|
|
||||||
.WithDescription(Format.Bold("You lose."))
|
|
||||||
.AddField("It was", game.Term.GetWord())
|
|
||||||
.WithFooter(string.Join(" ", game.PreviousGuesses))
|
|
||||||
.WithErrorColor();
|
|
||||||
|
|
||||||
if (Uri.IsWellFormedUriString(game.Term.ImageUrl, UriKind.Absolute))
|
|
||||||
loseEmbed.WithImageUrl(game.Term.ImageUrl);
|
|
||||||
|
|
||||||
return ctx.Channel.EmbedAsync(loseEmbed);
|
|
||||||
}
|
|
||||||
|
|
||||||
var reward = _gcs.Data.Hangman.CurrencyReward;
|
|
||||||
if (reward > 0)
|
|
||||||
_cs.AddAsync(userId, "hangman win", reward, true);
|
|
||||||
|
|
||||||
var winEmbed = _eb.Create().WithTitle($"Hangman Game ({game.TermType}) - Ended")
|
|
||||||
.WithDescription(Format.Bold($"{winner} Won."))
|
|
||||||
.AddField("It was", game.Term.GetWord())
|
|
||||||
.WithFooter(string.Join(" ", game.PreviousGuesses))
|
|
||||||
.WithOkColor();
|
|
||||||
|
|
||||||
if (Uri.IsWellFormedUriString(game.Term.ImageUrl, UriKind.Absolute))
|
|
||||||
winEmbed.WithImageUrl(game.Term.ImageUrl);
|
|
||||||
|
|
||||||
return ctx.Channel.EmbedAsync(winEmbed);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task Hm_OnLetterAlreadyUsed(Hangman game, string user, char guess)
|
|
||||||
{
|
|
||||||
return SendErrorAsync($"Hangman Game ({game.TermType})", $"{user} Letter `{guess}` has already been used. You can guess again in 3 seconds.\n" + game.ScrambledWord + "\n" + game.GetHangman(),
|
|
||||||
footer: string.Join(" ", game.PreviousGuesses));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task Hm_OnGuessSucceeded(Hangman game, string user, char guess)
|
|
||||||
{
|
|
||||||
return SendConfirmAsync($"Hangman Game ({game.TermType})", $"{user} guessed a letter `{guess}`!\n" + game.ScrambledWord + "\n" + game.GetHangman(),
|
|
||||||
footer: string.Join(" ", game.PreviousGuesses));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task Hm_OnGuessFailed(Hangman game, string user, char guess)
|
|
||||||
{
|
|
||||||
return SendErrorAsync($"Hangman Game ({game.TermType})", $"{user} Letter `{guess}` does not exist. You can guess again in 3 seconds.\n" + game.ScrambledWord + "\n" + game.GetHangman(),
|
|
||||||
footer: string.Join(" ", game.PreviousGuesses));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[NadekoCommand, Aliases]
|
[NadekoCommand, Aliases]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
public async Task HangmanStop()
|
public async Task HangmanStop()
|
||||||
{
|
{
|
||||||
if (_service.HangmanGames.TryRemove(ctx.Channel.Id, out var removed))
|
if (await _service.StopHangman(ctx.Channel.Id))
|
||||||
{
|
{
|
||||||
await removed.Stop().ConfigureAwait(false);
|
|
||||||
await ReplyConfirmLocalizedAsync(strs.hangman_stopped).ConfigureAwait(false);
|
await ReplyConfirmLocalizedAsync(strs.hangman_stopped).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,6 @@ using NadekoBot.Services;
|
|||||||
using NadekoBot.Extensions;
|
using NadekoBot.Extensions;
|
||||||
using NadekoBot.Modules.Games.Common;
|
using NadekoBot.Modules.Games.Common;
|
||||||
using NadekoBot.Modules.Games.Common.Acrophobia;
|
using NadekoBot.Modules.Games.Common.Acrophobia;
|
||||||
using NadekoBot.Modules.Games.Common.Hangman;
|
|
||||||
using NadekoBot.Modules.Games.Common.Nunchi;
|
using NadekoBot.Modules.Games.Common.Nunchi;
|
||||||
using NadekoBot.Modules.Games.Common.Trivia;
|
using NadekoBot.Modules.Games.Common.Trivia;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
@@ -12,7 +11,6 @@ using System;
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -40,10 +38,6 @@ namespace NadekoBot.Modules.Games.Services
|
|||||||
|
|
||||||
//channelId, game
|
//channelId, game
|
||||||
public ConcurrentDictionary<ulong, AcrophobiaGame> AcrophobiaGames { get; } = new ConcurrentDictionary<ulong, AcrophobiaGame>();
|
public ConcurrentDictionary<ulong, AcrophobiaGame> AcrophobiaGames { get; } = new ConcurrentDictionary<ulong, AcrophobiaGame>();
|
||||||
|
|
||||||
public ConcurrentDictionary<ulong, Hangman> HangmanGames { get; } = new ConcurrentDictionary<ulong, Hangman>();
|
|
||||||
public TermPool TermPool { get; } = new TermPool();
|
|
||||||
|
|
||||||
public ConcurrentDictionary<ulong, TriviaGame> RunningTrivias { get; } = new ConcurrentDictionary<ulong, TriviaGame>();
|
public ConcurrentDictionary<ulong, TriviaGame> RunningTrivias { get; } = new ConcurrentDictionary<ulong, TriviaGame>();
|
||||||
public Dictionary<ulong, TicTacToe> TicTacToeGames { get; } = new Dictionary<ulong, TicTacToe>();
|
public Dictionary<ulong, TicTacToe> TicTacToeGames { get; } = new Dictionary<ulong, TicTacToe>();
|
||||||
public ConcurrentDictionary<ulong, TypingGame> RunningContests { get; } = new ConcurrentDictionary<ulong, TypingGame>();
|
public ConcurrentDictionary<ulong, TypingGame> RunningContests { get; } = new ConcurrentDictionary<ulong, TypingGame>();
|
||||||
|
15
src/NadekoBot/Modules/Nsfw/Common/Booru.cs
Normal file
15
src/NadekoBot/Modules/Nsfw/Common/Booru.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
namespace NadekoBot.Modules.Nsfw.Common
|
||||||
|
{
|
||||||
|
public enum Booru
|
||||||
|
{
|
||||||
|
Safebooru,
|
||||||
|
E621,
|
||||||
|
Derpibooru,
|
||||||
|
Rule34,
|
||||||
|
Gelbooru,
|
||||||
|
Konachan,
|
||||||
|
Yandere,
|
||||||
|
Danbooru,
|
||||||
|
Sankaku
|
||||||
|
}
|
||||||
|
}
|
18
src/NadekoBot/Modules/Nsfw/Common/DapiImageObject.cs
Normal file
18
src/NadekoBot/Modules/Nsfw/Common/DapiImageObject.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Nsfw.Common
|
||||||
|
{
|
||||||
|
public class DapiImageObject : IImageData
|
||||||
|
{
|
||||||
|
[JsonPropertyName("File_Url")]
|
||||||
|
public string FileUrl { get; set; }
|
||||||
|
public string Tags { get; set; }
|
||||||
|
[JsonPropertyName("Tag_String")]
|
||||||
|
public string TagString { get; set; }
|
||||||
|
public int Score { get; set; }
|
||||||
|
public string Rating { get; set; }
|
||||||
|
|
||||||
|
public ImageData ToCachedImageData(Booru type)
|
||||||
|
=> new ImageData(this.FileUrl, type, this.Tags?.Split(' ') ?? this.TagString?.Split(' '), Score.ToString() ?? Rating);
|
||||||
|
}
|
||||||
|
}
|
15
src/NadekoBot/Modules/Nsfw/Common/DapiTag.cs
Normal file
15
src/NadekoBot/Modules/Nsfw/Common/DapiTag.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Nsfw.Common
|
||||||
|
{
|
||||||
|
public readonly struct DapiTag
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
public DapiTag(string name)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
src/NadekoBot/Modules/Nsfw/Common/DerpiContainer.cs
Normal file
19
src/NadekoBot/Modules/Nsfw/Common/DerpiContainer.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Nsfw.Common
|
||||||
|
{
|
||||||
|
public class DerpiContainer
|
||||||
|
{
|
||||||
|
public DerpiImageObject[] Images { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DerpiImageObject : IImageData
|
||||||
|
{
|
||||||
|
[JsonPropertyName("view_url")]
|
||||||
|
public string ViewUrl { get; set; }
|
||||||
|
public string[] Tags { get; set; }
|
||||||
|
public int Score { get; set; }
|
||||||
|
public ImageData ToCachedImageData(Booru type)
|
||||||
|
=> new(ViewUrl, type, Tags, Score.ToString("F1"));
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user