Compare commits

...

57 Commits
3.0.0 ... 3.0.5

Author SHA1 Message Date
Kwoth
2d92424dd4 Version upped to 3.0.5 2021-09-20 19:45:13 +02:00
Kwoth
ffba03adbe Added guides on the different ways to run the bot on linux - contributed by Bark Ranger 2021-09-18 06:19:53 +02:00
Kwoth
76ebb87fb5 updated docs formatting and windows from source update guide 2021-09-17 19:02:18 +02:00
Kwoth
4a50c30c56 Fixed .logserver - should no longer throw an exception if you had no logsettings previously 2021-09-17 17:19:11 +02:00
Kwoth
e70a91ae60 Fixed images not automatically reloading on startup if the keys don't exist 2021-09-17 17:10:38 +02:00
Kwoth
3470762b30 woops, missed an extra bracket 2021-09-17 01:08:20 +02:00
Kwoth
2cbb4ecd3d Version upped to 3.0.4 2021-09-16 23:10:19 +02:00
Kwoth
619bee811d Fixed most of the commands which used wrong error color for confirm messages 2021-09-16 23:09:17 +02:00
Kwoth
a09be96200 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 several commands which used error color for success confirmation messages
2021-09-16 22:52:04 +02:00
Kwoth
81254ed5f6 Added %server.boosters% and %server.boost_level% placeholders 2021-09-16 19:22:05 +02:00
Kwoth
d82e3bd444 Merge branch 'v3' of https://gitlab.com/kwoth/nadekobot into v3 2021-09-16 19:09:51 +02:00
Kwoth
9011646b02 Update responses.pt-BR.json (POEditor.com) 2021-09-16 17:09:46 +00:00
Kwoth
2a1f45819d .waifugift pagination fixed - maximum number of pages properly scales with the number of items 2021-09-16 19:09:19 +02:00
Kwoth
d2d0cb9e03 - 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
2021-09-16 07:19:08 +02:00
Kwoth
ed039977c2 Release 3.0.3 2021-09-15 19:07:36 +02:00
Kwoth
786ede3290 Added .config games hangman.currency_reward and a property with the same name in games.yml 2021-09-15 01:50:48 +02:00
Kwoth
3edcca4927 Updated CHANGELOG.md, removed an unused enum 2021-09-15 01:30:26 +02:00
Kwoth
0635a0ddc8 Merge branch 'v3' of https://gitlab.com/kwoth/nadekobot into v3 2021-09-15 01:30:06 +02:00
Kwoth
cb514e4219 Update responses.es-ES.json (POEditor.com) 2021-09-14 23:29:46 +00:00
Kwoth
cccb37854c Added .imageonlychannel / .imageonly to prevent users from posting anything but images in the channel 2021-09-15 01:26:41 +02:00
Kwoth
35d549f4e6 Fixed .yun command help 2021-09-14 01:27:37 +02:00
Kwoth
c0d81a5d9c as a shortcut for subscribing to a youtube channel's rss feed**** 2021-09-14 01:11:03 +02:00
Kwoth
711d6c7caa Added .youtubeuploadnotif / .yun as a shortcut to a subscribing channel's rss feed 2021-09-14 01:09:31 +02:00
Kwoth
cea944cdb8 Update responses.uk-UA.json (POEditor.com) 2021-09-13 15:42:50 +00:00
Kwoth
2d70ea487e Update responses.ru-RU.json (POEditor.com) 2021-09-13 15:42:49 +00:00
Kwoth
9f65f979fd Update responses.fr-FR.json (POEditor.com) 2021-09-13 15:42:48 +00:00
Kwoth
c12b41ddd1 Added .massban 2021-09-13 17:41:33 +02:00
Kwoth
a5f9ac1540 .boostmsg will now properly show boost, and not greet message 2021-09-13 12:59:47 +02:00
Kwoth
8c5214def2 Ban .warnp will now prune user's messages 2021-09-13 01:30:11 +02:00
Kwoth
467a8bdaf6 Updated CHANGELOG.md 2021-09-12 22:09:32 +02:00
Kwoth
d115261536 Fixed .log commands 2021-09-12 22:07:56 +02:00
Kwoth
0429210a73 .boost should now us the correct channel, and it should send a message for users boosting the same server more than once 2021-09-12 21:05:19 +02:00
Kwoth
8ee1160a00 Added .boost, .boostmsg and .boostdel commands which allow you to have customizable messages when someone boosts your server, with auto-deletion support
- Updated response embed colors in greet commands
- Updated .greetmsg and .byemsg command help to match the new .boost command help
2021-09-12 20:44:35 +02:00
Kwoth
a27ea7a0aa Merge branch 'v3' of https://gitlab.com/kwoth/nadekobot into v3 2021-09-12 20:01:16 +02:00
Kwoth
9396f59a34 Update responses.de-DE.json (POEditor.com) 2021-09-12 17:58:57 +00:00
Kwoth
37cdea4f6a Removed .novel command as it no longer works 2021-09-12 13:31:57 +02:00
Kwoth
d028e23bc1 .timely will now correctly use Ok color 2021-09-12 01:22:13 +02:00
Kwoth
1df947d54b .rero now optionally takes a message id to which to attach the reaction roles 2021-09-12 01:07:19 +02:00
Kwoth
596a5c05e0 docs: Fixed more indentation in osx and migration guides 2021-09-12 00:18:54 +02:00
Kwoth
1d5a702b46 docs: Updated bot sharding guide in advanced creds guide, Added comments to coord.yml, Updated manual migration guide with correct indentation 2021-09-12 00:09:55 +02:00
Kwoth
712cb9300f docs: Fixed up jsons-explained.md (thx ala) 2021-09-11 22:48:48 +02:00
Kwoth
ab0d013035 docs: removed from source sidebar link 2021-09-11 22:28:28 +02:00
Kwoth
46b384f0f4 Docs format and link fixes 2021-09-11 16:52:14 +02:00
Kwoth
1284312a55 Clarified that running the 2.x bot prior to migration is mandatory 2021-09-11 00:58:22 +02:00
Kwoth
42bdc6417c Updated osx guide with instructions on installing wget 2021-09-11 00:47:08 +02:00
Kwoth
3715dd4e00 Fixed version in the changelog 2021-09-11 00:16:14 +02:00
Kwoth
ef13020d96 Updated changelog 2021-09-11 00:14:55 +02:00
Kwoth
dd708d4b7c Slight change to windows updater migration guide 2021-09-11 00:03:52 +02:00
Kwoth
8735dcf94d Upped version to 3.0.1 2021-09-11 00:01:05 +02:00
Kwoth
b6e03dcaed Fixed embedbuilder withimageurl and withurl (thx ene) 2021-09-10 20:43:28 +02:00
Kwoth
1785a27772 docs: Instruct users to run their old bots before updating to v3 2021-09-10 16:59:02 +02:00
Kwoth
9fbe43ec8f docs: removed minify entry from plugins in mkdocs 2021-09-09 20:56:25 +02:00
Kwoth
d3878414e0 docs: Remove the incompatible package 2021-09-09 20:54:32 +02:00
Kwoth
00f9a05cfc Set setup tools to 57.5.0, possible fix for failing build 2021-09-09 20:44:00 +02:00
Kwoth
d9e15a9abd Possible fix for broken docs build 2021-09-09 19:14:38 +02:00
Kwoth
8a5539448e Updated credentials.json references to creds.yml 2021-09-07 16:38:05 +02:00
Kwoth
e005ec11fd Correct version in releases-v3.json, also it's an array 2021-09-07 16:31:37 +02:00
106 changed files with 17937 additions and 6079 deletions

View File

@@ -91,7 +91,8 @@ 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"

View File

@@ -4,7 +4,76 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.
## Unreleased
## [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

View File

@@ -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`

View File

@@ -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`

View File

@@ -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.

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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_" }]

View File

@@ -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

View File

@@ -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
{

View File

@@ -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(

View File

@@ -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,
}
}

View File

@@ -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"
};
}
}

View File

@@ -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;

View File

@@ -113,33 +113,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.IgnoredChannels)
.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)

View File

@@ -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; }

View File

@@ -0,0 +1,8 @@
namespace NadekoBot.Services.Database.Models
{
public class ImageOnlyChannel : DbEntity
{
public ulong GuildId { get; set; }
public ulong ChannelId { get; set; }
}
}

View File

@@ -2,12 +2,12 @@
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 ulong GuildId { get; set; }
public ulong? LogOtherId { get; set; }
public ulong? MessageUpdatedId { get; set; }
public ulong? MessageDeletedId { get; set; }

View File

@@ -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
@@ -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,27 @@ 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());
#endregion
modelBuilder.Entity<ImageOnlyChannel>(ioc => ioc
.HasIndex(x => x.ChannelId)
.IsUnique());
}
}
}

File diff suppressed because it is too large Load Diff

View 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);
}
}
}

File diff suppressed because it is too large Load Diff

View 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");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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");
}
}
}

View File

