Compare commits
No commits in common. "v5" and "5.1.16" have entirely different histories.
243 changed files with 6982 additions and 57110 deletions
356
CHANGELOG.md
356
CHANGELOG.md
|
@ -2,263 +2,20 @@
|
|||
|
||||
Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except date format. a-c-f-r-o
|
||||
|
||||
## [5.3.3] - 16.12.2024
|
||||
|
||||
## Fixed
|
||||
|
||||
- `.notify` commands are no longer owner only, they now require Admin permissions
|
||||
- `.notify` messages can now mention anyone
|
||||
|
||||
## [5.3.2] - 14.12.2024
|
||||
|
||||
## Fixed
|
||||
|
||||
- `.banner` should be working properly now with both server and global user banners
|
||||
|
||||
## [5.3.1] - 13.12.2024
|
||||
|
||||
## Changed
|
||||
|
||||
- `.translate` will now use 2 embeds, to allow for longer messages
|
||||
- Added role icon to `.inrole`, if it exists
|
||||
- `.honeypot` will now add a 'Honeypot' as a ban reason.
|
||||
|
||||
## Fixed
|
||||
|
||||
- `.winlb` looks better, has a title, shows 9 entries now
|
||||
- `.sar ex` help updated
|
||||
- `.banner` partially fixed, it still can't show global banners, but it will show guild ones correctly, in a good enough size
|
||||
- `.sclr` will now show correct color hexes without alpha
|
||||
- `.dmcmd` will now correctly block commands in dms, not globally
|
||||
|
||||
## [5.3.0] - 10.12.2024
|
||||
|
||||
## Added
|
||||
|
||||
- Added `.minesweeper` / `.mw` command - spoiler-based minesweeper minigame. Just for fun
|
||||
- Added `.temprole` command - add a role to a user for a certain amount of time, after which the role will be removed
|
||||
- Added `.xplevelset` - you can now set a level for a user in your server
|
||||
- Added `.winlb` command - leaderboard of top gambling wins
|
||||
- Added `.notify` command
|
||||
- Specify an event to be notified about, and the bot will post the specified message in the current channel when the
|
||||
event occurs
|
||||
- A few events supported right now:
|
||||
- `UserLevelUp` when user levels up in the server
|
||||
- `AddRoleReward` when a role is added to a user through .xpreward system
|
||||
- `RemoveRoleReward` when a role is removed from a user through .xpreward system
|
||||
- `Protection` when antialt, antiraid or antispam protection is triggered
|
||||
- Added `.banner` command to see someone's banner
|
||||
- Selfhosters:
|
||||
- Added `.dmmod` and `.dmcmd` - you can now disable or enable whether commands or modules can be executed in bot's
|
||||
DMs
|
||||
|
||||
## Changed
|
||||
|
||||
- Giveaway improvements
|
||||
- Now mentions winners in a separate message
|
||||
- Shows the timestamp of when the giveaway ends
|
||||
- Xp Changes
|
||||
- Removed awarded xp (the number in the brackets on the xp card)
|
||||
- Awarded xp, (or the new level set) now directly apply to user's real xp
|
||||
- Server xp notifications are now set by the server admin/manager in a specified channel
|
||||
- `.sclr show` will now show hex code of the current color
|
||||
- Queueing a song will now restart the playback if the queue is on the last track and stopped (there were no more tracks
|
||||
to play)
|
||||
- `.translate` will now use 2 embeds instead of 1
|
||||
|
||||
## Fixed
|
||||
|
||||
- .setstream and .setactivity will now pause .ropl (rotating statuses)
|
||||
- Fixed `.sar ex` help description
|
||||
|
||||
## Removed
|
||||
|
||||
- `.xpnotify` command, superseded by `.notify`, although as of right now you can't post user's level up in the same
|
||||
channel user last typed, because you have to specify a channel where the notify messages will be posted
|
||||
|
||||
## [5.2.4] - 29.11.2024
|
||||
|
||||
## Fixed
|
||||
|
||||
- More fixes for .sclr
|
||||
- `.iamn` fixed
|
||||
|
||||
## [5.2.3] - 29.11.2024
|
||||
|
||||
### Fixed
|
||||
|
||||
- `.iam` Fixed
|
||||
- `.sclr` will now properly change color on many commands it didn't work previously
|
||||
|
||||
### Changed
|
||||
|
||||
- `.rps` now also has bet amount in the result, like other gambling commands
|
||||
|
||||
## [5.2.2] - 29.11.2024
|
||||
|
||||
### Changed
|
||||
|
||||
- Button roles are now non-exclusive by default
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed sar migration, again (this time correctly)
|
||||
- Fixed `.sclr` not updating unless bot is restarted, the changes should be immediate now for warn and error
|
||||
- Fixed group buttons exclusivity message always saying groups are exclusive
|
||||
|
||||
|
||||
## [5.2.1] - 28.11.2024
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed old self assigned missing
|
||||
|
||||
## [5.2.0] - 28.11.2024
|
||||
|
||||
### Added
|
||||
|
||||
- Added `.todo undone` command to unmark a todo as done
|
||||
- Added Button Roles!
|
||||
- `.btr a` to add a button role to the specified message
|
||||
- `.btr list` to list all button roles on the server
|
||||
- `.btr rm` to remove a button role from the specified message
|
||||
- `.btr rma` to remove all button roles on the specified message
|
||||
- `.btr excl` to toggle exclusive button roles (only 1 role per message or any number)
|
||||
- Use `.h btr` for more info
|
||||
- Added `.wrongsong` which will delete the last queued song.
|
||||
- Useful in case you made a mistake, or the bot queued a wrong song
|
||||
- It will reset after a shuffle or fairplay toggle, or similar events.
|
||||
- Added Server color Commands!
|
||||
- Every Server can now set their own colors for ok/error/pending embed (the default green/red/yellow color on the
|
||||
left side of the message the bot sends)
|
||||
- Use `.h .sclr` to see the list of commands
|
||||
- `.sclr show` will show the current server colors
|
||||
- `.sclr ok <color hex>` to set ok color
|
||||
- `.sclr warn <color hex>` to set warn color
|
||||
- `.sclr error <color hex>` to set error color
|
||||
|
||||
### Changed
|
||||
|
||||
- Self Assigned Roles reworked! Use `.h .sar` for the list of commands
|
||||
- `.sar autodel`
|
||||
- Toggles the automatic deletion of the user's message and Nadeko's confirmations for .iam and .iamn commands.
|
||||
- `.sar ad`
|
||||
- Adds a role to the list of self-assignable roles. You can also specify a group.
|
||||
- If 'Exclusive self-assignable roles' feature is enabled (.sar exclusive), users will be able to pick one role
|
||||
per group.
|
||||
- `.sar groupname`
|
||||
- Sets a self assignable role group name. Provide no name to remove.
|
||||
- `.sar remove`
|
||||
- Removes a specified role from the list of self-assignable roles.
|
||||
- `.sar list`
|
||||
- Lists self-assignable roles. Shows 20 roles per page.
|
||||
- `.sar exclusive`
|
||||
- Toggles whether self-assigned roles are exclusive. While enabled, users can only have one self-assignable role
|
||||
per group.
|
||||
- `.sar rolelvlreq`
|
||||
- Set a level requirement on a self-assignable role.
|
||||
- `.sar grouprolereq`
|
||||
- Set a role that users have to have in order to assign a self-assignable role from the specified group.
|
||||
- `.sar groupdelete`
|
||||
- Deletes a self-assignable role group
|
||||
- `.iam` and `.iamn` are unchanged
|
||||
- Removed patron limits from Reaction Roles. Anyone can have as many reros as they like.
|
||||
- `.timely` captcha made stronger and cached per user.
|
||||
- `.bsreset` price reduced by 90%
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `.sinfo` for servers on other shard
|
||||
|
||||
## [5.1.20] - 13.11.2024
|
||||
|
||||
### Added
|
||||
|
||||
- Added `.rakeback` command, get a % of house edge back as claimable currency
|
||||
- Added `.snipe` command to quickly get a copy of a posted message as an embed
|
||||
- You can reply to a message to snipe that message
|
||||
- Or just type .snipe and the bot will snipe the last message in the channel with content or image
|
||||
- Added `.betstatsreset` / `.bsreset` command to reset your stats for a fee
|
||||
- Added `.gamblestatsreset` / `.gsreset` owner-only command to reset bot stats for all games
|
||||
- Added `.waifuclaims` command which lists all of your claimed waifus
|
||||
- Added and changed `%bot.time%` and `%bot.date%` placeholders. They use timestamp tags now
|
||||
|
||||
### Changed
|
||||
|
||||
- `.divorce` no longer has a cooldown
|
||||
- `.betroll` has a 2% better payout
|
||||
- `.slot` payout balanced out (less volatile), reduced jackpot win but increased other wins,
|
||||
- now has a new symbol, wheat
|
||||
- worse around 1% in total (now shares the top spot with .bf)
|
||||
|
||||
## [5.1.19] - 05.11.2024
|
||||
|
||||
### Added
|
||||
|
||||
- Added `.betstats`
|
||||
- See your own stats with .betstats
|
||||
- Target someone else: .betstats @mai_lanfiel
|
||||
- You can also specify a game .betstats lula
|
||||
- Or both! .betstats mai_lanfiel br
|
||||
- `.timely` can now have a server boost bonus
|
||||
- Configure server ids and reward amount in data/gambling.yml
|
||||
- anyone who boosts one of the sepcified servers gets the amount as base timely bonus
|
||||
|
||||
### Changed
|
||||
|
||||
- `.plant/pick` password font size will be slightly bigger
|
||||
- `.race` will now have 82-94% payout rate based on the number of players playing (1-12, x0.01 per player).
|
||||
- Any player over 12 won't increase payout
|
||||
|
||||
### Fixed
|
||||
|
||||
- `.xplb` and `.xpglb` now have proper ranks after page 1
|
||||
- Fixed boost bonus on shards different than the specified servers' shard
|
||||
|
||||
## [5.1.18] - 04.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
|
||||
## [5.1.16] - 29.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`
|
||||
- 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
|
||||
|
||||
|
@ -273,7 +30,7 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
|
|||
|
||||
## Fixed
|
||||
|
||||
- Fixed a big issue which caused several features to not get loaded on bot restart
|
||||
- Fixed a big issue which caused several features to not get loaded on bot restart
|
||||
- Alias collision fix `.qse` is now quotesearch, `.qs` will stay `.queuesearch`
|
||||
- Fixed some migrations which would prevent users from updating from ancient versions
|
||||
- Waifulb will no longer show #0000 discrims
|
||||
|
@ -313,11 +70,10 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
|
|||
|
||||
### Added
|
||||
|
||||
- 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.
|
||||
- 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.
|
||||
- Added initial version of grpc api. Beta
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed a bug which caused `.bye` and `.greet` messages to be randomly disabled
|
||||
|
@ -326,9 +82,9 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
|
|||
### Changed
|
||||
|
||||
- Youtube now always uses `yt-dlp`. Dropped support for `youtube-dl`
|
||||
- If you've previously renamed your yt-dlp file to youtube-dl, please rename it back.
|
||||
- If you've previously renamed your yt-dlp file to youtube-dl, please rename it back.
|
||||
- ytProvider in data/searches.yml now also controls where you're getting your song streams from.
|
||||
- (Invidious support added for .q)
|
||||
- (Invidious support added for .q)
|
||||
|
||||
## [5.1.10] - 24.09.2024
|
||||
|
||||
|
@ -347,28 +103,26 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
|
|||
- Fixed `.greettest`, and other `.*test` commands if you didn't have them enabled.
|
||||
- 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.
|
||||
- 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 bug was introduced in 5.1.8. PLEASE UPDATE if you are on 5.1.8
|
||||
- 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 bug was introduced in 5.1.8. PLEASE UPDATE if you are on 5.1.8
|
||||
- Selfhosters only: Fixed marmalade 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] - 20.09.2024
|
||||
|
||||
### Added
|
||||
|
||||
- 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...)
|
||||
|
||||
### Changed
|
||||
|
||||
- `.quote` commands cleaned up and improved
|
||||
- All quote commands now start with `.q<whatever>` and follow the same naming pattern as Expression commands
|
||||
- `.liqu` renamed to `.qli`
|
||||
- `.quotesearch` / `.qse` is now paginated for easier searching
|
||||
- All quote commands now start with `.q<whatever>` and follow the same naming pattern as Expression commands
|
||||
- `.liqu` renamed to `.qli`
|
||||
- `.quotesearch` / `.qse` is now paginated for easier searching
|
||||
- `.whosplaying` 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
|
||||
|
@ -388,7 +142,7 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
|
|||
### Removed
|
||||
|
||||
- Removed mysql support as it didn't work for a while, and requires some special handling/maintenance
|
||||
- Sqlite and Postgres support stays
|
||||
- Sqlite and Postgres support stays
|
||||
|
||||
## [5.1.7] - 09.08.2024
|
||||
|
||||
|
@ -410,18 +164,17 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
|
|||
|
||||
- `.afk` messages can no longer ping, and the response is moved to DMs to avoid abuse
|
||||
- Possible fix for `.remind` timestamp
|
||||
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed old bloat / semi broken / dumb commands
|
||||
- `.memelist` / `.memegen` (too inconvenient to use)
|
||||
- `.activity` (useless owner-only command)
|
||||
- `.rafflecur` (Just use raffle and then award manually instead)
|
||||
- `.rollduel` (we had this command?)
|
||||
- Removed old bloat / semi broken / dumb commands
|
||||
- `.memelist` / `.memegen` (too inconvenient to use)
|
||||
- `.activity` (useless owner-only command)
|
||||
- `.rafflecur` (Just use raffle and then award manually instead)
|
||||
- `.rollduel` (we had this command?)
|
||||
- You can no longer bet on `.connect4`
|
||||
- `.economy` Removed.
|
||||
- Was buggy and didn't really show the real state of the economy.
|
||||
- It might come back improved in the future
|
||||
- Was buggy and didn.t really show the real state of the economy.
|
||||
- It might come back improved in the future
|
||||
- `.mal` Removed. Useless information / semi broken
|
||||
|
||||
## [5.1.5] - 01.08.2024
|
||||
|
@ -429,9 +182,9 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
|
|||
### Added
|
||||
|
||||
- Added: Added a `.afk <msg>?` command which sets an afk message which will trigger whenever someone pings you
|
||||
- Message will when you type a message in any channel that the bot sees, or after 8 hours, whichever comes first
|
||||
- The specified message will be prefixed with "The user is afk: "
|
||||
- The afk message will disappear 30 seconds after being triggered
|
||||
- Message will when you type a message in any channel that the bot sees, or after 8 hours, whichever comes first
|
||||
- The specified message will be prefixed with "The user is afk: "
|
||||
- The afk message will disappear 30 seconds after being triggered
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -439,7 +192,7 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
|
|||
- Updated some bet descriptions to include 'all' 'half' usage instructions
|
||||
- Updated some command strings
|
||||
- dev: Vastly simplified marmalade 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
|
||||
|
||||
|
@ -458,10 +211,8 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
|
|||
- 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)
|
||||
- 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 support for any openai compatible api for the chatterbot feature change:
|
||||
- Changed games.yml to allow input of the apiUrl (needs to be openai compatible) and modelName as a string.
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -503,9 +254,9 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
|
|||
### Added
|
||||
|
||||
- Added `.honeypot` command, which automatically softbans (ban and immediate unban) any user who posts in that channel.
|
||||
- Useful to auto softban bots who spam every channel upon joining
|
||||
- Users who run commands or expressions won't be softbanned.
|
||||
- Users who have ban member permissions are also excluded.
|
||||
- Useful to auto softban bots who spam every channel upon joining
|
||||
- Users who run commands or expressions won't be softbanned.
|
||||
- Users who have ban member permissions are also excluded.
|
||||
|
||||
### Fixed
|
||||
|
||||
|
@ -519,6 +270,7 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
|
|||
- Added support for `gpt-4o` in `data/games.yml`
|
||||
- Added EllieAiToken to `creds.yml`
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- Remind will now show a timestamp tag for durations
|
||||
|
@ -531,8 +283,8 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
|
|||
|
||||
- Fixed xp bg buy button not working, and possibly some other buttons too
|
||||
- Fixed shopbuy %user% placeholders and updated help text
|
||||
- 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.
|
||||
- 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.
|
||||
- Fixed remind not showing correct time
|
||||
|
||||
### Removed
|
||||
|
@ -544,14 +296,12 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
|
|||
|
||||
### Added
|
||||
|
||||
- Added `.setserverbanner` and `.setservericon` commands
|
||||
- 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
|
||||
- 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
|
||||
- Added `'setserverbanner` and `'setservericon` commands
|
||||
- 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
|
||||
- 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:
|
||||
- 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
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -559,7 +309,7 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
|
|||
|
||||
### Fixed
|
||||
|
||||
- `.verbose` will now be respected for expression errors
|
||||
- Using `.pick` will now correctly show the name of the user who picked the currency
|
||||
- Fixed `.h` not working on some commands
|
||||
- `.langset` and `.langsetd` should no longer allow unsupported languages and nonsense to be typed in
|
||||
- `'verbose` will now be respected for expression errors
|
||||
- Using `'pick` will now correctly show the name of the user who picked the currency
|
||||
- Fixed `'h` not working on some commands
|
||||
- `'langset` and `'langsetd` should no longer allow unsupported languages and nonsense to be typed in
|
|
@ -11,7 +11,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||
Dockerfile = Dockerfile
|
||||
ellie-menu.ps1 = ellie-menu.ps1
|
||||
LICENSE = LICENSE
|
||||
migrate.ps1 = migrate.ps1
|
||||
README.md = README.md
|
||||
remove-migrations.ps1 = remove-migrations.ps1
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EllieBot", "src\EllieBot\EllieBot.csproj", "{4D9001F7-B3E8-48FE-97AA-CFD36DA65A64}"
|
||||
|
@ -28,7 +30,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ellie.Marmalade", "src\Elli
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EllieBot.Voice", "src\EllieBot.Voice\EllieBot.Voice.csproj", "{1D93CE3C-80B4-49C7-A9A2-99988920AAEC}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EllieBot.GrpcApiBase", "src\EllieBot.GrpcApiBase\EllieBot.GrpcApiBase.csproj", "{3B71F0BF-AE6C-480C-AB88-FCE23EDC7D91}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EllieBot.GrpcApiBase", "src\EllieBot.GrpcApiBase\EllieBot.GrpcApiBase.csproj", "{3B71F0BF-AE6C-480C-AB88-FCE23EDC7D91}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
|
8
migrate.ps1
Normal file
8
migrate.ps1
Normal file
|
@ -0,0 +1,8 @@
|
|||
if ($args.Length -eq 0) {
|
||||
Write-Host "Please provide a migration name." -ForegroundColor Red
|
||||
}
|
||||
else {
|
||||
$migrationName = $args[0]
|
||||
dotnet ef migrations add $migrationName -o Migrations/Mysql -c SqliteContext -p src/EllieBot/EllieBot.csproj
|
||||
dotnet ef migrations add $migrationName -o Migrations/PostgreSql -c PostgreSqlContext -p src/EllieBot/EllieBot.csproj
|
||||
}
|
|
@ -9,7 +9,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Discord.Net.Core" Version="3.16.0" />
|
||||
<PackageReference Include="Discord.Net.Core" Version="3.15.3" />
|
||||
<PackageReference Include="Serilog" Version="3.1.1" />
|
||||
<PackageReference Include="YamlDotNet" Version="15.1.4" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
syntax = "proto3";
|
||||
|
||||
option csharp_namespace = "EllieBot.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;
|
||||
}
|
|
@ -1,120 +0,0 @@
|
|||
syntax = "proto3";
|
||||
|
||||
option csharp_namespace = "EllieBot.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);
|
||||
|
||||
rpc SetServerExclusion(SetServerExclusionRequest) returns (SetServerExclusionReply);
|
||||
}
|
||||
|
||||
message SetServerExclusionRequest {
|
||||
uint64 guildId = 1;
|
||||
bool serverExcluded = 2;
|
||||
}
|
||||
|
||||
message SetServerExclusionReply {
|
||||
bool success = 1;
|
||||
}
|
||||
|
||||
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;
|
||||
int64 levelPercent = 5;
|
||||
string avatar = 6;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
|
@ -356,5 +356,3 @@ resharper_arrange_redundant_parentheses_highlighting = hint
|
|||
|
||||
# IDE0011: Add braces
|
||||
dotnet_diagnostic.IDE0011.severity = warning
|
||||
|
||||
resharper_arrange_type_member_modifiers_highlighting = hint
|
|
@ -2,7 +2,6 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using EllieBot.Db.Models;
|
||||
using EllieBot.Modules.Administration.Services;
|
||||
|
||||
// ReSharper disable UnusedAutoPropertyAccessor.Global
|
||||
|
||||
|
@ -15,6 +14,7 @@ public abstract class EllieContext : DbContext
|
|||
|
||||
public DbSet<Quote> Quotes { get; set; }
|
||||
public DbSet<Reminder> Reminders { get; set; }
|
||||
public DbSet<SelfAssignedRole> SelfAssignableRoles { get; set; }
|
||||
public DbSet<MusicPlaylist> MusicPlaylists { get; set; }
|
||||
public DbSet<EllieExpression> Expressions { get; set; }
|
||||
public DbSet<CurrencyTransaction> CurrencyTransactions { get; set; }
|
||||
|
@ -62,7 +62,6 @@ public abstract class EllieContext : DbContext
|
|||
public DbSet<ArchivedTodoListModel> TodosArchive { get; set; }
|
||||
public DbSet<HoneypotChannel> HoneyPotChannels { get; set; }
|
||||
|
||||
|
||||
// public DbSet<GuildColors> GuildColors { get; set; }
|
||||
|
||||
|
||||
|
@ -74,133 +73,16 @@ public abstract class EllieContext : DbContext
|
|||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
#region Notify
|
||||
|
||||
modelBuilder.Entity<Notify>(e =>
|
||||
{
|
||||
e.HasAlternateKey(x => new
|
||||
{
|
||||
x.GuildId,
|
||||
Event = x.Type
|
||||
});
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
#region TempRoles
|
||||
|
||||
modelBuilder.Entity<TempRole>(e =>
|
||||
{
|
||||
e.HasAlternateKey(x => new
|
||||
{
|
||||
x.GuildId,
|
||||
x.UserId,
|
||||
x.RoleId
|
||||
});
|
||||
|
||||
e.HasIndex(x => x.ExpiresAt);
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
#region GuildColors
|
||||
|
||||
modelBuilder.Entity<GuildColors>()
|
||||
.HasIndex(x => x.GuildId)
|
||||
.IsUnique(true);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Button Roles
|
||||
|
||||
modelBuilder.Entity<ButtonRole>(br =>
|
||||
{
|
||||
br.HasIndex(x => x.GuildId)
|
||||
.IsUnique(false);
|
||||
|
||||
br.HasAlternateKey(x => new
|
||||
{
|
||||
x.RoleId,
|
||||
x.MessageId,
|
||||
});
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
#region New Sar
|
||||
|
||||
modelBuilder.Entity<SarGroup>(sg =>
|
||||
{
|
||||
sg.HasAlternateKey(x => new
|
||||
{
|
||||
x.GuildId,
|
||||
x.GroupNumber
|
||||
});
|
||||
|
||||
sg.HasMany(x => x.Roles)
|
||||
.WithOne()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Sar>()
|
||||
.HasAlternateKey(x => new
|
||||
{
|
||||
x.GuildId,
|
||||
x.RoleId
|
||||
});
|
||||
|
||||
modelBuilder.Entity<SarAutoDelete>()
|
||||
.HasIndex(x => x.GuildId)
|
||||
.IsUnique();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Rakeback
|
||||
|
||||
modelBuilder.Entity<Rakeback>()
|
||||
.HasKey(x => x.UserId);
|
||||
|
||||
#endregion
|
||||
|
||||
#region UserBetStats
|
||||
|
||||
modelBuilder.Entity<UserBetStats>(ubs =>
|
||||
{
|
||||
ubs.HasIndex(x => new
|
||||
{
|
||||
x.UserId,
|
||||
x.Game
|
||||
})
|
||||
.IsUnique();
|
||||
|
||||
ubs.HasIndex(x => x.MaxWin)
|
||||
.IsUnique(false);
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
#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
|
||||
|
||||
var quoteEntity = modelBuilder.Entity<Quote>();
|
||||
|
@ -379,6 +261,11 @@ public abstract class EllieContext : DbContext
|
|||
.HasForeignKey(x => x.GuildConfigId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<GuildConfig>()
|
||||
.HasMany(x => x.SelfAssignableRoleGroupNames)
|
||||
.WithOne()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<FeedSub>()
|
||||
.HasAlternateKey(x => new
|
||||
{
|
||||
|
@ -407,6 +294,21 @@ public abstract class EllieContext : DbContext
|
|||
|
||||
#endregion
|
||||
|
||||
#region Self Assignable Roles
|
||||
|
||||
var selfassignableRolesEntity = modelBuilder.Entity<SelfAssignedRole>();
|
||||
|
||||
selfassignableRolesEntity.HasIndex(s => new
|
||||
{
|
||||
s.GuildId,
|
||||
s.RoleId
|
||||
})
|
||||
.IsUnique();
|
||||
|
||||
selfassignableRolesEntity.Property(x => x.Group).HasDefaultValue(0);
|
||||
|
||||
#endregion
|
||||
|
||||
#region MusicPlaylists
|
||||
|
||||
var musicPlaylistEntity = modelBuilder.Entity<MusicPlaylist>();
|
||||
|
@ -483,6 +385,7 @@ public abstract class EllieContext : DbContext
|
|||
xps.HasIndex(x => x.UserId);
|
||||
xps.HasIndex(x => x.GuildId);
|
||||
xps.HasIndex(x => x.Xp);
|
||||
xps.HasIndex(x => x.AwardedXp);
|
||||
|
||||
#endregion
|
||||
|
||||
|
@ -588,6 +491,23 @@ public abstract class EllieContext : DbContext
|
|||
|
||||
#endregion
|
||||
|
||||
#region GroupName
|
||||
|
||||
modelBuilder.Entity<GroupName>()
|
||||
.HasIndex(x => new
|
||||
{
|
||||
x.GuildConfigId,
|
||||
x.Number
|
||||
})
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<GroupName>()
|
||||
.HasOne(x => x.GuildConfig)
|
||||
.WithMany(x => x.SelfAssignableRoleGroupNames)
|
||||
.IsRequired();
|
||||
|
||||
#endregion
|
||||
|
||||
#region BanTemplate
|
||||
|
||||
modelBuilder.Entity<BanTemplate>().HasIndex(x => x.GuildId).IsUnique();
|
||||
|
@ -792,7 +712,7 @@ public abstract class EllieContext : DbContext
|
|||
gs
|
||||
.Property(x => x.IsEnabled)
|
||||
.HasDefaultValue(false);
|
||||
|
||||
|
||||
gs
|
||||
.Property(x => x.AutoDeleteTimer)
|
||||
.HasDefaultValue(0);
|
||||
|
|
|
@ -25,6 +25,7 @@ public static class DiscordUserExtensions
|
|||
{
|
||||
UserId = userId,
|
||||
Username = username,
|
||||
Discriminator = discrim,
|
||||
AvatarId = avatarId,
|
||||
TotalXp = 0,
|
||||
CurrencyAmount = 0
|
||||
|
@ -32,6 +33,7 @@ public static class DiscordUserExtensions
|
|||
old => new()
|
||||
{
|
||||
Username = username,
|
||||
Discriminator = discrim,
|
||||
AvatarId = avatarId
|
||||
},
|
||||
() => new()
|
||||
|
@ -47,7 +49,8 @@ public static class DiscordUserExtensions
|
|||
() => new()
|
||||
{
|
||||
UserId = userId,
|
||||
Username = "??Unknown",
|
||||
Username = "Unknown",
|
||||
Discriminator = "????",
|
||||
AvatarId = string.Empty,
|
||||
TotalXp = 0,
|
||||
CurrencyAmount = 0
|
||||
|
|
22
src/EllieBot/Db/Extensions/SelfAssignableRolesExtensions.cs
Normal file
22
src/EllieBot/Db/Extensions/SelfAssignableRolesExtensions.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using EllieBot.Db.Models;
|
||||
|
||||
namespace EllieBot.Db;
|
||||
|
||||
public static class SelfAssignableRolesExtensions
|
||||
{
|
||||
public static bool DeleteByGuildAndRoleId(this DbSet<SelfAssignedRole> roles, ulong guildId, ulong roleId)
|
||||
{
|
||||
var role = roles.FirstOrDefault(s => s.GuildId == guildId && s.RoleId == roleId);
|
||||
|
||||
if (role is null)
|
||||
return false;
|
||||
|
||||
roles.Remove(role);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static IReadOnlyCollection<SelfAssignedRole> GetFromGuild(this DbSet<SelfAssignedRole> roles, ulong guildId)
|
||||
=> roles.AsQueryable().Where(s => s.GuildId == guildId).ToArray();
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using LinqToDB;
|
||||
#nullable disable
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using EllieBot.Db.Models;
|
||||
|
@ -7,9 +8,6 @@ namespace EllieBot.Db;
|
|||
|
||||
public static class UserXpExtensions
|
||||
{
|
||||
public static async Task<UserXpStats?> GetGuildUserXp(this ITable<UserXpStats> table, ulong guildId, ulong userId)
|
||||
=> await table.FirstOrDefaultAsyncLinqToDB(x => x.GuildId == guildId && x.UserId == userId);
|
||||
|
||||
public static UserXpStats GetOrCreateUserXpStats(this DbContext ctx, ulong guildId, ulong userId)
|
||||
{
|
||||
var usr = ctx.Set<UserXpStats>().FirstOrDefault(x => x.UserId == userId && x.GuildId == guildId);
|
||||
|
@ -20,6 +18,7 @@ public static class UserXpExtensions
|
|||
{
|
||||
Xp = 0,
|
||||
UserId = userId,
|
||||
NotifyOnLevelUp = XpNotificationLocation.None,
|
||||
GuildId = guildId
|
||||
});
|
||||
}
|
||||
|
@ -30,21 +29,24 @@ public static class UserXpExtensions
|
|||
public static async Task<List<UserXpStats>> GetTopUserXps(this DbSet<UserXpStats> xps, ulong guildId, int count)
|
||||
=> await xps.ToLinqToDBTable()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.OrderByDescending(x => x.Xp)
|
||||
.OrderByDescending(x => x.Xp + x.AwardedXp)
|
||||
.Take(count)
|
||||
.ToListAsyncLinqToDB();
|
||||
|
||||
public static async Task<int> GetUserGuildRanking(this DbSet<UserXpStats> xps, ulong userId, ulong guildId)
|
||||
=> await xps.ToLinqToDBTable()
|
||||
.Where(x => x.GuildId == guildId
|
||||
&& x.Xp
|
||||
&& x.Xp + x.AwardedXp
|
||||
> xps.AsQueryable()
|
||||
.Where(y => y.UserId == userId && y.GuildId == guildId)
|
||||
.Select(y => y.Xp)
|
||||
.Select(y => y.Xp + y.AwardedXp)
|
||||
.FirstOrDefault())
|
||||
.CountAsyncLinqToDB()
|
||||
+ 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)
|
||||
=> xps.Delete(x => x.GuildId == guildId);
|
||||
|
||||
|
@ -52,6 +54,6 @@ public static class UserXpExtensions
|
|||
=> await userXp
|
||||
.Where(x => x.GuildId == guildId && x.UserId == userId)
|
||||
.FirstOrDefaultAsyncLinqToDB() is UserXpStats uxs
|
||||
? new(uxs.Xp)
|
||||
? new(uxs.Xp + uxs.AwardedXp)
|
||||
: new(0);
|
||||
}
|
|
@ -3,28 +3,38 @@ namespace EllieBot.Db;
|
|||
|
||||
public readonly struct LevelStats
|
||||
{
|
||||
public const int XP_REQUIRED_LVL_1 = 36;
|
||||
|
||||
public long Level { get; }
|
||||
public long LevelXp { get; }
|
||||
public long RequiredXp { get; }
|
||||
public long TotalXp { get; }
|
||||
|
||||
public LevelStats(long totalXp)
|
||||
public LevelStats(long xp)
|
||||
{
|
||||
if (totalXp < 0)
|
||||
totalXp = 0;
|
||||
if (xp < 0)
|
||||
xp = 0;
|
||||
|
||||
TotalXp = totalXp;
|
||||
Level = GetLevelByTotalXp(totalXp);
|
||||
LevelXp = totalXp - GetTotalXpReqForLevel(Level);
|
||||
RequiredXp = (9 * (Level + 1)) + 27;
|
||||
TotalXp = xp;
|
||||
|
||||
const int baseXp = XP_REQUIRED_LVL_1;
|
||||
|
||||
var required = baseXp;
|
||||
var totalXp = 0;
|
||||
var lvl = 1;
|
||||
while (true)
|
||||
{
|
||||
required = (int)(baseXp + (baseXp / 4.0 * (lvl - 1)));
|
||||
|
||||
if (required + totalXp > xp)
|
||||
break;
|
||||
|
||||
totalXp += required;
|
||||
lvl++;
|
||||
}
|
||||
|
||||
Level = lvl - 1;
|
||||
LevelXp = xp - totalXp;
|
||||
RequiredXp = required;
|
||||
}
|
||||
|
||||
public static LevelStats CreateForLevel(long level)
|
||||
=> new(GetTotalXpReqForLevel(level));
|
||||
|
||||
public static long GetTotalXpReqForLevel(long level)
|
||||
=> ((9 * level * level) + (63 * level)) / 2;
|
||||
|
||||
public static long GetLevelByTotalXp(long totalXp)
|
||||
=> (long)((-7.0 / 2) + (1 / 6.0 * Math.Sqrt((8 * totalXp) + 441)));
|
||||
}
|
|
@ -6,7 +6,7 @@ public class DiscordUser : DbEntity
|
|||
{
|
||||
public ulong UserId { get; set; }
|
||||
public string Username { get; set; }
|
||||
// public string Discriminator { get; set; }
|
||||
public string Discriminator { get; set; }
|
||||
public string AvatarId { get; set; }
|
||||
|
||||
public int? ClubId { get; set; }
|
||||
|
@ -26,6 +26,9 @@ public class DiscordUser : DbEntity
|
|||
|
||||
public override string ToString()
|
||||
{
|
||||
return Username;
|
||||
if (string.IsNullOrWhiteSpace(Discriminator) || Discriminator == "0000")
|
||||
return Username;
|
||||
|
||||
return Username + "#" + Discriminator;
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
#nullable disable
|
||||
namespace EllieBot.Db.Models;
|
||||
|
||||
public class FlagTranslateChannel : DbEntity
|
||||
{
|
||||
public ulong GuildId { get; set; }
|
||||
public ulong ChannelId { get; set; }
|
||||
}
|
|
@ -9,8 +9,7 @@ public class FollowedStream : DbEntity
|
|||
Picarto = 3,
|
||||
Youtube = 4,
|
||||
Facebook = 5,
|
||||
Trovo = 6,
|
||||
Kick = 7,
|
||||
Trovo = 6
|
||||
}
|
||||
|
||||
public ulong GuildId { get; set; }
|
||||
|
|
11
src/EllieBot/Db/Models/GroupName.cs
Normal file
11
src/EllieBot/Db/Models/GroupName.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
#nullable disable
|
||||
namespace EllieBot.Db.Models;
|
||||
|
||||
public class GroupName : DbEntity
|
||||
{
|
||||
public int GuildConfigId { get; set; }
|
||||
public GuildConfig GuildConfig { get; set; }
|
||||
|
||||
public int Number { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
|
@ -5,15 +5,14 @@ namespace EllieBot.Db.Models;
|
|||
public class GuildColors
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
public ulong GuildId { get; set; }
|
||||
|
||||
[MaxLength(9)]
|
||||
[Length(0, 9)]
|
||||
public string? OkColor { get; set; }
|
||||
|
||||
[MaxLength(9)]
|
||||
[Length(0, 9)]
|
||||
public string? ErrorColor { get; set; }
|
||||
|
||||
[MaxLength(9)]
|
||||
[Length(0, 9)]
|
||||
public string? PendingColor { get; set; }
|
||||
}
|
|
@ -13,11 +13,28 @@ public class GuildConfig : DbEntity
|
|||
|
||||
public string AutoAssignRoleIds { get; set; }
|
||||
|
||||
//todo FUTURE: DELETE, UNUSED
|
||||
// //greet stuff
|
||||
// public int AutoDeleteGreetMessagesTimer { get; set; } = 30;
|
||||
// public int AutoDeleteByeMessagesTimer { get; set; } = 30;
|
||||
//
|
||||
// public ulong GreetMessageChannelId { get; set; }
|
||||
// public ulong ByeMessageChannelId { get; set; }
|
||||
//
|
||||
// public bool SendDmGreetMessage { get; set; }
|
||||
// public string DmGreetMessageText { get; set; } = "Welcome to the %server% server, %user%!";
|
||||
//
|
||||
// public bool SendChannelGreetMessage { get; set; }
|
||||
// public string ChannelGreetMessageText { get; set; } = "Welcome to the %server% server, %user%!";
|
||||
//
|
||||
// public bool SendChannelByeMessage { get; set; }
|
||||
// public string ChannelByeMessageText { get; set; } = "%user% has left!";
|
||||
// public bool SendBoostMessage { get; set; }
|
||||
// pulic int BoostMessageDeleteAfter { get; set; }
|
||||
|
||||
//self assignable roles
|
||||
public bool ExclusiveSelfAssignedRoles { get; set; }
|
||||
public bool AutoDeleteSelfAssignedRoleMessages { get; set; }
|
||||
|
||||
|
||||
//stream notifications
|
||||
public HashSet<FollowedStream> FollowedStreams { get; set; } = new();
|
||||
|
||||
|
@ -36,37 +53,29 @@ public class GuildConfig : DbEntity
|
|||
public HashSet<FilterChannelId> FilterInvitesChannelIds { get; set; } = new();
|
||||
public HashSet<FilterLinksChannelId> FilterLinksChannelIds { get; set; } = new();
|
||||
|
||||
//public bool FilterLinks { get; set; }
|
||||
//public HashSet<FilterLinksChannelId> FilterLinksChannels { get; set; } = new HashSet<FilterLinksChannelId>();
|
||||
|
||||
public bool FilterWords { get; set; }
|
||||
public HashSet<FilteredWord> FilteredWords { get; set; } = new();
|
||||
public HashSet<FilterWordsChannelId> FilterWordsChannelIds { get; set; } = new();
|
||||
|
||||
// mute
|
||||
public HashSet<MutedUserId> MutedUsers { get; set; } = new();
|
||||
|
||||
public string MuteRoleName { get; set; }
|
||||
|
||||
// chatterbot
|
||||
public bool CleverbotEnabled { get; set; }
|
||||
|
||||
// protection
|
||||
public AntiRaidSetting AntiRaidSetting { get; set; }
|
||||
public AntiSpamSetting AntiSpamSetting { get; set; }
|
||||
public AntiAltSetting AntiAltSetting { get; set; }
|
||||
|
||||
// time
|
||||
public string Locale { get; set; }
|
||||
public string TimeZoneId { get; set; }
|
||||
|
||||
|
||||
// timers
|
||||
public HashSet<UnmuteTimer> UnmuteTimers { get; set; } = new();
|
||||
public HashSet<UnbanTimer> UnbanTimer { get; set; } = new();
|
||||
public HashSet<UnroleTimer> UnroleTimer { get; set; } = new();
|
||||
|
||||
// vcrole
|
||||
public HashSet<VcRoleInfo> VcRoleInfos { get; set; }
|
||||
|
||||
// aliases
|
||||
public HashSet<CommandAlias> CommandAliases { get; set; } = new();
|
||||
public bool WarningsInitialized { get; set; }
|
||||
public HashSet<SlowmodeIgnoredUser> SlowmodeIgnoredUsers { get; set; }
|
||||
|
@ -82,10 +91,15 @@ public class GuildConfig : DbEntity
|
|||
public List<FeedSub> FeedSubs { get; set; } = new();
|
||||
public bool NotifyStreamOffline { get; set; }
|
||||
public bool DeleteStreamOnlineMessage { get; set; }
|
||||
public List<GroupName> SelfAssignableRoleGroupNames { get; set; }
|
||||
public int WarnExpireHours { get; set; }
|
||||
public WarnExpireAction WarnExpireAction { get; set; } = WarnExpireAction.Clear;
|
||||
|
||||
public bool DisableGlobalExpressions { get; set; } = false;
|
||||
|
||||
#region Boost Message
|
||||
|
||||
public bool StickyRoles { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace EllieBot.Db.Models;
|
||||
|
||||
public class Notify
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
public ulong GuildId { get; set; }
|
||||
public ulong ChannelId { get; set; }
|
||||
public NotifyType Type { get; set; }
|
||||
|
||||
[MaxLength(10_000)]
|
||||
public string Message { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public enum NotifyType
|
||||
{
|
||||
LevelUp = 0,
|
||||
Protection = 1, Prot = 1,
|
||||
AddRoleReward = 2,
|
||||
RemoveRoleReward = 3,
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace EllieBot.Db.Models;
|
||||
|
||||
public sealed class SarGroup
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
public int GroupNumber { get; set; }
|
||||
public ulong GuildId { get; set; }
|
||||
public ulong? RoleReq { get; set; }
|
||||
public ICollection<Sar> Roles { get; set; } = [];
|
||||
public bool IsExclusive { get; set; }
|
||||
|
||||
[MaxLength(100)]
|
||||
public string? Name { get; set; }
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace EllieBot.Db.Models;
|
||||
|
||||
public sealed class ButtonRole
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
[MaxLength(200)]
|
||||
public string ButtonId { get; set; } = string.Empty;
|
||||
|
||||
public ulong GuildId { get; set; }
|
||||
public ulong ChannelId { get; set; }
|
||||
public ulong MessageId { get; set; }
|
||||
|
||||
public int Position { get; set; }
|
||||
public ulong RoleId { get; set; }
|
||||
|
||||
[MaxLength(100)]
|
||||
public string Emote { get; set; } = string.Empty;
|
||||
|
||||
[MaxLength(50)]
|
||||
public string Label { get; set; } = string.Empty;
|
||||
|
||||
public bool Exclusive { get; set; }
|
||||
}
|
|
@ -1,24 +1,11 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
#nullable disable
|
||||
namespace EllieBot.Db.Models;
|
||||
|
||||
public sealed class Sar
|
||||
public class SelfAssignedRole : DbEntity
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
public ulong GuildId { get; set; }
|
||||
public ulong RoleId { get; set; }
|
||||
|
||||
public int SarGroupId { get; set; }
|
||||
public int LevelReq { get; set; }
|
||||
}
|
||||
|
||||
public sealed class SarAutoDelete
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
public ulong GuildId { get; set; }
|
||||
public bool IsEnabled { get; set; } = false;
|
||||
public int Group { get; set; }
|
||||
public int LevelRequirement { get; set; }
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
namespace EllieBot.Db.Models;
|
||||
|
||||
public class TempRole
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public ulong GuildId { get; set; }
|
||||
public bool Remove { get; set; }
|
||||
public ulong RoleId { get; set; }
|
||||
public ulong UserId { get; set; }
|
||||
|
||||
public DateTime ExpiresAt { get; set; }
|
||||
}
|
|
@ -1,12 +1,8 @@
|
|||
#nullable disable
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace EllieBot.Db.Models;
|
||||
|
||||
public class PatronUser
|
||||
{
|
||||
// [Key]
|
||||
// public int Id { get; set; }
|
||||
public string UniquePlatformUserId { get; set; }
|
||||
public ulong UserId { get; set; }
|
||||
public int AmountCents { get; set; }
|
||||
|
|
|
@ -6,4 +6,6 @@ public class UserXpStats : DbEntity
|
|||
public ulong UserId { get; set; }
|
||||
public ulong GuildId { get; set; }
|
||||
public long Xp { get; set; }
|
||||
public long AwardedXp { get; set; }
|
||||
public XpNotificationLocation NotifyOnLevelUp { get; set; }
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>true</ImplicitUsings>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
<Version>5.3.3</Version>
|
||||
<Version>5.1.16</Version>
|
||||
|
||||
<!-- Output/build -->
|
||||
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
||||
|
@ -29,7 +29,7 @@
|
|||
</PackageReference>
|
||||
<PackageReference Include="CodeHollow.FeedReader" Version="1.2.6" />
|
||||
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageReference Include="Discord.Net" Version="3.16.0" />
|
||||
<PackageReference Include="Discord.Net" Version="3.15.3" />
|
||||
<PackageReference Include="CoreCLR-NCalc" Version="3.1.246" />
|
||||
<PackageReference Include="Google.Apis.Urlshortener.v1" Version="1.41.1.138" />
|
||||
<PackageReference Include="Google.Apis.YouTube.v3" Version="1.68.0.3414" />
|
||||
|
|
|
@ -5,54 +5,6 @@ namespace EllieBot.Migrations;
|
|||
|
||||
public static class MigrationQueries
|
||||
{
|
||||
public static void MergeAwardedXp(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql("""
|
||||
UPDATE UserXpStats
|
||||
SET Xp = AwardedXp + Xp,
|
||||
AwardedXp = 0
|
||||
WHERE AwardedXp > 0;
|
||||
""");
|
||||
}
|
||||
|
||||
public static void MigrateSar(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
if (migrationBuilder.IsNpgsql())
|
||||
return;
|
||||
|
||||
migrationBuilder.Sql("""
|
||||
INSERT INTO GroupName (Number, GuildConfigId)
|
||||
SELECT DISTINCT "Group", GC.Id
|
||||
FROM SelfAssignableRoles as SAR
|
||||
INNER JOIN GuildConfigs as GC
|
||||
ON SAR.GuildId = GC.GuildId
|
||||
WHERE SAR.GuildId not in (SELECT GuildConfigs.GuildId from GroupName LEFT JOIN GuildConfigs ON GroupName.GuildConfigId = GuildConfigs.Id);
|
||||
|
||||
INSERT INTO SarGroup (Id, GroupNumber, Name, IsExclusive, GuildId)
|
||||
SELECT GN.Id, GN.Number, GN.Name, GC.ExclusiveSelfAssignedRoles, GC.GuildId
|
||||
FROM GroupName as GN
|
||||
INNER JOIN GuildConfigs as GC ON GN.GuildConfigId = GC.Id;
|
||||
|
||||
INSERT INTO Sar (GuildId, RoleId, SarGroupId, LevelReq)
|
||||
SELECT SAR.GuildId, SAR.RoleId, (SELECT Id FROM SarGroup WHERE SG.Number = SarGroup.GroupNumber AND SG.GuildId = SarGroup.GuildId), MIN(SAR.LevelRequirement)
|
||||
FROM SelfAssignableRoles as SAR
|
||||
INNER JOIN (SELECT GuildId, gn.Number FROM GroupName as gn
|
||||
INNER JOIN GuildConfigs as gc ON gn.GuildConfigId =gc.Id
|
||||
) as SG
|
||||
ON SG.GuildId = SAR.GuildId
|
||||
WHERE SG.Number IN (SELECT GroupNumber FROM SarGroup WHERE Sar.GuildId = SarGroup.GuildId)
|
||||
GROUP BY SAR.GuildId, SAR.RoleId;
|
||||
|
||||
INSERT INTO SarAutoDelete (GuildId, IsEnabled)
|
||||
SELECT GuildId, AutoDeleteSelfAssignedRoleMessages FROM GuildConfigs WHERE AutoDeleteSelfAssignedRoleMessages = TRUE;
|
||||
""");
|
||||
}
|
||||
|
||||
public static void UpdateUsernames(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql("UPDATE DiscordUser SET Username = '??' || Username WHERE Discriminator = '????';");
|
||||
}
|
||||
|
||||
public static void MigrateRero(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
if (migrationBuilder.IsSqlite())
|
||||
|
@ -121,7 +73,7 @@ left join guildconfigs on reactionrolemessage.guildconfigid = guildconfigs.id;")
|
|||
DELETE FROM WarningPunishment WHERE GuildConfigId IS NULL OR GuildConfigId NOT IN (SELECT Id FROM GuildConfigs);
|
||||
UPDATE WarningPunishment
|
||||
SET GuildId = (SELECT GuildId FROM GuildConfigs WHERE Id = GuildConfigId);
|
||||
|
||||
|
||||
DELETE FROM WarningPunishment as wp
|
||||
WHERE (wp.Count, wp.GuildConfigId) in (
|
||||
SELECT wp2.Count, wp2.GuildConfigId FROM WarningPunishment as wp2
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,54 +0,0 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace EllieBot.Migrations.PostgreSql
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class nodiscrimandflagtranslate : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder 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);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,48 +0,0 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace EllieBot.Migrations.PostgreSql
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class betstats : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "userbetstats",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
userid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
game = table.Column<int>(type: "integer", nullable: false),
|
||||
wincount = table.Column<long>(type: "bigint", nullable: false),
|
||||
losecount = table.Column<long>(type: "bigint", nullable: false),
|
||||
totalbet = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
paidout = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
maxwin = table.Column<long>(type: "bigint", nullable: false),
|
||||
maxbet = table.Column<long>(type: "bigint", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_userbetstats", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_userbetstats_userid_game",
|
||||
table: "userbetstats",
|
||||
columns: new[] { "userid", "game" },
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "userbetstats");
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,33 +0,0 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace EllieBot.Migrations.PostgreSql
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class rakeback : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "rakeback",
|
||||
columns: table => new
|
||||
{
|
||||
userid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
amount = table.Column<decimal>(type: "numeric", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_rakeback", x => x.userid);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "rakeback");
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,74 +0,0 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace EllieBot.Migrations.PostgreSql
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class btnroles_guildcolors : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "buttonrole",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
buttonid = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
channelid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
messageid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
position = table.Column<int>(type: "integer", nullable: false),
|
||||
roleid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
emote = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
|
||||
label = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
|
||||
exclusive = table.Column<bool>(type: "boolean", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_buttonrole", x => x.id);
|
||||
table.UniqueConstraint("ak_buttonrole_roleid_messageid", x => new { x.roleid, x.messageid });
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "guildcolors",
|
||||
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),
|
||||
okcolor = table.Column<string>(type: "character varying(9)", maxLength: 9, nullable: true),
|
||||
errorcolor = table.Column<string>(type: "character varying(9)", maxLength: 9, nullable: true),
|
||||
pendingcolor = table.Column<string>(type: "character varying(9)", maxLength: 9, nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_guildcolors", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_buttonrole_guildid",
|
||||
table: "buttonrole",
|
||||
column: "guildid");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_guildcolors_guildid",
|
||||
table: "guildcolors",
|
||||
column: "guildid",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "buttonrole");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "guildcolors");
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,153 +0,0 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace EllieBot.Migrations.PostgreSql
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class sarrework : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "groupname");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "selfassignableroles");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "sarautodelete",
|
||||
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),
|
||||
isenabled = table.Column<bool>(type: "boolean", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_sarautodelete", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "sargroup",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
groupnumber = table.Column<int>(type: "integer", nullable: false),
|
||||
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
rolereq = table.Column<decimal>(type: "numeric(20,0)", nullable: true),
|
||||
isexclusive = table.Column<bool>(type: "boolean", nullable: false),
|
||||
name = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true),
|
||||
dateadded = table.Column<DateTime>(type: "timestamp without time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_sargroup", x => x.id);
|
||||
table.UniqueConstraint("ak_sargroup_guildid_groupnumber", x => new { x.guildid, x.groupnumber });
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "sar",
|
||||
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),
|
||||
roleid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
sargroupid = table.Column<int>(type: "integer", nullable: false),
|
||||
levelreq = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_sar", x => x.id);
|
||||
table.UniqueConstraint("ak_sar_guildid_roleid", x => new { x.guildid, x.roleid });
|
||||
table.ForeignKey(
|
||||
name: "fk_sar_sargroup_sargroupid",
|
||||
column: x => x.sargroupid,
|
||||
principalTable: "sargroup",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_sar_sargroupid",
|
||||
table: "sar",
|
||||
column: "sargroupid");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_sarautodelete_guildid",
|
||||
table: "sarautodelete",
|
||||
column: "guildid",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "sar");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "sarautodelete");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "sargroup");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "groupname",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
guildconfigid = table.Column<int>(type: "integer", nullable: false),
|
||||
dateadded = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
|
||||
name = table.Column<string>(type: "text", nullable: true),
|
||||
number = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_groupname", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_groupname_guildconfigs_guildconfigid",
|
||||
column: x => x.guildconfigid,
|
||||
principalTable: "guildconfigs",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "selfassignableroles",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
dateadded = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
|
||||
group = table.Column<int>(type: "integer", nullable: false, defaultValue: 0),
|
||||
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
levelrequirement = table.Column<int>(type: "integer", nullable: false),
|
||||
roleid = table.Column<decimal>(type: "numeric(20,0)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_selfassignableroles", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_groupname_guildconfigid_number",
|
||||
table: "groupname",
|
||||
columns: new[] { "guildconfigid", "number" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_selfassignableroles_guildid_roleid",
|
||||
table: "selfassignableroles",
|
||||
columns: new[] { "guildid", "roleid" },
|
||||
unique: true);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,109 +0,0 @@
|
|||
using System;
|
||||
using EllieBot.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace EllieBot.Migrations.PostgreSql
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class awardedxptemprolenotify : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "ix_userxpstats_awardedxp",
|
||||
table: "userxpstats");
|
||||
|
||||
MigrationQueries.MergeAwardedXp(migrationBuilder);
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "awardedxp",
|
||||
table: "userxpstats");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "notifyonlevelup",
|
||||
table: "userxpstats");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "notify",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
channelid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
type = table.Column<int>(type: "integer", nullable: false),
|
||||
message = table.Column<string>(type: "character varying(10000)", maxLength: 10000, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_notify", x => x.id);
|
||||
table.UniqueConstraint("ak_notify_guildid_type", x => new { x.guildid, x.type });
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "temprole",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
remove = table.Column<bool>(type: "boolean", nullable: false),
|
||||
roleid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
userid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
expiresat = table.Column<DateTime>(type: "timestamp without time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_temprole", x => x.id);
|
||||
table.UniqueConstraint("ak_temprole_guildid_userid_roleid", x => new { x.guildid, x.userid, x.roleid });
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_userbetstats_maxwin",
|
||||
table: "userbetstats",
|
||||
column: "maxwin");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_temprole_expiresat",
|
||||
table: "temprole",
|
||||
column: "expiresat");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "notify");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "temprole");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "ix_userbetstats_maxwin",
|
||||
table: "userbetstats");
|
||||
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "awardedxp",
|
||||
table: "userxpstats",
|
||||
type: "bigint",
|
||||
nullable: false,
|
||||
defaultValue: 0L);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "notifyonlevelup",
|
||||
table: "userxpstats",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_userxpstats_awardedxp",
|
||||
table: "userxpstats",
|
||||
column: "awardedxp");
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,53 +0,0 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace EllieBot.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class nodiscrimandflagtranslate : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder 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);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,47 +0,0 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace EllieBot.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class betstats : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "UserBetStats",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
UserId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
Game = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
WinCount = table.Column<long>(type: "INTEGER", nullable: false),
|
||||
LoseCount = table.Column<long>(type: "INTEGER", nullable: false),
|
||||
TotalBet = table.Column<decimal>(type: "TEXT", nullable: false),
|
||||
PaidOut = table.Column<decimal>(type: "TEXT", nullable: false),
|
||||
MaxWin = table.Column<long>(type: "INTEGER", nullable: false),
|
||||
MaxBet = table.Column<long>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_UserBetStats", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UserBetStats_UserId_Game",
|
||||
table: "UserBetStats",
|
||||
columns: new[] { "UserId", "Game" },
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "UserBetStats");
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,34 +0,0 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace EllieBot.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class rakeback : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Rakeback",
|
||||
columns: table => new
|
||||
{
|
||||
UserId = table.Column<ulong>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Amount = table.Column<decimal>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Rakeback", x => x.UserId);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Rakeback");
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,73 +0,0 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace EllieBot.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class btnroles_guildcolors : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ButtonRole",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
ButtonId = table.Column<string>(type: "TEXT", maxLength: 200, nullable: false),
|
||||
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
MessageId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
Position = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
RoleId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
Emote = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
||||
Label = table.Column<string>(type: "TEXT", maxLength: 50, nullable: false),
|
||||
Exclusive = table.Column<bool>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ButtonRole", x => x.Id);
|
||||
table.UniqueConstraint("AK_ButtonRole_RoleId_MessageId", x => new { x.RoleId, x.MessageId });
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "GuildColors",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
OkColor = table.Column<string>(type: "TEXT", maxLength: 9, nullable: true),
|
||||
ErrorColor = table.Column<string>(type: "TEXT", maxLength: 9, nullable: true),
|
||||
PendingColor = table.Column<string>(type: "TEXT", maxLength: 9, nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_GuildColors", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ButtonRole_GuildId",
|
||||
table: "ButtonRole",
|
||||
column: "GuildId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_GuildColors_GuildId",
|
||||
table: "GuildColors",
|
||||
column: "GuildId",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ButtonRole");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "GuildColors");
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,152 +0,0 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace EllieBot.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class sarrework : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "GroupName");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "SelfAssignableRoles");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SarAutoDelete",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
IsEnabled = table.Column<bool>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_SarAutoDelete", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SarGroup",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
GroupNumber = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
RoleReq = table.Column<ulong>(type: "INTEGER", nullable: true),
|
||||
IsExclusive = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
Name = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
|
||||
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_SarGroup", x => x.Id);
|
||||
table.UniqueConstraint("AK_SarGroup_GuildId_GroupNumber", x => new { x.GuildId, x.GroupNumber });
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Sar",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
RoleId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
SarGroupId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
LevelReq = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Sar", x => x.Id);
|
||||
table.UniqueConstraint("AK_Sar_GuildId_RoleId", x => new { x.GuildId, x.RoleId });
|
||||
table.ForeignKey(
|
||||
name: "FK_Sar_SarGroup_SarGroupId",
|
||||
column: x => x.SarGroupId,
|
||||
principalTable: "SarGroup",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Sar_SarGroupId",
|
||||
table: "Sar",
|
||||
column: "SarGroupId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SarAutoDelete_GuildId",
|
||||
table: "SarAutoDelete",
|
||||
column: "GuildId",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Sar");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "SarAutoDelete");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "SarGroup");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "GroupName",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
GuildConfigId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||
Name = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Number = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_GroupName", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_GroupName_GuildConfigs_GuildConfigId",
|
||||
column: x => x.GuildConfigId,
|
||||
principalTable: "GuildConfigs",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SelfAssignableRoles",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||
Group = table.Column<int>(type: "INTEGER", nullable: false, defaultValue: 0),
|
||||
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
LevelRequirement = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
RoleId = table.Column<ulong>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_SelfAssignableRoles", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_GroupName_GuildConfigId_Number",
|
||||
table: "GroupName",
|
||||
columns: new[] { "GuildConfigId", "Number" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SelfAssignableRoles_GuildId_RoleId",
|
||||
table: "SelfAssignableRoles",
|
||||
columns: new[] { "GuildId", "RoleId" },
|
||||
unique: true);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,107 +0,0 @@
|
|||
using System;
|
||||
using EllieBot.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace EllieBot.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class awardedxptemprolenotify : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_UserXpStats_AwardedXp",
|
||||
table: "UserXpStats");
|
||||
|
||||
MigrationQueries.MergeAwardedXp(migrationBuilder);
|
||||
migrationBuilder.DropColumn(
|
||||
name: "AwardedXp",
|
||||
table: "UserXpStats");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "NotifyOnLevelUp",
|
||||
table: "UserXpStats");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Notify",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
Type = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Message = table.Column<string>(type: "TEXT", maxLength: 10000, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Notify", x => x.Id);
|
||||
table.UniqueConstraint("AK_Notify_GuildId_Type", x => new { x.GuildId, x.Type });
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "TempRole",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
Remove = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
RoleId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
UserId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
ExpiresAt = table.Column<DateTime>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_TempRole", x => x.Id);
|
||||
table.UniqueConstraint("AK_TempRole_GuildId_UserId_RoleId", x => new { x.GuildId, x.UserId, x.RoleId });
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UserBetStats_MaxWin",
|
||||
table: "UserBetStats",
|
||||
column: "MaxWin");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TempRole_ExpiresAt",
|
||||
table: "TempRole",
|
||||
column: "ExpiresAt");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Notify");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "TempRole");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_UserBetStats_MaxWin",
|
||||
table: "UserBetStats");
|
||||
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "AwardedXp",
|
||||
table: "UserXpStats",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0L);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "NotifyOnLevelUp",
|
||||
table: "UserXpStats",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UserXpStats_AwardedXp",
|
||||
table: "UserXpStats",
|
||||
column: "AwardedXp");
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -46,7 +46,7 @@ public partial class Administration : EllieModule<AdministrationService>
|
|||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[BotPerm(GuildPerm.ManageGuild)]
|
||||
public async Task ImageOnlyChannel(ParsedTimespan timespan = null)
|
||||
public async Task ImageOnlyChannel(StoopidTime time = null)
|
||||
{
|
||||
var newValue = await _somethingOnly.ToggleImageOnlyChannelAsync(ctx.Guild.Id, ctx.Channel.Id);
|
||||
if (newValue)
|
||||
|
@ -54,12 +54,12 @@ public partial class Administration : EllieModule<AdministrationService>
|
|||
else
|
||||
await Response().Pending(strs.imageonly_disable).SendAsync();
|
||||
}
|
||||
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[BotPerm(GuildPerm.ManageGuild)]
|
||||
public async Task LinkOnlyChannel(ParsedTimespan timespan = null)
|
||||
public async Task LinkOnlyChannel(StoopidTime time = null)
|
||||
{
|
||||
var newValue = await _somethingOnly.ToggleLinkOnlyChannelAsync(ctx.Guild.Id, ctx.Channel.Id);
|
||||
if (newValue)
|
||||
|
@ -72,10 +72,10 @@ public partial class Administration : EllieModule<AdministrationService>
|
|||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(ChannelPerm.ManageChannels)]
|
||||
[BotPerm(ChannelPerm.ManageChannels)]
|
||||
public async Task Slowmode(ParsedTimespan timespan = null)
|
||||
public async Task Slowmode(StoopidTime time = null)
|
||||
{
|
||||
var seconds = (int?)timespan?.Time.TotalSeconds ?? 0;
|
||||
if (timespan is not null && (timespan.Time < TimeSpan.FromSeconds(0) || timespan.Time > TimeSpan.FromHours(6)))
|
||||
var seconds = (int?)time?.Time.TotalSeconds ?? 0;
|
||||
if (time is not null && (time.Time < TimeSpan.FromSeconds(0) || time.Time > TimeSpan.FromHours(6)))
|
||||
return;
|
||||
|
||||
await ((ITextChannel)ctx.Channel).ModifyAsync(tcp =>
|
||||
|
@ -96,7 +96,7 @@ public partial class Administration : EllieModule<AdministrationService>
|
|||
var guild = (SocketGuild)ctx.Guild;
|
||||
var (enabled, channels) = _service.GetDelMsgOnCmdData(ctx.Guild.Id);
|
||||
|
||||
var embed = CreateEmbed()
|
||||
var embed = _sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.server_delmsgoncmd))
|
||||
.WithDescription(enabled ? "✅" : "❌");
|
||||
|
@ -221,7 +221,7 @@ public partial class Administration : EllieModule<AdministrationService>
|
|||
[BotPerm(GuildPerm.ManageChannels)]
|
||||
public async Task CreaTxtChanl([Leftover] string channelName)
|
||||
{
|
||||
var txtCh = await ctx.Guild.CreateTextChannelAsync(channelName);
|
||||
var txtCh = await ctx.Guild.CreateTextChannelAsync(channelName);
|
||||
await Response().Confirm(strs.createtextchan(Format.Bold(txtCh.Name))).SendAsync();
|
||||
}
|
||||
|
||||
|
@ -298,18 +298,18 @@ public partial class Administration : EllieModule<AdministrationService>
|
|||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(ChannelPerm.ManageMessages)]
|
||||
[BotPerm(ChannelPerm.ManageMessages)]
|
||||
public Task Delete(ulong messageId, ParsedTimespan timespan = null)
|
||||
=> Delete((ITextChannel)ctx.Channel, messageId, timespan);
|
||||
public Task Delete(ulong messageId, StoopidTime time = null)
|
||||
=> Delete((ITextChannel)ctx.Channel, messageId, time);
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Delete(ITextChannel channel, ulong messageId, ParsedTimespan timespan = null)
|
||||
=> await InternalMessageAction(channel, messageId, timespan, msg => msg.DeleteAsync());
|
||||
public async Task Delete(ITextChannel channel, ulong messageId, StoopidTime time = null)
|
||||
=> await InternalMessageAction(channel, messageId, time, msg => msg.DeleteAsync());
|
||||
|
||||
private async Task InternalMessageAction(
|
||||
ITextChannel channel,
|
||||
ulong messageId,
|
||||
ParsedTimespan timespan,
|
||||
StoopidTime time,
|
||||
Func<IMessage, Task> func)
|
||||
{
|
||||
var userPerms = ((SocketGuildUser)ctx.User).GetPermissions(channel);
|
||||
|
@ -334,13 +334,13 @@ public partial class Administration : EllieModule<AdministrationService>
|
|||
return;
|
||||
}
|
||||
|
||||
if (timespan is null)
|
||||
if (time is null)
|
||||
await msg.DeleteAsync();
|
||||
else if (timespan.Time <= TimeSpan.FromDays(7))
|
||||
else if (time.Time <= TimeSpan.FromDays(7))
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(timespan.Time);
|
||||
await Task.Delay(time.Time);
|
||||
await msg.DeleteAsync();
|
||||
});
|
||||
}
|
||||
|
@ -360,11 +360,11 @@ public partial class Administration : EllieModule<AdministrationService>
|
|||
{
|
||||
if (ctx.Channel is not SocketTextChannel stc)
|
||||
return;
|
||||
|
||||
|
||||
await stc.CreateThreadAsync(name, message: ctx.Message.ReferencedMessage);
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
|
||||
|
||||
[Cmd]
|
||||
[BotPerm(ChannelPermission.ManageThreads)]
|
||||
[UserPerm(ChannelPermission.ManageThreads)]
|
||||
|
@ -380,7 +380,7 @@ public partial class Administration : EllieModule<AdministrationService>
|
|||
await Response().Error(strs.not_found).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
await t.DeleteAsync();
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
|
@ -406,7 +406,7 @@ public partial class Administration : EllieModule<AdministrationService>
|
|||
await Response().Confirm(strs.autopublish_disable).SendAsync();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Cmd]
|
||||
[UserPerm(GuildPerm.ManageNicknames)]
|
||||
[BotPerm(GuildPerm.ChangeNickname)]
|
||||
|
@ -450,9 +450,8 @@ public partial class Administration : EllieModule<AdministrationService>
|
|||
public async Task SetServerBanner([Leftover] string img = null)
|
||||
{
|
||||
// Tier2 or higher is required to set a banner.
|
||||
if (ctx.Guild.PremiumTier is PremiumTier.Tier1 or PremiumTier.None)
|
||||
return;
|
||||
|
||||
if (ctx.Guild.PremiumTier is PremiumTier.Tier1 or PremiumTier.None) return;
|
||||
|
||||
var result = await _service.SetServerBannerAsync(ctx.Guild, img);
|
||||
|
||||
switch (result)
|
||||
|
@ -473,7 +472,7 @@ public partial class Administration : EllieModule<AdministrationService>
|
|||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPermission.ManageGuild)]
|
||||
|
|
|
@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore;
|
|||
using EllieBot.Db.Models;
|
||||
using EllieBot.Modules.Administration._common.results;
|
||||
|
||||
namespace EllieBot.Modules.Administration;
|
||||
namespace EllieBot.Modules.Administration.Services;
|
||||
|
||||
public class AdministrationService : IEService
|
||||
{
|
||||
|
|
|
@ -25,13 +25,6 @@ public partial class Administration
|
|||
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);
|
||||
if (roles.Count == 0)
|
||||
await Response().Confirm(strs.aar_disabled).SendAsync();
|
||||
|
|
|
@ -75,7 +75,7 @@ public sealed class CleanupService : ICleanupService, IReadyExecutor, IEService
|
|||
Log.Information("Leaving {RemainingCount} guilds, 1 every second. {DontDeleteCount} will remain",
|
||||
allGuildIds.Length - dontDelete.Count,
|
||||
dontDelete.Count);
|
||||
|
||||
|
||||
foreach (var guildId in allGuildIds)
|
||||
{
|
||||
if (dontDelete.Contains(guildId))
|
||||
|
@ -136,7 +136,7 @@ public sealed class CleanupService : ICleanupService, IReadyExecutor, IEService
|
|||
await using var linqCtx = ctx.CreateLinqToDBContext();
|
||||
await using var tempTable = linqCtx.CreateTempTable<CleanupId>();
|
||||
|
||||
foreach (var chunk in allIds.Chunk(10000))
|
||||
foreach (var chunk in allIds.Chunk(20000))
|
||||
{
|
||||
await tempTable.BulkCopyAsync(chunk.Select(x => new CleanupId()
|
||||
{
|
||||
|
@ -187,6 +187,13 @@ public sealed class CleanupService : ICleanupService, IReadyExecutor, IEService
|
|||
.Contains(x.GuildId))
|
||||
.DeleteAsync();
|
||||
|
||||
// delete ignored users
|
||||
await ctx.GetTable<DiscordPermOverride>()
|
||||
.Where(x => x.GuildId != null
|
||||
&& !tempTable.Select(x => x.GuildId)
|
||||
.Contains(x.GuildId.Value))
|
||||
.DeleteAsync();
|
||||
|
||||
// delete perm overrides
|
||||
await ctx.GetTable<DiscordPermOverride>()
|
||||
.Where(x => x.GuildId != null
|
||||
|
@ -212,54 +219,6 @@ public sealed class CleanupService : ICleanupService, IReadyExecutor, IEService
|
|||
.Contains(x.GuildId))
|
||||
.DeleteAsync();
|
||||
|
||||
// delete sar
|
||||
await ctx.GetTable<SarGroup>()
|
||||
.Where(x => !tempTable.Select(x => x.GuildId)
|
||||
.Contains(x.GuildId))
|
||||
.DeleteAsync();
|
||||
|
||||
// delete warnings
|
||||
await ctx.GetTable<Warning>()
|
||||
.Where(x => !tempTable.Select(x => x.GuildId)
|
||||
.Contains(x.GuildId))
|
||||
.DeleteAsync();
|
||||
|
||||
// delete warn punishments
|
||||
await ctx.GetTable<WarningPunishment>()
|
||||
.Where(x => !tempTable.Select(x => x.GuildId)
|
||||
.Contains(x.GuildId))
|
||||
.DeleteAsync();
|
||||
|
||||
// delete sticky roles
|
||||
await ctx.GetTable<StickyRole>()
|
||||
.Where(x => !tempTable.Select(x => x.GuildId)
|
||||
.Contains(x.GuildId))
|
||||
.DeleteAsync();
|
||||
|
||||
// delete at channels
|
||||
await ctx.GetTable<AutoTranslateChannel>()
|
||||
.Where(x => !tempTable.Select(x => x.GuildId)
|
||||
.Contains(x.GuildId))
|
||||
.DeleteAsync();
|
||||
|
||||
// delete ban templates
|
||||
await ctx.GetTable<BanTemplate>()
|
||||
.Where(x => !tempTable.Select(x => x.GuildId)
|
||||
.Contains(x.GuildId))
|
||||
.DeleteAsync();
|
||||
|
||||
// delete reminders
|
||||
await ctx.GetTable<Reminder>()
|
||||
.Where(x => !tempTable.Select(x => x.GuildId)
|
||||
.Contains(x.ServerId))
|
||||
.DeleteAsync();
|
||||
|
||||
// delete button roles
|
||||
await ctx.GetTable<ButtonRole>()
|
||||
.Where(x => !tempTable.Select(x => x.GuildId)
|
||||
.Contains(x.GuildId))
|
||||
.DeleteAsync();
|
||||
|
||||
return new()
|
||||
{
|
||||
GuildCount = guildIds.Keys.Count,
|
||||
|
|
|
@ -42,9 +42,9 @@ public partial class Administration
|
|||
.Page((items, _) =>
|
||||
{
|
||||
if (!items.Any())
|
||||
return CreateEmbed().WithErrorColor().WithFooter(sql).WithDescription("-");
|
||||
return _sender.CreateEmbed().WithErrorColor().WithFooter(sql).WithDescription("-");
|
||||
|
||||
return CreateEmbed()
|
||||
return _sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithFooter(sql)
|
||||
.WithTitle(string.Join(" ║ ", result.ColumnNames))
|
||||
|
@ -99,7 +99,7 @@ public partial class Administration
|
|||
{
|
||||
try
|
||||
{
|
||||
var embed = CreateEmbed()
|
||||
var embed = _sender.CreateEmbed()
|
||||
.WithTitle(GetText(strs.sql_confirm_exec))
|
||||
.WithDescription(Format.Code(sql));
|
||||
|
||||
|
@ -119,7 +119,7 @@ public partial class Administration
|
|||
[OwnerOnly]
|
||||
public async Task PurgeUser(ulong userId)
|
||||
{
|
||||
var embed = CreateEmbed()
|
||||
var embed = _sender.CreateEmbed()
|
||||
.WithDescription(GetText(strs.purge_user_confirm(Format.Bold(userId.ToString()))));
|
||||
|
||||
if (!await PromptUserConfirmAsync(embed))
|
||||
|
|
|
@ -339,7 +339,7 @@ public class GreetService : IEService, IReadyExecutor
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Unable to send Greet DM. Probably the user has closed DMs");
|
||||
Log.Error(ex, "Error sending greet dm");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -71,7 +71,7 @@ public sealed class HoneyPotService : IHoneyPotService, IReadyExecutor, IExecNoC
|
|||
try
|
||||
{
|
||||
Log.Information("Honeypot caught user {User} [{UserId}]", user, user.Id);
|
||||
await user.BanAsync(pruneDays: 1, reason: "Honeypot");
|
||||
await user.BanAsync(pruneDays: 1);
|
||||
await user.Guild.RemoveBanAsync(user.Id);
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
|
@ -123,7 +123,7 @@ public partial class Administration
|
|||
|
||||
[Cmd]
|
||||
public async Task LanguagesList()
|
||||
=> await Response().Embed(CreateEmbed()
|
||||
=> await Response().Embed(_sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.lang_list))
|
||||
.WithDescription(string.Join("\n",
|
||||
|
|
|
@ -72,18 +72,18 @@ public partial class Administration
|
|||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles | GuildPerm.MuteMembers)]
|
||||
[Priority(1)]
|
||||
public async Task Mute(ParsedTimespan timespan, IGuildUser user, [Leftover] string reason = "")
|
||||
public async Task Mute(StoopidTime time, IGuildUser user, [Leftover] string reason = "")
|
||||
{
|
||||
if (timespan.Time < TimeSpan.FromMinutes(1) || timespan.Time > TimeSpan.FromDays(49))
|
||||
if (time.Time < TimeSpan.FromMinutes(1) || time.Time > TimeSpan.FromDays(49))
|
||||
return;
|
||||
try
|
||||
{
|
||||
if (!await VerifyMutePermissions((IGuildUser)ctx.User, user))
|
||||
return;
|
||||
|
||||
await _service.TimedMute(user, ctx.User, timespan.Time, reason: reason);
|
||||
await _service.TimedMute(user, ctx.User, time.Time, reason: reason);
|
||||
await Response().Confirm(strs.user_muted_time(Format.Bold(user.ToString()),
|
||||
(int)timespan.Time.TotalMinutes)).SendAsync();
|
||||
(int)time.Time.TotalMinutes)).SendAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -133,18 +133,18 @@ public partial class Administration
|
|||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[Priority(1)]
|
||||
public async Task ChatMute(ParsedTimespan timespan, IGuildUser user, [Leftover] string reason = "")
|
||||
public async Task ChatMute(StoopidTime time, IGuildUser user, [Leftover] string reason = "")
|
||||
{
|
||||
if (timespan.Time < TimeSpan.FromMinutes(1) || timespan.Time > TimeSpan.FromDays(49))
|
||||
if (time.Time < TimeSpan.FromMinutes(1) || time.Time > TimeSpan.FromDays(49))
|
||||
return;
|
||||
try
|
||||
{
|
||||
if (!await VerifyMutePermissions((IGuildUser)ctx.User, user))
|
||||
return;
|
||||
|
||||
await _service.TimedMute(user, ctx.User, timespan.Time, MuteType.Chat, reason);
|
||||
await _service.TimedMute(user, ctx.User, time.Time, MuteType.Chat, reason);
|
||||
await Response().Confirm(strs.user_chat_mute_time(Format.Bold(user.ToString()),
|
||||
(int)timespan.Time.TotalMinutes)).SendAsync();
|
||||
(int)time.Time.TotalMinutes)).SendAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -193,18 +193,18 @@ public partial class Administration
|
|||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.MuteMembers)]
|
||||
[Priority(1)]
|
||||
public async Task VoiceMute(ParsedTimespan timespan, IGuildUser user, [Leftover] string reason = "")
|
||||
public async Task VoiceMute(StoopidTime time, IGuildUser user, [Leftover] string reason = "")
|
||||
{
|
||||
if (timespan.Time < TimeSpan.FromMinutes(1) || timespan.Time > TimeSpan.FromDays(49))
|
||||
if (time.Time < TimeSpan.FromMinutes(1) || time.Time > TimeSpan.FromDays(49))
|
||||
return;
|
||||
try
|
||||
{
|
||||
if (!await VerifyMutePermissions((IGuildUser)ctx.User, user))
|
||||
return;
|
||||
|
||||
await _service.TimedMute(user, ctx.User, timespan.Time, MuteType.Voice, reason);
|
||||
await _service.TimedMute(user, ctx.User, time.Time, MuteType.Voice, reason);
|
||||
await Response().Confirm(strs.user_voice_mute_time(Format.Bold(user.ToString()),
|
||||
(int)timespan.Time.TotalMinutes)).SendAsync();
|
||||
(int)time.Time.TotalMinutes)).SendAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
|
@ -122,7 +122,7 @@ public class MuteService : IEService
|
|||
return;
|
||||
|
||||
_ = Task.Run(() => _sender.Response(user)
|
||||
.Embed(_sender.CreateEmbed(user?.GuildId)
|
||||
.Embed(_sender.CreateEmbed()
|
||||
.WithDescription($"You've been muted in {user.Guild} server")
|
||||
.AddField("Mute Type", type.ToString())
|
||||
.AddField("Moderator", mod.ToString())
|
||||
|
@ -140,7 +140,7 @@ public class MuteService : IEService
|
|||
return;
|
||||
|
||||
_ = Task.Run(() => _sender.Response(user)
|
||||
.Embed(_sender.CreateEmbed(user.GuildId)
|
||||
.Embed(_sender.CreateEmbed()
|
||||
.WithDescription($"You've been unmuted in {user.Guild} server")
|
||||
.AddField("Unmute Type", type.ToString())
|
||||
.AddField("Moderator", mod.ToString())
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
using EllieBot.Db.Models;
|
||||
using System.Collections;
|
||||
|
||||
namespace EllieBot.Modules.Administration;
|
||||
|
||||
public interface INotifyModel
|
||||
{
|
||||
static abstract string KeyName { get; }
|
||||
static abstract NotifyType NotifyType { get; }
|
||||
IReadOnlyDictionary<string, Func<SocketGuild, string>> GetReplacements();
|
||||
|
||||
public virtual bool TryGetGuildId(out ulong guildId)
|
||||
{
|
||||
guildId = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual bool TryGetUserId(out ulong userId)
|
||||
{
|
||||
userId = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
namespace EllieBot.Modules.Administration;
|
||||
|
||||
public interface INotifySubscriber
|
||||
{
|
||||
Task NotifyAsync<T>(T data, bool isShardLocal = false)
|
||||
where T : struct, INotifyModel;
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
using EllieBot.Db.Models;
|
||||
using EllieBot.Modules.Administration;
|
||||
|
||||
namespace EllieBot.Modules.Xp.Services;
|
||||
|
||||
public record struct AddRoleRewardNotifyModel(ulong GuildId, ulong RoleId, ulong UserId, long Level) : INotifyModel
|
||||
{
|
||||
public static string KeyName
|
||||
=> "notify.reward.addrole";
|
||||
|
||||
public static NotifyType NotifyType
|
||||
=> NotifyType.AddRoleReward;
|
||||
|
||||
public IReadOnlyDictionary<string, Func<SocketGuild, string>> GetReplacements()
|
||||
{
|
||||
var model = this;
|
||||
return new Dictionary<string, Func<SocketGuild, string>>()
|
||||
{
|
||||
{ "%event.user%", g => g.GetUser(model.UserId)?.ToString() ?? model.UserId.ToString() },
|
||||
{ "%event.role%", g => g.GetRole(model.RoleId)?.ToString() ?? model.RoleId.ToString() },
|
||||
{ "%event.level%", g => model.Level.ToString() }
|
||||
};
|
||||
}
|
||||
|
||||
public bool TryGetUserId(out ulong userId)
|
||||
{
|
||||
userId = UserId;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetGuildId(out ulong guildId)
|
||||
{
|
||||
guildId = GuildId;
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
using EllieBot.Db.Models;
|
||||
|
||||
namespace EllieBot.Modules.Administration;
|
||||
|
||||
public record struct LevelUpNotifyModel(
|
||||
ulong GuildId,
|
||||
ulong ChannelId,
|
||||
ulong UserId,
|
||||
long Level) : INotifyModel
|
||||
{
|
||||
public static string KeyName
|
||||
=> "notify.levelup";
|
||||
|
||||
public static NotifyType NotifyType
|
||||
=> NotifyType.LevelUp;
|
||||
|
||||
public IReadOnlyDictionary<string, Func<SocketGuild, string>> GetReplacements()
|
||||
{
|
||||
var data = this;
|
||||
return new Dictionary<string, Func<SocketGuild, string>>()
|
||||
{
|
||||
{ "%event.level%", g => data.Level.ToString() },
|
||||
{ "%event.user%", g => g.GetUser(data.UserId)?.ToString() ?? data.UserId.ToString() },
|
||||
};
|
||||
}
|
||||
|
||||
public bool TryGetGuildId(out ulong guildId)
|
||||
{
|
||||
guildId = GuildId;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetUserId(out ulong userId)
|
||||
{
|
||||
userId = UserId;
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
#nullable disable
|
||||
using EllieBot.Db.Models;
|
||||
|
||||
namespace EllieBot.Modules.Administration.Services;
|
||||
|
||||
public record struct ProtectionNotifyModel(ulong GuildId, ProtectionType ProtType, ulong UserId) : INotifyModel
|
||||
{
|
||||
public static string KeyName
|
||||
=> "notify.protection";
|
||||
|
||||
public static NotifyType NotifyType
|
||||
=> NotifyType.Protection;
|
||||
|
||||
public IReadOnlyDictionary<string, Func<SocketGuild, string>> GetReplacements()
|
||||
{
|
||||
var data = this;
|
||||
return new Dictionary<string, Func<SocketGuild, string>>()
|
||||
{
|
||||
{ "%event.type%", g => data.ProtType.ToString() },
|
||||
};
|
||||
}
|
||||
|
||||
public bool TryGetUserId(out ulong userId)
|
||||
{
|
||||
userId = UserId;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetGuildId(out ulong guildId)
|
||||
{
|
||||
guildId = GuildId;
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
using EllieBot.Db.Models;
|
||||
using EllieBot.Modules.Administration;
|
||||
|
||||
namespace EllieBot.Modules.Xp.Services;
|
||||
|
||||
public record struct RemoveRoleRewardNotifyModel(ulong GuildId, ulong RoleId, ulong UserId, long Level) : INotifyModel
|
||||
{
|
||||
public static string KeyName
|
||||
=> "notify.reward.removerole";
|
||||
|
||||
public static NotifyType NotifyType
|
||||
=> NotifyType.RemoveRoleReward;
|
||||
|
||||
public IReadOnlyDictionary<string, Func<SocketGuild, string>> GetReplacements()
|
||||
{
|
||||
var model = this;
|
||||
return new Dictionary<string, Func<SocketGuild, string>>()
|
||||
{
|
||||
{ "%event.user%", g => g.GetUser(model.UserId)?.ToString() ?? model.UserId.ToString() },
|
||||
{ "%event.role%", g => g.GetRole(model.RoleId)?.ToString() ?? model.RoleId.ToString() },
|
||||
{ "%event.level%", g => model.Level.ToString() },
|
||||
};
|
||||
}
|
||||
|
||||
public bool TryGetUserId(out ulong userId)
|
||||
{
|
||||
userId = UserId;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetGuildId(out ulong guildId)
|
||||
{
|
||||
guildId = GuildId;
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
using EllieBot.Db.Models;
|
||||
using System.Text;
|
||||
|
||||
namespace EllieBot.Modules.Administration;
|
||||
|
||||
public partial class Administration
|
||||
{
|
||||
public class NotifyCommands : EllieModule<NotifyService>
|
||||
{
|
||||
[Cmd]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task Notify()
|
||||
{
|
||||
await Response()
|
||||
.Paginated()
|
||||
.Items(Enum.GetValues<NotifyType>())
|
||||
.PageSize(5)
|
||||
.Page((items, page) =>
|
||||
{
|
||||
var eb = CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.notify_available));
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
eb.AddField(item.ToString(), GetText(GetDescription(item)), false);
|
||||
}
|
||||
|
||||
return eb;
|
||||
})
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
private LocStr GetDescription(NotifyType item)
|
||||
=> item switch
|
||||
{
|
||||
NotifyType.LevelUp => strs.notify_desc_levelup,
|
||||
NotifyType.Protection => strs.notify_desc_protection,
|
||||
NotifyType.AddRoleReward => strs.notify_desc_addrolerew,
|
||||
NotifyType.RemoveRoleReward => strs.notify_desc_removerolerew,
|
||||
_ => strs.notify_desc_not_found
|
||||
};
|
||||
|
||||
[Cmd]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task Notify(NotifyType nType, [Leftover] string? message = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
{
|
||||
// show msg
|
||||
var conf = await _service.GetNotifyAsync(ctx.Guild.Id, nType);
|
||||
if (conf is null)
|
||||
{
|
||||
await Response().Confirm(strs.notify_msg_not_set).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
var eb = CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.notify_msg))
|
||||
.WithDescription(conf.Message.TrimTo(2048))
|
||||
.AddField(GetText(strs.notify_type), conf.Type.ToString(), true)
|
||||
.AddField(GetText(strs.channel),
|
||||
$"""
|
||||
<#{conf.ChannelId}>
|
||||
`{conf.ChannelId}`
|
||||
""",
|
||||
true);
|
||||
|
||||
await Response().Embed(eb).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
await _service.EnableAsync(ctx.Guild.Id, ctx.Channel.Id, nType, message);
|
||||
await Response().Confirm(strs.notify_on($"<#{ctx.Channel.Id}>", Format.Bold(nType.ToString()))).SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task NotifyList(int page = 1)
|
||||
{
|
||||
if (--page < 0)
|
||||
return;
|
||||
|
||||
var notifs = await _service.GetForGuildAsync(ctx.Guild.Id);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
foreach (var notif in notifs)
|
||||
{
|
||||
sb.AppendLine($"""
|
||||
- **{notif.Type}**
|
||||
<#{notif.ChannelId}> `{notif.ChannelId}`
|
||||
|
||||
""");
|
||||
}
|
||||
|
||||
if (notifs.Count == 0)
|
||||
sb.AppendLine(GetText(strs.notify_none));
|
||||
|
||||
await Response()
|
||||
.Confirm(GetText(strs.notify_list), text: sb.ToString())
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task NotifyClear(NotifyType nType)
|
||||
{
|
||||
await _service.DisableAsync(ctx.Guild.Id, nType);
|
||||
await Response().Confirm(strs.notify_off(nType)).SendAsync();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
namespace EllieBot.Modules.Administration;
|
||||
|
||||
public static class NotifyKeys
|
||||
{
|
||||
public static TypedKey<LevelUpNotifyModel> LevelUp { get; } = new("notify:levelup");
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
namespace EllieBot.Modules.Administration;
|
||||
|
||||
public static class NotifyModelExtensions
|
||||
{
|
||||
public static TypedKey<T> GetTypedKey<T>(this T model)
|
||||
where T : struct, INotifyModel
|
||||
=> new(T.KeyName);
|
||||
}
|
|
@ -1,227 +0,0 @@
|
|||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using EllieBot.Common.ModuleBehaviors;
|
||||
using EllieBot.Db.Models;
|
||||
using EllieBot.Generators;
|
||||
|
||||
namespace EllieBot.Modules.Administration;
|
||||
|
||||
public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService
|
||||
{
|
||||
private readonly DbService _db;
|
||||
private readonly IMessageSenderService _mss;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IBotCreds _creds;
|
||||
private readonly IReplacementService _repSvc;
|
||||
private readonly IPubSub _pubSub;
|
||||
private ConcurrentDictionary<NotifyType, ConcurrentDictionary<ulong, Notify>> _events = new();
|
||||
|
||||
public NotifyService(
|
||||
DbService db,
|
||||
IMessageSenderService mss,
|
||||
DiscordSocketClient client,
|
||||
IBotCreds creds,
|
||||
IReplacementService repSvc,
|
||||
IPubSub pubSub)
|
||||
{
|
||||
_db = db;
|
||||
_mss = mss;
|
||||
_client = client;
|
||||
_creds = creds;
|
||||
_repSvc = repSvc;
|
||||
_pubSub = pubSub;
|
||||
}
|
||||
|
||||
public async Task OnReadyAsync()
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
_events = (await uow.GetTable<Notify>()
|
||||
.Where(x => Linq2DbExpressions.GuildOnShard(x.GuildId,
|
||||
_creds.TotalShards,
|
||||
_client.ShardId))
|
||||
.ToListAsyncLinqToDB())
|
||||
.GroupBy(x => x.Type)
|
||||
.ToDictionary(x => x.Key, x => x.ToDictionary(x => x.GuildId).ToConcurrent())
|
||||
.ToConcurrent();
|
||||
|
||||
|
||||
await SubscribeToEvent<LevelUpNotifyModel>();
|
||||
}
|
||||
|
||||
private async Task SubscribeToEvent<T>()
|
||||
where T : struct, INotifyModel
|
||||
{
|
||||
await _pubSub.Sub(new TypedKey<T>(T.KeyName), async (model) => await OnEvent(model));
|
||||
}
|
||||
|
||||
public async Task NotifyAsync<T>(T data, bool isShardLocal = false)
|
||||
where T : struct, INotifyModel
|
||||
{
|
||||
try
|
||||
{
|
||||
if (isShardLocal)
|
||||
{
|
||||
await OnEvent(data);
|
||||
return;
|
||||
}
|
||||
|
||||
await _pubSub.Pub(data.GetTypedKey(), data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex,
|
||||
"Unknown error occurred while trying to triger {NotifyEvent} for {NotifyModel}",
|
||||
T.KeyName,
|
||||
data);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnEvent<T>(T model)
|
||||
where T : struct, INotifyModel
|
||||
{
|
||||
if (_events.TryGetValue(T.NotifyType, out var subs))
|
||||
{
|
||||
if (model.TryGetGuildId(out var gid))
|
||||
{
|
||||
if (!subs.TryGetValue(gid, out var conf))
|
||||
return;
|
||||
|
||||
await HandleNotifyEvent(conf, model);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var key in subs.Keys.ToArray())
|
||||
{
|
||||
if (subs.TryGetValue(key, out var notif))
|
||||
{
|
||||
try
|
||||
{
|
||||
await HandleNotifyEvent(notif, model);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex,
|
||||
"Error occured while sending notification {NotifyEvent} to guild {GuildId}: {ErrorMessage}",
|
||||
T.NotifyType,
|
||||
key,
|
||||
ex.Message);
|
||||
}
|
||||
|
||||
await Task.Delay(500);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleNotifyEvent(Notify conf, INotifyModel model)
|
||||
{
|
||||
var guild = _client.GetGuild(conf.GuildId);
|
||||
var channel = guild?.GetTextChannel(conf.ChannelId);
|
||||
|
||||
if (guild is null || channel is null)
|
||||
return;
|
||||
|
||||
IUser? user = null;
|
||||
if (model.TryGetUserId(out var userId))
|
||||
{
|
||||
user = guild.GetUser(userId) ?? _client.GetUser(userId);
|
||||
}
|
||||
|
||||
var rctx = new ReplacementContext(guild: guild, channel: channel, user: user);
|
||||
|
||||
var st = SmartText.CreateFrom(conf.Message);
|
||||
foreach (var modelRep in model.GetReplacements())
|
||||
{
|
||||
rctx.WithOverride(modelRep.Key, () => modelRep.Value(guild));
|
||||
}
|
||||
|
||||
st = await _repSvc.ReplaceAsync(st, rctx);
|
||||
if (st is SmartPlainText spt)
|
||||
{
|
||||
await _mss.Response(channel)
|
||||
.Confirm(spt.Text)
|
||||
.SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
await _mss.Response(channel)
|
||||
.Text(st)
|
||||
.Sanitize(false)
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
public async Task EnableAsync(
|
||||
ulong guildId,
|
||||
ulong channelId,
|
||||
NotifyType nType,
|
||||
string message)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
await uow.GetTable<Notify>()
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
ChannelId = channelId,
|
||||
Type = nType,
|
||||
Message = message,
|
||||
},
|
||||
(_) => new()
|
||||
{
|
||||
Message = message,
|
||||
ChannelId = channelId
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
Type = nType
|
||||
});
|
||||
|
||||
var eventDict = _events.GetOrAdd(nType, _ => new());
|
||||
eventDict[guildId] = new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
ChannelId = channelId,
|
||||
Type = nType,
|
||||
Message = message
|
||||
};
|
||||
}
|
||||
|
||||
public async Task DisableAsync(ulong guildId, NotifyType nType)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var deleted = await uow.GetTable<Notify>()
|
||||
.Where(x => x.GuildId == guildId && x.Type == nType)
|
||||
.DeleteAsync();
|
||||
|
||||
if (deleted == 0)
|
||||
return;
|
||||
|
||||
if (!_events.TryGetValue(nType, out var guildsDict))
|
||||
return;
|
||||
|
||||
guildsDict.TryRemove(guildId, out _);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<Notify>> GetForGuildAsync(ulong guildId, int page = 0)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(page);
|
||||
|
||||
await using var ctx = _db.GetDbContext();
|
||||
var list = await ctx.GetTable<Notify>()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.OrderBy(x => x.Type)
|
||||
.Skip(page * 10)
|
||||
.Take(10)
|
||||
.ToListAsyncLinqToDB();
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public async Task<Notify?> GetNotifyAsync(ulong guildId, NotifyType nType)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
return await ctx.GetTable<Notify>()
|
||||
.Where(x => x.GuildId == guildId && x.Type == nType)
|
||||
.FirstOrDefaultAsyncLinqToDB();
|
||||
}
|
||||
}
|
|
@ -36,7 +36,7 @@ public partial class Administration
|
|||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task DiscordPermOverrideReset()
|
||||
{
|
||||
var result = await PromptUserConfirmAsync(CreateEmbed()
|
||||
var result = await PromptUserConfirmAsync(_sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithDescription(GetText(strs.perm_override_all_confirm)));
|
||||
|
||||
|
@ -65,7 +65,7 @@ public partial class Administration
|
|||
.CurrentPage(page)
|
||||
.Page((items, _) =>
|
||||
{
|
||||
var eb = CreateEmbed().WithTitle(GetText(strs.perm_overrides)).WithOkColor();
|
||||
var eb = _sender.CreateEmbed().WithTitle(GetText(strs.perm_overrides)).WithOkColor();
|
||||
|
||||
if (items.Count == 0)
|
||||
eb.WithDescription(GetText(strs.perm_override_page_none));
|
||||
|
|
|
@ -6,7 +6,7 @@ namespace EllieBot.Modules.Administration;
|
|||
public partial class Administration
|
||||
{
|
||||
[Group]
|
||||
public partial class PlayingRotateCommands : EllieModule<IBotActivityService>
|
||||
public partial class PlayingRotateCommands : EllieModule<PlayingRotateService>
|
||||
{
|
||||
[Cmd]
|
||||
[OwnerOnly]
|
||||
|
|
|
@ -5,134 +5,36 @@ using EllieBot.Db.Models;
|
|||
|
||||
namespace EllieBot.Modules.Administration.Services;
|
||||
|
||||
public sealed class BotActivityService : IBotActivityService, IReadyExecutor, IEService
|
||||
public sealed class PlayingRotateService : IEService, IReadyExecutor
|
||||
{
|
||||
private readonly TypedKey<ActivityPubData> _activitySetKey = new("activity.set");
|
||||
|
||||
private readonly IPubSub _pubSub;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly DbService _db;
|
||||
private readonly IReplacementService _rep;
|
||||
private readonly BotConfigService _bss;
|
||||
private readonly SelfService _selfService;
|
||||
private readonly IReplacementService _repService;
|
||||
// private readonly Replacer _rep;
|
||||
private readonly DbService _db;
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
public BotActivityService(
|
||||
IPubSub pubSub,
|
||||
public PlayingRotateService(
|
||||
DiscordSocketClient client,
|
||||
DbService db,
|
||||
IReplacementService rep,
|
||||
BotConfigService bss)
|
||||
BotConfigService bss,
|
||||
IEnumerable<IPlaceholderProvider> phProviders,
|
||||
SelfService selfService,
|
||||
IReplacementService repService)
|
||||
{
|
||||
_pubSub = pubSub;
|
||||
_client = client;
|
||||
_db = db;
|
||||
_rep = rep;
|
||||
_bss = bss;
|
||||
}
|
||||
_selfService = selfService;
|
||||
_repService = repService;
|
||||
_client = client;
|
||||
|
||||
public async Task<string> RemovePlayingAsync(int index)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(index);
|
||||
|
||||
await using var uow = _db.GetDbContext();
|
||||
var toRemove = await uow.Set<RotatingPlayingStatus>()
|
||||
.AsQueryable()
|
||||
.AsNoTracking()
|
||||
.Skip(index)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (toRemove is null)
|
||||
return null;
|
||||
|
||||
uow.Remove(toRemove);
|
||||
await uow.SaveChangesAsync();
|
||||
return toRemove.Status;
|
||||
}
|
||||
|
||||
public async Task AddPlaying(ActivityType activityType, string status)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var toAdd = new RotatingPlayingStatus
|
||||
{
|
||||
Status = status,
|
||||
Type = (EllieBot.Db.DbActivityType)activityType
|
||||
};
|
||||
uow.Add(toAdd);
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public void DisableRotatePlaying()
|
||||
{
|
||||
_bss.ModifyConfig(bs => { bs.RotateStatuses = false; });
|
||||
}
|
||||
|
||||
public bool ToggleRotatePlaying()
|
||||
{
|
||||
var enabled = false;
|
||||
_bss.ModifyConfig(bs => { enabled = bs.RotateStatuses = !bs.RotateStatuses; });
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public IReadOnlyList<RotatingPlayingStatus> GetRotatingStatuses()
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
return uow.Set<RotatingPlayingStatus>().AsNoTracking().ToList();
|
||||
}
|
||||
|
||||
public Task SetActivityAsync(string game, ActivityType? type)
|
||||
=> _pubSub.Pub(_activitySetKey,
|
||||
new()
|
||||
{
|
||||
Name = game,
|
||||
Link = null,
|
||||
Type = type
|
||||
});
|
||||
|
||||
public Task SetStreamAsync(string name, string link)
|
||||
=> _pubSub.Pub(_activitySetKey,
|
||||
new()
|
||||
{
|
||||
Name = name,
|
||||
Link = link,
|
||||
Type = ActivityType.Streaming
|
||||
});
|
||||
|
||||
private sealed class ActivityPubData
|
||||
{
|
||||
public string Name { get; init; }
|
||||
public string Link { get; init; }
|
||||
public ActivityType? Type { get; init; }
|
||||
}
|
||||
|
||||
public async Task OnReadyAsync()
|
||||
{
|
||||
await _pubSub.Sub(_activitySetKey,
|
||||
async data =>
|
||||
{
|
||||
if (_client.ShardId == 0)
|
||||
{
|
||||
DisableRotatePlaying();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (data.Type is { } activityType)
|
||||
{
|
||||
await _client.SetGameAsync(data.Name, data.Link, activityType);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _client.SetCustomStatusAsync(data.Name);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error setting activity");
|
||||
}
|
||||
});
|
||||
|
||||
if (_client.ShardId != 0)
|
||||
return;
|
||||
|
||||
|
||||
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(1));
|
||||
var index = 0;
|
||||
while (await timer.WaitForNextTickAsync())
|
||||
|
@ -155,8 +57,8 @@ public sealed class BotActivityService : IBotActivityService, IReadyExecutor, IE
|
|||
? rotatingStatuses[index = 0]
|
||||
: rotatingStatuses[index++];
|
||||
|
||||
var statusText = await _rep.ReplaceAsync(playingStatus.Status, new(client: _client));
|
||||
await SetActivityAsync(statusText, (ActivityType)playingStatus.Type);
|
||||
var statusText = await _repService.ReplaceAsync(playingStatus.Status, new (client: _client));
|
||||
await _selfService.SetActivityAsync(statusText, (ActivityType)playingStatus.Type);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -164,4 +66,44 @@ public sealed class BotActivityService : IBotActivityService, IReadyExecutor, IE
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> RemovePlayingAsync(int index)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(index);
|
||||
|
||||
await using var uow = _db.GetDbContext();
|
||||
var toRemove = await uow.Set<RotatingPlayingStatus>().AsQueryable().AsNoTracking().Skip(index).FirstOrDefaultAsync();
|
||||
|
||||
if (toRemove is null)
|
||||
return null;
|
||||
|
||||
uow.Remove(toRemove);
|
||||
await uow.SaveChangesAsync();
|
||||
return toRemove.Status;
|
||||
}
|
||||
|
||||
public async Task AddPlaying(ActivityType activityType, string status)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var toAdd = new RotatingPlayingStatus
|
||||
{
|
||||
Status = status,
|
||||
Type = (EllieBot.Db.DbActivityType)activityType
|
||||
};
|
||||
uow.Add(toAdd);
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public bool ToggleRotatePlaying()
|
||||
{
|
||||
var enabled = false;
|
||||
_bss.ModifyConfig(bs => { enabled = bs.RotateStatuses = !bs.RotateStatuses; });
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public IReadOnlyList<RotatingPlayingStatus> GetRotatingStatuses()
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
return uow.Set<RotatingPlayingStatus>().AsNoTracking().ToList();
|
||||
}
|
||||
}
|
|
@ -28,17 +28,17 @@ public partial class Administration
|
|||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task AntiAlt(
|
||||
ParsedTimespan minAge,
|
||||
StoopidTime minAge,
|
||||
PunishmentAction action,
|
||||
[Leftover] ParsedTimespan punishTimespan = null)
|
||||
[Leftover] StoopidTime punishTime = null)
|
||||
{
|
||||
var minAgeMinutes = (int)minAge.Time.TotalMinutes;
|
||||
var punishTimeMinutes = (int?)punishTimespan?.Time.TotalMinutes ?? 0;
|
||||
var punishTimeMinutes = (int?)punishTime?.Time.TotalMinutes ?? 0;
|
||||
|
||||
if (minAgeMinutes < 1 || punishTimeMinutes < 0)
|
||||
return;
|
||||
|
||||
var minutes = (int?)punishTimespan?.Time.TotalMinutes ?? 0;
|
||||
var minutes = (int?)punishTime?.Time.TotalMinutes ?? 0;
|
||||
if (action is PunishmentAction.TimeOut && minutes < 1)
|
||||
minutes = 1;
|
||||
|
||||
|
@ -53,7 +53,7 @@ public partial class Administration
|
|||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task AntiAlt(ParsedTimespan minAge, PunishmentAction action, [Leftover] IRole role)
|
||||
public async Task AntiAlt(StoopidTime minAge, PunishmentAction action, [Leftover] IRole role)
|
||||
{
|
||||
var minAgeMinutes = (int)minAge.Time.TotalMinutes;
|
||||
|
||||
|
@ -86,8 +86,8 @@ public partial class Administration
|
|||
int userThreshold,
|
||||
int seconds,
|
||||
PunishmentAction action,
|
||||
[Leftover] ParsedTimespan punishTimespan)
|
||||
=> InternalAntiRaid(userThreshold, seconds, action, punishTimespan);
|
||||
[Leftover] StoopidTime punishTime)
|
||||
=> InternalAntiRaid(userThreshold, seconds, action, punishTime);
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
|
@ -100,7 +100,7 @@ public partial class Administration
|
|||
int userThreshold,
|
||||
int seconds = 10,
|
||||
PunishmentAction action = PunishmentAction.Mute,
|
||||
ParsedTimespan punishTimespan = null)
|
||||
StoopidTime punishTime = null)
|
||||
{
|
||||
if (action == PunishmentAction.AddRole)
|
||||
{
|
||||
|
@ -120,13 +120,13 @@ public partial class Administration
|
|||
return;
|
||||
}
|
||||
|
||||
if (punishTimespan is not null)
|
||||
if (punishTime is not null)
|
||||
{
|
||||
if (!_service.IsDurationAllowed(action))
|
||||
await Response().Error(strs.prot_cant_use_time).SendAsync();
|
||||
}
|
||||
|
||||
var time = (int?)punishTimespan?.Time.TotalMinutes ?? 0;
|
||||
var time = (int?)punishTime?.Time.TotalMinutes ?? 0;
|
||||
if (time is < 0 or > 60 * 24)
|
||||
return;
|
||||
|
||||
|
@ -170,8 +170,8 @@ public partial class Administration
|
|||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[Priority(1)]
|
||||
public Task AntiSpam(int messageCount, PunishmentAction action, [Leftover] ParsedTimespan punishTimespan)
|
||||
=> InternalAntiSpam(messageCount, action, punishTimespan);
|
||||
public Task AntiSpam(int messageCount, PunishmentAction action, [Leftover] StoopidTime punishTime)
|
||||
=> InternalAntiSpam(messageCount, action, punishTime);
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
|
@ -183,19 +183,19 @@ public partial class Administration
|
|||
private async Task InternalAntiSpam(
|
||||
int messageCount,
|
||||
PunishmentAction action,
|
||||
ParsedTimespan timespanData = null,
|
||||
StoopidTime timeData = null,
|
||||
IRole role = null)
|
||||
{
|
||||
if (messageCount is < 2 or > 10)
|
||||
return;
|
||||
|
||||
if (timespanData is not null)
|
||||
if (timeData is not null)
|
||||
{
|
||||
if (!_service.IsDurationAllowed(action))
|
||||
await Response().Error(strs.prot_cant_use_time).SendAsync();
|
||||
}
|
||||
|
||||
var time = (int?)timespanData?.Time.TotalMinutes ?? 0;
|
||||
var time = (int?)timeData?.Time.TotalMinutes ?? 0;
|
||||
if (time is < 0 or > 60 * 24)
|
||||
return;
|
||||
|
||||
|
@ -241,7 +241,7 @@ public partial class Administration
|
|||
return;
|
||||
}
|
||||
|
||||
var embed = CreateEmbed().WithOkColor().WithTitle(GetText(strs.prot_active));
|
||||
var embed = _sender.CreateEmbed().WithOkColor().WithTitle(GetText(strs.prot_active));
|
||||
|
||||
if (spam is not null)
|
||||
embed.AddField("Anti-Spam", GetAntiSpamString(spam).TrimTo(1024), true);
|
||||
|
|
|
@ -22,7 +22,6 @@ public class ProtectionService : IEService
|
|||
private readonly MuteService _mute;
|
||||
private readonly DbService _db;
|
||||
private readonly UserPunishService _punishService;
|
||||
private readonly INotifySubscriber _notifySub;
|
||||
|
||||
private readonly Channel<PunishQueueItem> _punishUserQueue =
|
||||
Channel.CreateUnbounded<PunishQueueItem>(new()
|
||||
|
@ -36,14 +35,12 @@ public class ProtectionService : IEService
|
|||
IBot bot,
|
||||
MuteService mute,
|
||||
DbService db,
|
||||
UserPunishService punishService,
|
||||
INotifySubscriber notifySub)
|
||||
UserPunishService punishService)
|
||||
{
|
||||
_client = client;
|
||||
_mute = mute;
|
||||
_db = db;
|
||||
_punishService = punishService;
|
||||
_notifySub = notifySub;
|
||||
|
||||
var ids = client.GetGuildIds();
|
||||
using (var uow = db.GetDbContext())
|
||||
|
@ -178,9 +175,6 @@ public class ProtectionService : IEService
|
|||
alts.RoleId,
|
||||
user);
|
||||
|
||||
await _notifySub.NotifyAsync(new ProtectionNotifyModel(user.Guild.Id,
|
||||
ProtectionType.Alting,
|
||||
user.Id));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -200,8 +194,6 @@ public class ProtectionService : IEService
|
|||
var settings = stats.AntiRaidSettings;
|
||||
|
||||
await PunishUsers(settings.Action, ProtectionType.Raiding, settings.PunishDuration, null, users);
|
||||
await _notifySub.NotifyAsync(
|
||||
new ProtectionNotifyModel(user.Guild.Id, ProtectionType.Raiding, users[0].Id));
|
||||
}
|
||||
|
||||
await Task.Delay(1000 * stats.AntiRaidSettings.Seconds);
|
||||
|
@ -254,10 +246,6 @@ public class ProtectionService : IEService
|
|||
settings.MuteTime,
|
||||
settings.RoleId,
|
||||
(IGuildUser)msg.Author);
|
||||
|
||||
await _notifySub.NotifyAsync(new ProtectionNotifyModel(channel.GuildId,
|
||||
ProtectionType.Spamming,
|
||||
msg.Author.Id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -113,7 +113,7 @@ public partial class Administration
|
|||
{
|
||||
await progressMsg.ModifyAsync(props =>
|
||||
{
|
||||
props.Embed = CreateEmbed()
|
||||
props.Embed = _sender.CreateEmbed()
|
||||
.WithPendingColor()
|
||||
.WithDescription(GetText(strs.prune_progress(deleted, total)))
|
||||
.Build();
|
||||
|
|
|
@ -1,302 +0,0 @@
|
|||
using EllieBot.Common.TypeReaders.Models;
|
||||
using EllieBot.Db.Models;
|
||||
using EllieBot.Modules.Administration.Services;
|
||||
using System.Text;
|
||||
using ContextType = Discord.Commands.ContextType;
|
||||
|
||||
namespace EllieBot.Modules.Administration;
|
||||
|
||||
public partial class Administration
|
||||
{
|
||||
[Group("btr")]
|
||||
public partial class ButtonRoleCommands : EllieModule<ButtonRolesService>
|
||||
{
|
||||
private List<ActionRowBuilder> GetActionRows(IReadOnlyList<ButtonRole> roles)
|
||||
{
|
||||
var rows = roles.Select((x, i) => (Index: i, ButtonRole: x))
|
||||
.GroupBy(x => x.Index / 5)
|
||||
.Select(x => x.Select(y => y.ButtonRole))
|
||||
.Select(x =>
|
||||
{
|
||||
var ab = new ActionRowBuilder()
|
||||
.WithComponents(x.Select(y =>
|
||||
{
|
||||
var curRole = ctx.Guild.GetRole(y.RoleId);
|
||||
var label = string.IsNullOrWhiteSpace(y.Label)
|
||||
? curRole?.ToString() ?? "?missing " + y.RoleId
|
||||
: y.Label;
|
||||
|
||||
var btnEmote = EmoteTypeReader.TryParse(y.Emote, out var e)
|
||||
? e
|
||||
: null;
|
||||
|
||||
return new ButtonBuilder()
|
||||
.WithCustomId(y.ButtonId)
|
||||
.WithEmote(btnEmote)
|
||||
.WithLabel(label)
|
||||
.WithStyle(ButtonStyle.Secondary)
|
||||
.Build() as IMessageComponent;
|
||||
})
|
||||
.ToList());
|
||||
|
||||
return ab;
|
||||
})
|
||||
.ToList();
|
||||
return rows;
|
||||
}
|
||||
|
||||
private async Task<MessageLink?> CreateMessageLinkAsync(ulong messageId)
|
||||
{
|
||||
var msg = await ctx.Channel.GetMessageAsync(messageId);
|
||||
if (msg is null)
|
||||
return null;
|
||||
|
||||
return new MessageLink(ctx.Guild, ctx.Channel, msg);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[RequireUserPermission(GuildPerm.ManageRoles)]
|
||||
public async Task BtnRoleAdd(ulong messageId, IEmote emote, [Leftover] IRole role)
|
||||
{
|
||||
var link = await CreateMessageLinkAsync(messageId);
|
||||
|
||||
if (link is null)
|
||||
{
|
||||
await Response().Error(strs.invalid_message_id).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
await BtnRoleAdd(link, emote, role);
|
||||
}
|
||||
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[RequireUserPermission(GuildPerm.ManageRoles)]
|
||||
public async Task BtnRoleAdd(MessageLink link, IEmote emote, [Leftover] IRole role)
|
||||
{
|
||||
if (link.Message is not IUserMessage msg || !msg.IsAuthor(ctx.Client))
|
||||
{
|
||||
await Response().Error(strs.invalid_message_link).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await CheckRoleHierarchy(role))
|
||||
{
|
||||
await Response().Error(strs.hierarchy).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
var success = await _service.AddButtonRole(ctx.Guild.Id, link.Channel.Id, role.Id, link.Message.Id, emote);
|
||||
if (!success)
|
||||
{
|
||||
await Response().Error(strs.btnrole_message_max).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
var roles = await _service.GetButtonRoles(ctx.Guild.Id, link.Message.Id);
|
||||
|
||||
var rows = GetActionRows(roles);
|
||||
|
||||
await msg.ModifyAsync(x => x.Components = new(new ComponentBuilder().WithRows(rows).Build()));
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[RequireUserPermission(GuildPerm.ManageRoles)]
|
||||
public Task BtnRoleRemove(ulong messageId, IRole role)
|
||||
=> BtnRoleRemove(messageId, role.Id);
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[RequireUserPermission(GuildPerm.ManageRoles)]
|
||||
public Task BtnRoleRemove(MessageLink link, IRole role)
|
||||
=> BtnRoleRemove(link.Message.Id, role.Id);
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[RequireUserPermission(GuildPerm.ManageRoles)]
|
||||
public Task BtnRoleRemove(MessageLink link, ulong roleId)
|
||||
=> BtnRoleRemove(link.Message.Id, roleId);
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[RequireUserPermission(GuildPerm.ManageRoles)]
|
||||
public async Task BtnRoleRemove(ulong messageId, ulong roleId)
|
||||
{
|
||||
var removed = await _service.RemoveButtonRole(ctx.Guild.Id, messageId, roleId);
|
||||
if (removed is null)
|
||||
{
|
||||
await Response().Error(strs.btnrole_not_found).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
var roles = await _service.GetButtonRoles(ctx.Guild.Id, messageId);
|
||||
|
||||
var ch = await ctx.Guild.GetTextChannelAsync(removed.ChannelId);
|
||||
|
||||
if (ch is null)
|
||||
{
|
||||
await Response().Error(strs.btnrole_removeall_not_found).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
var msg = await ch.GetMessageAsync(removed.MessageId) as IUserMessage;
|
||||
|
||||
if (msg is null)
|
||||
{
|
||||
await Response().Error(strs.btnrole_removeall_not_found).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
var rows = GetActionRows(roles);
|
||||
await msg.ModifyAsync(x => x.Components = new(new ComponentBuilder().WithRows(rows).Build()));
|
||||
await Response().Confirm(strs.btnrole_removed).SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[RequireUserPermission(GuildPerm.ManageRoles)]
|
||||
public Task BtnRoleRemoveAll(MessageLink link)
|
||||
=> BtnRoleRemoveAll(link.Message.Id);
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[RequireUserPermission(GuildPerm.ManageRoles)]
|
||||
public async Task BtnRoleRemoveAll(ulong messageId)
|
||||
{
|
||||
var succ = await _service.RemoveButtonRoles(ctx.Guild.Id, messageId);
|
||||
|
||||
if (succ.Count == 0)
|
||||
{
|
||||
await Response().Error(strs.btnrole_not_found).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
var info = succ[0];
|
||||
|
||||
var ch = await ctx.Guild.GetTextChannelAsync(info.ChannelId);
|
||||
if (ch is null)
|
||||
{
|
||||
await Response().Pending(strs.btnrole_removeall_not_found).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
var msg = await ch.GetMessageAsync(info.MessageId) as IUserMessage;
|
||||
if (msg is null)
|
||||
{
|
||||
await Response().Pending(strs.btnrole_removeall_not_found).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
await msg.ModifyAsync(x => x.Components = new(new ComponentBuilder().Build()));
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[RequireUserPermission(GuildPerm.ManageRoles)]
|
||||
public async Task BtnRoleList()
|
||||
{
|
||||
var btnRoles = await _service.GetButtonRoles(ctx.Guild.Id, null);
|
||||
|
||||
var groups = btnRoles
|
||||
.GroupBy(x => (x.ChannelId, x.MessageId))
|
||||
.ToList();
|
||||
|
||||
await Response()
|
||||
.Paginated()
|
||||
.Items(groups)
|
||||
.PageSize(1)
|
||||
.AddFooter(false)
|
||||
.Page(async (items, page) =>
|
||||
{
|
||||
var eb = CreateEmbed()
|
||||
.WithOkColor();
|
||||
|
||||
var item = items.FirstOrDefault();
|
||||
if (item == default)
|
||||
{
|
||||
eb.WithPendingColor()
|
||||
.WithDescription(GetText(strs.btnrole_none));
|
||||
|
||||
return eb;
|
||||
}
|
||||
|
||||
var (cid, msgId) = item.Key;
|
||||
|
||||
var str = new StringBuilder();
|
||||
|
||||
var ch = await ctx.Client.GetChannelAsync(cid) as IMessageChannel;
|
||||
|
||||
str.AppendLine($"Channel: {ch?.ToString() ?? cid.ToString()}");
|
||||
str.AppendLine($"Message: {msgId}");
|
||||
|
||||
if (ch is not null)
|
||||
{
|
||||
var msg = await ch.GetMessageAsync(msgId);
|
||||
if (msg is not null)
|
||||
{
|
||||
str.AppendLine(new MessageLink(ctx.Guild, ch, msg).ToString());
|
||||
}
|
||||
}
|
||||
|
||||
str.AppendLine("---");
|
||||
|
||||
foreach (var x in item.AsEnumerable())
|
||||
{
|
||||
var role = ctx.Guild.GetRole(x.RoleId);
|
||||
|
||||
str.AppendLine($"{x.Emote} {(role?.ToString() ?? x.RoleId.ToString())}");
|
||||
}
|
||||
|
||||
eb.WithDescription(str.ToString());
|
||||
|
||||
return eb;
|
||||
})
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[RequireUserPermission(GuildPerm.ManageRoles)]
|
||||
public Task BtnRoleExclusive(MessageLink link, PermissionAction exclusive)
|
||||
=> BtnRoleExclusive(link.Message.Id, exclusive);
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[RequireUserPermission(GuildPerm.ManageRoles)]
|
||||
public async Task BtnRoleExclusive(ulong messageId, PermissionAction exclusive)
|
||||
{
|
||||
var res = await _service.SetExclusiveButtonRoles(ctx.Guild.Id, messageId, exclusive.Value);
|
||||
|
||||
if (!res)
|
||||
{
|
||||
await Response().Error(strs.btnrole_not_found).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
if (exclusive.Value)
|
||||
{
|
||||
await Response().Confirm(strs.btnrole_exclusive).SendAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await Response().Confirm(strs.btnrole_multiple).SendAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,187 +0,0 @@
|
|||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using LinqToDB.SqlQuery;
|
||||
using EllieBot.Common.ModuleBehaviors;
|
||||
using EllieBot.Db.Models;
|
||||
using NCalc;
|
||||
|
||||
namespace EllieBot.Modules.Administration.Services;
|
||||
|
||||
public sealed class ButtonRolesService : IEService, IReadyExecutor
|
||||
{
|
||||
private const string BTN_PREFIX = "n:btnrole:";
|
||||
|
||||
private readonly DbService _db;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IBotCreds _creds;
|
||||
|
||||
public ButtonRolesService(IBotCreds creds, DiscordSocketClient client, DbService db)
|
||||
{
|
||||
_creds = creds;
|
||||
_client = client;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
|
||||
public Task OnReadyAsync()
|
||||
{
|
||||
_client.InteractionCreated += OnInteraction;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task OnInteraction(SocketInteraction inter)
|
||||
{
|
||||
if (inter is not SocketMessageComponent smc)
|
||||
return;
|
||||
|
||||
if (!smc.Data.CustomId.StartsWith(BTN_PREFIX))
|
||||
return;
|
||||
|
||||
await inter.DeferAsync();
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var buttonRole = await uow.GetTable<ButtonRole>()
|
||||
.Where(x => x.ButtonId == smc.Data.CustomId && x.MessageId == smc.Message.Id)
|
||||
.FirstOrDefaultAsyncLinqToDB();
|
||||
|
||||
if (buttonRole is null)
|
||||
return;
|
||||
|
||||
var guild = _client.GetGuild(buttonRole.GuildId);
|
||||
if (guild is null)
|
||||
return;
|
||||
|
||||
var role = guild.GetRole(buttonRole.RoleId);
|
||||
if (role is null)
|
||||
return;
|
||||
|
||||
if (smc.User is not IGuildUser user)
|
||||
return;
|
||||
|
||||
if (user.GetRoles().Any(x => x.Id == role.Id))
|
||||
{
|
||||
await user.RemoveRoleAsync(role.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (buttonRole.Exclusive)
|
||||
{
|
||||
var otherRoles = await uow.GetTable<ButtonRole>()
|
||||
.Where(x => x.GuildId == smc.GuildId && x.MessageId == smc.Message.Id)
|
||||
.Select(x => x.RoleId)
|
||||
.ToListAsyncLinqToDB();
|
||||
|
||||
await user.RemoveRolesAsync(otherRoles);
|
||||
}
|
||||
|
||||
await user.AddRoleAsync(role.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Unable to handle button role interaction for user {UserId}", inter.User.Id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<bool> AddButtonRole(
|
||||
ulong guildId,
|
||||
ulong channelId,
|
||||
ulong roleId,
|
||||
ulong messageId,
|
||||
IEmote emote
|
||||
)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
|
||||
// up to 25 per message
|
||||
if (await uow.GetTable<ButtonRole>()
|
||||
.Where(x => x.MessageId == messageId)
|
||||
.CountAsyncLinqToDB()
|
||||
>= 25)
|
||||
return false;
|
||||
|
||||
|
||||
var emoteStr = emote.ToString()!;
|
||||
var guid = Guid.NewGuid();
|
||||
await uow.GetTable<ButtonRole>()
|
||||
.InsertOrUpdateAsync(() => new ButtonRole()
|
||||
{
|
||||
GuildId = guildId,
|
||||
ChannelId = channelId,
|
||||
RoleId = roleId,
|
||||
MessageId = messageId,
|
||||
Position =
|
||||
uow
|
||||
.GetTable<ButtonRole>()
|
||||
.Any(x => x.MessageId == messageId)
|
||||
? uow.GetTable<ButtonRole>()
|
||||
.Where(x => x.MessageId == messageId)
|
||||
.Max(x => x.Position)
|
||||
: 1,
|
||||
Emote = emoteStr,
|
||||
Label = string.Empty,
|
||||
ButtonId = $"{BTN_PREFIX}:{guildId}:{guid}",
|
||||
Exclusive = uow.GetTable<ButtonRole>()
|
||||
.Any(x => x.GuildId == guildId && x.MessageId == messageId)
|
||||
&& uow.GetTable<ButtonRole>()
|
||||
.Where(x => x.GuildId == guildId && x.MessageId == messageId)
|
||||
.All(x => x.Exclusive)
|
||||
},
|
||||
_ => new()
|
||||
{
|
||||
Emote = emoteStr,
|
||||
Label = string.Empty,
|
||||
ButtonId = $"{BTN_PREFIX}:{guildId}:{guid}"
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
RoleId = roleId,
|
||||
MessageId = messageId,
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<ButtonRole>> RemoveButtonRoles(ulong guildId, ulong messageId)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
return await uow.GetTable<ButtonRole>()
|
||||
.Where(x => x.GuildId == guildId && x.MessageId == messageId)
|
||||
.DeleteWithOutputAsync();
|
||||
}
|
||||
|
||||
public async Task<ButtonRole?> RemoveButtonRole(ulong guildId, ulong messageId, ulong roleId)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var deleted = await uow.GetTable<ButtonRole>()
|
||||
.Where(x => x.GuildId == guildId && x.MessageId == messageId && x.RoleId == roleId)
|
||||
.DeleteWithOutputAsync();
|
||||
|
||||
return deleted.FirstOrDefault();
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<ButtonRole>> GetButtonRoles(ulong guildId, ulong? messageId)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
return await uow.GetTable<ButtonRole>()
|
||||
.Where(x => x.GuildId == guildId && (messageId == null || x.MessageId == messageId))
|
||||
.OrderBy(x => x.Id)
|
||||
.ToListAsyncLinqToDB();
|
||||
}
|
||||
|
||||
public async Task<bool> SetExclusiveButtonRoles(ulong guildId, ulong messageId, bool exclusive)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
return await uow.GetTable<ButtonRole>()
|
||||
.Where(x => x.GuildId == guildId && x.MessageId == messageId)
|
||||
.UpdateAsync((_) => new()
|
||||
{
|
||||
Exclusive = exclusive
|
||||
}) > 0;
|
||||
}
|
||||
}
|
|
@ -80,7 +80,7 @@ public partial class Administration
|
|||
.CurrentPage(page)
|
||||
.Page((items, _) =>
|
||||
{
|
||||
var embed = CreateEmbed()
|
||||
var embed = _sender.CreateEmbed()
|
||||
.WithOkColor();
|
||||
|
||||
var content = string.Empty;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
using LinqToDB;
|
||||
#nullable disable
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using EllieBot.Common.ModuleBehaviors;
|
||||
using EllieBot.Modules.Patronage;
|
||||
using EllieBot.Db.Models;
|
||||
using OneOf.Types;
|
||||
using OneOf;
|
||||
|
@ -16,15 +18,18 @@ public sealed class ReactionRolesService : IReadyExecutor, IEService, IReactionR
|
|||
private ConcurrentDictionary<ulong, List<ReactionRoleV2>> _cache;
|
||||
private readonly object _cacheLock = new();
|
||||
private readonly SemaphoreSlim _assignementLock = new(1, 1);
|
||||
private readonly IPatronageService _ps;
|
||||
|
||||
public ReactionRolesService(
|
||||
DiscordSocketClient client,
|
||||
IPatronageService ps,
|
||||
DbService db,
|
||||
IBotCreds creds)
|
||||
{
|
||||
_db = db;
|
||||
_client = client;
|
||||
_creds = creds;
|
||||
_ps = ps;
|
||||
_cache = new();
|
||||
}
|
||||
|
||||
|
@ -52,7 +57,7 @@ public sealed class ReactionRolesService : IReadyExecutor, IEService, IReactionR
|
|||
var guild = _client.GetGuild(rero.GuildId);
|
||||
var role = guild?.GetRole(rero.RoleId);
|
||||
|
||||
if (guild is null || role is null)
|
||||
if (role is null)
|
||||
return default;
|
||||
|
||||
var user = guild.GetUser(userId) as IGuildUser
|
||||
|
@ -75,8 +80,8 @@ public sealed class ReactionRolesService : IReadyExecutor, IEService, IReactionR
|
|||
_ = Task.Run(async () =>
|
||||
{
|
||||
var emote = await GetFixedEmoteAsync(cmsg, r.Emote);
|
||||
|
||||
var rero = reros.FirstOrDefault(x => x.Emote == emote.Name
|
||||
|
||||
var rero = reros.FirstOrDefault(x => x.Emote == emote.Name
|
||||
|| x.Emote == emote.ToString());
|
||||
if (rero is null)
|
||||
return;
|
||||
|
@ -91,11 +96,10 @@ public sealed class ReactionRolesService : IReadyExecutor, IEService, IReactionR
|
|||
{
|
||||
if (user.RoleIds.Contains(role.Id))
|
||||
{
|
||||
await user.RemoveRoleAsync(role.Id,
|
||||
new RequestOptions()
|
||||
{
|
||||
AuditLogReason = $"Reaction role"
|
||||
});
|
||||
await user.RemoveRoleAsync(role.Id, new RequestOptions()
|
||||
{
|
||||
AuditLogReason = $"Reaction role"
|
||||
});
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
@ -107,7 +111,7 @@ public sealed class ReactionRolesService : IReadyExecutor, IEService, IReactionR
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// had to add this because for some reason, reactionremoved event's reaction doesn't have IsAnimated set,
|
||||
// causing the .ToString() to be wrong on animated custom emotes
|
||||
private async Task<IEmote> GetFixedEmoteAsync(
|
||||
|
@ -206,11 +210,10 @@ public sealed class ReactionRolesService : IReadyExecutor, IEService, IReactionR
|
|||
}
|
||||
}
|
||||
|
||||
await user.AddRoleAsync(role.Id,
|
||||
new()
|
||||
{
|
||||
AuditLogReason = "Reaction role"
|
||||
});
|
||||
await user.AddRoleAsync(role.Id, new()
|
||||
{
|
||||
AuditLogReason = "Reaction role"
|
||||
});
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
@ -241,23 +244,36 @@ public sealed class ReactionRolesService : IReadyExecutor, IEService, IReactionR
|
|||
int levelReq = 0)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(group);
|
||||
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(levelReq);
|
||||
|
||||
await using var ctx = _db.GetDbContext();
|
||||
|
||||
await using var tran = await ctx.Database.BeginTransactionAsync();
|
||||
var activeReactionRoles = await ctx.GetTable<ReactionRoleV2>()
|
||||
.Where(x => x.GuildId == guild.Id)
|
||||
.CountAsync();
|
||||
|
||||
var limit = await _ps.GetUserLimit(LimitedFeatureName.ReactionRole, guild.OwnerId);
|
||||
|
||||
if (!_creds.IsOwner(guild.OwnerId) && (activeReactionRoles >= limit.Quota && limit.Quota >= 0))
|
||||
{
|
||||
return new Error();
|
||||
}
|
||||
|
||||
await ctx.GetTable<ReactionRoleV2>()
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
GuildId = guild.Id,
|
||||
ChannelId = msg.Channel.Id,
|
||||
{
|
||||
GuildId = guild.Id,
|
||||
ChannelId = msg.Channel.Id,
|
||||
|
||||
MessageId = msg.Id,
|
||||
Emote = emote,
|
||||
MessageId = msg.Id,
|
||||
Emote = emote,
|
||||
|
||||
RoleId = role.Id,
|
||||
Group = group,
|
||||
LevelReq = levelReq
|
||||
},
|
||||
RoleId = role.Id,
|
||||
Group = group,
|
||||
LevelReq = levelReq
|
||||
},
|
||||
(old) => new()
|
||||
{
|
||||
RoleId = role.Id,
|
||||
|
@ -270,6 +286,8 @@ public sealed class ReactionRolesService : IReadyExecutor, IEService, IReactionR
|
|||
Emote = emote,
|
||||
});
|
||||
|
||||
await tran.CommitAsync();
|
||||
|
||||
var obj = new ReactionRoleV2()
|
||||
{
|
||||
GuildId = guild.Id,
|
||||
|
@ -362,9 +380,9 @@ public sealed class ReactionRolesService : IReadyExecutor, IEService, IReactionR
|
|||
var updated = ctx.GetTable<ReactionRoleV2>()
|
||||
.Where(x => x.GuildId == guildId && x.MessageId == fromMessageId)
|
||||
.UpdateWithOutput(old => new()
|
||||
{
|
||||
MessageId = toMessageId
|
||||
},
|
||||
{
|
||||
MessageId = toMessageId
|
||||
},
|
||||
(old, neu) => neu);
|
||||
lock (_cacheLock)
|
||||
{
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
#nullable disable
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using EllieBot.Common.TypeReaders.Models;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Color = SixLabors.ImageSharp.Color;
|
||||
|
||||
|
@ -15,18 +13,13 @@ public partial class Administration
|
|||
Excl
|
||||
}
|
||||
|
||||
private readonly TempRoleService _tempRoleService;
|
||||
private readonly IServiceProvider _services;
|
||||
private StickyRolesService _stickyRoleSvc;
|
||||
|
||||
public RoleCommands(
|
||||
IServiceProvider services,
|
||||
StickyRolesService stickyRoleSvc,
|
||||
TempRoleService tempRoleService)
|
||||
public RoleCommands(IServiceProvider services, StickyRolesService stickyRoleSvc)
|
||||
{
|
||||
_services = services;
|
||||
_stickyRoleSvc = stickyRoleSvc;
|
||||
_tempRoleService = tempRoleService;
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
|
@ -41,16 +34,13 @@ public partial class Administration
|
|||
return;
|
||||
try
|
||||
{
|
||||
await targetUser.AddRoleAsync(roleToAdd,
|
||||
new RequestOptions()
|
||||
{
|
||||
AuditLogReason = $"Added by [{ctx.User.Username}]"
|
||||
});
|
||||
await targetUser.AddRoleAsync(roleToAdd, new RequestOptions()
|
||||
{
|
||||
AuditLogReason = $"Added by [{ctx.User.Username}]"
|
||||
});
|
||||
|
||||
await Response()
|
||||
.Confirm(strs.setrole(Format.Bold(roleToAdd.Name),
|
||||
Format.Bold(targetUser.ToString())))
|
||||
.SendAsync();
|
||||
await Response().Confirm(strs.setrole(Format.Bold(roleToAdd.Name),
|
||||
Format.Bold(targetUser.ToString()))).SendAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -72,10 +62,8 @@ public partial class Administration
|
|||
try
|
||||
{
|
||||
await targetUser.RemoveRoleAsync(roleToRemove);
|
||||
await Response()
|
||||
.Confirm(strs.remrole(Format.Bold(roleToRemove.Name),
|
||||
Format.Bold(targetUser.ToString())))
|
||||
.SendAsync();
|
||||
await Response().Confirm(strs.remrole(Format.Bold(roleToRemove.Name),
|
||||
Format.Bold(targetUser.ToString()))).SendAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
@ -186,11 +174,12 @@ public partial class Administration
|
|||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[Priority(0)]
|
||||
public async Task RoleColor(Rgba32 color, [Leftover] IRole role)
|
||||
public async Task RoleColor(Color color, [Leftover] IRole role)
|
||||
{
|
||||
try
|
||||
{
|
||||
await role.ModifyAsync(r => r.Color = new Discord.Color(color.R, color.G, color.B));
|
||||
var rgba32 = color.ToPixel<Rgba32>();
|
||||
await role.ModifyAsync(r => r.Color = new Discord.Color(rgba32.R, rgba32.G, rgba32.B));
|
||||
await Response().Confirm(strs.rc(Format.Bold(role.Name))).SendAsync();
|
||||
}
|
||||
catch (Exception)
|
||||
|
@ -216,29 +205,5 @@ public partial class Administration
|
|||
await Response().Confirm(strs.sticky_roles_disabled).SendAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
public async Task TempRole(ParsedTimespan timespan, IUser user, [Leftover] IRole role)
|
||||
{
|
||||
if (!await CheckRoleHierarchy(role))
|
||||
{
|
||||
await Response()
|
||||
.Error(strs.hierarchy)
|
||||
.SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
await _tempRoleService.AddTempRoleAsync(ctx.Guild.Id, role.Id, user.Id, timespan.Time);
|
||||
|
||||
|
||||
await Response()
|
||||
.Confirm(strs.temp_role_added(user.Mention,
|
||||
Format.Bold(role.Name),
|
||||
TimestampTag.FromDateTime(DateTime.UtcNow.Add(timespan.Time), TimestampTagStyles.Relative)))
|
||||
.SendAsync();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,140 +0,0 @@
|
|||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using EllieBot.Common.ModuleBehaviors;
|
||||
using EllieBot.Db.Models;
|
||||
|
||||
namespace EllieBot.Modules.Administration;
|
||||
|
||||
public class TempRoleService : IReadyExecutor, IEService
|
||||
{
|
||||
private readonly DbService _db;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IBotCreds _creds;
|
||||
|
||||
private TaskCompletionSource<bool> _tcs = new();
|
||||
|
||||
public TempRoleService(
|
||||
DbService db,
|
||||
DiscordSocketClient client,
|
||||
IBotCreds creds)
|
||||
{
|
||||
_db = db;
|
||||
_client = client;
|
||||
_creds = creds;
|
||||
}
|
||||
|
||||
public async Task AddTempRoleAsync(
|
||||
ulong guildId,
|
||||
ulong roleId,
|
||||
ulong userId,
|
||||
TimeSpan duration)
|
||||
{
|
||||
if (duration == TimeSpan.Zero)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
await uow.GetTable<TempRole>()
|
||||
.Where(x => x.GuildId == guildId && x.UserId == userId)
|
||||
.DeleteAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
var until = DateTime.UtcNow.Add(duration);
|
||||
await using var ctx = _db.GetDbContext();
|
||||
await ctx.GetTable<TempRole>()
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
RoleId = roleId,
|
||||
UserId = userId,
|
||||
Remove = false,
|
||||
ExpiresAt = until
|
||||
},
|
||||
(old) => new()
|
||||
{
|
||||
ExpiresAt = until,
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
UserId = userId,
|
||||
RoleId = roleId
|
||||
});
|
||||
|
||||
_tcs.TrySetResult(true);
|
||||
}
|
||||
|
||||
public async Task OnReadyAsync()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
_tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var latest = await _db.GetDbContext()
|
||||
.GetTable<TempRole>()
|
||||
.Where(x => Linq2DbExpressions.GuildOnShard(x.GuildId,
|
||||
_creds.TotalShards,
|
||||
_client.ShardId))
|
||||
.OrderBy(x => x.ExpiresAt)
|
||||
.FirstOrDefaultAsyncLinqToDB();
|
||||
|
||||
if (latest == default)
|
||||
{
|
||||
await _tcs.Task;
|
||||
continue;
|
||||
}
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
if (latest.ExpiresAt > now)
|
||||
{
|
||||
await Task.WhenAny(Task.Delay(latest.ExpiresAt - now), _tcs.Task);
|
||||
continue;
|
||||
}
|
||||
|
||||
var deleted = await _db.GetDbContext()
|
||||
.GetTable<TempRole>()
|
||||
.Where(x => Linq2DbExpressions.GuildOnShard(x.GuildId,
|
||||
_creds.TotalShards,
|
||||
_client.ShardId)
|
||||
&& x.ExpiresAt <= now)
|
||||
.DeleteWithOutputAsync();
|
||||
|
||||
foreach (var d in deleted)
|
||||
{
|
||||
try
|
||||
{
|
||||
await RemoveRole(d);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Log.Warning("Unable to remove temp role {RoleId} from user {UserId}",
|
||||
d.RoleId,
|
||||
d.UserId);
|
||||
}
|
||||
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Unexpected error occurred in temprole loop");
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RemoveRole(TempRole tempRole)
|
||||
{
|
||||
var guild = _client.GetGuild(tempRole.GuildId);
|
||||
|
||||
var role = guild?.GetRole(tempRole.RoleId);
|
||||
if (role is null)
|
||||
return;
|
||||
|
||||
var user = guild?.GetUser(tempRole.UserId);
|
||||
if (user is null)
|
||||
return;
|
||||
|
||||
await user.RemoveRoleAsync(role);
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@ public sealed class CheckForUpdatesService : IEService, IReadyExecutor
|
|||
private readonly IMessageSenderService _sender;
|
||||
|
||||
|
||||
private const string RELEASES_URL = "https://toastielab.dev/api/v1/repos/EllieBotDevs/elliebot/releases";
|
||||
private const string RELEASES_URL = "https://toastielab.dev/api/v1/repos/Emotions-stuff/elliebot/releases";
|
||||
|
||||
public CheckForUpdatesService(
|
||||
BotConfigService bcs,
|
||||
|
@ -72,7 +72,7 @@ public sealed class CheckForUpdatesService : IEService, IReadyExecutor
|
|||
UpdateLastKnownVersion(latestVersion);
|
||||
|
||||
// pull changelog
|
||||
var changelog = await http.GetStringAsync("https://toastielab.dev/EllieBotDevs/elliebot/raw/branch/v5/CHANGELOG.md");
|
||||
var changelog = await http.GetStringAsync("https://toastielab.dev/Emotions-stuff/elliebot/raw/branch/v5/CHANGELOG.md");
|
||||
|
||||
var thisVersionChangelog = GetVersionChangelog(latestVersion, changelog);
|
||||
|
||||
|
@ -95,7 +95,7 @@ public sealed class CheckForUpdatesService : IEService, IReadyExecutor
|
|||
.WithOkColor()
|
||||
.WithAuthor($"EllieBot v{latest} Released!")
|
||||
.WithTitle("Changelog")
|
||||
.WithUrl("https://toastielab.dev/EllieBotDevs/elliebot/src/branch/v5/CHANGELOG.md")
|
||||
.WithUrl("https://toastielab.dev/Emotions-stuff/elliebot/src/branch/v5/CHANGELOG.md")
|
||||
.WithDescription(thisVersionChangelog.TrimTo(4096))
|
||||
.WithFooter(
|
||||
"You may disable these messages by typing '.conf bot checkforupdates false'");
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
#nullable disable
|
||||
using EllieBot.Db.Models;
|
||||
|
||||
namespace EllieBot.Modules.Administration.Services;
|
||||
|
||||
public interface IBotActivityService
|
||||
{
|
||||
Task SetActivityAsync(string game, ActivityType? type);
|
||||
Task SetStreamAsync(string name, string link);
|
||||
bool ToggleRotatePlaying();
|
||||
Task AddPlaying(ActivityType statusType, string status);
|
||||
Task<string> RemovePlayingAsync(int index);
|
||||
IReadOnlyList<RotatingPlayingStatus> GetRotatingStatuses();
|
||||
}
|
|
@ -24,22 +24,19 @@ public partial class Administration
|
|||
private readonly IMarmaladeLoaderService _marmaladeLoader;
|
||||
private readonly ICoordinator _coord;
|
||||
private readonly DbService _db;
|
||||
private readonly IBotActivityService _bas;
|
||||
|
||||
public SelfCommands(
|
||||
DiscordSocketClient client,
|
||||
DbService db,
|
||||
IBotStrings strings,
|
||||
ICoordinator coord,
|
||||
IMarmaladeLoaderService marmaladeLoader,
|
||||
IBotActivityService bas)
|
||||
IMarmaladeLoaderService marmaladeLoader)
|
||||
{
|
||||
_client = client;
|
||||
_db = db;
|
||||
_strings = strings;
|
||||
_coord = coord;
|
||||
_marmaladeLoader = marmaladeLoader;
|
||||
_bas = bas;
|
||||
}
|
||||
|
||||
|
||||
|
@ -65,10 +62,10 @@ public partial class Administration
|
|||
var (added, updated) = await _service.RefreshUsersAsync(users);
|
||||
|
||||
await message.ModifyAsync(x =>
|
||||
x.Embed = CreateEmbed()
|
||||
.WithDescription(GetText(strs.cache_users_done(added, updated)))
|
||||
.WithOkColor()
|
||||
.Build()
|
||||
x.Embed = _sender.CreateEmbed()
|
||||
.WithDescription(GetText(strs.cache_users_done(added, updated)))
|
||||
.WithOkColor()
|
||||
.Build()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -118,14 +115,14 @@ public partial class Administration
|
|||
_service.AddNewAutoCommand(cmd);
|
||||
|
||||
await Response()
|
||||
.Embed(CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.scadd))
|
||||
.AddField(GetText(strs.server),
|
||||
cmd.GuildId is null ? "-" : $"{cmd.GuildName}/{cmd.GuildId}",
|
||||
true)
|
||||
.AddField(GetText(strs.channel), $"{cmd.ChannelName}/{cmd.ChannelId}", true)
|
||||
.AddField(GetText(strs.command_text), cmdText))
|
||||
.Embed(_sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.scadd))
|
||||
.AddField(GetText(strs.server),
|
||||
cmd.GuildId is null ? "-" : $"{cmd.GuildName}/{cmd.GuildId}",
|
||||
true)
|
||||
.AddField(GetText(strs.channel), $"{cmd.ChannelName}/{cmd.ChannelId}", true)
|
||||
.AddField(GetText(strs.command_text), cmdText))
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
|
@ -323,16 +320,16 @@ public partial class Administration
|
|||
.ToArray());
|
||||
|
||||
var allShardStrings = statuses.Select(st =>
|
||||
{
|
||||
var timeDiff = DateTime.UtcNow - st.LastUpdate;
|
||||
var stateStr = ConnectionStateToEmoji(st);
|
||||
var maxGuildCountLength =
|
||||
statuses.Max(x => x.GuildCount).ToString().Length;
|
||||
return $"`{stateStr} "
|
||||
+ $"| #{st.ShardId.ToString().PadBoth(3)} "
|
||||
+ $"| {timeDiff:mm\\:ss} "
|
||||
+ $"| {st.GuildCount.ToString().PadBoth(maxGuildCountLength)} `";
|
||||
})
|
||||
{
|
||||
var timeDiff = DateTime.UtcNow - st.LastUpdate;
|
||||
var stateStr = ConnectionStateToEmoji(st);
|
||||
var maxGuildCountLength =
|
||||
statuses.Max(x => x.GuildCount).ToString().Length;
|
||||
return $"`{stateStr} "
|
||||
+ $"| #{st.ShardId.ToString().PadBoth(3)} "
|
||||
+ $"| {timeDiff:mm\\:ss} "
|
||||
+ $"| {st.GuildCount.ToString().PadBoth(maxGuildCountLength)} `";
|
||||
})
|
||||
.ToArray();
|
||||
await Response()
|
||||
.Paginated()
|
||||
|
@ -346,7 +343,7 @@ public partial class Administration
|
|||
if (string.IsNullOrWhiteSpace(str))
|
||||
str = GetText(strs.no_shards_on_page);
|
||||
|
||||
return CreateEmbed().WithOkColor().WithDescription($"{status}\n\n{str}");
|
||||
return _sender.CreateEmbed().WithOkColor().WithDescription($"{status}\n\n{str}");
|
||||
})
|
||||
.SendAsync();
|
||||
}
|
||||
|
@ -440,8 +437,7 @@ public partial class Administration
|
|||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{ await Response().Confirm(strs.restarting).SendAsync(); }
|
||||
try { await Response().Confirm(strs.restarting).SendAsync(); }
|
||||
catch { }
|
||||
}
|
||||
|
||||
|
@ -500,7 +496,7 @@ public partial class Administration
|
|||
// var rep = new ReplacementBuilder().WithDefault(Context).Build();
|
||||
|
||||
var repCtx = new ReplacementContext(ctx);
|
||||
await _bas.SetActivityAsync(game is null ? game : await repSvc.ReplaceAsync(game, repCtx), type);
|
||||
await _service.SetActivityAsync(game is null ? game : await repSvc.ReplaceAsync(game, repCtx), type);
|
||||
|
||||
await Response().Confirm(strs.set_activity).SendAsync();
|
||||
}
|
||||
|
@ -522,7 +518,7 @@ public partial class Administration
|
|||
{
|
||||
name ??= "";
|
||||
|
||||
await _bas.SetStreamAsync(name, url);
|
||||
await _service.SetStreamAsync(name, url);
|
||||
|
||||
await Response().Confirm(strs.set_stream).SendAsync();
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, IEService
|
|||
private readonly IMessageSenderService _sender;
|
||||
|
||||
//keys
|
||||
private readonly TypedKey<ActivityPubData> _activitySetKey;
|
||||
private readonly TypedKey<string> _guildLeaveKey;
|
||||
|
||||
public SelfService(
|
||||
|
@ -50,8 +51,11 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, IEService
|
|||
_bss = bss;
|
||||
_pubSub = pubSub;
|
||||
_sender = sender;
|
||||
_activitySetKey = new("activity.set");
|
||||
_guildLeaveKey = new("guild.leave");
|
||||
|
||||
HandleStatusChanges();
|
||||
|
||||
_pubSub.Sub(_guildLeaveKey,
|
||||
async input =>
|
||||
{
|
||||
|
@ -67,6 +71,7 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, IEService
|
|||
if (server.OwnerId != _client.CurrentUser.Id)
|
||||
{
|
||||
await server.LeaveAsync();
|
||||
Log.Information("Left server {Name} [{Id}]", server.Name, server.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -390,6 +395,49 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, IEService
|
|||
return channelId is not null;
|
||||
}
|
||||
|
||||
private void HandleStatusChanges()
|
||||
=> _pubSub.Sub(_activitySetKey,
|
||||
async data =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (data.Type is { } activityType)
|
||||
await _client.SetGameAsync(data.Name, data.Link, activityType);
|
||||
else
|
||||
await _client.SetCustomStatusAsync(data.Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error setting activity");
|
||||
}
|
||||
});
|
||||
|
||||
public Task SetActivityAsync(string game, ActivityType? type)
|
||||
=> _pubSub.Pub(_activitySetKey,
|
||||
new()
|
||||
{
|
||||
Name = game,
|
||||
Link = null,
|
||||
Type = type
|
||||
});
|
||||
|
||||
public Task SetStreamAsync(string name, string link)
|
||||
=> _pubSub.Pub(_activitySetKey,
|
||||
new()
|
||||
{
|
||||
Name = name,
|
||||
Link = link,
|
||||
Type = ActivityType.Streaming
|
||||
});
|
||||
|
||||
private sealed class ActivityPubData
|
||||
{
|
||||
public string Name { get; init; }
|
||||
public string Link { get; init; }
|
||||
public ActivityType? Type { get; init; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified <paramref name="users"/> to the database. If a database user with placeholder name
|
||||
/// and discriminator is present in <paramref name="users"/>, their name and discriminator get updated accordingly.
|
||||
|
@ -405,6 +453,7 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, IEService
|
|||
{
|
||||
x.UserId,
|
||||
x.Username,
|
||||
x.Discriminator
|
||||
})
|
||||
.Where(x => users.Select(y => y.Id).Contains(x.UserId))
|
||||
.ToArrayAsyncEF();
|
||||
|
@ -416,11 +465,12 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, IEService
|
|||
UserId = x.Id,
|
||||
AvatarId = x.AvatarId,
|
||||
Username = x.Username,
|
||||
Discriminator = x.Discriminator
|
||||
});
|
||||
|
||||
var added = (await ctx.BulkCopyAsync(usersToAdd)).RowsCopied;
|
||||
var toUpdateUserIds = presentDbUsers
|
||||
.Where(x => x.Username.StartsWith("??"))
|
||||
.Where(x => x.Username == "Unknown" && x.Discriminator == "????")
|
||||
.Select(x => x.UserId)
|
||||
.ToArray();
|
||||
|
||||
|
@ -431,6 +481,7 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, IEService
|
|||
.UpdateAsync(x => new DiscordUser()
|
||||
{
|
||||
Username = user.Username,
|
||||
Discriminator = user.Discriminator,
|
||||
|
||||
// .award tends to set AvatarId and DateAdded to NULL, so account for that.
|
||||
AvatarId = user.AvatarId,
|
||||
|
|
|
@ -6,136 +6,16 @@ namespace EllieBot.Modules.Administration;
|
|||
|
||||
public partial class Administration
|
||||
{
|
||||
public partial class SelfAssignedRolesHelpers : EllieModule<SelfAssignedRolesService>
|
||||
{
|
||||
private readonly SarAssignerService _sas;
|
||||
|
||||
public SelfAssignedRolesHelpers(SarAssignerService sas)
|
||||
{
|
||||
_sas = sas;
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Iam([Leftover] IRole role)
|
||||
{
|
||||
var guildUser = (IGuildUser)ctx.User;
|
||||
|
||||
var group = await _service.GetRoleGroup(ctx.Guild.Id, role.Id);
|
||||
|
||||
IUserMessage msg = null;
|
||||
try
|
||||
{
|
||||
if (group is null)
|
||||
{
|
||||
msg = await Response().Error(strs.self_assign_not).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<SarAssignResult>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
await _sas.Add(new()
|
||||
{
|
||||
Group = group,
|
||||
RoleId = role.Id,
|
||||
User = guildUser,
|
||||
CompletionTask = tcs
|
||||
});
|
||||
|
||||
var res = await tcs.Task;
|
||||
|
||||
if (res.TryPickT0(out _, out var error))
|
||||
{
|
||||
msg = await Response()
|
||||
.Confirm(strs.self_assign_success(Format.Bold(role.Name)))
|
||||
.SendAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
var resStr = error.Match(
|
||||
_ => strs.error_occured,
|
||||
lvlReq => strs.self_assign_not_level(Format.Bold(lvlReq.Level.ToString())),
|
||||
roleRq => strs.self_assign_role_req(Format.Bold(ctx.Guild.GetRole(roleRq.RoleId).ToString()
|
||||
?? "missing role " + roleRq.RoleId),
|
||||
group.Name),
|
||||
_ => strs.self_assign_already(Format.Bold(role.Name)),
|
||||
_ => strs.self_assign_perms);
|
||||
|
||||
msg = await Response().Error(resStr).SendAsync();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
var ad = _service.GetAutoDelete(ctx.Guild.Id);
|
||||
|
||||
if (ad)
|
||||
{
|
||||
msg?.DeleteAfter(3);
|
||||
ctx.Message.DeleteAfter(3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Iamnot([Leftover] IRole role)
|
||||
{
|
||||
var guildUser = (IGuildUser)ctx.User;
|
||||
|
||||
IUserMessage msg = null;
|
||||
try
|
||||
{
|
||||
if (!guildUser.RoleIds.Contains(role.Id))
|
||||
{
|
||||
msg = await Response().Error(strs.self_assign_not_have(Format.Bold(role.Name))).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
var group = await _service.GetRoleGroup(ctx.Guild.Id, role.Id);
|
||||
|
||||
if (group is null || group.Roles.All(x => x.RoleId != role.Id))
|
||||
{
|
||||
msg = await Response().Error(strs.self_assign_not).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
if (role.Position >= ((SocketGuild)ctx.Guild).CurrentUser.Roles.Max(x => x.Position))
|
||||
{
|
||||
msg = await Response().Error(strs.self_assign_perms).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
await guildUser.RemoveRoleAsync(role);
|
||||
msg = await Response().Confirm(strs.self_assign_remove(Format.Bold(role.Name))).SendAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
var ad = _service.GetAutoDelete(ctx.Guild.Id);
|
||||
if (ad)
|
||||
{
|
||||
msg?.DeleteAfter(3);
|
||||
ctx.Message.DeleteAfter(3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Group("sar")]
|
||||
[Group]
|
||||
public partial class SelfAssignedRolesCommands : EllieModule<SelfAssignedRolesService>
|
||||
{
|
||||
private readonly SarAssignerService _sas;
|
||||
|
||||
public SelfAssignedRolesCommands(SarAssignerService sas)
|
||||
{
|
||||
_sas = sas;
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
[BotPerm(GuildPerm.ManageMessages)]
|
||||
public async Task SarAutoDelete()
|
||||
public async Task AdSarm()
|
||||
{
|
||||
var newVal = await _service.ToggleAutoDelete(ctx.Guild.Id);
|
||||
var newVal = _service.ToggleAdSarm(ctx.Guild.Id);
|
||||
|
||||
if (newVal)
|
||||
await Response().Confirm(strs.adsarm_enable(prefix)).SendAsync();
|
||||
|
@ -148,24 +28,30 @@ public partial class Administration
|
|||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[Priority(1)]
|
||||
public Task SarAdd([Leftover] IRole role)
|
||||
=> SarAdd(0, role);
|
||||
public Task Asar([Leftover] IRole role)
|
||||
=> Asar(0, role);
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[Priority(0)]
|
||||
public async Task SarAdd(int group, [Leftover] IRole role)
|
||||
public async Task Asar(int group, [Leftover] IRole role)
|
||||
{
|
||||
if (!await CheckRoleHierarchy(role))
|
||||
var guser = (IGuildUser)ctx.User;
|
||||
if (ctx.User.Id != guser.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= role.Position)
|
||||
return;
|
||||
|
||||
await _service.AddAsync(ctx.Guild.Id, role.Id, group);
|
||||
var succ = _service.AddNew(ctx.Guild.Id, role, group);
|
||||
|
||||
await Response()
|
||||
.Confirm(strs.role_added(Format.Bold(role.Name), Format.Bold(group.ToString())))
|
||||
.SendAsync();
|
||||
if (succ)
|
||||
{
|
||||
await Response()
|
||||
.Confirm(strs.role_added(Format.Bold(role.Name), Format.Bold(group.ToString())))
|
||||
.SendAsync();
|
||||
}
|
||||
else
|
||||
await Response().Error(strs.role_in_list(Format.Bold(role.Name))).SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
|
@ -173,9 +59,9 @@ public partial class Administration
|
|||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[Priority(0)]
|
||||
public async Task SarGroupName(int group, [Leftover] string name = null)
|
||||
public async Task Sargn(int group, [Leftover] string name = null)
|
||||
{
|
||||
var set = await _service.SetGroupNameAsync(ctx.Guild.Id, group, name);
|
||||
var set = await _service.SetNameAsync(ctx.Guild.Id, group, name);
|
||||
|
||||
if (set)
|
||||
{
|
||||
|
@ -184,19 +70,19 @@ public partial class Administration
|
|||
.SendAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await Response().Confirm(strs.group_name_removed(Format.Bold(group.ToString()))).SendAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
public async Task SarRemove([Leftover] IRole role)
|
||||
public async Task Rsar([Leftover] IRole role)
|
||||
{
|
||||
var guser = (IGuildUser)ctx.User;
|
||||
if (ctx.User.Id != guser.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= role.Position)
|
||||
return;
|
||||
|
||||
var success = await _service.RemoveAsync(role.Guild.Id, role.Id);
|
||||
var success = _service.RemoveSar(role.Guild.Id, role.Id);
|
||||
if (!success)
|
||||
await Response().Error(strs.self_assign_not).SendAsync();
|
||||
else
|
||||
|
@ -205,81 +91,59 @@ public partial class Administration
|
|||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task SarList(int page = 1)
|
||||
public async Task Lsar(int page = 1)
|
||||
{
|
||||
if (--page < 0)
|
||||
return;
|
||||
|
||||
var groups = await _service.GetSarsAsync(ctx.Guild.Id);
|
||||
|
||||
var gDict = groups.ToDictionary(x => x.Id, x => x);
|
||||
var (exclusive, roles, groups) = _service.GetRoles(ctx.Guild);
|
||||
|
||||
await Response()
|
||||
.Paginated()
|
||||
.Items(groups.SelectMany(x => x.Roles).ToList())
|
||||
.Items(roles.OrderBy(x => x.Model.Group).ToList())
|
||||
.PageSize(20)
|
||||
.CurrentPage(page)
|
||||
.Page(async (items, _) =>
|
||||
.Page((items, _) =>
|
||||
{
|
||||
var rolesStr = new StringBuilder();
|
||||
var roleGroups = items
|
||||
.GroupBy(x => x.SarGroupId)
|
||||
.GroupBy(x => x.Model.Group)
|
||||
.OrderBy(x => x.Key);
|
||||
|
||||
var eb = CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.self_assign_list(groups.Sum(x => x.Roles.Count))));
|
||||
|
||||
foreach (var kvp in roleGroups)
|
||||
{
|
||||
var group = gDict[kvp.Key];
|
||||
string groupNameText;
|
||||
if (!groups.TryGetValue(kvp.Key, out var name))
|
||||
groupNameText = Format.Bold(GetText(strs.self_assign_group(kvp.Key)));
|
||||
else
|
||||
groupNameText = Format.Bold($"{kvp.Key} - {name.TrimTo(25, true)}");
|
||||
|
||||
var groupNameText = "";
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(group.Name))
|
||||
groupNameText += $" **{group.Name}**";
|
||||
|
||||
groupNameText = $"`{group.GroupNumber}` {groupNameText}";
|
||||
|
||||
var rolesStr = new StringBuilder();
|
||||
|
||||
if (group.IsExclusive)
|
||||
rolesStr.AppendLine("\t\t\t\t ⟪" + groupNameText + "⟫");
|
||||
foreach (var (model, role) in kvp.AsEnumerable())
|
||||
{
|
||||
rolesStr.AppendLine(Format.Italics(GetText(strs.choose_one)));
|
||||
}
|
||||
|
||||
if (group.RoleReq is ulong rrId)
|
||||
{
|
||||
var rr = ctx.Guild.GetRole(rrId);
|
||||
|
||||
if (rr is null)
|
||||
if (role is null)
|
||||
{
|
||||
await _service.SetGroupRoleReq(group.GuildId, group.GroupNumber, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
rolesStr.AppendLine(
|
||||
Format.Italics(GetText(strs.requires_role(Format.Bold(rr.Name)))));
|
||||
// first character is invisible space
|
||||
if (model.LevelRequirement == 0)
|
||||
rolesStr.AppendLine(" " + role.Name);
|
||||
else
|
||||
rolesStr.AppendLine(" " + role.Name + $" (lvl {model.LevelRequirement}+)");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var sar in kvp)
|
||||
{
|
||||
var roleName = (ctx.Guild.GetRole(sar.RoleId)?.Name ?? (sar.RoleId + " (deleted)"));
|
||||
rolesStr.Append("- " + Format.Code(roleName));
|
||||
|
||||
if (sar.LevelReq > 0)
|
||||
{
|
||||
rolesStr.Append($" *[lvl {sar.LevelReq}+]*");
|
||||
}
|
||||
|
||||
rolesStr.AppendLine();
|
||||
}
|
||||
|
||||
|
||||
eb.AddField(groupNameText, rolesStr, false);
|
||||
rolesStr.AppendLine();
|
||||
}
|
||||
|
||||
return eb;
|
||||
return _sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(Format.Bold(GetText(strs.self_assign_list(roles.Count()))))
|
||||
.WithDescription(rolesStr.ToString())
|
||||
.WithFooter(exclusive
|
||||
? GetText(strs.self_assign_are_exclusive)
|
||||
: GetText(strs.self_assign_are_not_exclusive));
|
||||
})
|
||||
.SendAsync();
|
||||
}
|
||||
|
@ -288,36 +152,25 @@ public partial class Administration
|
|||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
public async Task SarExclusive(int groupNumber)
|
||||
public async Task Togglexclsar()
|
||||
{
|
||||
var areExclusive = await _service.SetGroupExclusivityAsync(ctx.Guild.Id, groupNumber);
|
||||
|
||||
if (areExclusive is null)
|
||||
{
|
||||
await Response().Error(strs.sar_group_not_found).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
if (areExclusive is true)
|
||||
{
|
||||
var areExclusive = _service.ToggleEsar(ctx.Guild.Id);
|
||||
if (areExclusive)
|
||||
await Response().Confirm(strs.self_assign_excl).SendAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await Response().Confirm(strs.self_assign_no_excl).SendAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
public async Task SarRoleLevelReq(int level, [Leftover] IRole role)
|
||||
public async Task RoleLevelReq(int level, [Leftover] IRole role)
|
||||
{
|
||||
if (level < 0)
|
||||
return;
|
||||
|
||||
var succ = await _service.SetRoleLevelReq(ctx.Guild.Id, role.Id, level);
|
||||
var succ = _service.SetLevelReq(ctx.Guild.Id, role, level);
|
||||
|
||||
if (!succ)
|
||||
{
|
||||
|
@ -333,35 +186,54 @@ public partial class Administration
|
|||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
public async Task SarGroupRoleReq(int groupNumber, [Leftover] IRole role)
|
||||
public async Task Iam([Leftover] IRole role)
|
||||
{
|
||||
var succ = await _service.SetGroupRoleReq(ctx.Guild.Id, groupNumber, role.Id);
|
||||
var guildUser = (IGuildUser)ctx.User;
|
||||
|
||||
if (!succ)
|
||||
var (result, autoDelete, extra) = await _service.Assign(guildUser, role);
|
||||
|
||||
IUserMessage msg;
|
||||
if (result == SelfAssignedRolesService.AssignResult.ErrNotAssignable)
|
||||
msg = await Response().Error(strs.self_assign_not).SendAsync();
|
||||
else if (result == SelfAssignedRolesService.AssignResult.ErrLvlReq)
|
||||
msg = await Response().Error(strs.self_assign_not_level(Format.Bold(extra.ToString()))).SendAsync();
|
||||
else if (result == SelfAssignedRolesService.AssignResult.ErrAlreadyHave)
|
||||
msg = await Response().Error(strs.self_assign_already(Format.Bold(role.Name))).SendAsync();
|
||||
else if (result == SelfAssignedRolesService.AssignResult.ErrNotPerms)
|
||||
msg = await Response().Error(strs.self_assign_perms).SendAsync();
|
||||
else
|
||||
msg = await Response().Confirm(strs.self_assign_success(Format.Bold(role.Name))).SendAsync();
|
||||
|
||||
if (autoDelete)
|
||||
{
|
||||
await Response().Error(strs.sar_group_not_found).SendAsync();
|
||||
return;
|
||||
msg.DeleteAfter(3);
|
||||
ctx.Message.DeleteAfter(3);
|
||||
}
|
||||
|
||||
await Response()
|
||||
.Confirm(strs.self_assign_group_role_req(
|
||||
Format.Bold(groupNumber.ToString()),
|
||||
Format.Bold(role.Name)))
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
public async Task SarGroupDelete(int groupNumber)
|
||||
public async Task Iamnot([Leftover] IRole role)
|
||||
{
|
||||
var succ = await _service.DeleteRoleGroup(ctx.Guild.Id, groupNumber);
|
||||
if (succ)
|
||||
await Response().Confirm(strs.sar_group_deleted(Format.Bold(groupNumber.ToString()))).SendAsync();
|
||||
var guildUser = (IGuildUser)ctx.User;
|
||||
|
||||
var (result, autoDelete) = await _service.Remove(guildUser, role);
|
||||
|
||||
IUserMessage msg;
|
||||
if (result == SelfAssignedRolesService.RemoveResult.ErrNotAssignable)
|
||||
msg = await Response().Error(strs.self_assign_not).SendAsync();
|
||||
else if (result == SelfAssignedRolesService.RemoveResult.ErrNotHave)
|
||||
msg = await Response().Error(strs.self_assign_not_have(Format.Bold(role.Name))).SendAsync();
|
||||
else if (result == SelfAssignedRolesService.RemoveResult.ErrNotPerms)
|
||||
msg = await Response().Error(strs.self_assign_perms).SendAsync();
|
||||
else
|
||||
await Response().Error(strs.sar_group_not_found).SendAsync();
|
||||
msg = await Response().Confirm(strs.self_assign_remove(Format.Bold(role.Name))).SendAsync();
|
||||
|
||||
if (autoDelete)
|
||||
{
|
||||
msg.DeleteAfter(3);
|
||||
ctx.Message.DeleteAfter(3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,335 +1,233 @@
|
|||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using EllieBot.Common.ModuleBehaviors;
|
||||
using EllieBot.Db.Models;
|
||||
using EllieBot.Modules.Xp.Services;
|
||||
using OneOf;
|
||||
using OneOf.Types;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Threading.Channels;
|
||||
|
||||
namespace EllieBot.Modules.Administration.Services;
|
||||
|
||||
public class SelfAssignedRolesService : IEService, IReadyExecutor
|
||||
public class SelfAssignedRolesService : IEService
|
||||
{
|
||||
public enum AssignResult
|
||||
{
|
||||
Assigned, // successfully removed
|
||||
ErrNotAssignable, // not assignable (error)
|
||||
ErrAlreadyHave, // you already have that role (error)
|
||||
ErrNotPerms, // bot doesn't have perms (error)
|
||||
ErrLvlReq // you are not required level (error)
|
||||
}
|
||||
|
||||
public enum RemoveResult
|
||||
{
|
||||
Removed, // successfully removed
|
||||
ErrNotAssignable, // not assignable (error)
|
||||
ErrNotHave, // you don't have a role you want to remove (error)
|
||||
ErrNotPerms // bot doesn't have perms (error)
|
||||
}
|
||||
|
||||
private readonly DbService _db;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IBotCreds _creds;
|
||||
|
||||
private ConcurrentHashSet<ulong> _sarAds = new();
|
||||
public SelfAssignedRolesService(DbService db)
|
||||
=> _db = db;
|
||||
|
||||
public SelfAssignedRolesService(DbService db, DiscordSocketClient client, IBotCreds creds)
|
||||
public bool AddNew(ulong guildId, IRole role, int group)
|
||||
{
|
||||
_db = db;
|
||||
_client = client;
|
||||
_creds = creds;
|
||||
}
|
||||
|
||||
public async Task AddAsync(ulong guildId, ulong roleId, int groupNumber)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
|
||||
await ctx.GetTable<SarGroup>()
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
GroupNumber = groupNumber,
|
||||
IsExclusive = false
|
||||
},
|
||||
_ => new()
|
||||
{
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
GroupNumber = groupNumber
|
||||
});
|
||||
|
||||
await ctx.GetTable<Sar>()
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
RoleId = roleId,
|
||||
LevelReq = 0,
|
||||
GuildId = guildId,
|
||||
SarGroupId = ctx.GetTable<SarGroup>()
|
||||
.Where(x => x.GuildId == guildId && x.GroupNumber == groupNumber)
|
||||
.Select(x => x.Id)
|
||||
.First()
|
||||
},
|
||||
_ => new()
|
||||
{
|
||||
SarGroupId = ctx.GetTable<SarGroup>()
|
||||
.Where(x => x.GuildId == guildId && x.GroupNumber == groupNumber)
|
||||
.Select(x => x.Id)
|
||||
.First()
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
RoleId = roleId,
|
||||
GuildId = guildId,
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<bool> RemoveAsync(ulong guildId, ulong roleId)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
|
||||
var deleted = await ctx.GetTable<Sar>()
|
||||
.Where(x => x.RoleId == roleId && x.GuildId == guildId)
|
||||
.DeleteAsync();
|
||||
|
||||
return deleted > 0;
|
||||
}
|
||||
|
||||
public async Task<bool> SetGroupNameAsync(ulong guildId, int groupNumber, string? name)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
|
||||
var changes = await ctx.GetTable<SarGroup>()
|
||||
.Where(x => x.GuildId == guildId && x.GroupNumber == groupNumber)
|
||||
.UpdateAsync(x => new()
|
||||
{
|
||||
Name = name
|
||||
});
|
||||
|
||||
return changes > 0;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<SarGroup>> GetSarsAsync(ulong guildId)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
|
||||
var sgs = await ctx.GetTable<SarGroup>()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.LoadWith(x => x.Roles)
|
||||
.ToListAsyncLinqToDB();
|
||||
|
||||
return sgs;
|
||||
}
|
||||
|
||||
public async Task<bool> SetRoleLevelReq(ulong guildId, ulong roleId, int levelReq)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
var changes = await ctx.GetTable<Sar>()
|
||||
.Where(x => x.GuildId == guildId && x.RoleId == roleId)
|
||||
.UpdateAsync(_ => new()
|
||||
{
|
||||
LevelReq = levelReq,
|
||||
});
|
||||
|
||||
return changes > 0;
|
||||
}
|
||||
|
||||
public async Task<bool> SetGroupRoleReq(ulong guildId, int groupNumber, ulong? roleId)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
var changes = await ctx.GetTable<SarGroup>()
|
||||
.Where(x => x.GuildId == guildId && x.GroupNumber == groupNumber)
|
||||
.UpdateAsync(_ => new()
|
||||
{
|
||||
RoleReq = roleId
|
||||
});
|
||||
|
||||
return changes > 0;
|
||||
}
|
||||
|
||||
public async Task<bool?> SetGroupExclusivityAsync(ulong guildId, int groupNumber)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
var changes = await ctx.GetTable<SarGroup>()
|
||||
.Where(x => x.GuildId == guildId && x.GroupNumber == groupNumber)
|
||||
.UpdateWithOutputAsync(old => new()
|
||||
{
|
||||
IsExclusive = !old.IsExclusive
|
||||
},
|
||||
(o, n) => n.IsExclusive);
|
||||
|
||||
if (changes.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return changes[0];
|
||||
}
|
||||
|
||||
public async Task<SarGroup?> GetRoleGroup(ulong guildId, ulong roleId)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
|
||||
var group = await ctx.GetTable<SarGroup>()
|
||||
.Where(x => x.GuildId == guildId && x.Roles.Any(x => x.RoleId == roleId))
|
||||
.LoadWith(x => x.Roles)
|
||||
.FirstOrDefaultAsyncLinqToDB();
|
||||
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteRoleGroup(ulong guildId, int groupNumber)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
|
||||
var deleted = await ctx.GetTable<SarGroup>()
|
||||
.Where(x => x.GuildId == guildId && x.GroupNumber == groupNumber)
|
||||
.DeleteAsync();
|
||||
|
||||
return deleted > 0;
|
||||
}
|
||||
|
||||
public async Task<bool> ToggleAutoDelete(ulong guildId)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
|
||||
var delted = await ctx.GetTable<SarAutoDelete>()
|
||||
.DeleteAsync(x => x.GuildId == guildId);
|
||||
|
||||
if (delted > 0)
|
||||
{
|
||||
_sarAds.TryRemove(guildId);
|
||||
using var uow = _db.GetDbContext();
|
||||
var roles = uow.Set<SelfAssignedRole>().GetFromGuild(guildId);
|
||||
if (roles.Any(s => s.RoleId == role.Id && s.GuildId == role.Guild.Id))
|
||||
return false;
|
||||
}
|
||||
|
||||
await ctx.GetTable<SarAutoDelete>()
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
IsEnabled = true,
|
||||
GuildId = guildId,
|
||||
},
|
||||
(_) => new()
|
||||
{
|
||||
IsEnabled = true
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
GuildId = guildId
|
||||
});
|
||||
|
||||
_sarAds.Add(guildId);
|
||||
uow.Set<SelfAssignedRole>().Add(new()
|
||||
{
|
||||
Group = group,
|
||||
RoleId = role.Id,
|
||||
GuildId = role.Guild.Id
|
||||
});
|
||||
uow.SaveChanges();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool GetAutoDelete(ulong guildId)
|
||||
=> _sarAds.Contains(guildId);
|
||||
|
||||
public async Task OnReadyAsync()
|
||||
public bool ToggleAdSarm(ulong guildId)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var guilds = await uow.GetTable<SarAutoDelete>()
|
||||
.Where(x => x.IsEnabled && Linq2DbExpressions.GuildOnShard(x.GuildId, _creds.TotalShards, _client.ShardId))
|
||||
.Select(x => x.GuildId)
|
||||
.ToListAsyncLinqToDB();
|
||||
|
||||
_sarAds = new(guilds);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SarAssignerService : IEService, IReadyExecutor
|
||||
{
|
||||
private readonly XpService _xp;
|
||||
private readonly DbService _db;
|
||||
|
||||
private readonly Channel<SarAssignerDataItem> _channel =
|
||||
Channel.CreateBounded<SarAssignerDataItem>(100);
|
||||
|
||||
|
||||
public SarAssignerService(XpService xp, DbService db)
|
||||
{
|
||||
_xp = xp;
|
||||
_db = db;
|
||||
bool newval;
|
||||
using var uow = _db.GetDbContext();
|
||||
var config = uow.GuildConfigsForId(guildId, set => set);
|
||||
newval = config.AutoDeleteSelfAssignedRoleMessages = !config.AutoDeleteSelfAssignedRoleMessages;
|
||||
uow.SaveChanges();
|
||||
return newval;
|
||||
}
|
||||
|
||||
public async Task OnReadyAsync()
|
||||
public async Task<(AssignResult Result, bool AutoDelete, object extra)> Assign(IGuildUser guildUser, IRole role)
|
||||
{
|
||||
var reader = _channel.Reader;
|
||||
while (true)
|
||||
LevelStats userLevelData;
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var item = await reader.ReadAsync();
|
||||
var stats = uow.GetOrCreateUserXpStats(guildUser.Guild.Id, guildUser.Id);
|
||||
userLevelData = new(stats.Xp + stats.AwardedXp);
|
||||
}
|
||||
|
||||
try
|
||||
var (autoDelete, exclusive, roles) = GetAdAndRoles(guildUser.Guild.Id);
|
||||
|
||||
var theRoleYouWant = roles.FirstOrDefault(r => r.RoleId == role.Id);
|
||||
if (theRoleYouWant is null)
|
||||
return (AssignResult.ErrNotAssignable, autoDelete, null);
|
||||
if (theRoleYouWant.LevelRequirement > userLevelData.Level)
|
||||
return (AssignResult.ErrLvlReq, autoDelete, theRoleYouWant.LevelRequirement);
|
||||
if (guildUser.RoleIds.Contains(role.Id))
|
||||
return (AssignResult.ErrAlreadyHave, autoDelete, null);
|
||||
|
||||
var roleIds = roles.Where(x => x.Group == theRoleYouWant.Group).Select(x => x.RoleId).ToArray();
|
||||
if (exclusive)
|
||||
{
|
||||
var sameRoles = guildUser.RoleIds.Where(r => roleIds.Contains(r));
|
||||
|
||||
foreach (var roleId in sameRoles)
|
||||
{
|
||||
var sar = item.Group.Roles.First(x => x.RoleId == item.RoleId);
|
||||
|
||||
if (item.User.RoleIds.Contains(item.RoleId))
|
||||
var sameRole = guildUser.Guild.GetRole(roleId);
|
||||
if (sameRole is not null)
|
||||
{
|
||||
item.CompletionTask.TrySetResult(new SarAlreadyHasRole());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.Group.RoleReq is { } rid)
|
||||
{
|
||||
if (!item.User.RoleIds.Contains(rid))
|
||||
try
|
||||
{
|
||||
item.CompletionTask.TrySetResult(new SarRoleRequirement(rid));
|
||||
continue;
|
||||
await guildUser.RemoveRoleAsync(sameRole);
|
||||
await Task.Delay(300);
|
||||
}
|
||||
|
||||
// passed
|
||||
}
|
||||
|
||||
// check level requirement
|
||||
if (sar.LevelReq > 0)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
var xpStats = await ctx.GetTable<UserXpStats>().GetGuildUserXp(sar.GuildId, item.User.Id);
|
||||
var lvlData = new LevelStats(xpStats?.Xp ?? 0);
|
||||
|
||||
if (lvlData.Level < sar.LevelReq)
|
||||
catch
|
||||
{
|
||||
item.CompletionTask.TrySetResult(new SarLevelRequirement(sar.LevelReq));
|
||||
continue;
|
||||
// ignored
|
||||
}
|
||||
|
||||
// passed
|
||||
}
|
||||
|
||||
if (item.Group.IsExclusive)
|
||||
{
|
||||
var rolesToRemove = item.Group.Roles
|
||||
.Where(x => item.User.RoleIds.Contains(x.RoleId))
|
||||
.Select(x => x.RoleId)
|
||||
.ToArray();
|
||||
if (rolesToRemove.Length > 0)
|
||||
await item.User.RemoveRolesAsync(rolesToRemove);
|
||||
}
|
||||
|
||||
await item.User.AddRoleAsync(item.RoleId);
|
||||
|
||||
item.CompletionTask.TrySetResult(new Success());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Unknown error ocurred in SAR runner: {Error}", ex.Message);
|
||||
item.CompletionTask.TrySetResult(new Error());
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await guildUser.AddRoleAsync(role);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return (AssignResult.ErrNotPerms, autoDelete, ex);
|
||||
}
|
||||
|
||||
return (AssignResult.Assigned, autoDelete, null);
|
||||
}
|
||||
|
||||
public async Task Add(SarAssignerDataItem item)
|
||||
public async Task<bool> SetNameAsync(ulong guildId, int group, string name)
|
||||
{
|
||||
await _channel.Writer.WriteAsync(item);
|
||||
var set = false;
|
||||
await using var uow = _db.GetDbContext();
|
||||
var gc = uow.GuildConfigsForId(guildId, y => y.Include(x => x.SelfAssignableRoleGroupNames));
|
||||
var toUpdate = gc.SelfAssignableRoleGroupNames.FirstOrDefault(x => x.Number == group);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
if (toUpdate is not null)
|
||||
gc.SelfAssignableRoleGroupNames.Remove(toUpdate);
|
||||
}
|
||||
else if (toUpdate is null)
|
||||
{
|
||||
gc.SelfAssignableRoleGroupNames.Add(new()
|
||||
{
|
||||
Name = name,
|
||||
Number = group
|
||||
});
|
||||
set = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
toUpdate.Name = name;
|
||||
set = true;
|
||||
}
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
}
|
||||
public async Task<(RemoveResult Result, bool AutoDelete)> Remove(IGuildUser guildUser, IRole role)
|
||||
{
|
||||
var (autoDelete, _, roles) = GetAdAndRoles(guildUser.Guild.Id);
|
||||
|
||||
public sealed class SarAssignerDataItem
|
||||
{
|
||||
public required SarGroup Group { get; init; }
|
||||
public required IGuildUser User { get; init; }
|
||||
public required ulong RoleId { get; init; }
|
||||
public required TaskCompletionSource<SarAssignResult> CompletionTask { get; init; }
|
||||
}
|
||||
if (roles.FirstOrDefault(r => r.RoleId == role.Id) is null)
|
||||
return (RemoveResult.ErrNotAssignable, autoDelete);
|
||||
if (!guildUser.RoleIds.Contains(role.Id))
|
||||
return (RemoveResult.ErrNotHave, autoDelete);
|
||||
try
|
||||
{
|
||||
await guildUser.RemoveRoleAsync(role);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return (RemoveResult.ErrNotPerms, autoDelete);
|
||||
}
|
||||
|
||||
[GenerateOneOf]
|
||||
public sealed partial class SarAssignResult
|
||||
: OneOfBase<Success, Error, SarLevelRequirement, SarRoleRequirement, SarAlreadyHasRole, SarInsuffPerms>
|
||||
{
|
||||
}
|
||||
return (RemoveResult.Removed, autoDelete);
|
||||
}
|
||||
|
||||
public record class SarLevelRequirement(int Level);
|
||||
public bool RemoveSar(ulong guildId, ulong roleId)
|
||||
{
|
||||
bool success;
|
||||
using var uow = _db.GetDbContext();
|
||||
success = uow.Set<SelfAssignedRole>().DeleteByGuildAndRoleId(guildId, roleId);
|
||||
uow.SaveChanges();
|
||||
return success;
|
||||
}
|
||||
|
||||
public record class SarRoleRequirement(ulong RoleId);
|
||||
public (bool AutoDelete, bool Exclusive, IReadOnlyCollection<SelfAssignedRole>) GetAdAndRoles(ulong guildId)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var gc = uow.GuildConfigsForId(guildId, set => set);
|
||||
var autoDelete = gc.AutoDeleteSelfAssignedRoleMessages;
|
||||
var exclusive = gc.ExclusiveSelfAssignedRoles;
|
||||
var roles = uow.Set<SelfAssignedRole>().GetFromGuild(guildId);
|
||||
|
||||
public record class SarAlreadyHasRole();
|
||||
return (autoDelete, exclusive, roles);
|
||||
}
|
||||
|
||||
public record class SarInsuffPerms();
|
||||
public bool SetLevelReq(ulong guildId, IRole role, int level)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var roles = uow.Set<SelfAssignedRole>().GetFromGuild(guildId);
|
||||
var sar = roles.FirstOrDefault(x => x.RoleId == role.Id);
|
||||
if (sar is not null)
|
||||
{
|
||||
sar.LevelRequirement = level;
|
||||
uow.SaveChanges();
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ToggleEsar(ulong guildId)
|
||||
{
|
||||
bool areExclusive;
|
||||
using var uow = _db.GetDbContext();
|
||||
var config = uow.GuildConfigsForId(guildId, set => set);
|
||||
|
||||
areExclusive = config.ExclusiveSelfAssignedRoles = !config.ExclusiveSelfAssignedRoles;
|
||||
uow.SaveChanges();
|
||||
return areExclusive;
|
||||
}
|
||||
|
||||
public (bool Exclusive, IReadOnlyCollection<(SelfAssignedRole Model, IRole Role)> Roles, IDictionary<int, string>
|
||||
GroupNames
|
||||
) GetRoles(IGuild guild)
|
||||
{
|
||||
var exclusive = false;
|
||||
|
||||
IReadOnlyCollection<(SelfAssignedRole Model, IRole Role)> roles;
|
||||
IDictionary<int, string> groupNames;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var gc = uow.GuildConfigsForId(guild.Id, set => set.Include(x => x.SelfAssignableRoleGroupNames));
|
||||
exclusive = gc.ExclusiveSelfAssignedRoles;
|
||||
groupNames = gc.SelfAssignableRoleGroupNames.ToDictionary(x => x.Number, x => x.Name);
|
||||
var roleModels = uow.Set<SelfAssignedRole>().GetFromGuild(guild.Id);
|
||||
roles = roleModels.Select(x => (Model: x, Role: guild.GetRole(x.RoleId)))
|
||||
.ToList();
|
||||
uow.Set<SelfAssignedRole>().RemoveRange(roles.Where(x => x.Role is null).Select(x => x.Model).ToArray());
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
return (exclusive, roles.Where(x => x.Role is not null).ToList(), groupNames);
|
||||
}
|
||||
}
|
|
@ -35,7 +35,7 @@ public partial class Administration
|
|||
var usrs = settings?.LogIgnores.Where(x => x.ItemType == IgnoredItemType.User).ToList()
|
||||
?? new List<IgnoredLogItem>();
|
||||
|
||||
var eb = CreateEmbed()
|
||||
var eb = _sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.AddField(GetText(strs.log_ignored_channels),
|
||||
chs.Count == 0
|
||||
|
|
|
@ -42,7 +42,7 @@ public partial class Administration
|
|||
.Items(timezoneStrings)
|
||||
.PageSize(timezonesPerPage)
|
||||
.CurrentPage(page)
|
||||
.Page((items, _) => CreateEmbed()
|
||||
.Page((items, _) => _sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.timezones_available))
|
||||
.WithDescription(string.Join("\n", items)))
|
||||
|
|
|
@ -23,6 +23,27 @@ public partial class Administration
|
|||
_mute = mute;
|
||||
}
|
||||
|
||||
private async Task<bool> CheckRoleHierarchy(IGuildUser target)
|
||||
{
|
||||
var curUser = ((SocketGuild)ctx.Guild).CurrentUser;
|
||||
var ownerId = ctx.Guild.OwnerId;
|
||||
var modMaxRole = ((IGuildUser)ctx.User).GetRoles().Max(r => r.Position);
|
||||
var targetMaxRole = target.GetRoles().Max(r => r.Position);
|
||||
var botMaxRole = curUser.GetRoles().Max(r => r.Position);
|
||||
// bot can't punish a user who is higher in the hierarchy. Discord will return 403
|
||||
// moderator can be owner, in which case role hierarchy doesn't matter
|
||||
// otherwise, moderator has to have a higher role
|
||||
if (botMaxRole <= targetMaxRole
|
||||
|| (ctx.User.Id != ownerId && targetMaxRole >= modMaxRole)
|
||||
|| target.Id == ownerId)
|
||||
{
|
||||
await Response().Error(strs.hierarchy).SendAsync();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
|
@ -44,7 +65,7 @@ public partial class Administration
|
|||
try
|
||||
{
|
||||
await _sender.Response(user)
|
||||
.Embed(CreateEmbed()
|
||||
.Embed(_sender.CreateEmbed()
|
||||
.WithErrorColor()
|
||||
.WithDescription(GetText(strs.warned_on(ctx.Guild.ToString())))
|
||||
.AddField(GetText(strs.moderator), ctx.User.ToString())
|
||||
|
@ -64,7 +85,7 @@ public partial class Administration
|
|||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Exception occured while warning a user");
|
||||
var errorEmbed = CreateEmbed()
|
||||
var errorEmbed = _sender.CreateEmbed()
|
||||
.WithErrorColor()
|
||||
.WithDescription(GetText(strs.cant_apply_punishment));
|
||||
|
||||
|
@ -75,7 +96,7 @@ public partial class Administration
|
|||
return;
|
||||
}
|
||||
|
||||
var embed = CreateEmbed().WithOkColor();
|
||||
var embed = _sender.CreateEmbed().WithOkColor();
|
||||
if (punishment is null)
|
||||
embed.WithDescription(GetText(strs.user_warned(Format.Bold(user.ToString()))));
|
||||
else
|
||||
|
@ -184,7 +205,7 @@ public partial class Administration
|
|||
.Page((warnings, page) =>
|
||||
{
|
||||
var user = (ctx.Guild as SocketGuild)?.GetUser(userId)?.ToString() ?? userId.ToString();
|
||||
var embed = CreateEmbed().WithOkColor().WithTitle(GetText(strs.warnlog_for(user)));
|
||||
var embed = _sender.CreateEmbed().WithOkColor().WithTitle(GetText(strs.warnlog_for(user)));
|
||||
|
||||
if (!warnings.Any())
|
||||
embed.WithDescription(GetText(strs.warnings_none));
|
||||
|
@ -245,7 +266,7 @@ public partial class Administration
|
|||
+ $" | {total} ({all} - {forgiven})";
|
||||
});
|
||||
|
||||
return CreateEmbed()
|
||||
return _sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.warnings_list))
|
||||
.WithDescription(string.Join("\n", ws));
|
||||
|
@ -313,7 +334,7 @@ public partial class Administration
|
|||
int number,
|
||||
AddRole _,
|
||||
IRole role,
|
||||
ParsedTimespan timespan = null)
|
||||
StoopidTime time = null)
|
||||
{
|
||||
var punish = PunishmentAction.AddRole;
|
||||
|
||||
|
@ -324,12 +345,12 @@ public partial class Administration
|
|||
return;
|
||||
}
|
||||
|
||||
var success = await _service.WarnPunish(ctx.Guild.Id, number, punish, timespan, role);
|
||||
var success = await _service.WarnPunish(ctx.Guild.Id, number, punish, time, role);
|
||||
|
||||
if (!success)
|
||||
return;
|
||||
|
||||
if (timespan is null)
|
||||
if (time is null)
|
||||
{
|
||||
await Response()
|
||||
.Confirm(strs.warn_punish_set(Format.Bold(punish.ToString()),
|
||||
|
@ -341,7 +362,7 @@ public partial class Administration
|
|||
await Response()
|
||||
.Confirm(strs.warn_punish_set_timed(Format.Bold(punish.ToString()),
|
||||
Format.Bold(number.ToString()),
|
||||
Format.Bold(timespan.Input)))
|
||||
Format.Bold(time.Input)))
|
||||
.SendAsync();
|
||||
}
|
||||
}
|
||||
|
@ -349,7 +370,7 @@ public partial class Administration
|
|||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
public async Task WarnPunish(int number, PunishmentAction punish, ParsedTimespan timespan = null)
|
||||
public async Task WarnPunish(int number, PunishmentAction punish, StoopidTime time = null)
|
||||
{
|
||||
// this should never happen. Addrole has its own method with higher priority
|
||||
// also disallow warn punishment for getting warned
|
||||
|
@ -357,15 +378,15 @@ public partial class Administration
|
|||
return;
|
||||
|
||||
// you must specify the time for timeout
|
||||
if (punish is PunishmentAction.TimeOut && timespan is null)
|
||||
if (punish is PunishmentAction.TimeOut && time is null)
|
||||
return;
|
||||
|
||||
var success = await _service.WarnPunish(ctx.Guild.Id, number, punish, timespan);
|
||||
var success = await _service.WarnPunish(ctx.Guild.Id, number, punish, time);
|
||||
|
||||
if (!success)
|
||||
return;
|
||||
|
||||
if (timespan is null)
|
||||
if (time is null)
|
||||
{
|
||||
await Response()
|
||||
.Confirm(strs.warn_punish_set(Format.Bold(punish.ToString()),
|
||||
|
@ -377,7 +398,7 @@ public partial class Administration
|
|||
await Response()
|
||||
.Confirm(strs.warn_punish_set_timed(Format.Bold(punish.ToString()),
|
||||
Format.Bold(number.ToString()),
|
||||
Format.Bold(timespan.Input)))
|
||||
Format.Bold(time.Input)))
|
||||
.SendAsync();
|
||||
}
|
||||
}
|
||||
|
@ -417,17 +438,17 @@ public partial class Administration
|
|||
[UserPerm(GuildPerm.BanMembers)]
|
||||
[BotPerm(GuildPerm.BanMembers)]
|
||||
[Priority(1)]
|
||||
public Task Ban(ParsedTimespan timespan, IUser user, [Leftover] string msg = null)
|
||||
=> Ban(timespan, user.Id, msg);
|
||||
public Task Ban(StoopidTime time, IUser user, [Leftover] string msg = null)
|
||||
=> Ban(time, user.Id, msg);
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
[BotPerm(GuildPerm.BanMembers)]
|
||||
[Priority(0)]
|
||||
public async Task Ban(ParsedTimespan timespan, ulong userId, [Leftover] string msg = null)
|
||||
public async Task Ban(StoopidTime time, ulong userId, [Leftover] string msg = null)
|
||||
{
|
||||
if (timespan.Time > TimeSpan.FromDays(49))
|
||||
if (time.Time > TimeSpan.FromDays(49))
|
||||
return;
|
||||
|
||||
var guildUser = await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(ctx.Guild.Id, userId);
|
||||
|
@ -444,7 +465,7 @@ public partial class Administration
|
|||
{
|
||||
var defaultMessage = GetText(strs.bandm(Format.Bold(ctx.Guild.Name), msg));
|
||||
var smartText =
|
||||
await _service.GetBanUserDmEmbed(Context, guildUser, defaultMessage, msg, timespan.Time);
|
||||
await _service.GetBanUserDmEmbed(Context, guildUser, defaultMessage, msg, time.Time);
|
||||
if (smartText is not null)
|
||||
await Response().User(guildUser).Text(smartText).SendAsync();
|
||||
}
|
||||
|
@ -456,14 +477,14 @@ public partial class Administration
|
|||
|
||||
var user = await ctx.Client.GetUserAsync(userId);
|
||||
var banPrune = await _service.GetBanPruneAsync(ctx.Guild.Id) ?? 7;
|
||||
await _mute.TimedBan(ctx.Guild, userId, timespan.Time, (ctx.User + " | " + msg).TrimTo(512), banPrune);
|
||||
var toSend = CreateEmbed()
|
||||
await _mute.TimedBan(ctx.Guild, userId, time.Time, (ctx.User + " | " + msg).TrimTo(512), banPrune);
|
||||
var toSend = _sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle("⛔️ " + GetText(strs.banned_user))
|
||||
.AddField(GetText(strs.username), user?.ToString() ?? userId.ToString(), true)
|
||||
.AddField("ID", userId.ToString(), true)
|
||||
.AddField(GetText(strs.duration),
|
||||
timespan.Time.ToPrettyStringHm(),
|
||||
time.Time.ToPrettyStringHm(),
|
||||
true);
|
||||
|
||||
if (dmFailed)
|
||||
|
@ -486,7 +507,7 @@ public partial class Administration
|
|||
await ctx.Guild.AddBanAsync(userId, banPrune, (ctx.User + " | " + msg).TrimTo(512));
|
||||
|
||||
await Response()
|
||||
.Embed(CreateEmbed()
|
||||
.Embed(_sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle("⛔️ " + GetText(strs.banned_user))
|
||||
.AddField("ID", userId.ToString(), true))
|
||||
|
@ -523,7 +544,7 @@ public partial class Administration
|
|||
var banPrune = await _service.GetBanPruneAsync(ctx.Guild.Id) ?? 7;
|
||||
await ctx.Guild.AddBanAsync(user, banPrune, (ctx.User + " | " + msg).TrimTo(512));
|
||||
|
||||
var toSend = CreateEmbed()
|
||||
var toSend = _sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle("⛔️ " + GetText(strs.banned_user))
|
||||
.AddField(GetText(strs.username), user.ToString(), true)
|
||||
|
@ -601,7 +622,7 @@ public partial class Administration
|
|||
[UserPerm(GuildPerm.BanMembers)]
|
||||
[BotPerm(GuildPerm.BanMembers)]
|
||||
[Priority(1)]
|
||||
public Task BanMessageTest(ParsedTimespan duration, [Leftover] string reason = null)
|
||||
public Task BanMessageTest(StoopidTime duration, [Leftover] string reason = null)
|
||||
=> InternalBanMessageTest(reason, duration.Time);
|
||||
|
||||
private async Task InternalBanMessageTest(string reason, TimeSpan? duration)
|
||||
|
@ -719,7 +740,7 @@ public partial class Administration
|
|||
{ await ctx.Guild.RemoveBanAsync(user); }
|
||||
catch { await ctx.Guild.RemoveBanAsync(user); }
|
||||
|
||||
var toSend = CreateEmbed()
|
||||
var toSend = _sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle("☣ " + GetText(strs.sb_user))
|
||||
.AddField(GetText(strs.username), user.ToString(), true)
|
||||
|
@ -774,7 +795,7 @@ public partial class Administration
|
|||
|
||||
await user.KickAsync((ctx.User + " | " + msg).TrimTo(512));
|
||||
|
||||
var toSend = CreateEmbed()
|
||||
var toSend = _sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.kicked_user))
|
||||
.AddField(GetText(strs.username), user.ToString(), true)
|
||||
|
@ -791,7 +812,7 @@ public partial class Administration
|
|||
[UserPerm(GuildPerm.ModerateMembers)]
|
||||
[BotPerm(GuildPerm.ModerateMembers)]
|
||||
[Priority(2)]
|
||||
public async Task Timeout(IUser globalUser, ParsedTimespan timespan, [Leftover] string msg = null)
|
||||
public async Task Timeout(IUser globalUser, StoopidTime time, [Leftover] string msg = null)
|
||||
{
|
||||
var user = await ctx.Guild.GetUserAsync(globalUser.Id);
|
||||
|
||||
|
@ -807,7 +828,7 @@ public partial class Administration
|
|||
{
|
||||
var dmMessage = GetText(strs.timeoutdm(Format.Bold(ctx.Guild.Name), msg));
|
||||
await _sender.Response(user)
|
||||
.Embed(CreateEmbed()
|
||||
.Embed(_sender.CreateEmbed()
|
||||
.WithPendingColor()
|
||||
.WithDescription(dmMessage))
|
||||
.SendAsync();
|
||||
|
@ -817,9 +838,9 @@ public partial class Administration
|
|||
dmFailed = true;
|
||||
}
|
||||
|
||||
await user.SetTimeOutAsync(timespan.Time);
|
||||
await user.SetTimeOutAsync(time.Time);
|
||||
|
||||
var toSend = CreateEmbed()
|
||||
var toSend = _sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle("⏳ " + GetText(strs.timedout_user))
|
||||
.AddField(GetText(strs.username), user.ToString(), true)
|
||||
|
@ -880,7 +901,7 @@ public partial class Administration
|
|||
if (string.IsNullOrWhiteSpace(missStr))
|
||||
missStr = "-";
|
||||
|
||||
var toSend = CreateEmbed()
|
||||
var toSend = _sender.CreateEmbed()
|
||||
.WithDescription(GetText(strs.mass_ban_in_progress(banning.Count)))
|
||||
.AddField(GetText(strs.invalid(missing.Count)), missStr)
|
||||
.WithPendingColor();
|
||||
|
@ -900,7 +921,7 @@ public partial class Administration
|
|||
}
|
||||
}
|
||||
|
||||
await banningMessage.ModifyAsync(x => x.Embed = CreateEmbed()
|
||||
await banningMessage.ModifyAsync(x => x.Embed = _sender.CreateEmbed()
|
||||
.WithDescription(
|
||||
GetText(strs.mass_ban_completed(
|
||||
banning.Count())))
|
||||
|
@ -928,7 +949,7 @@ public partial class Administration
|
|||
|
||||
//send a message but don't wait for it
|
||||
var banningMessageTask = Response()
|
||||
.Embed(CreateEmbed()
|
||||
.Embed(_sender.CreateEmbed()
|
||||
.WithDescription(
|
||||
GetText(strs.mass_kill_in_progress(bans.Count())))
|
||||
.AddField(GetText(strs.invalid(missing)), missStr)
|
||||
|
@ -949,7 +970,7 @@ public partial class Administration
|
|||
//wait for the message and edit it
|
||||
var banningMessage = await banningMessageTask;
|
||||
|
||||
await banningMessage.ModifyAsync(x => x.Embed = CreateEmbed()
|
||||
await banningMessage.ModifyAsync(x => x.Embed = _sender.CreateEmbed()
|
||||
.WithDescription(
|
||||
GetText(strs.mass_kill_completed(bans.Count())))
|
||||
.AddField(GetText(strs.invalid(missing)), missStr)
|
||||
|
|
|
@ -68,7 +68,7 @@ public partial class Administration
|
|||
else
|
||||
text = GetText(strs.no_vcroles);
|
||||
|
||||
await Response().Embed(CreateEmbed()
|
||||
await Response().Embed(_sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.vc_role_list))
|
||||
.WithDescription(text)).SendAsync();
|
||||
|
|
|
@ -34,7 +34,7 @@ public partial class EllieExpressions : EllieModule<EllieExpressionsService>
|
|||
var ex = await _service.AddAsync(ctx.Guild?.Id, key, message);
|
||||
|
||||
await Response()
|
||||
.Embed(CreateEmbed()
|
||||
.Embed(_sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.expr_new))
|
||||
.WithDescription($"#{new kwum(ex.Id)}")
|
||||
|
@ -104,7 +104,7 @@ public partial class EllieExpressions : EllieModule<EllieExpressionsService>
|
|||
if (ex is not null)
|
||||
{
|
||||
await Response()
|
||||
.Embed(CreateEmbed()
|
||||
.Embed(_sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.expr_edited))
|
||||
.WithDescription($"#{id}")
|
||||
|
@ -159,7 +159,7 @@ public partial class EllieExpressions : EllieModule<EllieExpressionsService>
|
|||
: " // " + string.Join(" ", ex.GetReactions())))
|
||||
.Join('\n');
|
||||
|
||||
return CreateEmbed().WithOkColor().WithTitle(GetText(strs.expressions)).WithDescription(desc);
|
||||
return _sender.CreateEmbed().WithOkColor().WithTitle(GetText(strs.expressions)).WithDescription(desc);
|
||||
})
|
||||
.SendAsync();
|
||||
}
|
||||
|
@ -179,7 +179,7 @@ public partial class EllieExpressions : EllieModule<EllieExpressionsService>
|
|||
|
||||
await Response()
|
||||
.Interaction(IsValidExprEditor() ? inter : null)
|
||||
.Embed(CreateEmbed()
|
||||
.Embed(_sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithDescription($"#{id}")
|
||||
.AddField(GetText(strs.trigger), found.Trigger.TrimTo(1024))
|
||||
|
@ -224,7 +224,7 @@ public partial class EllieExpressions : EllieModule<EllieExpressionsService>
|
|||
if (ex is not null)
|
||||
{
|
||||
await Response()
|
||||
.Embed(CreateEmbed()
|
||||
.Embed(_sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.expr_deleted))
|
||||
.WithDescription($"#{id}")
|
||||
|
@ -375,7 +375,7 @@ public partial class EllieExpressions : EllieModule<EllieExpressionsService>
|
|||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task ExprClear()
|
||||
{
|
||||
if (await PromptUserConfirmAsync(CreateEmbed()
|
||||
if (await PromptUserConfirmAsync(_sender.CreateEmbed()
|
||||
.WithTitle("Expression clear")
|
||||
.WithDescription("This will delete all expressions on this server.")))
|
||||
{
|
||||
|
|
|
@ -6,10 +6,6 @@ namespace EllieBot.Modules.Gambling.Common.AnimalRacing;
|
|||
|
||||
public sealed class AnimalRace : IDisposable
|
||||
{
|
||||
public const double BASE_MULTIPLIER = 0.87;
|
||||
public const double MAX_MULTIPLIER = 0.94;
|
||||
public const double MULTI_PER_USER = 0.005;
|
||||
|
||||
public enum Phase
|
||||
{
|
||||
WaitingForPlayers,
|
||||
|
@ -104,7 +100,7 @@ public sealed class AnimalRace : IDisposable
|
|||
foreach (var user in _users)
|
||||
{
|
||||
if (user.Bet > 0)
|
||||
await _currency.AddAsync(user.UserId, (long)(user.Bet + BASE_MULTIPLIER), new("animalrace", "refund"));
|
||||
await _currency.AddAsync(user.UserId, user.Bet, new("animalrace", "refund"));
|
||||
}
|
||||
|
||||
_ = OnStartingFailed?.Invoke(this);
|
||||
|
@ -120,7 +116,7 @@ public sealed class AnimalRace : IDisposable
|
|||
{
|
||||
foreach (var user in _users)
|
||||
{
|
||||
user.Progress += rng.Next(1, 10);
|
||||
user.Progress += rng.Next(1, 11);
|
||||
if (user.Progress >= 60)
|
||||
user.Progress = 60;
|
||||
}
|
||||
|
@ -130,15 +126,13 @@ public sealed class AnimalRace : IDisposable
|
|||
FinishedUsers.AddRange(finished);
|
||||
|
||||
_ = OnStateUpdate?.Invoke(this);
|
||||
await Task.Delay(1750);
|
||||
await Task.Delay(2500);
|
||||
}
|
||||
|
||||
if (FinishedUsers[0].Bet > 0)
|
||||
{
|
||||
Multi = FinishedUsers.Count
|
||||
* Math.Min(MAX_MULTIPLIER, BASE_MULTIPLIER + (MULTI_PER_USER * FinishedUsers.Count));
|
||||
await _currency.AddAsync(FinishedUsers[0].UserId,
|
||||
(long)(FinishedUsers[0].Bet * Multi),
|
||||
FinishedUsers[0].Bet * (_users.Count - 1),
|
||||
new("animalrace", "win"));
|
||||
}
|
||||
|
||||
|
@ -146,8 +140,6 @@ public sealed class AnimalRace : IDisposable
|
|||
});
|
||||
}
|
||||
|
||||
public double Multi { get; set; } = BASE_MULTIPLIER;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
CurrentPhase = Phase.Ended;
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace EllieBot.Modules.Gambling;
|
|||
public partial class Gambling
|
||||
{
|
||||
[Group]
|
||||
public partial class AnimalRacingCommands : GamblingModule<AnimalRaceService>
|
||||
public partial class AnimalRacingCommands : GamblingSubmodule<AnimalRaceService>
|
||||
{
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
@ -74,14 +74,10 @@ public partial class Gambling
|
|||
if (race.FinishedUsers[0].Bet > 0)
|
||||
{
|
||||
return Response()
|
||||
.Embed(CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.animal_race))
|
||||
.WithDescription(GetText(strs.animal_race_won_money(
|
||||
Format.Bold(winner.Username),
|
||||
winner.Animal.Icon,
|
||||
N(race.FinishedUsers[0].Bet * race.Multi))))
|
||||
.WithFooter($"x{race.Multi:F2}"))
|
||||
.Confirm(GetText(strs.animal_race),
|
||||
GetText(strs.animal_race_won_money(Format.Bold(winner.Username),
|
||||
winner.Animal.Icon,
|
||||
(race.FinishedUsers[0].Bet * (race.Users.Count - 1)) + CurrencySign)))
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
|
@ -117,14 +113,14 @@ public partial class Gambling
|
|||
|
||||
private async Task Ar_OnStateUpdate(AnimalRace race)
|
||||
{
|
||||
var text = $@"|🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁🔚|
|
||||
var text = $@"|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|
|
||||
{string.Join("\n", race.Users.Select(p =>
|
||||
{
|
||||
var index = race.FinishedUsers.IndexOf(p);
|
||||
var extra = index == -1 ? "" : $"#{index + 1} {(index == 0 ? "🏆" : "")}";
|
||||
return $"{(int)(p.Progress / 60f * 100),-2}%|{new string('‣', p.Progress) + p.Animal.Icon + extra}";
|
||||
}))}
|
||||
|🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁🔚|";
|
||||
|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|";
|
||||
|
||||
var msg = raceMessage;
|
||||
|
||||
|
@ -132,11 +128,11 @@ public partial class Gambling
|
|||
raceMessage = await Response().Confirm(text).SendAsync();
|
||||
else
|
||||
{
|
||||
await msg.ModifyAsync(x => x.Embed = CreateEmbed()
|
||||
.WithTitle(GetText(strs.animal_race))
|
||||
.WithDescription(text)
|
||||
.WithOkColor()
|
||||
.Build());
|
||||
await msg.ModifyAsync(x => x.Embed = _sender.CreateEmbed()
|
||||
.WithTitle(GetText(strs.animal_race))
|
||||
.WithDescription(text)
|
||||
.WithOkColor()
|
||||
.Build());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ public partial class Gambling
|
|||
{
|
||||
var bal = await _bank.GetBalanceAsync(ctx.User.Id);
|
||||
|
||||
var eb = CreateEmbed()
|
||||
var eb = _sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithDescription(GetText(strs.bank_balance(N(bal))));
|
||||
|
||||
|
@ -80,7 +80,7 @@ public partial class Gambling
|
|||
{
|
||||
var bal = await _bank.GetBalanceAsync(user.Id);
|
||||
|
||||
var eb = CreateEmbed()
|
||||
var eb = _sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithDescription(GetText(strs.bank_balance_other(user.ToString(), N(bal))));
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue