Production-ready Discord–IRC–XMPP bridge with multi-presence and modern protocol support.
- Multi-presence: Each Discord user gets their own IRC connection and XMPP JID (puppets)
- Modern protocols: IRCv3 capabilities, XMPP XEPs for edits/reactions/replies
- Identity-first: Portal is the source of truth—no account provisioning on the bridge
- Production-ready: Comprehensive test suite, retry logic, error recovery
# Install
uv sync
# Configure
cp config.example.yaml config.yaml
# Edit config.yaml with your channels and credentials
# Run
export DISCORD_TOKEN="your-token"
export PORTAL_BASE_URL="https://portal.example.com"
export PORTAL_TOKEN="your-portal-token"
export XMPP_COMPONENT_JID="bridge.atl.chat"
export XMPP_COMPONENT_SECRET="your-secret"
bridge --config config.yaml- Event-driven architecture: Central event bus with typed events (MessageIn/Out, Join, Part, Delete, Reaction, Typing)
- Channel mappings: Config-based Discord ↔ IRC ↔ XMPP routing
- Identity resolution: Portal API integration with configurable TTL caching
- Message relay: Bidirectional with edit/delete support; content filtering (regex)
- IRCv3 capabilities: message-tags, msgid, draft/reply, echo-message, labeled-response
- Reply threading: Discord replies ↔ IRC
+draft/replytags - Typing indicators: Discord typing → IRC
TAGMSGwith+typing=active - Puppet management: Per-user connections with idle timeout (24h default)
- Message ID tracking: 1-hour TTL cache for edit/delete correlation
- Flood control: Token bucket rate limiting and configurable throttle
- Component protocol: Single connection, multiple JIDs (XEP-0114)
- Stream Management: Reliable delivery with resumption (XEP-0198)
- Message features:
- Corrections (XEP-0308) - Edit messages
- Retractions (XEP-0424) - Delete messages
- Reactions (XEP-0444) - Emoji reactions
- Replies (XEP-0461) - Reply threading
- Spoilers (XEP-0382) - Content warnings
- File transfers: HTTP Upload (XEP-0363) with IBB fallback
- JID escaping: XEP-0106 for special characters in usernames
- History filtering: XEP-0203 delayed delivery detection
- Webhooks: Per-identity webhooks for native nick/avatar display
- Message edits: XMPP corrections and IRC edits → Discord
edit_message - Typing indicators: IRC typing → Discord
channel.typing() - !bridge status: Show linked IRC/XMPP accounts (requires Portal identity)
- Retry logic: Exponential backoff for transient errors (5 attempts, 2-30s)
- Error recovery: Graceful handling of network failures
- Comprehensive tests: 320+ tests covering core, adapters, formatting, and edge cases
mappings:
- discord_channel_id: "123456789012345678"
irc:
server: "irc.libera.chat"
port: 6697
tls: true
channel: "#atl"
xmpp:
muc_jid: "atl@conference.example.com"
announce_joins_and_quits: true
irc_puppet_idle_timeout_hours: 24See config.example.yaml for all options (throttling, SASL, content filtering, etc.).
| Variable | Required | Description |
|---|---|---|
DISCORD_TOKEN |
Yes | Discord bot token |
PORTAL_BASE_URL |
Yes | Portal API URL |
PORTAL_TOKEN |
Yes | Portal service token |
XMPP_COMPONENT_JID |
Yes | Component JID (e.g., bridge.atl.chat) |
XMPP_COMPONENT_SECRET |
Yes | Prosody component secret |
XMPP_COMPONENT_SERVER |
No | Server hostname (default: localhost) |
XMPP_COMPONENT_PORT |
No | Component port (default: 5347) |
IRC_NICK |
No | Main IRC nick (default: atl-bridge) |
┌─────────────────────────────────────┐
│ Discord Bot │
│ Webhooks + Message Events │
└──────────────┬──────────────────────┘
│
│ MessageIn/MessageOut
│ Join/Part/Quit
▼
┌─────────────────────────────────────┐
│ Event Bus │
│ Async dispatch + Error isolation │
└──────────────┬──────────────────────┘
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Channel │ │ Identity │ │ Message ID │
│ Router │ │ Resolver │ │ Trackers │
│ │ │ │ │ │
│ Discord ↔ │ │ Portal API + │ │ IRC + XMPP │
│ IRC ↔ XMPP │ │ TTL cache │ │ 1hr TTL │
└──────────────┘ └──────────────┘ └──────────────┘
│ │ │
└──────────────┼──────────────┘
│
┌──────────────┴──────────────┐
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ IRC Adapter │ │ XMPP Component │
│ │ │ │
│ • Main connection │ │ • ComponentXMPP │
│ • Puppet manager │ │ • Multi-presence │
│ • IRCv3 caps │ │ • 8 XEPs │
│ • 24h idle timeout │ │ • Stream mgmt │
└─────────────────────┘ └─────────────────────┘
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ IRC Server │ │ XMPP Server │
│ (unrealircd) │ │ (prosody) │
└─────────────────────┘ └─────────────────────┘
Discord → IRC/XMPP:
- Discord user sends message
- Discord adapter creates
MessageInevent - Event bus dispatches to all adapters
- Identity resolver checks Portal for IRC/XMPP links
- Channel router finds target IRC channel + XMPP MUC
- IRC adapter sends via puppet (if linked) or main connection
- XMPP component sends from user's JID (e.g.,
user@bridge.atl.chat)
IRC → Discord/XMPP:
- IRC puppet receives message with
msgidtag - IRC adapter creates
MessageInevent, stores msgid mapping - Event bus dispatches to Discord + XMPP adapters
- Discord adapter sends via webhook (shows IRC nick)
- XMPP component relays to MUC from bridge JID
Edit/Delete Flow:
- Discord edit event received → Relay emits MessageOut with
is_edit - IRC/XMPP adapters look up stored msgid; Discord adapter resolves via trackers
- IRC:
TAGMSGwith edit; XMPP: correction (XEP-0308); Discord:webhook.edit_message - IRC REDACT / XMPP retraction → Discord
message.delete
Reactions & Typing:
- Discord reactions → Relay → IRC/XMPP; IRC/XMPP reactions → Relay → Discord
- Typing indicators bridged both directions (Discord ↔ IRC; throttled)
- Event Bus: Central dispatcher for typed events (MessageIn, MessageOut, Join, Part, Quit, MessageDelete, ReactionIn/Out, TypingIn/Out)
- Relay: Transforms MessageIn → MessageOut for target protocols; applies content filtering and formatting
- Channel Router: Maps Discord channels ↔ IRC channels ↔ XMPP MUCs
- Identity Resolver: Portal API client with configurable TTL caching
- Adapters: Protocol-specific handlers (Discord, IRC, XMPP)
# Install with dev dependencies
uv sync
# Run tests
uv run pytest tests -v
# Linting
uv run ruff check src tests
uv run basedpyrightsrc/bridge/
├── __main__.py # Entry point
├── config.py # YAML config loading
├── events.py # Event types
├── identity.py # Portal client + cache
├── gateway/
│ ├── bus.py # Event dispatcher
│ ├── relay.py # MessageIn → MessageOut routing
│ └── router.py # Channel mapping
├── formatting/
│ ├── discord_to_irc.py # Discord markdown → IRC
│ ├── irc_to_discord.py # IRC control codes → Discord
│ └── irc_message_split.py # Long message splitting
└── adapters/
├── base.py # Adapter interface
├── disc.py # Discord adapter
├── irc.py # IRC client
├── irc_puppet.py # IRC puppet manager
├── irc_throttle.py # IRC flood control
├── irc_msgid.py # IRC message ID tracker
├── xmpp.py # XMPP adapter
├── xmpp_component.py # XMPP component
└── xmpp_msgid.py # XMPP message ID tracker
# All tests
uv run pytest tests -v
# Specific feature
uv run pytest tests/test_xmpp_features.py -v
# With coverage
uv run pytest tests --cov --cov-report=htmlTest Coverage: 320+ tests covering:
- Core bridging logic and relay
- Discord adapter (webhooks, edits, reactions, typing)
- IRC reply threading, puppets, message ID tracking
- XMPP XEPs (8 extensions), message ID tracking
- Formatting (Discord↔IRC, message splitting)
- File transfers
- Error handling
- Concurrency and ordering
# Build
docker build -f Containerfile -t atl-bridge .
# Run
docker run -v $(pwd)/config.yaml:/app/config.yaml \
-e DISCORD_TOKEN="..." \
-e PORTAL_BASE_URL="..." \
-e PORTAL_TOKEN="..." \
-e XMPP_COMPONENT_JID="..." \
-e XMPP_COMPONENT_SECRET="..." \
atl-bridgeThe bridge requires Prosody (or compatible XMPP server) with component configuration. Configure a component for the XMPP_COMPONENT_JID and set the component secret to match XMPP_COMPONENT_SECRET.
- Single guild: One bridge instance per Discord guild
- No DMs: Only channels/MUCs, no private messages
- File size: 10MB limit for XMPP file transfers
- IRC puppet timeout: Idle puppets disconnect after 24 hours (configurable)
- Check firewall rules for IRC ports (6667, 6697)
- Verify IRC server allows multiple connections from same IP
- Check IRC nick is not already in use
- Verify Prosody component configuration
- Check component secret matches
- Ensure MUC exists and bridge has joined
- Review Prosody logs:
/var/log/prosody/prosody.log
- Check Portal API is responding (< 100ms)
- Verify identity cache is working (check logs)
- Monitor event bus queue depth
- Fork the repository
- Create a feature branch
- Add tests for new features
- Ensure all tests pass:
uv run pytest tests -v - Run linters:
uv run ruff check src tests - Submit a pull request
MIT License - see LICENSE file for details.
- Built for All Things Linux
- Uses discord.py for Discord
- Uses pydle for IRC
- Uses slixmpp for XMPP
Status: Production-ready • Maintained: Yes • Tests: 320+ passing