@@ -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 =>
{
@@ -187,6 +187,29 @@ namespace NadekoBot.Migrations
b.ToTable("FollowedStream");
});
modelBuilder.Entity("NadekoBot.Services.Database.ImageOnlyChannel", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<ulong>("ChannelId")
.HasColumnType("INTEGER");
b.Property<DateTime?>("DateAdded")
.HasColumnType("TEXT");
b.Property<ulong>("GuildId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ChannelId")
.IsUnique();
b.ToTable("ImageOnlyChannels");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiAltSetting", b =>
{
b.Property<int>("Id")
@@ -741,6 +764,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 +818,6 @@ namespace NadekoBot.Migrations
b.Property<string>("Locale")
.HasColumnType("TEXT");
b.Property<int?>("LogSettingId")
.HasColumnType("INTEGER");
b.Property<string>("MuteRoleName")
.HasColumnType("TEXT");
@@ -801,6 +830,9 @@ namespace NadekoBot.Migrations
b.Property<string>("Prefix")
.HasColumnType("TEXT");
b.Property<bool>("SendBoostMessage")
.HasColumnType("INTEGER");
b.Property<bool>("SendChannelByeMessage")
.HasColumnType("INTEGER");
@@ -833,8 +865,6 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildId")
.IsUnique();
b.HasIndex("LogSettingId");
b.HasIndex("WarnExpireHours");
b.ToTable("GuildConfigs");
@@ -902,6 +932,9 @@ namespace NadekoBot.Migrations
b.Property<DateTime?>("DateAdded")
.HasColumnType("TEXT");
b.Property<ulong>("GuildId")
.HasColumnType("INTEGER");
b.Property<ulong?>("LogOtherId")
.HasColumnType("INTEGER");
@@ -940,6 +973,9 @@ namespace NadekoBot.Migrations
b.HasKey("Id");
b.HasIndex("GuildId")
.IsUnique();
b.ToTable("LogSettings");
});
@@ -2233,15 +2269,6 @@ namespace NadekoBot.Migrations
b.Navigation("GuildConfig");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", 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")
@@ -2307,7 +2334,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 =>

View File

@@ -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)]

View File

@@ -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
{

View File

@@ -61,7 +61,7 @@ namespace NadekoBot.Modules.Administration
if (msg is null)
return;
await ReplyErrorLocalizedAsync(strs.reprm(msg));
await ReplyConfirmLocalizedAsync(strs.reprm(msg));
}
}
}

View File

@@ -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"));
}
}

View File

@@ -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]

View File

@@ -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));
}
}

View File

@@ -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]

View File

