Compare commits

...

18 commits
6.1.0 ... v6

Author SHA1 Message Date
ced58d17c3
Merge pull request 'docs-beta' () from docs-beta into v6
Reviewed-on: 
Reviewed-by: Liarel <liarel@toastielab.dev>
2025-04-03 05:55:33 +00:00
d171b92989
Merge branch 'v6' into docs-beta 2025-04-03 05:39:26 +00:00
172b1ed5e6
.feed will now properly add feeds to the database 2025-04-03 17:56:08 +13:00
8f6ecff5af
Update changelog 2025-04-03 16:31:46 +13:00
94aee4ad10
added some config options .conf fish
adding 4 basic items automatically to fish.yml once user updates and has no items
2025-04-03 16:29:45 +13:00
d910683d78
added hangman category to hangman output - regardless of whether you've selected a category or got a random one. 2025-04-03 15:50:06 +13:00
8ed26e11d3
Files :D 2025-04-02 18:27:34 +13:00
d682909bab
Merge branch 'v6' into docs-beta 2025-04-02 02:08:52 +00:00
015724a150
fixed a typo in fish shop
updated fish design.md
2025-04-02 14:57:53 +13:00
7f36481987
Added copyright to the footer. 2025-04-01 23:18:24 +13:00
16c64762b7
Changed font text to be Source Sans 3 so mkdocs.yml will stop yelling at me. 2025-04-01 22:48:41 +13:00
d8aa040f75
Added vps linux and cli guides. 2025-04-01 22:17:19 +13:00
37522cf65c
Added more files again. 2025-04-01 14:38:03 +13:00
ea06e9f217
Merge branch 'v6' into docs-beta 2025-04-01 00:36:24 +00:00
68e736ceb8
.fishlb will now use totalcatches as a tiebreaker 2025-04-01 13:26:17 +13:00
d3c90ab59f
fishlb will now compare unique fish caught, instead of total catches 2025-04-01 13:24:46 +13:00
9aa567b276
Added more files. 2025-04-01 13:17:02 +13:00
026e5a151e
Initial stuff. 2025-04-01 13:04:14 +13:00
39 changed files with 1263 additions and 108 deletions

View file

@ -2,6 +2,21 @@
*a,c,f,r,o*
## [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
- hangman category now appears in .hangman output
## [6.1.0] - 30.03.2025
### Added

Binary file not shown.

After

(image error) Size: 52 KiB

Binary file not shown.

After

(image error) Size: 58 KiB

Binary file not shown.

After

(image error) Size: 56 KiB

Binary file not shown.

After

(image error) Size: 15 KiB

Binary file not shown.

After

(image error) Size: 1.2 KiB

Binary file not shown.

After

(image error) Size: 1,014 B

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

Binary file not shown.

After

