Co-authored-by: Cursor <cursoragent@cursor.com>
Stoat Role Bot
Self-hosted bot for Stoat (open-source chat platform at stoat.chat) that gives members roles via reactions or text commands in server channels.
Stoat is the rebrand of Revolt and uses the same API; this bot works with Stoat and Revolt instances.
Features
- Reaction roles: Users react to a specific message with an emoji to get (or remove) a role.
- Text commands: Users run
!role add <name>/!role remove <name>for self-assignable roles;!roleslists them. - Admin commands: Configure reaction-role message and assignable role list from the chat (or via
config/roles.json).
Quick start (Docker)
-
Create a bot on your Stoat server
- In Stoat, open your server → Settings → Bots (or equivalent).
- Create a bot and copy its token.
- Ensure the bot has Assign Roles permission (and that its role is above any roles it should assign).
-
Configure and run
cp .env.example .env # Edit .env and set STOAT_BOT_TOKEN=your_bot_token cp config/roles.json.example config/roles.json # Edit config/roles.json: replace YOUR_SERVER_ID and MESSAGE_ID (see below) docker compose up -d -
Server ID: In Stoat, enable developer mode if available, then right‑click your server → copy ID. Use that in
roles.jsonasYOUR_SERVER_ID. -
Reaction-role message: Send a message in the channel (e.g. “React to get roles”), then copy that message’s ID. Put it under
reaction_rolesinroles.jsonwith emoji → role name. When users add/remove reactions, the bot will add/remove the role.
Configuration
-
Environment
STOAT_BOT_TOKENorREVOLT_BOT_TOKEN(required): Bot token from your Stoat/Revolt server.PREFIX(optional): Command prefix (default!).CONFIG_PATH(optional): Path to config dir inside container (default/app/config).
-
config/roles.jsonservers.<SERVER_ID>.reaction_roles.<MESSAGE_ID>: map emoji (unicode or custom emoji ID) → role name.servers.<SERVER_ID>.assignable_roles: list of role names that can be used with!role add/remove.
Example:
{
"servers": {
"01ABC123DEF456": {
"reaction_roles": {
"01MSG789XYZ": {
"👍": "Notifications",
"🔔": "Announcements"
}
},
"assignable_roles": ["Notifications", "Announcements", "Gamer"]
}
}
}
The bot writes updated config when admins use the admin commands below, so the container needs write access to the mounted config directory.
Commands
| Command | Who | Description |
|---|---|---|
!roles |
Everyone | List self-assignable roles. |
!role add <name> |
Everyone | Give yourself a self-assignable role. |
!role remove <name> |
Everyone | Remove a self-assignable role. |
!setreactionroles <message_id> emoji=RoleName ... |
Admin (Manage Role) | Set which message and emoji→role pairs are used for reaction roles. |
!setassignableroles Role1, Role2, Role3 |
Admin (Manage Role) | Set the list of self-assignable roles (comma-separated names). |
For custom server emojis in reaction roles, use the emoji’s ID in roles.json (see Stoat/Revolt docs for how to get it). Unicode emojis (e.g. 👍) can be used as-is.
Setting up reaction roles in config
-
Create the message in Stoat
In the channel where you want reaction roles, send a message (e.g. "React below to get roles"). That message must stay there; the bot will watch reactions on it. -
Get the message ID
In Stoat, enable developer mode if available, then right-click that message and copy its ID. It's a long string like01ABC123MSG456.... -
Edit
config/roles.json
Under your server (sameSERVER_IDyou use forassignable_roles), add areaction_rolesblock. The key is the message ID; the value is an object mapping emoji to role name (role names must match your server's roles exactly):"reaction_roles": { "01ABC123MSG456": { "👍": "FFXIV", "🎮": "Minecraft", "🃏": "MtG" } }- Unicode emojis (👍, 🎮, etc.): use the character as the key.
- Custom server emojis: use the emoji's ID as the key.
-
Save the file. The bot reloads config on each reaction; restart the container only if needed.
-
Add the reactions to the message
Add the same emojis to the message. When users click a reaction they get the role; when they remove it they lose the role.
Example for your server (replace PASTE_MESSAGE_ID_HERE with the real message ID):
"01KHEGSQZB7HXG0YCKMCA2W5PW": {
"reaction_roles": {
"PASTE_MESSAGE_ID_HERE": {
"👍": "FFXIV",
"🎮": "Minecraft",
"🃏": "MtG"
}
},
"assignable_roles": ["FFXIV", "Ascension", "Tarnished", "Minecraft", "Tabletop", "MtG", "Smash", "Soul Calibur", "Hearthstone", "Degenerate"]
}
Alternative: An admin can set reaction roles from chat:
!setreactionroles <message_id> 👍=FFXIV 🎮=Minecraft 🃏=MtG
Self-hosted Stoat / custom API URL
If you run your own Stoat (or Revolt) instance, point the bot at your API and WebSocket. Set the client configuration when building the bot (revolt.js accepts a configuration object with revolt and ws URLs). The default in revolt.js is the public Revolt/Stoat API (api.revolt.chat / wss://ws.revolt.chat). To use a different instance you would need to pass a custom configuration to the Client constructor; see revolt.js and Stoat developers.
Hosting the image on Gitea (automated pull on NAS)
New to Gitea? See docs/GITEA-SETUP.md for a simple step-by-step guide.
To build in Gitea and pull the image on your NAS:
-
Push this repo to your Gitea (e.g.
myuser/stoat-role-bot). -
Enable Gitea Actions and add Secrets for the container registry:
- Settings → Secrets:
REGISTRY_USER= your Gitea username,REGISTRY_PASSWORD= your password or a Personal Access Token (with package write).
- Settings → Secrets:
-
On every push to
main, the workflow in.gitea/workflows/docker.ymlbuilds and pushes the image to your Gitea Container Registry. The image will be at:{your-gitea-host}/{owner}/stoat-role-bot:latest(e.g.gitea.example.com/myuser/stoat-role-bot:latest). -
On your NAS, use the image from Gitea instead of building locally:
- Create
.envwithGITEA_IMAGE=gitea.example.com/myuser/stoat-role-bot:latest, plusSTOAT_BOT_TOKEN, etc. - Run:
docker compose -f docker-compose.pull.yml pull && docker compose -f docker-compose.pull.yml up -d - To auto-update: use Watchtower or a cron job that runs
docker compose -f docker-compose.pull.yml pulland restarts the container.
- Create
Manual push (from your PC, without Actions):
export GITEA_REGISTRY=gitea.example.com
export GITEA_OWNER=myuser
./push-to-gitea.sh
Run without Docker
Requires Node.js 22+.
npm install
cp .env.example .env
# Set STOAT_BOT_TOKEN in .env
export CONFIG_PATH=./config
node bot/index.js
License
MIT.