A lightweight bidirectional bridge that forwards messages between Discord and Stoat channels using webhooks and masquerade.
Discord user → Discord Bot → Stoat channel (via Stoat masquerade)
Stoat user → Stoat Bot → Discord channel (via Discord webhook)
Messages are forwarded in real time. Usernames and avatars are carried over so it looks native on both platforms.
| Direction | Behaviour |
|---|---|
| Discord → Stoat | Native reply when the original message is in cache; quote fallback otherwise |
| Stoat → Discord | Quote fallback (-# ↩ **Author**: *snippet*) – Discord webhooks do not support native replies |
The bridge caches the last 500 message ID pairs in memory (configurable). Replies to messages older than that are sent without a reply indicator.
| Direction | Behaviour |
|---|---|
| Discord → Stoat | The bare attachment URL is appended to the message text |
| Stoat → Discord | File is downloaded into RAM and re-uploaded as a Discord attachment. Files larger than 25 MiB are sent as a fallback link instead |
| Direction | What gets resolved |
|---|---|
| Discord → Stoat | <@id> → @Nickname, <#id> → #channel-name, <@&id> → @role-name, <:n:id> → :n: |
| Stoat → Discord | <@ULID> → @DisplayName, :ULID: → :emoji-name: |
- Python 3.10+
- A Discord bot with the Message Content, Server Members, Guilds, and Webhooks intents enabled
- A Stoat bot
pip install discord.py stoat.py aiohttp python-dotenv1. Clone the repo
git clone https://github.com/JMGstudios/DiscordStoatBridgeBot.git
cd DiscordStoatBridgeBot2. Run the bridge
python bridge.pyIf no .env file is found, or if required values are missing, the bridge will automatically start an interactive setup wizard on first launch:
============================================================
Stoat ↔ Discord Bridge – First-Time / Repair Setup
============================================================
› Discord Bot Token
Get it at: https://discord.com/developers/applications
Discord Bot Token:
⌨ Typing is hidden for security – this is normal, just type and press Enter.
>
› Stoat Bot Token
...
› Channel Pairs
You will now link Discord channels to Stoat channels one pair at a time.
── Pair 1 (first pair) ──
Discord Channel ID for pair 1: 123456789012345678
Stoat Channel ID for pair 1: ABCDEFGHIJ1234567890ABCDEF
✔ Pair 1 saved: Discord 123456789012345678 ↔ Stoat ABCDEFGHIJ1234567890ABCDEF
Add another channel pair? [y/N]:
✔ Configuration saved to /your/path/.env
============================================================
The generated .env is saved next to bridge.py. On every subsequent start the bridge prints the config path so you always know which file it loaded:
Config loaded from: /your/path/.env
Note on token input: when entering bot tokens the cursor won't move and nothing will appear on screen. This is intentional — input is hidden for security. Just type normally and press Enter.
3. Discord bot permissions
Make sure your bot has the following permissions in the target channel:
- Read Messages
- Send Messages
- Manage Webhooks
1. Pull the image
docker pull ghcr.io/jmgstudios/discordstoatbridgebot:latest2. Create a persistent config file on your host
touch ~/.config/bridgebot/.envImportant: You must mount only the
.envfile – not the entire directory. Mounting the whole folder would hidebridge.pyinside the container and the bot won't start.
3. First start – interactive setup
sudo docker run -it -v ~/.config/bridgebot/.env:/app/.env ghcr.io/jmgstudios/discordstoatbridgebot:latestThe setup wizard will guide you through the configuration and save your tokens and channel pairs to ~/.config/bridgebot/.env on your host machine.
4. Every new start
sudo docker run -it -v ~/.config/bridgebot/.env:/app/.env ghcr.io/jmgstudios/discordstoatbridgebot:latestSame command – the bridge will find the existing .env, skip the setup wizard, and start immediately.
The
-itflag is required for the interactive prompts on first launch. You can keep using it on subsequent starts without any downside.
| Variable | Required | Description |
|---|---|---|
DISCORD_BOT_TOKEN |
✅ | Token from the Discord Developer Portal |
STOAT_BOT_TOKEN |
✅ | Token from your Stoat bot settings |
DISCORD_CHANNEL_IDS |
✅ | Comma-separated Discord channel IDs |
STOAT_CHANNEL_IDS |
✅ | Comma-separated Stoat channel IDs |
REVOLT_API_URL |
➖ | Revolt API base URL. Defaults to https://api.revolt.chat |
Channel pairing: the IDs are matched by position — the first Discord ID is bridged with the first Stoat ID, the second with the second, and so on. Both lists must have the same length. The setup wizard handles this automatically when configuring pairs one by one.
- The bridge creates a webhook named
Stoat Bridgein your Discord channel automatically. If one already exists from a previous run it will be reused. - Messages originating from the bridge webhook are ignored to prevent forwarding loops.
- Message deletion is synced in both directions. If a message is deleted on one platform it is automatically removed on the other.
- Stoat custom emoji names are resolved via the Stoat API and cached in memory for the duration of the process.
- First-time users on both platforms receive a one-time DM explaining what the bridge does and how their messages are handled.