(image error) Size: 28 KiB

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
View 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
![Invite the bot to your server](https://cdn.elliebot.net/bot-invite-guide.gif)
- 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
View 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
View 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`
///

View 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).
![Create a new bot](../assets/elliehub-1.png "Create a new bot")
2. Click the plus button to add a new bot
![Open bot page](../assets/elliehub-2.png "Open bot page")
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)
![Download](../assets/elliehub-3.png "Download")
![Creds](../assets/elliehub-4.png "Edit creds")
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

View 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.
///

View 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

View 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
View 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

View 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

View 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)

View 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)

View 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]

80
docs/mkdocs.yml Normal file
View 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 &copy; 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

View file

@ -4,7 +4,7 @@
<Nullable>enable</Nullable>
<ImplicitUsings>true</ImplicitUsings>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<Version>6.1.0</Version>
<Version>6.1.2</Version>
<!-- Output/build -->
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>

View file

@ -32,10 +32,11 @@ flowchart TD
A[.fish] --> B[Catch Fish]
B --> C["Show off with .fili"]
B --> G["Show up in fishfeed"] --> E
B --> D["Complete the set"]:::todo
D --> E[Recognition]:::todo & F[Buff?]:::todo
D --> E[Recognition] & F[Buff?]:::todo
F --> B
B --> X[Buy equipment]:::todo
X1[Modify Odds]:::todo --> B
B --> X[Buy equipment]
X1[Modify Odds] --> B
X --> X1
```

View file

@ -15,17 +15,91 @@ public sealed class FishConfigService : ConfigServiceBase<FishConfig>
IPubSub pubSub)
: base(FILE_PATH, serializer, pubSub, _changeKey)
{
AddParsedProp("captcha",
static (conf) => conf.RequireCaptcha,
bool.TryParse,
ConfigPrinters.ToString);
AddParsedProp("chance.nothing",
static (conf) => conf.Chance.Nothing,
int.TryParse,
ConfigPrinters.ToString);
AddParsedProp("chance.fish",
static (conf) => conf.Chance.Fish,
int.TryParse,
ConfigPrinters.ToString);
AddParsedProp("chance.trash",
static (conf) => conf.Chance.Trash,
int.TryParse,
ConfigPrinters.ToString);
Migrate();
}
private void Migrate()
{
if (data.Version < 2)
if (data.Version < 11)
{
ModifyConfig(c =>
{
c.Version = 2;
c.RequireCaptcha = true;
c.Version = 11;
if (c.Items is { Count: > 0 })
return;
c.Items =
[
new FishItem
{
Id = 1,
ItemType = FishItemType.Pole,
Name = "Wooden Rod",
Description = "Better than catching it with bare hands.",
Price = 1000,
FishMultiplier = 1.2
},
new FishItem
{
Id = 11,
ItemType = FishItemType.Pole,
Name = "Magnet on a Stick",
Description = "Attracts all trash, not just metal.",
Price = 3000,
FishMultiplier = 0.9,
TrashMultiplier = 2
},
new FishItem
{
Id = 21,
ItemType = FishItemType.Bait,
Name = "Corn",
Description = "Just some cooked corn.",
Price = 100,
Uses = 100,
RareMultiplier = 1.1
},
new FishItem
{
Id = 31,
ItemType = FishItemType.Potion,
Name = "A Cup of Tea",
Description = "Helps you focus.",
Price = 12000,
DurationMinutes = 30,
MaxStarMultiplier = 1.1,
FishingSpeedMultiplier = 1.01
},
new FishItem
{
Id = 41,
ItemType = FishItemType.Boat,
Name = "Canoe",
Description = "Lets you fish a little faster.",
Price = 3000,
FishingSpeedMultiplier = 1.201,
MaxStarMultiplier = 1.1
}
];
});
}
}

View file

@ -21,7 +21,7 @@ public partial class Games
{
var eb = CreateEmbed()
.WithTitle(GetText(strs.fish_items_title))
.WithFooter("`.fibuy <id>` to by an item")
.WithFooter("`.fibuy <id>` to buy an item")
.WithOkColor();
foreach (var item in pageItems)

View file

@ -469,22 +469,23 @@ public sealed class FishService(
return catches;
}
public async Task<IReadOnlyCollection<(ulong UserId, int Catches)>> GetFishLbAsync(int page)
public async Task<IReadOnlyCollection<(ulong UserId, int Catches, int Unique)>> GetFishLbAsync(int page)
{
await using var ctx = db.GetDbContext();
var result = await ctx.GetTable<FishCatch>()
.GroupBy(x => x.UserId)
.OrderByDescending(x => x.Sum(x => x.Count))
.OrderByDescending(x => x.Count()).ThenByDescending(x => x.Sum(x => x.Count))
.Skip(page * 10)
.Take(10)
.Select(x => new
{
UserId = x.Key,
Catches = x.Sum(x => x.Count)
Catches = x.Sum(x => x.Count),
Unique = x.Count()
})
.ToListAsyncLinqToDB()
.Fmap(x => x.Map(y => (y.UserId, y.Catches)));
.Fmap(x => x.Map(y => (y.UserId, y.Catches, y.Unique)).ToList());
return result;
}

View file

@ -235,8 +235,14 @@ public partial class Games
? ud.ToString()
: data.UserId.ToString();
var text =
$"""
{GetText(strs.fish_unique(Format.Bold(data.Unique.ToString())))}
*{GetText(strs.fish_catches(data.Catches))}*
""";
eb.AddField("#" + (page * 9 + i + 1) + " | " + user,
GetText(strs.fish_catches(Format.Bold(data.Catches.ToString()))),
text,
false);
}

