Compare commits
22 commits
Author | SHA1 | Date | |
---|---|---|---|
5e302fd02e | |||
85f735fcc7 | |||
e357c78fbe | |||
c3e3f5fabb | |||
8498f40f2c | |||
7e97d99637 | |||
6362b7f2e1 | |||
3838adf5d9 | |||
c7519baff9 | |||
15fafc577d | |||
ff46c4eab6 | |||
df6012bb86 | |||
3f6468f374 | |||
e0ff50b5cd | |||
276d311ff7 | |||
0aff208cf0 | |||
11141df435 | |||
525273bad6 | |||
3032aaa5b0 | |||
cbb9f8d1ac | |||
0e41cb7cc2 | |||
ffe4e64387 |
25 changed files with 1666 additions and 1040 deletions
.github/workflows
CHANGELOG.mdREADME.mddocs
src
EllieBot.VotesApi/Common
EllieBot
Db/Extensions
EllieBot.csprojModules
data
strings
36
.github/workflows/mkdocs-deploy.yml
vendored
Normal file
36
.github/workflows/mkdocs-deploy.yml
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
name: Deploy EllieBot Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["v6"]
|
||||
paths:
|
||||
- 'docs/**'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
cache: 'pip'
|
||||
cache-dependency-path: 'docs/mkdocs-requirements.txt'
|
||||
|
||||
- name: Install project dependencies
|
||||
run: pip install -r docs/mkdocs-requirements.txt
|
||||
|
||||
- name: Build the site with MkDocs
|
||||
working-directory: ./docs
|
||||
run: mkdocs build --strict
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
working-directory: ./docs
|
||||
run: mkdocs gh-deploy --force
|
282
CHANGELOG.md
282
CHANGELOG.md
|
@ -2,6 +2,33 @@
|
|||
|
||||
*a,c,f,r,o*
|
||||
|
||||
## [6.1.7] - 16.04.2025
|
||||
|
||||
### Fixed
|
||||
|
||||
- `.streamrole` fixed
|
||||
- fixed `.ura` hierarchy check (it will let owners assign roles too)
|
||||
|
||||
## [6.1.6] - 13.04.2025
|
||||
|
||||
### Fixed
|
||||
|
||||
- QuestCommands no longer appear as a separate module
|
||||
|
||||
## [6.1.5] - 07.04.2025
|
||||
|
||||
### Fixed
|
||||
|
||||
- `.xpadd` will finally apply rewards and trigger notifications
|
||||
- Fixed `.hangman` dislocation
|
||||
|
||||
## [6.1.4] - 05.04.2025
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed .timely awarding multiple times
|
||||
- Fixed .plant password - moved it down and right to avoid cutoff on phones
|
||||
|
||||
## [6.1.3] - 05.04.2025
|
||||
|
||||
### Fixed
|
||||
|
@ -14,10 +41,10 @@
|
|||
- Fixed `.feed` not adding new feeds to the database
|
||||
|
||||
## [6.1.1] - 03.04.2025
|
||||
|
||||
|
||||
### Added
|
||||
- Added some config options for .conf fish
|
||||
|
||||
|
||||
### Fixed
|
||||
- Fixed a typo in fish shop
|
||||
- .fishlb will now compare unique fish caught, instead of total catches
|
||||
|
@ -108,54 +135,54 @@
|
|||
- selfhosters: `.yml` parsing errors will now tell you which .yml file is causing the issue and why.
|
||||
|
||||
## [6.0.9] - 19.03.2025
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- `.cinfo` now also has a member list
|
||||
|
||||
|
||||
- `.cinfo` now also has a member list
|
||||
|
||||
### Fixed
|
||||
|
||||
- `.antispamignore` will now properly persist through restarts
|
||||
- livechannels and scheduled commands will now be inside utility module as they should
|
||||
|
||||
- `.antispamignore` will now properly persist through restarts
|
||||
- livechannels and scheduled commands will now be inside utility module as they should
|
||||
|
||||
## [6.0.8] - 19.03.2025
|
||||
|
||||
|
||||
### Added
|
||||
|
||||
- Live channel commands
|
||||
- `.lcha` adds a channel with a template message (supports placeholders, and works on category channels too!)
|
||||
- Every 10 minutes, channel name will be updated
|
||||
- example: `.lcha #my-channel --> Members: %server.members% <--` will display the number of members in the server as a channel name, updating once every 10 minutes
|
||||
- `.lchl` lists all live channels (Up to 5)
|
||||
- `.lchd <channel or channelId>` removed a live channel
|
||||
|
||||
|
||||
- Live channel commands
|
||||
- `.lcha` adds a channel with a template message (supports placeholders, and works on category channels too!)
|
||||
- Every 10 minutes, channel name will be updated
|
||||
- example: `.lcha #my-channel --> Members: %server.members% <--` will display the number of members in the server as a channel name, updating once every 10 minutes
|
||||
- `.lchl` lists all live channels (Up to 5)
|
||||
- `.lchd <channel or channelId>` removed a live channel
|
||||
|
||||
### Fixed
|
||||
|
||||
- `.antispamignore` fixed
|
||||
- `.antispamignore` fixed
|
||||
|
||||
## [6.0.7] - 19.03.2025
|
||||
|
||||
|
||||
### Added
|
||||
|
||||
- Schedule commands!
|
||||
- `.scha <time> <text>` adds the command to be excuted after the specified amount of time
|
||||
- `.schd <id>` deletes the command with the specified id
|
||||
- `.schl` lists your scheduled commands
|
||||
- `.masskick` added as massban and masskill already exist
|
||||
- `.xpex` and `.xpexl` are back, as there was no way to exclude specific users or roles with .xprate
|
||||
|
||||
|
||||
- Schedule commands!
|
||||
- `.scha <time> <text>` adds the command to be excuted after the specified amount of time
|
||||
- `.schd <id>` deletes the command with the specified id
|
||||
- `.schl` lists your scheduled commands
|
||||
- `.masskick` added as massban and masskill already exist
|
||||
- `.xpex` and `.xpexl` are back, as there was no way to exclude specific users or roles with .xprate
|
||||
|
||||
### Fix
|
||||
|
||||
- `.xprate` will now (as exclusion did) respect parent channel xp rates in threads
|
||||
- the xprate system will first check if a thread channel has a rate set
|
||||
- if it doesn't it will try to use the parent channel's rate
|
||||
|
||||
- `.xprate` will now (as exclusion did) respect parent channel xp rates in threads
|
||||
- the xprate system will first check if a thread channel has a rate set
|
||||
- if it doesn't it will try to use the parent channel's rate
|
||||
|
||||
## [6.0.6] - 16.03.2025
|
||||
|
||||
### Added
|
||||
|
||||
- Added youtube live stream notification support for `.streamadd`
|
||||
- it only works by using an invidious instance (with a working api) from data/searches.yml
|
||||
- it only works by using an invidious instance (with a working api) from data/searches.yml
|
||||
|
||||
### Fixed
|
||||
|
||||
|
@ -163,56 +190,56 @@
|
|||
- Fixed `.sfl` and similar toggles not working
|
||||
- Fixed `.antialt` and other protection commands not properly turning on
|
||||
- Fixed `%bot.time%` and `%bot.date%` placeholders showing wrong date.
|
||||
- No longer a timestamp
|
||||
- No longer a timestamp
|
||||
|
||||
## [6.0.5] - 15.03.2025
|
||||
|
||||
|
||||
### Added
|
||||
|
||||
- Aded a title in `.whosplaying`
|
||||
- Added a crown emoji next to commands if -v 1 or -v2 option is specified
|
||||
|
||||
### Changed
|
||||
|
||||
- `.remind` looks better
|
||||
- `.savechat` no longer owner only, up to 1000 messages - unlimited if ran by the bot owner
|
||||
|
||||
|
||||
- Aded a title in `.whosplaying`
|
||||
- Added a crown emoji next to commands if -v 1 or -v2 option is specified
|
||||
|
||||
### Changed
|
||||
|
||||
- `.remind` looks better
|
||||
- `.savechat` no longer owner only, up to 1000 messages - unlimited if ran by the bot owner
|
||||
|
||||
### Fixed
|
||||
|
||||
- `.ropl` fixed
|
||||
|
||||
- `.ropl` fixed
|
||||
|
||||
## [6.0.4] - 14.03.2025
|
||||
|
||||
### Added
|
||||
|
||||
- `.xp` system reworked
|
||||
- Global XP has been removed in favor of server XP
|
||||
- You can now set `.xprate` for each channel in your server!
|
||||
- You can set voice, image, and text rates
|
||||
- Use `.xpratereset` to reset it back to default
|
||||
- This feature makes `.xpexclude` obsolete
|
||||
- Requirement to create a club removed
|
||||
- `.xp` card should generate faster
|
||||
- Fixed countless possible issues with xp where some users didn't gain xp, or froze, etc
|
||||
- Global XP has been removed in favor of server XP
|
||||
- You can now set `.xprate` for each channel in your server!
|
||||
- You can set voice, image, and text rates
|
||||
- Use `.xpratereset` to reset it back to default
|
||||
- This feature makes `.xpexclude` obsolete
|
||||
- Requirement to create a club removed
|
||||
- `.xp` card should generate faster
|
||||
- Fixed countless possible issues with xp where some users didn't gain xp, or froze, etc
|
||||
- user-role commands added!
|
||||
- `.ura <user> <role>` - assign a role to a user
|
||||
- `.url <user?>` - list assigned roles for all users or a specific user
|
||||
- `.urm` - show 'my' (your) assigned roles
|
||||
- `.urn <role> <new_name>` - set a name for your role
|
||||
- `.urc <role> <hex_color>` - set a color for your role
|
||||
- `.uri <role> <url/server_emoji>` - set an icon for your role (accepts either a server emoji or a link to an image)
|
||||
- `.ura <user> <role>` - assign a role to a user
|
||||
- `.url <user?>` - list assigned roles for all users or a specific user
|
||||
- `.urm` - show 'my' (your) assigned roles
|
||||
- `.urn <role> <new_name>` - set a name for your role
|
||||
- `.urc <role> <hex_color>` - set a color for your role
|
||||
- `.uri <role> <url/server_emoji>` - set an icon for your role (accepts either a server emoji or a link to an image)
|
||||
- `.notify` improved
|
||||
- Lets you specify source channel (for some events) as the message output
|
||||
- Lets you specify source channel (for some events) as the message output
|
||||
- `.pload <id> --shuffle` lets you load a saved playlist in random order
|
||||
- `.lyrics <song_name>` added - find lyrics for a song (it's not always accurate)
|
||||
|
||||
- For Selfhosters
|
||||
- you have to update to latest v5 before updating to v6, otherwise migrations will fail
|
||||
- migration system was reworked
|
||||
- Xp card is now 500x245
|
||||
- xp_template.json backed up to old_xp_template.json
|
||||
- check pinned message in #dev channel to see full selfhoster announcement
|
||||
- Get bot version via --version
|
||||
- you have to update to latest v5 before updating to v6, otherwise migrations will fail
|
||||
- migration system was reworked
|
||||
- Xp card is now 500x245
|
||||
- xp_template.json backed up to old_xp_template.json
|
||||
- check pinned message in #dev channel to see full selfhoster announcement
|
||||
- Get bot version via --version
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -231,99 +258,99 @@
|
|||
|
||||
## [5.3.9] - 31.01.2025
|
||||
|
||||
## Added
|
||||
### Added
|
||||
|
||||
- Added `.todo archive done <name>`
|
||||
- Creates an archive of only currently completed todos
|
||||
- Added `.todo archive done <name>`
|
||||
- Creates an archive of only currently completed todos
|
||||
- An alternative to ".todo archive add <name>" which moves all todos to an archive
|
||||
|
||||
## Changed
|
||||
### Changed
|
||||
|
||||
- Increased todo and archive limits slightly
|
||||
- Global ellie captcha patron ad will show 12.5% of the time now, down from 20%, and be smaller
|
||||
- Global ellie captcha patron ad will show 12.5% of the time now, down from 20%, and be smaller
|
||||
- `.remind` now has a 1 year max timeout, up from 2 months
|
||||
|
||||
## Fixed
|
||||
### Fixed
|
||||
|
||||
- Captcha is now slightly bigger, with larger margin, to mitigate phone edge issues
|
||||
- Fixed `.stock` command, unless there is some ip blocking going on
|
||||
|
||||
## [5.3.8] - 29.01.2025
|
||||
|
||||
## Fixed
|
||||
### Fixed
|
||||
|
||||
- `.temprole` now correctly adds a role
|
||||
- `.h temprole` also shows the correct overload now
|
||||
- `.h temprole` also shows the correct overload now
|
||||
|
||||
## [5.3.7] - 21.01.2025
|
||||
|
||||
## Changed
|
||||
### Changed
|
||||
|
||||
- You can now run `.prune` in DMs
|
||||
- It deletes only bot messages
|
||||
- You can't specify a number of messages to delete (100 default)
|
||||
- It deletes only bot messages
|
||||
- You can't specify a number of messages to delete (100 default)
|
||||
- Updated command list
|
||||
|
||||
## [5.3.6] - 21.01.2025
|
||||
|
||||
## Added
|
||||
### Added
|
||||
|
||||
- Added player skill stat when fishing
|
||||
- Starts at 0, goes up to 100
|
||||
- Every time you fish you have a chance to get an extra skill point
|
||||
- Higher skill gives you more chance to catch fish (and therefore less chance to catch trash)
|
||||
- Starts at 0, goes up to 100
|
||||
- Every time you fish you have a chance to get an extra skill point
|
||||
- Higher skill gives you more chance to catch fish (and therefore less chance to catch trash)
|
||||
|
||||
## Changed
|
||||
### Changed
|
||||
|
||||
- Patrons no longer have `.timely` and `.fish` captcha on the public bot
|
||||
|
||||
## Fixed
|
||||
### Fixed
|
||||
|
||||
- Fixed fishing spots again (Your channels will once again change a spot, last time hopefully)
|
||||
- There was a mistake in spot calculation for each channel
|
||||
- There was a mistake in spot calculation for each channel
|
||||
|
||||
## [5.3.5] - 18.01.2025
|
||||
|
||||
## Fixed
|
||||
### Fixed
|
||||
|
||||
- .sar rm will now accept role ids in case the role was deleted
|
||||
- `.deletewaifus` should work again
|
||||
|
||||
## [5.3.4] - 14.01.2025
|
||||
|
||||
## Added
|
||||
### Added
|
||||
|
||||
- Added `.fish` commands
|
||||
- `.fish` - Attempt to catch a fish - different fish live in different places, at different times and during different times of the day
|
||||
- `.fishlist` - Look at your fish catalogue - shows how many of each fish you caught and what was the highest quality - for each caught fish, it also shows its required spot, time of day and weather
|
||||
- `.fishspot` - Shows information about the current fish spot, time of day and weather
|
||||
- `.fish` - Attempt to catch a fish - different fish live in different places, at different times and during different times of the day
|
||||
- `.fishlist` - Look at your fish catalogue - shows how many of each fish you caught and what was the highest quality - for each caught fish, it also shows its required spot, time of day and weather
|
||||
- `.fishspot` - Shows information about the current fish spot, time of day and weather
|
||||
|
||||
## Fixed
|
||||
### Fixed
|
||||
|
||||
- `.timely` fixed captcha sometimes generating only 2 characters
|
||||
|
||||
## [5.3.3] - 16.12.2024
|
||||
|
||||
## Fixed
|
||||
### Fixed
|
||||
|
||||
- `.notify` commands are no longer owner only, they now require Admin permissions
|
||||
- `.notify` messages can now mention anyone
|
||||
|
||||
## [5.3.2] - 14.12.2024
|
||||
|
||||
## Fixed
|
||||
### Fixed
|
||||
|
||||
- `.banner` should be working properly now with both server and global user banners
|
||||
|
||||
## [5.3.1] - 13.12.2024
|
||||
|
||||
## Changed
|
||||
### Changed
|
||||
|
||||
- `.translate` will now use 2 embeds, to allow for longer messages
|
||||
- Added role icon to `.inrole`, if it exists
|
||||
- `.honeypot` will now add a 'Honeypot' as a ban reason.
|
||||
|
||||
## Fixed
|
||||
### Fixed
|
||||
|
||||
- `.winlb` looks better, has a title, shows 9 entries now
|
||||
- `.sar ex` help updated
|
||||
|
@ -333,7 +360,7 @@
|
|||
|
||||
## [5.3.0] - 10.12.2024
|
||||
|
||||
## Added
|
||||
### Added
|
||||
|
||||
- Added `.minesweeper` / `.mw` command - spoiler-based minesweeper minigame. Just for fun
|
||||
- Added `.temprole` command - add a role to a user for a certain amount of time, after which the role will be removed
|
||||
|
@ -352,7 +379,7 @@
|
|||
- Added `.dmmod` and `.dmcmd` - you can now disable or enable whether commands or modules can be executed in bot's
|
||||
DMs
|
||||
|
||||
## Changed
|
||||
### Changed
|
||||
|
||||
- Giveaway improvements
|
||||
- Now mentions winners in a separate message
|
||||
|
@ -366,19 +393,19 @@
|
|||
to play)
|
||||
- `.translate` will now use 2 embeds instead of 1
|
||||
|
||||
## Fixed
|
||||
### Fixed
|
||||
|
||||
- .setstream and .setactivity will now pause .ropl (rotating statuses)
|
||||
- Fixed `.sar ex` help description
|
||||
|
||||
## Removed
|
||||
### Removed
|
||||
|
||||
- `.xpnotify` command, superseded by `.notify`, although as of right now you can't post user's level up in the same
|
||||
channel user last typed, because you have to specify a channel where the notify messages will be posted
|
||||
|
||||
## [5.2.4] - 29.11.2024
|
||||
|
||||
## Fixed
|
||||
### Fixed
|
||||
|
||||
- More fixes for .sclr
|
||||
- `.iamn` fixed
|
||||
|
@ -406,7 +433,6 @@
|
|||
- Fixed `.sclr` not updating unless bot is restarted, the changes should be immediate now for warn and error
|
||||
- Fixed group buttons exclusivity message always saying groups are exclusive
|
||||
|
||||
|
||||
## [5.2.1] - 28.11.2024
|
||||
|
||||
### Fixed
|
||||
|
@ -525,16 +551,18 @@
|
|||
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
|
||||
- `.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
|
||||
### 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
|
||||
- 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
|
||||
### Fixed
|
||||
|
||||
- nunchi join game message is now ok color instead of error color
|
||||
|
||||
|
@ -562,16 +590,16 @@
|
|||
|
||||
## [5.1.15] - 21.10.2024
|
||||
|
||||
## Added
|
||||
### Added
|
||||
|
||||
- Added -c option for `.xpglb`
|
||||
|
||||
## Change
|
||||
### Change
|
||||
|
||||
- Leaderboards will now show 10 users per page
|
||||
- A lot of internal changes and improvements
|
||||
|
||||
## Fixed
|
||||
### Fixed
|
||||
|
||||
- Fixed a big issue which caused several features to not get loaded on bot restart
|
||||
- Alias collision fix `.qse` is now quotesearch, `.qs` will stay `.queuesearch`
|
||||
|
@ -583,11 +611,11 @@
|
|||
|
||||
## [5.1.14] - 03.10.2024
|
||||
|
||||
## Changed
|
||||
### Changed
|
||||
|
||||
- Improved `.xplb -c`, it will now correctly only show users who are still in the server with no count limit
|
||||
|
||||
## Fixed
|
||||
### Fixed
|
||||
|
||||
- Fixed marmalade load error on startup
|
||||
|
||||
|
@ -659,7 +687,8 @@
|
|||
|
||||
### 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.
|
||||
- `.adpl` now supports custom statuses (you no longer need to specify Playing, Watching, etc...)
|
||||
|
||||
|
@ -671,7 +700,8 @@
|
|||
- `.quotesearch` / `.qse` is now paginated for easier searching
|
||||
- `.whosplaying` is now paginated
|
||||
- `.img` is now paginated
|
||||
- `.setgame` renamed to`.setactivity` and now supports custom text activity. You don't have to specify playing, listening etc before the activity
|
||||
- `.setgame` renamed to`.setactivity` and now supports custom text activity. You don't have to specify playing,
|
||||
listening etc before the activity
|
||||
- Clarified and added some embed / placeholder links to command help where needed
|
||||
- dev: A lot of code cleanup and internal improvements
|
||||
|
||||
|
@ -739,7 +769,8 @@
|
|||
- Updated some bet descriptions to include 'all' 'half' usage instructions
|
||||
- Updated some command strings
|
||||
- dev: Vastly simplified marmalade creation using dotnet templates, docs updated
|
||||
- Slight refactor of .wiki, time, .catfact, .wikia, .define, .bible and .quran commands, no significant change in functionality
|
||||
- Slight refactor of .wiki, time, .catfact, .wikia, .define, .bible and .quran commands, no significant change in
|
||||
functionality
|
||||
|
||||
### Fixed
|
||||
|
||||
|
@ -756,8 +787,10 @@
|
|||
|
||||
- Added `.coins` command which lists top 10 cryptos ordered by marketcap
|
||||
- Added Clubs rank in the leaderboard to `.clubinfo`
|
||||
- Bot owners can now check other people's bank balance (Not server owners, only bot owner, the person who is hosting the bot)
|
||||
- You can now send multiple waifu gifts at once to waifus. For example `.waifugift 3xRose @user` will give that user 3 roses
|
||||
- Bot owners can now check other people's bank balance (Not server owners, only bot owner, the person who is hosting the
|
||||
bot)
|
||||
- You can now send multiple waifu gifts at once to waifus. For example `.waifugift 3xRose @user` will give that user 3
|
||||
roses
|
||||
- The format is `<NUMBER>x<ITEM>`, no spaces
|
||||
- Added `.boosttest` command
|
||||
- Added support for any openai compatible api for the chatterbot feature change:
|
||||
|
@ -783,20 +816,20 @@
|
|||
|
||||
### Changed
|
||||
|
||||
- Replying to the bot's message in the channel where chatterbot is enabled will also trigger the ai response, as if you pinged the bot. This only works for chatterbot, but not for ellie ai command prompts
|
||||
- Replying to the bot's message in the channel where chatterbot is enabled will also trigger the ai response, as if you
|
||||
pinged the bot. This only works for chatterbot, but not for ellie ai command prompts
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `.stickeradd` it now properly supports 300x300 image uploads.
|
||||
- Bot should now trim the invalid characters from chatterbot usernames to avoid openai errors
|
||||
- Fixed prompt triggering chatterbot responses twice
|
||||
- Honeypot commands now actually works
|
||||
|
||||
## [5.1.2] - 29.06.2024
|
||||
|
||||
### Fixed
|
||||
|
||||
- Compile issues by disabling honeypot submodule for the time being
|
||||
- Fixed `.honeypot` not unbanning and not pruning messages
|
||||
|
||||
## [5.1.1] - 29.06.2024
|
||||
|
||||
|
@ -823,17 +856,20 @@
|
|||
|
||||
- Remind will now show a timestamp tag for durations
|
||||
- Only `Gpt35Turbo` and `Gpt4o` are valid inputs in games.yml now
|
||||
- `data/patron.yml` changed. It now has limits. The entire feature limit system has been reworked. Your previous settings will be reset
|
||||
- `data/patron.yml` changed. It now has limits. The entire feature limit system has been reworked. Your previous
|
||||
settings will be reset
|
||||
- A lot of updates to bot strings
|
||||
- Improved cleanup command to delete a lot more data once cleanup is ran, not only guild configs (please don't use this command unless you have your database bakced up and you know 100% what you're doing)
|
||||
- Improved cleanup command to delete a lot more data once cleanup is ran, not only guild configs (please don't use this
|
||||
command unless you have your database bakced up and you know 100% what you're doing)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed xp bg buy button not working, and possibly some other buttons too
|
||||
- Fixed shopbuy %user% placeholders and updated help text
|
||||
- All .feed overloads should now work"
|
||||
- `.xpexclude` should will now work with forums too. If you exclude a forum you won't be able to gain xp in any of the threads.
|
||||
- Fixed remind not showing correct time
|
||||
- `.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 the correct time
|
||||
|
||||
### Removed
|
||||
|
||||
|
@ -862,4 +898,4 @@
|
|||
- `.verbose` will now be respected for expression errors
|
||||
- Using `.pick` will now correctly show the name of the user who picked the currency
|
||||
- Fixed `.h` not working on some commands
|
||||
- `.langset` and `.langsetd` should no longer allow unsupported languages and nonsense to be typed in
|
||||
- `.langset` and `.langsetd` should no longer allow unsupported languages and nonsense to be typed in
|
|
@ -1,10 +1,10 @@
|
|||
# EllieBot
|
||||
|
||||
[](https://nogithub.codeberg.page)
|
||||
[](https://nogithub.codeberg.page) 
|
||||
|
||||
## Guides
|
||||
|
||||
For hosting guides go https://docs.elliebot.net/ellie/
|
||||
For hosting guides go https://docs.elliebot.net/
|
||||
|
||||
## Support
|
||||
|
||||
|
|
3
docs/mkdocs-requirements.txt
Normal file
3
docs/mkdocs-requirements.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
mkdocs-material~=9.6
|
||||
mkdocs~=1.6
|
||||
mkdocs-exclude~=1.0
|
|
@ -28,6 +28,8 @@ namespace EllieBot.VotesApi
|
|||
|
||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
await Task.Yield();
|
||||
|
||||
if (!Request.Headers.TryGetValue("Authorization", out var authHeader))
|
||||
{
|
||||
return AuthenticateResult.Fail("Authorization header missing");
|
||||
|
|
|
@ -29,10 +29,10 @@ public static class GuildConfigExtensions
|
|||
/// </summary>
|
||||
/// <param name="ctx">Db Context</param>
|
||||
/// <param name="guildId">Id of the guild to get stream role settings for.</param>
|
||||
/// <returns>Guild'p stream role settings</returns>
|
||||
/// <returns>Guild's stream role settings</returns>
|
||||
public static async Task<StreamRoleSettings> GetOrCreateStreamRoleSettings(this DbContext ctx, ulong guildId)
|
||||
{
|
||||
var srs = await ctx.GetTable<StreamRoleSettings>()
|
||||
var srs = await ctx.Set<StreamRoleSettings>()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.FirstOrDefaultAsyncEF();
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>true</ImplicitUsings>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
<Version>6.1.3</Version>
|
||||
<Version>6.1.7</Version>
|
||||
|
||||
<!-- Output/build -->
|
||||
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
||||
|
@ -23,38 +23,38 @@
|
|||
<PrivateAssets>all</PrivateAssets>
|
||||
<Publish>True</Publish>
|
||||
</PackageReference>
|
||||
<PackageReference Include="CodeHollow.FeedReader" Version="1.2.6" />
|
||||
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageReference Include="CodeHollow.FeedReader" Version="1.2.6"/>
|
||||
<PackageReference Include="CommandLineParser" Version="2.9.1"/>
|
||||
<PackageReference Include="Discord.Net" Version="3.17.2" />
|
||||
<PackageReference Include="CoreCLR-NCalc" Version="3.1.253" />
|
||||
<PackageReference Include="Google.Apis.Urlshortener.v1" Version="1.41.1.138" />
|
||||
<PackageReference Include="Google.Apis.Urlshortener.v1" Version="1.41.1.138"/>
|
||||
<PackageReference Include="Google.Apis.YouTube.v3" Version="1.68.0.3653" />
|
||||
<PackageReference Include="Google.Apis.Customsearch.v1" Version="1.49.0.2084" />
|
||||
|
||||
<PackageReference Include="Google.Apis.Customsearch.v1" Version="1.49.0.2084"/>
|
||||
|
||||
<PackageReference Include="Google.Protobuf" Version="3.29.3" />
|
||||
<PackageReference Include="Grpc" Version="2.46.6" />
|
||||
<PackageReference Include="Grpc.Net.Client" Version="2.67.0" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.68.1" PrivateAssets="All" />
|
||||
|
||||
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.8.0" />
|
||||
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.1" />
|
||||
|
||||
<PackageReference Include="MorseCode.ITask" Version="2.0.3" />
|
||||
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="3.1.0" />
|
||||
<PackageReference Include="MorseCode.ITask" Version="2.0.3"/>
|
||||
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="3.1.0"/>
|
||||
|
||||
<!-- DI -->
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.1" />
|
||||
<PackageReference Include="DryIoc.dll" Version="5.4.3" />
|
||||
<PackageReference Include="DryIoc.dll" Version="5.4.3"/>
|
||||
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="9.0.1" />
|
||||
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="NonBlocking" Version="2.1.2" />
|
||||
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2"/>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
|
||||
<PackageReference Include="NonBlocking" Version="2.1.2"/>
|
||||
<PackageReference Include="OneOf" Version="3.0.271" />
|
||||
<PackageReference Include="OneOf.SourceGenerator" Version="3.0.271" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
|
@ -63,10 +63,10 @@
|
|||
<PackageReference Include="SixLabors.Fonts" Version="2.1.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.7" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.5" />
|
||||
<PackageReference Include="SixLabors.Shapes" Version="1.0.0-beta0009" />
|
||||
<PackageReference Include="SixLabors.Shapes" Version="1.0.0-beta0009"/>
|
||||
<PackageReference Include="StackExchange.Redis" Version="2.8.24" />
|
||||
<PackageReference Include="YamlDotNet" Version="15.1.6" />
|
||||
<PackageReference Include="SharpToken" Version="2.0.3" />
|
||||
<PackageReference Include="SharpToken" Version="2.0.3"/>
|
||||
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2024.3.0" />
|
||||
|
||||
|
@ -94,14 +94,14 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\EllieBot.GrpcApiBase\EllieBot.GrpcApiBase.csproj" />
|
||||
<ProjectReference Include="..\Ellie.Marmalade\Ellie.Marmalade.csproj" />
|
||||
<ProjectReference Include="..\EllieBot.Voice\EllieBot.Voice.csproj" />
|
||||
<ProjectReference Include="..\EllieBot.Generators\EllieBot.Generators.csproj" OutputItemType="Analyzer" />
|
||||
<ProjectReference Include="..\EllieBot.GrpcApiBase\EllieBot.GrpcApiBase.csproj"/>
|
||||
<ProjectReference Include="..\Ellie.Marmalade\Ellie.Marmalade.csproj"/>
|
||||
<ProjectReference Include="..\EllieBot.Voice\EllieBot.Voice.csproj"/>
|
||||
<ProjectReference Include="..\EllieBot.Generators\EllieBot.Generators.csproj" OutputItemType="Analyzer"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="strings\responses\responses.en-US.json" />
|
||||
<AdditionalFiles Include="strings\responses\responses.en-US.json"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -138,4 +138,4 @@
|
|||
<DebugType>portable</DebugType>
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
</Project>
|
|
@ -4,24 +4,11 @@ using EllieBot.Db.Models;
|
|||
namespace EllieBot.Modules.EllieExpressions;
|
||||
|
||||
[Name("Expressions")]
|
||||
public partial class EllieExpressions : EllieModule<EllieExpressionsService>
|
||||
public sealed class EllieExpressions(IBotCreds creds, IHttpClientFactory clientFactory)
|
||||
: EllieModule<EllieExpressionsService>
|
||||
{
|
||||
public enum All
|
||||
{
|
||||
All
|
||||
}
|
||||
|
||||
private readonly IBotCreds _creds;
|
||||
private readonly IHttpClientFactory _clientFactory;
|
||||
|
||||
public EllieExpressions(IBotCreds creds, IHttpClientFactory clientFactory)
|
||||
{
|
||||
_creds = creds;
|
||||
_clientFactory = clientFactory;
|
||||
}
|
||||
|
||||
private bool AdminInGuildOrOwnerInDm()
|
||||
=> (ctx.Guild is null && _creds.IsOwner(ctx.User))
|
||||
=> (ctx.Guild is null && creds.IsOwner(ctx.User))
|
||||
|| (ctx.Guild is not null && ((IGuildUser)ctx.User).GuildPermissions.Administrator);
|
||||
|
||||
private async Task ExprAddInternalAsync(string key, string message)
|
||||
|
@ -34,14 +21,14 @@ public partial class EllieExpressions : EllieModule<EllieExpressionsService>
|
|||
var ex = await _service.AddAsync(ctx.Guild?.Id, key, message);
|
||||
|
||||
await Response()
|
||||
.Embed(CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.expr_new))
|
||||
.WithDescription($"#{new kwum(ex.Id)}")
|
||||
.AddField(GetText(strs.trigger), key)
|
||||
.AddField(GetText(strs.response),
|
||||
message.Length > 1024 ? GetText(strs.redacted_too_long) : message))
|
||||
.SendAsync();
|
||||
.Embed(CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.expr_new))
|
||||
.WithDescription($"#{new kwum(ex.Id)}")
|
||||
.AddField(GetText(strs.trigger), key)
|
||||
.AddField(GetText(strs.response),
|
||||
message.Length > 1024 ? GetText(strs.redacted_too_long) : message))
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
|
@ -104,14 +91,14 @@ public partial class EllieExpressions : EllieModule<EllieExpressionsService>
|
|||
if (ex is not null)
|
||||
{
|
||||
await Response()
|
||||
.Embed(CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.expr_edited))
|
||||
.WithDescription($"#{id}")
|
||||
.AddField(GetText(strs.trigger), ex.Trigger)
|
||||
.AddField(GetText(strs.response),
|
||||
message.Length > 1024 ? GetText(strs.redacted_too_long) : message))
|
||||
.SendAsync();
|
||||
.Embed(CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.expr_edited))
|
||||
.WithDescription($"#{id}")
|
||||
.AddField(GetText(strs.trigger), ex.Trigger)
|
||||
.AddField(GetText(strs.response),
|
||||
message.Length > 1024 ? GetText(strs.redacted_too_long) : message))
|
||||
.SendAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -121,7 +108,7 @@ public partial class EllieExpressions : EllieModule<EllieExpressionsService>
|
|||
|
||||
private bool IsValidExprEditor()
|
||||
=> (ctx.Guild is not null && ((IGuildUser)ctx.User).GuildPermissions.Administrator)
|
||||
|| (ctx.Guild is null && _creds.IsOwner(ctx.User));
|
||||
|| (ctx.Guild is null && creds.IsOwner(ctx.User));
|
||||
|
||||
[Cmd]
|
||||
[Priority(1)]
|
||||
|
@ -133,8 +120,8 @@ public partial class EllieExpressions : EllieModule<EllieExpressionsService>
|
|||
}
|
||||
|
||||
var allExpressions = _service.GetExpressionsFor(ctx.Guild?.Id)
|
||||
.OrderBy(x => x.Trigger)
|
||||
.ToArray();
|
||||
.OrderBy(x => x.Trigger)
|
||||
.ToArray();
|
||||
|
||||
if (!allExpressions.Any())
|
||||
{
|
||||
|
@ -143,25 +130,25 @@ public partial class EllieExpressions : EllieModule<EllieExpressionsService>
|
|||
}
|
||||
|
||||
await Response()
|
||||
.Paginated()
|
||||
.Items(allExpressions)
|
||||
.PageSize(20)
|
||||
.CurrentPage(page)
|
||||
.Page((exprs, _) =>
|
||||
{
|
||||
var desc = exprs
|
||||
.Select(ex => $"{(ex.ContainsAnywhere ? "🗯" : "◾")}"
|
||||
+ $"{(ex.DmResponse ? "✉" : "◾")}"
|
||||
+ $"{(ex.AutoDeleteTrigger ? "❌" : "◾")}"
|
||||
+ $"`{(kwum)ex.Id}` {ex.Trigger}"
|
||||
+ (string.IsNullOrWhiteSpace(ex.Reactions)
|
||||
? string.Empty
|
||||
: " // " + string.Join(" ", ex.GetReactions())))
|
||||
.Join('\n');
|
||||
.Paginated()
|
||||
.Items(allExpressions)
|
||||
.PageSize(20)
|
||||
.CurrentPage(page)
|
||||
.Page((exprs, _) =>
|
||||
{
|
||||
var desc = exprs
|
||||
.Select(ex => $"{(ex.ContainsAnywhere ? "🗯" : "◾")}"
|
||||
+ $"{(ex.DmResponse ? "✉" : "◾")}"
|
||||
+ $"{(ex.AutoDeleteTrigger ? "❌" : "◾")}"
|
||||
+ $"`{(kwum)ex.Id}` {ex.Trigger}"
|
||||
+ (string.IsNullOrWhiteSpace(ex.Reactions)
|
||||
? string.Empty
|
||||
: " // " + string.Join(" ", ex.GetReactions())))
|
||||
.Join('\n');
|
||||
|
||||
return CreateEmbed().WithOkColor().WithTitle(GetText(strs.expressions)).WithDescription(desc);
|
||||
})
|
||||
.SendAsync();
|
||||
return CreateEmbed().WithOkColor().WithTitle(GetText(strs.expressions)).WithDescription(desc);
|
||||
})
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
|
@ -178,27 +165,27 @@ public partial class EllieExpressions : EllieModule<EllieExpressionsService>
|
|||
var inter = CreateEditInteraction(id, found);
|
||||
|
||||
await Response()
|
||||
.Interaction(IsValidExprEditor() ? inter : null)
|
||||
.Embed(CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithDescription($"#{id}")
|
||||
.AddField(GetText(strs.trigger), found.Trigger.TrimTo(1024))
|
||||
.AddField(GetText(strs.response),
|
||||
found.Response.TrimTo(1000).Replace("](", "]\\(")))
|
||||
.SendAsync();
|
||||
.Interaction(IsValidExprEditor() ? inter : null)
|
||||
.Embed(CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithDescription($"#{id}")
|
||||
.AddField(GetText(strs.trigger), found.Trigger.TrimTo(1024))
|
||||
.AddField(GetText(strs.response),
|
||||
found.Response.TrimTo(1000).Replace("](", "]\\(")))
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
private EllieInteractionBase CreateEditInteraction(kwum id, EllieExpression found)
|
||||
{
|
||||
var modal = new ModalBuilder()
|
||||
.WithCustomId("expr:edit_modal")
|
||||
.WithTitle($"Edit expression {id}")
|
||||
.AddTextInput(new TextInputBuilder()
|
||||
.WithLabel(GetText(strs.response))
|
||||
.WithValue(found.Response)
|
||||
.WithMinLength(1)
|
||||
.WithCustomId("expr:edit_modal:response")
|
||||
.WithStyle(TextInputStyle.Paragraph));
|
||||
.WithCustomId("expr:edit_modal")
|
||||
.WithTitle($"Edit expression {id}")
|
||||
.AddTextInput(new TextInputBuilder()
|
||||
.WithLabel(GetText(strs.response))
|
||||
.WithValue(found.Response)
|
||||
.WithMinLength(1)
|
||||
.WithCustomId("expr:edit_modal:response")
|
||||
.WithStyle(TextInputStyle.Paragraph));
|
||||
|
||||
var inter = _inter.Create(ctx.User.Id,
|
||||
new ButtonBuilder()
|
||||
|
@ -224,13 +211,13 @@ public partial class EllieExpressions : EllieModule<EllieExpressionsService>
|
|||
if (ex is not null)
|
||||
{
|
||||
await Response()
|
||||
.Embed(CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.expr_deleted))
|
||||
.WithDescription($"#{id}")
|
||||
.AddField(GetText(strs.trigger), ex.Trigger.TrimTo(1024))
|
||||
.AddField(GetText(strs.response), ex.Response.TrimTo(1024)))
|
||||
.SendAsync();
|
||||
.Embed(CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.expr_deleted))
|
||||
.WithDescription($"#{id}")
|
||||
.AddField(GetText(strs.trigger), ex.Trigger.TrimTo(1024))
|
||||
.AddField(GetText(strs.response), ex.Response.TrimTo(1024)))
|
||||
.SendAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -296,7 +283,9 @@ public partial class EllieExpressions : EllieModule<EllieExpressionsService>
|
|||
break;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (succ.Count == 0)
|
||||
|
@ -309,9 +298,9 @@ public partial class EllieExpressions : EllieModule<EllieExpressionsService>
|
|||
|
||||
|
||||
await Response()
|
||||
.Confirm(strs.expr_set(Format.Bold(id.ToString()),
|
||||
succ.Select(static x => x.ToString()).Join(", ")))
|
||||
.SendAsync();
|
||||
.Confirm(strs.expr_set(Format.Bold(id.ToString()),
|
||||
succ.Select(static x => x.ToString()).Join(", ")))
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
|
@ -357,16 +346,16 @@ public partial class EllieExpressions : EllieModule<EllieExpressionsService>
|
|||
if (newVal)
|
||||
{
|
||||
await Response()
|
||||
.Confirm(strs.option_enabled(Format.Code(option.ToString()),
|
||||
Format.Code(id.ToString())))
|
||||
.SendAsync();
|
||||
.Confirm(strs.option_enabled(Format.Code(option.ToString()),
|
||||
Format.Code(id.ToString())))
|
||||
.SendAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await Response()
|
||||
.Confirm(strs.option_disabled(Format.Code(option.ToString()),
|
||||
Format.Code(id.ToString())))
|
||||
.SendAsync();
|
||||
.Confirm(strs.option_disabled(Format.Code(option.ToString()),
|
||||
Format.Code(id.ToString())))
|
||||
.SendAsync();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -376,8 +365,8 @@ public partial class EllieExpressions : EllieModule<EllieExpressionsService>
|
|||
public async Task ExprClear()
|
||||
{
|
||||
if (await PromptUserConfirmAsync(CreateEmbed()
|
||||
.WithTitle("Expression clear")
|
||||
.WithDescription("This will delete all expressions on this server.")))
|
||||
.WithTitle("Expression clear")
|
||||
.WithDescription("This will delete all expressions on this server.")))
|
||||
{
|
||||
var count = _service.DeleteAllExpressions(ctx.Guild.Id);
|
||||
await Response().Confirm(strs.exprs_cleared(count)).SendAsync();
|
||||
|
@ -397,7 +386,8 @@ public partial class EllieExpressions : EllieModule<EllieExpressionsService>
|
|||
|
||||
var serialized = _service.ExportExpressions(ctx.Guild?.Id);
|
||||
await using var stream = await serialized.ToStream();
|
||||
await ctx.User.SendFileAsync(stream, $"exprs-export_{DateTime.UtcNow:yyyy-MM-dd-HH-mm-ss}_{(ctx.Guild?.Id.ToString() ?? "global")}.yml");
|
||||
await ctx.User.SendFileAsync(stream,
|
||||
$"exprs-export_{DateTime.UtcNow:yyyy-MM-dd-HH-mm-ss}_{(ctx.Guild?.Id.ToString() ?? "global")}.yml");
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
|
@ -423,7 +413,7 @@ public partial class EllieExpressions : EllieModule<EllieExpressionsService>
|
|||
return;
|
||||
}
|
||||
|
||||
using var client = _clientFactory.CreateClient();
|
||||
using var client = clientFactory.CreateClient();
|
||||
input = await client.GetStringAsync(attachment.Url);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
|
|
|
@ -215,8 +215,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||
await Response().Error(strs.timely_none).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
await _cs.AddAsync(ctx.User.Id, val, new("timely", "claim"));
|
||||
|
||||
if (Config.Timely.ProtType == TimelyProt.Button)
|
||||
{
|
||||
var interaction = CreateTimelyInteraction();
|
||||
|
|
|
@ -135,10 +135,10 @@ public class PlantPickService(
|
|||
|
||||
// fill the background with black, add 5 pixels on each side to make it look better
|
||||
x.FillPolygon(Color.ParseHex("00000080"),
|
||||
new PointF(1, 1),
|
||||
new PointF(size.Width + 5, 0),
|
||||
new PointF(size.Width + 5, size.Height + 10),
|
||||
new PointF(0, size.Height + 10));
|
||||
new PointF(5, 5),
|
||||
new PointF(size.Width + 10, 5),
|
||||
new PointF(size.Width + 10, size.Height + 15),
|
||||
new PointF(5, size.Height + 15));
|
||||
|
||||
var strikeoutRun = new RichTextRun
|
||||
{
|
||||
|
@ -152,7 +152,7 @@ public class PlantPickService(
|
|||
// draw the password over the background
|
||||
x.DrawText(new RichTextOptions(font)
|
||||
{
|
||||
Origin = new(0, 0),
|
||||
Origin = new(5, 5),
|
||||
TextRuns =
|
||||
[
|
||||
strikeoutRun
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
using Microsoft.Extensions.Caching.Memory;
|
||||
using EllieBot.Modules.Games.Common;
|
||||
using EllieBot.Modules.Games.Common.Acrophobia;
|
||||
using EllieBot.Modules.Games.Common.Nunchi;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace EllieBot.Modules.Games.Services;
|
||||
|
@ -21,7 +20,7 @@ public class GamesService : IEService
|
|||
public ConcurrentDictionary<ulong, AcrophobiaGame> AcrophobiaGames { get; } = new();
|
||||
public Dictionary<ulong, TicTacToe> TicTacToeGames { get; } = new();
|
||||
public ConcurrentDictionary<ulong, TypingGame> RunningContests { get; } = new();
|
||||
public ConcurrentDictionary<ulong, NunchiGame> NunchiGames { get; } = new();
|
||||
public ConcurrentDictionary<ulong, CountUpGame> Games { get; } = new();
|
||||
|
||||
private readonly GamesConfigService _gamesConfig;
|
||||
|
||||
|
|
|
@ -27,8 +27,8 @@ public partial class Games
|
|||
```
|
||||
┌─────┐
|
||||
│ {head}
|
||||
│ {leftArm}{torso}{rightArm}
|
||||
│ {leftLeg} {rightLeg}
|
||||
│ {leftArm}{torso}{rightArm}
|
||||
│ {leftLeg} {rightLeg}
|
||||
─┴─
|
||||
```
|
||||
""";
|
||||
|
|
117
src/EllieBot/Modules/Games/Nunchi/CountUpCommands.cs
Normal file
117
src/EllieBot/Modules/Games/Nunchi/CountUpCommands.cs
Normal file
|
@ -0,0 +1,117 @@
|
|||
#nullable disable
|
||||
using EllieBot.Modules.Games.Services;
|
||||
|
||||
namespace EllieBot.Modules.Games;
|
||||
|
||||
public partial class Games
|
||||
{
|
||||
[Group]
|
||||
public partial class CountUpCommands : EllieModule<GamesService>
|
||||
{
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
public CountUpCommands(DiscordSocketClient client)
|
||||
=> _client = client;
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task CountUp()
|
||||
{
|
||||
var newGame = new CountUpGame(ctx.User.Id, ctx.User.ToString());
|
||||
CountUpGame countUp;
|
||||
|
||||
// if a game was already active
|
||||
if ((countUp = _service.Games.GetOrAdd(ctx.Guild.Id, newGame)) != newGame)
|
||||
{
|
||||
// join it
|
||||
// if you failed joining, that means the game is running or just ended
|
||||
if (!await countUp.Join(ctx.User.Id, ctx.User.ToString()))
|
||||
return;
|
||||
|
||||
await Response().Confirm(strs.countup_joined(countUp.ParticipantCount)).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{ await Response().Confirm(strs.countup_created).SendAsync(); }
|
||||
catch { }
|
||||
|
||||
countUp.OnGameEnded += CountUpOnGameEnded;
|
||||
countUp.OnRoundEnded += CountUpOnRoundEnded;
|
||||
countUp.OnUserGuessed += CountUpOnUserGuessed;
|
||||
countUp.OnRoundStarted += CountUpOnRoundStarted;
|
||||
_client.MessageReceived += ClientMessageReceived;
|
||||
|
||||
var success = await countUp.Initialize();
|
||||
if (!success)
|
||||
{
|
||||
if (_service.Games.TryRemove(ctx.Guild.Id, out var game))
|
||||
game.Dispose();
|
||||
await Response().Confirm(strs.countup_failed_to_start).SendAsync();
|
||||
}
|
||||
|
||||
Task ClientMessageReceived(SocketMessage arg)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
if (arg.Channel.Id != ctx.Channel.Id)
|
||||
return;
|
||||
|
||||
if (!int.TryParse(arg.Content, out var number))
|
||||
return;
|
||||
try
|
||||
{
|
||||
await countUp.Input(arg.Author.Id, arg.Author.ToString(), number);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task CountUpOnGameEnded(CountUpGame arg1, string arg2)
|
||||
{
|
||||
if (_service.Games.TryRemove(ctx.Guild.Id, out var game))
|
||||
{
|
||||
_client.MessageReceived -= ClientMessageReceived;
|
||||
game.Dispose();
|
||||
}
|
||||
|
||||
if (arg2 is null)
|
||||
return Response().Confirm(strs.countup_ended_no_winner).SendAsync();
|
||||
return Response().Confirm(strs.countup_ended(Format.Bold(arg2))).SendAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private Task CountUpOnRoundStarted(CountUpGame arg, int cur)
|
||||
=> Response()
|
||||
.Confirm(strs.countup_round_started(Format.Bold(arg.ParticipantCount.ToString()),
|
||||
Format.Bold(cur.ToString())))
|
||||
.SendAsync();
|
||||
|
||||
private Task CountUpOnUserGuessed(CountUpGame arg)
|
||||
=> Response()
|
||||
.Embed(CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithDescription(GetText(strs.countup_next_number(Format.Bold(arg.CurrentNumber.ToString()))))
|
||||
.WithFooter($"{arg.PassedCount} / {arg.ParticipantCount}"))
|
||||
.SendAsync();
|
||||
|
||||
private Task CountUpOnRoundEnded(CountUpGame arg1, (ulong Id, string Name)? arg2)
|
||||
{
|
||||
if (arg2.HasValue)
|
||||
return Response().Confirm(strs.countup_round_ended(Format.Bold(arg2.Value.Name))).SendAsync();
|
||||
return Response()
|
||||
.Confirm(strs.countup_round_ended_boot(
|
||||
Format.Bold("\n"
|
||||
+ string.Join("\n, ",
|
||||
arg1.Participants.Select(x
|
||||
=> x.Name)))))
|
||||
.SendAsync(); // this won't work if there are too many users
|
||||
}
|
||||
|
||||
private Task CountUpOnGameStarted(CountUpGame arg)
|
||||
=> Response().Confirm(strs.countup_started(Format.Bold(arg.ParticipantCount.ToString()))).SendAsync();
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
#nullable disable
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace EllieBot.Modules.Games.Common.Nunchi;
|
||||
namespace EllieBot.Modules.Games;
|
||||
|
||||
public sealed class NunchiGame : IDisposable
|
||||
public sealed class CountUpGame : IDisposable
|
||||
{
|
||||
public enum Phase
|
||||
{
|
||||
|
@ -16,11 +16,11 @@ public sealed class NunchiGame : IDisposable
|
|||
private const int KILL_TIMEOUT = 20 * 1000;
|
||||
private const int NEXT_ROUND_TIMEOUT = 5 * 1000;
|
||||
|
||||
public event Func<NunchiGame, Task> OnGameStarted;
|
||||
public event Func<NunchiGame, int, Task> OnRoundStarted;
|
||||
public event Func<NunchiGame, Task> OnUserGuessed;
|
||||
public event Func<NunchiGame, (ulong Id, string Name)?, Task> OnRoundEnded; // tuple of the user who failed
|
||||
public event Func<NunchiGame, string, Task> OnGameEnded; // name of the user who won
|
||||
public event Func<CountUpGame, Task> OnGameStarted;
|
||||
public event Func<CountUpGame, int, Task> OnRoundStarted;
|
||||
public event Func<CountUpGame, Task> OnUserGuessed;
|
||||
public event Func<CountUpGame, (ulong Id, string Name)?, Task> OnRoundEnded; // tuple of the user who failed
|
||||
public event Func<CountUpGame, string, Task> OnGameEnded; // name of the user who won
|
||||
|
||||
public int CurrentNumber { get; private set; } = new EllieRandom().Next(0, 100);
|
||||
public Phase CurrentPhase { get; private set; } = Phase.Joining;
|
||||
|
@ -31,13 +31,16 @@ public sealed class NunchiGame : IDisposable
|
|||
public int ParticipantCount
|
||||
=> participants.Count;
|
||||
|
||||
public int PassedCount
|
||||
=> _passed.Count;
|
||||
|
||||
private readonly SemaphoreSlim _locker = new(1, 1);
|
||||
|
||||
private HashSet<(ulong Id, string Name)> participants = [];
|
||||
private readonly HashSet<(ulong Id, string Name)> _passed = [];
|
||||
private Timer killTimer;
|
||||
|
||||
public NunchiGame(ulong creatorId, string creatorName)
|
||||
public CountUpGame(ulong creatorId, string creatorName)
|
||||
=> participants.Add((creatorId, creatorName));
|
||||
|
||||
public async Task<bool> Join(ulong userId, string userName)
|
|
@ -1,114 +0,0 @@
|
|||
#nullable disable
|
||||
using EllieBot.Modules.Games.Common.Nunchi;
|
||||
using EllieBot.Modules.Games.Services;
|
||||
|
||||
namespace EllieBot.Modules.Games;
|
||||
|
||||
public partial class Games
|
||||
{
|
||||
[Group]
|
||||
public partial class NunchiCommands : EllieModule<GamesService>
|
||||
{
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
public NunchiCommands(DiscordSocketClient client)
|
||||
=> _client = client;
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Nunchi()
|
||||
{
|
||||
var newNunchi = new NunchiGame(ctx.User.Id, ctx.User.ToString());
|
||||
NunchiGame nunchi;
|
||||
|
||||
//if a game was already active
|
||||
if ((nunchi = _service.NunchiGames.GetOrAdd(ctx.Guild.Id, newNunchi)) != newNunchi)
|
||||
{
|
||||
// join it
|
||||
// if you failed joining, that means game is running or just ended
|
||||
if (!await nunchi.Join(ctx.User.Id, ctx.User.ToString()))
|
||||
return;
|
||||
|
||||
await Response().Confirm(strs.nunchi_joined(nunchi.ParticipantCount)).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try { await Response().Confirm(strs.nunchi_created).SendAsync(); }
|
||||
catch { }
|
||||
|
||||
nunchi.OnGameEnded += NunchiOnGameEnded;
|
||||
//nunchi.OnGameStarted += Nunchi_OnGameStarted;
|
||||
nunchi.OnRoundEnded += Nunchi_OnRoundEnded;
|
||||
nunchi.OnUserGuessed += Nunchi_OnUserGuessed;
|
||||
nunchi.OnRoundStarted += Nunchi_OnRoundStarted;
|
||||
_client.MessageReceived += ClientMessageReceived;
|
||||
|
||||
var success = await nunchi.Initialize();
|
||||
if (!success)
|
||||
{
|
||||
if (_service.NunchiGames.TryRemove(ctx.Guild.Id, out var game))
|
||||
game.Dispose();
|
||||
await Response().Confirm(strs.nunchi_failed_to_start).SendAsync();
|
||||
}
|
||||
|
||||
Task ClientMessageReceived(SocketMessage arg)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
if (arg.Channel.Id != ctx.Channel.Id)
|
||||
return;
|
||||
|
||||
if (!int.TryParse(arg.Content, out var number))
|
||||
return;
|
||||
try
|
||||
{
|
||||
await nunchi.Input(arg.Author.Id, arg.Author.ToString(), number);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task NunchiOnGameEnded(NunchiGame arg1, string arg2)
|
||||
{
|
||||
if (_service.NunchiGames.TryRemove(ctx.Guild.Id, out var game))
|
||||
{
|
||||
_client.MessageReceived -= ClientMessageReceived;
|
||||
game.Dispose();
|
||||
}
|
||||
|
||||
if (arg2 is null)
|
||||
return Response().Confirm(strs.nunchi_ended_no_winner).SendAsync();
|
||||
return Response().Confirm(strs.nunchi_ended(Format.Bold(arg2))).SendAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private Task Nunchi_OnRoundStarted(NunchiGame arg, int cur)
|
||||
=> Response()
|
||||
.Confirm(strs.nunchi_round_started(Format.Bold(arg.ParticipantCount.ToString()),
|
||||
Format.Bold(cur.ToString())))
|
||||
.SendAsync();
|
||||
|
||||
private Task Nunchi_OnUserGuessed(NunchiGame arg)
|
||||
=> Response().Confirm(strs.nunchi_next_number(Format.Bold(arg.CurrentNumber.ToString()))).SendAsync();
|
||||
|
||||
private Task Nunchi_OnRoundEnded(NunchiGame arg1, (ulong Id, string Name)? arg2)
|
||||
{
|
||||
if (arg2.HasValue)
|
||||
return Response().Confirm(strs.nunchi_round_ended(Format.Bold(arg2.Value.Name))).SendAsync();
|
||||
return Response()
|
||||
.Confirm(strs.nunchi_round_ended_boot(
|
||||
Format.Bold("\n"
|
||||
+ string.Join("\n, ",
|
||||
arg1.Participants.Select(x
|
||||
=> x.Name)))))
|
||||
.SendAsync(); // this won't work if there are too many users
|
||||
}
|
||||
|
||||
private Task Nunchi_OnGameStarted(NunchiGame arg)
|
||||
=> Response().Confirm(strs.nunchi_started(Format.Bold(arg.ParticipantCount.ToString()))).SendAsync();
|
||||
}
|
||||
}
|
|
@ -1,38 +1,43 @@
|
|||
namespace EllieBot.Modules.Games.Quests;
|
||||
using EllieBot.Modules.Games.Quests;
|
||||
|
||||
public class QuestCommands : EllieModule<QuestService>
|
||||
namespace EllieBot.Modules.Games;
|
||||
|
||||
public partial class Games
|
||||
{
|
||||
[Cmd]
|
||||
public async Task QuestLog()
|
||||
public class QuestCommands : EllieModule<QuestService>
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var quests = await _service.GetUserQuestsAsync(ctx.User.Id, now);
|
||||
|
||||
var embed = CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.quest_log));
|
||||
|
||||
var allDone = quests.All(x => x.UserQuest.IsCompleted);
|
||||
|
||||
var tmrw = now.AddDays(1).Date;
|
||||
var desc = GetText(strs.dailies_reset(TimestampTag.FromDateTime(tmrw, TimestampTagStyles.Relative)));
|
||||
if (allDone)
|
||||
desc = GetText(strs.dailies_done) + "\n" + desc;
|
||||
|
||||
embed.WithDescription(desc);
|
||||
|
||||
foreach (var res in quests)
|
||||
[Cmd]
|
||||
public async Task QuestLog()
|
||||
{
|
||||
if (res.Quest is null)
|
||||
continue;
|
||||
var now = DateTime.UtcNow;
|
||||
var quests = await _service.GetUserQuestsAsync(ctx.User.Id, now);
|
||||
|
||||
embed.AddField(
|
||||
(res.UserQuest.IsCompleted ? IQuest.COMPLETED : IQuest.INCOMPLETE) + " " + res.Quest.Name,
|
||||
$"{res.Quest.Desc}\n\n" +
|
||||
res.Quest.ToString(res.UserQuest.Progress),
|
||||
true);
|
||||
var embed = CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.quest_log));
|
||||
|
||||
var allDone = quests.All(x => x.UserQuest.IsCompleted);
|
||||
|
||||
var tmrw = now.AddDays(1).Date;
|
||||
var desc = GetText(strs.dailies_reset(TimestampTag.FromDateTime(tmrw, TimestampTagStyles.Relative)));
|
||||
if (allDone)
|
||||
desc = GetText(strs.dailies_done) + "\n" + desc;
|
||||
|
||||
embed.WithDescription(desc);
|
||||
|
||||
foreach (var res in quests)
|
||||
{
|
||||
if (res.Quest is null)
|
||||
continue;
|
||||
|
||||
embed.AddField(
|
||||
(res.UserQuest.IsCompleted ? IQuest.COMPLETED : IQuest.INCOMPLETE) + " " + res.Quest.Name,
|
||||
$"{res.Quest.Desc}\n\n" +
|
||||
res.Quest.ToString(res.UserQuest.Progress),
|
||||
true);
|
||||
}
|
||||
|
||||
await Response().Embed(embed).SendAsync();
|
||||
}
|
||||
|
||||
await Response().Embed(embed).SendAsync();
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ namespace EllieBot.Modules.Help;
|
|||
|
||||
internal class CommandJsonObject
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string[] Aliases { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string[] Usage { get; set; }
|
||||
|
|
|
@ -40,6 +40,7 @@ public sealed partial class CommandListGenerator(
|
|||
|
||||
return new CommandJsonObject
|
||||
{
|
||||
Name = prefix + com.Aliases.First(),
|
||||
Aliases = com.Aliases.Select(alias => prefix + alias).ToArray(),
|
||||
Description = com.RealSummary(strings, marmalades, culture, prefix),
|
||||
Usage = com.RealRemarksArr(strings, marmalades, culture, prefix),
|
||||
|
|
|
@ -20,22 +20,12 @@ public partial class Utility
|
|||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
public async Task UserRoleAssign(IGuildUser user, IRole role)
|
||||
{
|
||||
var modUser = (IGuildUser)ctx.User;
|
||||
|
||||
if (modUser.GetRoles().Max(x => x.Position) <= role.Position)
|
||||
{
|
||||
await Response().Error(strs.userrole_hierarchy_error).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
var botUser = ((SocketGuild)ctx.Guild).CurrentUser;
|
||||
|
||||
if (botUser.GetRoles().Max(x => x.Position) <= role.Position)
|
||||
if (!await CheckRoleHierarchy(role))
|
||||
{
|
||||
await Response().Error(strs.hierarchy).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var success = await _urs.AddRoleAsync(ctx.Guild.Id, user.Id, role.Id);
|
||||
|
||||
if (!success)
|
||||
|
@ -50,16 +40,16 @@ public partial class Utility
|
|||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
public async Task UserRoleRemove(IUser user, IRole role)
|
||||
=> await UserRoleRemove(user, role.Id);
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
public async Task UserRoleRemove(IUser user, ulong roleId)
|
||||
{
|
||||
var modUser = (IGuildUser)ctx.User;
|
||||
var role = ctx.Guild.GetRole(roleId);
|
||||
|
||||
if (modUser.GetRoles().Max(x => x.Position) <= role.Position)
|
||||
{
|
||||
await Response().Error(strs.userrole_hierarchy_error).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
var success = await _urs.RemoveRoleAsync(ctx.Guild.Id, user.Id, role.Id);
|
||||
var success = await _urs.RemoveRoleAsync(ctx.Guild.Id, user.Id, roleId);
|
||||
if (!success)
|
||||
{
|
||||
await Response().Error(strs.userrole_not_found).SendAsync();
|
||||
|
@ -67,7 +57,9 @@ public partial class Utility
|
|||
}
|
||||
|
||||
await Response()
|
||||
.Confirm(strs.userrole_removed(Format.Bold(user.ToString()), Format.Bold(role.Name)))
|
||||
.Confirm(strs.userrole_removed(
|
||||
Format.Bold(user.ToString()),
|
||||
Format.Bold(role?.Name ?? roleId.ToString())))
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
|
|
|
@ -101,33 +101,33 @@ public partial class Xp : EllieModule<XpService>
|
|||
}
|
||||
|
||||
await Response()
|
||||
.Paginated()
|
||||
.PageItems(GetPageItems)
|
||||
.PageSize(10)
|
||||
.CurrentPage(page)
|
||||
.Page((users, curPage) =>
|
||||
{
|
||||
var embed = CreateEmbed().WithTitle(GetText(strs.server_leaderboard)).WithOkColor();
|
||||
.Paginated()
|
||||
.PageItems(GetPageItems)
|
||||
.PageSize(10)
|
||||
.CurrentPage(page)
|
||||
.Page((users, curPage) =>
|
||||
{
|
||||
var embed = CreateEmbed().WithTitle(GetText(strs.server_leaderboard)).WithOkColor();
|
||||
|
||||
if (!users.Any())
|
||||
return embed.WithDescription("-");
|
||||
if (!users.Any())
|
||||
return embed.WithDescription("-");
|
||||
|
||||
for (var i = 0; i < users.Count; i++)
|
||||
{
|
||||
var levelStats = new LevelStats(users[i].Xp);
|
||||
var user = ((SocketGuild)ctx.Guild).GetUser(users[i].UserId);
|
||||
for (var i = 0; i < users.Count; i++)
|
||||
{
|
||||
var levelStats = new LevelStats(users[i].Xp);
|
||||
var user = ((SocketGuild)ctx.Guild).GetUser(users[i].UserId);
|
||||
|
||||
var userXpData = users[i];
|
||||
var userXpData = users[i];
|
||||
|
||||
var awardStr = string.Empty;
|
||||
var awardStr = string.Empty;
|
||||
|
||||
embed.AddField($"#{i + 1 + (curPage * 10)} {user?.ToString() ?? users[i].UserId.ToString()}",
|
||||
$"{GetText(strs.level_x(levelStats.Level))} - {levelStats.TotalXp}xp {awardStr}");
|
||||
}
|
||||
embed.AddField($"#{i + 1 + (curPage * 10)} {user?.ToString() ?? users[i].UserId.ToString()}",
|
||||
$"{GetText(strs.level_x(levelStats.Level))} - {levelStats.TotalXp}xp {awardStr}");
|
||||
}
|
||||
|
||||
return embed;
|
||||
})
|
||||
.SendAsync();
|
||||
return embed;
|
||||
})
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
|
@ -148,8 +148,8 @@ public partial class Xp : EllieModule<XpService>
|
|||
|
||||
await _service.SetLevelAsync(ctx.Guild.Id, userId, level);
|
||||
await Response()
|
||||
.Confirm(strs.level_set($"<@{userId}>", Format.Bold(level.ToString())))
|
||||
.SendAsync();
|
||||
.Confirm(strs.level_set($"<@{userId}>", Format.Bold(level.ToString())))
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
|
@ -164,36 +164,28 @@ public partial class Xp : EllieModule<XpService>
|
|||
if (role.IsManaged)
|
||||
return;
|
||||
|
||||
var count = await _service.AddXpToUsersAsync(ctx.Guild.Id,
|
||||
await _service.AddXpAsync(ctx.Channel.Id,
|
||||
amount,
|
||||
role.Members.Select(x => x.Id).ToArray());
|
||||
role.Members.Cast<IGuildUser>().ToArray());
|
||||
|
||||
await Response()
|
||||
.Confirm(
|
||||
strs.xpadd_users(Format.Bold(amount.ToString()), Format.Bold(count.ToString())))
|
||||
.SendAsync();
|
||||
.Confirm(strs.xpadd_users(Format.Bold(amount.ToString()), Format.Bold(role.Name)))
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[Priority(3)]
|
||||
public async Task XpAdd(int amount, ulong userId)
|
||||
public async Task XpAdd(int amount, [Leftover] IGuildUser user)
|
||||
{
|
||||
if (amount == 0)
|
||||
return;
|
||||
|
||||
await _service.AddXpAsync(userId, ctx.Guild.Id, amount);
|
||||
var usr = ((SocketGuild)ctx.Guild).GetUser(userId)?.ToString() ?? userId.ToString();
|
||||
await Response().Confirm(strs.modified(Format.Bold(usr), Format.Bold(amount.ToString()))).SendAsync();
|
||||
await _service.AddXpAsync(ctx.Channel.Id, amount, user);
|
||||
await Response().Confirm(strs.modified(Format.Bold(user.ToString()), Format.Bold(amount.ToString()))).SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[Priority(4)]
|
||||
public Task XpAdd(int amount, [Leftover] IGuildUser user)
|
||||
=> XpAdd(amount, user.Id);
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
|
@ -216,8 +208,8 @@ public partial class Xp : EllieModule<XpService>
|
|||
public async Task XpReset(ulong userId)
|
||||
{
|
||||
var embed = CreateEmbed()
|
||||
.WithTitle(GetText(strs.reset))
|
||||
.WithDescription(GetText(strs.reset_user_confirm));
|
||||
.WithTitle(GetText(strs.reset))
|
||||
.WithDescription(GetText(strs.reset_user_confirm));
|
||||
|
||||
if (!await PromptUserConfirmAsync(embed))
|
||||
return;
|
||||
|
@ -233,8 +225,8 @@ public partial class Xp : EllieModule<XpService>
|
|||
public async Task XpReset()
|
||||
{
|
||||
var embed = CreateEmbed()
|
||||
.WithTitle(GetText(strs.reset))
|
||||
.WithDescription(GetText(strs.reset_server_confirm));
|
||||
.WithTitle(GetText(strs.reset))
|
||||
.WithDescription(GetText(strs.reset_server_confirm));
|
||||
|
||||
if (!await PromptUserConfirmAsync(embed))
|
||||
return;
|
||||
|
@ -267,12 +259,12 @@ public partial class Xp : EllieModule<XpService>
|
|||
}
|
||||
|
||||
await Response()
|
||||
.Confirm(GetText(strs.available_commands),
|
||||
$"""
|
||||
`{prefix}xpshop bgs`
|
||||
`{prefix}xpshop frames`
|
||||
""")
|
||||
.SendAsync();
|
||||
.Confirm(GetText(strs.available_commands),
|
||||
$"""
|
||||
`{prefix}xpshop bgs`
|
||||
`{prefix}xpshop frames`
|
||||
""")
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
|
@ -300,80 +292,80 @@ public partial class Xp : EllieModule<XpService>
|
|||
}
|
||||
|
||||
await Response()
|
||||
.Paginated()
|
||||
.Items(allItems)
|
||||
.PageSize(1)
|
||||
.CurrentPage(page)
|
||||
.AddFooter(false)
|
||||
.Page((items, _) =>
|
||||
{
|
||||
if (!items.Any())
|
||||
return CreateEmbed()
|
||||
.WithDescription(GetText(strs.not_found))
|
||||
.WithErrorColor();
|
||||
.Paginated()
|
||||
.Items(allItems)
|
||||
.PageSize(1)
|
||||
.CurrentPage(page)
|
||||
.AddFooter(false)
|
||||
.Page((items, _) =>
|
||||
{
|
||||
if (!items.Any())
|
||||
return CreateEmbed()
|
||||
.WithDescription(GetText(strs.not_found))
|
||||
.WithErrorColor();
|
||||
|
||||
var (key, item) = items.FirstOrDefault();
|
||||
var (_, item) = items.FirstOrDefault();
|
||||
|
||||
var eb = CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(item.Name)
|
||||
.AddField(GetText(strs.price),
|
||||
CurrencyHelper.N(item.Price, Culture, _gss.GetCurrencySign()),
|
||||
true)
|
||||
.WithImageUrl(string.IsNullOrWhiteSpace(item.Preview)
|
||||
? item.Url
|
||||
: item.Preview);
|
||||
var eb = CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(item.Name)
|
||||
.AddField(GetText(strs.price),
|
||||
CurrencyHelper.N(item.Price, Culture, _gss.GetCurrencySign()),
|
||||
true)
|
||||
.WithImageUrl(string.IsNullOrWhiteSpace(item.Preview)
|
||||
? item.Url
|
||||
: item.Preview);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(item.Desc))
|
||||
eb.AddField(GetText(strs.desc), item.Desc);
|
||||
if (!string.IsNullOrWhiteSpace(item.Desc))
|
||||
eb.AddField(GetText(strs.desc), item.Desc);
|
||||
|
||||
return eb;
|
||||
})
|
||||
.Interaction(async current =>
|
||||
{
|
||||
var (key, _) = allItems.Skip(current).First();
|
||||
return eb;
|
||||
})
|
||||
.Interaction(async current =>
|
||||
{
|
||||
var (key, _) = allItems.Skip(current).First();
|
||||
|
||||
var itemType = type == XpShopInputType.Backgrounds
|
||||
? XpShopItemType.Background
|
||||
: XpShopItemType.Frame;
|
||||
var itemType = type == XpShopInputType.Backgrounds
|
||||
? XpShopItemType.Background
|
||||
: XpShopItemType.Frame;
|
||||
|
||||
var ownedItem = await _service.GetUserItemAsync(ctx.User.Id, itemType, key);
|
||||
if (ownedItem is not null)
|
||||
{
|
||||
var button = new ButtonBuilder(ownedItem.IsUsing
|
||||
? GetText(strs.in_use)
|
||||
: GetText(strs.use),
|
||||
"xpshop:use",
|
||||
emote: Emoji.Parse("👐"),
|
||||
isDisabled: ownedItem.IsUsing);
|
||||
var ownedItem = await _service.GetUserItemAsync(ctx.User.Id, itemType, key);
|
||||
if (ownedItem is not null)
|
||||
{
|
||||
var button = new ButtonBuilder(ownedItem.IsUsing
|
||||
? GetText(strs.in_use)
|
||||
: GetText(strs.use),
|
||||
"xpshop:use",
|
||||
emote: Emoji.Parse("👐"),
|
||||
isDisabled: ownedItem.IsUsing);
|
||||
|
||||
var inter = _inter.Create(
|
||||
ctx.User.Id,
|
||||
button,
|
||||
OnShopUse,
|
||||
(key, itemType),
|
||||
clearAfter: false);
|
||||
var inter = _inter.Create(
|
||||
ctx.User.Id,
|
||||
button,
|
||||
OnShopUse,
|
||||
(key, itemType),
|
||||
clearAfter: false);
|
||||
|
||||
return inter;
|
||||
}
|
||||
else
|
||||
{
|
||||
var button = new ButtonBuilder(GetText(strs.buy),
|
||||
"xpshop:buy",
|
||||
emote: Emoji.Parse("💰"));
|
||||
return inter;
|
||||
}
|
||||
else
|
||||
{
|
||||
var button = new ButtonBuilder(GetText(strs.buy),
|
||||
"xpshop:buy",
|
||||
emote: Emoji.Parse("💰"));
|
||||
|
||||
var inter = _inter.Create(
|
||||
ctx.User.Id,
|
||||
button,
|
||||
OnShopBuy,
|
||||
(key, itemType),
|
||||
singleUse: true,
|
||||
clearAfter: false);
|
||||
var inter = _inter.Create(
|
||||
ctx.User.Id,
|
||||
button,
|
||||
OnShopBuy,
|
||||
(key, itemType),
|
||||
singleUse: true,
|
||||
clearAfter: false);
|
||||
|
||||
return inter;
|
||||
}
|
||||
})
|
||||
.SendAsync();
|
||||
return inter;
|
||||
}
|
||||
})
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
|
@ -396,8 +388,8 @@ public partial class Xp : EllieModule<XpService>
|
|||
{
|
||||
BuyResult.XpShopDisabled => await Response().Error(strs.xp_shop_disabled).SendAsync(),
|
||||
BuyResult.InsufficientFunds => await Response()
|
||||
.Error(strs.not_enough(_gss.GetCurrencySign()))
|
||||
.SendAsync(),
|
||||
.Error(strs.not_enough(_gss.GetCurrencySign()))
|
||||
.SendAsync(),
|
||||
BuyResult.AlreadyOwned =>
|
||||
await Response().Error(strs.xpshop_already_owned).Interaction(GetUseInteraction()).SendAsync(),
|
||||
BuyResult.UnknownItem => await Response().Error(strs.xpshop_item_not_found).SendAsync(),
|
||||
|
@ -407,10 +399,10 @@ public partial class Xp : EllieModule<XpService>
|
|||
}
|
||||
|
||||
await Response()
|
||||
.Confirm(strs.xpshop_buy_success(type.ToString().ToLowerInvariant(),
|
||||
key.ToLowerInvariant()))
|
||||
.Interaction(GetUseInteraction())
|
||||
.SendAsync();
|
||||
.Confirm(strs.xpshop_buy_success(type.ToString().ToLowerInvariant(),
|
||||
key.ToLowerInvariant()))
|
||||
.Interaction(GetUseInteraction())
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
|
|
|
@ -551,25 +551,12 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
return false;
|
||||
}
|
||||
|
||||
public async Task<int> AddXpToUsersAsync(ulong guildId, long amount, params ulong[] userIds)
|
||||
public Task AddXpAsync(ulong channelId, long amount, params IGuildUser[] users)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
return await ctx.GetTable<UserXpStats>()
|
||||
.Where(x => x.GuildId == guildId && userIds.Contains(x.UserId))
|
||||
.UpdateAsync(old => new()
|
||||
{
|
||||
Xp = old.Xp + amount
|
||||
});
|
||||
}
|
||||
|
||||
public async Task AddXpAsync(ulong userId, ulong guildId, int amount)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var usr = uow.GetOrCreateUserXpStats(guildId, userId);
|
||||
|
||||
usr.Xp += amount;
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
foreach (var user in users)
|
||||
_usersBatch.Add(new(user, amount, channelId));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task<bool> TryAddUserGainedXpAsync(ulong userId, float cdInMinutes)
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -442,7 +442,8 @@ race:
|
|||
joinrace:
|
||||
- joinrace
|
||||
- jr
|
||||
nunchi:
|
||||
countup:
|
||||
- countup
|
||||
- nunchi
|
||||
connect4:
|
||||
- connect4
|
||||
|
|
|
@ -1473,8 +1473,12 @@ joinrace:
|
|||
params:
|
||||
- amount:
|
||||
desc: "The amount to be wagered on the race."
|
||||
nunchi:
|
||||
desc: Creates or joins an existing nunchi game. Users have to count up by 1 from the starting number shown by the bot. If someone makes a mistake (types an incorrect number, or repeats the same number) they are out of the game and a new round starts without them. Minimum 3 users required.
|
||||
countup:
|
||||
desc: |-
|
||||
Creates or joins an existing CountUp game.
|
||||
Bot will show a number - count up from it.
|
||||
Whoever writes a duplicate number, or is the last person without a number loses, a new round starts!
|
||||
Minimum 3 users required.
|
||||
ex:
|
||||
- ''
|
||||
params:
|
||||
|
|
|
@ -806,16 +806,16 @@
|
|||
"connect4_failed_to_start": "Connect4 game failed to start because nobody joined.",
|
||||
"connect4_draw": "Connect4 game ended in a draw.",
|
||||
"connect4_won": "{0} won the game of Connect4 against {1}.",
|
||||
"nunchi_joined": "Joined nunchi game. {0} users joined so far.",
|
||||
"nunchi_ended": "Nunchi game ended. {0} won",
|
||||
"nunchi_ended_no_winner": "Nunchi game ended with no winner.",
|
||||
"nunchi_started": "Nunchi game started with {0} participants.",
|
||||
"nunchi_round_ended": "Nunchi round ended. {0} is out of the game.",
|
||||
"nunchi_round_ended_boot": "Nunchi round ended due to timeout of some users. These users are still in the game: {0}",
|
||||
"nunchi_round_started": "Nunchi round started with {0} users. Start counting from the number {1}.",
|
||||
"nunchi_next_number": "Number registered. Last number was {0}.",
|
||||
"nunchi_failed_to_start": "Nunchi failed to start because there were not enough participants.",
|
||||
"nunchi_created": "Nunchi game created. Waiting for users to join.",
|
||||
"countup_joined": "Joined countup game. {0} users joined so far.",
|
||||
"countup_ended": "CountUp game ended. {0} won",
|
||||
"countup_ended_no_winner": "CountUp game ended with no winner.",
|
||||
"countup_started": "CountUp game started with {0} participants.",
|
||||
"countup_round_ended": "CountUp round ended. {0} is out of the game.",
|
||||
"countup_round_ended_boot": "CountUp round ended due to timeout of some users. These users are still in the game: {0}",
|
||||
"countup_round_started": "CountUp round started with {0} users. Start counting from the number {1}.",
|
||||
"countup_next_number": "Number registered. Last number was {0}.",
|
||||
"countup_failed_to_start": "CountUp failed to start because there were not enough participants.",
|
||||
"countup_created": "CountUp game created. Waiting for users to join.",
|
||||
"stream_role_enabled": "When a user from {0} role starts streaming, I will give them {1} role.",
|
||||
"stream_role_disabled": "Stream role feature has been disabled.",
|
||||
"stream_role_kw_set": "Streamers now require {0} keyword in order to receive the role.",
|
||||
|
@ -994,7 +994,7 @@
|
|||
"module_page_empty": "No module on this page.",
|
||||
"module_description_help": "Get command help, descriptions and usage examples",
|
||||
"module_description_gambling": "Bet on dice rolls, blackjack, slots, coinflips and others",
|
||||
"module_description_games": "Play trivia, nunchi, hangman, connect4 and other games",
|
||||
"module_description_games": "Play trivia, countup, hangman, connect4 and other games",
|
||||
"module_description_music": "Play music from youtube, local files and radio streams",
|
||||
"module_description_utility": "Manage custom quotes, repeating messages and check facts about the server",
|
||||
"module_description_administration": "Moderation, punish users, setup self assignable roles and greet messages",
|
||||
|
@ -1204,7 +1204,6 @@
|
|||
"userrole_icon_success": "The icon for {0} has been saved.",
|
||||
"userrole_icon_fail": "Failed to set the role icon.",
|
||||
"userrole_icon_invalid": "The role icon cannot be empty.",
|
||||
"userrole_hierarchy_error": "You can't assign or modify roles that are higher than or equal to your, or bots highest role.",
|
||||
"userrole_role_not_exists": "That role doesn't exist.",
|
||||
"whos_playing_game": "{0} users are playing {1}",
|
||||
"schedule_list_title": "Scheduled Commands",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue