{"id":45535592,"url":"https://github.com/allthingslinux/bridge","last_synced_at":"2026-02-23T02:01:02.156Z","repository":{"id":339301063,"uuid":"1160486771","full_name":"allthingslinux/bridge","owner":"allthingslinux","description":"Bridge","archived":false,"fork":false,"pushed_at":"2026-02-19T06:42:27.000Z","size":245,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-19T07:41:02.440Z","etag":null,"topics":["bridge","discord","docker","irc","ircv3","just","lefthook","prosody","pydle","pytest","python","slixmpp","unrealircd","uv","xmpp"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/allthingslinux.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"open_collective":"allthingslinux"}},"created_at":"2026-02-18T02:02:05.000Z","updated_at":"2026-02-19T06:42:32.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/allthingslinux/bridge","commit_stats":null,"previous_names":["allthingslinux/bridge"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/allthingslinux/bridge","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allthingslinux%2Fbridge","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allthingslinux%2Fbridge/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allthingslinux%2Fbridge/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allthingslinux%2Fbridge/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/allthingslinux","download_url":"https://codeload.github.com/allthingslinux/bridge/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allthingslinux%2Fbridge/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29734845,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-22T20:09:16.275Z","status":"online","status_checked_at":"2026-02-23T02:00:06.784Z","response_time":90,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bridge","discord","docker","irc","ircv3","just","lefthook","prosody","pydle","pytest","python","slixmpp","unrealircd","uv","xmpp"],"created_at":"2026-02-23T02:00:24.557Z","updated_at":"2026-02-23T02:01:02.138Z","avatar_url":"https://github.com/allthingslinux.png","language":"Python","readme":"# ATL Bridge\n\n**Production-ready Discord–IRC–XMPP bridge with multi-presence and modern protocol support.**\n\n[![Tests](https://img.shields.io/badge/tests-654%20passing-brightgreen)]() [![Python](https://img.shields.io/badge/python-3.10+-blue)]() [![License](https://img.shields.io/badge/license-MIT-blue)]()\n\n## Why ATL Bridge?\n\n- **Multi-presence**: Each Discord user gets their own IRC connection and XMPP JID (puppets)\n- **Modern protocols**: IRCv3 capabilities, XMPP XEPs for edits/reactions/replies\n- **Identity-first**: Portal is the source of truth—no account provisioning on the bridge\n- **Production-ready**: Comprehensive test suite, retry logic, error recovery\n\n## Quick Start\n\n```bash\n# Install\nuv sync\n\n# Configure\ncp config.example.yaml config.yaml\n# Edit config.yaml with your channels and credentials\n\n# Run\nexport DISCORD_TOKEN=\"your-token\"\nexport PORTAL_BASE_URL=\"https://portal.example.com\"\nexport PORTAL_TOKEN=\"your-portal-token\"\nexport XMPP_COMPONENT_JID=\"bridge.atl.chat\"\nexport XMPP_COMPONENT_SECRET=\"your-secret\"\n\nbridge --config config.yaml\n```\n\n## Features\n\n### Core Bridging\n- **Event-driven architecture**: Central event bus with typed events (MessageIn/Out, Join, Part, Delete, Reaction, Typing)\n- **Channel mappings**: Config-based Discord ↔ IRC ↔ XMPP routing\n- **Identity resolution**: Portal API integration with configurable TTL caching\n- **Message relay**: Bidirectional with edit/delete support; content filtering (regex)\n\n### IRC Support\n- **IRCv3 capabilities**: message-tags, msgid, draft/reply, echo-message, labeled-response\n- **Reply threading**: Discord replies ↔ IRC `+draft/reply` tags\n- **Typing indicators**: Discord typing → IRC `TAGMSG` with `+typing=active`\n- **Puppet management**: Per-user connections with idle timeout (24h default)\n- **Puppet keep-alive**: Configurable PING interval to prevent silent server-side drops\n- **Pre-join commands**: Send NickServ IDENTIFY, MODE, etc. immediately after puppet connects\n- **Message ID tracking**: 1-hour TTL cache for edit/delete correlation\n- **Flood control**: Token bucket rate limiting and configurable throttle\n\n### XMPP Support\n- **Component protocol**: Single connection, multiple JIDs (XEP-0114)\n- **Stream Management**: Reliable delivery with resumption (XEP-0198)\n- **Message features**:\n  - Corrections (XEP-0308) - Edit messages\n  - Retractions (XEP-0424) - Delete messages\n  - Reactions (XEP-0444) - Emoji reactions\n  - Replies (XEP-0461) - Reply threading\n  - Spoilers (XEP-0382) - Content warnings\n- **File transfers**: HTTP Upload (XEP-0363) with IBB fallback\n- **JID escaping**: XEP-0106 for special characters in usernames\n- **History filtering**: XEP-0203 delayed delivery detection\n\n### Discord Support\n- **Webhooks**: Per-identity webhooks for native nick/avatar display\n- **Raw event handling**: Edits and deletes fire for all messages, not just cached ones\n- **Bulk delete**: Moderator purges relay each deleted message to IRC/XMPP\n- **Message edits**: XMPP corrections and IRC edits → Discord `edit_message`\n- **Reactions**: Add and remove reactions bridged to/from IRC/XMPP\n- **Typing indicators**: IRC typing → Discord `channel.typing()`\n- **Mention safety**: `@everyone` and role pings suppressed on bridged content\n- **!bridge status**: Show linked IRC/XMPP accounts (requires Portal identity)\n\n### Reliability\n- **Retry logic**: Exponential backoff for transient errors (5 attempts, 2-30s)\n- **Error recovery**: Graceful handling of network failures\n- **Event loop**: uvloop for 2-4x faster async I/O (Linux/macOS; falls back to asyncio on Windows)\n- **Comprehensive tests**: 654 tests covering core, adapters, formatting, and edge cases\n\n## Configuration\n\n### Minimal Example\n\n```yaml\nmappings:\n  - discord_channel_id: \"123456789012345678\"\n    irc:\n      server: \"irc.libera.chat\"\n      port: 6697\n      tls: true\n      channel: \"#atl\"\n    xmpp:\n      muc_jid: \"atl@conference.example.com\"\n\nannounce_joins_and_quits: true\nirc_puppet_idle_timeout_hours: 24\nirc_puppet_ping_interval: 120        # keep-alive PING every 2 minutes\nirc_puppet_prejoin_commands:         # sent immediately after puppet connects\n  - \"MODE {nick} +D\"\n```\n\nSee `config.example.yaml` for all options (throttling, SASL, content filtering, etc.).\n\n### Environment Variables\n\n| Variable | Required | Description |\n|----------|----------|-------------|\n| `DISCORD_TOKEN` | Yes | Discord bot token |\n| `PORTAL_BASE_URL` | Yes | Portal API URL |\n| `PORTAL_TOKEN` | Yes | Portal service token |\n| `XMPP_COMPONENT_JID` | Yes | Component JID (e.g., `bridge.atl.chat`) |\n| `XMPP_COMPONENT_SECRET` | Yes | Prosody component secret |\n| `XMPP_COMPONENT_SERVER` | No | Server hostname (default: `localhost`) |\n| `XMPP_COMPONENT_PORT` | No | Component port (default: `5347`) |\n| `IRC_NICK` | No | Main IRC nick (default: `atl-bridge`) |\n\n## Architecture\n\n```\n                    ┌─────────────────────────────────────┐\n                    │         Discord Bot                 │\n                    │   Webhooks + Message Events         │\n                    └──────────────┬──────────────────────┘\n                                   │\n                                   │ MessageIn/MessageOut\n                                   │ Join/Part/Quit\n                                   ▼\n                    ┌─────────────────────────────────────┐\n                    │         Event Bus                   │\n                    │   Async dispatch + Error isolation  │\n                    └──────────────┬──────────────────────┘\n                                   │\n                    ┌──────────────┼──────────────┐\n                    │              │              │\n                    ▼              ▼              ▼\n         ┌──────────────┐  ┌──────────────┐  ┌──────────────┐\n         │   Channel    │  │   Identity   │  │  Message ID  │\n         │   Router     │  │   Resolver   │  │   Trackers   │\n         │              │  │              │  │              │\n         │  Discord ↔   │  │ Portal API + │  │ IRC + XMPP   │\n         │  IRC ↔ XMPP  │  │  TTL cache   │  │   1hr TTL    │\n         └──────────────┘  └──────────────┘  └──────────────┘\n                    │              │              │\n                    └──────────────┼──────────────┘\n                                   │\n                    ┌──────────────┴──────────────┐\n                    │                             │\n                    ▼                             ▼\n         ┌─────────────────────┐      ┌─────────────────────┐\n         │   IRC Adapter       │      │   XMPP Component    │\n         │                     │      │                     │\n         │ • Main connection   │      │ • ComponentXMPP     │\n         │ • Puppet manager    │      │ • Multi-presence    │\n         │ • IRCv3 caps        │      │ • 8 XEPs            │\n         │ • 24h idle timeout  │      │ • Stream mgmt       │\n         └─────────────────────┘      └─────────────────────┘\n                    │                             │\n                    ▼                             ▼\n         ┌─────────────────────┐      ┌─────────────────────┐\n         │  IRC Server         │      │  XMPP Server        │\n         │  (unrealircd)       │      │  (prosody)          │\n         └─────────────────────┘      └─────────────────────┘\n```\n\n### Data Flow\n\n**Discord → IRC/XMPP:**\n1. Discord user sends message\n2. Discord adapter creates `MessageIn` event\n3. Event bus dispatches to all adapters\n4. Identity resolver checks Portal for IRC/XMPP links\n5. Channel router finds target IRC channel + XMPP MUC\n6. IRC adapter sends via puppet (if linked) or main connection\n7. XMPP component sends from user's JID (e.g., `user@bridge.atl.chat`)\n\n**IRC → Discord/XMPP:**\n1. IRC puppet receives message with `msgid` tag\n2. IRC adapter creates `MessageIn` event, stores msgid mapping\n3. Event bus dispatches to Discord + XMPP adapters\n4. Discord adapter sends via webhook (shows IRC nick)\n5. XMPP component relays to MUC from bridge JID\n\n**Edit/Delete Flow:**\n1. Discord edit event received → Relay emits MessageOut with `is_edit`\n2. IRC/XMPP adapters look up stored msgid; Discord adapter resolves via trackers\n3. IRC: `TAGMSG` with edit; XMPP: correction (XEP-0308); Discord: `webhook.edit_message`\n4. IRC REDACT / XMPP retraction → Discord `message.delete`\n\n**Reactions \u0026 Typing:**\n- Discord reactions → Relay → IRC/XMPP; IRC/XMPP reactions → Relay → Discord\n- Typing indicators bridged both directions (Discord ↔ IRC; throttled)\n\n### Key Components\n\n- **Event Bus**: Central dispatcher for typed events (MessageIn, MessageOut, Join, Part, Quit, MessageDelete, ReactionIn/Out, TypingIn/Out)\n- **Relay**: Transforms MessageIn → MessageOut for target protocols; applies content filtering and formatting\n- **Channel Router**: Maps Discord channels ↔ IRC channels ↔ XMPP MUCs\n- **Identity Resolver**: Portal API client with configurable TTL caching\n- **Adapters**: Protocol-specific handlers (Discord, IRC, XMPP)\n\n## Development\n\n### Setup\n\n```bash\n# Install with dev dependencies\nuv sync\n\n# Run tests\nuv run pytest tests -v\n\n# Linting\nuv run ruff check src tests\nuv run basedpyright\n```\n\n### Project Structure\n\n```\nsrc/bridge/\n├── __main__.py          # Entry point\n├── config.py            # YAML config loading\n├── events.py            # Event types\n├── identity.py          # Portal client + cache\n├── gateway/\n│   ├── bus.py          # Event dispatcher\n│   ├── relay.py        # MessageIn → MessageOut routing\n│   └── router.py       # Channel mapping\n├── formatting/\n│   ├── discord_to_irc.py   # Discord markdown → IRC\n│   ├── irc_to_discord.py   # IRC control codes → Discord\n│   └── irc_message_split.py  # Long message splitting\n└── adapters/\n    ├── base.py         # Adapter interface\n    ├── disc.py         # Discord adapter\n    ├── irc.py          # IRC client\n    ├── irc_puppet.py   # IRC puppet manager\n    ├── irc_throttle.py # IRC flood control\n    ├── irc_msgid.py    # IRC message ID tracker\n    ├── xmpp.py         # XMPP adapter\n    ├── xmpp_component.py   # XMPP component\n    └── xmpp_msgid.py   # XMPP message ID tracker\n```\n\n### Testing\n\n```bash\n# All tests\nuv run pytest tests -v\n\n# Specific feature\nuv run pytest tests/test_xmpp_features.py -v\n\n# With coverage\nuv run pytest tests --cov --cov-report=html\n```\n\n**Test Coverage**: 654 tests covering:\n- Core bridging logic and relay\n- Discord adapter (webhooks, edits, reactions, typing)\n- IRC reply threading, puppets, message ID tracking\n- XMPP XEPs (8 extensions), message ID tracking\n- Formatting (Discord↔IRC, message splitting)\n- File transfers\n- Error handling\n- Concurrency and ordering\n\n## Docker\n\n```bash\n# Build\ndocker build -f Containerfile -t atl-bridge .\n\n# Run\ndocker run -v $(pwd)/config.yaml:/app/config.yaml \\\n  -e DISCORD_TOKEN=\"...\" \\\n  -e PORTAL_BASE_URL=\"...\" \\\n  -e PORTAL_TOKEN=\"...\" \\\n  -e XMPP_COMPONENT_JID=\"...\" \\\n  -e XMPP_COMPONENT_SECRET=\"...\" \\\n  atl-bridge\n```\n\n## XMPP Server Setup\n\nThe 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`.\n\n## Limitations\n\n- **Single guild**: One bridge instance per Discord guild\n- **No DMs**: Only channels/MUCs, no private messages\n- **File size**: 10MB limit for XMPP file transfers\n- **IRC puppet timeout**: Idle puppets disconnect after 24 hours (configurable)\n\n## Troubleshooting\n\n### Bridge not connecting to IRC\n- Check firewall rules for IRC ports (6667, 6697)\n- Verify IRC server allows multiple connections from same IP\n- Check IRC nick is not already in use\n\n### XMPP messages not bridging\n- Verify Prosody component configuration\n- Check component secret matches\n- Ensure MUC exists and bridge has joined\n- Review Prosody logs: `/var/log/prosody/prosody.log`\n\n### Discord messages delayed\n- Check Portal API is responding (\u003c 100ms)\n- Verify identity cache is working (check logs)\n- Monitor event bus queue depth\n\n## Contributing\n\n1. Fork the repository\n2. Create a feature branch\n3. Add tests for new features\n4. Ensure all tests pass: `uv run pytest tests -v`\n5. Run linters: `uv run ruff check src tests`\n6. Submit a pull request\n\n## License\n\nGPL3 License - see [LICENSE](LICENSE) file for details.\n\n## Acknowledgments\n\n- Built for [All Things Linux](https://discord.gg/linux)\n- Uses [discord.py](https://github.com/Rapptz/discord.py) for Discord\n- Uses [pydle](https://github.com/Shizmob/pydle) for IRC\n- Uses [slixmpp](https://github.com/poezio/slixmpp) for XMPP\n\n---\n\n**Status**: Production-ready • **Maintained**: Yes • **Tests**: 654 passing\n","funding_links":["https://opencollective.com/allthingslinux"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fallthingslinux%2Fbridge","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fallthingslinux%2Fbridge","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fallthingslinux%2Fbridge/lists"}