View file

@ -44,7 +44,7 @@ public sealed class DefaultHangmanSource : IHangmanSource
public IReadOnlyCollection<string> GetCategories()
=> termsDict.Keys.ToList();
public bool GetTerm(string? category, [NotNullWhen(true)] out HangmanTerm? term)
public bool GetTerm(string? category, [NotNullWhen(true)] out (HangmanTerm Term, string Category)? term)
{
if (category is null)
{
@ -54,7 +54,7 @@ public sealed class DefaultHangmanSource : IHangmanSource
if (termsDict.TryGetValue(category, out var terms))
{
term = terms[_rng.Next(0, terms.Length)];
term = (terms[_rng.Next(0, terms.Length)], category);
return true;
}

View file

@ -10,7 +10,8 @@ public partial class Games
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task Hangmanlist()
=> await Response().Confirm(GetText(strs.hangman_types(prefix)), _service.GetHangmanTypes().Join('\n'))
=> await Response()
.Confirm(GetText(strs.hangman_types(prefix)), _service.GetHangmanTypes().Join('\n'))
.SendAsync();
private static string Draw(HangmanGame.State state)
@ -35,29 +36,25 @@ public partial class Games
public static EmbedBuilder GetEmbed(IMessageSenderService sender, HangmanGame.State state)
{
var eb = sender.CreateEmbed()
.WithOkColor()
.AddField("Hangman", Draw(state))
.AddField("Guess", Format.Code(state.Word));
if (state.Phase == HangmanGame.Phase.Running)
{
return sender.CreateEmbed()
.WithOkColor()
.AddField("Hangman", Draw(state))
.AddField("Guess", Format.Code(state.Word))
.WithFooter(state.MissedLetters.Join(' '));
return eb
.WithFooter(state.MissedLetters.Join(' '))
.WithAuthor(state.Category);
}
if (state.Phase == HangmanGame.Phase.Ended && state.Failed)
{
return sender.CreateEmbed()
.WithErrorColor()
.AddField("Hangman", Draw(state))
.AddField("Guess", Format.Code(state.Word))
return eb
.WithFooter(state.MissedLetters.Join(' '));
}
return sender.CreateEmbed()
.WithOkColor()
.AddField("Hangman", Draw(state))
.AddField("Guess", Format.Code(state.Word))
.WithFooter(state.MissedLetters.Join(' '));
return eb.WithFooter(state.MissedLetters.Join(' '));
}
[Cmd]

View file

@ -4,9 +4,20 @@ namespace EllieBot.Modules.Games.Hangman;
public sealed class HangmanGame
{
public enum GuessResult { NoAction, AlreadyTried, Incorrect, Guess, Win }
public enum GuessResult
{
NoAction,
AlreadyTried,
Incorrect,
Guess,
Win
}
public enum Phase { Running, Ended }
public enum Phase
{
Running,
Ended
}
private Phase CurrentPhase { get; set; }
@ -17,16 +28,21 @@ public sealed class HangmanGame
private readonly string _word;
private readonly string _imageUrl;
public HangmanGame(HangmanTerm term)
public string Category { get; }
public HangmanGame(HangmanTerm term, string cat)
{
_word = term.Word;
_imageUrl = term.ImageUrl;
Category = cat;
_remaining = _word.ToLowerInvariant().Where(x => char.IsLetter(x)).Select(char.ToLowerInvariant).ToHashSet();
}
public State GetState(GuessResult guessResult = GuessResult.NoAction)
=> new(_incorrect.Count,
=> new(
Category,
_incorrect.Count,
CurrentPhase,
CurrentPhase == Phase.Ended ? _word : GetScrambledWord(),
guessResult,
@ -99,6 +115,7 @@ public sealed class HangmanGame
}
public record State(
string Category,
int Errors,
Phase Phase,
string Word,

View file

@ -36,10 +36,10 @@ public sealed class HangmanService : IHangmanService, IExecNoCommand
public bool StartHangman(ulong channelId, string? category, [NotNullWhen(true)] out HangmanGame.State? state)
{
state = null;
if (!_source.GetTerm(category, out var term))
if (!_source.GetTerm(category, out var termData))
return false;
var game = new HangmanGame(term);
var game = new HangmanGame(termData.Value.Term, termData.Value.Category);
lock (_locker)
{
var hc = _hangmanGames.GetOrAdd(channelId, game);

View file

@ -1,4 +1,6 @@
#nullable disable
using YamlDotNet.Serialization;
namespace EllieBot.Modules.Games.Hangman;
public sealed class HangmanTerm

View file

@ -6,5 +6,5 @@ public interface IHangmanSource : IEService
{
public IReadOnlyCollection<string> GetCategories();
public void Reload();
public bool GetTerm(string? category, [NotNullWhen(true)] out HangmanTerm? term);
public bool GetTerm(string? category, [NotNullWhen(true)] out (HangmanTerm Term, string Category)? term);
}

View file

@ -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,32 @@ 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();
}
}
}

View file

@ -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();

View file

@ -1,5 +1,5 @@
# DO NOT CHANGE
version: 2
version: 11
weatherSeed: w%29';^eGE)9oWHM(aI9I;%1[.r^z2ZS7ShV,l')o(e%#"hVzb>oxQq^`.&/7srh
requireCaptcha: true
starEmojis:
@ -45,40 +45,66 @@ trash:
items:
- id: 1
itemType: Pole
name: "Wooden Rod"
description: "Better than catching it with bare hands."
name: Wooden Rod
emoji: ''
description: Better than catching it with bare hands.
price: 1000
uses:
durationMinutes:
fishMultiplier: 1.2
trashMultiplier:
maxStarMultiplier:
rareMultiplier:
fishingSpeedMultiplier:
- id: 11
itemType: Pole
name: Magnet on a Stick
description: "Attracts all trash, not just metal."
emoji: ''
description: Attracts all trash, not just metal.
price: 3000
uses:
durationMinutes:
fishMultiplier: 0.9
trashMultiplier: 2
maxStarMultiplier:
rareMultiplier:
fishingSpeedMultiplier:
- id: 21
itemType: Bait
name: "Corn"
description: "Just some cooked corn."
name: Corn
emoji: ''
description: Just some cooked corn.
price: 100
uses: 100
durationMinutes:
fishMultiplier:
trashMultiplier:
maxStarMultiplier:
rareMultiplier: 1.1
fishingSpeedMultiplier:
- id: 31
itemType: Potion
name: "A Cup of Tea"
description: "Helps you focus."
name: A Cup of Tea
emoji: ''
description: Helps you focus.
price: 12000
uses:
durationMinutes: 30
fishMultiplier:
trashMultiplier:
maxStarMultiplier: 1.1
rareMultiplier:
fishingSpeedMultiplier: 1.01
- id: 41
itemType: Boat
name: "Canoe"
description: "Lets you fish a little faster."
name: Canoe
emoji: ''
description: Lets you fish a little faster.
price: 3000
uses:
durationMinutes:
fishMultiplier:
trashMultiplier:
maxStarMultiplier: 1.1
rareMultiplier:
fishingSpeedMultiplier: 1.201
maxStarMultiplier: 1.1

View file

@ -1260,5 +1260,6 @@
"fish_inv_title": "Fishing Inventory",
"fish_cant_uneq_potion": "You can't unequip a potion.",
"fish_lb_title": "Fishing Leaderboard",
"fish_unique": "Caught {0} unique fish",
"fish_catches": "{0} catches"
}