mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-11 09:48:26 -04:00
Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
32fc8b6e03 | ||
|
297e2fde0e | ||
|
729f26caab | ||
|
4b12e4e923 | ||
|
12f4ce7f2a | ||
|
00944e08c3 | ||
|
569abd7194 | ||
|
474a1db41d | ||
|
0f6255947e | ||
|
f68f219a25 | ||
|
8f16b11d02 | ||
|
df5eced904 | ||
|
1dcd158f43 | ||
|
757c9b564d | ||
|
07cef3eb5e | ||
|
85c525e19b |
317
CHANGELOG.md
317
CHANGELOG.md
@@ -2,6 +2,51 @@
|
|||||||
|
|
||||||
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.1.18] - 02.11.2024
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added `.translateflags` / `.trfl` command.
|
||||||
|
- Enable on a per-channel basis.
|
||||||
|
- Reacting on any message in that channel with a flag emoji will post the translation of that message in the
|
||||||
|
language of that country
|
||||||
|
- 5 second cooldown per user
|
||||||
|
- The message can only be translated once per language (counter resets every 24h)
|
||||||
|
- `.timely` now has a button. Togglable via `.conf gambling` it's called pass because previously it was a captcha, but captchas are too annoying
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
|
||||||
|
- [public bot] Patreon reward bonus for flowers reduced. Timely bonuses stay the same
|
||||||
|
- discriminators removed from the databases. All users who had ???? as discriminator have been renamed to ??username.
|
||||||
|
- all new unknown users will have ??Unknown as their name
|
||||||
|
- Flower currency generation will now have a strikeout to try combat the pickbots. This is the weakest but easiest protection to implement. There may be more options in the future
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
- nunchi join game message is now ok color instead of error color
|
||||||
|
|
||||||
|
## [5.1.17] - 29.10.2024
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- fix: Bot will now not accept .aar Role if that Role is higher than or equal to bot's role. Previously bot would just
|
||||||
|
fail silently, now there is a proper error message.
|
||||||
|
|
||||||
|
## [5.1.16] - 28.10.2024
|
||||||
|
|
||||||
|
## Added
|
||||||
|
|
||||||
|
- Added .ncanvas and related commands.
|
||||||
|
- You can set pixel colors (and text) on a 500x350 canvas, pepega version of r/place
|
||||||
|
- You use currency to set pixels.
|
||||||
|
- Commands:
|
||||||
|
- see the entire canvas: `.nc`
|
||||||
|
- zoom: `.ncz <pos>` or `.ncz x y`
|
||||||
|
- set pixel: `.ncsp <pos> <color> <text?>`
|
||||||
|
- get pixel: `.ncp <pos>`
|
||||||
|
- Owners can use .ncsetimg to set a starting image, use `.h .setimg` for instructions
|
||||||
|
- Owners can reset the whole canvas via `.ncreset`
|
||||||
|
|
||||||
## [5.1.15] - 21.10.2024
|
## [5.1.15] - 21.10.2024
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
@@ -55,7 +100,8 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added `%user.displayname%` placeholder. It will show users nickname, if there is one, otherwise it will show the username.
|
- Added `%user.displayname%` placeholder. It will show users nickname, if there is one, otherwise it will show the
|
||||||
|
username.
|
||||||
- Nickname won't be shown in bye messages.
|
- Nickname won't be shown in bye messages.
|
||||||
- Added initial version of grpc api. Beta
|
- Added initial version of grpc api. Beta
|
||||||
|
|
||||||
@@ -88,17 +134,20 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
- Fixed `.greettest`, and other `.*test` commands if you didn't have them enabled.
|
- Fixed `.greettest`, and other `.*test` commands if you didn't have them enabled.
|
||||||
- Fixed `.greetdmtest` sending messages twice.
|
- Fixed `.greetdmtest` sending messages twice.
|
||||||
- Fixed a serious bug which caused greet messages to be jumbled up, and wrong ones to be sent for the wrong events.
|
- Fixed a serious bug which caused greet messages to be jumbled up, and wrong ones to be sent for the wrong events.
|
||||||
- There is no database issue, all greet messages are safe, the cache was caching any setting every 3 seconds with no regard for the type of the event
|
- There is no database issue, all greet messages are safe, the cache was caching any setting every 3 seconds with no
|
||||||
|
regard for the type of the event
|
||||||
- This also caused `.greetdm` messages to not be sent if `.greet` is enabled
|
- This also caused `.greetdm` messages to not be sent if `.greet` is enabled
|
||||||
- This bug was introduced in 5.1.8. PLEASE UPDATE if you are on 5.1.8
|
- This bug was introduced in 5.1.8. PLEASE UPDATE if you are on 5.1.8
|
||||||
- Selfhosters only: Fixed medusa dependency loading
|
- Selfhosters only: Fixed medusa dependency loading
|
||||||
- Note: Make sure to not publish any other DLLs besides the ones you are sure you will need, as there can be version conflicts which didn't happen before.
|
- Note: Make sure to not publish any other DLLs besides the ones you are sure you will need, as there can be version
|
||||||
|
conflicts which didn't happen before.
|
||||||
|
|
||||||
## [5.1.8] - 19.09.2024
|
## [5.1.8] - 19.09.2024
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added `.leaveunkeptservers` which will make the bot leave all servers on all shards whose owners didn't run `.keep` command.
|
- Added `.leaveunkeptservers` which will make the bot leave all servers on all shards whose owners didn't run `.keep`
|
||||||
|
command.
|
||||||
- This is a dangerous and irreversible command, don't use it. Meant for use on the public bot.
|
- This is a dangerous and irreversible command, don't use it. Meant for use on the public bot.
|
||||||
- `.adpl` now supports custom statuses (you no longer need to specify Playing, Watching, etc...)
|
- `.adpl` now supports custom statuses (you no longer need to specify Playing, Watching, etc...)
|
||||||
|
|
||||||
@@ -110,7 +159,8 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
- `.quotesearch` / `.qse` is now paginated for easier searching
|
- `.quotesearch` / `.qse` is now paginated for easier searching
|
||||||
- `.whosplaying` is now paginated
|
- `.whosplaying` is now paginated
|
||||||
- `.img` is now paginated
|
- `.img` is now paginated
|
||||||
- `.setgame` renamed to`.setactivity` and now supports custom text activity. You don't have to specify playing, listening etc before the activity
|
- `.setgame` renamed to`.setactivity` and now supports custom text activity. You don't have to specify playing,
|
||||||
|
listening etc before the activity
|
||||||
- Clarified and added some embed / placeholder links to command help where needed
|
- Clarified and added some embed / placeholder links to command help where needed
|
||||||
- dev: A lot of code cleanup and internal improvements
|
- dev: A lot of code cleanup and internal improvements
|
||||||
|
|
||||||
@@ -151,6 +201,7 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
- Possible fix for `.remind` timestamp
|
- Possible fix for `.remind` timestamp
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- Removed old bloat / semi broken / dumb commands
|
- Removed old bloat / semi broken / dumb commands
|
||||||
- `.memelist` / `.memegen` (too inconvenient to use)
|
- `.memelist` / `.memegen` (too inconvenient to use)
|
||||||
- `.activity` (useless owner-only command)
|
- `.activity` (useless owner-only command)
|
||||||
@@ -177,7 +228,8 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
- Updated some bet descriptions to include 'all' 'half' usage instructions
|
- Updated some bet descriptions to include 'all' 'half' usage instructions
|
||||||
- Updated some command strings
|
- Updated some command strings
|
||||||
- dev: Vastly simplified medusa creation using dotnet templates, docs updated
|
- dev: Vastly simplified medusa creation using dotnet templates, docs updated
|
||||||
- Slight refactor of .wiki, time, .catfact, .wikia, .define, .bible and .quran commands, no significant change in functionality
|
- Slight refactor of .wiki, time, .catfact, .wikia, .define, .bible and .quran commands, no significant change in
|
||||||
|
functionality
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
@@ -185,6 +237,7 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
- You can once again disable cleverbot responses using fake 'cleverbot:response' module name in permission commands
|
- You can once again disable cleverbot responses using fake 'cleverbot:response' module name in permission commands
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- Removed .rip command
|
- Removed .rip command
|
||||||
|
|
||||||
## [5.1.4] - 13.07.2024
|
## [5.1.4] - 13.07.2024
|
||||||
@@ -193,8 +246,10 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
|
|
||||||
- Added `.coins` command which lists top 10 cryptos ordered by marketcap
|
- Added `.coins` command which lists top 10 cryptos ordered by marketcap
|
||||||
- Added Clubs rank in the leaderboard to `.clubinfo`
|
- Added Clubs rank in the leaderboard to `.clubinfo`
|
||||||
- Bot owners can now check other people's bank balance (Not server owners, only bot owner, the person who is hosting the bot)
|
- Bot owners can now check other people's bank balance (Not server owners, only bot owner, the person who is hosting the
|
||||||
- You can now send multiple waifu gifts at once to waifus. For example `.waifugift 3xRose @user` will give that user 3 roses
|
bot)
|
||||||
|
- You can now send multiple waifu gifts at once to waifus. For example `.waifugift 3xRose @user` will give that user 3
|
||||||
|
roses
|
||||||
- The format is `<NUMBER>x<ITEM>`, no spaces
|
- The format is `<NUMBER>x<ITEM>`, no spaces
|
||||||
- Added `.boosttest` command
|
- Added `.boosttest` command
|
||||||
- Added support for any openai compatible api for the chatterbot feature change:
|
- Added support for any openai compatible api for the chatterbot feature change:
|
||||||
@@ -220,7 +275,8 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Replying to the bot's message in the channel where chatterbot is enabled will also trigger the ai response, as if you pinged the bot. This only works for chatterbot, but not for nadeko ai command prompts
|
- Replying to the bot's message in the channel where chatterbot is enabled will also trigger the ai response, as if you
|
||||||
|
pinged the bot. This only works for chatterbot, but not for nadeko ai command prompts
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
@@ -253,28 +309,33 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added `.prompt` command, Nadeko Ai Assistant
|
- Added `.prompt` command, Nadeko Ai Assistant
|
||||||
- You can send natural language questions, queries or execute commands. For example "@Nadeko how's the weather in paris" and it will return `.we Paris` and run it for you.
|
- You can send natural language questions, queries or execute commands. For example "@Nadeko how's the weather in
|
||||||
- In case the bot can't execute a command using your query, It will fall back to your chatter bot, in case you have it enabled in data/games.yml. (Cleverbot or chatgpt)
|
paris" and it will return `.we Paris` and run it for you.
|
||||||
|
- In case the bot can't execute a command using your query, It will fall back to your chatter bot, in case you have
|
||||||
|
it enabled in data/games.yml. (Cleverbot or chatgpt)
|
||||||
- (It's far from perfect so please don't ask the bot to do dangerous things like banning or pruning)
|
- (It's far from perfect so please don't ask the bot to do dangerous things like banning or pruning)
|
||||||
- Requires Patreon subscription, after which you'll be able to run it on global @Nadeko bot.
|
- Requires Patreon subscription, after which you'll be able to run it on global @Nadeko bot.
|
||||||
- Selfhosters: If you're selfhosting, you also will need to acquire the api key from <https://dashy.nadeko.bot/me> after pledging on patreon and put it in nadekoAiToken in creds.yml
|
- Selfhosters: If you're selfhosting, you also will need to acquire the api key
|
||||||
|
from <https://dashy.nadeko.bot/me> after pledging on patreon and put it in nadekoAiToken in creds.yml
|
||||||
- Added support for `gpt-4o` in `data/games.yml`
|
- Added support for `gpt-4o` in `data/games.yml`
|
||||||
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Remind will now show a timestamp tag for durations
|
- Remind will now show a timestamp tag for durations
|
||||||
- Only `Gpt35Turbo` and `Gpt4o` are valid inputs in games.yml now
|
- Only `Gpt35Turbo` and `Gpt4o` are valid inputs in games.yml now
|
||||||
- `data/patron.yml` changed. It now has limits. The entire feature limit system has been reworked. Your previous settings will be reset
|
- `data/patron.yml` changed. It now has limits. The entire feature limit system has been reworked. Your previous
|
||||||
|
settings will be reset
|
||||||
- A lot of updates to bot strings (thanks Ene)
|
- A lot of updates to bot strings (thanks Ene)
|
||||||
- Improved cleanup command to delete a lot more data once cleanup is ran, not only guild configs (please don't use this command unless you have your database bakced up and you know 100% what you're doing)
|
- Improved cleanup command to delete a lot more data once cleanup is ran, not only guild configs (please don't use this
|
||||||
|
command unless you have your database bakced up and you know 100% what you're doing)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed xp bg buy button not working, and possibly some other buttons too
|
- Fixed xp bg buy button not working, and possibly some other buttons too
|
||||||
- Fixed shopbuy %user% placeholders and updated help text
|
- Fixed shopbuy %user% placeholders and updated help text
|
||||||
- All .feed overloads should now work"
|
- All .feed overloads should now work"
|
||||||
- `.xpexclude` should will now work with forums too. If you exclude a forum you won't be able to gain xp in any of the threads.
|
- `.xpexclude` should will now work with forums too. If you exclude a forum you won't be able to gain xp in any of the
|
||||||
|
threads.
|
||||||
- Fixed remind not showing correct time (thx cata)
|
- Fixed remind not showing correct time (thx cata)
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
@@ -288,8 +349,10 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
|
|
||||||
- Added `.setserverbanner` and `.setservericon` commands (thx cata)
|
- Added `.setserverbanner` and `.setservericon` commands (thx cata)
|
||||||
- Added overloads section to `.h command` which will show you all versions of command usage with param names
|
- Added overloads section to `.h command` which will show you all versions of command usage with param names
|
||||||
- You can now check commands for submodules, for example `.cmds SelfAssignedRoles` will show brief help for each of the commands in that submodule
|
- You can now check commands for submodules, for example `.cmds SelfAssignedRoles` will show brief help for each of the
|
||||||
- Added dropdown menus for .mdls and .cmds (both module and group versions) which will give you the option to see more detailed help for each specific module, group or command respectively
|
commands in that submodule
|
||||||
|
- Added dropdown menus for .mdls and .cmds (both module and group versions) which will give you the option to see more
|
||||||
|
detailed help for each specific module, group or command respectively
|
||||||
- Self-Hosters only:
|
- Self-Hosters only:
|
||||||
- Added a dangerous cleanup command that you don't have to know about
|
- Added a dangerous cleanup command that you don't have to know about
|
||||||
|
|
||||||
@@ -308,7 +371,8 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- `.streammessage` will once again be able to mention anyone (as long as the user setting the message has the permission to mention everyone)
|
- `.streammessage` will once again be able to mention anyone (as long as the user setting the message has the permission
|
||||||
|
to mention everyone)
|
||||||
- `.streammsgall` fixed
|
- `.streammsgall` fixed
|
||||||
- `.xplb` and `.xpglb` pagination fixed
|
- `.xplb` and `.xpglb` pagination fixed
|
||||||
- Fixed page number when the total number of elements is unknown
|
- Fixed page number when the total number of elements is unknown
|
||||||
@@ -337,15 +401,19 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added `.shopadd command` You can now sell commands in the shop. The command will execute as if you were the one running it when someone buys it
|
- Added `.shopadd command` You can now sell commands in the shop. The command will execute as if you were the one
|
||||||
|
running it when someone buys it
|
||||||
- type `.h .shopadd` for more info
|
- type `.h .shopadd` for more info
|
||||||
- Added `.stickyroles` Users leaving the server will have their roles saved to the database and reapplied if they rejoin within 30 days.
|
- Added `.stickyroles` Users leaving the server will have their roles saved to the database and reapplied if they rejoin
|
||||||
|
within 30 days.
|
||||||
- Giveaway commands
|
- Giveaway commands
|
||||||
- `.ga start <duration> <text>` starts the giveway with the specified duration and message (prize). You may have up to 5 giveaways on the server at once
|
- `.ga start <duration> <text>` starts the giveway with the specified duration and message (prize). You may have up
|
||||||
|
to 5 giveaways on the server at once
|
||||||
- `.ga end <id>` prematurely ends the giveaway and selects a winner
|
- `.ga end <id>` prematurely ends the giveaway and selects a winner
|
||||||
- `.ga cancel <id>` cancels the giveaway and doesn't select a winner
|
- `.ga cancel <id>` cancels the giveaway and doesn't select a winner
|
||||||
- `.ga list` lists active giveaways on the current server
|
- `.ga list` lists active giveaways on the current server
|
||||||
- `.ga reroll <id>` rerolls the winner on the completed giveaway. This only works for 24 hours after the giveaway has ended, or until the bot restarts.
|
- `.ga reroll <id>` rerolls the winner on the completed giveaway. This only works for 24 hours after the giveaway
|
||||||
|
has ended, or until the bot restarts.
|
||||||
- Users can join the giveaway by adding a :tada: reaction
|
- Users can join the giveaway by adding a :tada: reaction
|
||||||
- Added Todo Commands
|
- Added Todo Commands
|
||||||
- `.todo add <name>` - adds a new todo
|
- `.todo add <name>` - adds a new todo
|
||||||
@@ -355,7 +423,8 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
- `.todo edit <id> <new message>` - edits a todo item message
|
- `.todo edit <id> <new message>` - edits a todo item message
|
||||||
- `.todo show <id>` - Shows the text of the specified todo item
|
- `.todo show <id>` - Shows the text of the specified todo item
|
||||||
- In addition to that, there are also Todo archive commands
|
- In addition to that, there are also Todo archive commands
|
||||||
- `.todo archive add <name>` - adds all current todos (completed and not completed) to the archived list, your current todo list will become cleared
|
- `.todo archive add <name>` - adds all current todos (completed and not completed) to the archived list, your
|
||||||
|
current todo list will become cleared
|
||||||
- `.todo archive list` - lists all your archived todo lists
|
- `.todo archive list` - lists all your archived todo lists
|
||||||
- `.todo archive show <id>` - shows the todo items from one of your archived lists
|
- `.todo archive show <id>` - shows the todo items from one of your archived lists
|
||||||
- `.todo archive delete <id>` - deletes and archived todo list
|
- `.todo archive delete <id>` - deletes and archived todo list
|
||||||
@@ -374,8 +443,10 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Users who have manage messages perm in the channel will now be excluded from link and invite filtering (`.sfi` and `.sfl`)
|
- Users who have manage messages perm in the channel will now be excluded from link and invite filtering (`.sfi`
|
||||||
- `.send` command should work consistently and correctly now. You can have targets from other shards too. The usage has been changed. refer to `.h .send` for more info
|
and `.sfl`)
|
||||||
|
- `.send` command should work consistently and correctly now. You can have targets from other shards too. The usage has
|
||||||
|
been changed. refer to `.h .send` for more info
|
||||||
- `.serverinfo` no longer takes a server name. It only takes an id or no arguments
|
- `.serverinfo` no longer takes a server name. It only takes an id or no arguments
|
||||||
- You can now target a different channel with .repeat
|
- You can now target a different channel with .repeat
|
||||||
- `.cmds <module name>`, `.cmds <group name` and `.mdls` looks better
|
- `.cmds <module name>`, `.cmds <group name` and `.mdls` looks better
|
||||||
@@ -387,7 +458,8 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- `.feed` should now correctly accept (and show) the message which can be passed as the third parameter
|
- `.feed` should now correctly accept (and show) the message which can be passed as the third parameter
|
||||||
- `.say` will now correctly report errors if the user or the bot don't have sufficent perms to send a message in the targeted channel
|
- `.say` will now correctly report errors if the user or the bot don't have sufficent perms to send a message in the
|
||||||
|
targeted channel
|
||||||
- Fixed `.invitelist` not paginating correctly
|
- Fixed `.invitelist` not paginating correctly
|
||||||
- `.serverinfo` will now correctly work for other shards
|
- `.serverinfo` will now correctly work for other shards
|
||||||
- `.send` will now correctly work for other shards
|
- `.send` will now correctly work for other shards
|
||||||
@@ -405,15 +477,17 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
## [4.3.22] - 23.04.2024
|
## [4.3.22] - 23.04.2024
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added `.setbanner` command (thx cata)
|
- Added `.setbanner` command (thx cata)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Fixed pagination error due to a missing emoji
|
|
||||||
|
|
||||||
|
- Fixed pagination error due to a missing emoji
|
||||||
|
|
||||||
## [4.3.21] - 19.04.2024
|
## [4.3.21] - 19.04.2024
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Possible fix for a duplicate in `.h bank`
|
- Possible fix for a duplicate in `.h bank`
|
||||||
- Fixed `.stock` command
|
- Fixed `.stock` command
|
||||||
- Fixed `.clubapply` and `.clubaccept`
|
- Fixed `.clubapply` and `.clubaccept`
|
||||||
@@ -422,14 +496,17 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
## [4.3.20] - 20.01.2024
|
## [4.3.20] - 20.01.2024
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed `.config searches followedStreams.maxCount` not working
|
- Fixed `.config searches followedStreams.maxCount` not working
|
||||||
|
|
||||||
## [4.3.19] - 20.01.2024
|
## [4.3.19] - 20.01.2024
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added `followedStreams.maxCount` to `searches.yml` which lets bot owners change the default of 10 per server
|
- Added `followedStreams.maxCount` to `searches.yml` which lets bot owners change the default of 10 per server
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Improvements to GPT ChatterBot (thx alexandra)
|
- Improvements to GPT ChatterBot (thx alexandra)
|
||||||
- Add a personality prompt to tweak the way chatgpt bot behaves
|
- Add a personality prompt to tweak the way chatgpt bot behaves
|
||||||
- Added Chat history support to chatgpt ChatterBot
|
- Added Chat history support to chatgpt ChatterBot
|
||||||
@@ -452,7 +529,8 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
- Fixed `icon_url` when using `.showembed`
|
- Fixed `icon_url` when using `.showembed`
|
||||||
- Fixed `.quoteshow` not showing sometimes (thx Cata)
|
- Fixed `.quoteshow` not showing sometimes (thx Cata)
|
||||||
- Notifications will no longer be sent if dms are off when using `.give`
|
- Notifications will no longer be sent if dms are off when using `.give`
|
||||||
- Users should no longer be able to apply to clubs while in a club already (especially not to the same club they're already in)
|
- Users should no longer be able to apply to clubs while in a club already (especially not to the same club they're
|
||||||
|
already in)
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
@@ -551,10 +629,12 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- .meload and .meunload are now case sensitive. Previously loaded medusae may need to be reloaded or data/medusae/medusa.yml may need to be edited manually
|
- .meload and .meunload are now case sensitive. Previously loaded medusae may need to be reloaded or
|
||||||
|
data/medusae/medusa.yml may need to be edited manually
|
||||||
- Several club related command have their error messages improved
|
- Several club related command have their error messages improved
|
||||||
- Updated help text for .antispam and .antiraid
|
- Updated help text for .antispam and .antiraid
|
||||||
- You can now specify time and date (time is optional) in `.remind` command instead of relative time, in the format `HH:mm dd.MM.YYYY`
|
- You can now specify time and date (time is optional) in `.remind` command instead of relative time, in the
|
||||||
|
format `HH:mm dd.MM.YYYY`
|
||||||
- OwnerId will be automatically added to `creds.yml` at bot startup if it's missing
|
- OwnerId will be automatically added to `creds.yml` at bot startup if it's missing
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
@@ -614,13 +694,15 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added `.exprdelserv` (.exds) to completement .exas. Deletes an expression on the current server and is susceptible to .dpo, unlike .exd
|
- Added `.exprdelserv` (.exds) to completement .exas. Deletes an expression on the current server and is susceptible to
|
||||||
|
.dpo, unlike .exd
|
||||||
- Added `.shopreq` which lets you set role requirement for specific shop items
|
- Added `.shopreq` which lets you set role requirement for specific shop items
|
||||||
- Added `.shopbuy` alias to `.buy`
|
- Added `.shopbuy` alias to `.buy`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed `.convertlist` showing currencies twice (this may not apply to existing users and it may require you to manually remove all currencies from units.json)
|
- Fixed `.convertlist` showing currencies twice (this may not apply to existing users and it may require you to manually
|
||||||
|
remove all currencies from units.json)
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
@@ -630,9 +712,12 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added `.expraddserver` (.exas) which will server as a server-only alternative to '.exa' in case users want to override default Admin permissions with .dpo
|
- Added `.expraddserver` (.exas) which will server as a server-only alternative to '.exa' in case users want to override
|
||||||
- Added .banprune command which sets how many days worth of messages will be pruned when bot (soft)bans a person either through a command or another punishment feature.
|
default Admin permissions with .dpo
|
||||||
- Added .qdelauth - Delete all quotes by the specified author on this server. If you target yourself - no permission required
|
- Added .banprune command which sets how many days worth of messages will be pruned when bot (soft)bans a person either
|
||||||
|
through a command or another punishment feature.
|
||||||
|
- Added .qdelauth - Delete all quotes by the specified author on this server. If you target yourself - no permission
|
||||||
|
required
|
||||||
- Added `.timeout` command
|
- Added `.timeout` command
|
||||||
- Added an option to award currency based on received xp
|
- Added an option to award currency based on received xp
|
||||||
|
|
||||||
@@ -690,7 +775,8 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
- Possibly fixed .trivia not stopping bug
|
- Possibly fixed .trivia not stopping bug
|
||||||
- Fixed very low payout rate on `.betroll`
|
- Fixed very low payout rate on `.betroll`
|
||||||
- Fixed an issue with youtube song resolver which caused invalid data to be cached
|
- Fixed an issue with youtube song resolver which caused invalid data to be cached
|
||||||
- Added client id to the cache key as a potential fix for VoiceXp 'bug'. The solution may be to use different redis instances for each bot, or to switch from botCache: from 'redis' to 'memory' in creds.yml
|
- Added client id to the cache key as a potential fix for VoiceXp 'bug'. The solution may be to use different redis
|
||||||
|
instances for each bot, or to switch from botCache: from 'redis' to 'memory' in creds.yml
|
||||||
- Bot owner should now be able to buy items from the xpshop when patron requirement is set
|
- Bot owner should now be able to buy items from the xpshop when patron requirement is set
|
||||||
- Fixed youtube-dl caching invalid data. Please use yt-dlp instead
|
- Fixed youtube-dl caching invalid data. Please use yt-dlp instead
|
||||||
|
|
||||||
@@ -722,10 +808,12 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
- multipliers < 1 are considered losses, > 1 considered wins
|
- multipliers < 1 are considered losses, > 1 considered wins
|
||||||
- Added `.betdraw` command which lets you guess red/black and/or high/low for a random card
|
- Added `.betdraw` command which lets you guess red/black and/or high/low for a random card
|
||||||
- They payouts are very good, but seven always loses
|
- They payouts are very good, but seven always loses
|
||||||
- Added `.lula` command. Plays the same as `.wof` but looks much nicer, and is easily customizable from gambling.yml without any changes to the sourcecode needed.
|
- Added `.lula` command. Plays the same as `.wof` but looks much nicer, and is easily customizable from gambling.yml
|
||||||
|
without any changes to the sourcecode needed.
|
||||||
- Added `.repeatskip` command which makes the next repeat trigger not post anything
|
- Added `.repeatskip` command which makes the next repeat trigger not post anything
|
||||||
- Added `.linkonly` which will make the bot only allow link posts in the channel. Exclusive with `.imageonly`
|
- Added `.linkonly` which will make the bot only allow link posts in the channel. Exclusive with `.imageonly`
|
||||||
- Added release notifications. Bot owners will now receive new release notifications in dms if they have `checkForUpdates` set to `true` in data/bot.yml
|
- Added release notifications. Bot owners will now receive new release notifications in dms if they
|
||||||
|
have `checkForUpdates` set to `true` in data/bot.yml
|
||||||
- You can also configure it via `.conf bot checkfor
|
- You can also configure it via `.conf bot checkfor
|
||||||
- updates <true/false>`
|
- updates <true/false>`
|
||||||
- Added `.xpshop` which lets bot owners add xp backgrounds and xp frames for sale by configuring `data/xp.yml`
|
- Added `.xpshop` which lets bot owners add xp backgrounds and xp frames for sale by configuring `data/xp.yml`
|
||||||
@@ -793,7 +881,8 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed elipsis character issue with aliases/quotes. You should now be able to set an elipsis to be an alias of `.quoteprint`
|
- Fixed elipsis character issue with aliases/quotes. You should now be able to set an elipsis to be an alias
|
||||||
|
of `.quoteprint`
|
||||||
|
|
||||||
## [4.2.13] - 30.06.2022
|
## [4.2.13] - 30.06.2022
|
||||||
|
|
||||||
@@ -859,7 +948,8 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed `.crypto`, you will still need coinmarketcapApiKey in `creds.yml` in order to make it run consistently as the key is shared
|
- Fixed `.crypto`, you will still need coinmarketcapApiKey in `creds.yml` in order to make it run consistently as the
|
||||||
|
key is shared
|
||||||
|
|
||||||
## [4.2.3] - 17.06.2022
|
## [4.2.3] - 17.06.2022
|
||||||
|
|
||||||
@@ -912,7 +1002,8 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
invidiousInstances: []
|
invidiousInstances: []
|
||||||
```
|
```
|
||||||
- Added new properties to `creds.yml`. google -> searchId and google -> searchImageId.
|
- Added new properties to `creds.yml`. google -> searchId and google -> searchImageId.
|
||||||
- These properties are used as `cx` (google api query parameter) in case you've setup your `data/searches.yml` to use the official google api.
|
- These properties are used as `cx` (google api query parameter) in case you've setup your `data/searches.yml` to use
|
||||||
|
the official google api.
|
||||||
`searchId` is used for web search
|
`searchId` is used for web search
|
||||||
`searchimageId` is used for image search
|
`searchimageId` is used for image search
|
||||||
```yml
|
```yml
|
||||||
@@ -923,12 +1014,15 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
- Check `creds_example.yml` for comments explaining how to obtain them.
|
- Check `creds_example.yml` for comments explaining how to obtain them.
|
||||||
|
|
||||||
#### Patronage system added
|
#### Patronage system added
|
||||||
|
|
||||||
- Added `data/patron.yml` for configuration
|
- Added `data/patron.yml` for configuration
|
||||||
- Implemented only for patreon so far
|
- Implemented only for patreon so far
|
||||||
- Patreon subscription code completely rewritten
|
- Patreon subscription code completely rewritten
|
||||||
- Users who pledge on patreon get benefits based on the amount they pledged
|
- Users who pledge on patreon get benefits based on the amount they pledged
|
||||||
- Public nadeko only. But selfhosters can adapt it to their own patreon pages by configuring their patreon credentials in `creds.yml` and enabling the system in `data/patron.yml` file.
|
- Public nadeko only. But selfhosters can adapt it to their own patreon pages by configuring their patreon credentials
|
||||||
- Most of the patronage system strings are hardcoded atm, so if you wish to use this system on selfhosts, you will have to modify the source
|
in `creds.yml` and enabling the system in `data/patron.yml` file.
|
||||||
|
- Most of the patronage system strings are hardcoded atm, so if you wish to use this system on selfhosts, you will
|
||||||
|
have to modify the source
|
||||||
- Pledge amounts are split into tiers. This is not configurable atm.
|
- Pledge amounts are split into tiers. This is not configurable atm.
|
||||||
- Tier I - 1$ - 4.99$ a month
|
- Tier I - 1$ - 4.99$ a month
|
||||||
- Tier V - 5$ - 9.99$ a month
|
- Tier V - 5$ - 9.99$ a month
|
||||||
@@ -938,12 +1032,14 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
|
|||||||
- Tier C - 100$+ a month
|
- Tier C - 100$+ a month
|
||||||
- Rewards and command quotas for each of the tiers are configurable
|
- Rewards and command quotas for each of the tiers are configurable
|
||||||
- Limitations to certain features are also configurable. ex:
|
- Limitations to certain features are also configurable. ex:
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
quotas:
|
quotas:
|
||||||
features:
|
features:
|
||||||
"rero:max_count":
|
"rero:max_count":
|
||||||
x: 50
|
x: 50
|
||||||
```
|
```
|
||||||
|
|
||||||
- ^ this setting would set the maximum number of reaction roles to be 50 for a user who is in Patron Tier X
|
- ^ this setting would set the maximum number of reaction roles to be 50 for a user who is in Patron Tier X
|
||||||
- Read the comments in the .yml file for (much) more info
|
- Read the comments in the .yml file for (much) more info
|
||||||
- Quota system allows the owner to set up hourly, daily and monthly quota usage for each tier
|
- Quota system allows the owner to set up hourly, daily and monthly quota usage for each tier
|
||||||
@@ -954,10 +1050,12 @@ quotas:
|
|||||||
- If you're enabling patron system for a selfhost, you will want to edit it
|
- If you're enabling patron system for a selfhost, you will want to edit it
|
||||||
|
|
||||||
Added `.patron` and `.patronmessage` commands
|
Added `.patron` and `.patronmessage` commands
|
||||||
|
|
||||||
- `.patron` checks your patronage status, and quotas. Requires patron system to be enabled.
|
- `.patron` checks your patronage status, and quotas. Requires patron system to be enabled.
|
||||||
- `.patronmessage` (owner only) sends message to all patrons with the specified tier or higher. Supports embeds
|
- `.patronmessage` (owner only) sends message to all patrons with the specified tier or higher. Supports embeds
|
||||||
|
|
||||||
- Added a fake `.cmdcd` command `cleverbot:response` which can be used to limit how often users can talk to the cleverbot.
|
- Added a fake `.cmdcd` command `cleverbot:response` which can be used to limit how often users can talk to the
|
||||||
|
cleverbot.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
@@ -968,26 +1066,34 @@ Added `.patron` and `.patronmessage` commands
|
|||||||
- Added interaction explaining selfhosting
|
- Added interaction explaining selfhosting
|
||||||
|
|
||||||
- `.google` reimplemented. It now has 2 modes configurable in `data/searches.yml` under the `webSearchengine` property
|
- `.google` reimplemented. It now has 2 modes configurable in `data/searches.yml` under the `webSearchengine` property
|
||||||
- If set to `google`, official custom search api will be used. You will need to set googleapikey and google.searchId in `creds.yml`
|
- If set to `google`, official custom search api will be used. You will need to set googleapikey and google.searchId
|
||||||
- if set to `searx` one of the instances specified in the `searxInstances:` property will be randomly chosen for each request
|
in `creds.yml`
|
||||||
|
- if set to `searx` one of the instances specified in the `searxInstances:` property will be randomly chosen for
|
||||||
|
each request
|
||||||
- instances must have `format=json` allowed (public ones usually don't allow it)
|
- instances must have `format=json` allowed (public ones usually don't allow it)
|
||||||
- instances are specified as a fully qualified url, example: `https://my.cool.searx.instance.io`
|
- instances are specified as a fully qualified url, example: `https://my.cool.searx.instance.io`
|
||||||
- `.image` reimplemented. Same as `.google` - it uses either `google` official api (in which case it uses `google.searchImageId` from `creds.yml`) or `searx`
|
- `.image` reimplemented. Same as `.google` - it uses either `google` official api (in which case it
|
||||||
|
uses `google.searchImageId` from `creds.yml`) or `searx`
|
||||||
|
|
||||||
- `.youtube` reimplemented. It will use a `ytProvider:` property from `data/searches.yml` to determine how to retrieve results
|
- `.youtube` reimplemented. It will use a `ytProvider:` property from `data/searches.yml` to determine how to retrieve
|
||||||
- `ytdataapi` will use the official google api (requires `GoogleApiKey` specified in `creds.yml`) and YoutubeDataApi enabled in the dev console
|
results
|
||||||
- `ytdl` will use `youtube-dl` program from the host machine. It must be downloaded and it's location must be added to path env variable.
|
- `ytdataapi` will use the official google api (requires `GoogleApiKey` specified in `creds.yml`) and YoutubeDataApi
|
||||||
|
enabled in the dev console
|
||||||
|
- `ytdl` will use `youtube-dl` program from the host machine. It must be downloaded and it's location must be added
|
||||||
|
to path env variable.
|
||||||
- `ytdlp` will use `yt-dlp` program from the host machine. Same as `youtube-dl` - must be in path env variable.
|
- `ytdlp` will use `yt-dlp` program from the host machine. Same as `youtube-dl` - must be in path env variable.
|
||||||
- `invidious` will use one of invidious instances specified in the `invidiousInstances` property. Very good.
|
- `invidious` will use one of invidious instances specified in the `invidiousInstances` property. Very good.
|
||||||
|
|
||||||
- `.google`, `.youtube` and `.image` moved to the new Search group
|
- `.google`, `.youtube` and `.image` moved to the new Search group
|
||||||
|
|
||||||
Note: Results of each `.youtube` query will be cached for 1 hour to improve perfomance
|
Note: Results of each `.youtube` query will be cached for 1 hour to improve perfomance
|
||||||
|
|
||||||
- Removed 30 second `.ping` ratelimit on public nadeko
|
- Removed 30 second `.ping` ratelimit on public nadeko
|
||||||
|
|
||||||
- xp image generation changes
|
- xp image generation changes
|
||||||
- In case you have default settings, your xp image will look slightly different
|
- In case you have default settings, your xp image will look slightly different
|
||||||
- If you've modified xp_template.json, your xp image might look broken. Your old template will be saved in xp_template.json.old
|
- If you've modified xp_template.json, your xp image might look broken. Your old template will be saved in
|
||||||
|
xp_template.json.old
|
||||||
- Xp number outline is now slightly thicker
|
- Xp number outline is now slightly thicker
|
||||||
- Xp number will now have Center vertical and horizontal alignment
|
- Xp number will now have Center vertical and horizontal alignment
|
||||||
- LastLevelUp no longer supported
|
- LastLevelUp no longer supported
|
||||||
@@ -1051,7 +1157,8 @@ Note: Results of each `.youtube` query will be cached for 1 hour to improve perf
|
|||||||
- Embed arrays use color hex values instead of an integer
|
- Embed arrays use color hex values instead of an integer
|
||||||
- Old embed format will still work
|
- Old embed format will still work
|
||||||
- There shouldn't be any breaking changes
|
- There shouldn't be any breaking changes
|
||||||
- Added `.stondel` command which, when toggled, will make the bot delete online stream messages on the server when the stream goes offline
|
- Added `.stondel` command which, when toggled, will make the bot delete online stream messages on the server when the
|
||||||
|
stream goes offline
|
||||||
- Added a simple bank system.
|
- Added a simple bank system.
|
||||||
- Users can deposit, withdraw and check the balance of their currency in the bank.
|
- Users can deposit, withdraw and check the balance of their currency in the bank.
|
||||||
- Users can't check other user's bank balances.
|
- Users can't check other user's bank balances.
|
||||||
@@ -1077,7 +1184,6 @@ Note: Results of each `.youtube` query will be cached for 1 hour to improve perf
|
|||||||
- Bot will now support much higher XP values for global and server levels
|
- Bot will now support much higher XP values for global and server levels
|
||||||
- [dev] Small change and generation perf improvement for the localized response strings
|
- [dev] Small change and generation perf improvement for the localized response strings
|
||||||
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed `.deletexp` command
|
- Fixed `.deletexp` command
|
||||||
@@ -1096,7 +1202,8 @@ Note: Results of each `.youtube` query will be cached for 1 hour to improve perf
|
|||||||
|
|
||||||
- NadekoBot now supports mysql, postgresql and sqlite
|
- NadekoBot now supports mysql, postgresql and sqlite
|
||||||
- To change the db nadeko will use, simply change the `db type` in `creds.yml`
|
- To change the db nadeko will use, simply change the `db type` in `creds.yml`
|
||||||
- There is no migration code right now, which means that if you want to switch to another system you'll either have to manually export/import your database or start fresh
|
- There is no migration code right now, which means that if you want to switch to another system you'll either have
|
||||||
|
to manually export/import your database or start fresh
|
||||||
- Medusa system
|
- Medusa system
|
||||||
- A massive new feature which allows developers to create custom modules/plugins/cogs
|
- A massive new feature which allows developers to create custom modules/plugins/cogs
|
||||||
- They can be load/unloaded/updated at runtime without restarting the bot
|
- They can be load/unloaded/updated at runtime without restarting the bot
|
||||||
@@ -1106,9 +1213,11 @@ Note: Results of each `.youtube` query will be cached for 1 hour to improve perf
|
|||||||
- Minor club rework
|
- Minor club rework
|
||||||
- Clubs names are now case sensitive (owo and OwO can be 2 different clubs)
|
- Clubs names are now case sensitive (owo and OwO can be 2 different clubs)
|
||||||
- Removed discriminators
|
- Removed discriminators
|
||||||
- Current discriminators which are greater than 1 are appended to clubnames to avoid duplicates, you can rename your club with `.clubrename` to remove it
|
- Current discriminators which are greater than 1 are appended to clubnames to avoid duplicates, you can rename
|
||||||
|
your club with `.clubrename` to remove it
|
||||||
- Most of the clubs with #1 discriminator no longer have it (For example MyClub#1 will now just be MyClub)
|
- Most of the clubs with #1 discriminator no longer have it (For example MyClub#1 will now just be MyClub)
|
||||||
- [dev] A lot of refactoring and slight functionality changes within Nadeko's behavior system and command handler which were required in order to support the medusa system
|
- [dev] A lot of refactoring and slight functionality changes within Nadeko's behavior system and command handler which
|
||||||
|
were required in order to support the medusa system
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
@@ -1158,6 +1267,7 @@ Note: Results of each `.youtube` query will be cached for 1 hour to improve perf
|
|||||||
## [4.0.0] - 02.03.2022
|
## [4.0.0] - 02.03.2022
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added `.deleteemptyservers` command
|
- Added `.deleteemptyservers` command
|
||||||
- Added `.curtr <id>` which lets you see full information about one of your own transactions with the specified id
|
- Added `.curtr <id>` which lets you see full information about one of your own transactions with the specified id
|
||||||
- Added trovo.live support for stream notifications (`.stadd`)
|
- Added trovo.live support for stream notifications (`.stadd`)
|
||||||
@@ -1165,18 +1275,24 @@ Note: Results of each `.youtube` query will be cached for 1 hour to improve perf
|
|||||||
- Added 3 new settings to `data/gambling.yml` to control it:
|
- Added 3 new settings to `data/gambling.yml` to control it:
|
||||||
- waifu.decay.percent - How much % to subtract from unclaimed waifu
|
- waifu.decay.percent - How much % to subtract from unclaimed waifu
|
||||||
- waifu.decay.hourInterval - How often to decay the price
|
- waifu.decay.hourInterval - How often to decay the price
|
||||||
- waifu.decay.minPrice - Unclaimed waifus with price lower than the one specified here will not be affected by the decay
|
- waifu.decay.minPrice - Unclaimed waifus with price lower than the one specified here will not be affected by
|
||||||
- Added `currency.transactionsLifetime` to `data/gambling.yml` Any transaction older than the number of days specified will be automatically deleted
|
the decay
|
||||||
|
- Added `currency.transactionsLifetime` to `data/gambling.yml` Any transaction older than the number of days specified
|
||||||
|
will be automatically deleted
|
||||||
- Added `.stock` command to check stock prices and charts
|
- Added `.stock` command to check stock prices and charts
|
||||||
- Re-added `.qap / .queueautoplay`
|
- Re-added `.qap / .queueautoplay`
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- CustomReactions module (and customreactions db table) has been renamed to Expressions.
|
- CustomReactions module (and customreactions db table) has been renamed to Expressions.
|
||||||
- This was done to remove confusion about how it relates to discord Reactions (it doesn't, it was created and named before discord reactions existed)
|
- This was done to remove confusion about how it relates to discord Reactions (it doesn't, it was created and named
|
||||||
|
before discord reactions existed)
|
||||||
- Expression command now start with ex/expr and end with the name of the action or setting.
|
- Expression command now start with ex/expr and end with the name of the action or setting.
|
||||||
- For example `.exd` (`.dcr`) is expression delete, `.exa` (`.acr`)
|
- For example `.exd` (`.dcr`) is expression delete, `.exa` (`.acr`)
|
||||||
- Permissions (`.lp`) be automatically updated with "ACTUALEXPRESSIONS", "EXPRESSIONS" instead of "ACTUALCUSTOMREACTIONS" and "CUSTOMREACTIONS"
|
- Permissions (`.lp`) be automatically updated with "ACTUALEXPRESSIONS", "EXPRESSIONS" instead of "
|
||||||
- Permissions for `.ecr` (now `.exe`), `.scr` (now `.exs`), `.dcr` (now `.exd`), `.acr` (now `.exa`), `.lcr` (now `.exl`) will be automatically updated
|
ACTUALCUSTOMREACTIONS" and "CUSTOMREACTIONS"
|
||||||
|
- Permissions for `.ecr` (now `.exe`), `.scr` (now `.exs`), `.dcr` (now `.exd`), `.acr` (now `.exa`), `.lcr` (
|
||||||
|
now `.exl`) will be automatically updated
|
||||||
- If you have custom permissions for other CustomReaction commands
|
- If you have custom permissions for other CustomReaction commands
|
||||||
- Some of the old aliases like `.acr` `.dcr` `.lcr` and a few others have been kept
|
- Some of the old aliases like `.acr` `.dcr` `.lcr` and a few others have been kept
|
||||||
- Currency output format improvement (will use guild locale now for some commands)
|
- Currency output format improvement (will use guild locale now for some commands)
|
||||||
@@ -1195,6 +1311,7 @@ Note: Results of each `.youtube` query will be cached for 1 hour to improve perf
|
|||||||
- [dev] Moved FilterWordsChannelId to a separate table
|
- [dev] Moved FilterWordsChannelId to a separate table
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed twitch stream notifications (rewrote it to use the new api)
|
- Fixed twitch stream notifications (rewrote it to use the new api)
|
||||||
- Fixed an extra whitespace in usage part of command help if the command has no arguments
|
- Fixed an extra whitespace in usage part of command help if the command has no arguments
|
||||||
- Possible small fix for `.prune` ratelimiting
|
- Possible small fix for `.prune` ratelimiting
|
||||||
@@ -1206,8 +1323,10 @@ Note: Results of each `.youtube` query will be cached for 1 hour to improve perf
|
|||||||
- Fixed embed color when disabling `.antialt`
|
- Fixed embed color when disabling `.antialt`
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- Removed `.bce` - use `.config` or `.config bot` specifically for bot config
|
- Removed `.bce` - use `.config` or `.config bot` specifically for bot config
|
||||||
- Removed obsolete placeholders: %users% %servers% %userfull% %username% %userdiscrim% %useravatar% %id% %uid% %chname% %cid% %sid% %members% %server_time% %shardid% %time% %mention%
|
- Removed obsolete placeholders: %users% %servers% %userfull% %username% %userdiscrim% %useravatar% %id% %uid% %chname%
|
||||||
|
%cid% %sid% %members% %server_time% %shardid% %time% %mention%
|
||||||
- Removed some obsolete commands and strings
|
- Removed some obsolete commands and strings
|
||||||
- Removed code which migrated 2.x to v3 credentials, settings, etc...
|
- Removed code which migrated 2.x to v3 credentials, settings, etc...
|
||||||
|
|
||||||
@@ -1221,6 +1340,7 @@ Note: Results of each `.youtube` query will be cached for 1 hour to improve perf
|
|||||||
## [3.0.12] - 06.01.2022
|
## [3.0.12] - 06.01.2022
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- `.smch` Fixed
|
- `.smch` Fixed
|
||||||
- `.trans` command will now work properly with capitilized language names
|
- `.trans` command will now work properly with capitilized language names
|
||||||
- Ban message color with plain text fixed
|
- Ban message color with plain text fixed
|
||||||
@@ -1232,12 +1352,15 @@ Note: Results of each `.youtube` query will be cached for 1 hour to improve perf
|
|||||||
## [3.0.11] - 17.12.2021
|
## [3.0.11] - 17.12.2021
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- `.remindl` and `.remindrm` commands now supports optional 'server' parameter for Administrators which allows them to delete any reminder created on the server
|
|
||||||
|
- `.remindl` and `.remindrm` commands now supports optional 'server' parameter for Administrators which allows them to
|
||||||
|
delete any reminder created on the server
|
||||||
- Added slots.currencyFontColor to gambling.yml
|
- Added slots.currencyFontColor to gambling.yml
|
||||||
- Added `.qexport` and `.qimport` commands which allow you to export and import quotes just like `.crsexport`
|
- Added `.qexport` and `.qimport` commands which allow you to export and import quotes just like `.crsexport`
|
||||||
- Added `.showembed <msgid>` and `.showembed #channel <msgid>` which will show you embed json from the specified message
|
- Added `.showembed <msgid>` and `.showembed #channel <msgid>` which will show you embed json from the specified message
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- `.at` and `.atl` commands reworked
|
- `.at` and `.atl` commands reworked
|
||||||
- Persist restarts
|
- Persist restarts
|
||||||
- Will now only translate non-commands
|
- Will now only translate non-commands
|
||||||
@@ -1247,55 +1370,67 @@ Note: Results of each `.youtube` query will be cached for 1 hour to improve perf
|
|||||||
- Looks much nicer
|
- Looks much nicer
|
||||||
- Bot will now reply to user messages with a translation if `del` is disabled
|
- Bot will now reply to user messages with a translation if `del` is disabled
|
||||||
- Bot will make an embed with original and translated text with user avatar and name if `del` is enabled
|
- Bot will make an embed with original and translated text with user avatar and name if `del` is enabled
|
||||||
- If the bot is unable to delete messages while having `del` enabled, it will reset back to the no-del behavior for the current session
|
- If the bot is unable to delete messages while having `del` enabled, it will reset back to the no-del behavior for
|
||||||
|
the current session
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- `.crypto` now supports top 5000 coins
|
- `.crypto` now supports top 5000 coins
|
||||||
|
|
||||||
## [3.0.10] - 01.12.2021
|
## [3.0.10] - 01.12.2021
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- `.warn` now supports weighted warnings
|
- `.warn` now supports weighted warnings
|
||||||
- `.warnlog` will now show current amount and total amount of warnings
|
- `.warnlog` will now show current amount and total amount of warnings
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- `.xprewsreset` now has correct permissions
|
- `.xprewsreset` now has correct permissions
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- Removed slot.numbers from `images.yml` as they're no longer used
|
- Removed slot.numbers from `images.yml` as they're no longer used
|
||||||
|
|
||||||
## [3.0.9] - 21.11.2021
|
## [3.0.9] - 21.11.2021
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- `.ea` will now use an image attachments if you omit imageUrl
|
- `.ea` will now use an image attachments if you omit imageUrl
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added `.emojiadd` with 3 overloads
|
- Added `.emojiadd` with 3 overloads
|
||||||
- `.ea :customEmoji:` which copies another server's emoji
|
- `.ea :customEmoji:` which copies another server's emoji
|
||||||
- `.ea newName :customEmoji:` which copies emoji under a different name
|
- `.ea newName :customEmoji:` which copies emoji under a different name
|
||||||
- `.ea emojiName <imagelink.png>` which creates a new emoji from the specified image
|
- `.ea emojiName <imagelink.png>` which creates a new emoji from the specified image
|
||||||
- Patreon Access and Refresh Tokens should now be automatically updated once a month as long as the user has provided the necessary credentials in creds.yml file:
|
- Patreon Access and Refresh Tokens should now be automatically updated once a month as long as the user has provided
|
||||||
|
the necessary credentials in creds.yml file:
|
||||||
- `Patreon.ClientId`
|
- `Patreon.ClientId`
|
||||||
- `Patreon.RefreshToken` (will also get updated once a month but needs an initial value)
|
- `Patreon.RefreshToken` (will also get updated once a month but needs an initial value)
|
||||||
- `Patreon.ClientSecret`
|
- `Patreon.ClientSecret`
|
||||||
- `Patreon.CampaignId`
|
- `Patreon.CampaignId`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed an error that would show up in the console when a club image couldn't be drawn in certain circumstances
|
- Fixed an error that would show up in the console when a club image couldn't be drawn in certain circumstances
|
||||||
|
|
||||||
## [3.0.8] - 03.11.2021
|
## [3.0.8] - 03.11.2021
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Created VotesApi project nad re-worked vote rewards handling
|
- Created VotesApi project nad re-worked vote rewards handling
|
||||||
- Updated votes entries in creds.yml with explanations on how to set up vote links
|
- Updated votes entries in creds.yml with explanations on how to set up vote links
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed adding currency to users who don't exist in the database
|
- Fixed adding currency to users who don't exist in the database
|
||||||
- Memory used by the bot is now correct (thanks to kotz)
|
- Memory used by the bot is now correct (thanks to kotz)
|
||||||
- Ban/kick will no longer fail due to too long reasons
|
- Ban/kick will no longer fail due to too long reasons
|
||||||
- Fixed some fields not preserving inline after string replacements
|
- Fixed some fields not preserving inline after string replacements
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- `images.json` moved to `images.yml`
|
- `images.json` moved to `images.yml`
|
||||||
- Links will use the new cdn url
|
- Links will use the new cdn url
|
||||||
- Heads and Tails images will be updated if you haven't changed them already
|
- Heads and Tails images will be updated if you haven't changed them already
|
||||||
@@ -1305,15 +1440,18 @@ Note: Results of each `.youtube` query will be cached for 1 hour to improve perf
|
|||||||
## [3.0.7] - 05.10.2021
|
## [3.0.7] - 05.10.2021
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- `.streamsclear` re-added. It will remove all followed streams on the server.
|
- `.streamsclear` re-added. It will remove all followed streams on the server.
|
||||||
- `.gifts` now have 3 new ✂️ Haircut 🧻 ToiletPaper and 🥀 WiltedRose which **reduce** waifu's value
|
- `.gifts` now have 3 new ✂️ Haircut 🧻 ToiletPaper and 🥀 WiltedRose which **reduce** waifu's value
|
||||||
- They are called negative gifts
|
- They are called negative gifts
|
||||||
- They show up at the end of the `.gifts` page and are marked with a broken heart
|
- They show up at the end of the `.gifts` page and are marked with a broken heart
|
||||||
- They have a separate multiplier (`waifu.multi.negative_gift_effect` default 0.5, changeable via `.config gambling` or `data/gambling.yml`)
|
- They have a separate multiplier (`waifu.multi.negative_gift_effect` default 0.5, changeable via `.config gambling`
|
||||||
|
or `data/gambling.yml`)
|
||||||
- When gifted, the waifu's price will be reduced by the `price * multiplier`
|
- When gifted, the waifu's price will be reduced by the `price * multiplier`
|
||||||
- Negative gifts don't show up in `.waifuinfo` nor is the record of them kept in the database
|
- Negative gifts don't show up in `.waifuinfo` nor is the record of them kept in the database
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed `%users%` and `%shard.usercount%` placeholders not showing correct values
|
- Fixed `%users%` and `%shard.usercount%` placeholders not showing correct values
|
||||||
|
|
||||||
## [3.0.6] - 27.09.2021
|
## [3.0.6] - 27.09.2021
|
||||||
@@ -1382,7 +1520,8 @@ Note: Results of each `.youtube` query will be cached for 1 hour to improve perf
|
|||||||
|
|
||||||
- `.rero` now optionally takes a message id to which to attach the reaction roles
|
- `.rero` now optionally takes a message id to which to attach the reaction roles
|
||||||
- Fully translated to German 🎉
|
- Fully translated to German 🎉
|
||||||
- Added `.boost`, `.boostmsg` and `.boostdel` commands which allow you to have customizable messages when someone boosts your server, with auto-deletion support
|
- Added `.boost`, `.boostmsg` and `.boostdel` commands which allow you to have customizable messages when someone boosts
|
||||||
|
your server, with auto-deletion support
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
@@ -1415,10 +1554,12 @@ Note: Results of each `.youtube` query will be cached for 1 hour to improve perf
|
|||||||
- Explanations on how to get the keys are added as the comments
|
- Explanations on how to get the keys are added as the comments
|
||||||
- Code cleanup
|
- Code cleanup
|
||||||
- Command attributes cleaned up
|
- Command attributes cleaned up
|
||||||
- Removed dummy Remarks and Usages attributes as hey were unused for a few patches but stayed in the code to avoid big git diffsmigration code has ran and it can be safely removed
|
- Removed dummy Remarks and Usages attributes as hey were unused for a few patches but stayed in the code to
|
||||||
|
avoid big git diffsmigration code has ran and it can be safely removed
|
||||||
- There are 2 projects: NadekoBot and NadekoBot.Coordinator
|
- There are 2 projects: NadekoBot and NadekoBot.Coordinator
|
||||||
- You can directly run NadekoBot as the regular bot with one shard
|
- You can directly run NadekoBot as the regular bot with one shard
|
||||||
- Run NadekoBot.Coordinator if you want more control over your shards and a grpc api for coordinator with which you can start, restart, kill and see status of shards
|
- Run NadekoBot.Coordinator if you want more control over your shards and a grpc api for coordinator with which
|
||||||
|
you can start, restart, kill and see status of shards
|
||||||
- Small performance improvements
|
- Small performance improvements
|
||||||
- Db Migrations squashed
|
- Db Migrations squashed
|
||||||
- A lot of cleanup all around
|
- A lot of cleanup all around
|
||||||
@@ -1432,7 +1573,8 @@ Note: Results of each `.youtube` query will be cached for 1 hour to improve perf
|
|||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- Removed All database migrations and data (json file) migrations
|
- Removed All database migrations and data (json file) migrations
|
||||||
- As updating to the latest 2.x version before switching over to v3 is mandated (or fresh v3 install), that means all
|
- As updating to the latest 2.x version before switching over to v3 is mandated (or fresh v3 install), that means
|
||||||
|
all
|
||||||
|
|
||||||
## [2.46.2] - 14.07.2021
|
## [2.46.2] - 14.07.2021
|
||||||
|
|
||||||
@@ -1462,9 +1604,11 @@ Note: Results of each `.youtube` query will be cached for 1 hour to improve perf
|
|||||||
- Use `.aar` to list roles which will be added
|
- Use `.aar` to list roles which will be added
|
||||||
- Roles which are deleted are automatically cleaned up from `.aar`
|
- Roles which are deleted are automatically cleaned up from `.aar`
|
||||||
- `.inrole` now also shows user ids
|
- `.inrole` now also shows user ids
|
||||||
- Blacklist commands (owner only) `.ubl` `.sbl` and `.cbl` will now list blacklisted items when no argument (or a page number) is provided
|
- Blacklist commands (owner only) `.ubl` `.sbl` and `.cbl` will now list blacklisted items when no argument (or a page
|
||||||
|
number) is provided
|
||||||
- `.cmdcd` now works with customreactions too
|
- `.cmdcd` now works with customreactions too
|
||||||
- `.xprr` usage changed. It now takes add/rm parameter to add/remove a role ex. You can only take or remove a single role, adding and removing a role at the same level doesn't work (yet?)
|
- `.xprr` usage changed. It now takes add/rm parameter to add/remove a role ex. You can only take or remove a single
|
||||||
|
role, adding and removing a role at the same level doesn't work (yet?)
|
||||||
- example: `.xprr 5 add Member` or `.xprr 1 rm Newbie`
|
- example: `.xprr 5 add Member` or `.xprr 1 rm Newbie`
|
||||||
|
|
||||||
## [2.45.2] - 14.06.2021
|
## [2.45.2] - 14.06.2021
|
||||||
@@ -1485,16 +1629,19 @@ Note: Results of each `.youtube` query will be cached for 1 hour to improve perf
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added many new aliases to custom reaction commands in the format ex + "action" to prepare for the future rename from CustomReactions to Expressions
|
- Added many new aliases to custom reaction commands in the format ex + "action" to prepare for the future rename from
|
||||||
|
CustomReactions to Expressions
|
||||||
- You can now `.divorce` via username#discrim even if the user no longer exists
|
- You can now `.divorce` via username#discrim even if the user no longer exists
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- DmHelpText should now have %prefix% and %bot.prefix% placeholders available
|
- DmHelpText should now have %prefix% and %bot.prefix% placeholders available
|
||||||
- Added squares which show enabled features for each cr in `.lcr`
|
- Added squares which show enabled features for each cr in `.lcr`
|
||||||
- Changed CustomReactions' IDs to show, and accept base 32 unambigous characters instead of the normal database IDs (this will result in much shorter cr IDs in case you have a lot of them)
|
- Changed CustomReactions' IDs to show, and accept base 32 unambigous characters instead of the normal database IDs (
|
||||||
|
this will result in much shorter cr IDs in case you have a lot of them)
|
||||||
- Improved `.lcr` helptext to explain what's shown in the output
|
- Improved `.lcr` helptext to explain what's shown in the output
|
||||||
- `.rolecolor <color> <role>` changed to take color, then the role, to make it easier to set color for roles with multiple words without mentioning the role
|
- `.rolecolor <color> <role>` changed to take color, then the role, to make it easier to set color for roles with
|
||||||
|
multiple words without mentioning the role
|
||||||
- `.acmdcds` alias chanaged to `.cmdcds`
|
- `.acmdcds` alias chanaged to `.cmdcds`
|
||||||
- `.8ball` will now cache results for a day
|
- `.8ball` will now cache results for a day
|
||||||
- `.chatmute` and `.voicemute` now support timed mutes
|
- `.chatmute` and `.voicemute` now support timed mutes
|
||||||
@@ -1511,10 +1658,13 @@ Note: Results of each `.youtube` query will be cached for 1 hour to improve perf
|
|||||||
- Allows for quick export/import of server or global custom reactions
|
- Allows for quick export/import of server or global custom reactions
|
||||||
- Requires admin permissions for server crs, and owner for global crs
|
- Requires admin permissions for server crs, and owner for global crs
|
||||||
- Explanation of the fields is in the comment at the top of the `.crsexport` .yml file
|
- Explanation of the fields is in the comment at the top of the `.crsexport` .yml file
|
||||||
- Added `.mquality` / `.musicquality` - Set encoding quality. Has 4 presets - Low, Medium, High, Highest. Default is Highest
|
- Added `.mquality` / `.musicquality` - Set encoding quality. Has 4 presets - Low, Medium, High, Highest. Default is
|
||||||
|
Highest
|
||||||
- Added `.xprewsreset` which resets all currently set xp level up rewards
|
- Added `.xprewsreset` which resets all currently set xp level up rewards
|
||||||
- Added `.purgeuser @User` which will remove the specified from the database completely. Removed settings include: Xp, clubs, waifu, currency, etc...
|
- Added `.purgeuser @User` which will remove the specified from the database completely. Removed settings include: Xp,
|
||||||
- Added `.config xp txt.per_image` and xpFromImage to xp.yml - Change this config to allow xp gain from posting images. Images must be 128x128 or greater in size
|
clubs, waifu, currency, etc...
|
||||||
|
- Added `.config xp txt.per_image` and xpFromImage to xp.yml - Change this config to allow xp gain from posting images.
|
||||||
|
Images must be 128x128 or greater in size
|
||||||
- Added `.take <amount> <role>` to complement `.award <amount> role`
|
- Added `.take <amount> <role>` to complement `.award <amount> role`
|
||||||
- Added **Fans** list to `.waifuinfo` which shows how many people have their affinity set to you
|
- Added **Fans** list to `.waifuinfo` which shows how many people have their affinity set to you
|
||||||
- Added `.antialt` which will punish any user whose account is younger than specified threshold
|
- Added `.antialt` which will punish any user whose account is younger than specified threshold
|
||||||
@@ -1528,7 +1678,8 @@ Note: Results of each `.youtube` query will be cached for 1 hour to improve perf
|
|||||||
- Re-created GuildRepeaters table and renamed to Repeaters
|
- Re-created GuildRepeaters table and renamed to Repeaters
|
||||||
- confirmation prompts will now use pending color from bot config, instead of okcolor
|
- confirmation prompts will now use pending color from bot config, instead of okcolor
|
||||||
- `.mute` can now have up to 49 days mute to match .warnp
|
- `.mute` can now have up to 49 days mute to match .warnp
|
||||||
- `.warnlog` now has proper pagination (with reactions) and checking your own warnings past page 1 works correctly now with `.warnlog 2`
|
- `.warnlog` now has proper pagination (with reactions) and checking your own warnings past page 1 works correctly now
|
||||||
|
with `.warnlog 2`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
@@ -1592,7 +1743,8 @@ Note: Results of each `.youtube` query will be cached for 1 hour to improve perf
|
|||||||
- Higher quality audio (no stuttering too!)
|
- Higher quality audio (no stuttering too!)
|
||||||
- Local tracks will now have durations if you have ffprobe installed (comes with ffmpeg)
|
- Local tracks will now have durations if you have ffprobe installed (comes with ffmpeg)
|
||||||
- Bot supports joining a different vc without skipping the song if you use `.j`
|
- Bot supports joining a different vc without skipping the song if you use `.j`
|
||||||
- ⚠️ **DO NOT DRAG THE BOT** to another vc, as it's not properly supported atm, and you will have to do `.play` after dragging it)
|
- ⚠️ **DO NOT DRAG THE BOT** to another vc, as it's not properly supported atm, and you will have to do `.play`
|
||||||
|
after dragging it)
|
||||||
- `.j` makes the bot join your voice channel
|
- `.j` makes the bot join your voice channel
|
||||||
- `.p` is now alias of play, pause is `.pause`
|
- `.p` is now alias of play, pause is `.pause`
|
||||||
- `.qs` should work without google api key now for most users as it is using a custom loader
|
- `.qs` should work without google api key now for most users as it is using a custom loader
|
||||||
@@ -1615,4 +1767,5 @@ Note: Results of each `.youtube` query will be cached for 1 hour to improve perf
|
|||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- Removed admin requirement on `.scrm` as it didn't make sense
|
- Removed admin requirement on `.scrm` as it didn't make sense
|
||||||
- Some Music commands are removed because of the complexity they bring in with little value (if you *really* want them back, you can open an issue and specify your *good* reason)
|
- Some Music commands are removed because of the complexity they bring in with little value (if you *really* want them
|
||||||
|
back, you can open an issue and specify your *good* reason)
|
||||||
|
47
src/NadekoBot.GrpcApiBase/protos/canvas.proto
Normal file
47
src/NadekoBot.GrpcApiBase/protos/canvas.proto
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
option csharp_namespace = "NadekoBot.GrpcApi";
|
||||||
|
|
||||||
|
import "google/protobuf/empty.proto";
|
||||||
|
|
||||||
|
package ncanvas;
|
||||||
|
|
||||||
|
service GrpcNCanvas {
|
||||||
|
rpc GetCanvas(google.protobuf.Empty) returns (CanvasReply);
|
||||||
|
rpc GetPixel(GetPixelRequest) returns (GetPixelReply);
|
||||||
|
rpc SetPixel(SetPixelRequest) returns (SetPixelReply);
|
||||||
|
}
|
||||||
|
|
||||||
|
message CanvasReply {
|
||||||
|
repeated uint32 pixels = 1;
|
||||||
|
int32 width = 2;
|
||||||
|
int32 height = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetPixelRequest {
|
||||||
|
int32 x = 1;
|
||||||
|
int32 y = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetPixelReply {
|
||||||
|
string color = 1;
|
||||||
|
uint32 packedColor = 2;
|
||||||
|
int32 positionX = 3;
|
||||||
|
int32 positionY = 4;
|
||||||
|
int64 price = 5;
|
||||||
|
string text = 6;
|
||||||
|
string position = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetPixelRequest {
|
||||||
|
string position = 1;
|
||||||
|
string color = 2;
|
||||||
|
string text = 3;
|
||||||
|
int64 price = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetPixelReply {
|
||||||
|
string error = 1;
|
||||||
|
bool success = 2;
|
||||||
|
optional GetPixelReply pixel = 3;
|
||||||
|
}
|
@@ -1,26 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
option csharp_namespace = "NadekoBot.GrpcApi";
|
|
||||||
|
|
||||||
package econ;
|
|
||||||
|
|
||||||
service GrpcEcon {
|
|
||||||
rpc GetEconomy(EconomyRequest) returns (EconomyReply);
|
|
||||||
}
|
|
||||||
|
|
||||||
message EconomyRequest {
|
|
||||||
string guildId = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message EconomyReply {
|
|
||||||
uint64 totalOwned = 1;
|
|
||||||
uint64 byTopOnePercent = 2;
|
|
||||||
uint64 plantedAmount = 3;
|
|
||||||
uint64 ownedByTheBot = 4;
|
|
||||||
uint64 inTheBank = 5;
|
|
||||||
uint64 totalEconomy = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
message CurrencyLbRequest {
|
|
||||||
int32 page = 1;
|
|
||||||
}
|
|
60
src/NadekoBot.GrpcApiBase/protos/fin.proto
Normal file
60
src/NadekoBot.GrpcApiBase/protos/fin.proto
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
option csharp_namespace = "NadekoBot.GrpcApi";
|
||||||
|
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
|
||||||
|
package fin;
|
||||||
|
|
||||||
|
service GrpcFin {
|
||||||
|
rpc GetTransactions(GetTransactionsRequest) returns (GetTransactionsReply);
|
||||||
|
rpc GetHoldings(GetHoldingsRequest) returns (GetHoldingsReply);
|
||||||
|
rpc Withdraw(WithdrawRequest) returns (WithdrawReply);
|
||||||
|
rpc Deposit(DepositRequest) returns (DepositReply);
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetTransactionsRequest {
|
||||||
|
int32 page = 1;
|
||||||
|
uint64 userId = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetTransactionsReply {
|
||||||
|
repeated TransactionReply transactions = 1;
|
||||||
|
int32 total = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TransactionReply {
|
||||||
|
int64 amount = 1;
|
||||||
|
string note = 2;
|
||||||
|
string type = 3;
|
||||||
|
string extra = 4;
|
||||||
|
google.protobuf.Timestamp timestamp = 5;
|
||||||
|
string id = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetHoldingsRequest {
|
||||||
|
uint64 userId = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetHoldingsReply {
|
||||||
|
int64 cash = 1;
|
||||||
|
int64 bank = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WithdrawRequest {
|
||||||
|
uint64 userId = 1;
|
||||||
|
int64 amount = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WithdrawReply {
|
||||||
|
bool success = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DepositRequest {
|
||||||
|
uint64 userId = 1;
|
||||||
|
int64 amount = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DepositReply {
|
||||||
|
bool success = 1;
|
||||||
|
}
|
@@ -3,13 +3,11 @@ syntax = "proto3";
|
|||||||
option csharp_namespace = "NadekoBot.GrpcApi";
|
option csharp_namespace = "NadekoBot.GrpcApi";
|
||||||
|
|
||||||
import "google/protobuf/empty.proto";
|
import "google/protobuf/empty.proto";
|
||||||
import "google/protobuf/timestamp.proto";
|
|
||||||
|
|
||||||
package other;
|
package other;
|
||||||
|
|
||||||
service GrpcOther {
|
service GrpcOther {
|
||||||
rpc BotOnGuild(BotOnGuildRequest) returns (BotOnGuildReply);
|
rpc BotOnGuild(BotOnGuildRequest) returns (BotOnGuildReply);
|
||||||
rpc GetGuilds(google.protobuf.Empty) returns (GetGuildsReply);
|
|
||||||
rpc GetTextChannels(GetTextChannelsRequest) returns (GetTextChannelsReply);
|
rpc GetTextChannels(GetTextChannelsRequest) returns (GetTextChannelsReply);
|
||||||
rpc GetRoles(GetRolesRequest) returns (GetRolesReply);
|
rpc GetRoles(GetRolesRequest) returns (GetRolesReply);
|
||||||
|
|
||||||
@@ -17,10 +15,15 @@ service GrpcOther {
|
|||||||
rpc GetXpLb(GetLbRequest) returns (XpLbReply);
|
rpc GetXpLb(GetLbRequest) returns (XpLbReply);
|
||||||
rpc GetWaifuLb(GetLbRequest) returns (WaifuLbReply);
|
rpc GetWaifuLb(GetLbRequest) returns (WaifuLbReply);
|
||||||
|
|
||||||
rpc GetShardStatuses(google.protobuf.Empty) returns (GetShardStatusesReply);
|
rpc GetShardStats(google.protobuf.Empty) returns (stream ShardStatsReply);
|
||||||
|
rpc GetCommandFeed(google.protobuf.Empty) returns (stream CommandFeedEntry);
|
||||||
rpc GetServerInfo(ServerInfoRequest) returns (GetServerInfoReply);
|
rpc GetServerInfo(ServerInfoRequest) returns (GetServerInfoReply);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message CommandFeedEntry {
|
||||||
|
string command = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message GetRolesRequest {
|
message GetRolesRequest {
|
||||||
uint64 guildId = 1;
|
uint64 guildId = 1;
|
||||||
}
|
}
|
||||||
@@ -37,26 +40,13 @@ message BotOnGuildReply {
|
|||||||
bool success = 1;
|
bool success = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetGuildsReply {
|
message ShardStatsReply {
|
||||||
repeated GuildReply guilds = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GuildReply {
|
|
||||||
uint64 id = 1;
|
|
||||||
string name = 2;
|
|
||||||
string iconUrl = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetShardStatusesReply {
|
|
||||||
repeated ShardStatusReply shards = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ShardStatusReply {
|
|
||||||
int32 id = 1;
|
int32 id = 1;
|
||||||
string status = 2;
|
string status = 2;
|
||||||
|
|
||||||
int32 guildCount = 3;
|
int32 guildCount = 3;
|
||||||
google.protobuf.Timestamp lastUpdate = 4;
|
string uptime = 4;
|
||||||
|
int64 commands = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetTextChannelsRequest{
|
message GetTextChannelsRequest{
|
||||||
|
108
src/NadekoBot.GrpcApiBase/protos/xp.proto
Normal file
108
src/NadekoBot.GrpcApiBase/protos/xp.proto
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
option csharp_namespace = "NadekoBot.GrpcApi";
|
||||||
|
|
||||||
|
package xp;
|
||||||
|
|
||||||
|
service GrpcXp {
|
||||||
|
rpc GetXpLb(GetXpLbRequest) returns (GetXpLbReply);
|
||||||
|
rpc ResetUserXp(ResetUserXpRequest) returns (ResetUserXpReply);
|
||||||
|
|
||||||
|
rpc GetXpSettings(GetXpSettingsRequest) returns (GetXpSettingsReply);
|
||||||
|
|
||||||
|
rpc AddExclusion(AddExclusionRequest) returns (AddExclusionReply);
|
||||||
|
rpc DeleteExclusion(DeleteExclusionRequest) returns (DeleteExclusionReply);
|
||||||
|
|
||||||
|
rpc AddReward(AddRewardRequest) returns (AddRewardReply);
|
||||||
|
rpc DeleteReward(DeleteRewardRequest) returns (DeleteRewardReply);
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetXpLbRequest {
|
||||||
|
uint64 guildId = 1;
|
||||||
|
int32 page = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetXpLbReply {
|
||||||
|
repeated XpLbUserReply users = 1;
|
||||||
|
int32 total = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message XpLbUserReply {
|
||||||
|
uint64 userId = 1;
|
||||||
|
string username = 2;
|
||||||
|
int64 xp = 3;
|
||||||
|
int64 level = 4;
|
||||||
|
string avatar = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ResetUserXpRequest {
|
||||||
|
uint64 guildId = 1;
|
||||||
|
uint64 userId = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ResetUserXpReply {
|
||||||
|
bool success = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetXpSettingsReply {
|
||||||
|
repeated ExclItemReply exclusions = 1;
|
||||||
|
repeated RewItemReply rewards = 2;
|
||||||
|
bool serverExcluded = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetXpSettingsRequest {
|
||||||
|
uint64 guildId = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ExclItemReply {
|
||||||
|
string type = 1;
|
||||||
|
uint64 id = 2;
|
||||||
|
string name = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RewItemReply {
|
||||||
|
int32 level = 1;
|
||||||
|
string type = 2;
|
||||||
|
string value = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddExclusionRequest {
|
||||||
|
uint64 guildId = 1;
|
||||||
|
string type = 2;
|
||||||
|
uint64 id = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddExclusionReply {
|
||||||
|
bool success = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeleteExclusionRequest {
|
||||||
|
uint64 guildId = 1;
|
||||||
|
string type = 2;
|
||||||
|
uint64 id = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeleteExclusionReply {
|
||||||
|
bool success = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddRewardRequest {
|
||||||
|
uint64 guildId = 1;
|
||||||
|
int32 level = 2;
|
||||||
|
string type = 3;
|
||||||
|
string value = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddRewardReply {
|
||||||
|
bool success = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeleteRewardRequest {
|
||||||
|
uint64 guildId = 1;
|
||||||
|
int32 level = 2;
|
||||||
|
string type = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeleteRewardReply {
|
||||||
|
bool success = 1;
|
||||||
|
}
|
@@ -25,7 +25,6 @@ public static class DiscordUserExtensions
|
|||||||
{
|
{
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
Username = username,
|
Username = username,
|
||||||
Discriminator = discrim,
|
|
||||||
AvatarId = avatarId,
|
AvatarId = avatarId,
|
||||||
TotalXp = 0,
|
TotalXp = 0,
|
||||||
CurrencyAmount = 0
|
CurrencyAmount = 0
|
||||||
@@ -33,7 +32,6 @@ public static class DiscordUserExtensions
|
|||||||
old => new()
|
old => new()
|
||||||
{
|
{
|
||||||
Username = username,
|
Username = username,
|
||||||
Discriminator = discrim,
|
|
||||||
AvatarId = avatarId
|
AvatarId = avatarId
|
||||||
},
|
},
|
||||||
() => new()
|
() => new()
|
||||||
@@ -49,8 +47,7 @@ public static class DiscordUserExtensions
|
|||||||
() => new()
|
() => new()
|
||||||
{
|
{
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
Username = "Unknown",
|
Username = "??Unknown",
|
||||||
Discriminator = "????",
|
|
||||||
AvatarId = string.Empty,
|
AvatarId = string.Empty,
|
||||||
TotalXp = 0,
|
TotalXp = 0,
|
||||||
CurrencyAmount = 0
|
CurrencyAmount = 0
|
||||||
|
@@ -44,9 +44,6 @@ public static class UserXpExtensions
|
|||||||
.CountAsyncLinqToDB()
|
.CountAsyncLinqToDB()
|
||||||
+ 1;
|
+ 1;
|
||||||
|
|
||||||
public static void ResetGuildUserXp(this DbSet<UserXpStats> xps, ulong userId, ulong guildId)
|
|
||||||
=> xps.Delete(x => x.UserId == userId && x.GuildId == guildId);
|
|
||||||
|
|
||||||
public static void ResetGuildXp(this DbSet<UserXpStats> xps, ulong guildId)
|
public static void ResetGuildXp(this DbSet<UserXpStats> xps, ulong guildId)
|
||||||
=> xps.Delete(x => x.GuildId == guildId);
|
=> xps.Delete(x => x.GuildId == guildId);
|
||||||
|
|
||||||
|
@@ -7,7 +7,7 @@ public class DiscordUser : DbEntity
|
|||||||
{
|
{
|
||||||
public ulong UserId { get; set; }
|
public ulong UserId { get; set; }
|
||||||
public string Username { get; set; }
|
public string Username { get; set; }
|
||||||
public string Discriminator { get; set; }
|
// public string Discriminator { get; set; }
|
||||||
public string AvatarId { get; set; }
|
public string AvatarId { get; set; }
|
||||||
|
|
||||||
public int? ClubId { get; set; }
|
public int? ClubId { get; set; }
|
||||||
@@ -27,9 +27,6 @@ public class DiscordUser : DbEntity
|
|||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(Discriminator) || Discriminator == "0000")
|
|
||||||
return Username;
|
return Username;
|
||||||
|
|
||||||
return Username + "#" + Discriminator;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
8
src/NadekoBot/Db/Models/FlagTranslateChannel.cs
Normal file
8
src/NadekoBot/Db/Models/FlagTranslateChannel.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#nullable disable
|
||||||
|
namespace NadekoBot.Db.Models;
|
||||||
|
|
||||||
|
public class FlagTranslateChannel : DbEntity
|
||||||
|
{
|
||||||
|
public ulong GuildId { get; set; }
|
||||||
|
public ulong ChannelId { get; set; }
|
||||||
|
}
|
19
src/NadekoBot/Db/Models/NCanvas/NCanvas.cs
Normal file
19
src/NadekoBot/Db/Models/NCanvas/NCanvas.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace NadekoBot.Db.Models;
|
||||||
|
|
||||||
|
public class NCPixel
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public required int Position { get; init; }
|
||||||
|
|
||||||
|
public required long Price { get; init; }
|
||||||
|
|
||||||
|
public required ulong OwnerId { get; init; }
|
||||||
|
public required uint Color { get; init; }
|
||||||
|
|
||||||
|
[MaxLength(256)]
|
||||||
|
public required string Text { get; init; }
|
||||||
|
}
|
@@ -73,6 +73,24 @@ public abstract class NadekoContext : DbContext
|
|||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
|
#region Flag Translate
|
||||||
|
|
||||||
|
modelBuilder.Entity<FlagTranslateChannel>()
|
||||||
|
.HasIndex(x => new { x.GuildId, x.ChannelId })
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region NCanvas
|
||||||
|
|
||||||
|
modelBuilder.Entity<NCPixel>()
|
||||||
|
.HasAlternateKey(x => x.Position);
|
||||||
|
|
||||||
|
modelBuilder.Entity<NCPixel>()
|
||||||
|
.HasIndex(x => x.OwnerId);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region QUOTES
|
#region QUOTES
|
||||||
|
|
||||||
var quoteEntity = modelBuilder.Entity<Quote>();
|
var quoteEntity = modelBuilder.Entity<Quote>();
|
||||||
|
@@ -5,6 +5,11 @@ namespace NadekoBot.Migrations;
|
|||||||
|
|
||||||
public static class MigrationQueries
|
public static class MigrationQueries
|
||||||
{
|
{
|
||||||
|
public static void UpdateUsernames(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.Sql("UPDATE DiscordUser SET Username = '??' + Username WHERE Discriminator = '????';");
|
||||||
|
}
|
||||||
|
|
||||||
public static void MigrateRero(MigrationBuilder migrationBuilder)
|
public static void MigrateRero(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
if (migrationBuilder.IsSqlite())
|
if (migrationBuilder.IsSqlite())
|
||||||
|
3824
src/NadekoBot/Migrations/PostgreSql/20241028033704_ncanvas.Designer.cs
generated
Normal file
3824
src/NadekoBot/Migrations/PostgreSql/20241028033704_ncanvas.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,54 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace NadekoBot.Migrations.PostgreSql
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class ncanvas : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ncpixel",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
position = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
price = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
ownerid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||||
|
color = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
text = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_ncpixel", x => x.id);
|
||||||
|
table.UniqueConstraint("ak_ncpixel_position", x => x.position);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_discorduser_username",
|
||||||
|
table: "discorduser",
|
||||||
|
column: "username");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_ncpixel_ownerid",
|
||||||
|
table: "ncpixel",
|
||||||
|
column: "ownerid");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "ncpixel");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "ix_discorduser_username",
|
||||||
|
table: "discorduser");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3851
src/NadekoBot/Migrations/PostgreSql/20241102022956_no-discrim-and-flag-translate.Designer.cs
generated
Normal file
3851
src/NadekoBot/Migrations/PostgreSql/20241102022956_no-discrim-and-flag-translate.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,56 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace NadekoBot.Migrations.PostgreSql
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class nodiscrimandflagtranslate : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
MigrationQueries.UpdateUsernames(migrationBuilder);
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "discriminator",
|
||||||
|
table: "discorduser");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "flagtranslatechannel",
|
||||||
|
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),
|
||||||
|
dateadded = table.Column<DateTime>(type: "timestamp without time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_flagtranslatechannel", x => x.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_flagtranslatechannel_guildid_channelid",
|
||||||
|
table: "flagtranslatechannel",
|
||||||
|
columns: new[] { "guildid", "channelid" },
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "flagtranslatechannel");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "discriminator",
|
||||||
|
table: "discorduser",
|
||||||
|
type: "text",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -751,10 +751,6 @@ namespace NadekoBot.Migrations.PostgreSql
|
|||||||
.HasColumnType("timestamp without time zone")
|
.HasColumnType("timestamp without time zone")
|
||||||
.HasColumnName("dateadded");
|
.HasColumnName("dateadded");
|
||||||
|
|
||||||
b.Property<string>("Discriminator")
|
|
||||||
.HasColumnType("text")
|
|
||||||
.HasColumnName("discriminator");
|
|
||||||
|
|
||||||
b.Property<bool>("IsClubAdmin")
|
b.Property<bool>("IsClubAdmin")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("boolean")
|
.HasColumnType("boolean")
|
||||||
@@ -799,6 +795,9 @@ namespace NadekoBot.Migrations.PostgreSql
|
|||||||
b.HasIndex("UserId")
|
b.HasIndex("UserId")
|
||||||
.HasDatabaseName("ix_discorduser_userid");
|
.HasDatabaseName("ix_discorduser_userid");
|
||||||
|
|
||||||
|
b.HasIndex("Username")
|
||||||
|
.HasDatabaseName("ix_discorduser_username");
|
||||||
|
|
||||||
b.ToTable("discorduser", (string)null);
|
b.ToTable("discorduser", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -995,6 +994,37 @@ namespace NadekoBot.Migrations.PostgreSql
|
|||||||
b.ToTable("filteredword", (string)null);
|
b.ToTable("filteredword", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Db.Models.FlagTranslateChannel", 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<DateTime?>("DateAdded")
|
||||||
|
.HasColumnType("timestamp without time zone")
|
||||||
|
.HasColumnName("dateadded");
|
||||||
|
|
||||||
|
b.Property<decimal>("GuildId")
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("guildid");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_flagtranslatechannel");
|
||||||
|
|
||||||
|
b.HasIndex("GuildId", "ChannelId")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("ix_flagtranslatechannel_guildid_channelid");
|
||||||
|
|
||||||
|
b.ToTable("flagtranslatechannel", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Db.Models.FollowedStream", b =>
|
modelBuilder.Entity("NadekoBot.Db.Models.FollowedStream", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -1627,6 +1657,49 @@ namespace NadekoBot.Migrations.PostgreSql
|
|||||||
b.ToTable("muteduserid", (string)null);
|
b.ToTable("muteduserid", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Db.Models.NCPixel", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("Color")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("color");
|
||||||
|
|
||||||
|
b.Property<decimal>("OwnerId")
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("ownerid");
|
||||||
|
|
||||||
|
b.Property<int>("Position")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("position");
|
||||||
|
|
||||||
|
b.Property<long>("Price")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("price");
|
||||||
|
|
||||||
|
b.Property<string>("Text")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)")
|
||||||
|
.HasColumnName("text");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_ncpixel");
|
||||||
|
|
||||||
|
b.HasAlternateKey("Position")
|
||||||
|
.HasName("ak_ncpixel_position");
|
||||||
|
|
||||||
|
b.HasIndex("OwnerId")
|
||||||
|
.HasDatabaseName("ix_ncpixel_ownerid");
|
||||||
|
|
||||||
|
b.ToTable("ncpixel", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Db.Models.NadekoExpression", b =>
|
modelBuilder.Entity("NadekoBot.Db.Models.NadekoExpression", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
2953
src/NadekoBot/Migrations/Sqlite/20241028033656_ncanvas.Designer.cs
generated
Normal file
2953
src/NadekoBot/Migrations/Sqlite/20241028033656_ncanvas.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
53
src/NadekoBot/Migrations/Sqlite/20241028033656_ncanvas.cs
Normal file
53
src/NadekoBot/Migrations/Sqlite/20241028033656_ncanvas.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace NadekoBot.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class ncanvas : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "NCPixel",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
Position = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
Price = table.Column<long>(type: "INTEGER", nullable: false),
|
||||||
|
OwnerId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||||
|
Color = table.Column<uint>(type: "INTEGER", nullable: false),
|
||||||
|
Text = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_NCPixel", x => x.Id);
|
||||||
|
table.UniqueConstraint("AK_NCPixel_Position", x => x.Position);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_DiscordUser_Username",
|
||||||
|
table: "DiscordUser",
|
||||||
|
column: "Username");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_NCPixel_OwnerId",
|
||||||
|
table: "NCPixel",
|
||||||
|
column: "OwnerId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "NCPixel");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_DiscordUser_Username",
|
||||||
|
table: "DiscordUser");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2973
src/NadekoBot/Migrations/Sqlite/20241102022949_no-discrim-and-flag-translate.Designer.cs
generated
Normal file
2973
src/NadekoBot/Migrations/Sqlite/20241102022949_no-discrim-and-flag-translate.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,55 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace NadekoBot.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class nodiscrimandflagtranslate : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
MigrationQueries.UpdateUsernames(migrationBuilder);
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Discriminator",
|
||||||
|
table: "DiscordUser");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "FlagTranslateChannel",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||||
|
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||||
|
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_FlagTranslateChannel", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_FlagTranslateChannel_GuildId_ChannelId",
|
||||||
|
table: "FlagTranslateChannel",
|
||||||
|
columns: new[] { "GuildId", "ChannelId" },
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "FlagTranslateChannel");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Discriminator",
|
||||||
|
table: "DiscordUser",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -560,9 +560,6 @@ namespace NadekoBot.Migrations
|
|||||||
b.Property<DateTime?>("DateAdded")
|
b.Property<DateTime?>("DateAdded")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<string>("Discriminator")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<bool>("IsClubAdmin")
|
b.Property<bool>("IsClubAdmin")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("INTEGER")
|
.HasColumnType("INTEGER")
|
||||||
@@ -596,6 +593,8 @@ namespace NadekoBot.Migrations
|
|||||||
|
|
||||||
b.HasIndex("UserId");
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.HasIndex("Username");
|
||||||
|
|
||||||
b.ToTable("DiscordUser");
|
b.ToTable("DiscordUser");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -741,6 +740,29 @@ namespace NadekoBot.Migrations
|
|||||||
b.ToTable("FilteredWord");
|
b.ToTable("FilteredWord");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Db.Models.FlagTranslateChannel", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<ulong>("ChannelId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DateAdded")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<ulong>("GuildId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("GuildId", "ChannelId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("FlagTranslateChannel");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Db.Models.FollowedStream", b =>
|
modelBuilder.Entity("NadekoBot.Db.Models.FollowedStream", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -1213,6 +1235,38 @@ namespace NadekoBot.Migrations
|
|||||||
b.ToTable("MutedUserId");
|
b.ToTable("MutedUserId");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Db.Models.NCPixel", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<uint>("Color")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<ulong>("OwnerId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Position")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long>("Price")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Text")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasAlternateKey("Position");
|
||||||
|
|
||||||
|
b.HasIndex("OwnerId");
|
||||||
|
|
||||||
|
b.ToTable("NCPixel");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Db.Models.NadekoExpression", b =>
|
modelBuilder.Entity("NadekoBot.Db.Models.NadekoExpression", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
@@ -25,6 +25,13 @@ public partial class Administration
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the user can't aar the role which is greater or equal to the bot's highest role
|
||||||
|
if (role.Position >= ((SocketGuild)ctx.Guild).CurrentUser.GetRoles().Max(x => x.Position))
|
||||||
|
{
|
||||||
|
await Response().Error(strs.hierarchy).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var roles = await _service.ToggleAarAsync(ctx.Guild.Id, role.Id);
|
var roles = await _service.ToggleAarAsync(ctx.Guild.Id, role.Id);
|
||||||
if (roles.Count == 0)
|
if (roles.Count == 0)
|
||||||
await Response().Confirm(strs.aar_disabled).SendAsync();
|
await Response().Confirm(strs.aar_disabled).SendAsync();
|
||||||
|
@@ -339,7 +339,7 @@ public class GreetService : INService, IReadyExecutor
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Error(ex, "Error sending greet dm");
|
Log.Warning(ex, "Unable to send Greet DM. Probably the user has closed DMs");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -453,7 +453,6 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
|
|||||||
{
|
{
|
||||||
x.UserId,
|
x.UserId,
|
||||||
x.Username,
|
x.Username,
|
||||||
x.Discriminator
|
|
||||||
})
|
})
|
||||||
.Where(x => users.Select(y => y.Id).Contains(x.UserId))
|
.Where(x => users.Select(y => y.Id).Contains(x.UserId))
|
||||||
.ToArrayAsyncEF();
|
.ToArrayAsyncEF();
|
||||||
@@ -465,12 +464,11 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
|
|||||||
UserId = x.Id,
|
UserId = x.Id,
|
||||||
AvatarId = x.AvatarId,
|
AvatarId = x.AvatarId,
|
||||||
Username = x.Username,
|
Username = x.Username,
|
||||||
Discriminator = x.Discriminator
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var added = (await ctx.BulkCopyAsync(usersToAdd)).RowsCopied;
|
var added = (await ctx.BulkCopyAsync(usersToAdd)).RowsCopied;
|
||||||
var toUpdateUserIds = presentDbUsers
|
var toUpdateUserIds = presentDbUsers
|
||||||
.Where(x => x.Username == "Unknown" && x.Discriminator == "????")
|
.Where(x => x.Username.StartsWith("??"))
|
||||||
.Select(x => x.UserId)
|
.Select(x => x.UserId)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
@@ -481,7 +479,6 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
|
|||||||
.UpdateAsync(x => new DiscordUser()
|
.UpdateAsync(x => new DiscordUser()
|
||||||
{
|
{
|
||||||
Username = user.Username,
|
Username = user.Username,
|
||||||
Discriminator = user.Discriminator,
|
|
||||||
|
|
||||||
// .award tends to set AvatarId and DateAdded to NULL, so account for that.
|
// .award tends to set AvatarId and DateAdded to NULL, so account for that.
|
||||||
AvatarId = user.AvatarId,
|
AvatarId = user.AvatarId,
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using NadekoBot.Common.ModuleBehaviors;
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
using NadekoBot.Common.TypeReaders.Models;
|
|
||||||
using NadekoBot.Modules.Permissions.Services;
|
using NadekoBot.Modules.Permissions.Services;
|
||||||
using NadekoBot.Db.Models;
|
using NadekoBot.Db.Models;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
@@ -116,7 +116,7 @@ public sealed class AnimalRace : IDisposable
|
|||||||
{
|
{
|
||||||
foreach (var user in _users)
|
foreach (var user in _users)
|
||||||
{
|
{
|
||||||
user.Progress += rng.Next(1, 11);
|
user.Progress += rng.Next(1, 10);
|
||||||
if (user.Progress >= 60)
|
if (user.Progress >= 60)
|
||||||
user.Progress = 60;
|
user.Progress = 60;
|
||||||
}
|
}
|
||||||
@@ -126,7 +126,7 @@ public sealed class AnimalRace : IDisposable
|
|||||||
FinishedUsers.AddRange(finished);
|
FinishedUsers.AddRange(finished);
|
||||||
|
|
||||||
_ = OnStateUpdate?.Invoke(this);
|
_ = OnStateUpdate?.Invoke(this);
|
||||||
await Task.Delay(2500);
|
await Task.Delay(1750);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FinishedUsers[0].Bet > 0)
|
if (FinishedUsers[0].Bet > 0)
|
||||||
|
@@ -26,6 +26,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
private readonly NumberFormatInfo _enUsCulture;
|
private readonly NumberFormatInfo _enUsCulture;
|
||||||
private readonly DownloadTracker _tracker;
|
private readonly DownloadTracker _tracker;
|
||||||
private readonly GamblingConfigService _configService;
|
private readonly GamblingConfigService _configService;
|
||||||
|
private readonly FontProvider _fonts;
|
||||||
private readonly IBankService _bank;
|
private readonly IBankService _bank;
|
||||||
private readonly IRemindService _remind;
|
private readonly IRemindService _remind;
|
||||||
private readonly GamblingTxTracker _gamblingTxTracker;
|
private readonly GamblingTxTracker _gamblingTxTracker;
|
||||||
@@ -38,6 +39,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
DiscordSocketClient client,
|
DiscordSocketClient client,
|
||||||
DownloadTracker tracker,
|
DownloadTracker tracker,
|
||||||
GamblingConfigService configService,
|
GamblingConfigService configService,
|
||||||
|
FontProvider fonts,
|
||||||
IBankService bank,
|
IBankService bank,
|
||||||
IRemindService remind,
|
IRemindService remind,
|
||||||
IPatronageService patronage,
|
IPatronageService patronage,
|
||||||
@@ -52,12 +54,14 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
_remind = remind;
|
_remind = remind;
|
||||||
_gamblingTxTracker = gamblingTxTracker;
|
_gamblingTxTracker = gamblingTxTracker;
|
||||||
_ps = patronage;
|
_ps = patronage;
|
||||||
|
_rng = new NadekoRandom();
|
||||||
|
|
||||||
_enUsCulture = new CultureInfo("en-US", false).NumberFormat;
|
_enUsCulture = new CultureInfo("en-US", false).NumberFormat;
|
||||||
_enUsCulture.NumberDecimalDigits = 0;
|
_enUsCulture.NumberDecimalDigits = 0;
|
||||||
_enUsCulture.NumberGroupSeparator = " ";
|
_enUsCulture.NumberGroupSeparator = " ";
|
||||||
_tracker = tracker;
|
_tracker = tracker;
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
|
_fonts = fonts;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetBalanceStringAsync(ulong userId)
|
public async Task<string> GetBalanceStringAsync(ulong userId)
|
||||||
@@ -140,6 +144,18 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
(smc) => RemindTimelyAction(smc, DateTime.UtcNow.Add(TimeSpan.FromMilliseconds(ms)))
|
(smc) => RemindTimelyAction(smc, DateTime.UtcNow.Add(TimeSpan.FromMilliseconds(ms)))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
private NadekoInteractionBase CreateTimelyInteraction()
|
||||||
|
=> _inter
|
||||||
|
.Create(ctx.User.Id,
|
||||||
|
new ButtonBuilder(
|
||||||
|
label: "Timely",
|
||||||
|
emote: Emoji.Parse("💰"),
|
||||||
|
customId: "timely:" + _rng.Next(123456, 999999)),
|
||||||
|
async (smc) =>
|
||||||
|
{
|
||||||
|
await ClaimTimely();
|
||||||
|
});
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
public async Task Timely()
|
public async Task Timely()
|
||||||
{
|
{
|
||||||
@@ -151,6 +167,69 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Config.Timely.RequirePassword)
|
||||||
|
{
|
||||||
|
// var password = _service.GeneratePassword();
|
||||||
|
//
|
||||||
|
// var img = new Image<Rgba32>(100, 40);
|
||||||
|
//
|
||||||
|
// var font = _fonts.NotoSans.CreateFont(30);
|
||||||
|
// var outlinePen = new SolidPen(Color.Black, 1f);
|
||||||
|
// var strikeoutRun = new RichTextRun
|
||||||
|
// {
|
||||||
|
// Start = 0,
|
||||||
|
// End = password.GetGraphemeCount(),
|
||||||
|
// Font = font,
|
||||||
|
// StrikeoutPen = new SolidPen(Color.White, 3),
|
||||||
|
// TextDecorations = TextDecorations.Strikeout
|
||||||
|
// };
|
||||||
|
// // draw password on the image
|
||||||
|
// img.Mutate(x =>
|
||||||
|
// {
|
||||||
|
// x.DrawText(new RichTextOptions(font)
|
||||||
|
// {
|
||||||
|
// HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
// VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
// FallbackFontFamilies = _fonts.FallBackFonts,
|
||||||
|
// Origin = new(50, 20),
|
||||||
|
// TextRuns = [strikeoutRun]
|
||||||
|
// },
|
||||||
|
// password,
|
||||||
|
// Brushes.Solid(Color.White),
|
||||||
|
// outlinePen);
|
||||||
|
// });
|
||||||
|
// using var stream = await img.ToStreamAsync();
|
||||||
|
// var captcha = await Response()
|
||||||
|
// .Embed(_sender.CreateEmbed()
|
||||||
|
// .WithOkColor()
|
||||||
|
// .WithImageUrl("attachment://timely.png"))
|
||||||
|
// .File(stream, "timely.png")
|
||||||
|
// .SendAsync();
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
// var userInput = await GetUserInputAsync(ctx.User.Id, ctx.Channel.Id);
|
||||||
|
// if (userInput?.ToLowerInvariant() != password?.ToLowerInvariant())
|
||||||
|
// {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// finally
|
||||||
|
// {
|
||||||
|
// _ = captcha.DeleteAsync();
|
||||||
|
// }
|
||||||
|
|
||||||
|
var interaction = CreateTimelyInteraction();
|
||||||
|
var msg = await Response().Pending(strs.timely_button).Interaction(interaction).SendAsync();
|
||||||
|
await msg.DeleteAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ClaimTimely();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ClaimTimely()
|
||||||
|
{
|
||||||
|
var period = Config.Timely.Cooldown;
|
||||||
if (await _service.ClaimTimelyAsync(ctx.User.Id, period) is { } remainder)
|
if (await _service.ClaimTimelyAsync(ctx.User.Id, period) is { } remainder)
|
||||||
{
|
{
|
||||||
// Get correct time form remainder
|
// Get correct time form remainder
|
||||||
@@ -169,6 +248,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var val = Config.Timely.Amount;
|
||||||
var patron = await _ps.GetPatronAsync(ctx.User.Id);
|
var patron = await _ps.GetPatronAsync(ctx.User.Id);
|
||||||
|
|
||||||
var percentBonus = (_ps.PercentBonus(patron) / 100f);
|
var percentBonus = (_ps.PercentBonus(patron) / 100f);
|
||||||
@@ -762,6 +842,8 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
private static readonly ImmutableArray<string> _emojis =
|
private static readonly ImmutableArray<string> _emojis =
|
||||||
new[] { "⬆", "↖", "⬅", "↙", "⬇", "↘", "➡", "↗" }.ToImmutableArray();
|
new[] { "⬆", "↖", "⬅", "↙", "⬇", "↘", "➡", "↗" }.ToImmutableArray();
|
||||||
|
|
||||||
|
private readonly NadekoRandom _rng;
|
||||||
|
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
public async Task LuckyLadder([OverrideTypeReader(typeof(BalanceTypeReader))] long amount)
|
public async Task LuckyLadder([OverrideTypeReader(typeof(BalanceTypeReader))] long amount)
|
||||||
|
@@ -11,7 +11,7 @@ namespace NadekoBot.Modules.Gambling.Common;
|
|||||||
public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
|
public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
|
||||||
{
|
{
|
||||||
[Comment("""DO NOT CHANGE""")]
|
[Comment("""DO NOT CHANGE""")]
|
||||||
public int Version { get; set; } = 8;
|
public int Version { get; set; } = 9;
|
||||||
|
|
||||||
[Comment("""Currency settings""")]
|
[Comment("""Currency settings""")]
|
||||||
public CurrencyConfig Currency { get; set; }
|
public CurrencyConfig Currency { get; set; }
|
||||||
@@ -111,6 +111,11 @@ public partial class TimelyConfig
|
|||||||
setting to 0 or less will disable this feature
|
setting to 0 or less will disable this feature
|
||||||
""")]
|
""")]
|
||||||
public int Cooldown { get; set; } = 24;
|
public int Cooldown { get; set; } = 24;
|
||||||
|
|
||||||
|
[Comment("""
|
||||||
|
Whether the users are required to type a password when they do timely.
|
||||||
|
""")]
|
||||||
|
public bool RequirePassword { get; set; } = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cloneable]
|
[Cloneable]
|
||||||
|
@@ -144,6 +144,11 @@ public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
|
|||||||
ConfigPrinters.ToString,
|
ConfigPrinters.ToString,
|
||||||
val => val >= 0);
|
val => val >= 0);
|
||||||
|
|
||||||
|
AddParsedProp("timely.pass",
|
||||||
|
gs => gs.Timely.RequirePassword,
|
||||||
|
bool.TryParse,
|
||||||
|
ConfigPrinters.ToString);
|
||||||
|
|
||||||
Migrate();
|
Migrate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,22 +172,6 @@ public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.Version < 5)
|
|
||||||
{
|
|
||||||
ModifyConfig(c =>
|
|
||||||
{
|
|
||||||
c.Version = 5;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.Version < 6)
|
|
||||||
{
|
|
||||||
ModifyConfig(c =>
|
|
||||||
{
|
|
||||||
c.Version = 6;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.Version < 7)
|
if (data.Version < 7)
|
||||||
{
|
{
|
||||||
ModifyConfig(c =>
|
ModifyConfig(c =>
|
||||||
@@ -199,5 +188,13 @@ public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
|
|||||||
c.Waifu.Decay.UnclaimedDecayPercent = 0;
|
c.Waifu.Decay.UnclaimedDecayPercent = 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.Version < 9)
|
||||||
|
{
|
||||||
|
ModifyConfig(c =>
|
||||||
|
{
|
||||||
|
c.Version = 9;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -16,6 +16,7 @@ public class GamblingService : INService, IReadyExecutor
|
|||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
private readonly IBotCache _cache;
|
private readonly IBotCache _cache;
|
||||||
private readonly GamblingConfigService _gss;
|
private readonly GamblingConfigService _gss;
|
||||||
|
private readonly NadekoRandom _rng;
|
||||||
|
|
||||||
private static readonly TypedKey<long> _curDecayKey = new("currency:last_decay");
|
private static readonly TypedKey<long> _curDecayKey = new("currency:last_decay");
|
||||||
|
|
||||||
@@ -29,11 +30,19 @@ public class GamblingService : INService, IReadyExecutor
|
|||||||
_client = client;
|
_client = client;
|
||||||
_cache = cache;
|
_cache = cache;
|
||||||
_gss = gss;
|
_gss = gss;
|
||||||
|
_rng = new NadekoRandom();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task OnReadyAsync()
|
public Task OnReadyAsync()
|
||||||
=> Task.WhenAll(CurrencyDecayLoopAsync(), TransactionClearLoopAsync());
|
=> Task.WhenAll(CurrencyDecayLoopAsync(), TransactionClearLoopAsync());
|
||||||
|
|
||||||
|
|
||||||
|
public string GeneratePassword()
|
||||||
|
{
|
||||||
|
var num = _rng.Next((int)Math.Pow(31, 2), (int)Math.Pow(32, 3));
|
||||||
|
return new kwum(num).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task TransactionClearLoopAsync()
|
private async Task TransactionClearLoopAsync()
|
||||||
{
|
{
|
||||||
if (_client.ShardId != 0)
|
if (_client.ShardId != 0)
|
||||||
@@ -133,6 +142,7 @@ public class GamblingService : INService, IReadyExecutor
|
|||||||
private static TypedKey<Dictionary<ulong, long>> _timelyKey
|
private static TypedKey<Dictionary<ulong, long>> _timelyKey
|
||||||
= new("timely:claims");
|
= new("timely:claims");
|
||||||
|
|
||||||
|
|
||||||
public async Task<TimeSpan?> ClaimTimelyAsync(ulong userId, int period)
|
public async Task<TimeSpan?> ClaimTimelyAsync(ulong userId, int period)
|
||||||
{
|
{
|
||||||
if (period == 0)
|
if (period == 0)
|
||||||
@@ -178,7 +188,8 @@ public class GamblingService : INService, IReadyExecutor
|
|||||||
public bool UserHasTimelyReminder(ulong userId)
|
public bool UserHasTimelyReminder(ulong userId)
|
||||||
{
|
{
|
||||||
var db = _db.GetDbContext();
|
var db = _db.GetDbContext();
|
||||||
return db.GetTable<Reminder>().Any(x => x.UserId == userId
|
return db.GetTable<Reminder>()
|
||||||
|
.Any(x => x.UserId == userId
|
||||||
&& x.Type == ReminderType.Timely);
|
&& x.Type == ReminderType.Timely);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,8 +1,11 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
using LinqToDB;
|
||||||
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NadekoBot.Common.ModuleBehaviors;
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
using NadekoBot.Db.Models;
|
using NadekoBot.Db.Models;
|
||||||
using SixLabors.Fonts;
|
using SixLabors.Fonts;
|
||||||
|
using SixLabors.Fonts.Unicode;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.Drawing.Processing;
|
using SixLabors.ImageSharp.Drawing.Processing;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
@@ -25,6 +28,7 @@ public class PlantPickService : INService, IExecNoCommand
|
|||||||
private readonly NadekoRandom _rng;
|
private readonly NadekoRandom _rng;
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
private readonly GamblingConfigService _gss;
|
private readonly GamblingConfigService _gss;
|
||||||
|
private readonly GamblingService _gs;
|
||||||
|
|
||||||
private readonly ConcurrentHashSet<ulong> _generationChannels;
|
private readonly ConcurrentHashSet<ulong> _generationChannels;
|
||||||
private readonly SemaphoreSlim _pickLock = new(1, 1);
|
private readonly SemaphoreSlim _pickLock = new(1, 1);
|
||||||
@@ -37,7 +41,8 @@ public class PlantPickService : INService, IExecNoCommand
|
|||||||
ICurrencyService cs,
|
ICurrencyService cs,
|
||||||
CommandHandler cmdHandler,
|
CommandHandler cmdHandler,
|
||||||
DiscordSocketClient client,
|
DiscordSocketClient client,
|
||||||
GamblingConfigService gss)
|
GamblingConfigService gss,
|
||||||
|
GamblingService gs)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_strings = strings;
|
_strings = strings;
|
||||||
@@ -48,6 +53,7 @@ public class PlantPickService : INService, IExecNoCommand
|
|||||||
_rng = new();
|
_rng = new();
|
||||||
_client = client;
|
_client = client;
|
||||||
_gss = gss;
|
_gss = gss;
|
||||||
|
_gs = gs;
|
||||||
|
|
||||||
using var uow = db.GetDbContext();
|
using var uow = db.GetDbContext();
|
||||||
var guildIds = client.Guilds.Select(x => x.Id).ToList();
|
var guildIds = client.Guilds.Select(x => x.Id).ToList();
|
||||||
@@ -87,6 +93,7 @@ public class PlantPickService : INService, IExecNoCommand
|
|||||||
var toDelete = guildConfig.GenerateCurrencyChannelIds.FirstOrDefault(x => x.Equals(toAdd));
|
var toDelete = guildConfig.GenerateCurrencyChannelIds.FirstOrDefault(x => x.Equals(toAdd));
|
||||||
if (toDelete is not null)
|
if (toDelete is not null)
|
||||||
uow.Remove(toDelete);
|
uow.Remove(toDelete);
|
||||||
|
|
||||||
_generationChannels.TryRemove(cid);
|
_generationChannels.TryRemove(cid);
|
||||||
enabled = false;
|
enabled = false;
|
||||||
}
|
}
|
||||||
@@ -157,8 +164,26 @@ public class PlantPickService : INService, IExecNoCommand
|
|||||||
new PointF(size.Width + 5, size.Height + 10),
|
new PointF(size.Width + 5, size.Height + 10),
|
||||||
new PointF(0, size.Height + 10));
|
new PointF(0, size.Height + 10));
|
||||||
|
|
||||||
|
var strikeoutRun = new RichTextRun
|
||||||
|
{
|
||||||
|
Start = 0,
|
||||||
|
End = pass.GetGraphemeCount(),
|
||||||
|
Font = font,
|
||||||
|
StrikeoutPen = new SolidPen(Color.White, 5),
|
||||||
|
TextDecorations = TextDecorations.Strikeout
|
||||||
|
};
|
||||||
|
|
||||||
// draw the password over the background
|
// draw the password over the background
|
||||||
x.DrawText(pass, font, Color.White, new(0, 0));
|
x.DrawText(new RichTextOptions(font)
|
||||||
|
{
|
||||||
|
Origin = new(0, 0),
|
||||||
|
TextRuns =
|
||||||
|
[
|
||||||
|
strikeoutRun
|
||||||
|
]
|
||||||
|
},
|
||||||
|
pass,
|
||||||
|
new SolidBrush(Color.White));
|
||||||
});
|
});
|
||||||
// return image as a stream for easy sending
|
// return image as a stream for easy sending
|
||||||
var format = img.Metadata.DecodedImageFormat;
|
var format = img.Metadata.DecodedImageFormat;
|
||||||
@@ -208,7 +233,7 @@ public class PlantPickService : INService, IExecNoCommand
|
|||||||
+ " "
|
+ " "
|
||||||
+ GetText(channel.GuildId, strs.pick_pl(prefix));
|
+ GetText(channel.GuildId, strs.pick_pl(prefix));
|
||||||
|
|
||||||
var pw = config.Generation.HasPassword ? GenerateCurrencyPassword().ToUpperInvariant() : null;
|
var pw = config.Generation.HasPassword ? _gs.GeneratePassword().ToUpperInvariant() : null;
|
||||||
|
|
||||||
IUserMessage sent;
|
IUserMessage sent;
|
||||||
var (stream, ext) = await GetRandomCurrencyImageAsync(pw);
|
var (stream, ext) = await GetRandomCurrencyImageAsync(pw);
|
||||||
@@ -232,26 +257,11 @@ public class PlantPickService : INService, IExecNoCommand
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Generate a hexadecimal string from 1000 to ffff.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A hexadecimal string from 1000 to ffff</returns>
|
|
||||||
private string GenerateCurrencyPassword()
|
|
||||||
{
|
|
||||||
// generate a number from 1000 to ffff
|
|
||||||
var num = _rng.Next(4096, 65536);
|
|
||||||
// convert it to hexadecimal
|
|
||||||
return num.ToString("x4");
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<long> PickAsync(
|
public async Task<long> PickAsync(
|
||||||
ulong gid,
|
ulong gid,
|
||||||
ITextChannel ch,
|
ITextChannel ch,
|
||||||
ulong uid,
|
ulong uid,
|
||||||
string pass)
|
string pass)
|
||||||
{
|
|
||||||
await _pickLock.WaitAsync();
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
long amount;
|
long amount;
|
||||||
ulong[] ids;
|
ulong[] ids;
|
||||||
@@ -260,28 +270,25 @@ public class PlantPickService : INService, IExecNoCommand
|
|||||||
// this method will sum all plants with that password,
|
// this method will sum all plants with that password,
|
||||||
// remove them, and get messageids of the removed plants
|
// remove them, and get messageids of the removed plants
|
||||||
|
|
||||||
pass = pass?.Trim().TrimTo(10, true).ToUpperInvariant();
|
pass = pass?.Trim().TrimTo(10, true)?.ToUpperInvariant();
|
||||||
// gets all plants in this channel with the same password
|
// gets all plants in this channel with the same password
|
||||||
var entries = uow.Set<PlantedCurrency>()
|
var entries = await uow.GetTable<PlantedCurrency>()
|
||||||
.AsQueryable()
|
|
||||||
.Where(x => x.ChannelId == ch.Id && pass == x.Password)
|
.Where(x => x.ChannelId == ch.Id && pass == x.Password)
|
||||||
.ToList();
|
.DeleteWithOutputAsync();
|
||||||
// sum how much currency that is, and get all of the message ids (so that i can delete them)
|
|
||||||
|
if (!entries.Any())
|
||||||
|
return 0;
|
||||||
|
|
||||||
amount = entries.Sum(x => x.Amount);
|
amount = entries.Sum(x => x.Amount);
|
||||||
ids = entries.Select(x => x.MessageId).ToArray();
|
ids = entries.Select(x => x.MessageId).ToArray();
|
||||||
// remove them from the database
|
}
|
||||||
uow.RemoveRange(entries);
|
|
||||||
|
|
||||||
|
|
||||||
if (amount > 0)
|
if (amount > 0)
|
||||||
// give the picked currency to the user
|
|
||||||
await _cs.AddAsync(uid, amount, new("currency", "collect"));
|
await _cs.AddAsync(uid, amount, new("currency", "collect"));
|
||||||
await uow.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// delete all of the plant messages which have just been picked
|
|
||||||
_ = ch.DeleteMessagesAsync(ids);
|
_ = ch.DeleteMessagesAsync(ids);
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
@@ -289,11 +296,6 @@ public class PlantPickService : INService, IExecNoCommand
|
|||||||
// return the amount of currency the user picked
|
// return the amount of currency the user picked
|
||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
_pickLock.Release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ulong?> SendPlantMessageAsync(
|
public async Task<ulong?> SendPlantMessageAsync(
|
||||||
ulong gid,
|
ulong gid,
|
||||||
|
@@ -603,7 +603,7 @@ public class WaifuService : INService, IReadyExecutor
|
|||||||
.Where(wi => wi.ClaimerId == waifuId)
|
.Where(wi => wi.ClaimerId == waifuId)
|
||||||
.Select(wi => wi.WaifuId)
|
.Select(wi => wi.WaifuId)
|
||||||
.Contains(x.Id))
|
.Contains(x.Id))
|
||||||
.Select(x => $"{x.Username}#{x.Discriminator}")
|
.Select(x => x.Username)
|
||||||
.ToListAsyncEF();
|
.ToListAsyncEF();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -615,7 +615,7 @@ public class WaifuService : INService, IReadyExecutor
|
|||||||
.Where(wi => wi.AffinityId == waifuId)
|
.Where(wi => wi.AffinityId == waifuId)
|
||||||
.Select(wi => wi.WaifuId)
|
.Select(wi => wi.WaifuId)
|
||||||
.Contains(x.Id))
|
.Contains(x.Id))
|
||||||
.Select(x => $"{x.Username}#{x.Discriminator}")
|
.Select(x => x.Username)
|
||||||
.ToListAsyncEF();
|
.ToListAsyncEF();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -42,15 +42,12 @@ public static class WaifuExtensions
|
|||||||
{
|
{
|
||||||
Affinity = x.Affinity == null
|
Affinity = x.Affinity == null
|
||||||
? null
|
? null
|
||||||
: x.Affinity.Username
|
: x.Affinity.Username,
|
||||||
+ (x.Affinity.Discriminator != "0000" ? "#" + x.Affinity.Discriminator : ""),
|
|
||||||
ClaimerName =
|
ClaimerName =
|
||||||
x.Claimer == null
|
x.Claimer == null
|
||||||
? null
|
? null
|
||||||
: x.Claimer.Username
|
: x.Claimer.Username,
|
||||||
+ (x.Claimer.Discriminator != "0000" ? "#" + x.Claimer.Discriminator : ""),
|
WaifuName = x.Waifu.Username,
|
||||||
WaifuName = x.Waifu.Username
|
|
||||||
+ (x.Waifu.Discriminator != "0000" ? "#" + x.Waifu.Discriminator : ""),
|
|
||||||
Price = x.Price
|
Price = x.Price
|
||||||
})
|
})
|
||||||
.ToListAsyncEF();
|
.ToListAsyncEF();
|
||||||
@@ -62,7 +59,7 @@ public static class WaifuExtensions
|
|||||||
public static ulong GetWaifuUserId(this DbSet<WaifuInfo> waifus, ulong ownerId, string name)
|
public static ulong GetWaifuUserId(this DbSet<WaifuInfo> waifus, ulong ownerId, string name)
|
||||||
=> waifus.AsQueryable()
|
=> waifus.AsQueryable()
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(x => x.Claimer.UserId == ownerId && x.Waifu.Username + "#" + x.Waifu.Discriminator == name)
|
.Where(x => x.Claimer.UserId == ownerId && x.Waifu.Username == name)
|
||||||
.Select(x => x.Waifu.UserId)
|
.Select(x => x.Waifu.UserId)
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|
||||||
@@ -100,7 +97,7 @@ public static class WaifuExtensions
|
|||||||
ctx.Set<DiscordUser>()
|
ctx.Set<DiscordUser>()
|
||||||
.AsQueryable()
|
.AsQueryable()
|
||||||
.Where(u => u.UserId == userId)
|
.Where(u => u.UserId == userId)
|
||||||
.Select(u => u.Username + "#" + u.Discriminator)
|
.Select(u => u.Username)
|
||||||
.FirstOrDefault(),
|
.FirstOrDefault(),
|
||||||
AffinityCount =
|
AffinityCount =
|
||||||
ctx.Set<WaifuUpdate>()
|
ctx.Set<WaifuUpdate>()
|
||||||
@@ -112,14 +109,14 @@ public static class WaifuExtensions
|
|||||||
ctx.Set<DiscordUser>()
|
ctx.Set<DiscordUser>()
|
||||||
.AsQueryable()
|
.AsQueryable()
|
||||||
.Where(u => u.Id == w.AffinityId)
|
.Where(u => u.Id == w.AffinityId)
|
||||||
.Select(u => u.Username + "#" + u.Discriminator)
|
.Select(u => u.Username)
|
||||||
.FirstOrDefault(),
|
.FirstOrDefault(),
|
||||||
ClaimCount = ctx.Set<WaifuInfo>().AsQueryable().Count(x => x.ClaimerId == w.WaifuId),
|
ClaimCount = ctx.Set<WaifuInfo>().AsQueryable().Count(x => x.ClaimerId == w.WaifuId),
|
||||||
ClaimerName =
|
ClaimerName =
|
||||||
ctx.Set<DiscordUser>()
|
ctx.Set<DiscordUser>()
|
||||||
.AsQueryable()
|
.AsQueryable()
|
||||||
.Where(u => u.Id == w.ClaimerId)
|
.Where(u => u.Id == w.ClaimerId)
|
||||||
.Select(u => u.Username + "#" + u.Discriminator)
|
.Select(u => u.Username)
|
||||||
.FirstOrDefault(),
|
.FirstOrDefault(),
|
||||||
DivorceCount =
|
DivorceCount =
|
||||||
ctx.Set<WaifuUpdate>()
|
ctx.Set<WaifuUpdate>()
|
||||||
|
24
src/NadekoBot/Modules/Games/NCanvas/INCanvasService.cs
Normal file
24
src/NadekoBot/Modules/Games/NCanvas/INCanvasService.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using NadekoBot.Db.Models;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Games;
|
||||||
|
|
||||||
|
public interface INCanvasService
|
||||||
|
{
|
||||||
|
Task<uint[]> GetCanvas();
|
||||||
|
Task<NCPixel[]> GetPixelGroup(int position);
|
||||||
|
|
||||||
|
Task<SetPixelResult> SetPixel(
|
||||||
|
int position,
|
||||||
|
uint color,
|
||||||
|
string text,
|
||||||
|
ulong userId,
|
||||||
|
long price);
|
||||||
|
|
||||||
|
Task<bool> SetImage(uint[] img);
|
||||||
|
|
||||||
|
Task<NCPixel?> GetPixel(int x, int y);
|
||||||
|
Task<NCPixel?> GetPixel(int position);
|
||||||
|
int GetHeight();
|
||||||
|
int GetWidth();
|
||||||
|
Task ResetAsync();
|
||||||
|
}
|
305
src/NadekoBot/Modules/Games/NCanvas/NCanvasCommands.cs
Normal file
305
src/NadekoBot/Modules/Games/NCanvas/NCanvasCommands.cs
Normal file
@@ -0,0 +1,305 @@
|
|||||||
|
using NadekoBot.Modules.Gambling.Services;
|
||||||
|
using SixLabors.Fonts;
|
||||||
|
using SixLabors.ImageSharp;
|
||||||
|
using SixLabors.ImageSharp.Advanced;
|
||||||
|
using SixLabors.ImageSharp.Drawing.Processing;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
using Image = SixLabors.ImageSharp.Image;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Games;
|
||||||
|
|
||||||
|
public partial class Games
|
||||||
|
{
|
||||||
|
public sealed class NCanvasCommands : NadekoModule
|
||||||
|
{
|
||||||
|
private readonly INCanvasService _service;
|
||||||
|
private readonly IHttpClientFactory _http;
|
||||||
|
private readonly FontProvider _fonts;
|
||||||
|
private readonly GamblingConfigService _gcs;
|
||||||
|
|
||||||
|
public NCanvasCommands(
|
||||||
|
INCanvasService service,
|
||||||
|
IHttpClientFactory http,
|
||||||
|
FontProvider fonts,
|
||||||
|
GamblingConfigService gcs)
|
||||||
|
{
|
||||||
|
_service = service;
|
||||||
|
_http = http;
|
||||||
|
_fonts = fonts;
|
||||||
|
_gcs = gcs;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public async Task NCanvas()
|
||||||
|
{
|
||||||
|
var pixels = await _service.GetCanvas();
|
||||||
|
var image = new Image<Rgba32>(_service.GetWidth(), _service.GetHeight());
|
||||||
|
|
||||||
|
Parallel.For(0,
|
||||||
|
image.Height,
|
||||||
|
y =>
|
||||||
|
{
|
||||||
|
var pixelAccessor = image.DangerousGetPixelRowMemory(y);
|
||||||
|
var row = pixelAccessor.Span;
|
||||||
|
for (int x = 0; x < image.Width; x++)
|
||||||
|
{
|
||||||
|
row[x] = new Rgba32(pixels[(y * image.Width) + x]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await using var stream = await image.ToStreamAsync();
|
||||||
|
|
||||||
|
var hint = GetText(strs.nc_hint(prefix, _service.GetWidth(), _service.GetHeight()));
|
||||||
|
await Response()
|
||||||
|
.File(stream, "ncanvas.png")
|
||||||
|
.Embed(_sender.CreateEmbed()
|
||||||
|
.WithOkColor()
|
||||||
|
#if GLOBAL_NADEKO
|
||||||
|
.WithDescription("https://dashy.nadeko.bot/ncanvas")
|
||||||
|
#endif
|
||||||
|
.WithFooter(hint)
|
||||||
|
.WithImageUrl("attachment://ncanvas.png"))
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public Task NCzoom(int row, int col)
|
||||||
|
=> NCzoom((col * _service.GetWidth()) + row);
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public async Task NCzoom(kwum position)
|
||||||
|
{
|
||||||
|
var w = _service.GetWidth();
|
||||||
|
var h = _service.GetHeight();
|
||||||
|
|
||||||
|
if (position < 0 || position >= w * h)
|
||||||
|
{
|
||||||
|
await Response().Error(strs.invalid_input).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var img = await GetZoomImage(position);
|
||||||
|
await using var stream = await img.ToStreamAsync();
|
||||||
|
await ctx.Channel.SendFileAsync(stream, $"zoom_{position}.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Image<Rgba32>> GetZoomImage(kwum position)
|
||||||
|
{
|
||||||
|
var w = _service.GetWidth();
|
||||||
|
var pixels = await _service.GetPixelGroup(position);
|
||||||
|
|
||||||
|
var origX = ((position % w) - 2) * 100;
|
||||||
|
var origY = ((position / w) - 2) * 100;
|
||||||
|
|
||||||
|
var image = new Image<Rgba32>(500, 500);
|
||||||
|
|
||||||
|
const float fontSize = 30;
|
||||||
|
|
||||||
|
var posFont = _fonts.NotoSans.CreateFont(fontSize, FontStyle.Bold);
|
||||||
|
var size = TextMeasurer.MeasureSize("wwww", new TextOptions(posFont));
|
||||||
|
var scale = 100f / size.Width;
|
||||||
|
if (scale < 1)
|
||||||
|
posFont = _fonts.NotoSans.CreateFont(fontSize * scale, FontStyle.Bold);
|
||||||
|
var outlinePen = new SolidPen(SixLabors.ImageSharp.Color.Black, 1f);
|
||||||
|
|
||||||
|
Parallel.For(0,
|
||||||
|
pixels.Length,
|
||||||
|
i =>
|
||||||
|
{
|
||||||
|
var pix = pixels[i];
|
||||||
|
var startX = pix.Position % w * 100 - origX;
|
||||||
|
var startY = pix.Position / w * 100 - origY;
|
||||||
|
|
||||||
|
var color = new Rgba32(pix.Color);
|
||||||
|
image.Mutate(x => FillRectangleExtensions.Fill(x,
|
||||||
|
new SolidBrush(color),
|
||||||
|
new RectangleF(startX, startY, 100, 100)));
|
||||||
|
|
||||||
|
image.Mutate(x =>
|
||||||
|
{
|
||||||
|
x.DrawText(new RichTextOptions(posFont)
|
||||||
|
{
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
Origin = new(startX + 50, startY + 50)
|
||||||
|
},
|
||||||
|
((kwum)pix.Position).ToString().PadLeft(2, '2'),
|
||||||
|
Brushes.Solid(SixLabors.ImageSharp.Color.White),
|
||||||
|
outlinePen);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// write the position on each section of the image
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public async Task NcSetPixel(kwum position, string colorHex, [Leftover] string text = "")
|
||||||
|
{
|
||||||
|
if (position < 0 || position >= _service.GetWidth() * _service.GetHeight())
|
||||||
|
{
|
||||||
|
await Response().Error(strs.invalid_input).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (colorHex.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
|
||||||
|
colorHex = colorHex[2..];
|
||||||
|
|
||||||
|
if (!Rgba32.TryParseHex(colorHex, out var clr))
|
||||||
|
{
|
||||||
|
await Response().Error(strs.invalid_color).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pixel = await _service.GetPixel(position);
|
||||||
|
if (pixel is null)
|
||||||
|
{
|
||||||
|
await Response().Error(strs.nc_pixel_not_found).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var prompt = GetText(strs.nc_pixel_set_confirm(Format.Code(position.ToString()),
|
||||||
|
Format.Bold(CurrencyHelper.N(pixel.Price,
|
||||||
|
Culture,
|
||||||
|
_gcs.Data.Currency.Sign))));
|
||||||
|
|
||||||
|
if (!await PromptUserConfirmAsync(_sender.CreateEmbed()
|
||||||
|
.WithPendingColor()
|
||||||
|
.WithDescription(prompt)))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await _service.SetPixel(position, clr.PackedValue, text, ctx.User.Id, pixel.Price);
|
||||||
|
|
||||||
|
if (result == SetPixelResult.NotEnoughMoney)
|
||||||
|
{
|
||||||
|
await Response().Error(strs.not_enough(_gcs.Data.Currency.Sign)).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (result == SetPixelResult.InsufficientPayment)
|
||||||
|
{
|
||||||
|
await Response().Error(strs.nc_insuff_payment).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (result == SetPixelResult.InvalidInput)
|
||||||
|
{
|
||||||
|
await Response().Error(strs.invalid_input).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var img = await GetZoomImage(position);
|
||||||
|
await using var stream = await img.ToStreamAsync();
|
||||||
|
|
||||||
|
await Response()
|
||||||
|
.Embed(_sender.CreateEmbed()
|
||||||
|
.WithOkColor()
|
||||||
|
.WithDescription(GetText(strs.nc_pixel_set(Format.Code(position.ToString()))))
|
||||||
|
.WithImageUrl($"attachment://zoom_{position}.png"))
|
||||||
|
.File(stream, $"zoom_{position}.png")
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public async Task NcPixel(int x, int y)
|
||||||
|
=> await NcPixel((y * _service.GetWidth()) + x);
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public async Task NcPixel(kwum position)
|
||||||
|
{
|
||||||
|
if (position < 0 || position >= _service.GetWidth() * _service.GetHeight())
|
||||||
|
{
|
||||||
|
await Response().Error(strs.invalid_input).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pixel = await _service.GetPixel(position);
|
||||||
|
if (pixel is null)
|
||||||
|
{
|
||||||
|
await Response().Error(strs.nc_pixel_not_found).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var image = new Image<Rgba32>(100, 100);
|
||||||
|
image.Mutate(x
|
||||||
|
=> x.Fill(new SolidBrush(new Rgba32(pixel.Color)),
|
||||||
|
new RectangleF(0, 0, 100, 100)));
|
||||||
|
|
||||||
|
await using var stream = await image.ToStreamAsync();
|
||||||
|
|
||||||
|
var pos = new kwum(pixel.Position);
|
||||||
|
await Response()
|
||||||
|
.File(stream, $"{pixel.Position}.png")
|
||||||
|
.Embed(_sender.CreateEmbed()
|
||||||
|
.WithOkColor()
|
||||||
|
.WithDescription(string.IsNullOrWhiteSpace(pixel.Text) ? string.Empty : pixel.Text)
|
||||||
|
.WithTitle(GetText(strs.nc_pixel(pos)))
|
||||||
|
.AddField(GetText(strs.nc_position),
|
||||||
|
$"{pixel.Position % _service.GetWidth()} {pixel.Position / _service.GetWidth()}",
|
||||||
|
true)
|
||||||
|
.AddField(GetText(strs.price), pixel.Price.ToString(), true)
|
||||||
|
.AddField(GetText(strs.color), "#" + new Rgba32(pixel.Color).ToHex())
|
||||||
|
.WithImageUrl($"attachment://{pixel.Position}.png"))
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[OwnerOnly]
|
||||||
|
public async Task NcSetImg()
|
||||||
|
{
|
||||||
|
var attach = ctx.Message.Attachments.FirstOrDefault();
|
||||||
|
if (attach is null)
|
||||||
|
{
|
||||||
|
await Response().Error(strs.no_attach_found).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var w = _service.GetWidth();
|
||||||
|
var h = _service.GetHeight();
|
||||||
|
if (attach.Width != w || attach.Height != h)
|
||||||
|
{
|
||||||
|
await Response().Error(strs.invalid_img_size(w, h)).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!await PromptUserConfirmAsync(_sender.CreateEmbed()
|
||||||
|
.WithDescription(
|
||||||
|
"This will reset the canvas to the specified image. All prices, text and colors will be reset.\n\n"
|
||||||
|
+ "Are you sure you want to continue?")))
|
||||||
|
return;
|
||||||
|
|
||||||
|
using var http = _http.CreateClient();
|
||||||
|
await using var stream = await http.GetStreamAsync(attach.Url);
|
||||||
|
using var img = await Image.LoadAsync<Rgba32>(stream);
|
||||||
|
|
||||||
|
var pixels = new uint[_service.GetWidth() * _service.GetHeight()];
|
||||||
|
|
||||||
|
Parallel.For(0,
|
||||||
|
_service.GetWidth() * _service.GetHeight(),
|
||||||
|
i => pixels[i] = img[i % _service.GetWidth(), i / _service.GetWidth()].PackedValue);
|
||||||
|
|
||||||
|
// for (var y = 0; y < _service.GetHeight(); y++)
|
||||||
|
// for (var x = 0; x < _service.GetWidth(); x++)
|
||||||
|
// pixels[(y * _service.GetWidth()) + x] = img[x, y].PackedValue;
|
||||||
|
|
||||||
|
await _service.SetImage(pixels);
|
||||||
|
await ctx.OkAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[OwnerOnly]
|
||||||
|
public async Task NcReset()
|
||||||
|
{
|
||||||
|
await _service.ResetAsync();
|
||||||
|
|
||||||
|
if (!await PromptUserConfirmAsync(_sender.CreateEmbed()
|
||||||
|
.WithDescription(
|
||||||
|
"This will delete all pixels and reset the canvas.\n\n"
|
||||||
|
+ "Are you sure you want to continue?")))
|
||||||
|
return;
|
||||||
|
|
||||||
|
await ctx.OkAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
206
src/NadekoBot/Modules/Games/NCanvas/NCanvasService.cs
Normal file
206
src/NadekoBot/Modules/Games/NCanvas/NCanvasService.cs
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
using LinqToDB;
|
||||||
|
using LinqToDB.Data;
|
||||||
|
using LinqToDB.EntityFrameworkCore;
|
||||||
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
|
using NadekoBot.Db.Models;
|
||||||
|
using SixLabors.ImageSharp.ColorSpaces;
|
||||||
|
using SixLabors.ImageSharp.ColorSpaces.Conversion;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Games;
|
||||||
|
|
||||||
|
public sealed class NCanvasService : INCanvasService, IReadyExecutor, INService
|
||||||
|
{
|
||||||
|
private readonly TypedKey<uint[]> _canvasKey = new("ncanvas");
|
||||||
|
|
||||||
|
private readonly DbService _db;
|
||||||
|
private readonly IBotCache _cache;
|
||||||
|
private readonly DiscordSocketClient _client;
|
||||||
|
private readonly ICurrencyService _cs;
|
||||||
|
|
||||||
|
public const int CANVAS_WIDTH = 500;
|
||||||
|
public const int CANVAS_HEIGHT = 350;
|
||||||
|
public const int INITIAL_PRICE = 3;
|
||||||
|
|
||||||
|
public NCanvasService(
|
||||||
|
DbService db,
|
||||||
|
IBotCache cache,
|
||||||
|
DiscordSocketClient client,
|
||||||
|
ICurrencyService cs)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
_cache = cache;
|
||||||
|
_client = client;
|
||||||
|
_cs = cs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OnReadyAsync()
|
||||||
|
{
|
||||||
|
if (_client.ShardId != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
|
||||||
|
if (await uow.GetTable<NCPixel>().CountAsyncLinqToDB() > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await ResetAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ResetAsync()
|
||||||
|
{
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
await uow.GetTable<NCPixel>().DeleteAsync();
|
||||||
|
|
||||||
|
var toAdd = new List<int>();
|
||||||
|
for (var i = 0; i < CANVAS_WIDTH * CANVAS_HEIGHT; i++)
|
||||||
|
{
|
||||||
|
toAdd.Add(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
await uow.GetTable<NCPixel>()
|
||||||
|
.BulkCopyAsync(toAdd.Select(x =>
|
||||||
|
{
|
||||||
|
var clr = ColorSpaceConverter.ToRgb(new Hsv(((float)Random.Shared.NextDouble() * 360),
|
||||||
|
(float)(0.5 + (Random.Shared.NextDouble() * 0.49)),
|
||||||
|
(float)(0.4 + (Random.Shared.NextDouble() / 5 + (x % 100 * 0.2)))))
|
||||||
|
.ToVector3();
|
||||||
|
|
||||||
|
var packed = new Rgba32(clr).PackedValue;
|
||||||
|
return new NCPixel()
|
||||||
|
{
|
||||||
|
Color = packed,
|
||||||
|
Price = 1,
|
||||||
|
Position = x,
|
||||||
|
Text = "",
|
||||||
|
OwnerId = 0
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private async Task<uint[]> InternalGetCanvas()
|
||||||
|
{
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
var colors = await uow.GetTable<NCPixel>()
|
||||||
|
.OrderBy(x => x.Position)
|
||||||
|
.Select(x => x.Color)
|
||||||
|
.ToArrayAsyncLinqToDB();
|
||||||
|
|
||||||
|
return colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<uint[]> GetCanvas()
|
||||||
|
{
|
||||||
|
return await _cache.GetOrAddAsync(_canvasKey,
|
||||||
|
async () => await InternalGetCanvas(),
|
||||||
|
TimeSpan.FromSeconds(15))
|
||||||
|
?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SetPixelResult> SetPixel(
|
||||||
|
int position,
|
||||||
|
uint color,
|
||||||
|
string text,
|
||||||
|
ulong userId,
|
||||||
|
long price)
|
||||||
|
{
|
||||||
|
if (position < 0 || position >= CANVAS_WIDTH * CANVAS_HEIGHT)
|
||||||
|
return SetPixelResult.InvalidInput;
|
||||||
|
|
||||||
|
var wallet = await _cs.GetWalletAsync(userId);
|
||||||
|
|
||||||
|
var paid = await wallet.Take(price, new("canvas", "pixel-buy", $"Bought pixel {new kwum(position)}"));
|
||||||
|
if (!paid)
|
||||||
|
{
|
||||||
|
return SetPixelResult.NotEnoughMoney;
|
||||||
|
}
|
||||||
|
|
||||||
|
var success = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
var updates = await uow.GetTable<NCPixel>()
|
||||||
|
.Where(x => x.Position == position && x.Price <= price)
|
||||||
|
.UpdateAsync(old => new NCPixel()
|
||||||
|
{
|
||||||
|
Position = position,
|
||||||
|
Color = color,
|
||||||
|
Text = text,
|
||||||
|
OwnerId = userId,
|
||||||
|
Price = price + 1
|
||||||
|
});
|
||||||
|
success = updates > 0;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
await wallet.Add(price, new("canvas", "pixel-refund", $"Refund pixel {new kwum(position)} purchase"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return success ? SetPixelResult.Success : SetPixelResult.InsufficientPayment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> SetImage(uint[] colors)
|
||||||
|
{
|
||||||
|
if (colors.Length != CANVAS_WIDTH * CANVAS_HEIGHT)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
await uow.GetTable<NCPixel>().DeleteAsync();
|
||||||
|
await uow.GetTable<NCPixel>()
|
||||||
|
.BulkCopyAsync(colors.Select((x, i) => new NCPixel()
|
||||||
|
{
|
||||||
|
Color = x,
|
||||||
|
Price = INITIAL_PRICE,
|
||||||
|
Position = i,
|
||||||
|
Text = "",
|
||||||
|
OwnerId = 0
|
||||||
|
}));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<NCPixel?> GetPixel(int x, int y)
|
||||||
|
{
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(x);
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(y);
|
||||||
|
|
||||||
|
if (x >= CANVAS_WIDTH || y >= CANVAS_HEIGHT)
|
||||||
|
return Task.FromResult<NCPixel?>(null);
|
||||||
|
|
||||||
|
return GetPixel(x + (y * CANVAS_WIDTH));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<NCPixel?> GetPixel(int position)
|
||||||
|
{
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(position);
|
||||||
|
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
return await uow.GetTable<NCPixel>().FirstOrDefaultAsync(x => x.Position == position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<NCPixel[]> GetPixelGroup(int position)
|
||||||
|
{
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(position);
|
||||||
|
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(position, CANVAS_WIDTH * CANVAS_HEIGHT);
|
||||||
|
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
return await uow.GetTable<NCPixel>()
|
||||||
|
.Where(x => x.Position % CANVAS_WIDTH >= (position % CANVAS_WIDTH) - 2
|
||||||
|
&& x.Position % CANVAS_WIDTH <= (position % CANVAS_WIDTH) + 2
|
||||||
|
&& x.Position / CANVAS_WIDTH >= (position / CANVAS_WIDTH) - 2
|
||||||
|
&& x.Position / CANVAS_WIDTH <= (position / CANVAS_WIDTH) + 2)
|
||||||
|
.OrderBy(x => x.Position)
|
||||||
|
.ToArrayAsyncLinqToDB();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetHeight()
|
||||||
|
=> CANVAS_HEIGHT;
|
||||||
|
|
||||||
|
public int GetWidth()
|
||||||
|
=> CANVAS_WIDTH;
|
||||||
|
}
|
9
src/NadekoBot/Modules/Games/NCanvas/SetPixelResult.cs
Normal file
9
src/NadekoBot/Modules/Games/NCanvas/SetPixelResult.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace NadekoBot.Modules.Games;
|
||||||
|
|
||||||
|
public enum SetPixelResult
|
||||||
|
{
|
||||||
|
Success,
|
||||||
|
InsufficientPayment,
|
||||||
|
NotEnoughMoney,
|
||||||
|
InvalidInput
|
||||||
|
}
|
@@ -29,7 +29,7 @@ public partial class Games
|
|||||||
if (!await nunchi.Join(ctx.User.Id, ctx.User.ToString()))
|
if (!await nunchi.Join(ctx.User.Id, ctx.User.ToString()))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await Response().Error(strs.nunchi_joined(nunchi.ParticipantCount)).SendAsync();
|
await Response().Confirm(strs.nunchi_joined(nunchi.ParticipantCount)).SendAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -122,11 +122,11 @@ public sealed class CurrencyRewardService : INService, IReadyExecutor
|
|||||||
var dollarValue = pledgeCents / 100;
|
var dollarValue = pledgeCents / 100;
|
||||||
percentBonus = dollarValue switch
|
percentBonus = dollarValue switch
|
||||||
{
|
{
|
||||||
>= 100 => 100,
|
>= 100 => 20,
|
||||||
>= 50 => 50,
|
>= 50 => 10,
|
||||||
>= 20 => 20,
|
>= 20 => 5,
|
||||||
>= 10 => 10,
|
>= 10 => 3,
|
||||||
>= 5 => 5,
|
>= 5 => 1,
|
||||||
_ => 0
|
_ => 0
|
||||||
};
|
};
|
||||||
return (long)(modifiedAmount * (1 + (percentBonus / 100.0f)));
|
return (long)(modifiedAmount * (1 + (percentBonus / 100.0f)));
|
||||||
|
@@ -67,7 +67,7 @@ public partial class Permissions
|
|||||||
|
|
||||||
return _sender.CreateEmbed()
|
return _sender.CreateEmbed()
|
||||||
.WithTitle(title)
|
.WithTitle(title)
|
||||||
.WithDescription(allItems.Join('\n'))
|
.WithDescription(pageItems.Join('\n'))
|
||||||
.WithOkColor();
|
.WithOkColor();
|
||||||
})
|
})
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
|
@@ -18,6 +18,9 @@ public sealed class YoutubeDataApiSearchService : IYoutubeSearchService, INServi
|
|||||||
if(results.Count == 0)
|
if(results.Count == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return results.Map(r => new VideoInfo(r));
|
return results.Map(r => new VideoInfo()
|
||||||
|
{
|
||||||
|
Url = r
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
191
src/NadekoBot/Modules/Searches/Translate/FlagTranslateService.cs
Normal file
191
src/NadekoBot/Modules/Searches/Translate/FlagTranslateService.cs
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
#nullable disable
|
||||||
|
using LinqToDB;
|
||||||
|
using LinqToDB.EntityFrameworkCore;
|
||||||
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
|
using NadekoBot.Db.Models;
|
||||||
|
using System.Collections.Frozen;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Searches;
|
||||||
|
|
||||||
|
public sealed partial class FlagTranslateService : IReadyExecutor, INService
|
||||||
|
{
|
||||||
|
private readonly IBotCreds _creds;
|
||||||
|
private readonly DiscordSocketClient _client;
|
||||||
|
private readonly TranslateService _ts;
|
||||||
|
private readonly IMessageSenderService _sender;
|
||||||
|
private IReadOnlyDictionary<string, string> _supportedFlags;
|
||||||
|
private readonly DbService _db;
|
||||||
|
private ConcurrentHashSet<ulong> _enabledChannels;
|
||||||
|
private readonly IBotCache _cache;
|
||||||
|
|
||||||
|
// disallow same message being translated multiple times to the same language
|
||||||
|
private readonly ConcurrentHashSet<(ulong, string)> _msgLangs = new();
|
||||||
|
|
||||||
|
public FlagTranslateService(
|
||||||
|
IBotCreds creds,
|
||||||
|
DiscordSocketClient client,
|
||||||
|
TranslateService ts,
|
||||||
|
IMessageSenderService sender,
|
||||||
|
DbService db,
|
||||||
|
IBotCache cache)
|
||||||
|
{
|
||||||
|
_creds = creds;
|
||||||
|
_client = client;
|
||||||
|
_ts = ts;
|
||||||
|
_sender = sender;
|
||||||
|
_db = db;
|
||||||
|
_cache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OnReadyAsync()
|
||||||
|
{
|
||||||
|
_supportedFlags = COUNTRIES
|
||||||
|
.Split('\n')
|
||||||
|
.Select(x => x.Split(' '))
|
||||||
|
.ToDictionary(x => x[0], x => x[1].TrimEnd())
|
||||||
|
.ToFrozenDictionary();
|
||||||
|
|
||||||
|
await using (var uow = _db.GetDbContext())
|
||||||
|
{
|
||||||
|
_enabledChannels = (await uow.GetTable<FlagTranslateChannel>()
|
||||||
|
.Where(x => Linq2DbExpressions.GuildOnShard(x.GuildId,
|
||||||
|
_creds.TotalShards,
|
||||||
|
_client.ShardId))
|
||||||
|
.Select(x => new
|
||||||
|
{
|
||||||
|
x.ChannelId,
|
||||||
|
x.GuildId
|
||||||
|
})
|
||||||
|
.ToListAsyncLinqToDB())
|
||||||
|
.Select(x => x.ChannelId)
|
||||||
|
.ToHashSet()
|
||||||
|
.ToConcurrentSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
_client.ReactionAdded += OnReactionAdded;
|
||||||
|
|
||||||
|
var periodicCleanup = new PeriodicTimer(TimeSpan.FromHours(24));
|
||||||
|
|
||||||
|
while (await periodicCleanup.WaitForNextTickAsync())
|
||||||
|
{
|
||||||
|
_msgLangs.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const int FLAG_START = 127462;
|
||||||
|
|
||||||
|
private static TypedKey<bool> CdKey(ulong userId)
|
||||||
|
=> new($"flagtranslate:{userId}");
|
||||||
|
|
||||||
|
private Task OnReactionAdded(
|
||||||
|
Cacheable<IUserMessage, ulong> arg1,
|
||||||
|
Cacheable<IMessageChannel, ulong> arg2,
|
||||||
|
SocketReaction reaction)
|
||||||
|
{
|
||||||
|
if (!_enabledChannels.Contains(reaction.Channel.Id))
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
var runes = reaction.Emote.Name.EnumerateRunes();
|
||||||
|
if (!runes.MoveNext()
|
||||||
|
|| runes.Current is not { Value: >= 127462 and <= 127487 } l1
|
||||||
|
|| !runes.MoveNext()
|
||||||
|
|| runes.Current is not { Value: >= 127462 and <= 127487 } l2)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
if (reaction.Channel is not SocketTextChannel tc)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var user = await ((IGuild)tc.Guild).GetUserAsync(reaction.UserId);
|
||||||
|
|
||||||
|
if (user is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!user.GetPermissions(tc).SendMessages)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!tc.Guild.CurrentUser.GetPermissions(tc).SendMessages
|
||||||
|
|| !tc.Guild.CurrentUser.GetPermissions(tc).EmbedLinks)
|
||||||
|
{
|
||||||
|
await Disable(tc.Guild.Id, tc.Id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var c1 = (char)(l1.Value - FLAG_START + 65);
|
||||||
|
var c2 = (char)(l2.Value - FLAG_START + 65);
|
||||||
|
|
||||||
|
var code = $"{c1}{c2}".ToUpper();
|
||||||
|
|
||||||
|
if (!_supportedFlags.TryGetValue(code, out var lang))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_msgLangs.Add((reaction.MessageId, lang)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var result = await _cache.GetAsync(CdKey(reaction.UserId));
|
||||||
|
if (result.TryPickT0(out _, out _))
|
||||||
|
return;
|
||||||
|
|
||||||
|
await _cache.AddAsync(CdKey(reaction.UserId), true, TimeSpan.FromSeconds(5));
|
||||||
|
|
||||||
|
var msg = await arg1.GetOrDownloadAsync();
|
||||||
|
|
||||||
|
var response = await _ts.Translate("", lang, msg.Content).ConfigureAwait(false);
|
||||||
|
|
||||||
|
await msg.ReplyAsync(embed: _sender.CreateEmbed()
|
||||||
|
.WithOkColor()
|
||||||
|
.WithFooter(user.ToString() ?? reaction.UserId.ToString(),
|
||||||
|
user.RealAvatarUrl().ToString())
|
||||||
|
.WithDescription(response)
|
||||||
|
.WithAuthor(reaction.Emote.ToString())
|
||||||
|
.Build(),
|
||||||
|
allowedMentions: AllowedMentions.None
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Disable(ulong guildId, ulong tcId)
|
||||||
|
{
|
||||||
|
if (!_enabledChannels.TryRemove(tcId))
|
||||||
|
return;
|
||||||
|
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
await uow.GetTable<FlagTranslateChannel>()
|
||||||
|
.Where(x => x.GuildId == guildId
|
||||||
|
&& x.ChannelId == tcId)
|
||||||
|
.DeleteAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> Toggle(ulong guildId, ulong tcId)
|
||||||
|
{
|
||||||
|
if (_enabledChannels.Contains(tcId))
|
||||||
|
{
|
||||||
|
await Disable(guildId, tcId);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Enable(guildId, tcId);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Enable(ulong guildId, ulong tcId)
|
||||||
|
{
|
||||||
|
if (!_enabledChannels.Add(tcId))
|
||||||
|
return;
|
||||||
|
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
await uow.GetTable<FlagTranslateChannel>()
|
||||||
|
.InsertAsync(() => new FlagTranslateChannel
|
||||||
|
{
|
||||||
|
GuildId = guildId,
|
||||||
|
ChannelId = tcId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,73 @@
|
|||||||
|
namespace NadekoBot.Modules.Searches;
|
||||||
|
|
||||||
|
public partial class FlagTranslateService
|
||||||
|
{
|
||||||
|
private const string COUNTRIES = """
|
||||||
|
CN zh
|
||||||
|
IN hi
|
||||||
|
US en
|
||||||
|
ID id
|
||||||
|
PK ur
|
||||||
|
BR pt
|
||||||
|
NG ha
|
||||||
|
BD bn
|
||||||
|
RU ru
|
||||||
|
JP ja
|
||||||
|
MX es
|
||||||
|
PH tl
|
||||||
|
VN vi
|
||||||
|
EG ar
|
||||||
|
ET am
|
||||||
|
DE de
|
||||||
|
IR fa
|
||||||
|
TR tr
|
||||||
|
TH th
|
||||||
|
FR fr
|
||||||
|
CD fr
|
||||||
|
MM my
|
||||||
|
UG en
|
||||||
|
MZ pt
|
||||||
|
ZA zu
|
||||||
|
CO es
|
||||||
|
BG bg
|
||||||
|
HR hr
|
||||||
|
MY ms
|
||||||
|
NL nl
|
||||||
|
RO ro
|
||||||
|
CZ cs
|
||||||
|
GR el
|
||||||
|
SK sk
|
||||||
|
PT pt
|
||||||
|
KR ko
|
||||||
|
IT it
|
||||||
|
ES es
|
||||||
|
RS sr
|
||||||
|
TN ar
|
||||||
|
PL pl
|
||||||
|
SD ar
|
||||||
|
CM fr
|
||||||
|
SN fr
|
||||||
|
ML fr
|
||||||
|
NE ha
|
||||||
|
BI fr
|
||||||
|
AO pt
|
||||||
|
AF ps
|
||||||
|
MA ar
|
||||||
|
DZ ar
|
||||||
|
GB en
|
||||||
|
AR es
|
||||||
|
ZW ny
|
||||||
|
KE sw
|
||||||
|
GH en
|
||||||
|
SA ar
|
||||||
|
IL he
|
||||||
|
IQ ar
|
||||||
|
UA ua
|
||||||
|
LY ar
|
||||||
|
KW ar
|
||||||
|
OM ar
|
||||||
|
YE ar
|
||||||
|
AL sq
|
||||||
|
AE ar
|
||||||
|
""";
|
||||||
|
}
|
@@ -44,12 +44,10 @@ public sealed class TranslateService : ITranslateService, IExecNoCommand, IReady
|
|||||||
foreach (var c in cs)
|
foreach (var c in cs)
|
||||||
{
|
{
|
||||||
_atcs[c.ChannelId] = c.AutoDelete;
|
_atcs[c.ChannelId] = c.AutoDelete;
|
||||||
_users[c.ChannelId] =
|
_users[c.ChannelId] = new(c.Users.ToDictionary(x => x.UserId, x => (x.Source.ToLower(), x.Target.ToLower())));
|
||||||
new(c.Users.ToDictionary(x => x.UserId, x => (x.Source.ToLower(), x.Target.ToLower())));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task ExecOnNoCommandAsync(IGuild guild, IUserMessage msg)
|
public async Task ExecOnNoCommandAsync(IGuild guild, IUserMessage msg)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(msg.Content))
|
if (string.IsNullOrWhiteSpace(msg.Content))
|
||||||
@@ -95,7 +93,7 @@ public sealed class TranslateService : ITranslateService, IExecNoCommand, IReady
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> Translate(string source, string target, string text = null)
|
public async Task<string> Translate(string source, string target, string text)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(text))
|
if (string.IsNullOrWhiteSpace(text))
|
||||||
throw new ArgumentException("Text is empty or null", nameof(text));
|
throw new ArgumentException("Text is empty or null", nameof(text));
|
||||||
|
@@ -6,6 +6,14 @@ public partial class Searches
|
|||||||
[Group]
|
[Group]
|
||||||
public partial class TranslateCommands : NadekoModule<ITranslateService>
|
public partial class TranslateCommands : NadekoModule<ITranslateService>
|
||||||
{
|
{
|
||||||
|
private readonly FlagTranslateService _flagSvc;
|
||||||
|
|
||||||
|
public TranslateCommands(FlagTranslateService flagSvc)
|
||||||
|
{
|
||||||
|
_flagSvc = flagSvc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public enum AutoDeleteAutoTranslate
|
public enum AutoDeleteAutoTranslate
|
||||||
{
|
{
|
||||||
Del,
|
Del,
|
||||||
@@ -91,5 +99,18 @@ public partial class Searches
|
|||||||
|
|
||||||
await Response().Embed(eb).SendAsync();
|
await Response().Embed(eb).SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
[UserPerm(ChannelPermission.ManageChannels)]
|
||||||
|
[BotPerm(ChannelPermission.SendMessages | ChannelPermission.EmbedLinks)]
|
||||||
|
public async Task TranslateFlags()
|
||||||
|
{
|
||||||
|
var enabled = await _flagSvc.Toggle(ctx.Guild.Id, ctx.Channel.Id);
|
||||||
|
if (enabled)
|
||||||
|
await Response().Confirm(strs.trfl_enabled).SendAsync();
|
||||||
|
else
|
||||||
|
await Response().Confirm(strs.trfl_disabled).SendAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -357,7 +357,7 @@ public partial class Xp : NadekoModule<XpService>
|
|||||||
if (!await PromptUserConfirmAsync(embed))
|
if (!await PromptUserConfirmAsync(embed))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_service.XpReset(ctx.Guild.Id, userId);
|
await _service.XpReset(ctx.Guild.Id, userId);
|
||||||
|
|
||||||
await Response().Confirm(strs.reset_user(userId)).SendAsync();
|
await Response().Confirm(strs.reset_user(userId)).SendAsync();
|
||||||
}
|
}
|
||||||
|
@@ -20,6 +20,31 @@ 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;
|
||||||
@@ -1437,11 +1462,11 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void XpReset(ulong guildId, ulong userId)
|
public async Task XpReset(ulong guildId, ulong userId)
|
||||||
{
|
{
|
||||||
using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
uow.Set<UserXpStats>().ResetGuildUserXp(userId, guildId);
|
await uow.GetTable<UserXpStats>()
|
||||||
uow.SaveChanges();
|
.DeleteAsync(x => x.UserId == userId && x.GuildId == guildId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void XpReset(ulong guildId)
|
public void XpReset(ulong guildId)
|
||||||
@@ -1637,6 +1662,15 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
|||||||
|
|
||||||
public bool IsShopEnabled()
|
public bool IsShopEnabled()
|
||||||
=> _xpConfig.Data.Shop.IsEnabled;
|
=> _xpConfig.Data.Shop.IsEnabled;
|
||||||
|
|
||||||
|
public async Task<int> GetTotalGuildUsers(ulong requestGuildId, List<ulong>? guildUsers = null)
|
||||||
|
{
|
||||||
|
await using var ctx = _db.GetDbContext();
|
||||||
|
return await ctx.GetTable<UserXpStats>()
|
||||||
|
.Where(x => x.GuildId == requestGuildId
|
||||||
|
&& (guildUsers == null || guildUsers.Contains(x.UserId)))
|
||||||
|
.CountAsyncLinqToDB();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum BuyResult
|
public enum BuyResult
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>true</ImplicitUsings>
|
<ImplicitUsings>true</ImplicitUsings>
|
||||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||||
<Version>5.1.15</Version>
|
<Version>5.1.18</Version>
|
||||||
|
|
||||||
<!-- Output/build -->
|
<!-- Output/build -->
|
||||||
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
||||||
|
@@ -22,9 +22,6 @@ public class ExprsSvc : GrpcExprs.GrpcExprsBase, IGrpcSvc, INService
|
|||||||
public ServerServiceDefinition Bind()
|
public ServerServiceDefinition Bind()
|
||||||
=> GrpcExprs.BindService(this);
|
=> GrpcExprs.BindService(this);
|
||||||
|
|
||||||
private ulong GetUserId(Metadata meta)
|
|
||||||
=> ulong.Parse(meta.FirstOrDefault(x => x.Key == "userid")!.Value);
|
|
||||||
|
|
||||||
public override async Task<AddExprReply> AddExpr(AddExprRequest request, ServerCallContext context)
|
public override async Task<AddExprReply> AddExpr(AddExprRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(request.Expr.Trigger) || string.IsNullOrWhiteSpace(request.Expr.Response))
|
if (string.IsNullOrWhiteSpace(request.Expr.Trigger) || string.IsNullOrWhiteSpace(request.Expr.Response))
|
||||||
@@ -109,7 +106,7 @@ public class ExprsSvc : GrpcExprs.GrpcExprsBase, IGrpcSvc, INService
|
|||||||
|
|
||||||
public override async Task<AddQuoteReply> AddQuote(AddQuoteRequest request, ServerCallContext context)
|
public override async Task<AddQuoteReply> AddQuote(AddQuoteRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
var userId = GetUserId(context.RequestHeaders);
|
var userId = context.RequestHeaders.GetUserId();
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(request.Quote.Trigger) || string.IsNullOrWhiteSpace(request.Quote.Response))
|
if (string.IsNullOrWhiteSpace(request.Quote.Trigger) || string.IsNullOrWhiteSpace(request.Quote.Response))
|
||||||
throw new RpcException(new Status(StatusCode.InvalidArgument, "Trigger and response are required"));
|
throw new RpcException(new Status(StatusCode.InvalidArgument, "Trigger and response are required"));
|
||||||
@@ -146,7 +143,7 @@ public class ExprsSvc : GrpcExprs.GrpcExprsBase, IGrpcSvc, INService
|
|||||||
|
|
||||||
public override async Task<Empty> DeleteQuote(DeleteQuoteRequest request, ServerCallContext context)
|
public override async Task<Empty> DeleteQuote(DeleteQuoteRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
await _qs.DeleteQuoteAsync(request.GuildId, GetUserId(context.RequestHeaders), true, new kwum(request.Id));
|
await _qs.DeleteQuoteAsync(request.GuildId, context.RequestHeaders.GetUserId(), true, new kwum(request.Id));
|
||||||
return new Empty();
|
return new Empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
89
src/NadekoBot/Services/GrpcApi/FinSvc.cs
Normal file
89
src/NadekoBot/Services/GrpcApi/FinSvc.cs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
using Google.Protobuf.WellKnownTypes;
|
||||||
|
using Grpc.Core;
|
||||||
|
using NadekoBot.Db.Models;
|
||||||
|
using NadekoBot.Modules.Gambling.Bank;
|
||||||
|
using NadekoBot.Modules.NadekoExpressions;
|
||||||
|
using NadekoBot.Modules.Utility;
|
||||||
|
|
||||||
|
namespace NadekoBot.GrpcApi;
|
||||||
|
|
||||||
|
public class FinSvc : GrpcFin.GrpcFinBase, IGrpcSvc, INService
|
||||||
|
{
|
||||||
|
private readonly ICurrencyService _cs;
|
||||||
|
private readonly IBankService _bank;
|
||||||
|
|
||||||
|
public FinSvc(ICurrencyService cs, IBankService bank)
|
||||||
|
{
|
||||||
|
_cs = cs;
|
||||||
|
_bank = bank;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerServiceDefinition Bind()
|
||||||
|
=> GrpcFin.BindService(this);
|
||||||
|
|
||||||
|
[GrpcNoAuthRequired]
|
||||||
|
public override async Task<DepositReply> Deposit(DepositRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
if (request.Amount <= 0)
|
||||||
|
throw new RpcException(new Status(StatusCode.InvalidArgument, "Amount must be greater than 0"));
|
||||||
|
|
||||||
|
var succ = await _bank.DepositAsync(request.UserId, request.Amount);
|
||||||
|
|
||||||
|
return new DepositReply
|
||||||
|
{
|
||||||
|
Success = succ
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[GrpcNoAuthRequired]
|
||||||
|
public override async Task<WithdrawReply> Withdraw(WithdrawRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
if (request.Amount <= 0)
|
||||||
|
throw new RpcException(new Status(StatusCode.InvalidArgument, "Amount must be greater than 0"));
|
||||||
|
|
||||||
|
var succ = await _bank.WithdrawAsync(request.UserId, request.Amount);
|
||||||
|
|
||||||
|
return new WithdrawReply
|
||||||
|
{
|
||||||
|
Success = succ
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[GrpcNoAuthRequired]
|
||||||
|
public override async Task<GetHoldingsReply> GetHoldings(GetHoldingsRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
return new GetHoldingsReply
|
||||||
|
{
|
||||||
|
Bank = await _bank.GetBalanceAsync(request.UserId),
|
||||||
|
Cash = await _cs.GetBalanceAsync(request.UserId)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[GrpcNoAuthRequired]
|
||||||
|
public override async Task<GetTransactionsReply> GetTransactions(
|
||||||
|
GetTransactionsRequest request,
|
||||||
|
ServerCallContext context)
|
||||||
|
{
|
||||||
|
if (request.Page < 1)
|
||||||
|
throw new RpcException(new Status(StatusCode.InvalidArgument, "Page must be greater than 0"));
|
||||||
|
|
||||||
|
var trs = await _cs.GetTransactionsAsync(request.UserId, request.Page - 1);
|
||||||
|
|
||||||
|
var reply = new GetTransactionsReply
|
||||||
|
{
|
||||||
|
Total = await _cs.GetTransactionsCountAsync(request.UserId)
|
||||||
|
};
|
||||||
|
|
||||||
|
reply.Transactions.AddRange(trs.Select(x => new TransactionReply()
|
||||||
|
{
|
||||||
|
Id = new kwum(x.Id).ToString(),
|
||||||
|
Timestamp = Timestamp.FromDateTime(DateTime.UtcNow),
|
||||||
|
Amount = x.Amount,
|
||||||
|
Extra = x.Extra ?? string.Empty,
|
||||||
|
Note = x.Note ?? string.Empty,
|
||||||
|
Type = x.Type ?? string.Empty,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
}
|
95
src/NadekoBot/Services/GrpcApi/NCanvasSvc.cs
Normal file
95
src/NadekoBot/Services/GrpcApi/NCanvasSvc.cs
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
using Google.Protobuf.WellKnownTypes;
|
||||||
|
using Grpc.Core;
|
||||||
|
using NadekoBot.Db.Models;
|
||||||
|
using NadekoBot.Modules.Games;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
|
||||||
|
namespace NadekoBot.GrpcApi;
|
||||||
|
|
||||||
|
public class NCanvasSvc : GrpcNCanvas.GrpcNCanvasBase, IGrpcSvc, INService
|
||||||
|
{
|
||||||
|
private readonly INCanvasService _nCanvas;
|
||||||
|
private readonly DiscordSocketClient _client;
|
||||||
|
|
||||||
|
public NCanvasSvc(INCanvasService nCanvas, DiscordSocketClient client)
|
||||||
|
{
|
||||||
|
_nCanvas = nCanvas;
|
||||||
|
_client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerServiceDefinition Bind()
|
||||||
|
=> GrpcNCanvas.BindService(this);
|
||||||
|
|
||||||
|
[GrpcNoAuthRequired]
|
||||||
|
public override async Task<CanvasReply> GetCanvas(Empty request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
var pixels = await _nCanvas.GetCanvas();
|
||||||
|
var reply = new CanvasReply()
|
||||||
|
{
|
||||||
|
Width = _nCanvas.GetWidth(),
|
||||||
|
Height = _nCanvas.GetHeight()
|
||||||
|
};
|
||||||
|
reply.Pixels.AddRange(pixels);
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
[GrpcNoAuthRequired]
|
||||||
|
public override async Task<GetPixelReply> GetPixel(GetPixelRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
var pixel = await _nCanvas.GetPixel(request.X, request.Y);
|
||||||
|
if (pixel is null)
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, "Pixel not found"));
|
||||||
|
|
||||||
|
var reply = MapPixelToGrpcPixel(pixel);
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GetPixelReply MapPixelToGrpcPixel(NCPixel pixel)
|
||||||
|
{
|
||||||
|
var reply = new GetPixelReply
|
||||||
|
{
|
||||||
|
Color = "#" + new Rgba32(pixel.Color).ToHex(),
|
||||||
|
PackedColor = pixel.Color,
|
||||||
|
Position = new kwum(pixel.Position).ToString(),
|
||||||
|
PositionX = pixel.Position % _nCanvas.GetWidth(),
|
||||||
|
PositionY = pixel.Position / _nCanvas.GetWidth(),
|
||||||
|
// Owner = await ((IDiscordClient)_client).GetUserAsync(pixel.OwnerId)?.ToString() ?? string.Empty,
|
||||||
|
// OwnerId = pixel.OwnerId.ToString(),
|
||||||
|
Price = pixel.Price,
|
||||||
|
Text = pixel.Text
|
||||||
|
};
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
[GrpcNoAuthRequired]
|
||||||
|
public override async Task<SetPixelReply> SetPixel(SetPixelRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
if (!kwum.TryParse(request.Position, out var pos))
|
||||||
|
throw new RpcException(new Status(StatusCode.InvalidArgument, "Position is invalid"));
|
||||||
|
|
||||||
|
if (!Rgba32.TryParseHex(request.Color, out var clr))
|
||||||
|
throw new RpcException(new Status(StatusCode.InvalidArgument, "Color is invalid"));
|
||||||
|
|
||||||
|
var userId = context.RequestHeaders.GetUserId();
|
||||||
|
var result = await _nCanvas.SetPixel(pos, clr.PackedValue, request.Text, userId, request.Price);
|
||||||
|
var reply = new SetPixelReply()
|
||||||
|
{
|
||||||
|
Success = result == SetPixelResult.Success,
|
||||||
|
Error = result switch
|
||||||
|
{
|
||||||
|
SetPixelResult.Success => string.Empty,
|
||||||
|
SetPixelResult.InsufficientPayment => "You have to pay equal or more than the price.",
|
||||||
|
SetPixelResult.NotEnoughMoney => "You don't have enough currency. ",
|
||||||
|
SetPixelResult.InvalidInput =>
|
||||||
|
$"Invalid input. Position has to be >= 0 and < {_nCanvas.GetWidth()}x{_nCanvas.GetHeight()}",
|
||||||
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var pixel = await _nCanvas.GetPixel(pos);
|
||||||
|
if (pixel is not null)
|
||||||
|
reply.Pixel = MapPixelToGrpcPixel(pixel);
|
||||||
|
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
}
|
@@ -13,51 +13,48 @@ public static class GrpcApiExtensions
|
|||||||
|
|
||||||
public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IGrpcSvc, INService
|
public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IGrpcSvc, INService
|
||||||
{
|
{
|
||||||
private readonly IDiscordClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
private readonly XpService _xp;
|
private readonly XpService _xp;
|
||||||
private readonly ICurrencyService _cur;
|
private readonly ICurrencyService _cur;
|
||||||
private readonly WaifuService _waifus;
|
private readonly WaifuService _waifus;
|
||||||
private readonly ICoordinator _coord;
|
|
||||||
private readonly IStatsService _stats;
|
private readonly IStatsService _stats;
|
||||||
private readonly IBotCache _cache;
|
private readonly CommandHandler _cmdHandler;
|
||||||
|
|
||||||
public OtherSvc(
|
public OtherSvc(
|
||||||
DiscordSocketClient client,
|
DiscordSocketClient client,
|
||||||
XpService xp,
|
XpService xp,
|
||||||
ICurrencyService cur,
|
ICurrencyService cur,
|
||||||
WaifuService waifus,
|
WaifuService waifus,
|
||||||
ICoordinator coord,
|
|
||||||
IStatsService stats,
|
IStatsService stats,
|
||||||
IBotCache cache)
|
CommandHandler cmdHandler)
|
||||||
{
|
{
|
||||||
_client = client;
|
_client = client;
|
||||||
_xp = xp;
|
_xp = xp;
|
||||||
_cur = cur;
|
_cur = cur;
|
||||||
_waifus = waifus;
|
_waifus = waifus;
|
||||||
_coord = coord;
|
|
||||||
_stats = stats;
|
_stats = stats;
|
||||||
_cache = cache;
|
_cmdHandler = cmdHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerServiceDefinition Bind()
|
public ServerServiceDefinition Bind()
|
||||||
=> GrpcOther.BindService(this);
|
=> GrpcOther.BindService(this);
|
||||||
|
|
||||||
[GrpcNoAuthRequired]
|
[GrpcNoAuthRequired]
|
||||||
public override async Task<BotOnGuildReply> BotOnGuild(BotOnGuildRequest request, ServerCallContext context)
|
public override Task<BotOnGuildReply> BotOnGuild(BotOnGuildRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
var guild = await _client.GetGuildAsync(request.GuildId);
|
var guild = _client.GetGuild(request.GuildId);
|
||||||
|
|
||||||
var reply = new BotOnGuildReply
|
var reply = new BotOnGuildReply
|
||||||
{
|
{
|
||||||
Success = guild is not null
|
Success = guild is not null
|
||||||
};
|
};
|
||||||
|
|
||||||
return reply;
|
return Task.FromResult(reply);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<GetRolesReply> GetRoles(GetRolesRequest request, ServerCallContext context)
|
public override Task<GetRolesReply> GetRoles(GetRolesRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
var g = await _client.GetGuildAsync(request.GuildId);
|
var g = _client.GetGuild(request.GuildId);
|
||||||
var roles = g?.Roles;
|
var roles = g?.Roles;
|
||||||
var reply = new GetRolesReply();
|
var reply = new GetRolesReply();
|
||||||
reply.Roles.AddRange(roles?.Select(x => new RoleReply()
|
reply.Roles.AddRange(roles?.Select(x => new RoleReply()
|
||||||
@@ -66,16 +63,17 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IGrpcSvc, INService
|
|||||||
Name = x.Name,
|
Name = x.Name,
|
||||||
Color = x.Color.ToString(),
|
Color = x.Color.ToString(),
|
||||||
IconUrl = x.GetIconUrl() ?? string.Empty,
|
IconUrl = x.GetIconUrl() ?? string.Empty,
|
||||||
}) ?? new List<RoleReply>());
|
})
|
||||||
|
?? new List<RoleReply>());
|
||||||
|
|
||||||
return reply;
|
return Task.FromResult(reply);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<GetTextChannelsReply> GetTextChannels(
|
public override async Task<GetTextChannelsReply> GetTextChannels(
|
||||||
GetTextChannelsRequest request,
|
GetTextChannelsRequest request,
|
||||||
ServerCallContext context)
|
ServerCallContext context)
|
||||||
{
|
{
|
||||||
var g = await _client.GetGuildAsync(request.GuildId);
|
IGuild g = _client.GetGuild(request.GuildId);
|
||||||
var reply = new GetTextChannelsReply();
|
var reply = new GetTextChannelsReply();
|
||||||
|
|
||||||
var chs = await g.GetTextChannelsAsync();
|
var chs = await g.GetTextChannelsAsync();
|
||||||
@@ -89,33 +87,6 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IGrpcSvc, INService
|
|||||||
return reply;
|
return reply;
|
||||||
}
|
}
|
||||||
|
|
||||||
[GrpcNoAuthRequired]
|
|
||||||
public override async Task<GetGuildsReply> GetGuilds(Empty request, ServerCallContext context)
|
|
||||||
{
|
|
||||||
var guilds = await _client.GetGuildsAsync(CacheMode.CacheOnly);
|
|
||||||
|
|
||||||
var reply = new GetGuildsReply();
|
|
||||||
var userId = context.GetUserId();
|
|
||||||
|
|
||||||
var toReturn = new List<IGuild>();
|
|
||||||
foreach (var g in guilds)
|
|
||||||
{
|
|
||||||
var user = await g.GetUserAsync(userId);
|
|
||||||
if (user is not null && user.GuildPermissions.Has(GuildPermission.Administrator))
|
|
||||||
toReturn.Add(g);
|
|
||||||
}
|
|
||||||
|
|
||||||
reply.Guilds.AddRange(toReturn
|
|
||||||
.Select(x => new GuildReply()
|
|
||||||
{
|
|
||||||
Id = x.Id,
|
|
||||||
Name = x.Name,
|
|
||||||
IconUrl = x.IconUrl
|
|
||||||
}));
|
|
||||||
|
|
||||||
return reply;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[GrpcNoAuthRequired]
|
[GrpcNoAuthRequired]
|
||||||
public override async Task<CurrencyLbReply> GetCurrencyLb(GetLbRequest request, ServerCallContext context)
|
public override async Task<CurrencyLbReply> GetCurrencyLb(GetLbRequest request, ServerCallContext context)
|
||||||
@@ -123,16 +94,16 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IGrpcSvc, INService
|
|||||||
var users = await _cur.GetTopRichest(_client.CurrentUser.Id, request.Page, request.PerPage);
|
var users = await _cur.GetTopRichest(_client.CurrentUser.Id, request.Page, request.PerPage);
|
||||||
|
|
||||||
var reply = new CurrencyLbReply();
|
var reply = new CurrencyLbReply();
|
||||||
var entries = users.Select(async x =>
|
var entries = users.Select(x =>
|
||||||
{
|
{
|
||||||
var user = await _client.GetUserAsync(x.UserId, CacheMode.CacheOnly);
|
var user = _client.GetUser(x.UserId);
|
||||||
return new CurrencyLbEntryReply()
|
return Task.FromResult(new CurrencyLbEntryReply()
|
||||||
{
|
{
|
||||||
Amount = x.CurrencyAmount,
|
Amount = x.CurrencyAmount,
|
||||||
User = user?.ToString() ?? x.Username,
|
User = user?.ToString() ?? x.Username,
|
||||||
UserId = x.UserId,
|
UserId = x.UserId,
|
||||||
Avatar = user?.RealAvatarUrl().ToString() ?? x.RealAvatarUrl()?.ToString()
|
Avatar = user?.RealAvatarUrl().ToString() ?? x.RealAvatarUrl()?.ToString()
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
reply.Entries.AddRange(await entries.WhenAll());
|
reply.Entries.AddRange(await entries.WhenAll());
|
||||||
@@ -182,26 +153,66 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IGrpcSvc, INService
|
|||||||
}
|
}
|
||||||
|
|
||||||
[GrpcNoAuthRequired]
|
[GrpcNoAuthRequired]
|
||||||
public override async Task<GetShardStatusesReply> GetShardStatuses(Empty request, ServerCallContext context)
|
public override async Task GetShardStats(
|
||||||
|
Empty request,
|
||||||
|
IServerStreamWriter<ShardStatsReply> responseStream,
|
||||||
|
ServerCallContext context)
|
||||||
{
|
{
|
||||||
var reply = new GetShardStatusesReply();
|
while (true)
|
||||||
|
|
||||||
await _cache.GetOrAddAsync<List<ShardStatus>>("coord:statuses",
|
|
||||||
() => Task.FromResult(_coord.GetAllShardStatuses().ToList())!,
|
|
||||||
TimeSpan.FromMinutes(1));
|
|
||||||
|
|
||||||
var shards = _coord.GetAllShardStatuses();
|
|
||||||
|
|
||||||
reply.Shards.AddRange(shards.Select(x => new ShardStatusReply()
|
|
||||||
{
|
{
|
||||||
Id = x.ShardId,
|
var stats = new ShardStatsReply()
|
||||||
Status = x.ConnectionState.ToString(),
|
{
|
||||||
GuildCount = x.GuildCount,
|
Id = _client.ShardId,
|
||||||
LastUpdate = Timestamp.FromDateTime(x.LastUpdate),
|
Commands = _stats.CommandsRan,
|
||||||
}));
|
Uptime = _stats.GetUptimeString(),
|
||||||
|
Status = GetConnectionState(_client.ConnectionState),
|
||||||
|
GuildCount = _client.Guilds.Count,
|
||||||
|
};
|
||||||
|
|
||||||
|
await responseStream.WriteAsync(stats);
|
||||||
|
await Task.Delay(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return reply;
|
[GrpcNoAuthRequired]
|
||||||
|
public override async Task GetCommandFeed(
|
||||||
|
Empty request,
|
||||||
|
IServerStreamWriter<CommandFeedEntry> responseStream,
|
||||||
|
ServerCallContext context)
|
||||||
|
{
|
||||||
|
var taskCompletion = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
|
Task OnCommandExecuted(IUserMessage userMessage, CommandInfo commandInfo)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
responseStream.WriteAsync(new()
|
||||||
|
{
|
||||||
|
Command = commandInfo.Name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_cmdHandler.CommandExecuted -= OnCommandExecuted;
|
||||||
|
taskCompletion.TrySetResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
_cmdHandler.CommandExecuted += OnCommandExecuted;
|
||||||
|
|
||||||
|
await taskCompletion.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetConnectionState(ConnectionState clientConnectionState)
|
||||||
|
{
|
||||||
|
return clientConnectionState switch
|
||||||
|
{
|
||||||
|
ConnectionState.Connected => "Connected",
|
||||||
|
ConnectionState.Connecting => "Connecting",
|
||||||
|
_ => "Disconnected"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<GetServerInfoReply> GetServerInfo(ServerInfoRequest request, ServerCallContext context)
|
public override async Task<GetServerInfoReply> GetServerInfo(ServerInfoRequest request, ServerCallContext context)
|
||||||
|
267
src/NadekoBot/Services/GrpcApi/XpSvc.cs
Normal file
267
src/NadekoBot/Services/GrpcApi/XpSvc.cs
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
using Google.Protobuf.WellKnownTypes;
|
||||||
|
using Grpc.Core;
|
||||||
|
using NadekoBot.Db.Models;
|
||||||
|
using NadekoBot.Modules.Gambling.Bank;
|
||||||
|
using NadekoBot.Modules.NadekoExpressions;
|
||||||
|
using NadekoBot.Modules.Utility;
|
||||||
|
using NadekoBot.Modules.Xp.Services;
|
||||||
|
|
||||||
|
namespace NadekoBot.GrpcApi;
|
||||||
|
|
||||||
|
public class XpSvc : GrpcXp.GrpcXpBase, IGrpcSvc, INService
|
||||||
|
{
|
||||||
|
private readonly XpService _xp;
|
||||||
|
private readonly DiscordSocketClient _client;
|
||||||
|
private readonly IUserService _duSvc;
|
||||||
|
|
||||||
|
public XpSvc(XpService xp, DiscordSocketClient client, IUserService duSvc)
|
||||||
|
{
|
||||||
|
_xp = xp;
|
||||||
|
_client = client;
|
||||||
|
_duSvc = duSvc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerServiceDefinition Bind()
|
||||||
|
=> GrpcXp.BindService(this);
|
||||||
|
|
||||||
|
public override async Task<GetXpSettingsReply> GetXpSettings(
|
||||||
|
GetXpSettingsRequest request,
|
||||||
|
ServerCallContext context)
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
|
||||||
|
var guild = _client.GetGuild(request.GuildId);
|
||||||
|
|
||||||
|
if (guild is null)
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, "Guild not found"));
|
||||||
|
|
||||||
|
var excludedChannels = _xp.GetExcludedChannels(request.GuildId);
|
||||||
|
var excludedRoles = _xp.GetExcludedRoles(request.GuildId);
|
||||||
|
var isServerExcluded = _xp.IsServerExcluded(request.GuildId);
|
||||||
|
|
||||||
|
var reply = new GetXpSettingsReply();
|
||||||
|
|
||||||
|
reply.Exclusions.AddRange(excludedChannels
|
||||||
|
.Select(x => new ExclItemReply()
|
||||||
|
{
|
||||||
|
Id = x,
|
||||||
|
Type = "Channel",
|
||||||
|
Name = guild.GetChannel(x)?.Name ?? "????"
|
||||||
|
})
|
||||||
|
.Concat(excludedRoles
|
||||||
|
.Select(x => new ExclItemReply()
|
||||||
|
{
|
||||||
|
Id = x,
|
||||||
|
Type = "Role",
|
||||||
|
Name = guild.GetRole(x)?.Name ?? "????"
|
||||||
|
})));
|
||||||
|
|
||||||
|
var curRews = _xp.GetCurrencyRewards(request.GuildId);
|
||||||
|
var roleRews = _xp.GetRoleRewards(request.GuildId);
|
||||||
|
|
||||||
|
var rews = curRews.Select(x => new RewItemReply()
|
||||||
|
{
|
||||||
|
Level = x.Level,
|
||||||
|
Type = "Currency",
|
||||||
|
Value = x.Amount.ToString()
|
||||||
|
});
|
||||||
|
|
||||||
|
rews = rews.Concat(roleRews.Select(x => new RewItemReply()
|
||||||
|
{
|
||||||
|
Level = x.Level,
|
||||||
|
Type = "Role",
|
||||||
|
Value = guild.GetRole(x.RoleId)?.ToString() ?? x.RoleId.ToString()
|
||||||
|
}))
|
||||||
|
.OrderBy(x => x.Level);
|
||||||
|
|
||||||
|
reply.Rewards.AddRange(rews);
|
||||||
|
|
||||||
|
reply.ServerExcluded = isServerExcluded;
|
||||||
|
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<AddExclusionReply> AddExclusion(AddExclusionRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
|
||||||
|
var success = false;
|
||||||
|
var guild = _client.GetGuild(request.GuildId);
|
||||||
|
|
||||||
|
if (guild is null)
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, "Guild not found"));
|
||||||
|
|
||||||
|
if (request.Type == "Role")
|
||||||
|
{
|
||||||
|
if (guild.GetRole(request.Id) is null)
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Success = false
|
||||||
|
};
|
||||||
|
|
||||||
|
success = _xp.ToggleExcludeRole(request.GuildId, request.Id);
|
||||||
|
}
|
||||||
|
else if (request.Type == "Channel")
|
||||||
|
{
|
||||||
|
if (guild.GetTextChannel(request.Id) is null)
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Success = false
|
||||||
|
};
|
||||||
|
|
||||||
|
success = _xp.ToggleExcludeChannel(request.GuildId, request.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Success = success
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<DeleteExclusionReply> DeleteExclusion(
|
||||||
|
DeleteExclusionRequest request,
|
||||||
|
ServerCallContext context)
|
||||||
|
{
|
||||||
|
var success = false;
|
||||||
|
if (request.Type == "Role")
|
||||||
|
success = _xp.ToggleExcludeRole(request.GuildId, request.Id);
|
||||||
|
else
|
||||||
|
success = _xp.ToggleExcludeChannel(request.GuildId, request.Id);
|
||||||
|
|
||||||
|
return Task.FromResult(new DeleteExclusionReply
|
||||||
|
{
|
||||||
|
Success = success
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<AddRewardReply> AddReward(AddRewardRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
|
||||||
|
var success = false;
|
||||||
|
var guild = _client.GetGuild(request.GuildId);
|
||||||
|
|
||||||
|
if (guild is null)
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, "Guild not found"));
|
||||||
|
|
||||||
|
if (request.Type == "AddRole" || request.Type == "RemoveRole")
|
||||||
|
{
|
||||||
|
if (!ulong.TryParse(request.Value, out var rid))
|
||||||
|
throw new RpcException(new Status(StatusCode.InvalidArgument, "Invalid role id"));
|
||||||
|
|
||||||
|
var role = guild.GetRole(rid);
|
||||||
|
if (role is null)
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Success = false
|
||||||
|
};
|
||||||
|
|
||||||
|
_xp.SetRoleReward(request.GuildId, request.Level, rid, request.Type == "RemoveRole");
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
else if (request.Type == "Currency")
|
||||||
|
{
|
||||||
|
if (!int.TryParse(request.Value, out var amount))
|
||||||
|
throw new RpcException(new Status(StatusCode.InvalidArgument, "Invalid amount"));
|
||||||
|
|
||||||
|
_xp.SetCurrencyReward(request.GuildId, request.Level, amount);
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Success = success
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<DeleteRewardReply> DeleteReward(DeleteRewardRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
var success = false;
|
||||||
|
|
||||||
|
if (request.Type == "AddRole" || request.Type == "RemoveRole")
|
||||||
|
{
|
||||||
|
_xp.ResetRoleReward(request.GuildId, request.Level);
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
else if (request.Type == "Currency")
|
||||||
|
{
|
||||||
|
_xp.SetCurrencyReward(request.GuildId, request.Level, 0);
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(new DeleteRewardReply
|
||||||
|
{
|
||||||
|
Success = success
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<ResetUserXpReply> ResetUserXp(ResetUserXpRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
await _xp.XpReset(request.GuildId, request.UserId);
|
||||||
|
|
||||||
|
return new ResetUserXpReply
|
||||||
|
{
|
||||||
|
Success = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<GetXpLbReply> GetXpLb(GetXpLbRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
if (request.Page < 0)
|
||||||
|
throw new RpcException(new Status(StatusCode.InvalidArgument, "Page must be greater than or equal to 0"));
|
||||||
|
|
||||||
|
var guild = _client.GetGuild(request.GuildId);
|
||||||
|
|
||||||
|
if (guild is null)
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, "Guild not found"));
|
||||||
|
|
||||||
|
var data = await _xp.GetGuildUserXps(request.GuildId, request.Page);
|
||||||
|
var total = await _xp.GetTotalGuildUsers(request.GuildId);
|
||||||
|
|
||||||
|
var reply = new GetXpLbReply
|
||||||
|
{
|
||||||
|
Total = total
|
||||||
|
};
|
||||||
|
|
||||||
|
reply.Users.AddRange(await data
|
||||||
|
.Select(async x =>
|
||||||
|
{
|
||||||
|
var user = guild.GetUser(x.UserId);
|
||||||
|
|
||||||
|
if (user is null)
|
||||||
|
{
|
||||||
|
var du = await _duSvc.GetUserAsync(x.UserId);
|
||||||
|
if (du is null)
|
||||||
|
return new XpLbUserReply
|
||||||
|
{
|
||||||
|
UserId = x.UserId,
|
||||||
|
Avatar = string.Empty,
|
||||||
|
Username = string.Empty,
|
||||||
|
Xp = x.Xp,
|
||||||
|
Level = new LevelStats(x.Xp).Level
|
||||||
|
};
|
||||||
|
|
||||||
|
return new XpLbUserReply()
|
||||||
|
{
|
||||||
|
UserId = x.UserId,
|
||||||
|
Avatar = du.RealAvatarUrl()?.ToString() ?? string.Empty,
|
||||||
|
Username = du.ToString() ?? string.Empty,
|
||||||
|
Xp = x.Xp,
|
||||||
|
Level = new LevelStats(x.Xp).Level
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new XpLbUserReply
|
||||||
|
{
|
||||||
|
UserId = x.UserId,
|
||||||
|
Avatar = user?.GetAvatarUrl() ?? string.Empty,
|
||||||
|
Username = user?.ToString() ?? string.Empty,
|
||||||
|
Xp = x.Xp,
|
||||||
|
Level = new LevelStats(x.Xp).Level
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.WhenAll());
|
||||||
|
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
}
|
@@ -14,10 +14,7 @@ public sealed partial class GrpcApiPermsInterceptor : Interceptor
|
|||||||
_client = client;
|
_client = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
|
private async Task RequestHandler(ServerCallContext context)
|
||||||
TRequest request,
|
|
||||||
ServerCallContext context,
|
|
||||||
UnaryServerMethod<TRequest, TResponse> continuation)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -42,7 +39,7 @@ public sealed partial class GrpcApiPermsInterceptor : Interceptor
|
|||||||
|
|
||||||
// if the method is explicitly marked as not requiring auth
|
// if the method is explicitly marked as not requiring auth
|
||||||
if (_noAuthRequired.Contains(method))
|
if (_noAuthRequired.Contains(method))
|
||||||
return await continuation(request, context);
|
return;
|
||||||
|
|
||||||
// otherwise the method requires auth, and if it requires auth then the guildid has to be specified
|
// otherwise the method requires auth, and if it requires auth then the guildid has to be specified
|
||||||
if (string.IsNullOrWhiteSpace(gidString))
|
if (string.IsNullOrWhiteSpace(gidString))
|
||||||
@@ -61,8 +58,6 @@ public sealed partial class GrpcApiPermsInterceptor : Interceptor
|
|||||||
// if not then use the default, which is Administrator permission
|
// if not then use the default, which is Administrator permission
|
||||||
await EnsureUserHasPermission(guildId, userId, DEFAULT_PERMISSION);
|
await EnsureUserHasPermission(guildId, userId, DEFAULT_PERMISSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await continuation(request, context);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -83,4 +78,42 @@ public sealed partial class GrpcApiPermsInterceptor : Interceptor
|
|||||||
throw new RpcException(new Status(StatusCode.PermissionDenied,
|
throw new RpcException(new Status(StatusCode.PermissionDenied,
|
||||||
$"You need {perm} permission to use this method"));
|
$"You need {perm} permission to use this method"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override async Task<TResponse> ClientStreamingServerHandler<TRequest, TResponse>(
|
||||||
|
IAsyncStreamReader<TRequest> requestStream,
|
||||||
|
ServerCallContext context,
|
||||||
|
ClientStreamingServerMethod<TRequest, TResponse> continuation)
|
||||||
|
{
|
||||||
|
await RequestHandler(context);
|
||||||
|
return await continuation(requestStream, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task DuplexStreamingServerHandler<TRequest, TResponse>(
|
||||||
|
IAsyncStreamReader<TRequest> requestStream,
|
||||||
|
IServerStreamWriter<TResponse> responseStream,
|
||||||
|
ServerCallContext context,
|
||||||
|
DuplexStreamingServerMethod<TRequest, TResponse> continuation)
|
||||||
|
{
|
||||||
|
await RequestHandler(context);
|
||||||
|
await continuation(requestStream, responseStream, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task ServerStreamingServerHandler<TRequest, TResponse>(
|
||||||
|
TRequest request,
|
||||||
|
IServerStreamWriter<TResponse> responseStream,
|
||||||
|
ServerCallContext context,
|
||||||
|
ServerStreamingServerMethod<TRequest, TResponse> continuation)
|
||||||
|
{
|
||||||
|
await RequestHandler(context);
|
||||||
|
await continuation(request, responseStream, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
|
||||||
|
TRequest request,
|
||||||
|
ServerCallContext context,
|
||||||
|
UnaryServerMethod<TRequest, TResponse> continuation)
|
||||||
|
{
|
||||||
|
await RequestHandler(context);
|
||||||
|
return await continuation(request, context);
|
||||||
|
}
|
||||||
}
|
}
|
9
src/NadekoBot/Services/SvcExtensions.cs
Normal file
9
src/NadekoBot/Services/SvcExtensions.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using Grpc.Core;
|
||||||
|
|
||||||
|
namespace NadekoBot.GrpcApi;
|
||||||
|
|
||||||
|
public static class SvcExtensions
|
||||||
|
{
|
||||||
|
public static ulong GetUserId(this Metadata meta)
|
||||||
|
=> ulong.Parse(meta.FirstOrDefault(x => x.Key == "userid")!.Value);
|
||||||
|
}
|
@@ -76,6 +76,9 @@ public readonly struct kwum : IEquatable<kwum>
|
|||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
|
if (_value == 0)
|
||||||
|
return VALID_CHARACTERS[0].ToString();
|
||||||
|
|
||||||
var count = VALID_CHARACTERS.Length;
|
var count = VALID_CHARACTERS.Length;
|
||||||
var localValue = _value;
|
var localValue = _value;
|
||||||
var arrSize = (int)Math.Log(localValue, count) + 1;
|
var arrSize = (int)Math.Log(localValue, count) + 1;
|
||||||
|
@@ -40,4 +40,11 @@ public interface ICurrencyService
|
|||||||
TxData? txData);
|
TxData? txData);
|
||||||
|
|
||||||
Task<IReadOnlyList<DiscordUser>> GetTopRichest(ulong ignoreId, int page = 0, int perPage = 9);
|
Task<IReadOnlyList<DiscordUser>> GetTopRichest(ulong ignoreId, int page = 0, int perPage = 9);
|
||||||
|
|
||||||
|
Task<IReadOnlyList<CurrencyTransaction>> GetTransactionsAsync(
|
||||||
|
ulong userId,
|
||||||
|
int page,
|
||||||
|
int perPage = 15);
|
||||||
|
|
||||||
|
Task<int> GetTransactionsCountAsync(ulong userId);
|
||||||
}
|
}
|
@@ -201,9 +201,12 @@ public sealed partial class GoogleApiService : IGoogleApiService, INService
|
|||||||
{
|
{
|
||||||
string text;
|
string text;
|
||||||
|
|
||||||
if (!Languages.ContainsKey(sourceLanguage) || !Languages.ContainsKey(targetLanguage))
|
if (!Languages.ContainsKey(targetLanguage))
|
||||||
throw new ArgumentException(nameof(sourceLanguage) + "/" + nameof(targetLanguage));
|
throw new ArgumentException(nameof(sourceLanguage) + "/" + nameof(targetLanguage));
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(sourceLanguage) || !Languages.ContainsKey(sourceLanguage))
|
||||||
|
sourceLanguage = "auto";
|
||||||
|
|
||||||
|
|
||||||
var url = new Uri(string.Format(
|
var url = new Uri(string.Format(
|
||||||
"https://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}",
|
"https://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}",
|
||||||
@@ -223,7 +226,7 @@ public sealed partial class GoogleApiService : IGoogleApiService, INService
|
|||||||
private string ConvertToLanguageCode(string language)
|
private string ConvertToLanguageCode(string language)
|
||||||
{
|
{
|
||||||
Languages.TryGetValue(language, out var mode);
|
Languages.TryGetValue(language, out var mode);
|
||||||
return mode;
|
return string.IsNullOrWhiteSpace(mode) ? language : mode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -154,7 +154,6 @@ public sealed partial class GoogleApiService
|
|||||||
}
|
}
|
||||||
|
|
||||||
Languages = langs;
|
Languages = langs;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -112,4 +112,29 @@ public sealed class CurrencyService : ICurrencyService, INService
|
|||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
return await uow.Set<DiscordUser>().GetTopRichest(ignoreId, page, perPage);
|
return await uow.Set<DiscordUser>().GetTopRichest(ignoreId, page, perPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<CurrencyTransaction>> GetTransactionsAsync(
|
||||||
|
ulong userId,
|
||||||
|
int page,
|
||||||
|
int perPage = 15)
|
||||||
|
{
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
|
||||||
|
var trs = await uow.GetTable<CurrencyTransaction>()
|
||||||
|
.Where(x => x.UserId == userId)
|
||||||
|
.OrderByDescending(x => x.DateAdded)
|
||||||
|
.Skip(perPage * page)
|
||||||
|
.Take(perPage)
|
||||||
|
.ToListAsyncLinqToDB();
|
||||||
|
|
||||||
|
return trs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> GetTransactionsCountAsync(ulong userId)
|
||||||
|
{
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
return await uow.GetTable<CurrencyTransaction>()
|
||||||
|
.Where(x => x.UserId == userId)
|
||||||
|
.CountAsyncLinqToDB();
|
||||||
|
}
|
||||||
}
|
}
|
@@ -77,8 +77,7 @@ public class DefaultWallet : IWallet
|
|||||||
.InsertOrUpdateAsync(() => new()
|
.InsertOrUpdateAsync(() => new()
|
||||||
{
|
{
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
Username = "Unknown",
|
Username = "??Unknown",
|
||||||
Discriminator = "????",
|
|
||||||
CurrencyAmount = amount,
|
CurrencyAmount = amount,
|
||||||
},
|
},
|
||||||
(old) => new()
|
(old) => new()
|
||||||
|
@@ -193,7 +193,7 @@ public sealed class StatsService : IStatsService, IReadyExecutor, INService
|
|||||||
Id = g.Id,
|
Id = g.Id,
|
||||||
IconUrl = g.IconUrl,
|
IconUrl = g.IconUrl,
|
||||||
Name = g.Name,
|
Name = g.Name,
|
||||||
Owner = (await ig.GetUserAsync(g.OwnerId))?.Username ?? "Unknown",
|
Owner = (await ig.GetUserAsync(g.OwnerId))?.Username ?? "??Unknown",
|
||||||
OwnerId = g.OwnerId,
|
OwnerId = g.OwnerId,
|
||||||
CreatedAt = g.CreatedAt.UtcDateTime,
|
CreatedAt = g.CreatedAt.UtcDateTime,
|
||||||
VoiceChannels = g.VoiceChannels.Count,
|
VoiceChannels = g.VoiceChannels.Count,
|
||||||
|
@@ -1425,3 +1425,28 @@ keep:
|
|||||||
- keep
|
- keep
|
||||||
leaveunkeptservers:
|
leaveunkeptservers:
|
||||||
- leaveunkeptservers
|
- leaveunkeptservers
|
||||||
|
ncanvas:
|
||||||
|
- ncanvas
|
||||||
|
- nc
|
||||||
|
- ncanv
|
||||||
|
ncsetimg:
|
||||||
|
- ncsetimg
|
||||||
|
- ncsi
|
||||||
|
ncsetpixel:
|
||||||
|
- ncsetpixel
|
||||||
|
- ncsp
|
||||||
|
- ncs
|
||||||
|
nczoom:
|
||||||
|
- nczoom
|
||||||
|
- ncz
|
||||||
|
ncpixel:
|
||||||
|
- ncpixel
|
||||||
|
- ncp
|
||||||
|
- ncgp
|
||||||
|
ncreset:
|
||||||
|
- ncreset
|
||||||
|
translateflags:
|
||||||
|
- translateflags
|
||||||
|
- trfl
|
||||||
|
- fltr
|
||||||
|
- transflags
|
@@ -15,7 +15,7 @@ color:
|
|||||||
pending: faa61a
|
pending: faa61a
|
||||||
# Default bot language. It has to be in the list of supported languages (.langli)
|
# Default bot language. It has to be in the list of supported languages (.langli)
|
||||||
defaultLocale: en-US
|
defaultLocale: en-US
|
||||||
# Style in which executed commands will show up in the console.
|
# Style in which executed commands will show up in the logs.
|
||||||
# Allowed values: Simple, Normal, None
|
# Allowed values: Simple, Normal, None
|
||||||
consoleOutputType: Normal
|
consoleOutputType: Normal
|
||||||
# Whether the bot will check for new releases every hour
|
# Whether the bot will check for new releases every hour
|
||||||
|
@@ -394,6 +394,21 @@
|
|||||||
"Administrator Server Permission"
|
"Administrator Server Permission"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".leaveunkeptservers"
|
||||||
|
],
|
||||||
|
"Description": "Leaves all servers whose owners didn't run .keep",
|
||||||
|
"Usage": [
|
||||||
|
".leaveunkeptservers"
|
||||||
|
],
|
||||||
|
"Submodule": "CleanupCommands",
|
||||||
|
"Module": "Administration",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": [
|
||||||
|
"Bot Owner Only"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".sqlselect"
|
".sqlselect"
|
||||||
@@ -586,13 +601,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".greetdel",
|
".greet"
|
||||||
".grdel"
|
|
||||||
],
|
],
|
||||||
"Description": "Sets the time it takes (in seconds) for greet messages to be auto-deleted. Set it to `0` to disable automatic deletion.",
|
"Description": "Toggles announcements on the current channel when someone joins the server.",
|
||||||
"Usage": [
|
"Usage": [
|
||||||
".greetdel 0",
|
".greet"
|
||||||
".greetdel 30"
|
|
||||||
],
|
],
|
||||||
"Submodule": "GreetCommands",
|
"Submodule": "GreetCommands",
|
||||||
"Module": "Administration",
|
"Module": "Administration",
|
||||||
@@ -603,11 +616,13 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".greet"
|
".greetdel",
|
||||||
|
".grdel"
|
||||||
],
|
],
|
||||||
"Description": "Toggles announcements on the current channel when someone joins the server.",
|
"Description": "Sets the time it takes (in seconds) for greet messages to be auto-deleted. Set it to `0` to disable automatic deletion.",
|
||||||
"Usage": [
|
"Usage": [
|
||||||
".greet"
|
".greetdel 0",
|
||||||
|
".greetdel 30"
|
||||||
],
|
],
|
||||||
"Submodule": "GreetCommands",
|
"Submodule": "GreetCommands",
|
||||||
"Module": "Administration",
|
"Module": "Administration",
|
||||||
@@ -676,21 +691,6 @@
|
|||||||
"ManageServer Server Permission"
|
"ManageServer Server Permission"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"Aliases": [
|
|
||||||
".byemsg"
|
|
||||||
],
|
|
||||||
"Description": "Sets a new leave announcement message which will be shown in the current channel. \nUsing this command with no message will show the current bye message. \nSupports [placeholders](https://docs.nadeko.bot/en/latest/placeholders/) and [embeds](https://eb.nadeko.bot/)",
|
|
||||||
"Usage": [
|
|
||||||
".byemsg %user.name% has left."
|
|
||||||
],
|
|
||||||
"Submodule": "GreetCommands",
|
|
||||||
"Module": "Administration",
|
|
||||||
"Options": null,
|
|
||||||
"Requirements": [
|
|
||||||
"ManageServer Server Permission"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".byedel"
|
".byedel"
|
||||||
@@ -709,12 +709,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".byetest"
|
".byemsg"
|
||||||
],
|
],
|
||||||
"Description": "Sends the bye message in the current channel as if you just left the server. You can optionally specify a different user.",
|
"Description": "Sets a new leave announcement message which will be shown in the current channel. \nUsing this command with no message will show the current bye message. \nSupports [placeholders](https://docs.nadeko.bot/en/latest/placeholders/) and [embeds](https://eb.nadeko.bot/)",
|
||||||
"Usage": [
|
"Usage": [
|
||||||
".byetest",
|
".byemsg %user.name% has left."
|
||||||
".byetest @SomeoneElse"
|
|
||||||
],
|
],
|
||||||
"Submodule": "GreetCommands",
|
"Submodule": "GreetCommands",
|
||||||
"Module": "Administration",
|
"Module": "Administration",
|
||||||
@@ -755,6 +754,22 @@
|
|||||||
"ManageServer Server Permission"
|
"ManageServer Server Permission"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".byetest"
|
||||||
|
],
|
||||||
|
"Description": "Sends the bye message in the current channel as if you just left the server. You can optionally specify a different user.",
|
||||||
|
"Usage": [
|
||||||
|
".byetest",
|
||||||
|
".byetest @SomeoneElse"
|
||||||
|
],
|
||||||
|
"Submodule": "GreetCommands",
|
||||||
|
"Module": "Administration",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": [
|
||||||
|
"ManageServer Server Permission"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".boosttest"
|
".boosttest"
|
||||||
@@ -1090,7 +1105,7 @@
|
|||||||
"Aliases": [
|
"Aliases": [
|
||||||
".antialt"
|
".antialt"
|
||||||
],
|
],
|
||||||
"Description": "Applies a punishment action to any user whose account is younger than the specified threshold. Specify time after the punishment to have a timed punishment (not all punishments support timers).",
|
"Description": "Applies a punishment action to any user whose account is younger than the specified threshold. \nAvailable Punishments are: Ban, Kick, Softban, Mute, VoiceMute, ChatMute, RemoveRoles, AddRole, Warn, TimeOut\nYou can specify an additional time argument to do a timed punishment for actions which support it (Ban, Mute, etc) up to 24h.\nMax message count is 10.\nProvide no parameters to disable.",
|
||||||
"Usage": [
|
"Usage": [
|
||||||
".antialt 1h Ban",
|
".antialt 1h Ban",
|
||||||
".antialt 3d Mute 1h"
|
".antialt 3d Mute 1h"
|
||||||
@@ -1106,7 +1121,7 @@
|
|||||||
"Aliases": [
|
"Aliases": [
|
||||||
".antiraid"
|
".antiraid"
|
||||||
],
|
],
|
||||||
"Description": "Sets an anti-raid protection on the server. Provide no parameters to disable. First parameter is number of people which will trigger the protection. Second parameter is a time interval in which that number of people needs to join in order to trigger the protection, and third parameter is punishment for those people. You can specify an additional time argument to do a timed punishment for actions which support it (Ban, Mute, etc) up to 24h. Available punishments: Ban, Kick, Softban, Mute, VoiceMute, ChatMute, RemoveRoles, AddRole, Warn, TimeOut",
|
"Description": "Sets an anti-raid protection on the server.\n\nFirst parameter is number of people which will trigger the protection.\n\nSecond parameter is a time interval in which that number of people needs to join in order to trigger the protection.\n\nThird parameter is punishment for those people.\nAvailable punishments: Ban, Kick, Softban, Mute, VoiceMute, ChatMute, RemoveRoles, AddRole, Warn, TimeOut\nYou can specify an additional time argument to do a timed punishment for actions which support it (Ban, Mute, etc) up to 24h.\n\nProvide no parameters to disable.",
|
||||||
"Usage": [
|
"Usage": [
|
||||||
".antiraid 5 20 Kick",
|
".antiraid 5 20 Kick",
|
||||||
".antiraid 7 9 Ban",
|
".antiraid 7 9 Ban",
|
||||||
@@ -1124,7 +1139,7 @@
|
|||||||
"Aliases": [
|
"Aliases": [
|
||||||
".antispam"
|
".antispam"
|
||||||
],
|
],
|
||||||
"Description": "Stops people from repeating same message X times in a row. Provide no parameters to disable. You can specify to either mute, kick or ban the offenders. You can specify an additional time argument to do a timed punishment for actions which support it (Ban, Mute, etc) up to 24h. Max message count is 10. Available punishments: Ban, Kick, Softban, Mute, VoiceMute, ChatMute, AddRole, RemoveRoles, Warn, TimeOut",
|
"Description": "Applies a Punishment to people who repeat the same message X times in a row.\nAvailable Punishments are: Ban, Kick, Softban, Mute, VoiceMute, ChatMute, RemoveRoles, AddRole, Warn, TimeOut\nYou can specify an additional time argument to do a timed punishment for actions which support it (Ban, Mute, etc) up to 24h.\nMax message count is 10. \nProvide no parameters to disable.",
|
||||||
"Usage": [
|
"Usage": [
|
||||||
".antispam 3 Mute",
|
".antispam 3 Mute",
|
||||||
".antispam 5 Ban",
|
".antispam 5 Ban",
|
||||||
@@ -1939,13 +1954,14 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
|
".setactivity",
|
||||||
".setgame"
|
".setgame"
|
||||||
],
|
],
|
||||||
"Description": "Sets the bots game status to either Playing, Listening, or Watching.",
|
"Description": "Sets the bots game status to a Custom, Playing, Listening, or Watching status.",
|
||||||
"Usage": [
|
"Usage": [
|
||||||
".setgame Playing with snakes.",
|
".setactivity Just chilling",
|
||||||
".setgame Watching anime.",
|
".setactivity Playing with snakes",
|
||||||
".setgame Listening music."
|
".setactivity Listening music"
|
||||||
],
|
],
|
||||||
"Submodule": "SelfCommands",
|
"Submodule": "SelfCommands",
|
||||||
"Module": "Administration",
|
"Module": "Administration",
|
||||||
@@ -3674,7 +3690,7 @@
|
|||||||
"Aliases": [
|
"Aliases": [
|
||||||
".choose"
|
".choose"
|
||||||
],
|
],
|
||||||
"Description": "Chooses a thing from a list of things",
|
"Description": "Chooses a thing from a list of things. Separate items with a semicolon ;",
|
||||||
"Usage": [
|
"Usage": [
|
||||||
".choose Get up;Sleep;Sleep more"
|
".choose Get up;Sleep;Sleep more"
|
||||||
],
|
],
|
||||||
@@ -3772,6 +3788,98 @@
|
|||||||
"Options": null,
|
"Options": null,
|
||||||
"Requirements": []
|
"Requirements": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".ncanvas",
|
||||||
|
".nc",
|
||||||
|
".ncanv"
|
||||||
|
],
|
||||||
|
"Description": "Shows the current nCanvas.\nThe canvas allows users to set each pixel's color and text using currency.",
|
||||||
|
"Usage": [
|
||||||
|
".ncanvas"
|
||||||
|
],
|
||||||
|
"Submodule": "NCanvasCommands",
|
||||||
|
"Module": "Games",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".nczoom",
|
||||||
|
".ncz"
|
||||||
|
],
|
||||||
|
"Description": "Zooms in on the nCanvas.\nBot will show the 10x10 grid with the position of each cell for use with `ncset`.\nYou can either use alphanumeric position (ex. s4u) or pixel x and y (ex. 123 123)",
|
||||||
|
"Usage": [
|
||||||
|
".nczoom sgu",
|
||||||
|
".nczoom 123 123"
|
||||||
|
],
|
||||||
|
"Submodule": "NCanvasCommands",
|
||||||
|
"Module": "Games",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".ncsetpixel",
|
||||||
|
".ncsp",
|
||||||
|
".ncs"
|
||||||
|
],
|
||||||
|
"Description": "Sets a pixel's color and text on the nCanvas.\nYou must specify the position of the pixel to set in alphanumeric format.\nYou can obtain alphanumeric position of the pixel by using `nczoom` or `ncp <x> <y>`",
|
||||||
|
"Usage": [
|
||||||
|
".ncsetpixel sgu #ff0000 Some text"
|
||||||
|
],
|
||||||
|
"Submodule": "NCanvasCommands",
|
||||||
|
"Module": "Games",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".ncpixel",
|
||||||
|
".ncp",
|
||||||
|
".ncgp"
|
||||||
|
],
|
||||||
|
"Description": "Shows the pixel at the specified position.\nYou can get pixel positions by using `nczoom`",
|
||||||
|
"Usage": [
|
||||||
|
".ncpixel sgu",
|
||||||
|
".ncpixel 123 123"
|
||||||
|
],
|
||||||
|
"Submodule": "NCanvasCommands",
|
||||||
|
"Module": "Games",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".ncsetimg",
|
||||||
|
".ncsi"
|
||||||
|
],
|
||||||
|
"Description": "Attach the image to the message sending the command to overwrite the nCanvas with it.\nAll prices and colors will be reset.\nThe image must be equal to the size of the nCanvas (default is 500x350)\nThis command is dangerous and irreversible.",
|
||||||
|
"Usage": [
|
||||||
|
".ncsetimg"
|
||||||
|
],
|
||||||
|
"Submodule": "NCanvasCommands",
|
||||||
|
"Module": "Games",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": [
|
||||||
|
"Bot Owner Only"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".ncreset"
|
||||||
|
],
|
||||||
|
"Description": "Clears the nCanvas.\nAll prices and colors will be reset.\nThis command is dangerous and irreversible.",
|
||||||
|
"Usage": [
|
||||||
|
".ncreset"
|
||||||
|
],
|
||||||
|
"Submodule": "NCanvasCommands",
|
||||||
|
"Module": "Games",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": [
|
||||||
|
"Bot Owner Only"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".nunchi"
|
".nunchi"
|
||||||
@@ -5171,7 +5279,7 @@
|
|||||||
],
|
],
|
||||||
"Description": "Toggles whether a module can be used on any server.",
|
"Description": "Toggles whether a module can be used on any server.",
|
||||||
"Usage": [
|
"Usage": [
|
||||||
".globalmodule nsfw"
|
".globalmodule Gambling"
|
||||||
],
|
],
|
||||||
"Submodule": "GlobalPermissionCommands",
|
"Submodule": "GlobalPermissionCommands",
|
||||||
"Module": "Permissions",
|
"Module": "Permissions",
|
||||||
@@ -6719,13 +6827,15 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".listquotes",
|
".quotelist",
|
||||||
".liqu"
|
".qli",
|
||||||
|
".quli",
|
||||||
|
".qulist"
|
||||||
],
|
],
|
||||||
"Description": "Lists all quotes on the server ordered alphabetically or by ID. 15 Per page.",
|
"Description": "Lists all quotes on the server ordered alphabetically or by ID. 15 Per page.",
|
||||||
"Usage": [
|
"Usage": [
|
||||||
".listquotes 3",
|
".quotelist 3",
|
||||||
".listquotes 3 id"
|
".quotelist 3 id"
|
||||||
],
|
],
|
||||||
"Submodule": "QuoteCommands",
|
"Submodule": "QuoteCommands",
|
||||||
"Module": "Utility",
|
"Module": "Utility",
|
||||||
@@ -6735,7 +6845,10 @@
|
|||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".quoteprint",
|
".quoteprint",
|
||||||
"..."
|
".qp",
|
||||||
|
".qup",
|
||||||
|
"...",
|
||||||
|
".qprint"
|
||||||
],
|
],
|
||||||
"Description": "Prints a random quote with a specified name.",
|
"Description": "Prints a random quote with a specified name.",
|
||||||
"Usage": [
|
"Usage": [
|
||||||
@@ -6749,7 +6862,9 @@
|
|||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".quoteshow",
|
".quoteshow",
|
||||||
".qshow"
|
".qsh",
|
||||||
|
".qshow",
|
||||||
|
".qushow"
|
||||||
],
|
],
|
||||||
"Description": "Shows information about a quote with the specified ID.",
|
"Description": "Shows information about a quote with the specified ID.",
|
||||||
"Usage": [
|
"Usage": [
|
||||||
@@ -6763,6 +6878,7 @@
|
|||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".quotesearch",
|
".quotesearch",
|
||||||
|
".qse",
|
||||||
".qsearch"
|
".qsearch"
|
||||||
],
|
],
|
||||||
"Description": "Shows a random quote given a search query. Partially matches in several ways: 1) Only content of any quote, 2) only by author, 3) keyword and content, 3) or keyword and author",
|
"Description": "Shows a random quote given a search query. Partially matches in several ways: 1) Only content of any quote, 2) only by author, 3) keyword and content, 3) or keyword and author",
|
||||||
@@ -6782,7 +6898,7 @@
|
|||||||
".quoteid",
|
".quoteid",
|
||||||
".qid"
|
".qid"
|
||||||
],
|
],
|
||||||
"Description": "Displays the quote with the specified ID number. Quote ID numbers can be found by typing `.liqu [num]` where `[num]` is a number of a page which contains 15 quotes.",
|
"Description": "-| Displays the quote with the specified ID number.",
|
||||||
"Usage": [
|
"Usage": [
|
||||||
".quoteid 123456"
|
".quoteid 123456"
|
||||||
],
|
],
|
||||||
@@ -6794,6 +6910,9 @@
|
|||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".quoteadd",
|
".quoteadd",
|
||||||
|
".qa",
|
||||||
|
".qadd",
|
||||||
|
".quadd",
|
||||||
".."
|
".."
|
||||||
],
|
],
|
||||||
"Description": "Adds a new quote with the specified name and message.",
|
"Description": "Adds a new quote with the specified name and message.",
|
||||||
@@ -6808,6 +6927,8 @@
|
|||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".quoteedit",
|
".quoteedit",
|
||||||
|
".qe",
|
||||||
|
".que",
|
||||||
".qedit"
|
".qedit"
|
||||||
],
|
],
|
||||||
"Description": "Edits a quote with the specified ID.",
|
"Description": "Edits a quote with the specified ID.",
|
||||||
@@ -6822,7 +6943,9 @@
|
|||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".quotedelete",
|
".quotedelete",
|
||||||
".qdel"
|
".qd",
|
||||||
|
".qdel",
|
||||||
|
".qdelete"
|
||||||
],
|
],
|
||||||
"Description": "Deletes a quote with the specified ID. You have to either have the Manage Messages permission or be the creator of the quote to delete it.",
|
"Description": "Deletes a quote with the specified ID. You have to either have the Manage Messages permission or be the creator of the quote to delete it.",
|
||||||
"Usage": [
|
"Usage": [
|
||||||
@@ -6836,6 +6959,7 @@
|
|||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".quotedeleteauthor",
|
".quotedeleteauthor",
|
||||||
|
".qda",
|
||||||
".qdelauth"
|
".qdelauth"
|
||||||
],
|
],
|
||||||
"Description": "Deletes all quotes by the specified author. If the author is not you, then ManageMessage server permission is required.",
|
"Description": "Deletes all quotes by the specified author. If the author is not you, then ManageMessage server permission is required.",
|
||||||
@@ -6849,13 +6973,13 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".delallquotes",
|
".quotesdeleteall",
|
||||||
".daq",
|
".qdall",
|
||||||
".delallq"
|
".qdeleteall"
|
||||||
],
|
],
|
||||||
"Description": "Deletes all quotes on a specified keyword.",
|
"Description": "Deletes all quotes with the specified keyword.",
|
||||||
"Usage": [
|
"Usage": [
|
||||||
".delallquotes kek"
|
".quotesdeleteall kek"
|
||||||
],
|
],
|
||||||
"Submodule": "QuoteCommands",
|
"Submodule": "QuoteCommands",
|
||||||
"Module": "Utility",
|
"Module": "Utility",
|
||||||
@@ -6867,6 +6991,7 @@
|
|||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".quotesexport",
|
".quotesexport",
|
||||||
|
".qex",
|
||||||
".qexport"
|
".qexport"
|
||||||
],
|
],
|
||||||
"Description": "Exports quotes from the current server into a .yml file",
|
"Description": "Exports quotes from the current server into a .yml file",
|
||||||
@@ -6883,6 +7008,8 @@
|
|||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".quotesimport",
|
".quotesimport",
|
||||||
|
".qim",
|
||||||
|
".qimp",
|
||||||
".qimport"
|
".qimport"
|
||||||
],
|
],
|
||||||
"Description": "Upload the file or send the raw .yml data with this command to import all quotes from the specified string or file into the current server.",
|
"Description": "Upload the file or send the raw .yml data with this command to import all quotes from the specified string or file into the current server.",
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
# DO NOT CHANGE
|
# DO NOT CHANGE
|
||||||
version: 8
|
version: 9
|
||||||
# Currency settings
|
# Currency settings
|
||||||
currency:
|
currency:
|
||||||
# What is the emoji/character which represents the currency
|
# What is the emoji/character which represents the currency
|
||||||
@@ -56,6 +56,8 @@ timely:
|
|||||||
# How often (in hours) can users claim currency with .timely command
|
# How often (in hours) can users claim currency with .timely command
|
||||||
# setting to 0 or less will disable this feature
|
# setting to 0 or less will disable this feature
|
||||||
cooldown: 12
|
cooldown: 12
|
||||||
|
# Whether the users are required to type a password when they do timely.
|
||||||
|
requirePassword: true
|
||||||
# How much will each user's owned currency decay over time.
|
# How much will each user's owned currency decay over time.
|
||||||
decay:
|
decay:
|
||||||
# Percentage of user's current currency which will be deducted every 24h.
|
# Percentage of user's current currency which will be deducted every 24h.
|
||||||
@@ -125,12 +127,13 @@ waifu:
|
|||||||
# Settings for periodic waifu price decay.
|
# Settings for periodic waifu price decay.
|
||||||
# Waifu price decays only if the waifu has no claimer.
|
# Waifu price decays only if the waifu has no claimer.
|
||||||
decay:
|
decay:
|
||||||
# Percentage (0 - 100) of the waifu value to reduce.
|
# Unclaimed waifus will decay by this percentage (0 - 100).
|
||||||
# Set 0 to disable
|
# Default is 0 (disabled)
|
||||||
# For example if a waifu has a price of 500$, setting this value to 10 would reduce the waifu value by 10% (50$)
|
# For example if a waifu has a price of 500$, setting this value to 10 would reduce the waifu value by 10% (50$)
|
||||||
unclaimedDecayPercent: 0
|
unclaimedDecayPercent: 0
|
||||||
# Claimed waifus will decay by this percentage (0 - 100).
|
# Claimed waifus will decay by this percentage (0 - 100).
|
||||||
# Default is 0 (disabled)
|
# Default is 0 (disabled)
|
||||||
|
# For example if a waifu has a price of 500$, setting this value to 10 would reduce the waifu value by 10% (50$)
|
||||||
claimedDecayPercent: 0
|
claimedDecayPercent: 0
|
||||||
# How often to decay waifu values, in hours
|
# How often to decay waifu values, in hours
|
||||||
hourInterval: 24
|
hourInterval: 24
|
||||||
|
@@ -4568,3 +4568,77 @@ leaveunkeptservers:
|
|||||||
desc: "Shard id from which to start leaving unkept servers."
|
desc: "Shard id from which to start leaving unkept servers."
|
||||||
- delay:
|
- delay:
|
||||||
desc: "Delay in miliseconds between leaves"
|
desc: "Delay in miliseconds between leaves"
|
||||||
|
ncanvas:
|
||||||
|
desc: |-
|
||||||
|
Shows the current nCanvas.
|
||||||
|
The canvas allows users to set each pixel's color and text using currency.
|
||||||
|
ex:
|
||||||
|
- ''
|
||||||
|
params:
|
||||||
|
- { }
|
||||||
|
nczoom:
|
||||||
|
desc: |-
|
||||||
|
Zooms in on the nCanvas.
|
||||||
|
Bot will show the 10x10 grid with the position of each cell for use with `ncset`.
|
||||||
|
You can either use alphanumeric position (ex. s4u) or pixel x and y (ex. 123 123)
|
||||||
|
ex:
|
||||||
|
- 'sgu'
|
||||||
|
- '123 123'
|
||||||
|
params:
|
||||||
|
- position:
|
||||||
|
desc: "The position of the pixel to set in alphanumeric format."
|
||||||
|
- position:
|
||||||
|
desc: "The position of the pixel to set in pixel x and y format."
|
||||||
|
ncsetpixel:
|
||||||
|
desc: |-
|
||||||
|
Sets a pixel's color and text on the nCanvas.
|
||||||
|
You must specify the position of the pixel to set in alphanumeric format.
|
||||||
|
You can obtain alphanumeric position of the pixel by using `nczoom` or `ncp <x> <y>`
|
||||||
|
ex:
|
||||||
|
- 'sgu #ff0000 Some text'
|
||||||
|
params:
|
||||||
|
- position:
|
||||||
|
desc: "The position of the pixel to set in alphanumeric format."
|
||||||
|
- color:
|
||||||
|
desc: "The color of the pixel to set in HEX."
|
||||||
|
- text:
|
||||||
|
desc: "The optional text to set on the pixel."
|
||||||
|
ncsetimg:
|
||||||
|
desc: |-
|
||||||
|
Attach the image to the message sending the command to overwrite the nCanvas with it.
|
||||||
|
All prices and colors will be reset.
|
||||||
|
The image must be equal to the size of the nCanvas (default is 500x350)
|
||||||
|
This command is dangerous and irreversible.
|
||||||
|
ex:
|
||||||
|
- ''
|
||||||
|
params:
|
||||||
|
- { }
|
||||||
|
ncpixel:
|
||||||
|
desc: |-
|
||||||
|
Shows the pixel at the specified position.
|
||||||
|
You can get pixel positions by using `nczoom`
|
||||||
|
ex:
|
||||||
|
- 'sgu'
|
||||||
|
- '123 123'
|
||||||
|
params:
|
||||||
|
- position:
|
||||||
|
desc: "The position of the pixel to retrieve in alphanumeric format."
|
||||||
|
- position:
|
||||||
|
desc: "The position of the pixel to retrieve in pixel x and y format."
|
||||||
|
ncreset:
|
||||||
|
desc: |-
|
||||||
|
Clears the nCanvas.
|
||||||
|
All prices and colors will be reset.
|
||||||
|
This command is dangerous and irreversible.
|
||||||
|
ex:
|
||||||
|
- ''
|
||||||
|
params:
|
||||||
|
- { }
|
||||||
|
translateflags:
|
||||||
|
desc: |-
|
||||||
|
Toggles translate flags on the current channel.
|
||||||
|
Reacting with a country flag will translate the message to that country's language.
|
||||||
|
ex:
|
||||||
|
- ''
|
||||||
|
params:
|
||||||
|
- { }
|
@@ -622,6 +622,7 @@
|
|||||||
"region": "Region",
|
"region": "Region",
|
||||||
"remind2": "I will remind {0} to {1} {2} ({3})",
|
"remind2": "I will remind {0} to {1} {2} ({3})",
|
||||||
"remind_timely": "I will remind you about your timely reward {0}",
|
"remind_timely": "I will remind you about your timely reward {0}",
|
||||||
|
"timely_button": "Click the button to claim your timely reward.",
|
||||||
"remind_invalid": "Not a valid remind format. Remind must have a target, timer and a reason. Check the command list.",
|
"remind_invalid": "Not a valid remind format. Remind must have a target, timer and a reason. Check the command list.",
|
||||||
"remind_too_long": "Remind time has exceeded maximum.",
|
"remind_too_long": "Remind time has exceeded maximum.",
|
||||||
"repeater_redundant_no": "Repeater **#{0}** won't post redundant messages anymore.",
|
"repeater_redundant_no": "Repeater **#{0}** won't post redundant messages anymore.",
|
||||||
@@ -1101,5 +1102,17 @@
|
|||||||
"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.",
|
||||||
|
"nc_pixel_not_found": "Pixel not found",
|
||||||
|
"nc_pixel": "Pixel {0}",
|
||||||
|
"nc_position": "Position",
|
||||||
|
"nc_pixel_set": "Pixel {0} successfully set!",
|
||||||
|
"invalid_color": "Color you've specified is invalid.",
|
||||||
|
"nc_pixel_set_confirm": "Are you sure you want to set pixel {0}? It will cost you {1}",
|
||||||
|
"nc_hint": "Use `{0}nczoom x y` command to zoom in. Image is {1}x{2} pixels.",
|
||||||
|
"nc_insuff_payment": "Invalid payment.",
|
||||||
|
"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." ,
|
||||||
|
"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."
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user