Compare commits
36 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 | |||
427a011590 | |||
682ecb6f08 | |||
ced58d17c3 | |||
d171b92989 | |||
172b1ed5e6 | |||
8ed26e11d3 | |||
d682909bab | |||
7f36481987 | |||
16c64762b7 | |||
d8aa040f75 | |||
37522cf65c | |||
ea06e9f217 | |||
9aa567b276 | |||
026e5a151e |
51 changed files with 2817 additions and 1149 deletions
.github/workflows
CHANGELOG.mdREADME.mddocs
md
assets
elliehub-1.pngelliehub-2.pngelliehub-3.pngelliehub-4.pngelliehub-5.pngelliehub-6.pngfavicon.pngpatreon.pngpaypal.png
creds-guide.mddonate.mdguides
index.mdmarmalade
snippets
src
EllieBot.VotesApi/Common
EllieBot
Db/Extensions
EllieBot.csprojModules
Administration/Self
Expressions
Gambling
Games
Help
Searches/Feeds
Utility/UserRole
Xp
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
|
295
CHANGELOG.md
295
CHANGELOG.md
|
@ -2,11 +2,49 @@
|
|||
|
||||
*a,c,f,r,o*
|
||||
|
||||
## [6.1.1] - 02.04.2025
|
||||
|
||||
## [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
|
||||
|
||||
- Bot will no longer fail to startup if ownerids are wrong
|
||||
|
||||
## [6.1.2] - 03.04.2025
|
||||
|
||||
### Fixed
|
||||
- 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
|
||||
|
@ -97,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
|
||||
|
||||
|
@ -152,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
|
||||
|
||||
|
@ -220,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
|
||||
|
@ -322,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
|
||||
|
@ -341,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
|
||||
|
@ -355,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
|
||||
|
@ -395,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
|
||||
|
@ -514,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
|
||||
|
||||
|
@ -551,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`
|
||||
|
@ -572,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
|
||||
|
||||
|
@ -648,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...)
|
||||
|
||||
|
@ -660,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
|
||||
|
||||
|
@ -728,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
|
||||
|
||||
|
@ -745,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:
|
||||
|
@ -772,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
|
||||
|
||||
|
@ -812,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
|
||||
|
||||
|
@ -851,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
|
||||
|
||||
|
|
BIN
docs/md/assets/elliehub-1.png
Normal file
BIN
docs/md/assets/elliehub-1.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 52 KiB |
BIN
docs/md/assets/elliehub-2.png
Normal file
BIN
docs/md/assets/elliehub-2.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 58 KiB |
BIN
docs/md/assets/elliehub-3.png
Normal file
BIN
docs/md/assets/elliehub-3.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 56 KiB |
BIN
docs/md/assets/elliehub-4.png
Normal file
BIN
docs/md/assets/elliehub-4.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 15 KiB |
BIN
docs/md/assets/elliehub-5.png
Normal file
BIN
docs/md/assets/elliehub-5.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 1.2 KiB |
BIN
docs/md/assets/elliehub-6.png
Normal file
BIN
docs/md/assets/elliehub-6.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 1,014 B |
BIN
docs/md/assets/favicon.png
Normal file
BIN
docs/md/assets/favicon.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 273 KiB |
BIN
docs/md/assets/patreon.png
Normal file
BIN
docs/md/assets/patreon.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 28 KiB |
BIN
docs/md/assets/paypal.png
Normal file
BIN
docs/md/assets/paypal.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 27 KiB |
32
docs/md/creds-guide.md
Normal file
32
docs/md/creds-guide.md
Normal file
|
@ -0,0 +1,32 @@
|
|||
## Creating your own Discord bot
|
||||
|
||||
This guide will show you how to create your own discord bot, invite it to your server, and obtain the credentials needed to run it.
|
||||
|
||||
1. Go to [the Discord developer application page][Discord].
|
||||
2. Log in with your Discord account.
|
||||
3. Click **New Application**.
|
||||
4. Fill out the `Name` field however you like, accept the terms, and confirm.
|
||||
5. Go to the **Bot** tab on the left sidebar.
|
||||
6. Click on the `Add a Bot` button and confirm that you do want to add a bot to this app.
|
||||
7. **Optional:** Add bot's avatar and description.
|
||||
8. Copy your Token to `creds.yml` as shown above.
|
||||
9. Scroll down to the **`Privileged Gateway Intents`** section
|
||||
- You MUST enable the following:
|
||||
- **PRESENCE INTENT**
|
||||
- **SERVER MEMBERS INTENT**
|
||||
- **MESSAGE CONTENT INTENT**
|
||||
|
||||
### Inviting your bot to your server
|
||||
|
||||

|
||||
|
||||
- On the **General Information** tab, copy your `Application ID` from your [applications page][Discord].
|
||||
- Replace the `YOUR_CLIENT_ID_HERE` in this link:
|
||||
`https://discord.com/oauth2/authorize?client_id=YOUR_CLIENT_ID_HERE&scope=bot&permissions=66186303` with your `Client ID`
|
||||
- The link should now look something like this:
|
||||
`https://discord.com/oauth2/authorize?client_id=123123123123&scope=bot&permissions=66186303`
|
||||
- Access that newly created link, pick your Discord server, click `Authorize` and confirm with the captcha at the end
|
||||
- The bot should now be in your server
|
||||
|
||||
|
||||
[Discord]: https://discord.com/developers/applications/me
|
37
docs/md/donate.md
Normal file
37
docs/md/donate.md
Normal file
|
@ -0,0 +1,37 @@
|
|||
# Donate
|
||||
|
||||
Ellie is an [open-source project][toastielab], and we rely on your help to develop the bot, pay hosting fees, maintain our website and more.
|
||||
Donations go a long way in helping us keep the project alive, and we appreciate every single one of them.
|
||||
|
||||
## Perks
|
||||
|
||||
Donating to us also gives you the following benefits:
|
||||
|
||||
- A hoisted **Patron** role in [Ellie Discord server][discord-server]
|
||||
- Access to exclusive **#donator-general** text and voice channels
|
||||
- **3000 candy** on the public bot per dollar donated (after fees)
|
||||
|
||||
## Patreon
|
||||
|
||||
You can set up a monthly pledge on [Patreon][patreon] and support the project's growth, and also get candy rewards for every month you donate!
|
||||
|
||||
!!! Note
|
||||
Connect your Discord account on Patreon to receive your candy automatically
|
||||
|
||||
[![img][patreon-button]][patreon]
|
||||
|
||||
## PayPal
|
||||
|
||||
You can also donate to us through [PayPal][paypal] for one-time donations using the button below, or by donating to `toastie@dragonschildstudios.com`.
|
||||
|
||||
!!! Note
|
||||
Mention your Discord username or user id in the payment note to receive candy rewards.
|
||||
|
||||
[![img][paypal-button]][paypal]
|
||||
|
||||
[toastielab]: https://toastielab.dev/EllieBotDevs/elliebot
|
||||
[discord-server]: https://discord.nadeko.bot/
|
||||
[patreon]: https://www.patreon.com/elliebot
|
||||
[patreon-button]: ./assets/patreon.png
|
||||
[paypal]: https://paypal.me/toastie_t0ast
|
||||
[paypal-button]: ./assets/paypal.png
|
235
docs/md/guides/cli-guide.md
Normal file
235
docs/md/guides/cli-guide.md
Normal file
|
@ -0,0 +1,235 @@
|
|||
# EllieBot CLI Guide (via Bash Installer)
|
||||
|
||||
### Supported Operating Systems
|
||||
|
||||
--8<-- "md/snippets/supported-platforms.md:linux"
|
||||
--8<-- "md/snippets/supported-platforms.md:macos"
|
||||
|
||||
### Prerequisites
|
||||
|
||||
macOS:
|
||||
|
||||
- [Homebrew](https://brew.sh/)
|
||||
- [Curl](#__tabbed_1_5)
|
||||
|
||||
Linux:
|
||||
|
||||
- [Curl](#__tabbed_1_1)
|
||||
|
||||
---
|
||||
|
||||
??? note "24/7 Up-time via VPS (Digital Ocean Guide)"
|
||||
--8<-- "md/guides/vps-linux-guide.md"
|
||||
|
||||
??? note "Creating a Discord Bot & Getting Credentials"
|
||||
--8<-- "md/creds-guide.md"
|
||||
|
||||
---
|
||||
|
||||
## Installation Instructions
|
||||
|
||||
!!! failure
|
||||
This script is broken and should no be used. please use [Desktop Guide](../guides/desktop-guide.md)
|
||||
|
||||
Open Terminal (if you're on an installation with a window manager) and navigate to the location where you want to install the bot (for example `cd ~`)
|
||||
|
||||
1. First make sure that curl is installed
|
||||
|
||||
/// tab | Ubuntu | Debian | Mint
|
||||
|
||||
```bash
|
||||
sudo apt install curl
|
||||
```
|
||||
|
||||
///
|
||||
/// tab | Rocky | Alma | Fedora
|
||||
|
||||
```bash
|
||||
sudo dnf install curl
|
||||
```
|
||||
|
||||
///
|
||||
/// tab | openSUSE
|
||||
|
||||
```bash
|
||||
sudo zypper install curl
|
||||
```
|
||||
|
||||
///
|
||||
/// tab | Arch | Artix
|
||||
|
||||
```bash
|
||||
sudo pacman -S curl
|
||||
```
|
||||
|
||||
///
|
||||
/// tab | macOS
|
||||
|
||||
```bash
|
||||
brew install curl
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
2. Download and run the **new** installer script
|
||||
``` sh
|
||||
cd ~
|
||||
curl -L -o e-install.sh https://toastielab.dev/EllieBotDevs/ellie-bash-installer/raw/branch/v6/e-install.sh
|
||||
bash e-install.sh
|
||||
```
|
||||
3. Install the bot (type `1` and press enter)
|
||||
4. Edit creds (type `3` and press enter)
|
||||
- *ALTERNATIVELY*, you can exit the installer (option `6`) and edit `ellie/creds.yml` file yourself
|
||||
5. Follow the instruction [below](#creating-your-own-discord-bot) to create your own Discord bot and obtain the credentials needed to run it.
|
||||
- After you're done, you can close nano (and save the file) by inputting, in order:
|
||||
- `CTRL` + `X`
|
||||
- `Y`
|
||||
- `Enter`
|
||||
6. Run the installer script again
|
||||
- `bash e-install.sh`
|
||||
7. Run the bot (type `3` and press enter)
|
||||
8. Done!
|
||||
|
||||
## Update Instructions
|
||||
|
||||
1. ⚠ Stop the bot ⚠
|
||||
2. Navigate to your bot's folder, we'll use home directory as an example
|
||||
- `cd ~`
|
||||
3. Simply re-install the bot with a newer version by running the installer script
|
||||
- `curl -L -o e-install.sh https://toastielab.dev/EllieBotDevs/ellie-bash-installer/raw/branch/v6/e-install.sh && bash e-install.sh`
|
||||
4. Select option 1, and select a NEWER version
|
||||
|
||||
## Running Ellie
|
||||
|
||||
There are two main methods to run EllieBot: using `tmux` (macOS and Linux) or using `systemd` with a script (Linux only).
|
||||
|
||||
/// tab | Tmux (Preferred Method)
|
||||
|
||||
Using `tmux` is the simplest method, and is therefore recommended for most users.
|
||||
|
||||
!!! warning
|
||||
Before proceeding, make sure your bot is not currently running by either running `.die` in your Discord server or exiting the process with `Ctrl+C`.
|
||||
|
||||
1. Access the directory where `e-install.sh` and `ellie` is located.
|
||||
2. Create a new tmux session: `tmux new -s ellie`
|
||||
- The above command will create a new session named **ellie**. You may replace **ellie** with any name you prefer.
|
||||
3. Run the installer: `bash e-install.sh`
|
||||
4. Start the bot by typing `3` and pressing `Enter`.
|
||||
5. Detach from the tmux session, allowing the bot to run in the background:
|
||||
- Press `Ctrl` + `B`
|
||||
- Then press `D`
|
||||
|
||||
Now check your Discord server, the bot should be online. Ellie should now be running in the background of your system.
|
||||
|
||||
To re-open the tmux session to either update, restart, or whatever, execute `tmux a -t ellie`. *(Make sure to replace "ellie" with your session name. If you didn't change it, leave it as it is.)*
|
||||
|
||||
///
|
||||
/// tab | Systemd
|
||||
|
||||
!!! note
|
||||
Systemd is only available on Linux. macOS utilizes Launchd, which is not covered in this guide. If you're on macOS, please use the `tmux` method, or use [EllieHub](desktop-guide.md) to run EllieBot.
|
||||
|
||||
This method is a bit more complex and involved, but comes with the added benefit of better error logging and control over what happens before and after the startup of Ellie.
|
||||
|
||||
1. Access the directory where `e-install.sh` and `ellie` is located.
|
||||
2. Use the following command to create a service that will be used to execute `EllieRun.bash`:
|
||||
```bash
|
||||
echo "[Unit]
|
||||
Description=EllieBot service
|
||||
After=network.target
|
||||
StartLimitIntervalSec=60
|
||||
StartLimitBurst=2
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=$USER
|
||||
WorkingDirectory=$PWD
|
||||
ExecStart=/bin/bash EllieRun.bash
|
||||
#ExecStart=./ellie/EllieBot
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=EllieBot
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target" | sudo tee /etc/systemd/system/ellie.service
|
||||
```
|
||||
3. Make the new service available: `sudo systemctl daemon-reload`
|
||||
4. Use the following command to create a script that will be used to start Ellie:
|
||||
```bash
|
||||
cat <<EOF > EllieRun.bash
|
||||
#!/bin/bash
|
||||
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
|
||||
is_python3_installed=\$(command -v python3 &>/dev/null && echo true || echo false)
|
||||
is_yt_dlp_installed=\$(command -v yt-dlp &>/dev/null && echo true || echo false)
|
||||
|
||||
[[ \$is_python3_installed == true ]] \\
|
||||
&& echo "[INFO] python3 path: \$(which python3)" \\
|
||||
&& echo "[INFO] python3 version: \$(python3 --version)"
|
||||
[[ \$is_yt_dlp_installed == true ]] \\
|
||||
&& echo "[INFO] yt-dlp path: \$(which yt-dlp)"
|
||||
|
||||
echo "[INFO] Running EllieBot in the background with auto restart"
|
||||
if [[ \$is_yt_dlp_installed == true ]]; then
|
||||
yt-dlp -U || echo "[ERROR] Failed to update 'yt-dlp'" >&2
|
||||
fi
|
||||
|
||||
echo "[INFO] Starting EllieBot..."
|
||||
|
||||
while true; do
|
||||
if [[ -d $PWD/ellie ]]; then
|
||||
cd "$PWD/ellie" || {
|
||||
echo "[ERROR] Failed to change working directory to '$PWD/ellie'" >&2
|
||||
echo "[INFO] Exiting..."
|
||||
exit 1
|
||||
}
|
||||
else
|
||||
echo "[WARN] '$PWD/ellie' doesn't exist" >&2
|
||||
echo "[INFO] Exiting..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
./EllieBot || {
|
||||
echo "[ERROR] An error occurred when trying to start EllieBot" >&2
|
||||
echo "[INFO] Exiting..."
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "[INFO] Waiting 5 seconds..."
|
||||
sleep 5
|
||||
if [[ \$is_yt_dlp_installed == true ]]; then
|
||||
yt-dlp -U || echo "[ERROR] Failed to update 'yt-dlp'" >&2
|
||||
fi
|
||||
echo "[INFO] Restarting EllieBot..."
|
||||
done
|
||||
|
||||
echo "[INFO] Stopping EllieBot..."
|
||||
EOF
|
||||
```
|
||||
|
||||
With everything set up, you can run EllieBot in one of three modes:
|
||||
|
||||
1. **Auto-Restart Mode**: EllieBot will restart automatically if you restart it via the `.die` command.
|
||||
- To enable this mode, start the service: `sudo systemctl start ellie`
|
||||
2. **Auto-Restart on Reboot Mode**: In addition to auto-restarting after `.die`, EllieBot will also start automatically on system reboot.
|
||||
- To enable this mode, run:
|
||||
```bash
|
||||
sudo systemctl enable ellie
|
||||
sudo systemctl start ellie
|
||||
```
|
||||
3. **Standard Mode**: EllieBot will stop completely when you use `.die`, without restarting automatically.
|
||||
- To switch to this mode:
|
||||
1. Stop the service: `sudo systemctl stop ellie`
|
||||
2. Edit the service file: `sudo <editor> /etc/systemd/system/ellie.service`
|
||||
3. Modify the `ExecStart` line:
|
||||
- **Comment out**: `ExecStart=/bin/bash EllieRun.bash`
|
||||
- **Uncomment**: `#ExecStart=./ellie/EllieBot`
|
||||
4. Save and exit the editor.
|
||||
5. Reload systemd: `sudo systemctl daemon-reload`
|
||||
6. Disable automatic startup: `sudo systemctl disable ellie`
|
||||
7. Start EllieBot manually: `sudo systemctl start ellie`
|
||||
|
||||
///
|
53
docs/md/guides/desktop-guide.md
Normal file
53
docs/md/guides/desktop-guide.md
Normal file
|
@ -0,0 +1,53 @@
|
|||
# EllieBot Desktop Guide (via EllieHub)
|
||||
|
||||
### Supported Operating Systems
|
||||
|
||||
--8<-- "md/snippets/supported-platforms.md"
|
||||
|
||||
---
|
||||
|
||||
??? note "Creating a Discord Bot & Getting Credentials"
|
||||
--8<-- "md/creds-guide.md"
|
||||
|
||||
---
|
||||
|
||||
## Setup
|
||||
|
||||
1. Download and run [elliehub](https://toastielab.dev/EllieBotDevs/EllieHub/releases/latest).
|
||||
|
||||

|
||||
|
||||
2. Click the plus button to add a new bot
|
||||
|
||||

|
||||
|
||||
3. If you want to use the music module, click on the settings icon then click **`Install`** next to `ffmpeg` and `yt-dlp` near the top of the setting tab.
|
||||
4. Click on the newly created bot
|
||||
|
||||
5. Click on **Install** located above the live console (This may take a bit)
|
||||
|
||||

|
||||

|
||||
|
||||
6. When installation is finished, click on **`CREDS`** (`1`) above the **`RUN`** (`3`) button on the lower left
|
||||
- **`2`** simply opens your bot's data folder.
|
||||
7. Paste in your **BOT TOKEN** previously obtained
|
||||
|
||||
## Starting EllieBot
|
||||
|
||||
- Either click on **`RUN`** button in the updater or run the bot via its desktop shortcut.
|
||||
|
||||
## Updating EllieBot
|
||||
|
||||
!!! warning "IMPORTANT"
|
||||
|
||||
- Make sure Ellie is closed and not running
|
||||
- Run `.die` in a connected server to make sure.
|
||||
- Make sure you don't have `data` folder, bot folder, or any other bot file open in any program, as the updater will fail to replace your version
|
||||
|
||||
1. Run `elliehub` if not already running
|
||||
2. Click on your bot
|
||||
3. Click on **`Check for updates`**
|
||||
4. If updates are available, you will be able to click on the Update button
|
||||
5. Click `Update`
|
||||
6. Click `RUN` after it's done
|
72
docs/md/guides/docker-guide.md
Normal file
72
docs/md/guides/docker-guide.md
Normal file
|
@ -0,0 +1,72 @@
|
|||
# Docker Guide
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- [Docker Core Engine](https://docs.docker.com/engine/install/)
|
||||
- [Docker Compose](https://docs.docker.com/compose/install/) (optional, but recommended)
|
||||
|
||||
---
|
||||
|
||||
??? note "Creating a Discord Bot & Getting Credentials"
|
||||
--8<-- "md/creds-guide.md"
|
||||
|
||||
---
|
||||
|
||||
## Installing EllieBot with Docker
|
||||
|
||||
When deploying EllieBot with Docker, you have two options: using [Docker](#__tabbed_1_1) or [Docker Compose](#__tabbed_1_2). The following sections provide step-by-step instructions for both methods.
|
||||
|
||||
/// tab | Docker
|
||||
|
||||
### Deploying EllieBot with Docker
|
||||
|
||||
1. Move to a directory where you want your Elliebot's data folder to be (data folder will keep the database and config files) and create a data folder there.
|
||||
``` sh
|
||||
cd ~ && mkdir ellie && cd ellie && mkdir data
|
||||
```
|
||||
2. Mount the newly created empty data folder as a volume while starting your docker container. Replace YOUR_TOKEN_HERE with the bot token obtained from the creds guide above.
|
||||
``` sh
|
||||
docker run -d --name ellie toastielab.dev/elliebotdevs/elliebot:v6 -e bot_token=YOUR_TOKEN_HERE -v "./data:/app/data" && docker logs -f --tail 500 ellie
|
||||
```
|
||||
3. Enjoy! 🎉
|
||||
|
||||
### Updating your bot
|
||||
|
||||
If you want to update elliebot to the latest version, all you have to do is pull the latest image and re-run.
|
||||
|
||||
1. Pull the latest image
|
||||
``` sh
|
||||
docker pull toastielab.dev/elliebotdevs/elliebot:v6
|
||||
```
|
||||
2. Re-run your bot the same way you did before
|
||||
``` sh
|
||||
docker run -d --name ellie toastielab.dev/elliebotdevs/elliebot:v6 -e bot_token=YOUR_TOKEN_HERE -v "./data:/app/data" && docker logs -f --tail 500 ellie
|
||||
```
|
||||
3. Done! 🎉
|
||||
|
||||
///
|
||||
/// tab | Docker Compose
|
||||
|
||||
1. **Choose Your Workspace:** Select a directory where you'll set up your EllieBot stack. Use your terminal to navigate to this directory. For the purpose of this guide, we'll use `/opt/stacks/ellie/` as an example, but you can choose any directory that suits your needs.
|
||||
2. **Create a Docker Compose File:** In this directory, create a Docker Compose file named `docker-compose.yml`. You can use any text editor for this task. For instance, to use the `nano` editor, type `nano docker-compose.yml`.
|
||||
3. **Configure Your Docker Compose File:** Populate your Docker Compose file with the following configuration:
|
||||
``` yml
|
||||
services:
|
||||
ellie:
|
||||
image: toastielab.dev/elliebotdevs/elliebot:v6
|
||||
container_name: ellie
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
TZ: Europe/Rome # Modify this to your timezone
|
||||
bot_token: YOUR_TOKEN_HERE
|
||||
volumes:
|
||||
- /opt/stacks/ellie/data:/app/data
|
||||
networks: {}
|
||||
```
|
||||
|
||||
1. **Launch Your Bot:** Now, you're ready to run Docker Compose. Use the following command: `docker compose up -d`.
|
||||
2. **Navigate to Your Directory:** Use `cd /opt/stacks/ellie/` to go to the directory containing your Docker Compose file.
|
||||
3. **Pull the Latest Images:** Use `docker compose pull` to fetch the latest images.
|
||||
4. **Restart Your Containers:** Use `docker compose up -d` to restart the containers.
|
||||
|
||||
///
|
66
docs/md/guides/source-guide.md
Normal file
66
docs/md/guides/source-guide.md
Normal file
|
@ -0,0 +1,66 @@
|
|||
# Setting Up EllieBot on Windows from source
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Windows 10 or later (64-bit)
|
||||
- [.net 8 sdk](https://dotnet.microsoft.com/download/dotnet/8.0)
|
||||
- If you want ellie to play music: [Visual C++ 2010 (x86)] and [Visual C++ 2017 (x64)] (both are required, you may install them later)
|
||||
- [git](https://git-scm.com/downloads) - needed to clone the repository (you can also download the zip manually and extract it, but this guide assumes you're using git)
|
||||
- **Optional** Any code editor, for example [Visual Studio Code](https://code.visualstudio.com/Download)
|
||||
- You'll need to at least modify creds.yml, notepad is inadequate
|
||||
|
||||
---
|
||||
|
||||
??? note "Creating a Discord Bot & Getting Credentials"
|
||||
--8<-- "md/creds-guide.md"
|
||||
|
||||
---
|
||||
|
||||
## Installation Instructions
|
||||
|
||||
Open PowerShell (press windows button on your keyboard and type powershell, it should show up; alternatively, right click the start menu and select Windows PowerShell), and
|
||||
|
||||
1. Navigate to the location where you want to install the bot
|
||||
- for example, type `cd ~/Desktop/` and press enter
|
||||
2. `git clone https://toastielab.dev/EllieBotDevs/elliebot -b v6 --depth 1`
|
||||
3. `cd elliebot/src/EllieBot`
|
||||
4. `dotnet build -c Release`
|
||||
5. `cp data/creds_example.yml data/creds.yml`
|
||||
6. "You're done installing, you may now proceed to set up your bot's credentials by following the creds guide
|
||||
- Once done, come back here and run the last command
|
||||
6. Run the bot `dotnet EllieBot.dll`
|
||||
7. 🎉 Enjoy
|
||||
|
||||
## Update Instructions
|
||||
|
||||
Open PowerShell as described above and run the following commands:
|
||||
|
||||
1. Stop the bot
|
||||
- ⚠️ Make sure you don't have your database, credentials or any other elliebot folder open in some application, this might prevent some of the steps from executing successfully
|
||||
2. Navigate to your bot's folder, example:
|
||||
- `cd ~/Desktop/elliebot`
|
||||
3. Pull the new version, and make sure you're on the v6 branch
|
||||
- `git pull`
|
||||
- ⚠️ IF this fails, you may want to `git stash` or remove your code changes if you don't know how to resolve merge conflicts
|
||||
4. **Backup** old output in case your data is overwritten
|
||||
- `cp -r -fo output/ output-old`
|
||||
5. Build the bot again
|
||||
- `dotnet run -c Release src/EllieBot/`
|
||||
6. Copy old data, and new strings
|
||||
- `cp -r -fo .\output-old\data\ .\output\`
|
||||
7. Run the bot
|
||||
- `cd output`
|
||||
- `dotnet EllieBot.dll`
|
||||
8. 🎉 Enjoy
|
||||
|
||||
## Music Prerequisites
|
||||
|
||||
In order to use music commands, you need ffmpeg and yt-dlp installed.
|
||||
|
||||
- [ffmpeg]
|
||||
- [yt-dlp]
|
||||
- Click to download the `yt-dlp.exe` file, then move `yt-dlp.exe` to a path that's in your PATH environment variable. If you don't know what that is, just move the `yt-dlp.exe` file to your elliebot's output folder.
|
||||
|
||||
[.net]: https://dotnet.microsoft.com/download/dotnet/8.0
|
||||
[ffmpeg]: https://github.com/GyanD/codexffmpeg/releases/latest
|
||||
[yt-dlp]: https://github.com/yt-dlp/yt-dlp/releases/latest
|
54
docs/md/guides/vps-linux-guide.md
Normal file
54
docs/md/guides/vps-linux-guide.md
Normal file
|
@ -0,0 +1,54 @@
|
|||
## Setting up Ellie on a Linux VPS (Digital Ocean Droplet)
|
||||
|
||||
If you want Ellie to play music for you 24/7 without having to hosting it on your PC and want to keep it cheap, reliable and convenient as possible, you can try Ellie on Linux Digital Ocean Droplet using the link [DigitalOcean](https://m.do.co/c/1bb1db830f41/) (by using this link, you will get **$10 credit** and also support Ellie)
|
||||
|
||||
To set up the VPS, please select the options below
|
||||
```
|
||||
These are the min requirements you must follow:
|
||||
|
||||
OS: Any between Ubuntu, Fedora, and Debian
|
||||
|
||||
Droplet Type: SHARED CPU | Basic
|
||||
|
||||
CPU options: Regular | Disk type: SSD
|
||||
6$/mo
|
||||
1 GB / 1 CPU
|
||||
25 GB SSD Disk
|
||||
1000 GB transfer
|
||||
|
||||
Note: You can select the cheapest option with 512 MB / 1 CPU but this has been a hit or miss.
|
||||
|
||||
Datacenter region: Choose one depending on where you are located.
|
||||
|
||||
Authentication: Password or SSH
|
||||
(Select SSH if you know what you are doing, otherwise choose password)
|
||||
Click create droplet
|
||||
```
|
||||
**Setting up EllieBot**
|
||||
Assuming you have followed the link above to setup an account and a Droplet with a 64-bit operational system on Digital Ocean and got the `IP address and root password (in your e-mail)` to login, it's time to get started.
|
||||
|
||||
**This section is only relevant to those who want to host Ellie on DigitalOcean. Go through this whole section before setting the bot up.**
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Download [PuTTY](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html)
|
||||
- Download [WinSCP](https://winscp.net/eng/download.php) *(optional)*
|
||||
- [Create and invite the bot](../creds-guide.md).
|
||||
|
||||
### Starting up
|
||||
|
||||
- **Open PuTTY** and paste or enter your `IP address` and then click **Open**.
|
||||
If you entered your Droplets IP address correctly, it should show **login as:** in a newly opened window.
|
||||
- Now for **login as:**, type `root` and press enter.
|
||||
- It should then ask for a password. Type the `root password` you have received in your e-mail address, then press Enter.
|
||||
|
||||
If you are running your droplet for the first time, it will most likely ask you to change your root password. To do that, copy the **password you've received by e-mail** and paste it on PuTTY.
|
||||
|
||||
- To paste, just right-click the window (it won't show any changes on the screen), then press Enter.
|
||||
- Type a **new password** somewhere, copy and paste it on PuTTY. Press Enter then paste it again.
|
||||
|
||||
**Save the new password somewhere safe.**
|
||||
|
||||
After that, your droplet should be ready for use.
|
||||
|
||||
[Setting up Ellie on a VPS (Digital Ocean)]: #setting-up-ellie-on-a-linux-vps-digital-ocean-droplet
|
40
docs/md/index.md
Normal file
40
docs/md/index.md
Normal file
|
@ -0,0 +1,40 @@
|
|||
# EllieBot Documentation
|
||||
|
||||
<!-- ![img][header] -->
|
||||
|
||||
## Inviting Ellie
|
||||
|
||||
There are two versions of Ellie, a public bot and a self-hostable bot.
|
||||
|
||||
To invite public Ellie to your server or to view its commands, click on the buttons below:
|
||||
|
||||
[:material-plus: Add Ellie to your server][invite]{ .md-button .md-button--primary }
|
||||
[:material-format-list-text: View commands][commands]{ .md-button }
|
||||
|
||||
To self-host your own Ellie, use the guides below:
|
||||
|
||||
- [:material-television-guide: Desktop guide (Windows/Linux/macOS)][desktop-guide]
|
||||
- [:material-console: CLI guide (Linux/macOS)][cli-guide]
|
||||
- [:material-docker: Docker guide][docker-guide]
|
||||
- [:material-source-branch: From source guide][from-source-guide]
|
||||
|
||||
In case you need any help, join our [Discord server][discord-server] where we may provide support.
|
||||
|
||||
## About Ellie
|
||||
|
||||
EllieBot is an [open source project][toastielab]. Any issues with the bot may be filed [here][issues].
|
||||
|
||||
If you're unsure whether something is an issue, ask in our support server first.
|
||||
|
||||
[Donations are welcome][donate], and we rely on your contributions to help keep the project alive.
|
||||
|
||||
[invite]: https://discordapp.com/oauth2/authorize?client_id=608119997713350679&scope=bot&permissions=66186303/
|
||||
[commands]: https://commands.elliebot.net/
|
||||
[desktop-guide]: ./guides/desktop-guide.md
|
||||
[cli-guide]: ./guides/cli-guide.md
|
||||
[docker-guide]: ./guides/docker-guide.md
|
||||
[from-source-guide]: ./guides/source-guide.md
|
||||
[discord-server]: https://discord.gg/etQdZxSyEH
|
||||
[toastielab]: https://toastielab.dev/EllieBotDevs/elliebot
|
||||
[issues]: https://toastielab.dev/EllieBotDevs/elliebot/issues
|
||||
[donate]: ./donate.md
|
18
docs/md/marmalade/canary-lifecycle.md
Normal file
18
docs/md/marmalade/canary-lifecycle.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Canary Lifecycle
|
||||
|
||||
*You can override several methods to hook into command handler's lifecycle.
|
||||
These methods start with `Exec*`*
|
||||
|
||||
|
||||
- `ExecOnMessageAsync` runs first right after any message was received
|
||||
- `ExecInputTransformAsync` runs after ExecOnMessageAsync and allows you to transform the message content before the bot looks for the matching command
|
||||
- `ExecPreCommandAsync` runs after a command was found but not executed, allowing you to potentially prevent command execution
|
||||
- `ExecPostCommandAsync` runs if the command was successfully executed
|
||||
- `ExecOnNoCommandAsync` runs instead of ExecPostCommandAsync if no command was found for a message
|
||||
|
||||
|
||||
*Besides that, canaries have 2 methods with which you can initialize and cleanup your canary*
|
||||
|
||||
|
||||
- `InitializeAsync` Runs when the marmalade which contains this canary is being loaded
|
||||
- `DisposeAsync` Runs when the marmalade which contains this canary is being unloaded
|
279
docs/md/marmalade/creating-a-marmalade.md
Normal file
279
docs/md/marmalade/creating-a-marmalade.md
Normal file
|
@ -0,0 +1,279 @@
|
|||
## Practice
|
||||
|
||||
This section will guide you through how to create a simple custom marmalade. You can find the entirety of this code hosted [here](https://toastielab.dev/ellie/example_marmalade)
|
||||
|
||||
#### Prerequisite
|
||||
- [.net8 sdk](https://dotnet.microsoft.com/en-us/download) installed
|
||||
- Optional: use [vscode](https://code.visualstudio.com/download) to write code
|
||||
|
||||
#### There are currently two ways of creating a marmalade and you can view both of them using the tabs below.
|
||||
|
||||
/// tab | Using our template
|
||||
|
||||
### Prerequisite
|
||||
- [git](https://git-scm.com/downloads) installed
|
||||
|
||||
### Guide
|
||||
|
||||
- Open your favorite terminal and navigate to a folder where you will keep your project .
|
||||
|
||||
- Install the ellie-marmalade template
|
||||
- `git clone https://toastielab.dev/EllieBotDevs/ellie-marmalade`
|
||||
- `cd ellie-marmalade`
|
||||
- `dotnet new install .\`
|
||||
|
||||
- Create a new folder and move into it
|
||||
- `mkdir example_marmalade `
|
||||
- `cd example_marmalade`
|
||||
|
||||
- Make a new Ellie Marmalade project
|
||||
- `dotnet new ellie-marmalade`
|
||||
- This can be any name you want you just have to specify `-n <any name you want here>` after the first part of the command
|
||||
- Here is an example `dotnet new ellie-marmalade -n my-cool-marmalade`
|
||||
- This will create a marmalade project with the name my-cool-marmalade
|
||||
|
||||
Now follow the instructions below and you should be good to go.
|
||||
///
|
||||
|
||||
/// tab | Building from scratch
|
||||
|
||||
### Guide
|
||||
|
||||
!!! info
|
||||
This requires writing a little bit of code but we will help you through it as much as we can.
|
||||
|
||||
|
||||
### Without any further issues we shall now begin
|
||||
|
||||
- Open your favorite terminal and navigate to a folder where you will keep your project .
|
||||
|
||||
- Create a new folder
|
||||
- `mkdir example_marmalade`
|
||||
- Create a new .net class library
|
||||
- `dotnet new classlib`
|
||||
- Open the current folder with your favorite editor/IDE. In this case we'll use VsCode
|
||||
- `code .`
|
||||
- Remove the `Class1.cs` file
|
||||
- Replace the contents of the `.csproj` file with the following contents
|
||||
```xml
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
|
||||
<!-- Reduces some boilerplate in your .cs files -->
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
||||
<!-- Use latest .net features -->
|
||||
<LangVersion>preview</LangVersion>
|
||||
<EnablePreviewFeatures>true</EnablePreviewFeatures>
|
||||
<GenerateRequiresPreviewFeaturesAttribute>true</GenerateRequiresPreviewFeaturesAttribute>
|
||||
|
||||
<!-- tell .net that this library will be used as a plugin -->
|
||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Base marmalade package. You MUST reference this in order to have a working marmalade -->
|
||||
<!-- Also, this package comes from Toastielab, which requires you to have a NuGet.Config file next to your .csproj -->
|
||||
<PackageReference Include="Ellie.Marmalade" Version="6.*">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
<!-- Note: If you want to use EllieBot services etc... You will have to manually clone
|
||||
the https://toastielab.dev/EllieBotDevs/elliebot repo locally and reference the EllieBot.csproj because there is no EllieBot package atm.
|
||||
It is strongly recommended that you checkout a specific tag which matches your version of ellie,
|
||||
as there could be breaking changes even between minor versions of EllieBot.
|
||||
For example if you're running EllieBot 4.1.0 locally for which you want to create a marmalade for,
|
||||
you should do "git checkout 4.1.0" in your EllieBot solution and then reference the EllieBot.csproj
|
||||
-->
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Copy shortcut and full strings to output (if they exist) -->
|
||||
<ItemGroup>
|
||||
<None Update="res.yml;cmds.yml;strings/**">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
```
|
||||
- Create a `MyCanary.cs` file and add the following contents
|
||||
```cs
|
||||
using EllieBot.Marmalade;
|
||||
using Discord;
|
||||
|
||||
public sealed class MyCanary : Canary
|
||||
{
|
||||
[cmd]
|
||||
public async Task Hello(AnyContext ctx)
|
||||
{
|
||||
await ctx.Channel.SendMessageAsync($"Hello everyone!");
|
||||
}
|
||||
|
||||
[cmd]
|
||||
public async Task Hello(AnyContext ctx, IUser target)
|
||||
{
|
||||
await ctx.ConfirmLocalizedAsync("hello", target);
|
||||
}
|
||||
}
|
||||
```
|
||||
- Create `res.yml` and `cmds.yml` files with the following contents
|
||||
|
||||
`res.yml`
|
||||
```yml
|
||||
marmalade.description: "This is my marmalade's description"
|
||||
hello: "Hello {0}, from res.yml!"
|
||||
```
|
||||
|
||||
`cmds.yml`
|
||||
```yml
|
||||
hello:
|
||||
desc: "This is a basic hello command"
|
||||
args:
|
||||
- ""
|
||||
- "@Someone"
|
||||
```
|
||||
|
||||
- Add `NuGet.Config` file which will let you use the base Ellie.Marmalade package. This file should always look like this and you shouldn't change it
|
||||
|
||||
```xml
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
|
||||
<add key="toastielab.dev" value="https://toastielab.dev/api/packages/ellie/nuget/index.json" protocolVersion="3" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
```
|
||||
///
|
||||
|
||||
### Build it
|
||||
|
||||
- Build your Marmalade into a dll that Ellie can load. In your terminal, type:
|
||||
- `dotnet publish -o bin/marmalades/example_marmalade /p:DebugType=embedded`
|
||||
|
||||
- Done. You can now try it out in action.
|
||||
|
||||
### Try it out
|
||||
|
||||
- Copy the `bin/marmalades/example_marmalade` folder into your EllieBot's `data/marmalades/` folder. (EllieBot version 4.1.0+)
|
||||
|
||||
- Load it with `.maload example_marmalade`
|
||||
|
||||
- In the channel your bot can see, run the following commands to try it out
|
||||
- `.hello` and
|
||||
- `.hello @<someone>`
|
||||
|
||||
- Check its information with
|
||||
- `.mainfo example_marmalade`
|
||||
|
||||
- Unload it
|
||||
- `.maunload example_marmalade`
|
||||
|
||||
- 🎉 Congrats! You've just made your first marmalade! 🎉
|
||||
|
||||
## Theory
|
||||
|
||||
Marmalade system allows you to write independent marmalades (known as "modules", "cogs" or "plugins" in other software) which you can then load, unload and update at will without restarting the bot.
|
||||
|
||||
The marmalade base classes used for development are open source [here](https://toastielab.dev/EllieBotDevs/elliebot/src/branch/v5/src/Ellie.Marmalade) in case you need reference, as there is no generated documentation at the moment.
|
||||
|
||||
### Term list
|
||||
|
||||
#### Marmalade
|
||||
|
||||
- The project itself which compiles to a single `.dll` (and some optional auxiliary files), it can contain multiple [Canaries](#canary), [Services](#service), and [ParamParsers](#param-parser)
|
||||
|
||||
#### Canary
|
||||
|
||||
- A class which will be added as a single Module to EllieBot on load. It also acts as a [lifecycle handler](canary-lifecycle.md) and as a singleton service with the support for initialize and cleanup.
|
||||
- It can contain a Canary (called SubCanary) but only 1 level of nesting is supported (you can only have a canary contain a subcanary, but a subcanary can't contain any other canaries)
|
||||
- Canaries can have their own prefix
|
||||
- For example if you set this to 'test' then a command called 'cmd' will have to be invoked by using `.test cmd` instead of `.cmd`
|
||||
|
||||
#### Canary Command
|
||||
|
||||
- Acts as a normal command
|
||||
- Has context injected as a first argument which controls where the command can be executed
|
||||
- `AnyContext` the command can be executed in both DMs and Servers
|
||||
- `GuildContext` the command can only be executed in Servers
|
||||
- `DmContext` the command can only be executed in DMs
|
||||
- Support the usual features such as default values, leftover, params, etc.
|
||||
- It also supports dependency injection via `[inject]` attribute. These dependencies must come after the context and before any input parameters
|
||||
- Supports `ValueTask`, `Task`, `Task<T>` and `void` return types
|
||||
|
||||
#### Param Parser
|
||||
|
||||
- Allows custom parsing of command arguments into your own types.
|
||||
- Overriding existing parsers (for example for IGuildUser, etc...) can cause issues.
|
||||
|
||||
#### Service
|
||||
|
||||
- Usually not needed.
|
||||
- They are marked with a `[svc]` attribute, and offer a way to inject dependencies to different parts of your marmalade.
|
||||
- Transient and Singleton lifetimes are supported.
|
||||
|
||||
### Localization
|
||||
|
||||
Response and command strings can be kept in one of three different places based on whether you plan to allow support for localization
|
||||
|
||||
option 1) `res.yml` and `cmds.yml`
|
||||
|
||||
If you don't plan on having your app localized, but you just *may* in the future, you should keep your strings in the `res.yml` and `cmds.yml` file the root folder of your project, and they will be automatically copied to the output whenever you build your marmalade.
|
||||
|
||||
##### Example project folder structure:
|
||||
- uwu/
|
||||
- uwu.csproj
|
||||
- uwu.cs
|
||||
- res.yml
|
||||
- cmds.yml
|
||||
|
||||
##### Example output folder structure:
|
||||
- marmalades/uwu/
|
||||
- uwu.dll
|
||||
- res.yml
|
||||
- cmds.yml
|
||||
|
||||
option 2) `strings` folder
|
||||
|
||||
If you plan on having your app localized (or want to allow your consumers to easily add languages themselves), you should keep your response strings in the `strings/res/en-us.yml` and your command strings in `strings/cmds/en-us.yml` file. This will be your base file, and from there you can make support for additional languages, for example `strings/res/ru-ru.yml` and `strings/cmds/ru-ru.yml`
|
||||
|
||||
##### Example project folder structure:
|
||||
- uwu/
|
||||
- uwu.csproj
|
||||
- uwu.cs
|
||||
- strings/
|
||||
- res/
|
||||
- en-us.yml
|
||||
- cmds/
|
||||
- en-us.yml
|
||||
|
||||
##### Example output folder structure:
|
||||
- marmalades/uwu/
|
||||
- uwu.dll
|
||||
- strings/
|
||||
- res/
|
||||
- en-us.yml
|
||||
- cmds/
|
||||
- en-us.yml
|
||||
|
||||
option 3) In the code
|
||||
|
||||
If you don't want any auxiliary files, and you don't want to bother making new .yml files to keep your strings in, you can specify the command strings directly in the `[cmd]` attribute itself, and use non-localized methods for message sending in your commands.
|
||||
|
||||
If you update your response strings .yml file(s) while the marmalade is loaded and running, running `.stringsreload` will reload the responses without the need to reload the marmalade or restart the bot.
|
||||
|
||||
#### Bot marmalade config file
|
||||
|
||||
- Marmalade config is kept in `marmalades/marmalade.yml` file
|
||||
- At the moment this config only keeps track of which marmalades are currently loaded (they will also be always loaded at startup)
|
||||
- If a marmalade is causing issues and you're unable to unload it, you can remove it from the `loaded:` list in this config file and restart the bot. It won't be loaded next time the bot is started up
|
||||
|
||||
#### Unloadability issues
|
||||
|
||||
To make sure your marmalade can be properly unloaded/reloaded you must:
|
||||
|
||||
- Make sure that none of your types and objects are referenced by the Bot or Bot's services after the DisposeAsync is called on your Canary instances.
|
||||
|
||||
- Make sure that all of your commands execute quickly and don't have any long running tasks, as they will hold a reference to a type from your assembly
|
||||
|
||||
- If you are still having issues, you can always run `.maunload` followed by a bot restart, or if you want to find what is causing the marmalade unloadability issues, you can check the [microsoft's assembly unloadability debugging guide](https://docs.microsoft.com/en-us/dotnet/standard/assembly/unloadability)
|
31
docs/md/marmalade/getting-started.md
Normal file
31
docs/md/marmalade/getting-started.md
Normal file
|
@ -0,0 +1,31 @@
|
|||
## Getting Started
|
||||
|
||||
### What is the Marmalade system?
|
||||
|
||||
- It is a dynamic module/plugin/cog system for EllieBot introduced in **EllieBot 4.1.0**
|
||||
|
||||
- Allows developers to add custom functionality to Ellie without modifying the original code
|
||||
|
||||
- Allows for those custom features to be updated during bot runtime (if properly written), without the need for bot restart.
|
||||
|
||||
- They are added to `data/marmalades` folder and are loaded, unloaded and handled through discord commands.
|
||||
- `.maload` Loads the specified marmalade (see `.h .maload`)
|
||||
- `.maunload` Unloads the specified marmalade (see `.h .maunload`)
|
||||
- `.mainfo` Checks marmalades information (see `.h .mainfo`)
|
||||
- `.malist` Lists the available marmalades (see `.h .malist`)
|
||||
|
||||
### How to make one?
|
||||
|
||||
Marmalades are written in [C#](https://docs.microsoft.com/en-us/dotnet/csharp/tour-of-csharp/) programming language, so you will need at least low-intermediate knowledge of it in order to make a useful Marmalade.
|
||||
|
||||
Follow the [creating a marmalade guide](creating-a-marmalade.md)
|
||||
|
||||
### Where to get marmalades other people made?
|
||||
|
||||
⚠ *It is EXTREMELY, and I repeat **EXTREMELY** dangerous to run marmalades of strangers or people you don't FULLY trust.* ⚠
|
||||
|
||||
⚠ *It can not only lead to your bot being stolen, but it also puts your entire computer and personal files in jeopardy.* ⚠
|
||||
|
||||
**It is strongly recommended to run only the marmalades you yourself wrote, and only on a hosted VPS or dedicated server which ONLY hosts your bot, to minimize the potential damage caused by bad actors.**
|
||||
|
||||
No easy way at the moment, except asking in the `#dev-and-modding` chat in [Ellie's Home server](https://discord.gg/etQdZxSyEH)
|
18
docs/md/snippets/supported-platforms.md
Normal file
18
docs/md/snippets/supported-platforms.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
<!-- TODO: These should potentially be reformated to be more readable... -->
|
||||
--8<-- [start:windows]
|
||||
- **Windows 10** or later (64-bit)
|
||||
--8<-- [end:windows]
|
||||
--8<-- [start:linux]
|
||||
- **Ubuntu**: 20.04, 22.04, 24.04
|
||||
- **Mint**: 19, 20, 21
|
||||
- **Debian**: 10, 11, 12
|
||||
- **RockyLinux**: 8, 9
|
||||
- **AlmaLinux**: 8, 9
|
||||
- **openSUSE Leap**: 15.5, 15.6
|
||||
- **openSUSE Tumbleweed**
|
||||
- **Fedora**: 38, 39, 40, 41, 42
|
||||
- **Arch** & **Artix**
|
||||
--8<-- [end:linux]
|
||||
--8<-- [start:macos]
|
||||
- **macOS 13 (Ventura)** or later
|
||||
--8<-- [end:macos]
|
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
|
80
docs/mkdocs.yml
Normal file
80
docs/mkdocs.yml
Normal file
|
@ -0,0 +1,80 @@
|
|||
site_name: EllieBot docs
|
||||
site_url: https://docs.elliebot.net
|
||||
repo_url: 'https://toastielab.dev/EllieBotDevs/elliebot'
|
||||
site_author: Toastie_t0ast
|
||||
docs_dir: 'md'
|
||||
copyright: Copyright © 2018 - 2025 Toastie_t0ast & EllieBotDevs
|
||||
theme:
|
||||
name: material
|
||||
palette:
|
||||
- media: "(prefers-color-scheme: light)"
|
||||
scheme: default
|
||||
primary: indigo
|
||||
accent: blue
|
||||
toggle:
|
||||
icon: material/weather-sunny
|
||||
name: Switch to dark mode
|
||||
- media: "(prefers-color-scheme: dark)"
|
||||
scheme: slate
|
||||
primary: black
|
||||
accent: blue
|
||||
toggle:
|
||||
icon: material/weather-night
|
||||
name: Switch to light mode
|
||||
features:
|
||||
- navigation.instant
|
||||
- navigation.expand
|
||||
- navigation.top
|
||||
font:
|
||||
text: Source Sans 3
|
||||
code: Source Code Pro
|
||||
logo: assets/favicon.png
|
||||
favicon: assets/favicon.png
|
||||
icon:
|
||||
repo: material/git
|
||||
extra:
|
||||
homepage: https://elliebot.net
|
||||
plugins:
|
||||
- search
|
||||
- exclude:
|
||||
glob:
|
||||
- 'guides/vps-linux-guide.md'
|
||||
- 'snippets/supported-platforms.md'
|
||||
|
||||
markdown_extensions:
|
||||
- attr_list
|
||||
- codehilite:
|
||||
guess_lang: false
|
||||
- toc:
|
||||
permalink: true
|
||||
- pymdownx.betterem:
|
||||
smart_enable: all
|
||||
- admonition
|
||||
- pymdownx.inlinehilite
|
||||
- pymdownx.superfences
|
||||
- pymdownx.blocks.tab:
|
||||
alternate_style: true
|
||||
- pymdownx.snippets
|
||||
- pymdownx.details
|
||||
- pymdownx.emoji:
|
||||
emoji_index: !!python/name:material.extensions.emoji.twemoji
|
||||
emoji_generator: !!python/name:material.extensions.emoji.to_svg
|
||||
options:
|
||||
custom_icons:
|
||||
- overrides/.icons
|
||||
nav:
|
||||
- Home: index.md
|
||||
- Guides:
|
||||
- Desktop Guide: guides/desktop-guide.md
|
||||
- CLI Guide: guides/cli-guide.md
|
||||
- Docker Guide: guides/docker-guide.md
|
||||
- Source Guide: guides/source-guide.md
|
||||
- Commands:
|
||||
- Commands List: https://commands.elliebot.net
|
||||
- Features Explained:
|
||||
- Basic Creds: creds-guide.md
|
||||
- Marmalade System:
|
||||
- marmalade/getting-started.md
|
||||
- marmalade/creating-a-marmalade.md
|
||||
- marmalade/canary-lifecycle.md
|
||||
- Donate: donate.md
|
|
@ -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.1</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>
|
|
@ -81,13 +81,13 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, IEService
|
|||
await using var uow = _db.GetDbContext();
|
||||
|
||||
autoCommands = uow.Set<AutoCommand>()
|
||||
.AsNoTracking()
|
||||
.Where(x => x.Interval >= 5)
|
||||
.AsEnumerable()
|
||||
.GroupBy(x => x.GuildId)
|
||||
.ToDictionary(x => x.Key,
|
||||
y => y.ToDictionary(x => x.Id, TimerFromAutoCommand).ToConcurrent())
|
||||
.ToConcurrent();
|
||||
.AsNoTracking()
|
||||
.Where(x => x.Interval >= 5)
|
||||
.AsEnumerable()
|
||||
.GroupBy(x => x.GuildId)
|
||||
.ToDictionary(x => x.Key,
|
||||
y => y.ToDictionary(x => x.Id, TimerFromAutoCommand).ToConcurrent())
|
||||
.ToConcurrent();
|
||||
|
||||
var startupCommands = uow.Set<AutoCommand>().AsNoTracking().Where(x => x.Interval == 0);
|
||||
foreach (var cmd in startupCommands)
|
||||
|
@ -101,8 +101,10 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, IEService
|
|||
}
|
||||
}
|
||||
|
||||
if (_client.ShardId == 0)
|
||||
await LoadOwnerChannels();
|
||||
if (_client.ShardId != 0)
|
||||
return;
|
||||
|
||||
await LoadOwnerChannels();
|
||||
}
|
||||
|
||||
private Timer TimerFromAutoCommand(AutoCommand x)
|
||||
|
@ -165,19 +167,27 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, IEService
|
|||
|
||||
private async Task LoadOwnerChannels()
|
||||
{
|
||||
var channels = await _creds.OwnerIds.Select(id =>
|
||||
{
|
||||
var user = _client.GetUser(id);
|
||||
if (user is null)
|
||||
return Task.FromResult<IDMChannel>(null);
|
||||
var channels = await _creds.OwnerIds.Select(async id =>
|
||||
{
|
||||
var user = _client.GetUser(id);
|
||||
if (user is null)
|
||||
return null;
|
||||
|
||||
return user.CreateDMChannelAsync();
|
||||
})
|
||||
.WhenAll();
|
||||
try
|
||||
{
|
||||
return await user.CreateDMChannelAsync();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Log.Error("Unable to DM Owner {UserId} - please remove that id from the owner list", user.Id);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.WhenAll();
|
||||
|
||||
ownerChannels = channels.Where(x => x is not null)
|
||||
.ToDictionary(x => x.Recipient.Id, x => x)
|
||||
.ToImmutableDictionary();
|
||||
.ToDictionary(x => x.Recipient.Id, x => x)
|
||||
.ToImmutableDictionary();
|
||||
|
||||
if (!ownerChannels.Any())
|
||||
{
|
||||
|
@ -401,41 +411,41 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, IEService
|
|||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
var presentDbUsers = await ctx.GetTable<DiscordUser>()
|
||||
.Select(x => new
|
||||
{
|
||||
x.UserId,
|
||||
x.Username,
|
||||
})
|
||||
.Where(x => users.Select(y => y.Id).Contains(x.UserId))
|
||||
.ToArrayAsyncEF();
|
||||
.Select(x => new
|
||||
{
|
||||
x.UserId,
|
||||
x.Username,
|
||||
})
|
||||
.Where(x => users.Select(y => y.Id).Contains(x.UserId))
|
||||
.ToArrayAsyncEF();
|
||||
|
||||
var usersToAdd = users
|
||||
.Where(x => !presentDbUsers.Select(x => x.UserId).Contains(x.Id))
|
||||
.Select(x => new DiscordUser()
|
||||
{
|
||||
UserId = x.Id,
|
||||
AvatarId = x.AvatarId,
|
||||
Username = x.Username,
|
||||
});
|
||||
.Where(x => !presentDbUsers.Select(x => x.UserId).Contains(x.Id))
|
||||
.Select(x => new DiscordUser()
|
||||
{
|
||||
UserId = x.Id,
|
||||
AvatarId = x.AvatarId,
|
||||
Username = x.Username,
|
||||
});
|
||||
|
||||
var added = (await ctx.BulkCopyAsync(usersToAdd)).RowsCopied;
|
||||
var toUpdateUserIds = presentDbUsers
|
||||
.Where(x => x.Username.StartsWith("??"))
|
||||
.Select(x => x.UserId)
|
||||
.ToArray();
|
||||
.Where(x => x.Username.StartsWith("??"))
|
||||
.Select(x => x.UserId)
|
||||
.ToArray();
|
||||
|
||||
foreach (var user in users.Where(x => toUpdateUserIds.Contains(x.Id)))
|
||||
{
|
||||
await ctx.GetTable<DiscordUser>()
|
||||
.Where(x => x.UserId == user.Id)
|
||||
.UpdateAsync(x => new DiscordUser()
|
||||
{
|
||||
Username = user.Username,
|
||||
.Where(x => x.UserId == user.Id)
|
||||
.UpdateAsync(x => new DiscordUser()
|
||||
{
|
||||
Username = user.Username,
|
||||
|
||||
// .award tends to set AvatarId and DateAdded to NULL, so account for that.
|
||||
AvatarId = user.AvatarId,
|
||||
DateAdded = x.DateAdded ?? DateTime.UtcNow
|
||||
});
|
||||
// .award tends to set AvatarId and DateAdded to NULL, so account for that.
|
||||
AvatarId = user.AvatarId,
|
||||
DateAdded = x.DateAdded ?? DateTime.UtcNow
|
||||
});
|
||||
}
|
||||
|
||||
return (added, toUpdateUserIds.Length);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Globalization;
|
|||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using EllieBot.Common.ModuleBehaviors;
|
||||
using EllieBot.Common.TypeReaders;
|
||||
using EllieBot.Db.Models;
|
||||
using EllieBot.Modules.Gambling.Common;
|
||||
using EllieBot.Modules.Gambling.Common.Connect4;
|
||||
|
@ -208,8 +209,9 @@ public class GamblingService : IEService, IReadyExecutor
|
|||
private string N(long amount)
|
||||
=> CurrencyHelper.N(amount, CultureInfo.InvariantCulture, _gcs.Data.Currency.Sign);
|
||||
|
||||
public async Task<(long val, string msg)> GetAmountAndMessage(ulong userId, long originalAmount)
|
||||
public async Task<(long val, string msg)> GetAmountAndMessage(ulong userId, long baseAmount)
|
||||
{
|
||||
var totalAmount = baseAmount;
|
||||
var gcsData = _gcs.Data;
|
||||
var boostGuilds = gcsData.BoostBonus.GuildIds ?? [];
|
||||
var guildUsers = await boostGuilds
|
||||
|
@ -232,19 +234,19 @@ public class GamblingService : IEService, IReadyExecutor
|
|||
var booster = userInfo != default;
|
||||
|
||||
if (booster)
|
||||
originalAmount += gcsData.BoostBonus.BaseTimelyBonus;
|
||||
totalAmount += gcsData.BoostBonus.BaseTimelyBonus;
|
||||
|
||||
var hasCompletedDailies = await _quests.UserCompletedDailies(userId);
|
||||
|
||||
if (hasCompletedDailies)
|
||||
originalAmount = (long)(1.5 * originalAmount);
|
||||
totalAmount = (long)(1.5 * totalAmount);
|
||||
|
||||
var patron = await _ps.GetPatronAsync(userId);
|
||||
var percentBonus = (_ps.PercentBonus(patron) / 100f);
|
||||
|
||||
originalAmount += (long)(originalAmount * percentBonus);
|
||||
totalAmount += (long)(totalAmount * percentBonus);
|
||||
|
||||
var msg = $"**{N(originalAmount)}** base reward\n\n";
|
||||
var msg = $"**{N(baseAmount)}** base reward\n\n";
|
||||
if (boostGuilds.Count > 0)
|
||||
{
|
||||
if (booster)
|
||||
|
@ -261,7 +263,7 @@ public class GamblingService : IEService, IReadyExecutor
|
|||
else
|
||||
msg += $"❌ *+0 bonus for the [Patreon](https://patreon.com/elliebot) pledge*\n";
|
||||
}
|
||||
|
||||
|
||||
if (hasCompletedDailies)
|
||||
{
|
||||
msg += $"✅ *+50% bonus for completing daily quests*\n";
|
||||
|
@ -271,6 +273,7 @@ public class GamblingService : IEService, IReadyExecutor
|
|||
msg += $"❌ *+0 bonus for completing daily quests*\n";
|
||||
}
|
||||
|
||||
return (originalAmount, msg);
|
||||
|
||||
return (totalAmount, msg);
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
|
|
|
@ -10,7 +10,7 @@ public partial class Searches
|
|||
public partial class FeedCommands : EllieModule<FeedsService>
|
||||
{
|
||||
private static readonly Regex _ytChannelRegex =
|
||||
new(@"youtube\.com\/(?:c\/|channel\/|user\/)?(?<channelid>[a-zA-Z0-9\-_]{1,})");
|
||||
MyRegex();
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
|
@ -53,6 +53,8 @@ public partial class Searches
|
|||
[Priority(1)]
|
||||
public async Task Feed(string url, ITextChannel? channel = null, [Leftover] string? message = null)
|
||||
{
|
||||
await ctx.Channel.TriggerTypingAsync();
|
||||
|
||||
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri)
|
||||
|| (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps))
|
||||
{
|
||||
|
@ -61,7 +63,7 @@ public partial class Searches
|
|||
}
|
||||
|
||||
channel ??= (ITextChannel)ctx.Channel;
|
||||
|
||||
|
||||
if (!((IGuildUser)ctx.User).GetPermissions(channel).MentionEveryone)
|
||||
message = message?.SanitizeAllMentions();
|
||||
|
||||
|
@ -79,7 +81,7 @@ public partial class Searches
|
|||
if (ctx.User is not IGuildUser gu || !gu.GuildPermissions.Administrator)
|
||||
message = message?.SanitizeMentions(true);
|
||||
|
||||
var result = _service.AddFeed(ctx.Guild.Id, channel.Id, url, message);
|
||||
var result = await _service.AddFeedAsync(ctx.Guild.Id, channel.Id, url, message);
|
||||
if (result == FeedAddResult.Success)
|
||||
{
|
||||
await Response().Confirm(strs.feed_added).SendAsync();
|
||||
|
@ -117,32 +119,35 @@ public partial class Searches
|
|||
{
|
||||
if (--page < 0)
|
||||
return;
|
||||
|
||||
|
||||
var feeds = _service.GetFeeds(ctx.Guild.Id);
|
||||
|
||||
if (!feeds.Any())
|
||||
{
|
||||
await Response()
|
||||
.Embed(CreateEmbed().WithOkColor().WithDescription(GetText(strs.feed_no_feed)))
|
||||
.SendAsync();
|
||||
.Embed(CreateEmbed().WithOkColor().WithDescription(GetText(strs.feed_no_feed)))
|
||||
.SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
await Response()
|
||||
.Paginated()
|
||||
.Items(feeds)
|
||||
.PageSize(10)
|
||||
.CurrentPage(page)
|
||||
.Page((items, cur) =>
|
||||
{
|
||||
var embed = CreateEmbed().WithOkColor();
|
||||
var i = 0;
|
||||
var fs = string.Join("\n",
|
||||
items.Select(x => $"`{(cur * 10) + ++i}.` <#{x.ChannelId}> {x.Url}"));
|
||||
.Paginated()
|
||||
.Items(feeds)
|
||||
.PageSize(10)
|
||||
.CurrentPage(page)
|
||||
.Page((items, cur) =>
|
||||
{
|
||||
var embed = CreateEmbed().WithOkColor();
|
||||
var i = 0;
|
||||
var fs = string.Join("\n",
|
||||
items.Select(x => $"`{(cur * 10) + ++i}.` <#{x.ChannelId}> {x.Url}"));
|
||||
|
||||
return embed.WithDescription(fs);
|
||||
})
|
||||
.SendAsync();
|
||||
return embed.WithDescription(fs);
|
||||
})
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"youtube\.com\/(?:c\/|channel\/|user\/)?(?<channelid>[a-zA-Z0-9\-_]{1,})")]
|
||||
private static partial Regex MyRegex();
|
||||
}
|
||||
}
|
|
@ -40,13 +40,13 @@ public class FeedsService : IEService, IReadyExecutor
|
|||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var subs = await uow.Set<FeedSub>()
|
||||
.AsQueryable()
|
||||
.Where(x => Queries.GuildOnShard(x.GuildId, _shardData.TotalShards, _shardData.ShardId))
|
||||
.ToListAsyncLinqToDB();
|
||||
.AsQueryable()
|
||||
.Where(x => Queries.GuildOnShard(x.GuildId, _shardData.TotalShards, _shardData.ShardId))
|
||||
.ToListAsyncLinqToDB();
|
||||
_subs = subs
|
||||
.GroupBy(x => x.Url.ToLower())
|
||||
.ToDictionary(x => x.Key, x => x.ToList())
|
||||
.ToConcurrent();
|
||||
.GroupBy(x => x.Url.ToLower())
|
||||
.ToDictionary(x => x.Key, x => x.ToList())
|
||||
.ToConcurrent();
|
||||
}
|
||||
|
||||
await TrackFeeds();
|
||||
|
@ -66,7 +66,7 @@ public class FeedsService : IEService, IReadyExecutor
|
|||
// remove from db
|
||||
await using var ctx = _db.GetDbContext();
|
||||
await ctx.GetTable<FeedSub>()
|
||||
.DeleteAsync(x => ids.Contains(x.Id));
|
||||
.DeleteAsync(x => ids.Contains(x.Id));
|
||||
|
||||
// remove from the local cache
|
||||
_subs.TryRemove(url, out _);
|
||||
|
@ -163,12 +163,12 @@ public class FeedsService : IEService, IReadyExecutor
|
|||
if (!gotImage && feedItem.SpecificItem is AtomFeedItem afi)
|
||||
{
|
||||
var previewElement = afi.Element.Elements()
|
||||
.FirstOrDefault(x => x.Name.LocalName == "preview");
|
||||
.FirstOrDefault(x => x.Name.LocalName == "preview");
|
||||
|
||||
if (previewElement is null)
|
||||
{
|
||||
previewElement = afi.Element.Elements()
|
||||
.FirstOrDefault(x => x.Name.LocalName == "thumbnail");
|
||||
.FirstOrDefault(x => x.Name.LocalName == "thumbnail");
|
||||
}
|
||||
|
||||
if (previewElement is not null)
|
||||
|
@ -201,11 +201,11 @@ public class FeedsService : IEService, IReadyExecutor
|
|||
continue;
|
||||
|
||||
var sendTask = _sender.Response(ch)
|
||||
.Embed(embed)
|
||||
.Text(string.IsNullOrWhiteSpace(val.Message)
|
||||
? string.Empty
|
||||
: val.Message)
|
||||
.SendAsync();
|
||||
.Embed(embed)
|
||||
.Text(string.IsNullOrWhiteSpace(val.Message)
|
||||
? string.Empty
|
||||
: val.Message)
|
||||
.SendAsync();
|
||||
tasks.Add(sendTask);
|
||||
}
|
||||
|
||||
|
@ -236,12 +236,14 @@ public class FeedsService : IEService, IReadyExecutor
|
|||
using var uow = _db.GetDbContext();
|
||||
|
||||
return uow.GetTable<FeedSub>()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.OrderBy(x => x.Id)
|
||||
.ToList();
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.OrderBy(x => x.Id)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public FeedAddResult AddFeed(
|
||||
private const int MAX_FEEDS = 10;
|
||||
|
||||
public async Task<FeedAddResult> AddFeedAsync(
|
||||
ulong guildId,
|
||||
ulong channelId,
|
||||
string rssFeed,
|
||||
|
@ -249,25 +251,24 @@ public class FeedsService : IEService, IReadyExecutor
|
|||
{
|
||||
ArgumentNullException.ThrowIfNull(rssFeed, nameof(rssFeed));
|
||||
|
||||
var fs = new FeedSub
|
||||
{
|
||||
ChannelId = channelId,
|
||||
Url = rssFeed.Trim(),
|
||||
Message = message
|
||||
};
|
||||
|
||||
using var uow = _db.GetDbContext();
|
||||
var feeds = uow.GetTable<FeedSub>()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.ToArray();
|
||||
|
||||
if (feeds.Any(x => x.Url.ToLower() == fs.Url.ToLower()))
|
||||
await using var uow = _db.GetDbContext();
|
||||
var feedUrl = rssFeed.Trim();
|
||||
if (await uow.GetTable<FeedSub>().AnyAsyncLinqToDB(x => x.GuildId == guildId &&
|
||||
x.Url.ToLower() == feedUrl.ToLower()))
|
||||
return FeedAddResult.Duplicate;
|
||||
if (feeds.Length >= 10)
|
||||
|
||||
var count = await uow.GetTable<FeedSub>().CountAsyncLinqToDB(x => x.GuildId == guildId);
|
||||
if (count >= MAX_FEEDS)
|
||||
return FeedAddResult.LimitReached;
|
||||
|
||||
uow.Add(fs);
|
||||
uow.SaveChanges();
|
||||
var fs = await uow.GetTable<FeedSub>()
|
||||
.InsertWithOutputAsync(() => new FeedSub
|
||||
{
|
||||
GuildId = guildId,
|
||||
ChannelId = channelId,
|
||||
Url = feedUrl,
|
||||
Message = message,
|
||||
});
|
||||
|
||||
_subs.AddOrUpdate(fs.Url.ToLower(),
|
||||
[fs],
|
||||
|
@ -293,10 +294,7 @@ public class FeedsService : IEService, IReadyExecutor
|
|||
var toRemove = items[index];
|
||||
_subs.AddOrUpdate(toRemove.Url.ToLower(),
|
||||
[],
|
||||
(_, old) =>
|
||||
{
|
||||
return old.Where(x => x.Id != toRemove.Id).ToList();
|
||||
});
|
||||
(_, old) => { return old.Where(x => x.Id != toRemove.Id).ToList(); });
|
||||
uow.Remove(toRemove);
|
||||
uow.SaveChanges();
|
||||
|
||||
|
|
|
@ -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