mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 17:28:27 -04:00
Compare commits
74 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
db7cf3d757 | ||
|
83ea046d5f | ||
|
d5c94424e9 | ||
|
ff95b3d00f | ||
|
4b5fa3bb04 | ||
|
52c9ec670d | ||
|
1ac472c676 | ||
|
39f01a3164 | ||
|
486916944b | ||
|
68e96cd1bb | ||
|
5a647d100a | ||
|
a562a571e2 | ||
|
12146ad2da | ||
|
453ac3efd2 | ||
|
c9e89e1911 | ||
|
611817f78a | ||
|
2e66137f3e | ||
|
2d92424dd4 | ||
|
ffba03adbe | ||
|
76ebb87fb5 | ||
|
4a50c30c56 | ||
|
e70a91ae60 | ||
|
3470762b30 | ||
|
2cbb4ecd3d | ||
|
619bee811d | ||
|
a09be96200 | ||
|
81254ed5f6 | ||
|
d82e3bd444 | ||
|
9011646b02 | ||
|
2a1f45819d | ||
|
d2d0cb9e03 | ||
|
ed039977c2 | ||
|
786ede3290 | ||
|
3edcca4927 | ||
|
0635a0ddc8 | ||
|
cb514e4219 | ||
|
cccb37854c | ||
|
35d549f4e6 | ||
|
c0d81a5d9c | ||
|
711d6c7caa | ||
|
cea944cdb8 | ||
|
2d70ea487e | ||
|
9f65f979fd | ||
|
c12b41ddd1 | ||
|
a5f9ac1540 | ||
|
8c5214def2 | ||
|
467a8bdaf6 | ||
|
d115261536 | ||
|
0429210a73 | ||
|
8ee1160a00 | ||
|
a27ea7a0aa | ||
|
9396f59a34 | ||
|
37cdea4f6a | ||
|
d028e23bc1 | ||
|
1df947d54b | ||
|
596a5c05e0 | ||
|
1d5a702b46 | ||
|
712cb9300f | ||
|
ab0d013035 | ||
|
46b384f0f4 | ||
|
1284312a55 | ||
|
42bdc6417c | ||
|
3715dd4e00 | ||
|
ef13020d96 | ||
|
dd708d4b7c | ||
|
8735dcf94d | ||
|
b6e03dcaed | ||
|
1785a27772 | ||
|
9fbe43ec8f | ||
|
d3878414e0 | ||
|
00f9a05cfc | ||
|
d9e15a9abd | ||
|
8a5539448e | ||
|
e005ec11fd |
@@ -9,6 +9,7 @@
|
||||
!src/NadekoBot.Generators/**
|
||||
# Use Ayu stuff
|
||||
!src/ayu/**
|
||||
!docker-entrypoint.sh
|
||||
|
||||
# ignore bin and obj folders in projects
|
||||
src/**/bin/*
|
||||
|
@@ -91,7 +91,35 @@ upload-windows-updater-release:
|
||||
name: amazon/aws-cli
|
||||
entrypoint: [""]
|
||||
script:
|
||||
- sed -i "s/INSTALLER_FILE_NAME/$INSTALLER_FILE_NAME/g" releases-v3.json
|
||||
- sed -i "s/_INSTALLER_FILE_NAME_/$INSTALLER_FILE_NAME/g" releases-v3.json
|
||||
- sed -i "s/_VERSION_/$CI_COMMIT_TAG/g" releases-v3.json
|
||||
- aws --version
|
||||
- aws --endpoint-url $AWS_SERVICE_URL s3api put-object --bucket "$AWS_BUCKET_NAME" --key "dl/bot/$INSTALLER_FILE_NAME" --acl public-read --body "$INSTALLER_OUTPUT_DIR/$INSTALLER_FILE_NAME"
|
||||
- aws --endpoint-url $AWS_SERVICE_URL s3api put-object --bucket "$AWS_BUCKET_NAME" --key "dl/bot/releases-v3.json" --acl public-read --body "releases-v3.json"
|
||||
- aws --endpoint-url $AWS_SERVICE_URL s3api put-object --bucket "$AWS_BUCKET_NAME" --key "dl/bot/releases-v3.json" --acl public-read --body "releases-v3.json"
|
||||
|
||||
docker-build:
|
||||
# Use the official docker image.
|
||||
image: docker:latest
|
||||
stage: build
|
||||
services:
|
||||
- docker:dind
|
||||
before_script:
|
||||
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
|
||||
# Default branch leaves tag empty (= latest tag)
|
||||
# All other branches are tagged with the escaped branch name (commit ref slug)
|
||||
script:
|
||||
- |
|
||||
if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then
|
||||
tag=""
|
||||
echo "Running on default branch '$CI_DEFAULT_BRANCH': tag = 'latest'"
|
||||
else
|
||||
tag=":$CI_COMMIT_REF_SLUG"
|
||||
echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
|
||||
fi
|
||||
- docker build --pull -t "$CI_REGISTRY_IMAGE${tag}" .
|
||||
- docker push "$CI_REGISTRY_IMAGE${tag}"
|
||||
# Run this job in a branch where a Dockerfile exists
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH
|
||||
exists:
|
||||
- Dockerfile
|
||||
|
102
CHANGELOG.md
102
CHANGELOG.md
@@ -4,7 +4,109 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.
|
||||
|
||||
## Unreleased
|
||||
|
||||
## [3.0.7]
|
||||
|
||||
### Added
|
||||
- `.streamsclear` re-added. It will remove all followed streams on the server.
|
||||
- `.gifts` now have 3 new ✂️ Haircut 🧻 ToiletPaper and 🥀 WiltedRose which **reduce** waifu's value
|
||||
- They are called negative gifts
|
||||
- They show up at the end of the `.gifts` page and are marked with a broken heart
|
||||
- They have a separate multiplier (`waifu.multi.negative_gift_effect` default 0.5, changeable via `.config gambling` or `data/gambling.yml`)
|
||||
- When gifted, the waifu's price will be reduced by the `price * multiplier`
|
||||
- Negative gifts don't show up in `.waifuinfo` nor is the record of them kept in the database
|
||||
|
||||
### Fixed
|
||||
- Fixed `%users%` and `%shard.usercount%` placeholders not showing correct values
|
||||
|
||||
## [3.0.6] - 27.09.2021
|
||||
|
||||
### Added
|
||||
|
||||
- .logignore now supports ignoring users and channels. Use without parameters to see the ignore list
|
||||
|
||||
### Changed
|
||||
|
||||
- Hangman rewrite
|
||||
- Hangman categories are now held in separate .yml files in data/hangman/XYZ.yml where XYZ is the category name
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed an exception which caused repeater queue to break
|
||||
- Fixed url field not working in embeds
|
||||
|
||||
## [3.0.5] - 20.09.2021
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed images not automatically reloading on startup if the keys don't exist
|
||||
- Fixed `.logserver` - it should no longer throw an exception if you had no logsettings previously
|
||||
|
||||
## [3.0.4] - 16.09.2021
|
||||
|
||||
### Added
|
||||
|
||||
- Fully translated to Brazilian Portuguese 🎉
|
||||
- Added `%server.boosters%` and `%server.boost_level%` placeholders
|
||||
- Added `DmHelpTextKeywords` to `data/bot.yml`
|
||||
- Bot now sends dm help text ONLY if the message contains one of the keywords specified
|
||||
- If no keywords are specified, bot will reply to every DM (like before)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Possible fix for `.repeat` bug
|
||||
- Slight adjustment for repeater logic
|
||||
- Timer should no longer increase on some repeaters
|
||||
- Repeaters should no longer have periods when they're missing from the list
|
||||
- Fixed several commands which used error color for success confirmation messages
|
||||
|
||||
## [3.0.3] - 15.09.2021
|
||||
|
||||
### Added
|
||||
|
||||
- Added `.massban` to ban multiple people at once. 30 second cooldown
|
||||
- Added `.youtubeuploadnotif` / `.yun` as a shortcut for subscribing to a youtube channel's rss feed
|
||||
- Added `.imageonlychannel` / `.imageonly` to prevent users from posting anything but images in the channel
|
||||
- Added `.config games hangman.currency_reward` and a property with the same name in games.yml
|
||||
- If set, users will gain the specified amount of currency for each hangman win
|
||||
- Fully translated to Spanish, Russian and Ukrainian 🎉
|
||||
|
||||
### Changed
|
||||
|
||||
- Ban `.warnp` will now prune user's messages
|
||||
|
||||
### Fixed
|
||||
|
||||
- `.boostmsg` will now properly show boost, and not greet message
|
||||
|
||||
## [3.0.2] - 12.09.2021
|
||||
|
||||
### Added
|
||||
|
||||
- `.rero` now optionally takes a message id to which to attach the reaction roles
|
||||
- Fully translated to German 🎉
|
||||
- Added `.boost`, `.boostmsg` and `.boostdel` commands which allow you to have customizable messages when someone boosts your server, with auto-deletion support
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated `.greetmsg` and `.byemsg` command help to match the new `.boost` command help
|
||||
- Updated response embed colors in greet commands
|
||||
- Success -> green
|
||||
- Warning or Disable -> yellow.
|
||||
|
||||
### Fixed
|
||||
|
||||
- `.timely` will now correctly use `Ok` color
|
||||
- Fixed `.log` commands
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed `.novel` command as it no longer works
|
||||
|
||||
## [3.0.1] - 10.09.2021
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed some issues with the embeds not showing the correct data
|
||||
|
||||
## [3.0.0] - 06.09.2021
|
||||
|
||||
|
36
Dockerfile
36
Dockerfile
@@ -1,4 +1,4 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
|
||||
FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim AS build
|
||||
WORKDIR /source
|
||||
|
||||
COPY src/NadekoBot/*.csproj src/NadekoBot/
|
||||
@@ -9,14 +9,34 @@ RUN dotnet restore src/NadekoBot/
|
||||
|
||||
COPY . .
|
||||
WORKDIR /source/src/NadekoBot
|
||||
RUN dotnet --version
|
||||
RUN dotnet publish -c Release -o /app --no-restore
|
||||
RUN set -xe; \
|
||||
dotnet --version; \
|
||||
dotnet publish -c Release -o /app --no-restore; \
|
||||
mv /app/data /app/data_init; \
|
||||
rm -Rf libopus* libsodium* opus.* runtimes/win* runtimes/osx* runtimes/linux-arm* runtimes/linux-mips*; \
|
||||
find /app -type f -exec chmod -x {} \; ;\
|
||||
chmod +x /app/NadekoBot
|
||||
|
||||
# final stage/image
|
||||
FROM mcr.microsoft.com/dotnet/runtime:5.0
|
||||
FROM mcr.microsoft.com/dotnet/runtime:5.0-buster-slim
|
||||
WORKDIR /app
|
||||
|
||||
RUN set -xe; \
|
||||
useradd -m nadeko; \
|
||||
apt-get update; \
|
||||
apt-get install -y libopus0 libsodium23 libsqlite3-0 curl ffmpeg python3 python3-pip sudo; \
|
||||
update-alternatives --install /usr/bin/python python /usr/bin/python3.7 1; \
|
||||
echo 'Defaults>nadeko env_keep+="ASPNETCORE_* DOTNET_* NadekoBot_* shard_id total_shards TZ"' > /etc/sudoers.d/nadeko; \
|
||||
pip3 install --upgrade youtube-dl; \
|
||||
apt-get remove -y python3-pip; \
|
||||
chmod +x /usr/local/bin/youtube-dl
|
||||
|
||||
COPY --from=build /app ./
|
||||
COPY docker-entrypoint.sh /usr/local/sbin
|
||||
|
||||
ENV shard_id=0
|
||||
ENV total_shards=1
|
||||
WORKDIR /app
|
||||
COPY --from=build /app ./
|
||||
VOLUME [ "app/data", "app/creds.yml", "app/creds_example.yml" ]
|
||||
ENTRYPOINT dotnet NadekoBot.dll "$shard_id" "$total_shards"
|
||||
|
||||
VOLUME [ "app/data" ]
|
||||
ENTRYPOINT [ "/usr/local/sbin/docker-entrypoint.sh" ]
|
||||
CMD dotnet NadekoBot.dll "$shard_id" "$total_shards"
|
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 "$@"
|
@@ -1,3 +1,35 @@
|
||||
# Setting up NadekoBot with Docker
|
||||
|
||||
Soon:tm:
|
||||
# DO NOT USE YET - WORK IN PROGRESS
|
||||
|
||||
Upgrade from 2.x to v3 does not work because the file is mount readonly
|
||||
|
||||
### Docker Compose
|
||||
```yml
|
||||
version: "3.7"
|
||||
services:
|
||||
nadeko:
|
||||
image: registry.gitlab.com/veovis/nadekobot:v3-docker
|
||||
depends_on:
|
||||
- redis
|
||||
environment:
|
||||
TZ: Europe/Paris
|
||||
#NadekoBot_RedisOptions: redis,name=nadeko
|
||||
#NadekoBot_ShardRunCommand: dotnet
|
||||
#NadekoBot_ShardRunArguments: /app/NadekoBot.dll {0} {1}
|
||||
volumes:
|
||||
- /srv/nadeko/conf/creds.yml:/app/creds.yml:ro
|
||||
- /srv/nadeko/data:/app/data
|
||||
|
||||
redis:
|
||||
image: redis:4-alpine
|
||||
sysctls:
|
||||
- net.core.somaxconn=511
|
||||
command: redis-server --maxmemory 32M --maxmemory-policy volatile-lru
|
||||
volumes:
|
||||
- /srv/nadeko/redis-data:/data
|
||||
```
|
||||
### Updating
|
||||
- `cd /srv/nadeko`
|
||||
- `docker-compose pull`
|
||||
- `docker-compose up -d`
|
||||
|
@@ -16,12 +16,12 @@ Open Terminal (if you're on an installation with a window manager) and navigate
|
||||
4. Exit the installer in order to set up your `creds.yml`
|
||||
5. Copy the creds.yml template `cp nadekobot/output/creds_example.yml nadekobot/output/creds.yml`
|
||||
6. Open `nadekobot/output/creds.yml` with your favorite text editor. We will use nano here
|
||||
- `nano nadekobot/output/creds.yml`
|
||||
- `nano nadekobot/output/creds.yml`
|
||||
7. [Enter your bot's token](../../creds-guide)
|
||||
- After you're done, you can close nano (and save the file) by inputting, in order
|
||||
- `CTRL` + `X`
|
||||
- `Y`
|
||||
- `Enter`
|
||||
- After you're done, you can close nano (and save the file) by inputting, in order
|
||||
- `CTRL` + `X`
|
||||
- `Y`
|
||||
- `Enter`
|
||||
8. Run the bot (type `3` and press enter)
|
||||
|
||||
##### Update Instructions
|
||||
@@ -37,51 +37,51 @@ Open Terminal (if you're on an installation with a window manager) and navigate
|
||||
##### Installation Instructions
|
||||
|
||||
1. Download the latest release from <https://gitlab.com/Kwoth/nadekobot/-/releases>
|
||||
- Look for the file called "X.XX.X-linux-x64-build.tar" (where X.XX.X is a series of numbers) and download it
|
||||
- Look for the file called "X.XX.X-linux-x64-build.tar" (where X.XX.X is a series of numbers) and download it
|
||||
2. Untar it
|
||||
- ⚠ Make sure that you change X.XX.X to the same series of numbers as in step 1!
|
||||
- `tar xf X.XX.X-linux-x64-build.tar`
|
||||
- ⚠ Make sure that you change X.XX.X to the same series of numbers as in step 1!
|
||||
- `tar xf X.XX.X-linux-x64-build.tar`
|
||||
3. Rename the `nadekobot-linux-x64` to `nadekobot`
|
||||
- `mv nadekobot-linux-x64 nadekobot`
|
||||
- `mv nadekobot-linux-x64 nadekobot`
|
||||
4. Move into nadekobot directory and make NadekoBot executable
|
||||
- `cd nadekobot && chmod +x NadekoBot`
|
||||
- `cd nadekobot && chmod +x NadekoBot`
|
||||
5. Copy the creds.yml template
|
||||
- `cp creds_example.yml creds.yml`
|
||||
- `cp creds_example.yml creds.yml`
|
||||
6. Open `creds.yml` with your favorite text editor. We will use nano here
|
||||
- `nano nadekobot/output/creds.yml`
|
||||
- `nano nadekobot/output/creds.yml`
|
||||
8. [Enter your bot's token](#creds-guide)
|
||||
- After you're done, you can close nano (and save the file) by inputting, in order
|
||||
- `CTRL` + `X`
|
||||
- `Y`
|
||||
- `Enter`
|
||||
- After you're done, you can close nano (and save the file) by inputting, in order
|
||||
- `CTRL` + `X`
|
||||
- `Y`
|
||||
- `Enter`
|
||||
9. Run the bot
|
||||
- `./NadekoBot`
|
||||
- `./NadekoBot`
|
||||
|
||||
##### Update Instructions
|
||||
|
||||
1. Stop the bot
|
||||
2. Download the latest release from <https://gitlab.com/Kwoth/nadekobot/-/releases>
|
||||
- Look for the file called "X.XX.X-linux-x64-build.tar" (where X.XX.X is a series of numbers) and download it
|
||||
- Look for the file called "x.x.x-linux-x64-build.tar" (where `X.X.X` is a version, for example 3.0.4) and download it
|
||||
3. Untar it
|
||||
- ⚠ Make sure that you change X.XX.X to the same series of numbers as in step 2!
|
||||
- `tar xf 2.99.8-linux-x64-build.tar`
|
||||
- ⚠ Make sure that you change `X.X.X` to the same series of numbers as in step 2!
|
||||
- `tar xf x.x.x-linux-x64-build.tar`
|
||||
4. Rename the old nadekobot directory to nadekobot-old (remove your old backup first if you have one, or back it up under a different name)
|
||||
- `rm -rf nadekobot-old 2>/dev/null`
|
||||
- `mv nadekobot nadekobot-old`
|
||||
- `rm -rf nadekobot-old 2>/dev/null`
|
||||
- `mv nadekobot nadekobot-old`
|
||||
5. Rename the new nadekobot directory to nadekobot
|
||||
- `mv nadekobot-linux-x64 nadekobot`
|
||||
- `mv nadekobot-linux-x64 nadekobot`
|
||||
6. Remove old strings and aliases to avoid overwriting the updated versions of those files
|
||||
- ⚠ If you've modified said files, back them up instead
|
||||
- `rm nadekobot-old/data/aliases.yml`
|
||||
- `rm -r nadekobot-old/data/strings`
|
||||
- ⚠ If you've modified said files, back them up instead
|
||||
- `rm nadekobot-old/data/aliases.yml`
|
||||
- `rm -r nadekobot-old/data/strings`
|
||||
7. Copy old data
|
||||
- `cp -RT nadekobot-old/data/ nadekobot/data/`
|
||||
- `cp -RT nadekobot-old/data/ nadekobot/data`
|
||||
8. Copy creds.yml
|
||||
- `cp nadekobot-old/creds.yml nadekobot/`
|
||||
- `cp nadekobot-old/creds.yml nadekobot/`
|
||||
9. Move into nadekobot directory and make the NadekoBot executable
|
||||
- `cd nadekobot && chmod +x NadekoBot`
|
||||
- `cd nadekobot && chmod +x NadekoBot`
|
||||
10. Run the bot
|
||||
- `./NadekoBot`
|
||||
- `./NadekoBot`
|
||||
|
||||
🎉 Enjoy
|
||||
|
||||
@@ -95,7 +95,135 @@ mv nadekobot nadekobot-old && \
|
||||
mv nadekobot-linux-x64 nadekobot && \
|
||||
rm nadekobot-old/data/aliases.yml && \
|
||||
rm -r nadekobot-old/data/strings && \
|
||||
cp -RT nadekobot-old/data/ nadekobot/data/ && \
|
||||
cp -RT nadekobot-old/data/ nadekobot/data && \
|
||||
cp nadekobot-old/creds.yml nadekobot/ && \
|
||||
cd nadekobot && chmod +x NadekoBot
|
||||
```
|
||||
|
||||
## Running Nadeko
|
||||
|
||||
While there are two run modes built into the installer, these options only run Nadeko within the current session. Below are 3 methods of running Nadeko as a background process.
|
||||
|
||||
### Tmux (Preferred Method)
|
||||
|
||||
Using `tmux` is the simplest method, and is therefore recommended for most users.
|
||||
|
||||
1. Start a tmux session:
|
||||
- `tmux`
|
||||
2. Navigate to the project's root directory
|
||||
- Project root directory location example: `/home/user/nadekobot/`
|
||||
3. Enter the `output` directory:
|
||||
- `cd output`
|
||||
4. Run the bot using:
|
||||
- `dotnet NadekoBot.dll`
|
||||
5. Detatch the tmux session:
|
||||
- Press `Ctrl` + `B`
|
||||
- Then press `D`
|
||||
|
||||
Nadeko should now be running in the background of your system. To re-open the tmux session to either update, restart, or whatever, execute `tmux a`.
|
||||
|
||||
### Systemd
|
||||
|
||||
Compared to using tmux, this method requires a little bit more work to set up, but has the benefit of allowing Nadeko to automatically start back up after a system reboot or the execution of the `.die` command.
|
||||
|
||||
1. Navigate to the project's root directory
|
||||
- Project root directory location example: `/home/user/nadekobot/`
|
||||
2. Use the following command to create a service that will be used to start Nadeko:
|
||||
|
||||
```bash
|
||||
echo "[Unit]
|
||||
Description=NadekoBot service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=$USER
|
||||
WorkingDirectory=$PWD/output
|
||||
# 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
|
||||
# source code.
|
||||
#ExecStartPre=/usr/bin/dotnet build ../src/NadekoBot/NadekoBot.csproj -c Release -o output/
|
||||
ExecStart=/usr/bin/dotnet NadekoBot.dll
|
||||
StandardOutput=syslog
|
||||
StandardError=syslog
|
||||
SyslogIdentifier=NadekoBot
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target" | sudo tee /etc/systemd/system/nadeko.service
|
||||
```
|
||||
|
||||
3. Make the new service available:
|
||||
- `sudo systemctl daemon-reload`
|
||||
4. Start Nadeko:
|
||||
- `sudo systemctl start nadeko.service && sudo systemctl enable nadeko.service`
|
||||
|
||||
|
||||
### Systemd + Script
|
||||
|
||||
This method is similar to the one above, but requires one extra step, with the added benefit of better error logging and control over what happens before and after the startup of Nadeko.
|
||||
|
||||
1. Locate the project and move to its parent directory
|
||||
- Project location example: `/home/user/nadekobot/`
|
||||
- Parent directory example: `/home/user/`
|
||||
2. Use the following command to create a service that will be used to execute `NadekoRun.sh`:
|
||||
|
||||
```bash
|
||||
echo "[Unit]
|
||||
Description=NadekoBot service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=$USER
|
||||
WorkingDirectory=$PWD
|
||||
ExecStart=/bin/bash NadekoRun.sh
|
||||
StandardOutput=syslog
|
||||
StandardError=syslog
|
||||
SyslogIdentifier=NadekoBot
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target" | sudo tee /etc/systemd/system/nadeko.service
|
||||
```
|
||||
|
||||
3. Make the new service available:
|
||||
- `sudo systemctl daemon-reload`
|
||||
4. Use the following command to create a script that will be used to start Nadeko:
|
||||
|
||||
```bash
|
||||
echo "#\!/bin/bash
|
||||
|
||||
echo \"\"
|
||||
echo \"Running NadekoBot in the background with auto restart\"
|
||||
youtube-dl -U
|
||||
|
||||
# If you want Nadeko to be compiled prior to every startup, uncomment the lines
|
||||
# below. Note that it's not neccessary unless you are personally modifying the
|
||||
# source code.
|
||||
#echo \"Compiling NadekoBot...\"
|
||||
#cd \"$PWD\"/nadekobot
|
||||
#dotnet build src/NadekoBot/NadekoBot.csproj -c Release -o output/
|
||||
|
||||
echo \"Starting NadekoBot...\"
|
||||
|
||||
while true; do
|
||||
{
|
||||
cd \"$PWD\"/nadekobot/output
|
||||
dotnet NadekoBot.dll
|
||||
## If a non-zero exit code is produced, exit this script.
|
||||
} || {
|
||||
error_code=\"\$?\"
|
||||
echo \"An error occurred when trying to start NadekBot\"
|
||||
echo \"EXIT CODE: \$?\"
|
||||
exit \"\$error_code\"
|
||||
}
|
||||
|
||||
youtube-dl -U
|
||||
echo \"Restarting NadekoBot...\"
|
||||
done
|
||||
|
||||
echo \"Stopping NadekoBot...\"" > NadekoRun.sh
|
||||
```
|
||||
|
||||
5. Start Nadeko:
|
||||
- `sudo systemctl start nadeko.service && sudo systemctl enable nadeko.service`
|
@@ -3,10 +3,12 @@
|
||||
## Windows
|
||||
|
||||
1. Run your NadekoBot Updater first, and **make sure your bot is updated to at least 2.46.5**
|
||||
- **Run your 2.46.5 Bot** and make sure it works, and then **stop it**
|
||||
- Close your old NadekoBot Updater
|
||||
2. Get the new NadekoBot v3 Updater [here](https://dl.nadeko.bot/v3)
|
||||
3. Click on the + icon to add a new bot
|
||||
4. Next to the path, click on the folder icon and select the folder where your 2.46.5 bot is
|
||||
- ℹ In case you're not sure where it's located, you can open your old updater and see it
|
||||
- ℹ In case you're not sure where it's located, you can open your old updater and see it
|
||||
5. If you've selected the correct path, you should have an **Update** button available, click it
|
||||
6. You're done; you can now run your bot, and you can uninstall your old updater if you no longer have 2.x bots
|
||||
7. 🎉
|
||||
@@ -14,48 +16,51 @@
|
||||
## Linux
|
||||
|
||||
1. In order to migrate a bot hosted on **Linux**, first update your current version to the latest 2.x version using the 2.x installer, run the bot, and make sure it works. Then:
|
||||
- Run the **old** installer with `cd ~ && wget -N https://github.com/Kwoth/NadekoBot-BashScript/raw/1.9/linuxAIO.sh && bash linuxAIO.sh`
|
||||
- Run option **1** again
|
||||
- Run the bot
|
||||
- Type `.stats` and ensure the version is `2.46.5` or later
|
||||
- Stop the bot
|
||||
- Run the **old** installer with `cd ~ && wget -N https://github.com/Kwoth/NadekoBot-BashScript/raw/1.9/linuxAIO.sh && bash linuxAIO.sh`
|
||||
- Run option **1** again
|
||||
- You **MUST** Run the bot now to ensure database is ready for migration
|
||||
- Type `.stats` and ensure the version is `2.46.5` or later
|
||||
- Stop the bot
|
||||
2. Make sure your bot's folder is called `NadekoBot`
|
||||
- Run `cd ~ && ls`
|
||||
- Confirm there is a folder called NadekoBot (not nadekobot, in all lowercase)
|
||||
- Run `cd ~ && ls`
|
||||
- Confirm there is a folder called NadekoBot (not nadekobot, in all lowercase)
|
||||
3. Migrate your bot's data using the new installer:
|
||||
- Run the **new** installer `cd ~ && wget -N https://gitlab.com/Kwoth/nadeko-bash-installer/-/raw/master/linuxAIO.sh && bash linuxAIO.sh`
|
||||
- The installer should notify you that your data is ready for migration in a message above the menu.
|
||||
- Install prerequisites (type `1` and press enter), and make sure it is successful
|
||||
- Download NadekoBot v3 (type `2` and press enter)
|
||||
- Run the bot (type `3` and press enter)
|
||||
- Run the **new** installer `cd ~ && wget -N https://gitlab.com/Kwoth/nadeko-bash-installer/-/raw/master/linuxAIO.sh && bash linuxAIO.sh`
|
||||
- The installer should notify you that your data is ready for migration in a message above the menu.
|
||||
- Install prerequisites (type `1` and press enter), and make sure it is successful
|
||||
- Download NadekoBot v3 (type `2` and press enter)
|
||||
- Run the bot (type `3` and press enter)
|
||||
4. Make sure your permissions, custom reactions, credentials, and other data is preserved
|
||||
- `.stats` to ensure owner id (credentials) is correct
|
||||
- `.lcr` to see custom reactions
|
||||
- `.lp` to list permissions
|
||||
5. 🎉 Enjoy. If you want to learn how to update the bot, click (here)[../linux-guide/#update-instructions].
|
||||
- `.stats` to ensure owner id (credentials) is correct
|
||||
- `.lcr` to see custom reactions
|
||||
- `.lp` to list permissions
|
||||
5. 🎉 Enjoy. If you want to learn how to update the bot, click [here](../linux-guide/#update-instructions)
|
||||
|
||||
## Manual
|
||||
|
||||
⚠ NOT RECOMMENDED
|
||||
⚠ NadekoBot v3 requires [.net 5](https://dotnet.microsoft.com/download/dotnet/5.0)
|
||||
|
||||
1. In order to migrate a bot hosted **on Linux or from source on Windows**, first update your current version to the latest 2.x version using the 2.x installer, run the bot, and make sure it works. Then:
|
||||
1. In order to migrate a bot hosted **on Linux or from source on Windows**
|
||||
- First update your current version to the latest 2.x version using the 2.x installer
|
||||
- Then you **must** run the bot to prepare the database for the migration, and make sure the bot works prior to upgrade.
|
||||
Then:
|
||||
2. Rename your old nadeko bot folder to `nadekobot_2x`
|
||||
- `mv NadekoBot nadekobot_2x`
|
||||
- `mv NadekoBot nadekobot_2x`
|
||||
3. Build the new version and move old data to the output folder
|
||||
1. Clone the v3 branch to a separate folder
|
||||
- `git clone https://gitlab.com/kwoth/nadekobot -b v3 --depth 1`
|
||||
2. Build the bot
|
||||
- `dotnet publish -c Release -o output/ src/NadekoBot/`
|
||||
3. Copy old data
|
||||
- ⚠ Be sure you copy the correct command for your system!
|
||||
- **Windows:** `cp -r -fo nadekobot_2x/src/NadekoBot/data nadekobot/src/NadekoBot/data`
|
||||
- **Linux:** `cp -rf nadekobot_2x/src/NadekoBot/data nadekobot/src/NadekoBot/data`
|
||||
4. Copy the database
|
||||
- `cp nadekobot_2x/src/NadekoBot/bin/Release/netcoreapp2.1/data/NadekoBot.db nadekobot/output/data`
|
||||
5. Copy your credentials
|
||||
- `cp nadekobot_2x/src/NadekoBot/credentials.json nadekobot/output/`
|
||||
1. Clone the v3 branch to a separate folder
|
||||
- `git clone https://gitlab.com/kwoth/nadekobot -b v3 --depth 1`
|
||||
2. Build the bot
|
||||
- `dotnet publish -c Release -o output/ src/NadekoBot/`
|
||||
3. Copy old data
|
||||
- ⚠ Be sure you copy the correct command for your system!
|
||||
- **Windows:** `cp -r -fo nadekobot_2x/src/NadekoBot/data nadekobot/src/NadekoBot/data`
|
||||
- **Linux:** `cp -rf nadekobot_2x/src/NadekoBot/data nadekobot/src/NadekoBot/data`
|
||||
4. Copy the database
|
||||
- `cp nadekobot_2x/src/NadekoBot/bin/Release/netcoreapp2.1/data/NadekoBot.db nadekobot/output/data`
|
||||
5. Copy your credentials
|
||||
- `cp nadekobot_2x/src/NadekoBot/credentials.json nadekobot/output/`
|
||||
4. Run the bot
|
||||
- `cd nadekobot/output`
|
||||
- `dotnet NadekoBot.dll`
|
||||
- `cd nadekobot/output`
|
||||
- `dotnet NadekoBot.dll`
|
||||
5. That's it. Just make sure that when you're updating the bot, you're properly backing up your old data.
|
||||
|
@@ -1,6 +1,14 @@
|
||||
### Linux From Source
|
||||
## MacOS From Source
|
||||
|
||||
Open Terminal (if you're on an installation with a window manager) and navigate to the location where you want to install the bot (for example `cd ~`)
|
||||
Open Terminal (if you don't know how to, click on the magnifying glass on the top right corner of your screen and type **Terminal** on the window that pops up) and navigate to the location where you want to install the bot (for example `cd ~`)
|
||||
|
||||
##### Installing Homebrew and wget
|
||||
|
||||
*Skip this step if you already have homebrew installed*
|
||||
- Copy and paste this command, then press Enter:
|
||||
- `/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"`
|
||||
- Install wget
|
||||
- `brew install wget`
|
||||
|
||||
##### Installation Instructions
|
||||
|
||||
@@ -8,11 +16,12 @@ Open Terminal (if you're on an installation with a window manager) and navigate
|
||||
2. Install prerequisites (type `1` and press enter)
|
||||
3. Download the bot (type `2` and press enter)
|
||||
4. Exit the installer in order to set up your `creds.yml`
|
||||
5. Copy the creds.yml template `cp nadekobot/output/creds_example.yml nadekobot/output/creds.yml`
|
||||
5. Copy the creds.yml template
|
||||
`cp nadekobot/output/creds_example.yml nadekobot/output/creds.yml`
|
||||
6. Open `nadekobot/output/creds.yml` with your favorite text editor. We will use nano here
|
||||
- `nano nadekobot/output/creds.yml`
|
||||
- `nano nadekobot/output/creds.yml`
|
||||
7. [Enter your bot's token](#creds-guide)
|
||||
- After you're done, you can close nano (and save the file) by inputting, in order
|
||||
- After you're done, you can close nano (and save the file) by inputting, in order
|
||||
- `CTRL`+`X`
|
||||
- `Y`
|
||||
- `Enter`
|
||||
@@ -26,7 +35,7 @@ Open Terminal (if you're on an installation with a window manager) and navigate
|
||||
4. Run the bot (type `3` and press enter)
|
||||
5. 🎉
|
||||
|
||||
### Linux Release
|
||||
## MacOS Manual Release installation instructions
|
||||
|
||||
##### Installation Instructions
|
||||
|
||||
@@ -34,48 +43,48 @@ Open Terminal (if you're on an installation with a window manager) and navigate
|
||||
- Look for the file called "X.XX.X-linux-x64-build.tar" (where X.XX.X is a series of numbers) and download it
|
||||
2. Untar it
|
||||
⚠ Make sure that you change X.XX.X to the same series of numbers as in step 1!
|
||||
- `tar xf X.XX.X-linux-x64-build.tar`
|
||||
- `tar xf X.XX.X-linux-x64-build.tar`
|
||||
3. Rename the `nadekobot-linux-x64` to `nadekobot`
|
||||
- `mv nadekobot-linux-x64 nadekobot`
|
||||
- `mv nadekobot-linux-x64 nadekobot`
|
||||
4. Move into nadekobot directory and make NadekoBot executable
|
||||
- `cd nadekobot && chmod +x NadekoBot`
|
||||
- `cd nadekobot && chmod +x NadekoBot`
|
||||
5. Copy the creds.yml template
|
||||
- `cp creds_example.yml creds.yml`
|
||||
- `cp creds_example.yml creds.yml`
|
||||
6. Open `creds.yml` with your favorite text editor. We will use nano here
|
||||
- `nano nadekobot/output/creds.yml`
|
||||
- `nano nadekobot/output/creds.yml`
|
||||
8. [Enter your bot's token](#creds-guide)
|
||||
- After you're done, you can close nano (and save the file) by inputting, in order
|
||||
- `CTRL`+`X`
|
||||
- `Y`
|
||||
- `Enter`
|
||||
- After you're done, you can close nano (and save the file) by inputting, in order
|
||||
- `CTRL`+`X`
|
||||
- `Y`
|
||||
- `Enter`
|
||||
9. Run the bot
|
||||
- `./NadekoBot`
|
||||
- `./NadekoBot`
|
||||
|
||||
##### Update Instructions
|
||||
|
||||
1. Stop the bot
|
||||
2. Download the latest release from <https://gitlab.com/Kwoth/nadekobot/-/releases>
|
||||
- Look for the file called "X.XX.X-linux-x64-build.tar" (where X.XX.X is a series of numbers) and download it
|
||||
- Look for the file called "X.XX.X-linux-x64-build.tar" (where X.XX.X is a series of numbers) and download it
|
||||
3. Untar it
|
||||
⚠ Make sure that you change X.XX.X to the same series of numbers as in step 2!
|
||||
- `tar xf 2.99.8-linux-x64-build.tar`
|
||||
- `tar xf 2.99.8-linux-x64-build.tar`
|
||||
4. Rename the old nadekobot directory to nadekobot-old (remove your old backup first if you have one, or back it up under a different name)
|
||||
- `rm -rf nadekobot-old 2>/dev/null`
|
||||
- `mv nadekobot nadekobot-old`
|
||||
- `rm -rf nadekobot-old 2>/dev/null`
|
||||
- `mv nadekobot nadekobot-old`
|
||||
5. Rename the new nadekobot directory to nadekobot
|
||||
- `mv nadekobot-linux-x64 nadekobot`
|
||||
- `mv nadekobot-linux-x64 nadekobot`
|
||||
6. Remove old strings and aliases to avoid overwriting the updated versions of those files
|
||||
⚠ If you've modified said files, back them up instead
|
||||
- `rm nadekobot-old/data/aliases.yml`
|
||||
- `rm -r nadekobot-old/data/strings`
|
||||
- `rm nadekobot-old/data/aliases.yml`
|
||||
- `rm -r nadekobot-old/data/strings`
|
||||
7. Copy old data
|
||||
- `cp -RT nadekobot-old/data/ nadekobot/data/`
|
||||
- `cp -RT nadekobot-old/data/ nadekobot/data/`
|
||||
8. Copy creds.yml
|
||||
- `cp nadekobot-old/creds.yml nadekobot/`
|
||||
- `cp nadekobot-old/creds.yml nadekobot/`
|
||||
9. Move into nadekobot directory and make the NadekoBot executable
|
||||
- `cd nadekobot && chmod +x NadekoBot`
|
||||
- `cd nadekobot && chmod +x NadekoBot`
|
||||
10. Run the bot
|
||||
- `./NadekoBot`
|
||||
- `./NadekoBot`
|
||||
|
||||
🎉 Enjoy
|
||||
|
||||
|
@@ -80,6 +80,7 @@ You can still install them manually:
|
||||
Open PowerShell (press windows button on your keyboard and type powershell, it should show up; alternatively, right click the start menu and select Windows PowerShell), and navigate to the location where you want to install the bot (for example `cd ~/Desktop/`)
|
||||
|
||||
1. `git clone https://gitlab.com/kwoth/nadekobot -b v3 --depth 1`
|
||||
2. `cd nadekobot`
|
||||
3. `dotnet publish -c Release -o output/ src/NadekoBot/`
|
||||
4. `cd output && cp creds_example.yml creds.yml`
|
||||
5. Open `creds.yml` with your favorite text editor (Please don't use Notepad or WordPad. You can use Notepad++, VSCode, Atom, Sublime, or something similar)
|
||||
@@ -91,16 +92,30 @@ Open PowerShell (press windows button on your keyboard and type powershell, it s
|
||||
|
||||
Open PowerShell as described above and run the following commands:
|
||||
|
||||
1. Navigate to your bot's folder, for example `cd ~/Desktop/nadekobot/src/NadekoBot`
|
||||
2. Pull the latest updates (this will fail if you have custom code changes).
|
||||
- If you don't have custom code changes, just run `git pull`
|
||||
- If you do have custom code changes, You have 3 options
|
||||
- Undo all changes with `git checkout -- * && git pull`
|
||||
- Stash changes and try to re-apply them `git stash && git pull && git stash apply`
|
||||
- Commit your changes and resolve merge conflicts `git add . && git commit -m "My commit message" && git pull`
|
||||
3. Re-run the bot `dotnet run -c Release`
|
||||
1. Stop the bot
|
||||
- ⚠️ Make sure you don't have your database, credentials or any other nadekobot folder open in some application, this might prevent some of the steps from executing succesfully
|
||||
2. Navigate to your bot's folder, example:
|
||||
- `cd ~/Desktop/nadekobot`
|
||||
3. Pull the new version
|
||||
- `git pull`
|
||||
- ⚠️ If this fails, you may want to stash or remove your code changes if you don't know how to resolve merge conflicts
|
||||
4. **Backup** old output in case your data is overwritten
|
||||
- `cp -r -fo output/ output-old`
|
||||
5. Build the bot again
|
||||
- `dotnet publish -c Release -o output/ src/NadekoBot/`
|
||||
6. Remove old strings and aliases to avoid overwriting the updated versions of those files
|
||||
- ⚠ If you've modified said files, back them up instead
|
||||
- `rm output-old/data/aliases.yml`
|
||||
- `rm -r output-old/data/strings`
|
||||
7. Copy old data
|
||||
- `cp -Recurse .\output-old\data\ .\output\ -Force`
|
||||
8. Copy creds.yml
|
||||
- `cp output-old/creds.yml output/`
|
||||
9. Run the bot
|
||||
- `cd output`
|
||||
- `dotnet NadekoBot.dll`
|
||||
|
||||
⚠ You're expected to understand that your database will be in `bin/Release/<framework>/data/`, and if `<framework>` gets changed in the future, you will have to move your database manually.
|
||||
🎉 Enjoy
|
||||
|
||||
#### Music prerequisites
|
||||
In order to use music commands, you need ffmpeg and youtube-dl installed.
|
||||
|
@@ -18,7 +18,7 @@ This part is completely optional, **however it's necessary for music and a few o
|
||||
- Open up `creds.yml` and look for `GoogleAPIKey`, paste your API key after the `:`.
|
||||
- It should look like this:
|
||||
```yml
|
||||
GoogleApiKey: "AIzaSyDSci1sdlWQOWNVj1vlXxxxxxbk0oWMEzM",
|
||||
GoogleApiKey: "AIzaSyDSci1sdlWQOWNVj1vlXxxxxxbk0oWMEzM"
|
||||
```
|
||||
- **MashapeKey**
|
||||
- Required for Hearthstone cards.
|
||||
@@ -42,9 +42,8 @@ This part is completely optional, **however it's necessary for music and a few o
|
||||
- Select `Chat Bot` from the Category dropdown
|
||||
- Once created, clicking on your application will show a new Client ID field
|
||||
- Copy it to your creds.yml as shown below
|
||||
- *(if you're adding it as the last key inside your creds.yml, remove the trailling comma from the example below)*
|
||||
```yml
|
||||
TwitchClientId: "516tr61tr1qweqwe86trg3g",
|
||||
TwitchClientId: "516tr61tr1qweqwe86trg3g"
|
||||
```
|
||||
- **LocationIqApiKey**
|
||||
- Optional. Used only for the `.time` command. https://locationiq.com api key (register and you will receive the token in the email).
|
||||
@@ -75,43 +74,93 @@ RestartCommand:
|
||||
For Windows (Source), Linux or OSX, add this to your `creds.yml`
|
||||
|
||||
```yml
|
||||
"RestartCommand": {
|
||||
"Cmd": "dotnet",
|
||||
"Args": "run -c Release"
|
||||
},
|
||||
RestartCommand:
|
||||
Cmd: dotnet
|
||||
Args: "NadekoBot.dll -- {0}"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### End Result
|
||||
|
||||
**This is an example of how the `creds.yml` looks like with multiple owners, the restart command (optional) and all the API keys (also optional):**
|
||||
**This is an example of how the `creds.yml` looks like with multiple owners, the restart command (optional) and some of the API keys (also optional):**
|
||||
|
||||
```yml
|
||||
{
|
||||
"Token": "MTc5MzcyXXX2MDI1ODY3MjY0.ChKs4g.I8J_R9XX0t-QY-0PzXXXiN0-7vo",
|
||||
"OwnerIds": [
|
||||
105635123466156544,
|
||||
145521851676884992,
|
||||
341420590009417729
|
||||
],
|
||||
"GoogleApiKey": "AIzaSyDSci1sdlWQOWNVj1vlXxxxxxbk0oWMEzM",
|
||||
"MashapeKey": "4UrKpcWXc2mshS8RKi00000y8Kf5p1Q8kI6jsn32bmd8oVWiY7",
|
||||
"OsuApiKey": "4c8c8fdff8e1234581725db27fd140a7d93320d6",
|
||||
"CleverbotApiKey": "",
|
||||
"Db": null,
|
||||
"TotalShards": 1,
|
||||
"PatreonAccessToken": "",
|
||||
"PatreonCampaignId": "334038",
|
||||
"RestartCommand": {
|
||||
"Cmd": "NadekoBot.exe"
|
||||
},
|
||||
"ShardRunCommand": "",
|
||||
"ShardRunArguments": "",
|
||||
"ShardRunPort": null,
|
||||
"TwitchClientId": null,
|
||||
"RedisOptions": null
|
||||
}
|
||||
# DO NOT CHANGE
|
||||
version: 1
|
||||
# Bot token. Do not share with anyone ever -> https://discordapp.com/developers/applications/
|
||||
token: 'MTE5Nzc3MDIxMzE5NTc3NjEw.VlhNCw.BuqJFyzdIUAK1PRf1eK1Cu89Jew'
|
||||
# List of Ids of the users who have bot owner permissions
|
||||
# **DO NOT ADD PEOPLE YOU DON'T TRUST**
|
||||
ownerIds: [
|
||||
105635123466156544,
|
||||
145521851676884992,
|
||||
341420590009417729
|
||||
]
|
||||
# The number of shards that the bot will running on.
|
||||
# Leave at 1 if you don't know what you're doing.
|
||||
totalShards: 1
|
||||
# Login to https://console.cloud.google.com, create a new project, go to APIs & Services -> Library -> YouTube Data API and enable it.
|
||||
# Then, go to APIs and Services -> Credentials and click Create credentials -> API key.
|
||||
# Used only for Youtube Data Api (at the moment).
|
||||
googleApiKey: 'AIzaSyDScfdfdfi1sdlWQOWxxxxxbk0oWMEzM'
|
||||
# Settings for voting system for discordbots. Meant for use on global Nadeko.
|
||||
votes:
|
||||
url: ''
|
||||
key: ''
|
||||
# Patreon auto reward system settings.
|
||||
# go to https://www.patreon.com/portal -> my clients -> create client
|
||||
patreon:
|
||||
# Access token. You have to manually update this 1st of each month by refreshing the token on https://patreon.com/portal
|
||||
accessToken: ''
|
||||
# Unused atm
|
||||
refreshToken: ''
|
||||
# Unused atm
|
||||
clientSecret: ''
|
||||
# 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)
|
||||
campaignId: ''
|
||||
# Api key for sending stats to DiscordBotList.
|
||||
botListToken: ''
|
||||
# Official cleverbot api key.
|
||||
cleverbotApiKey: ''
|
||||
# Redis connection string. Don't change if you don't know what you're doing.
|
||||
redisOptions: localhost:6379,syncTimeout=30000,responseTimeout=30000,allowAdmin=true,password=
|
||||
# Database options. Don't change if you don't know what you're doing. Leave null for default values
|
||||
db:
|
||||
# Database type. Only sqlite supported atm
|
||||
type: sqlite
|
||||
# Connection string. Will default to "Data Source=data/NadekoBot.db"
|
||||
connectionString: Data Source=data/NadekoBot.db
|
||||
# Address and port of the coordinator endpoint. Leave empty for default.
|
||||
# Change only if you've changed the coordinator address or port.
|
||||
coordinatorUrl: http://localhost:3442
|
||||
# Api key obtained on https://rapidapi.com (go to MyApps -> Add New App -> Enter Name -> Application key)
|
||||
rapidApiKey: 4UrKpcWXcxxxxxxxxxxxxxxp1Q8kI6jsn32xxxoVWiY7
|
||||
# https://locationiq.com api key (register and you will receive the token in the email).
|
||||
# Used only for .time command.
|
||||
locationIqApiKey:
|
||||
# https://timezonedb.com api key (register and you will receive the token in the email).
|
||||
# Used only for .time command
|
||||
timezoneDbApiKey:
|
||||
# https://pro.coinmarketcap.com/account/ api key. There is a free plan for personal use.
|
||||
# Used for cryptocurrency related commands.
|
||||
coinmarketcapApiKey:
|
||||
# Api key used for Osu related commands. Obtain this key at https://osu.ppy.sh/p/api
|
||||
osuApiKey: 4c8c8fdffdsfdsfsdfsfa33f3f3140a7d93320d6
|
||||
# Command and args which will be used to restart the bot.
|
||||
# Only used if bot is executed directly (NOT through the coordinator)
|
||||
# placeholders:
|
||||
# {0} -> shard id
|
||||
# {1} -> total shards
|
||||
# Linux default
|
||||
# cmd: dotnet
|
||||
# args: "NadekoBot.dll -- {0}"
|
||||
# Windows default
|
||||
# cmd: NadekoBot.exe
|
||||
# args: {0}
|
||||
restartCommand:
|
||||
cmd:
|
||||
args:
|
||||
```
|
||||
|
||||
---
|
||||
@@ -121,13 +170,13 @@ For Windows (Source), Linux or OSX, add this to your `creds.yml`
|
||||
Nadeko saves all settings and data in the database file `NadekoBot.db`, located in:
|
||||
|
||||
- Windows (Updater): `system/data` (can be easily accessed through the `Data` button on the updater)
|
||||
- Windows (Source), Linux and OSX: `NadekoBot/src/NadekoBot/bin/Release/netcoreapp2.1/data/NadekoBot.db`
|
||||
- Windows (Source), Linux and OSX: `nadekobot/output/data/NadekoBot.db`
|
||||
|
||||
In order to open it you will need [SQLite Browser](http://sqlitebrowser.org/).
|
||||
|
||||
*NOTE: You don't have to worry if you don't have the `NadekoBot.db` file, it gets automatically created once you successfully run the bot for the first time.*
|
||||
|
||||
**To make changes:**
|
||||
**To make changes to the database on windows:**
|
||||
|
||||
- Shut your bot down.
|
||||
- Copy the `NadekoBot.db` file to someplace safe. (Back up)
|
||||
@@ -146,20 +195,24 @@ In order to open it you will need [SQLite Browser](http://sqlitebrowser.org/).
|
||||
|
||||
## Sharding your bot
|
||||
|
||||
- **ShardRunCommand**
|
||||
- Command with which to run shards 1+
|
||||
- Required if you're sharding your bot on windows using .exe, or in a custom way.
|
||||
- This internally defaults to `dotnet`
|
||||
- For example, if you want to shard your NadekoBot which you installed using windows installer, you would want to set it to something like this: `C:\Program Files\NadekoBot\system\NadekoBot.exe`
|
||||
- **ShardRunArguments**
|
||||
- Arguments to the shard run command
|
||||
- Required if you're sharding your bot on windows using .exe, or in a custom way.
|
||||
- This internally defaults to `run -c Release --no-build -- {0} {1} {2}` which will be enough to run linux and other 'from source' setups
|
||||
- {0} will be replaced by the `shard ID` of the shard being ran, {1} by the shard 0's process id, and {2} by the port shard communication is happening on
|
||||
- If shard0 (main window) is closed, all other shards will close too
|
||||
- For example, if you want to shard your NadekoBot which you installed using windows installer, you would want to set it to `{0} {1} {2}`
|
||||
- **ShardRunPort**
|
||||
- Bot uses a random UDP port in [5000, 6000] range for communication between shards
|
||||
To run a sharded bot, you will want to run `src/NadekoBot.Coordinator` project.
|
||||
Shards communicate with the coordinator using gRPC
|
||||
To configure your Coordinator, you will need to edit the `src/NadekoBot.Coordinator/coord.yml` file
|
||||
|
||||
```yml
|
||||
# total number of shards
|
||||
TotalShards: 3
|
||||
# How often do shards ping their state back to the coordinator
|
||||
RecheckIntervalMs: 5000
|
||||
# Command to run the shard
|
||||
ShardStartCommand: dotnet
|
||||
# Arguments to run the shard
|
||||
# {0} = shard id
|
||||
# {1} = total number of shards
|
||||
ShardStartArgs: ../../output/NadekoBot.dll -- {0} {1}
|
||||
# How long does it take for the shard to be forcefully restarted once it stops reporting its state
|
||||
UnresponsiveSec: 30
|
||||
```
|
||||
|
||||
[Google Console]: https://console.developers.google.com
|
||||
[DiscordApp]: https://discordapp.com/developers/applications/me
|
||||
|
@@ -1,5 +1,4 @@
|
||||
mkdocs-material==7.1.4
|
||||
mkdocs==1.1.2
|
||||
mkdocs-minify-plugin==0.4.0
|
||||
mkdocs-git-revision-date-localized-plugin==0.9.2
|
||||
mkdocs-material-extensions==1.0.1
|
||||
mkdocs-material>=7.1.4
|
||||
mkdocs>=1.1.2
|
||||
mkdocs-git-revision-date-localized-plugin>=0.9.2
|
||||
mkdocs-material-extensions>=1.0.1
|
||||
|
@@ -41,8 +41,6 @@ extra_css:
|
||||
- stylesheets/theme.css
|
||||
|
||||
plugins:
|
||||
- minify:
|
||||
minify_html: true
|
||||
- git-revision-date-localized:
|
||||
type: date
|
||||
- search
|
||||
@@ -78,7 +76,6 @@ nav:
|
||||
- Windows Guide: guides/windows-guide.md
|
||||
- Linux Guide: guides/linux-guide.md
|
||||
- OSX Guide: guides/osx-guide.md
|
||||
- From Source: guides/from-source.md
|
||||
- Docker Guide (unsupported): guides/docker-guide.md
|
||||
- Commands:
|
||||
- Readme: commands-readme.md
|
||||
|
@@ -1 +1 @@
|
||||
{ "VersionName":"$CI_COMMIT_TAG", "DownloadLink":"https://cdn.nadeko.bot/dl/bot/INSTALLER_FILE_NAME" }
|
||||
[{ "VersionName":"_VERSION_", "DownloadLink":"https://cdn.nadeko.bot/dl/bot/_INSTALLER_FILE_NAME_" }]
|
@@ -1,5 +1,12 @@
|
||||
# total number of shards
|
||||
TotalShards: 3
|
||||
# How often do shards ping their state back to the coordinator
|
||||
RecheckIntervalMs: 5000
|
||||
# Command to run the shard
|
||||
ShardStartCommand: dotnet
|
||||
# Arguments to run the shard
|
||||
# {0} = shard id
|
||||
# {1} = total number of shards
|
||||
ShardStartArgs: run -p "..\NadekoBot\NadekoBot.csproj" --no-build -- {0} {1}
|
||||
# How long does it take for the shard to be forcefully restarted once it stops reporting its state
|
||||
UnresponsiveSec: 30
|
||||
|
@@ -2,12 +2,10 @@
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using AngleSharp.Common;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Modules;
|
||||
using YamlDotNet.Serialization;
|
||||
|
||||
namespace NadekoBot.Tests
|
||||
{
|
||||
|
@@ -102,7 +102,6 @@ namespace NadekoBot
|
||||
.AddSingleton(Client) // discord socket client
|
||||
.AddSingleton(_commandService)
|
||||
.AddSingleton(this)
|
||||
.AddSingleton<IDataCache, RedisCache>()
|
||||
.AddSingleton<ISeria, JsonSeria>()
|
||||
.AddSingleton<IPubSub, RedisPubSub>()
|
||||
.AddSingleton<IConfigSeria, YamlSeria>()
|
||||
@@ -132,10 +131,18 @@ namespace NadekoBot
|
||||
}
|
||||
else
|
||||
{
|
||||
svcs.AddSingleton<ICoordinator, RemoteGrpcCoordinator>()
|
||||
.AddSingleton<IReadyExecutor>(x => (IReadyExecutor)x.GetRequiredService<ICoordinator>());
|
||||
svcs.AddSingleton<RemoteGrpcCoordinator>()
|
||||
.AddSingleton<ICoordinator>(x => x.GetRequiredService<RemoteGrpcCoordinator>())
|
||||
.AddSingleton<IReadyExecutor>(x => x.GetRequiredService<RemoteGrpcCoordinator>());
|
||||
}
|
||||
|
||||
svcs.AddSingleton<RedisLocalDataCache>()
|
||||
.AddSingleton<ILocalDataCache>(x => x.GetRequiredService<RedisLocalDataCache>())
|
||||
.AddSingleton<RedisImagesCache>()
|
||||
.AddSingleton<IImageCache>(x => x.GetRequiredService<RedisImagesCache>())
|
||||
.AddSingleton<IReadyExecutor>(x => x.GetRequiredService<RedisImagesCache>())
|
||||
.AddSingleton<IDataCache, RedisCache>();
|
||||
|
||||
svcs.Scan(scan => scan
|
||||
.FromAssemblyOf<IReadyExecutor>()
|
||||
.AddClasses(classes => classes.AssignableToAny(
|
||||
|
@@ -1,26 +0,0 @@
|
||||
namespace NadekoBot.Common
|
||||
{
|
||||
public enum BotConfigEditType
|
||||
{
|
||||
/// <summary>
|
||||
/// The amount of currency awarded to the winner of the trivia game.
|
||||
/// Default is 0.
|
||||
/// </summary>
|
||||
TriviaCurrencyReward,
|
||||
/// <summary>
|
||||
/// Users can't start trivia games which have smaller win requirement than specified by this setting.
|
||||
/// Default is 0.
|
||||
/// </summary>
|
||||
MinimumTriviaWinReq,
|
||||
/// <summary>
|
||||
/// The amount of XP the user receives when they send a message (which is not too short).
|
||||
/// Default is 3.
|
||||
/// </summary>
|
||||
XpPerMessage,
|
||||
/// <summary>
|
||||
/// This value represents how often the user can receive XP from sending messages.
|
||||
/// Default is 5.
|
||||
/// </summary>
|
||||
XpMinutesTimeout,
|
||||
}
|
||||
}
|
@@ -12,7 +12,7 @@ namespace NadekoBot.Common.Configs
|
||||
public sealed partial class BotConfig : ICloneable<BotConfig>
|
||||
{
|
||||
[Comment(@"DO NOT CHANGE")]
|
||||
public int Version { get; set; }
|
||||
public int Version { get; set; } = 2;
|
||||
|
||||
[Comment(@"Most commands, when executed, have a small colored line
|
||||
next to the response. The color depends whether the command
|
||||
@@ -48,6 +48,11 @@ they will receive this message. Leave empty for no response. The string which wi
|
||||
Supports embeds. How it looks: https://puu.sh/B0BLV.png")]
|
||||
[YamlMember(ScalarStyle = ScalarStyle.Literal)]
|
||||
public string DmHelpText { get; set; }
|
||||
|
||||
[Comment(@"Only users who send a DM to the bot containing one of the specified words will get a DmHelpText response.
|
||||
Case insensitive.
|
||||
Leave empty to reply with DmHelpText to every DM.")]
|
||||
public List<string> DmHelpTextKeywords { get; set; }
|
||||
|
||||
[Comment(@"This is the response for the .h command")]
|
||||
[YamlMember(ScalarStyle = ScalarStyle.Literal)]
|
||||
@@ -89,7 +94,6 @@ See RotatingStatuses submodule in Administration.")]
|
||||
|
||||
public BotConfig()
|
||||
{
|
||||
Version = 1;
|
||||
var color = new ColorConfig();
|
||||
Color = color;
|
||||
DefaultLocale = new CultureInfo("en-US");
|
||||
@@ -127,6 +131,14 @@ See RotatingStatuses submodule in Administration.")]
|
||||
Prefix = ".";
|
||||
RotateStatuses = false;
|
||||
GroupGreets = false;
|
||||
DmHelpTextKeywords = new List<string>()
|
||||
{
|
||||
"help",
|
||||
"commands",
|
||||
"cmds",
|
||||
"module",
|
||||
"can you do"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -89,6 +89,8 @@ namespace NadekoBot.Common.Replacements
|
||||
_reps.TryAdd("%server.id%", () => g is null ? "DM" : g.Id.ToString());
|
||||
_reps.TryAdd("%server.name%", () => g is null ? "DM" : g.Name);
|
||||
_reps.TryAdd("%server.members%", () => g != null && g is SocketGuild sg ? sg.MemberCount.ToString() : "?");
|
||||
_reps.TryAdd("%server.boosters%", () => g.PremiumSubscriptionCount.ToString());
|
||||
_reps.TryAdd("%server.boost_level%", () => ((int)g.PremiumTier).ToString());
|
||||
_reps.TryAdd("%server.time%", () =>
|
||||
{
|
||||
TimeZoneInfo to = TimeZoneInfo.Local;
|
||||
@@ -175,13 +177,13 @@ namespace NadekoBot.Common.Replacements
|
||||
/*OBSOLETE*/
|
||||
_reps.TryAdd("%servers%", () => c.Guilds.Count.ToString());
|
||||
#if !GLOBAL_NADEKO
|
||||
_reps.TryAdd("%users%", () => c.Guilds.Sum(s => s.Users.Count).ToString());
|
||||
_reps.TryAdd("%users%", () => c.Guilds.Sum(g => g.MemberCount).ToString());
|
||||
#endif
|
||||
|
||||
/*NEW*/
|
||||
_reps.TryAdd("%shard.servercount%", () => c.Guilds.Count.ToString());
|
||||
#if !GLOBAL_NADEKO
|
||||
_reps.TryAdd("%shard.usercount%", () => c.Guilds.Sum(s => s.Users.Count).ToString());
|
||||
_reps.TryAdd("%shard.usercount%", () => c.Guilds.Sum(g => g.MemberCount).ToString());
|
||||
#endif
|
||||
_reps.TryAdd("%shard.id%", () => c.ShardId.ToString());
|
||||
return this;
|
||||
|
@@ -53,6 +53,7 @@ namespace NadekoBot.Common.Replacements
|
||||
newEmbedData.Title = Replace(embedData.Title);
|
||||
newEmbedData.Thumbnail = Replace(embedData.Thumbnail);
|
||||
newEmbedData.Image = Replace(embedData.Image);
|
||||
newEmbedData.Url = Replace(embedData.Url);
|
||||
if (embedData.Author != null)
|
||||
{
|
||||
newEmbedData.Author = new SmartTextEmbedAuthor();
|
||||
|
@@ -48,6 +48,7 @@ namespace NadekoBot.Db
|
||||
|
||||
private static IQueryable<GuildConfig> IncludeEverything(this DbSet<GuildConfig> configs)
|
||||
{
|
||||
// todo split query
|
||||
return configs
|
||||
.AsQueryable()
|
||||
.Include(gc => gc.CommandCooldowns)
|
||||
@@ -113,33 +114,24 @@ namespace NadekoBot.Db
|
||||
return config;
|
||||
}
|
||||
|
||||
public static GuildConfig LogSettingsFor(this NadekoContext ctx, ulong guildId)
|
||||
public static LogSetting LogSettingsFor(this NadekoContext ctx, ulong guildId)
|
||||
{
|
||||
var config = ctx
|
||||
.GuildConfigs
|
||||
var logSetting = ctx.LogSettings
|
||||
.AsQueryable()
|
||||
.Include(gc => gc.LogSetting)
|
||||
.ThenInclude(gc => gc.IgnoredChannels)
|
||||
.FirstOrDefault(x => x.GuildId == guildId);
|
||||
.Include(x => x.LogIgnores)
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (config is null)
|
||||
if (logSetting is null)
|
||||
{
|
||||
ctx.GuildConfigs.Add((config = new GuildConfig
|
||||
ctx.LogSettings.Add(logSetting = new ()
|
||||
{
|
||||
GuildId = guildId,
|
||||
Permissions = Permissionv2.GetDefaultPermlist,
|
||||
WarningsInitialized = true,
|
||||
WarnPunishments = DefaultWarnPunishments,
|
||||
}));
|
||||
GuildId = guildId
|
||||
});
|
||||
ctx.SaveChanges();
|
||||
}
|
||||
|
||||
if (!config.WarningsInitialized)
|
||||
{
|
||||
config.WarningsInitialized = true;
|
||||
config.WarnPunishments = DefaultWarnPunishments;
|
||||
}
|
||||
return config;
|
||||
|
||||
return logSetting;
|
||||
}
|
||||
|
||||
public static IEnumerable<GuildConfig> Permissionsv2ForAll(this DbSet<GuildConfig> configs, List<ulong> include)
|
||||
|
@@ -29,11 +29,18 @@ namespace NadekoBot.Services.Database.Models
|
||||
public bool SendChannelGreetMessage { get; set; }
|
||||
public string ChannelGreetMessageText { get; set; } = "Welcome to the %server% server, %user%!";
|
||||
|
||||
#region Boost Message
|
||||
|
||||
public bool SendBoostMessage { get; set; }
|
||||
public string BoostMessage { get; set; } = "%user% just boosted this server!";
|
||||
public ulong BoostMessageChannelId { get; set; }
|
||||
public int BoostMessageDeleteAfter { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
public bool SendChannelByeMessage { get; set; }
|
||||
public string ChannelByeMessageText { get; set; } = "%user% has left!";
|
||||
|
||||
public LogSetting LogSetting { get; set; } = new LogSetting();
|
||||
|
||||
//self assignable roles
|
||||
public bool ExclusiveSelfAssignedRoles { get; set; }
|
||||
public bool AutoDeleteSelfAssignedRoleMessages { 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,
|
||||
}
|
||||
}
|
@@ -1,8 +1,8 @@
|
||||
namespace NadekoBot.Services.Database.Models
|
||||
{
|
||||
public class IgnoredLogChannel : DbEntity
|
||||
public class ImageOnlyChannel : DbEntity
|
||||
{
|
||||
public LogSetting LogSetting { get; set; }
|
||||
public ulong GuildId { get; set; }
|
||||
public ulong ChannelId { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,12 +2,11 @@
|
||||
|
||||
namespace NadekoBot.Services.Database.Models
|
||||
{
|
||||
|
||||
public class LogSetting : DbEntity
|
||||
{
|
||||
public HashSet<IgnoredLogChannel> IgnoredChannels { get; set; } = new HashSet<IgnoredLogChannel>();
|
||||
public HashSet<IgnoredVoicePresenceChannel> IgnoredVoicePresenceChannelIds { get; set; } = new HashSet<IgnoredVoicePresenceChannel>();
|
||||
public List<IgnoredLogItem> LogIgnores { get; set; } = new List<IgnoredLogItem>();
|
||||
|
||||
public ulong GuildId { get; set; }
|
||||
public ulong? LogOtherId { get; set; }
|
||||
public ulong? MessageUpdatedId { get; set; }
|
||||
public ulong? MessageDeletedId { get; set; }
|
||||
|
@@ -2,11 +2,9 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Services;
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Db.Models;
|
||||
|
||||
namespace NadekoBot.Services.Database
|
||||
@@ -44,8 +42,8 @@ namespace NadekoBot.Services.Database
|
||||
|
||||
//logging
|
||||
public DbSet<LogSetting> LogSettings { get; set; }
|
||||
public DbSet<IgnoredLogChannel> IgnoredLogChannels { get; set; }
|
||||
public DbSet<IgnoredVoicePresenceChannel> IgnoredVoicePresenceCHannels { get; set; }
|
||||
public DbSet<IgnoredLogItem> IgnoredLogChannels { get; set; }
|
||||
|
||||
public DbSet<RotatingPlayingStatus> RotatingStatus { get; set; }
|
||||
public DbSet<BlacklistEntry> Blacklist { get; set; }
|
||||
@@ -60,6 +58,7 @@ namespace NadekoBot.Services.Database
|
||||
public DbSet<Repeater> Repeaters { get; set; }
|
||||
public DbSet<Poll> Poll { get; set; }
|
||||
public DbSet<WaifuInfo> WaifuInfo { get; set; }
|
||||
public DbSet<ImageOnlyChannel> ImageOnlyChannels { get; set; }
|
||||
|
||||
public NadekoContext(DbContextOptions<NadekoContext> options) : base(options)
|
||||
{
|
||||
@@ -328,6 +327,36 @@ namespace NadekoBot.Services.Database
|
||||
.HasDefaultValue(100);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Reaction roles
|
||||
|
||||
modelBuilder.Entity<ReactionRoleMessage>(rrm => rrm
|
||||
.HasMany(x => x.ReactionRoles)
|
||||
.WithOne()
|
||||
.OnDelete(DeleteBehavior.Cascade));
|
||||
|
||||
#endregion
|
||||
|
||||
#region LogSettings
|
||||
|
||||
modelBuilder.Entity<LogSetting>(ls => ls
|
||||
.HasIndex(x => x.GuildId)
|
||||
.IsUnique());
|
||||
|
||||
modelBuilder.Entity<LogSetting>(ls => ls
|
||||
.HasMany(x => x.LogIgnores)
|
||||
.WithOne(x => x.LogSetting)
|
||||
.OnDelete(DeleteBehavior.Cascade));
|
||||
|
||||
modelBuilder.Entity<IgnoredLogItem>(ili => ili
|
||||
.HasIndex(x => new { x.LogSettingId, x.LogItemId, x.ItemType })
|
||||
.IsUnique());
|
||||
|
||||
#endregion
|
||||
|
||||
modelBuilder.Entity<ImageOnlyChannel>(ioc => ioc
|
||||
.HasIndex(x => x.ChannelId)
|
||||
.IsUnique());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
2626
src/NadekoBot/Migrations/20210911225622_rero-cascade.Designer.cs
generated
Normal file
2626
src/NadekoBot/Migrations/20210911225622_rero-cascade.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
37
src/NadekoBot/Migrations/20210911225622_rero-cascade.cs
Normal file
37
src/NadekoBot/Migrations/20210911225622_rero-cascade.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
public partial class rerocascade : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_ReactionRole_ReactionRoleMessage_ReactionRoleMessageId",
|
||||
table: "ReactionRole");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_ReactionRole_ReactionRoleMessage_ReactionRoleMessageId",
|
||||
table: "ReactionRole",
|
||||
column: "ReactionRoleMessageId",
|
||||
principalTable: "ReactionRoleMessage",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_ReactionRole_ReactionRoleMessage_ReactionRoleMessageId",
|
||||
table: "ReactionRole");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_ReactionRole_ReactionRoleMessage_ReactionRoleMessageId",
|
||||
table: "ReactionRole",
|
||||
column: "ReactionRoleMessageId",
|
||||
principalTable: "ReactionRoleMessage",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
}
|
||||
}
|
||||
}
|
2638
src/NadekoBot/Migrations/20210912182515_boost-messages.Designer.cs
generated
Normal file
2638
src/NadekoBot/Migrations/20210912182515_boost-messages.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
56
src/NadekoBot/Migrations/20210912182515_boost-messages.cs
Normal file
56
src/NadekoBot/Migrations/20210912182515_boost-messages.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
public partial class boostmessages : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "BoostMessage",
|
||||
table: "GuildConfigs",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<ulong>(
|
||||
name: "BoostMessageChannelId",
|
||||
table: "GuildConfigs",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0ul);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "BoostMessageDeleteAfter",
|
||||
table: "GuildConfigs",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "SendBoostMessage",
|
||||
table: "GuildConfigs",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BoostMessage",
|
||||
table: "GuildConfigs");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BoostMessageChannelId",
|
||||
table: "GuildConfigs");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BoostMessageDeleteAfter",
|
||||
table: "GuildConfigs");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SendBoostMessage",
|
||||
table: "GuildConfigs");
|
||||
}
|
||||
}
|
||||
}
|
2630
src/NadekoBot/Migrations/20210912200106_logsettings-independence.Designer.cs
generated
Normal file
2630
src/NadekoBot/Migrations/20210912200106_logsettings-independence.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,69 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
public partial class logsettingsindependence : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<ulong>(
|
||||
name: "GuildId",
|
||||
table: "LogSettings",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0ul);
|
||||
|
||||
migrationBuilder.Sql(
|
||||
@"UPDATE LogSettings SET GuildId = (SELECT GuildId FROM GuildConfigs WHERE LogSettingId = LogSettings.Id);
|
||||
DELETE FROM LogSettings WHERE GuildId = 0;");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_GuildConfigs_LogSettings_LogSettingId",
|
||||
table: "GuildConfigs");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_GuildConfigs_LogSettingId",
|
||||
table: "GuildConfigs");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LogSettingId",
|
||||
table: "GuildConfigs");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_LogSettings_GuildId",
|
||||
table: "LogSettings",
|
||||
column: "GuildId",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_LogSettings_GuildId",
|
||||
table: "LogSettings");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "GuildId",
|
||||
table: "LogSettings");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "LogSettingId",
|
||||
table: "GuildConfigs",
|
||||
type: "INTEGER",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_GuildConfigs_LogSettingId",
|
||||
table: "GuildConfigs",
|
||||
column: "LogSettingId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_GuildConfigs_LogSettings_LogSettingId",
|
||||
table: "GuildConfigs",
|
||||
column: "LogSettingId",
|
||||
principalTable: "LogSettings",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
}
|
||||
}
|
||||
}
|
2653
src/NadekoBot/Migrations/20210914180026_image-only-channels.Designer.cs
generated
Normal file
2653
src/NadekoBot/Migrations/20210914180026_image-only-channels.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
public partial class imageonlychannels : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ImageOnlyChannels",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ImageOnlyChannels", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ImageOnlyChannels_ChannelId",
|
||||
table: "ImageOnlyChannels",
|
||||
column: "ChannelId",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ImageOnlyChannels");
|
||||
}
|
||||
}
|
||||
}
|
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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -14,7 +14,7 @@ namespace NadekoBot.Migrations
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "5.0.7");
|
||||
.HasAnnotation("ProductVersion", "5.0.8");
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.ClubApplicants", b =>
|
||||
{
|
||||
@@ -741,6 +741,15 @@ namespace NadekoBot.Migrations
|
||||
b.Property<bool>("AutoDeleteSelfAssignedRoleMessages")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("BoostMessage")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<ulong>("BoostMessageChannelId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("BoostMessageDeleteAfter")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("ByeMessageChannelId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
@@ -786,9 +795,6 @@ namespace NadekoBot.Migrations
|
||||
b.Property<string>("Locale")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("LogSettingId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("MuteRoleName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@@ -801,6 +807,9 @@ namespace NadekoBot.Migrations
|
||||
b.Property<string>("Prefix")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("SendBoostMessage")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("SendChannelByeMessage")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
@@ -833,31 +842,33 @@ namespace NadekoBot.Migrations
|
||||
b.HasIndex("GuildId")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("LogSettingId");
|
||||
|
||||
b.HasIndex("WarnExpireHours");
|
||||
|
||||
b.ToTable("GuildConfigs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b =>
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogItem", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("ChannelId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("LogSettingId")
|
||||
b.Property<int>("ItemType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("LogItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("LogSettingId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("LogSettingId");
|
||||
b.HasIndex("LogSettingId", "LogItemId", "ItemType")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("IgnoredLogChannels");
|
||||
});
|
||||
@@ -884,6 +895,29 @@ namespace NadekoBot.Migrations
|
||||
b.ToTable("IgnoredVoicePresenceCHannels");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ImageOnlyChannel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("ChannelId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<ulong>("GuildId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChannelId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ImageOnlyChannels");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -902,6 +936,9 @@ namespace NadekoBot.Migrations
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<ulong>("GuildId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong?>("LogOtherId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
@@ -940,6 +977,9 @@ namespace NadekoBot.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("GuildId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("LogSettings");
|
||||
});
|
||||
|
||||
@@ -2233,20 +2273,13 @@ namespace NadekoBot.Migrations
|
||||
b.Navigation("GuildConfig");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b =>
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogItem", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting")
|
||||
.WithMany()
|
||||
.HasForeignKey("LogSettingId");
|
||||
|
||||
b.Navigation("LogSetting");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting")
|
||||
.WithMany("IgnoredChannels")
|
||||
.HasForeignKey("LogSettingId");
|
||||
.WithMany("LogIgnores")
|
||||
.HasForeignKey("LogSettingId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("LogSetting");
|
||||
});
|
||||
@@ -2254,7 +2287,7 @@ namespace NadekoBot.Migrations
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting")
|
||||
.WithMany("IgnoredVoicePresenceChannelIds")
|
||||
.WithMany()
|
||||
.HasForeignKey("LogSettingId");
|
||||
|
||||
b.Navigation("LogSetting");
|
||||
@@ -2307,7 +2340,8 @@ namespace NadekoBot.Migrations
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.ReactionRoleMessage", null)
|
||||
.WithMany("ReactionRoles")
|
||||
.HasForeignKey("ReactionRoleMessageId");
|
||||
.HasForeignKey("ReactionRoleMessageId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleMessage", b =>
|
||||
@@ -2570,9 +2604,7 @@ namespace NadekoBot.Migrations
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b =>
|
||||
{
|
||||
b.Navigation("IgnoredChannels");
|
||||
|
||||
b.Navigation("IgnoredVoicePresenceChannelIds");
|
||||
b.Navigation("LogIgnores");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b =>
|
||||
|
@@ -13,13 +13,31 @@ namespace NadekoBot.Modules.Administration
|
||||
{
|
||||
public partial class Administration : NadekoModule<AdministrationService>
|
||||
{
|
||||
private readonly ImageOnlyChannelService _imageOnly;
|
||||
|
||||
public Administration(ImageOnlyChannelService imageOnly)
|
||||
{
|
||||
_imageOnly = imageOnly;
|
||||
}
|
||||
|
||||
public enum List
|
||||
{
|
||||
List = 0,
|
||||
Ls = 0
|
||||
}
|
||||
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[BotPerm(GuildPerm.Administrator)]
|
||||
public async Task ImageOnlyChannel(StoopidTime time = null)
|
||||
{
|
||||
var newValue = _imageOnly.ToggleImageOnlyChannel(ctx.Guild.Id, ctx.Channel.Id);
|
||||
if (newValue)
|
||||
await ReplyConfirmLocalizedAsync(strs.imageonly_enable);
|
||||
else
|
||||
await ReplyPendingLocalizedAsync(strs.imageonly_disable);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
|
@@ -16,6 +16,7 @@ namespace NadekoBot.Modules.Administration
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
public async Task AutoAssignRole([Leftover] IRole role)
|
||||
{
|
||||
var guser = (IGuildUser) ctx.User;
|
||||
@@ -47,6 +48,7 @@ namespace NadekoBot.Modules.Administration
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
public async Task AutoAssignRole()
|
||||
{
|
||||
if (!_service.TryGetRoles(ctx.Guild.Id, out var roles))
|
||||
|
@@ -7,6 +7,7 @@ using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Administration.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -18,12 +19,6 @@ namespace NadekoBot.Modules.Administration
|
||||
[NoPublicBot]
|
||||
public class LogCommands : NadekoSubmodule<ILogCommandService>
|
||||
{
|
||||
public enum EnableDisable
|
||||
{
|
||||
Enable,
|
||||
Disable
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
@@ -43,14 +38,51 @@ namespace NadekoBot.Modules.Administration
|
||||
[OwnerOnly]
|
||||
public async Task LogIgnore()
|
||||
{
|
||||
var channel = (ITextChannel)ctx.Channel;
|
||||
var settings = _service.GetGuildLogSettings(ctx.Guild.Id);
|
||||
|
||||
var removed = _service.LogIgnore(ctx.Guild.Id, ctx.Channel.Id);
|
||||
var chs = settings?.LogIgnores.Where(x => x.ItemType == IgnoredItemType.Channel).ToList()
|
||||
?? new List<IgnoredLogItem>();
|
||||
var usrs = settings?.LogIgnores.Where(x => x.ItemType == IgnoredItemType.User).ToList()
|
||||
?? new List<IgnoredLogItem>();
|
||||
|
||||
var eb = _eb.Create(ctx)
|
||||
.WithOkColor()
|
||||
.AddField(GetText(strs.log_ignored_channels),
|
||||
chs.Count == 0 ? "-" : string.Join('\n', chs.Select(x => $"{x.LogItemId} | <#{x.LogItemId}>")))
|
||||
.AddField(GetText(strs.log_ignored_users),
|
||||
usrs.Count == 0 ? "-" : string.Join('\n', usrs.Select(x => $"{x.LogItemId} | <@{x.LogItemId}>")));
|
||||
|
||||
await ctx.Channel.EmbedAsync(eb);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[OwnerOnly]
|
||||
public async Task LogIgnore([Leftover]ITextChannel target)
|
||||
{
|
||||
target ??= (ITextChannel)ctx.Channel;
|
||||
|
||||
var removed = _service.LogIgnore(ctx.Guild.Id, target.Id, IgnoredItemType.Channel);
|
||||
|
||||
if (!removed)
|
||||
await ReplyConfirmLocalizedAsync(strs.log_ignore(Format.Bold(channel.Mention + "(" + channel.Id + ")"))).ConfigureAwait(false);
|
||||
await ReplyConfirmLocalizedAsync(strs.log_ignore_chan(Format.Bold(target.Mention + "(" + target.Id + ")"))).ConfigureAwait(false);
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.log_not_ignore(Format.Bold(channel.Mention + "(" + channel.Id + ")"))).ConfigureAwait(false);
|
||||
await ReplyConfirmLocalizedAsync(strs.log_not_ignore_chan(Format.Bold(target.Mention + "(" + target.Id + ")"))).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[OwnerOnly]
|
||||
public async Task LogIgnore([Leftover]IUser target)
|
||||
{
|
||||
var removed = _service.LogIgnore(ctx.Guild.Id, target.Id, IgnoredItemType.User);
|
||||
|
||||
if (!removed)
|
||||
await ReplyConfirmLocalizedAsync(strs.log_ignore_user(Format.Bold(target.Mention + "(" + target.Id + ")"))).ConfigureAwait(false);
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.log_not_ignore_user(Format.Bold(target.Mention + "(" + target.Id + ")"))).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
@@ -90,7 +90,7 @@ namespace NadekoBot.Modules.Administration
|
||||
return;
|
||||
|
||||
await _service.TimedMute(user, ctx.User, time.Time, reason: reason).ConfigureAwait(false);
|
||||
await ReplyErrorLocalizedAsync(strs.user_muted_time(Format.Bold(user.ToString()), (int)time.Time.TotalMinutes));
|
||||
await ReplyConfirmLocalizedAsync(strs.user_muted_time(Format.Bold(user.ToString()), (int)time.Time.TotalMinutes));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -150,7 +150,7 @@ namespace NadekoBot.Modules.Administration
|
||||
return;
|
||||
|
||||
await _service.TimedMute(user, ctx.User, time.Time, MuteType.Chat, reason: reason).ConfigureAwait(false);
|
||||
await ReplyErrorLocalizedAsync(strs.user_chat_mute_time(Format.Bold(user.ToString()), (int)time.Time.TotalMinutes));
|
||||
await ReplyConfirmLocalizedAsync(strs.user_chat_mute_time(Format.Bold(user.ToString()), (int)time.Time.TotalMinutes));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -209,7 +209,7 @@ namespace NadekoBot.Modules.Administration
|
||||
return;
|
||||
|
||||
await _service.TimedMute(user, ctx.User, time.Time, MuteType.Voice, reason: reason).ConfigureAwait(false);
|
||||
await ReplyErrorLocalizedAsync(strs.user_voice_mute_time(Format.Bold(user.ToString()), (int)time.Time.TotalMinutes));
|
||||
await ReplyConfirmLocalizedAsync(strs.user_voice_mute_time(Format.Bold(user.ToString()), (int)time.Time.TotalMinutes));
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
@@ -61,7 +61,7 @@ namespace NadekoBot.Modules.Administration
|
||||
if (msg is null)
|
||||
return;
|
||||
|
||||
await ReplyErrorLocalizedAsync(strs.reprm(msg));
|
||||
await ReplyConfirmLocalizedAsync(strs.reprm(msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -28,7 +28,7 @@ namespace NadekoBot.Modules.Administration
|
||||
return;
|
||||
}
|
||||
|
||||
await ReplyErrorLocalizedAsync(strs.protection_not_running("Anti-Alt"));
|
||||
await ReplyConfirmLocalizedAsync(strs.protection_not_running("Anti-Alt"));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
@@ -69,11 +69,11 @@ namespace NadekoBot.Modules.Administration
|
||||
{
|
||||
if (_service.TryStopAntiRaid(ctx.Guild.Id))
|
||||
{
|
||||
return ReplyErrorLocalizedAsync(strs.prot_disable("Anti-Raid"));
|
||||
return ReplyConfirmLocalizedAsync(strs.prot_disable("Anti-Raid"));
|
||||
}
|
||||
else
|
||||
{
|
||||
return ReplyErrorLocalizedAsync(strs.protection_not_running("Anti-Raid"));
|
||||
return ReplyPendingLocalizedAsync(strs.protection_not_running("Anti-Raid"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,11 +145,11 @@ namespace NadekoBot.Modules.Administration
|
||||
{
|
||||
if (_service.TryStopAntiSpam(ctx.Guild.Id))
|
||||
{
|
||||
return ReplyErrorLocalizedAsync(strs.prot_disable("Anti-Spam"));
|
||||
return ReplyConfirmLocalizedAsync(strs.prot_disable("Anti-Spam"));
|
||||
}
|
||||
else
|
||||
{
|
||||
return ReplyErrorLocalizedAsync(strs.protection_not_running("Anti-Spam"));
|
||||
return ReplyPendingLocalizedAsync(strs.protection_not_running("Anti-Spam"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -27,13 +27,13 @@ namespace NadekoBot.Modules.Administration
|
||||
_services = services;
|
||||
}
|
||||
|
||||
public async Task InternalReactionRoles(bool exclusive, params string[] input)
|
||||
public async Task InternalReactionRoles(bool exclusive, ulong? messageId, params string[] input)
|
||||
{
|
||||
var msgs = await ((SocketTextChannel)ctx.Channel).GetMessagesAsync().FlattenAsync().ConfigureAwait(false);
|
||||
var prev = (IUserMessage)msgs.FirstOrDefault(x => x is IUserMessage && x.Id != ctx.Message.Id);
|
||||
|
||||
if (prev is null)
|
||||
return;
|
||||
var target = messageId is ulong msgId
|
||||
? await ctx.Channel.GetMessageAsync(msgId).ConfigureAwait(false)
|
||||
: (await ctx.Channel.GetMessagesAsync(2).FlattenAsync().ConfigureAwait(false))
|
||||
.Skip(1)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (input.Length % 2 != 0)
|
||||
return;
|
||||
@@ -69,7 +69,7 @@ namespace NadekoBot.Modules.Administration
|
||||
{
|
||||
try
|
||||
{
|
||||
await prev.AddReactionAsync(x.emote, new RequestOptions()
|
||||
await target.AddReactionAsync(x.emote, new RequestOptions()
|
||||
{
|
||||
RetryMode = RetryMode.Retry502 | RetryMode.RetryRatelimit
|
||||
}).ConfigureAwait(false);
|
||||
@@ -86,8 +86,8 @@ namespace NadekoBot.Modules.Administration
|
||||
if (_service.Add(ctx.Guild.Id, new ReactionRoleMessage()
|
||||
{
|
||||
Exclusive = exclusive,
|
||||
MessageId = prev.Id,
|
||||
ChannelId = prev.Channel.Id,
|
||||
MessageId = target.Id,
|
||||
ChannelId = target.Channel.Id,
|
||||
ReactionRoles = all.Select(x =>
|
||||
{
|
||||
return new ReactionRole()
|
||||
@@ -106,6 +106,24 @@ namespace NadekoBot.Modules.Administration
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NoPublicBot]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[Priority(0)]
|
||||
public Task ReactionRoles(ulong messageId, params string[] input) =>
|
||||
InternalReactionRoles(false, messageId, input);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NoPublicBot]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[Priority(1)]
|
||||
public Task ReactionRoles(ulong messageId, Exclude _, params string[] input) =>
|
||||
InternalReactionRoles(true, messageId, input);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NoPublicBot]
|
||||
@@ -113,7 +131,7 @@ namespace NadekoBot.Modules.Administration
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[Priority(0)]
|
||||
public Task ReactionRoles(params string[] input) =>
|
||||
InternalReactionRoles(false, input);
|
||||
InternalReactionRoles(false, null, input);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
@@ -122,7 +140,7 @@ namespace NadekoBot.Modules.Administration
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[Priority(1)]
|
||||
public Task ReactionRoles(Exclude _, params string[] input) =>
|
||||
InternalReactionRoles(true, input);
|
||||
InternalReactionRoles(true, null, input);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
@@ -171,7 +189,7 @@ namespace NadekoBot.Modules.Administration
|
||||
index--;
|
||||
var rr = rrs[index];
|
||||
_service.Remove(ctx.Guild.Id, index);
|
||||
await ReplyErrorLocalizedAsync(strs.reaction_role_removed(index + 1));
|
||||
await ReplyConfirmLocalizedAsync(strs.reaction_role_removed(index + 1));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
@@ -24,11 +24,11 @@ namespace NadekoBot.Modules.Administration
|
||||
|
||||
if (newVal)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.adsarm_enable(Prefix));
|
||||
await ReplyConfirmLocalizedAsync(strs.adsarm_enable(Prefix));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.adsarm_disable(Prefix));
|
||||
await ReplyConfirmLocalizedAsync(strs.adsarm_disable(Prefix));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -88,7 +88,7 @@ namespace NadekoBot.Modules.Administration
|
||||
};
|
||||
_service.AddNewAutoCommand(cmd);
|
||||
|
||||
await ReplyErrorLocalizedAsync(strs.autocmd_add(Format.Code(Format.Sanitize(cmdText)), cmd.Interval));
|
||||
await ReplyConfirmLocalizedAsync(strs.autocmd_add(Format.Code(Format.Sanitize(cmdText)), cmd.Interval));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
@@ -226,7 +226,7 @@ namespace NadekoBot.Modules.Administration
|
||||
if (enabled)
|
||||
await ReplyConfirmLocalizedAsync(strs.fwdm_start).ConfigureAwait(false);
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.fwdm_stop).ConfigureAwait(false);
|
||||
await ReplyPendingLocalizedAsync(strs.fwdm_stop).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
@@ -238,7 +238,7 @@ namespace NadekoBot.Modules.Administration
|
||||
if (enabled)
|
||||
await ReplyConfirmLocalizedAsync(strs.fwall_start).ConfigureAwait(false);
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.fwall_stop).ConfigureAwait(false);
|
||||
await ReplyPendingLocalizedAsync(strs.fwall_stop).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
|
||||
@@ -376,7 +376,7 @@ namespace NadekoBot.Modules.Administration
|
||||
var curUser = await ctx.Guild.GetCurrentUserAsync().ConfigureAwait(false);
|
||||
await curUser.ModifyAsync(u => u.Nickname = newNick).ConfigureAwait(false);
|
||||
|
||||
await ReplyErrorLocalizedAsync(strs.bot_nick(Format.Bold(newNick) ?? "-"));
|
||||
await ReplyConfirmLocalizedAsync(strs.bot_nick(Format.Bold(newNick) ?? "-"));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
@@ -395,7 +395,7 @@ namespace NadekoBot.Modules.Administration
|
||||
|
||||
await gu.ModifyAsync(u => u.Nickname = newNick).ConfigureAwait(false);
|
||||
|
||||
await ReplyErrorLocalizedAsync(strs.user_nick(Format.Bold(gu.ToString()), Format.Bold(newNick) ?? "-"));
|
||||
await ReplyConfirmLocalizedAsync(strs.user_nick(Format.Bold(gu.ToString()), Format.Bold(newNick) ?? "-"));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
@@ -496,7 +496,7 @@ namespace NadekoBot.Modules.Administration
|
||||
public async Task ImagesReload()
|
||||
{
|
||||
await _service.ReloadImagesAsync();
|
||||
await ReplyErrorLocalizedAsync(strs.images_loading);
|
||||
await ReplyConfirmLocalizedAsync(strs.images_loading);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
@@ -12,6 +12,62 @@ namespace NadekoBot.Modules.Administration
|
||||
[Group]
|
||||
public class ServerGreetCommands : NadekoSubmodule<GreetSettingsService>
|
||||
{
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageGuild)]
|
||||
public async Task Boost()
|
||||
{
|
||||
var enabled = await _service.ToggleBoost(ctx.Guild.Id, ctx.Channel.Id);
|
||||
|
||||
if (enabled)
|
||||
await ReplyConfirmLocalizedAsync(strs.boost_on).ConfigureAwait(false);
|
||||
else
|
||||
await ReplyPendingLocalizedAsync(strs.boost_off).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageGuild)]
|
||||
public async Task BoostDel(int timer = 30)
|
||||
{
|
||||
if (timer < 0 || timer > 600)
|
||||
return;
|
||||
|
||||
await _service.SetBoostDel(ctx.Guild.Id, timer);
|
||||
|
||||
if (timer > 0)
|
||||
await ReplyConfirmLocalizedAsync(strs.boostdel_on(timer));
|
||||
else
|
||||
await ReplyPendingLocalizedAsync(strs.boostdel_off).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageGuild)]
|
||||
public Task BoostMsg()
|
||||
{
|
||||
var boostMessage = _service.GetBoostMessage(ctx.Guild.Id);
|
||||
return ReplyConfirmLocalizedAsync(strs.boostmsg_cur(boostMessage?.SanitizeMentions()));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageGuild)]
|
||||
public async Task BoostMsg([Leftover] string text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
await BoostMsg().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var sendBoostEnabled = _service.SetBoostMessage(ctx.Guild.Id, ref text);
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.boostmsg_new).ConfigureAwait(false);
|
||||
if (!sendBoostEnabled)
|
||||
await ReplyPendingLocalizedAsync(strs.boostmsg_enable($"`{Prefix}boost`"));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageGuild)]
|
||||
@@ -23,9 +79,9 @@ namespace NadekoBot.Modules.Administration
|
||||
await _service.SetGreetDel(ctx.Guild.Id, timer).ConfigureAwait(false);
|
||||
|
||||
if (timer > 0)
|
||||
await ReplyErrorLocalizedAsync(strs.greetdel_on(timer));
|
||||
await ReplyConfirmLocalizedAsync(strs.greetdel_on(timer));
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.greetdel_off).ConfigureAwait(false);
|
||||
await ReplyPendingLocalizedAsync(strs.greetdel_off).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
@@ -38,7 +94,7 @@ namespace NadekoBot.Modules.Administration
|
||||
if (enabled)
|
||||
await ReplyConfirmLocalizedAsync(strs.greet_on).ConfigureAwait(false);
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.greet_off).ConfigureAwait(false);
|
||||
await ReplyPendingLocalizedAsync(strs.greet_off).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
@@ -46,8 +102,8 @@ namespace NadekoBot.Modules.Administration
|
||||
[UserPerm(GuildPerm.ManageGuild)]
|
||||
public Task GreetMsg()
|
||||
{
|
||||
string greetMsg = _service.GetGreetMsg(ctx.Guild.Id);
|
||||
return ReplyErrorLocalizedAsync(strs.greetmsg_cur(greetMsg?.SanitizeMentions()));
|
||||
var greetMsg = _service.GetGreetMsg(ctx.Guild.Id);
|
||||
return ReplyConfirmLocalizedAsync(strs.greetmsg_cur(greetMsg?.SanitizeMentions()));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
@@ -65,7 +121,7 @@ namespace NadekoBot.Modules.Administration
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.greetmsg_new).ConfigureAwait(false);
|
||||
if (!sendGreetEnabled)
|
||||
await ReplyErrorLocalizedAsync(strs.greetmsg_enable($"`{Prefix}greet`"));
|
||||
await ReplyPendingLocalizedAsync(strs.greetmsg_enable($"`{Prefix}greet`"));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
@@ -87,7 +143,7 @@ namespace NadekoBot.Modules.Administration
|
||||
public Task GreetDmMsg()
|
||||
{
|
||||
var dmGreetMsg = _service.GetDmGreetMsg(ctx.Guild.Id);
|
||||
return ReplyErrorLocalizedAsync(strs.greetdmmsg_cur(dmGreetMsg?.SanitizeMentions()));
|
||||
return ReplyConfirmLocalizedAsync(strs.greetdmmsg_cur(dmGreetMsg?.SanitizeMentions()));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
@@ -105,7 +161,7 @@ namespace NadekoBot.Modules.Administration
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.greetdmmsg_new).ConfigureAwait(false);
|
||||
if (!sendGreetEnabled)
|
||||
await ReplyErrorLocalizedAsync(strs.greetdmmsg_enable($"`{Prefix}greetdm`"));
|
||||
await ReplyPendingLocalizedAsync(strs.greetdmmsg_enable($"`{Prefix}greetdm`"));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
@@ -127,7 +183,7 @@ namespace NadekoBot.Modules.Administration
|
||||
public Task ByeMsg()
|
||||
{
|
||||
var byeMsg = _service.GetByeMessage(ctx.Guild.Id);
|
||||
return ReplyErrorLocalizedAsync(strs.byemsg_cur(byeMsg?.SanitizeMentions()));
|
||||
return ReplyConfirmLocalizedAsync(strs.byemsg_cur(byeMsg?.SanitizeMentions()));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
@@ -145,7 +201,7 @@ namespace NadekoBot.Modules.Administration
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.byemsg_new).ConfigureAwait(false);
|
||||
if (!sendByeEnabled)
|
||||
await ReplyErrorLocalizedAsync(strs.byemsg_enable($"`{Prefix}bye`"));
|
||||
await ReplyPendingLocalizedAsync(strs.byemsg_enable($"`{Prefix}bye`"));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
@@ -156,9 +212,9 @@ namespace NadekoBot.Modules.Administration
|
||||
await _service.SetByeDel(ctx.Guild.Id, timer).ConfigureAwait(false);
|
||||
|
||||
if (timer > 0)
|
||||
await ReplyErrorLocalizedAsync(strs.byedel_on(timer));
|
||||
await ReplyConfirmLocalizedAsync(strs.byedel_on(timer));
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.byedel_off).ConfigureAwait(false);
|
||||
await ReplyPendingLocalizedAsync(strs.byedel_off).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -174,7 +230,7 @@ namespace NadekoBot.Modules.Administration
|
||||
var enabled = _service.GetByeEnabled(ctx.Guild.Id);
|
||||
if (!enabled)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.byemsg_enable($"`{Prefix}bye`"));
|
||||
await ReplyPendingLocalizedAsync(strs.byemsg_enable($"`{Prefix}bye`"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +246,7 @@ namespace NadekoBot.Modules.Administration
|
||||
var enabled = _service.GetGreetEnabled(ctx.Guild.Id);
|
||||
if (!enabled)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.greetmsg_enable($"`{Prefix}greet`"));
|
||||
await ReplyPendingLocalizedAsync(strs.greetmsg_enable($"`{Prefix}greet`"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +266,7 @@ namespace NadekoBot.Modules.Administration
|
||||
await ctx.WarningAsync();
|
||||
var enabled = _service.GetGreetDmEnabled(ctx.Guild.Id);
|
||||
if (!enabled)
|
||||
await ReplyErrorLocalizedAsync(strs.greetdmmsg_enable($"`{Prefix}greetdm`"));
|
||||
await ReplyPendingLocalizedAsync(strs.greetdmmsg_enable($"`{Prefix}greetdm`"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,7 @@
|
||||
using System;
|
||||
using Discord;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Collections;
|
||||
using NadekoBot.Common.Replacements;
|
||||
using NadekoBot.Services;
|
||||
@@ -24,14 +22,11 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
|
||||
private readonly DbService _db;
|
||||
private readonly ILogCommandService _logService;
|
||||
private readonly IEmbedBuilderService _eb;
|
||||
|
||||
public AdministrationService(Bot bot, CommandHandler cmdHandler, DbService db,
|
||||
ILogCommandService logService, IEmbedBuilderService eb)
|
||||
public AdministrationService(Bot bot, CommandHandler cmdHandler, DbService db, ILogCommandService logService)
|
||||
{
|
||||
_db = db;
|
||||
_logService = logService;
|
||||
_eb = eb;
|
||||
|
||||
DeleteMessagesOnCommand = new ConcurrentHashSet<ulong>(bot.AllGuildConfigs
|
||||
.Where(g => g.DeleteMessageOnCommand)
|
||||
|
@@ -0,0 +1,187 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Net;
|
||||
using Discord.WebSocket;
|
||||
using LinqToDB;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using NadekoBot.Common.Collections;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Services;
|
||||
using Serilog;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
public sealed class ImageOnlyChannelService : IEarlyBehavior
|
||||
{
|
||||
private readonly IMemoryCache _ticketCache;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly DbService _db;
|
||||
private readonly ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> _enabledOn;
|
||||
|
||||
private Channel<IUserMessage> _deleteQueue = Channel.CreateBounded<IUserMessage>(new BoundedChannelOptions(100)
|
||||
{
|
||||
FullMode = BoundedChannelFullMode.DropOldest,
|
||||
SingleReader = true,
|
||||
SingleWriter = false,
|
||||
});
|
||||
|
||||
|
||||
public ImageOnlyChannelService(IMemoryCache ticketCache, DiscordSocketClient client, DbService db)
|
||||
{
|
||||
_ticketCache = ticketCache;
|
||||
_client = client;
|
||||
_db = db;
|
||||
|
||||
var uow = _db.GetDbContext();
|
||||
_enabledOn = uow.ImageOnlyChannels
|
||||
.ToList()
|
||||
.GroupBy(x => x.GuildId)
|
||||
.ToDictionary(x => x.Key, x => new ConcurrentHashSet<ulong>(x.Select(x => x.ChannelId)))
|
||||
.ToConcurrent();
|
||||
|
||||
_ = Task.Run(DeleteQueueRunner);
|
||||
|
||||
_client.ChannelDestroyed += ClientOnChannelDestroyed;
|
||||
}
|
||||
|
||||
private Task ClientOnChannelDestroyed(SocketChannel ch)
|
||||
{
|
||||
if (ch is not IGuildChannel gch)
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (_enabledOn.TryGetValue(gch.GuildId, out var channels) && channels.TryRemove(ch.Id))
|
||||
ToggleImageOnlyChannel(gch.GuildId, ch.Id, true);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task DeleteQueueRunner()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var toDelete = await _deleteQueue.Reader.ReadAsync();
|
||||
try
|
||||
{
|
||||
await toDelete.DeleteAsync();
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
// disable if bot can't delete messages in the channel
|
||||
ToggleImageOnlyChannel(((ITextChannel)toDelete.Channel).GuildId, toDelete.Channel.Id, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ToggleImageOnlyChannel(ulong guildId, ulong channelId, bool forceDisable = false)
|
||||
{
|
||||
var newState = false;
|
||||
using var uow = _db.GetDbContext();
|
||||
if (forceDisable
|
||||
|| (_enabledOn.TryGetValue(guildId, out var channels)
|
||||
&& channels.TryRemove(channelId)))
|
||||
{
|
||||
uow.ImageOnlyChannels.Delete(x => x.ChannelId == channelId);
|
||||
}
|
||||
else
|
||||
{
|
||||
uow.ImageOnlyChannels.Add(new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
ChannelId = channelId
|
||||
});
|
||||
|
||||
channels = _enabledOn.GetOrAdd(guildId, new ConcurrentHashSet<ulong>());
|
||||
channels.Add(channelId);
|
||||
newState = true;
|
||||
}
|
||||
|
||||
uow.SaveChanges();
|
||||
return newState;
|
||||
}
|
||||
|
||||
public async Task<bool> RunBehavior(IGuild guild, IUserMessage msg)
|
||||
{
|
||||
if (msg.Channel is not ITextChannel tch)
|
||||
return false;
|
||||
|
||||
if (msg.Attachments.Any(x => x is { Height: > 0, Width: > 0 }))
|
||||
return false;
|
||||
|
||||
if (!_enabledOn.TryGetValue(tch.GuildId, out var chs)
|
||||
|| !chs.Contains(msg.Channel.Id))
|
||||
return false;
|
||||
|
||||
var user = await tch.Guild.GetUserAsync(msg.Author.Id)
|
||||
?? await _client.Rest.GetGuildUserAsync(tch.GuildId, msg.Author.Id);
|
||||
|
||||
if (user is null)
|
||||
return false;
|
||||
|
||||
// ignore owner and admin
|
||||
if (user.Id == tch.Guild.OwnerId || user.GuildPermissions.Administrator)
|
||||
{
|
||||
Log.Information("Image-Only: Ignoring owner od admin ({ChannelId})", msg.Channel.Id);
|
||||
return false;
|
||||
}
|
||||
|
||||
// ignore users higher in hierarchy
|
||||
var botUser = await tch.Guild.GetCurrentUserAsync();
|
||||
if (user.GetRoles().Max(x => x.Position) >= botUser.GetRoles().Max(x => x.Position))
|
||||
return false;
|
||||
|
||||
// can't modify channel perms if not admin apparently
|
||||
if (!botUser.GuildPermissions.ManageGuild)
|
||||
{
|
||||
ToggleImageOnlyChannel( tch.GuildId, tch.Id, true);;
|
||||
return false;
|
||||
}
|
||||
|
||||
var shouldLock = AddUserTicket(tch.GuildId, msg.Author.Id);
|
||||
if (shouldLock)
|
||||
{
|
||||
await tch.AddPermissionOverwriteAsync(msg.Author, new(sendMessages: PermValue.Deny));
|
||||
Log.Warning("Image-Only: User {User} [{UserId}] has been banned from typing in the channel [{ChannelId}]",
|
||||
msg.Author,
|
||||
msg.Author.Id,
|
||||
msg.Channel.Id);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _deleteQueue.Writer.WriteAsync(msg);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error deleting message {MessageId} in image-only channel {ChannelId}.",
|
||||
msg.Id,
|
||||
tch.Id);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool AddUserTicket(ulong guildId, ulong userId)
|
||||
{
|
||||
var old = _ticketCache.GetOrCreate($"{guildId}_{userId}", entry =>
|
||||
{
|
||||
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1);
|
||||
return 0;
|
||||
});
|
||||
|
||||
_ticketCache.Set($"{guildId}_{userId}", ++old);
|
||||
|
||||
// if this is the third time that the user posts a
|
||||
// non image in an image-only channel on this server
|
||||
return old > 2;
|
||||
}
|
||||
|
||||
public int Priority { get; } = 0;
|
||||
}
|
||||
}
|
@@ -6,6 +6,8 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using NadekoBot.Common.Collections;
|
||||
@@ -21,7 +23,7 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
void AddDeleteIgnore(ulong xId);
|
||||
Task LogServer(ulong guildId, ulong channelId, bool actionValue);
|
||||
bool LogIgnore(ulong guildId, ulong channelId);
|
||||
bool LogIgnore(ulong guildId, ulong itemId, IgnoredItemType itemType);
|
||||
LogSetting GetGuildLogSettings(ulong guildId);
|
||||
bool Log(ulong guildId, ulong? channelId, LogType type);
|
||||
}
|
||||
@@ -37,7 +39,7 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public bool LogIgnore(ulong guildId, ulong channelId)
|
||||
public bool LogIgnore(ulong guildId, ulong itemId, IgnoredItemType itemType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -93,15 +95,15 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
var guildIds = client.Guilds.Select(x => x.Id).ToList();
|
||||
var configs = uow
|
||||
.Set<GuildConfig>()
|
||||
.LogSettings
|
||||
.AsQueryable()
|
||||
.Include(gc => gc.LogSetting)
|
||||
.ThenInclude(ls => ls.IgnoredChannels)
|
||||
.AsNoTracking()
|
||||
.Where(x => guildIds.Contains(x.GuildId))
|
||||
.Include(ls => ls.LogIgnores)
|
||||
.ToList();
|
||||
|
||||
GuildLogSettings = configs
|
||||
.ToDictionary(g => g.GuildId, g => g.LogSetting)
|
||||
.ToDictionary(ls => ls.GuildId)
|
||||
.ToConcurrent();
|
||||
}
|
||||
|
||||
@@ -165,23 +167,23 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
_ignoreMessageIds.Add(messageId);
|
||||
}
|
||||
|
||||
public bool LogIgnore(ulong gid, ulong cid)
|
||||
public bool LogIgnore(ulong gid, ulong itemId, IgnoredItemType itemType)
|
||||
{
|
||||
int removed = 0;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.LogSettingsFor(gid);
|
||||
LogSetting logSetting = GuildLogSettings.GetOrAdd(gid, (id) => config.LogSetting);
|
||||
removed = logSetting.IgnoredChannels.RemoveWhere(ilc => ilc.ChannelId == cid);
|
||||
config.LogSetting.IgnoredChannels.RemoveWhere(ilc => ilc.ChannelId == cid);
|
||||
var logSetting = uow.LogSettingsFor(gid);
|
||||
removed = logSetting.LogIgnores
|
||||
.RemoveAll(x => x.ItemType == itemType && itemId == x.LogItemId);
|
||||
|
||||
if (removed == 0)
|
||||
{
|
||||
var toAdd = new IgnoredLogChannel {ChannelId = cid};
|
||||
logSetting.IgnoredChannels.Add(toAdd);
|
||||
config.LogSetting.IgnoredChannels.Add(toAdd);
|
||||
var toAdd = new IgnoredLogItem { LogItemId = itemId, ItemType = itemType};
|
||||
logSetting.LogIgnores.Add(toAdd);
|
||||
}
|
||||
|
||||
uow.SaveChanges();
|
||||
GuildLogSettings.AddOrUpdate(gid, logSetting, (_, _) => logSetting);
|
||||
}
|
||||
|
||||
return removed > 0;
|
||||
@@ -209,11 +211,10 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
|
||||
public async Task LogServer(ulong guildId, ulong channelId, bool value)
|
||||
{
|
||||
LogSetting logSetting;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
logSetting = uow.LogSettingsFor(guildId).LogSetting;
|
||||
GuildLogSettings.AddOrUpdate(guildId, (id) => logSetting, (id, old) => logSetting);
|
||||
var logSetting = uow.LogSettingsFor(guildId);
|
||||
|
||||
logSetting.LogOtherId =
|
||||
logSetting.MessageUpdatedId =
|
||||
logSetting.MessageDeletedId =
|
||||
@@ -230,8 +231,9 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
logSetting.UserMutedId =
|
||||
logSetting.LogVoicePresenceTTSId =
|
||||
(value ? channelId : (ulong?) null);
|
||||
|
||||
;
|
||||
await uow.SaveChangesAsync();
|
||||
GuildLogSettings.AddOrUpdate(guildId, (id) => logSetting, (id, old) => logSetting);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,7 +303,7 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
ulong? channelId = null;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var logSetting = uow.LogSettingsFor(gid).LogSetting;
|
||||
var logSetting = uow.LogSettingsFor(gid);
|
||||
GuildLogSettings.AddOrUpdate(gid, (id) => logSetting, (id, old) => logSetting);
|
||||
switch (type)
|
||||
{
|
||||
@@ -582,7 +584,8 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!GuildLogSettings.TryGetValue(before.Guild.Id, out LogSetting logSetting))
|
||||
if (!GuildLogSettings.TryGetValue(before.Guild.Id, out LogSetting logSetting)
|
||||
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == after.Id && ilc.ItemType == IgnoredItemType.User))
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
@@ -684,7 +687,7 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
|
||||
if (!GuildLogSettings.TryGetValue(before.Guild.Id, out LogSetting logSetting)
|
||||
|| (logSetting.ChannelUpdatedId is null)
|
||||
|| logSetting.IgnoredChannels.Any(ilc => ilc.ChannelId == after.Id))
|
||||
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == after.Id && ilc.ItemType == IgnoredItemType.Channel))
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
@@ -735,7 +738,7 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
|
||||
if (!GuildLogSettings.TryGetValue(ch.Guild.Id, out LogSetting logSetting)
|
||||
|| (logSetting.ChannelDestroyedId is null)
|
||||
|| logSetting.IgnoredChannels.Any(ilc => ilc.ChannelId == ch.Id))
|
||||
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == ch.Id && ilc.ItemType == IgnoredItemType.Channel))
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
@@ -774,7 +777,7 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
return;
|
||||
|
||||
if (!GuildLogSettings.TryGetValue(ch.Guild.Id, out LogSetting logSetting)
|
||||
|| (logSetting.ChannelCreatedId is null))
|
||||
|| logSetting.ChannelCreatedId is null)
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
@@ -819,7 +822,8 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
return;
|
||||
|
||||
if (!GuildLogSettings.TryGetValue(usr.Guild.Id, out LogSetting logSetting)
|
||||
|| (logSetting.LogVoicePresenceId is null))
|
||||
|| (logSetting.LogVoicePresenceId is null)
|
||||
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == iusr.Id && ilc.ItemType == IgnoredItemType.User))
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
@@ -864,49 +868,6 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
//private Task _client_UserPresenceUpdated(Optional<SocketGuild> optGuild, SocketUser usr, SocketPresence before, SocketPresence after)
|
||||
//{
|
||||
// var _ = Task.Run(async () =>
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// var guild = optGuild.GetValueOrDefault() ?? (usr as SocketGuildUser)?.Guild;
|
||||
|
||||
// if (guild is null)
|
||||
// return;
|
||||
|
||||
// if (!GuildLogSettings.TryGetValue(guild.Id, out LogSetting logSetting)
|
||||
// || (logSetting.LogUserPresenceId is null)
|
||||
// || before.Status == after.Status)
|
||||
// return;
|
||||
|
||||
// ITextChannel logChannel;
|
||||
// if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserPresence)) is null)
|
||||
// return;
|
||||
// string str = "";
|
||||
// if (before.Status != after.Status)
|
||||
// str = "🎭" + Format.Code(PrettyCurrentTime(g)) +
|
||||
// GetText(logChannel.Guild, strs.user_status_change(,
|
||||
// "👤" + Format.Bold(usr.Username),
|
||||
// Format.Bold(after.Status.ToString()));
|
||||
|
||||
// //if (before.Game?.Name != after.Game?.Name)
|
||||
// //{
|
||||
// // if (str != "")
|
||||
// // str += "\n";
|
||||
// // str += $"👾`{prettyCurrentTime}`👤__**{usr.Username}**__ is now playing **{after.Game?.Name}**.";
|
||||
// //}
|
||||
|
||||
// PresenceUpdates.AddOrUpdate(logChannel, new List<string>() { str }, (id, list) => { list.Add(str); return list; });
|
||||
// }
|
||||
// catch
|
||||
// {
|
||||
// // ignored
|
||||
// }
|
||||
// });
|
||||
// return Task.CompletedTask;
|
||||
//}
|
||||
|
||||
private Task _client_UserLeft(IGuildUser usr)
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
@@ -914,7 +875,8 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
try
|
||||
{
|
||||
if (!GuildLogSettings.TryGetValue(usr.Guild.Id, out LogSetting logSetting)
|
||||
|| (logSetting.UserLeftId is null))
|
||||
|| (logSetting.UserLeftId is null)
|
||||
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == usr.Id && ilc.ItemType == IgnoredItemType.User))
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
@@ -989,7 +951,8 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
try
|
||||
{
|
||||
if (!GuildLogSettings.TryGetValue(guild.Id, out LogSetting logSetting)
|
||||
|| (logSetting.UserUnbannedId is null))
|
||||
|| (logSetting.UserUnbannedId is null)
|
||||
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == usr.Id && ilc.ItemType == IgnoredItemType.User))
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
@@ -1023,7 +986,8 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
try
|
||||
{
|
||||
if (!GuildLogSettings.TryGetValue(guild.Id, out LogSetting logSetting)
|
||||
|| (logSetting.UserBannedId is null))
|
||||
|| (logSetting.UserBannedId is null)
|
||||
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == usr.Id && ilc.ItemType == IgnoredItemType.User))
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
@@ -1071,7 +1035,7 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
|
||||
if (!GuildLogSettings.TryGetValue(channel.Guild.Id, out LogSetting logSetting)
|
||||
|| (logSetting.MessageDeletedId is null)
|
||||
|| logSetting.IgnoredChannels.Any(ilc => ilc.ChannelId == channel.Id))
|
||||
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == channel.Id && ilc.ItemType == IgnoredItemType.Channel))
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
@@ -1129,7 +1093,7 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
|
||||
if (!GuildLogSettings.TryGetValue(channel.Guild.Id, out LogSetting logSetting)
|
||||
|| (logSetting.MessageUpdatedId is null)
|
||||
|| logSetting.IgnoredChannels.Any(ilc => ilc.ChannelId == channel.Id))
|
||||
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == channel.Id && ilc.ItemType == IgnoredItemType.Channel))
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
@@ -1238,7 +1202,7 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var newLogSetting = uow.LogSettingsFor(guildId).LogSetting;
|
||||
var newLogSetting = uow.LogSettingsFor(guildId);
|
||||
switch (logChannelType)
|
||||
{
|
||||
case LogType.Other:
|
||||
|
@@ -8,6 +8,8 @@ using NadekoBot.Extensions;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using NadekoBot.Db;
|
||||
using Serilog;
|
||||
|
||||
@@ -155,19 +157,23 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
|
||||
public bool Add(ulong id, ReactionRoleMessage rrm)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var gc = uow.GuildConfigsForId(id, set => set
|
||||
.Include(x => x.ReactionRoleMessages)
|
||||
.ThenInclude(x => x.ReactionRoles));
|
||||
if (gc.ReactionRoleMessages.Count >= 10)
|
||||
return false;
|
||||
gc.ReactionRoleMessages.Add(rrm);
|
||||
_models.AddOrUpdate(id,
|
||||
gc.ReactionRoleMessages,
|
||||
delegate { return gc.ReactionRoleMessages; });
|
||||
uow.SaveChanges();
|
||||
}
|
||||
using var uow = _db.GetDbContext();
|
||||
var table = uow.GetTable<ReactionRoleMessage>();
|
||||
table.Delete(x => x.MessageId == rrm.MessageId);
|
||||
|
||||
var gc = uow.GuildConfigsForId(id, set => set
|
||||
.Include(x => x.ReactionRoleMessages)
|
||||
.ThenInclude(x => x.ReactionRoles));
|
||||
|
||||
if (gc.ReactionRoleMessages.Count >= 10)
|
||||
return false;
|
||||
|
||||
gc.ReactionRoleMessages.Add(rrm);
|
||||
uow.SaveChanges();
|
||||
|
||||
_models.AddOrUpdate(id,
|
||||
gc.ReactionRoleMessages,
|
||||
delegate { return gc.ReactionRoleMessages; });
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -224,7 +224,7 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
|
||||
if (!ownerChannels.Any())
|
||||
Log.Warning(
|
||||
"No owner channels created! Make sure you've specified the correct OwnerId in the credentials.json file and invited the bot to a Discord server.");
|
||||
"No owner channels created! Make sure you've specified the correct OwnerId in the creds.yml file and invited the bot to a Discord server.");
|
||||
else
|
||||
Log.Information($"Created {ownerChannels.Count} out of {_creds.OwnerIds.Count} owner message channels.");
|
||||
}
|
||||
|
@@ -123,7 +123,7 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
break;
|
||||
case PunishmentAction.Ban:
|
||||
if (minutes == 0)
|
||||
await guild.AddBanAsync(user, reason: reason).ConfigureAwait(false);
|
||||
await guild.AddBanAsync(user, reason: reason, pruneDays: 7).ConfigureAwait(false);
|
||||
else
|
||||
await _mute.TimedBan(user.Guild, user, TimeSpan.FromMinutes(minutes), reason)
|
||||
.ConfigureAwait(false);
|
||||
|
@@ -9,9 +9,11 @@ using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Administration.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Modules.Permissions.Services;
|
||||
using NadekoBot.Modules.Searches.Common;
|
||||
using Serilog;
|
||||
|
||||
namespace NadekoBot.Modules.Administration
|
||||
@@ -762,6 +764,78 @@ namespace NadekoBot.Modules.Administration
|
||||
await ctx.Channel.EmbedAsync(toSend)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
[BotPerm(GuildPerm.BanMembers)]
|
||||
[Ratelimit(30)]
|
||||
public async Task MassBan(params string[] userStrings)
|
||||
{
|
||||
if (userStrings.Length == 0)
|
||||
return;
|
||||
|
||||
var missing = new List<string>();
|
||||
var banning = new HashSet<IGuildUser>();
|
||||
|
||||
await ctx.Channel.TriggerTypingAsync();
|
||||
foreach (var userStr in userStrings)
|
||||
{
|
||||
if (ulong.TryParse(userStr, out var userId))
|
||||
{
|
||||
var user = await ctx.Guild.GetUserAsync(userId) ??
|
||||
await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(ctx.Guild.Id, userId);
|
||||
|
||||
if (user is null)
|
||||
{
|
||||
missing.Add(userStr);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!await CheckRoleHierarchy(user))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
banning.Add(user);
|
||||
}
|
||||
else
|
||||
{
|
||||
missing.Add(userStr);
|
||||
}
|
||||
}
|
||||
|
||||
var missStr = string.Join("\n", missing);
|
||||
if (string.IsNullOrWhiteSpace(missStr))
|
||||
missStr = "-";
|
||||
|
||||
var toSend = _eb.Create(ctx)
|
||||
.WithDescription(GetText(strs.mass_ban_in_progress(banning.Count)))
|
||||
.AddField(GetText(strs.invalid(missing.Count)), missStr)
|
||||
.WithPendingColor();
|
||||
|
||||
var banningMessage = await ctx.Channel.EmbedAsync(toSend);
|
||||
|
||||
foreach (var toBan in banning)
|
||||
{
|
||||
try
|
||||
{
|
||||
await toBan.BanAsync(7);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error banning {User} user in {GuildId} server",
|
||||
toBan.Id,
|
||||
ctx.Guild.Id);
|
||||
}
|
||||
}
|
||||
|
||||
await banningMessage.ModifyAsync(x => x.Embed = _eb.Create()
|
||||
.WithDescription(GetText(strs.mass_ban_completed(banning.Count())))
|
||||
.AddField(GetText(strs.invalid(missing.Count)), missStr)
|
||||
.WithOkColor()
|
||||
.Build()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
@@ -783,7 +857,7 @@ namespace NadekoBot.Modules.Administration
|
||||
var banningMessageTask = ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithDescription(GetText(strs.mass_kill_in_progress(bans.Count())))
|
||||
.AddField(GetText(strs.invalid(missing)), missStr)
|
||||
.WithOkColor());
|
||||
.WithPendingColor());
|
||||
|
||||
//do the banning
|
||||
await Task.WhenAll(bans
|
||||
|
@@ -294,7 +294,7 @@ namespace NadekoBot.Modules.CustomReactions
|
||||
.WithDescription("This will delete all custom reactions on this server.")).ConfigureAwait(false))
|
||||
{
|
||||
var count = _service.DeleteAllCustomReactions(ctx.Guild.Id);
|
||||
await ReplyErrorLocalizedAsync(strs.cleared(count));
|
||||
await ReplyConfirmLocalizedAsync(strs.cleared(count));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using Cloneable;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Yml;
|
||||
using YamlDotNet.Serialization;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common
|
||||
{
|
||||
@@ -179,7 +180,8 @@ default is 0.02, which is 2%")]
|
||||
|
||||
public MultipliersData Multipliers { get; set; } = new MultipliersData();
|
||||
|
||||
[Comment(@"List of items available for gifting.")]
|
||||
[Comment(@"List of items available for gifting.
|
||||
If negative is true, gift will instead reduce waifu value.")]
|
||||
public List<WaifuItemModel> Items { get; set; } = new List<WaifuItemModel>();
|
||||
|
||||
public WaifuConfig()
|
||||
@@ -260,6 +262,11 @@ Default 1 (meaning no effect)")]
|
||||
Default 0.95 (meaning 95%)
|
||||
Example: If a waifu is worth 1000, and she receives a gift worth 100, her new value will be 1095)")]
|
||||
public decimal GiftEffect { get; set; } = 0.95M;
|
||||
|
||||
[Comment(@"What percentage of the value of the gift will a waifu lose when she's gifted a gift marked as 'negative'.
|
||||
Default 0.5 (meaning 50%)
|
||||
Example: If a waifu is worth 1000, and she receives a negative gift worth 100, her new value will be 950)")]
|
||||
public decimal NegativeGiftEffect { get; set; } = 0.50M;
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
@@ -268,19 +275,24 @@ Example: If a waifu is worth 1000, and she receives a gift worth 100, her new va
|
||||
public string ItemEmoji { get; set; }
|
||||
public int Price { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
[YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)]
|
||||
public bool Negative { get; set; }
|
||||
|
||||
public WaifuItemModel()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public WaifuItemModel(string itemEmoji, int price, string name)
|
||||
public WaifuItemModel(string itemEmoji, int price, string name, bool negative = false)
|
||||
{
|
||||
ItemEmoji = itemEmoji;
|
||||
Price = price;
|
||||
Name = name;
|
||||
Negative = negative;
|
||||
}
|
||||
|
||||
|
||||
public override string ToString() => Name;
|
||||
}
|
||||
|
||||
|
@@ -1,14 +0,0 @@
|
||||
namespace NadekoBot.Modules.Gambling.Common.Waifu
|
||||
{
|
||||
public struct WaifuProfileTitle
|
||||
{
|
||||
public int Count { get; }
|
||||
public string Title { get; }
|
||||
|
||||
public WaifuProfileTitle(int count, string title)
|
||||
{
|
||||
Count = count;
|
||||
Title = title;
|
||||
}
|
||||
}
|
||||
}
|
@@ -17,11 +17,6 @@ namespace NadekoBot.Modules.Gambling
|
||||
[Group]
|
||||
public class CurrencyEventsCommands : GamblingSubmodule<CurrencyEventsService>
|
||||
{
|
||||
public enum OtherEvent
|
||||
{
|
||||
BotListUpvoters
|
||||
}
|
||||
|
||||
public CurrencyEventsCommands(GamblingConfigService gamblingConf) : base(gamblingConf)
|
||||
{
|
||||
}
|
||||
@@ -37,41 +32,36 @@ namespace NadekoBot.Modules.Gambling
|
||||
ctx.Channel.Id,
|
||||
ev,
|
||||
opts,
|
||||
GetEmbed
|
||||
).ConfigureAwait(false))
|
||||
GetEmbed))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.start_event_fail).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private IEmbedBuilder GetEmbed(CurrencyEvent.Type type, EventOptions opts, long currentPot)
|
||||
{
|
||||
switch (type)
|
||||
return type switch
|
||||
{
|
||||
case CurrencyEvent.Type.Reaction:
|
||||
return _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.event_title(type.ToString())))
|
||||
.WithDescription(GetReactionDescription(opts.Amount, currentPot))
|
||||
.WithFooter(GetText(strs.event_duration_footer(opts.Hours)));
|
||||
case CurrencyEvent.Type.GameStatus:
|
||||
return _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.event_title(type.ToString())))
|
||||
.WithDescription(GetGameStatusDescription(opts.Amount, currentPot))
|
||||
.WithFooter(GetText(strs.event_duration_footer(opts.Hours)));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
throw new ArgumentOutOfRangeException(nameof(type));
|
||||
CurrencyEvent.Type.Reaction => _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.event_title(type.ToString())))
|
||||
.WithDescription(GetReactionDescription(opts.Amount, currentPot))
|
||||
.WithFooter(GetText(strs.event_duration_footer(opts.Hours))),
|
||||
CurrencyEvent.Type.GameStatus => _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.event_title(type.ToString())))
|
||||
.WithDescription(GetGameStatusDescription(opts.Amount, currentPot))
|
||||
.WithFooter(GetText(strs.event_duration_footer(opts.Hours))),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type))
|
||||
};
|
||||
}
|
||||
|
||||
private string GetReactionDescription(long amount, long potSize)
|
||||
{
|
||||
string potSizeStr = Format.Bold(potSize == 0
|
||||
var potSizeStr = Format.Bold(potSize == 0
|
||||
? "∞" + CurrencySign
|
||||
: potSize.ToString() + CurrencySign);
|
||||
: potSize + CurrencySign);
|
||||
|
||||
return GetText(strs.new_reaction_event(
|
||||
CurrencySign,
|
||||
Format.Bold(amount + CurrencySign),
|
||||
@@ -80,9 +70,10 @@ namespace NadekoBot.Modules.Gambling
|
||||
|
||||
private string GetGameStatusDescription(long amount, long potSize)
|
||||
{
|
||||
string potSizeStr = Format.Bold(potSize == 0
|
||||
var potSizeStr = Format.Bold(potSize == 0
|
||||
? "∞" + CurrencySign
|
||||
: potSize.ToString() + CurrencySign);
|
||||
: potSize + CurrencySign);
|
||||
|
||||
return GetText(strs.new_gamestatus_event(
|
||||
CurrencySign,
|
||||
Format.Bold(amount + CurrencySign),
|
||||
|
@@ -97,7 +97,7 @@ namespace NadekoBot.Modules.Gambling
|
||||
|
||||
await _cs.AddAsync(ctx.User.Id, "Timely claim", val).ConfigureAwait(false);
|
||||
|
||||
await ReplyErrorLocalizedAsync(strs.timely(n(val) + CurrencySign, period));
|
||||
await ReplyConfirmLocalizedAsync(strs.timely(n(val) + CurrencySign, period));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
@@ -217,7 +217,7 @@ namespace NadekoBot.Modules.Gambling
|
||||
[Priority(0)]
|
||||
public async Task Cash(ulong userId)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.has(Format.Code(userId.ToString()), $"{GetCurrency(userId)} {CurrencySign}"));
|
||||
await ReplyConfirmLocalizedAsync(strs.has(Format.Code(userId.ToString()), $"{GetCurrency(userId)} {CurrencySign}"));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
@@ -269,7 +269,7 @@ namespace NadekoBot.Modules.Gambling
|
||||
$"Awarded by bot owner. ({ctx.User.Username}/{ctx.User.Id}) {(msg ?? "")}",
|
||||
amount,
|
||||
gamble: (ctx.Client.CurrentUser.Id != usrId)).ConfigureAwait(false);
|
||||
await ReplyErrorLocalizedAsync(strs.awarded(n(amount) + CurrencySign, $"<@{usrId}>"));
|
||||
await ReplyConfirmLocalizedAsync(strs.awarded(n(amount) + CurrencySign, $"<@{usrId}>"));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
@@ -340,7 +340,7 @@ namespace NadekoBot.Modules.Gambling
|
||||
|
||||
if (await _cs.RemoveAsync(usrId, $"Taken by bot owner.({ctx.User.Username}/{ctx.User.Id})", amount,
|
||||
gamble: (ctx.Client.CurrentUser.Id != usrId)).ConfigureAwait(false))
|
||||
await ReplyErrorLocalizedAsync(strs.take(amount + CurrencySign, $"<@{usrId}>"));
|
||||
await ReplyConfirmLocalizedAsync(strs.take(amount + CurrencySign, $"<@{usrId}>"));
|
||||
else
|
||||
await ReplyErrorLocalizedAsync(strs.take_fail(amount + CurrencySign, Format.Code(usrId.ToString()), CurrencySign));
|
||||
}
|
||||
|
@@ -1,7 +1,11 @@
|
||||
using NadekoBot.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Configs;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Services
|
||||
{
|
||||
@@ -34,9 +38,31 @@ namespace NadekoBot.Modules.Gambling.Services
|
||||
AddParsedProp("waifu.multi.divorce_value", gs => gs.Waifu.Multipliers.DivorceNewValue, decimal.TryParse, ConfigPrinters.ToString, val => val > 0);
|
||||
AddParsedProp("waifu.multi.all_gifts", gs => gs.Waifu.Multipliers.AllGiftPrices, decimal.TryParse, ConfigPrinters.ToString, val => val > 0);
|
||||
AddParsedProp("waifu.multi.gift_effect", gs => gs.Waifu.Multipliers.GiftEffect, decimal.TryParse, ConfigPrinters.ToString, val => val >= 0);
|
||||
AddParsedProp("waifu.multi.negative_gift_effect", gs => gs.Waifu.Multipliers.NegativeGiftEffect, decimal.TryParse, ConfigPrinters.ToString, val => val >= 0);
|
||||
AddParsedProp("decay.percent", gs => gs.Decay.Percent, decimal.TryParse, ConfigPrinters.ToString, val => val >= 0 && val <= 1);
|
||||
AddParsedProp("decay.maxdecay", gs => gs.Decay.MaxDecay, int.TryParse, ConfigPrinters.ToString, val => val >= 0);
|
||||
AddParsedProp("decay.threshold", gs => gs.Decay.MinThreshold, int.TryParse, ConfigPrinters.ToString, val => val >= 0);
|
||||
|
||||
Migrate();
|
||||
}
|
||||
|
||||
private readonly IEnumerable<WaifuItemModel> antiGiftSeed = new[]
|
||||
{
|
||||
new WaifuItemModel("🥀", 100, "WiltedRose", true),
|
||||
new WaifuItemModel("✂️", 1000, "Haircut", true),
|
||||
new WaifuItemModel("🧻", 10000, "ToiletPaper", true),
|
||||
};
|
||||
|
||||
public void Migrate()
|
||||
{
|
||||
if (_data.Version < 2)
|
||||
{
|
||||
ModifyConfig(c =>
|
||||
{
|
||||
c.Waifu.Items = c.Waifu.Items.Concat(antiGiftSeed).ToList();
|
||||
c.Version = 2;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -394,19 +394,28 @@ namespace NadekoBot.Modules.Gambling.Services
|
||||
});
|
||||
}
|
||||
|
||||
w.Items.Add(new WaifuItem()
|
||||
if (!itemObj.Negative)
|
||||
{
|
||||
Name = itemObj.Name.ToLowerInvariant(),
|
||||
ItemEmoji = itemObj.ItemEmoji,
|
||||
});
|
||||
|
||||
if (w.Claimer?.UserId == from.Id)
|
||||
{
|
||||
w.Price += (int) (itemObj.Price * _gss.Data.Waifu.Multipliers.GiftEffect);
|
||||
w.Items.Add(new WaifuItem()
|
||||
{
|
||||
Name = itemObj.Name.ToLowerInvariant(),
|
||||
ItemEmoji = itemObj.ItemEmoji,
|
||||
});
|
||||
|
||||
if (w.Claimer?.UserId == from.Id)
|
||||
{
|
||||
w.Price += (int)(itemObj.Price * _gss.Data.Waifu.Multipliers.GiftEffect);
|
||||
}
|
||||
else
|
||||
{
|
||||
w.Price += itemObj.Price / 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
w.Price += itemObj.Price / 2;
|
||||
w.Price -= (int)(itemObj.Price * _gss.Data.Waifu.Multipliers.NegativeGiftEffect);
|
||||
if (w.Price < 1)
|
||||
w.Price = 1;
|
||||
}
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
@@ -512,7 +521,7 @@ namespace NadekoBot.Modules.Gambling.Services
|
||||
{
|
||||
var conf = _gss.Data;
|
||||
return conf.Waifu.Items
|
||||
.Select(x => new WaifuItemModel(x.ItemEmoji, (int)(x.Price * conf.Waifu.Multipliers.AllGiftPrices), x.Name))
|
||||
.Select(x => new WaifuItemModel(x.ItemEmoji, (int)(x.Price * conf.Waifu.Multipliers.AllGiftPrices), x.Name, x.Negative))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
@@ -225,6 +225,9 @@ namespace NadekoBot.Modules.Gambling
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
public async Task ShopAdd(Role _, int price, [Leftover] IRole role)
|
||||
{
|
||||
if (price < 1)
|
||||
return;
|
||||
|
||||
var entry = new ShopEntry()
|
||||
{
|
||||
Name = "-",
|
||||
@@ -252,8 +255,11 @@ namespace NadekoBot.Modules.Gambling
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task ShopAdd(List _, int price, [Leftover]string name)
|
||||
public async Task ShopAdd(List _, int price, [Leftover] string name)
|
||||
{
|
||||
if (price < 1)
|
||||
return;
|
||||
|
||||
var entry = new ShopEntry()
|
||||
{
|
||||
Name = name.TrimTo(100),
|
||||
@@ -266,13 +272,14 @@ namespace NadekoBot.Modules.Gambling
|
||||
{
|
||||
var entries = new IndexedCollection<ShopEntry>(uow.GuildConfigsForId(ctx.Guild.Id,
|
||||
set => set.Include(x => x.ShopEntries)
|
||||
.ThenInclude(x => x.Items)).ShopEntries)
|
||||
.ThenInclude(x => x.Items)).ShopEntries)
|
||||
{
|
||||
entry
|
||||
};
|
||||
uow.GuildConfigsForId(ctx.Guild.Id, set => set).ShopEntries = entries;
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
await ctx.Channel.EmbedAsync(EntryToEmbed(entry)
|
||||
.WithTitle(GetText(strs.shop_item_add))).ConfigureAwait(false);
|
||||
}
|
||||
|
@@ -145,11 +145,11 @@ namespace NadekoBot.Modules.Gambling
|
||||
|
||||
if (result == DivorceResult.SucessWithPenalty)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.waifu_divorced_like(Format.Bold(w.Waifu.ToString()), amount + CurrencySign));
|
||||
await ReplyConfirmLocalizedAsync(strs.waifu_divorced_like(Format.Bold(w.Waifu.ToString()), amount + CurrencySign));
|
||||
}
|
||||
else if (result == DivorceResult.Success)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.waifu_divorced_notlike(amount + CurrencySign));
|
||||
await ReplyConfirmLocalizedAsync(strs.waifu_divorced_notlike(amount + CurrencySign));
|
||||
}
|
||||
else if (result == DivorceResult.NotYourWife)
|
||||
{
|
||||
@@ -306,7 +306,7 @@ namespace NadekoBot.Modules.Gambling
|
||||
[Priority(1)]
|
||||
public async Task WaifuGift(int page = 1)
|
||||
{
|
||||
if (--page < 0 || page > 3)
|
||||
if (--page < 0 || page > (_config.Waifu.Items.Count - 1) / 9)
|
||||
return;
|
||||
|
||||
var waifuItems = _service.GetWaifuItems();
|
||||
@@ -317,10 +317,14 @@ namespace NadekoBot.Modules.Gambling
|
||||
.WithOkColor();
|
||||
|
||||
waifuItems
|
||||
.OrderBy(x => x.Price)
|
||||
.OrderBy(x => x.Negative)
|
||||
.ThenBy(x => x.Price)
|
||||
.Skip(9 * cur)
|
||||
.Take(9)
|
||||
.ForEach(x => embed.AddField($"{x.ItemEmoji} {x.Name}", x.Price, true));
|
||||
.ForEach(x => embed
|
||||
.AddField($"{(!x.Negative ? string.Empty : "\\💔")} {x.ItemEmoji} {x.Name}",
|
||||
Format.Bold(x.Price.ToString()) + _config.Currency.Sign,
|
||||
true));
|
||||
|
||||
return embed;
|
||||
}, waifuItems.Count, 9);
|
||||
|
@@ -8,6 +8,15 @@ namespace NadekoBot.Modules.Games.Common
|
||||
[Cloneable]
|
||||
public sealed partial class GamesConfig : ICloneable<GamesConfig>
|
||||
{
|
||||
[Comment("DO NOT CHANGE")]
|
||||
public int Version { get; set; }
|
||||
|
||||
[Comment("Hangman related settings (.hangman command)")]
|
||||
public HangmanConfig Hangman { get; set; } = new HangmanConfig()
|
||||
{
|
||||
CurrencyReward = 0
|
||||
};
|
||||
|
||||
[Comment("Trivia related settings (.t command)")]
|
||||
public TriviaConfig Trivia { get; set; } = new TriviaConfig()
|
||||
{
|
||||
@@ -57,6 +66,13 @@ namespace NadekoBot.Modules.Games.Common
|
||||
};
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
public sealed partial class HangmanConfig
|
||||
{
|
||||
[Comment("The amount of currency awarded to the winner of a hangman game")]
|
||||
public long CurrencyReward { get; set; }
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
public sealed partial class TriviaConfig
|
||||
{
|
||||
|
@@ -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, 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);
|
||||
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);
|
||||
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); // 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,141 +1,85 @@
|
||||
#nullable enable
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Modules.Games.Common.Hangman;
|
||||
using NadekoBot.Modules.Games.Services;
|
||||
using NadekoBot.Modules.Games.Common.Hangman.Exceptions;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Games.Hangman;
|
||||
using NadekoBot.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Games
|
||||
{
|
||||
public partial class Games
|
||||
{
|
||||
[Group]
|
||||
public class HangmanCommands : NadekoSubmodule<GamesService>
|
||||
public class HangmanCommands : NadekoSubmodule<IHangmanService>
|
||||
{
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
public HangmanCommands(DiscordSocketClient client)
|
||||
{
|
||||
_client = client;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
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]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Hangman([Leftover]string type = "random")
|
||||
public async Task Hangman([Leftover] string? type = null)
|
||||
{
|
||||
Hangman hm;
|
||||
try
|
||||
{
|
||||
hm = new Hangman(type, _service.TermPool);
|
||||
}
|
||||
catch (TermNotFoundException)
|
||||
if (!_service.StartHangman(ctx.Channel.Id, type, out var hangman))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.hangman_running);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_service.HangmanGames.TryAdd(ctx.Channel.Id, hm))
|
||||
{
|
||||
hm.Dispose();
|
||||
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)
|
||||
{
|
||||
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 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));
|
||||
var eb = GetEmbed(_eb, hangman);
|
||||
eb.WithDescription(GetText(strs.hangman_game_started));
|
||||
await ctx.Channel.EmbedAsync(eb);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@@ -18,6 +18,25 @@ namespace NadekoBot.Modules.Games.Services
|
||||
ConfigPrinters.ToString, val => val > 0);
|
||||
AddParsedProp("trivia.currency_reward", gs => gs.Trivia.CurrencyReward, long.TryParse,
|
||||
ConfigPrinters.ToString, val => val >= 0);
|
||||
AddParsedProp("hangman.currency_reward", gs => gs.Hangman.CurrencyReward, long.TryParse,
|
||||
ConfigPrinters.ToString, val => val >= 0);
|
||||
|
||||
Migrate();
|
||||
}
|
||||
|
||||
private void Migrate()
|
||||
{
|
||||
if (_data.Version < 1)
|
||||
{
|
||||
ModifyConfig(c =>
|
||||
{
|
||||
c.Version = 1;
|
||||
c.Hangman = new HangmanConfig()
|
||||
{
|
||||
CurrencyReward = 0
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,7 +4,6 @@ using NadekoBot.Services;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Games.Common;
|
||||
using NadekoBot.Modules.Games.Common.Acrophobia;
|
||||
using NadekoBot.Modules.Games.Common.Hangman;
|
||||
using NadekoBot.Modules.Games.Common.Nunchi;
|
||||
using NadekoBot.Modules.Games.Common.Trivia;
|
||||
using Newtonsoft.Json;
|
||||
@@ -12,7 +11,6 @@ using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -40,10 +38,6 @@ namespace NadekoBot.Modules.Games.Services
|
||||
|
||||
//channelId, game
|
||||
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 Dictionary<ulong, TicTacToe> TicTacToeGames { get; } = new Dictionary<ulong, TicTacToe>();
|
||||
public ConcurrentDictionary<ulong, TypingGame> RunningContests { get; } = new ConcurrentDictionary<ulong, TypingGame>();
|
||||
|
@@ -362,6 +362,14 @@ namespace NadekoBot.Modules.Help
|
||||
if (!(serviceUrl is null || accessKey is null || secretAcccessKey is null))
|
||||
{
|
||||
var config = new AmazonS3Config {ServiceURL = serviceUrl};
|
||||
|
||||
using var dlClient = new AmazonS3Client(accessKey, secretAcccessKey, config);
|
||||
var oldVersionObject = await dlClient.GetObjectAsync(new GetObjectRequest()
|
||||
{
|
||||
BucketName = "nadeko-pictures",
|
||||
Key = "cmds/versions.json",
|
||||
});
|
||||
|
||||
using (var client = new AmazonS3Client(accessKey, secretAcccessKey, config))
|
||||
{
|
||||
await client.PutObjectAsync(new PutObjectRequest()
|
||||
@@ -375,11 +383,10 @@ namespace NadekoBot.Modules.Help
|
||||
});
|
||||
}
|
||||
|
||||
const string cmdVersionsFilePath = "../../cmd-versions.json";
|
||||
var versionListString = File.Exists(cmdVersionsFilePath)
|
||||
? await File.ReadAllTextAsync(cmdVersionsFilePath)
|
||||
: "[]";
|
||||
|
||||
using var ms = new MemoryStream();
|
||||
await oldVersionObject.ResponseStream.CopyToAsync(ms);
|
||||
var versionListString = Encoding.UTF8.GetString(ms.ToArray());
|
||||
|
||||
var versionList = System.Text.Json.JsonSerializer.Deserialize<List<string>>(versionListString);
|
||||
if (!versionList.Contains(StatsService.BotVersion))
|
||||
{
|
||||
@@ -391,7 +398,6 @@ namespace NadekoBot.Modules.Help
|
||||
{
|
||||
WriteIndented = true
|
||||
});
|
||||
await File.WriteAllTextAsync(cmdVersionsFilePath, versionListString);
|
||||
|
||||
// upload the updated version list
|
||||
using var client = new AmazonS3Client(accessKey, secretAcccessKey, config);
|
||||
@@ -428,7 +434,7 @@ namespace NadekoBot.Modules.Help
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Donate()
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.donate(PatreonUrl, PaypalUrl));
|
||||
await ReplyConfirmLocalizedAsync(strs.donate(PatreonUrl, PaypalUrl));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -44,6 +44,11 @@ namespace NadekoBot.Modules.Help.Services
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(settings.DmHelpText) || settings.DmHelpText == "-")
|
||||
return Task.CompletedTask;
|
||||
|
||||
// only send dm help text if it contains one of the keywords, if they're specified
|
||||
// if they're not, then reply to every DM
|
||||
if (settings.DmHelpTextKeywords.Any() && !settings.DmHelpTextKeywords.Any(k => msg.Content.Contains(k)))
|
||||
return Task.CompletedTask;
|
||||
|
||||
var rep = new ReplacementBuilder()
|
||||
.WithOverride("%prefix%", () => _bss.Data.Prefix)
|
||||
|
@@ -125,7 +125,7 @@ namespace NadekoBot.Modules.Music
|
||||
queuedMessage?.DeleteAfter(10, _logService);
|
||||
if (mp.IsStopped)
|
||||
{
|
||||
var msg = await ReplyErrorLocalizedAsync(strs.queue_stopped(Format.Code(Prefix + "play")));
|
||||
var msg = await ReplyPendingLocalizedAsync(strs.queue_stopped(Format.Code(Prefix + "play")));
|
||||
msg.DeleteAfter(10, _logService);
|
||||
}
|
||||
}
|
||||
@@ -230,7 +230,7 @@ namespace NadekoBot.Modules.Music
|
||||
return;
|
||||
|
||||
await _service.SetVolumeAsync(ctx.Guild.Id, vol);
|
||||
await ReplyErrorLocalizedAsync(strs.volume_set(vol));
|
||||
await ReplyConfirmLocalizedAsync(strs.volume_set(vol));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
@@ -163,7 +163,7 @@ namespace NadekoBot.Modules.NSFW
|
||||
{
|
||||
if (!_service.AutoBoobTimers.TryRemove(ctx.Channel.Id, out t)) return;
|
||||
|
||||
t.Change(Timeout.Infinite, Timeout.Infinite); //proper way to disable the timer
|
||||
t.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
await ReplyConfirmLocalizedAsync(strs.stopped).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
@@ -189,7 +189,7 @@ namespace NadekoBot.Modules.NSFW
|
||||
return t;
|
||||
});
|
||||
|
||||
await ReplyErrorLocalizedAsync(strs.started(interval));
|
||||
await ReplyConfirmLocalizedAsync(strs.started(interval));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
@@ -229,7 +229,7 @@ namespace NadekoBot.Modules.NSFW
|
||||
return t;
|
||||
});
|
||||
|
||||
await ReplyErrorLocalizedAsync(strs.started(interval));
|
||||
await ReplyConfirmLocalizedAsync(strs.started(interval));
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -363,9 +363,9 @@ namespace NadekoBot.Modules.NSFW
|
||||
var added = _service.ToggleBlacklistedTag(ctx.Guild.Id, tag);
|
||||
|
||||
if (added)
|
||||
await ReplyErrorLocalizedAsync(strs.blacklisted_tag_add(tag));
|
||||
await ReplyPendingLocalizedAsync(strs.blacklisted_tag_add(tag));
|
||||
else
|
||||
await ReplyErrorLocalizedAsync(strs.blacklisted_tag_remove(tag));
|
||||
await ReplyPendingLocalizedAsync(strs.blacklisted_tag_remove(tag));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -217,7 +217,7 @@ namespace NadekoBot.Modules.Permissions
|
||||
{
|
||||
}
|
||||
}
|
||||
await ReplyErrorLocalizedAsync(strs.perm_out_of_range).ConfigureAwait(false);
|
||||
await ReplyConfirmLocalizedAsync(strs.perm_out_of_range).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
@@ -16,33 +16,33 @@ namespace NadekoBot.Modules.Searches
|
||||
[Group]
|
||||
public class AnimeSearchCommands : NadekoSubmodule<AnimeSearchService>
|
||||
{
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Novel([Leftover] string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
return;
|
||||
|
||||
var novelData = await _service.GetNovelData(query).ConfigureAwait(false);
|
||||
|
||||
if (novelData is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.failed_finding_novel).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithDescription(novelData.Description.Replace("<br>", Environment.NewLine, StringComparison.InvariantCulture))
|
||||
.WithTitle(novelData.Title)
|
||||
.WithUrl(novelData.Link)
|
||||
.WithImageUrl(novelData.ImageUrl)
|
||||
.AddField(GetText(strs.authors), string.Join("\n", novelData.Authors), true)
|
||||
.AddField(GetText(strs.status), novelData.Status, true)
|
||||
.AddField(GetText(strs.genres), string.Join(" ", novelData.Genres.Any() ? novelData.Genres : new[] { "none" }), true)
|
||||
.WithFooter($"{GetText(strs.score)} {novelData.Score}");
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
// [NadekoCommand, Aliases]
|
||||
// public async Task Novel([Leftover] string query)
|
||||
// {
|
||||
// if (string.IsNullOrWhiteSpace(query))
|
||||
// return;
|
||||
//
|
||||
// var novelData = await _service.GetNovelData(query).ConfigureAwait(false);
|
||||
//
|
||||
// if (novelData is null)
|
||||
// {
|
||||
// await ReplyErrorLocalizedAsync(strs.failed_finding_novel).ConfigureAwait(false);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// var embed = _eb.Create()
|
||||
// .WithOkColor()
|
||||
// .WithDescription(novelData.Description.Replace("<br>", Environment.NewLine, StringComparison.InvariantCulture))
|
||||
// .WithTitle(novelData.Title)
|
||||
// .WithUrl(novelData.Link)
|
||||
// .WithImageUrl(novelData.ImageUrl)
|
||||
// .AddField(GetText(strs.authors), string.Join("\n", novelData.Authors), true)
|
||||
// .AddField(GetText(strs.status), novelData.Status, true)
|
||||
// .AddField(GetText(strs.genres), string.Join(" ", novelData.Genres.Any() ? novelData.Genres : new[] { "none" }), true)
|
||||
// .WithFooter($"{GetText(strs.score)} {novelData.Score}");
|
||||
//
|
||||
// await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
// }
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[Priority(0)]
|
||||
|
@@ -5,6 +5,7 @@ using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Searches.Services;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
|
||||
@@ -15,6 +16,25 @@ namespace NadekoBot.Modules.Searches
|
||||
[Group]
|
||||
public class FeedCommands : NadekoSubmodule<FeedsService>
|
||||
{
|
||||
private static readonly Regex YtChannelRegex =
|
||||
new Regex(@"youtube\.com\/(?:c\/|channel\/|user\/)?(?<channelid>[a-zA-Z0-9\-]{1,})");
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public Task YtUploadNotif(string url, [Leftover] ITextChannel channel = null)
|
||||
{
|
||||
var m = YtChannelRegex.Match(url);
|
||||
if (!m.Success)
|
||||
{
|
||||
return ReplyErrorLocalizedAsync(strs.invalid_input);
|
||||
}
|
||||
|
||||
var channelId = m.Groups["channelid"].Value;
|
||||
|
||||
return Feed("https://www.youtube.com/feeds/videos.xml?channel_id=" + channelId, channel);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
@@ -46,7 +66,7 @@ namespace NadekoBot.Modules.Searches
|
||||
}
|
||||
}
|
||||
|
||||
await ReplyErrorLocalizedAsync(strs.feed_not_valid).ConfigureAwait(false);
|
||||
await ReplyConfirmLocalizedAsync(strs.feed_not_valid).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
@@ -50,7 +50,6 @@ namespace NadekoBot.Modules.Searches.Services
|
||||
}
|
||||
}
|
||||
|
||||
// todo fix novel
|
||||
public async Task<NovelResult> GetNovelData(string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
@@ -59,7 +58,10 @@ namespace NadekoBot.Modules.Searches.Services
|
||||
query = query.Replace(" ", "-", StringComparison.InvariantCulture);
|
||||
try
|
||||
{
|
||||
var link = "http://www.novelupdates.com/series/" + Uri.EscapeDataString(query.Replace("/", " ", StringComparison.InvariantCulture));
|
||||
var link = "https://www.novelupdates.com/series/" + Uri.EscapeDataString(query
|
||||
.Replace(" ", "-")
|
||||
.Replace("/", " ")
|
||||
);
|
||||
link = link.ToLowerInvariant();
|
||||
var (ok, data) = await _cache.TryGetNovelDataAsync(link).ConfigureAwait(false);
|
||||
if (!ok)
|
||||
|
@@ -14,6 +14,8 @@ using NadekoBot.Extensions;
|
||||
using StackExchange.Redis;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using NadekoBot.Common.Collections;
|
||||
using NadekoBot.Common.Replacements;
|
||||
using NadekoBot.Db;
|
||||
@@ -342,19 +344,18 @@ namespace NadekoBot.Modules.Searches.Services
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public int ClearAllStreams(ulong guildId)
|
||||
public async Task<int> ClearAllStreams(ulong guildId)
|
||||
{
|
||||
// todo future clear streams
|
||||
int count;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.FollowedStreams));
|
||||
count = gc.FollowedStreams.Count;
|
||||
gc.FollowedStreams.Clear();
|
||||
uow.SaveChanges();
|
||||
}
|
||||
using var uow = _db.GetDbContext();
|
||||
var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.FollowedStreams));
|
||||
uow.RemoveRange(gc.FollowedStreams);
|
||||
|
||||
return count;
|
||||
foreach (var s in gc.FollowedStreams)
|
||||
await PublishUnfollowStream(s);
|
||||
|
||||
uow.SaveChanges();
|
||||
|
||||
return gc.FollowedStreams.Count;
|
||||
}
|
||||
|
||||
public async Task<FollowedStream> UnfollowStreamAsync(ulong guildId, int index)
|
||||
|
@@ -69,14 +69,14 @@ namespace NadekoBot.Modules.Searches
|
||||
fs.Type));
|
||||
}
|
||||
|
||||
// [NadekoCommand, Usage, Description, Aliases]
|
||||
// [RequireContext(ContextType.Guild)]
|
||||
// [UserPerm(GuildPerm.Administrator)]
|
||||
// public async Task StreamsClear()
|
||||
// {
|
||||
// var count = _service.ClearAllStreams(ctx.Guild.Id);
|
||||
// await ReplyErrorLocalizedAsync(strs.streams_cleared(count)));
|
||||
// }
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task StreamsClear()
|
||||
{
|
||||
var count = _service.ClearAllStreams(ctx.Guild.Id);
|
||||
await ReplyConfirmLocalizedAsync(strs.streams_cleared);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
@@ -191,7 +191,7 @@ namespace NadekoBot.Modules.Searches
|
||||
return;
|
||||
}
|
||||
|
||||
await ReplyErrorLocalizedAsync(strs.stream_message_set_all(count));
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_message_set_all(count));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
@@ -118,7 +118,7 @@ namespace NadekoBot.Modules.Searches
|
||||
|
||||
_searches.UserLanguages.AddOrUpdate(ucp, langs, (key, val) => langs);
|
||||
|
||||
await ReplyErrorLocalizedAsync(strs.atl_set(from, to));
|
||||
await ReplyConfirmLocalizedAsync(strs.atl_set(from, to));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
@@ -37,7 +37,7 @@ namespace NadekoBot.Modules.Utility
|
||||
public async Task AliasesClear()
|
||||
{
|
||||
var count = _service.ClearAliases(ctx.Guild.Id);
|
||||
await ReplyErrorLocalizedAsync(strs.aliases_cleared(count));
|
||||
await ReplyConfirmLocalizedAsync(strs.aliases_cleared(count));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
@@ -148,7 +148,7 @@ namespace NadekoBot.Modules.Utility
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.reminder_deleted(index + 1));
|
||||
await ReplyConfirmLocalizedAsync(strs.reminder_deleted(index + 1));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -183,7 +183,9 @@ namespace NadekoBot.Modules.Utility
|
||||
private string GetRepeaterInfoString(RunningRepeater runner)
|
||||
{
|
||||
var intervalString = Format.Bold(runner.Repeater.Interval.ToPrettyStringHM());
|
||||
var executesIn = runner.NextTime - DateTime.UtcNow;
|
||||
var executesIn = runner.NextTime < DateTime.UtcNow
|
||||
? TimeSpan.Zero
|
||||
: runner.NextTime - DateTime.UtcNow;
|
||||
var executesInString = Format.Bold(executesIn.ToPrettyStringHM());
|
||||
var message = Format.Sanitize(runner.Repeater.Message.TrimTo(50));
|
||||
|
||||
|
@@ -76,7 +76,9 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};")
|
||||
// because repeaters might've been modified meanwhile
|
||||
if (timeout > TimeSpan.Zero)
|
||||
{
|
||||
await Task.Delay(timeout);
|
||||
await Task.Delay(timeout > TimeSpan.FromMinutes(1)
|
||||
? TimeSpan.FromMinutes(1)
|
||||
: timeout);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -84,16 +86,17 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};")
|
||||
var now = DateTime.UtcNow + TimeSpan.FromSeconds(3);
|
||||
|
||||
var toExecute = new List<RunningRepeater>();
|
||||
while (true)
|
||||
lock (_repeaterQueue)
|
||||
{
|
||||
lock (_repeaterQueue)
|
||||
var current = _repeaterQueue.First;
|
||||
while (true)
|
||||
{
|
||||
var current = _repeaterQueue.First;
|
||||
|
||||
if (current is null || current.Value.NextTime > now)
|
||||
break;
|
||||
|
||||
toExecute.Add(current.Value);
|
||||
_repeaterQueue.RemoveFirst();
|
||||
current = current.Next;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,14 +124,24 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};")
|
||||
{
|
||||
if (rep.ErrorCount >= 10)
|
||||
{
|
||||
RemoveFromQueue(rep.Repeater.Id);
|
||||
await RemoveRepeaterInternal(rep.Repeater);
|
||||
return;
|
||||
}
|
||||
|
||||
rep.UpdateNextTime();
|
||||
AddToQueue(rep);
|
||||
|
||||
UpdatePosition(rep);
|
||||
}
|
||||
|
||||
|
||||
private void UpdatePosition(RunningRepeater rep)
|
||||
{
|
||||
lock (_queueLocker)
|
||||
{
|
||||
rep.UpdateNextTime();
|
||||
_repeaterQueue.Remove(rep);
|
||||
AddToQueue(rep);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> TriggerExternal(ulong guildId, int index)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
@@ -205,7 +218,7 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};")
|
||||
|
||||
var channel = _client.GetChannel(repeater.ChannelId) as ITextChannel;
|
||||
if (channel is null)
|
||||
channel = await _client.Rest.GetChannelAsync(repeater.ChannelId) as ITextChannel;
|
||||
try { channel = await _client.Rest.GetChannelAsync(repeater.ChannelId) as ITextChannel; } catch { }
|
||||
|
||||
if (channel is null)
|
||||
{
|
||||
|
@@ -92,5 +92,15 @@ namespace NadekoBot.Modules.Utility.Services
|
||||
var initialIntervalMultiplier = 1 - (triggerCount - Math.Truncate(triggerCount));
|
||||
return DateTime.UtcNow + (Repeater.Interval * initialIntervalMultiplier);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is RunningRepeater rr && rr.Repeater.Id == this.Repeater.Id;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.Repeater.Id;
|
||||
}
|
||||
}
|
||||
}
|
@@ -31,7 +31,7 @@ namespace NadekoBot.Modules.Xp
|
||||
|
||||
_service.XpReset(ctx.Guild.Id, userId);
|
||||
|
||||
await ReplyErrorLocalizedAsync(strs.reset_user(userId));
|
||||
await ReplyConfirmLocalizedAsync(strs.reset_user(userId));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
@@ -30,7 +30,7 @@ namespace NadekoBot.Modules.Xp.Services
|
||||
|
||||
private void Migrate()
|
||||
{
|
||||
if (_data.Version <= 1)
|
||||
if (_data.Version < 2)
|
||||
{
|
||||
ModifyConfig(c =>
|
||||
{
|
||||
|
@@ -123,16 +123,18 @@ namespace NadekoBot.Modules.Xp
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(2)]
|
||||
public async Task XpRoleReward(int level)
|
||||
{
|
||||
_service.ResetRoleReward(ctx.Guild.Id, level);
|
||||
await ReplyErrorLocalizedAsync(strs.xp_role_reward_cleared(level));
|
||||
await ReplyConfirmLocalizedAsync(strs.xp_role_reward_cleared(level));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public async Task XpRoleReward(int level, AddRemove action, [Leftover] IRole role)
|
||||
|
@@ -48,8 +48,56 @@ namespace NadekoBot.Services
|
||||
|
||||
bot.JoinedGuild += Bot_JoinedGuild;
|
||||
_client.LeftGuild += _client_LeftGuild;
|
||||
|
||||
_client.GuildMemberUpdated += ClientOnGuildMemberUpdated;
|
||||
}
|
||||
|
||||
private Task ClientOnGuildMemberUpdated(SocketGuildUser oldUser, SocketGuildUser newUser)
|
||||
{
|
||||
// if user is a new booster
|
||||
// or boosted again the same server
|
||||
if ((oldUser is { PremiumSince: null } && newUser is { PremiumSince: not null })
|
||||
|| (oldUser?.PremiumSince is DateTimeOffset oldDate
|
||||
&& newUser?.PremiumSince is DateTimeOffset newDate
|
||||
&& newDate > oldDate))
|
||||
{
|
||||
var conf = GetOrAddSettingsForGuild(newUser.Guild.Id);
|
||||
if (!conf.SendBoostMessage) return Task.CompletedTask;
|
||||
|
||||
_ = Task.Run(TriggerBoostMessage(conf, newUser));
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Func<Task> TriggerBoostMessage(GreetSettings conf, SocketGuildUser user) => async () =>
|
||||
{
|
||||
var channel = user.Guild.GetTextChannel(conf.BoostMessageChannelId);
|
||||
if (channel is null)
|
||||
return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(conf.BoostMessage))
|
||||
return;
|
||||
|
||||
var toSend = SmartText.CreateFrom(conf.BoostMessage);
|
||||
var rep = new ReplacementBuilder()
|
||||
.WithDefault(user, channel, user.Guild, _client)
|
||||
.Build();
|
||||
|
||||
try
|
||||
{
|
||||
var toDelete = await channel.SendAsync(rep.Replace(toSend));
|
||||
if (conf.BoostMessageDeleteAfter > 0)
|
||||
{
|
||||
toDelete.DeleteAfter(conf.BoostMessageDeleteAfter);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error sending boost message.");
|
||||
}
|
||||
};
|
||||
|
||||
private Task _client_LeftGuild(SocketGuild arg)
|
||||
{
|
||||
GuildConfigsCache.TryRemove(arg.Id, out _);
|
||||
@@ -124,6 +172,12 @@ namespace NadekoBot.Services
|
||||
return uow.GuildConfigsForId(gid, set => set).ChannelGreetMessageText;
|
||||
}
|
||||
}
|
||||
|
||||
public string GetBoostMessage(ulong gid)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
return uow.GuildConfigsForId(gid, set => set).BoostMessage;
|
||||
}
|
||||
|
||||
private Task ByeUsers(GreetSettings conf, ITextChannel channel, IUser user)
|
||||
=> ByeUsers(conf, channel, new[] {user});
|
||||
@@ -524,6 +578,49 @@ namespace NadekoBot.Services
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public bool SetBoostMessage(ulong guildId, ref string message)
|
||||
{
|
||||
message = message?.SanitizeMentions();
|
||||
|
||||
using var uow = _db.GetDbContext();
|
||||
var conf = uow.GuildConfigsForId(guildId, set => set);
|
||||
conf.BoostMessage = message;
|
||||
|
||||
var toAdd = GreetSettings.Create(conf);
|
||||
GuildConfigsCache.AddOrUpdate(guildId, toAdd,(_, _) => toAdd);
|
||||
|
||||
uow.SaveChanges();
|
||||
return conf.SendBoostMessage;
|
||||
}
|
||||
|
||||
public async Task SetBoostDel(ulong guildId, int timer)
|
||||
{
|
||||
if (timer < 0 || timer > 600)
|
||||
throw new ArgumentOutOfRangeException(nameof(timer));
|
||||
|
||||
using var uow = _db.GetDbContext();
|
||||
var conf = uow.GuildConfigsForId(guildId, set => set);
|
||||
conf.BoostMessageDeleteAfter = timer;
|
||||
|
||||
var toAdd = GreetSettings.Create(conf);
|
||||
GuildConfigsCache.AddOrUpdate(guildId, toAdd,(_, _) => toAdd);
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> ToggleBoost(ulong guildId, ulong channelId)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var conf = uow.GuildConfigsForId(guildId, set => set);
|
||||
conf.SendBoostMessage = !conf.SendBoostMessage;
|
||||
conf.BoostMessageChannelId = channelId;
|
||||
await uow.SaveChangesAsync();
|
||||
|
||||
var toAdd = GreetSettings.Create(conf);
|
||||
GuildConfigsCache.AddOrUpdate(guildId, toAdd,(_, _) => toAdd);
|
||||
return conf.SendBoostMessage;
|
||||
}
|
||||
}
|
||||
|
||||
public class GreetSettings
|
||||
@@ -542,6 +639,11 @@ namespace NadekoBot.Services
|
||||
|
||||
public bool SendChannelByeMessage { get; set; }
|
||||
public string ChannelByeMessageText { get; set; }
|
||||
|
||||
public bool SendBoostMessage { get; set; }
|
||||
public string BoostMessage { get; set; }
|
||||
public int BoostMessageDeleteAfter { get; set; }
|
||||
public ulong BoostMessageChannelId { get; set; }
|
||||
|
||||
public static GreetSettings Create(GuildConfig g) => new GreetSettings()
|
||||
{
|
||||
@@ -555,6 +657,11 @@ namespace NadekoBot.Services
|
||||
ChannelGreetMessageText = g.ChannelGreetMessageText,
|
||||
SendChannelByeMessage = g.SendChannelByeMessage,
|
||||
ChannelByeMessageText = g.ChannelByeMessageText,
|
||||
|
||||
SendBoostMessage = g.SendBoostMessage,
|
||||
BoostMessage = g.BoostMessage,
|
||||
BoostMessageDeleteAfter = g.BoostMessageDeleteAfter,
|
||||
BoostMessageChannelId = g.BoostMessageChannelId
|
||||
};
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user