@@ -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`"));
}
}
}

View File

@@ -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)

View File

@@ -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;
}
}

View File

@@ -93,15 +93,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.IgnoredChannels)
.ToList();
GuildLogSettings = configs
.ToDictionary(g => g.GuildId, g => g.LogSetting)
.ToDictionary(ls => ls.GuildId)
.ToConcurrent();
}
@@ -170,17 +170,15 @@ namespace NadekoBot.Modules.Administration.Services
int removed = 0;
using (var uow = _db.GetDbContext())
{
var config = uow.LogSettingsFor(gid);
LogSetting logSetting = GuildLogSettings.GetOrAdd(gid, (id) => config.LogSetting);
var logSetting = uow.LogSettingsFor(gid);
removed = logSetting.IgnoredChannels.RemoveWhere(ilc => ilc.ChannelId == cid);
config.LogSetting.IgnoredChannels.RemoveWhere(ilc => ilc.ChannelId == cid);
if (removed == 0)
{
var toAdd = new IgnoredLogChannel {ChannelId = cid};
logSetting.IgnoredChannels.Add(toAdd);
config.LogSetting.IgnoredChannels.Add(toAdd);
}
GuildLogSettings.AddOrUpdate(gid, logSetting, (_, _) => logSetting);
uow.SaveChanges();
}
@@ -209,11 +207,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 +227,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 +299,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)
{
@@ -1238,7 +1236,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:

View File

@@ -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;
}

View File

@@ -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.");
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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));
}
}

View File

@@ -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));
}

View File

@@ -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();

View File

@@ -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
{

View File

@@ -45,7 +45,7 @@ namespace NadekoBot.Modules.Games.Common.Hangman
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, ulong, Task> OnGameEnded = delegate { return Task.CompletedTask; };
public event Func<Hangman, string, char, Task> OnLetterAlreadyUsed = delegate { return Task.CompletedTask; };
public event Func<Hangman, string, char, Task> OnGuessFailed = delegate { return Task.CompletedTask; };
public event Func<Hangman, string, char, Task> OnGuessSucceeded = delegate { return Task.CompletedTask; };
@@ -68,7 +68,7 @@ namespace NadekoBot.Modules.Games.Common.Hangman
{
if (++Errors > MaxErrors)
{
var _ = OnGameEnded(this, null);
var _ = OnGameEnded(this, null, 0);
CurrentPhase = Phase.Ended;
}
}
@@ -102,7 +102,7 @@ namespace NadekoBot.Modules.Games.Common.Hangman
if (input != Term.Word) // failed
return;
var _ = OnGameEnded?.Invoke(this, userName);
var _ = OnGameEnded?.Invoke(this, userName, userId);
CurrentPhase = Phase.Ended;
return;
}
@@ -127,7 +127,7 @@ namespace NadekoBot.Modules.Games.Common.Hangman
else if (Term.Word.All(x => _previousGuesses.IsSupersetOf(Term.Word.ToLowerInvariant()
.Where(char.IsLetterOrDigit))))
{
var _ = OnGameEnded.Invoke(this, userName); // if all letters are guessed
var _ = OnGameEnded.Invoke(this, userName, userId); // if all letters are guessed
CurrentPhase = Phase.Ended;
}
else // guessed but not last letter

View File

@@ -8,6 +8,7 @@ using NadekoBot.Common.Attributes;
using NadekoBot.Modules.Games.Common.Hangman;
using NadekoBot.Modules.Games.Services;
using NadekoBot.Modules.Games.Common.Hangman.Exceptions;
using NadekoBot.Services;
namespace NadekoBot.Modules.Games
{
@@ -17,10 +18,14 @@ namespace NadekoBot.Modules.Games
public class HangmanCommands : NadekoSubmodule<GamesService>
{
private readonly DiscordSocketClient _client;
private readonly ICurrencyService _cs;
private readonly GamesConfigService _gcs;
public HangmanCommands(DiscordSocketClient client)
public HangmanCommands(DiscordSocketClient client, ICurrencyService cs, GamesConfigService gcs)
{
_client = client;
_cs = cs;
_gcs = gcs;
}
[NadekoCommand, Aliases]
@@ -83,7 +88,7 @@ namespace NadekoBot.Modules.Games
}
}
Task Hm_OnGameEnded(Hangman game, string winner)
Task Hm_OnGameEnded(Hangman game, string winner, ulong userId)
{
if (winner is null)
{
@@ -99,6 +104,10 @@ namespace NadekoBot.Modules.Games
return ctx.Channel.EmbedAsync(loseEmbed);
}
var reward = _gcs.Data.Hangman.CurrencyReward;
if (reward > 0)
_cs.AddAsync(userId, "hangman win", reward, true);
var winEmbed = _eb.Create().WithTitle($"Hangman Game ({game.TermType}) - Ended")
.WithDescription(Format.Bold($"{winner} Won."))
.AddField("It was", game.Term.GetWord())

View File

@@ -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
};
});
}
}
}
}

View File

@@ -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));
}
}

View File

@@ -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)

View File

@@ -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]

View File

@@ -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));
}
}

View File

@@ -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]

View File

@@ -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)]

View File

@@ -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]

View File

@@ -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)

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -148,7 +148,7 @@ namespace NadekoBot.Modules.Utility
}
else
{
await ReplyErrorLocalizedAsync(strs.reminder_deleted(index + 1));
await ReplyConfirmLocalizedAsync(strs.reminder_deleted(index + 1));
}
}

View File

@@ -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));

View File

@@ -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();

View File

@@ -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;
}
}
}

View File

@@ -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]

View File

@@ -30,7 +30,7 @@ namespace NadekoBot.Modules.Xp.Services
private void Migrate()
{
if (_data.Version <= 1)
if (_data.Version < 2)
{
ModifyConfig(c =>
{

View File

@@ -128,7 +128,7 @@ namespace NadekoBot.Modules.Xp
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]

View File

@@ -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
};
}
}

View File

@@ -55,10 +55,10 @@ namespace NadekoBot.Services
=> Wrap(_embed.WithAuthor(name, iconUrl, url));
public IEmbedBuilder WithUrl(string url)
=> Wrap(_embed.WithAuthor(url));
=> Wrap(_embed.WithUrl(url));
public IEmbedBuilder WithImageUrl(string url)
=> Wrap(_embed.WithAuthor(url));
=> Wrap(_embed.WithImageUrl(url));
public IEmbedBuilder WithThumbnailUrl(string url)
=> Wrap(_embed.WithThumbnailUrl(url));

View File

@@ -10,7 +10,6 @@ using Serilog;
namespace NadekoBot.Services
{
// todo check why is memory usage so unstable
public sealed class BotCredsProvider
{
private readonly int? _totalShards;
@@ -37,7 +36,7 @@ namespace NadekoBot.Services
if (string.IsNullOrWhiteSpace(_creds.Token))
{
Log.Error("Token is missing from credentials.json or Environment variables.\n" +
Log.Error("Token is missing from creds.yml or Environment variables.\n" +
"Add it and restart the program.");
Helpers.ReadErrorAndExit(5);
return;

View File

@@ -11,11 +11,12 @@ using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using NadekoBot.Common.ModuleBehaviors;
using Serilog;
namespace NadekoBot.Services
{
public sealed class RedisImagesCache : IImageCache
public sealed class RedisImagesCache : IImageCache, IReadyExecutor
{
private readonly ConnectionMultiplexer _con;
private readonly IBotCredentials _creds;
@@ -73,6 +74,14 @@ namespace NadekoBot.Services
Currency,
}
public async Task OnReadyAsync()
{
if (await AllKeysExist())
return;
await Reload();
}
public RedisImagesCache(ConnectionMultiplexer con, IBotCredentials creds)
{
_con = con;

View File

@@ -12,8 +12,6 @@ using NadekoBot.Modules.Administration;
namespace NadekoBot.Services
{
// todo future use guild locale more in the code (from guild settings) (for dates, currency, etc?)
// todo future maybe Write a sourcegen for response strings
// and use const/static fields (maybe even typed to enforce correct number of arguments)
public class Localization : ILocalization, INService
{
private readonly BotConfigService _bss;

View File

@@ -5,7 +5,6 @@ using System;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Discord;
using Discord.WebSocket;
namespace NadekoBot.Services
@@ -20,12 +19,13 @@ namespace NadekoBot.Services
private readonly string _redisKey;
private readonly EndPoint _redisEndpoint;
public RedisCache(ConnectionMultiplexer redis, IBotCredentials creds, DiscordSocketClient client)
public RedisCache(ConnectionMultiplexer redis, IBotCredentials creds,
IImageCache imageCache, ILocalDataCache dataCache)
{
Redis = redis;
_redisEndpoint = Redis.GetEndPoints().First();
LocalImages = new RedisImagesCache(Redis, creds);
LocalData = new RedisLocalDataCache(Redis, creds, client.ShardId);
LocalImages = imageCache;
LocalData = dataCache;
_redisKey = creds.RedisKey();
}

View File

@@ -7,6 +7,8 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Discord;
using Discord.WebSocket;
using Serilog;
namespace NadekoBot.Services
@@ -25,56 +27,33 @@ namespace NadekoBot.Services
public IReadOnlyDictionary<string, SearchPokemon> Pokemons
{
get
{
return Get<Dictionary<string, SearchPokemon>>("pokemon_list");
}
private set
{
Set("pokemon_list", value);
}
get => Get<Dictionary<string, SearchPokemon>>("pokemon_list");
private set => Set("pokemon_list", value);
}
public IReadOnlyDictionary<string, SearchPokemonAbility> PokemonAbilities
{
get
{
return Get<Dictionary<string, SearchPokemonAbility>>("pokemon_abilities");
}
private set
{
Set("pokemon_abilities", value);
}
get => Get<Dictionary<string, SearchPokemonAbility>>("pokemon_abilities");
private set => Set("pokemon_abilities", value);
}
public TriviaQuestion[] TriviaQuestions
{
get
{
return Get<TriviaQuestion[]>("trivia_questions");
}
private set
{
Set("trivia_questions", value);
}
get => Get<TriviaQuestion[]>("trivia_questions");
private set => Set("trivia_questions", value);
}
public IReadOnlyDictionary<int, string> PokemonMap
{
get
{
return Get<Dictionary<int, string>>("pokemon_map");
}
private set
{
Set("pokemon_map", value);
}
get => Get<Dictionary<int, string>>("pokemon_map");
private set => Set("pokemon_map", value);
}
public RedisLocalDataCache(ConnectionMultiplexer con, IBotCredentials creds, int shardId)
public RedisLocalDataCache(ConnectionMultiplexer con, IBotCredentials creds, DiscordSocketClient client)
{
_con = con;
_creds = creds;
var shardId = client.ShardId;
if (shardId == 0)
{

View File

@@ -23,7 +23,7 @@ namespace NadekoBot.Services
if (string.IsNullOrWhiteSpace(_creds.RestartCommand?.Cmd)
|| string.IsNullOrWhiteSpace(_creds.RestartCommand?.Args))
{
Log.Error("You must set RestartCommand.Cmd and RestartCommand.Args in credentials.json");
Log.Error("You must set RestartCommand.Cmd and RestartCommand.Args in creds.yml");
return false;
}

View File

@@ -19,7 +19,7 @@ namespace NadekoBot.Services
private readonly IBotCredentials _creds;
private readonly DateTime _started;
public const string BotVersion = "3.0.0";
public const string BotVersion = "3.0.5";
public string Author => "Kwoth#2452";
public string Library => "Discord.Net";

View File

@@ -28,19 +28,15 @@ namespace NadekoBot.Services
AddParsedProp("locale", bs => bs.DefaultLocale, ConfigParsers.Culture, ConfigPrinters.Culture);
AddParsedProp("prefix", bs => bs.Prefix, ConfigParsers.String, ConfigPrinters.ToString);
UpdateColors();
Migrate();
}
private void UpdateColors()
private void Migrate()
{
var ok = _data.Color.Ok;
var error = _data.Color.Error;
var pend = _data.Color.Pending;
}
protected override void OnStateUpdate()
{
UpdateColors();
if (_data.Version < 2)
{
ModifyConfig(c => c.Version = 2);
}
}
}
}

View File

@@ -32,6 +32,12 @@ greetdmtest:
- greetdmtest
byetest:
- byetest
boost:
- boost
boostmsg:
- boostmsg
boostdel:
- boostdel
logserver:
- logserver
logignore:
@@ -1134,6 +1140,9 @@ autobutts:
eightball:
- eightball
- 8ball
ytuploadnotif:
- ytuploadnotif
- yun
feed:
- feed
- feedadd
@@ -1181,14 +1190,14 @@ timely:
- timely
timelyreset:
- timelyreset
novel:
- novel
crypto:
- crypto
- c
rolelevelreq:
- rolelevelreq
- rlr
massban:
- massban
masskill:
- masskill
pathofexile:
@@ -1245,4 +1254,8 @@ crsimport:
- eximport
crsexport:
- crsexport
- exexport
- exexport
imageonlychannel:
- imageonlychannel
- imageonly
- imagesonly

View File

@@ -1,5 +1,5 @@
# DO NOT CHANGE
version: 1
version: 2
# Most commands, when executed, have a small colored line
# next to the response. The color depends whether the command
# is completed, errored or in progress (pending)
@@ -28,6 +28,15 @@ forwardToAllOwners: false
# Supports embeds. How it looks: https://puu.sh/B0BLV.png
dmHelpText: |-
{"description": "Type `%prefix%h` for help."}
# 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.
dmHelpTextKeywords:
- help
- commands
- cmds
- module
- can you do
# This is the response for the .h command
helpText: |-
{

View File

@@ -1,3 +1,9 @@
# DO NOT CHANGE
version: 1
# Hangman related settings (.hangman command)
hangman:
# The amount of currency awarded to the winner of a hangman game
currencyReward: 0
# Trivia related settings (.t command)
trivia:
# The amount of currency awarded to the winner of the trivia game.

View File

@@ -31,7 +31,12 @@ greet:
args:
- ""
greetmsg:
desc: "Sets a new join announcement message which will be shown in the server's channel. Type `%user.mention%` if you want to mention the new member. Using it with no message will show the current greet message. You can use embed json from <https://eb.nadeko.bot/> instead of a regular text, if you want the message to be embedded."
desc: |-
Sets a new join announcement message which will be shown in the server's channel.
Type `%user.mention%` if you want to mention the new member.
Full list of placeholders can be found here <https://nadekobot.readthedocs.io/en/latest/placeholders/>
Using it with no message will show the current greet message.
You can use embed json from <https://eb.nadeko.bot/> instead of a regular text, if you want the message to be embedded.
args:
- "Welcome, %user.mention%."
bye:
@@ -39,7 +44,12 @@ bye:
args:
- ""
byemsg:
desc: "Sets a new leave announcement message. Type `%user.mention%` if you want to show the name the user who left. Type `%id%` to show id. Using this command with no message will show the current bye message. You can use embed json from <https://eb.nadeko.bot/> instead of a regular text, if you want the message to be embedded."
desc: |-
Sets a new leave announcement message.
Type `%user.mention%` if you want to show the name the user who left.
Full list of placeholders can be found here <https://nadekobot.readthedocs.io/en/latest/placeholders/>
Using this command with no message will show the current bye message.
You can use embed json from <https://eb.nadeko.bot/> instead of a regular text, if you want the message to be embedded.
args:
- "%user.mention% has left."
byedel:
@@ -66,6 +76,24 @@ byetest:
args:
- ""
- "@SomeoneElse"
boost:
desc: "Toggles anouncements on the current channel when someone boosts the server."
args:
- ""
boostmsg:
desc: |-
Sets a new boost announcement message.
Type `%user.mention%` if you want to show the name the user who left.
Full list of placeholders can be found here <https://nadekobot.readthedocs.io/en/latest/placeholders/>
Using this command with no message will show the current boost message.
You can use embed json from <https://eb.nadeko.bot/> instead of a regular text, if you want the message to be embedded.
args:
- "%user.mention% has boosted the server!!!"
boostdel:
desc: "Sets the time it takes (in seconds) for boost messages to be auto-deleted. Set it to `0` to disable automatic deletion."
args:
- "0"
- "30"
logserver:
desc: "Enables or Disables ALL log events. If enabled, all log events will log to this channel."
args:
@@ -1152,7 +1180,7 @@ forwardmessages:
args:
- ""
forwardtoall:
desc: "Toggles whether messages will be forwarded to all bot owners or only to the first one specified in the credentials.json file"
desc: "Toggles whether messages will be forwarded to all bot owners or only to the first one specified in the creds.yml file"
args:
- ""
resetperms:
@@ -1884,6 +1912,12 @@ eightball:
desc: "Ask the 8ball a yes/no question."
args:
- "Is b1nzy a nice guy?"
ytuploadnotif:
desc: |-
Subscribe to a youtube channel's upload rss feed.
Shortcut for `.feed https://www.youtube.com/feeds/videos.xml?channel_id=%3Cyoutube_channel_id`
args:
- "https://www.youtube.com/channel/UCSJ4gkVC6NrvII8umztf0Ow"
feed:
desc: "Subscribes to a feed. Bot will post an update up to once every 10 seconds. You can have up to 10 feeds on one server. All feeds must have unique URLs. Set a channel as a second optional parameter to specify where to send the updates."
args:
@@ -1973,10 +2007,6 @@ timelyreset:
desc: "Resets all user timeouts on `{0}timely` command."
args:
- ""
novel:
desc: "Searches for a novel on `http://novelupdates.com/`. You have to provide an exact name."
args:
- "the nine cauldrons"
crypto:
desc: "Shows basic stats about a cryptocurrency from coinmarketcap.com. You can use either a name or an abbreviation of the currency."
args:
@@ -1986,6 +2016,10 @@ rolelevelreq:
desc: "Set a level requirement on a self-assignable role."
args:
- "5 SomeRole"
massban:
desc: "Bans multiple users at once. Specify a space separated list of IDs of users who you wish to ban."
args:
- "123123123 3333333333 444444444"
masskill:
desc: "Specify a new-line separated list of `userid reason`. You can use Username#discrim instead of UserId. Specified users will be banned from the current server, blacklisted from the bot, and have all of their flowers taken away."
args:
@@ -2008,10 +2042,12 @@ rollduel:
- "50 @Someone"
- "@Challenger"
reactionroles:
desc: "Specify role names and server emojis with which they're represented, the bot will then add those emojis to the previous message in the channel, and users will be able to get the roles by clicking on the emoji. You can set 'excl' as the first parameter to make them exclusive. You can have up to 5 of these enabled on one server at a time."
desc: "Specify role names and server emojis with which they're represented, the bot will then add those emojis to the previous message in the channel, and users will be able to get the roles by clicking on the emoji. You can set 'excl' as the parameter before the reactions and roles to make them exclusive. You can have up to 5 of these enabled on one server at a time. Optionally you can specify target message if you don't want it to be the previous one."
args:
- "Gamer :SomeServerEmoji: Streamer :Other: Watcher :Other2:"
- "excl Horde :Horde: Alliance :Alliance:"
- "886382471732662332 excl Horde :Horde: Alliance :Alliance:"
- "886382471732662332 Gamer :SomeServerEmoji: Streamer :Other: Watcher :Other2:"
reactionroleslist:
desc: "Lists all ReactionRole messages on this channel and their indexes."
args:
@@ -2078,3 +2114,9 @@ nhentai:
args:
- "273426"
- "cute girl"
imageonlychannel:
desc: |-
Toggles whether the channel only allows images.
Users who send more than a few non-image messages will be banned from using the channel.
args:
- ""

