mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-11 01:38:27 -04:00
Compare commits
35 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
7ba345b0fc | ||
|
5d775c9589 | ||
|
2bd8ead10c | ||
|
1148ba3e6e | ||
|
5498bec8cc | ||
|
9eed0c6be5 | ||
|
acf6b7cf58 | ||
|
8598419c5f | ||
|
758093eb32 | ||
|
f44dd03f1a | ||
|
8ac5ec9f57 | ||
|
5209ba802a | ||
|
adfce6670c | ||
|
f8fbc71985 | ||
|
d4e2516a17 | ||
|
f62a67e2e6 | ||
|
7895b5e702 | ||
|
28c8ccfb5f | ||
|
79026e0c27 | ||
|
8b228b840a | ||
|
f61c1a159d | ||
|
2cdcdb2b23 | ||
|
8d0a3ecb20 | ||
|
97f1405a94 | ||
|
0622236523 | ||
|
0f240925e8 | ||
|
d583e2b99a | ||
|
2d72f6f498 | ||
|
5143e42dff | ||
|
790c36df8d | ||
|
15709bc8fb | ||
|
f280f72227 | ||
|
4069368beb | ||
|
e036a2d3c9 | ||
|
62a16f3faf |
178
CHANGELOG.md
178
CHANGELOG.md
@@ -2,6 +2,99 @@
|
|||||||
|
|
||||||
Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
|
Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
|
||||||
|
|
||||||
|
|
||||||
|
## [5.3.3] - 15.12.2024
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
- `.notify` commands are no longer owner only, they now require Admin permissions
|
||||||
|
- `.notify` messages can now mention anyone
|
||||||
|
|
||||||
|
## [5.3.2] - 14.12.2024
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
- `.banner` should be working properly now with both server and global user banners
|
||||||
|
|
||||||
|
## [5.3.1] - 13.12.2024
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
|
||||||
|
- `.translate` will now use 2 embeds, to allow for longer messages
|
||||||
|
- Added role icon to `.inrole`, if it exists
|
||||||
|
- `.honeypot` will now add a 'Honeypot' as a ban reason.
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
- `.winlb` looks better, has a title, shows 9 entries now
|
||||||
|
- `.sar ex` help updated
|
||||||
|
- `.banner` partially fixed, it still can't show global banners, but it will show guild ones correctly, in a good enough size
|
||||||
|
- `.sclr` will now show correct color hexes without alpha
|
||||||
|
- `.dmcmd` will now correctly block commands in dms, not globally
|
||||||
|
|
||||||
|
## [5.3.0] - 10.12.2024
|
||||||
|
|
||||||
|
## Added
|
||||||
|
|
||||||
|
- Added `.minesweeper` / `.mw` command - spoiler-based minesweeper minigame. Just for fun
|
||||||
|
- Added `.temprole` command - add a role to a user for a certain amount of time, after which the role will be removed
|
||||||
|
- Added `.xplevelset` - you can now set a level for a user in your server
|
||||||
|
- Added `.winlb` command - leaderboard of top gambling wins
|
||||||
|
- Added `.notify` command
|
||||||
|
- Specify an event to be notified about, and the bot will post the specified message in the current channel when the
|
||||||
|
event occurs
|
||||||
|
- A few events supported right now:
|
||||||
|
- `UserLevelUp` when user levels up in the server
|
||||||
|
- `AddRoleReward` when a role is added to a user through .xpreward system
|
||||||
|
- `RemoveRoleReward` when a role is removed from a user through .xpreward system
|
||||||
|
- `Protection` when antialt, antiraid or antispam protection is triggered
|
||||||
|
- Added `.banner` command to see someone's banner
|
||||||
|
- Selfhosters:
|
||||||
|
- Added `.dmmod` and `.dmcmd` - you can now disable or enable whether commands or modules can be executed in bot's
|
||||||
|
DMs
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
|
||||||
|
- Giveaway improvements
|
||||||
|
- Now mentions winners in a separate message
|
||||||
|
- Shows the timestamp of when the giveaway ends
|
||||||
|
- Xp Changes
|
||||||
|
- Removed awarded xp (the number in the brackets on the xp card)
|
||||||
|
- Awarded xp, (or the new level set) now directly apply to user's real xp
|
||||||
|
- Server xp notifications are now set by the server admin/manager in a specified channel
|
||||||
|
- `.sclr show` will now show hex code of the current color
|
||||||
|
- Queueing a song will now restart the playback if the queue is on the last track and stopped (there were no more tracks
|
||||||
|
to play)
|
||||||
|
- `.translate` will now use 2 embeds instead of 1
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
- .setstream and .setactivity will now pause .ropl (rotating statuses)
|
||||||
|
- Fixed `.sar ex` help description
|
||||||
|
|
||||||
|
## Removed
|
||||||
|
|
||||||
|
- `.xpnotify` command, superseded by `.notify`, although as of right now you can't post user's level up in the same
|
||||||
|
channel user last typed, because you have to specify a channel where the notify messages will be posted
|
||||||
|
|
||||||
|
## [5.2.4] - 27.11.2024
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
- More fixes for .sclr
|
||||||
|
- `.iamn` fixed
|
||||||
|
|
||||||
|
## [5.2.3] - 27.11.2024
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
- `.iam` Fixed
|
||||||
|
- `.sclr` will now properly change color on many commands it didn't work previously
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `.rps` now also has bet amount in the result, like other gambling commands
|
||||||
|
|
||||||
## [5.2.2] - 27.11.2024
|
## [5.2.2] - 27.11.2024
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
@@ -26,46 +119,49 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
|
|
||||||
- Added `.todo undone` command to unmark a todo as done
|
- Added `.todo undone` command to unmark a todo as done
|
||||||
- Added Button Roles!
|
- Added Button Roles!
|
||||||
- `.btr a` to add a button role to the specified message
|
- `.btr a` to add a button role to the specified message
|
||||||
- `.btr list` to list all button roles on the server
|
- `.btr list` to list all button roles on the server
|
||||||
- `.btr rm` to remove a button role from the specified message
|
- `.btr rm` to remove a button role from the specified message
|
||||||
- `.btr rma` to remove all button roles on the specified message
|
- `.btr rma` to remove all button roles on the specified message
|
||||||
- `.btr excl` to toggle exclusive button roles (only 1 role per message or any number)
|
- `.btr excl` to toggle exclusive button roles (only 1 role per message or any number)
|
||||||
- Use `.h btr` for more info
|
- Use `.h btr` for more info
|
||||||
- Added `.wrongsong` which will delete the last queued song.
|
- Added `.wrongsong` which will delete the last queued song.
|
||||||
- Useful in case you made a mistake, or the bot queued a wrong song
|
- Useful in case you made a mistake, or the bot queued a wrong song
|
||||||
- It will reset after a shuffle or fairplay toggle, or similar events.
|
- It will reset after a shuffle or fairplay toggle, or similar events.
|
||||||
- Added Server color Commands!
|
- Added Server color Commands!
|
||||||
- Every Server can now set their own colors for ok/error/pending embed (the default green/red/yellow color on the left side of the message the bot sends)
|
- Every Server can now set their own colors for ok/error/pending embed (the default green/red/yellow color on the
|
||||||
- Use `.h .sclr` to see the list of commands
|
left side of the message the bot sends)
|
||||||
- `.sclr show` will show the current server colors
|
- Use `.h .sclr` to see the list of commands
|
||||||
- `.sclr ok <color hex>` to set ok color
|
- `.sclr show` will show the current server colors
|
||||||
- `.sclr warn <color hex>` to set warn color
|
- `.sclr ok <color hex>` to set ok color
|
||||||
- `.sclr error <color hex>` to set error color
|
- `.sclr warn <color hex>` to set warn color
|
||||||
|
- `.sclr error <color hex>` to set error color
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Self Assigned Roles reworked! Use `.h .sar` for the list of commands
|
- Self Assigned Roles reworked! Use `.h .sar` for the list of commands
|
||||||
- `.sar autodel`
|
- `.sar autodel`
|
||||||
- Toggles the automatic deletion of the user's message and Nadeko's confirmations for .iam and .iamn commands.
|
- Toggles the automatic deletion of the user's message and Nadeko's confirmations for .iam and .iamn commands.
|
||||||
- `.sar ad`
|
- `.sar ad`
|
||||||
- Adds a role to the list of self-assignable roles. You can also specify a group.
|
- Adds a role to the list of self-assignable roles. You can also specify a group.
|
||||||
- If 'Exclusive self-assignable roles' feature is enabled (.sar exclusive), users will be able to pick one role per group.
|
- If 'Exclusive self-assignable roles' feature is enabled (.sar exclusive), users will be able to pick one role
|
||||||
- `.sar groupname`
|
per group.
|
||||||
- Sets a self assignable role group name. Provide no name to remove.
|
- `.sar groupname`
|
||||||
- `.sar remove`
|
- Sets a self assignable role group name. Provide no name to remove.
|
||||||
- Removes a specified role from the list of self-assignable roles.
|
- `.sar remove`
|
||||||
- `.sar list`
|
- Removes a specified role from the list of self-assignable roles.
|
||||||
- Lists self-assignable roles. Shows 20 roles per page.
|
- `.sar list`
|
||||||
- `.sar exclusive`
|
- Lists self-assignable roles. Shows 20 roles per page.
|
||||||
- Toggles whether self-assigned roles are exclusive. While enabled, users can only have one self-assignable role per group.
|
- `.sar exclusive`
|
||||||
- `.sar rolelvlreq`
|
- Toggles whether self-assigned roles are exclusive. While enabled, users can only have one self-assignable role
|
||||||
- Set a level requirement on a self-assignable role.
|
per group.
|
||||||
- `.sar grouprolereq`
|
- `.sar rolelvlreq`
|
||||||
- Set a role that users have to have in order to assign a self-assignable role from the specified group.
|
- Set a level requirement on a self-assignable role.
|
||||||
- `.sar groupdelete`
|
- `.sar grouprolereq`
|
||||||
- Deletes a self-assignable role group
|
- Set a role that users have to have in order to assign a self-assignable role from the specified group.
|
||||||
- `.iam` and `.iamn` are unchanged
|
- `.sar groupdelete`
|
||||||
|
- Deletes a self-assignable role group
|
||||||
|
- `.iam` and `.iamn` are unchanged
|
||||||
- Removed patron limits from Reaction Roles. Anyone can have as many reros as they like.
|
- Removed patron limits from Reaction Roles. Anyone can have as many reros as they like.
|
||||||
- `.timely` captcha made stronger and cached per user.
|
- `.timely` captcha made stronger and cached per user.
|
||||||
- `.bsreset` price reduced by 90%
|
- `.bsreset` price reduced by 90%
|
||||||
@@ -79,9 +175,9 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added `.rakeback` command, get a % of house edge back as claimable currency
|
- Added `.rakeback` command, get a % of house edge back as claimable currency
|
||||||
- Added `.snipe` command to quickly get a copy of a posted message as an embed
|
- Added `.snipe` command to quickly get a copy of a posted message as an embed
|
||||||
- You can reply to a message to snipe that message
|
- You can reply to a message to snipe that message
|
||||||
- Or just type .snipe and the bot will snipe the last message in the channel with content or image
|
- Or just type .snipe and the bot will snipe the last message in the channel with content or image
|
||||||
- Added `.betstatsreset` / `.bsreset` command to reset your stats for a fee
|
- Added `.betstatsreset` / `.bsreset` command to reset your stats for a fee
|
||||||
- Added `.gamblestatsreset` / `.gsreset` owner-only command to reset bot stats for all games
|
- Added `.gamblestatsreset` / `.gsreset` owner-only command to reset bot stats for all games
|
||||||
- Added `.waifuclaims` command which lists all of your claimed waifus
|
- Added `.waifuclaims` command which lists all of your claimed waifus
|
||||||
@@ -91,9 +187,9 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
|
|
||||||
- `.divorce` no longer has a cooldown
|
- `.divorce` no longer has a cooldown
|
||||||
- `.betroll` has a 2% better payout
|
- `.betroll` has a 2% better payout
|
||||||
- `.slot` payout balanced out (less volatile), reduced jackpot win but increased other wins,
|
- `.slot` payout balanced out (less volatile), reduced jackpot win but increased other wins,
|
||||||
- now has a new symbol, wheat
|
- now has a new symbol, wheat
|
||||||
- worse around 1% in total (now shares the top spot with .bf)
|
- worse around 1% in total (now shares the top spot with .bf)
|
||||||
|
|
||||||
## [5.1.19] - 04.11.2024
|
## [5.1.19] - 04.11.2024
|
||||||
|
|
||||||
@@ -112,7 +208,7 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
|
|
||||||
- `.plant/pick` password font size will be slightly bigger
|
- `.plant/pick` password font size will be slightly bigger
|
||||||
- `.race` will now have 82-94% payout rate based on the number of players playing (1-12, x0.01 per player).
|
- `.race` will now have 82-94% payout rate based on the number of players playing (1-12, x0.01 per player).
|
||||||
- Any player over 12 won't increase payout
|
- Any player over 12 won't increase payout
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
# Config
|
# Config
|
||||||
`.config` is the new `.bce`, it gives you a fast and easy way to edit most bot settings/values. Use `.h .config` for explanation.
|
`.config` gives you a fast and easy way to edit most bot settings/values. Use `.h .config` for explanation.
|
||||||
|
|
||||||
Use `.config` to see the list of editable config files
|
Use `.config` to see the list of editable config files
|
||||||
Use `.config <config-name>` to see the list of settable properties on that config
|
Use `.config <config-name>` to see the list of settable properties on that config
|
||||||
|
@@ -1,27 +1,30 @@
|
|||||||
## Creds Guide
|
## Creds Guide
|
||||||
|
|
||||||
This document aims to guide you through the process of creating a Discord account for your bot
|
This guide will show you how to create your own discord bot, invite it to your server, and copy it's credentials to your `creds.yml` in order to run your bot.
|
||||||
(the Discord Bot application), and inviting that account into your Discord server.
|
|
||||||
|
- Start by opening your creds.yml
|
||||||
|
- If you're on a windows installer version, click on the creds button next to your bot's RUN button.
|
||||||
|
- If you're on linux from source or windows from source version, open `nadekobot/output/creds.yml`. Please use visual studio code, notepad++ or another code editor. Usage of notepad is discouraged.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
- Go to [the Discord developer application page][DiscordApp].
|
1. Go to [the Discord developer application page][DiscordApp].
|
||||||
- Log in with your Discord account.
|
2. Log in with your Discord account.
|
||||||
- Click **New Application**.
|
3. Click **New Application**.
|
||||||
- Fill out the `Name` field however you like.
|
3. Fill out the `Name` field however you like, accept the terms, and confirm.
|
||||||
- Go to the **Bot** tab on the left sidebar.
|
1. Go to the **Bot** tab on the left sidebar.
|
||||||
- Click on the `Add a Bot` button and confirm that you do want to add a bot to this app.
|
1. Click on the `Add a Bot` button and confirm that you do want to add a bot to this app.
|
||||||
- **Optional:** Add bot's avatar and description.
|
1. **Optional:** Add bot's avatar and description.
|
||||||
- Copy your Token to `creds.yml` as shown above.
|
1. Copy your Token to `creds.yml` as shown above.
|
||||||
- Scroll down to the **`Privileged Gateway Intents`** section
|
1. Scroll down to the **`Privileged Gateway Intents`** section
|
||||||
- **Enable the following:**
|
- Enable the following:
|
||||||
- **PRESENCE INTENT**
|
- **PRESENCE INTENT**
|
||||||
- **SERVER MEMBERS INTENT**
|
- **SERVER MEMBERS INTENT**
|
||||||
- **MESSAGE CONTENT INTENT**
|
- **MESSAGE CONTENT INTENT**
|
||||||
|
|
||||||
These are required for a number of features to function properly, and all should be on.
|
These are required for a number of features to function properly, and all should be on.
|
||||||
|
|
||||||
##### Getting Owner ID*(s)*:
|
##### Getting Owner ID
|
||||||
|
|
||||||
- Go to your Discord server and attempt to mention yourself, but put a backslash at the start
|
- Go to your Discord server and attempt to mention yourself, but put a backslash at the start
|
||||||
*(to make it slightly easier, add the backslash after the mention has been typed)*.
|
*(to make it slightly easier, add the backslash after the mention has been typed)*.
|
||||||
|
@@ -7,10 +7,9 @@ Donations go a long way in helping us keep the project alive, and we appreciate
|
|||||||
|
|
||||||
Donating to us also gives you the following benefits:
|
Donating to us also gives you the following benefits:
|
||||||
|
|
||||||
- A hoisted **Donators role** in our [Discord server][discord-server]
|
- A hoisted **Patron** role in [Nadeko Discord server][discord-server]
|
||||||
- Access to exclusive **#noticed** text and voice channels
|
- Access to exclusive **#noticed** text and voice channels
|
||||||
- **1000 flowers** on the public bot per dollar donated (after fees)
|
- **3000 flowers** on the public bot per dollar donated (after fees)
|
||||||
- **Expressions** on the public bot for [Patreon pledges][patreon] of $5 or higher
|
|
||||||
|
|
||||||
## Patreon
|
## Patreon
|
||||||
|
|
||||||
@@ -26,7 +25,7 @@ You can set up a monthly pledge on [Patreon][patreon] and support the project's
|
|||||||
You can also donate to us through [PayPal][paypal] for one-time donations using the button below, or by donating to `nadekodiscordbot@gmail.com`.
|
You can also donate to us through [PayPal][paypal] for one-time donations using the button below, or by donating to `nadekodiscordbot@gmail.com`.
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
Mention your Discord tag (Username#1234) in the payment note to receive flower rewards.
|
Mention your Discord username or user id in the payment note to receive flower rewards.
|
||||||
|
|
||||||
[![img][paypal-button]][paypal]
|
[![img][paypal-button]][paypal]
|
||||||
|
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
## Setting Up NadekoBot on Windows With the Updater
|
## Setting Up NadekoBot on Windows With the Updater
|
||||||
|
|
||||||
| Table of Contents|
|
| Table of Contents|
|
||||||
| :---------------------------------------------------------------------------------------------------------------------------|
|
| :-|
|
||||||
| [Prerequisites](#prerequisites) |
|
| [Prerequisites](#prerequisites) |
|
||||||
| [Setup](#setup) |
|
| [Setup](#setup) |
|
||||||
| [Starting the Bot](#starting-the-bot) |
|
| [Starting the Bot](#starting-the-bot) |
|
||||||
| [Updating Nadeko](#updating-nadeko) |
|
| [Updating Nadeko](#updating-nadeko) |
|
||||||
| [Manually Installing the Prerequisites from the Updater](#music-prerequisites) |
|
| [Manually Installing the Prerequisites from the Updater](#music-prerequisites) |
|
||||||
|
|
||||||
*Note: If you want to make changes to Nadeko's source code, please follow the [From Source](#windows-from-source) guide instead.*
|
*Note: If you want to make changes to Nadeko's source code, please follow the [From Source](#windows-from-source) guide instead.*
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
#### Prerequisites
|
#### Prerequisites
|
||||||
|
|
||||||
- Windows 10 or later (64-bit)
|
- Windows 10 or later (64-bit)
|
||||||
- [Create a Discord Bot application and invite the bot to your server](../creds-guide.md)
|
|
||||||
|
|
||||||
**Optional**
|
**Optional**
|
||||||
|
|
||||||
@@ -29,8 +28,7 @@
|
|||||||

|

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

|

|
||||||
- **Note: Redis is optional. install Redis manually here: [Redis] Download and run the **`.msi`** file.**
|
- If you want to use the music module, click on **`Install`** next to **`FFMPEG`** and **`Youtube-DLP`**.
|
||||||
- If you will use the music module, click on **`Install`** next to **`FFMPEG`** and **`Youtube-DLP`**.
|
|
||||||
- If any dependencies fail to install, you can temporarily disable your Windows Defender/AV until you install them. If you don't want to, then read [the last section of this guide](#Manual-Prerequisite-Installation).
|
- If any dependencies fail to install, you can temporarily disable your Windows Defender/AV until you install them. If you don't want to, then read [the last section of this guide](#Manual-Prerequisite-Installation).
|
||||||
- When installation is finished, click on **`CREDS`** to the left of **`RUN`** at the lower right.
|
- When installation is finished, click on **`CREDS`** to the left of **`RUN`** at the lower right.
|
||||||
- Follow the guide on how to [Set up the creds.yml](../../creds-guide) file.
|
- Follow the guide on how to [Set up the creds.yml](../../creds-guide) file.
|
||||||
@@ -39,8 +37,6 @@
|
|||||||
|
|
||||||
- Either click on **`RUN`** button in the updater or run the bot via its desktop shortcut.
|
- Either click on **`RUN`** button in the updater or run the bot via its desktop shortcut.
|
||||||
|
|
||||||
### If you get a "No owner channels created..." message. Please follow the creds guide again [**HERE**](../../creds-guide).
|
|
||||||
|
|
||||||
#### Updating Nadeko
|
#### Updating Nadeko
|
||||||
|
|
||||||
- Make sure Nadeko is closed and not running
|
- Make sure Nadeko is closed and not running
|
||||||
@@ -52,71 +48,6 @@
|
|||||||
- Launch the bot
|
- Launch the bot
|
||||||
- You've updated and are running again, easy as that!
|
- You've updated and are running again, easy as that!
|
||||||
|
|
||||||
#### Manual Prerequisite Installation
|
|
||||||
|
|
||||||
You can still install them manually:
|
|
||||||
|
|
||||||
- [Redis] (OPTIONAL) - Download and run the **`.msi`** file
|
|
||||||
- [ffmpeg-32bit] | [ffmpeg-64bit] - Download the **appropriate version** for your system (32 bit if you're running a 32 bit OS, or 64 if you're running a 64bit OS). Unzip it, and move `ffmpeg.exe` to a path that's in your PATH environment variable. If you don't know what that is, then just move the `ffmpeg.exe` file to NadekoBot/system
|
|
||||||
- [youtube-dlp] - Click to download the `yt-dlp.exe` file then put `yt-dlp.exe` in a path that's in your PATH environment variable. If you don't know what that is, then just move the `yt-dlp.exe` file to NadekoBot/system
|
|
||||||
|
|
||||||
## **⚠ IF YOU ARE FOLLOWING THE GUIDE ABOVE, IGNORE THIS SECTION ⚠**
|
|
||||||
|
|
||||||
### Windows From Source
|
|
||||||
|
|
||||||
##### Prerequisites
|
|
||||||
|
|
||||||
**Install these before proceeding or your bot will not work!**
|
|
||||||
- [.net 8](https://dotnet.microsoft.com/en-us/download) - needed to compile and run the bot
|
|
||||||
- [git](https://git-scm.com/downloads) - needed to clone the repository (you can also download the zip manually and extract it, but this guide assumes you're using git)
|
|
||||||
- [Redis] (OPTIONAL)- to cache things needed by some features and persist through restarts
|
|
||||||
|
|
||||||
##### Installation Instructions
|
|
||||||
|
|
||||||
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 v5 --depth 1`
|
|
||||||
2. `cd nadekobot`
|
|
||||||
3. `dotnet publish -c Release -o output/ src/NadekoBot/`
|
|
||||||
4. `cd output`
|
|
||||||
5. `cp creds_example.yml creds.yml`
|
|
||||||
6. Open `creds.yml` with your favorite text editor (Please don't use Notepad or WordPad. You can use Notepad++, VSCode, Atom, Sublime, or something similar)
|
|
||||||
7. [Enter your bot's token](#creds-guide)
|
|
||||||
8. Run the bot `dotnet NadekoBot.dll`
|
|
||||||
9. 🎉
|
|
||||||
|
|
||||||
##### Update Instructions
|
|
||||||
|
|
||||||
Open PowerShell as described above and run the following commands:
|
|
||||||
|
|
||||||
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, and make sure you're on the v5 branch
|
|
||||||
- *⚠️ the first 3 lines can be omitted if you're already on v5. If you're updating from v4, you must run them*
|
|
||||||
- `git remote set-branches origin '*'`
|
|
||||||
- `git fetch -v --depth=1`
|
|
||||||
- `git checkout v5`
|
|
||||||
- `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`
|
|
||||||
|
|
||||||
🎉 Enjoy
|
|
||||||
|
|
||||||
#### Music prerequisites
|
#### Music prerequisites
|
||||||
In order to use music commands, you need ffmpeg and yt-dlp installed.
|
In order to use music commands, you need ffmpeg and yt-dlp installed.
|
||||||
@@ -131,4 +62,4 @@ In order to use music commands, you need ffmpeg and yt-dlp installed.
|
|||||||
[Visual C++ 2017 (x64)]: https://aka.ms/vs/15/release/vc_redist.x64.exe
|
[Visual C++ 2017 (x64)]: https://aka.ms/vs/15/release/vc_redist.x64.exe
|
||||||
[ffmpeg-32bit]: https://cdn.nadeko.bot/dl/ffmpeg-32.zip
|
[ffmpeg-32bit]: https://cdn.nadeko.bot/dl/ffmpeg-32.zip
|
||||||
[ffmpeg-64bit]: https://cdn.nadeko.bot/dl/ffmpeg-64.zip
|
[ffmpeg-64bit]: https://cdn.nadeko.bot/dl/ffmpeg-64.zip
|
||||||
[youtube-dlp]: https://github.com/yt-dlp/yt-dlp/releases
|
[youtube-dlp]: https://github.com/yt-dlp/yt-dlp/releases
|
77
docs/guides/windows-source-guide.md
Normal file
77
docs/guides/windows-source-guide.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
## Setting Up NadekoBot on Windows from source
|
||||||
|
|
||||||
|
1. Prerequisites
|
||||||
|
|
||||||
|
- Windows 10 or later (64-bit)
|
||||||
|
- [.net 8 sdk](https://dotnet.microsoft.com/download/dotnet/8.0)
|
||||||
|
- If you want nadeko to play music: [Visual C++ 2010 (x86)] and [Visual C++ 2017 (x64)] (both are required, you may install them later)
|
||||||
|
- [git](https://git-scm.com/downloads) - needed to clone the repository (you can also download the zip manually and extract it, but this guide assumes you're using git)
|
||||||
|
- **Optional** Any code editor, for example [Visual Studio Code](https://code.visualstudio.com/Download)
|
||||||
|
- You'll need to at least modify creds.yml, notepad is inadequate
|
||||||
|
|
||||||
|
|
||||||
|
##### Installation Instructions
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
0. Navigate to the location where you want to install the bot
|
||||||
|
- for example, type `cd ~/Desktop/` and press enter
|
||||||
|
1. `git clone https://gitlab.com/kwoth/nadekobot -b v5 --depth 1`
|
||||||
|
2. `cd nadekobot`
|
||||||
|
3. `dotnet publish -c Release -o output/ src/NadekoBot/`
|
||||||
|
4. `cd output`
|
||||||
|
5. `cp creds_example.yml creds.yml`
|
||||||
|
6. "You're done installing, you may now proceed to set up your bot's credentials by following the [#creds-guide]
|
||||||
|
- Once done, come back here and run the last command
|
||||||
|
8. Run the bot `dotnet NadekoBot.dll`
|
||||||
|
9. 🎉 Enjoy
|
||||||
|
|
||||||
|
##### Update Instructions
|
||||||
|
|
||||||
|
Open PowerShell as described above and run the following commands:
|
||||||
|
|
||||||
|
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, and make sure you're on the v5 branch
|
||||||
|
- *⚠️ If you're on v4, you must run these commands, if not, you may skip them.*
|
||||||
|
- `git remote set-branches origin '*'`
|
||||||
|
- `git fetch -v --depth=1`
|
||||||
|
- `git checkout v5`
|
||||||
|
- `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, and new strings
|
||||||
|
- `cp -Recurse -Force .\output-old\data\ .\output\`
|
||||||
|
- `cp -Recurse -Force src/NadekoBot/data/strings/ output/data/`
|
||||||
|
8. Copy creds.yml
|
||||||
|
- `cp output-old/creds.yml output/`
|
||||||
|
9. Run the bot
|
||||||
|
- `cd output`
|
||||||
|
- `dotnet NadekoBot.dll`
|
||||||
|
|
||||||
|
🎉 Enjoy
|
||||||
|
|
||||||
|
#### Music prerequisites
|
||||||
|
In order to use music commands, you need ffmpeg and yt-dlp installed.
|
||||||
|
- [ffmpeg-32bit] | [ffmpeg-64bit] - Download the **appropriate version** for your system (32 bit if you're running a 32 bit OS, or 64 if you're running a 64bit OS). Unzip it, and move `ffmpeg.exe` to a path that's in your PATH environment variable. If you don't know what that is, just move the `ffmpeg.exe` file to `NadekoBot/output`.
|
||||||
|
- [youtube-dlp] - Click to download the `yt-dlp.exe` file, then move `yt-dlp.exe` to a path that's in your PATH environment variable. If you don't know what that is, just move the `yt-dlp.exe` file to `NadekoBot/system`.
|
||||||
|
|
||||||
|
[Updater]: https://dl.nadeko.bot/v3/
|
||||||
|
[Notepad++]: https://notepad-plus-plus.org/
|
||||||
|
[.net]: https://dotnet.microsoft.com/download/dotnet/8.0
|
||||||
|
[Redis]: https://github.com/MicrosoftArchive/redis/releases/download/win-3.0.504/Redis-x64-3.0.504.msi
|
||||||
|
[Visual C++ 2010 (x86)]: https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x86.exe
|
||||||
|
[Visual C++ 2017 (x64)]: https://aka.ms/vs/15/release/vc_redist.x64.exe
|
||||||
|
[ffmpeg-32bit]: https://cdn.nadeko.bot/dl/ffmpeg-32.zip
|
||||||
|
[ffmpeg-64bit]: https://cdn.nadeko.bot/dl/ffmpeg-64.zip
|
||||||
|
[youtube-dlp]: https://github.com/yt-dlp/yt-dlp/releases
|
@@ -16,6 +16,7 @@ To self-host your own Nadeko, use the guides below:
|
|||||||
- [:material-microsoft-windows: Windows guide][windows-guide]
|
- [:material-microsoft-windows: Windows guide][windows-guide]
|
||||||
- [:material-linux: Linux guide][linux-guide]
|
- [:material-linux: Linux guide][linux-guide]
|
||||||
- [:material-apple: Mac OS guide][macos-guide]
|
- [:material-apple: Mac OS guide][macos-guide]
|
||||||
|
- [:material-microsoft-windows: Windows (from source) guide][windows-source-guide]
|
||||||
|
|
||||||
In case you need any help, join our [Discord server][discord-server] where we may provide support.
|
In case you need any help, join our [Discord server][discord-server] where we may provide support.
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ If you're unsure whether something is an issue, ask in our support server first.
|
|||||||
[invite]: https://invite.nadeko.bot/
|
[invite]: https://invite.nadeko.bot/
|
||||||
[commands]: https://nadeko.bot/commands/
|
[commands]: https://nadeko.bot/commands/
|
||||||
[windows-guide]: ./guides/windows-guide.md
|
[windows-guide]: ./guides/windows-guide.md
|
||||||
|
[windows-source-guide]: ./guides/windows-source-guide.md
|
||||||
[linux-guide]: ./guides/linux-guide.md
|
[linux-guide]: ./guides/linux-guide.md
|
||||||
[macos-guide]: ./guides/osx-guide.md
|
[macos-guide]: ./guides/osx-guide.md
|
||||||
[from-source-guide]: ./guides/from-source.md
|
[from-source-guide]: ./guides/from-source.md
|
||||||
|
@@ -75,6 +75,7 @@ nav:
|
|||||||
- Windows Guide: guides/windows-guide.md
|
- Windows Guide: guides/windows-guide.md
|
||||||
- Linux Guide: guides/linux-guide.md
|
- Linux Guide: guides/linux-guide.md
|
||||||
- OSX Guide: guides/osx-guide.md
|
- OSX Guide: guides/osx-guide.md
|
||||||
|
- Windows Guide (from source): guides/windows-source-guide.md
|
||||||
- Docker Guide (unsupported): guides/docker-guide.md
|
- Docker Guide (unsupported): guides/docker-guide.md
|
||||||
- Commands:
|
- Commands:
|
||||||
- Readme: commands-readme.md
|
- Readme: commands-readme.md
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Discord.Net.Core" Version="3.15.3" />
|
<PackageReference Include="Discord.Net.Core" Version="3.16.0" />
|
||||||
<PackageReference Include="Serilog" Version="3.1.1" />
|
<PackageReference Include="Serilog" Version="3.1.1" />
|
||||||
<PackageReference Include="YamlDotNet" Version="15.1.4" />
|
<PackageReference Include="YamlDotNet" Version="15.1.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@@ -357,3 +357,4 @@ resharper_arrange_redundant_parentheses_highlighting = hint
|
|||||||
# IDE0011: Add braces
|
# IDE0011: Add braces
|
||||||
dotnet_diagnostic.IDE0011.severity = warning
|
dotnet_diagnostic.IDE0011.severity = warning
|
||||||
|
|
||||||
|
resharper_arrange_type_member_modifiers_highlighting = hint
|
@@ -20,7 +20,6 @@ public static class UserXpExtensions
|
|||||||
{
|
{
|
||||||
Xp = 0,
|
Xp = 0,
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
NotifyOnLevelUp = XpNotificationLocation.None,
|
|
||||||
GuildId = guildId
|
GuildId = guildId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -31,17 +30,17 @@ public static class UserXpExtensions
|
|||||||
public static async Task<List<UserXpStats>> GetTopUserXps(this DbSet<UserXpStats> xps, ulong guildId, int count)
|
public static async Task<List<UserXpStats>> GetTopUserXps(this DbSet<UserXpStats> xps, ulong guildId, int count)
|
||||||
=> await xps.ToLinqToDBTable()
|
=> await xps.ToLinqToDBTable()
|
||||||
.Where(x => x.GuildId == guildId)
|
.Where(x => x.GuildId == guildId)
|
||||||
.OrderByDescending(x => x.Xp + x.AwardedXp)
|
.OrderByDescending(x => x.Xp)
|
||||||
.Take(count)
|
.Take(count)
|
||||||
.ToListAsyncLinqToDB();
|
.ToListAsyncLinqToDB();
|
||||||
|
|
||||||
public static async Task<int> GetUserGuildRanking(this DbSet<UserXpStats> xps, ulong userId, ulong guildId)
|
public static async Task<int> GetUserGuildRanking(this DbSet<UserXpStats> xps, ulong userId, ulong guildId)
|
||||||
=> await xps.ToLinqToDBTable()
|
=> await xps.ToLinqToDBTable()
|
||||||
.Where(x => x.GuildId == guildId
|
.Where(x => x.GuildId == guildId
|
||||||
&& x.Xp + x.AwardedXp
|
&& x.Xp
|
||||||
> xps.AsQueryable()
|
> xps.AsQueryable()
|
||||||
.Where(y => y.UserId == userId && y.GuildId == guildId)
|
.Where(y => y.UserId == userId && y.GuildId == guildId)
|
||||||
.Select(y => y.Xp + y.AwardedXp)
|
.Select(y => y.Xp)
|
||||||
.FirstOrDefault())
|
.FirstOrDefault())
|
||||||
.CountAsyncLinqToDB()
|
.CountAsyncLinqToDB()
|
||||||
+ 1;
|
+ 1;
|
||||||
@@ -53,6 +52,6 @@ public static class UserXpExtensions
|
|||||||
=> await userXp
|
=> await userXp
|
||||||
.Where(x => x.GuildId == guildId && x.UserId == userId)
|
.Where(x => x.GuildId == guildId && x.UserId == userId)
|
||||||
.FirstOrDefaultAsyncLinqToDB() is UserXpStats uxs
|
.FirstOrDefaultAsyncLinqToDB() is UserXpStats uxs
|
||||||
? new(uxs.Xp + uxs.AwardedXp)
|
? new(uxs.Xp)
|
||||||
: new(0);
|
: new(0);
|
||||||
}
|
}
|
@@ -4,38 +4,28 @@ namespace NadekoBot.Db;
|
|||||||
|
|
||||||
public readonly struct LevelStats
|
public readonly struct LevelStats
|
||||||
{
|
{
|
||||||
public const int XP_REQUIRED_LVL_1 = 36;
|
|
||||||
|
|
||||||
public long Level { get; }
|
public long Level { get; }
|
||||||
public long LevelXp { get; }
|
public long LevelXp { get; }
|
||||||
public long RequiredXp { get; }
|
public long RequiredXp { get; }
|
||||||
public long TotalXp { get; }
|
public long TotalXp { get; }
|
||||||
|
|
||||||
public LevelStats(long xp)
|
public LevelStats(long totalXp)
|
||||||
{
|
{
|
||||||
if (xp < 0)
|
if (totalXp < 0)
|
||||||
xp = 0;
|
totalXp = 0;
|
||||||
|
|
||||||
TotalXp = xp;
|
TotalXp = totalXp;
|
||||||
|
Level = GetLevelByTotalXp(totalXp);
|
||||||
const int baseXp = XP_REQUIRED_LVL_1;
|
LevelXp = totalXp - GetTotalXpReqForLevel(Level);
|
||||||
|
RequiredXp = (9 * (Level + 1)) + 27;
|
||||||
var required = baseXp;
|
|
||||||
var totalXp = 0;
|
|
||||||
var lvl = 1;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
required = (int)(baseXp + (baseXp / 4.0 * (lvl - 1)));
|
|
||||||
|
|
||||||
if (required + totalXp > xp)
|
|
||||||
break;
|
|
||||||
|
|
||||||
totalXp += required;
|
|
||||||
lvl++;
|
|
||||||
}
|
|
||||||
|
|
||||||
Level = lvl - 1;
|
|
||||||
LevelXp = xp - totalXp;
|
|
||||||
RequiredXp = required;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static LevelStats CreateForLevel(long level)
|
||||||
|
=> new(GetTotalXpReqForLevel(level));
|
||||||
|
|
||||||
|
public static long GetTotalXpReqForLevel(long level)
|
||||||
|
=> ((9 * level * level) + (63 * level)) / 2;
|
||||||
|
|
||||||
|
public static long GetLevelByTotalXp(long totalXp)
|
||||||
|
=> (long)((-7.0 / 2) + (1 / 6.0 * Math.Sqrt((8 * totalXp) + 441)));
|
||||||
}
|
}
|
@@ -36,29 +36,37 @@ public class GuildConfig : DbEntity
|
|||||||
public HashSet<FilterChannelId> FilterInvitesChannelIds { get; set; } = new();
|
public HashSet<FilterChannelId> FilterInvitesChannelIds { get; set; } = new();
|
||||||
public HashSet<FilterLinksChannelId> FilterLinksChannelIds { get; set; } = new();
|
public HashSet<FilterLinksChannelId> FilterLinksChannelIds { get; set; } = new();
|
||||||
|
|
||||||
//public bool FilterLinks { get; set; }
|
|
||||||
//public HashSet<FilterLinksChannelId> FilterLinksChannels { get; set; } = new HashSet<FilterLinksChannelId>();
|
|
||||||
|
|
||||||
public bool FilterWords { get; set; }
|
public bool FilterWords { get; set; }
|
||||||
public HashSet<FilteredWord> FilteredWords { get; set; } = new();
|
public HashSet<FilteredWord> FilteredWords { get; set; } = new();
|
||||||
public HashSet<FilterWordsChannelId> FilterWordsChannelIds { get; set; } = new();
|
public HashSet<FilterWordsChannelId> FilterWordsChannelIds { get; set; } = new();
|
||||||
|
|
||||||
|
// mute
|
||||||
public HashSet<MutedUserId> MutedUsers { get; set; } = new();
|
public HashSet<MutedUserId> MutedUsers { get; set; } = new();
|
||||||
|
|
||||||
public string MuteRoleName { get; set; }
|
public string MuteRoleName { get; set; }
|
||||||
|
|
||||||
|
// chatterbot
|
||||||
public bool CleverbotEnabled { get; set; }
|
public bool CleverbotEnabled { get; set; }
|
||||||
|
|
||||||
|
// protection
|
||||||
public AntiRaidSetting AntiRaidSetting { get; set; }
|
public AntiRaidSetting AntiRaidSetting { get; set; }
|
||||||
public AntiSpamSetting AntiSpamSetting { get; set; }
|
public AntiSpamSetting AntiSpamSetting { get; set; }
|
||||||
public AntiAltSetting AntiAltSetting { get; set; }
|
public AntiAltSetting AntiAltSetting { get; set; }
|
||||||
|
|
||||||
|
// time
|
||||||
public string Locale { get; set; }
|
public string Locale { get; set; }
|
||||||
public string TimeZoneId { get; set; }
|
public string TimeZoneId { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
// timers
|
||||||
public HashSet<UnmuteTimer> UnmuteTimers { get; set; } = new();
|
public HashSet<UnmuteTimer> UnmuteTimers { get; set; } = new();
|
||||||
public HashSet<UnbanTimer> UnbanTimer { get; set; } = new();
|
public HashSet<UnbanTimer> UnbanTimer { get; set; } = new();
|
||||||
public HashSet<UnroleTimer> UnroleTimer { get; set; } = new();
|
public HashSet<UnroleTimer> UnroleTimer { get; set; } = new();
|
||||||
|
|
||||||
|
// vcrole
|
||||||
public HashSet<VcRoleInfo> VcRoleInfos { get; set; }
|
public HashSet<VcRoleInfo> VcRoleInfos { get; set; }
|
||||||
|
|
||||||
|
// aliases
|
||||||
public HashSet<CommandAlias> CommandAliases { get; set; } = new();
|
public HashSet<CommandAlias> CommandAliases { get; set; } = new();
|
||||||
public bool WarningsInitialized { get; set; }
|
public bool WarningsInitialized { get; set; }
|
||||||
public HashSet<SlowmodeIgnoredUser> SlowmodeIgnoredUsers { get; set; }
|
public HashSet<SlowmodeIgnoredUser> SlowmodeIgnoredUsers { get; set; }
|
||||||
|
24
src/NadekoBot/Db/Models/Notify.cs
Normal file
24
src/NadekoBot/Db/Models/Notify.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace NadekoBot.Db.Models;
|
||||||
|
|
||||||
|
public class Notify
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public ulong GuildId { get; set; }
|
||||||
|
public ulong ChannelId { get; set; }
|
||||||
|
public NotifyType Type { get; set; }
|
||||||
|
|
||||||
|
[MaxLength(10_000)]
|
||||||
|
public string Message { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum NotifyType
|
||||||
|
{
|
||||||
|
LevelUp = 0,
|
||||||
|
Protection = 1, Prot = 1,
|
||||||
|
AddRoleReward = 2,
|
||||||
|
RemoveRoleReward = 3,
|
||||||
|
}
|
12
src/NadekoBot/Db/Models/roles/TempRole.cs
Normal file
12
src/NadekoBot/Db/Models/roles/TempRole.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace NadekoBot.Db.Models;
|
||||||
|
|
||||||
|
public class TempRole
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public ulong GuildId { get; set; }
|
||||||
|
public bool Remove { get; set; }
|
||||||
|
public ulong RoleId { get; set; }
|
||||||
|
public ulong UserId { get; set; }
|
||||||
|
|
||||||
|
public DateTime ExpiresAt { get; set; }
|
||||||
|
}
|
@@ -6,6 +6,4 @@ public class UserXpStats : DbEntity
|
|||||||
public ulong UserId { get; set; }
|
public ulong UserId { get; set; }
|
||||||
public ulong GuildId { get; set; }
|
public ulong GuildId { get; set; }
|
||||||
public long Xp { get; set; }
|
public long Xp { get; set; }
|
||||||
public long AwardedXp { get; set; }
|
|
||||||
public XpNotificationLocation NotifyOnLevelUp { get; set; }
|
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
namespace NadekoBot.Db.Models;
|
namespace NadekoBot.Db.Models;
|
||||||
|
|
||||||
public enum XpNotificationLocation
|
public enum XpNotificationLocation
|
||||||
{
|
{
|
||||||
|
@@ -74,6 +74,35 @@ public abstract class NadekoContext : DbContext
|
|||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
|
#region Notify
|
||||||
|
|
||||||
|
modelBuilder.Entity<Notify>(e =>
|
||||||
|
{
|
||||||
|
e.HasAlternateKey(x => new
|
||||||
|
{
|
||||||
|
x.GuildId,
|
||||||
|
Event = x.Type
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region TempRoles
|
||||||
|
|
||||||
|
modelBuilder.Entity<TempRole>(e =>
|
||||||
|
{
|
||||||
|
e.HasAlternateKey(x => new
|
||||||
|
{
|
||||||
|
x.GuildId,
|
||||||
|
x.UserId,
|
||||||
|
x.RoleId
|
||||||
|
});
|
||||||
|
|
||||||
|
e.HasIndex(x => x.ExpiresAt);
|
||||||
|
});
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region GuildColors
|
#region GuildColors
|
||||||
|
|
||||||
modelBuilder.Entity<GuildColors>()
|
modelBuilder.Entity<GuildColors>()
|
||||||
@@ -135,13 +164,18 @@ public abstract class NadekoContext : DbContext
|
|||||||
|
|
||||||
#region UserBetStats
|
#region UserBetStats
|
||||||
|
|
||||||
modelBuilder.Entity<UserBetStats>()
|
modelBuilder.Entity<UserBetStats>(ubs =>
|
||||||
.HasIndex(x => new
|
{
|
||||||
{
|
ubs.HasIndex(x => new
|
||||||
x.UserId,
|
{
|
||||||
x.Game
|
x.UserId,
|
||||||
})
|
x.Game
|
||||||
.IsUnique();
|
})
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
ubs.HasIndex(x => x.MaxWin)
|
||||||
|
.IsUnique(false);
|
||||||
|
});
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -449,7 +483,6 @@ public abstract class NadekoContext : DbContext
|
|||||||
xps.HasIndex(x => x.UserId);
|
xps.HasIndex(x => x.UserId);
|
||||||
xps.HasIndex(x => x.GuildId);
|
xps.HasIndex(x => x.GuildId);
|
||||||
xps.HasIndex(x => x.Xp);
|
xps.HasIndex(x => x.Xp);
|
||||||
xps.HasIndex(x => x.AwardedXp);
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
@@ -5,6 +5,16 @@ namespace NadekoBot.Migrations;
|
|||||||
|
|
||||||
public static class MigrationQueries
|
public static class MigrationQueries
|
||||||
{
|
{
|
||||||
|
public static void MergeAwardedXp(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.Sql("""
|
||||||
|
UPDATE UserXpStats
|
||||||
|
SET Xp = AwardedXp + Xp,
|
||||||
|
AwardedXp = 0
|
||||||
|
WHERE AwardedXp > 0;
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
public static void MigrateSar(MigrationBuilder migrationBuilder)
|
public static void MigrateSar(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
migrationBuilder.Sql("""
|
migrationBuilder.Sql("""
|
||||||
|
4117
src/NadekoBot/Migrations/PostgreSql/20241207150050_awardedxp-temprole-notify.Designer.cs
generated
Normal file
4117
src/NadekoBot/Migrations/PostgreSql/20241207150050_awardedxp-temprole-notify.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,108 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace NadekoBot.Migrations.PostgreSql
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class awardedxptemprolenotify : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "ix_userxpstats_awardedxp",
|
||||||
|
table: "userxpstats");
|
||||||
|
|
||||||
|
MigrationQueries.MergeAwardedXp(migrationBuilder);
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "awardedxp",
|
||||||
|
table: "userxpstats");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "notifyonlevelup",
|
||||||
|
table: "userxpstats");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "notify",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||||
|
channelid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||||
|
type = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
message = table.Column<string>(type: "character varying(10000)", maxLength: 10000, nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_notify", x => x.id);
|
||||||
|
table.UniqueConstraint("ak_notify_guildid_type", x => new { x.guildid, x.type });
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "temprole",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||||
|
remove = table.Column<bool>(type: "boolean", nullable: false),
|
||||||
|
roleid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||||
|
userid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||||
|
expiresat = table.Column<DateTime>(type: "timestamp without time zone", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_temprole", x => x.id);
|
||||||
|
table.UniqueConstraint("ak_temprole_guildid_userid_roleid", x => new { x.guildid, x.userid, x.roleid });
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_userbetstats_maxwin",
|
||||||
|
table: "userbetstats",
|
||||||
|
column: "maxwin");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_temprole_expiresat",
|
||||||
|
table: "temprole",
|
||||||
|
column: "expiresat");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "notify");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "temprole");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "ix_userbetstats_maxwin",
|
||||||
|
table: "userbetstats");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<long>(
|
||||||
|
name: "awardedxp",
|
||||||
|
table: "userxpstats",
|
||||||
|
type: "bigint",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0L);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "notifyonlevelup",
|
||||||
|
table: "userxpstats",
|
||||||
|
type: "integer",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_userxpstats_awardedxp",
|
||||||
|
table: "userxpstats",
|
||||||
|
column: "awardedxp");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1817,6 +1817,42 @@ namespace NadekoBot.Migrations.PostgreSql
|
|||||||
b.ToTable("expressions", (string)null);
|
b.ToTable("expressions", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Db.Models.Notify", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<decimal>("ChannelId")
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("channelid");
|
||||||
|
|
||||||
|
b.Property<decimal>("GuildId")
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("guildid");
|
||||||
|
|
||||||
|
b.Property<string>("Message")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(10000)
|
||||||
|
.HasColumnType("character varying(10000)")
|
||||||
|
.HasColumnName("message");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("type");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_notify");
|
||||||
|
|
||||||
|
b.HasAlternateKey("GuildId", "Type")
|
||||||
|
.HasName("ak_notify_guildid_type");
|
||||||
|
|
||||||
|
b.ToTable("notify", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Db.Models.PatronUser", b =>
|
modelBuilder.Entity("NadekoBot.Db.Models.PatronUser", b =>
|
||||||
{
|
{
|
||||||
b.Property<decimal>("UserId")
|
b.Property<decimal>("UserId")
|
||||||
@@ -2702,6 +2738,47 @@ namespace NadekoBot.Migrations.PostgreSql
|
|||||||
b.ToTable("streamrolewhitelisteduser", (string)null);
|
b.ToTable("streamrolewhitelisteduser", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Db.Models.TempRole", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("ExpiresAt")
|
||||||
|
.HasColumnType("timestamp without time zone")
|
||||||
|
.HasColumnName("expiresat");
|
||||||
|
|
||||||
|
b.Property<decimal>("GuildId")
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("guildid");
|
||||||
|
|
||||||
|
b.Property<bool>("Remove")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("remove");
|
||||||
|
|
||||||
|
b.Property<decimal>("RoleId")
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("roleid");
|
||||||
|
|
||||||
|
b.Property<decimal>("UserId")
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("userid");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_temprole");
|
||||||
|
|
||||||
|
b.HasAlternateKey("GuildId", "UserId", "RoleId")
|
||||||
|
.HasName("ak_temprole_guildid_userid_roleid");
|
||||||
|
|
||||||
|
b.HasIndex("ExpiresAt")
|
||||||
|
.HasDatabaseName("ix_temprole_expiresat");
|
||||||
|
|
||||||
|
b.ToTable("temprole", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Db.Models.TodoModel", b =>
|
modelBuilder.Entity("NadekoBot.Db.Models.TodoModel", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -2858,10 +2935,6 @@ namespace NadekoBot.Migrations.PostgreSql
|
|||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
b.Property<long>("AwardedXp")
|
|
||||||
.HasColumnType("bigint")
|
|
||||||
.HasColumnName("awardedxp");
|
|
||||||
|
|
||||||
b.Property<DateTime?>("DateAdded")
|
b.Property<DateTime?>("DateAdded")
|
||||||
.HasColumnType("timestamp without time zone")
|
.HasColumnType("timestamp without time zone")
|
||||||
.HasColumnName("dateadded");
|
.HasColumnName("dateadded");
|
||||||
@@ -2870,10 +2943,6 @@ namespace NadekoBot.Migrations.PostgreSql
|
|||||||
.HasColumnType("numeric(20,0)")
|
.HasColumnType("numeric(20,0)")
|
||||||
.HasColumnName("guildid");
|
.HasColumnName("guildid");
|
||||||
|
|
||||||
b.Property<int>("NotifyOnLevelUp")
|
|
||||||
.HasColumnType("integer")
|
|
||||||
.HasColumnName("notifyonlevelup");
|
|
||||||
|
|
||||||
b.Property<decimal>("UserId")
|
b.Property<decimal>("UserId")
|
||||||
.HasColumnType("numeric(20,0)")
|
.HasColumnType("numeric(20,0)")
|
||||||
.HasColumnName("userid");
|
.HasColumnName("userid");
|
||||||
@@ -2885,9 +2954,6 @@ namespace NadekoBot.Migrations.PostgreSql
|
|||||||
b.HasKey("Id")
|
b.HasKey("Id")
|
||||||
.HasName("pk_userxpstats");
|
.HasName("pk_userxpstats");
|
||||||
|
|
||||||
b.HasIndex("AwardedXp")
|
|
||||||
.HasDatabaseName("ix_userxpstats_awardedxp");
|
|
||||||
|
|
||||||
b.HasIndex("GuildId")
|
b.HasIndex("GuildId")
|
||||||
.HasDatabaseName("ix_userxpstats_guildid");
|
.HasDatabaseName("ix_userxpstats_guildid");
|
||||||
|
|
||||||
@@ -3416,6 +3482,9 @@ namespace NadekoBot.Migrations.PostgreSql
|
|||||||
b.HasKey("Id")
|
b.HasKey("Id")
|
||||||
.HasName("pk_userbetstats");
|
.HasName("pk_userbetstats");
|
||||||
|
|
||||||
|
b.HasIndex("MaxWin")
|
||||||
|
.HasDatabaseName("ix_userbetstats_maxwin");
|
||||||
|
|
||||||
b.HasIndex("UserId", "Game")
|
b.HasIndex("UserId", "Game")
|
||||||
.IsUnique()
|
.IsUnique()
|
||||||
.HasDatabaseName("ix_userbetstats_userid_game");
|
.HasDatabaseName("ix_userbetstats_userid_game");
|
||||||
|
3173
src/NadekoBot/Migrations/Sqlite/20241207150041_awardedxp-temprole-notify.Designer.cs
generated
Normal file
3173
src/NadekoBot/Migrations/Sqlite/20241207150041_awardedxp-temprole-notify.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,106 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace NadekoBot.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class awardedxptemprolenotify : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_UserXpStats_AwardedXp",
|
||||||
|
table: "UserXpStats");
|
||||||
|
|
||||||
|
MigrationQueries.MergeAwardedXp(migrationBuilder);
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "AwardedXp",
|
||||||
|
table: "UserXpStats");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "NotifyOnLevelUp",
|
||||||
|
table: "UserXpStats");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Notify",
|
||||||
|
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),
|
||||||
|
Type = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
Message = table.Column<string>(type: "TEXT", maxLength: 10000, nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Notify", x => x.Id);
|
||||||
|
table.UniqueConstraint("AK_Notify_GuildId_Type", x => new { x.GuildId, x.Type });
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "TempRole",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||||
|
Remove = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||||
|
RoleId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||||
|
UserId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||||
|
ExpiresAt = table.Column<DateTime>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_TempRole", x => x.Id);
|
||||||
|
table.UniqueConstraint("AK_TempRole_GuildId_UserId_RoleId", x => new { x.GuildId, x.UserId, x.RoleId });
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_UserBetStats_MaxWin",
|
||||||
|
table: "UserBetStats",
|
||||||
|
column: "MaxWin");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_TempRole_ExpiresAt",
|
||||||
|
table: "TempRole",
|
||||||
|
column: "ExpiresAt");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Notify");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "TempRole");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_UserBetStats_MaxWin",
|
||||||
|
table: "UserBetStats");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<long>(
|
||||||
|
name: "AwardedXp",
|
||||||
|
table: "UserXpStats",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0L);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "NotifyOnLevelUp",
|
||||||
|
table: "UserXpStats",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_UserXpStats_AwardedXp",
|
||||||
|
table: "UserXpStats",
|
||||||
|
column: "AwardedXp");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1356,6 +1356,33 @@ namespace NadekoBot.Migrations
|
|||||||
b.ToTable("Expressions");
|
b.ToTable("Expressions");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Db.Models.Notify", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<ulong>("ChannelId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<ulong>("GuildId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Message")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(10000)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasAlternateKey("GuildId", "Type");
|
||||||
|
|
||||||
|
b.ToTable("Notify");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Db.Models.PatronUser", b =>
|
modelBuilder.Entity("NadekoBot.Db.Models.PatronUser", b =>
|
||||||
{
|
{
|
||||||
b.Property<ulong>("UserId")
|
b.Property<ulong>("UserId")
|
||||||
@@ -2013,6 +2040,36 @@ namespace NadekoBot.Migrations
|
|||||||
b.ToTable("StreamRoleWhitelistedUser");
|
b.ToTable("StreamRoleWhitelistedUser");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Db.Models.TempRole", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ExpiresAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<ulong>("GuildId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("Remove")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<ulong>("RoleId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<ulong>("UserId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasAlternateKey("GuildId", "UserId", "RoleId");
|
||||||
|
|
||||||
|
b.HasIndex("ExpiresAt");
|
||||||
|
|
||||||
|
b.ToTable("TempRole");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Db.Models.TodoModel", b =>
|
modelBuilder.Entity("NadekoBot.Db.Models.TodoModel", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -2127,18 +2184,12 @@ namespace NadekoBot.Migrations
|
|||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<long>("AwardedXp")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<DateTime?>("DateAdded")
|
b.Property<DateTime?>("DateAdded")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<ulong>("GuildId")
|
b.Property<ulong>("GuildId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<int>("NotifyOnLevelUp")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<ulong>("UserId")
|
b.Property<ulong>("UserId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
@@ -2147,8 +2198,6 @@ namespace NadekoBot.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("AwardedXp");
|
|
||||||
|
|
||||||
b.HasIndex("GuildId");
|
b.HasIndex("GuildId");
|
||||||
|
|
||||||
b.HasIndex("UserId");
|
b.HasIndex("UserId");
|
||||||
@@ -2541,6 +2590,8 @@ namespace NadekoBot.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("MaxWin");
|
||||||
|
|
||||||
b.HasIndex("UserId", "Game")
|
b.HasIndex("UserId", "Game")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
|
@@ -46,7 +46,7 @@ public partial class Administration : NadekoModule<AdministrationService>
|
|||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.Administrator)]
|
[UserPerm(GuildPerm.Administrator)]
|
||||||
[BotPerm(GuildPerm.ManageGuild)]
|
[BotPerm(GuildPerm.ManageGuild)]
|
||||||
public async Task ImageOnlyChannel(StoopidTime time = null)
|
public async Task ImageOnlyChannel(ParsedTimespan timespan = null)
|
||||||
{
|
{
|
||||||
var newValue = await _somethingOnly.ToggleImageOnlyChannelAsync(ctx.Guild.Id, ctx.Channel.Id);
|
var newValue = await _somethingOnly.ToggleImageOnlyChannelAsync(ctx.Guild.Id, ctx.Channel.Id);
|
||||||
if (newValue)
|
if (newValue)
|
||||||
@@ -59,7 +59,7 @@ public partial class Administration : NadekoModule<AdministrationService>
|
|||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.Administrator)]
|
[UserPerm(GuildPerm.Administrator)]
|
||||||
[BotPerm(GuildPerm.ManageGuild)]
|
[BotPerm(GuildPerm.ManageGuild)]
|
||||||
public async Task LinkOnlyChannel(StoopidTime time = null)
|
public async Task LinkOnlyChannel(ParsedTimespan timespan = null)
|
||||||
{
|
{
|
||||||
var newValue = await _somethingOnly.ToggleLinkOnlyChannelAsync(ctx.Guild.Id, ctx.Channel.Id);
|
var newValue = await _somethingOnly.ToggleLinkOnlyChannelAsync(ctx.Guild.Id, ctx.Channel.Id);
|
||||||
if (newValue)
|
if (newValue)
|
||||||
@@ -72,10 +72,10 @@ public partial class Administration : NadekoModule<AdministrationService>
|
|||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(ChannelPerm.ManageChannels)]
|
[UserPerm(ChannelPerm.ManageChannels)]
|
||||||
[BotPerm(ChannelPerm.ManageChannels)]
|
[BotPerm(ChannelPerm.ManageChannels)]
|
||||||
public async Task Slowmode(StoopidTime time = null)
|
public async Task Slowmode(ParsedTimespan timespan = null)
|
||||||
{
|
{
|
||||||
var seconds = (int?)time?.Time.TotalSeconds ?? 0;
|
var seconds = (int?)timespan?.Time.TotalSeconds ?? 0;
|
||||||
if (time is not null && (time.Time < TimeSpan.FromSeconds(0) || time.Time > TimeSpan.FromHours(6)))
|
if (timespan is not null && (timespan.Time < TimeSpan.FromSeconds(0) || timespan.Time > TimeSpan.FromHours(6)))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await ((ITextChannel)ctx.Channel).ModifyAsync(tcp =>
|
await ((ITextChannel)ctx.Channel).ModifyAsync(tcp =>
|
||||||
@@ -298,18 +298,18 @@ public partial class Administration : NadekoModule<AdministrationService>
|
|||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(ChannelPerm.ManageMessages)]
|
[UserPerm(ChannelPerm.ManageMessages)]
|
||||||
[BotPerm(ChannelPerm.ManageMessages)]
|
[BotPerm(ChannelPerm.ManageMessages)]
|
||||||
public Task Delete(ulong messageId, StoopidTime time = null)
|
public Task Delete(ulong messageId, ParsedTimespan timespan = null)
|
||||||
=> Delete((ITextChannel)ctx.Channel, messageId, time);
|
=> Delete((ITextChannel)ctx.Channel, messageId, timespan);
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
public async Task Delete(ITextChannel channel, ulong messageId, StoopidTime time = null)
|
public async Task Delete(ITextChannel channel, ulong messageId, ParsedTimespan timespan = null)
|
||||||
=> await InternalMessageAction(channel, messageId, time, msg => msg.DeleteAsync());
|
=> await InternalMessageAction(channel, messageId, timespan, msg => msg.DeleteAsync());
|
||||||
|
|
||||||
private async Task InternalMessageAction(
|
private async Task InternalMessageAction(
|
||||||
ITextChannel channel,
|
ITextChannel channel,
|
||||||
ulong messageId,
|
ulong messageId,
|
||||||
StoopidTime time,
|
ParsedTimespan timespan,
|
||||||
Func<IMessage, Task> func)
|
Func<IMessage, Task> func)
|
||||||
{
|
{
|
||||||
var userPerms = ((SocketGuildUser)ctx.User).GetPermissions(channel);
|
var userPerms = ((SocketGuildUser)ctx.User).GetPermissions(channel);
|
||||||
@@ -334,13 +334,13 @@ public partial class Administration : NadekoModule<AdministrationService>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (time is null)
|
if (timespan is null)
|
||||||
await msg.DeleteAsync();
|
await msg.DeleteAsync();
|
||||||
else if (time.Time <= TimeSpan.FromDays(7))
|
else if (timespan.Time <= TimeSpan.FromDays(7))
|
||||||
{
|
{
|
||||||
_ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await Task.Delay(time.Time);
|
await Task.Delay(timespan.Time);
|
||||||
await msg.DeleteAsync();
|
await msg.DeleteAsync();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -71,7 +71,7 @@ public sealed class HoneyPotService : IHoneyPotService, IReadyExecutor, IExecNoC
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Log.Information("Honeypot caught user {User} [{UserId}]", user, user.Id);
|
Log.Information("Honeypot caught user {User} [{UserId}]", user, user.Id);
|
||||||
await user.BanAsync(pruneDays: 1);
|
await user.BanAsync(pruneDays: 1, reason: "Honeypot");
|
||||||
await user.Guild.RemoveBanAsync(user.Id);
|
await user.Guild.RemoveBanAsync(user.Id);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
@@ -72,18 +72,18 @@ public partial class Administration
|
|||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.ManageRoles | GuildPerm.MuteMembers)]
|
[UserPerm(GuildPerm.ManageRoles | GuildPerm.MuteMembers)]
|
||||||
[Priority(1)]
|
[Priority(1)]
|
||||||
public async Task Mute(StoopidTime time, IGuildUser user, [Leftover] string reason = "")
|
public async Task Mute(ParsedTimespan timespan, IGuildUser user, [Leftover] string reason = "")
|
||||||
{
|
{
|
||||||
if (time.Time < TimeSpan.FromMinutes(1) || time.Time > TimeSpan.FromDays(49))
|
if (timespan.Time < TimeSpan.FromMinutes(1) || timespan.Time > TimeSpan.FromDays(49))
|
||||||
return;
|
return;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!await VerifyMutePermissions((IGuildUser)ctx.User, user))
|
if (!await VerifyMutePermissions((IGuildUser)ctx.User, user))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await _service.TimedMute(user, ctx.User, time.Time, reason: reason);
|
await _service.TimedMute(user, ctx.User, timespan.Time, reason: reason);
|
||||||
await Response().Confirm(strs.user_muted_time(Format.Bold(user.ToString()),
|
await Response().Confirm(strs.user_muted_time(Format.Bold(user.ToString()),
|
||||||
(int)time.Time.TotalMinutes)).SendAsync();
|
(int)timespan.Time.TotalMinutes)).SendAsync();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -133,18 +133,18 @@ public partial class Administration
|
|||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.ManageRoles)]
|
[UserPerm(GuildPerm.ManageRoles)]
|
||||||
[Priority(1)]
|
[Priority(1)]
|
||||||
public async Task ChatMute(StoopidTime time, IGuildUser user, [Leftover] string reason = "")
|
public async Task ChatMute(ParsedTimespan timespan, IGuildUser user, [Leftover] string reason = "")
|
||||||
{
|
{
|
||||||
if (time.Time < TimeSpan.FromMinutes(1) || time.Time > TimeSpan.FromDays(49))
|
if (timespan.Time < TimeSpan.FromMinutes(1) || timespan.Time > TimeSpan.FromDays(49))
|
||||||
return;
|
return;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!await VerifyMutePermissions((IGuildUser)ctx.User, user))
|
if (!await VerifyMutePermissions((IGuildUser)ctx.User, user))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await _service.TimedMute(user, ctx.User, time.Time, MuteType.Chat, reason);
|
await _service.TimedMute(user, ctx.User, timespan.Time, MuteType.Chat, reason);
|
||||||
await Response().Confirm(strs.user_chat_mute_time(Format.Bold(user.ToString()),
|
await Response().Confirm(strs.user_chat_mute_time(Format.Bold(user.ToString()),
|
||||||
(int)time.Time.TotalMinutes)).SendAsync();
|
(int)timespan.Time.TotalMinutes)).SendAsync();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -193,18 +193,18 @@ public partial class Administration
|
|||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.MuteMembers)]
|
[UserPerm(GuildPerm.MuteMembers)]
|
||||||
[Priority(1)]
|
[Priority(1)]
|
||||||
public async Task VoiceMute(StoopidTime time, IGuildUser user, [Leftover] string reason = "")
|
public async Task VoiceMute(ParsedTimespan timespan, IGuildUser user, [Leftover] string reason = "")
|
||||||
{
|
{
|
||||||
if (time.Time < TimeSpan.FromMinutes(1) || time.Time > TimeSpan.FromDays(49))
|
if (timespan.Time < TimeSpan.FromMinutes(1) || timespan.Time > TimeSpan.FromDays(49))
|
||||||
return;
|
return;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!await VerifyMutePermissions((IGuildUser)ctx.User, user))
|
if (!await VerifyMutePermissions((IGuildUser)ctx.User, user))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await _service.TimedMute(user, ctx.User, time.Time, MuteType.Voice, reason);
|
await _service.TimedMute(user, ctx.User, timespan.Time, MuteType.Voice, reason);
|
||||||
await Response().Confirm(strs.user_voice_mute_time(Format.Bold(user.ToString()),
|
await Response().Confirm(strs.user_voice_mute_time(Format.Bold(user.ToString()),
|
||||||
(int)time.Time.TotalMinutes)).SendAsync();
|
(int)timespan.Time.TotalMinutes)).SendAsync();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
23
src/NadekoBot/Modules/Administration/Notify/INotifyModel.cs
Normal file
23
src/NadekoBot/Modules/Administration/Notify/INotifyModel.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using NadekoBot.Db.Models;
|
||||||
|
using System.Collections;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Administration;
|
||||||
|
|
||||||
|
public interface INotifyModel
|
||||||
|
{
|
||||||
|
static abstract string KeyName { get; }
|
||||||
|
static abstract NotifyType NotifyType { get; }
|
||||||
|
IReadOnlyDictionary<string, Func<SocketGuild, string>> GetReplacements();
|
||||||
|
|
||||||
|
public virtual bool TryGetGuildId(out ulong guildId)
|
||||||
|
{
|
||||||
|
guildId = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool TryGetUserId(out ulong userId)
|
||||||
|
{
|
||||||
|
userId = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,7 @@
|
|||||||
|
namespace NadekoBot.Modules.Administration;
|
||||||
|
|
||||||
|
public interface INotifySubscriber
|
||||||
|
{
|
||||||
|
Task NotifyAsync<T>(T data, bool isShardLocal = false)
|
||||||
|
where T : struct, INotifyModel;
|
||||||
|
}
|
@@ -0,0 +1,36 @@
|
|||||||
|
using NadekoBot.Db.Models;
|
||||||
|
using NadekoBot.Modules.Administration;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Xp.Services;
|
||||||
|
|
||||||
|
public record struct AddRoleRewardNotifyModel(ulong GuildId, ulong RoleId, ulong UserId, long Level) : INotifyModel
|
||||||
|
{
|
||||||
|
public static string KeyName
|
||||||
|
=> "notify.reward.addrole";
|
||||||
|
|
||||||
|
public static NotifyType NotifyType
|
||||||
|
=> NotifyType.AddRoleReward;
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<string, Func<SocketGuild, string>> GetReplacements()
|
||||||
|
{
|
||||||
|
var model = this;
|
||||||
|
return new Dictionary<string, Func<SocketGuild, string>>()
|
||||||
|
{
|
||||||
|
{ "%event.user%", g => g.GetUser(model.UserId)?.ToString() ?? model.UserId.ToString() },
|
||||||
|
{ "%event.role%", g => g.GetRole(model.RoleId)?.ToString() ?? model.RoleId.ToString() },
|
||||||
|
{ "%event.level%", g => model.Level.ToString() }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetUserId(out ulong userId)
|
||||||
|
{
|
||||||
|
userId = UserId;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetGuildId(out ulong guildId)
|
||||||
|
{
|
||||||
|
guildId = GuildId;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,38 @@
|
|||||||
|
using NadekoBot.Db.Models;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Administration;
|
||||||
|
|
||||||
|
public record struct LevelUpNotifyModel(
|
||||||
|
ulong GuildId,
|
||||||
|
ulong ChannelId,
|
||||||
|
ulong UserId,
|
||||||
|
long Level) : INotifyModel
|
||||||
|
{
|
||||||
|
public static string KeyName
|
||||||
|
=> "notify.levelup";
|
||||||
|
|
||||||
|
public static NotifyType NotifyType
|
||||||
|
=> NotifyType.LevelUp;
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<string, Func<SocketGuild, string>> GetReplacements()
|
||||||
|
{
|
||||||
|
var data = this;
|
||||||
|
return new Dictionary<string, Func<SocketGuild, string>>()
|
||||||
|
{
|
||||||
|
{ "%event.level%", g => data.Level.ToString() },
|
||||||
|
{ "%event.user%", g => g.GetUser(data.UserId)?.ToString() ?? data.UserId.ToString() },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetGuildId(out ulong guildId)
|
||||||
|
{
|
||||||
|
guildId = GuildId;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetUserId(out ulong userId)
|
||||||
|
{
|
||||||
|
userId = UserId;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,34 @@
|
|||||||
|
#nullable disable
|
||||||
|
using NadekoBot.Db.Models;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Administration.Services;
|
||||||
|
|
||||||
|
public record struct ProtectionNotifyModel(ulong GuildId, ProtectionType ProtType, ulong UserId) : INotifyModel
|
||||||
|
{
|
||||||
|
public static string KeyName
|
||||||
|
=> "notify.protection";
|
||||||
|
|
||||||
|
public static NotifyType NotifyType
|
||||||
|
=> NotifyType.Protection;
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<string, Func<SocketGuild, string>> GetReplacements()
|
||||||
|
{
|
||||||
|
var data = this;
|
||||||
|
return new Dictionary<string, Func<SocketGuild, string>>()
|
||||||
|
{
|
||||||
|
{ "%event.type%", g => data.ProtType.ToString() },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetUserId(out ulong userId)
|
||||||
|
{
|
||||||
|
userId = UserId;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetGuildId(out ulong guildId)
|
||||||
|
{
|
||||||
|
guildId = GuildId;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,36 @@
|
|||||||
|
using NadekoBot.Db.Models;
|
||||||
|
using NadekoBot.Modules.Administration;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Xp.Services;
|
||||||
|
|
||||||
|
public record struct RemoveRoleRewardNotifyModel(ulong GuildId, ulong RoleId, ulong UserId, long Level) : INotifyModel
|
||||||
|
{
|
||||||
|
public static string KeyName
|
||||||
|
=> "notify.reward.removerole";
|
||||||
|
|
||||||
|
public static NotifyType NotifyType
|
||||||
|
=> NotifyType.RemoveRoleReward;
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<string, Func<SocketGuild, string>> GetReplacements()
|
||||||
|
{
|
||||||
|
var model = this;
|
||||||
|
return new Dictionary<string, Func<SocketGuild, string>>()
|
||||||
|
{
|
||||||
|
{ "%event.user%", g => g.GetUser(model.UserId)?.ToString() ?? model.UserId.ToString() },
|
||||||
|
{ "%event.role%", g => g.GetRole(model.RoleId)?.ToString() ?? model.RoleId.ToString() },
|
||||||
|
{ "%event.level%", g => model.Level.ToString() },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetUserId(out ulong userId)
|
||||||
|
{
|
||||||
|
userId = UserId;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetGuildId(out ulong guildId)
|
||||||
|
{
|
||||||
|
guildId = GuildId;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
114
src/NadekoBot/Modules/Administration/Notify/NotifyCommands.cs
Normal file
114
src/NadekoBot/Modules/Administration/Notify/NotifyCommands.cs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
using NadekoBot.Db.Models;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Administration;
|
||||||
|
|
||||||
|
public partial class Administration
|
||||||
|
{
|
||||||
|
public class NotifyCommands : NadekoModule<NotifyService>
|
||||||
|
{
|
||||||
|
[Cmd]
|
||||||
|
[UserPerm(GuildPerm.Administrator)]
|
||||||
|
public async Task Notify()
|
||||||
|
{
|
||||||
|
await Response()
|
||||||
|
.Paginated()
|
||||||
|
.Items(Enum.GetValues<NotifyType>())
|
||||||
|
.PageSize(5)
|
||||||
|
.Page((items, page) =>
|
||||||
|
{
|
||||||
|
var eb = CreateEmbed()
|
||||||
|
.WithOkColor()
|
||||||
|
.WithTitle(GetText(strs.notify_available));
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
eb.AddField(item.ToString(), GetText(GetDescription(item)), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return eb;
|
||||||
|
})
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private LocStr GetDescription(NotifyType item)
|
||||||
|
=> item switch
|
||||||
|
{
|
||||||
|
NotifyType.LevelUp => strs.notify_desc_levelup,
|
||||||
|
NotifyType.Protection => strs.notify_desc_protection,
|
||||||
|
NotifyType.AddRoleReward => strs.notify_desc_addrolerew,
|
||||||
|
NotifyType.RemoveRoleReward => strs.notify_desc_removerolerew,
|
||||||
|
_ => strs.notify_desc_not_found
|
||||||
|
};
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[UserPerm(GuildPerm.Administrator)]
|
||||||
|
public async Task Notify(NotifyType nType, [Leftover] string? message = null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(message))
|
||||||
|
{
|
||||||
|
// show msg
|
||||||
|
var conf = await _service.GetNotifyAsync(ctx.Guild.Id, nType);
|
||||||
|
if (conf is null)
|
||||||
|
{
|
||||||
|
await Response().Confirm(strs.notify_msg_not_set).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var eb = CreateEmbed()
|
||||||
|
.WithOkColor()
|
||||||
|
.WithTitle(GetText(strs.notify_msg))
|
||||||
|
.WithDescription(conf.Message.TrimTo(2048))
|
||||||
|
.AddField(GetText(strs.notify_type), conf.Type.ToString(), true)
|
||||||
|
.AddField(GetText(strs.channel),
|
||||||
|
$"""
|
||||||
|
<#{conf.ChannelId}>
|
||||||
|
`{conf.ChannelId}`
|
||||||
|
""",
|
||||||
|
true);
|
||||||
|
|
||||||
|
await Response().Embed(eb).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _service.EnableAsync(ctx.Guild.Id, ctx.Channel.Id, nType, message);
|
||||||
|
await Response().Confirm(strs.notify_on($"<#{ctx.Channel.Id}>", Format.Bold(nType.ToString()))).SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[UserPerm(GuildPerm.Administrator)]
|
||||||
|
public async Task NotifyList(int page = 1)
|
||||||
|
{
|
||||||
|
if (--page < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var notifs = await _service.GetForGuildAsync(ctx.Guild.Id);
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
foreach (var notif in notifs)
|
||||||
|
{
|
||||||
|
sb.AppendLine($"""
|
||||||
|
- **{notif.Type}**
|
||||||
|
<#{notif.ChannelId}> `{notif.ChannelId}`
|
||||||
|
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notifs.Count == 0)
|
||||||
|
sb.AppendLine(GetText(strs.notify_none));
|
||||||
|
|
||||||
|
await Response()
|
||||||
|
.Confirm(GetText(strs.notify_list), text: sb.ToString())
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[UserPerm(GuildPerm.Administrator)]
|
||||||
|
public async Task NotifyClear(NotifyType nType)
|
||||||
|
{
|
||||||
|
await _service.DisableAsync(ctx.Guild.Id, nType);
|
||||||
|
await Response().Confirm(strs.notify_off(nType)).SendAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,6 @@
|
|||||||
|
namespace NadekoBot.Modules.Administration;
|
||||||
|
|
||||||
|
public static class NotifyKeys
|
||||||
|
{
|
||||||
|
public static TypedKey<LevelUpNotifyModel> LevelUp { get; } = new("notify:levelup");
|
||||||
|
}
|
@@ -0,0 +1,8 @@
|
|||||||
|
namespace NadekoBot.Modules.Administration;
|
||||||
|
|
||||||
|
public static class NotifyModelExtensions
|
||||||
|
{
|
||||||
|
public static TypedKey<T> GetTypedKey<T>(this T model)
|
||||||
|
where T : struct, INotifyModel
|
||||||
|
=> new(T.KeyName);
|
||||||
|
}
|
227
src/NadekoBot/Modules/Administration/Notify/NotifyService.cs
Normal file
227
src/NadekoBot/Modules/Administration/Notify/NotifyService.cs
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
using LinqToDB;
|
||||||
|
using LinqToDB.EntityFrameworkCore;
|
||||||
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
|
using NadekoBot.Db.Models;
|
||||||
|
using NadekoBot.Generators;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Administration;
|
||||||
|
|
||||||
|
public sealed class NotifyService : IReadyExecutor, INotifySubscriber, INService
|
||||||
|
{
|
||||||
|
private readonly DbService _db;
|
||||||
|
private readonly IMessageSenderService _mss;
|
||||||
|
private readonly DiscordSocketClient _client;
|
||||||
|
private readonly IBotCreds _creds;
|
||||||
|
private readonly IReplacementService _repSvc;
|
||||||
|
private readonly IPubSub _pubSub;
|
||||||
|
private ConcurrentDictionary<NotifyType, ConcurrentDictionary<ulong, Notify>> _events = new();
|
||||||
|
|
||||||
|
public NotifyService(
|
||||||
|
DbService db,
|
||||||
|
IMessageSenderService mss,
|
||||||
|
DiscordSocketClient client,
|
||||||
|
IBotCreds creds,
|
||||||
|
IReplacementService repSvc,
|
||||||
|
IPubSub pubSub)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
_mss = mss;
|
||||||
|
_client = client;
|
||||||
|
_creds = creds;
|
||||||
|
_repSvc = repSvc;
|
||||||
|
_pubSub = pubSub;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OnReadyAsync()
|
||||||
|
{
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
_events = (await uow.GetTable<Notify>()
|
||||||
|
.Where(x => Linq2DbExpressions.GuildOnShard(x.GuildId,
|
||||||
|
_creds.TotalShards,
|
||||||
|
_client.ShardId))
|
||||||
|
.ToListAsyncLinqToDB())
|
||||||
|
.GroupBy(x => x.Type)
|
||||||
|
.ToDictionary(x => x.Key, x => x.ToDictionary(x => x.GuildId).ToConcurrent())
|
||||||
|
.ToConcurrent();
|
||||||
|
|
||||||
|
|
||||||
|
await SubscribeToEvent<LevelUpNotifyModel>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SubscribeToEvent<T>()
|
||||||
|
where T : struct, INotifyModel
|
||||||
|
{
|
||||||
|
await _pubSub.Sub(new TypedKey<T>(T.KeyName), async (model) => await OnEvent(model));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task NotifyAsync<T>(T data, bool isShardLocal = false)
|
||||||
|
where T : struct, INotifyModel
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (isShardLocal)
|
||||||
|
{
|
||||||
|
await OnEvent(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _pubSub.Pub(data.GetTypedKey(), data);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Warning(ex,
|
||||||
|
"Unknown error occurred while trying to triger {NotifyEvent} for {NotifyModel}",
|
||||||
|
T.KeyName,
|
||||||
|
data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnEvent<T>(T model)
|
||||||
|
where T : struct, INotifyModel
|
||||||
|
{
|
||||||
|
if (_events.TryGetValue(T.NotifyType, out var subs))
|
||||||
|
{
|
||||||
|
if (model.TryGetGuildId(out var gid))
|
||||||
|
{
|
||||||
|
if (!subs.TryGetValue(gid, out var conf))
|
||||||
|
return;
|
||||||
|
|
||||||
|
await HandleNotifyEvent(conf, model);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var key in subs.Keys.ToArray())
|
||||||
|
{
|
||||||
|
if (subs.TryGetValue(key, out var notif))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await HandleNotifyEvent(notif, model);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex,
|
||||||
|
"Error occured while sending notification {NotifyEvent} to guild {GuildId}: {ErrorMessage}",
|
||||||
|
T.NotifyType,
|
||||||
|
key,
|
||||||
|
ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleNotifyEvent(Notify conf, INotifyModel model)
|
||||||
|
{
|
||||||
|
var guild = _client.GetGuild(conf.GuildId);
|
||||||
|
var channel = guild?.GetTextChannel(conf.ChannelId);
|
||||||
|
|
||||||
|
if (guild is null || channel is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
IUser? user = null;
|
||||||
|
if (model.TryGetUserId(out var userId))
|
||||||
|
{
|
||||||
|
user = guild.GetUser(userId) ?? _client.GetUser(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var rctx = new ReplacementContext(guild: guild, channel: channel, user: user);
|
||||||
|
|
||||||
|
var st = SmartText.CreateFrom(conf.Message);
|
||||||
|
foreach (var modelRep in model.GetReplacements())
|
||||||
|
{
|
||||||
|
rctx.WithOverride(modelRep.Key, () => modelRep.Value(guild));
|
||||||
|
}
|
||||||
|
|
||||||
|
st = await _repSvc.ReplaceAsync(st, rctx);
|
||||||
|
if (st is SmartPlainText spt)
|
||||||
|
{
|
||||||
|
await _mss.Response(channel)
|
||||||
|
.Confirm(spt.Text)
|
||||||
|
.SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _mss.Response(channel)
|
||||||
|
.Text(st)
|
||||||
|
.Sanitize(false)
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task EnableAsync(
|
||||||
|
ulong guildId,
|
||||||
|
ulong channelId,
|
||||||
|
NotifyType nType,
|
||||||
|
string message)
|
||||||
|
{
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
await uow.GetTable<Notify>()
|
||||||
|
.InsertOrUpdateAsync(() => new()
|
||||||
|
{
|
||||||
|
GuildId = guildId,
|
||||||
|
ChannelId = channelId,
|
||||||
|
Type = nType,
|
||||||
|
Message = message,
|
||||||
|
},
|
||||||
|
(_) => new()
|
||||||
|
{
|
||||||
|
Message = message,
|
||||||
|
ChannelId = channelId
|
||||||
|
},
|
||||||
|
() => new()
|
||||||
|
{
|
||||||
|
GuildId = guildId,
|
||||||
|
Type = nType
|
||||||
|
});
|
||||||
|
|
||||||
|
var eventDict = _events.GetOrAdd(nType, _ => new());
|
||||||
|
eventDict[guildId] = new()
|
||||||
|
{
|
||||||
|
GuildId = guildId,
|
||||||
|
ChannelId = channelId,
|
||||||
|
Type = nType,
|
||||||
|
Message = message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DisableAsync(ulong guildId, NotifyType nType)
|
||||||
|
{
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
var deleted = await uow.GetTable<Notify>()
|
||||||
|
.Where(x => x.GuildId == guildId && x.Type == nType)
|
||||||
|
.DeleteAsync();
|
||||||
|
|
||||||
|
if (deleted == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_events.TryGetValue(nType, out var guildsDict))
|
||||||
|
return;
|
||||||
|
|
||||||
|
guildsDict.TryRemove(guildId, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyCollection<Notify>> GetForGuildAsync(ulong guildId, int page = 0)
|
||||||
|
{
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(page);
|
||||||
|
|
||||||
|
await using var ctx = _db.GetDbContext();
|
||||||
|
var list = await ctx.GetTable<Notify>()
|
||||||
|
.Where(x => x.GuildId == guildId)
|
||||||
|
.OrderBy(x => x.Type)
|
||||||
|
.Skip(page * 10)
|
||||||
|
.Take(10)
|
||||||
|
.ToListAsyncLinqToDB();
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Notify?> GetNotifyAsync(ulong guildId, NotifyType nType)
|
||||||
|
{
|
||||||
|
await using var ctx = _db.GetDbContext();
|
||||||
|
return await ctx.GetTable<Notify>()
|
||||||
|
.Where(x => x.GuildId == guildId && x.Type == nType)
|
||||||
|
.FirstOrDefaultAsyncLinqToDB();
|
||||||
|
}
|
||||||
|
}
|
@@ -6,7 +6,7 @@ namespace NadekoBot.Modules.Administration;
|
|||||||
public partial class Administration
|
public partial class Administration
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class PlayingRotateCommands : NadekoModule<PlayingRotateService>
|
public partial class PlayingRotateCommands : NadekoModule<IBotActivityService>
|
||||||
{
|
{
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[OwnerOnly]
|
[OwnerOnly]
|
||||||
|
@@ -28,17 +28,17 @@ public partial class Administration
|
|||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.Administrator)]
|
[UserPerm(GuildPerm.Administrator)]
|
||||||
public async Task AntiAlt(
|
public async Task AntiAlt(
|
||||||
StoopidTime minAge,
|
ParsedTimespan minAge,
|
||||||
PunishmentAction action,
|
PunishmentAction action,
|
||||||
[Leftover] StoopidTime punishTime = null)
|
[Leftover] ParsedTimespan punishTimespan = null)
|
||||||
{
|
{
|
||||||
var minAgeMinutes = (int)minAge.Time.TotalMinutes;
|
var minAgeMinutes = (int)minAge.Time.TotalMinutes;
|
||||||
var punishTimeMinutes = (int?)punishTime?.Time.TotalMinutes ?? 0;
|
var punishTimeMinutes = (int?)punishTimespan?.Time.TotalMinutes ?? 0;
|
||||||
|
|
||||||
if (minAgeMinutes < 1 || punishTimeMinutes < 0)
|
if (minAgeMinutes < 1 || punishTimeMinutes < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var minutes = (int?)punishTime?.Time.TotalMinutes ?? 0;
|
var minutes = (int?)punishTimespan?.Time.TotalMinutes ?? 0;
|
||||||
if (action is PunishmentAction.TimeOut && minutes < 1)
|
if (action is PunishmentAction.TimeOut && minutes < 1)
|
||||||
minutes = 1;
|
minutes = 1;
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ public partial class Administration
|
|||||||
[Cmd]
|
[Cmd]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.Administrator)]
|
[UserPerm(GuildPerm.Administrator)]
|
||||||
public async Task AntiAlt(StoopidTime minAge, PunishmentAction action, [Leftover] IRole role)
|
public async Task AntiAlt(ParsedTimespan minAge, PunishmentAction action, [Leftover] IRole role)
|
||||||
{
|
{
|
||||||
var minAgeMinutes = (int)minAge.Time.TotalMinutes;
|
var minAgeMinutes = (int)minAge.Time.TotalMinutes;
|
||||||
|
|
||||||
@@ -86,8 +86,8 @@ public partial class Administration
|
|||||||
int userThreshold,
|
int userThreshold,
|
||||||
int seconds,
|
int seconds,
|
||||||
PunishmentAction action,
|
PunishmentAction action,
|
||||||
[Leftover] StoopidTime punishTime)
|
[Leftover] ParsedTimespan punishTimespan)
|
||||||
=> InternalAntiRaid(userThreshold, seconds, action, punishTime);
|
=> InternalAntiRaid(userThreshold, seconds, action, punishTimespan);
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
@@ -100,7 +100,7 @@ public partial class Administration
|
|||||||
int userThreshold,
|
int userThreshold,
|
||||||
int seconds = 10,
|
int seconds = 10,
|
||||||
PunishmentAction action = PunishmentAction.Mute,
|
PunishmentAction action = PunishmentAction.Mute,
|
||||||
StoopidTime punishTime = null)
|
ParsedTimespan punishTimespan = null)
|
||||||
{
|
{
|
||||||
if (action == PunishmentAction.AddRole)
|
if (action == PunishmentAction.AddRole)
|
||||||
{
|
{
|
||||||
@@ -120,13 +120,13 @@ public partial class Administration
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (punishTime is not null)
|
if (punishTimespan is not null)
|
||||||
{
|
{
|
||||||
if (!_service.IsDurationAllowed(action))
|
if (!_service.IsDurationAllowed(action))
|
||||||
await Response().Error(strs.prot_cant_use_time).SendAsync();
|
await Response().Error(strs.prot_cant_use_time).SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
var time = (int?)punishTime?.Time.TotalMinutes ?? 0;
|
var time = (int?)punishTimespan?.Time.TotalMinutes ?? 0;
|
||||||
if (time is < 0 or > 60 * 24)
|
if (time is < 0 or > 60 * 24)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -170,8 +170,8 @@ public partial class Administration
|
|||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.Administrator)]
|
[UserPerm(GuildPerm.Administrator)]
|
||||||
[Priority(1)]
|
[Priority(1)]
|
||||||
public Task AntiSpam(int messageCount, PunishmentAction action, [Leftover] StoopidTime punishTime)
|
public Task AntiSpam(int messageCount, PunishmentAction action, [Leftover] ParsedTimespan punishTimespan)
|
||||||
=> InternalAntiSpam(messageCount, action, punishTime);
|
=> InternalAntiSpam(messageCount, action, punishTimespan);
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
@@ -183,19 +183,19 @@ public partial class Administration
|
|||||||
private async Task InternalAntiSpam(
|
private async Task InternalAntiSpam(
|
||||||
int messageCount,
|
int messageCount,
|
||||||
PunishmentAction action,
|
PunishmentAction action,
|
||||||
StoopidTime timeData = null,
|
ParsedTimespan timespanData = null,
|
||||||
IRole role = null)
|
IRole role = null)
|
||||||
{
|
{
|
||||||
if (messageCount is < 2 or > 10)
|
if (messageCount is < 2 or > 10)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (timeData is not null)
|
if (timespanData is not null)
|
||||||
{
|
{
|
||||||
if (!_service.IsDurationAllowed(action))
|
if (!_service.IsDurationAllowed(action))
|
||||||
await Response().Error(strs.prot_cant_use_time).SendAsync();
|
await Response().Error(strs.prot_cant_use_time).SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
var time = (int?)timeData?.Time.TotalMinutes ?? 0;
|
var time = (int?)timespanData?.Time.TotalMinutes ?? 0;
|
||||||
if (time is < 0 or > 60 * 24)
|
if (time is < 0 or > 60 * 24)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@@ -22,6 +22,7 @@ public class ProtectionService : INService
|
|||||||
private readonly MuteService _mute;
|
private readonly MuteService _mute;
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
private readonly UserPunishService _punishService;
|
private readonly UserPunishService _punishService;
|
||||||
|
private readonly INotifySubscriber _notifySub;
|
||||||
|
|
||||||
private readonly Channel<PunishQueueItem> _punishUserQueue =
|
private readonly Channel<PunishQueueItem> _punishUserQueue =
|
||||||
Channel.CreateUnbounded<PunishQueueItem>(new()
|
Channel.CreateUnbounded<PunishQueueItem>(new()
|
||||||
@@ -35,12 +36,14 @@ public class ProtectionService : INService
|
|||||||
IBot bot,
|
IBot bot,
|
||||||
MuteService mute,
|
MuteService mute,
|
||||||
DbService db,
|
DbService db,
|
||||||
UserPunishService punishService)
|
UserPunishService punishService,
|
||||||
|
INotifySubscriber notifySub)
|
||||||
{
|
{
|
||||||
_client = client;
|
_client = client;
|
||||||
_mute = mute;
|
_mute = mute;
|
||||||
_db = db;
|
_db = db;
|
||||||
_punishService = punishService;
|
_punishService = punishService;
|
||||||
|
_notifySub = notifySub;
|
||||||
|
|
||||||
var ids = client.GetGuildIds();
|
var ids = client.GetGuildIds();
|
||||||
using (var uow = db.GetDbContext())
|
using (var uow = db.GetDbContext())
|
||||||
@@ -175,6 +178,9 @@ public class ProtectionService : INService
|
|||||||
alts.RoleId,
|
alts.RoleId,
|
||||||
user);
|
user);
|
||||||
|
|
||||||
|
await _notifySub.NotifyAsync(new ProtectionNotifyModel(user.Guild.Id,
|
||||||
|
ProtectionType.Alting,
|
||||||
|
user.Id));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,6 +200,8 @@ public class ProtectionService : INService
|
|||||||
var settings = stats.AntiRaidSettings;
|
var settings = stats.AntiRaidSettings;
|
||||||
|
|
||||||
await PunishUsers(settings.Action, ProtectionType.Raiding, settings.PunishDuration, null, users);
|
await PunishUsers(settings.Action, ProtectionType.Raiding, settings.PunishDuration, null, users);
|
||||||
|
await _notifySub.NotifyAsync(
|
||||||
|
new ProtectionNotifyModel(user.Guild.Id, ProtectionType.Raiding, users[0].Id));
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.Delay(1000 * stats.AntiRaidSettings.Seconds);
|
await Task.Delay(1000 * stats.AntiRaidSettings.Seconds);
|
||||||
@@ -246,6 +254,10 @@ public class ProtectionService : INService
|
|||||||
settings.MuteTime,
|
settings.MuteTime,
|
||||||
settings.RoleId,
|
settings.RoleId,
|
||||||
(IGuildUser)msg.Author);
|
(IGuildUser)msg.Author);
|
||||||
|
|
||||||
|
await _notifySub.NotifyAsync(new ProtectionNotifyModel(channel.GuildId,
|
||||||
|
ProtectionType.Spamming,
|
||||||
|
msg.Author.Id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
using Google.Protobuf.WellKnownTypes;
|
||||||
|
using NadekoBot.Common.TypeReaders.Models;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
using Color = SixLabors.ImageSharp.Color;
|
using Color = SixLabors.ImageSharp.Color;
|
||||||
|
|
||||||
@@ -13,13 +15,18 @@ public partial class Administration
|
|||||||
Excl
|
Excl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly TempRoleService _tempRoleService;
|
||||||
private readonly IServiceProvider _services;
|
private readonly IServiceProvider _services;
|
||||||
private StickyRolesService _stickyRoleSvc;
|
private StickyRolesService _stickyRoleSvc;
|
||||||
|
|
||||||
public RoleCommands(IServiceProvider services, StickyRolesService stickyRoleSvc)
|
public RoleCommands(
|
||||||
|
IServiceProvider services,
|
||||||
|
StickyRolesService stickyRoleSvc,
|
||||||
|
TempRoleService tempRoleService)
|
||||||
{
|
{
|
||||||
_services = services;
|
_services = services;
|
||||||
_stickyRoleSvc = stickyRoleSvc;
|
_stickyRoleSvc = stickyRoleSvc;
|
||||||
|
_tempRoleService = tempRoleService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
@@ -34,13 +41,16 @@ public partial class Administration
|
|||||||
return;
|
return;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await targetUser.AddRoleAsync(roleToAdd, new RequestOptions()
|
await targetUser.AddRoleAsync(roleToAdd,
|
||||||
{
|
new RequestOptions()
|
||||||
AuditLogReason = $"Added by [{ctx.User.Username}]"
|
{
|
||||||
});
|
AuditLogReason = $"Added by [{ctx.User.Username}]"
|
||||||
|
});
|
||||||
|
|
||||||
await Response().Confirm(strs.setrole(Format.Bold(roleToAdd.Name),
|
await Response()
|
||||||
Format.Bold(targetUser.ToString()))).SendAsync();
|
.Confirm(strs.setrole(Format.Bold(roleToAdd.Name),
|
||||||
|
Format.Bold(targetUser.ToString())))
|
||||||
|
.SendAsync();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -62,8 +72,10 @@ public partial class Administration
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await targetUser.RemoveRoleAsync(roleToRemove);
|
await targetUser.RemoveRoleAsync(roleToRemove);
|
||||||
await Response().Confirm(strs.remrole(Format.Bold(roleToRemove.Name),
|
await Response()
|
||||||
Format.Bold(targetUser.ToString()))).SendAsync();
|
.Confirm(strs.remrole(Format.Bold(roleToRemove.Name),
|
||||||
|
Format.Bold(targetUser.ToString())))
|
||||||
|
.SendAsync();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -204,5 +216,29 @@ public partial class Administration
|
|||||||
await Response().Confirm(strs.sticky_roles_disabled).SendAsync();
|
await Response().Confirm(strs.sticky_roles_disabled).SendAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
[UserPerm(GuildPerm.Administrator)]
|
||||||
|
[BotPerm(GuildPerm.ManageRoles)]
|
||||||
|
public async Task TempRole(ParsedTimespan timespan, IUser user, [Leftover] IRole role)
|
||||||
|
{
|
||||||
|
if (!await CheckRoleHierarchy(role))
|
||||||
|
{
|
||||||
|
await Response()
|
||||||
|
.Error(strs.hierarchy)
|
||||||
|
.SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _tempRoleService.AddTempRoleAsync(ctx.Guild.Id, role.Id, user.Id, timespan.Time);
|
||||||
|
|
||||||
|
|
||||||
|
await Response()
|
||||||
|
.Confirm(strs.temp_role_added(user.Mention,
|
||||||
|
Format.Bold(role.Name),
|
||||||
|
TimestampTag.FromDateTime(DateTime.UtcNow.Add(timespan.Time), TimestampTagStyles.Relative)))
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
140
src/NadekoBot/Modules/Administration/Role/TempRoleService.cs
Normal file
140
src/NadekoBot/Modules/Administration/Role/TempRoleService.cs
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
using LinqToDB;
|
||||||
|
using LinqToDB.EntityFrameworkCore;
|
||||||
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
|
using NadekoBot.Db.Models;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Administration;
|
||||||
|
|
||||||
|
public class TempRoleService : IReadyExecutor, INService
|
||||||
|
{
|
||||||
|
private readonly DbService _db;
|
||||||
|
private readonly DiscordSocketClient _client;
|
||||||
|
private readonly IBotCreds _creds;
|
||||||
|
|
||||||
|
private TaskCompletionSource<bool> _tcs = new();
|
||||||
|
|
||||||
|
public TempRoleService(
|
||||||
|
DbService db,
|
||||||
|
DiscordSocketClient client,
|
||||||
|
IBotCreds creds)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
_client = client;
|
||||||
|
_creds = creds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddTempRoleAsync(
|
||||||
|
ulong guildId,
|
||||||
|
ulong roleId,
|
||||||
|
ulong userId,
|
||||||
|
TimeSpan duration)
|
||||||
|
{
|
||||||
|
if (duration == TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
await uow.GetTable<TempRole>()
|
||||||
|
.Where(x => x.GuildId == guildId && x.UserId == userId)
|
||||||
|
.DeleteAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var until = DateTime.UtcNow.Add(duration);
|
||||||
|
await using var ctx = _db.GetDbContext();
|
||||||
|
await ctx.GetTable<TempRole>()
|
||||||
|
.InsertOrUpdateAsync(() => new()
|
||||||
|
{
|
||||||
|
GuildId = guildId,
|
||||||
|
RoleId = roleId,
|
||||||
|
UserId = userId,
|
||||||
|
Remove = false,
|
||||||
|
ExpiresAt = until
|
||||||
|
},
|
||||||
|
(old) => new()
|
||||||
|
{
|
||||||
|
ExpiresAt = until,
|
||||||
|
},
|
||||||
|
() => new()
|
||||||
|
{
|
||||||
|
GuildId = guildId,
|
||||||
|
UserId = userId,
|
||||||
|
RoleId = roleId
|
||||||
|
});
|
||||||
|
|
||||||
|
_tcs.TrySetResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OnReadyAsync()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
var latest = await _db.GetDbContext()
|
||||||
|
.GetTable<TempRole>()
|
||||||
|
.Where(x => Linq2DbExpressions.GuildOnShard(x.GuildId,
|
||||||
|
_creds.TotalShards,
|
||||||
|
_client.ShardId))
|
||||||
|
.OrderBy(x => x.ExpiresAt)
|
||||||
|
.FirstOrDefaultAsyncLinqToDB();
|
||||||
|
|
||||||
|
if (latest == default)
|
||||||
|
{
|
||||||
|
await _tcs.Task;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
if (latest.ExpiresAt > now)
|
||||||
|
{
|
||||||
|
await Task.WhenAny(Task.Delay(latest.ExpiresAt - now), _tcs.Task);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var deleted = await _db.GetDbContext()
|
||||||
|
.GetTable<TempRole>()
|
||||||
|
.Where(x => Linq2DbExpressions.GuildOnShard(x.GuildId,
|
||||||
|
_creds.TotalShards,
|
||||||
|
_client.ShardId)
|
||||||
|
&& x.ExpiresAt <= now)
|
||||||
|
.DeleteWithOutputAsync();
|
||||||
|
|
||||||
|
foreach (var d in deleted)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await RemoveRole(d);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Log.Warning("Unable to remove temp role {RoleId} from user {UserId}",
|
||||||
|
d.RoleId,
|
||||||
|
d.UserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Unexpected error occurred in temprole loop");
|
||||||
|
await Task.Delay(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RemoveRole(TempRole tempRole)
|
||||||
|
{
|
||||||
|
var guild = _client.GetGuild(tempRole.GuildId);
|
||||||
|
|
||||||
|
var role = guild?.GetRole(tempRole.RoleId);
|
||||||
|
if (role is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var user = guild?.GetUser(tempRole.UserId);
|
||||||
|
if (user is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await user.RemoveRoleAsync(role);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,71 +1,32 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NadekoBot.Common.ModuleBehaviors;
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
using NadekoBot.Db.Models;
|
using NadekoBot.Db.Models;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Administration.Services;
|
namespace NadekoBot.Modules.Administration.Services;
|
||||||
|
|
||||||
public sealed class PlayingRotateService : INService, IReadyExecutor
|
public sealed class BotActivityService : IBotActivityService, IReadyExecutor, INService
|
||||||
{
|
{
|
||||||
private readonly BotConfigService _bss;
|
private readonly TypedKey<ActivityPubData> _activitySetKey = new("activity.set");
|
||||||
private readonly SelfService _selfService;
|
|
||||||
|
|
||||||
private readonly IReplacementService _repService;
|
private readonly IPubSub _pubSub;
|
||||||
|
|
||||||
// private readonly Replacer _rep;
|
|
||||||
private readonly DbService _db;
|
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
|
private readonly DbService _db;
|
||||||
|
private readonly IReplacementService _rep;
|
||||||
|
private readonly BotConfigService _bss;
|
||||||
|
|
||||||
public PlayingRotateService(
|
public BotActivityService(
|
||||||
|
IPubSub pubSub,
|
||||||
DiscordSocketClient client,
|
DiscordSocketClient client,
|
||||||
DbService db,
|
DbService db,
|
||||||
BotConfigService bss,
|
IReplacementService rep,
|
||||||
IEnumerable<IPlaceholderProvider> phProviders,
|
BotConfigService bss)
|
||||||
SelfService selfService,
|
|
||||||
IReplacementService repService)
|
|
||||||
{
|
{
|
||||||
_db = db;
|
_pubSub = pubSub;
|
||||||
_bss = bss;
|
|
||||||
_selfService = selfService;
|
|
||||||
_repService = repService;
|
|
||||||
_client = client;
|
_client = client;
|
||||||
}
|
_db = db;
|
||||||
|
_rep = rep;
|
||||||
public async Task OnReadyAsync()
|
_bss = bss;
|
||||||
{
|
|
||||||
if (_client.ShardId != 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(1));
|
|
||||||
var index = 0;
|
|
||||||
while (await timer.WaitForNextTickAsync())
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!_bss.Data.RotateStatuses)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
IReadOnlyList<RotatingPlayingStatus> rotatingStatuses;
|
|
||||||
await using (var uow = _db.GetDbContext())
|
|
||||||
{
|
|
||||||
rotatingStatuses = uow.Set<RotatingPlayingStatus>().AsNoTracking().OrderBy(x => x.Id).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rotatingStatuses.Count == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var playingStatus = index >= rotatingStatuses.Count
|
|
||||||
? rotatingStatuses[index = 0]
|
|
||||||
: rotatingStatuses[index++];
|
|
||||||
|
|
||||||
var statusText = await _repService.ReplaceAsync(playingStatus.Status, new(client: _client));
|
|
||||||
await _selfService.SetActivityAsync(statusText, (ActivityType)playingStatus.Type);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Warning(ex, "Rotating playing status errored: {ErrorMessage}", ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> RemovePlayingAsync(int index)
|
public async Task<string> RemovePlayingAsync(int index)
|
||||||
@@ -116,4 +77,91 @@ public sealed class PlayingRotateService : INService, IReadyExecutor
|
|||||||
using var uow = _db.GetDbContext();
|
using var uow = _db.GetDbContext();
|
||||||
return uow.Set<RotatingPlayingStatus>().AsNoTracking().ToList();
|
return uow.Set<RotatingPlayingStatus>().AsNoTracking().ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task SetActivityAsync(string game, ActivityType? type)
|
||||||
|
=> _pubSub.Pub(_activitySetKey,
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Name = game,
|
||||||
|
Link = null,
|
||||||
|
Type = type
|
||||||
|
});
|
||||||
|
|
||||||
|
public Task SetStreamAsync(string name, string link)
|
||||||
|
=> _pubSub.Pub(_activitySetKey,
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
Link = link,
|
||||||
|
Type = ActivityType.Streaming
|
||||||
|
});
|
||||||
|
|
||||||
|
private sealed class ActivityPubData
|
||||||
|
{
|
||||||
|
public string Name { get; init; }
|
||||||
|
public string Link { get; init; }
|
||||||
|
public ActivityType? Type { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OnReadyAsync()
|
||||||
|
{
|
||||||
|
await _pubSub.Sub(_activitySetKey,
|
||||||
|
async data =>
|
||||||
|
{
|
||||||
|
if (_client.ShardId == 0)
|
||||||
|
{
|
||||||
|
DisableRotatePlaying();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (data.Type is { } activityType)
|
||||||
|
{
|
||||||
|
await _client.SetGameAsync(data.Name, data.Link, activityType);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _client.SetCustomStatusAsync(data.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Warning(ex, "Error setting activity");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (_client.ShardId != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(1));
|
||||||
|
var index = 0;
|
||||||
|
while (await timer.WaitForNextTickAsync())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_bss.Data.RotateStatuses)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
IReadOnlyList<RotatingPlayingStatus> rotatingStatuses;
|
||||||
|
await using (var uow = _db.GetDbContext())
|
||||||
|
{
|
||||||
|
rotatingStatuses = uow.Set<RotatingPlayingStatus>().AsNoTracking().OrderBy(x => x.Id).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rotatingStatuses.Count == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var playingStatus = index >= rotatingStatuses.Count
|
||||||
|
? rotatingStatuses[index = 0]
|
||||||
|
: rotatingStatuses[index++];
|
||||||
|
|
||||||
|
var statusText = await _rep.ReplaceAsync(playingStatus.Status, new(client: _client));
|
||||||
|
await SetActivityAsync(statusText, (ActivityType)playingStatus.Type);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Warning(ex, "Rotating playing status errored: {ErrorMessage}", ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@@ -0,0 +1,14 @@
|
|||||||
|
#nullable disable
|
||||||
|
using NadekoBot.Db.Models;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Administration.Services;
|
||||||
|
|
||||||
|
public interface IBotActivityService
|
||||||
|
{
|
||||||
|
Task SetActivityAsync(string game, ActivityType? type);
|
||||||
|
Task SetStreamAsync(string name, string link);
|
||||||
|
bool ToggleRotatePlaying();
|
||||||
|
Task AddPlaying(ActivityType statusType, string status);
|
||||||
|
Task<string> RemovePlayingAsync(int index);
|
||||||
|
IReadOnlyList<RotatingPlayingStatus> GetRotatingStatuses();
|
||||||
|
}
|
@@ -24,19 +24,22 @@ public partial class Administration
|
|||||||
private readonly IMedusaLoaderService _medusaLoader;
|
private readonly IMedusaLoaderService _medusaLoader;
|
||||||
private readonly ICoordinator _coord;
|
private readonly ICoordinator _coord;
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
|
private readonly IBotActivityService _bas;
|
||||||
|
|
||||||
public SelfCommands(
|
public SelfCommands(
|
||||||
DiscordSocketClient client,
|
DiscordSocketClient client,
|
||||||
DbService db,
|
DbService db,
|
||||||
IBotStrings strings,
|
IBotStrings strings,
|
||||||
ICoordinator coord,
|
ICoordinator coord,
|
||||||
IMedusaLoaderService medusaLoader)
|
IMedusaLoaderService medusaLoader,
|
||||||
|
IBotActivityService bas)
|
||||||
{
|
{
|
||||||
_client = client;
|
_client = client;
|
||||||
_db = db;
|
_db = db;
|
||||||
_strings = strings;
|
_strings = strings;
|
||||||
_coord = coord;
|
_coord = coord;
|
||||||
_medusaLoader = medusaLoader;
|
_medusaLoader = medusaLoader;
|
||||||
|
_bas = bas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -63,9 +66,9 @@ public partial class Administration
|
|||||||
|
|
||||||
await message.ModifyAsync(x =>
|
await message.ModifyAsync(x =>
|
||||||
x.Embed = CreateEmbed()
|
x.Embed = CreateEmbed()
|
||||||
.WithDescription(GetText(strs.cache_users_done(added, updated)))
|
.WithDescription(GetText(strs.cache_users_done(added, updated)))
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.Build()
|
.Build()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,13 +119,13 @@ public partial class Administration
|
|||||||
|
|
||||||
await Response()
|
await Response()
|
||||||
.Embed(CreateEmbed()
|
.Embed(CreateEmbed()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithTitle(GetText(strs.scadd))
|
.WithTitle(GetText(strs.scadd))
|
||||||
.AddField(GetText(strs.server),
|
.AddField(GetText(strs.server),
|
||||||
cmd.GuildId is null ? "-" : $"{cmd.GuildName}/{cmd.GuildId}",
|
cmd.GuildId is null ? "-" : $"{cmd.GuildName}/{cmd.GuildId}",
|
||||||
true)
|
true)
|
||||||
.AddField(GetText(strs.channel), $"{cmd.ChannelName}/{cmd.ChannelId}", true)
|
.AddField(GetText(strs.channel), $"{cmd.ChannelName}/{cmd.ChannelId}", true)
|
||||||
.AddField(GetText(strs.command_text), cmdText))
|
.AddField(GetText(strs.command_text), cmdText))
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -496,7 +499,7 @@ public partial class Administration
|
|||||||
// var rep = new ReplacementBuilder().WithDefault(Context).Build();
|
// var rep = new ReplacementBuilder().WithDefault(Context).Build();
|
||||||
|
|
||||||
var repCtx = new ReplacementContext(ctx);
|
var repCtx = new ReplacementContext(ctx);
|
||||||
await _service.SetActivityAsync(game is null ? game : await repSvc.ReplaceAsync(game, repCtx), type);
|
await _bas.SetActivityAsync(game is null ? game : await repSvc.ReplaceAsync(game, repCtx), type);
|
||||||
|
|
||||||
await Response().Confirm(strs.set_activity).SendAsync();
|
await Response().Confirm(strs.set_activity).SendAsync();
|
||||||
}
|
}
|
||||||
@@ -518,7 +521,7 @@ public partial class Administration
|
|||||||
{
|
{
|
||||||
name ??= "";
|
name ??= "";
|
||||||
|
|
||||||
await _service.SetStreamAsync(name, url);
|
await _bas.SetStreamAsync(name, url);
|
||||||
|
|
||||||
await Response().Confirm(strs.set_stream).SendAsync();
|
await Response().Confirm(strs.set_stream).SendAsync();
|
||||||
}
|
}
|
||||||
|
@@ -28,7 +28,6 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
|
|||||||
private readonly IMessageSenderService _sender;
|
private readonly IMessageSenderService _sender;
|
||||||
|
|
||||||
//keys
|
//keys
|
||||||
private readonly TypedKey<ActivityPubData> _activitySetKey;
|
|
||||||
private readonly TypedKey<string> _guildLeaveKey;
|
private readonly TypedKey<string> _guildLeaveKey;
|
||||||
|
|
||||||
public SelfService(
|
public SelfService(
|
||||||
@@ -51,11 +50,8 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
|
|||||||
_bss = bss;
|
_bss = bss;
|
||||||
_pubSub = pubSub;
|
_pubSub = pubSub;
|
||||||
_sender = sender;
|
_sender = sender;
|
||||||
_activitySetKey = new("activity.set");
|
|
||||||
_guildLeaveKey = new("guild.leave");
|
_guildLeaveKey = new("guild.leave");
|
||||||
|
|
||||||
HandleStatusChanges();
|
|
||||||
|
|
||||||
_pubSub.Sub(_guildLeaveKey,
|
_pubSub.Sub(_guildLeaveKey,
|
||||||
async input =>
|
async input =>
|
||||||
{
|
{
|
||||||
@@ -394,49 +390,6 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
|
|||||||
return channelId is not null;
|
return channelId is not null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleStatusChanges()
|
|
||||||
=> _pubSub.Sub(_activitySetKey,
|
|
||||||
async data =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (data.Type is { } activityType)
|
|
||||||
await _client.SetGameAsync(data.Name, data.Link, activityType);
|
|
||||||
else
|
|
||||||
await _client.SetCustomStatusAsync(data.Name);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Warning(ex, "Error setting activity");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
public Task SetActivityAsync(string game, ActivityType? type)
|
|
||||||
=> _pubSub.Pub(_activitySetKey,
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Name = game,
|
|
||||||
Link = null,
|
|
||||||
Type = type
|
|
||||||
});
|
|
||||||
|
|
||||||
public Task SetStreamAsync(string name, string link)
|
|
||||||
=> _pubSub.Pub(_activitySetKey,
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Name = name,
|
|
||||||
Link = link,
|
|
||||||
Type = ActivityType.Streaming
|
|
||||||
});
|
|
||||||
|
|
||||||
private sealed class ActivityPubData
|
|
||||||
{
|
|
||||||
public string Name { get; init; }
|
|
||||||
public string Link { get; init; }
|
|
||||||
public ActivityType? Type { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the specified <paramref name="users"/> to the database. If a database user with placeholder name
|
/// Adds the specified <paramref name="users"/> to the database. If a database user with placeholder name
|
||||||
/// and discriminator is present in <paramref name="users"/>, their name and discriminator get updated accordingly.
|
/// and discriminator is present in <paramref name="users"/>, their name and discriminator get updated accordingly.
|
||||||
|
@@ -21,7 +21,7 @@ public partial class Administration
|
|||||||
{
|
{
|
||||||
var guildUser = (IGuildUser)ctx.User;
|
var guildUser = (IGuildUser)ctx.User;
|
||||||
|
|
||||||
var group = await _service.GetRoleGroup(ctx.User.Id, role.Id);
|
var group = await _service.GetRoleGroup(ctx.Guild.Id, role.Id);
|
||||||
|
|
||||||
IUserMessage msg = null;
|
IUserMessage msg = null;
|
||||||
try
|
try
|
||||||
@@ -84,13 +84,13 @@ public partial class Administration
|
|||||||
IUserMessage msg = null;
|
IUserMessage msg = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (guildUser.RoleIds.Contains(role.Id))
|
if (!guildUser.RoleIds.Contains(role.Id))
|
||||||
{
|
{
|
||||||
msg = await Response().Error(strs.self_assign_not_have(Format.Bold(role.Name))).SendAsync();
|
msg = await Response().Error(strs.self_assign_not_have(Format.Bold(role.Name))).SendAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var group = await _service.GetRoleGroup(role.Guild.Id, role.Id);
|
var group = await _service.GetRoleGroup(ctx.Guild.Id, role.Id);
|
||||||
|
|
||||||
if (group is null || group.Roles.All(x => x.RoleId != role.Id))
|
if (group is null || group.Roles.All(x => x.RoleId != role.Id))
|
||||||
{
|
{
|
||||||
|
@@ -313,7 +313,7 @@ public partial class Administration
|
|||||||
int number,
|
int number,
|
||||||
AddRole _,
|
AddRole _,
|
||||||
IRole role,
|
IRole role,
|
||||||
StoopidTime time = null)
|
ParsedTimespan timespan = null)
|
||||||
{
|
{
|
||||||
var punish = PunishmentAction.AddRole;
|
var punish = PunishmentAction.AddRole;
|
||||||
|
|
||||||
@@ -324,12 +324,12 @@ public partial class Administration
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var success = await _service.WarnPunish(ctx.Guild.Id, number, punish, time, role);
|
var success = await _service.WarnPunish(ctx.Guild.Id, number, punish, timespan, role);
|
||||||
|
|
||||||
if (!success)
|
if (!success)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (time is null)
|
if (timespan is null)
|
||||||
{
|
{
|
||||||
await Response()
|
await Response()
|
||||||
.Confirm(strs.warn_punish_set(Format.Bold(punish.ToString()),
|
.Confirm(strs.warn_punish_set(Format.Bold(punish.ToString()),
|
||||||
@@ -341,7 +341,7 @@ public partial class Administration
|
|||||||
await Response()
|
await Response()
|
||||||
.Confirm(strs.warn_punish_set_timed(Format.Bold(punish.ToString()),
|
.Confirm(strs.warn_punish_set_timed(Format.Bold(punish.ToString()),
|
||||||
Format.Bold(number.ToString()),
|
Format.Bold(number.ToString()),
|
||||||
Format.Bold(time.Input)))
|
Format.Bold(timespan.Input)))
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -349,7 +349,7 @@ public partial class Administration
|
|||||||
[Cmd]
|
[Cmd]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.BanMembers)]
|
[UserPerm(GuildPerm.BanMembers)]
|
||||||
public async Task WarnPunish(int number, PunishmentAction punish, StoopidTime time = null)
|
public async Task WarnPunish(int number, PunishmentAction punish, ParsedTimespan timespan = null)
|
||||||
{
|
{
|
||||||
// this should never happen. Addrole has its own method with higher priority
|
// this should never happen. Addrole has its own method with higher priority
|
||||||
// also disallow warn punishment for getting warned
|
// also disallow warn punishment for getting warned
|
||||||
@@ -357,15 +357,15 @@ public partial class Administration
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// you must specify the time for timeout
|
// you must specify the time for timeout
|
||||||
if (punish is PunishmentAction.TimeOut && time is null)
|
if (punish is PunishmentAction.TimeOut && timespan is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var success = await _service.WarnPunish(ctx.Guild.Id, number, punish, time);
|
var success = await _service.WarnPunish(ctx.Guild.Id, number, punish, timespan);
|
||||||
|
|
||||||
if (!success)
|
if (!success)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (time is null)
|
if (timespan is null)
|
||||||
{
|
{
|
||||||
await Response()
|
await Response()
|
||||||
.Confirm(strs.warn_punish_set(Format.Bold(punish.ToString()),
|
.Confirm(strs.warn_punish_set(Format.Bold(punish.ToString()),
|
||||||
@@ -377,7 +377,7 @@ public partial class Administration
|
|||||||
await Response()
|
await Response()
|
||||||
.Confirm(strs.warn_punish_set_timed(Format.Bold(punish.ToString()),
|
.Confirm(strs.warn_punish_set_timed(Format.Bold(punish.ToString()),
|
||||||
Format.Bold(number.ToString()),
|
Format.Bold(number.ToString()),
|
||||||
Format.Bold(time.Input)))
|
Format.Bold(timespan.Input)))
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -417,17 +417,17 @@ public partial class Administration
|
|||||||
[UserPerm(GuildPerm.BanMembers)]
|
[UserPerm(GuildPerm.BanMembers)]
|
||||||
[BotPerm(GuildPerm.BanMembers)]
|
[BotPerm(GuildPerm.BanMembers)]
|
||||||
[Priority(1)]
|
[Priority(1)]
|
||||||
public Task Ban(StoopidTime time, IUser user, [Leftover] string msg = null)
|
public Task Ban(ParsedTimespan timespan, IUser user, [Leftover] string msg = null)
|
||||||
=> Ban(time, user.Id, msg);
|
=> Ban(timespan, user.Id, msg);
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.BanMembers)]
|
[UserPerm(GuildPerm.BanMembers)]
|
||||||
[BotPerm(GuildPerm.BanMembers)]
|
[BotPerm(GuildPerm.BanMembers)]
|
||||||
[Priority(0)]
|
[Priority(0)]
|
||||||
public async Task Ban(StoopidTime time, ulong userId, [Leftover] string msg = null)
|
public async Task Ban(ParsedTimespan timespan, ulong userId, [Leftover] string msg = null)
|
||||||
{
|
{
|
||||||
if (time.Time > TimeSpan.FromDays(49))
|
if (timespan.Time > TimeSpan.FromDays(49))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var guildUser = await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(ctx.Guild.Id, userId);
|
var guildUser = await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(ctx.Guild.Id, userId);
|
||||||
@@ -444,7 +444,7 @@ public partial class Administration
|
|||||||
{
|
{
|
||||||
var defaultMessage = GetText(strs.bandm(Format.Bold(ctx.Guild.Name), msg));
|
var defaultMessage = GetText(strs.bandm(Format.Bold(ctx.Guild.Name), msg));
|
||||||
var smartText =
|
var smartText =
|
||||||
await _service.GetBanUserDmEmbed(Context, guildUser, defaultMessage, msg, time.Time);
|
await _service.GetBanUserDmEmbed(Context, guildUser, defaultMessage, msg, timespan.Time);
|
||||||
if (smartText is not null)
|
if (smartText is not null)
|
||||||
await Response().User(guildUser).Text(smartText).SendAsync();
|
await Response().User(guildUser).Text(smartText).SendAsync();
|
||||||
}
|
}
|
||||||
@@ -456,14 +456,14 @@ public partial class Administration
|
|||||||
|
|
||||||
var user = await ctx.Client.GetUserAsync(userId);
|
var user = await ctx.Client.GetUserAsync(userId);
|
||||||
var banPrune = await _service.GetBanPruneAsync(ctx.Guild.Id) ?? 7;
|
var banPrune = await _service.GetBanPruneAsync(ctx.Guild.Id) ?? 7;
|
||||||
await _mute.TimedBan(ctx.Guild, userId, time.Time, (ctx.User + " | " + msg).TrimTo(512), banPrune);
|
await _mute.TimedBan(ctx.Guild, userId, timespan.Time, (ctx.User + " | " + msg).TrimTo(512), banPrune);
|
||||||
var toSend = CreateEmbed()
|
var toSend = CreateEmbed()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithTitle("⛔️ " + GetText(strs.banned_user))
|
.WithTitle("⛔️ " + GetText(strs.banned_user))
|
||||||
.AddField(GetText(strs.username), user?.ToString() ?? userId.ToString(), true)
|
.AddField(GetText(strs.username), user?.ToString() ?? userId.ToString(), true)
|
||||||
.AddField("ID", userId.ToString(), true)
|
.AddField("ID", userId.ToString(), true)
|
||||||
.AddField(GetText(strs.duration),
|
.AddField(GetText(strs.duration),
|
||||||
time.Time.ToPrettyStringHm(),
|
timespan.Time.ToPrettyStringHm(),
|
||||||
true);
|
true);
|
||||||
|
|
||||||
if (dmFailed)
|
if (dmFailed)
|
||||||
@@ -601,7 +601,7 @@ public partial class Administration
|
|||||||
[UserPerm(GuildPerm.BanMembers)]
|
[UserPerm(GuildPerm.BanMembers)]
|
||||||
[BotPerm(GuildPerm.BanMembers)]
|
[BotPerm(GuildPerm.BanMembers)]
|
||||||
[Priority(1)]
|
[Priority(1)]
|
||||||
public Task BanMessageTest(StoopidTime duration, [Leftover] string reason = null)
|
public Task BanMessageTest(ParsedTimespan duration, [Leftover] string reason = null)
|
||||||
=> InternalBanMessageTest(reason, duration.Time);
|
=> InternalBanMessageTest(reason, duration.Time);
|
||||||
|
|
||||||
private async Task InternalBanMessageTest(string reason, TimeSpan? duration)
|
private async Task InternalBanMessageTest(string reason, TimeSpan? duration)
|
||||||
@@ -790,7 +790,7 @@ public partial class Administration
|
|||||||
[UserPerm(GuildPerm.ModerateMembers)]
|
[UserPerm(GuildPerm.ModerateMembers)]
|
||||||
[BotPerm(GuildPerm.ModerateMembers)]
|
[BotPerm(GuildPerm.ModerateMembers)]
|
||||||
[Priority(2)]
|
[Priority(2)]
|
||||||
public async Task Timeout(IUser globalUser, StoopidTime time, [Leftover] string msg = null)
|
public async Task Timeout(IUser globalUser, ParsedTimespan timespan, [Leftover] string msg = null)
|
||||||
{
|
{
|
||||||
var user = await ctx.Guild.GetUserAsync(globalUser.Id);
|
var user = await ctx.Guild.GetUserAsync(globalUser.Id);
|
||||||
|
|
||||||
@@ -816,7 +816,7 @@ public partial class Administration
|
|||||||
dmFailed = true;
|
dmFailed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
await user.SetTimeOutAsync(time.Time);
|
await user.SetTimeOutAsync(timespan.Time);
|
||||||
|
|
||||||
var toSend = CreateEmbed()
|
var toSend = CreateEmbed()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using NadekoBot.Modules.Gambling.Common;
|
using NadekoBot.Modules.Gambling.Common;
|
||||||
using NadekoBot.Modules.Gambling.Services;
|
using NadekoBot.Modules.Gambling.Services;
|
||||||
|
using NadekoBot.Modules.Xp.Services;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Gambling;
|
namespace NadekoBot.Modules.Gambling;
|
||||||
|
|
||||||
@@ -10,13 +11,19 @@ public partial class Gambling
|
|||||||
public sealed class BetStatsCommands : GamblingModule<UserBetStatsService>
|
public sealed class BetStatsCommands : GamblingModule<UserBetStatsService>
|
||||||
{
|
{
|
||||||
private readonly GamblingTxTracker _gamblingTxTracker;
|
private readonly GamblingTxTracker _gamblingTxTracker;
|
||||||
|
private readonly IBotCache _cache;
|
||||||
|
private readonly IUserService _userService;
|
||||||
|
|
||||||
public BetStatsCommands(
|
public BetStatsCommands(
|
||||||
GamblingTxTracker gamblingTxTracker,
|
GamblingTxTracker gamblingTxTracker,
|
||||||
GamblingConfigService gcs)
|
GamblingConfigService gcs,
|
||||||
|
IBotCache cache,
|
||||||
|
IUserService userService)
|
||||||
: base(gcs)
|
: base(gcs)
|
||||||
{
|
{
|
||||||
_gamblingTxTracker = gamblingTxTracker;
|
_gamblingTxTracker = gamblingTxTracker;
|
||||||
|
_cache = cache;
|
||||||
|
_userService = userService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
@@ -25,12 +32,12 @@ public partial class Gambling
|
|||||||
var price = await _service.GetResetStatsPriceAsync(ctx.User.Id, game);
|
var price = await _service.GetResetStatsPriceAsync(ctx.User.Id, game);
|
||||||
|
|
||||||
var result = await PromptUserConfirmAsync(CreateEmbed()
|
var result = await PromptUserConfirmAsync(CreateEmbed()
|
||||||
.WithDescription(
|
.WithDescription(
|
||||||
$"""
|
$"""
|
||||||
Are you sure you want to reset your bet stats for **{GetGameName(game)}**?
|
Are you sure you want to reset your bet stats for **{GetGameName(game)}**?
|
||||||
|
|
||||||
It will cost you {N(price)}
|
It will cost you {N(price)}
|
||||||
"""));
|
"""));
|
||||||
|
|
||||||
if (!result)
|
if (!result)
|
||||||
return;
|
return;
|
||||||
@@ -88,15 +95,15 @@ public partial class Gambling
|
|||||||
};
|
};
|
||||||
|
|
||||||
var eb = CreateEmbed()
|
var eb = CreateEmbed()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithAuthor(user)
|
.WithAuthor(user)
|
||||||
.AddField("Total Won", N(stats.Sum(x => x.PaidOut)), true)
|
.AddField("Total Won", N(stats.Sum(x => x.PaidOut)), true)
|
||||||
.AddField("Biggest Win", N(stats.Max(x => x.MaxWin)), true)
|
.AddField("Biggest Win", N(stats.Max(x => x.MaxWin)), true)
|
||||||
.AddField("Biggest Bet", N(stats.Max(x => x.MaxBet)), true)
|
.AddField("Biggest Bet", N(stats.Max(x => x.MaxBet)), true)
|
||||||
.AddField("# Bets", stats.Sum(x => x.WinCount + x.LoseCount), true)
|
.AddField("# Bets", stats.Sum(x => x.WinCount + x.LoseCount), true)
|
||||||
.AddField("Payout",
|
.AddField("Payout",
|
||||||
(stats.Sum(x => x.PaidOut) / stats.Sum(x => x.TotalBet)).ToString("P2", Culture),
|
(stats.Sum(x => x.PaidOut) / stats.Sum(x => x.TotalBet)).ToString("P2", Culture),
|
||||||
true);
|
true);
|
||||||
if (game == null)
|
if (game == null)
|
||||||
{
|
{
|
||||||
var favGame = stats.MaxBy(x => x.WinCount + x.LoseCount);
|
var favGame = stats.MaxBy(x => x.WinCount + x.LoseCount);
|
||||||
@@ -115,13 +122,86 @@ public partial class Gambling
|
|||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly record struct WinLbStat(
|
||||||
|
int Rank,
|
||||||
|
string User,
|
||||||
|
GamblingGame Game,
|
||||||
|
long MaxWin);
|
||||||
|
|
||||||
|
private TypedKey<List<WinLbStat>> GetWinLbKey(int page)
|
||||||
|
=> new($"winlb:{page}");
|
||||||
|
|
||||||
|
private async Task<IReadOnlyCollection<WinLbStat>> GetCachedWinLbAsync(int page)
|
||||||
|
{
|
||||||
|
return await _cache.GetOrAddAsync(GetWinLbKey(page),
|
||||||
|
async () =>
|
||||||
|
{
|
||||||
|
var items = await _service.GetWinLbAsync(page);
|
||||||
|
|
||||||
|
if (items.Count == 0)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
var outputItems = new List<WinLbStat>(items.Count);
|
||||||
|
for (var i = 0; i < items.Count; i++)
|
||||||
|
{
|
||||||
|
var x = items[i];
|
||||||
|
var user = (await ctx.Client.GetUserAsync(x.UserId, CacheMode.CacheOnly))?.ToString()
|
||||||
|
?? (await _userService.GetUserAsync(x.UserId))?.Username
|
||||||
|
?? x.UserId.ToString();
|
||||||
|
|
||||||
|
if (user.StartsWith("??"))
|
||||||
|
user = x.UserId.ToString();
|
||||||
|
|
||||||
|
outputItems.Add(new WinLbStat(i + 1 + (page * 9), user, x.Game, x.MaxWin));
|
||||||
|
}
|
||||||
|
|
||||||
|
return outputItems;
|
||||||
|
},
|
||||||
|
expiry: TimeSpan.FromMinutes(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public async Task WinLb(int page = 1)
|
||||||
|
{
|
||||||
|
if (--page < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await Response()
|
||||||
|
.Paginated()
|
||||||
|
.PageItems(p => GetCachedWinLbAsync(p))
|
||||||
|
.PageSize(9)
|
||||||
|
.Page((items, curPage) =>
|
||||||
|
{
|
||||||
|
var eb = CreateEmbed()
|
||||||
|
.WithTitle(GetText(strs.winlb))
|
||||||
|
.WithOkColor();
|
||||||
|
|
||||||
|
if (items.Count == 0)
|
||||||
|
{
|
||||||
|
eb.WithDescription(GetText(strs.empty_page));
|
||||||
|
return eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < items.Count; i++)
|
||||||
|
{
|
||||||
|
var item = items[i];
|
||||||
|
eb.AddField($"#{item.Rank} {item.User}",
|
||||||
|
$"{N(item.MaxWin)}\n`{item.Game.ToString().ToLower()}`",
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return eb;
|
||||||
|
})
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
public async Task GambleStats()
|
public async Task GambleStats()
|
||||||
{
|
{
|
||||||
var stats = await _gamblingTxTracker.GetAllAsync();
|
var stats = await _gamblingTxTracker.GetAllAsync();
|
||||||
|
|
||||||
var eb = CreateEmbed()
|
var eb = CreateEmbed()
|
||||||
.WithOkColor();
|
.WithOkColor();
|
||||||
|
|
||||||
var str = "` Feature `|` Bet `|`Paid Out`|` RoI `\n";
|
var str = "` Feature `|` Bet `|`Paid Out`|` RoI `\n";
|
||||||
str += "――――――――――――――――――――\n";
|
str += "――――――――――――――――――――\n";
|
||||||
@@ -157,13 +237,13 @@ public partial class Gambling
|
|||||||
public async Task GambleStatsReset()
|
public async Task GambleStatsReset()
|
||||||
{
|
{
|
||||||
if (!await PromptUserConfirmAsync(CreateEmbed()
|
if (!await PromptUserConfirmAsync(CreateEmbed()
|
||||||
.WithDescription(
|
.WithDescription(
|
||||||
"""
|
"""
|
||||||
Are you sure?
|
Are you sure?
|
||||||
This will completely reset Gambling Stats.
|
This will completely reset Gambling Stats.
|
||||||
|
|
||||||
This action is irreversible.
|
This action is irreversible.
|
||||||
""")))
|
""")))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await GambleStats();
|
await GambleStats();
|
||||||
|
@@ -135,7 +135,6 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
});
|
});
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[RequireContext(ContextType.Guild)]
|
|
||||||
public async Task Timely()
|
public async Task Timely()
|
||||||
{
|
{
|
||||||
var val = Config.Timely.Amount;
|
var val = Config.Timely.Amount;
|
||||||
@@ -390,7 +389,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
[Priority(0)]
|
[Priority(0)]
|
||||||
public Task CurrencyTransactions([Leftover] IUser usr)
|
public Task CurrencyTransactions([Leftover] IUser usr)
|
||||||
=> InternalCurrencyTransactions(usr.Id, 1);
|
=> InternalCurrencyTransactions(usr.Id, 1);
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[OwnerOnly]
|
[OwnerOnly]
|
||||||
[Priority(-1)]
|
[Priority(-1)]
|
||||||
@@ -872,9 +871,6 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
}
|
}
|
||||||
else if (result.Result == RpsResultType.Win)
|
else if (result.Result == RpsResultType.Win)
|
||||||
{
|
{
|
||||||
if ((long)result.Won > 0)
|
|
||||||
embed.AddField(GetText(strs.won), N((long)result.Won));
|
|
||||||
|
|
||||||
msg = GetText(strs.rps_win(ctx.User.Mention,
|
msg = GetText(strs.rps_win(ctx.User.Mention,
|
||||||
GetRpsPick(pick),
|
GetRpsPick(pick),
|
||||||
GetRpsPick((InputRpsPick)result.ComputerPick)));
|
GetRpsPick((InputRpsPick)result.ComputerPick)));
|
||||||
@@ -890,6 +886,13 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithDescription(msg);
|
.WithDescription(msg);
|
||||||
|
|
||||||
|
if (amount > 0)
|
||||||
|
{
|
||||||
|
embed
|
||||||
|
.AddField(GetText(strs.bet), N(amount), true)
|
||||||
|
.AddField(GetText(strs.won), $"{N((long)result.Won)}", true);
|
||||||
|
}
|
||||||
|
|
||||||
await Response().Embed(embed).SendAsync();
|
await Response().Embed(embed).SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -42,7 +42,7 @@ public sealed class UserBetStatsService : INService
|
|||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
await ctx.GetTable<UserBetStats>()
|
await ctx.GetTable<UserBetStats>()
|
||||||
.DeleteAsync(x => x.UserId == userId && (game == null || x.Game == game));
|
.DeleteAsync(x => x.UserId == userId && (game == null || x.Game == game));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,4 +52,16 @@ public sealed class UserBetStatsService : INService
|
|||||||
await ctx.GetTable<GamblingStats>()
|
await ctx.GetTable<GamblingStats>()
|
||||||
.DeleteAsync();
|
.DeleteAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<UserBetStats>> GetWinLbAsync(int page)
|
||||||
|
{
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(page);
|
||||||
|
|
||||||
|
await using var ctx = _db.GetDbContext();
|
||||||
|
return await ctx.GetTable<UserBetStats>()
|
||||||
|
.OrderByDescending(x => x.MaxWin)
|
||||||
|
.Skip(page * 9)
|
||||||
|
.Take(9)
|
||||||
|
.ToArrayAsyncLinqToDB();
|
||||||
|
}
|
||||||
}
|
}
|
@@ -1,5 +1,6 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using NadekoBot.Modules.Games.Services;
|
using NadekoBot.Modules.Games.Services;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Games;
|
namespace NadekoBot.Modules.Games;
|
||||||
|
|
||||||
@@ -38,10 +39,72 @@ public partial class Games : NadekoModule<GamesService>
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var res = _service.GetEightballResponse(ctx.User.Id, question);
|
var res = _service.GetEightballResponse(ctx.User.Id, question);
|
||||||
await Response().Embed(CreateEmbed()
|
await Response()
|
||||||
.WithOkColor()
|
.Embed(CreateEmbed()
|
||||||
.WithDescription(ctx.User.ToString())
|
.WithOkColor()
|
||||||
.AddField("❓ " + GetText(strs.question), question)
|
.WithDescription(ctx.User.ToString())
|
||||||
.AddField("🎱 " + GetText(strs._8ball), res)).SendAsync();
|
.AddField("❓ " + GetText(strs.question), question)
|
||||||
|
.AddField("🎱 " + GetText(strs._8ball), res))
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly string[] _numberEmojis = ["0️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣"];
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public async Task Minesweeper(int numberOfMines = 12)
|
||||||
|
{
|
||||||
|
var boardSizeX = 9;
|
||||||
|
var boardSizeY = 10;
|
||||||
|
|
||||||
|
if (numberOfMines < 1)
|
||||||
|
{
|
||||||
|
numberOfMines = 1;
|
||||||
|
}
|
||||||
|
else if (numberOfMines > boardSizeX * boardSizeY / 2)
|
||||||
|
{
|
||||||
|
numberOfMines = boardSizeX * boardSizeY / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
var mineIndicies = Enumerable.Range(0, boardSizeX * boardSizeY)
|
||||||
|
.ToArray()
|
||||||
|
.Shuffle()
|
||||||
|
.Take(numberOfMines)
|
||||||
|
.ToHashSet();
|
||||||
|
|
||||||
|
string GetNumberOnCell(int x, int y)
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
for (var i = -1; i < 2; i++)
|
||||||
|
{
|
||||||
|
for (var j = -1; j < 2; j++)
|
||||||
|
{
|
||||||
|
if (y + j >= boardSizeY || y + j < 0)
|
||||||
|
continue;
|
||||||
|
if (x + i >= boardSizeX || x + i < 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var boardIndex = (y + j) * boardSizeX + (x + i);
|
||||||
|
if (mineIndicies.Contains(boardIndex))
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _numberEmojis[count];
|
||||||
|
}
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine($"### Minesweeper [{numberOfMines}\\💣]");
|
||||||
|
for (var i = 0; i < boardSizeY; i++)
|
||||||
|
{
|
||||||
|
for (var j = 0; j < boardSizeX; j++)
|
||||||
|
{
|
||||||
|
var emoji = mineIndicies.Contains((i * boardSizeX) + j) ? "💣" : GetNumberOnCell(j, i);
|
||||||
|
sb.Append($"||{emoji}||");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
await Response().Text(sb.ToString()).SendAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -65,7 +65,17 @@ public sealed class MusicPlayer : IMusicPlayer
|
|||||||
|
|
||||||
_songBuffer = new PoopyBufferImmortalized(_vc.InputLength);
|
_songBuffer = new PoopyBufferImmortalized(_vc.InputLength);
|
||||||
|
|
||||||
_thread = new(async () => { await PlayLoop(); });
|
_thread = new(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await PlayLoop();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Music player thread crashed");
|
||||||
|
}
|
||||||
|
});
|
||||||
_thread.Start();
|
_thread.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,12 +412,24 @@ public sealed class MusicPlayer : IMusicPlayer
|
|||||||
if (song is null)
|
if (song is null)
|
||||||
return default;
|
return default;
|
||||||
|
|
||||||
int index;
|
|
||||||
|
|
||||||
if (asNext)
|
var wasLast = _queue.IsLast();
|
||||||
return (_queue.EnqueueNext(song, queuer, out index), index);
|
|
||||||
|
|
||||||
return (_queue.Enqueue(song, queuer, out index), index);
|
try
|
||||||
|
{
|
||||||
|
int index;
|
||||||
|
if (asNext)
|
||||||
|
return (_queue.EnqueueNext(song, queuer, out index), index);
|
||||||
|
|
||||||
|
return (_queue.Enqueue(song, queuer, out index), index);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// if (wasLast && IsStopped)
|
||||||
|
// {
|
||||||
|
// IsStopped = false;
|
||||||
|
// }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task EnqueueManyAsync(IEnumerable<(string Query, MusicPlatform Platform)> queries, string queuer)
|
public async Task EnqueueManyAsync(IEnumerable<(string Query, MusicPlatform Platform)> queries, string queuer)
|
||||||
|
@@ -73,5 +73,38 @@ public partial class Permissions
|
|||||||
|
|
||||||
await Response().Confirm(strs.gcmd_remove(Format.Bold(cmd.Name))).SendAsync();
|
await Response().Confirm(strs.gcmd_remove(Format.Bold(cmd.Name))).SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[OwnerOnly]
|
||||||
|
public async Task DmModule(ModuleOrExpr module)
|
||||||
|
{
|
||||||
|
var moduleName = module.Name.ToLowerInvariant();
|
||||||
|
|
||||||
|
var added = _service.ToggleModule(moduleName, true);
|
||||||
|
|
||||||
|
if (added)
|
||||||
|
{
|
||||||
|
await Response().Confirm(strs.dmmod_add(Format.Bold(module.Name))).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Response().Confirm(strs.dmmod_remove(Format.Bold(module.Name))).SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[OwnerOnly]
|
||||||
|
public async Task DmCommand(CommandOrExprInfo cmd)
|
||||||
|
{
|
||||||
|
var commandName = cmd.Name.ToLowerInvariant();
|
||||||
|
var added = _service.ToggleCommand(commandName, true);
|
||||||
|
|
||||||
|
if (added)
|
||||||
|
{
|
||||||
|
await Response().Confirm(strs.dmcmd_add(Format.Bold(cmd.Name))).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Response().Confirm(strs.dmcmd_remove(Format.Bold(cmd.Name))).SendAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -24,10 +24,19 @@ public class GlobalPermissionService : IExecPreCommand, INService
|
|||||||
var settings = _bss.Data;
|
var settings = _bss.Data;
|
||||||
var commandName = command.Name.ToLowerInvariant();
|
var commandName = command.Name.ToLowerInvariant();
|
||||||
|
|
||||||
if (commandName != "resetglobalperms"
|
if (commandName != "resetglobalperms")
|
||||||
&& (settings.Blocked.Commands.Contains(commandName)
|
{
|
||||||
|| settings.Blocked.Modules.Contains(moduleName.ToLowerInvariant())))
|
if (settings.Blocked.Commands.Contains(commandName)
|
||||||
return Task.FromResult(true);
|
|| settings.Blocked.Modules.Contains(moduleName.ToLowerInvariant()))
|
||||||
|
return Task.FromResult(true);
|
||||||
|
|
||||||
|
if (ctx.Guild is null)
|
||||||
|
{
|
||||||
|
if (settings.DmBlocked.Commands.Contains(commandName)
|
||||||
|
|| settings.DmBlocked.Modules.Contains(moduleName.ToLowerInvariant()))
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Task.FromResult(false);
|
return Task.FromResult(false);
|
||||||
}
|
}
|
||||||
@@ -37,13 +46,30 @@ public class GlobalPermissionService : IExecPreCommand, INService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="moduleName">Lowercase module name</param>
|
/// <param name="moduleName">Lowercase module name</param>
|
||||||
/// <returns>Whether the module is added</returns>
|
/// <returns>Whether the module is added</returns>
|
||||||
public bool ToggleModule(string moduleName)
|
public bool ToggleModule(string moduleName, bool priv = false)
|
||||||
{
|
{
|
||||||
var added = false;
|
var added = false;
|
||||||
_bss.ModifyConfig(bs =>
|
_bss.ModifyConfig(bs =>
|
||||||
{
|
{
|
||||||
|
if (priv)
|
||||||
|
{
|
||||||
|
if (bs.DmBlocked.Modules.Add(moduleName))
|
||||||
|
{
|
||||||
|
added = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bs.DmBlocked.Modules.Remove(moduleName);
|
||||||
|
added = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (bs.Blocked.Modules.Add(moduleName))
|
if (bs.Blocked.Modules.Add(moduleName))
|
||||||
|
{
|
||||||
added = true;
|
added = true;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
bs.Blocked.Modules.Remove(moduleName);
|
bs.Blocked.Modules.Remove(moduleName);
|
||||||
@@ -59,13 +85,30 @@ public class GlobalPermissionService : IExecPreCommand, INService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="commandName">Lowercase command name</param>
|
/// <param name="commandName">Lowercase command name</param>
|
||||||
/// <returns>Whether the command is added</returns>
|
/// <returns>Whether the command is added</returns>
|
||||||
public bool ToggleCommand(string commandName)
|
public bool ToggleCommand(string commandName, bool priv = false)
|
||||||
{
|
{
|
||||||
var added = false;
|
var added = false;
|
||||||
_bss.ModifyConfig(bs =>
|
_bss.ModifyConfig(bs =>
|
||||||
{
|
{
|
||||||
|
if (priv)
|
||||||
|
{
|
||||||
|
if (bs.DmBlocked.Commands.Add(commandName))
|
||||||
|
{
|
||||||
|
added = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bs.DmBlocked.Commands.Remove(commandName);
|
||||||
|
added = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (bs.Blocked.Commands.Add(commandName))
|
if (bs.Blocked.Commands.Add(commandName))
|
||||||
|
{
|
||||||
added = true;
|
added = true;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
bs.Blocked.Commands.Remove(commandName);
|
bs.Blocked.Commands.Remove(commandName);
|
||||||
|
@@ -103,11 +103,11 @@ public partial class Searches : NadekoModule<SearchesService>
|
|||||||
}
|
}
|
||||||
|
|
||||||
var eb = CreateEmbed()
|
var eb = CreateEmbed()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithTitle(GetText(strs.time_new))
|
.WithTitle(GetText(strs.time_new))
|
||||||
.WithDescription(Format.Code(data.Time.ToString(Culture)))
|
.WithDescription(Format.Code(data.Time.ToString(Culture)))
|
||||||
.AddField(GetText(strs.location), string.Join('\n', data.Address.Split(", ")), true)
|
.AddField(GetText(strs.location), string.Join('\n', data.Address.Split(", ")), true)
|
||||||
.AddField(GetText(strs.timezone), data.TimeZoneName, true);
|
.AddField(GetText(strs.timezone), data.TimeZoneName, true);
|
||||||
|
|
||||||
await Response().Embed(eb).SendAsync();
|
await Response().Embed(eb).SendAsync();
|
||||||
}
|
}
|
||||||
@@ -129,16 +129,16 @@ public partial class Searches : NadekoModule<SearchesService>
|
|||||||
|
|
||||||
await Response()
|
await Response()
|
||||||
.Embed(CreateEmbed()
|
.Embed(CreateEmbed()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithTitle(movie.Title)
|
.WithTitle(movie.Title)
|
||||||
.WithUrl($"https://www.imdb.com/title/{movie.ImdbId}/")
|
.WithUrl($"https://www.imdb.com/title/{movie.ImdbId}/")
|
||||||
.WithDescription(movie.Plot.TrimTo(1000))
|
.WithDescription(movie.Plot.TrimTo(1000))
|
||||||
.AddField("Rating", movie.ImdbRating, true)
|
.AddField("Rating", movie.ImdbRating, true)
|
||||||
.AddField("Genre", movie.Genre, true)
|
.AddField("Genre", movie.Genre, true)
|
||||||
.AddField("Year", movie.Year, true)
|
.AddField("Year", movie.Year, true)
|
||||||
.WithImageUrl(Uri.IsWellFormedUriString(movie.Poster, UriKind.Absolute)
|
.WithImageUrl(Uri.IsWellFormedUriString(movie.Poster, UriKind.Absolute)
|
||||||
? movie.Poster
|
? movie.Poster
|
||||||
: null))
|
: null))
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,9 +191,9 @@ public partial class Searches : NadekoModule<SearchesService>
|
|||||||
|
|
||||||
await Response()
|
await Response()
|
||||||
.Embed(CreateEmbed()
|
.Embed(CreateEmbed()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.AddField(GetText(strs.original_url), $"<{query}>")
|
.AddField(GetText(strs.original_url), $"<{query}>")
|
||||||
.AddField(GetText(strs.short_url), $"<{shortLink}>"))
|
.AddField(GetText(strs.short_url), $"<{shortLink}>"))
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,13 +214,13 @@ public partial class Searches : NadekoModule<SearchesService>
|
|||||||
}
|
}
|
||||||
|
|
||||||
var embed = CreateEmbed()
|
var embed = CreateEmbed()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithTitle(card.Name)
|
.WithTitle(card.Name)
|
||||||
.WithDescription(card.Description)
|
.WithDescription(card.Description)
|
||||||
.WithImageUrl(card.ImageUrl)
|
.WithImageUrl(card.ImageUrl)
|
||||||
.AddField(GetText(strs.store_url), card.StoreUrl, true)
|
.AddField(GetText(strs.store_url), card.StoreUrl, true)
|
||||||
.AddField(GetText(strs.cost), card.ManaCost, true)
|
.AddField(GetText(strs.cost), card.ManaCost, true)
|
||||||
.AddField(GetText(strs.types), card.Types, true);
|
.AddField(GetText(strs.types), card.Types, true);
|
||||||
|
|
||||||
await Response().Embed(embed).SendAsync();
|
await Response().Embed(embed).SendAsync();
|
||||||
}
|
}
|
||||||
@@ -281,10 +281,10 @@ public partial class Searches : NadekoModule<SearchesService>
|
|||||||
{
|
{
|
||||||
var item = items[0];
|
var item = items[0];
|
||||||
return CreateEmbed()
|
return CreateEmbed()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithUrl(item.Permalink)
|
.WithUrl(item.Permalink)
|
||||||
.WithTitle(item.Word)
|
.WithTitle(item.Word)
|
||||||
.WithDescription(item.Definition);
|
.WithDescription(item.Definition);
|
||||||
})
|
})
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
@@ -312,11 +312,11 @@ public partial class Searches : NadekoModule<SearchesService>
|
|||||||
{
|
{
|
||||||
var model = items.First();
|
var model = items.First();
|
||||||
var embed = CreateEmbed()
|
var embed = CreateEmbed()
|
||||||
.WithDescription(ctx.User.Mention)
|
.WithDescription(ctx.User.Mention)
|
||||||
.AddField(GetText(strs.word), model.Word, true)
|
.AddField(GetText(strs.word), model.Word, true)
|
||||||
.AddField(GetText(strs._class), model.WordType, true)
|
.AddField(GetText(strs._class), model.WordType, true)
|
||||||
.AddField(GetText(strs.definition), model.Definition)
|
.AddField(GetText(strs.definition), model.Definition)
|
||||||
.WithOkColor();
|
.WithOkColor();
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(model.Example))
|
if (!string.IsNullOrWhiteSpace(model.Example))
|
||||||
embed.AddField(GetText(strs.example), model.Example);
|
embed.AddField(GetText(strs.example), model.Example);
|
||||||
@@ -404,10 +404,38 @@ public partial class Searches : NadekoModule<SearchesService>
|
|||||||
await Response()
|
await Response()
|
||||||
.Embed(
|
.Embed(
|
||||||
CreateEmbed()
|
CreateEmbed()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.AddField("Username", usr.ToString())
|
.AddField("Username", usr.ToString())
|
||||||
.AddField("Avatar Url", avatarUrl)
|
.AddField("Avatar Url", avatarUrl)
|
||||||
.WithThumbnailUrl(avatarUrl.ToString()))
|
.WithThumbnailUrl(avatarUrl.ToString()))
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
public async Task Banner([Leftover] IGuildUser? usr = null)
|
||||||
|
{
|
||||||
|
usr ??= (IGuildUser)ctx.User;
|
||||||
|
|
||||||
|
var bannerUrl = usr.GetGuildBannerUrl(size: 2048)
|
||||||
|
?? (await ((DiscordSocketClient)ctx.Client).Rest.GetUserAsync(usr.Id))?.GetBannerUrl();
|
||||||
|
|
||||||
|
if (bannerUrl is null)
|
||||||
|
{
|
||||||
|
await Response()
|
||||||
|
.Error(strs.no_banner)
|
||||||
|
.SendAsync();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Response()
|
||||||
|
.Embed(
|
||||||
|
CreateEmbed()
|
||||||
|
.WithOkColor()
|
||||||
|
.AddField("Username", usr.ToString(), true)
|
||||||
|
.AddField("Banner Url", bannerUrl, true)
|
||||||
|
.WithImageUrl(bannerUrl))
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -122,12 +122,15 @@ public sealed partial class FlagTranslateService : IReadyExecutor, INService
|
|||||||
if (!_supportedFlags.TryGetValue(code, out var lang))
|
if (!_supportedFlags.TryGetValue(code, out var lang))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!_msgLangs.Add((reaction.MessageId, lang)))
|
if (_msgLangs.Contains((reaction.MessageId, lang)))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var result = await _cache.GetAsync(CdKey(reaction.UserId));
|
var result = await _cache.GetAsync(CdKey(reaction.UserId));
|
||||||
if (result.TryPickT0(out _, out _))
|
if (result.TryPickT0(out _, out _))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (!_msgLangs.Add((reaction.MessageId, lang)))
|
||||||
|
return;
|
||||||
|
|
||||||
await _cache.AddAsync(CdKey(reaction.UserId), true, TimeSpan.FromSeconds(5));
|
await _cache.AddAsync(CdKey(reaction.UserId), true, TimeSpan.FromSeconds(5));
|
||||||
|
|
||||||
|
@@ -28,9 +28,19 @@ public partial class Searches
|
|||||||
await ctx.Channel.TriggerTypingAsync();
|
await ctx.Channel.TriggerTypingAsync();
|
||||||
var translation = await _service.Translate(fromLang, toLang, text);
|
var translation = await _service.Translate(fromLang, toLang, text);
|
||||||
|
|
||||||
var embed = CreateEmbed().WithOkColor().AddField(fromLang, text).AddField(toLang, translation);
|
var embed = CreateEmbed()
|
||||||
|
.WithOkColor()
|
||||||
|
.WithTitle(fromLang)
|
||||||
|
.WithDescription(text);
|
||||||
|
|
||||||
await Response().Embed(embed).SendAsync();
|
var embed2 = CreateEmbed()
|
||||||
|
.WithOkColor()
|
||||||
|
.WithTitle(toLang)
|
||||||
|
.WithDescription(translation);
|
||||||
|
|
||||||
|
await Response()
|
||||||
|
.Embeds([embed, embed2])
|
||||||
|
.SendAsync();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -65,7 +75,10 @@ public partial class Searches
|
|||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
public async Task AutoTransLang(string fromLang, string toLang)
|
public async Task AutoTransLang(string fromLang, string toLang)
|
||||||
{
|
{
|
||||||
var succ = await _service.RegisterUserAsync(ctx.User.Id, ctx.Channel.Id, fromLang.ToLower(), toLang.ToLower());
|
var succ = await _service.RegisterUserAsync(ctx.User.Id,
|
||||||
|
ctx.Channel.Id,
|
||||||
|
fromLang.ToLower(),
|
||||||
|
toLang.ToLower());
|
||||||
|
|
||||||
if (succ is null)
|
if (succ is null)
|
||||||
{
|
{
|
||||||
@@ -87,10 +100,10 @@ public partial class Searches
|
|||||||
public async Task Translangs()
|
public async Task Translangs()
|
||||||
{
|
{
|
||||||
var langs = _service.GetLanguages().ToList();
|
var langs = _service.GetLanguages().ToList();
|
||||||
|
|
||||||
var eb = CreateEmbed()
|
var eb = CreateEmbed()
|
||||||
.WithTitle(GetText(strs.supported_languages))
|
.WithTitle(GetText(strs.supported_languages))
|
||||||
.WithOkColor();
|
.WithOkColor();
|
||||||
|
|
||||||
foreach (var chunk in langs.Chunk(15))
|
foreach (var chunk in langs.Chunk(15))
|
||||||
{
|
{
|
||||||
|
@@ -9,6 +9,7 @@ public sealed class AfkService : INService, IReadyExecutor
|
|||||||
private readonly MessageSenderService _mss;
|
private readonly MessageSenderService _mss;
|
||||||
|
|
||||||
private static readonly TimeSpan _maxAfkDuration = 8.Hours();
|
private static readonly TimeSpan _maxAfkDuration = 8.Hours();
|
||||||
|
|
||||||
public AfkService(IBotCache cache, DiscordSocketClient client, MessageSenderService mss)
|
public AfkService(IBotCache cache, DiscordSocketClient client, MessageSenderService mss)
|
||||||
{
|
{
|
||||||
_cache = cache;
|
_cache = cache;
|
||||||
@@ -19,6 +20,9 @@ public sealed class AfkService : INService, IReadyExecutor
|
|||||||
private static TypedKey<string> GetKey(ulong userId)
|
private static TypedKey<string> GetKey(ulong userId)
|
||||||
=> new($"afk:msg:{userId}");
|
=> new($"afk:msg:{userId}");
|
||||||
|
|
||||||
|
private static TypedKey<bool> GetRecentlySentKey(ulong userId, ulong channelId)
|
||||||
|
=> new($"afk:recent:{userId}:{channelId}");
|
||||||
|
|
||||||
public async Task<bool> SetAfkAsync(ulong userId, string text)
|
public async Task<bool> SetAfkAsync(ulong userId, string text)
|
||||||
{
|
{
|
||||||
var added = await _cache.AddAsync(GetKey(userId), text, _maxAfkDuration, overwrite: true);
|
var added = await _cache.AddAsync(GetKey(userId), text, _maxAfkDuration, overwrite: true);
|
||||||
@@ -43,9 +47,7 @@ public sealed class AfkService : INService, IReadyExecutor
|
|||||||
msg.DeleteAfter(5);
|
msg.DeleteAfter(5);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -61,7 +63,7 @@ public sealed class AfkService : INService, IReadyExecutor
|
|||||||
await Task.Delay(_maxAfkDuration);
|
await Task.Delay(_maxAfkDuration);
|
||||||
_client.MessageReceived -= StopAfk;
|
_client.MessageReceived -= StopAfk;
|
||||||
});
|
});
|
||||||
|
|
||||||
return added;
|
return added;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,36 +74,29 @@ public sealed class AfkService : INService, IReadyExecutor
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task TryTriggerAfkMessage(SocketMessage arg)
|
private Task TryTriggerAfkMessage(SocketMessage sm)
|
||||||
{
|
{
|
||||||
if (arg.Author.IsBot || arg.Author.IsWebhook)
|
if (sm.Author.IsBot || sm.Author.IsWebhook)
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|
||||||
if (arg is not IUserMessage uMsg || uMsg.Channel is not ITextChannel tc)
|
if (sm is not IUserMessage uMsg || uMsg.Channel is not ITextChannel tc)
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|
||||||
if ((arg.MentionedUsers.Count is 0 or > 3) && uMsg.ReferencedMessage is null)
|
if ((sm.MentionedUsers.Count is 0 or > 3) && uMsg.ReferencedMessage is null)
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
var botUser = await tc.Guild.GetCurrentUserAsync();
|
|
||||||
|
|
||||||
var perms = botUser.GetPermissions(tc);
|
|
||||||
|
|
||||||
if (!perms.SendMessages)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ulong mentionedUserId = 0;
|
ulong mentionedUserId = 0;
|
||||||
|
|
||||||
if (arg.MentionedUsers.Count <= 3)
|
if (sm.MentionedUsers.Count <= 3)
|
||||||
{
|
{
|
||||||
foreach (var uid in uMsg.MentionedUserIds)
|
foreach (var uid in uMsg.MentionedUserIds)
|
||||||
{
|
{
|
||||||
if (uid == arg.Author.Id)
|
if (uid == sm.Author.Id)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (arg.Content.StartsWith($"<@{uid}>") || arg.Content.StartsWith($"<@!{uid}>"))
|
if (sm.Content.StartsWith($"<@{uid}>") || sm.Content.StartsWith($"<@!{uid}>"))
|
||||||
{
|
{
|
||||||
mentionedUserId = uid;
|
mentionedUserId = uid;
|
||||||
break;
|
break;
|
||||||
@@ -115,26 +110,46 @@ public sealed class AfkService : INService, IReadyExecutor
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mentionedUserId = repliedUserId;
|
mentionedUserId = repliedUserId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = await _cache.GetAsync(GetKey(mentionedUserId));
|
var result = await _cache.GetAsync(GetKey(mentionedUserId));
|
||||||
if (result.TryPickT0(out var msg, out _))
|
if (result.TryPickT0(out var msg, out _))
|
||||||
{
|
{
|
||||||
var st = SmartText.CreateFrom(msg);
|
var st = SmartText.CreateFrom(msg);
|
||||||
|
|
||||||
st = $"The user you've pinged (<#{mentionedUserId}>) is AFK: " + st;
|
st = $"The user you've pinged (<#{mentionedUserId}>) is AFK: " + st;
|
||||||
|
|
||||||
var toDelete = await _mss.Response(arg.Channel)
|
var toDelete = await _mss.Response(sm.Channel)
|
||||||
.User(arg.Author)
|
.User(sm.Author)
|
||||||
.Message(uMsg)
|
.Message(uMsg)
|
||||||
.Text(st)
|
.Text(st)
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
|
|
||||||
toDelete.DeleteAfter(30);
|
toDelete.DeleteAfter(30);
|
||||||
|
|
||||||
|
var botUser = await tc.Guild.GetCurrentUserAsync();
|
||||||
|
var perms = botUser.GetPermissions(tc);
|
||||||
|
if (!perms.SendMessages)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var key = GetRecentlySentKey(mentionedUserId, sm.Channel.Id);
|
||||||
|
var recent = await _cache.GetAsync(key);
|
||||||
|
|
||||||
|
if (!recent.TryPickT0(out _, out _))
|
||||||
|
{
|
||||||
|
var chMsg = await _mss.Response(sm.Channel)
|
||||||
|
.Message(uMsg)
|
||||||
|
.Pending(strs.user_afk($"<@{mentionedUserId}>"))
|
||||||
|
.SendAsync();
|
||||||
|
|
||||||
|
chMsg.DeleteAfter(5);
|
||||||
|
await _cache.AddAsync(key, true, expiry: TimeSpan.FromMinutes(5));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (HttpException ex)
|
catch (HttpException ex)
|
||||||
|
@@ -14,7 +14,7 @@ public partial class Utility
|
|||||||
}
|
}
|
||||||
|
|
||||||
private string GetCommandString(NadekoCommandCallModel res)
|
private string GetCommandString(NadekoCommandCallModel res)
|
||||||
=> $"{_bcs.Data.Prefix}{res.Name} {res.Arguments.Select((x, i) => GetParamString(x, i + 1 == res.Arguments.Count)).Join(" ")}";
|
=> $"{prefix}{res.Name} {res.Arguments.Select((x, i) => GetParamString(x, i + 1 == res.Arguments.Count)).Join(" ")}";
|
||||||
|
|
||||||
private static string GetParamString(string val, bool isLast)
|
private static string GetParamString(string val, bool isLast)
|
||||||
=> isLast ? val : "\"" + val + "\"";
|
=> isLast ? val : "\"" + val + "\"";
|
||||||
|
@@ -38,6 +38,8 @@ public partial class Utility
|
|||||||
eb
|
eb
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithTitle(GetText(strs.giveaway_started))
|
.WithTitle(GetText(strs.giveaway_started))
|
||||||
|
.AddField(GetText(strs.lasts_until), TimestampTag.FromDateTime(DateTime.UtcNow.Add(duration)), true)
|
||||||
|
// .AddField(GetText(strs.winners_count), "1", true)
|
||||||
.WithFooter($"id: {new kwum(id).ToString()}");
|
.WithFooter($"id: {new kwum(id).ToString()}");
|
||||||
|
|
||||||
await startingMsg.AddReactionAsync(new Emoji(GiveawayService.GiveawayEmoji));
|
await startingMsg.AddReactionAsync(new Emoji(GiveawayService.GiveawayEmoji));
|
||||||
|
@@ -341,6 +341,9 @@ public sealed class GiveawayService : INService, IReadyExecutor
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await msg.ModifyAsync(x => x.Embed = eb.Build());
|
await msg.ModifyAsync(x => x.Embed = eb.Build());
|
||||||
|
|
||||||
|
if (winner is not null)
|
||||||
|
await _sender.Response(ch).Message(msg).Text($"🎉 <@{winner.UserId}>").SendAsync();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
@@ -12,17 +12,21 @@ public partial class Utility
|
|||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
public async Task ServerColorsShow()
|
public async Task ServerColorsShow()
|
||||||
{
|
{
|
||||||
|
var colors = _service.GetColors(ctx.Guild.Id);
|
||||||
|
var okHex = colors?.Ok?.RawValue.ToString("x6");
|
||||||
|
var warnHex = colors?.Warn?.RawValue.ToString("x6");
|
||||||
|
var errHex = colors?.Error?.RawValue.ToString("x6");
|
||||||
EmbedBuilder[] ebs =
|
EmbedBuilder[] ebs =
|
||||||
[
|
[
|
||||||
CreateEmbed()
|
CreateEmbed()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithDescription("\\✅"),
|
.WithDescription($"\\✅ {okHex}"),
|
||||||
CreateEmbed()
|
CreateEmbed()
|
||||||
.WithPendingColor()
|
.WithPendingColor()
|
||||||
.WithDescription("\\⏳\\⚠️"),
|
.WithDescription($"\\⏳\\⚠️ {warnHex}"),
|
||||||
CreateEmbed()
|
CreateEmbed()
|
||||||
.WithErrorColor()
|
.WithErrorColor()
|
||||||
.WithDescription("\\❌")
|
.WithDescription($"\\❌ {errHex}")
|
||||||
];
|
];
|
||||||
|
|
||||||
await Response()
|
await Response()
|
||||||
|
@@ -98,10 +98,10 @@ public partial class Utility
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var embed = CreateEmbed()
|
var embed = CreateEmbed()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithTitle(GetText(guildId is not null
|
.WithTitle(GetText(guildId is not null
|
||||||
? strs.reminder_server_list
|
? strs.reminder_server_list
|
||||||
: strs.reminder_list));
|
: strs.reminder_list));
|
||||||
|
|
||||||
List<Reminder> rems;
|
List<Reminder> rems;
|
||||||
if (guildId is { } gid)
|
if (guildId is { } gid)
|
||||||
@@ -193,23 +193,14 @@ public partial class Utility
|
|||||||
message = message.SanitizeAllMentions();
|
message = message.SanitizeAllMentions();
|
||||||
}
|
}
|
||||||
|
|
||||||
var rem = new Reminder
|
await _service.AddReminderAsync(ctx.User.Id,
|
||||||
{
|
targetId,
|
||||||
ChannelId = targetId,
|
ctx.Guild?.Id,
|
||||||
IsPrivate = isPrivate,
|
isPrivate,
|
||||||
When = time,
|
time,
|
||||||
Message = message,
|
message,
|
||||||
UserId = ctx.User.Id,
|
ReminderType.User);
|
||||||
ServerId = ctx.Guild?.Id ?? 0
|
|
||||||
};
|
|
||||||
|
|
||||||
await using (var uow = _db.GetDbContext())
|
|
||||||
{
|
|
||||||
uow.Set<Reminder>().Add(rem);
|
|
||||||
await uow.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
// var gTime = ctx.Guild is null ? time : TimeZoneInfo.ConvertTime(time, _tz.GetTimeZoneOrUtc(ctx.Guild.Id));
|
|
||||||
await Response()
|
await Response()
|
||||||
.Confirm($"\u23f0 {GetText(strs.remind2(
|
.Confirm($"\u23f0 {GetText(strs.remind2(
|
||||||
Format.Bold(!isPrivate ? $"<#{targetId}>" : ctx.User.Username),
|
Format.Bold(!isPrivate ? $"<#{targetId}>" : ctx.User.Username),
|
||||||
|
@@ -21,6 +21,8 @@ public class RemindService : INService, IReadyExecutor, IRemindService
|
|||||||
private readonly IMessageSenderService _sender;
|
private readonly IMessageSenderService _sender;
|
||||||
private readonly CultureInfo _culture;
|
private readonly CultureInfo _culture;
|
||||||
|
|
||||||
|
private TaskCompletionSource<bool> _tcs;
|
||||||
|
|
||||||
public RemindService(
|
public RemindService(
|
||||||
DiscordSocketClient client,
|
DiscordSocketClient client,
|
||||||
DbService db,
|
DbService db,
|
||||||
@@ -44,8 +46,7 @@ public class RemindService : INService, IReadyExecutor, IRemindService
|
|||||||
|
|
||||||
public async Task OnReadyAsync()
|
public async Task OnReadyAsync()
|
||||||
{
|
{
|
||||||
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(15));
|
while (true)
|
||||||
while (await timer.WaitForNextTickAsync())
|
|
||||||
{
|
{
|
||||||
await OnReminderLoopTickInternalAsync();
|
await OnReminderLoopTickInternalAsync();
|
||||||
}
|
}
|
||||||
@@ -55,8 +56,7 @@ public class RemindService : INService, IReadyExecutor, IRemindService
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var now = DateTime.UtcNow;
|
var reminders = await GetRemindersBeforeAsync();
|
||||||
var reminders = await GetRemindersBeforeAsync(now);
|
|
||||||
if (reminders.Count == 0)
|
if (reminders.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -67,7 +67,6 @@ public class RemindService : INService, IReadyExecutor, IRemindService
|
|||||||
{
|
{
|
||||||
var executedReminders = group.ToList();
|
var executedReminders = group.ToList();
|
||||||
await executedReminders.Select(ReminderTimerAction).WhenAll();
|
await executedReminders.Select(ReminderTimerAction).WhenAll();
|
||||||
await RemoveReminders(executedReminders.Select(x => x.Id));
|
|
||||||
await Task.Delay(1500);
|
await Task.Delay(1500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,21 +79,51 @@ public class RemindService : INService, IReadyExecutor, IRemindService
|
|||||||
private async Task RemoveReminders(IEnumerable<int> reminders)
|
private async Task RemoveReminders(IEnumerable<int> reminders)
|
||||||
{
|
{
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
await uow.Set<Reminder>()
|
await uow.GetTable<Reminder>()
|
||||||
.ToLinqToDBTable()
|
|
||||||
.DeleteAsync(x => reminders.Contains(x.Id));
|
.DeleteAsync(x => reminders.Contains(x.Id));
|
||||||
|
|
||||||
await uow.SaveChangesAsync();
|
await uow.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<Reminder>> GetRemindersBeforeAsync(DateTime now)
|
private async Task<IReadOnlyList<Reminder>> GetRemindersBeforeAsync()
|
||||||
{
|
{
|
||||||
await using var uow = _db.GetDbContext();
|
while (true)
|
||||||
return await uow.Set<Reminder>()
|
{
|
||||||
.ToLinqToDBTable()
|
_tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
.Where(x => Linq2DbExpressions.GuildOnShard(x.ServerId, _creds.TotalShards, _client.ShardId)
|
await using var uow = _db.GetDbContext();
|
||||||
&& x.When < now)
|
var earliest = await uow.Set<Reminder>()
|
||||||
.ToListAsyncLinqToDB();
|
.ToLinqToDBTable()
|
||||||
|
.Where(x => Linq2DbExpressions.GuildOnShard(x.ServerId,
|
||||||
|
_creds.TotalShards,
|
||||||
|
_client.ShardId))
|
||||||
|
.OrderBy(x => x.When)
|
||||||
|
.FirstOrDefaultAsyncLinqToDB();
|
||||||
|
|
||||||
|
if (earliest == default)
|
||||||
|
{
|
||||||
|
await _tcs.Task;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
if (earliest.When > now)
|
||||||
|
{
|
||||||
|
var diff = earliest.When - now;
|
||||||
|
// Log.Information("Waiting for {Diff}", diff);
|
||||||
|
await Task.WhenAny(Task.Delay(diff), _tcs.Task);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var reminders = await uow.Set<Reminder>()
|
||||||
|
.ToLinqToDBTable()
|
||||||
|
.Where(x => Linq2DbExpressions.GuildOnShard(x.ServerId,
|
||||||
|
_creds.TotalShards,
|
||||||
|
_client.ShardId))
|
||||||
|
.Where(x => x.When <= now)
|
||||||
|
.DeleteWithOutputAsync();
|
||||||
|
|
||||||
|
return reminders;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryParseRemindMessage(string input, out RemindObject obj)
|
public bool TryParseRemindMessage(string input, out RemindObject obj)
|
||||||
@@ -243,21 +272,24 @@ public class RemindService : INService, IReadyExecutor, IRemindService
|
|||||||
string message,
|
string message,
|
||||||
ReminderType reminderType)
|
ReminderType reminderType)
|
||||||
{
|
{
|
||||||
var rem = new Reminder
|
await using (var ctx = _db.GetDbContext())
|
||||||
{
|
{
|
||||||
UserId = userId,
|
await ctx.GetTable<Reminder>()
|
||||||
ChannelId = targetId,
|
.InsertAsync(() => new Reminder
|
||||||
ServerId = guildId ?? 0,
|
{
|
||||||
IsPrivate = isPrivate,
|
UserId = userId,
|
||||||
When = time,
|
ChannelId = targetId,
|
||||||
Message = message,
|
ServerId = guildId ?? 0,
|
||||||
Type = reminderType
|
IsPrivate = isPrivate,
|
||||||
};
|
When = time,
|
||||||
|
Message = message,
|
||||||
|
Type = reminderType,
|
||||||
|
DateAdded = DateTime.UtcNow
|
||||||
|
});
|
||||||
|
await ctx.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
await using var ctx = _db.GetDbContext();
|
_tcs.SetResult(true);
|
||||||
await ctx.Set<Reminder>()
|
|
||||||
.AddAsync(rem);
|
|
||||||
await ctx.SaveChangesAsync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<Reminder>> GetServerReminders(int page, ulong guildId)
|
public async Task<List<Reminder>> GetServerReminders(int page, ulong guildId)
|
||||||
|
@@ -110,14 +110,14 @@ public partial class Utility
|
|||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.ManageMessages)]
|
[UserPerm(GuildPerm.ManageMessages)]
|
||||||
[Priority(0)]
|
[Priority(0)]
|
||||||
public Task Repeat(StoopidTime interval, [Leftover] string message)
|
public Task Repeat(ParsedTimespan interval, [Leftover] string message)
|
||||||
=> Repeat(ctx.Channel, null, interval, message);
|
=> Repeat(ctx.Channel, null, interval, message);
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.ManageMessages)]
|
[UserPerm(GuildPerm.ManageMessages)]
|
||||||
[Priority(0)]
|
[Priority(0)]
|
||||||
public Task Repeat(ITextChannel channel, StoopidTime interval, [Leftover] string message)
|
public Task Repeat(ITextChannel channel, ParsedTimespan interval, [Leftover] string message)
|
||||||
=> Repeat(channel, null, interval, message);
|
=> Repeat(channel, null, interval, message);
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
@@ -138,14 +138,14 @@ public partial class Utility
|
|||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.ManageMessages)]
|
[UserPerm(GuildPerm.ManageMessages)]
|
||||||
[Priority(2)]
|
[Priority(2)]
|
||||||
public Task Repeat(GuildDateTime? timeOfDay, StoopidTime? interval, [Leftover] string message)
|
public Task Repeat(GuildDateTime? timeOfDay, ParsedTimespan? interval, [Leftover] string message)
|
||||||
=> Repeat(ctx.Channel, timeOfDay, interval, message);
|
=> Repeat(ctx.Channel, timeOfDay, interval, message);
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.ManageMessages)]
|
[UserPerm(GuildPerm.ManageMessages)]
|
||||||
[Priority(3)]
|
[Priority(3)]
|
||||||
public async Task Repeat(IMessageChannel channel, GuildDateTime? timeOfDay, StoopidTime? interval,
|
public async Task Repeat(IMessageChannel channel, GuildDateTime? timeOfDay, ParsedTimespan? interval,
|
||||||
[Leftover] string message)
|
[Leftover] string message)
|
||||||
{
|
{
|
||||||
if (channel is not ITextChannel txtCh || txtCh.GuildId != ctx.Guild.Id)
|
if (channel is not ITextChannel txtCh || txtCh.GuildId != ctx.Guild.Id)
|
||||||
|
@@ -186,7 +186,7 @@ public partial class Utility : NadekoModule
|
|||||||
|
|
||||||
return CreateEmbed()
|
return CreateEmbed()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithTitle(GetText(strs.inrole_list(roleName, roleUsers.Count)))
|
.WithTitle(GetText(strs.inrole_list(role?.GetIconUrl() + roleName, roleUsers.Count)))
|
||||||
.WithDescription(string.Join("\n", pageUsers));
|
.WithDescription(string.Join("\n", pageUsers));
|
||||||
})
|
})
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
|
11
src/NadekoBot/Modules/Xp/BuyResult.cs
Normal file
11
src/NadekoBot/Modules/Xp/BuyResult.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace NadekoBot.Modules.Xp.Services;
|
||||||
|
|
||||||
|
public enum BuyResult
|
||||||
|
{
|
||||||
|
Success,
|
||||||
|
XpShopDisabled,
|
||||||
|
AlreadyOwned,
|
||||||
|
InsufficientFunds,
|
||||||
|
UnknownItem,
|
||||||
|
InsufficientPatronTier,
|
||||||
|
}
|
@@ -51,33 +51,6 @@ public partial class Xp : NadekoModule<XpService>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
|
||||||
[RequireContext(ContextType.Guild)]
|
|
||||||
public async Task XpNotify()
|
|
||||||
{
|
|
||||||
var globalSetting = _service.GetNotificationType(ctx.User);
|
|
||||||
var serverSetting = _service.GetNotificationType(ctx.User.Id, ctx.Guild.Id);
|
|
||||||
|
|
||||||
var embed = CreateEmbed()
|
|
||||||
.WithOkColor()
|
|
||||||
.AddField(GetText(strs.xpn_setting_global), GetNotifLocationString(globalSetting))
|
|
||||||
.AddField(GetText(strs.xpn_setting_server), GetNotifLocationString(serverSetting));
|
|
||||||
|
|
||||||
await Response().Embed(embed).SendAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Cmd]
|
|
||||||
[RequireContext(ContextType.Guild)]
|
|
||||||
public async Task XpNotify(NotifyPlace place, XpNotificationLocation type)
|
|
||||||
{
|
|
||||||
if (place == NotifyPlace.Guild)
|
|
||||||
await _service.ChangeNotificationType(ctx.User.Id, ctx.Guild.Id, type);
|
|
||||||
else
|
|
||||||
await _service.ChangeNotificationType(ctx.User, type);
|
|
||||||
|
|
||||||
await ctx.OkAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.Administrator)]
|
[UserPerm(GuildPerm.Administrator)]
|
||||||
@@ -154,9 +127,9 @@ public partial class Xp : NadekoModule<XpService>
|
|||||||
.Page((items, _) =>
|
.Page((items, _) =>
|
||||||
{
|
{
|
||||||
var embed = CreateEmbed()
|
var embed = CreateEmbed()
|
||||||
.WithTitle(GetText(strs.exclusion_list))
|
.WithTitle(GetText(strs.exclusion_list))
|
||||||
.WithDescription(string.Join('\n', items))
|
.WithDescription(string.Join('\n', items))
|
||||||
.WithOkColor();
|
.WithOkColor();
|
||||||
|
|
||||||
return embed;
|
return embed;
|
||||||
})
|
})
|
||||||
@@ -214,16 +187,12 @@ public partial class Xp : NadekoModule<XpService>
|
|||||||
|
|
||||||
for (var i = 0; i < users.Count; i++)
|
for (var i = 0; i < users.Count; i++)
|
||||||
{
|
{
|
||||||
var levelStats = new LevelStats(users[i].Xp + users[i].AwardedXp);
|
var levelStats = new LevelStats(users[i].Xp);
|
||||||
var user = ((SocketGuild)ctx.Guild).GetUser(users[i].UserId);
|
var user = ((SocketGuild)ctx.Guild).GetUser(users[i].UserId);
|
||||||
|
|
||||||
var userXpData = users[i];
|
var userXpData = users[i];
|
||||||
|
|
||||||
var awardStr = string.Empty;
|
var awardStr = string.Empty;
|
||||||
if (userXpData.AwardedXp > 0)
|
|
||||||
awardStr = $"(+{userXpData.AwardedXp})";
|
|
||||||
else if (userXpData.AwardedXp < 0)
|
|
||||||
awardStr = $"({userXpData.AwardedXp})";
|
|
||||||
|
|
||||||
embed.AddField($"#{i + 1 + (curPage * 10)} {user?.ToString() ?? users[i].UserId.ToString()}",
|
embed.AddField($"#{i + 1 + (curPage * 10)} {user?.ToString() ?? users[i].UserId.ToString()}",
|
||||||
$"{GetText(strs.level_x(levelStats.Level))} - {levelStats.TotalXp}xp {awardStr}");
|
$"{GetText(strs.level_x(levelStats.Level))} - {levelStats.TotalXp}xp {awardStr}");
|
||||||
@@ -266,8 +235,8 @@ public partial class Xp : NadekoModule<XpService>
|
|||||||
.Page((users, curPage) =>
|
.Page((users, curPage) =>
|
||||||
{
|
{
|
||||||
var embed = CreateEmbed()
|
var embed = CreateEmbed()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithTitle(GetText(strs.global_leaderboard));
|
.WithTitle(GetText(strs.global_leaderboard));
|
||||||
|
|
||||||
if (!users.Any())
|
if (!users.Any())
|
||||||
{
|
{
|
||||||
@@ -287,6 +256,28 @@ public partial class Xp : NadekoModule<XpService>
|
|||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
[UserPerm(GuildPerm.Administrator)]
|
||||||
|
[Priority(1)]
|
||||||
|
public Task XpLevelSet(int level, IGuildUser user)
|
||||||
|
=> XpLevelSet(level, user.Id);
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
[UserPerm(GuildPerm.Administrator)]
|
||||||
|
[Priority(0)]
|
||||||
|
public async Task XpLevelSet(int level, ulong userId)
|
||||||
|
{
|
||||||
|
if (level < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await _service.SetLevelAsync(ctx.Guild.Id, userId, level);
|
||||||
|
await Response()
|
||||||
|
.Confirm(strs.level_set($"<@{userId}>", Format.Bold(level.ToString())))
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.Administrator)]
|
[UserPerm(GuildPerm.Administrator)]
|
||||||
@@ -351,8 +342,8 @@ public partial class Xp : NadekoModule<XpService>
|
|||||||
public async Task XpReset(ulong userId)
|
public async Task XpReset(ulong userId)
|
||||||
{
|
{
|
||||||
var embed = CreateEmbed()
|
var embed = CreateEmbed()
|
||||||
.WithTitle(GetText(strs.reset))
|
.WithTitle(GetText(strs.reset))
|
||||||
.WithDescription(GetText(strs.reset_user_confirm));
|
.WithDescription(GetText(strs.reset_user_confirm));
|
||||||
|
|
||||||
if (!await PromptUserConfirmAsync(embed))
|
if (!await PromptUserConfirmAsync(embed))
|
||||||
return;
|
return;
|
||||||
@@ -368,8 +359,8 @@ public partial class Xp : NadekoModule<XpService>
|
|||||||
public async Task XpReset()
|
public async Task XpReset()
|
||||||
{
|
{
|
||||||
var embed = CreateEmbed()
|
var embed = CreateEmbed()
|
||||||
.WithTitle(GetText(strs.reset))
|
.WithTitle(GetText(strs.reset))
|
||||||
.WithDescription(GetText(strs.reset_server_confirm));
|
.WithDescription(GetText(strs.reset_server_confirm));
|
||||||
|
|
||||||
if (!await PromptUserConfirmAsync(embed))
|
if (!await PromptUserConfirmAsync(embed))
|
||||||
return;
|
return;
|
||||||
@@ -446,20 +437,20 @@ public partial class Xp : NadekoModule<XpService>
|
|||||||
{
|
{
|
||||||
if (!items.Any())
|
if (!items.Any())
|
||||||
return CreateEmbed()
|
return CreateEmbed()
|
||||||
.WithDescription(GetText(strs.not_found))
|
.WithDescription(GetText(strs.not_found))
|
||||||
.WithErrorColor();
|
.WithErrorColor();
|
||||||
|
|
||||||
var (key, item) = items.FirstOrDefault();
|
var (key, item) = items.FirstOrDefault();
|
||||||
|
|
||||||
var eb = CreateEmbed()
|
var eb = CreateEmbed()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithTitle(item.Name)
|
.WithTitle(item.Name)
|
||||||
.AddField(GetText(strs.price),
|
.AddField(GetText(strs.price),
|
||||||
CurrencyHelper.N(item.Price, Culture, _gss.GetCurrencySign()),
|
CurrencyHelper.N(item.Price, Culture, _gss.GetCurrencySign()),
|
||||||
true)
|
true)
|
||||||
.WithImageUrl(string.IsNullOrWhiteSpace(item.Preview)
|
.WithImageUrl(string.IsNullOrWhiteSpace(item.Preview)
|
||||||
? item.Url
|
? item.Url
|
||||||
: item.Preview);
|
: item.Preview);
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(item.Desc))
|
if (!string.IsNullOrWhiteSpace(item.Desc))
|
||||||
eb.AddField(GetText(strs.desc), item.Desc);
|
eb.AddField(GetText(strs.desc), item.Desc);
|
||||||
@@ -604,15 +595,4 @@ public partial class Xp : NadekoModule<XpService>
|
|||||||
await _service.UseShopItemAsync(ctx.User.Id, type, key);
|
await _service.UseShopItemAsync(ctx.User.Id, type, key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetNotifLocationString(XpNotificationLocation loc)
|
|
||||||
{
|
|
||||||
if (loc == XpNotificationLocation.Channel)
|
|
||||||
return GetText(strs.xpn_notif_channel);
|
|
||||||
|
|
||||||
if (loc == XpNotificationLocation.Dm)
|
|
||||||
return GetText(strs.xpn_notif_dm);
|
|
||||||
|
|
||||||
return GetText(strs.xpn_notif_disabled);
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -13,6 +13,7 @@ using SixLabors.ImageSharp.Processing;
|
|||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using LinqToDB.Tools;
|
using LinqToDB.Tools;
|
||||||
|
using NadekoBot.Modules.Administration;
|
||||||
using NadekoBot.Modules.Patronage;
|
using NadekoBot.Modules.Patronage;
|
||||||
using Color = SixLabors.ImageSharp.Color;
|
using Color = SixLabors.ImageSharp.Color;
|
||||||
using Exception = System.Exception;
|
using Exception = System.Exception;
|
||||||
@@ -20,31 +21,6 @@ using Image = SixLabors.ImageSharp.Image;
|
|||||||
|
|
||||||
namespace NadekoBot.Modules.Xp.Services;
|
namespace NadekoBot.Modules.Xp.Services;
|
||||||
|
|
||||||
public interface IUserService
|
|
||||||
{
|
|
||||||
Task<DiscordUser?> GetUserAsync(ulong userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class UserService : IUserService, INService
|
|
||||||
{
|
|
||||||
private readonly DbService _db;
|
|
||||||
|
|
||||||
public UserService(DbService db)
|
|
||||||
{
|
|
||||||
_db = db;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<DiscordUser> GetUserAsync(ulong userId)
|
|
||||||
{
|
|
||||||
await using var uow = _db.GetDbContext();
|
|
||||||
var user = await uow
|
|
||||||
.GetTable<DiscordUser>()
|
|
||||||
.FirstOrDefaultAsyncLinqToDB(u => u.UserId == userId);
|
|
||||||
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class XpService : INService, IReadyExecutor, IExecNoCommand
|
public class XpService : INService, IReadyExecutor, IExecNoCommand
|
||||||
{
|
{
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
@@ -72,6 +48,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
|||||||
private readonly QueueRunner _levelUpQueue = new QueueRunner(0, 50);
|
private readonly QueueRunner _levelUpQueue = new QueueRunner(0, 50);
|
||||||
private readonly Channel<UserXpGainData> _xpGainQueue = Channel.CreateUnbounded<UserXpGainData>();
|
private readonly Channel<UserXpGainData> _xpGainQueue = Channel.CreateUnbounded<UserXpGainData>();
|
||||||
private readonly IMessageSenderService _sender;
|
private readonly IMessageSenderService _sender;
|
||||||
|
private readonly INotifySubscriber _notifySub;
|
||||||
|
|
||||||
public XpService(
|
public XpService(
|
||||||
DiscordSocketClient client,
|
DiscordSocketClient client,
|
||||||
@@ -87,7 +64,8 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
|||||||
XpConfigService xpConfig,
|
XpConfigService xpConfig,
|
||||||
IPubSub pubSub,
|
IPubSub pubSub,
|
||||||
IPatronageService ps,
|
IPatronageService ps,
|
||||||
IMessageSenderService sender)
|
IMessageSenderService sender,
|
||||||
|
INotifySubscriber notifySub)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_images = images;
|
_images = images;
|
||||||
@@ -99,6 +77,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
|||||||
_xpConfig = xpConfig;
|
_xpConfig = xpConfig;
|
||||||
_pubSub = pubSub;
|
_pubSub = pubSub;
|
||||||
_sender = sender;
|
_sender = sender;
|
||||||
|
_notifySub = notifySub;
|
||||||
_excludedServers = new();
|
_excludedServers = new();
|
||||||
_excludedChannels = new();
|
_excludedChannels = new();
|
||||||
_client = client;
|
_client = client;
|
||||||
@@ -159,14 +138,6 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class MiniGuildXpStats
|
|
||||||
{
|
|
||||||
public long Xp { get; set; }
|
|
||||||
public XpNotificationLocation NotifyOnLevelUp { get; set; }
|
|
||||||
public ulong GuildId { get; set; }
|
|
||||||
public ulong UserId { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task UpdateXp()
|
private async Task UpdateXp()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -197,9 +168,9 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
|||||||
|
|
||||||
var dus = new List<DiscordUser>(globalToAdd.Count);
|
var dus = new List<DiscordUser>(globalToAdd.Count);
|
||||||
var gxps = new List<UserXpStats>(globalToAdd.Count);
|
var gxps = new List<UserXpStats>(globalToAdd.Count);
|
||||||
|
var conf = _xpConfig.Data;
|
||||||
await using (var ctx = _db.GetDbContext())
|
await using (var ctx = _db.GetDbContext())
|
||||||
{
|
{
|
||||||
var conf = _xpConfig.Data;
|
|
||||||
if (conf.CurrencyPerXp > 0)
|
if (conf.CurrencyPerXp > 0)
|
||||||
{
|
{
|
||||||
foreach (var user in globalToAdd)
|
foreach (var user in globalToAdd)
|
||||||
@@ -261,8 +232,6 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
|||||||
GuildId = guildId,
|
GuildId = guildId,
|
||||||
Xp = group.Key,
|
Xp = group.Key,
|
||||||
DateAdded = DateTime.UtcNow,
|
DateAdded = DateTime.UtcNow,
|
||||||
AwardedXp = 0,
|
|
||||||
NotifyOnLevelUp = XpNotificationLocation.None
|
|
||||||
},
|
},
|
||||||
_ => new()
|
_ => new()
|
||||||
{
|
{
|
||||||
@@ -300,8 +269,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
|||||||
du.UserId,
|
du.UserId,
|
||||||
false,
|
false,
|
||||||
oldLevel.Level,
|
oldLevel.Level,
|
||||||
newLevel.Level,
|
newLevel.Level));
|
||||||
du.NotifyOnLevelUp));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,8 +278,8 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
|||||||
if (guildToAdd.TryGetValue(du.GuildId, out var users)
|
if (guildToAdd.TryGetValue(du.GuildId, out var users)
|
||||||
&& users.TryGetValue(du.UserId, out var xpGainData))
|
&& users.TryGetValue(du.UserId, out var xpGainData))
|
||||||
{
|
{
|
||||||
var oldLevel = new LevelStats(du.Xp - xpGainData.XpAmount + du.AwardedXp);
|
var oldLevel = new LevelStats(du.Xp - xpGainData.XpAmount);
|
||||||
var newLevel = new LevelStats(du.Xp + du.AwardedXp);
|
var newLevel = new LevelStats(du.Xp);
|
||||||
|
|
||||||
if (oldLevel.Level < newLevel.Level)
|
if (oldLevel.Level < newLevel.Level)
|
||||||
{
|
{
|
||||||
@@ -321,8 +289,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
|||||||
du.UserId,
|
du.UserId,
|
||||||
true,
|
true,
|
||||||
oldLevel.Level,
|
oldLevel.Level,
|
||||||
newLevel.Level,
|
newLevel.Level));
|
||||||
du.NotifyOnLevelUp));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -339,8 +306,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
|||||||
ulong userId,
|
ulong userId,
|
||||||
bool isServer,
|
bool isServer,
|
||||||
long oldLevel,
|
long oldLevel,
|
||||||
long newLevel,
|
long newLevel)
|
||||||
XpNotificationLocation notifyLoc)
|
|
||||||
=> async () =>
|
=> async () =>
|
||||||
{
|
{
|
||||||
if (isServer)
|
if (isServer)
|
||||||
@@ -348,7 +314,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
|||||||
await HandleRewardsInternalAsync(guildId, userId, oldLevel, newLevel);
|
await HandleRewardsInternalAsync(guildId, userId, oldLevel, newLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
await HandleNotifyInternalAsync(guildId, channelId, userId, isServer, newLevel, notifyLoc);
|
await HandleNotifyInternalAsync(guildId, channelId, userId, isServer, newLevel);
|
||||||
};
|
};
|
||||||
|
|
||||||
private async Task HandleRewardsInternalAsync(
|
private async Task HandleRewardsInternalAsync(
|
||||||
@@ -378,9 +344,45 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
|||||||
if (role is not null && user is not null)
|
if (role is not null && user is not null)
|
||||||
{
|
{
|
||||||
if (rrew.Remove)
|
if (rrew.Remove)
|
||||||
_ = user.RemoveRoleAsync(role);
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await user.RemoveRoleAsync(role);
|
||||||
|
await _notifySub.NotifyAsync(new RemoveRoleRewardNotifyModel(guild.Id,
|
||||||
|
role.Id,
|
||||||
|
user.Id,
|
||||||
|
newLevel),
|
||||||
|
isShardLocal: true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Warning(ex,
|
||||||
|
"Unable to remove role {RoleId} from user {UserId}: {Message}",
|
||||||
|
role.Id,
|
||||||
|
user.Id,
|
||||||
|
ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
_ = user.AddRoleAsync(role);
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await user.AddRoleAsync(role);
|
||||||
|
await _notifySub.NotifyAsync(new AddRoleRewardNotifyModel(guild.Id,
|
||||||
|
role.Id,
|
||||||
|
user.Id,
|
||||||
|
newLevel),
|
||||||
|
isShardLocal: true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Warning(ex,
|
||||||
|
"Unable to add role {RoleId} to user {UserId}: {Message}",
|
||||||
|
role.Id,
|
||||||
|
user.Id,
|
||||||
|
ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,59 +401,25 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
|||||||
ulong channelId,
|
ulong channelId,
|
||||||
ulong userId,
|
ulong userId,
|
||||||
bool isServer,
|
bool isServer,
|
||||||
long newLevel,
|
long newLevel)
|
||||||
XpNotificationLocation notifyLoc)
|
|
||||||
{
|
{
|
||||||
if (notifyLoc == XpNotificationLocation.None)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var guild = _client.GetGuild(guildId);
|
var guild = _client.GetGuild(guildId);
|
||||||
var user = guild?.GetUser(userId);
|
var user = guild?.GetUser(userId);
|
||||||
var ch = guild?.GetTextChannel(channelId);
|
|
||||||
|
|
||||||
if (guild is null || user is null)
|
if (guild is null || user is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (isServer)
|
if (isServer)
|
||||||
{
|
{
|
||||||
if (notifyLoc == XpNotificationLocation.Dm)
|
var model = new LevelUpNotifyModel()
|
||||||
{
|
{
|
||||||
await _sender.Response(user)
|
GuildId = guildId,
|
||||||
.Confirm(_strings.GetText(strs.level_up_dm(user.Mention,
|
UserId = userId,
|
||||||
Format.Bold(newLevel.ToString()),
|
ChannelId = channelId,
|
||||||
Format.Bold(guild.ToString() ?? "-")),
|
Level = newLevel
|
||||||
guild.Id))
|
|
||||||
.SendAsync();
|
|
||||||
}
|
|
||||||
else // channel
|
|
||||||
{
|
|
||||||
if (ch is not null)
|
|
||||||
{
|
|
||||||
await _sender.Response(ch)
|
|
||||||
.Confirm(_strings.GetText(strs.level_up_channel(user.Mention,
|
|
||||||
Format.Bold(newLevel.ToString())),
|
|
||||||
guild.Id))
|
|
||||||
.SendAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else // global level
|
|
||||||
{
|
|
||||||
var chan = notifyLoc switch
|
|
||||||
{
|
|
||||||
XpNotificationLocation.Dm => (IMessageChannel)await user.CreateDMChannelAsync(),
|
|
||||||
XpNotificationLocation.Channel => ch,
|
|
||||||
_ => null
|
|
||||||
};
|
};
|
||||||
|
await _notifySub.NotifyAsync(model, true);
|
||||||
if (chan is null)
|
return;
|
||||||
return;
|
|
||||||
|
|
||||||
await _sender.Response(chan)
|
|
||||||
.Confirm(_strings.GetText(strs.level_up_global(user.Mention,
|
|
||||||
Format.Bold(newLevel.ToString())),
|
|
||||||
guild.Id))
|
|
||||||
.SendAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -595,7 +563,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
|||||||
return await uow
|
return await uow
|
||||||
.UserXpStats
|
.UserXpStats
|
||||||
.Where(x => x.GuildId == guildId)
|
.Where(x => x.GuildId == guildId)
|
||||||
.OrderByDescending(x => x.Xp + x.AwardedXp)
|
.OrderByDescending(x => x.Xp)
|
||||||
.Skip(page * 10)
|
.Skip(page * 10)
|
||||||
.Take(10)
|
.Take(10)
|
||||||
.ToArrayAsyncLinqToDB();
|
.ToArrayAsyncLinqToDB();
|
||||||
@@ -606,7 +574,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
|||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
return await uow.Set<UserXpStats>()
|
return await uow.Set<UserXpStats>()
|
||||||
.Where(x => x.GuildId == guildId && x.UserId.In(users))
|
.Where(x => x.GuildId == guildId && x.UserId.In(users))
|
||||||
.OrderByDescending(x => x.Xp + x.AwardedXp)
|
.OrderByDescending(x => x.Xp)
|
||||||
.Skip(page * 10)
|
.Skip(page * 10)
|
||||||
.Take(10)
|
.Take(10)
|
||||||
.ToArrayAsyncLinqToDB();
|
.ToArrayAsyncLinqToDB();
|
||||||
@@ -635,35 +603,6 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
|||||||
.ToArrayAsyncLinqToDB();
|
.ToArrayAsyncLinqToDB();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ChangeNotificationType(ulong userId, ulong guildId, XpNotificationLocation type)
|
|
||||||
{
|
|
||||||
await using var uow = _db.GetDbContext();
|
|
||||||
var user = uow.GetOrCreateUserXpStats(guildId, userId);
|
|
||||||
user.NotifyOnLevelUp = type;
|
|
||||||
await uow.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public XpNotificationLocation GetNotificationType(ulong userId, ulong guildId)
|
|
||||||
{
|
|
||||||
using var uow = _db.GetDbContext();
|
|
||||||
var user = uow.GetOrCreateUserXpStats(guildId, userId);
|
|
||||||
return user.NotifyOnLevelUp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public XpNotificationLocation GetNotificationType(IUser user)
|
|
||||||
{
|
|
||||||
using var uow = _db.GetDbContext();
|
|
||||||
return uow.GetOrCreateUser(user).NotifyOnLevelUp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ChangeNotificationType(IUser user, XpNotificationLocation type)
|
|
||||||
{
|
|
||||||
await using var uow = _db.GetDbContext();
|
|
||||||
var du = uow.GetOrCreateUser(user);
|
|
||||||
du.NotifyOnLevelUp = type;
|
|
||||||
await uow.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task Client_OnGuildAvailable(SocketGuild guild)
|
private Task Client_OnGuildAvailable(SocketGuild guild)
|
||||||
{
|
{
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
@@ -903,7 +842,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
|||||||
using var uow = _db.GetDbContext();
|
using var uow = _db.GetDbContext();
|
||||||
var usr = uow.GetOrCreateUserXpStats(guildId, userId);
|
var usr = uow.GetOrCreateUserXpStats(guildId, userId);
|
||||||
|
|
||||||
usr.AwardedXp += amount;
|
usr.Xp += amount;
|
||||||
|
|
||||||
uow.SaveChanges();
|
uow.SaveChanges();
|
||||||
}
|
}
|
||||||
@@ -949,7 +888,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
|||||||
return new(du,
|
return new(du,
|
||||||
stats,
|
stats,
|
||||||
new(totalXp),
|
new(totalXp),
|
||||||
new(stats.Xp + stats.AwardedXp),
|
new(stats.Xp),
|
||||||
globalRank,
|
globalRank,
|
||||||
guildRank);
|
guildRank);
|
||||||
}
|
}
|
||||||
@@ -1192,19 +1131,6 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
|||||||
outlinePen));
|
outlinePen));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stats.FullGuildStats.AwardedXp != 0 && template.User.Xp.Awarded.Show)
|
|
||||||
{
|
|
||||||
var sign = stats.FullGuildStats.AwardedXp > 0 ? "+ " : "";
|
|
||||||
var awX = template.User.Xp.Awarded.Pos.X
|
|
||||||
- (Math.Max(0, stats.FullGuildStats.AwardedXp.ToString().Length - 2) * 5);
|
|
||||||
var awY = template.User.Xp.Awarded.Pos.Y;
|
|
||||||
img.Mutate(x => x.DrawText($"({sign}{stats.FullGuildStats.AwardedXp})",
|
|
||||||
_fonts.NotoSans.CreateFont(template.User.Xp.Awarded.FontSize, FontStyle.Bold),
|
|
||||||
Brushes.Solid(template.User.Xp.Awarded.Color),
|
|
||||||
outlinePen,
|
|
||||||
new(awX, awY)));
|
|
||||||
}
|
|
||||||
|
|
||||||
var rankPen = new SolidPen(Color.White, 1);
|
var rankPen = new SolidPen(Color.White, 1);
|
||||||
//ranking
|
//ranking
|
||||||
if (template.User.GlobalRank.Show)
|
if (template.User.GlobalRank.Show)
|
||||||
@@ -1671,14 +1597,27 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
|||||||
&& (guildUsers == null || guildUsers.Contains(x.UserId)))
|
&& (guildUsers == null || guildUsers.Contains(x.UserId)))
|
||||||
.CountAsyncLinqToDB();
|
.CountAsyncLinqToDB();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public enum BuyResult
|
public async Task SetLevelAsync(ulong guildId, ulong userId, int level)
|
||||||
{
|
{
|
||||||
Success,
|
var lvlStats = LevelStats.CreateForLevel(level);
|
||||||
XpShopDisabled,
|
await using var ctx = _db.GetDbContext();
|
||||||
AlreadyOwned,
|
await ctx.GetTable<UserXpStats>()
|
||||||
InsufficientFunds,
|
.InsertOrUpdateAsync(() => new()
|
||||||
UnknownItem,
|
{
|
||||||
InsufficientPatronTier,
|
GuildId = guildId,
|
||||||
|
UserId = userId,
|
||||||
|
Xp = lvlStats.TotalXp,
|
||||||
|
DateAdded = DateTime.UtcNow
|
||||||
|
},
|
||||||
|
(old) => new()
|
||||||
|
{
|
||||||
|
Xp = lvlStats.TotalXp
|
||||||
|
},
|
||||||
|
() => new()
|
||||||
|
{
|
||||||
|
GuildId = guildId,
|
||||||
|
UserId = userId
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
@@ -4,7 +4,7 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>true</ImplicitUsings>
|
<ImplicitUsings>true</ImplicitUsings>
|
||||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||||
<Version>5.2.2</Version>
|
<Version>5.3.3</Version>
|
||||||
|
|
||||||
<!-- Output/build -->
|
<!-- Output/build -->
|
||||||
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="CodeHollow.FeedReader" Version="1.2.6"/>
|
<PackageReference Include="CodeHollow.FeedReader" Version="1.2.6"/>
|
||||||
<PackageReference Include="CommandLineParser" Version="2.9.1"/>
|
<PackageReference Include="CommandLineParser" Version="2.9.1"/>
|
||||||
<PackageReference Include="Discord.Net" Version="3.15.3"/>
|
<PackageReference Include="Discord.Net" Version="3.16.0" />
|
||||||
<PackageReference Include="CoreCLR-NCalc" Version="3.1.246"/>
|
<PackageReference Include="CoreCLR-NCalc" Version="3.1.246"/>
|
||||||
<PackageReference Include="Google.Apis.Urlshortener.v1" Version="1.41.1.138"/>
|
<PackageReference Include="Google.Apis.Urlshortener.v1" Version="1.41.1.138"/>
|
||||||
<PackageReference Include="Google.Apis.YouTube.v3" Version="1.68.0.3414"/>
|
<PackageReference Include="Google.Apis.YouTube.v3" Version="1.68.0.3414"/>
|
||||||
|
2
src/NadekoBot/NadekoBot.csproj.DotSettings
Normal file
2
src/NadekoBot/NadekoBot.csproj.DotSettings
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=modules_005Cadministration_005Cnotify_005Cmodels/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
@@ -3,8 +3,8 @@ namespace NadekoBot.Common;
|
|||||||
|
|
||||||
public enum AddRemove
|
public enum AddRemove
|
||||||
{
|
{
|
||||||
Add = int.MinValue,
|
Add = 0,
|
||||||
Remove = int.MinValue + 1,
|
Remove = 1,
|
||||||
Rem = int.MinValue + 1,
|
Rem = 1,
|
||||||
Rm = int.MinValue + 1
|
Rm = 1,
|
||||||
}
|
}
|
@@ -13,7 +13,7 @@ namespace NadekoBot.Common.Configs;
|
|||||||
public sealed partial class BotConfig : ICloneable<BotConfig>
|
public sealed partial class BotConfig : ICloneable<BotConfig>
|
||||||
{
|
{
|
||||||
[Comment("""DO NOT CHANGE""")]
|
[Comment("""DO NOT CHANGE""")]
|
||||||
public int Version { get; set; } = 8;
|
public int Version { get; set; } = 9;
|
||||||
|
|
||||||
[Comment("""
|
[Comment("""
|
||||||
Most commands, when executed, have a small colored line
|
Most commands, when executed, have a small colored line
|
||||||
@@ -82,6 +82,9 @@ public sealed partial class BotConfig : ICloneable<BotConfig>
|
|||||||
[Comment("""List of modules and commands completely blocked on the bot""")]
|
[Comment("""List of modules and commands completely blocked on the bot""")]
|
||||||
public BlockedConfig Blocked { get; set; }
|
public BlockedConfig Blocked { get; set; }
|
||||||
|
|
||||||
|
[Comment("""List of modules and commands blocked from usage in DMs on the bot""")]
|
||||||
|
public BlockedConfig DmBlocked { get; set; } = new();
|
||||||
|
|
||||||
[Comment("""Which string will be used to recognize the commands""")]
|
[Comment("""Which string will be used to recognize the commands""")]
|
||||||
public string Prefix { get; set; }
|
public string Prefix { get; set; }
|
||||||
|
|
||||||
|
@@ -157,6 +157,9 @@ public sealed class DoAsUserMessage : IUserMessage
|
|||||||
public MessageCallData? CallData
|
public MessageCallData? CallData
|
||||||
=> _msg.CallData;
|
=> _msg.CallData;
|
||||||
|
|
||||||
|
public IReadOnlyCollection<MessageSnapshot> ForwardedMessages
|
||||||
|
=> _msg.ForwardedMessages;
|
||||||
|
|
||||||
public Task ModifyAsync(Action<MessageProperties> func, RequestOptions? options = null)
|
public Task ModifyAsync(Action<MessageProperties> func, RequestOptions? options = null)
|
||||||
{
|
{
|
||||||
return _msg.ModifyAsync(func, options);
|
return _msg.ModifyAsync(func, options);
|
||||||
|
@@ -19,7 +19,6 @@ public abstract class NadekoModule : ModuleBase
|
|||||||
public INadekoInteractionService _inter { get; set; }
|
public INadekoInteractionService _inter { get; set; }
|
||||||
public IReplacementService repSvc { get; set; }
|
public IReplacementService repSvc { get; set; }
|
||||||
public IMessageSenderService _sender { get; set; }
|
public IMessageSenderService _sender { get; set; }
|
||||||
public BotConfigService _bcs { get; set; }
|
|
||||||
|
|
||||||
protected string prefix
|
protected string prefix
|
||||||
=> _cmdHandler.GetPrefix(ctx.Guild);
|
=> _cmdHandler.GetPrefix(ctx.Guild);
|
||||||
@@ -31,7 +30,7 @@ public abstract class NadekoModule : ModuleBase
|
|||||||
=> _sender.CreateEmbed(ctx.Guild?.Id);
|
=> _sender.CreateEmbed(ctx.Guild?.Id);
|
||||||
|
|
||||||
public ResponseBuilder Response()
|
public ResponseBuilder Response()
|
||||||
=> new ResponseBuilder(Strings, _bcs, (DiscordSocketClient)ctx.Client)
|
=> new ResponseBuilder(Strings, _sender, (DiscordSocketClient)ctx.Client)
|
||||||
.Context(ctx);
|
.Context(ctx);
|
||||||
|
|
||||||
protected override void BeforeExecute(CommandInfo command)
|
protected override void BeforeExecute(CommandInfo command)
|
||||||
|
@@ -9,31 +9,31 @@ public sealed class MessageSenderService : IMessageSenderService, INService
|
|||||||
|
|
||||||
public MessageSenderService(
|
public MessageSenderService(
|
||||||
IBotStrings bs,
|
IBotStrings bs,
|
||||||
BotConfigService bcs,
|
|
||||||
DiscordSocketClient client,
|
DiscordSocketClient client,
|
||||||
IGuildColorsService gcs)
|
IGuildColorsService gcs,
|
||||||
|
BotConfigService bcs)
|
||||||
{
|
{
|
||||||
_bs = bs;
|
_bs = bs;
|
||||||
_bcs = bcs;
|
|
||||||
_client = client;
|
_client = client;
|
||||||
_gcs = gcs;
|
_gcs = gcs;
|
||||||
|
_bcs = bcs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public ResponseBuilder Response(IMessageChannel channel)
|
public ResponseBuilder Response(IMessageChannel channel)
|
||||||
=> new ResponseBuilder(_bs, _bcs, _client)
|
=> new ResponseBuilder(_bs, this, _client)
|
||||||
.Channel(channel);
|
.Channel(channel);
|
||||||
|
|
||||||
public ResponseBuilder Response(ICommandContext ctx)
|
public ResponseBuilder Response(ICommandContext ctx)
|
||||||
=> new ResponseBuilder(_bs, _bcs, _client)
|
=> new ResponseBuilder(_bs, this, _client)
|
||||||
.Context(ctx);
|
.Context(ctx);
|
||||||
|
|
||||||
public ResponseBuilder Response(IUser user)
|
public ResponseBuilder Response(IUser user)
|
||||||
=> new ResponseBuilder(_bs, _bcs, _client)
|
=> new ResponseBuilder(_bs, this, _client)
|
||||||
.User(user);
|
.User(user);
|
||||||
|
|
||||||
public ResponseBuilder Response(SocketMessageComponent smc)
|
public ResponseBuilder Response(SocketMessageComponent smc)
|
||||||
=> new ResponseBuilder(_bs, _bcs, _client)
|
=> new ResponseBuilder(_bs, this, _client)
|
||||||
.Channel(smc.Channel);
|
.Channel(smc.Channel);
|
||||||
|
|
||||||
public NadekoEmbedBuilder CreateEmbed(ulong? guildId = null)
|
public NadekoEmbedBuilder CreateEmbed(ulong? guildId = null)
|
||||||
@@ -51,7 +51,7 @@ public class NadekoEmbedBuilder : EmbedBuilder
|
|||||||
var bcColors = bcsData.Data.Color;
|
var bcColors = bcsData.Data.Color;
|
||||||
_okColor = guildColors?.Ok ?? bcColors.Ok.ToDiscordColor();
|
_okColor = guildColors?.Ok ?? bcColors.Ok.ToDiscordColor();
|
||||||
_errorColor = guildColors?.Error ?? bcColors.Error.ToDiscordColor();
|
_errorColor = guildColors?.Error ?? bcColors.Error.ToDiscordColor();
|
||||||
_pendingColor = guildColors?.Pending ?? bcColors.Pending.ToDiscordColor();
|
_pendingColor = guildColors?.Warn ?? bcColors.Pending.ToDiscordColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
public EmbedBuilder WithOkColor()
|
public EmbedBuilder WithOkColor()
|
||||||
|
@@ -6,16 +6,17 @@ public sealed partial class ResponseBuilder
|
|||||||
{
|
{
|
||||||
private ICommandContext? ctx;
|
private ICommandContext? ctx;
|
||||||
private IMessageChannel? channel;
|
private IMessageChannel? channel;
|
||||||
|
private IUser? user;
|
||||||
|
private IUserMessage? msg;
|
||||||
|
|
||||||
private string? plainText;
|
private string? plainText;
|
||||||
private IReadOnlyCollection<EmbedBuilder>? embeds;
|
private IReadOnlyCollection<EmbedBuilder>? embeds;
|
||||||
private IUserMessage? msg;
|
|
||||||
private IUser? user;
|
|
||||||
private bool sanitizeMentions = true;
|
private bool sanitizeMentions = true;
|
||||||
private LocStr? locTxt;
|
private LocStr? locTxt;
|
||||||
private object[] locParams = [];
|
private object[] locParams = [];
|
||||||
private bool shouldReply = true;
|
private bool shouldReply = true;
|
||||||
private readonly IBotStrings _bs;
|
private readonly IBotStrings _bs;
|
||||||
private readonly BotConfigService _bcs;
|
private readonly IMessageSenderService _sender;
|
||||||
private EmbedBuilder? embedBuilder;
|
private EmbedBuilder? embedBuilder;
|
||||||
private NadekoInteractionBase? inter;
|
private NadekoInteractionBase? inter;
|
||||||
private Stream? fileStream;
|
private Stream? fileStream;
|
||||||
@@ -25,10 +26,10 @@ public sealed partial class ResponseBuilder
|
|||||||
|
|
||||||
public DiscordSocketClient Client { get; set; }
|
public DiscordSocketClient Client { get; set; }
|
||||||
|
|
||||||
public ResponseBuilder(IBotStrings bs, BotConfigService bcs, DiscordSocketClient client)
|
public ResponseBuilder(IBotStrings bs, IMessageSenderService sender, DiscordSocketClient client)
|
||||||
{
|
{
|
||||||
_bs = bs;
|
_bs = bs;
|
||||||
_bcs = bcs;
|
_sender = sender;
|
||||||
Client = client;
|
Client = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +198,7 @@ public sealed partial class ResponseBuilder
|
|||||||
string? url = null,
|
string? url = null,
|
||||||
string? footer = null)
|
string? footer = null)
|
||||||
{
|
{
|
||||||
var eb = new NadekoEmbedBuilder(_bcs)
|
var eb = _sender.CreateEmbed(ctx?.Guild?.Id ?? (channel as ITextChannel)?.GuildId ?? (user as IGuildUser)?.GuildId)
|
||||||
.WithDescription(text);
|
.WithDescription(text);
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(title))
|
if (!string.IsNullOrWhiteSpace(title))
|
||||||
|
8
src/NadekoBot/_common/Services/IUserService.cs
Normal file
8
src/NadekoBot/_common/Services/IUserService.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using NadekoBot.Db.Models;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Xp.Services;
|
||||||
|
|
||||||
|
public interface IUserService
|
||||||
|
{
|
||||||
|
Task<DiscordUser?> GetUserAsync(ulong userId);
|
||||||
|
}
|
@@ -108,7 +108,7 @@ public sealed class GuildColorsService : IReadyExecutor, IGuildColorsService, IN
|
|||||||
{
|
{
|
||||||
_colors[guildId] = _colors[guildId] with
|
_colors[guildId] = _colors[guildId] with
|
||||||
{
|
{
|
||||||
Pending = color?.ToDiscordColor()
|
Warn = color?.ToDiscordColor()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,8 +126,8 @@ public sealed class GuildColorsService : IReadyExecutor, IGuildColorsService, IN
|
|||||||
{
|
{
|
||||||
var colors = new Colors(
|
var colors = new Colors(
|
||||||
ConvertColor(color.OkColor),
|
ConvertColor(color.OkColor),
|
||||||
ConvertColor(color.ErrorColor),
|
ConvertColor(color.PendingColor),
|
||||||
ConvertColor(color.PendingColor));
|
ConvertColor(color.ErrorColor));
|
||||||
|
|
||||||
_colors.TryAdd(color.GuildId, colors);
|
_colors.TryAdd(color.GuildId, colors);
|
||||||
}
|
}
|
||||||
|
@@ -10,4 +10,4 @@ public interface IGuildColorsService
|
|||||||
Task SetPendingColor(ulong guildId, Rgba32? color);
|
Task SetPendingColor(ulong guildId, Rgba32? color);
|
||||||
}
|
}
|
||||||
|
|
||||||
public record struct Colors(Color? Ok, Color? Pending, Color? Error);
|
public record struct Colors(Color? Ok, Color? Warn, Color? Error);
|
24
src/NadekoBot/_common/Services/UserService.cs
Normal file
24
src/NadekoBot/_common/Services/UserService.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using LinqToDB.EntityFrameworkCore;
|
||||||
|
using NadekoBot.Db.Models;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Xp.Services;
|
||||||
|
|
||||||
|
public sealed class UserService : IUserService, INService
|
||||||
|
{
|
||||||
|
private readonly DbService _db;
|
||||||
|
|
||||||
|
public UserService(DbService db)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<DiscordUser?> GetUserAsync(ulong userId)
|
||||||
|
{
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
var user = await uow
|
||||||
|
.GetTable<DiscordUser>()
|
||||||
|
.FirstOrDefaultAsyncLinqToDB(u => u.UserId == userId);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
@@ -70,10 +70,10 @@ public sealed class BotConfigService : ConfigServiceBase<BotConfig>
|
|||||||
c.IgnoreOtherBots = true;
|
c.IgnoreOtherBots = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
if(data.Version < 8)
|
if(data.Version < 9)
|
||||||
ModifyConfig(c =>
|
ModifyConfig(c =>
|
||||||
{
|
{
|
||||||
c.Version = 8;
|
c.Version = 9;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace NadekoBot.Common.TypeReaders.Models;
|
namespace NadekoBot.Common.TypeReaders.Models;
|
||||||
|
|
||||||
public class StoopidTime
|
public class ParsedTimespan
|
||||||
{
|
{
|
||||||
private static readonly Regex _regex = new(
|
private static readonly Regex _regex = new(
|
||||||
@"^(?:(?<months>\d)mo)?(?:(?<weeks>\d{1,2})w)?(?:(?<days>\d{1,2})d)?(?:(?<hours>\d{1,4})h)?(?:(?<minutes>\d{1,5})m)?(?:(?<seconds>\d{1,6})s)?$",
|
@"^(?:(?<months>\d)mo)?(?:(?<weeks>\d{1,2})w)?(?:(?<days>\d{1,2})d)?(?:(?<hours>\d{1,4})h)?(?:(?<minutes>\d{1,5})m)?(?:(?<seconds>\d{1,6})s)?$",
|
||||||
@@ -11,9 +11,9 @@ public class StoopidTime
|
|||||||
public string Input { get; set; } = string.Empty;
|
public string Input { get; set; } = string.Empty;
|
||||||
public TimeSpan Time { get; set; } = default;
|
public TimeSpan Time { get; set; } = default;
|
||||||
|
|
||||||
private StoopidTime() { }
|
private ParsedTimespan() { }
|
||||||
|
|
||||||
public static StoopidTime FromInput(string input)
|
public static ParsedTimespan FromInput(string input)
|
||||||
{
|
{
|
||||||
var m = _regex.Match(input);
|
var m = _regex.Match(input);
|
||||||
|
|
||||||
@@ -52,10 +52,10 @@ public class StoopidTime
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static implicit operator TimeSpan?(StoopidTime? st)
|
public static implicit operator TimeSpan?(ParsedTimespan? st)
|
||||||
=> st?.Time;
|
=> st?.Time;
|
||||||
|
|
||||||
public static implicit operator StoopidTime(TimeSpan ts)
|
public static implicit operator ParsedTimespan(TimeSpan ts)
|
||||||
=> new()
|
=> new()
|
||||||
{
|
{
|
||||||
Input = ts.ToString(),
|
Input = ts.ToString(),
|
||||||
|
@@ -3,20 +3,20 @@ using NadekoBot.Common.TypeReaders.Models;
|
|||||||
|
|
||||||
namespace NadekoBot.Common.TypeReaders;
|
namespace NadekoBot.Common.TypeReaders;
|
||||||
|
|
||||||
public sealed class StoopidTimeTypeReader : NadekoTypeReader<StoopidTime>
|
public sealed class StoopidTimeTypeReader : NadekoTypeReader<ParsedTimespan>
|
||||||
{
|
{
|
||||||
public override ValueTask<TypeReaderResult<StoopidTime>> ReadAsync(ICommandContext context, string input)
|
public override ValueTask<TypeReaderResult<ParsedTimespan>> ReadAsync(ICommandContext context, string input)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(input))
|
if (string.IsNullOrWhiteSpace(input))
|
||||||
return new(TypeReaderResult.FromError<StoopidTime>(CommandError.Unsuccessful, "Input is empty."));
|
return new(TypeReaderResult.FromError<ParsedTimespan>(CommandError.Unsuccessful, "Input is empty."));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var time = StoopidTime.FromInput(input);
|
var time = ParsedTimespan.FromInput(input);
|
||||||
return new(TypeReaderResult.FromSuccess(time));
|
return new(TypeReaderResult.FromSuccess(time));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return new(TypeReaderResult.FromError<StoopidTime>(CommandError.Exception, ex.Message));
|
return new(TypeReaderResult.FromError<ParsedTimespan>(CommandError.Exception, ex.Message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -9,7 +9,7 @@ public static class SocketMessageComponentExtensions
|
|||||||
MsgType type,
|
MsgType type,
|
||||||
bool ephemeral = false)
|
bool ephemeral = false)
|
||||||
{
|
{
|
||||||
var embed = sender.CreateEmbed().WithDescription(text);
|
var embed = sender.CreateEmbed(ch.GuildId).WithDescription(text);
|
||||||
|
|
||||||
embed = (type switch
|
embed = (type switch
|
||||||
{
|
{
|
||||||
|
@@ -715,6 +715,8 @@ color:
|
|||||||
avatar:
|
avatar:
|
||||||
- avatar
|
- avatar
|
||||||
- av
|
- av
|
||||||
|
banner:
|
||||||
|
- banner
|
||||||
translate:
|
translate:
|
||||||
- translate
|
- translate
|
||||||
- trans
|
- trans
|
||||||
@@ -1039,6 +1041,12 @@ gamevoicechannel:
|
|||||||
- gvc
|
- gvc
|
||||||
shoplistadd:
|
shoplistadd:
|
||||||
- shoplistadd
|
- shoplistadd
|
||||||
|
dmcommand:
|
||||||
|
- dmcommand
|
||||||
|
- dmcmd
|
||||||
|
dmmodule:
|
||||||
|
- dmmodule
|
||||||
|
- dmmod
|
||||||
globalcommand:
|
globalcommand:
|
||||||
- globalcommand
|
- globalcommand
|
||||||
- gcmd
|
- gcmd
|
||||||
@@ -1089,9 +1097,6 @@ xpexclusionlist:
|
|||||||
xpexclude:
|
xpexclude:
|
||||||
- xpexclude
|
- xpexclude
|
||||||
- xpex
|
- xpex
|
||||||
xpnotify:
|
|
||||||
- xpnotify
|
|
||||||
- xpn
|
|
||||||
xpleveluprewards:
|
xpleveluprewards:
|
||||||
- xplvluprewards
|
- xplvluprewards
|
||||||
- xprews
|
- xprews
|
||||||
@@ -1121,6 +1126,8 @@ xpshopbuy:
|
|||||||
- xpshopbuy
|
- xpshopbuy
|
||||||
xpshopuse:
|
xpshopuse:
|
||||||
- xpshopuse
|
- xpshopuse
|
||||||
|
xplevelset:
|
||||||
|
- xplevelset
|
||||||
clubcreate:
|
clubcreate:
|
||||||
- clubcreate
|
- clubcreate
|
||||||
clubtransfer:
|
clubtransfer:
|
||||||
@@ -1533,4 +1540,23 @@ servercolorpending:
|
|||||||
- pending
|
- pending
|
||||||
- warn
|
- warn
|
||||||
- warning
|
- warning
|
||||||
- pend
|
- pend
|
||||||
|
minesweeper:
|
||||||
|
- minesweeper
|
||||||
|
- mw
|
||||||
|
temprole:
|
||||||
|
- temprole
|
||||||
|
notify:
|
||||||
|
- notify
|
||||||
|
- nfy
|
||||||
|
notifylist:
|
||||||
|
- notifylist
|
||||||
|
- notifyl
|
||||||
|
notifyclear:
|
||||||
|
- notifyclear
|
||||||
|
- notifyremove
|
||||||
|
- notifyrm
|
||||||
|
- notifclr
|
||||||
|
winlb:
|
||||||
|
- winlb
|
||||||
|
- wins
|
@@ -1,5 +1,5 @@
|
|||||||
# DO NOT CHANGE
|
# DO NOT CHANGE
|
||||||
version: 8
|
version: 9
|
||||||
# Most commands, when executed, have a small colored line
|
# Most commands, when executed, have a small colored line
|
||||||
# next to the response. The color depends whether the command
|
# next to the response. The color depends whether the command
|
||||||
# is completed, errored or in progress (pending)
|
# is completed, errored or in progress (pending)
|
||||||
@@ -78,6 +78,10 @@ helpText: |-
|
|||||||
blocked:
|
blocked:
|
||||||
commands: []
|
commands: []
|
||||||
modules: []
|
modules: []
|
||||||
|
# List of modules and commands blocked from usage in DMs on the bot
|
||||||
|
dmBlocked:
|
||||||
|
commands: []
|
||||||
|
modules: []
|
||||||
# Which string will be used to recognize the commands
|
# Which string will be used to recognize the commands
|
||||||
prefix: .
|
prefix: .
|
||||||
# Whether the bot will rotate through all specified statuses.
|
# Whether the bot will rotate through all specified statuses.
|
||||||
|
@@ -961,6 +961,56 @@
|
|||||||
"MuteMembers Server Permission"
|
"MuteMembers Server Permission"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".notify",
|
||||||
|
".nfy"
|
||||||
|
],
|
||||||
|
"Description": "Sends a message to the current channel once the specified event occurs.\nProvide no parameters to see all available events.",
|
||||||
|
"Usage": [
|
||||||
|
".notify levelup Congratulations to user %user.name% for reaching level %event.level%"
|
||||||
|
],
|
||||||
|
"Submodule": "NotifyCommands",
|
||||||
|
"Module": "Administration",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": [
|
||||||
|
"Bot Owner Only"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".notifylist",
|
||||||
|
".notifyl"
|
||||||
|
],
|
||||||
|
"Description": "Lists all active notifications in this server.",
|
||||||
|
"Usage": [
|
||||||
|
".notifylist"
|
||||||
|
],
|
||||||
|
"Submodule": "NotifyCommands",
|
||||||
|
"Module": "Administration",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": [
|
||||||
|
"Bot Owner Only"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".notifyclear",
|
||||||
|
".notifyremove",
|
||||||
|
".notifyrm",
|
||||||
|
".notifclr"
|
||||||
|
],
|
||||||
|
"Description": "Removes the specified notify event.",
|
||||||
|
"Usage": [
|
||||||
|
".notifyclear levelup"
|
||||||
|
],
|
||||||
|
"Submodule": "NotifyCommands",
|
||||||
|
"Module": "Administration",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": [
|
||||||
|
"Bot Owner Only"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".dpo"
|
".dpo"
|
||||||
@@ -1523,6 +1573,22 @@
|
|||||||
"Administrator Server Permission"
|
"Administrator Server Permission"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".temprole"
|
||||||
|
],
|
||||||
|
"Description": "Grants a user a temporary role for the specified number of time.\nThe role must exist and be lower in the role hierarchy than your highest role.",
|
||||||
|
"Usage": [
|
||||||
|
".temprole 15m @User Jail",
|
||||||
|
".temprole 7d @Newbie Trial Member"
|
||||||
|
],
|
||||||
|
"Submodule": "RoleCommands",
|
||||||
|
"Module": "Administration",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": [
|
||||||
|
"Administrator Server Permission"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".iam"
|
".iam"
|
||||||
@@ -1648,9 +1714,9 @@
|
|||||||
".sar excl",
|
".sar excl",
|
||||||
".sar tesar"
|
".sar tesar"
|
||||||
],
|
],
|
||||||
"Description": "Toggles whether self-assigned roles are exclusive. While enabled, users can only have one self-assignable role per group.",
|
"Description": "Toggles the sar group as exclusive.\nWhile enabled, users can only have one self-assignable role from that group.",
|
||||||
"Usage": [
|
"Usage": [
|
||||||
".sar exclusive"
|
".sar exclusive 1"
|
||||||
],
|
],
|
||||||
"Submodule": "sar",
|
"Submodule": "sar",
|
||||||
"Module": "Administration",
|
"Module": "Administration",
|
||||||
@@ -3274,6 +3340,21 @@
|
|||||||
"Options": null,
|
"Options": null,
|
||||||
"Requirements": []
|
"Requirements": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".winlb",
|
||||||
|
".wins"
|
||||||
|
],
|
||||||
|
"Description": "Shows the biggest wins leaderboard",
|
||||||
|
"Usage": [
|
||||||
|
".winlb",
|
||||||
|
".winlb 5"
|
||||||
|
],
|
||||||
|
"Submodule": "BetStatsCommands",
|
||||||
|
"Module": "Gambling",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".gamblestats",
|
".gamblestats",
|
||||||
@@ -3915,6 +3996,20 @@
|
|||||||
"Options": null,
|
"Options": null,
|
||||||
"Requirements": []
|
"Requirements": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".minesweeper",
|
||||||
|
".mw"
|
||||||
|
],
|
||||||
|
"Description": "Creates a spoiler-based minesweeper mini game.\nYou may specify the number of mines.",
|
||||||
|
"Usage": [
|
||||||
|
".minesweeper 15"
|
||||||
|
],
|
||||||
|
"Submodule": "Games",
|
||||||
|
"Module": "Games",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".acrophobia",
|
".acrophobia",
|
||||||
@@ -5522,6 +5617,38 @@
|
|||||||
"Bot Owner Only"
|
"Bot Owner Only"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".dmmodule",
|
||||||
|
".dmmod"
|
||||||
|
],
|
||||||
|
"Description": "Toggles whether a module can be used in DMs.",
|
||||||
|
"Usage": [
|
||||||
|
".dmmodule Gambling"
|
||||||
|
],
|
||||||
|
"Submodule": "GlobalPermissionCommands",
|
||||||
|
"Module": "Permissions",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": [
|
||||||
|
"Bot Owner Only"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".dmcommand",
|
||||||
|
".dmcmd"
|
||||||
|
],
|
||||||
|
"Description": "Toggles whether a command can be used in DMs.",
|
||||||
|
"Usage": [
|
||||||
|
".dmcommand .stats"
|
||||||
|
],
|
||||||
|
"Submodule": "GlobalPermissionCommands",
|
||||||
|
"Module": "Permissions",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": [
|
||||||
|
"Bot Owner Only"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".resetperms"
|
".resetperms"
|
||||||
@@ -5791,6 +5918,19 @@
|
|||||||
"Options": null,
|
"Options": null,
|
||||||
"Requirements": []
|
"Requirements": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".banner"
|
||||||
|
],
|
||||||
|
"Description": "Shows a mentioned person's banner.",
|
||||||
|
"Usage": [
|
||||||
|
".banner @Someone"
|
||||||
|
],
|
||||||
|
"Submodule": "Searches",
|
||||||
|
"Module": "Searches",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".wikia",
|
".wikia",
|
||||||
@@ -7778,21 +7918,6 @@
|
|||||||
"Options": null,
|
"Options": null,
|
||||||
"Requirements": []
|
"Requirements": []
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"Aliases": [
|
|
||||||
".xpnotify",
|
|
||||||
".xpn"
|
|
||||||
],
|
|
||||||
"Description": "Sets how the bot should notify you when you get a `server` or `global` level. This is a personal setting and affects only how you receive Global or Server level-up notifications. You can set `dm` (for the bot to send you a direct message), `channel` (to get notified in the channel you sent the last message in) or `none` to disable.",
|
|
||||||
"Usage": [
|
|
||||||
".xpnotify global dm",
|
|
||||||
".xpnotify server channel"
|
|
||||||
],
|
|
||||||
"Submodule": "Xp",
|
|
||||||
"Module": "Xp",
|
|
||||||
"Options": null,
|
|
||||||
"Requirements": []
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".xpexclude",
|
".xpexclude",
|
||||||
@@ -7854,6 +7979,21 @@
|
|||||||
"Options": null,
|
"Options": null,
|
||||||
"Requirements": []
|
"Requirements": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".xplevelset"
|
||||||
|
],
|
||||||
|
"Description": "Sets the level of the user you specify.",
|
||||||
|
"Usage": [
|
||||||
|
".xplevelset 10 @User"
|
||||||
|
],
|
||||||
|
"Submodule": "Xp",
|
||||||
|
"Module": "Xp",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": [
|
||||||
|
"Administrator Server Permission"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".xpadd"
|
".xpadd"
|
||||||
|
@@ -368,7 +368,7 @@ sargroupname:
|
|||||||
- group:
|
- group:
|
||||||
desc: "The group number"
|
desc: "The group number"
|
||||||
name:
|
name:
|
||||||
desc: "The name to assign."
|
desc: "The optional name to assign, clears the name if no name is provided."
|
||||||
sargroupdelete:
|
sargroupdelete:
|
||||||
desc: "Deletes a self-assignable role group"
|
desc: "Deletes a self-assignable role group"
|
||||||
ex:
|
ex:
|
||||||
@@ -377,11 +377,14 @@ sargroupdelete:
|
|||||||
- group:
|
- group:
|
||||||
desc: "The number of the group to delete."
|
desc: "The number of the group to delete."
|
||||||
sarexclusive:
|
sarexclusive:
|
||||||
desc: Toggles whether self-assigned roles are exclusive. While enabled, users can only have one self-assignable role per group.
|
desc: |-
|
||||||
|
Toggles the sar group as exclusive.
|
||||||
|
While enabled, users can only have one self-assignable role from that group.
|
||||||
ex:
|
ex:
|
||||||
- ''
|
- '1'
|
||||||
params:
|
params:
|
||||||
- { }
|
- group:
|
||||||
|
desc: "The number of the group to set exclusive."
|
||||||
sarrolelevelreq:
|
sarrolelevelreq:
|
||||||
desc: Set a level requirement on a self-assignable role.
|
desc: Set a level requirement on a self-assignable role.
|
||||||
ex:
|
ex:
|
||||||
@@ -2170,6 +2173,13 @@ avatar:
|
|||||||
params:
|
params:
|
||||||
- usr:
|
- usr:
|
||||||
desc: "The user whose avatar is being displayed."
|
desc: "The user whose avatar is being displayed."
|
||||||
|
banner:
|
||||||
|
desc: Shows a mentioned person's banner.
|
||||||
|
ex:
|
||||||
|
- '@Someone'
|
||||||
|
params:
|
||||||
|
- usr:
|
||||||
|
desc: "The user whose banner is being displayed."
|
||||||
translate:
|
translate:
|
||||||
desc: Translates text from the given language to the destination language.
|
desc: Translates text from the given language to the destination language.
|
||||||
ex:
|
ex:
|
||||||
@@ -3377,14 +3387,28 @@ globalcommand:
|
|||||||
- '{0}stats'
|
- '{0}stats'
|
||||||
params:
|
params:
|
||||||
- cmd:
|
- cmd:
|
||||||
desc: "The type of command or expression being toggled."
|
desc: "The command or expression being toggled."
|
||||||
globalmodule:
|
globalmodule:
|
||||||
desc: Toggles whether a module can be used on any server.
|
desc: Toggles whether a module can be used on any server.
|
||||||
ex:
|
ex:
|
||||||
- 'Gambling'
|
- 'Gambling'
|
||||||
params:
|
params:
|
||||||
- module:
|
- module:
|
||||||
desc: "The type of module or configuration information being toggled."
|
desc: "The module being toggled."
|
||||||
|
dmcommand:
|
||||||
|
desc: Toggles whether a command can be used in DMs.
|
||||||
|
ex:
|
||||||
|
- '{0}stats'
|
||||||
|
params:
|
||||||
|
- cmd:
|
||||||
|
desc: "The command or expression being toggled."
|
||||||
|
dmmodule:
|
||||||
|
desc: Toggles whether a module can be used in DMs.
|
||||||
|
ex:
|
||||||
|
- 'Gambling'
|
||||||
|
params:
|
||||||
|
- module:
|
||||||
|
desc: "The module being toggled."
|
||||||
globalpermlist:
|
globalpermlist:
|
||||||
desc: Lists global permissions set by the bot owner.
|
desc: Lists global permissions set by the bot owner.
|
||||||
ex:
|
ex:
|
||||||
@@ -3515,17 +3539,6 @@ xpexclude:
|
|||||||
desc: "The ID of the channel to exclude from XP tracking."
|
desc: "The ID of the channel to exclude from XP tracking."
|
||||||
channel:
|
channel:
|
||||||
desc: "The ID of the channel to exclude from XP tracking."
|
desc: "The ID of the channel to exclude from XP tracking."
|
||||||
xpnotify:
|
|
||||||
desc: Sets how the bot should notify you when you get a `server` or `global` level. This is a personal setting and affects only how you receive Global or Server level-up notifications. You can set `dm` (for the bot to send you a direct message), `channel` (to get notified in the channel you sent the last message in) or `none` to disable.
|
|
||||||
ex:
|
|
||||||
- global dm
|
|
||||||
- server channel
|
|
||||||
params:
|
|
||||||
- { }
|
|
||||||
- place:
|
|
||||||
desc: "The location where notifications should be sent, such as a specific channel or DM."
|
|
||||||
type:
|
|
||||||
desc: "The location where notifications for server and global level-ups should be sent."
|
|
||||||
xpleveluprewards:
|
xpleveluprewards:
|
||||||
desc: Shows currently set level up rewards.
|
desc: Shows currently set level up rewards.
|
||||||
ex:
|
ex:
|
||||||
@@ -4821,3 +4834,74 @@ servercolorpending:
|
|||||||
params:
|
params:
|
||||||
- color:
|
- color:
|
||||||
desc: "The hex of the color to set"
|
desc: "The hex of the color to set"
|
||||||
|
xplevelset:
|
||||||
|
desc: |-
|
||||||
|
Sets the level of the user you specify.
|
||||||
|
ex:
|
||||||
|
- '10 @User'
|
||||||
|
params:
|
||||||
|
- level:
|
||||||
|
desc: "The level to set the user to."
|
||||||
|
- user:
|
||||||
|
desc: "The user to set the level of."
|
||||||
|
temprole:
|
||||||
|
desc: |-
|
||||||
|
Grants a user a temporary role for the specified number of time.
|
||||||
|
The role must exist and be lower in the role hierarchy than your highest role.
|
||||||
|
ex:
|
||||||
|
- '15m @User Jail'
|
||||||
|
- '7d @Newbie Trial Member'
|
||||||
|
params:
|
||||||
|
- days:
|
||||||
|
desc: "The time after which the role is automatically removed."
|
||||||
|
- user:
|
||||||
|
desc: "The user to give the role to."
|
||||||
|
- role:
|
||||||
|
desc: "The role to give to the user."
|
||||||
|
minesweeper:
|
||||||
|
desc: |-
|
||||||
|
Creates a spoiler-based minesweeper mini game.
|
||||||
|
You may specify the number of mines.
|
||||||
|
ex:
|
||||||
|
- '15'
|
||||||
|
params:
|
||||||
|
- mines:
|
||||||
|
desc: "The number of mines to create."
|
||||||
|
notify:
|
||||||
|
desc: |-
|
||||||
|
Sends a message to the current channel once the specified event occurs.
|
||||||
|
Provide no parameters to see all available events.
|
||||||
|
ex:
|
||||||
|
- 'levelup Congratulations to user %user.name% for reaching level %event.level%'
|
||||||
|
params:
|
||||||
|
- { }
|
||||||
|
- event:
|
||||||
|
desc: "The event to notify on."
|
||||||
|
- event:
|
||||||
|
desc: "The event to notify on."
|
||||||
|
message:
|
||||||
|
desc: "The message to send."
|
||||||
|
notifylist:
|
||||||
|
desc: |-
|
||||||
|
Lists all active notifications in this server.
|
||||||
|
ex:
|
||||||
|
- ''
|
||||||
|
params:
|
||||||
|
- { }
|
||||||
|
notifyclear:
|
||||||
|
desc: |-
|
||||||
|
Removes the specified notify event.
|
||||||
|
ex:
|
||||||
|
- 'levelup'
|
||||||
|
params:
|
||||||
|
- event:
|
||||||
|
desc: "The notify event to clear."
|
||||||
|
winlb:
|
||||||
|
desc: |-
|
||||||
|
Shows the biggest wins leaderboard
|
||||||
|
ex:
|
||||||
|
- ''
|
||||||
|
- '5'
|
||||||
|
params:
|
||||||
|
- page:
|
||||||
|
desc: "The optional page to display."
|
@@ -758,6 +758,10 @@
|
|||||||
"gcmd_remove": "Command {0} has been enabled on all servers.",
|
"gcmd_remove": "Command {0} has been enabled on all servers.",
|
||||||
"gmod_add": "Module {0} has been disabled on all servers.",
|
"gmod_add": "Module {0} has been disabled on all servers.",
|
||||||
"gmod_remove": "Module {0} has been enabled on all servers.",
|
"gmod_remove": "Module {0} has been enabled on all servers.",
|
||||||
|
"dmmod_add": "Module {0} has been disabled in bot DMs.",
|
||||||
|
"dmmod_remove": "Module {0} has been enabled in bot DMs.",
|
||||||
|
"dmcmd_add": "Command {0} has been disabled in bot DMs.",
|
||||||
|
"dmcmd_remove": "Command {0} has been enabled in bot DMs.",
|
||||||
"lgp_none": "No blocked commands or modules.",
|
"lgp_none": "No blocked commands or modules.",
|
||||||
"cant_read_or_send": "You can't read from or send messages to that channel.",
|
"cant_read_or_send": "You can't read from or send messages to that channel.",
|
||||||
"quotes_notfound": "No quotes found matching the quote ID specified.",
|
"quotes_notfound": "No quotes found matching the quote ID specified.",
|
||||||
@@ -1083,7 +1087,7 @@
|
|||||||
"giveaway_starting": "Starting giveaway...",
|
"giveaway_starting": "Starting giveaway...",
|
||||||
"winner": "Winner",
|
"winner": "Winner",
|
||||||
"giveaway_list": "List of active giveways",
|
"giveaway_list": "List of active giveways",
|
||||||
"todo_list_empty": "Your todo list is empty." ,
|
"todo_list_empty": "Your todo list is empty.",
|
||||||
"todo_list": "Todo List",
|
"todo_list": "Todo List",
|
||||||
"todo_stats": "{0} items | {1} completed | {2} remaining",
|
"todo_stats": "{0} items | {1} completed | {2} remaining",
|
||||||
"todo_add_max_limit": "You'reached the maximum amount of todos you can have.",
|
"todo_add_max_limit": "You'reached the maximum amount of todos you can have.",
|
||||||
@@ -1098,7 +1102,7 @@
|
|||||||
"search_results": "Search results",
|
"search_results": "Search results",
|
||||||
"queue_search_results": "Type the number of the search result to queue up that track.",
|
"queue_search_results": "Type the number of the search result to queue up that track.",
|
||||||
"overloads": "Overloads",
|
"overloads": "Overloads",
|
||||||
"honeypot_on": "Honeypot enabled on this channel." ,
|
"honeypot_on": "Honeypot enabled on this channel.",
|
||||||
"honeypot_off": "Honeypot disabled.",
|
"honeypot_off": "Honeypot disabled.",
|
||||||
"afk_set": "AFK message set. Type a message in any channel to clear.",
|
"afk_set": "AFK message set. Type a message in any channel to clear.",
|
||||||
"rero_message_not_found": "The specified message wasn't found. Make sure you've specified the message from this channel.",
|
"rero_message_not_found": "The specified message wasn't found. Make sure you've specified the message from this channel.",
|
||||||
@@ -1111,7 +1115,7 @@
|
|||||||
"nc_hint": "Use `{0}nczoom x y` command to zoom in. Image is {1}x{2} pixels.",
|
"nc_hint": "Use `{0}nczoom x y` command to zoom in. Image is {1}x{2} pixels.",
|
||||||
"nc_insuff_payment": "Invalid payment.",
|
"nc_insuff_payment": "Invalid payment.",
|
||||||
"invalid_img_size": "Image must to be {0}x{1} pixels.",
|
"invalid_img_size": "Image must to be {0}x{1} pixels.",
|
||||||
"no_attach_found": "No attachment found. Please send the image along with this command." ,
|
"no_attach_found": "No attachment found. Please send the image along with this command.",
|
||||||
"trfl_enabled": "Flag translation enabled on this channel. Reacting to a message with a flag will translate it to that language.",
|
"trfl_enabled": "Flag translation enabled on this channel. Reacting to a message with a flag will translate it to that language.",
|
||||||
"trfl_disabled": "Flag translation disabled.",
|
"trfl_disabled": "Flag translation disabled.",
|
||||||
"rakeback_claimed": "You've claimed {0} as rakeback!",
|
"rakeback_claimed": "You've claimed {0} as rakeback!",
|
||||||
@@ -1121,10 +1125,10 @@
|
|||||||
"self_assign_group_role_req": "Users can now self-assign a role from group {0} only if they have {1} role.",
|
"self_assign_group_role_req": "Users can now self-assign a role from group {0} only if they have {1} role.",
|
||||||
"sar_group_not_found": "Group with that number doesn't exist.",
|
"sar_group_not_found": "Group with that number doesn't exist.",
|
||||||
"sar_group_deleted": "Group {0} deleted.",
|
"sar_group_deleted": "Group {0} deleted.",
|
||||||
"choose_one": "Choose one" ,
|
"choose_one": "Choose one",
|
||||||
"requires_role": "Requires role: {0}",
|
"requires_role": "Requires role: {0}",
|
||||||
"invalid_message_id": "Invalid Message Id.",
|
"invalid_message_id": "Invalid Message Id.",
|
||||||
"invalid_message_link": "The message link must be from this server.",
|
"invalid_message_link": "The message link must be this Bot's message. The bot can't add buttons to other users' messages.",
|
||||||
"btnrole_message_max": "Limit reached. You may have up to 25 button roles per message.",
|
"btnrole_message_max": "Limit reached. You may have up to 25 button roles per message.",
|
||||||
"btnrole_not_found": "No button role found on that message.",
|
"btnrole_not_found": "No button role found on that message.",
|
||||||
"btnrole_none": "There are no button roles on this page.",
|
"btnrole_none": "There are no button roles on this page.",
|
||||||
@@ -1135,5 +1139,25 @@
|
|||||||
"no_last_queued_found": "No last queued track found.",
|
"no_last_queued_found": "No last queued track found.",
|
||||||
"wrongsong_success": "Oops! Wrong song removed: {0}",
|
"wrongsong_success": "Oops! Wrong song removed: {0}",
|
||||||
"server_not_found": "Server not found.",
|
"server_not_found": "Server not found.",
|
||||||
"server_color_set": "Successfully set a new server color."
|
"server_color_set": "Successfully set a new server color.",
|
||||||
|
"lasts_until": "Lasts Until",
|
||||||
|
"winners_count": "Winners",
|
||||||
|
"level_set": "Level of user {0} set to {1} on this server.",
|
||||||
|
"temp_role_added": "User {0} has been given {1} role temporarily. The role expires {2}",
|
||||||
|
"user_afk": "User {0} is AFK.",
|
||||||
|
"notify_on": "Notification message will be sent in {0} channel when {1} event triggers.",
|
||||||
|
"notify_off": "Notification message will no longer be sent when {0} event triggers.",
|
||||||
|
"notify_none": "No notifications on this page.",
|
||||||
|
"notify_msg_not_set": "Notification message is not set for this event.",
|
||||||
|
"notify_list": "Notify List",
|
||||||
|
"notify_type": "Type",
|
||||||
|
"notify_msg": "Notify Message",
|
||||||
|
"notify_available": "List of available notify events",
|
||||||
|
"notify_desc_levelup": "Triggers when a user levels up on this server.",
|
||||||
|
"notify_desc_protection": "Triggers when antialt, antispam or antiraid is triggered.",
|
||||||
|
"notify_desc_addrolerew": "Triggers when a user gets a role as a reward for reaching a level (xprew).",
|
||||||
|
"notify_desc_removerolerew": "Triggers when a user loses a role as a reward for reaching a level (xprew).",
|
||||||
|
"notify_desc_not_found": "No description found for this notify event. Please report this.",
|
||||||
|
"winlb": "Biggest Wins Leaderboard",
|
||||||
|
"no_banner": "No banner set."
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user