8.1 KiB
✅ Final Working Solution: Kosmi ↔ IRC Relay
Date: October 31, 2025, 1:10 PM
Status: ✅ FULLY FUNCTIONAL - BIDIRECTIONAL RELAY WORKING
Summary
Successfully implemented a fully working bidirectional message relay between Kosmi and IRC using a Playwright-based UI automation approach.
Test Results
✅ IRC → Kosmi: Working
✅ Kosmi → IRC: Working
✅ Username formatting: Consistent with RemoteNickFormat
✅ Message echo prevention: Working (messages with [irc] prefix filtered out)
✅ Clean logging: Debug code removed, production-ready
Final Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Matterbridge Gateway │
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ IRC Bridge │◄───────►│ Kosmi Bridge │ │
│ │ (irc.zeronode) │ │ (kosmi.hyperspaceout)│ │
│ └──────────────────────┘ └──────────┬───────────┘ │
│ │ │
└───────────────────────────────────────────────┼─────────────────┘
│
┌───────────▼───────────┐
│ Playwright Native │
│ Client │
│ │
│ • Browser automation │
│ • WebSocket (receive) │
│ • UI automation (send)│
└───────────┬────────────┘
│
┌───────────▼───────────┐
│ Kosmi Web UI │
│ (app.kosmi.io) │
└───────────────────────┘
Implementation Details
Message Receiving (Kosmi → IRC)
- Method: WebSocket subscription via Playwright-intercepted connection
- Mechanism: JavaScript injection captures WebSocket messages in the browser
- Subscription:
subscription { newMessage(roomId: "...") { body time user { displayName username } } } - Processing: Messages polled from JavaScript queue every 500ms
Message Sending (IRC → Kosmi)
- Method: UI automation via Playwright
- Mechanism: JavaScript evaluation to interact with DOM
- Process:
- Find visible chat input element (textarea, contenteditable, or text input)
- Set input value to message text
- Dispatch input/change events
- Trigger send via button click or Enter key press
Why This Approach?
After extensive investigation, we discovered:
- ❌ Direct WebSocket Connection: Fails with 403 Forbidden (authentication/bot detection)
- ❌ HTTP POST GraphQL Mutation: API only supports auth mutations (
anonLogin,slackLogin), notsendMessage - ❌ WebSocket Mutation via Playwright: Connection closes immediately after sending mutation (protocol/auth issues)
- ✅ UI Automation: Works reliably because it mimics real user interaction
Key Files
1. bridge/kosmi/native_client.go
The Playwright-based client implementation:
- Launches headless Chromium browser
- Injects WebSocket access layer
- Navigates to Kosmi room
- Subscribes to messages via WebSocket
- Sends messages via UI automation
2. bridge/kosmi/kosmi.go
The Matterbridge bridge implementation:
- Implements
bridge.Bridgerinterface - Manages
NativeClientlifecycle - Handles message routing
- Filters echo messages (prevents loops)
3. matterbridge.toml
Configuration file:
[kosmi.hyperspaceout]
RoomURL="https://app.kosmi.io/room/@hyperspaceout"
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
[irc.zeronode]
Server="irc.zeronode.net:6697"
Nick="kosmi-relay"
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
UseTLS=true
Message Flow
IRC → Kosmi
- User sends message in IRC:
Testing from IRC - IRC bridge receives PRIVMSG
- Matterbridge formats with
RemoteNickFormat:[irc] <username> Testing from IRC - Kosmi bridge receives message
NativeClient.SendMessage()uses UI automation- JavaScript finds chat input, sets value, triggers send
- Message appears in Kosmi chat
Kosmi → IRC
- User sends message in Kosmi:
Testing from Kosmi - WebSocket subscription receives
newMessageevent - JavaScript queue captures the message
pollMessages()retrieves from queue- Kosmi bridge filters echo messages (checks for
[irc]prefix) - Matterbridge formats with
RemoteNickFormat:[kosmi] <username> Testing from Kosmi - IRC bridge sends to channel
- Message appears in IRC
Echo Prevention
Messages are tagged with protocol prefixes via RemoteNickFormat:
- IRC messages sent to Kosmi:
[irc] <username> message - Kosmi messages sent to IRC:
[kosmi] <username> message
The Kosmi bridge filters out messages starting with [irc] to prevent echoing our own messages back.
Deployment
Docker Compose
services:
matterbridge:
build: .
container_name: kosmi-irc-relay
volumes:
- ./matterbridge.toml:/app/matterbridge.toml:ro
restart: unless-stopped
Running
docker-compose up -d --build
docker-compose logs -f
Performance Characteristics
- Startup Time: ~10 seconds (Playwright browser launch + page load)
- Message Latency:
- IRC → Kosmi: ~100-500ms (UI automation)
- Kosmi → IRC: ~500-1000ms (polling interval)
- Resource Usage:
- Memory: ~300-400 MB (Chromium browser)
- CPU: Low after initialization
Future Improvements
Potential Optimizations
- Reduce Polling Interval: Could decrease from 500ms to 250ms for lower latency
- WebSocket Send: If Kosmi's auth/protocol can be reverse-engineered properly
- Direct GraphQL API: If Kosmi exposes a
sendMessagemutation in the future
Known Limitations
- Browser Required: Must run full Chromium browser (can be headless)
- Polling Latency: 500ms delay for incoming messages
- UI Dependency: Breaks if Kosmi changes their UI structure (input selectors)
Troubleshooting
Common Issues
Problem: "Could not find chat input element"
Solution: Kosmi may have changed their UI. Update selectors in SendMessage() method.
Problem: Messages not appearing in Kosmi
Solution: Check browser console logs, verify UI automation script is working.
Problem: WebSocket not connecting
Solution: Check network connectivity, verify Kosmi URL is correct.
Problem: Echo loop (messages keep bouncing)
Solution: Verify RemoteNickFormat is set correctly and echo filter is working.
Conclusion
After extensive troubleshooting and multiple implementation attempts (direct WebSocket, HTTP POST, WebSocket mutations), we successfully achieved bidirectional message relay using Playwright UI automation. This approach is reliable, maintainable, and production-ready.
The relay now successfully:
✅ Sends messages from IRC to Kosmi
✅ Receives messages from Kosmi to IRC
✅ Prevents message echo loops
✅ Formats usernames consistently
✅ Runs in Docker with minimal configuration
Status: Production-ready ✅