Compare commits
42 commits
Author | SHA1 | Date | |
---|---|---|---|
af71e88985 | |||
4af3a9086f | |||
2d806119b4 | |||
f68ff81dce | |||
3e5bccd215 | |||
e851c01c91 | |||
b81de1f103 | |||
d0ecff7429 | |||
21572aad19 | |||
0b3582e476 | |||
2fe1b94cea | |||
e40a458dc5 | |||
11ed2aaba8 | |||
04a22e5995 | |||
6c38d803bc | |||
66870f6859 | |||
a8bb7e650e | |||
14ac3c92bb | |||
fae15a9e0a | |||
e7cfd3a752 | |||
c5aeb43046 | |||
9f44d6a854 | |||
7da8f2c403 | |||
39297c6f83 | |||
fca083a8fe | |||
40a71c1134 | |||
dee2b04dbb | |||
ed14c8ce7e | |||
090f50b253 | |||
945725e87c | |||
c330d086b7 | |||
7f9a939285 | |||
d1bc423b99 | |||
41f1c7aa11 | |||
7c69198bd6 | |||
129ac22afc | |||
e47e619ef9 | |||
82f7c3be27 | |||
bc0dce6e88 | |||
c5b27421a3 | |||
d2f70644ef | |||
23aabd26fa |
96 changed files with 23992 additions and 680 deletions
180
CHANGELOG.md
180
CHANGELOG.md
|
@ -2,20 +2,95 @@
|
||||||
|
|
||||||
Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except date format. a-c-f-r-o
|
Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except date format. a-c-f-r-o
|
||||||
|
|
||||||
## [5.1.16] - 29.10.2024
|
## [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
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
|
|
||||||
- Added .ncanvas and related commands.
|
- Added .ncanvas and related commands.
|
||||||
- You can set pixel colors (and text) on a 500x350 canvas, pepega version of r/place
|
- You can set pixel colors (and text) on a 500x350 canvas, pepega version of r/place
|
||||||
- You use currency to set pixels.
|
- You use currency to set pixels.
|
||||||
- Commands:
|
- Commands:
|
||||||
- see the entire canvas: `.nc`
|
- see the entire canvas: `.nc`
|
||||||
- zoom: `.ncz <pos>` or `.ncz x y`
|
- zoom: `.ncz <pos>` or `.ncz x y`
|
||||||
- set pixel: `.ncsp <pos> <color> <text?>`
|
- set pixel: `.ncsp <pos> <color> <text?>`
|
||||||
- get pixel: `.ncp <pos>`
|
- get pixel: `.ncp <pos>`
|
||||||
- Owners can use .ncsetimg to set a starting image, use `.h .setimg` for instructions
|
- Owners can use .ncsetimg to set a starting image, use `.h .setimg` for instructions
|
||||||
- Owners can reset the whole canvas via `.ncreset`
|
- Owners can reset the whole canvas via `.ncreset`
|
||||||
|
|
||||||
## [5.1.15] - 21.10.2024
|
## [5.1.15] - 21.10.2024
|
||||||
|
|
||||||
|
@ -70,8 +145,9 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added `%user.displayname%` placeholder. It will show users nickname, if there is one, otherwise it will show the username.
|
- Added `%user.displayname%` placeholder. It will show users nickname, if there is one, otherwise it will show the
|
||||||
- Nickname won't be shown in bye messages.
|
username.
|
||||||
|
- Nickname won't be shown in bye messages.
|
||||||
- Added initial version of grpc api. Beta
|
- Added initial version of grpc api. Beta
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
@ -82,9 +158,9 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Youtube now always uses `yt-dlp`. Dropped support for `youtube-dl`
|
- 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.
|
- 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
|
## [5.1.10] - 24.09.2024
|
||||||
|
|
||||||
|
@ -103,26 +179,28 @@ 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 `.greettest`, and other `.*test` commands if you didn't have them enabled.
|
||||||
- Fixed `.greetdmtest` sending messages twice.
|
- Fixed `.greetdmtest` sending messages twice.
|
||||||
- Fixed a serious bug which caused greet messages to be jumbled up, and wrong ones to be sent for the wrong events.
|
- Fixed a serious bug which caused greet messages to be jumbled up, and wrong ones to be sent for the wrong events.
|
||||||
- There is no database issue, all greet messages are safe, the cache was caching any setting every 3 seconds with no regard for the type of the event
|
- There is no database issue, all greet messages are safe, the cache was caching any setting every 3 seconds with no
|
||||||
- This also caused `.greetdm` messages to not be sent if `.greet` is enabled
|
regard for the type of the event
|
||||||
- This bug was introduced in 5.1.8. PLEASE UPDATE if you are on 5.1.8
|
- 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
|
- 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
|
## [5.1.8] - 20.09.2024
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added `.leaveunkeptservers` which will make the bot leave all servers on all shards whose owners didn't run `.keep` command.
|
- Added `.leaveunkeptservers` which will make the bot leave all servers on all shards whose owners didn't run `.keep` command.
|
||||||
- This is a dangerous and irreversible command, don't use it. Meant for use on the public bot.
|
- This is a dangerous and irreversible command, don't use it. Meant for use on the public bot.
|
||||||
- `.adpl` now supports custom statuses (you no longer need to specify Playing, Watching, etc...)
|
- `.adpl` now supports custom statuses (you no longer need to specify Playing, Watching, etc...)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- `.quote` commands cleaned up and improved
|
- `.quote` commands cleaned up and improved
|
||||||
- All quote commands now start with `.q<whatever>` and follow the same naming pattern as Expression commands
|
- All quote commands now start with `.q<whatever>` and follow the same naming pattern as Expression commands
|
||||||
- `.liqu` renamed to `.qli`
|
- `.liqu` renamed to `.qli`
|
||||||
- `.quotesearch` / `.qse` is now paginated for easier searching
|
- `.quotesearch` / `.qse` is now paginated for easier searching
|
||||||
- `.whosplaying` is now paginated
|
- `.whosplaying` is now paginated
|
||||||
- `.img` is now paginated
|
- `.img` is now paginated
|
||||||
- `.setgame` renamed to`.setactivity` and now supports custom text activity. You don't have to specify playing, listening etc before the activity
|
- `.setgame` renamed to`.setactivity` and now supports custom text activity. You don't have to specify playing, listening etc before the activity
|
||||||
|
@ -142,7 +220,7 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- Removed mysql support as it didn't work for a while, and requires some special handling/maintenance
|
- 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
|
## [5.1.7] - 09.08.2024
|
||||||
|
|
||||||
|
@ -166,15 +244,16 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
|
||||||
- Possible fix for `.remind` timestamp
|
- Possible fix for `.remind` timestamp
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- Removed old bloat / semi broken / dumb commands
|
- Removed old bloat / semi broken / dumb commands
|
||||||
- `.memelist` / `.memegen` (too inconvenient to use)
|
- `.memelist` / `.memegen` (too inconvenient to use)
|
||||||
- `.activity` (useless owner-only command)
|
- `.activity` (useless owner-only command)
|
||||||
- `.rafflecur` (Just use raffle and then award manually instead)
|
- `.rafflecur` (Just use raffle and then award manually instead)
|
||||||
- `.rollduel` (we had this command?)
|
- `.rollduel` (we had this command?)
|
||||||
- You can no longer bet on `.connect4`
|
- You can no longer bet on `.connect4`
|
||||||
- `.economy` Removed.
|
- `.economy` Removed.
|
||||||
- Was buggy and didn.t really show the real state of the economy.
|
- Was buggy and didn't really show the real state of the economy.
|
||||||
- It might come back improved in the future
|
- It might come back improved in the future
|
||||||
- `.mal` Removed. Useless information / semi broken
|
- `.mal` Removed. Useless information / semi broken
|
||||||
|
|
||||||
## [5.1.5] - 01.08.2024
|
## [5.1.5] - 01.08.2024
|
||||||
|
@ -182,9 +261,9 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added: Added a `.afk <msg>?` command which sets an afk message which will trigger whenever someone pings you
|
- 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
|
- 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 specified message will be prefixed with "The user is afk: "
|
||||||
- The afk message will disappear 30 seconds after being triggered
|
- The afk message will disappear 30 seconds after being triggered
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -192,7 +271,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 bet descriptions to include 'all' 'half' usage instructions
|
||||||
- Updated some command strings
|
- Updated some command strings
|
||||||
- dev: Vastly simplified marmalade creation using dotnet templates, docs updated
|
- 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
|
### Fixed
|
||||||
|
|
||||||
|
@ -211,8 +290,10 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
|
||||||
- Added Clubs rank in the leaderboard to `.clubinfo`
|
- Added Clubs rank in the leaderboard to `.clubinfo`
|
||||||
- Bot owners can now check other people's bank balance (Not server owners, only bot owner, the person who is hosting the bot)
|
- Bot owners can now check other people's bank balance (Not server owners, only bot owner, the person who is hosting the bot)
|
||||||
- You can now send multiple waifu gifts at once to waifus. For example `.waifugift 3xRose @user` will give that user 3 roses
|
- You can now send multiple waifu gifts at once to waifus. For example `.waifugift 3xRose @user` will give that user 3 roses
|
||||||
- The format is `<NUMBER>x<ITEM>`, no spaces
|
- The format is `<NUMBER>x<ITEM>`, no spaces
|
||||||
- Added `.boosttest` command
|
- Added `.boosttest` command
|
||||||
|
- Added support for any openai compatible api for the chatterbot feature change:
|
||||||
|
- Changed games.yml to allow input of the apiUrl (needs to be openai compatible) and modelName as a string.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -254,9 +335,9 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added `.honeypot` command, which automatically softbans (ban and immediate unban) any user who posts in that channel.
|
- 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
|
- Useful to auto softban bots who spam every channel upon joining
|
||||||
- Users who run commands or expressions won't be softbanned.
|
- Users who run commands or expressions won't be softbanned.
|
||||||
- Users who have ban member permissions are also excluded.
|
- Users who have ban member permissions are also excluded.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
@ -270,7 +351,6 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
|
||||||
- Added support for `gpt-4o` in `data/games.yml`
|
- Added support for `gpt-4o` in `data/games.yml`
|
||||||
- Added EllieAiToken to `creds.yml`
|
- Added EllieAiToken to `creds.yml`
|
||||||
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Remind will now show a timestamp tag for durations
|
- Remind will now show a timestamp tag for durations
|
||||||
|
@ -283,8 +363,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 xp bg buy button not working, and possibly some other buttons too
|
||||||
- Fixed shopbuy %user% placeholders and updated help text
|
- Fixed shopbuy %user% placeholders and updated help text
|
||||||
- All 'feed overloads should now work"
|
- All .feed overloads should now work"
|
||||||
- `'xpexclude` should will now work with forums too. If you exclude a forum you won't be able to gain xp in any of the threads.
|
- `.xpexclude` should will now work with forums too. If you exclude a forum you won't be able to gain xp in any of the threads.
|
||||||
- Fixed remind not showing correct time
|
- Fixed remind not showing correct time
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
@ -296,12 +376,14 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added `'setserverbanner` and `'setservericon` commands
|
- Added `.setserverbanner` and `.setservericon` commands
|
||||||
- Added overloads section to `'h command` which will show you all versions of command usage with param names
|
- Added overloads section to `.h command` which will show you all versions of command usage with param names
|
||||||
- You can now check commands for submodules, for example `'cmds SelfAssignedRoles` will show brief help for each of the commands in that submodule
|
- You can now check commands for submodules, for example `.cmds SelfAssignedRoles` will show brief help for each of the
|
||||||
- Added dropdown menus for 'mdls and 'cmds (both module and group versions) which will give you the option to see more detailed help for each specific module, group or command respectively
|
commands in that submodule
|
||||||
|
- Added dropdown menus for .mdls and .cmds (both module and group versions) which will give you the option to see more
|
||||||
|
detailed help for each specific module, group or command respectively
|
||||||
- Self-Hosters only:
|
- Self-Hosters only:
|
||||||
- Added a dangerous cleanup command that you don't have to know about
|
- Added a dangerous cleanup command that you don't have to know about
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -309,7 +391,7 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- `'verbose` will now be respected for expression errors
|
- `.verbose` will now be respected for expression errors
|
||||||
- Using `'pick` will now correctly show the name of the user who picked the currency
|
- Using `.pick` will now correctly show the name of the user who picked the currency
|
||||||
- Fixed `'h` not working on some commands
|
- Fixed `.h` not working on some commands
|
||||||
- `'langset` and `'langsetd` should no longer allow unsupported languages and nonsense to be typed in
|
- `.langset` and `.langsetd` should no longer allow unsupported languages and nonsense to be typed in
|
||||||
|
|
|
@ -11,9 +11,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||||
Dockerfile = Dockerfile
|
Dockerfile = Dockerfile
|
||||||
ellie-menu.ps1 = ellie-menu.ps1
|
ellie-menu.ps1 = ellie-menu.ps1
|
||||||
LICENSE = LICENSE
|
LICENSE = LICENSE
|
||||||
migrate.ps1 = migrate.ps1
|
|
||||||
README.md = README.md
|
README.md = README.md
|
||||||
remove-migrations.ps1 = remove-migrations.ps1
|
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EllieBot", "src\EllieBot\EllieBot.csproj", "{4D9001F7-B3E8-48FE-97AA-CFD36DA65A64}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EllieBot", "src\EllieBot\EllieBot.csproj", "{4D9001F7-B3E8-48FE-97AA-CFD36DA65A64}"
|
||||||
|
@ -30,7 +28,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ellie.Marmalade", "src\Elli
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EllieBot.Voice", "src\EllieBot.Voice\EllieBot.Voice.csproj", "{1D93CE3C-80B4-49C7-A9A2-99988920AAEC}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EllieBot.Voice", "src\EllieBot.Voice\EllieBot.Voice.csproj", "{1D93CE3C-80B4-49C7-A9A2-99988920AAEC}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EllieBot.GrpcApiBase", "src\EllieBot.GrpcApiBase\EllieBot.GrpcApiBase.csproj", "{3B71F0BF-AE6C-480C-AB88-FCE23EDC7D91}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EllieBot.GrpcApiBase", "src\EllieBot.GrpcApiBase\EllieBot.GrpcApiBase.csproj", "{3B71F0BF-AE6C-480C-AB88-FCE23EDC7D91}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
60
src/EllieBot.GrpcApiBase/protos/fin.proto
Normal file
60
src/EllieBot.GrpcApiBase/protos/fin.proto
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
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;
|
||||||
|
}
|
120
src/EllieBot.GrpcApiBase/protos/xp.proto
Normal file
120
src/EllieBot.GrpcApiBase/protos/xp.proto
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
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;
|
||||||
|
}
|
|
@ -62,6 +62,7 @@ public abstract class EllieContext : DbContext
|
||||||
public DbSet<ArchivedTodoListModel> TodosArchive { get; set; }
|
public DbSet<ArchivedTodoListModel> TodosArchive { get; set; }
|
||||||
public DbSet<HoneypotChannel> HoneyPotChannels { get; set; }
|
public DbSet<HoneypotChannel> HoneyPotChannels { get; set; }
|
||||||
|
|
||||||
|
|
||||||
// public DbSet<GuildColors> GuildColors { get; set; }
|
// public DbSet<GuildColors> GuildColors { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
@ -73,6 +74,30 @@ public abstract class EllieContext : DbContext
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
|
#region Rakeback
|
||||||
|
|
||||||
|
modelBuilder.Entity<Rakeback>()
|
||||||
|
.HasKey(x => x.UserId);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region UserBetStats
|
||||||
|
|
||||||
|
modelBuilder.Entity<UserBetStats>()
|
||||||
|
.HasIndex(x => new { x.UserId, x.Game })
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Flag Translate
|
||||||
|
|
||||||
|
modelBuilder.Entity<FlagTranslateChannel>()
|
||||||
|
.HasIndex(x => new { x.GuildId, x.ChannelId })
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region NCanvas
|
#region NCanvas
|
||||||
|
|
||||||
modelBuilder.Entity<NCPixel>()
|
modelBuilder.Entity<NCPixel>()
|
||||||
|
@ -299,10 +324,10 @@ public abstract class EllieContext : DbContext
|
||||||
var selfassignableRolesEntity = modelBuilder.Entity<SelfAssignedRole>();
|
var selfassignableRolesEntity = modelBuilder.Entity<SelfAssignedRole>();
|
||||||
|
|
||||||
selfassignableRolesEntity.HasIndex(s => new
|
selfassignableRolesEntity.HasIndex(s => new
|
||||||
{
|
{
|
||||||
s.GuildId,
|
s.GuildId,
|
||||||
s.RoleId
|
s.RoleId
|
||||||
})
|
})
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
selfassignableRolesEntity.Property(x => x.Group).HasDefaultValue(0);
|
selfassignableRolesEntity.Property(x => x.Group).HasDefaultValue(0);
|
||||||
|
@ -376,10 +401,10 @@ public abstract class EllieContext : DbContext
|
||||||
|
|
||||||
var xps = modelBuilder.Entity<UserXpStats>();
|
var xps = modelBuilder.Entity<UserXpStats>();
|
||||||
xps.HasIndex(x => new
|
xps.HasIndex(x => new
|
||||||
{
|
{
|
||||||
x.UserId,
|
x.UserId,
|
||||||
x.GuildId
|
x.GuildId
|
||||||
})
|
})
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
xps.HasIndex(x => x.UserId);
|
xps.HasIndex(x => x.UserId);
|
||||||
|
@ -425,9 +450,9 @@ public abstract class EllieContext : DbContext
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
ci.HasIndex(x => new
|
ci.HasIndex(x => new
|
||||||
{
|
{
|
||||||
x.Name
|
x.Name
|
||||||
})
|
})
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -546,10 +571,10 @@ public abstract class EllieContext : DbContext
|
||||||
.IsUnique(false);
|
.IsUnique(false);
|
||||||
|
|
||||||
rr2.HasIndex(x => new
|
rr2.HasIndex(x => new
|
||||||
{
|
{
|
||||||
x.MessageId,
|
x.MessageId,
|
||||||
x.Emote
|
x.Emote
|
||||||
})
|
})
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -624,11 +649,11 @@ public abstract class EllieContext : DbContext
|
||||||
{
|
{
|
||||||
// user can own only one of each item
|
// user can own only one of each item
|
||||||
x.HasIndex(model => new
|
x.HasIndex(model => new
|
||||||
{
|
{
|
||||||
model.UserId,
|
model.UserId,
|
||||||
model.ItemType,
|
model.ItemType,
|
||||||
model.ItemKey
|
model.ItemKey
|
||||||
})
|
})
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -653,10 +678,10 @@ public abstract class EllieContext : DbContext
|
||||||
#region Sticky Roles
|
#region Sticky Roles
|
||||||
|
|
||||||
modelBuilder.Entity<StickyRole>(sr => sr.HasIndex(x => new
|
modelBuilder.Entity<StickyRole>(sr => sr.HasIndex(x => new
|
||||||
{
|
{
|
||||||
x.GuildId,
|
x.GuildId,
|
||||||
x.UserId
|
x.UserId
|
||||||
})
|
})
|
||||||
.IsUnique());
|
.IsUnique());
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -701,10 +726,10 @@ public abstract class EllieContext : DbContext
|
||||||
|
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.Entity<GreetSettings>(gs => gs.HasIndex(x => new
|
.Entity<GreetSettings>(gs => gs.HasIndex(x => new
|
||||||
{
|
{
|
||||||
x.GuildId,
|
x.GuildId,
|
||||||
x.GreetType
|
x.GreetType
|
||||||
})
|
})
|
||||||
.IsUnique());
|
.IsUnique());
|
||||||
|
|
||||||
modelBuilder.Entity<GreetSettings>(gs =>
|
modelBuilder.Entity<GreetSettings>(gs =>
|
||||||
|
|
|
@ -25,7 +25,6 @@ public static class DiscordUserExtensions
|
||||||
{
|
{
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
Username = username,
|
Username = username,
|
||||||
Discriminator = discrim,
|
|
||||||
AvatarId = avatarId,
|
AvatarId = avatarId,
|
||||||
TotalXp = 0,
|
TotalXp = 0,
|
||||||
CurrencyAmount = 0
|
CurrencyAmount = 0
|
||||||
|
@ -33,7 +32,6 @@ public static class DiscordUserExtensions
|
||||||
old => new()
|
old => new()
|
||||||
{
|
{
|
||||||
Username = username,
|
Username = username,
|
||||||
Discriminator = discrim,
|
|
||||||
AvatarId = avatarId
|
AvatarId = avatarId
|
||||||
},
|
},
|
||||||
() => new()
|
() => new()
|
||||||
|
@ -49,8 +47,7 @@ public static class DiscordUserExtensions
|
||||||
() => new()
|
() => new()
|
||||||
{
|
{
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
Username = "Unknown",
|
Username = "??Unknown",
|
||||||
Discriminator = "????",
|
|
||||||
AvatarId = string.Empty,
|
AvatarId = string.Empty,
|
||||||
TotalXp = 0,
|
TotalXp = 0,
|
||||||
CurrencyAmount = 0
|
CurrencyAmount = 0
|
||||||
|
|
|
@ -44,9 +44,6 @@ public static class UserXpExtensions
|
||||||
.CountAsyncLinqToDB()
|
.CountAsyncLinqToDB()
|
||||||
+ 1;
|
+ 1;
|
||||||
|
|
||||||
public static void ResetGuildUserXp(this DbSet<UserXpStats> xps, ulong userId, ulong guildId)
|
|
||||||
=> xps.Delete(x => x.UserId == userId && x.GuildId == guildId);
|
|
||||||
|
|
||||||
public static void ResetGuildXp(this DbSet<UserXpStats> xps, ulong guildId)
|
public static void ResetGuildXp(this DbSet<UserXpStats> xps, ulong guildId)
|
||||||
=> xps.Delete(x => x.GuildId == guildId);
|
=> xps.Delete(x => x.GuildId == guildId);
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ public class DiscordUser : DbEntity
|
||||||
{
|
{
|
||||||
public ulong UserId { get; set; }
|
public ulong UserId { get; set; }
|
||||||
public string Username { get; set; }
|
public string Username { get; set; }
|
||||||
public string Discriminator { get; set; }
|
// public string Discriminator { get; set; }
|
||||||
public string AvatarId { get; set; }
|
public string AvatarId { get; set; }
|
||||||
|
|
||||||
public int? ClubId { get; set; }
|
public int? ClubId { get; set; }
|
||||||
|
@ -26,9 +26,6 @@ public class DiscordUser : DbEntity
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(Discriminator) || Discriminator == "0000")
|
return Username;
|
||||||
return Username;
|
|
||||||
|
|
||||||
return Username + "#" + Discriminator;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
8
src/EllieBot/Db/Models/FlagTranslateChannel.cs
Normal file
8
src/EllieBot/Db/Models/FlagTranslateChannel.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#nullable disable
|
||||||
|
namespace EllieBot.Db.Models;
|
||||||
|
|
||||||
|
public class FlagTranslateChannel : DbEntity
|
||||||
|
{
|
||||||
|
public ulong GuildId { get; set; }
|
||||||
|
public ulong ChannelId { get; set; }
|
||||||
|
}
|
|
@ -1,8 +1,12 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace EllieBot.Db.Models;
|
namespace EllieBot.Db.Models;
|
||||||
|
|
||||||
public class PatronUser
|
public class PatronUser
|
||||||
{
|
{
|
||||||
|
// [Key]
|
||||||
|
// public int Id { get; set; }
|
||||||
public string UniquePlatformUserId { get; set; }
|
public string UniquePlatformUserId { get; set; }
|
||||||
public ulong UserId { get; set; }
|
public ulong UserId { get; set; }
|
||||||
public int AmountCents { get; set; }
|
public int AmountCents { get; set; }
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>true</ImplicitUsings>
|
<ImplicitUsings>true</ImplicitUsings>
|
||||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||||
<Version>5.1.16</Version>
|
<Version>5.1.20</Version>
|
||||||
|
|
||||||
<!-- Output/build -->
|
<!-- Output/build -->
|
||||||
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
||||||
|
|
|
@ -5,6 +5,11 @@ namespace EllieBot.Migrations;
|
||||||
|
|
||||||
public static class MigrationQueries
|
public static class MigrationQueries
|
||||||
{
|
{
|
||||||
|
public static void UpdateUsernames(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.Sql("UPDATE DiscordUser SET Username = '??' || Username WHERE Discriminator = '????';");
|
||||||
|
}
|
||||||
|
|
||||||
public static void MigrateRero(MigrationBuilder migrationBuilder)
|
public static void MigrateRero(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
if (migrationBuilder.IsSqlite())
|
if (migrationBuilder.IsSqlite())
|
||||||
|
|
3851
src/EllieBot/Migrations/PostgreSql/20241102022956_no-discrim-and-flag-translate.Designer.cs
generated
Normal file
3851
src/EllieBot/Migrations/PostgreSql/20241102022956_no-discrim-and-flag-translate.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,56 @@
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace EllieBot.Migrations.PostgreSql
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class nodiscrimandflagtranslate : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
MigrationQueries.UpdateUsernames(migrationBuilder);
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "discriminator",
|
||||||
|
table: "discorduser");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "flagtranslatechannel",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||||
|
channelid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||||
|
dateadded = table.Column<DateTime>(type: "timestamp without time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_flagtranslatechannel", x => x.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_flagtranslatechannel_guildid_channelid",
|
||||||
|
table: "flagtranslatechannel",
|
||||||
|
columns: new[] { "guildid", "channelid" },
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "flagtranslatechannel");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "discriminator",
|
||||||
|
table: "discorduser",
|
||||||
|
type: "text",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3902
src/EllieBot/Migrations/PostgreSql/20241105024753_betstats.Designer.cs
generated
Normal file
3902
src/EllieBot/Migrations/PostgreSql/20241105024753_betstats.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,48 @@
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3919
src/EllieBot/Migrations/PostgreSql/20241107051622_rakeback.Designer.cs
generated
Normal file
3919
src/EllieBot/Migrations/PostgreSql/20241107051622_rakeback.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,33 @@
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
using System;
|
using System;
|
||||||
|
using EllieBot.Db;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
using EllieBot.Db;
|
|
||||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
@ -751,10 +751,6 @@ namespace EllieBot.Migrations.PostgreSql
|
||||||
.HasColumnType("timestamp without time zone")
|
.HasColumnType("timestamp without time zone")
|
||||||
.HasColumnName("dateadded");
|
.HasColumnName("dateadded");
|
||||||
|
|
||||||
b.Property<string>("Discriminator")
|
|
||||||
.HasColumnType("text")
|
|
||||||
.HasColumnName("discriminator");
|
|
||||||
|
|
||||||
b.Property<bool>("IsClubAdmin")
|
b.Property<bool>("IsClubAdmin")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("boolean")
|
.HasColumnType("boolean")
|
||||||
|
@ -805,6 +801,57 @@ namespace EllieBot.Migrations.PostgreSql
|
||||||
b.ToTable("discorduser", (string)null);
|
b.ToTable("discorduser", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EllieBot.Db.Models.EllieExpression", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<bool>("AllowTarget")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("allowtarget");
|
||||||
|
|
||||||
|
b.Property<bool>("AutoDeleteTrigger")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("autodeletetrigger");
|
||||||
|
|
||||||
|
b.Property<bool>("ContainsAnywhere")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("containsanywhere");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DateAdded")
|
||||||
|
.HasColumnType("timestamp without time zone")
|
||||||
|
.HasColumnName("dateadded");
|
||||||
|
|
||||||
|
b.Property<bool>("DmResponse")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("dmresponse");
|
||||||
|
|
||||||
|
b.Property<decimal?>("GuildId")
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("guildid");
|
||||||
|
|
||||||
|
b.Property<string>("Reactions")
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("reactions");
|
||||||
|
|
||||||
|
b.Property<string>("Response")
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("response");
|
||||||
|
|
||||||
|
b.Property<string>("Trigger")
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("trigger");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_expressions");
|
||||||
|
|
||||||
|
b.ToTable("expressions", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("EllieBot.Db.Models.ExcludedItem", b =>
|
modelBuilder.Entity("EllieBot.Db.Models.ExcludedItem", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
@ -998,6 +1045,37 @@ namespace EllieBot.Migrations.PostgreSql
|
||||||
b.ToTable("filteredword", (string)null);
|
b.ToTable("filteredword", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EllieBot.Db.Models.FlagTranslateChannel", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<decimal>("ChannelId")
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("channelid");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DateAdded")
|
||||||
|
.HasColumnType("timestamp without time zone")
|
||||||
|
.HasColumnName("dateadded");
|
||||||
|
|
||||||
|
b.Property<decimal>("GuildId")
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("guildid");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_flagtranslatechannel");
|
||||||
|
|
||||||
|
b.HasIndex("GuildId", "ChannelId")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("ix_flagtranslatechannel_guildid_channelid");
|
||||||
|
|
||||||
|
b.ToTable("flagtranslatechannel", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("EllieBot.Db.Models.FollowedStream", b =>
|
modelBuilder.Entity("EllieBot.Db.Models.FollowedStream", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
@ -1673,57 +1751,6 @@ namespace EllieBot.Migrations.PostgreSql
|
||||||
b.ToTable("ncpixel", (string)null);
|
b.ToTable("ncpixel", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("EllieBot.Db.Models.EllieExpression", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer")
|
|
||||||
.HasColumnName("id");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<bool>("AllowTarget")
|
|
||||||
.HasColumnType("boolean")
|
|
||||||
.HasColumnName("allowtarget");
|
|
||||||
|
|
||||||
b.Property<bool>("AutoDeleteTrigger")
|
|
||||||
.HasColumnType("boolean")
|
|
||||||
.HasColumnName("autodeletetrigger");
|
|
||||||
|
|
||||||
b.Property<bool>("ContainsAnywhere")
|
|
||||||
.HasColumnType("boolean")
|
|
||||||
.HasColumnName("containsanywhere");
|
|
||||||
|
|
||||||
b.Property<DateTime?>("DateAdded")
|
|
||||||
.HasColumnType("timestamp without time zone")
|
|
||||||
.HasColumnName("dateadded");
|
|
||||||
|
|
||||||
b.Property<bool>("DmResponse")
|
|
||||||
.HasColumnType("boolean")
|
|
||||||
.HasColumnName("dmresponse");
|
|
||||||
|
|
||||||
b.Property<decimal?>("GuildId")
|
|
||||||
.HasColumnType("numeric(20,0)")
|
|
||||||
.HasColumnName("guildid");
|
|
||||||
|
|
||||||
b.Property<string>("Reactions")
|
|
||||||
.HasColumnType("text")
|
|
||||||
.HasColumnName("reactions");
|
|
||||||
|
|
||||||
b.Property<string>("Response")
|
|
||||||
.HasColumnType("text")
|
|
||||||
.HasColumnName("response");
|
|
||||||
|
|
||||||
b.Property<string>("Trigger")
|
|
||||||
.HasColumnType("text")
|
|
||||||
.HasColumnName("trigger");
|
|
||||||
|
|
||||||
b.HasKey("Id")
|
|
||||||
.HasName("pk_expressions");
|
|
||||||
|
|
||||||
b.ToTable("expressions", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("EllieBot.Db.Models.PatronUser", b =>
|
modelBuilder.Entity("EllieBot.Db.Models.PatronUser", b =>
|
||||||
{
|
{
|
||||||
b.Property<decimal>("UserId")
|
b.Property<decimal>("UserId")
|
||||||
|
@ -3200,6 +3227,74 @@ namespace EllieBot.Migrations.PostgreSql
|
||||||
b.ToTable("greetsettings", (string)null);
|
b.ToTable("greetsettings", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EllieBot.Services.Rakeback", b =>
|
||||||
|
{
|
||||||
|
b.Property<decimal>("UserId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("userid");
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasColumnType("numeric")
|
||||||
|
.HasColumnName("amount");
|
||||||
|
|
||||||
|
b.HasKey("UserId")
|
||||||
|
.HasName("pk_rakeback");
|
||||||
|
|
||||||
|
b.ToTable("rakeback", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EllieBot.Services.UserBetStats", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("Game")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("game");
|
||||||
|
|
||||||
|
b.Property<long>("LoseCount")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("losecount");
|
||||||
|
|
||||||
|
b.Property<long>("MaxBet")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("maxbet");
|
||||||
|
|
||||||
|
b.Property<long>("MaxWin")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("maxwin");
|
||||||
|
|
||||||
|
b.Property<decimal>("PaidOut")
|
||||||
|
.HasColumnType("numeric")
|
||||||
|
.HasColumnName("paidout");
|
||||||
|
|
||||||
|
b.Property<decimal>("TotalBet")
|
||||||
|
.HasColumnType("numeric")
|
||||||
|
.HasColumnName("totalbet");
|
||||||
|
|
||||||
|
b.Property<decimal>("UserId")
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("userid");
|
||||||
|
|
||||||
|
b.Property<long>("WinCount")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("wincount");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_userbetstats");
|
||||||
|
|
||||||
|
b.HasIndex("UserId", "Game")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("ix_userbetstats_userid_game");
|
||||||
|
|
||||||
|
b.ToTable("userbetstats", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("EllieBot.Db.Models.AntiAltSetting", b =>
|
modelBuilder.Entity("EllieBot.Db.Models.AntiAltSetting", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("EllieBot.Db.Models.GuildConfig", null)
|
b.HasOne("EllieBot.Db.Models.GuildConfig", null)
|
||||||
|
|
2973
src/EllieBot/Migrations/Sqlite/20241102022949_no-discrim-and-flag-translate.Designer.cs
generated
Normal file
2973
src/EllieBot/Migrations/Sqlite/20241102022949_no-discrim-and-flag-translate.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,55 @@
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace EllieBot.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class nodiscrimandflagtranslate : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
MigrationQueries.UpdateUsernames(migrationBuilder);
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Discriminator",
|
||||||
|
table: "DiscordUser");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "FlagTranslateChannel",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||||
|
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||||
|
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_FlagTranslateChannel", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_FlagTranslateChannel_GuildId_ChannelId",
|
||||||
|
table: "FlagTranslateChannel",
|
||||||
|
columns: new[] { "GuildId", "ChannelId" },
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "FlagTranslateChannel");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Discriminator",
|
||||||
|
table: "DiscordUser",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3011
src/EllieBot/Migrations/Sqlite/20241105024653_betstats.Designer.cs
generated
Normal file
3011
src/EllieBot/Migrations/Sqlite/20241105024653_betstats.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
47
src/EllieBot/Migrations/Sqlite/20241105024653_betstats.cs
Normal file
47
src/EllieBot/Migrations/Sqlite/20241105024653_betstats.cs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3025
src/EllieBot/Migrations/Sqlite/20241107051525_rakeback.Designer.cs
generated
Normal file
3025
src/EllieBot/Migrations/Sqlite/20241107051525_rakeback.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
34
src/EllieBot/Migrations/Sqlite/20241107051525_rakeback.cs
Normal file
34
src/EllieBot/Migrations/Sqlite/20241107051525_rakeback.cs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
using System;
|
using System;
|
||||||
|
using EllieBot.Db;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
using EllieBot.Db;
|
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
@ -560,9 +560,6 @@ namespace EllieBot.Migrations
|
||||||
b.Property<DateTime?>("DateAdded")
|
b.Property<DateTime?>("DateAdded")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<string>("Discriminator")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<bool>("IsClubAdmin")
|
b.Property<bool>("IsClubAdmin")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("INTEGER")
|
.HasColumnType("INTEGER")
|
||||||
|
@ -601,6 +598,44 @@ namespace EllieBot.Migrations
|
||||||
b.ToTable("DiscordUser");
|
b.ToTable("DiscordUser");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EllieBot.Db.Models.EllieExpression", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("AllowTarget")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("AutoDeleteTrigger")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("ContainsAnywhere")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DateAdded")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("DmResponse")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<ulong?>("GuildId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Reactions")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Response")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Trigger")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Expressions");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("EllieBot.Db.Models.ExcludedItem", b =>
|
modelBuilder.Entity("EllieBot.Db.Models.ExcludedItem", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
@ -743,6 +778,29 @@ namespace EllieBot.Migrations
|
||||||
b.ToTable("FilteredWord");
|
b.ToTable("FilteredWord");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EllieBot.Db.Models.FlagTranslateChannel", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<ulong>("ChannelId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DateAdded")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<ulong>("GuildId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("GuildId", "ChannelId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("FlagTranslateChannel");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("EllieBot.Db.Models.FollowedStream", b =>
|
modelBuilder.Entity("EllieBot.Db.Models.FollowedStream", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
@ -1216,73 +1274,35 @@ namespace EllieBot.Migrations
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("EllieBot.Db.Models.NCPixel", b =>
|
modelBuilder.Entity("EllieBot.Db.Models.NCPixel", b =>
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<uint>("Color")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<ulong>("OwnerId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<int>("Position")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<long>("Price")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("Text")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasAlternateKey("Position");
|
|
||||||
|
|
||||||
b.HasIndex("OwnerId");
|
|
||||||
|
|
||||||
b.ToTable("NCPixel");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("EllieBot.Db.Models.EllieExpression", b =>
|
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<bool>("AllowTarget")
|
b.Property<uint>("Color")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<bool>("AutoDeleteTrigger")
|
b.Property<ulong>("OwnerId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<bool>("ContainsAnywhere")
|
b.Property<int>("Position")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<DateTime?>("DateAdded")
|
b.Property<long>("Price")
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<bool>("DmResponse")
|
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<ulong?>("GuildId")
|
b.Property<string>("Text")
|
||||||
.HasColumnType("INTEGER");
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
b.Property<string>("Reactions")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Response")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Trigger")
|
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.ToTable("Expressions");
|
b.HasAlternateKey("Position");
|
||||||
|
|
||||||
|
b.HasIndex("OwnerId");
|
||||||
|
|
||||||
|
b.ToTable("NCPixel");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("EllieBot.Db.Models.PatronUser", b =>
|
modelBuilder.Entity("EllieBot.Db.Models.PatronUser", b =>
|
||||||
|
@ -2379,6 +2399,58 @@ namespace EllieBot.Migrations
|
||||||
b.ToTable("GreetSettings");
|
b.ToTable("GreetSettings");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EllieBot.Services.Rakeback", b =>
|
||||||
|
{
|
||||||
|
b.Property<ulong>("UserId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("UserId");
|
||||||
|
|
||||||
|
b.ToTable("Rakeback");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EllieBot.Services.UserBetStats", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Game")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long>("LoseCount")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long>("MaxBet")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long>("MaxWin")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<decimal>("PaidOut")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal>("TotalBet")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<ulong>("UserId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long>("WinCount")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId", "Game")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("UserBetStats");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("EllieBot.Db.Models.AntiAltSetting", b =>
|
modelBuilder.Entity("EllieBot.Db.Models.AntiAltSetting", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("EllieBot.Db.Models.GuildConfig", null)
|
b.HasOne("EllieBot.Db.Models.GuildConfig", null)
|
||||||
|
|
|
@ -25,6 +25,13 @@ public partial class Administration
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the user can't aar the role which is greater or equal to the bot's highest role
|
||||||
|
if (role.Position >= ((SocketGuild)ctx.Guild).CurrentUser.GetRoles().Max(x => x.Position))
|
||||||
|
{
|
||||||
|
await Response().Error(strs.hierarchy).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var roles = await _service.ToggleAarAsync(ctx.Guild.Id, role.Id);
|
var roles = await _service.ToggleAarAsync(ctx.Guild.Id, role.Id);
|
||||||
if (roles.Count == 0)
|
if (roles.Count == 0)
|
||||||
await Response().Confirm(strs.aar_disabled).SendAsync();
|
await Response().Confirm(strs.aar_disabled).SendAsync();
|
||||||
|
|
|
@ -339,7 +339,7 @@ public class GreetService : IEService, IReadyExecutor
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Error(ex, "Error sending greet dm");
|
Log.Warning(ex, "Unable to send Greet DM. Probably the user has closed DMs");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,6 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, IEService
|
||||||
if (server.OwnerId != _client.CurrentUser.Id)
|
if (server.OwnerId != _client.CurrentUser.Id)
|
||||||
{
|
{
|
||||||
await server.LeaveAsync();
|
await server.LeaveAsync();
|
||||||
Log.Information("Left server {Name} [{Id}]", server.Name, server.Id);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -453,7 +452,6 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, IEService
|
||||||
{
|
{
|
||||||
x.UserId,
|
x.UserId,
|
||||||
x.Username,
|
x.Username,
|
||||||
x.Discriminator
|
|
||||||
})
|
})
|
||||||
.Where(x => users.Select(y => y.Id).Contains(x.UserId))
|
.Where(x => users.Select(y => y.Id).Contains(x.UserId))
|
||||||
.ToArrayAsyncEF();
|
.ToArrayAsyncEF();
|
||||||
|
@ -465,12 +463,11 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, IEService
|
||||||
UserId = x.Id,
|
UserId = x.Id,
|
||||||
AvatarId = x.AvatarId,
|
AvatarId = x.AvatarId,
|
||||||
Username = x.Username,
|
Username = x.Username,
|
||||||
Discriminator = x.Discriminator
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var added = (await ctx.BulkCopyAsync(usersToAdd)).RowsCopied;
|
var added = (await ctx.BulkCopyAsync(usersToAdd)).RowsCopied;
|
||||||
var toUpdateUserIds = presentDbUsers
|
var toUpdateUserIds = presentDbUsers
|
||||||
.Where(x => x.Username == "Unknown" && x.Discriminator == "????")
|
.Where(x => x.Username.StartsWith("??"))
|
||||||
.Select(x => x.UserId)
|
.Select(x => x.UserId)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
|
@ -481,7 +478,6 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, IEService
|
||||||
.UpdateAsync(x => new DiscordUser()
|
.UpdateAsync(x => new DiscordUser()
|
||||||
{
|
{
|
||||||
Username = user.Username,
|
Username = user.Username,
|
||||||
Discriminator = user.Discriminator,
|
|
||||||
|
|
||||||
// .award tends to set AvatarId and DateAdded to NULL, so account for that.
|
// .award tends to set AvatarId and DateAdded to NULL, so account for that.
|
||||||
AvatarId = user.AvatarId,
|
AvatarId = user.AvatarId,
|
||||||
|
|
|
@ -6,6 +6,10 @@ namespace EllieBot.Modules.Gambling.Common.AnimalRacing;
|
||||||
|
|
||||||
public sealed class AnimalRace : IDisposable
|
public sealed class AnimalRace : IDisposable
|
||||||
{
|
{
|
||||||
|
public const double BASE_MULTIPLIER = 0.82;
|
||||||
|
public const double MAX_MULTIPLIER = 0.94;
|
||||||
|
public const double MULTI_PER_USER = 0.01;
|
||||||
|
|
||||||
public enum Phase
|
public enum Phase
|
||||||
{
|
{
|
||||||
WaitingForPlayers,
|
WaitingForPlayers,
|
||||||
|
@ -100,7 +104,7 @@ public sealed class AnimalRace : IDisposable
|
||||||
foreach (var user in _users)
|
foreach (var user in _users)
|
||||||
{
|
{
|
||||||
if (user.Bet > 0)
|
if (user.Bet > 0)
|
||||||
await _currency.AddAsync(user.UserId, user.Bet, new("animalrace", "refund"));
|
await _currency.AddAsync(user.UserId, (long)(user.Bet + BASE_MULTIPLIER), new("animalrace", "refund"));
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = OnStartingFailed?.Invoke(this);
|
_ = OnStartingFailed?.Invoke(this);
|
||||||
|
@ -116,7 +120,7 @@ public sealed class AnimalRace : IDisposable
|
||||||
{
|
{
|
||||||
foreach (var user in _users)
|
foreach (var user in _users)
|
||||||
{
|
{
|
||||||
user.Progress += rng.Next(1, 11);
|
user.Progress += rng.Next(1, 10);
|
||||||
if (user.Progress >= 60)
|
if (user.Progress >= 60)
|
||||||
user.Progress = 60;
|
user.Progress = 60;
|
||||||
}
|
}
|
||||||
|
@ -126,13 +130,15 @@ public sealed class AnimalRace : IDisposable
|
||||||
FinishedUsers.AddRange(finished);
|
FinishedUsers.AddRange(finished);
|
||||||
|
|
||||||
_ = OnStateUpdate?.Invoke(this);
|
_ = OnStateUpdate?.Invoke(this);
|
||||||
await Task.Delay(2500);
|
await Task.Delay(1750);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FinishedUsers[0].Bet > 0)
|
if (FinishedUsers[0].Bet > 0)
|
||||||
{
|
{
|
||||||
|
Multi = FinishedUsers.Count
|
||||||
|
* Math.Min(MAX_MULTIPLIER, BASE_MULTIPLIER + (MULTI_PER_USER * FinishedUsers.Count));
|
||||||
await _currency.AddAsync(FinishedUsers[0].UserId,
|
await _currency.AddAsync(FinishedUsers[0].UserId,
|
||||||
FinishedUsers[0].Bet * (_users.Count - 1),
|
(long)(FinishedUsers[0].Bet * Multi),
|
||||||
new("animalrace", "win"));
|
new("animalrace", "win"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,6 +146,8 @@ public sealed class AnimalRace : IDisposable
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double Multi { get; set; } = BASE_MULTIPLIER;
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
CurrentPhase = Phase.Ended;
|
CurrentPhase = Phase.Ended;
|
||||||
|
|
|
@ -12,7 +12,7 @@ namespace EllieBot.Modules.Gambling;
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class AnimalRacingCommands : GamblingSubmodule<AnimalRaceService>
|
public partial class AnimalRacingCommands : GamblingModule<AnimalRaceService>
|
||||||
{
|
{
|
||||||
private readonly ICurrencyService _cs;
|
private readonly ICurrencyService _cs;
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
|
@ -74,10 +74,14 @@ public partial class Gambling
|
||||||
if (race.FinishedUsers[0].Bet > 0)
|
if (race.FinishedUsers[0].Bet > 0)
|
||||||
{
|
{
|
||||||
return Response()
|
return Response()
|
||||||
.Confirm(GetText(strs.animal_race),
|
.Embed(_sender.CreateEmbed()
|
||||||
GetText(strs.animal_race_won_money(Format.Bold(winner.Username),
|
.WithOkColor()
|
||||||
winner.Animal.Icon,
|
.WithTitle(GetText(strs.animal_race))
|
||||||
(race.FinishedUsers[0].Bet * (race.Users.Count - 1)) + CurrencySign)))
|
.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}"))
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,14 +117,14 @@ public partial class Gambling
|
||||||
|
|
||||||
private async Task Ar_OnStateUpdate(AnimalRace race)
|
private async Task Ar_OnStateUpdate(AnimalRace race)
|
||||||
{
|
{
|
||||||
var text = $@"|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|
|
var text = $@"|🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁🔚|
|
||||||
{string.Join("\n", race.Users.Select(p =>
|
{string.Join("\n", race.Users.Select(p =>
|
||||||
{
|
{
|
||||||
var index = race.FinishedUsers.IndexOf(p);
|
var index = race.FinishedUsers.IndexOf(p);
|
||||||
var extra = index == -1 ? "" : $"#{index + 1} {(index == 0 ? "🏆" : "")}";
|
var extra = index == -1 ? "" : $"#{index + 1} {(index == 0 ? "🏆" : "")}";
|
||||||
return $"{(int)(p.Progress / 60f * 100),-2}%|{new string('‣', p.Progress) + p.Animal.Icon + extra}";
|
return $"{(int)(p.Progress / 60f * 100),-2}%|{new string('‣', p.Progress) + p.Animal.Icon + extra}";
|
||||||
}))}
|
}))}
|
||||||
|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|";
|
|🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁🔚|";
|
||||||
|
|
||||||
var msg = raceMessage;
|
var msg = raceMessage;
|
||||||
|
|
||||||
|
@ -129,10 +133,10 @@ public partial class Gambling
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await msg.ModifyAsync(x => x.Embed = _sender.CreateEmbed()
|
await msg.ModifyAsync(x => x.Embed = _sender.CreateEmbed()
|
||||||
.WithTitle(GetText(strs.animal_race))
|
.WithTitle(GetText(strs.animal_race))
|
||||||
.WithDescription(text)
|
.WithDescription(text)
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.Build());
|
.Build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
175
src/EllieBot/Modules/Gambling/BetStatsCommands.cs
Normal file
175
src/EllieBot/Modules/Gambling/BetStatsCommands.cs
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
#nullable disable
|
||||||
|
using EllieBot.Modules.Gambling.Common;
|
||||||
|
using EllieBot.Modules.Gambling.Services;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Gambling;
|
||||||
|
|
||||||
|
public partial class Gambling
|
||||||
|
{
|
||||||
|
[Group]
|
||||||
|
public sealed class BetStatsCommands : GamblingModule<UserBetStatsService>
|
||||||
|
{
|
||||||
|
private readonly GamblingTxTracker _gamblingTxTracker;
|
||||||
|
|
||||||
|
public BetStatsCommands(
|
||||||
|
GamblingTxTracker gamblingTxTracker,
|
||||||
|
GamblingConfigService gcs)
|
||||||
|
: base(gcs)
|
||||||
|
{
|
||||||
|
_gamblingTxTracker = gamblingTxTracker;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public async Task BetStatsReset(GamblingGame? game = null)
|
||||||
|
{
|
||||||
|
var price = await _service.GetResetStatsPriceAsync(ctx.User.Id, game);
|
||||||
|
|
||||||
|
var result = await PromptUserConfirmAsync(_sender.CreateEmbed()
|
||||||
|
.WithDescription(
|
||||||
|
$"""
|
||||||
|
Are you sure you want to reset your bet stats for **{GetGameName(game)}**?
|
||||||
|
|
||||||
|
It will cost you {N(price)}
|
||||||
|
"""));
|
||||||
|
|
||||||
|
if (!result)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var success = await _service.ResetStatsAsync(ctx.User.Id, game);
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
await ctx.OkAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Response()
|
||||||
|
.Error(strs.not_enough(CurrencySign))
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetGameName(GamblingGame? game)
|
||||||
|
{
|
||||||
|
if (game is null)
|
||||||
|
return "all games";
|
||||||
|
|
||||||
|
return game.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[Priority(3)]
|
||||||
|
public async Task BetStats()
|
||||||
|
=> await BetStats(ctx.User, null);
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[Priority(2)]
|
||||||
|
public async Task BetStats(GamblingGame game)
|
||||||
|
=> await BetStats(ctx.User, game);
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[Priority(1)]
|
||||||
|
public async Task BetStats([Leftover] IUser user)
|
||||||
|
=> await BetStats(user, null);
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[Priority(0)]
|
||||||
|
public async Task BetStats(IUser user, GamblingGame? game)
|
||||||
|
{
|
||||||
|
var stats = await _gamblingTxTracker.GetUserStatsAsync(user.Id, game);
|
||||||
|
|
||||||
|
if (stats.Count == 0)
|
||||||
|
stats = new()
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
TotalBet = 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var eb = _sender.CreateEmbed()
|
||||||
|
.WithOkColor()
|
||||||
|
.WithAuthor(user)
|
||||||
|
.AddField("Total Won", N(stats.Sum(x => x.PaidOut)), true)
|
||||||
|
.AddField("Biggest Win", N(stats.Max(x => x.MaxWin)), true)
|
||||||
|
.AddField("Biggest Bet", N(stats.Max(x => x.MaxBet)), true)
|
||||||
|
.AddField("# Bets", stats.Sum(x => x.WinCount + x.LoseCount), true)
|
||||||
|
.AddField("Payout",
|
||||||
|
(stats.Sum(x => x.PaidOut) / stats.Sum(x => x.TotalBet)).ToString("P2", Culture),
|
||||||
|
true);
|
||||||
|
if (game == null)
|
||||||
|
{
|
||||||
|
var favGame = stats.MaxBy(x => x.WinCount + x.LoseCount);
|
||||||
|
eb.AddField("Favorite Game",
|
||||||
|
favGame.Game + "\n" + Format.Italics((favGame.WinCount + favGame.LoseCount) + " plays"),
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
eb.WithDescription(game.ToString())
|
||||||
|
.AddField("# Wins", stats.Sum(x => x.WinCount), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Response()
|
||||||
|
.Embed(eb)
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public async Task GambleStats()
|
||||||
|
{
|
||||||
|
var stats = await _gamblingTxTracker.GetAllAsync();
|
||||||
|
|
||||||
|
var eb = _sender.CreateEmbed()
|
||||||
|
.WithOkColor();
|
||||||
|
|
||||||
|
var str = "` Feature `|` Bet `|`Paid Out`|` RoI `\n";
|
||||||
|
str += "――――――――――――――――――――\n";
|
||||||
|
foreach (var stat in stats)
|
||||||
|
{
|
||||||
|
var perc = (stat.PaidOut / stat.Bet).ToString("P2", Culture);
|
||||||
|
str += $"`{stat.Feature.PadBoth(9)}`"
|
||||||
|
+ $"|`{stat.Bet.ToString("N0").PadLeft(8, ' ')}`"
|
||||||
|
+ $"|`{stat.PaidOut.ToString("N0").PadLeft(8, ' ')}`"
|
||||||
|
+ $"|`{perc.PadLeft(6, ' ')}`\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
var bet = stats.Sum(x => x.Bet);
|
||||||
|
var paidOut = stats.Sum(x => x.PaidOut);
|
||||||
|
|
||||||
|
if (bet == 0)
|
||||||
|
bet = 1;
|
||||||
|
|
||||||
|
var tPerc = (paidOut / bet).ToString("P2", Culture);
|
||||||
|
str += "――――――――――――――――――――\n";
|
||||||
|
str += $"` {("TOTAL").PadBoth(7)}` "
|
||||||
|
+ $"|**{N(bet).PadLeft(8, ' ')}**"
|
||||||
|
+ $"|**{N(paidOut).PadLeft(8, ' ')}**"
|
||||||
|
+ $"|`{tPerc.PadLeft(6, ' ')}`";
|
||||||
|
|
||||||
|
eb.WithDescription(str);
|
||||||
|
|
||||||
|
await Response().Embed(eb).SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[OwnerOnly]
|
||||||
|
public async Task GambleStatsReset()
|
||||||
|
{
|
||||||
|
if (!await PromptUserConfirmAsync(_sender.CreateEmbed()
|
||||||
|
.WithDescription(
|
||||||
|
"""
|
||||||
|
Are you sure?
|
||||||
|
This will completely reset Gambling Stats.
|
||||||
|
|
||||||
|
This action is irreversible.
|
||||||
|
""")))
|
||||||
|
return;
|
||||||
|
|
||||||
|
await GambleStats();
|
||||||
|
await _service.ResetGamblingStatsAsync();
|
||||||
|
|
||||||
|
await ctx.OkAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ namespace EllieBot.Modules.Gambling;
|
||||||
|
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
public partial class BlackJackCommands : GamblingSubmodule<BlackJackService>
|
public partial class BlackJackCommands : GamblingModule<BlackJackService>
|
||||||
{
|
{
|
||||||
public enum BjAction
|
public enum BjAction
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace EllieBot.Modules.Gambling;
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class Connect4Commands : GamblingSubmodule<GamblingService>
|
public partial class Connect4Commands : GamblingModule<GamblingService>
|
||||||
{
|
{
|
||||||
private static readonly string[] _numbers =
|
private static readonly string[] _numbers =
|
||||||
[
|
[
|
||||||
|
|
|
@ -12,7 +12,7 @@ namespace EllieBot.Modules.Gambling;
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class DrawCommands : GamblingSubmodule<IGamblingService>
|
public partial class DrawCommands : GamblingModule<IGamblingService>
|
||||||
{
|
{
|
||||||
private static readonly ConcurrentDictionary<IGuild, Deck> _allDecks = new();
|
private static readonly ConcurrentDictionary<IGuild, Deck> _allDecks = new();
|
||||||
private readonly IImageCache _images;
|
private readonly IImageCache _images;
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace EllieBot.Modules.Gambling;
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class CurrencyEventsCommands : GamblingSubmodule<CurrencyEventsService>
|
public partial class CurrencyEventsCommands : GamblingModule<CurrencyEventsService>
|
||||||
{
|
{
|
||||||
public CurrencyEventsCommands(GamblingConfigService gamblingConf)
|
public CurrencyEventsCommands(GamblingConfigService gamblingConf)
|
||||||
: base(gamblingConf)
|
: base(gamblingConf)
|
||||||
|
|
|
@ -11,7 +11,7 @@ namespace EllieBot.Modules.Gambling;
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class FlipCoinCommands : GamblingSubmodule<IGamblingService>
|
public partial class FlipCoinCommands : GamblingModule<IGamblingService>
|
||||||
{
|
{
|
||||||
public enum BetFlipGuess : byte
|
public enum BetFlipGuess : byte
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,6 +14,13 @@ using System.Text;
|
||||||
using EllieBot.Modules.Gambling.Rps;
|
using EllieBot.Modules.Gambling.Rps;
|
||||||
using EllieBot.Common.TypeReaders;
|
using EllieBot.Common.TypeReaders;
|
||||||
using EllieBot.Modules.Patronage;
|
using EllieBot.Modules.Patronage;
|
||||||
|
using SixLabors.Fonts;
|
||||||
|
using SixLabors.Fonts.Unicode;
|
||||||
|
using SixLabors.ImageSharp;
|
||||||
|
using SixLabors.ImageSharp.Drawing.Processing;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
using Color = SixLabors.ImageSharp.Color;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Gambling;
|
namespace EllieBot.Modules.Gambling;
|
||||||
|
|
||||||
|
@ -26,10 +33,12 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
private readonly NumberFormatInfo _enUsCulture;
|
private readonly NumberFormatInfo _enUsCulture;
|
||||||
private readonly DownloadTracker _tracker;
|
private readonly DownloadTracker _tracker;
|
||||||
private readonly GamblingConfigService _configService;
|
private readonly GamblingConfigService _configService;
|
||||||
|
private readonly FontProvider _fonts;
|
||||||
private readonly IBankService _bank;
|
private readonly IBankService _bank;
|
||||||
private readonly IRemindService _remind;
|
private readonly IRemindService _remind;
|
||||||
private readonly GamblingTxTracker _gamblingTxTracker;
|
private readonly GamblingTxTracker _gamblingTxTracker;
|
||||||
private readonly IPatronageService _ps;
|
private readonly IPatronageService _ps;
|
||||||
|
private readonly RakebackService _rb;
|
||||||
|
|
||||||
public Gambling(
|
public Gambling(
|
||||||
IGamblingService gs,
|
IGamblingService gs,
|
||||||
|
@ -38,10 +47,12 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
DiscordSocketClient client,
|
DiscordSocketClient client,
|
||||||
DownloadTracker tracker,
|
DownloadTracker tracker,
|
||||||
GamblingConfigService configService,
|
GamblingConfigService configService,
|
||||||
|
FontProvider fonts,
|
||||||
IBankService bank,
|
IBankService bank,
|
||||||
IRemindService remind,
|
IRemindService remind,
|
||||||
IPatronageService patronage,
|
IPatronageService patronage,
|
||||||
GamblingTxTracker gamblingTxTracker)
|
GamblingTxTracker gamblingTxTracker,
|
||||||
|
RakebackService rb)
|
||||||
: base(configService)
|
: base(configService)
|
||||||
{
|
{
|
||||||
_gs = gs;
|
_gs = gs;
|
||||||
|
@ -51,13 +62,16 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
_bank = bank;
|
_bank = bank;
|
||||||
_remind = remind;
|
_remind = remind;
|
||||||
_gamblingTxTracker = gamblingTxTracker;
|
_gamblingTxTracker = gamblingTxTracker;
|
||||||
|
_rb = rb;
|
||||||
_ps = patronage;
|
_ps = patronage;
|
||||||
|
_rng = new EllieRandom();
|
||||||
|
|
||||||
_enUsCulture = new CultureInfo("en-US", false).NumberFormat;
|
_enUsCulture = new CultureInfo("en-US", false).NumberFormat;
|
||||||
_enUsCulture.NumberDecimalDigits = 0;
|
_enUsCulture.NumberDecimalDigits = 0;
|
||||||
_enUsCulture.NumberGroupSeparator = " ";
|
_enUsCulture.NumberGroupSeparator = " ";
|
||||||
_tracker = tracker;
|
_tracker = tracker;
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
|
_fonts = fonts;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetBalanceStringAsync(ulong userId)
|
public async Task<string> GetBalanceStringAsync(ulong userId)
|
||||||
|
@ -66,42 +80,6 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
return N(bal);
|
return N(bal);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
|
||||||
public async Task BetStats()
|
|
||||||
{
|
|
||||||
var stats = await _gamblingTxTracker.GetAllAsync();
|
|
||||||
|
|
||||||
var eb = _sender.CreateEmbed()
|
|
||||||
.WithOkColor();
|
|
||||||
|
|
||||||
var str = "` Feature `|` Bet `|`Paid Out`|` RoI `\n";
|
|
||||||
str += "――――――――――――――――――――\n";
|
|
||||||
foreach (var stat in stats)
|
|
||||||
{
|
|
||||||
var perc = (stat.PaidOut / stat.Bet).ToString("P2", Culture);
|
|
||||||
str += $"`{stat.Feature.PadBoth(9)}`"
|
|
||||||
+ $"|`{stat.Bet.ToString("N0").PadLeft(8, ' ')}`"
|
|
||||||
+ $"|`{stat.PaidOut.ToString("N0").PadLeft(8, ' ')}`"
|
|
||||||
+ $"|`{perc.PadLeft(6, ' ')}`\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
var bet = stats.Sum(x => x.Bet);
|
|
||||||
var paidOut = stats.Sum(x => x.PaidOut);
|
|
||||||
|
|
||||||
if (bet == 0)
|
|
||||||
bet = 1;
|
|
||||||
|
|
||||||
var tPerc = (paidOut / bet).ToString("P2", Culture);
|
|
||||||
str += "――――――――――――――――――――\n";
|
|
||||||
str += $"` {("TOTAL").PadBoth(7)}` "
|
|
||||||
+ $"|**{N(bet).PadLeft(8, ' ')}**"
|
|
||||||
+ $"|**{N(paidOut).PadLeft(8, ' ')}**"
|
|
||||||
+ $"|`{tPerc.PadLeft(6, ' ')}`";
|
|
||||||
|
|
||||||
eb.WithDescription(str);
|
|
||||||
|
|
||||||
await Response().Embed(eb).SendAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task RemindTimelyAction(SocketMessageComponent smc, DateTime when)
|
private async Task RemindTimelyAction(SocketMessageComponent smc, DateTime when)
|
||||||
{
|
{
|
||||||
|
@ -140,7 +118,21 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
(smc) => RemindTimelyAction(smc, DateTime.UtcNow.Add(TimeSpan.FromMilliseconds(ms)))
|
(smc) => RemindTimelyAction(smc, DateTime.UtcNow.Add(TimeSpan.FromMilliseconds(ms)))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
private EllieInteractionBase CreateTimelyInteraction()
|
||||||
|
=> _inter
|
||||||
|
.Create(ctx.User.Id,
|
||||||
|
new ButtonBuilder(
|
||||||
|
label: "Timely",
|
||||||
|
emote: Emoji.Parse("💰"),
|
||||||
|
customId: "timely:" + _rng.Next(123456, 999999)),
|
||||||
|
async (smc) =>
|
||||||
|
{
|
||||||
|
await smc.DeferAsync();
|
||||||
|
await ClaimTimely();
|
||||||
|
});
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
public async Task Timely()
|
public async Task Timely()
|
||||||
{
|
{
|
||||||
var val = Config.Timely.Amount;
|
var val = Config.Timely.Amount;
|
||||||
|
@ -151,6 +143,71 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Config.Timely.ProtType == TimelyProt.Button)
|
||||||
|
{
|
||||||
|
var interaction = CreateTimelyInteraction();
|
||||||
|
var msg = await Response().Pending(strs.timely_button).Interaction(interaction).SendAsync();
|
||||||
|
await msg.DeleteAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (Config.Timely.ProtType == TimelyProt.Captcha)
|
||||||
|
{
|
||||||
|
var password = _service.GeneratePassword();
|
||||||
|
|
||||||
|
var img = new Image<Rgba32>(70, 35);
|
||||||
|
|
||||||
|
var font = _fonts.NotoSans.CreateFont(30);
|
||||||
|
var outlinePen = new SolidPen(Color.Black, 1f);
|
||||||
|
var strikeoutRun = new RichTextRun
|
||||||
|
{
|
||||||
|
Start = 0,
|
||||||
|
End = password.GetGraphemeCount(),
|
||||||
|
Font = font,
|
||||||
|
StrikeoutPen = new SolidPen(Color.White, 3),
|
||||||
|
TextDecorations = TextDecorations.Strikeout
|
||||||
|
};
|
||||||
|
// draw password on the image
|
||||||
|
img.Mutate(x =>
|
||||||
|
{
|
||||||
|
x.DrawText(new RichTextOptions(font)
|
||||||
|
{
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
FallbackFontFamilies = _fonts.FallBackFonts,
|
||||||
|
Origin = new(35, 17),
|
||||||
|
TextRuns = [strikeoutRun]
|
||||||
|
},
|
||||||
|
password,
|
||||||
|
Brushes.Solid(Color.White),
|
||||||
|
outlinePen);
|
||||||
|
});
|
||||||
|
using var stream = await img.ToStreamAsync();
|
||||||
|
var captcha = await Response()
|
||||||
|
// .Embed(_sender.CreateEmbed()
|
||||||
|
// .WithOkColor()
|
||||||
|
// .WithImageUrl("attachment://timely.png"))
|
||||||
|
.File(stream, "timely.png")
|
||||||
|
.SendAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var userInput = await GetUserInputAsync(ctx.User.Id, ctx.Channel.Id);
|
||||||
|
if (userInput?.ToLowerInvariant() != password?.ToLowerInvariant())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_ = captcha.DeleteAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await ClaimTimely();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ClaimTimely()
|
||||||
|
{
|
||||||
|
var period = Config.Timely.Cooldown;
|
||||||
if (await _service.ClaimTimelyAsync(ctx.User.Id, period) is { } remainder)
|
if (await _service.ClaimTimelyAsync(ctx.User.Id, period) is { } remainder)
|
||||||
{
|
{
|
||||||
// Get correct time form remainder
|
// Get correct time form remainder
|
||||||
|
@ -169,6 +226,30 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var val = Config.Timely.Amount;
|
||||||
|
var boostGuilds = Config.BoostBonus.GuildIds ?? new();
|
||||||
|
var guildUsers = await boostGuilds
|
||||||
|
.Select(async gid =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var guild = await _client.Rest.GetGuildAsync(gid, false);
|
||||||
|
var user = await _client.Rest.GetGuildUserAsync(gid, ctx.User.Id);
|
||||||
|
return (guild, user);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.WhenAll();
|
||||||
|
|
||||||
|
var userInfo = guildUsers.FirstOrDefault(x => x.user?.PremiumSince is not null);
|
||||||
|
var booster = userInfo != default;
|
||||||
|
|
||||||
|
if (booster)
|
||||||
|
val += Config.BoostBonus.BaseTimelyBonus;
|
||||||
|
|
||||||
var patron = await _ps.GetPatronAsync(ctx.User.Id);
|
var patron = await _ps.GetPatronAsync(ctx.User.Id);
|
||||||
|
|
||||||
var percentBonus = (_ps.PercentBonus(patron) / 100f);
|
var percentBonus = (_ps.PercentBonus(patron) / 100f);
|
||||||
|
@ -179,7 +260,21 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
|
|
||||||
await _cs.AddAsync(ctx.User.Id, val, new("timely", "claim"));
|
await _cs.AddAsync(ctx.User.Id, val, new("timely", "claim"));
|
||||||
|
|
||||||
await Response().Confirm(strs.timely(N(val), period)).Interaction(inter).SendAsync();
|
var msg = GetText(strs.timely(N(val), period));
|
||||||
|
if (booster || percentBonus > float.Epsilon)
|
||||||
|
{
|
||||||
|
msg += "\n\n";
|
||||||
|
if (booster)
|
||||||
|
msg += $"*+{N(Config.BoostBonus.BaseTimelyBonus)} bonus for boosting {userInfo.guild}!*\n";
|
||||||
|
|
||||||
|
if (percentBonus > float.Epsilon)
|
||||||
|
msg +=
|
||||||
|
$"*+{percentBonus:P0} bonus for the [Patreon](https://patreon.com/elliebot) pledge! <:hart:746995901758832712>*";
|
||||||
|
|
||||||
|
await Response().Confirm(msg).Interaction(inter).SendAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
await Response().Confirm(strs.timely(N(val), period)).Interaction(inter).SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
|
@ -290,8 +385,9 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
}
|
}
|
||||||
|
|
||||||
var embed = _sender.CreateEmbed()
|
var embed = _sender.CreateEmbed()
|
||||||
.WithTitle(GetText(strs.transactions(((SocketGuild)ctx.Guild)?.GetUser(userId)?.ToString()
|
.WithTitle(GetText(strs.transactions(
|
||||||
?? $"{userId}")))
|
((SocketGuild)ctx.Guild)?.GetUser(userId)?.ToString()
|
||||||
|
?? $"{userId}")))
|
||||||
.WithOkColor();
|
.WithOkColor();
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
@ -547,7 +643,9 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await Response().Error(strs.take_fail(N(amount), Format.Bold(user.ToString()), CurrencySign)).SendAsync();
|
await Response()
|
||||||
|
.Error(strs.take_fail(N(amount), Format.Bold(user.ToString()), CurrencySign))
|
||||||
|
.SendAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -568,7 +666,9 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await Response().Error(strs.take_fail(N(amount), Format.Code(usrId.ToString()), CurrencySign)).SendAsync();
|
await Response()
|
||||||
|
.Error(strs.take_fail(N(amount), Format.Code(usrId.ToString()), CurrencySign))
|
||||||
|
.SendAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -762,6 +862,8 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
private static readonly ImmutableArray<string> _emojis =
|
private static readonly ImmutableArray<string> _emojis =
|
||||||
new[] { "⬆", "↖", "⬅", "↙", "⬇", "↘", "➡", "↗" }.ToImmutableArray();
|
new[] { "⬆", "↖", "⬅", "↙", "⬇", "↘", "➡", "↗" }.ToImmutableArray();
|
||||||
|
|
||||||
|
private readonly EllieRandom _rng;
|
||||||
|
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
public async Task LuckyLadder([OverrideTypeReader(typeof(BalanceTypeReader))] long amount)
|
public async Task LuckyLadder([OverrideTypeReader(typeof(BalanceTypeReader))] long amount)
|
||||||
|
@ -900,4 +1002,45 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
footer: $"Total Bet: {tests} | Payout: {payout:F0} | {payout * 1.0M / tests * 100}%")
|
footer: $"Total Bet: {tests} | Payout: {payout:F0} | {payout * 1.0M / tests * 100}%")
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private EllieInteractionBase CreateRakebackInteraction()
|
||||||
|
=> _inter.Create(ctx.User.Id,
|
||||||
|
new ButtonBuilder(
|
||||||
|
customId: "cash:rakeback",
|
||||||
|
emote: new Emoji("💸")),
|
||||||
|
RakebackAction);
|
||||||
|
|
||||||
|
private async Task RakebackAction(SocketMessageComponent arg)
|
||||||
|
{
|
||||||
|
var rb = await _rb.ClaimRakebackAsync(ctx.User.Id);
|
||||||
|
|
||||||
|
if (rb == 0)
|
||||||
|
{
|
||||||
|
await arg.DeferAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await arg.RespondAsync(_sender, GetText(strs.rakeback_claimed(N(rb))), MsgType.Ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public async Task Rakeback()
|
||||||
|
{
|
||||||
|
var rb = await _rb.GetRakebackAsync(ctx.User.Id);
|
||||||
|
|
||||||
|
if (rb < 1)
|
||||||
|
{
|
||||||
|
await Response()
|
||||||
|
.Error(strs.rakeback_none)
|
||||||
|
.SendAsync();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var inter = CreateRakebackInteraction();
|
||||||
|
await Response()
|
||||||
|
.Pending(strs.rakeback_available(N(rb)))
|
||||||
|
.Interaction(inter)
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -11,7 +11,7 @@ namespace EllieBot.Modules.Gambling.Common;
|
||||||
public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
|
public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
|
||||||
{
|
{
|
||||||
[Comment("""DO NOT CHANGE""")]
|
[Comment("""DO NOT CHANGE""")]
|
||||||
public int Version { get; set; } = 8;
|
public int Version { get; set; } = 12;
|
||||||
|
|
||||||
[Comment("""Currency settings""")]
|
[Comment("""Currency settings""")]
|
||||||
public CurrencyConfig Currency { get; set; }
|
public CurrencyConfig Currency { get; set; }
|
||||||
|
@ -67,6 +67,11 @@ public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
|
||||||
[Comment("""Slot config""")]
|
[Comment("""Slot config""")]
|
||||||
public SlotsConfig Slots { get; set; }
|
public SlotsConfig Slots { get; set; }
|
||||||
|
|
||||||
|
[Comment("""
|
||||||
|
Bonus config for server boosts
|
||||||
|
""")]
|
||||||
|
public BoostBonusConfig BoostBonus { get; set; }
|
||||||
|
|
||||||
public GamblingConfig()
|
public GamblingConfig()
|
||||||
{
|
{
|
||||||
BetRoll = new();
|
BetRoll = new();
|
||||||
|
@ -79,6 +84,7 @@ public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
|
||||||
Slots = new();
|
Slots = new();
|
||||||
LuckyLadder = new();
|
LuckyLadder = new();
|
||||||
BotCuts = new();
|
BotCuts = new();
|
||||||
|
BoostBonus = new();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,13 +110,26 @@ public partial class TimelyConfig
|
||||||
How much currency will the users get every time they run .timely command
|
How much currency will the users get every time they run .timely command
|
||||||
setting to 0 or less will disable this feature
|
setting to 0 or less will disable this feature
|
||||||
""")]
|
""")]
|
||||||
public int Amount { get; set; } = 0;
|
public long Amount { get; set; } = 0;
|
||||||
|
|
||||||
[Comment("""
|
[Comment("""
|
||||||
How often (in hours) can users claim currency with .timely command
|
How often (in hours) can users claim currency with .timely command
|
||||||
setting to 0 or less will disable this feature
|
setting to 0 or less will disable this feature
|
||||||
""")]
|
""")]
|
||||||
public int Cooldown { get; set; } = 24;
|
public int Cooldown { get; set; } = 24;
|
||||||
|
|
||||||
|
[Comment("""
|
||||||
|
How will timely be protected?
|
||||||
|
None, Button (users have to click the button) or Captcha (users have to type the captcha from an image)
|
||||||
|
""")]
|
||||||
|
public TimelyProt ProtType { get; set; } = TimelyProt.Button;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TimelyProt
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Button,
|
||||||
|
Captcha
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cloneable]
|
[Cloneable]
|
||||||
|
@ -145,7 +164,7 @@ public partial class BetRollConfig
|
||||||
},
|
},
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
WhenAbove = 66,
|
WhenAbove = 65,
|
||||||
MultiplyBy = 2
|
MultiplyBy = 2
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
@ -207,7 +226,7 @@ public partial class LuckyLadderSettings
|
||||||
public decimal[] Multipliers { get; set; }
|
public decimal[] Multipliers { get; set; }
|
||||||
|
|
||||||
public LuckyLadderSettings()
|
public LuckyLadderSettings()
|
||||||
=> Multipliers = [2.4M, 1.7M, 1.5M, 1.2M, 0.5M, 0.3M, 0.2M, 0.1M];
|
=> Multipliers = [2.4M, 1.7M, 1.5M, 1.1M, 0.5M, 0.3M, 0.2M, 0.1M];
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cloneable]
|
[Cloneable]
|
||||||
|
@ -409,3 +428,14 @@ public sealed partial class BotCutConfig
|
||||||
""")]
|
""")]
|
||||||
public decimal ShopSaleCut { get; set; } = 0.1m;
|
public decimal ShopSaleCut { get; set; } = 0.1m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Cloneable]
|
||||||
|
public sealed partial class BoostBonusConfig
|
||||||
|
{
|
||||||
|
[Comment("Users will receive a bonus if they boost any of these servers")]
|
||||||
|
public List<ulong> GuildIds { get; set; } = new();
|
||||||
|
|
||||||
|
[Comment("This bonus will be added before any other multiplier is applied to the .timely command")]
|
||||||
|
|
||||||
|
public long BaseTimelyBonus { get; set; } = 50;
|
||||||
|
}
|
|
@ -144,6 +144,11 @@ public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
|
||||||
ConfigPrinters.ToString,
|
ConfigPrinters.ToString,
|
||||||
val => val >= 0);
|
val => val >= 0);
|
||||||
|
|
||||||
|
AddParsedProp("timely.prot",
|
||||||
|
gs => gs.Timely.ProtType,
|
||||||
|
ConfigParsers.InsensitiveEnum,
|
||||||
|
ConfigPrinters.ToString);
|
||||||
|
|
||||||
Migrate();
|
Migrate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,22 +172,6 @@ public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.Version < 5)
|
|
||||||
{
|
|
||||||
ModifyConfig(c =>
|
|
||||||
{
|
|
||||||
c.Version = 5;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.Version < 6)
|
|
||||||
{
|
|
||||||
ModifyConfig(c =>
|
|
||||||
{
|
|
||||||
c.Version = 6;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.Version < 7)
|
if (data.Version < 7)
|
||||||
{
|
{
|
||||||
ModifyConfig(c =>
|
ModifyConfig(c =>
|
||||||
|
@ -199,5 +188,18 @@ public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
|
||||||
c.Waifu.Decay.UnclaimedDecayPercent = 0;
|
c.Waifu.Decay.UnclaimedDecayPercent = 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.Version < 12)
|
||||||
|
{
|
||||||
|
ModifyConfig(c =>
|
||||||
|
{
|
||||||
|
c.Version = 12;
|
||||||
|
|
||||||
|
if (c.BetRoll.Pairs.Length == 3 && c.BetRoll.Pairs[2].WhenAbove == 66)
|
||||||
|
{
|
||||||
|
c.BetRoll.Pairs[2].WhenAbove = 65;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -16,6 +16,7 @@ public class GamblingService : IEService, IReadyExecutor
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
private readonly IBotCache _cache;
|
private readonly IBotCache _cache;
|
||||||
private readonly GamblingConfigService _gss;
|
private readonly GamblingConfigService _gss;
|
||||||
|
private readonly EllieRandom _rng;
|
||||||
|
|
||||||
private static readonly TypedKey<long> _curDecayKey = new("currency:last_decay");
|
private static readonly TypedKey<long> _curDecayKey = new("currency:last_decay");
|
||||||
|
|
||||||
|
@ -29,11 +30,19 @@ public class GamblingService : IEService, IReadyExecutor
|
||||||
_client = client;
|
_client = client;
|
||||||
_cache = cache;
|
_cache = cache;
|
||||||
_gss = gss;
|
_gss = gss;
|
||||||
|
_rng = new EllieRandom();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task OnReadyAsync()
|
public Task OnReadyAsync()
|
||||||
=> Task.WhenAll(CurrencyDecayLoopAsync(), TransactionClearLoopAsync());
|
=> Task.WhenAll(CurrencyDecayLoopAsync(), TransactionClearLoopAsync());
|
||||||
|
|
||||||
|
|
||||||
|
public string GeneratePassword()
|
||||||
|
{
|
||||||
|
var num = _rng.Next((int)Math.Pow(31, 2), (int)Math.Pow(32, 3));
|
||||||
|
return new kwum(num).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task TransactionClearLoopAsync()
|
private async Task TransactionClearLoopAsync()
|
||||||
{
|
{
|
||||||
if (_client.ShardId != 0)
|
if (_client.ShardId != 0)
|
||||||
|
@ -52,7 +61,7 @@ public class GamblingService : IEService, IReadyExecutor
|
||||||
var days = TimeSpan.FromDays(lifetime);
|
var days = TimeSpan.FromDays(lifetime);
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
await uow.Set<CurrencyTransaction>()
|
await uow.Set<CurrencyTransaction>()
|
||||||
.DeleteAsync(ct => ct.DateAdded == null || now - ct.DateAdded < days);
|
.DeleteAsync(ct => ct.DateAdded == null || now - ct.DateAdded < days);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -90,11 +99,11 @@ public class GamblingService : IEService, IReadyExecutor
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Information("""
|
Log.Information("""
|
||||||
--- Decaying users' currency ---
|
--- Decaying users' currency ---
|
||||||
| decay: {ConfigDecayPercent}%
|
| decay: {ConfigDecayPercent}%
|
||||||
| max: {MaxDecay}
|
| max: {MaxDecay}
|
||||||
| threshold: {DecayMinTreshold}
|
| threshold: {DecayMinTreshold}
|
||||||
""",
|
""",
|
||||||
config.Decay.Percent * 100,
|
config.Decay.Percent * 100,
|
||||||
maxDecay,
|
maxDecay,
|
||||||
config.Decay.MinThreshold);
|
config.Decay.MinThreshold);
|
||||||
|
@ -104,14 +113,14 @@ public class GamblingService : IEService, IReadyExecutor
|
||||||
|
|
||||||
var decay = (double)config.Decay.Percent;
|
var decay = (double)config.Decay.Percent;
|
||||||
await uow.Set<DiscordUser>()
|
await uow.Set<DiscordUser>()
|
||||||
.Where(x => x.CurrencyAmount > config.Decay.MinThreshold && x.UserId != _client.CurrentUser.Id)
|
.Where(x => x.CurrencyAmount > config.Decay.MinThreshold && x.UserId != _client.CurrentUser.Id)
|
||||||
.UpdateAsync(old => new()
|
.UpdateAsync(old => new()
|
||||||
{
|
{
|
||||||
CurrencyAmount =
|
CurrencyAmount =
|
||||||
maxDecay > Sql.Round((old.CurrencyAmount * decay) - 0.5)
|
maxDecay > Sql.Round((old.CurrencyAmount * decay) - 0.5)
|
||||||
? (long)(old.CurrencyAmount - Sql.Round((old.CurrencyAmount * decay) - 0.5))
|
? (long)(old.CurrencyAmount - Sql.Round((old.CurrencyAmount * decay) - 0.5))
|
||||||
: old.CurrencyAmount - maxDecay
|
: old.CurrencyAmount - maxDecay
|
||||||
});
|
});
|
||||||
|
|
||||||
await uow.SaveChangesAsync();
|
await uow.SaveChangesAsync();
|
||||||
|
|
||||||
|
@ -178,8 +187,9 @@ public class GamblingService : IEService, IReadyExecutor
|
||||||
public bool UserHasTimelyReminder(ulong userId)
|
public bool UserHasTimelyReminder(ulong userId)
|
||||||
{
|
{
|
||||||
var db = _db.GetDbContext();
|
var db = _db.GetDbContext();
|
||||||
return db.GetTable<Reminder>().Any(x => x.UserId == userId
|
return db.GetTable<Reminder>()
|
||||||
&& x.Type == ReminderType.Timely);
|
.Any(x => x.UserId == userId
|
||||||
|
&& x.Type == ReminderType.Timely);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RemoveAllTimelyClaimsAsync()
|
public async Task RemoveAllTimelyClaimsAsync()
|
||||||
|
|
|
@ -58,11 +58,3 @@ public abstract class GamblingModule<TService> : EllieModule<TService>
|
||||||
return InternalCheckBet(amount);
|
return InternalCheckBet(amount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class GamblingSubmodule<TService> : GamblingModule<TService>
|
|
||||||
{
|
|
||||||
protected GamblingSubmodule(GamblingConfigService gamblingConfService)
|
|
||||||
: base(gamblingConfService)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,7 +8,7 @@ namespace EllieBot.Modules.Gambling;
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class PlantPickCommands : GamblingSubmodule<PlantPickService>
|
public partial class PlantPickCommands : GamblingModule<PlantPickService>
|
||||||
{
|
{
|
||||||
private readonly ILogCommandService _logService;
|
private readonly ILogCommandService _logService;
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
using LinqToDB;
|
||||||
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
using SixLabors.Fonts;
|
using SixLabors.Fonts;
|
||||||
|
using SixLabors.Fonts.Unicode;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.Drawing.Processing;
|
using SixLabors.ImageSharp.Drawing.Processing;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
@ -25,6 +28,7 @@ public class PlantPickService : IEService, IExecNoCommand
|
||||||
private readonly EllieRandom _rng;
|
private readonly EllieRandom _rng;
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
private readonly GamblingConfigService _gss;
|
private readonly GamblingConfigService _gss;
|
||||||
|
private readonly GamblingService _gs;
|
||||||
|
|
||||||
private readonly ConcurrentHashSet<ulong> _generationChannels;
|
private readonly ConcurrentHashSet<ulong> _generationChannels;
|
||||||
private readonly SemaphoreSlim _pickLock = new(1, 1);
|
private readonly SemaphoreSlim _pickLock = new(1, 1);
|
||||||
|
@ -37,7 +41,8 @@ public class PlantPickService : IEService, IExecNoCommand
|
||||||
ICurrencyService cs,
|
ICurrencyService cs,
|
||||||
CommandHandler cmdHandler,
|
CommandHandler cmdHandler,
|
||||||
DiscordSocketClient client,
|
DiscordSocketClient client,
|
||||||
GamblingConfigService gss)
|
GamblingConfigService gss,
|
||||||
|
GamblingService gs)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_strings = strings;
|
_strings = strings;
|
||||||
|
@ -48,6 +53,7 @@ public class PlantPickService : IEService, IExecNoCommand
|
||||||
_rng = new();
|
_rng = new();
|
||||||
_client = client;
|
_client = client;
|
||||||
_gss = gss;
|
_gss = gss;
|
||||||
|
_gs = gs;
|
||||||
|
|
||||||
using var uow = db.GetDbContext();
|
using var uow = db.GetDbContext();
|
||||||
var guildIds = client.Guilds.Select(x => x.Id).ToList();
|
var guildIds = client.Guilds.Select(x => x.Id).ToList();
|
||||||
|
@ -87,6 +93,7 @@ public class PlantPickService : IEService, IExecNoCommand
|
||||||
var toDelete = guildConfig.GenerateCurrencyChannelIds.FirstOrDefault(x => x.Equals(toAdd));
|
var toDelete = guildConfig.GenerateCurrencyChannelIds.FirstOrDefault(x => x.Equals(toAdd));
|
||||||
if (toDelete is not null)
|
if (toDelete is not null)
|
||||||
uow.Remove(toDelete);
|
uow.Remove(toDelete);
|
||||||
|
|
||||||
_generationChannels.TryRemove(cid);
|
_generationChannels.TryRemove(cid);
|
||||||
enabled = false;
|
enabled = false;
|
||||||
}
|
}
|
||||||
|
@ -140,7 +147,7 @@ public class PlantPickService : IEService, IExecNoCommand
|
||||||
pass = pass.TrimTo(10, true).ToLowerInvariant();
|
pass = pass.TrimTo(10, true).ToLowerInvariant();
|
||||||
using var img = Image.Load<Rgba32>(curImg);
|
using var img = Image.Load<Rgba32>(curImg);
|
||||||
// choose font size based on the image height, so that it's visible
|
// choose font size based on the image height, so that it's visible
|
||||||
var font = _fonts.NotoSans.CreateFont(img.Height / 12.0f, FontStyle.Bold);
|
var font = _fonts.NotoSans.CreateFont(img.Height / 11.0f, FontStyle.Bold);
|
||||||
img.Mutate(x =>
|
img.Mutate(x =>
|
||||||
{
|
{
|
||||||
// measure the size of the text to be drawing
|
// measure the size of the text to be drawing
|
||||||
|
@ -152,13 +159,31 @@ public class PlantPickService : IEService, IExecNoCommand
|
||||||
|
|
||||||
// fill the background with black, add 5 pixels on each side to make it look better
|
// fill the background with black, add 5 pixels on each side to make it look better
|
||||||
x.FillPolygon(Color.ParseHex("00000080"),
|
x.FillPolygon(Color.ParseHex("00000080"),
|
||||||
new PointF(0, 0),
|
new PointF(1, 1),
|
||||||
new PointF(size.Width + 5, 0),
|
new PointF(size.Width + 5, 0),
|
||||||
new PointF(size.Width + 5, size.Height + 10),
|
new PointF(size.Width + 5, size.Height + 10),
|
||||||
new PointF(0, size.Height + 10));
|
new PointF(0, size.Height + 10));
|
||||||
|
|
||||||
|
var strikeoutRun = new RichTextRun
|
||||||
|
{
|
||||||
|
Start = 0,
|
||||||
|
End = pass.GetGraphemeCount(),
|
||||||
|
Font = font,
|
||||||
|
StrikeoutPen = new SolidPen(Color.White, 2),
|
||||||
|
TextDecorations = TextDecorations.Strikeout
|
||||||
|
};
|
||||||
|
|
||||||
// draw the password over the background
|
// draw the password over the background
|
||||||
x.DrawText(pass, font, Color.White, new(0, 0));
|
x.DrawText(new RichTextOptions(font)
|
||||||
|
{
|
||||||
|
Origin = new(0, 0),
|
||||||
|
TextRuns =
|
||||||
|
[
|
||||||
|
strikeoutRun
|
||||||
|
]
|
||||||
|
},
|
||||||
|
pass,
|
||||||
|
new SolidBrush(Color.White));
|
||||||
});
|
});
|
||||||
// return image as a stream for easy sending
|
// return image as a stream for easy sending
|
||||||
var format = img.Metadata.DecodedImageFormat;
|
var format = img.Metadata.DecodedImageFormat;
|
||||||
|
@ -208,7 +233,7 @@ public class PlantPickService : IEService, IExecNoCommand
|
||||||
+ " "
|
+ " "
|
||||||
+ GetText(channel.GuildId, strs.pick_pl(prefix));
|
+ GetText(channel.GuildId, strs.pick_pl(prefix));
|
||||||
|
|
||||||
var pw = config.Generation.HasPassword ? GenerateCurrencyPassword().ToUpperInvariant() : null;
|
var pw = config.Generation.HasPassword ? _gs.GeneratePassword().ToUpperInvariant() : null;
|
||||||
|
|
||||||
IUserMessage sent;
|
IUserMessage sent;
|
||||||
var (stream, ext) = await GetRandomCurrencyImageAsync(pw);
|
var (stream, ext) = await GetRandomCurrencyImageAsync(pw);
|
||||||
|
@ -232,67 +257,44 @@ public class PlantPickService : IEService, IExecNoCommand
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Generate a hexadecimal string from 1000 to ffff.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A hexadecimal string from 1000 to ffff</returns>
|
|
||||||
private string GenerateCurrencyPassword()
|
|
||||||
{
|
|
||||||
// generate a number from 1000 to ffff
|
|
||||||
var num = _rng.Next(4096, 65536);
|
|
||||||
// convert it to hexadecimal
|
|
||||||
return num.ToString("x4");
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<long> PickAsync(
|
public async Task<long> PickAsync(
|
||||||
ulong gid,
|
ulong gid,
|
||||||
ITextChannel ch,
|
ITextChannel ch,
|
||||||
ulong uid,
|
ulong uid,
|
||||||
string pass)
|
string pass)
|
||||||
{
|
{
|
||||||
await _pickLock.WaitAsync();
|
long amount;
|
||||||
|
ulong[] ids;
|
||||||
|
await using (var uow = _db.GetDbContext())
|
||||||
|
{
|
||||||
|
// this method will sum all plants with that password,
|
||||||
|
// remove them, and get messageids of the removed plants
|
||||||
|
|
||||||
|
pass = pass?.Trim().TrimTo(10, true)?.ToUpperInvariant();
|
||||||
|
// gets all plants in this channel with the same password
|
||||||
|
var entries = await uow.GetTable<PlantedCurrency>()
|
||||||
|
.Where(x => x.ChannelId == ch.Id && pass == x.Password)
|
||||||
|
.DeleteWithOutputAsync();
|
||||||
|
|
||||||
|
if (!entries.Any())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
amount = entries.Sum(x => x.Amount);
|
||||||
|
ids = entries.Select(x => x.MessageId).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amount > 0)
|
||||||
|
await _cs.AddAsync(uid, amount, new("currency", "collect"));
|
||||||
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
long amount;
|
_ = ch.DeleteMessagesAsync(ids);
|
||||||
ulong[] ids;
|
|
||||||
await using (var uow = _db.GetDbContext())
|
|
||||||
{
|
|
||||||
// this method will sum all plants with that password,
|
|
||||||
// remove them, and get messageids of the removed plants
|
|
||||||
|
|
||||||
pass = pass?.Trim().TrimTo(10, true).ToUpperInvariant();
|
|
||||||
// gets all plants in this channel with the same password
|
|
||||||
var entries = uow.Set<PlantedCurrency>()
|
|
||||||
.AsQueryable()
|
|
||||||
.Where(x => x.ChannelId == ch.Id && pass == x.Password)
|
|
||||||
.ToList();
|
|
||||||
// sum how much currency that is, and get all of the message ids (so that i can delete them)
|
|
||||||
amount = entries.Sum(x => x.Amount);
|
|
||||||
ids = entries.Select(x => x.MessageId).ToArray();
|
|
||||||
// remove them from the database
|
|
||||||
uow.RemoveRange(entries);
|
|
||||||
|
|
||||||
|
|
||||||
if (amount > 0)
|
|
||||||
// give the picked currency to the user
|
|
||||||
await _cs.AddAsync(uid, amount, new("currency", "collect"));
|
|
||||||
await uow.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// delete all of the plant messages which have just been picked
|
|
||||||
_ = ch.DeleteMessagesAsync(ids);
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
|
|
||||||
// return the amount of currency the user picked
|
|
||||||
return amount;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_pickLock.Release();
|
|
||||||
}
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
// return the amount of currency the user picked
|
||||||
|
return amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ulong?> SendPlantMessageAsync(
|
public async Task<ulong?> SendPlantMessageAsync(
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace EllieBot.Modules.Gambling;
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class ShopCommands : GamblingSubmodule<IShopService>
|
public partial class ShopCommands : GamblingModule<IShopService>
|
||||||
{
|
{
|
||||||
public enum List
|
public enum List
|
||||||
{
|
{
|
||||||
|
|
|
@ -21,7 +21,7 @@ public enum GamblingError
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class SlotCommands : GamblingSubmodule<IGamblingService>
|
public partial class SlotCommands : GamblingModule<IGamblingService>
|
||||||
{
|
{
|
||||||
private readonly IImageCache _images;
|
private readonly IImageCache _images;
|
||||||
private readonly FontProvider _fonts;
|
private readonly FontProvider _fonts;
|
||||||
|
|
55
src/EllieBot/Modules/Gambling/UserBetStatsService.cs
Normal file
55
src/EllieBot/Modules/Gambling/UserBetStatsService.cs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
#nullable disable
|
||||||
|
using LinqToDB;
|
||||||
|
using LinqToDB.EntityFrameworkCore;
|
||||||
|
using EllieBot.Db.Models;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Gambling.Services;
|
||||||
|
|
||||||
|
public sealed class UserBetStatsService : IEService
|
||||||
|
{
|
||||||
|
private const long RESET_MIN_PRICE = 1000;
|
||||||
|
private const decimal RESET_TOTAL_MULTIPLIER = 0.002m;
|
||||||
|
|
||||||
|
private readonly DbService _db;
|
||||||
|
private readonly ICurrencyService _cs;
|
||||||
|
|
||||||
|
public UserBetStatsService(DbService db, ICurrencyService cs)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
_cs = cs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> GetResetStatsPriceAsync(ulong userId, GamblingGame? game)
|
||||||
|
{
|
||||||
|
await using var ctx = _db.GetDbContext();
|
||||||
|
|
||||||
|
var totalBet = await ctx.GetTable<UserBetStats>()
|
||||||
|
.Where(x => x.UserId == userId && (game == null || x.Game == game))
|
||||||
|
.SumAsyncLinqToDB(x => x.TotalBet);
|
||||||
|
|
||||||
|
return Math.Max(RESET_MIN_PRICE, (long)Math.Ceiling(totalBet * RESET_TOTAL_MULTIPLIER));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ResetStatsAsync(ulong userId, GamblingGame? game)
|
||||||
|
{
|
||||||
|
var price = await GetResetStatsPriceAsync(userId, game);
|
||||||
|
|
||||||
|
if (!await _cs.RemoveAsync(userId, price, new("betstats", "reset")))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await using var ctx = _db.GetDbContext();
|
||||||
|
await ctx.GetTable<UserBetStats>()
|
||||||
|
.DeleteAsync(x => x.UserId == userId && (game == null || x.Game == game));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ResetGamblingStatsAsync()
|
||||||
|
{
|
||||||
|
await using var ctx = _db.GetDbContext();
|
||||||
|
await ctx.GetTable<GamblingStats>()
|
||||||
|
.DeleteAsync();
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ namespace EllieBot.Modules.Gambling;
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class WaifuClaimCommands : GamblingSubmodule<WaifuService>
|
public partial class WaifuClaimCommands : GamblingModule<WaifuService>
|
||||||
{
|
{
|
||||||
public WaifuClaimCommands(GamblingConfigService gamblingConfService)
|
public WaifuClaimCommands(GamblingConfigService gamblingConfService)
|
||||||
: base(gamblingConfService)
|
: base(gamblingConfService)
|
||||||
|
@ -37,6 +37,45 @@ public partial class Gambling
|
||||||
await Response().Error(strs.waifu_reset_fail).SendAsync();
|
await Response().Error(strs.waifu_reset_fail).SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
public async Task WaifuClaims()
|
||||||
|
{
|
||||||
|
await Response()
|
||||||
|
.Paginated()
|
||||||
|
.PageItems(async (page) => await _service.GetClaimsAsync(ctx.User.Id, page))
|
||||||
|
.Page((items, page) =>
|
||||||
|
{
|
||||||
|
var eb = _sender.CreateEmbed()
|
||||||
|
.WithOkColor()
|
||||||
|
.WithTitle("Waifus");
|
||||||
|
|
||||||
|
if (items.Count == 0)
|
||||||
|
{
|
||||||
|
eb
|
||||||
|
.WithPendingColor()
|
||||||
|
.WithDescription(GetText(strs.empty_page));
|
||||||
|
|
||||||
|
return eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < items.Count; i++)
|
||||||
|
{
|
||||||
|
var item = items[i];
|
||||||
|
eb.AddField($"`#{(page * 9) + 1 + i}` {N(item.Price)}",
|
||||||
|
$"""
|
||||||
|
{item.Username}
|
||||||
|
||{item.UserId}||
|
||||||
|
""",
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return eb;
|
||||||
|
})
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
public async Task WaifuClaim(long amount, [Leftover] IUser target)
|
public async Task WaifuClaim(long amount, [Leftover] IUser target)
|
||||||
|
@ -144,7 +183,7 @@ public partial class Gambling
|
||||||
if (targetId == ctx.User.Id)
|
if (targetId == ctx.User.Id)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var (w, result, amount, remaining) = await _service.DivorceWaifuAsync(ctx.User, targetId);
|
var (w, result, amount) = await _service.DivorceWaifuAsync(ctx.User, targetId);
|
||||||
|
|
||||||
if (result == DivorceResult.SucessWithPenalty)
|
if (result == DivorceResult.SucessWithPenalty)
|
||||||
{
|
{
|
||||||
|
@ -157,14 +196,6 @@ public partial class Gambling
|
||||||
await Response().Confirm(strs.waifu_divorced_notlike(N(amount))).SendAsync();
|
await Response().Confirm(strs.waifu_divorced_notlike(N(amount))).SendAsync();
|
||||||
else if (result == DivorceResult.NotYourWife)
|
else if (result == DivorceResult.NotYourWife)
|
||||||
await Response().Error(strs.waifu_not_yours).SendAsync();
|
await Response().Error(strs.waifu_not_yours).SendAsync();
|
||||||
else if (remaining is { } rem)
|
|
||||||
{
|
|
||||||
await Response()
|
|
||||||
.Error(strs.waifu_recent_divorce(
|
|
||||||
Format.Bold(((int)rem.TotalHours).ToString()),
|
|
||||||
Format.Bold(rem.Minutes.ToString())))
|
|
||||||
.SendAsync();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
|
|
|
@ -318,25 +318,20 @@ public class WaifuService : IEService, IReadyExecutor
|
||||||
private static TypedKey<long> GetAffinityKey(ulong userId)
|
private static TypedKey<long> GetAffinityKey(ulong userId)
|
||||||
=> new($"waifu:affinity:{userId}");
|
=> new($"waifu:affinity:{userId}");
|
||||||
|
|
||||||
public async Task<(WaifuInfo, DivorceResult, long, TimeSpan?)> DivorceWaifuAsync(IUser user, ulong targetId)
|
public async Task<(WaifuInfo, DivorceResult, long)> DivorceWaifuAsync(IUser user, ulong targetId)
|
||||||
{
|
{
|
||||||
DivorceResult result;
|
DivorceResult result;
|
||||||
TimeSpan? remaining = null;
|
|
||||||
long amount = 0;
|
long amount = 0;
|
||||||
WaifuInfo w;
|
WaifuInfo w;
|
||||||
await using (var uow = _db.GetDbContext())
|
await using (var uow = _db.GetDbContext())
|
||||||
{
|
{
|
||||||
w = uow.Set<WaifuInfo>().ByWaifuUserId(targetId);
|
w = uow.Set<WaifuInfo>().ByWaifuUserId(targetId);
|
||||||
if (w?.Claimer is null || w.Claimer.UserId != user.Id)
|
if (w?.Claimer is null || w.Claimer.UserId != user.Id)
|
||||||
|
{
|
||||||
result = DivorceResult.NotYourWife;
|
result = DivorceResult.NotYourWife;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
remaining = await _cache.GetRatelimitAsync(GetDivorceKey(user.Id), 6.Hours());
|
|
||||||
if (remaining is TimeSpan rem)
|
|
||||||
{
|
|
||||||
result = DivorceResult.Cooldown;
|
|
||||||
return (w, result, amount, rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
amount = w.Price / 2;
|
amount = w.Price / 2;
|
||||||
|
|
||||||
|
@ -369,7 +364,7 @@ public class WaifuService : IEService, IReadyExecutor
|
||||||
await uow.SaveChangesAsync();
|
await uow.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (w, result, amount, remaining);
|
return (w, result, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> GiftWaifuAsync(
|
public async Task<bool> GiftWaifuAsync(
|
||||||
|
@ -603,7 +598,7 @@ public class WaifuService : IEService, IReadyExecutor
|
||||||
.Where(wi => wi.ClaimerId == waifuId)
|
.Where(wi => wi.ClaimerId == waifuId)
|
||||||
.Select(wi => wi.WaifuId)
|
.Select(wi => wi.WaifuId)
|
||||||
.Contains(x.Id))
|
.Contains(x.Id))
|
||||||
.Select(x => $"{x.Username}#{x.Discriminator}")
|
.Select(x => x.Username)
|
||||||
.ToListAsyncEF();
|
.ToListAsyncEF();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -615,7 +610,7 @@ public class WaifuService : IEService, IReadyExecutor
|
||||||
.Where(wi => wi.AffinityId == waifuId)
|
.Where(wi => wi.AffinityId == waifuId)
|
||||||
.Select(wi => wi.WaifuId)
|
.Select(wi => wi.WaifuId)
|
||||||
.Contains(x.Id))
|
.Contains(x.Id))
|
||||||
.Select(x => $"{x.Username}#{x.Discriminator}")
|
.Select(x => x.Username)
|
||||||
.ToListAsyncEF();
|
.ToListAsyncEF();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -630,4 +625,38 @@ public class WaifuService : IEService, IReadyExecutor
|
||||||
.FirstOrDefault())
|
.FirstOrDefault())
|
||||||
.ToListAsyncEF();
|
.ToListAsyncEF();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyCollection<WaifuClaimsResult>> GetClaimsAsync(ulong userId, int page)
|
||||||
|
{
|
||||||
|
await using var ctx = _db.GetDbContext();
|
||||||
|
|
||||||
|
var wid = ctx.GetTable<DiscordUser>()
|
||||||
|
.Where(x => x.UserId == userId)
|
||||||
|
.Select(x => x.Id)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if (wid == 0)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
return await ctx.GetTable<WaifuInfo>()
|
||||||
|
.Where(x => x.ClaimerId == wid)
|
||||||
|
.LeftJoin(ctx.GetTable<DiscordUser>(),
|
||||||
|
(wi, du) => wi.WaifuId == du.Id,
|
||||||
|
(wi, du) => new WaifuClaimsResult(
|
||||||
|
du.Username,
|
||||||
|
du.UserId,
|
||||||
|
wi.Price
|
||||||
|
))
|
||||||
|
.OrderByDescending(x => x.Price)
|
||||||
|
.Skip(page * 9)
|
||||||
|
.Take(9)
|
||||||
|
.ToListAsyncLinqToDB();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class WaifuClaimsResult(string username, ulong userId, long price)
|
||||||
|
{
|
||||||
|
public string Username { get; } = username;
|
||||||
|
public ulong UserId { get; } = userId;
|
||||||
|
public long Price { get; } = price;
|
||||||
}
|
}
|
|
@ -42,15 +42,12 @@ public static class WaifuExtensions
|
||||||
{
|
{
|
||||||
Affinity = x.Affinity == null
|
Affinity = x.Affinity == null
|
||||||
? null
|
? null
|
||||||
: x.Affinity.Username
|
: x.Affinity.Username,
|
||||||
+ (x.Affinity.Discriminator != "0000" ? "#" + x.Affinity.Discriminator : ""),
|
|
||||||
ClaimerName =
|
ClaimerName =
|
||||||
x.Claimer == null
|
x.Claimer == null
|
||||||
? null
|
? null
|
||||||
: x.Claimer.Username
|
: x.Claimer.Username,
|
||||||
+ (x.Claimer.Discriminator != "0000" ? "#" + x.Claimer.Discriminator : ""),
|
WaifuName = x.Waifu.Username,
|
||||||
WaifuName = x.Waifu.Username
|
|
||||||
+ (x.Waifu.Discriminator != "0000" ? "#" + x.Waifu.Discriminator : ""),
|
|
||||||
Price = x.Price
|
Price = x.Price
|
||||||
})
|
})
|
||||||
.ToListAsyncEF();
|
.ToListAsyncEF();
|
||||||
|
@ -62,7 +59,7 @@ public static class WaifuExtensions
|
||||||
public static ulong GetWaifuUserId(this DbSet<WaifuInfo> waifus, ulong ownerId, string name)
|
public static ulong GetWaifuUserId(this DbSet<WaifuInfo> waifus, ulong ownerId, string name)
|
||||||
=> waifus.AsQueryable()
|
=> waifus.AsQueryable()
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(x => x.Claimer.UserId == ownerId && x.Waifu.Username + "#" + x.Waifu.Discriminator == name)
|
.Where(x => x.Claimer.UserId == ownerId && x.Waifu.Username == name)
|
||||||
.Select(x => x.Waifu.UserId)
|
.Select(x => x.Waifu.UserId)
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
@ -100,7 +97,7 @@ public static class WaifuExtensions
|
||||||
ctx.Set<DiscordUser>()
|
ctx.Set<DiscordUser>()
|
||||||
.AsQueryable()
|
.AsQueryable()
|
||||||
.Where(u => u.UserId == userId)
|
.Where(u => u.UserId == userId)
|
||||||
.Select(u => u.Username + "#" + u.Discriminator)
|
.Select(u => u.Username)
|
||||||
.FirstOrDefault(),
|
.FirstOrDefault(),
|
||||||
AffinityCount =
|
AffinityCount =
|
||||||
ctx.Set<WaifuUpdate>()
|
ctx.Set<WaifuUpdate>()
|
||||||
|
@ -112,14 +109,14 @@ public static class WaifuExtensions
|
||||||
ctx.Set<DiscordUser>()
|
ctx.Set<DiscordUser>()
|
||||||
.AsQueryable()
|
.AsQueryable()
|
||||||
.Where(u => u.Id == w.AffinityId)
|
.Where(u => u.Id == w.AffinityId)
|
||||||
.Select(u => u.Username + "#" + u.Discriminator)
|
.Select(u => u.Username)
|
||||||
.FirstOrDefault(),
|
.FirstOrDefault(),
|
||||||
ClaimCount = ctx.Set<WaifuInfo>().AsQueryable().Count(x => x.ClaimerId == w.WaifuId),
|
ClaimCount = ctx.Set<WaifuInfo>().AsQueryable().Count(x => x.ClaimerId == w.WaifuId),
|
||||||
ClaimerName =
|
ClaimerName =
|
||||||
ctx.Set<DiscordUser>()
|
ctx.Set<DiscordUser>()
|
||||||
.AsQueryable()
|
.AsQueryable()
|
||||||
.Where(u => u.Id == w.ClaimerId)
|
.Where(u => u.Id == w.ClaimerId)
|
||||||
.Select(u => u.Username + "#" + u.Discriminator)
|
.Select(u => u.Username)
|
||||||
.FirstOrDefault(),
|
.FirstOrDefault(),
|
||||||
DivorceCount =
|
DivorceCount =
|
||||||
ctx.Set<WaifuUpdate>()
|
ctx.Set<WaifuUpdate>()
|
||||||
|
|
|
@ -13,5 +13,10 @@ public interface IGamblingService
|
||||||
Task<OneOf<SlotResult, GamblingError>> SlotAsync(ulong userId, long amount);
|
Task<OneOf<SlotResult, GamblingError>> SlotAsync(ulong userId, long amount);
|
||||||
Task<FlipResult[]> FlipAsync(int count);
|
Task<FlipResult[]> FlipAsync(int count);
|
||||||
Task<OneOf<RpsResult, GamblingError>> RpsAsync(ulong userId, long amount, byte pick);
|
Task<OneOf<RpsResult, GamblingError>> RpsAsync(ulong userId, long amount, byte pick);
|
||||||
Task<OneOf<BetdrawResult, GamblingError>> BetDrawAsync(ulong userId, long amount, byte? maybeGuessValue, byte? maybeGuessColor);
|
|
||||||
|
Task<OneOf<BetdrawResult, GamblingError>> BetDrawAsync(
|
||||||
|
ulong userId,
|
||||||
|
long amount,
|
||||||
|
byte? maybeGuessValue,
|
||||||
|
byte? maybeGuessColor);
|
||||||
}
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
using LinqToDB;
|
||||||
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using EllieBot.Modules.Gambling.Betdraw;
|
using EllieBot.Modules.Gambling.Betdraw;
|
||||||
using EllieBot.Modules.Gambling.Rps;
|
using EllieBot.Modules.Gambling.Rps;
|
||||||
using EllieBot.Modules.Gambling.Services;
|
using EllieBot.Modules.Gambling.Services;
|
||||||
|
@ -8,12 +10,12 @@ namespace EllieBot.Modules.Gambling;
|
||||||
|
|
||||||
public sealed class NewGamblingService : IGamblingService, IEService
|
public sealed class NewGamblingService : IGamblingService, IEService
|
||||||
{
|
{
|
||||||
private readonly GamblingConfigService _bcs;
|
private readonly GamblingConfigService _gcs;
|
||||||
private readonly ICurrencyService _cs;
|
private readonly ICurrencyService _cs;
|
||||||
|
|
||||||
public NewGamblingService(GamblingConfigService bcs, ICurrencyService cs)
|
public NewGamblingService(GamblingConfigService gcs, ICurrencyService cs)
|
||||||
{
|
{
|
||||||
_bcs = bcs;
|
_gcs = gcs;
|
||||||
_cs = cs;
|
_cs = cs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,13 +33,13 @@ public sealed class NewGamblingService : IGamblingService, IEService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var game = new LulaGame(_bcs.Data.LuckyLadder.Multipliers);
|
var game = new LulaGame(_gcs.Data.LuckyLadder.Multipliers);
|
||||||
var result = game.Spin(amount);
|
var result = game.Spin(amount);
|
||||||
|
|
||||||
var won = (long)result.Won;
|
var won = (long)result.Won;
|
||||||
if (won > 0)
|
if (won > 0)
|
||||||
{
|
{
|
||||||
await _cs.AddAsync(userId, won, new("lula", "win"));
|
await _cs.AddAsync(userId, won, new("lula", result.Multiplier >= 1 ? "win" : "lose"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -57,9 +59,9 @@ public sealed class NewGamblingService : IGamblingService, IEService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var game = new BetrollGame(_bcs.Data.BetRoll.Pairs
|
var game = new BetrollGame(_gcs.Data.BetRoll.Pairs
|
||||||
.Select(x => (x.WhenAbove, (decimal)x.MultiplyBy))
|
.Select(x => (x.WhenAbove, (decimal)x.MultiplyBy))
|
||||||
.ToList());
|
.ToList());
|
||||||
|
|
||||||
var result = game.Roll(amount);
|
var result = game.Roll(amount);
|
||||||
|
|
||||||
|
@ -88,7 +90,7 @@ public sealed class NewGamblingService : IGamblingService, IEService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var game = new BetflipGame(_bcs.Data.BetFlip.Multiplier);
|
var game = new BetflipGame(_gcs.Data.BetFlip.Multiplier);
|
||||||
var result = game.Flip(guess, amount);
|
var result = game.Flip(guess, amount);
|
||||||
|
|
||||||
var won = (long)result.Won;
|
var won = (long)result.Won;
|
||||||
|
@ -100,7 +102,11 @@ public sealed class NewGamblingService : IGamblingService, IEService
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<OneOf<BetdrawResult, GamblingError>> BetDrawAsync(ulong userId, long amount, byte? maybeGuessValue, byte? maybeGuessColor)
|
public async Task<OneOf<BetdrawResult, GamblingError>> BetDrawAsync(
|
||||||
|
ulong userId,
|
||||||
|
long amount,
|
||||||
|
byte? maybeGuessValue,
|
||||||
|
byte? maybeGuessColor)
|
||||||
{
|
{
|
||||||
ArgumentOutOfRangeException.ThrowIfNegative(amount);
|
ArgumentOutOfRangeException.ThrowIfNegative(amount);
|
||||||
|
|
||||||
|
@ -155,7 +161,7 @@ public sealed class NewGamblingService : IGamblingService, IEService
|
||||||
var won = (long)result.Won;
|
var won = (long)result.Won;
|
||||||
if (won > 0)
|
if (won > 0)
|
||||||
{
|
{
|
||||||
await _cs.AddAsync(userId, won, new("slot", "won"));
|
await _cs.AddAsync(userId, won, new("slot", "win"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -266,3 +272,45 @@ public sealed class NewGamblingService : IGamblingService, IEService
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class RakebackService : IEService
|
||||||
|
{
|
||||||
|
private readonly DbService _db;
|
||||||
|
private readonly ICurrencyService _cs;
|
||||||
|
|
||||||
|
public RakebackService(DbService db, ICurrencyService cs)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
_cs = cs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> GetRakebackAsync(ulong userId)
|
||||||
|
{
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
|
||||||
|
var rb = uow.GetTable<Rakeback>()
|
||||||
|
.Where(x => x.UserId == userId)
|
||||||
|
.Select(x => x.Amount)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
return (long)rb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> ClaimRakebackAsync(ulong userId)
|
||||||
|
{
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
|
||||||
|
var rbs = await uow.GetTable<Rakeback>()
|
||||||
|
.Where(x => x.UserId == userId)
|
||||||
|
.DeleteWithOutputAsync((x) => x.Amount);
|
||||||
|
|
||||||
|
if (rbs.Length == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var rb = (long)rbs[0];
|
||||||
|
|
||||||
|
await _cs.AddAsync(userId, rb, new("rakeback", "claim"));
|
||||||
|
|
||||||
|
return rb;
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,6 @@ using SixLabors.ImageSharp.Advanced;
|
||||||
using SixLabors.ImageSharp.Drawing.Processing;
|
using SixLabors.ImageSharp.Drawing.Processing;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
using SixLabors.ImageSharp.Processing;
|
using SixLabors.ImageSharp.Processing;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Image = SixLabors.ImageSharp.Image;
|
using Image = SixLabors.ImageSharp.Image;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Games;
|
namespace EllieBot.Modules.Games;
|
||||||
|
@ -172,7 +171,23 @@ public partial class Games
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _service.SetPixel(position, clr.PackedValue, text, ctx.User.Id, pixel.Price);
|
var result = await _service.SetPixel(position, clr.PackedValue, text, ctx.User.Id, pixel.Price);
|
||||||
|
|
||||||
|
if (result == SetPixelResult.NotEnoughMoney)
|
||||||
|
{
|
||||||
|
await Response().Error(strs.not_enough(_gcs.Data.Currency.Sign)).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (result == SetPixelResult.InsufficientPayment)
|
||||||
|
{
|
||||||
|
await Response().Error(strs.nc_insuff_payment).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (result == SetPixelResult.InvalidInput)
|
||||||
|
{
|
||||||
|
await Response().Error(strs.invalid_input).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
using var img = await GetZoomImage(position);
|
using var img = await GetZoomImage(position);
|
||||||
await using var stream = await img.ToStreamAsync();
|
await using var stream = await img.ToStreamAsync();
|
||||||
|
|
|
@ -20,7 +20,7 @@ public sealed class NCanvasService : INCanvasService, IReadyExecutor, IEService
|
||||||
|
|
||||||
public const int CANVAS_WIDTH = 500;
|
public const int CANVAS_WIDTH = 500;
|
||||||
public const int CANVAS_HEIGHT = 350;
|
public const int CANVAS_HEIGHT = 350;
|
||||||
public const int INITIAL_PRICE = 10;
|
public const int INITIAL_PRICE = 3;
|
||||||
|
|
||||||
public NCanvasService(
|
public NCanvasService(
|
||||||
DbService db,
|
DbService db,
|
||||||
|
@ -110,7 +110,7 @@ public sealed class NCanvasService : INCanvasService, IReadyExecutor, IEService
|
||||||
|
|
||||||
var wallet = await _cs.GetWalletAsync(userId);
|
var wallet = await _cs.GetWalletAsync(userId);
|
||||||
|
|
||||||
var paid = await wallet.Take(price, new("canvas", "pixel", $"Bought pixel #{position}"));
|
var paid = await wallet.Take(price, new("canvas", "pixel-buy", $"Bought pixel {new kwum(position)}"));
|
||||||
if (!paid)
|
if (!paid)
|
||||||
{
|
{
|
||||||
return SetPixelResult.NotEnoughMoney;
|
return SetPixelResult.NotEnoughMoney;
|
||||||
|
@ -138,7 +138,7 @@ public sealed class NCanvasService : INCanvasService, IReadyExecutor, IEService
|
||||||
|
|
||||||
if (!success)
|
if (!success)
|
||||||
{
|
{
|
||||||
await wallet.Add(price, new("canvas", "pixel-refund", $"Refund pixel #{position} purchase"));
|
await wallet.Add(price, new("canvas", "pixel-refund", $"Refund pixel {new kwum(position)} purchase"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return success ? SetPixelResult.Success : SetPixelResult.InsufficientPayment;
|
return success ? SetPixelResult.Success : SetPixelResult.InsufficientPayment;
|
||||||
|
|
|
@ -29,7 +29,7 @@ public partial class Games
|
||||||
if (!await nunchi.Join(ctx.User.Id, ctx.User.ToString()))
|
if (!await nunchi.Join(ctx.User.Id, ctx.User.ToString()))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await Response().Error(strs.nunchi_joined(nunchi.ParticipantCount)).SendAsync();
|
await Response().Confirm(strs.nunchi_joined(nunchi.ParticipantCount)).SendAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -122,9 +122,9 @@ public sealed class CurrencyRewardService : IEService, IReadyExecutor
|
||||||
var dollarValue = pledgeCents / 100;
|
var dollarValue = pledgeCents / 100;
|
||||||
percentBonus = dollarValue switch
|
percentBonus = dollarValue switch
|
||||||
{
|
{
|
||||||
>= 100 => 100,
|
>= 100 => 25,
|
||||||
>= 50 => 50,
|
>= 50 => 20,
|
||||||
>= 20 => 20,
|
>= 20 => 15,
|
||||||
>= 10 => 10,
|
>= 10 => 10,
|
||||||
>= 5 => 5,
|
>= 5 => 5,
|
||||||
_ => 0
|
_ => 0
|
||||||
|
|
|
@ -404,9 +404,9 @@ public sealed class PatronageService
|
||||||
{
|
{
|
||||||
>= 10_000 => 100,
|
>= 10_000 => 100,
|
||||||
>= 5000 => 50,
|
>= 5000 => 50,
|
||||||
>= 2000 => 20,
|
>= 2000 => 30,
|
||||||
>= 1000 => 10,
|
>= 1000 => 20,
|
||||||
>= 500 => 5,
|
>= 500 => 10,
|
||||||
_ => 0
|
_ => 0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -18,39 +18,39 @@ public partial class Permissions
|
||||||
{
|
{
|
||||||
ArgumentOutOfRangeException.ThrowIfNegative(page);
|
ArgumentOutOfRangeException.ThrowIfNegative(page);
|
||||||
|
|
||||||
var list = _service.GetBlacklist();
|
var list = await _service.GetBlacklist(type);
|
||||||
var allItems = await list.Where(x => x.Type == type)
|
var allItems = await list
|
||||||
.Select(i =>
|
.Select(i =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return Task.FromResult(i.Type switch
|
return Task.FromResult(type switch
|
||||||
{
|
{
|
||||||
BlacklistType.Channel => Format.Code(i.ItemId.ToString())
|
BlacklistType.Channel => Format.Code(i.ItemId.ToString())
|
||||||
+ " "
|
|
||||||
+ (_client.GetChannel(i.ItemId)?.ToString()
|
|
||||||
?? ""),
|
|
||||||
BlacklistType.User => Format.Code(i.ItemId.ToString())
|
|
||||||
+ " "
|
|
||||||
+ ((_client.GetUser(i.ItemId))
|
|
||||||
?.ToString()
|
|
||||||
?? ""),
|
|
||||||
BlacklistType.Server => Format.Code(i.ItemId.ToString())
|
|
||||||
+ " "
|
+ " "
|
||||||
+ (_client.GetGuild(i.ItemId)?.ToString() ?? ""),
|
+ (_client.GetChannel(i.ItemId)?.ToString()
|
||||||
_ => Format.Code(i.ItemId.ToString())
|
?? ""),
|
||||||
});
|
BlacklistType.User => Format.Code(i.ItemId.ToString())
|
||||||
}
|
+ " "
|
||||||
catch
|
+ ((_client.GetUser(i.ItemId))
|
||||||
{
|
?.ToString()
|
||||||
Log.Warning("Can't get {BlacklistType} [{BlacklistItemId}]",
|
?? ""),
|
||||||
i.Type,
|
BlacklistType.Server => Format.Code(i.ItemId.ToString())
|
||||||
i.ItemId);
|
+ " "
|
||||||
|
+ (_client.GetGuild(i.ItemId)?.ToString() ?? ""),
|
||||||
|
_ => Format.Code(i.ItemId.ToString())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Log.Warning("Can't get {BlacklistType} [{BlacklistItemId}]",
|
||||||
|
i.Type,
|
||||||
|
i.ItemId);
|
||||||
|
|
||||||
return Task.FromResult(Format.Code(i.ItemId.ToString()));
|
return Task.FromResult(Format.Code(i.ItemId.ToString()));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.WhenAll();
|
.WhenAll();
|
||||||
|
|
||||||
await Response()
|
await Response()
|
||||||
.Paginated()
|
.Paginated()
|
||||||
|
@ -61,14 +61,14 @@ public partial class Permissions
|
||||||
{
|
{
|
||||||
if (pageItems.Count == 0)
|
if (pageItems.Count == 0)
|
||||||
return _sender.CreateEmbed()
|
return _sender.CreateEmbed()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithTitle(title)
|
.WithTitle(title)
|
||||||
.WithDescription(GetText(strs.empty_page));
|
.WithDescription(GetText(strs.empty_page));
|
||||||
|
|
||||||
return _sender.CreateEmbed()
|
return _sender.CreateEmbed()
|
||||||
.WithTitle(title)
|
.WithTitle(title)
|
||||||
.WithDescription(allItems.Join('\n'))
|
.WithDescription(pageItems.Join('\n'))
|
||||||
.WithOkColor();
|
.WithOkColor();
|
||||||
})
|
})
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,9 @@ public sealed class YoutubeDataApiSearchService : IYoutubeSearchService, IEServi
|
||||||
if (results.Count == 0)
|
if (results.Count == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return results.Map(r => new VideoInfo(r));
|
return results.Map(r => new VideoInfo()
|
||||||
|
{
|
||||||
|
Url = r
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
191
src/EllieBot/Modules/Searches/Translate/FlagTranslateService.cs
Normal file
191
src/EllieBot/Modules/Searches/Translate/FlagTranslateService.cs
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
#nullable disable
|
||||||
|
using LinqToDB;
|
||||||
|
using LinqToDB.EntityFrameworkCore;
|
||||||
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
|
using EllieBot.Db.Models;
|
||||||
|
using System.Collections.Frozen;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Searches;
|
||||||
|
|
||||||
|
public sealed partial class FlagTranslateService : IReadyExecutor, IEService
|
||||||
|
{
|
||||||
|
private readonly IBotCreds _creds;
|
||||||
|
private readonly DiscordSocketClient _client;
|
||||||
|
private readonly TranslateService _ts;
|
||||||
|
private readonly IMessageSenderService _sender;
|
||||||
|
private IReadOnlyDictionary<string, string> _supportedFlags;
|
||||||
|
private readonly DbService _db;
|
||||||
|
private ConcurrentHashSet<ulong> _enabledChannels;
|
||||||
|
private readonly IBotCache _cache;
|
||||||
|
|
||||||
|
// disallow same message being translated multiple times to the same language
|
||||||
|
private readonly ConcurrentHashSet<(ulong, string)> _msgLangs = new();
|
||||||
|
|
||||||
|
public FlagTranslateService(
|
||||||
|
IBotCreds creds,
|
||||||
|
DiscordSocketClient client,
|
||||||
|
TranslateService ts,
|
||||||
|
IMessageSenderService sender,
|
||||||
|
DbService db,
|
||||||
|
IBotCache cache)
|
||||||
|
{
|
||||||
|
_creds = creds;
|
||||||
|
_client = client;
|
||||||
|
_ts = ts;
|
||||||
|
_sender = sender;
|
||||||
|
_db = db;
|
||||||
|
_cache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OnReadyAsync()
|
||||||
|
{
|
||||||
|
_supportedFlags = COUNTRIES
|
||||||
|
.Split('\n')
|
||||||
|
.Select(x => x.Split(' '))
|
||||||
|
.ToDictionary(x => x[0], x => x[1].TrimEnd())
|
||||||
|
.ToFrozenDictionary();
|
||||||
|
|
||||||
|
await using (var uow = _db.GetDbContext())
|
||||||
|
{
|
||||||
|
_enabledChannels = (await uow.GetTable<FlagTranslateChannel>()
|
||||||
|
.Where(x => Linq2DbExpressions.GuildOnShard(x.GuildId,
|
||||||
|
_creds.TotalShards,
|
||||||
|
_client.ShardId))
|
||||||
|
.Select(x => new
|
||||||
|
{
|
||||||
|
x.ChannelId,
|
||||||
|
x.GuildId
|
||||||
|
})
|
||||||
|
.ToListAsyncLinqToDB())
|
||||||
|
.Select(x => x.ChannelId)
|
||||||
|
.ToHashSet()
|
||||||
|
.ToConcurrentSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
_client.ReactionAdded += OnReactionAdded;
|
||||||
|
|
||||||
|
var periodicCleanup = new PeriodicTimer(TimeSpan.FromHours(24));
|
||||||
|
|
||||||
|
while (await periodicCleanup.WaitForNextTickAsync())
|
||||||
|
{
|
||||||
|
_msgLangs.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const int FLAG_START = 127462;
|
||||||
|
|
||||||
|
private static TypedKey<bool> CdKey(ulong userId)
|
||||||
|
=> new($"flagtranslate:{userId}");
|
||||||
|
|
||||||
|
private Task OnReactionAdded(
|
||||||
|
Cacheable<IUserMessage, ulong> arg1,
|
||||||
|
Cacheable<IMessageChannel, ulong> arg2,
|
||||||
|
SocketReaction reaction)
|
||||||
|
{
|
||||||
|
if (!_enabledChannels.Contains(reaction.Channel.Id))
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
var runes = reaction.Emote.Name.EnumerateRunes();
|
||||||
|
if (!runes.MoveNext()
|
||||||
|
|| runes.Current is not { Value: >= 127462 and <= 127487 } l1
|
||||||
|
|| !runes.MoveNext()
|
||||||
|
|| runes.Current is not { Value: >= 127462 and <= 127487 } l2)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
if (reaction.Channel is not SocketTextChannel tc)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var user = await ((IGuild)tc.Guild).GetUserAsync(reaction.UserId);
|
||||||
|
|
||||||
|
if (user is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!user.GetPermissions(tc).SendMessages)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!tc.Guild.CurrentUser.GetPermissions(tc).SendMessages
|
||||||
|
|| !tc.Guild.CurrentUser.GetPermissions(tc).EmbedLinks)
|
||||||
|
{
|
||||||
|
await Disable(tc.Guild.Id, tc.Id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var c1 = (char)(l1.Value - FLAG_START + 65);
|
||||||
|
var c2 = (char)(l2.Value - FLAG_START + 65);
|
||||||
|
|
||||||
|
var code = $"{c1}{c2}".ToUpper();
|
||||||
|
|
||||||
|
if (!_supportedFlags.TryGetValue(code, out var lang))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_msgLangs.Add((reaction.MessageId, lang)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var result = await _cache.GetAsync(CdKey(reaction.UserId));
|
||||||
|
if (result.TryPickT0(out _, out _))
|
||||||
|
return;
|
||||||
|
|
||||||
|
await _cache.AddAsync(CdKey(reaction.UserId), true, TimeSpan.FromSeconds(5));
|
||||||
|
|
||||||
|
var msg = await arg1.GetOrDownloadAsync();
|
||||||
|
|
||||||
|
var response = await _ts.Translate("", lang, msg.Content).ConfigureAwait(false);
|
||||||
|
|
||||||
|
await msg.ReplyAsync(embed: _sender.CreateEmbed()
|
||||||
|
.WithOkColor()
|
||||||
|
.WithFooter(user.ToString() ?? reaction.UserId.ToString(),
|
||||||
|
user.RealAvatarUrl().ToString())
|
||||||
|
.WithDescription(response)
|
||||||
|
.WithAuthor(reaction.Emote.ToString())
|
||||||
|
.Build(),
|
||||||
|
allowedMentions: AllowedMentions.None
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Disable(ulong guildId, ulong tcId)
|
||||||
|
{
|
||||||
|
if (!_enabledChannels.TryRemove(tcId))
|
||||||
|
return;
|
||||||
|
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
await uow.GetTable<FlagTranslateChannel>()
|
||||||
|
.Where(x => x.GuildId == guildId
|
||||||
|
&& x.ChannelId == tcId)
|
||||||
|
.DeleteAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> Toggle(ulong guildId, ulong tcId)
|
||||||
|
{
|
||||||
|
if (_enabledChannels.Contains(tcId))
|
||||||
|
{
|
||||||
|
await Disable(guildId, tcId);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Enable(guildId, tcId);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Enable(ulong guildId, ulong tcId)
|
||||||
|
{
|
||||||
|
if (!_enabledChannels.Add(tcId))
|
||||||
|
return;
|
||||||
|
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
await uow.GetTable<FlagTranslateChannel>()
|
||||||
|
.InsertAsync(() => new FlagTranslateChannel
|
||||||
|
{
|
||||||
|
GuildId = guildId,
|
||||||
|
ChannelId = tcId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
namespace EllieBot.Modules.Searches;
|
||||||
|
|
||||||
|
public partial class FlagTranslateService
|
||||||
|
{
|
||||||
|
private const string COUNTRIES = """
|
||||||
|
CN zh
|
||||||
|
IN hi
|
||||||
|
US en
|
||||||
|
ID id
|
||||||
|
PK ur
|
||||||
|
BR pt
|
||||||
|
NG ha
|
||||||
|
BD bn
|
||||||
|
RU ru
|
||||||
|
JP ja
|
||||||
|
MX es
|
||||||
|
PH tl
|
||||||
|
VN vi
|
||||||
|
EG ar
|
||||||
|
ET am
|
||||||
|
DE de
|
||||||
|
IR fa
|
||||||
|
TR tr
|
||||||
|
TH th
|
||||||
|
FR fr
|
||||||
|
CD fr
|
||||||
|
MM my
|
||||||
|
UG en
|
||||||
|
MZ pt
|
||||||
|
ZA zu
|
||||||
|
CO es
|
||||||
|
BG bg
|
||||||
|
HR hr
|
||||||
|
MY ms
|
||||||
|
NL nl
|
||||||
|
RO ro
|
||||||
|
CZ cs
|
||||||
|
GR el
|
||||||
|
SK sk
|
||||||
|
PT pt
|
||||||
|
KR ko
|
||||||
|
IT it
|
||||||
|
ES es
|
||||||
|
RS sr
|
||||||
|
TN ar
|
||||||
|
PL pl
|
||||||
|
SD ar
|
||||||
|
CM fr
|
||||||
|
SN fr
|
||||||
|
ML fr
|
||||||
|
NE ha
|
||||||
|
BI fr
|
||||||
|
AO pt
|
||||||
|
AF ps
|
||||||
|
MA ar
|
||||||
|
DZ ar
|
||||||
|
GB en
|
||||||
|
AR es
|
||||||
|
ZW ny
|
||||||
|
KE sw
|
||||||
|
GH en
|
||||||
|
SA ar
|
||||||
|
IL he
|
||||||
|
IQ ar
|
||||||
|
UA ua
|
||||||
|
LY ar
|
||||||
|
KW ar
|
||||||
|
OM ar
|
||||||
|
YE ar
|
||||||
|
AL sq
|
||||||
|
AE ar
|
||||||
|
AU en
|
||||||
|
NZ en
|
||||||
|
KZ kz
|
||||||
|
NO no
|
||||||
|
SE sv
|
||||||
|
DK da
|
||||||
|
FI fi
|
||||||
|
HU hu
|
||||||
|
""";
|
||||||
|
}
|
|
@ -44,12 +44,10 @@ public sealed class TranslateService : ITranslateService, IExecNoCommand, IReady
|
||||||
foreach (var c in cs)
|
foreach (var c in cs)
|
||||||
{
|
{
|
||||||
_atcs[c.ChannelId] = c.AutoDelete;
|
_atcs[c.ChannelId] = c.AutoDelete;
|
||||||
_users[c.ChannelId] =
|
_users[c.ChannelId] = new(c.Users.ToDictionary(x => x.UserId, x => (x.Source.ToLower(), x.Target.ToLower())));
|
||||||
new(c.Users.ToDictionary(x => x.UserId, x => (x.Source.ToLower(), x.Target.ToLower())));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task ExecOnNoCommandAsync(IGuild guild, IUserMessage msg)
|
public async Task ExecOnNoCommandAsync(IGuild guild, IUserMessage msg)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(msg.Content))
|
if (string.IsNullOrWhiteSpace(msg.Content))
|
||||||
|
@ -95,7 +93,7 @@ public sealed class TranslateService : ITranslateService, IExecNoCommand, IReady
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> Translate(string source, string target, string text = null)
|
public async Task<string> Translate(string source, string target, string text)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(text))
|
if (string.IsNullOrWhiteSpace(text))
|
||||||
throw new ArgumentException("Text is empty or null", nameof(text));
|
throw new ArgumentException("Text is empty or null", nameof(text));
|
||||||
|
|
|
@ -6,6 +6,14 @@ public partial class Searches
|
||||||
[Group]
|
[Group]
|
||||||
public partial class TranslateCommands : EllieModule<ITranslateService>
|
public partial class TranslateCommands : EllieModule<ITranslateService>
|
||||||
{
|
{
|
||||||
|
private readonly FlagTranslateService _flagSvc;
|
||||||
|
|
||||||
|
public TranslateCommands(FlagTranslateService flagSvc)
|
||||||
|
{
|
||||||
|
_flagSvc = flagSvc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public enum AutoDeleteAutoTranslate
|
public enum AutoDeleteAutoTranslate
|
||||||
{
|
{
|
||||||
Del,
|
Del,
|
||||||
|
@ -91,5 +99,18 @@ public partial class Searches
|
||||||
|
|
||||||
await Response().Embed(eb).SendAsync();
|
await Response().Embed(eb).SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
[UserPerm(ChannelPermission.ManageChannels)]
|
||||||
|
[BotPerm(ChannelPermission.SendMessages | ChannelPermission.EmbedLinks)]
|
||||||
|
public async Task TranslateFlags()
|
||||||
|
{
|
||||||
|
var enabled = await _flagSvc.Toggle(ctx.Guild.Id, ctx.Channel.Id);
|
||||||
|
if (enabled)
|
||||||
|
await Response().Confirm(strs.trfl_enabled).SendAsync();
|
||||||
|
else
|
||||||
|
await Response().Confirm(strs.trfl_disabled).SendAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -783,4 +783,28 @@ public partial class Utility : EllieModule
|
||||||
await Response().Error(ex.Message).SendAsync();
|
await Response().Error(ex.Message).SendAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public async Task Snipe()
|
||||||
|
{
|
||||||
|
if (ctx.Message.ReferencedMessage is not { } msg)
|
||||||
|
{
|
||||||
|
var msgs = await ctx.Channel.GetMessagesAsync(ctx.Message, Direction.Before, 3).FlattenAsync();
|
||||||
|
msg = msgs.FirstOrDefault(x => !string.IsNullOrWhiteSpace(x.Content) || (x.Attachments.FirstOrDefault()?.Width is not null)) as IUserMessage;
|
||||||
|
|
||||||
|
if (msg is null)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var eb = _sender.CreateEmbed()
|
||||||
|
.WithOkColor()
|
||||||
|
.WithDescription(msg.Content)
|
||||||
|
.WithAuthor(msg.Author)
|
||||||
|
.WithTimestamp(msg.Timestamp)
|
||||||
|
.WithImageUrl(msg.Attachments.FirstOrDefault()?.Url)
|
||||||
|
.WithFooter(GetText(strs.sniped_by(ctx.User.ToString())), ctx.User.GetDisplayAvatarUrl());
|
||||||
|
|
||||||
|
ctx.Message.DeleteAfter(1);
|
||||||
|
await Response().Embed(eb).SendAsync();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -225,7 +225,7 @@ public partial class Xp : EllieModule<XpService>
|
||||||
else if (userXpData.AwardedXp < 0)
|
else if (userXpData.AwardedXp < 0)
|
||||||
awardStr = $"({userXpData.AwardedXp})";
|
awardStr = $"({userXpData.AwardedXp})";
|
||||||
|
|
||||||
embed.AddField($"#{i + 1 + (curPage * 9)} {user?.ToString() ?? users[i].UserId.ToString()}",
|
embed.AddField($"#{i + 1 + (curPage * 10)} {user?.ToString() ?? users[i].UserId.ToString()}",
|
||||||
$"{GetText(strs.level_x(levelStats.Level))} - {levelStats.TotalXp}xp {awardStr}");
|
$"{GetText(strs.level_x(levelStats.Level))} - {levelStats.TotalXp}xp {awardStr}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,7 +278,7 @@ public partial class Xp : EllieModule<XpService>
|
||||||
for (var i = 0; i < users.Count; i++)
|
for (var i = 0; i < users.Count; i++)
|
||||||
{
|
{
|
||||||
var user = users[i];
|
var user = users[i];
|
||||||
embed.AddField($"#{i + 1 + (curPage * 9)} {user}",
|
embed.AddField($"#{i + 1 + (curPage * 10)} {user}",
|
||||||
$"{GetText(strs.level_x(new LevelStats(users[i].TotalXp).Level))} - {users[i].TotalXp}xp");
|
$"{GetText(strs.level_x(new LevelStats(users[i].TotalXp).Level))} - {users[i].TotalXp}xp");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -357,7 +357,7 @@ public partial class Xp : EllieModule<XpService>
|
||||||
if (!await PromptUserConfirmAsync(embed))
|
if (!await PromptUserConfirmAsync(embed))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_service.XpReset(ctx.Guild.Id, userId);
|
await _service.XpReset(ctx.Guild.Id, userId);
|
||||||
|
|
||||||
await Response().Confirm(strs.reset_user(userId)).SendAsync();
|
await Response().Confirm(strs.reset_user(userId)).SendAsync();
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,31 @@ using Image = SixLabors.ImageSharp.Image;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Xp.Services;
|
namespace EllieBot.Modules.Xp.Services;
|
||||||
|
|
||||||
|
public interface IUserService
|
||||||
|
{
|
||||||
|
Task<DiscordUser?> GetUserAsync(ulong userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class UserService : IUserService, IEService
|
||||||
|
{
|
||||||
|
private readonly DbService _db;
|
||||||
|
|
||||||
|
public UserService(DbService db)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<DiscordUser> GetUserAsync(ulong userId)
|
||||||
|
{
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
var user = await uow
|
||||||
|
.GetTable<DiscordUser>()
|
||||||
|
.FirstOrDefaultAsyncLinqToDB(u => u.UserId == userId);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
{
|
{
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
|
@ -1437,11 +1462,11 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void XpReset(ulong guildId, ulong userId)
|
public async Task XpReset(ulong guildId, ulong userId)
|
||||||
{
|
{
|
||||||
using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
uow.Set<UserXpStats>().ResetGuildUserXp(userId, guildId);
|
await uow.GetTable<UserXpStats>()
|
||||||
uow.SaveChanges();
|
.DeleteAsync(x => x.UserId == userId && x.GuildId == guildId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void XpReset(ulong guildId)
|
public void XpReset(ulong guildId)
|
||||||
|
@ -1637,6 +1662,15 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
|
|
||||||
public bool IsShopEnabled()
|
public bool IsShopEnabled()
|
||||||
=> _xpConfig.Data.Shop.IsEnabled;
|
=> _xpConfig.Data.Shop.IsEnabled;
|
||||||
|
|
||||||
|
public async Task<int> GetTotalGuildUsers(ulong requestGuildId, List<ulong>? guildUsers = null)
|
||||||
|
{
|
||||||
|
await using var ctx = _db.GetDbContext();
|
||||||
|
return await ctx.GetTable<UserXpStats>()
|
||||||
|
.Where(x => x.GuildId == requestGuildId
|
||||||
|
&& (guildUsers == null || guildUsers.Contains(x.UserId)))
|
||||||
|
.CountAsyncLinqToDB();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum BuyResult
|
public enum BuyResult
|
||||||
|
|
89
src/EllieBot/Services/GrpcApi/FinSvc.cs
Normal file
89
src/EllieBot/Services/GrpcApi/FinSvc.cs
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
using Google.Protobuf.WellKnownTypes;
|
||||||
|
using Grpc.Core;
|
||||||
|
using EllieBot.Db.Models;
|
||||||
|
using EllieBot.Modules.Gambling.Bank;
|
||||||
|
using EllieBot.Modules.EllieExpressions;
|
||||||
|
using EllieBot.Modules.Utility;
|
||||||
|
|
||||||
|
namespace EllieBot.GrpcApi;
|
||||||
|
|
||||||
|
public class FinSvc : GrpcFin.GrpcFinBase, IGrpcSvc, IEService
|
||||||
|
{
|
||||||
|
private readonly ICurrencyService _cs;
|
||||||
|
private readonly IBankService _bank;
|
||||||
|
|
||||||
|
public FinSvc(ICurrencyService cs, IBankService bank)
|
||||||
|
{
|
||||||
|
_cs = cs;
|
||||||
|
_bank = bank;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerServiceDefinition Bind()
|
||||||
|
=> GrpcFin.BindService(this);
|
||||||
|
|
||||||
|
[GrpcNoAuthRequired]
|
||||||
|
public override async Task<DepositReply> Deposit(DepositRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
if (request.Amount <= 0)
|
||||||
|
throw new RpcException(new Status(StatusCode.InvalidArgument, "Amount must be greater than 0"));
|
||||||
|
|
||||||
|
var succ = await _bank.DepositAsync(request.UserId, request.Amount);
|
||||||
|
|
||||||
|
return new DepositReply
|
||||||
|
{
|
||||||
|
Success = succ
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[GrpcNoAuthRequired]
|
||||||
|
public override async Task<WithdrawReply> Withdraw(WithdrawRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
if (request.Amount <= 0)
|
||||||
|
throw new RpcException(new Status(StatusCode.InvalidArgument, "Amount must be greater than 0"));
|
||||||
|
|
||||||
|
var succ = await _bank.WithdrawAsync(request.UserId, request.Amount);
|
||||||
|
|
||||||
|
return new WithdrawReply
|
||||||
|
{
|
||||||
|
Success = succ
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[GrpcNoAuthRequired]
|
||||||
|
public override async Task<GetHoldingsReply> GetHoldings(GetHoldingsRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
return new GetHoldingsReply
|
||||||
|
{
|
||||||
|
Bank = await _bank.GetBalanceAsync(request.UserId),
|
||||||
|
Cash = await _cs.GetBalanceAsync(request.UserId)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[GrpcNoAuthRequired]
|
||||||
|
public override async Task<GetTransactionsReply> GetTransactions(
|
||||||
|
GetTransactionsRequest request,
|
||||||
|
ServerCallContext context)
|
||||||
|
{
|
||||||
|
if (request.Page < 1)
|
||||||
|
throw new RpcException(new Status(StatusCode.InvalidArgument, "Page must be greater than 0"));
|
||||||
|
|
||||||
|
var trs = await _cs.GetTransactionsAsync(request.UserId, request.Page - 1);
|
||||||
|
|
||||||
|
var reply = new GetTransactionsReply
|
||||||
|
{
|
||||||
|
Total = await _cs.GetTransactionsCountAsync(request.UserId)
|
||||||
|
};
|
||||||
|
|
||||||
|
reply.Transactions.AddRange(trs.Select(x => new TransactionReply()
|
||||||
|
{
|
||||||
|
Id = new kwum(x.Id).ToString(),
|
||||||
|
Timestamp = Timestamp.FromDateTime(DateTime.UtcNow),
|
||||||
|
Amount = x.Amount,
|
||||||
|
Extra = x.Extra ?? string.Empty,
|
||||||
|
Note = x.Note ?? string.Empty,
|
||||||
|
Type = x.Type ?? string.Empty,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,10 +17,13 @@ public sealed class GreetByeSvc : GrpcGreet.GrpcGreetBase, IGrpcSvc, IEService
|
||||||
public ServerServiceDefinition Bind()
|
public ServerServiceDefinition Bind()
|
||||||
=> GrpcGreet.BindService(this);
|
=> GrpcGreet.BindService(this);
|
||||||
|
|
||||||
private static GrpcGreetSettings ToConf(GreetSettings? conf)
|
private static GrpcGreetSettings ToConf(GreetSettings? conf, GreetType type)
|
||||||
{
|
{
|
||||||
if (conf is null)
|
if (conf is null)
|
||||||
return new GrpcGreetSettings();
|
return new GrpcGreetSettings()
|
||||||
|
{
|
||||||
|
Type = (GrpcGreetType)type
|
||||||
|
};
|
||||||
|
|
||||||
return new GrpcGreetSettings()
|
return new GrpcGreetSettings()
|
||||||
{
|
{
|
||||||
|
@ -35,9 +38,10 @@ public sealed class GreetByeSvc : GrpcGreet.GrpcGreetBase, IGrpcSvc, IEService
|
||||||
{
|
{
|
||||||
var guildId = request.GuildId;
|
var guildId = request.GuildId;
|
||||||
|
|
||||||
var conf = await _gs.GetGreetSettingsAsync(guildId, (GreetType)request.Type);
|
var type = (GreetType)request.Type;
|
||||||
|
var conf = await _gs.GetGreetSettingsAsync(guildId, type);
|
||||||
|
|
||||||
return ToConf(conf);
|
return ToConf(conf, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<UpdateGreetReply> UpdateGreet(UpdateGreetRequest request, ServerCallContext context)
|
public override async Task<UpdateGreetReply> UpdateGreet(UpdateGreetRequest request, ServerCallContext context)
|
||||||
|
|
282
src/EllieBot/Services/GrpcApi/XpSvc.cs
Normal file
282
src/EllieBot/Services/GrpcApi/XpSvc.cs
Normal file
|
@ -0,0 +1,282 @@
|
||||||
|
using Google.Protobuf.WellKnownTypes;
|
||||||
|
using Grpc.Core;
|
||||||
|
using EllieBot.Db.Models;
|
||||||
|
using EllieBot.Modules.Gambling.Bank;
|
||||||
|
using EllieBot.Modules.EllieExpressions;
|
||||||
|
using EllieBot.Modules.Utility;
|
||||||
|
using EllieBot.Modules.Xp.Services;
|
||||||
|
|
||||||
|
namespace EllieBot.GrpcApi;
|
||||||
|
|
||||||
|
public class XpSvc : GrpcXp.GrpcXpBase, IGrpcSvc, IEService
|
||||||
|
{
|
||||||
|
private readonly XpService _xp;
|
||||||
|
private readonly DiscordSocketClient _client;
|
||||||
|
private readonly IUserService _duSvc;
|
||||||
|
|
||||||
|
public XpSvc(XpService xp, DiscordSocketClient client, IUserService duSvc)
|
||||||
|
{
|
||||||
|
_xp = xp;
|
||||||
|
_client = client;
|
||||||
|
_duSvc = duSvc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerServiceDefinition Bind()
|
||||||
|
=> GrpcXp.BindService(this);
|
||||||
|
|
||||||
|
public override async Task<GetXpSettingsReply> GetXpSettings(
|
||||||
|
GetXpSettingsRequest request,
|
||||||
|
ServerCallContext context)
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
|
||||||
|
var guild = _client.GetGuild(request.GuildId);
|
||||||
|
|
||||||
|
if (guild is null)
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, "Guild not found"));
|
||||||
|
|
||||||
|
var excludedChannels = _xp.GetExcludedChannels(request.GuildId);
|
||||||
|
var excludedRoles = _xp.GetExcludedRoles(request.GuildId);
|
||||||
|
var isServerExcluded = _xp.IsServerExcluded(request.GuildId);
|
||||||
|
|
||||||
|
var reply = new GetXpSettingsReply();
|
||||||
|
|
||||||
|
reply.Exclusions.AddRange(excludedChannels
|
||||||
|
.Select(x => new ExclItemReply()
|
||||||
|
{
|
||||||
|
Id = x,
|
||||||
|
Type = "Channel",
|
||||||
|
Name = guild.GetChannel(x)?.Name ?? "????"
|
||||||
|
})
|
||||||
|
.Concat(excludedRoles
|
||||||
|
.Select(x => new ExclItemReply()
|
||||||
|
{
|
||||||
|
Id = x,
|
||||||
|
Type = "Role",
|
||||||
|
Name = guild.GetRole(x)?.Name ?? "????"
|
||||||
|
})));
|
||||||
|
|
||||||
|
var curRews = _xp.GetCurrencyRewards(request.GuildId);
|
||||||
|
var roleRews = _xp.GetRoleRewards(request.GuildId);
|
||||||
|
|
||||||
|
var rews = curRews.Select(x => new RewItemReply()
|
||||||
|
{
|
||||||
|
Level = x.Level,
|
||||||
|
Type = "Currency",
|
||||||
|
Value = x.Amount.ToString()
|
||||||
|
});
|
||||||
|
|
||||||
|
rews = rews.Concat(roleRews.Select(x => new RewItemReply()
|
||||||
|
{
|
||||||
|
Level = x.Level,
|
||||||
|
Type = x.Remove ? "RemoveRole" : "AddRole",
|
||||||
|
Value = guild.GetRole(x.RoleId)?.ToString() ?? x.RoleId.ToString()
|
||||||
|
}))
|
||||||
|
.OrderBy(x => x.Level);
|
||||||
|
|
||||||
|
reply.Rewards.AddRange(rews);
|
||||||
|
|
||||||
|
reply.ServerExcluded = isServerExcluded;
|
||||||
|
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<AddExclusionReply> AddExclusion(AddExclusionRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
|
||||||
|
var success = false;
|
||||||
|
var guild = _client.GetGuild(request.GuildId);
|
||||||
|
|
||||||
|
if (guild is null)
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, "Guild not found"));
|
||||||
|
|
||||||
|
if (request.Type == "Role")
|
||||||
|
{
|
||||||
|
if (guild.GetRole(request.Id) is null)
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Success = false
|
||||||
|
};
|
||||||
|
|
||||||
|
success = _xp.ToggleExcludeRole(request.GuildId, request.Id);
|
||||||
|
}
|
||||||
|
else if (request.Type == "Channel")
|
||||||
|
{
|
||||||
|
if (guild.GetTextChannel(request.Id) is null)
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Success = false
|
||||||
|
};
|
||||||
|
|
||||||
|
success = _xp.ToggleExcludeChannel(request.GuildId, request.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Success = success
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<DeleteExclusionReply> DeleteExclusion(
|
||||||
|
DeleteExclusionRequest request,
|
||||||
|
ServerCallContext context)
|
||||||
|
{
|
||||||
|
var success = false;
|
||||||
|
if (request.Type == "Role")
|
||||||
|
success = _xp.ToggleExcludeRole(request.GuildId, request.Id);
|
||||||
|
else
|
||||||
|
success = _xp.ToggleExcludeChannel(request.GuildId, request.Id);
|
||||||
|
|
||||||
|
return Task.FromResult(new DeleteExclusionReply
|
||||||
|
{
|
||||||
|
Success = success
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<AddRewardReply> AddReward(AddRewardRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
|
||||||
|
var success = false;
|
||||||
|
var guild = _client.GetGuild(request.GuildId);
|
||||||
|
|
||||||
|
if (guild is null)
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, "Guild not found"));
|
||||||
|
|
||||||
|
if (request.Type == "AddRole" || request.Type == "RemoveRole")
|
||||||
|
{
|
||||||
|
if (!ulong.TryParse(request.Value, out var rid))
|
||||||
|
throw new RpcException(new Status(StatusCode.InvalidArgument, "Invalid role id"));
|
||||||
|
|
||||||
|
var role = guild.GetRole(rid);
|
||||||
|
if (role is null)
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Success = false
|
||||||
|
};
|
||||||
|
|
||||||
|
_xp.SetRoleReward(request.GuildId, request.Level, rid, request.Type == "RemoveRole");
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
// else if (request.Type == "Currency")
|
||||||
|
// {
|
||||||
|
// if (!int.TryParse(request.Value, out var amount))
|
||||||
|
// throw new RpcException(new Status(StatusCode.InvalidArgument, "Invalid amount"));
|
||||||
|
//
|
||||||
|
// _xp.SetCurrencyReward(request.GuildId, request.Level, amount);
|
||||||
|
// success = true;
|
||||||
|
// }
|
||||||
|
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Success = success
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<DeleteRewardReply> DeleteReward(DeleteRewardRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
var success = false;
|
||||||
|
|
||||||
|
if (request.Type == "AddRole" || request.Type == "RemoveRole")
|
||||||
|
{
|
||||||
|
_xp.ResetRoleReward(request.GuildId, request.Level);
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
else if (request.Type == "Currency")
|
||||||
|
{
|
||||||
|
_xp.SetCurrencyReward(request.GuildId, request.Level, 0);
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(new DeleteRewardReply
|
||||||
|
{
|
||||||
|
Success = success
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<ResetUserXpReply> ResetUserXp(ResetUserXpRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
await _xp.XpReset(request.GuildId, request.UserId);
|
||||||
|
|
||||||
|
return new ResetUserXpReply
|
||||||
|
{
|
||||||
|
Success = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<GetXpLbReply> GetXpLb(GetXpLbRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
if (request.Page < 1)
|
||||||
|
throw new RpcException(new Status(StatusCode.InvalidArgument, "Page must be greater than or equal to 1"));
|
||||||
|
|
||||||
|
var guild = _client.GetGuild(request.GuildId);
|
||||||
|
|
||||||
|
if (guild is null)
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, "Guild not found"));
|
||||||
|
|
||||||
|
var data = await _xp.GetGuildUserXps(request.GuildId, request.Page - 1);
|
||||||
|
var total = await _xp.GetTotalGuildUsers(request.GuildId);
|
||||||
|
|
||||||
|
var reply = new GetXpLbReply
|
||||||
|
{
|
||||||
|
Total = total
|
||||||
|
};
|
||||||
|
|
||||||
|
var users = await data
|
||||||
|
.Select(async x =>
|
||||||
|
{
|
||||||
|
var user = guild.GetUser(x.UserId);
|
||||||
|
|
||||||
|
if (user is null)
|
||||||
|
{
|
||||||
|
var du = await _duSvc.GetUserAsync(x.UserId);
|
||||||
|
if (du is null)
|
||||||
|
return new XpLbUserReply
|
||||||
|
{
|
||||||
|
UserId = x.UserId,
|
||||||
|
Avatar = string.Empty,
|
||||||
|
Username = string.Empty,
|
||||||
|
Xp = x.Xp,
|
||||||
|
Level = new LevelStats(x.Xp).Level
|
||||||
|
};
|
||||||
|
|
||||||
|
return new XpLbUserReply()
|
||||||
|
{
|
||||||
|
UserId = x.UserId,
|
||||||
|
Avatar = du.RealAvatarUrl()?.ToString() ?? string.Empty,
|
||||||
|
Username = du.ToString() ?? string.Empty,
|
||||||
|
Xp = x.Xp,
|
||||||
|
Level = new LevelStats(x.Xp).Level
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new XpLbUserReply
|
||||||
|
{
|
||||||
|
UserId = x.UserId,
|
||||||
|
Avatar = user?.GetAvatarUrl() ?? string.Empty,
|
||||||
|
Username = user?.ToString() ?? string.Empty,
|
||||||
|
Xp = x.Xp,
|
||||||
|
Level = new LevelStats(x.Xp).Level
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.WhenAll();
|
||||||
|
|
||||||
|
reply.Users.AddRange(users);
|
||||||
|
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<SetServerExclusionReply> SetServerExclusion(
|
||||||
|
SetServerExclusionRequest request,
|
||||||
|
ServerCallContext context)
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
|
||||||
|
var newValue = _xp.ToggleExcludeServer(request.GuildId);
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Success = newValue
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
#nullable disable
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
|
||||||
namespace EllieBot.Db;
|
namespace EllieBot.Db;
|
||||||
|
@ -17,6 +16,8 @@ public static class SelfAssignableRolesExtensions
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IReadOnlyCollection<SelfAssignedRole> GetFromGuild(this DbSet<SelfAssignedRole> roles, ulong guildId)
|
public static IReadOnlyCollection<SelfAssignedRole> GetFromGuild(
|
||||||
|
this DbSet<SelfAssignedRole> roles,
|
||||||
|
ulong guildId)
|
||||||
=> roles.AsQueryable().Where(s => s.GuildId == guildId).ToArray();
|
=> roles.AsQueryable().Where(s => s.GuildId == guildId).ToArray();
|
||||||
}
|
}
|
|
@ -40,4 +40,11 @@ public interface ICurrencyService
|
||||||
TxData? txData);
|
TxData? txData);
|
||||||
|
|
||||||
Task<IReadOnlyList<DiscordUser>> GetTopRichest(ulong ignoreId, int page = 0, int perPage = 9);
|
Task<IReadOnlyList<DiscordUser>> GetTopRichest(ulong ignoreId, int page = 0, int perPage = 9);
|
||||||
|
|
||||||
|
Task<IReadOnlyList<CurrencyTransaction>> GetTransactionsAsync(
|
||||||
|
ulong userId,
|
||||||
|
int page,
|
||||||
|
int perPage = 15);
|
||||||
|
|
||||||
|
Task<int> GetTransactionsCountAsync(ulong userId);
|
||||||
}
|
}
|
|
@ -4,6 +4,6 @@ namespace EllieBot.Services;
|
||||||
|
|
||||||
public interface ITxTracker
|
public interface ITxTracker
|
||||||
{
|
{
|
||||||
Task TrackAdd(long amount, TxData? txData);
|
Task TrackAdd(ulong userId, long amount, TxData? txData);
|
||||||
Task TrackRemove(long amount, TxData? txData);
|
Task TrackRemove(ulong userId, long amount, TxData? txData);
|
||||||
}
|
}
|
|
@ -11,9 +11,9 @@ public class SlotGame
|
||||||
{
|
{
|
||||||
var rolls = new[]
|
var rolls = new[]
|
||||||
{
|
{
|
||||||
(byte)_rng.Next(0, 6),
|
(byte)_rng.Next(0, 7),
|
||||||
(byte)_rng.Next(0, 6),
|
(byte)_rng.Next(0, 7),
|
||||||
(byte)_rng.Next(0, 6)
|
(byte)_rng.Next(0, 7)
|
||||||
};
|
};
|
||||||
|
|
||||||
ref var a = ref rolls[0];
|
ref var a = ref rolls[0];
|
||||||
|
@ -24,24 +24,24 @@ public class SlotGame
|
||||||
var winType = SlotWinType.None;
|
var winType = SlotWinType.None;
|
||||||
if (a == b && b == c)
|
if (a == b && b == c)
|
||||||
{
|
{
|
||||||
if (a == 5)
|
if (a == 6)
|
||||||
{
|
{
|
||||||
winType = SlotWinType.TrippleJoker;
|
winType = SlotWinType.TrippleJoker;
|
||||||
multi = 30;
|
multi = 25;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
winType = SlotWinType.TrippleNormal;
|
winType = SlotWinType.TrippleNormal;
|
||||||
multi = 10;
|
multi = 15;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (a == 5 && (b == 5 || c == 5)
|
else if (a == 6 && (b == 6 || c == 6)
|
||||||
|| (b == 5 && c == 5))
|
|| (b == 6 && c == 6))
|
||||||
{
|
{
|
||||||
winType = SlotWinType.DoubleJoker;
|
winType = SlotWinType.DoubleJoker;
|
||||||
multi = 4;
|
multi = 6;
|
||||||
}
|
}
|
||||||
else if (a == 5 || b == 5 || c == 5)
|
else if (a == 6 || b == 6 || c == 6)
|
||||||
{
|
{
|
||||||
winType = SlotWinType.SingleJoker;
|
winType = SlotWinType.SingleJoker;
|
||||||
multi = 1;
|
multi = 1;
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace EllieBot.Common;
|
||||||
public partial class ImageUrls : ICloneable<ImageUrls>
|
public partial class ImageUrls : ICloneable<ImageUrls>
|
||||||
{
|
{
|
||||||
[Comment("DO NOT CHANGE")]
|
[Comment("DO NOT CHANGE")]
|
||||||
public int Version { get; set; } = 5;
|
public int Version { get; set; } = 6;
|
||||||
|
|
||||||
public CoinData Coins { get; set; }
|
public CoinData Coins { get; set; }
|
||||||
public Uri[] Currency { get; set; }
|
public Uri[] Currency { get; set; }
|
||||||
|
|
|
@ -201,9 +201,12 @@ public sealed partial class GoogleApiService : IGoogleApiService, IEService
|
||||||
{
|
{
|
||||||
string text;
|
string text;
|
||||||
|
|
||||||
if (!Languages.ContainsKey(sourceLanguage) || !Languages.ContainsKey(targetLanguage))
|
if (!Languages.ContainsKey(targetLanguage))
|
||||||
throw new ArgumentException(nameof(sourceLanguage) + "/" + nameof(targetLanguage));
|
throw new ArgumentException(nameof(sourceLanguage) + "/" + nameof(targetLanguage));
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(sourceLanguage) || !Languages.ContainsKey(sourceLanguage))
|
||||||
|
sourceLanguage = "auto";
|
||||||
|
|
||||||
|
|
||||||
var url = new Uri(string.Format(
|
var url = new Uri(string.Format(
|
||||||
"https://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}",
|
"https://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}",
|
||||||
|
@ -223,7 +226,7 @@ public sealed partial class GoogleApiService : IGoogleApiService, IEService
|
||||||
private string ConvertToLanguageCode(string language)
|
private string ConvertToLanguageCode(string language)
|
||||||
{
|
{
|
||||||
Languages.TryGetValue(language, out var mode);
|
Languages.TryGetValue(language, out var mode);
|
||||||
return mode;
|
return string.IsNullOrWhiteSpace(mode) ? language : mode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -154,7 +154,6 @@ public sealed partial class GoogleApiService
|
||||||
}
|
}
|
||||||
|
|
||||||
Languages = langs;
|
Languages = langs;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -12,7 +12,11 @@ public sealed partial class ReplacementPatternStore
|
||||||
{
|
{
|
||||||
Register("%bot.time%",
|
Register("%bot.time%",
|
||||||
static ()
|
static ()
|
||||||
=> DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()));
|
=> TimestampTag.FromDateTime(DateTime.UtcNow, TimestampTagStyles.ShortTime).ToString());
|
||||||
|
|
||||||
|
Register("%bot.date%",
|
||||||
|
static ()
|
||||||
|
=> TimestampTag.FromDateTime(DateTime.UtcNow, TimestampTagStyles.ShortTime).ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WithClient()
|
private void WithClient()
|
||||||
|
|
|
@ -165,7 +165,7 @@ public class CommandHandler : IEService, IReadyExecutor, ICommandHandler
|
||||||
Log.Information("Succ | g:{GuildId} | c: {ChannelId} | u: {UserId} | msg: {Message}",
|
Log.Information("Succ | g:{GuildId} | c: {ChannelId} | u: {UserId} | msg: {Message}",
|
||||||
channel?.Guild.Id.ToString() ?? "-",
|
channel?.Guild.Id.ToString() ?? "-",
|
||||||
channel?.Id.ToString() ?? "-",
|
channel?.Id.ToString() ?? "-",
|
||||||
usrMsg.Author.Id,
|
usrMsg.Author.Id.ToString(),
|
||||||
usrMsg.Content.TrimTo(10));
|
usrMsg.Content.TrimTo(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,14 +55,14 @@ public sealed class CurrencyService : ICurrencyService, IEService
|
||||||
{
|
{
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
await ctx
|
await ctx
|
||||||
.GetTable<DiscordUser>()
|
.GetTable<DiscordUser>()
|
||||||
.Where(x => userIds.Contains(x.UserId))
|
.Where(x => userIds.Contains(x.UserId))
|
||||||
.UpdateAsync(du => new()
|
.UpdateAsync(du => new()
|
||||||
{
|
{
|
||||||
CurrencyAmount = du.CurrencyAmount >= amount
|
CurrencyAmount = du.CurrencyAmount >= amount
|
||||||
? du.CurrencyAmount - amount
|
? du.CurrencyAmount - amount
|
||||||
: 0
|
: 0
|
||||||
});
|
});
|
||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,7 @@ public sealed class CurrencyService : ICurrencyService, IEService
|
||||||
{
|
{
|
||||||
var wallet = await GetWalletAsync(userId);
|
var wallet = await GetWalletAsync(userId);
|
||||||
await wallet.Add(amount, txData);
|
await wallet.Add(amount, txData);
|
||||||
await _txTracker.TrackAdd(amount, txData);
|
await _txTracker.TrackAdd(userId, amount, txData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AddAsync(
|
public async Task AddAsync(
|
||||||
|
@ -97,7 +97,7 @@ public sealed class CurrencyService : ICurrencyService, IEService
|
||||||
var wallet = await GetWalletAsync(userId);
|
var wallet = await GetWalletAsync(userId);
|
||||||
var result = await wallet.Take(amount, txData);
|
var result = await wallet.Take(amount, txData);
|
||||||
if (result)
|
if (result)
|
||||||
await _txTracker.TrackRemove(amount, txData);
|
await _txTracker.TrackRemove(userId, amount, txData);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,4 +112,29 @@ public sealed class CurrencyService : ICurrencyService, IEService
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
return await uow.Set<DiscordUser>().GetTopRichest(ignoreId, page, perPage);
|
return await uow.Set<DiscordUser>().GetTopRichest(ignoreId, page, perPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<CurrencyTransaction>> GetTransactionsAsync(
|
||||||
|
ulong userId,
|
||||||
|
int page,
|
||||||
|
int perPage = 15)
|
||||||
|
{
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
|
||||||
|
var trs = await uow.GetTable<CurrencyTransaction>()
|
||||||
|
.Where(x => x.UserId == userId)
|
||||||
|
.OrderByDescending(x => x.DateAdded)
|
||||||
|
.Skip(perPage * page)
|
||||||
|
.Take(perPage)
|
||||||
|
.ToListAsyncLinqToDB();
|
||||||
|
|
||||||
|
return trs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> GetTransactionsCountAsync(ulong userId)
|
||||||
|
{
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
return await uow.GetTable<CurrencyTransaction>()
|
||||||
|
.Where(x => x.UserId == userId)
|
||||||
|
.CountAsyncLinqToDB();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -77,8 +77,7 @@ public class DefaultWallet : IWallet
|
||||||
.InsertOrUpdateAsync(() => new()
|
.InsertOrUpdateAsync(() => new()
|
||||||
{
|
{
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
Username = "Unknown",
|
Username = "??Unknown",
|
||||||
Discriminator = "????",
|
|
||||||
CurrencyAmount = amount,
|
CurrencyAmount = amount,
|
||||||
},
|
},
|
||||||
(old) => new()
|
(old) => new()
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
|
using LinqToDB.Data;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
using EllieBot.Services.Currency;
|
using EllieBot.Services.Currency;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
using EllieBot.Modules.Gambling;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
namespace EllieBot.Services;
|
namespace EllieBot.Services;
|
||||||
|
|
||||||
|
@ -10,15 +13,11 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
||||||
{
|
{
|
||||||
private static readonly IReadOnlySet<string> _gamblingTypes = new HashSet<string>(new[]
|
private static readonly IReadOnlySet<string> _gamblingTypes = new HashSet<string>(new[]
|
||||||
{
|
{
|
||||||
"lula",
|
"lula", "betroll", "betflip", "blackjack", "betdraw", "slot",
|
||||||
"betroll",
|
|
||||||
"betflip",
|
|
||||||
"blackjack",
|
|
||||||
"betdraw",
|
|
||||||
"slot",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
private ConcurrentDictionary<string, (decimal Bet, decimal PaidOut)> _stats = new();
|
private NonBlocking.ConcurrentDictionary<string, (decimal Bet, decimal PaidOut)> globalStats = new();
|
||||||
|
private ConcurrentBag<UserBetStats> userStats = new();
|
||||||
|
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
|
|
||||||
|
@ -28,83 +27,333 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnReadyAsync()
|
public async Task OnReadyAsync()
|
||||||
|
=> await Task.WhenAll(RunUserStatsCollector(), RunBetStatsCollector());
|
||||||
|
|
||||||
|
public async Task RunBetStatsCollector()
|
||||||
{
|
{
|
||||||
using var timer = new PeriodicTimer(TimeSpan.FromHours(1));
|
using var timer = new PeriodicTimer(TimeSpan.FromHours(1));
|
||||||
while (await timer.WaitForNextTickAsync())
|
while (await timer.WaitForNextTickAsync())
|
||||||
{
|
{
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
await using var trans = await ctx.Database.BeginTransactionAsync();
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var keys = _stats.Keys;
|
// update betstats
|
||||||
|
var keys = globalStats.Keys;
|
||||||
foreach (var key in keys)
|
foreach (var key in keys)
|
||||||
{
|
{
|
||||||
if (_stats.TryRemove(key, out var stat))
|
if (globalStats.TryRemove(key, out var stat))
|
||||||
{
|
{
|
||||||
await ctx.GetTable<GamblingStats>()
|
await ctx.GetTable<GamblingStats>()
|
||||||
.InsertOrUpdateAsync(() => new()
|
.InsertOrUpdateAsync(() => new()
|
||||||
{
|
{
|
||||||
Feature = key,
|
Feature = key,
|
||||||
Bet = stat.Bet,
|
Bet = stat.Bet,
|
||||||
PaidOut = stat.PaidOut,
|
PaidOut = stat.PaidOut,
|
||||||
DateAdded = DateTime.UtcNow
|
DateAdded = DateTime.UtcNow
|
||||||
}, old => new()
|
},
|
||||||
{
|
old => new()
|
||||||
Bet = old.Bet + stat.Bet,
|
{
|
||||||
PaidOut = old.PaidOut + stat.PaidOut,
|
Bet = old.Bet + stat.Bet,
|
||||||
}, () => new()
|
PaidOut = old.PaidOut + stat.PaidOut,
|
||||||
{
|
},
|
||||||
Feature = key
|
() => new()
|
||||||
});
|
{
|
||||||
|
Feature = key
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Error(ex, "An error occurred in gambling tx tracker");
|
Log.Error(ex, "An error occurred in betstats gambling tx tracker");
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
await trans.CommitAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task TrackAdd(long amount, TxData? txData)
|
private async Task RunUserStatsCollector()
|
||||||
|
{
|
||||||
|
var timer = new PeriodicTimer(TimeSpan.FromSeconds(5));
|
||||||
|
while (await timer.WaitForNextTickAsync())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (userStats.Count == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var users = new List<UserBetStats>(userStats.Count + 5);
|
||||||
|
|
||||||
|
while (userStats.TryTake(out var s))
|
||||||
|
users.Add(s);
|
||||||
|
|
||||||
|
if (users.Count == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// rakeback
|
||||||
|
var rakebacks = new Dictionary<ulong, decimal>();
|
||||||
|
|
||||||
|
// update userstats
|
||||||
|
foreach (var (k, x) in users.GroupBy(x => (x.UserId, x.Game))
|
||||||
|
.ToDictionary(x => x.Key,
|
||||||
|
x => x.Aggregate((a, b) => new()
|
||||||
|
{
|
||||||
|
WinCount = a.WinCount + b.WinCount,
|
||||||
|
LoseCount = a.LoseCount + b.LoseCount,
|
||||||
|
TotalBet = a.TotalBet + b.TotalBet,
|
||||||
|
PaidOut = a.PaidOut + b.PaidOut,
|
||||||
|
MaxBet = Math.Max(a.MaxBet, b.MaxBet),
|
||||||
|
MaxWin = Math.Max(a.MaxWin, b.MaxWin),
|
||||||
|
})))
|
||||||
|
{
|
||||||
|
rakebacks.TryAdd(k.UserId, 0m);
|
||||||
|
rakebacks[k.UserId] += x.TotalBet * GetHouseEdge(k.Game) * BASE_RAKEBACK;
|
||||||
|
|
||||||
|
|
||||||
|
// bulk upsert in the future
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
await uow.GetTable<UserBetStats>()
|
||||||
|
.InsertOrUpdateAsync(() => new()
|
||||||
|
{
|
||||||
|
UserId = k.UserId,
|
||||||
|
Game = k.Game,
|
||||||
|
WinCount = x.WinCount,
|
||||||
|
LoseCount = Math.Max(0, x.LoseCount),
|
||||||
|
TotalBet = x.TotalBet,
|
||||||
|
PaidOut = x.PaidOut,
|
||||||
|
MaxBet = x.MaxBet,
|
||||||
|
MaxWin = x.MaxWin
|
||||||
|
},
|
||||||
|
o => new()
|
||||||
|
{
|
||||||
|
WinCount = o.WinCount + x.WinCount,
|
||||||
|
LoseCount = Math.Max(0, o.LoseCount + x.LoseCount),
|
||||||
|
TotalBet = o.TotalBet + x.TotalBet,
|
||||||
|
PaidOut = o.PaidOut + x.PaidOut,
|
||||||
|
MaxBet = Math.Max(o.MaxBet, x.MaxBet),
|
||||||
|
MaxWin = Math.Max(o.MaxWin, x.MaxWin),
|
||||||
|
},
|
||||||
|
() => new()
|
||||||
|
{
|
||||||
|
UserId = k.UserId,
|
||||||
|
Game = k.Game
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (k, v) in rakebacks)
|
||||||
|
{
|
||||||
|
await _db.GetDbContext()
|
||||||
|
.GetTable<Rakeback>()
|
||||||
|
.InsertOrUpdateAsync(() => new()
|
||||||
|
{
|
||||||
|
UserId = k,
|
||||||
|
Amount = v
|
||||||
|
},
|
||||||
|
(old) => new()
|
||||||
|
{
|
||||||
|
Amount = old.Amount + v
|
||||||
|
},
|
||||||
|
() => new()
|
||||||
|
{
|
||||||
|
UserId = k
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "An error occurred in UserBetStats gambling tx tracker");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const decimal BASE_RAKEBACK = 0.05m;
|
||||||
|
|
||||||
|
public Task TrackAdd(ulong userId, long amount, TxData? txData)
|
||||||
{
|
{
|
||||||
if (txData is null)
|
if (txData is null)
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|
||||||
if (_gamblingTypes.Contains(txData.Type))
|
if (_gamblingTypes.Contains(txData.Type))
|
||||||
{
|
{
|
||||||
_stats.AddOrUpdate(txData.Type,
|
globalStats.AddOrUpdate(txData.Type,
|
||||||
_ => (0, amount),
|
_ => (0, amount),
|
||||||
(_, old) => (old.Bet, old.PaidOut + amount));
|
(_, old) => (old.Bet, old.PaidOut + amount));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mType = GetGameType(txData.Type);
|
||||||
|
|
||||||
|
if (mType is not { } type)
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
if (txData.Type == "lula")
|
||||||
|
{
|
||||||
|
if (txData.Extra == "lose")
|
||||||
|
{
|
||||||
|
userStats.Add(new()
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
Game = type,
|
||||||
|
WinCount = 0,
|
||||||
|
LoseCount = 0,
|
||||||
|
TotalBet = 0,
|
||||||
|
PaidOut = amount,
|
||||||
|
MaxBet = 0,
|
||||||
|
MaxWin = amount,
|
||||||
|
});
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (txData.Type == "animalrace")
|
||||||
|
{
|
||||||
|
if (txData.Extra == "refund")
|
||||||
|
{
|
||||||
|
userStats.Add(new()
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
Game = type,
|
||||||
|
WinCount = 0,
|
||||||
|
LoseCount = -1,
|
||||||
|
TotalBet = -amount,
|
||||||
|
PaidOut = 0,
|
||||||
|
MaxBet = 0,
|
||||||
|
MaxWin = 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
userStats.Add(new UserBetStats()
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
Game = type,
|
||||||
|
WinCount = 1,
|
||||||
|
LoseCount = -1,
|
||||||
|
TotalBet = 0,
|
||||||
|
PaidOut = amount,
|
||||||
|
MaxBet = 0,
|
||||||
|
MaxWin = amount,
|
||||||
|
});
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task TrackRemove(long amount, TxData? txData)
|
public Task TrackRemove(ulong userId, long amount, TxData? txData)
|
||||||
{
|
{
|
||||||
if (txData is null)
|
if (txData is null)
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|
||||||
if (_gamblingTypes.Contains(txData.Type))
|
if (_gamblingTypes.Contains(txData.Type))
|
||||||
{
|
{
|
||||||
_stats.AddOrUpdate(txData.Type,
|
globalStats.AddOrUpdate(txData.Type,
|
||||||
_ => (amount, 0),
|
_ => (amount, 0),
|
||||||
(_, old) => (old.Bet + amount, old.PaidOut));
|
(_, old) => (old.Bet + amount, old.PaidOut));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mType = GetGameType(txData.Type);
|
||||||
|
|
||||||
|
if (mType is not { } type)
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
userStats.Add(new UserBetStats()
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
Game = type,
|
||||||
|
WinCount = 0,
|
||||||
|
LoseCount = 1,
|
||||||
|
TotalBet = amount,
|
||||||
|
PaidOut = 0,
|
||||||
|
MaxBet = amount,
|
||||||
|
MaxWin = 0
|
||||||
|
});
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static GamblingGame? GetGameType(string game)
|
||||||
|
=> game switch
|
||||||
|
{
|
||||||
|
"lula" => GamblingGame.Lula,
|
||||||
|
"betroll" => GamblingGame.Betroll,
|
||||||
|
"betflip" => GamblingGame.Betflip,
|
||||||
|
"blackjack" => GamblingGame.Blackjack,
|
||||||
|
"betdraw" => GamblingGame.Betdraw,
|
||||||
|
"slot" => GamblingGame.Slots,
|
||||||
|
"animalrace" => GamblingGame.Race,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
public async Task<IReadOnlyCollection<GamblingStats>> GetAllAsync()
|
public async Task<IReadOnlyCollection<GamblingStats>> GetAllAsync()
|
||||||
{
|
{
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
return await ctx.Set<GamblingStats>()
|
return await ctx.Set<GamblingStats>()
|
||||||
.ToListAsyncEF();
|
.ToListAsyncEF();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<List<UserBetStats>> GetUserStatsAsync(ulong userId, GamblingGame? game = null)
|
||||||
|
{
|
||||||
|
await using var ctx = _db.GetDbContext();
|
||||||
|
|
||||||
|
|
||||||
|
if (game is null)
|
||||||
|
return await ctx
|
||||||
|
.GetTable<UserBetStats>()
|
||||||
|
.Where(x => x.UserId == userId)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return await ctx
|
||||||
|
.GetTable<UserBetStats>()
|
||||||
|
.Where(x => x.UserId == userId && x.Game == game)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public decimal GetHouseEdge(GamblingGame game)
|
||||||
|
=> game switch
|
||||||
|
{
|
||||||
|
GamblingGame.Betflip => 0.025m,
|
||||||
|
GamblingGame.Betroll => 0.04m,
|
||||||
|
GamblingGame.Betdraw => 0.04m,
|
||||||
|
GamblingGame.Slots => 0.034m,
|
||||||
|
GamblingGame.Blackjack => 0.02m,
|
||||||
|
GamblingGame.Lula => 0.025m,
|
||||||
|
GamblingGame.Race => 0.06m,
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class UserBetStats
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public ulong UserId { get; set; }
|
||||||
|
public GamblingGame Game { get; set; }
|
||||||
|
public long WinCount { get; set; }
|
||||||
|
public long LoseCount { get; set; }
|
||||||
|
public decimal TotalBet { get; set; }
|
||||||
|
public decimal PaidOut { get; set; }
|
||||||
|
public long MaxWin { get; set; }
|
||||||
|
public long MaxBet { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum GamblingGame
|
||||||
|
{
|
||||||
|
Betflip = 0,
|
||||||
|
Bf = 0,
|
||||||
|
Betroll = 1,
|
||||||
|
Br = 1,
|
||||||
|
Betdraw = 2,
|
||||||
|
Bd = 2,
|
||||||
|
Slots = 3,
|
||||||
|
Slot = 3,
|
||||||
|
Blackjack = 4,
|
||||||
|
Bj = 4,
|
||||||
|
Lula = 5,
|
||||||
|
Race = 6,
|
||||||
|
AnimalRace = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class Rakeback
|
||||||
|
{
|
||||||
|
public ulong UserId { get; set; }
|
||||||
|
public decimal Amount { get; set; }
|
||||||
}
|
}
|
|
@ -1,13 +1,13 @@
|
||||||
#nullable disable
|
|
||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using LinqToDB.Data;
|
using LinqToDB.Data;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
using System.Collections.Frozen;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Permissions.Services;
|
namespace EllieBot.Modules.Permissions.Services;
|
||||||
|
|
||||||
public sealed class BlacklistService : IExecOnMessage
|
public sealed class BlacklistService : IExecOnMessage, IReadyExecutor
|
||||||
{
|
{
|
||||||
public int Priority
|
public int Priority
|
||||||
=> int.MaxValue;
|
=> int.MaxValue;
|
||||||
|
@ -15,69 +15,114 @@ public sealed class BlacklistService : IExecOnMessage
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
private readonly IPubSub _pubSub;
|
private readonly IPubSub _pubSub;
|
||||||
private readonly IBotCreds _creds;
|
private readonly IBotCreds _creds;
|
||||||
private IReadOnlyList<BlacklistEntry> blacklist;
|
private readonly DiscordSocketClient _client;
|
||||||
|
|
||||||
private readonly TypedKey<BlacklistEntry[]> _blPubKey = new("blacklist.reload");
|
private FrozenSet<ulong> blacklistedGuilds = new HashSet<ulong>().ToFrozenSet();
|
||||||
|
private FrozenSet<ulong> blacklistedUsers = new HashSet<ulong>().ToFrozenSet();
|
||||||
|
private FrozenSet<ulong> blacklistedChannels = new HashSet<ulong>().ToFrozenSet();
|
||||||
|
|
||||||
public BlacklistService(DbService db, IPubSub pubSub, IBotCreds creds)
|
private readonly TypedKey<bool> _blPubKey = new("blacklist.reload");
|
||||||
|
|
||||||
|
public BlacklistService(
|
||||||
|
DbService db,
|
||||||
|
IPubSub pubSub,
|
||||||
|
IBotCreds creds,
|
||||||
|
DiscordSocketClient client)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_pubSub = pubSub;
|
_pubSub = pubSub;
|
||||||
_creds = creds;
|
_creds = creds;
|
||||||
|
_client = client;
|
||||||
|
|
||||||
Reload(false);
|
_pubSub.Sub(_blPubKey, async _ => await Reload(false));
|
||||||
_pubSub.Sub(_blPubKey, OnReload);
|
}
|
||||||
|
|
||||||
|
public async Task OnReadyAsync()
|
||||||
|
{
|
||||||
|
_client.JoinedGuild += async (g) =>
|
||||||
|
{
|
||||||
|
if (blacklistedGuilds.Contains(g.Id))
|
||||||
|
{
|
||||||
|
await g.LeaveAsync();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await Reload(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ValueTask OnReload(BlacklistEntry[] newBlacklist)
|
private ValueTask OnReload(BlacklistEntry[] newBlacklist)
|
||||||
{
|
{
|
||||||
blacklist = newBlacklist;
|
newBlacklist ??= [];
|
||||||
|
|
||||||
|
blacklistedGuilds =
|
||||||
|
new HashSet<ulong>(newBlacklist.Where(x => x.Type == BlacklistType.Server).Select(x => x.ItemId))
|
||||||
|
.ToFrozenSet();
|
||||||
|
blacklistedChannels =
|
||||||
|
new HashSet<ulong>(newBlacklist.Where(x => x.Type == BlacklistType.Channel).Select(x => x.ItemId))
|
||||||
|
.ToFrozenSet();
|
||||||
|
blacklistedUsers =
|
||||||
|
new HashSet<ulong>(newBlacklist.Where(x => x.Type == BlacklistType.User).Select(x => x.ItemId))
|
||||||
|
.ToFrozenSet();
|
||||||
|
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<bool> ExecOnMessageAsync(IGuild guild, IUserMessage usrMsg)
|
public Task<bool> ExecOnMessageAsync(IGuild? guild, IUserMessage usrMsg)
|
||||||
{
|
{
|
||||||
foreach (var bl in blacklist)
|
if (guild is not null && blacklistedGuilds.Contains(guild.Id))
|
||||||
{
|
{
|
||||||
if (guild is not null && bl.Type == BlacklistType.Server && bl.ItemId == guild.Id)
|
Log.Information("Blocked input from blacklisted guild: {GuildName} [{GuildId}]",
|
||||||
{
|
guild.Name,
|
||||||
Log.Information("Blocked input from blacklisted guild: {GuildName} [{GuildId}]", guild.Name, guild.Id);
|
guild.Id.ToString());
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
return Task.FromResult(true);
|
if (blacklistedChannels.Contains(usrMsg.Channel.Id))
|
||||||
}
|
{
|
||||||
|
Log.Information("Blocked input from blacklisted channel: {ChannelName} [{ChannelId}]",
|
||||||
|
usrMsg.Channel.Name,
|
||||||
|
usrMsg.Channel.Id.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
if (bl.Type == BlacklistType.Channel && bl.ItemId == usrMsg.Channel.Id)
|
|
||||||
{
|
|
||||||
Log.Information("Blocked input from blacklisted channel: {ChannelName} [{ChannelId}]",
|
|
||||||
usrMsg.Channel.Name,
|
|
||||||
usrMsg.Channel.Id);
|
|
||||||
|
|
||||||
return Task.FromResult(true);
|
if (blacklistedUsers.Contains(usrMsg.Author.Id))
|
||||||
}
|
{
|
||||||
|
Log.Information("Blocked input from blacklisted user: {UserName} [{UserId}]",
|
||||||
if (bl.Type == BlacklistType.User && bl.ItemId == usrMsg.Author.Id)
|
usrMsg.Author.ToString(),
|
||||||
{
|
usrMsg.Author.Id.ToString());
|
||||||
Log.Information("Blocked input from blacklisted user: {UserName} [{UserId}]",
|
return Task.FromResult(true);
|
||||||
usrMsg.Author.ToString(),
|
|
||||||
usrMsg.Author.Id);
|
|
||||||
|
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult(false);
|
return Task.FromResult(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyList<BlacklistEntry> GetBlacklist()
|
public async Task<IReadOnlyList<BlacklistEntry>> GetBlacklist(BlacklistType type)
|
||||||
=> blacklist;
|
|
||||||
|
|
||||||
public void Reload(bool publish = true)
|
|
||||||
{
|
{
|
||||||
using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
var toPublish = uow.GetTable<BlacklistEntry>().ToArray();
|
|
||||||
blacklist = toPublish;
|
return await uow
|
||||||
|
.GetTable<BlacklistEntry>()
|
||||||
|
.Where(x => x.Type == type)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Reload(bool publish = true)
|
||||||
|
{
|
||||||
|
var totalShards = _creds.TotalShards;
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
var items = uow.GetTable<BlacklistEntry>()
|
||||||
|
.Where(x => x.Type != BlacklistType.Server
|
||||||
|
|| (x.Type == BlacklistType.Server
|
||||||
|
&& Linq2DbExpressions.GuildOnShard(x.ItemId, totalShards, _client.ShardId)))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
|
||||||
if (publish)
|
if (publish)
|
||||||
_pubSub.Pub(_blPubKey, toPublish);
|
{
|
||||||
|
await _pubSub.Pub(_blPubKey, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
await OnReload(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Blacklist(BlacklistType type, ulong id)
|
public async Task Blacklist(BlacklistType type, ulong id)
|
||||||
|
@ -88,34 +133,34 @@ public sealed class BlacklistService : IExecOnMessage
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
|
|
||||||
await uow
|
await uow
|
||||||
.GetTable<BlacklistEntry>()
|
.GetTable<BlacklistEntry>()
|
||||||
.InsertAsync(() => new()
|
.InsertAsync(() => new()
|
||||||
{
|
{
|
||||||
ItemId = id,
|
ItemId = id,
|
||||||
Type = type,
|
Type = type,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (type == BlacklistType.User)
|
if (type == BlacklistType.User)
|
||||||
{
|
{
|
||||||
await uow.GetTable<DiscordUser>()
|
await uow.GetTable<DiscordUser>()
|
||||||
.Where(x => x.UserId == id)
|
.Where(x => x.UserId == id)
|
||||||
.UpdateAsync(_ => new()
|
.UpdateAsync(_ => new()
|
||||||
{
|
{
|
||||||
CurrencyAmount = 0
|
CurrencyAmount = 0
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Reload();
|
await Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UnBlacklist(BlacklistType type, ulong id)
|
public async Task UnBlacklist(BlacklistType type, ulong id)
|
||||||
{
|
{
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
await uow.GetTable<BlacklistEntry>()
|
await uow.GetTable<BlacklistEntry>()
|
||||||
.Where(bi => bi.ItemId == id && bi.Type == type)
|
.Where(bi => bi.ItemId == id && bi.Type == type)
|
||||||
.DeleteAsync();
|
.DeleteAsync();
|
||||||
|
|
||||||
Reload();
|
await Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task BlacklistUsers(IReadOnlyCollection<ulong> toBlacklist)
|
public async Task BlacklistUsers(IReadOnlyCollection<ulong> toBlacklist)
|
||||||
|
@ -130,12 +175,12 @@ public sealed class BlacklistService : IExecOnMessage
|
||||||
|
|
||||||
var blList = toBlacklist.ToList();
|
var blList = toBlacklist.ToList();
|
||||||
await uow.GetTable<DiscordUser>()
|
await uow.GetTable<DiscordUser>()
|
||||||
.Where(x => blList.Contains(x.UserId))
|
.Where(x => blList.Contains(x.UserId))
|
||||||
.UpdateAsync(_ => new()
|
.UpdateAsync(_ => new()
|
||||||
{
|
{
|
||||||
CurrencyAmount = 0
|
CurrencyAmount = 0
|
||||||
});
|
});
|
||||||
|
|
||||||
Reload();
|
await Reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -27,5 +27,22 @@ public sealed class ImagesConfig : ConfigServiceBase<ImageUrls>
|
||||||
c.Version = 5;
|
c.Version = 5;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.Version < 6)
|
||||||
|
{
|
||||||
|
ModifyConfig(c =>
|
||||||
|
{
|
||||||
|
if (c.Slots.Emojis?.Length == 6)
|
||||||
|
{
|
||||||
|
c.Slots.Emojis =
|
||||||
|
[
|
||||||
|
new("https://cdn.nadeko.bot/slots/15.png"),
|
||||||
|
..c.Slots.Emojis
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Version = 6;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -193,7 +193,7 @@ public sealed class StatsService : IStatsService, IReadyExecutor, IEService
|
||||||
Id = g.Id,
|
Id = g.Id,
|
||||||
IconUrl = g.IconUrl,
|
IconUrl = g.IconUrl,
|
||||||
Name = g.Name,
|
Name = g.Name,
|
||||||
Owner = (await ig.GetUserAsync(g.OwnerId))?.Username ?? "Unknown",
|
Owner = (await ig.GetUserAsync(g.OwnerId))?.Username ?? "??Unknown",
|
||||||
OwnerId = g.OwnerId,
|
OwnerId = g.OwnerId,
|
||||||
CreatedAt = g.CreatedAt.UtcDateTime,
|
CreatedAt = g.CreatedAt.UtcDateTime,
|
||||||
VoiceChannels = g.VoiceChannels.Count,
|
VoiceChannels = g.VoiceChannels.Count,
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#nullable disable
|
using System.Text.RegularExpressions;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
namespace EllieBot.Common.TypeReaders.Models;
|
namespace EllieBot.Common.TypeReaders.Models;
|
||||||
|
|
||||||
|
@ -9,8 +8,8 @@ public class StoopidTime
|
||||||
@"^(?:(?<months>\d)mo)?(?:(?<weeks>\d{1,2})w)?(?:(?<days>\d{1,2})d)?(?:(?<hours>\d{1,4})h)?(?:(?<minutes>\d{1,5})m)?(?:(?<seconds>\d{1,6})s)?$",
|
@"^(?:(?<months>\d)mo)?(?:(?<weeks>\d{1,2})w)?(?:(?<days>\d{1,2})d)?(?:(?<hours>\d{1,4})h)?(?:(?<minutes>\d{1,5})m)?(?:(?<seconds>\d{1,6})s)?$",
|
||||||
RegexOptions.Compiled | RegexOptions.Multiline);
|
RegexOptions.Compiled | RegexOptions.Multiline);
|
||||||
|
|
||||||
public string Input { get; set; }
|
public string Input { get; set; } = string.Empty;
|
||||||
public TimeSpan Time { get; set; }
|
public TimeSpan Time { get; set; } = default;
|
||||||
|
|
||||||
private StoopidTime() { }
|
private StoopidTime() { }
|
||||||
|
|
||||||
|
@ -53,8 +52,8 @@ public class StoopidTime
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static implicit operator TimeSpan(StoopidTime st)
|
public static implicit operator TimeSpan?(StoopidTime? st)
|
||||||
=> st.Time;
|
=> st?.Time;
|
||||||
|
|
||||||
public static implicit operator StoopidTime(TimeSpan ts)
|
public static implicit operator StoopidTime(TimeSpan ts)
|
||||||
=> new()
|
=> new()
|
||||||
|
|
49
src/EllieBot/clean-migrations.ps1
Normal file
49
src/EllieBot/clean-migrations.ps1
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
# WORK IN PROGRESS
|
||||||
|
|
||||||
|
# Define the folders to search for designer.cs files
|
||||||
|
$folders = @("Migrations/PostgreSql", "Migrations/Sqlite")
|
||||||
|
|
||||||
|
# Loop through each folder
|
||||||
|
foreach ($folder in $folders) {
|
||||||
|
# Get all designer.cs files in the folder and subfolders
|
||||||
|
$files = Get-ChildItem -Path $folder -Filter *.designer.cs -Recurse
|
||||||
|
|
||||||
|
$excludedPattern = "cleanup|mysql-init|squash|rero-cascade"
|
||||||
|
|
||||||
|
$filteredFiles = $files | Where-Object { $_.Name -notmatch $excludedPattern }
|
||||||
|
# Loop through each file
|
||||||
|
foreach ($file in ($files | Where-Object { $_.Name -notmatch $excludedPattern })) {
|
||||||
|
# Read the contents of the file
|
||||||
|
$content = Get-Content -Path $file.FullName | Select-Object -First 30
|
||||||
|
|
||||||
|
# Find the attribute lines
|
||||||
|
$attributes = $content | Where-Object { $_ -match '\[.*\]' } | ForEach-Object { ' ' + $_.Trim() }
|
||||||
|
|
||||||
|
# Find the namespace
|
||||||
|
$namespace = $content | Where-Object { $_ -match 'namespace' } | ForEach-Object { $_.Split(' ')[1] }
|
||||||
|
|
||||||
|
# Find the class name
|
||||||
|
$class_name = $content | Where-Object { $_ -match 'partial class' } | ForEach-Object { $_.Trim().Split(' ')[2] }
|
||||||
|
|
||||||
|
# Replace the contents with the new template
|
||||||
|
$new_content = @"
|
||||||
|
// <auto-generated />
|
||||||
|
using EllieBot.Db;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace $namespace
|
||||||
|
{
|
||||||
|
$($attributes -join "`n")
|
||||||
|
partial class $class_name
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"@
|
||||||
|
|
||||||
|
# Write the new contents to the file
|
||||||
|
Set-Content -Path $file.FullName -Value $new_content
|
||||||
|
}
|
||||||
|
}
|
|
@ -848,6 +848,10 @@ eventstart:
|
||||||
- eventstart
|
- eventstart
|
||||||
betstats:
|
betstats:
|
||||||
- betstats
|
- betstats
|
||||||
|
- bs
|
||||||
|
gamblestats:
|
||||||
|
- gamblestats
|
||||||
|
- gs
|
||||||
bettest:
|
bettest:
|
||||||
- bettest
|
- bettest
|
||||||
slot:
|
slot:
|
||||||
|
@ -859,6 +863,11 @@ affinity:
|
||||||
waifuclaim:
|
waifuclaim:
|
||||||
- waifuclaim
|
- waifuclaim
|
||||||
- claim
|
- claim
|
||||||
|
- wc
|
||||||
|
waifuclaims:
|
||||||
|
- waifuclaims
|
||||||
|
- claims
|
||||||
|
- wcs
|
||||||
waifureset:
|
waifureset:
|
||||||
- waifureset
|
- waifureset
|
||||||
waifutransfer:
|
waifutransfer:
|
||||||
|
@ -1445,3 +1454,22 @@ ncpixel:
|
||||||
- ncgp
|
- ncgp
|
||||||
ncreset:
|
ncreset:
|
||||||
- ncreset
|
- ncreset
|
||||||
|
translateflags:
|
||||||
|
- translateflags
|
||||||
|
- trfl
|
||||||
|
- fltr
|
||||||
|
- transflags
|
||||||
|
rakeback:
|
||||||
|
- rakeback
|
||||||
|
- rb
|
||||||
|
betstatsreset:
|
||||||
|
- betstatsreset
|
||||||
|
- bsr
|
||||||
|
- bsreset
|
||||||
|
gamblestatsreset:
|
||||||
|
- gamblestatsreset
|
||||||
|
- gsr
|
||||||
|
- gsreset
|
||||||
|
snipe:
|
||||||
|
- snipe
|
||||||
|
- sn
|
|
@ -2756,19 +2756,6 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Gambling": [
|
"Gambling": [
|
||||||
{
|
|
||||||
"Aliases": [
|
|
||||||
".betstats"
|
|
||||||
],
|
|
||||||
"Description": "Shows the total stats of several gambling features.\nUpdates once an hour.",
|
|
||||||
"Usage": [
|
|
||||||
".betstats"
|
|
||||||
],
|
|
||||||
"Submodule": "Gambling",
|
|
||||||
"Module": "Gambling",
|
|
||||||
"Options": null,
|
|
||||||
"Requirements": []
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".timely"
|
".timely"
|
||||||
|
@ -3012,6 +2999,20 @@
|
||||||
"Bot Owner Only"
|
"Bot Owner Only"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".rakeback",
|
||||||
|
".rb"
|
||||||
|
],
|
||||||
|
"Description": "Try to claim any rakeback that you have available.\nRakeback is accumulated by betting (not by winning or losing).\nDefault rakeback is 0.05 * house edge\nHouse edge is defined per game",
|
||||||
|
"Usage": [
|
||||||
|
".rakeback"
|
||||||
|
],
|
||||||
|
"Submodule": "Gambling",
|
||||||
|
"Module": "Gambling",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".race"
|
".race"
|
||||||
|
@ -3119,6 +3120,70 @@
|
||||||
"Bot Owner Only"
|
"Bot Owner Only"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".betstatsreset",
|
||||||
|
".bsr",
|
||||||
|
".bsreset"
|
||||||
|
],
|
||||||
|
"Description": "Reset all of your Bet Stats for a fee.\nYou can alternatively reset Bet Stats for the specified game.",
|
||||||
|
"Usage": [
|
||||||
|
".betstatsreset",
|
||||||
|
".betstatsreset game"
|
||||||
|
],
|
||||||
|
"Submodule": "BetStatsCommands",
|
||||||
|
"Module": "Gambling",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".betstats",
|
||||||
|
".bs"
|
||||||
|
],
|
||||||
|
"Description": "Shows the current bet stats for yourself, or the targetted user.\nYou may optionally specify the game to show stats for.\nSupported games right now are: bf, br, bd, lula, slot, race",
|
||||||
|
"Usage": [
|
||||||
|
".betstats",
|
||||||
|
".betstats @someone",
|
||||||
|
".betstats @someone lula",
|
||||||
|
".betstats bd"
|
||||||
|
],
|
||||||
|
"Submodule": "BetStatsCommands",
|
||||||
|
"Module": "Gambling",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".gamblestats",
|
||||||
|
".gs"
|
||||||
|
],
|
||||||
|
"Description": "Shows the total stats of several gambling features.\nUpdates once an hour.",
|
||||||
|
"Usage": [
|
||||||
|
".gamblestats"
|
||||||
|
],
|
||||||
|
"Submodule": "BetStatsCommands",
|
||||||
|
"Module": "Gambling",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".gamblestatsreset",
|
||||||
|
".gsr",
|
||||||
|
".gsreset"
|
||||||
|
],
|
||||||
|
"Description": "Resets the gamble stats.",
|
||||||
|
"Usage": [
|
||||||
|
".gamblestatsreset"
|
||||||
|
],
|
||||||
|
"Submodule": "BetStatsCommands",
|
||||||
|
"Module": "Gambling",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": [
|
||||||
|
"Bot Owner Only"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".blackjack",
|
".blackjack",
|
||||||
|
@ -3583,10 +3648,25 @@
|
||||||
"Options": null,
|
"Options": null,
|
||||||
"Requirements": []
|
"Requirements": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".waifuclaims",
|
||||||
|
".claims",
|
||||||
|
".wcs"
|
||||||
|
],
|
||||||
|
"Description": "Shows all of your currently claimed waifus.",
|
||||||
|
"Usage": [
|
||||||
|
".waifuclaims"
|
||||||
|
],
|
||||||
|
"Submodule": "WaifuClaimCommands",
|
||||||
|
"Module": "Gambling",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".waifuclaim",
|
".waifuclaim",
|
||||||
".claim"
|
".claim - wc"
|
||||||
],
|
],
|
||||||
"Description": "Claim a waifu for yourself by spending currency. You must spend at least 10% more than her current value unless she set `.affinity` towards you.",
|
"Description": "Claim a waifu for yourself by spending currency. You must spend at least 10% more than her current value unless she set `.affinity` towards you.",
|
||||||
"Usage": [
|
"Usage": [
|
||||||
|
@ -6160,6 +6240,24 @@
|
||||||
"Options": null,
|
"Options": null,
|
||||||
"Requirements": []
|
"Requirements": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".translateflags",
|
||||||
|
".trfl",
|
||||||
|
".fltr",
|
||||||
|
".transflags"
|
||||||
|
],
|
||||||
|
"Description": "Toggles translate flags on the current channel.\nReacting with a country flag will translate the message to that country's language.",
|
||||||
|
"Usage": [
|
||||||
|
".translateflags"
|
||||||
|
],
|
||||||
|
"Submodule": "TranslateCommands",
|
||||||
|
"Module": "Searches",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": [
|
||||||
|
"ManageChannels Channel Permission"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".xkcd"
|
".xkcd"
|
||||||
|
@ -6508,6 +6606,20 @@
|
||||||
"No Public Bot"
|
"No Public Bot"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".snipe",
|
||||||
|
".sn"
|
||||||
|
],
|
||||||
|
"Description": "Snipe the message you replied to with this command.\nOtherwise, if you don't reply to a message, it will snipe the last message sent in the channel (out of the last few messages) which has text or an image.",
|
||||||
|
"Usage": [
|
||||||
|
".snipe"
|
||||||
|
],
|
||||||
|
"Submodule": "Utility",
|
||||||
|
"Module": "Utility",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".prompt"
|
".prompt"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# DO NOT CHANGE
|
# DO NOT CHANGE
|
||||||
version: 8
|
version: 12
|
||||||
# Currency settings
|
# Currency settings
|
||||||
currency:
|
currency:
|
||||||
# What is the emoji/character which represents the currency
|
# What is the emoji/character which represents the currency
|
||||||
|
@ -28,7 +28,7 @@ betRoll:
|
||||||
multiplyBy: 10
|
multiplyBy: 10
|
||||||
- whenAbove: 90
|
- whenAbove: 90
|
||||||
multiplyBy: 4
|
multiplyBy: 4
|
||||||
- whenAbove: 66
|
- whenAbove: 65
|
||||||
multiplyBy: 2
|
multiplyBy: 2
|
||||||
# Automatic currency generation settings.
|
# Automatic currency generation settings.
|
||||||
generation:
|
generation:
|
||||||
|
@ -56,6 +56,9 @@ timely:
|
||||||
# How often (in hours) can users claim currency with .timely command
|
# How often (in hours) can users claim currency with .timely command
|
||||||
# setting to 0 or less will disable this feature
|
# setting to 0 or less will disable this feature
|
||||||
cooldown: 12
|
cooldown: 12
|
||||||
|
# How will timely be protected?
|
||||||
|
# None, Button (users have to click the button) or Captcha (users have to type the captcha from an image)
|
||||||
|
protType: Button
|
||||||
# How much will each user's owned currency decay over time.
|
# How much will each user's owned currency decay over time.
|
||||||
decay:
|
decay:
|
||||||
# Percentage of user's current currency which will be deducted every 24h.
|
# Percentage of user's current currency which will be deducted every 24h.
|
||||||
|
@ -82,7 +85,7 @@ luckyLadder:
|
||||||
- 2.4
|
- 2.4
|
||||||
- 1.7
|
- 1.7
|
||||||
- 1.5
|
- 1.5
|
||||||
- 1.2
|
- 1.1
|
||||||
- 0.5
|
- 0.5
|
||||||
- 0.3
|
- 0.3
|
||||||
- 0.2
|
- 0.2
|
||||||
|
@ -125,12 +128,13 @@ waifu:
|
||||||
# Settings for periodic waifu price decay.
|
# Settings for periodic waifu price decay.
|
||||||
# Waifu price decays only if the waifu has no claimer.
|
# Waifu price decays only if the waifu has no claimer.
|
||||||
decay:
|
decay:
|
||||||
# Percentage (0 - 100) of the waifu value to reduce.
|
# Unclaimed waifus will decay by this percentage (0 - 100).
|
||||||
# Set 0 to disable
|
# Default is 0 (disabled)
|
||||||
# For example if a waifu has a price of 500$, setting this value to 10 would reduce the waifu value by 10% (50$)
|
# For example if a waifu has a price of 500$, setting this value to 10 would reduce the waifu value by 10% (50$)
|
||||||
unclaimedDecayPercent: 0
|
unclaimedDecayPercent: 0
|
||||||
# Claimed waifus will decay by this percentage (0 - 100).
|
# Claimed waifus will decay by this percentage (0 - 100).
|
||||||
# Default is 0 (disabled)
|
# Default is 0 (disabled)
|
||||||
|
# For example if a waifu has a price of 500$, setting this value to 10 would reduce the waifu value by 10% (50$)
|
||||||
claimedDecayPercent: 0
|
claimedDecayPercent: 0
|
||||||
# How often to decay waifu values, in hours
|
# How often to decay waifu values, in hours
|
||||||
hourInterval: 24
|
hourInterval: 24
|
||||||
|
@ -270,3 +274,9 @@ voteReward: 100
|
||||||
slots:
|
slots:
|
||||||
# Hex value of the color which the numbers on the slot image will have.
|
# Hex value of the color which the numbers on the slot image will have.
|
||||||
currencyFontColor: ff0000
|
currencyFontColor: ff0000
|
||||||
|
# Bonus config for server boosts
|
||||||
|
boostBonus:
|
||||||
|
# Users will receive a bonus if they boost any of these servers
|
||||||
|
guildIds: []
|
||||||
|
# This bonus will be added before any other multiplier is applied to the .timely command
|
||||||
|
baseTimelyBonus: 50
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# DO NOT CHANGE
|
# DO NOT CHANGE
|
||||||
version: 5
|
version: 6
|
||||||
coins:
|
coins:
|
||||||
heads:
|
heads:
|
||||||
- https://cdn.nadeko.bot/coins/heads3.png
|
- https://cdn.nadeko.bot/coins/heads3.png
|
||||||
|
@ -22,15 +22,13 @@ dice:
|
||||||
- https://cdn.nadeko.bot/other/dice/9.png
|
- https://cdn.nadeko.bot/other/dice/9.png
|
||||||
xp:
|
xp:
|
||||||
bg: https://cdn.nadeko.bot/other/xp/bg_k.png
|
bg: https://cdn.nadeko.bot/other/xp/bg_k.png
|
||||||
rip:
|
|
||||||
bg: https://cdn.nadeko.bot/other/rip/rip.png
|
|
||||||
overlay: https://cdn.nadeko.bot/other/rip/overlay.png
|
|
||||||
slots:
|
slots:
|
||||||
emojis:
|
emojis:
|
||||||
- https://cdn.nadeko.bot/slots/0.png
|
- https://cdn.nadeko.bot/slots/10.png
|
||||||
- https://cdn.nadeko.bot/slots/1.png
|
- https://cdn.nadeko.bot/slots/11.png
|
||||||
- https://cdn.nadeko.bot/slots/2.png
|
- https://cdn.nadeko.bot/slots/12.png
|
||||||
- https://cdn.nadeko.bot/slots/3.png
|
- https://cdn.nadeko.bot/slots/13.png
|
||||||
- https://cdn.nadeko.bot/slots/4.png
|
- https://cdn.nadeko.bot/slots/14.png
|
||||||
- https://cdn.nadeko.bot/slots/5.png
|
- https://cdn.nadeko.bot/slots/15.png
|
||||||
|
- https://cdn.nadeko.bot/slots/16.png
|
||||||
bg: https://cdn.nadeko.bot/slots/slots_bg.png
|
bg: https://cdn.nadeko.bot/slots/slots_bg.png
|
||||||
|
|
|
@ -1517,7 +1517,7 @@ take:
|
||||||
betroll:
|
betroll:
|
||||||
desc: |-
|
desc: |-
|
||||||
Bets the specified amount of currency and rolls a dice.
|
Bets the specified amount of currency and rolls a dice.
|
||||||
Rolling over 66 yields x2 of your currency, over 90 - x4 and 100 x10.
|
Rolling over 65 yields x2 of your currency, over 90 - x4 and 100 x10.
|
||||||
You can specify 'all', 'half' or 'X%' instead of the amount to bet that part of your current balance.
|
You can specify 'all', 'half' or 'X%' instead of the amount to bet that part of your current balance.
|
||||||
ex:
|
ex:
|
||||||
- 5
|
- 5
|
||||||
|
@ -2703,7 +2703,7 @@ eventstart:
|
||||||
desc: "The type of event being started."
|
desc: "The type of event being started."
|
||||||
options:
|
options:
|
||||||
desc: "The optional option flags for the event."
|
desc: "The optional option flags for the event."
|
||||||
betstats:
|
gamblestats:
|
||||||
desc: |-
|
desc: |-
|
||||||
Shows the total stats of several gambling features.
|
Shows the total stats of several gambling features.
|
||||||
Updates once an hour.
|
Updates once an hour.
|
||||||
|
@ -2711,6 +2711,13 @@ betstats:
|
||||||
- ''
|
- ''
|
||||||
params:
|
params:
|
||||||
- { }
|
- { }
|
||||||
|
gamblestatsreset:
|
||||||
|
desc: |-
|
||||||
|
Resets the gamble stats.
|
||||||
|
ex:
|
||||||
|
- ''
|
||||||
|
params:
|
||||||
|
- { }
|
||||||
slot:
|
slot:
|
||||||
desc: |-
|
desc: |-
|
||||||
Play Ellie slots by placing your bet.
|
Play Ellie slots by placing your bet.
|
||||||
|
@ -2738,6 +2745,12 @@ waifuclaim:
|
||||||
desc: "The cost of claiming the waifu."
|
desc: "The cost of claiming the waifu."
|
||||||
target:
|
target:
|
||||||
desc: "The user to whom the claim is being made, allowing the waifu to be claimed from their collection."
|
desc: "The user to whom the claim is being made, allowing the waifu to be claimed from their collection."
|
||||||
|
waifuclaims:
|
||||||
|
desc: Shows all of your currently claimed waifus.
|
||||||
|
ex:
|
||||||
|
- ''
|
||||||
|
params:
|
||||||
|
- { }
|
||||||
waifureset:
|
waifureset:
|
||||||
desc: Resets your waifu stats, except current waifus.
|
desc: Resets your waifu stats, except current waifus.
|
||||||
ex:
|
ex:
|
||||||
|
@ -4634,3 +4647,59 @@ ncreset:
|
||||||
- ''
|
- ''
|
||||||
params:
|
params:
|
||||||
- { }
|
- { }
|
||||||
|
translateflags:
|
||||||
|
desc: |-
|
||||||
|
Toggles translate flags on the current channel.
|
||||||
|
Reacting with a country flag will translate the message to that country's language.
|
||||||
|
ex:
|
||||||
|
- ''
|
||||||
|
params:
|
||||||
|
- { }
|
||||||
|
betstatsreset:
|
||||||
|
desc: |-
|
||||||
|
Reset all of your Bet Stats for a fee.
|
||||||
|
You can alternatively reset Bet Stats for the specified game.
|
||||||
|
ex:
|
||||||
|
- ''
|
||||||
|
- 'game'
|
||||||
|
params:
|
||||||
|
- game:
|
||||||
|
desc: 'The game to reset betstats for. Omit to reset all games'
|
||||||
|
betstats:
|
||||||
|
desc: |-
|
||||||
|
Shows the current bet stats for yourself, or the targetted user.
|
||||||
|
You may optionally specify the game to show stats for.
|
||||||
|
Supported games right now are: bf, br, bd, lula, slot, race
|
||||||
|
ex:
|
||||||
|
- ''
|
||||||
|
- '@someone'
|
||||||
|
- '@someone lula'
|
||||||
|
- 'bd'
|
||||||
|
params:
|
||||||
|
- {}
|
||||||
|
- user:
|
||||||
|
desc: 'The user for who to show the betstats for.'
|
||||||
|
- user:
|
||||||
|
desc: 'The user for who to show the betstats for.'
|
||||||
|
game:
|
||||||
|
desc: 'The game to show betstats for. Omit to show betstats for all games combined'
|
||||||
|
- game:
|
||||||
|
desc: 'The game to show betstats for. Omit to show betstats for all games combined'
|
||||||
|
rakeback:
|
||||||
|
desc: |-
|
||||||
|
Try to claim any rakeback that you have available.
|
||||||
|
Rakeback is accumulated by betting (not by winning or losing).
|
||||||
|
Default rakeback is 0.05 * house edge
|
||||||
|
House edge is defined per game
|
||||||
|
ex:
|
||||||
|
- ''
|
||||||
|
params:
|
||||||
|
- {}
|
||||||
|
snipe:
|
||||||
|
desc: |-
|
||||||
|
Snipe the message you replied to with this command.
|
||||||
|
Otherwise, if you don't reply to a message, it will snipe the last message sent in the channel (out of the last few messages) which has text or an image.
|
||||||
|
ex:
|
||||||
|
- ''
|
||||||
|
params:
|
||||||
|
- { }
|
|
@ -622,6 +622,7 @@
|
||||||
"region": "Region",
|
"region": "Region",
|
||||||
"remind2": "I will remind {0} to {1} {2} ({3})",
|
"remind2": "I will remind {0} to {1} {2} ({3})",
|
||||||
"remind_timely": "I will remind you about your timely reward {0}",
|
"remind_timely": "I will remind you about your timely reward {0}",
|
||||||
|
"timely_button": "Click the button to claim your timely reward.",
|
||||||
"remind_invalid": "Not a valid remind format. Remind must have a target, timer and a reason. Check the command list.",
|
"remind_invalid": "Not a valid remind format. Remind must have a target, timer and a reason. Check the command list.",
|
||||||
"remind_too_long": "Remind time has exceeded maximum.",
|
"remind_too_long": "Remind time has exceeded maximum.",
|
||||||
"repeater_redundant_no": "Repeater **#{0}** won't post redundant messages anymore.",
|
"repeater_redundant_no": "Repeater **#{0}** won't post redundant messages anymore.",
|
||||||
|
@ -1109,6 +1110,13 @@
|
||||||
"invalid_color": "Color you've specified is invalid.",
|
"invalid_color": "Color you've specified is invalid.",
|
||||||
"nc_pixel_set_confirm": "Are you sure you want to set pixel {0}? It will cost you {1}",
|
"nc_pixel_set_confirm": "Are you sure you want to set pixel {0}? It will cost you {1}",
|
||||||
"nc_hint": "Use `{0}nczoom x y` command to zoom in. Image is {1}x{2} pixels.",
|
"nc_hint": "Use `{0}nczoom x y` command to zoom in. Image is {1}x{2} pixels.",
|
||||||
|
"nc_insuff_payment": "Invalid payment.",
|
||||||
"invalid_img_size": "Image must to be {0}x{1} pixels.",
|
"invalid_img_size": "Image must to be {0}x{1} pixels.",
|
||||||
"no_attach_found": "No attachment found. Please send the image along with this command."
|
"no_attach_found": "No attachment found. Please send the image along with this command.",
|
||||||
|
"trfl_enabled": "Flag translation enabled on this channel. Reacting to a message with a flag will translate it to that language.",
|
||||||
|
"trfl_disabled": "Flag translation disabled.",
|
||||||
|
"rakeback_claimed": "You've claimed {0} as rakeback!",
|
||||||
|
"rakeback_none": "You don't have any rakeback to claim yet.",
|
||||||
|
"rakeback_available": "You have {0} rakeback available. Click the button to claim.",
|
||||||
|
"sniped_by": "Sniped by {0}"
|
||||||
}
|
}
|
||||||
|
|
8
src/EllieBot/migrate.ps1
Normal file
8
src/EllieBot/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 -c SqliteContext -p EllieBot.csproj
|
||||||
|
dotnet ef migrations add $migrationName -c PostgreSqlContext -p EllieBot.csproj
|
||||||
|
}
|
Loading…
Reference in a new issue