View File

@@ -807,7 +807,7 @@
"feed_out_of_range": "مؤشر خارج النطاق.",
"feed_removed": "تمت إزالة الخلاصة.",
"feed_no_feed": "لم تشترك في أي خلاصات على هذا الشبكة.",
"restart_fail": "يجب إعداد RestartCommand في ملف credentials.json",
"restart_fail": "يجب إعداد RestartCommand في ملف creds.yml",
"restarting": "إعادة تشغيل",
"edit_fail": "",
"streaming": "بث",

View File

@@ -807,7 +807,7 @@
"feed_out_of_range": "Index mimo dosah.",
"feed_removed": "Feed odebrán.",
"feed_no_feed": "Neodebíráš žádný feed na tomto serveru.",
"restart_fail": "Musíš nastavit RestartCommand ve svém souboru credentials.json",
"restart_fail": "Musíš nastavit RestartCommand ve svém souboru creds.yml",
"restarting": "Restartování.",
"edit_fail": "Vlastní reakce s tímto ID neexistuje.",
"streaming": "Streamování.",

File diff suppressed because it is too large Load Diff

View File

@@ -69,6 +69,13 @@
"greetmsg_new": "New greet message set.",
"greet_off": "Greet announcements disabled.",
"greet_on": "Greet announcements enabled on this channel.",
"boost_on": "Boost announcements enabled on this channel.",
"boost_off": "Boost announcements disabled.",
"boostmsg_cur": "Current boost message: {0}",
"boostmsg_enable": "Enable boost messages by typing {0}",
"boostmsg_new": "New boost message set.",
"boostdel_off": "Automatic deletion of boost messages has been disabled.",
"boostdel_on": "Boost messages will be deleted after {0} seconds.",
"hierarchy": "You can't use this command on users with a role higher or equal than yours (or mine) in the role hierarchy.",
"role_too_high": "You can't use this command with roles which are above your highest role, unless you're server administrator.",
"images_loading": "Images will be reloaded within a few seconds.",
@@ -856,7 +863,7 @@
"feed_out_of_range": "Index out of range.",
"feed_removed": "Feed removed.",
"feed_no_feed": "You haven't subscribed to any feeds on this server.",
"restart_fail": "You must setup RestartCommand in your credentials.json",
"restart_fail": "You must setup RestartCommand in your creds.yml",
"restarting": "Restarting.",
"edit_fail": "Custom reaction with that ID does not exist.",
"streaming": "Streaming",
@@ -882,6 +889,8 @@
"self_assign_not_level": "That self-assignable role requires at least server level {0}.",
"invalid": "Invalid / Can't be found ({0})",
"mass_kill_in_progress": "Mass Banning and Blacklisting of {0} users is in progress...",
"mass_ban_in_progress": "Banning {0} users...",
"mass_ban_completed": "Banned {0} users.",
"mass_kill_completed": "Mass Banning and Blacklisting of {0} users is complete.",
"failed_finding_novel": "Can't find that novel. Make sure you've typed the exact full name, and that it exists on novelupdates.com",
"club_transfered": "Ownership of the club {0} has been transferred to {1}",
@@ -953,5 +962,7 @@
"empty_page": "This page is empty.",
"pages": "Pages",
"favorites": "Favorites",
"tags": "Tags"
"tags": "Tags",
"imageonly_enable": "This channel is now image-only.",
"imageonly_disable": "This channel is no longer image-only."
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -807,7 +807,7 @@
"feed_out_of_range": "Az index a tartományon kívül.",
"feed_removed": "Hírcsatorna eltávolítva.",
"feed_no_feed": "Nem iratkoztál fel egyetlen hírcsatornára sem ezen a szerveren.",
"restart_fail": "Be kell állítanod a RestartCommand parancsot a credentials.json fájlodban",
"restart_fail": "Be kell állítanod a RestartCommand parancsot a creds.yml fájlodban",
"restarting": "Újraindítás.",
"edit_fail": "Egyedi reakció azzal az azonosítóval nem létezik.",
"streaming": "Közvetítés",

View File

@@ -807,7 +807,7 @@
"feed_out_of_range": "Index di luar batas.",
"feed_removed": "Feed dibuang",
"feed_no_feed": "Anda belum melanggani feed apapun di server ini.",
"restart_fail": "Anda harus menyiapkan RestartCommand di credentials.json anda",
"restart_fail": "Anda harus menyiapkan RestartCommand di creds.yml anda",
"restarting": "Memulai ulang.",
"edit_fail": "Reaksi kustom dengan ID tersebut tidak ada.",
"streaming": "Streaming",

View File

@@ -807,7 +807,7 @@
"feed_out_of_range": "Indice fuori portata.",
"feed_removed": "Feed rimosso.",
"feed_no_feed": "Non sei iscritto a nessun feed di questo server.",
"restart_fail": "Devi impostate RestartCommand nel tuo credentials.json",
"restart_fail": "Devi impostate RestartCommand nel tuo creds.yml",
"restarting": "Riavvio.",
"edit_fail": "Non esiste una reazione personalizzata sotto questo ID.",
"streaming": "Streaming",

View File

@@ -807,7 +807,7 @@
"feed_out_of_range": "",
"feed_removed": "",
"feed_no_feed": "",
"restart_fail": "Restartコマンドをcredentials.jsonで設定しなければなりません。",
"restart_fail": "Restartコマンドをcreds.ymlで設定しなければなりません。",
"restarting": "再起動しています。",
"edit_fail": "そのIDのカスタムリアクションは存在しません。",
"streaming": "ストリーミング",

View File

@@ -807,7 +807,7 @@
"feed_out_of_range": "인덱스가 범위를 벗어났습니다.",
"feed_removed": "피드가 제거되었습니다.",
"feed_no_feed": "이 서버에 피드를 구독하지 않았습니다.",
"restart_fail": "당신은 credentials.json에 RestartCommand를 설정해야합니다.",
"restart_fail": "당신은 creds.yml에 RestartCommand를 설정해야합니다.",
"restarting": "재시동 중.",
"edit_fail": "요청하신 ID에 대한 커스텀 리액션을 찾지 못했습니다.",
"streaming": "스트리밍",

View File

@@ -807,7 +807,7 @@
"feed_out_of_range": "Indeks utenfor rekkevidde.",
"feed_removed": "Feed fjernet.",
"feed_no_feed": "Du følger ingen feeds på denne serveren.",
"restart_fail": "Du må sette opp RestartCommand i credentials.json",
"restart_fail": "Du må sette opp RestartCommand i creds.yml",
"restarting": "Starter på nytt.",
"edit_fail": "Tilpasset reaksjon med den ID\u0027en eksisterer ikke.",
"streaming": "Strømmer",

View File

@@ -807,7 +807,7 @@
"feed_out_of_range": "Index buiten bereik.",
"feed_removed": "Feed verwijdert.",
"feed_no_feed": "Je bent nog niet geabonneerd op een van de feeds van deze server.",
"restart_fail": "Je moet RestartCommand instellen in jouw credentials.json",
"restart_fail": "Je moet RestartCommand instellen in jouw creds.yml",
"restarting": "Herstarten.",
"edit_fail": "Aangepaste reactie met dat ID bestaat niet.",
"streaming": "Streamen",

File diff suppressed because it is too large Load Diff

View File

@@ -807,7 +807,7 @@
"feed_out_of_range": "Index negăsit.",
"feed_removed": "Feed eliminat.",
"feed_no_feed": "Nu te-ai abonat la niciun feed pe acest server.",
"restart_fail": "Trebuie să instalezi RestartCommand in credentials.json",
"restart_fail": "Trebuie să instalezi RestartCommand in creds.yml",
"restarting": "Se restartează.",
"edit_fail": "Reacție personalizată cu acel ID nu există.",
"streaming": "Transmițând flux live",

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