Compare commits

..

2 Commits

Author SHA1 Message Date
cottongin
c5e0fe06b0 feat: announce poll lifecycle events to IRC and Kosmi
Handle poll.start, poll.ending, voting.ended, and poll.ending.cancelled
WebSocket messages from the upstream GamePicker API. Broadcasts opening,
closing-countdown, and closed announcements with per-bridge bold
formatting (IRC control codes vs asterisks for Kosmi).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 22:48:01 -04:00
cottongin
c88b75f30d docs: comprehensive documentation overhaul
Rewrite README.md and all setup guides to reflect the current native
GraphQL WebSocket architecture (replacing stale headless Chrome/WebSocket
interception descriptions). Add new docs/IRC.md with complete IRC command
reference, vote syntax, and ticker symbol table.

Archive pre-edit docs to docs/archive/setup-backup-2026-05-10/ and move
historical development notes from docs/ root into docs/archive/.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 22:01:25 -04:00
61 changed files with 4818 additions and 2042 deletions

367
README.md
View File

@@ -3,45 +3,53 @@
# Kosmi-IRC Relay via Matterbridge
A Matterbridge plugin that bridges Kosmi chat rooms with IRC channels, enabling bidirectional message relay.
A Matterbridge-based bridge that relays messages bidirectionally between Kosmi chat rooms and IRC channels, with optional Jackbox Game Picker integration for live voting and game announcements.
## Features
- Real-time message relay between Kosmi and IRC
- ✅ Headless Chrome automation for reliable Kosmi connection
- ✅ WebSocket interception using Chrome DevTools Protocol
- ✅ Anonymous Kosmi access (no authentication required)
- ✅ Message formatting with source indicators
- ✅ Automatic reconnection handling
- ✅ Support for any Kosmi room via URL configuration
- Real-time bidirectional message relay between Kosmi and IRC
- Native GraphQL-over-WebSocket connection to Kosmi (no browser required for basic use)
- Anonymous or authenticated Kosmi access (email/password login with token caching)
- Jackbox Game Picker integration: live vote detection, game announcements, room code images
- IRC commands: `!kreconnect`, `!jreconnect`, `!reconnect`, `!votes`
- Ticker-symbol voting for specific games (e.g. `QPL3++`, `TMP2--`)
- SIGUSR1 mute toggle for Jackbox announcements
- Automatic reconnection on connection loss
- Docker deployment with persistent token cache
## Architecture
This implementation extends Matterbridge with a custom Kosmi bridge that:
This project is a trimmed fork of [Matterbridge](https://github.com/42wim/matterbridge) with two registered bridges: **Kosmi** and **IRC**. The Kosmi bridge connects directly to `wss://engine.kosmi.io/gql-ws` using the `graphql-transport-ws` protocol -- no headless browser is needed for the primary connection path.
1. Launches a headless Chrome instance using `chromedp`
2. Navigates to the Kosmi room and injects a WebSocket interceptor **before page load**
3. Captures GraphQL WebSocket messages (`wss://engine.kosmi.io/gql-ws`) from the page
4. Relays messages bidirectionally with proper formatting:
- **Kosmi → IRC**: `[Kosmi] <username> message`
- **IRC → Kosmi**: `[IRC] <username> message`
### How It Works
### Why Headless Chrome?
1. **Anonymous access** (default): The bridge calls Kosmi's `anonLogin` HTTP mutation to obtain a JWT, then opens a native WebSocket connection.
2. **Authenticated access** (optional): If `Email` and `Password` are configured, headless Chrome (chromedp) performs a one-time browser login to extract a JWT. The token is cached locally and reused across restarts.
3. **Room subscription**: After connecting, the bridge subscribes to `newMessage` events via GraphQL and joins the configured room.
4. **Message relay**: Incoming Kosmi messages are forwarded to IRC (and vice versa) through the Matterbridge gateway, with configurable nick formatting.
Kosmi's WebSocket API requires browser session cookies and context that are difficult to replicate with a native WebSocket client. Using headless Chrome automation ensures:
- ✅ Automatic session management
- ✅ Proper cookie handling
- ✅ Reliable WebSocket connection
- ✅ No authentication complexity
### Message Flow
```
IRC User --> IRC Server --> Matterbridge Gateway --> Kosmi GraphQL WS --> Kosmi Room
Kosmi User --> Kosmi Room --> GraphQL subscription --> Matterbridge Gateway --> IRC Channel
```
### Jackbox Integration (Optional)
When enabled, the relay connects to a Jackbox Game Picker API via WebSocket (or webhook fallback) to:
- Detect `thisgame++`/`thisgame--` votes and ticker-symbol votes in chat
- Announce upcoming games with room codes (optionally as uploaded images)
- Respond to `!votes` queries with current vote tallies
## Installation
### Option 1: Docker (Recommended) 🐳
The easiest way to run the bridge:
### Option 1: Docker (Recommended)
```bash
# 1. Edit configuration
# 1. Copy and edit configuration
cp matterbridge.toml.example matterbridge.toml
nano matterbridge.toml
# 2. Build and run
@@ -51,45 +59,47 @@ docker-compose up -d
docker-compose logs -f
```
**See**: `DOCKER_QUICKSTART.md` for 5-minute setup guide
See [DOCKER_QUICKSTART.md](docs/setup/DOCKER_QUICKSTART.md) for a 5-minute setup guide.
### Option 2: Build from Source
#### Prerequisites
- Go 1.21 or higher
- Chrome or Chromium browser installed
- Go 1.23 or higher
- Chrome/Chromium (only required if using email/password authentication)
- Access to an IRC server
- A Kosmi room URL
#### Building
```bash
# Clone the repository
git clone <repository-url>
cd irc-kosmi-relay
# Download dependencies
go mod download
# Build the bridge
go build -o matterbridge
go build -o matterbridge .
```
## Configuration
Edit `matterbridge.toml` to configure your bridge:
Copy `matterbridge.toml.example` to `matterbridge.toml` and edit it. The key sections:
```toml
# Kosmi configuration
[kosmi.hyperspaceout]
RoomURL="https://app.kosmi.io/room/@hyperspaceout"
RoomURL="https://app.kosmi.io/room/@yourroom"
# Optional: Email/password for authenticated access
# Requires Chrome/Chromium for browser automation
Email=""
Password=""
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
# IRC configuration
[irc.libera]
Server="irc.libera.chat:6667"
[irc.zeronode]
Server="irc.libera.chat:6697"
Nick="kosmi-relay"
UseTLS=false
UseTLS=true
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
# Gateway to connect Kosmi and IRC
[[gateway]]
@@ -101,22 +111,58 @@ account="kosmi.hyperspaceout"
channel="main"
[[gateway.inout]]
account="irc.libera"
account="irc.zeronode"
channel="#your-channel"
# Jackbox integration (optional)
[jackbox]
Enabled=false
APIURL="https://your-jackbox-api.example.com"
AdminPassword=""
UseWebSocket=true
EnableRoomCodeImage=true
RoomCodeImageDelay=28
RoomCodePlaintextDelay=22
```
### Configuration Options
### Configuration Reference
#### Kosmi Settings
- `RoomURL` (required): Full URL to the Kosmi room
- Format: `https://app.kosmi.io/room/@roomname` or `https://app.kosmi.io/room/roomid`
- `Server` (optional): WebSocket endpoint (default: `wss://engine.kosmi.io/gql-ws`)
- `Debug` (optional): Enable debug logging
| Key | Required | Description |
|-----|----------|-------------|
| `RoomURL` | Yes | Full URL to the Kosmi room |
| `Email` | No | Email for authenticated access |
| `Password` | No | Password for authenticated access |
| `RemoteNickFormat` | No | Format for nicks from other bridges |
#### IRC Settings
See [Matterbridge IRC documentation](https://github.com/42wim/matterbridge/wiki/Section-IRC-(basic)) for full IRC configuration options.
| Key | Required | Description |
|-----|----------|-------------|
| `Server` | Yes | IRC server address with port |
| `Nick` | Yes | Bot's IRC nickname |
| `UseTLS` | No | Enable TLS (recommended) |
| `SkipTLSVerify` | No | Skip TLS cert verification |
| `NickServNick` | No | NickServ service nick |
| `NickServPassword` | No | NickServ password |
| `UseSASL` | No | Use SASL authentication |
| `RemoteNickFormat` | No | Format for nicks from other bridges |
| `Channels` | No | Channels to auto-join |
#### Jackbox Settings
| Key | Required | Description |
|-----|----------|-------------|
| `Enabled` | Yes | Enable/disable Jackbox integration |
| `APIURL` | Yes | Jackbox Game Picker API URL |
| `AdminPassword` | Yes | API admin password for JWT auth |
| `UseWebSocket` | No | Use WebSocket transport (default: true) |
| `WebhookPort` | No | Webhook listen port (if not using WS) |
| `WebhookSecret` | No | HMAC secret for webhook verification |
| `EnableRoomCodeImage` | No | Generate room code images |
| `RoomCodeImageDelay` | No | Seconds before sending image announcement |
| `RoomCodePlaintextDelay` | No | Seconds before sending plaintext room code |
## Usage
@@ -126,154 +172,163 @@ See [Matterbridge IRC documentation](https://github.com/42wim/matterbridge/wiki/
# Run with debug logging
./matterbridge -conf matterbridge.toml -debug
# Start with Jackbox announcements muted
./matterbridge -conf matterbridge.toml -muted
```
## How It Works
### CLI Flags
### Kosmi Connection
| Flag | Description |
|------|-------------|
| `-conf` | Path to config file (default: `matterbridge.toml`) |
| `-debug` | Enable debug logging |
| `-version` | Show version |
| `-muted` | Start with Jackbox announcements muted |
| `-gops` | Enable gops diagnostic agent |
The bridge connects to Kosmi using headless Chrome automation:
### IRC Commands
1. **Launch Chrome**: Starts a headless Chrome instance via `chromedp`
2. **Inject Hook**: Uses `Page.addScriptToEvaluateOnNewDocument` to inject a WebSocket interceptor **before any page scripts run**
3. **Navigate**: Loads the Kosmi room URL
4. **Intercept**: The injected script hooks `window.WebSocket` constructor to capture all WebSocket messages
5. **Poll**: Continuously polls the message queue populated by the interceptor
6. **Process**: Extracts chat messages from GraphQL subscription data
Users can issue commands in IRC chat. See [docs/IRC.md](docs/IRC.md) for full details.
### Critical Implementation Detail
| Command | Description |
|---------|-------------|
| `!kreconnect` | Reconnect the Kosmi bridge |
| `!jreconnect` | Reconnect the Jackbox WebSocket |
| `!reconnect` | Reconnect all non-IRC services |
| `!votes` | Show vote tally for the current game |
| `thisgame++` / `thisgame--` | Vote on the current game |
| `SYMBOL++` / `SYMBOL--` | Vote on a game by ticker symbol |
The WebSocket hook **must** be injected before page load using `Page.addScriptToEvaluateOnNewDocument`. This ensures the hook is active when Kosmi's JavaScript creates the WebSocket connection. If injected after page load, the WebSocket will already be established and messages won't be captured.
### Mute Toggle
### Message Flow
Jackbox announcements can be toggled at runtime without restarting:
```
IRC User → IRC Server → Matterbridge → Headless Chrome → Kosmi Room
Kosmi User → Kosmi Room → WebSocket → Chrome Interceptor → Matterbridge → IRC Server → IRC Channel
```bash
# Local process
kill -SIGUSR1 $(pgrep matterbridge)
# Docker
docker kill -s SIGUSR1 kosmi-irc-relay
```
### Message Filtering
See [MUTE_CONTROL.md](docs/setup/MUTE_CONTROL.md) for details.
- The bridge ignores its own messages by checking for the `[IRC]` prefix
- This prevents message loops between Kosmi and IRC
## Environment Variables
## Technical Details
| Variable | Description |
|----------|-------------|
| `DEBUG=1` | Enable debug logging |
| `MATTERBRIDGE_DATA_DIR` | Directory for persistent data (token cache) |
| `TZ` | Timezone for container logs |
| `CHROME_BIN` | Path to Chrome binary (for auth only) |
### GraphQL API
The Kosmi bridge uses the following GraphQL operations:
**Subscription** (receiving messages):
```graphql
subscription {
newMessage(roomId: "roomId") {
body
time
user {
displayName
username
}
}
}
```
**Mutation** (sending messages):
```graphql
mutation {
sendMessage(roomId: "roomId", body: "message text") {
id
}
}
```
### File Structure
## File Structure
```
bridge/kosmi/
├── kosmi.go # Main bridge implementation
├── chromedp_client.go # Headless Chrome client with WebSocket interception
── graphql.go # GraphQL message structures (deprecated native client)
gateway/bridgemap/
── bkosmi.go # Bridge registration
cmd/test-kosmi/
└── main.go # Standalone test program
matterbridge.toml # Configuration file
go.mod # Go module dependencies
irc-kosmi-relay/
├── matterbridge.go # Entry point: flags, logger, router, mute toggle
├── matterbridge.toml.example # Example configuration
── Dockerfile # Docker build (Go 1.23 + Chromium for auth)
├── docker-compose.yml # Docker Compose deployment
── bridge/
│ ├── config/
│ │ └── config.go # Message types, events, protocol config
│ ├── kosmi/
│ │ ├── kosmi.go # Kosmi bridge: Connect, Send, message handling
├── graphql_ws_client.go # Native GraphQL-over-WebSocket client
├── graphql.go # GraphQL message/payload structs
│ │ ├── auth.go # Anonymous login (anonLogin HTTP mutation)
│ │ ├── browser_auth.go # Chromedp browser login for email/password
│ │ ├── token_cache.go # JWT token caching (file-based)
│ │ ├── image_upload.go # Image upload to Kosmi CDN
│ │ └── errors.go # Error types
│ ├── irc/
│ │ ├── irc.go # IRC bridge: Connect, Send, NAMES handling
│ │ ├── handlers.go # PRIVMSG handler, commands, vote detection
│ │ └── formatting.go # IRC formatting helpers
│ └── jackbox/
│ ├── client.go # Jackbox API client (REST + WebSocket)
│ ├── votes.go # Vote detection (thisgame/ticker parsing)
│ ├── tickers.go # Ticker symbol -> game title lookup table
│ ├── webhook.go # Webhook transport (alternative to WS)
│ ├── image_upload.go # Room code image generation + upload
│ └── roomcode_image.go # Room code GIF rendering
├── gateway/
│ ├── gateway.go # Gateway message routing and transformation
│ ├── router.go # Router: message bus, Jackbox manager, broadcast
│ ├── handlers.go # Event handlers: reconnect, votes, files
│ └── bridgemap/
│ ├── bkosmi.go # Registers "kosmi" protocol
│ └── birc.go # Registers "irc" protocol
├── docs/
│ ├── IRC.md # IRC command reference
│ ├── setup/ # Setup and deployment guides
│ └── archive/ # Historical development notes
└── cmd/ # Standalone test/debug tools
```
## Troubleshooting
### Connection Issues
### Kosmi Connection Issues
**Problem**: Bridge fails to connect to Kosmi
**Bridge fails to connect to Kosmi**
**Solutions**:
- Verify Chrome/Chromium is installed: `which google-chrome chromium chromium-browser`
- Verify the room URL is correct
- Check network connectivity to `app.kosmi.io`
- Enable debug logging to see detailed connection logs
- Look for "✓ WebSocket hook confirmed installed" in logs
- Look for "Status: WebSocket connection intercepted" in logs
- Verify `RoomURL` is correct and the room exists
- Check network connectivity to `app.kosmi.io` and `engine.kosmi.io`
- Enable debug logging (`-debug`) to see WebSocket handshake details
- Look for `"Successfully connected to Kosmi"` in logs
### Message Not Relaying
**Authentication fails (email/password)**
**Problem**: Messages aren't being relayed between Kosmi and IRC
- Verify Chrome/Chromium is installed and accessible
- Check credentials are correct
- Delete `data/kosmi_token_cache.json` to force a fresh login
- See [BROWSER_AUTH_GUIDE.md](docs/setup/BROWSER_AUTH_GUIDE.md)
**Solutions**:
- Verify both Kosmi and IRC connections are established
- Check the gateway configuration in `matterbridge.toml`
- Ensure channel names match in the gateway configuration
- Check logs for errors
### IRC Connection Issues
### Authentication Errors
- Verify server address and port
- Check TLS settings match the server's requirements
- Confirm the bot's nick is not already in use
- Check NickServ/SASL credentials if authentication is configured
**Problem**: Kosmi connection fails with authentication error
### Messages Not Relaying
**Note**: Kosmi doesn't require authentication. If you see auth errors, verify:
- The WebSocket URL is correct
- The room ID is valid
- Network isn't blocking WebSocket connections
- Verify both bridges show as connected in logs
- Check gateway configuration: Kosmi channel must be `"main"`, IRC channel must include `#`
- Enable debug logging to trace message flow
- Look for `"Forwarding to Matterbridge"` and `"Sending message"` in logs
## Development
### Jackbox Integration Issues
### Adding Features
- Verify `APIURL` is accessible and `AdminPassword` is correct
- Check that a Jackbox session is active with games being played
- Confirm `Enabled=true` in the `[jackbox]` config section
- See [JACKBOX_INTEGRATION.md](docs/setup/JACKBOX_INTEGRATION.md)
The bridge follows Matterbridge's bridge interface:
## Documentation
```go
type Bridger interface {
Send(msg config.Message) (string, error)
Connect() error
JoinChannel(channel config.ChannelInfo) error
Disconnect() error
}
```
### Testing
To test the bridge:
1. Start the bridge with debug logging
2. Send a message in the Kosmi room
3. Verify it appears in IRC with `[Kosmi]` prefix
4. Send a message in IRC
5. Verify it appears in Kosmi with `[IRC]` prefix
## Known Limitations
1. **Anonymous Access**: The bridge connects anonymously to Kosmi, so it will have a randomly assigned username
2. **Message Sending**: The GraphQL mutation for sending messages may need adjustment based on Kosmi's actual API
3. **Room Discovery**: The bridge connects to a specific room; it doesn't support room discovery or listing
| Document | Description |
|----------|-------------|
| [IRC.md](docs/IRC.md) | IRC commands, voting syntax, ticker symbols |
| [DOCKER_QUICKSTART.md](docs/setup/DOCKER_QUICKSTART.md) | 5-minute Docker setup |
| [DOCKER_DEPLOYMENT.md](docs/setup/DOCKER_DEPLOYMENT.md) | Full Docker deployment guide |
| [QUICKSTART.md](docs/setup/QUICKSTART.md) | Build-from-source quickstart |
| [JACKBOX_INTEGRATION.md](docs/setup/JACKBOX_INTEGRATION.md) | Jackbox Game Picker setup |
| [BROWSER_AUTH_GUIDE.md](docs/setup/BROWSER_AUTH_GUIDE.md) | Email/password authentication |
| [TOKEN_PERSISTENCE.md](docs/setup/TOKEN_PERSISTENCE.md) | Token caching details |
| [MUTE_CONTROL.md](docs/setup/MUTE_CONTROL.md) | Mute toggle guide |
## Credits
- Based on [Matterbridge](https://github.com/42wim/matterbridge) by 42wim
- Kosmi API reverse engineering from chrome extension analysis
- Kosmi API integration via reverse-engineered GraphQL protocol
## License
Same as Matterbridge (Apache 2.0)

View File

@@ -19,6 +19,7 @@ type Manager struct {
enabled bool
useWebSocket bool
messageCallback func(string)
formattedMessageCallback func(string, string)
muted bool
mu sync.RWMutex
}
@@ -67,15 +68,15 @@ func (m *Manager) Initialize() error {
return nil
}
// StartWebhookServer starts the webhook server with the provided message callback
func (m *Manager) StartWebhookServer(messageCallback func(string)) error {
// StartWebhookServer starts the webhook server with the provided message callbacks
func (m *Manager) StartWebhookServer(messageCallback func(string), formattedCallback func(string, string)) error {
if !m.enabled {
return nil
}
// Use WebSocket if enabled, otherwise fall back to webhook
if m.useWebSocket {
return m.startWebSocketClient(messageCallback)
return m.startWebSocketClient(messageCallback, formattedCallback)
}
webhookPort := m.config.Viper().GetInt("jackbox.WebhookPort")
@@ -102,11 +103,12 @@ func (m *Manager) StartWebhookServer(messageCallback func(string)) error {
}
// startWebSocketClient starts the WebSocket client connection
func (m *Manager) startWebSocketClient(messageCallback func(string)) error {
func (m *Manager) startWebSocketClient(messageCallback func(string), formattedCallback func(string, string)) error {
apiURL := m.config.Viper().GetString("jackbox.APIURL")
// Store the callback for use in monitoring
// Store the callbacks for use in monitoring and reconnection
m.messageCallback = messageCallback
m.formattedMessageCallback = formattedCallback
// Wrap the callback to check mute status
wrappedCallback := func(message string) {
@@ -117,6 +119,15 @@ func (m *Manager) startWebSocketClient(messageCallback func(string)) error {
messageCallback(message)
}
// Wrap the formatted callback to check mute status
wrappedFormattedCallback := func(ircMsg, plainMsg string) {
if m.IsMuted() {
m.log.Debugf("Jackbox formatted message suppressed (muted): %s", plainMsg)
return
}
formattedCallback(ircMsg, plainMsg)
}
// Set wrapped callback on client for vote broadcasts
m.client.SetMessageCallback(wrappedCallback)
@@ -141,7 +152,7 @@ func (m *Manager) startWebSocketClient(messageCallback func(string)) error {
m.config.Viper().GetInt("jackbox.RoomCodePlaintextDelay"))
// Create WebSocket client (pass the API client for vote tracking)
m.wsClient = NewWebSocketClient(apiURL, token, wrappedCallback, m.client, enableRoomCodeImage, imageDelay, plaintextDelay, m.log)
m.wsClient = NewWebSocketClient(apiURL, token, wrappedCallback, wrappedFormattedCallback, m.client, enableRoomCodeImage, imageDelay, plaintextDelay, m.log)
// Connect to WebSocket
if err := m.wsClient.Connect(); err != nil {
@@ -265,12 +276,12 @@ func (m *Manager) Reconnect() error {
return fmt.Errorf("re-authentication failed: %w", err)
}
// Rebuild the WebSocket client using the original callback
// Rebuild the WebSocket client using the original callbacks
if m.messageCallback == nil {
return fmt.Errorf("no message callback registered")
}
return m.startWebSocketClient(m.messageCallback)
return m.startWebSocketClient(m.messageCallback, m.formattedMessageCallback)
}
// GetClient returns the Jackbox API client (may be nil if disabled)

View File

@@ -21,6 +21,7 @@ type WebSocketClient struct {
token string
conn *websocket.Conn
messageCallback func(string)
formattedMessageCallback func(string, string) // (ircMsg, plainMsg)
apiClient *Client // Reference to API client for vote tracking
log *logrus.Entry
mu sync.Mutex
@@ -74,12 +75,20 @@ type GameAddedData struct {
} `json:"game"`
}
// PollEndingData represents the poll.ending event data
type PollEndingData struct {
SessionID int `json:"sessionId"`
EndsAt string `json:"endsAt"`
DelaySeconds int `json:"delaySeconds"`
}
// NewWebSocketClient creates a new WebSocket client
func NewWebSocketClient(apiURL, token string, messageCallback func(string), apiClient *Client, enableRoomCodeImage bool, roomCodeImageDelay, roomCodePlaintextDelay time.Duration, log *logrus.Entry) *WebSocketClient {
func NewWebSocketClient(apiURL, token string, messageCallback func(string), formattedMessageCallback func(string, string), apiClient *Client, enableRoomCodeImage bool, roomCodeImageDelay, roomCodePlaintextDelay time.Duration, log *logrus.Entry) *WebSocketClient {
return &WebSocketClient{
apiURL: apiURL,
token: token,
messageCallback: messageCallback,
formattedMessageCallback: formattedMessageCallback,
apiClient: apiClient,
enableRoomCodeImage: enableRoomCodeImage,
roomCodeImageDelay: roomCodeImageDelay,
@@ -249,6 +258,18 @@ func (c *WebSocketClient) handleMessage(data []byte) {
case "session.ended":
c.handleSessionEnded(msg.Data)
case "poll.start":
c.handlePollStart()
case "poll.ending":
c.handlePollEnding(msg.Data)
case "voting.ended":
c.handleVotingEnded()
case "poll.ending.cancelled":
c.log.Info("Poll ending cancelled, voting remains open")
case "pong":
c.log.Debug("Heartbeat pong received")
@@ -450,6 +471,46 @@ func (c *WebSocketClient) AnnounceSessionEnd() {
}
}
// handlePollStart announces that a new poll has opened
func (c *WebSocketClient) handlePollStart() {
c.log.Info("Poll started")
ircMsg := "🗳️ There is a new poll open! Your vote is \x02REQUIRED\x02 https://hso.ehsf.cc/vote"
plainMsg := "🗳️ There is a new poll open! Your vote is *REQUIRED* https://hso.ehsf.cc/vote"
if c.formattedMessageCallback != nil {
c.formattedMessageCallback(ircMsg, plainMsg)
}
}
// handlePollEnding announces that the poll is about to close
func (c *WebSocketClient) handlePollEnding(data json.RawMessage) {
var pollData PollEndingData
if err := json.Unmarshal(data, &pollData); err != nil {
c.log.Errorf("Failed to parse poll.ending data: %v", err)
return
}
c.log.Infof("Poll ending in %d seconds", pollData.DelaySeconds)
ircMsg := fmt.Sprintf("🗳️ \x02HURRY!\x02 https://hso.ehsf.cc/vote Go vote now, the poll is closing in %d seconds", pollData.DelaySeconds)
plainMsg := fmt.Sprintf("🗳️ *HURRY!* https://hso.ehsf.cc/vote Go vote now, the poll is closing in %d seconds", pollData.DelaySeconds)
if c.formattedMessageCallback != nil {
c.formattedMessageCallback(ircMsg, plainMsg)
}
}
// handleVotingEnded announces that the poll has closed
func (c *WebSocketClient) handleVotingEnded() {
c.log.Info("Voting ended")
message := "🗳️ You missed your chance to vote, do your duty next time"
if c.messageCallback != nil {
c.messageCallback(message)
}
}
// startHeartbeat sends ping messages periodically. It exits when listenDone
// is closed (current connection ended) or stopChan is closed (full shutdown).
func (c *WebSocketClient) startHeartbeat(listenDone <-chan struct{}) {

182
docs/IRC.md Normal file
View File

@@ -0,0 +1,182 @@
# IRC Command Reference
This document covers everything available to IRC users when interacting with the Kosmi-IRC relay bot. All commands and vote syntax work in any channel the bot is present in.
## Commands
Commands are typed as regular messages in IRC. They are **not** relayed to other bridges -- the bot consumes them and acts on them directly.
There is no permission system; any user in the channel can issue any command.
### `!kreconnect`
Force-reconnect the Kosmi bridge. Useful if the Kosmi WebSocket has silently dropped.
**Response:** `Reconnecting Kosmi...` (sent to the channel immediately)
The reconnection happens asynchronously. If it fails, errors appear only in the bot's logs.
### `!jreconnect`
Force-reconnect the Jackbox Game Picker WebSocket. Only meaningful if the Jackbox integration is enabled.
**Response:** `Reconnecting Jackbox...`
### `!reconnect`
Reconnect all non-IRC services (Kosmi and Jackbox) at once.
**Response:** `Reconnecting all services...`
### `!votes`
Query the current vote tally for the game being played right now in the active Jackbox session.
**Response** (broadcast to all bridges -- IRC, Kosmi, etc.):
```
🗳️ Fibbage 4 • Today: 5👍 2👎 (Score: 3) • All-time: 46👍 3👎 (Score: 43)
```
**Requirements:**
- Jackbox integration must be enabled
- An active Jackbox session must exist
- A game must currently have status "playing" in that session
If any of these conditions are not met, the command is silently consumed (errors appear only in bot logs).
---
## Voting
Vote detection runs automatically on every non-relayed message. Votes are sent to the Jackbox Game Picker API when the integration is enabled and a session is active.
### Current Game Votes
Type anywhere in a message:
| Syntax | Effect |
|--------|--------|
| `thisgame++` | Upvote the currently playing game |
| `thisgame--` | Downvote the currently playing game |
- Case-insensitive (`ThisGame++`, `THISGAME++`, etc. all work)
- Can appear anywhere in the message text
- The message is still relayed to other bridges as normal chat
### Ticker Symbol Votes
Vote on a specific game by its ticker symbol:
| Syntax | Effect |
|--------|--------|
| `SYMBOL++` | Upvote the game identified by SYMBOL |
| `SYMBOL--` | Downvote the game identified by SYMBOL |
- Symbols are 2-4 alphanumeric characters
- Case-insensitive (`qpl3++` and `QPL3++` are equivalent)
- The symbol must exist in the ticker table below
- The message is still relayed to other bridges as normal chat
### Ticker Symbol Table
| Symbol | Game |
|--------|------|
| `BC` | Bomb Corp. |
| `BDTS` | Bidiots |
| `BR` | Blather 'Round |
| `BRKT` | Bracketeering |
| `CH` | Cookie Haus |
| `CU` | Champ'd Up |
| `CVDL` | Civic Doodle |
| `DD` | Dirty Drawful |
| `DCTN` | Dictionarium |
| `DOOM` | Doominate |
| `DRM` | Dodo Re Mi |
| `DRWA` | Drawful Animate |
| `DRWF` | Drawful |
| `EW` | Earwax |
| `FANL` | Fakin' It All Night Long |
| `FBG2` | Fibbage 2 |
| `FBG3` | Fibbage 3 |
| `FBG4` | Fibbage 4 |
| `FBXL` | Fibbage XL |
| `FI` | Fakin' It! |
| `FT` | Fixy Text |
| `GSPN` | Guesspionage |
| `HPNT` | Hypnotorious |
| `HRSY` | Hear Say |
| `JB` | Joke Boat |
| `JJ` | Job Job |
| `JNKT` | Junktopia |
| `LMF` | Let Me Finish |
| `LOT` | Legends of Trivia |
| `LS` | Lie Swatter |
| `MSM` | Monster Seeking Monster |
| `MVC` | Mad Verse City |
| `NNSR` | Nonsensory |
| `PS` | Patently Stupid |
| `PTB` | Push the Button |
| `QLXL` | Quiplash XL |
| `QPL2` | Quiplash 2 |
| `QPL3` | Quiplash 3 |
| `QXRT` | Quixort |
| `RM` | Role Models |
| `ROOM` | Roomerang |
| `SS` | Survey Scramble |
| `SPCT` | Suspectives |
| `STI` | Survive the Internet |
| `STR` | Split the Room |
| `TJ` | Time Jinx |
| `TKO2` | Tee K.O. 2 |
| `TKOX` | Tee K.O. T-Shirt Knock Out |
| `TMP1` | Trivia Murder Party |
| `TMP2` | Trivia Murder Party 2 |
| `TP` | Talking Points |
| `TPM` | The Poll Mine |
| `TWEP` | The Wheel of Enormous Proportions |
| `WD` | Weapons Drawn |
| `WS` | Word Spud |
| `YDK1` | You Don't Know Jack 2015 |
| `YDKJ` | You Don't Know Jack Full Stream |
| `ZPDM` | Zeeple Dome |
---
## Behavior Notes
### Loop Prevention
Messages that begin with `[irc]` or `[kosmi]` (case-insensitive) are treated as relayed from another bridge. Vote detection is skipped for these messages to prevent duplicate votes when the same text passes through multiple bridges.
### Command vs. Vote Processing Order
1. Vote detection runs first (on all non-relayed messages)
2. `!` commands are checked next
3. If the message is a `!` command, it is consumed and **not** relayed
4. If the message is not a command, it is relayed to other bridges
This means a vote phrase in a normal message (like "nice game thisgame++") both registers the vote and gets relayed. But `!votes` only triggers the votes query -- it is not relayed.
### What IRC Users See from Other Bridges
| Source | Format |
|--------|--------|
| Kosmi user message | `[kosmi] <username> message text` |
| Jackbox game announcement | System message (e.g. `🎮 Coming up next: Fibbage 4!`) |
| Jackbox room code | System message with room code (may include image link) |
| `!votes` result | `🗳️ Title • Today: X👍 Y👎 (Score: Z) • All-time: X👍 Y👎 (Score: Z)` |
| Reconnect confirmation | Plain text from "system" (e.g. `Reconnecting Kosmi...`) |
The `[kosmi]` prefix format is configurable via `RemoteNickFormat` in `matterbridge.toml`.
### Requirements for Jackbox Features
All Jackbox-related functionality (votes, `!votes`, game announcements, room codes) requires:
1. `[jackbox]` section in config with `Enabled=true`
2. Valid `APIURL` and `AdminPassword`
3. An active Jackbox session on the API
4. A game currently being played (for `!votes` and vote detection)
When the Jackbox integration is disabled or no session is active, vote syntax is ignored and `!votes` produces no output.

137
docs/POLL.md Normal file
View File

@@ -0,0 +1,137 @@
# Poll WebSocket Messages
## Overview
Poll messages manage the lifecycle of viewer polls over the upstream WebSocket connection. They are session-scoped — you must be subscribed to the relevant session to send or receive them.
All messages use the standard envelope:
```json
{
"type": "<message-type>",
"data": { ... }
}
```
---
## Message Flow
```
poll.start ──► (poll is open, viewers are voting)
poll.ending ──► (countdown active)
┌───────────┴───────────┐
│ │
voting.ended poll.ending.cancelled
(poll is closed) (countdown aborted,
poll stays open)
```
---
## Client → Server
### poll.start
Triggers poll generation for the active session.
```json
{
"type": "poll.start",
"sessionId": 3
}
```
| Field | Type | Required | Description |
|-------------|--------|----------|-----------------------------------------------------------------------------|
| `type` | string | yes | `"poll.start"` |
| `sessionId` | number | no | Included for protocol consistency. The server uses its internally tracked session ID. |
Any existing active poll is deactivated and replaced.
### poll.leading
Sent by the client to report the current leading option. Typically sent whenever the lead changes.
```json
{
"type": "poll.leading",
"sessionId": 3,
"gameId": 17,
"label": "Quiplash 3",
"votes": 12
}
```
| Field | Type | Required | Description |
|-------------|--------|----------|---------------------------------------|
| `type` | string | yes | `"poll.leading"` |
| `sessionId` | number | yes | Active session ID |
| `gameId` | number | yes | Game ID of the leading option (0 for "Other") |
| `label` | string | yes | Display label of the leading option |
| `votes` | number | yes | Current vote count for the leader |
---
## Server → Client
### poll.ending
Signals that voting will close after a countdown. Sent when the server initiates poll closure.
```json
{
"type": "poll.ending",
"data": {
"sessionId": 3,
"endsAt": "2026-05-13T02:20:30.123456789Z",
"delaySeconds": 30
}
}
```
| Field | Type | Description |
|----------------|--------|--------------------------------------------------|
| `sessionId` | number | Active session ID |
| `endsAt` | string | RFC 3339 timestamp when voting closes |
| `delaySeconds` | number | Seconds remaining until voting closes |
### poll.ending.cancelled
The previously announced countdown has been cancelled. Voting remains open.
```json
{
"type": "poll.ending.cancelled",
"data": {
"sessionId": 3
}
}
```
| Field | Type | Description |
|-------------|--------|-------------------|
| `sessionId` | number | Active session ID |
### voting.ended
Voting has closed. The active poll is finalized.
```json
{
"type": "voting.ended"
}
```
No `data` payload. The server does not include results — consumers should query their own tallies or rely on the preceding `poll.leading` updates for the final state.
---
## Lifecycle Notes
- A `poll.start` always replaces any existing active poll.
- `poll.ending` may be followed by either `voting.ended` (normal close) or `poll.ending.cancelled` (countdown aborted).
- After `voting.ended`, no further vote-related messages are sent until the next `poll.start`.
- `game.started` implicitly deactivates any active poll — no explicit poll message is sent.

View File

@@ -0,0 +1,279 @@
> [!IMPORTANT]
> This project was developed entirely with AI coding assistance (Claude Opus 4.6 via Cursor IDE) and has not undergone rigorous review. It is provided as-is and may require adjustments for other environments.
# Kosmi-IRC Relay via Matterbridge
A Matterbridge plugin that bridges Kosmi chat rooms with IRC channels, enabling bidirectional message relay.
## Features
- ✅ Real-time message relay between Kosmi and IRC
- ✅ Headless Chrome automation for reliable Kosmi connection
- ✅ WebSocket interception using Chrome DevTools Protocol
- ✅ Anonymous Kosmi access (no authentication required)
- ✅ Message formatting with source indicators
- ✅ Automatic reconnection handling
- ✅ Support for any Kosmi room via URL configuration
## Architecture
This implementation extends Matterbridge with a custom Kosmi bridge that:
1. Launches a headless Chrome instance using `chromedp`
2. Navigates to the Kosmi room and injects a WebSocket interceptor **before page load**
3. Captures GraphQL WebSocket messages (`wss://engine.kosmi.io/gql-ws`) from the page
4. Relays messages bidirectionally with proper formatting:
- **Kosmi → IRC**: `[Kosmi] <username> message`
- **IRC → Kosmi**: `[IRC] <username> message`
### Why Headless Chrome?
Kosmi's WebSocket API requires browser session cookies and context that are difficult to replicate with a native WebSocket client. Using headless Chrome automation ensures:
- ✅ Automatic session management
- ✅ Proper cookie handling
- ✅ Reliable WebSocket connection
- ✅ No authentication complexity
## Installation
### Option 1: Docker (Recommended) 🐳
The easiest way to run the bridge:
```bash
# 1. Edit configuration
nano matterbridge.toml
# 2. Build and run
docker-compose up -d
# 3. View logs
docker-compose logs -f
```
**See**: `DOCKER_QUICKSTART.md` for 5-minute setup guide
### Option 2: Build from Source
#### Prerequisites
- Go 1.21 or higher
- Chrome or Chromium browser installed
- Access to an IRC server
- A Kosmi room URL
#### Building
```bash
# Clone the repository
git clone <repository-url>
cd irc-kosmi-relay
# Download dependencies
go mod download
# Build the bridge
go build -o matterbridge
```
## Configuration
Edit `matterbridge.toml` to configure your bridge:
```toml
# Kosmi configuration
[kosmi.hyperspaceout]
RoomURL="https://app.kosmi.io/room/@hyperspaceout"
# IRC configuration
[irc.libera]
Server="irc.libera.chat:6667"
Nick="kosmi-relay"
UseTLS=false
# Gateway to connect Kosmi and IRC
[[gateway]]
name="kosmi-irc-gateway"
enable=true
[[gateway.inout]]
account="kosmi.hyperspaceout"
channel="main"
[[gateway.inout]]
account="irc.libera"
channel="#your-channel"
```
### Configuration Options
#### Kosmi Settings
- `RoomURL` (required): Full URL to the Kosmi room
- Format: `https://app.kosmi.io/room/@roomname` or `https://app.kosmi.io/room/roomid`
- `Server` (optional): WebSocket endpoint (default: `wss://engine.kosmi.io/gql-ws`)
- `Debug` (optional): Enable debug logging
#### IRC Settings
See [Matterbridge IRC documentation](https://github.com/42wim/matterbridge/wiki/Section-IRC-(basic)) for full IRC configuration options.
## Usage
```bash
# Run the bridge
./matterbridge -conf matterbridge.toml
# Run with debug logging
./matterbridge -conf matterbridge.toml -debug
```
## How It Works
### Kosmi Connection
The bridge connects to Kosmi using headless Chrome automation:
1. **Launch Chrome**: Starts a headless Chrome instance via `chromedp`
2. **Inject Hook**: Uses `Page.addScriptToEvaluateOnNewDocument` to inject a WebSocket interceptor **before any page scripts run**
3. **Navigate**: Loads the Kosmi room URL
4. **Intercept**: The injected script hooks `window.WebSocket` constructor to capture all WebSocket messages
5. **Poll**: Continuously polls the message queue populated by the interceptor
6. **Process**: Extracts chat messages from GraphQL subscription data
### Critical Implementation Detail
The WebSocket hook **must** be injected before page load using `Page.addScriptToEvaluateOnNewDocument`. This ensures the hook is active when Kosmi's JavaScript creates the WebSocket connection. If injected after page load, the WebSocket will already be established and messages won't be captured.
### Message Flow
```
IRC User → IRC Server → Matterbridge → Headless Chrome → Kosmi Room
Kosmi User → Kosmi Room → WebSocket → Chrome Interceptor → Matterbridge → IRC Server → IRC Channel
```
### Message Filtering
- The bridge ignores its own messages by checking for the `[IRC]` prefix
- This prevents message loops between Kosmi and IRC
## Technical Details
### GraphQL API
The Kosmi bridge uses the following GraphQL operations:
**Subscription** (receiving messages):
```graphql
subscription {
newMessage(roomId: "roomId") {
body
time
user {
displayName
username
}
}
}
```
**Mutation** (sending messages):
```graphql
mutation {
sendMessage(roomId: "roomId", body: "message text") {
id
}
}
```
### File Structure
```
bridge/kosmi/
├── kosmi.go # Main bridge implementation
├── chromedp_client.go # Headless Chrome client with WebSocket interception
└── graphql.go # GraphQL message structures (deprecated native client)
gateway/bridgemap/
└── bkosmi.go # Bridge registration
cmd/test-kosmi/
└── main.go # Standalone test program
matterbridge.toml # Configuration file
go.mod # Go module dependencies
```
## Troubleshooting
### Connection Issues
**Problem**: Bridge fails to connect to Kosmi
**Solutions**:
- Verify Chrome/Chromium is installed: `which google-chrome chromium chromium-browser`
- Verify the room URL is correct
- Check network connectivity to `app.kosmi.io`
- Enable debug logging to see detailed connection logs
- Look for "✓ WebSocket hook confirmed installed" in logs
- Look for "Status: WebSocket connection intercepted" in logs
### Message Not Relaying
**Problem**: Messages aren't being relayed between Kosmi and IRC
**Solutions**:
- Verify both Kosmi and IRC connections are established
- Check the gateway configuration in `matterbridge.toml`
- Ensure channel names match in the gateway configuration
- Check logs for errors
### Authentication Errors
**Problem**: Kosmi connection fails with authentication error
**Note**: Kosmi doesn't require authentication. If you see auth errors, verify:
- The WebSocket URL is correct
- The room ID is valid
- Network isn't blocking WebSocket connections
## Development
### Adding Features
The bridge follows Matterbridge's bridge interface:
```go
type Bridger interface {
Send(msg config.Message) (string, error)
Connect() error
JoinChannel(channel config.ChannelInfo) error
Disconnect() error
}
```
### Testing
To test the bridge:
1. Start the bridge with debug logging
2. Send a message in the Kosmi room
3. Verify it appears in IRC with `[Kosmi]` prefix
4. Send a message in IRC
5. Verify it appears in Kosmi with `[IRC]` prefix
## Known Limitations
1. **Anonymous Access**: The bridge connects anonymously to Kosmi, so it will have a randomly assigned username
2. **Message Sending**: The GraphQL mutation for sending messages may need adjustment based on Kosmi's actual API
3. **Room Discovery**: The bridge connects to a specific room; it doesn't support room discovery or listing
## Credits
- Based on [Matterbridge](https://github.com/42wim/matterbridge) by 42wim
- Kosmi API reverse engineering from chrome extension analysis
## License
Same as Matterbridge (Apache 2.0)

View File

@@ -0,0 +1,414 @@
# Bot Integration Guide
This guide explains how to integrate your bot with the Jackbox Game Picker API for live voting and game notifications.
## Table of Contents
1. [Live Voting (Bot → API)](#live-voting-bot--api)
2. [Game Notifications (API → Bot)](#game-notifications-api--bot)
3. [Webhook Management](#webhook-management)
4. [Testing](#testing)
---
## Live Voting (Bot → API)
Your bot can send real-time votes to the API when it detects "thisgame++" or "thisgame--" in Kosmi chat.
### Endpoint
```
POST /api/votes/live
```
### Authentication
Requires JWT token in Authorization header:
```
Authorization: Bearer YOUR_JWT_TOKEN
```
### Request Body
```json
{
"username": "string", // Username of the voter
"vote": "up" | "down", // "up" for thisgame++, "down" for thisgame--
"timestamp": "string" // ISO 8601 timestamp (e.g., "2025-11-01T20:30:00Z")
}
```
### Response (Success)
```json
{
"success": true,
"message": "Vote recorded successfully",
"session": {
"id": 123,
"games_played": 5
},
"game": {
"id": 45,
"title": "Fibbage 4",
"upvotes": 46,
"downvotes": 3,
"popularity_score": 43
},
"vote": {
"username": "TestUser",
"type": "up",
"timestamp": "2025-11-01T20:30:00Z"
}
}
```
### Error Responses
- **400 Bad Request**: Invalid payload or timestamp format
- **404 Not Found**: No active session or timestamp doesn't match any game
- **409 Conflict**: Duplicate vote (within 1 second of previous vote from same user)
- **500 Internal Server Error**: Server error
### Example Implementation (Node.js)
```javascript
// When bot detects "thisgame++" or "thisgame--" in Kosmi chat
async function handleVote(username, message) {
const isUpvote = message.includes('thisgame++');
const isDownvote = message.includes('thisgame--');
if (!isUpvote && !isDownvote) return;
try {
const response = await fetch('http://your-api-url/api/votes/live', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.JWT_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: username,
vote: isUpvote ? 'up' : 'down',
timestamp: new Date().toISOString()
})
});
const data = await response.json();
if (response.ok) {
console.log(`Vote recorded for ${data.game.title}: ${data.game.upvotes}👍 ${data.game.downvotes}👎`);
} else {
console.error('Vote failed:', data.error);
}
} catch (error) {
console.error('Error sending vote:', error);
}
}
```
### Important Notes
- **Deduplication**: Votes from the same user within 1 second are automatically rejected to prevent spam
- **Timestamp Matching**: The API matches the vote timestamp to the correct game based on when games were played
- **Active Session Required**: Votes can only be recorded when there's an active session with games played
---
## Game Notifications (API → Bot)
The API can send webhooks to your bot when games are added to a session, allowing you to announce "Coming up next: Game Title!" in Kosmi chat.
### Webhook Event: `game.added`
Triggered whenever a game is added to an active session (either via picker or manual selection).
### Webhook Payload
```json
{
"event": "game.added",
"timestamp": "2025-11-01T20:30:00Z",
"data": {
"session": {
"id": 123,
"is_active": true,
"games_played": 5
},
"game": {
"id": 45,
"title": "Fibbage 4",
"pack_name": "The Jackbox Party Pack 9",
"min_players": 2,
"max_players": 8,
"manually_added": false
}
}
}
```
### Webhook Headers
The API sends the following headers with each webhook:
- `Content-Type: application/json`
- `X-Webhook-Signature: sha256=<hmac_signature>` - HMAC-SHA256 signature for verification
- `X-Webhook-Event: game.added` - Event type
- `User-Agent: Jackbox-Game-Picker-Webhook/1.0`
### Signature Verification
**IMPORTANT**: Always verify the webhook signature to ensure the request is authentic.
```javascript
const crypto = require('crypto');
function verifyWebhookSignature(signature, payload, secret) {
if (!signature || !signature.startsWith('sha256=')) {
return false;
}
const expectedSignature = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
// Use timing-safe comparison
try {
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
} catch (err) {
return false;
}
}
```
### Example Implementation (Express.js)
```javascript
const express = require('express');
const crypto = require('crypto');
const app = express();
// IMPORTANT: Use express.json() with verify option to get raw body
app.use(express.json({
verify: (req, res, buf) => {
req.rawBody = buf.toString('utf8');
}
}));
app.post('/webhook/jackbox', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const secret = process.env.WEBHOOK_SECRET; // Your webhook secret
// Verify signature
if (!signature || !signature.startsWith('sha256=')) {
return res.status(401).send('Missing or invalid signature');
}
const expectedSignature = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(req.rawBody)
.digest('hex');
// Timing-safe comparison
try {
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
return res.status(401).send('Invalid signature');
}
} catch (err) {
return res.status(401).send('Invalid signature');
}
// Handle the event
if (req.body.event === 'game.added') {
const game = req.body.data.game;
// Send message to Kosmi chat
sendKosmiMessage(`🎮 Coming up next: ${game.title}!`);
console.log(`Announced game: ${game.title} from ${game.pack_name}`);
}
// Always respond with 200 OK
res.status(200).send('OK');
});
function sendKosmiMessage(message) {
// Your Kosmi chat integration here
console.log('Sending to Kosmi:', message);
}
app.listen(3001, () => {
console.log('Webhook receiver listening on port 3001');
});
```
---
## Webhook Management
You can manage webhooks through the API using the following endpoints (all require JWT authentication).
### List All Webhooks
```bash
GET /api/webhooks
Authorization: Bearer YOUR_JWT_TOKEN
```
### Create Webhook
```bash
POST /api/webhooks
Authorization: Bearer YOUR_JWT_TOKEN
Content-Type: application/json
{
"name": "Kosmi Bot",
"url": "http://your-bot-url/webhook/jackbox",
"secret": "your_shared_secret_key",
"events": ["game.added"]
}
```
### Update Webhook
```bash
PATCH /api/webhooks/:id
Authorization: Bearer YOUR_JWT_TOKEN
Content-Type: application/json
{
"enabled": false // Disable webhook
}
```
### Delete Webhook
```bash
DELETE /api/webhooks/:id
Authorization: Bearer YOUR_JWT_TOKEN
```
### Test Webhook
```bash
POST /api/webhooks/test/:id
Authorization: Bearer YOUR_JWT_TOKEN
```
Sends a test `game.added` event to verify your webhook is working.
### View Webhook Logs
```bash
GET /api/webhooks/:id/logs?limit=50
Authorization: Bearer YOUR_JWT_TOKEN
```
Returns recent webhook delivery attempts with status codes and errors.
---
## Testing
### Test Live Voting
```bash
# Get your JWT token first
curl -X POST "http://localhost:5000/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"apiKey": "YOUR_API_KEY"}'
# Send a test vote
curl -X POST "http://localhost:5000/api/votes/live" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "TestUser",
"vote": "up",
"timestamp": "2025-11-01T20:30:00Z"
}'
```
### Test Webhooks
```bash
# Create a webhook
curl -X POST "http://localhost:5000/api/webhooks" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Test Webhook",
"url": "http://localhost:3001/webhook/jackbox",
"secret": "test_secret_123",
"events": ["game.added"]
}'
# Test the webhook
curl -X POST "http://localhost:5000/api/webhooks/test/1" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
# Check webhook logs
curl -X GET "http://localhost:5000/api/webhooks/1/logs" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
```
---
## Available Events
Currently supported webhook events:
- `game.added` - Triggered when a game is added to an active session
More events may be added in the future (e.g., `session.started`, `session.ended`, `vote.recorded`).
---
## Security Best Practices
1. **Always verify webhook signatures** - Never trust webhook payloads without verification
2. **Use HTTPS in production** - Webhook URLs should use HTTPS to prevent man-in-the-middle attacks
3. **Keep secrets secure** - Store webhook secrets in environment variables, never in code
4. **Implement rate limiting** - Protect your webhook endpoints from abuse
5. **Log webhook activity** - Keep logs of webhook deliveries for debugging
6. **Use strong secrets** - Generate cryptographically secure random strings for webhook secrets
---
## Troubleshooting
### Votes Not Being Recorded
- Check that there's an active session with games played
- Verify the timestamp is within the timeframe of a played game
- Ensure you're not sending duplicate votes within 1 second
- Check API logs for error messages
### Webhooks Not Being Received
- Verify your webhook URL is publicly accessible
- Check webhook logs via `/api/webhooks/:id/logs`
- Test with `ngrok` or similar tool if developing locally
- Ensure your webhook endpoint responds with 200 OK
- Check that webhook is enabled in the database
### Signature Verification Failing
- Ensure you're using the raw request body for signature verification
- Check that the secret matches what's stored in the database
- Verify you're using HMAC-SHA256 algorithm
- Make sure to prefix with "sha256=" when comparing
---
## Support
For issues or questions, contact: cottongin@cottongin.xyz

View File

@@ -0,0 +1,277 @@
# Browser-Based Authentication Guide
## Overview
The Kosmi bridge now supports **fully automated email/password authentication** using headless Chrome via chromedp. No manual token extraction needed!
## Quick Start
### 1. Configure Email/Password
Edit `matterbridge.toml`:
```toml
[kosmi.hyperspaceout]
RoomURL="https://app.kosmi.io/room/@hyperspaceout"
Email="your-email@example.com"
Password="your-password"
```
### 2. Run the Bot
```bash
./irc-kosmi-relay -conf matterbridge.toml
```
That's it! The bot will:
1. Launch headless Chrome
2. Navigate to Kosmi
3. Log in with your credentials
4. Extract the JWT token from localStorage
5. Use the token for authenticated connections
6. Automatically refresh the token daily (checks for expiry 7 days in advance)
## How It Works
### Initial Login
When you start the bot with Email/Password configured:
1. **Browser Launch**: Headless Chrome starts (no visible window)
2. **Navigation**: Goes to https://app.kosmi.io
3. **Login Flow**:
- Clicks "Login" button
- Clicks "Login with Email"
- Fills in email and password
- Submits the form
4. **Token Extraction**: Reads `localStorage.getItem('token')`
5. **Token Parsing**: Extracts expiry time from JWT
6. **Connection**: Uses token for WebSocket authentication
### Automatic Token Refresh
- **Daily Check**: Every 24 hours, the bot checks if the token is still valid
- **Expiry Buffer**: Refreshes 7 days before expiration
- **Seamless**: Happens in the background without disconnecting
- **Logging**: You'll see "Checking token expiry..." in debug logs
## Requirements
### System Requirements
- **Chrome/Chromium**: Must be installed on your system
- macOS: Usually pre-installed or via Homebrew: `brew install chromium`
- Linux: `sudo apt install chromium-browser` or `sudo yum install chromium`
- Windows: Download from https://www.chromium.org/getting-involved/download-chromium/
### Go Dependencies
- `github.com/chromedp/chromedp` - Automatically installed with `go get`
## Configuration Options
### Option 1: Email/Password (Recommended)
```toml
Email="your-email@example.com"
Password="your-password"
```
- ✅ Fully automated
- ✅ Auto-refresh
- ✅ No manual intervention
- ⚠️ Requires Chrome/Chromium
- ⚠️ Stores credentials in config file
### Option 2: Manual Token
```toml
Token="eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9..."
```
- ✅ No browser required
- ✅ No credentials in config
- ❌ Manual extraction needed
- ❌ Must update when expired (~1 year)
### Option 3: Anonymous
```toml
# Leave both empty
Email=""
Password=""
Token=""
```
- ✅ No setup needed
- ❌ Limited permissions
- ❌ Can't access private rooms
## Troubleshooting
### "Browser automation failed"
**Possible causes:**
1. Chrome/Chromium not installed
2. Chrome/Chromium not in PATH
3. Network issues
4. Kosmi website changed
**Solutions:**
```bash
# Check if Chrome is installed
which chromium || which google-chrome || which chrome
# Install Chrome (macOS)
brew install chromium
# Install Chrome (Ubuntu/Debian)
sudo apt install chromium-browser
# Install Chrome (CentOS/RHEL)
sudo yum install chromium
```
### "No token found in localStorage after login"
**Possible causes:**
1. Login failed (wrong credentials)
2. Kosmi's login flow changed
3. Page didn't fully load
**Solutions:**
- Verify credentials are correct
- Check bot logs for detailed error messages
- Try manual token extraction as fallback
### "Token expired or expiring soon"
This is normal! The bot will automatically refresh the token. If refresh fails:
- Check Chrome is still installed
- Check network connectivity
- Restart the bot to force a fresh login
### Headless Chrome Issues
If you see Chrome-related errors:
```bash
# Set environment variable for debugging
export CHROMEDP_DISABLE_GPU=1
# Or run with visible browser (for debugging)
export CHROMEDP_NO_HEADLESS=1
```
## Security Considerations
### Credential Storage
- Credentials are stored in **plain text** in `matterbridge.toml`
- Ensure config file has restrictive permissions:
```bash
chmod 600 matterbridge.toml
```
- Do not commit config with real credentials to version control
- Consider using environment variables:
```bash
export KOSMI_EMAIL="your-email@example.com"
export KOSMI_PASSWORD="your-password"
```
### Browser Automation
- Headless Chrome runs with minimal privileges
- No data is stored or cached
- Browser closes immediately after token extraction
- Token is kept in memory only
### Token Security
- Tokens are JWT (JSON Web Tokens) signed by Kosmi
- Valid for ~1 year
- Can be revoked by logging out in browser
- Treat tokens like passwords
## Advanced Configuration
### Custom Chrome Path
If Chrome is installed in a non-standard location:
```go
// In browser_auth.go, modify NewContext call:
opts := append(chromedp.DefaultExecAllocatorOptions[:],
chromedp.ExecPath("/path/to/chrome"),
)
ctx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
```
### Timeout Adjustment
If login takes longer than 60 seconds:
```go
// In browser_auth.go, modify timeout:
ctx, cancel = context.WithTimeout(ctx, 120*time.Second)
```
### Refresh Interval
To check token more/less frequently:
```go
// In kosmi.go, modify ticker:
ticker := time.NewTicker(12 * time.Hour) // Check twice daily
```
## Comparison with Manual Token
| Feature | Browser Auth | Manual Token |
|---------|-------------|--------------|
| Setup Complexity | Easy | Medium |
| Automation | Full | None |
| Token Refresh | Automatic | Manual |
| Dependencies | Chrome | None |
| Security | Credentials in config | Token in config |
| Maintenance | Low | Medium |
## Logs to Expect
### Successful Login
```
INFO Using browser automation for email/password authentication
INFO Obtaining authentication token via browser automation...
INFO ✅ Successfully obtained token via browser automation
INFO Token expires in: 365d
INFO ✅ Browser authentication successful
INFO Successfully connected to Kosmi
```
### Daily Token Check
```
DEBUG Checking token expiry...
DEBUG Token check complete
```
### Token Refresh (when expiring)
```
INFO Token expired or expiring soon, will refresh
INFO Obtaining authentication token via browser automation...
INFO ✅ Successfully obtained token via browser automation
INFO Token expires in: 365d
```
## Migration from Manual Token
If you're currently using manual token:
1. **Add credentials** to config:
```toml
Email="your-email@example.com"
Password="your-password"
```
2. **Remove or comment out Token**:
```toml
#Token="..."
```
3. **Restart the bot**
The bot will automatically switch to browser-based auth!
## Performance Impact
- **Initial Login**: ~5-10 seconds (one-time per start)
- **Token Refresh**: ~5-10 seconds (once per year, or when expiring)
- **Daily Check**: <1ms (just checks expiry time)
- **Memory**: +50-100MB during browser operation (released after)
- **CPU**: Minimal (browser runs briefly)
## Conclusion
Browser-based authentication provides the best balance of:
- ✅ Full automation
- ✅ Reliable token refresh
- ✅ Simple configuration
- ✅ Low maintenance
For production use, this is the **recommended authentication method**.

View File

@@ -0,0 +1,534 @@
# Docker Deployment Guide
Complete guide for deploying the Kosmi-IRC bridge using Docker.
## Quick Start
```bash
# 1. Edit configuration
nano matterbridge.toml
# 2. Build and run
docker-compose up -d
# 3. View logs
docker-compose logs -f
```
## Prerequisites
- Docker (20.10+)
- Docker Compose (1.29+)
- A Kosmi room URL
- IRC server access
## Step-by-Step Setup
### 1. Configure the Bridge
Edit `matterbridge.toml` and update these settings:
```toml
[kosmi.hyperspaceout]
RoomURL="https://app.kosmi.io/room/@YOUR_ROOM" # ← Change this
Debug=false
[irc.libera]
Server="irc.libera.chat:6667" # ← Change to your IRC server
Nick="kosmi-relay" # ← Change your bot's nickname
[[gateway.inout]]
account="kosmi.hyperspaceout"
channel="main"
[[gateway.inout]]
account="irc.libera"
channel="#your-channel" # ← Change to your IRC channel
```
### 2. Build the Docker Image
```bash
docker-compose build
```
This will:
- Install Chrome/Chromium in the container
- Build the Matterbridge binary with Kosmi support
- Create an optimized production image
**Build time**: ~5-10 minutes (first time)
### 3. Run the Container
```bash
# Start in detached mode
docker-compose up -d
# Or start with logs visible
docker-compose up
```
### 4. Verify It's Working
```bash
# Check container status
docker-compose ps
# View logs
docker-compose logs -f matterbridge
# Look for these messages:
# INFO Successfully connected to Kosmi via Chrome
# INFO Successfully connected to IRC
# INFO Gateway(s) started successfully
```
### 5. Test Message Relay
1. **Kosmi → IRC**: Send a message in your Kosmi room
- Should appear in IRC as: `[Kosmi] <username> message`
2. **IRC → Kosmi**: Send a message in your IRC channel
- Should appear in Kosmi as: `[IRC] <username> message`
## Docker Commands Reference
### Container Management
```bash
# Start the bridge
docker-compose up -d
# Stop the bridge
docker-compose down
# Restart the bridge
docker-compose restart
# View logs
docker-compose logs -f
# View last 100 lines of logs
docker-compose logs --tail=100
# Check container status
docker-compose ps
# Execute commands in running container
docker-compose exec matterbridge sh
```
### Debugging
```bash
# Enable debug logging (edit docker-compose.yml first)
# Set Debug=true in matterbridge.toml, then:
docker-compose restart
# Check Chrome is installed
docker-compose exec matterbridge which chromium
# Check configuration
docker-compose exec matterbridge cat /app/matterbridge.toml
# Test connectivity
docker-compose exec matterbridge ping -c 3 app.kosmi.io
docker-compose exec matterbridge ping -c 3 irc.libera.chat
```
### Updating
```bash
# Pull latest code
git pull
# Rebuild image
docker-compose build --no-cache
# Restart with new image
docker-compose up -d
```
## Configuration Options
### docker-compose.yml
```yaml
services:
matterbridge:
build:
context: .
dockerfile: Dockerfile
container_name: kosmi-irc-relay
restart: unless-stopped
volumes:
- ./matterbridge.toml:/app/matterbridge.toml:ro
- ./logs:/app/logs
environment:
- CHROME_BIN=/usr/bin/chromium
- CHROME_PATH=/usr/bin/chromium
- TZ=America/New_York # ← Change to your timezone
security_opt:
- seccomp:unconfined # Required for Chrome
```
### Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `CHROME_BIN` | Path to Chrome binary | `/usr/bin/chromium` |
| `CHROME_PATH` | Chrome executable path | `/usr/bin/chromium` |
| `TZ` | Timezone for logs | `America/New_York` |
| `DEBUG` | Enable debug logging | `0` |
### Volume Mounts
| Host Path | Container Path | Purpose |
|-----------|----------------|---------|
| `./matterbridge.toml` | `/app/matterbridge.toml` | Configuration file (read-only) |
| `./logs` | `/app/logs` | Log files (optional) |
## Troubleshooting
### Container Won't Start
**Check logs**:
```bash
docker-compose logs
```
**Common issues**:
- Configuration file syntax error
- Missing `matterbridge.toml`
- Port already in use
**Solution**:
```bash
# Validate TOML syntax
docker run --rm -v $(pwd)/matterbridge.toml:/config.toml alpine sh -c "apk add --no-cache go && go install github.com/pelletier/go-toml/cmd/tomll@latest && tomll /config.toml"
# Check if file exists
ls -la matterbridge.toml
```
### Chrome/Chromium Not Found
**Symptoms**:
```
ERROR Chrome binary not found
```
**Solution**:
```bash
# Rebuild image
docker-compose build --no-cache
# Verify Chrome is installed
docker-compose run --rm matterbridge which chromium
```
### WebSocket Connection Failed
**Symptoms**:
```
ERROR Failed to connect to Kosmi
ERROR WebSocket connection failed
```
**Solution**:
```bash
# Test network connectivity
docker-compose exec matterbridge ping -c 3 app.kosmi.io
# Check if room URL is correct
docker-compose exec matterbridge cat /app/matterbridge.toml | grep RoomURL
# Enable debug logging
# Edit matterbridge.toml: Debug=true
docker-compose restart
```
### IRC Connection Failed
**Symptoms**:
```
ERROR Failed to connect to IRC
ERROR Connection refused
```
**Solution**:
```bash
# Test IRC connectivity
docker-compose exec matterbridge nc -zv irc.libera.chat 6667
# Check IRC configuration
docker-compose exec matterbridge cat /app/matterbridge.toml | grep -A 10 "\[irc\]"
# Verify nickname isn't already in use
# Try changing Nick in matterbridge.toml
```
### Messages Not Relaying
**Symptoms**:
- Container running
- Both bridges connected
- But messages don't appear
**Solution**:
```bash
# Enable debug logging
# Edit matterbridge.toml: Debug=true
docker-compose restart
# Watch logs for message flow
docker-compose logs -f | grep -E "Received|Sending|Forwarding"
# Verify gateway configuration
docker-compose exec matterbridge cat /app/matterbridge.toml | grep -A 20 "\[\[gateway\]\]"
# Check channel names match exactly
# Kosmi channel should be "main"
# IRC channel should include # (e.g., "#your-channel")
```
### High Memory Usage
**Symptoms**:
- Container using >500MB RAM
- System slowdown
**Solution**:
```bash
# Add memory limits to docker-compose.yml
services:
matterbridge:
mem_limit: 512m
mem_reservation: 256m
# Restart
docker-compose up -d
```
### Permission Denied Errors
**Symptoms**:
```
ERROR Permission denied writing to /app/logs
```
**Solution**:
```bash
# Create logs directory with correct permissions
mkdir -p logs
chmod 777 logs
# Or run container as root (not recommended)
# Edit docker-compose.yml:
# user: root
```
## Production Deployment
### Using Docker Swarm
```bash
# Initialize swarm
docker swarm init
# Deploy stack
docker stack deploy -c docker-compose.yml kosmi-relay
# Check status
docker stack services kosmi-relay
# View logs
docker service logs -f kosmi-relay_matterbridge
```
### Using Kubernetes
Create `kosmi-relay.yaml`:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: kosmi-irc-relay
spec:
replicas: 1
selector:
matchLabels:
app: kosmi-irc-relay
template:
metadata:
labels:
app: kosmi-irc-relay
spec:
containers:
- name: matterbridge
image: kosmi-irc-relay:latest
volumeMounts:
- name: config
mountPath: /app/matterbridge.toml
subPath: matterbridge.toml
env:
- name: CHROME_BIN
value: /usr/bin/chromium
- name: TZ
value: America/New_York
securityContext:
capabilities:
add:
- SYS_ADMIN # Required for Chrome
volumes:
- name: config
configMap:
name: matterbridge-config
```
Deploy:
```bash
kubectl create configmap matterbridge-config --from-file=matterbridge.toml
kubectl apply -f kosmi-relay.yaml
```
### Monitoring
#### Health Check
Add to `docker-compose.yml`:
```yaml
services:
matterbridge:
healthcheck:
test: ["CMD", "pgrep", "-f", "matterbridge"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
```
#### Prometheus Metrics
Matterbridge doesn't expose Prometheus metrics by default, but you can monitor:
```bash
# Container metrics
docker stats kosmi-irc-relay
# Log-based monitoring
docker-compose logs -f | grep -E "ERROR|WARN"
```
### Backup and Restore
```bash
# Backup configuration
cp matterbridge.toml matterbridge.toml.backup
# Backup logs
tar -czf logs-$(date +%Y%m%d).tar.gz logs/
# Restore configuration
cp matterbridge.toml.backup matterbridge.toml
docker-compose restart
```
## Security Best Practices
1. **Run as non-root user** (already configured in Dockerfile)
2. **Use read-only configuration mount**
3. **Limit container resources**
4. **Keep Docker images updated**
5. **Use secrets for sensitive data** (e.g., IRC passwords)
### Using Docker Secrets
```bash
# Create secret
echo "your_irc_password" | docker secret create irc_password -
# Update docker-compose.yml
services:
matterbridge:
secrets:
- irc_password
secrets:
irc_password:
external: true
```
## Performance Tuning
### Resource Limits
```yaml
services:
matterbridge:
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
```
### Chrome Optimization
Add to `docker-compose.yml`:
```yaml
services:
matterbridge:
environment:
- CHROME_FLAGS=--disable-dev-shm-usage --no-sandbox --disable-setuid-sandbox
```
## Next Steps
- ✅ Bridge is running in Docker
- 🔄 Set up monitoring and alerts
- 🔄 Configure log rotation
- 🔄 Set up automatic backups
- 🔄 Add more bridges (Discord, Slack, etc.)
## Getting Help
- Check logs: `docker-compose logs -f`
- Enable debug: Set `Debug=true` in `matterbridge.toml`
- Review `LESSONS_LEARNED.md` for common issues
- Check `QUICK_REFERENCE.md` for troubleshooting tips
## Example: Complete Setup
```bash
# 1. Clone repository
git clone <your-repo> kosmi-irc-relay
cd kosmi-irc-relay
# 2. Edit configuration
nano matterbridge.toml
# Update RoomURL, IRC server, channel
# 3. Build and start
docker-compose up -d
# 4. Watch logs
docker-compose logs -f
# 5. Test by sending messages in both Kosmi and IRC
# 6. If issues, enable debug
nano matterbridge.toml # Set Debug=true
docker-compose restart
docker-compose logs -f
```
That's it! Your Kosmi-IRC bridge is now running in Docker! 🎉

View File

@@ -0,0 +1,129 @@
# Docker Quick Start - 5 Minutes to Running Bridge
Get your Kosmi-IRC bridge running in Docker in 5 minutes!
## Prerequisites
- Docker installed
- Docker Compose installed
- A Kosmi room URL
- An IRC channel
## Steps
### 1. Edit Configuration (2 minutes)
Open `matterbridge.toml` and change these 3 things:
```toml
[kosmi.hyperspaceout]
RoomURL="https://app.kosmi.io/room/@YOUR_ROOM" # ← Your Kosmi room
[irc.libera]
Server="irc.libera.chat:6667" # ← Your IRC server
Nick="kosmi-relay" # ← Your bot's nickname
[[gateway.inout]]
account="irc.libera"
channel="#your-channel" # ← Your IRC channel
```
### 2. Build & Run (2 minutes)
```bash
docker-compose up -d
```
### 3. Check It's Working (1 minute)
```bash
docker-compose logs -f
```
Look for:
```
INFO Successfully connected to Kosmi via Chrome
INFO Successfully connected to IRC
INFO Gateway(s) started successfully
```
### 4. Test It!
- Send a message in Kosmi → should appear in IRC
- Send a message in IRC → should appear in Kosmi
## That's It! 🎉
Your bridge is running!
## Common Commands
```bash
# View logs
docker-compose logs -f
# Stop bridge
docker-compose down
# Restart bridge
docker-compose restart
# Rebuild after code changes
docker-compose build && docker-compose up -d
```
## Troubleshooting
### "Connection failed"
1. Check your configuration:
```bash
cat matterbridge.toml | grep -E "RoomURL|Server|channel"
```
2. Enable debug logging:
- Edit `matterbridge.toml`: Set `Debug=true`
- Restart: `docker-compose restart`
- Watch logs: `docker-compose logs -f`
### "Chrome not found"
```bash
# Rebuild image
docker-compose build --no-cache
docker-compose up -d
```
### "Messages not relaying"
1. Check both bridges are connected:
```bash
docker-compose logs | grep -i "connected"
```
2. Verify channel names:
- Kosmi channel must be `"main"`
- IRC channel must include `#` (e.g., `"#your-channel"`)
## Need More Help?
- Full guide: See `DOCKER_DEPLOYMENT.md`
- Troubleshooting: See `QUICK_REFERENCE.md`
- Implementation details: See `LESSONS_LEARNED.md`
## Example Output (Success)
```
INFO[...] Starting Matterbridge
INFO[...] Launching headless Chrome for Kosmi connection
INFO[...] Injecting WebSocket interceptor (runs before page load)...
INFO[...] ✓ WebSocket hook confirmed installed
INFO[...] Status: WebSocket connection intercepted
INFO[...] Successfully connected to Kosmi via Chrome
INFO[...] Connecting to IRC server irc.libera.chat:6667
INFO[...] Successfully connected to IRC
INFO[...] Gateway(s) started successfully. Now relaying messages
```
Now send a test message and watch it relay! 🚀

View File

@@ -0,0 +1,203 @@
# Jackbox Game Picker API Integration
This document describes how the Kosmi/IRC relay integrates with the Jackbox Game Picker API for live voting and game notifications.
## Features
### 1. Vote Detection
The relay automatically detects when users vote on games using the `thisgame++` or `thisgame--` syntax in either Kosmi or IRC chat.
**How it works:**
- Users type `thisgame++` to upvote the current game
- Users type `thisgame--` to downvote the current game
- Votes are case-insensitive
- The relay filters out relayed messages (messages with `[irc]` or `[kosmi]` prefix) to prevent duplicate votes
- Only votes from actual users in each chat are sent to the API
### 2. Game Notifications
When a new game is added to the Jackbox session via the API, the relay receives a webhook notification and broadcasts it to both Kosmi and IRC chats.
**Example notification:**
```
🎮 Coming up next: Fibbage 4!
```
## Configuration
Add the following section to your `matterbridge.toml`:
```toml
[jackbox]
# Enable Jackbox integration for vote detection and game notifications
Enabled=true
# Jackbox API URL
APIURL="http://localhost:5000"
# Admin password for API authentication
AdminPassword="your_admin_password_here"
# Webhook server port (for receiving game notifications)
WebhookPort=3001
# Webhook secret for signature verification
WebhookSecret="your_webhook_secret_here"
```
### Configuration Options
- **Enabled**: Set to `true` to enable the integration, `false` to disable
- **APIURL**: The URL of your Jackbox Game Picker API (e.g., `http://localhost:5000`)
- **AdminPassword**: Your API admin password (used to authenticate and get a JWT token)
- **WebhookPort**: Port for the webhook server to listen on (default: 3001)
- **WebhookSecret**: Shared secret for webhook signature verification (must match the secret configured in the API)
## Setup Steps
### 1. Configure the Relay
Edit `matterbridge.toml` and add the Jackbox configuration section with your API URL, admin password, and webhook secret.
### 2. Register the Webhook
After starting the relay, register the webhook with the Jackbox API:
```bash
# Get JWT token
curl -X POST "http://localhost:5000/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"apiKey": "your_admin_password"}'
# Register webhook (use the JWT token from above)
curl -X POST "http://localhost:5000/api/webhooks" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Kosmi/IRC Relay",
"url": "http://your-relay-host:3001/webhook/jackbox",
"secret": "your_webhook_secret_here",
"events": ["game.added"]
}'
```
**Important:** Replace `your-relay-host` with the actual hostname or IP address where your relay is running. If the API and relay are on the same machine, you can use `localhost`.
### 3. Test the Integration
#### Test Vote Detection
1. Start an active session in the Jackbox API with some games played
2. Send a message in Kosmi or IRC: `thisgame++`
3. Check the relay logs for: `Detected vote from <username>: up`
4. Verify the vote was recorded in the API
#### Test Game Notifications
1. Add a game to the active session via the Jackbox API
2. Both Kosmi and IRC chats should receive a notification: `🎮 Coming up next: <game title>!`
## How It Works
### Vote Flow
1. User sends a message containing `thisgame++` or `thisgame--` in Kosmi or IRC
2. The bridge detects the vote pattern (case-insensitive)
3. The bridge checks if the message is relayed (has `[irc]` or `[kosmi]` prefix)
4. If not relayed, the bridge extracts the username and vote type
5. The bridge sends the vote to the Jackbox API via HTTP POST to `/api/votes/live`
6. The API records the vote and associates it with the current game based on timestamp
### Notification Flow
1. A game is added to an active session in the Jackbox API
2. The API sends a webhook POST request to `http://your-relay-host:3001/webhook/jackbox`
3. The webhook includes an HMAC-SHA256 signature in the `X-Webhook-Signature` header
4. The relay verifies the signature using the configured webhook secret
5. If valid, the relay parses the `game.added` event
6. The relay broadcasts the game announcement to all connected bridges (Kosmi and IRC)
## Security
### Webhook Signature Verification
All incoming webhooks are verified using HMAC-SHA256 signatures to ensure they come from the legitimate Jackbox API.
**How it works:**
1. The API computes `HMAC-SHA256(webhook_secret, request_body)`
2. The signature is sent in the `X-Webhook-Signature` header as `sha256=<hex_signature>`
3. The relay computes the expected signature using the same method
4. The relay uses timing-safe comparison to verify the signatures match
5. If verification fails, the webhook is rejected with a 401 Unauthorized response
### JWT Authentication
The relay authenticates with the Jackbox API using the admin password to obtain a JWT token. This token is:
- Cached to avoid re-authentication on every vote
- Automatically refreshed if it expires (detected via 401 response)
- Valid for 24 hours (configurable in the API)
## Troubleshooting
### Votes Not Being Recorded
**Possible causes:**
- No active session in the Jackbox API
- Vote timestamp doesn't match any played game
- Duplicate vote within 1 second
- Authentication failure
**Check:**
1. Relay logs for vote detection: `grep "Detected vote" logs/matterbridge.log`
2. Relay logs for API errors: `grep "Failed to send vote" logs/matterbridge.log`
3. API logs for incoming vote requests
### Webhooks Not Being Received
**Possible causes:**
- Webhook URL is not accessible from the API server
- Webhook not registered in the API
- Signature verification failing
- Firewall blocking the webhook port
**Check:**
1. Verify webhook is registered: `GET /api/webhooks` (with JWT token)
2. Test webhook manually: `POST /api/webhooks/test/:id` (with JWT token)
3. Check webhook logs in API: `GET /api/webhooks/:id/logs` (with JWT token)
4. Verify webhook server is listening: `curl http://localhost:3001/health`
5. Check relay logs for signature verification errors
### Authentication Failures
**Possible causes:**
- Incorrect admin password in configuration
- API is not running or not accessible
**Check:**
1. Verify API URL is correct and accessible
2. Test authentication manually:
```bash
curl -X POST "http://localhost:5000/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"apiKey": "your_admin_password"}'
```
3. Check relay logs for authentication errors
## Logs
The relay logs all Jackbox-related activity with the `jackbox` prefix:
```
[jackbox] Initializing Jackbox integration...
[jackbox] Successfully authenticated with Jackbox API
[jackbox] Starting Jackbox webhook server on port 3001
[kosmi] Detected vote from Anonymous Llama: up
[jackbox] Vote recorded for Fibbage 4: Anonymous Llama - 5👍 2👎
[jackbox] Broadcasting Jackbox message: 🎮 Coming up next: Quiplash 3!
```
## Disabling the Integration
To disable the Jackbox integration, set `Enabled=false` in the `[jackbox]` section of `matterbridge.toml` and restart the relay.
The relay will continue to function normally for Kosmi ↔ IRC message relay without any Jackbox features.

View File

@@ -0,0 +1,225 @@
# Mute Control for Jackbox Announcements
The bot supports muting Jackbox announcements without restarting. This is useful when you want to test the Jackbox API or run games without spamming the chat.
## Features
- ✅ Start the bot muted
- ✅ Toggle mute/unmute while running
- ✅ Works in terminal and Docker
- ✅ Vote detection still works (votes are sent to API even when muted)
- ✅ Only Jackbox announcements are muted (IRC ↔ Kosmi relay still works)
## Starting Muted
### Terminal
```bash
./matterbridge -conf matterbridge.toml -muted
```
### Docker
Update `docker-compose.yml`:
```yaml
services:
kosmi-irc-relay:
command: ["/app/matterbridge", "-conf", "/app/matterbridge.toml", "-muted"]
```
Or run with override:
```bash
docker-compose run --rm kosmi-irc-relay /app/matterbridge -conf /app/matterbridge.toml -muted
```
## Toggling Mute While Running
The bot listens for the `SIGUSR1` signal to toggle mute status.
### Terminal (Local Process)
**Find the process ID:**
```bash
ps aux | grep matterbridge
# or
pgrep matterbridge
```
**Toggle mute:**
```bash
kill -SIGUSR1 <pid>
```
**Example:**
```bash
$ pgrep matterbridge
12345
$ kill -SIGUSR1 12345
# Bot logs: 🔇 Jackbox announcements MUTED
$ kill -SIGUSR1 12345
# Bot logs: 🔊 Jackbox announcements UNMUTED
```
### Docker
**Find the container name:**
```bash
docker ps
```
**Toggle mute:**
```bash
docker kill -s SIGUSR1 <container_name>
```
**Example:**
```bash
$ docker ps
CONTAINER ID IMAGE COMMAND NAMES
abc123def456 kosmi-irc-relay "/app/matterbridge -…" kosmi-irc-relay
$ docker kill -s SIGUSR1 kosmi-irc-relay
# Bot logs: 🔇 Jackbox announcements MUTED
$ docker kill -s SIGUSR1 kosmi-irc-relay
# Bot logs: 🔊 Jackbox announcements UNMUTED
```
## What Gets Muted?
### Muted Messages ❌
- 🎮 Game Night is starting!
- 🎮 Coming up next: [Game] - Room Code [CODE]
- 🗳️ Final votes for [Game]: X👍 Y👎
- 🌙 Game Night has ended! Thanks for playing!
### Still Works ✅
- Vote detection (`thisgame++` / `thisgame--`)
- Votes sent to Jackbox API
- IRC ↔ Kosmi message relay
- All other bot functionality
## Log Messages
**When starting muted:**
```
INFO Jackbox announcements started MUTED (use SIGUSR1 to toggle)
INFO Signal handler ready: Send SIGUSR1 to toggle mute (kill -SIGUSR1 <pid> or docker kill -s SIGUSR1 <container>)
```
**When toggling to muted:**
```
WARN 🔇 Jackbox announcements MUTED
```
**When toggling to unmuted:**
```
INFO 🔊 Jackbox announcements UNMUTED
```
**When a message is suppressed:**
```
DEBUG Jackbox message suppressed (muted): 🎮 Coming up next: Drawful 2 - Room Code C0D3
```
## Use Cases
### Testing Jackbox API
```bash
# Start muted
docker-compose up -d
# Test vote detection without spamming chat
# (votes are still sent to API)
# In chat: "thisgame++"
# Check logs to see votes are being processed
docker logs kosmi-irc-relay -f
# Unmute when ready
docker kill -s SIGUSR1 kosmi-irc-relay
```
### Running Private Games
```bash
# Mute during game setup
docker kill -s SIGUSR1 kosmi-irc-relay
# Play games without announcements
# (useful if you're testing or running a private session)
# Unmute for public game night
docker kill -s SIGUSR1 kosmi-irc-relay
```
### Quick Mute Script
Create a helper script `mute-toggle.sh`:
```bash
#!/bin/bash
docker kill -s SIGUSR1 kosmi-irc-relay
docker logs kosmi-irc-relay --tail 1
```
Make it executable:
```bash
chmod +x mute-toggle.sh
```
Use it:
```bash
./mute-toggle.sh
# 🔇 Jackbox announcements MUTED
./mute-toggle.sh
# 🔊 Jackbox announcements UNMUTED
```
## Troubleshooting
### Signal not working in Docker
Make sure your Docker container is running:
```bash
docker ps | grep kosmi-irc-relay
```
If the container is restarting, check logs:
```bash
docker logs kosmi-irc-relay
```
### Signal not working locally
Make sure the process is running:
```bash
ps aux | grep matterbridge
```
Check you're using the correct PID:
```bash
pgrep -f matterbridge
```
### Mute state not persisting after restart
Mute state is **not persisted** across restarts. If you restart the bot:
- Without `-muted` flag: Bot starts unmuted
- With `-muted` flag: Bot starts muted
This is intentional - you probably want announcements by default.
## Advanced: Systemd Service
If running as a systemd service, you can use `systemctl`:
**Create a mute toggle script:**
```bash
#!/bin/bash
# /usr/local/bin/matterbridge-mute-toggle
PID=$(systemctl show -p MainPID --value matterbridge.service)
kill -SIGUSR1 $PID
journalctl -u matterbridge.service -n 1 --no-pager
```
**Use it:**
```bash
sudo /usr/local/bin/matterbridge-mute-toggle
```

View File

@@ -0,0 +1,301 @@
# Quick Start Guide
Get the Kosmi-IRC bridge running in minutes!
## Prerequisites
- Go 1.21 or higher
- Chrome/Chromium browser installed (for headless browser automation)
- Access to a Kosmi room
- (Optional) IRC server access for full relay
## Step 1: Test Kosmi Connection
First, verify the bridge can connect to Kosmi:
```bash
# Build the test program
go build -o test-kosmi ./cmd/test-kosmi
# Run with your Kosmi room URL
./test-kosmi -room "https://app.kosmi.io/room/@hyperspaceout" -debug
```
You should see output like:
```
INFO[...] Starting Kosmi bridge test
INFO[...] Launching headless Chrome for Kosmi connection
INFO[...] Injecting WebSocket interceptor (runs before page load)...
INFO[...] Navigating to Kosmi room: https://app.kosmi.io/room/@hyperspaceout
INFO[...] ✓ WebSocket hook confirmed installed
INFO[...] Status: WebSocket connection intercepted
INFO[...] Successfully connected to Kosmi via Chrome
INFO[...] Listening for messages... Press Ctrl+C to exit
```
**Test it**: Send a message in the Kosmi room from your browser. You should see it appear in the terminal like:
```
INFO[...] Received message: [00:02:51] username: [Kosmi] <username> your message here
```
## Step 2: Integrate with Full Matterbridge
### Option A: Copy into Existing Matterbridge
If you already have Matterbridge:
```bash
# Navigate to your Matterbridge directory
cd /path/to/matterbridge
# Copy the Kosmi bridge
cp -r /path/to/irc-kosmi-relay/bridge/kosmi bridge/
# Copy the bridge registration
cp /path/to/irc-kosmi-relay/gateway/bridgemap/bkosmi.go gateway/bridgemap/
# Add dependencies
go get github.com/chromedp/chromedp@v0.11.2
go mod tidy
# Build
go build
```
### Option B: Use This Repository
This repository has a minimal Matterbridge structure. To add IRC support:
1. Copy IRC bridge from Matterbridge:
```bash
# From the Matterbridge repo
cp -r bridge/irc /path/to/irc-kosmi-relay/bridge/
cp gateway/bridgemap/birc.go /path/to/irc-kosmi-relay/gateway/bridgemap/
```
2. Update dependencies:
```bash
cd /path/to/irc-kosmi-relay
go mod tidy
```
## Step 3: Configure
Edit `matterbridge.toml`:
```toml
# Kosmi configuration
[kosmi.hyperspaceout]
RoomURL="https://app.kosmi.io/room/@hyperspaceout"
# IRC configuration
[irc.libera]
Server="irc.libera.chat:6667"
Nick="kosmi-bot"
UseTLS=false
# Gateway to connect them
[[gateway]]
name="kosmi-irc-relay"
enable=true
[[gateway.inout]]
account="kosmi.hyperspaceout"
channel="main"
[[gateway.inout]]
account="irc.libera"
channel="#your-channel"
```
**Important**: Replace:
- `https://app.kosmi.io/room/@hyperspaceout` with your Kosmi room URL
- `#your-channel` with your IRC channel
## Step 4: Run
```bash
./matterbridge -conf matterbridge.toml
```
Or with debug logging:
```bash
./matterbridge -conf matterbridge.toml -debug
```
## Step 5: Test the Relay
1. **Kosmi → IRC**: Send a message in Kosmi. It should appear in IRC as:
```
[Kosmi] <username> your message here
```
2. **IRC → Kosmi**: Send a message in IRC. It should appear in Kosmi as:
```
[IRC] <username> your message here
```
## Troubleshooting
### Test program doesn't connect
**Check**:
- Is Chrome/Chromium installed and accessible?
- Is the room URL correct?
- Can you access `app.kosmi.io` from your network?
- Try with `-debug` flag for more details
**Solution**:
```bash
# Test Chrome installation
which google-chrome chromium chromium-browser
# Test network connectivity
curl -I https://app.kosmi.io
# Run with debug
./test-kosmi -room "YOUR_ROOM_URL" -debug
```
### Messages not relaying
**Check**:
- Are both bridges connected? Look for "Successfully connected" in logs
- Is the gateway configuration correct?
- Are the channel names correct?
**Solution**:
```bash
# Run with debug to see message flow
./matterbridge -conf matterbridge.toml -debug
# Look for lines like:
# "Received message from Kosmi"
# "Forwarding to Matterbridge"
# "Sending to IRC"
```
### "Room ID extraction failed"
**Check**: Room URL format
**Supported formats**:
- `https://app.kosmi.io/room/@roomname`
- `https://app.kosmi.io/room/roomid`
- `@roomname`
- `roomid`
**Solution**: Use the full URL from your browser's address bar
### Messages not appearing from Kosmi
**Check**:
- Is the WebSocket hook installed? Look for "✓ WebSocket hook confirmed installed"
- Is the WebSocket connection detected? Look for "Status: WebSocket connection intercepted"
- Are messages being captured? Enable debug logging to see message processing
**Solution**:
The bridge uses headless Chrome with a WebSocket interceptor that runs **before page load**. This is critical for capturing messages. The implementation uses `Page.addScriptToEvaluateOnNewDocument` to ensure the hook is installed before any page JavaScript executes.
If messages still aren't appearing:
1. Check Chrome console logs in debug mode
2. Verify the room URL is correct
3. Try sending a test message and watch the debug output
### Cannot send messages to Kosmi
The send functionality uses the headless Chrome instance to inject messages into the Kosmi chat input field.
**To debug**:
1. Enable debug logging with `-debug` flag
2. Look for "Sending message to Kosmi" in logs
3. Check for any JavaScript errors in the browser console logs
## Next Steps
- **Customize message format**: Edit the format strings in `kosmi.go`
- **Add more bridges**: Matterbridge supports Discord, Slack, Telegram, etc.
- **Set up as a service**: Use systemd or similar to run automatically
- **Monitor logs**: Set up log rotation and monitoring
## Getting Help
- Check `INTEGRATION.md` for detailed integration steps
- Check `README.md` for architecture details
- Enable debug logging for detailed troubleshooting
- Review the chrome extension code in `.examples/` for API details
## Common Use Cases
### Home Server Setup
```toml
# Bridge your Kosmi room with your home IRC server
[kosmi.gamenight]
RoomURL="https://app.kosmi.io/room/@gamenight"
[irc.home]
Server="irc.home.local:6667"
Nick="kosmi-relay"
UseTLS=false
[[gateway]]
name="gamenight"
enable=true
[[gateway.inout]]
account="kosmi.gamenight"
channel="main"
[[gateway.inout]]
account="irc.home"
channel="#gamenight"
```
### Multiple Rooms
```toml
# Bridge multiple Kosmi rooms
[kosmi.room1]
RoomURL="https://app.kosmi.io/room/@room1"
[kosmi.room2]
RoomURL="https://app.kosmi.io/room/@room2"
[irc.libera]
Server="irc.libera.chat:6667"
Nick="kosmi-bot"
# Gateway for room1
[[gateway]]
name="gateway1"
enable=true
[[gateway.inout]]
account="kosmi.room1"
channel="main"
[[gateway.inout]]
account="irc.libera"
channel="#room1"
# Gateway for room2
[[gateway]]
name="gateway2"
enable=true
[[gateway.inout]]
account="kosmi.room2"
channel="main"
[[gateway.inout]]
account="irc.libera"
channel="#room2"
```
## Success!
If you see messages flowing both ways, congratulations! Your Kosmi-IRC bridge is working. 🎉
For advanced configuration and features, see the full documentation in `README.md` and `INTEGRATION.md`.

View File

@@ -0,0 +1,237 @@
# Quick Reference Guide
## Testing the Bridge
### Build and Run Test Program
```bash
cd /Users/erikfredericks/dev-ai/HSO/irc-kosmi-relay
go build -o test-kosmi ./cmd/test-kosmi
./test-kosmi -room "https://app.kosmi.io/room/@hyperspaceout" -debug
```
### Expected Output (Success)
```
INFO Launching headless Chrome for Kosmi connection
INFO Injecting WebSocket interceptor (runs before page load)...
INFO Navigating to Kosmi room: https://app.kosmi.io/room/@hyperspaceout
INFO ✓ WebSocket hook confirmed installed
INFO Status: WebSocket connection intercepted
INFO Successfully connected to Kosmi via Chrome
INFO Listening for messages... Press Ctrl+C to exit
```
### When Messages Arrive
```
INFO Processing 1 messages from queue
INFO Received message: [00:02:51] username: [Kosmi] <username> message text
```
## Key Status Indicators
| Status Message | Meaning | Action |
|---------------|---------|--------|
| `✓ WebSocket hook confirmed installed` | Hook script is active | ✅ Good |
| `Status: WebSocket connection intercepted` | WebSocket is being captured | ✅ Good |
| `Status: No WebSocket connection detected yet` | Hook missed the WebSocket | ❌ Check injection timing |
| `Processing N messages from queue` | Messages are being captured | ✅ Good |
## Common Issues
### Issue: "No WebSocket connection detected yet"
**Cause**: WebSocket hook was injected too late
**Fix**: Verify `injectWebSocketHookBeforeLoad()` uses `page.AddScriptToEvaluateOnNewDocument`
### Issue: "Chrome not found"
**Cause**: Chrome/Chromium not installed or not in PATH
**Fix**:
```bash
# macOS
brew install --cask google-chrome
# Ubuntu/Debian
sudo apt install chromium-browser
# Verify installation
which google-chrome chromium chromium-browser
```
### Issue: Messages not appearing
**Cause**: Multiple possibilities
**Debug**:
1. Check for "✓ WebSocket hook confirmed installed" ✓
2. Check for "Status: WebSocket connection intercepted" ✓
3. Enable debug logging: `-debug` flag
4. Send a test message in the Kosmi room
5. Look for "Processing N messages from queue"
## Configuration
### Minimal Test Configuration
```toml
[kosmi.test]
RoomURL="https://app.kosmi.io/room/@hyperspaceout"
Debug=true
```
### Full Matterbridge Configuration
```toml
# Kosmi
[kosmi.hyperspaceout]
RoomURL="https://app.kosmi.io/room/@hyperspaceout"
# IRC
[irc.libera]
Server="irc.libera.chat:6667"
Nick="kosmi-relay"
UseTLS=false
# Gateway
[[gateway]]
name="kosmi-irc"
enable=true
[[gateway.inout]]
account="kosmi.hyperspaceout"
channel="main"
[[gateway.inout]]
account="irc.libera"
channel="#your-channel"
```
## Message Format
### Kosmi → IRC
```
[Kosmi] <username> message text
```
### IRC → Kosmi
```
[IRC] <username> message text
```
## File Locations
| File | Purpose |
|------|---------|
| `bridge/kosmi/kosmi.go` | Main bridge implementation |
| `bridge/kosmi/chromedp_client.go` | Headless Chrome client |
| `bridge/kosmi/graphql.go` | GraphQL structures (legacy) |
| `cmd/test-kosmi/main.go` | Standalone test program |
| `matterbridge.toml` | Configuration file |
## Key Implementation Details
### WebSocket Hook Injection
**MUST** use `page.AddScriptToEvaluateOnNewDocument` to inject **before page load**:
```go
chromedp.ActionFunc(func(ctx context.Context) error {
_, err := page.AddScriptToEvaluateOnNewDocument(script).Do(ctx)
return err
})
```
### Hook Script
Wraps `window.WebSocket` constructor to intercept all WebSocket connections:
```javascript
window.WebSocket = function(url, protocols) {
const socket = new OriginalWebSocket(url, protocols);
// ... interception logic ...
return socket;
};
```
## Debugging Commands
```bash
# Test Chrome installation
which google-chrome chromium chromium-browser
# Test network connectivity
curl -I https://app.kosmi.io
# Build with verbose output
go build -v -o test-kosmi ./cmd/test-kosmi
# Run with debug logging
./test-kosmi -room "https://app.kosmi.io/room/@hyperspaceout" -debug
# Check for linter errors
go vet ./...
```
## Performance Notes
- **Chrome Startup**: ~1-2 seconds
- **Page Load**: ~1-2 seconds
- **Message Latency**: ~100-500ms
- **Memory Usage**: ~100-200MB (Chrome process)
## Security Considerations
- Bridge runs Chrome in headless mode (no GUI)
- No credentials stored (anonymous access)
- WebSocket traffic is intercepted in memory only
- Messages are not logged to disk (unless debug logging enabled)
## Production Deployment
### systemd Service Example
```ini
[Unit]
Description=Kosmi-IRC Relay
After=network.target
[Service]
Type=simple
User=matterbridge
WorkingDirectory=/opt/matterbridge
ExecStart=/opt/matterbridge/matterbridge -conf /etc/matterbridge/matterbridge.toml
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
```
### Docker Considerations
When running in Docker, ensure:
- Chrome/Chromium is installed in the container
- `--no-sandbox` flag may be needed for Chrome
- Sufficient memory allocation (512MB minimum)
## Resources
- **Documentation**: See `README.md`, `QUICKSTART.md`, `LESSONS_LEARNED.md`
- **Integration Guide**: See `INTEGRATION.md`
- **Implementation Details**: See `IMPLEMENTATION_SUMMARY.md`
- **ChromeDP Guide**: See `CHROMEDP_IMPLEMENTATION.md`
## Support
For issues:
1. Check this quick reference
2. Review `LESSONS_LEARNED.md` for common patterns
3. Enable debug logging for detailed output
4. Check Chrome console logs in debug mode

View File

@@ -0,0 +1,94 @@
# Quick Start: Testing Authentication
## Step 1: Create Bot Account
1. Go to https://app.kosmi.io
2. Sign up with a dedicated email (e.g., `your-bot@example.com`)
3. Choose a display name (e.g., "HSO Relay Bot")
4. Save the credentials securely
## Step 2: Test with Monitor Script
```bash
cd /Users/erikfredericks/dev-ai/HSO/irc-kosmi-relay
# Run monitor in login mode
./bin/monitor-auth -login
# In the browser that opens:
# 1. Log in with your bot credentials
# 2. Navigate to a room
# 3. Press Ctrl+C to stop
# Review the captured data
cat auth-monitor.log | grep -A 5 "login"
```
## Step 3: Configure Matterbridge
Edit `matterbridge.toml`:
```toml
[kosmi.hyperspaceout]
RoomURL="https://app.kosmi.io/room/@hyperspaceout"
Email="your-bot@example.com"
Password="your-secure-password"
```
## Step 4: Test Connection
```bash
# Build the bridge
go build
# Run with your config
./matterbridge -conf matterbridge.toml
# Watch the logs for:
# - "Using authenticated connection"
# - "Logged in as: HSO Relay Bot"
# - "Successfully connected to Kosmi"
```
## Verification Checklist
- [ ] Bot account created manually
- [ ] Credentials documented securely
- [ ] Monitor script captured login flow
- [ ] Config file updated with credentials
- [ ] Bridge logs show authenticated connection
- [ ] Bot display name appears correctly in chat
- [ ] Messages relay successfully
## Troubleshooting
### Wrong account logged in
Check the log for "Logged in as: {name}". If it doesn't match your bot:
- Verify email/password in config
- Check for typos
- Ensure you're using the correct credentials
### Anonymous connection despite credentials
Check that both Email AND Password are set:
```bash
grep -A 2 "Email=" matterbridge.toml
```
### Token expired
The bridge should auto-refresh. If not:
- Check logs for "Token refresh failed"
- Verify credentials are still valid
- Try manual login at app.kosmi.io
## Next Steps
Once authenticated connection works:
- Test reconnection (simulate network failure)
- Monitor for token refresh (wait 24 hours)
- Test with multiple rooms
- Set up as systemd service
See the monitoring script output and logs for detailed information about Kosmi's authentication behavior.

View File

@@ -0,0 +1,141 @@
# Room Code Image Feature
## Overview
The bot now supports displaying Jackbox room codes as images in Kosmi chat (with fallback to IRC text formatting for IRC chat).
## How It Works
### For Kosmi Chat (with `EnableRoomCodeImage=true`)
1. **Generate**: When a new game is added, the bot generates a PNG image of the room code using a monospace font (black background, white text)
2. **Upload**: The image is uploaded to Kosmi's CDN at `https://img.kosmi.io/`
3. **Broadcast**: The bot sends two messages:
- First: The game announcement (e.g., "🎮 Coming up next: Drawful 2!")
- Second: The image URL (Kosmi automatically displays it as a thumbnail)
### For IRC Chat (always)
Room codes are displayed with IRC formatting:
- **Bold** (`\x02`)
- **Monospace** (`\x11`)
- **Reset** (`\x0F`)
Example: `\x02\x11ROOM42\x0F` displays as **`ROOM42`** in IRC clients
### Fallback Behavior
If image generation or upload fails:
- The bot falls back to IRC text formatting for all chats
- An error is logged but the announcement still goes through
## Configuration
In `matterbridge.toml`:
```toml
[jackbox]
Enabled=true
APIURL="https://your-jackbox-api.com"
AdminPassword="your-password"
UseWebSocket=true
EnableRoomCodeImage=false # Set to true to enable image uploads
```
## Files Involved
### New Files
- `bridge/jackbox/roomcode_image.go` - PNG image generation
- `bridge/jackbox/image_upload.go` - HTTP upload to Kosmi CDN
- `bridge/irc/formatting.go` - IRC formatting helpers
- `bridge/kosmi/image_upload.go` - (Duplicate, can be removed)
- `KOSMI_IMAGE_UPLOAD.md` - Protocol documentation
### Modified Files
- `bridge/jackbox/websocket_client.go` - Image upload integration
- `bridge/jackbox/manager.go` - Config passing
- `matterbridge.toml` - Added `EnableRoomCodeImage` setting
### Test Files
- `cmd/test-roomcode-image/main.go` - Test image generation
- `cmd/test-image-upload/main.go` - Test full upload flow
## Testing
### Test Image Generation
```bash
./test-roomcode-image
# Generates: roomcode_ABCD.png, roomcode_TEST.png, etc.
```
### Test Image Upload
```bash
./test-image-upload
# Generates image, uploads to Kosmi CDN, displays URL
```
### Test Full Integration
1. Set `EnableRoomCodeImage=true` in `matterbridge.toml`
2. Start the bot: `./matterbridge -conf matterbridge.toml`
3. Add a game in the Jackbox Picker with a room code
4. Verify:
- Kosmi chat shows the game announcement + image thumbnail
- IRC chat shows the game announcement + formatted room code text
## Technical Details
### Image Specifications
- Format: PNG
- Size: 200x80 pixels
- Background: Black (`#000000`)
- Text: White (`#FFFFFF`)
- Font: `basicfont.Face7x13` (monospace)
- Typical file size: ~400-500 bytes
### Upload Endpoint
- URL: `https://img.kosmi.io/`
- Method: `POST`
- Content-Type: `multipart/form-data`
- Authentication: None required (CORS-protected)
- Response: `{"filename": "uuid.webp"}`
- Full URL: `https://img.kosmi.io/{filename}`
### Performance
- Image generation: <1ms
- Image upload: ~300-600ms (network dependent)
- Total delay: Minimal, upload happens asynchronously
## Future Enhancements
Potential improvements:
1. **Custom fonts**: Use a better monospace font (requires embedding TTF)
2. **Styling**: Add Jackbox branding, colors, or borders
3. **Caching**: Cache uploaded images to avoid re-uploading identical room codes
4. **Retry logic**: Add retry mechanism for failed uploads
5. **Compression**: Optimize PNG compression for smaller file sizes
## Troubleshooting
### Images not appearing in Kosmi
- Check that `EnableRoomCodeImage=true` in config
- Check logs for upload errors
- Verify network connectivity to `https://img.kosmi.io/`
- Test manually with `./test-image-upload`
### IRC formatting not working
- Ensure your IRC client supports formatting codes
- Some clients require enabling "Show colors/formatting"
- Fallback: The room code is still readable without formatting
### Build errors
- Ensure all dependencies are installed: `go mod tidy`
- Check Go version: Requires Go 1.19+
- Verify `golang.org/x/image` is available
## References
- [Kosmi Image Upload Protocol](KOSMI_IMAGE_UPLOAD.md)
- [IRC Formatting Codes](https://modern.ircdocs.horse/formatting.html)
- [Go image package](https://pkg.go.dev/image)
- [Jackbox Integration](JACKBOX_INTEGRATION.md)

View File

@@ -0,0 +1,255 @@
# Message Relay Testing Instructions
**Bridge Status**: ✅ **ACTIVE AND READY**
Both Kosmi and IRC are connected. The gateway is relaying messages.
## Current Configuration
- **Kosmi Room**: https://app.kosmi.io/room/@hyperspaceout
- **IRC Server**: irc.zeronode.net:6697
- **IRC Channel**: #cottongin
- **Status**: Both bridges connected and relaying
## Test 1: Kosmi → IRC
### Steps:
1. Open the Kosmi room in your browser: https://app.kosmi.io/room/@hyperspaceout
2. Type a message in the chat (e.g., "Test message from Kosmi 🚀")
3. Press Enter to send
### Expected Result:
- The message should appear in IRC channel #cottongin
- The logs will show:
```
level=info msg="📨 Received message from Kosmi: ..." prefix=kosmi
level=info msg="Relaying message from kosmi to irc"
```
### How to Verify:
Connect to IRC and join #cottongin:
```bash
# Using an IRC client (e.g., irssi, weechat, hexchat)
/connect irc.zeronode.net 6697
/join #cottongin
```
Or watch the Docker logs:
```bash
docker-compose logs -f | grep -E "(Received|Relaying|Sent)"
```
---
## Test 2: IRC → Kosmi
### Steps:
1. Connect to IRC: `irc.zeronode.net:6697`
2. Join channel: `/join #cottongin`
3. Send a message: "Test message from IRC 👋"
### Expected Result:
- The message should appear in the Kosmi chat room
- The logs will show:
```
level=info msg="Received message from IRC: ..." prefix=irc
level=info msg="Relaying message from irc to kosmi"
level=info msg="✅ Sent message via Playwright-assisted WebSocket: ..." prefix=kosmi
```
### How to Verify:
- Open Kosmi room in browser: https://app.kosmi.io/room/@hyperspaceout
- Check if your IRC message appears in the chat
---
## Watch Logs in Real-Time
```bash
# All logs
docker-compose logs -f
# Only message-related logs
docker-compose logs -f | grep -E "(message|Message|Received|Sent|Relaying)"
# Last 50 lines
docker-compose logs --tail=50
```
---
## Quick IRC Connection Methods
### Method 1: Web IRC Client
1. Go to: https://web.libera.chat/
2. Connect to: irc.zeronode.net (port 6697, SSL)
3. Join: #cottongin
### Method 2: Command-Line (irssi)
```bash
# Install irssi (if not installed)
brew install irssi # macOS
# or
apt-get install irssi # Linux
# Connect
irssi -c irc.zeronode.net -p 6697
/join #cottongin
```
### Method 3: Command-Line (nc for quick test)
```bash
# Simple netcat connection (read-only)
openssl s_client -connect irc.zeronode.net:6697
# Then type:
NICK testuser
USER testuser 0 * :Test User
JOIN #cottongin
```
---
## What You Should See
### In Docker Logs (Normal Operation):
```
✅ WebSocket established and ready!
✅ Native client fully connected!
Successfully connected to Kosmi
Connection succeeded (IRC)
irc.libera: joining #cottongin
Gateway(s) started successfully. Now relaying messages
```
### When a Message is Relayed (Kosmi → IRC):
```
level=info msg="📨 Received message from Kosmi: username: message text" prefix=kosmi
level=info msg="Handling message from kosmi.hyperspaceout" prefix=router
level=info msg="Relaying message to irc.libera" prefix=router
level=info msg="Sent message to #cottongin" prefix=irc
```
### When a Message is Relayed (IRC → Kosmi):
```
level=info msg="Received message from #cottongin: username: message text" prefix=irc
level=info msg="Handling message from irc.libera" prefix=router
level=info msg="Relaying message to kosmi.hyperspaceout" prefix=router
level=info msg="✅ Sent message via Playwright-assisted WebSocket: message text" prefix=kosmi
```
---
## Troubleshooting
### No Messages Appearing
1. **Check bridge is running**:
```bash
docker-compose ps
```
Should show `kosmi-irc-relay` as "Up"
2. **Check logs for errors**:
```bash
docker-compose logs --tail=100
```
3. **Verify connections**:
```bash
docker-compose logs | grep -E "(Connected|connected|joined)"
```
Should show both Kosmi WebSocket and IRC connected
4. **Restart if needed**:
```bash
docker-compose restart
docker-compose logs -f
```
### Messages Only Going One Direction
- **If Kosmi → IRC works but IRC → Kosmi doesn't**:
- Check Kosmi WebSocket is still connected: `docker-compose logs | grep WebSocket`
- The native client might have disconnected
- Restart: `docker-compose restart`
- **If IRC → Kosmi works but Kosmi → IRC doesn't**:
- Check IRC connection: `docker-compose logs | grep irc`
- Verify IRC authentication
### Message Formatting Issues
Messages are formatted as:
```
<username> message text
```
This is configurable in `matterbridge.toml` under the gateway settings.
---
## Success Criteria
✅ **Full Success** means:
1. Message sent in Kosmi appears in IRC #cottongin
2. Message sent in IRC #cottongin appears in Kosmi room
3. Usernames are preserved/displayed correctly
4. Messages appear within 1-2 seconds
5. No errors in logs
---
## Next Steps After Testing
Once both directions work:
1. **Monitor stability** - Let it run for a few hours
2. **Check memory usage**: `docker stats kosmi-irc-relay`
3. **Review message formatting** - Adjust in `matterbridge.toml` if needed
4. **Set up log rotation** - For long-term operation
5. **Consider adding more channels** - Edit `matterbridge.toml`
---
## Quick Commands Reference
```bash
# Start in background
docker-compose up -d
# Follow logs
docker-compose logs -f
# Stop
docker-compose down
# Restart
docker-compose restart
# Rebuild
docker-compose up --build -d
# Check status
docker-compose ps
# View resource usage
docker stats kosmi-irc-relay
```
---
## Ready to Test!
The bridge is **running and connected**. You can start testing immediately:
1. 🌐 Open: https://app.kosmi.io/room/@hyperspaceout
2. 💬 Send a test message
3. 👀 Watch the logs: `docker-compose logs -f`
4. 🔗 Connect to IRC and verify the message appeared
5. 🔄 Send a message from IRC and check Kosmi
Good luck! 🎉

View File

@@ -0,0 +1,181 @@
# Token Persistence Guide
## Overview
The Kosmi bridge now caches JWT authentication tokens to avoid repeated browser automation on every startup. The token is stored in a local directory that persists across Docker container rebuilds and restarts.
## How It Works
### Token Cache Location
The token cache is stored in a file called `kosmi_token_cache.json` in the following locations:
- **Docker (Production)**: `./data/kosmi_token_cache.json` (mounted from your host machine)
- **Local Development**: `~/.matterbridge/kosmi_token_cache.json`
### Token Cache Structure
The cache file contains:
```json
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"email": "your-email@example.com",
"expires_at": "2026-11-02T15:56:23Z",
"saved_at": "2025-11-02T15:56:23Z"
}
```
### Token Lifecycle
1. **On Startup**: The bridge checks for a cached token
- If found and valid, it uses the cached token (no browser automation needed)
- If expired or expiring within 7 days, it performs fresh authentication
- If not found, it performs fresh authentication
2. **Token Expiry**: Kosmi JWT tokens expire after 1 year
- The bridge automatically refreshes tokens that expire within 7 days
- You'll see a log message indicating how long until the token expires
3. **Token Storage**: After successful authentication, the token is saved to the cache file
- File permissions are set to `0600` (read/write for owner only)
- The cache directory is created automatically if it doesn't exist
## Docker Configuration
### Volume Mount
The `docker-compose.yml` includes a volume mount for persistent storage:
```yaml
volumes:
- ./data:/app/data:z
```
This mounts the `./data` directory from your host machine into the container at `/app/data`.
### Environment Variable
The container sets the `MATTERBRIDGE_DATA_DIR` environment variable:
```yaml
environment:
- MATTERBRIDGE_DATA_DIR=/app/data
```
This tells the bridge where to store persistent data like the token cache.
## Usage
### First Run
On the first run with email/password configured:
1. The bridge will launch a headless browser
2. Authenticate with Kosmi using your credentials
3. Extract and cache the JWT token
4. Save it to `./data/kosmi_token_cache.json`
You'll see logs like:
```
level=info msg="No cached token found, performing authentication..."
level=info msg="Starting browser automation for authentication..."
level=info msg="💾 Token cached (expires in 8760h)"
```
### Subsequent Runs
On subsequent runs (container restarts, rebuilds, etc.):
1. The bridge checks the cached token
2. If valid, uses it immediately (no browser needed)
3. Connects to Kosmi in seconds
You'll see logs like:
```
level=info msg="✅ Using cached token (expires in 8736h)"
```
### Token Refresh
When the token is close to expiring (within 7 days):
1. The bridge automatically performs fresh authentication
2. Updates the cached token
3. Continues normal operation
You'll see logs like:
```
level=info msg="Cached token expires soon (2025-11-09T15:56:23Z), will refresh"
level=info msg="Starting browser automation for authentication..."
level=info msg="💾 Token cached (expires in 8760h)"
```
## File Structure
After running with authentication, your directory structure will look like:
```
irc-kosmi-relay/
├── data/ # Persistent data directory
│ └── kosmi_token_cache.json # Cached JWT token
├── docker-compose.yml
├── matterbridge.toml
└── ...
```
## Troubleshooting
### Token Cache Not Persisting
If the token cache doesn't persist across container restarts:
1. Check that the `./data` directory exists and is writable
2. Verify the volume mount in `docker-compose.yml` is correct
3. Check container logs for permission errors
### Force Token Refresh
To force a fresh authentication (e.g., if credentials changed):
```bash
# Stop the container
docker-compose down
# Remove the cached token
rm ./data/kosmi_token_cache.json
# Start the container
docker-compose up -d
```
### Check Token Status
To view the current cached token:
```bash
cat ./data/kosmi_token_cache.json | jq .
```
This will show you:
- When the token was saved
- When it expires
- Which email it's associated with
## Security Notes
- The token cache file has restricted permissions (`0600`) for security
- The token is a JWT that expires after 1 year
- The cache file is stored locally and never transmitted
- If you commit your code to version control, add `data/` to `.gitignore`
## Benefits
1. **Faster Startup**: No browser automation on every restart (saves 10-15 seconds)
2. **Reduced Resource Usage**: No need to launch Chromium on every startup
3. **Persistence**: Token survives container rebuilds, restarts, and host reboots
4. **Automatic Refresh**: Token is automatically refreshed before expiry
5. **Local Storage**: Token is stored on your host machine, not in the container

View File

@@ -1,414 +1,145 @@
# Bot Integration Guide
# Bot Features Guide
This guide explains how to integrate your bot with the Jackbox Game Picker API for live voting and game notifications.
This document describes the features available in the Kosmi-IRC relay bot when the Jackbox Game Picker integration is enabled.
## Table of Contents
## Feature Summary
1. [Live Voting (Bot → API)](#live-voting-bot--api)
2. [Game Notifications (API → Bot)](#game-notifications-api--bot)
3. [Webhook Management](#webhook-management)
4. [Testing](#testing)
| Feature | Where | Description |
|---------|-------|-------------|
| Message relay | IRC + Kosmi | Bidirectional chat relay |
| Vote detection | IRC + Kosmi | Automatic `thisgame++/--` and ticker votes |
| `!votes` command | IRC + Kosmi | Query current game vote tally |
| `!kreconnect` | IRC | Reconnect Kosmi bridge |
| `!jreconnect` | IRC | Reconnect Jackbox WebSocket |
| `!reconnect` | IRC | Reconnect all non-IRC services |
| Game announcements | All bridges | Jackbox game/session notifications |
| Room code images | All bridges | Generated room code images in chat |
| Mute toggle | Operator (SIGUSR1) | Suppress Jackbox announcements |
---
## Vote Detection
## Live Voting (Bot → API)
The bot automatically detects votes in chat messages from both IRC and Kosmi. Votes are submitted to the Jackbox Game Picker API.
Your bot can send real-time votes to the API when it detects "thisgame++" or "thisgame--" in Kosmi chat.
### Current Game Votes
### Endpoint
- `thisgame++` -- Upvote the currently playing game
- `thisgame--` -- Downvote the currently playing game
```
POST /api/votes/live
```
Case-insensitive. Can appear anywhere in the message. The message is still relayed normally.
### Ticker Symbol Votes
- `SYMBOL++` -- Upvote a specific game by ticker symbol
- `SYMBOL--` -- Downvote a specific game by ticker symbol
Symbols are 2-4 alphanumeric characters. See [IRC.md](../IRC.md) for the complete ticker table.
### Loop Prevention
Messages prefixed with `[irc]` or `[kosmi]` (relayed messages) are excluded from vote detection to prevent duplicate counting.
### Deduplication
The API rejects duplicate votes from the same user within 1 second.
## Game Announcements
When connected to the Jackbox API via WebSocket (default) or webhook, the bot broadcasts events to all bridges:
| Event | Example Message |
|-------|----------------|
| Session started | `🎮 Game Night is starting!` |
| Game added | `🎮 Coming up next: Fibbage 4 - Room Code ABCD` |
| Session ended | `🌙 Game Night has ended! Thanks for playing!` |
### Room Code Images
When `EnableRoomCodeImage=true`, game announcements include an uploaded image of the room code. The image is uploaded to the Kosmi CDN. Timing is controlled by `RoomCodeImageDelay` and `RoomCodePlaintextDelay` in config.
If image upload fails, the bot falls back to plaintext.
## IRC Commands
All commands are available to any user in the channel. See [IRC.md](../IRC.md) for complete details.
| Command | Effect |
|---------|--------|
| `!kreconnect` | Reconnect Kosmi bridge |
| `!jreconnect` | Reconnect Jackbox WebSocket |
| `!reconnect` | Reconnect all non-IRC services |
| `!votes` | Show current game vote tally |
`!votes` is also available from Kosmi chat.
## Mute Control
Jackbox announcements can be suppressed without stopping the bot:
- **Start muted**: `./matterbridge -conf matterbridge.toml -muted`
- **Toggle at runtime**: `kill -SIGUSR1 <pid>` or `docker kill -s SIGUSR1 <container>`
When muted, vote detection and normal message relay continue to work. Only Jackbox-driven announcements (session start/end, game added) are suppressed.
See [MUTE_CONTROL.md](MUTE_CONTROL.md) for full details.
## API Communication
### Authentication
Requires JWT token in Authorization header:
The bot authenticates with `POST /api/auth/login` using the `AdminPassword` from config. The JWT is cached in memory and refreshed on 401 responses.
```
Authorization: Bearer YOUR_JWT_TOKEN
```
### Vote Submission
### Request Body
Votes are submitted via `POST /api/votes/live` with:
```json
{
"username": "string", // Username of the voter
"vote": "up" | "down", // "up" for thisgame++, "down" for thisgame--
"timestamp": "string" // ISO 8601 timestamp (e.g., "2025-11-01T20:30:00Z")
}
```
### Response (Success)
```json
{
"success": true,
"message": "Vote recorded successfully",
"session": {
"id": 123,
"games_played": 5
},
"game": {
"id": 45,
"title": "Fibbage 4",
"upvotes": 46,
"downvotes": 3,
"popularity_score": 43
},
"vote": {
"username": "TestUser",
"type": "up",
"timestamp": "2025-11-01T20:30:00Z"
}
}
```
### Error Responses
- **400 Bad Request**: Invalid payload or timestamp format
- **404 Not Found**: No active session or timestamp doesn't match any game
- **409 Conflict**: Duplicate vote (within 1 second of previous vote from same user)
- **500 Internal Server Error**: Server error
### Example Implementation (Node.js)
```javascript
// When bot detects "thisgame++" or "thisgame--" in Kosmi chat
async function handleVote(username, message) {
const isUpvote = message.includes('thisgame++');
const isDownvote = message.includes('thisgame--');
if (!isUpvote && !isDownvote) return;
try {
const response = await fetch('http://your-api-url/api/votes/live', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.JWT_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: username,
vote: isUpvote ? 'up' : 'down',
timestamp: new Date().toISOString()
})
});
const data = await response.json();
if (response.ok) {
console.log(`Vote recorded for ${data.game.title}: ${data.game.upvotes}👍 ${data.game.downvotes}👎`);
} else {
console.error('Vote failed:', data.error);
}
} catch (error) {
console.error('Error sending vote:', error);
}
}
```
### Important Notes
- **Deduplication**: Votes from the same user within 1 second are automatically rejected to prevent spam
- **Timestamp Matching**: The API matches the vote timestamp to the correct game based on when games were played
- **Active Session Required**: Votes can only be recorded when there's an active session with games played
---
## Game Notifications (API → Bot)
The API can send webhooks to your bot when games are added to a session, allowing you to announce "Coming up next: Game Title!" in Kosmi chat.
### Webhook Event: `game.added`
Triggered whenever a game is added to an active session (either via picker or manual selection).
### Webhook Payload
```json
{
"event": "game.added",
"timestamp": "2025-11-01T20:30:00Z",
"data": {
"session": {
"id": 123,
"is_active": true,
"games_played": 5
},
"game": {
"id": 45,
"title": "Fibbage 4",
"pack_name": "The Jackbox Party Pack 9",
"min_players": 2,
"max_players": 8,
"manually_added": false
}
}
}
```
### Webhook Headers
The API sends the following headers with each webhook:
- `Content-Type: application/json`
- `X-Webhook-Signature: sha256=<hmac_signature>` - HMAC-SHA256 signature for verification
- `X-Webhook-Event: game.added` - Event type
- `User-Agent: Jackbox-Game-Picker-Webhook/1.0`
### Signature Verification
**IMPORTANT**: Always verify the webhook signature to ensure the request is authentic.
```javascript
const crypto = require('crypto');
function verifyWebhookSignature(signature, payload, secret) {
if (!signature || !signature.startsWith('sha256=')) {
return false;
}
const expectedSignature = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
// Use timing-safe comparison
try {
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
} catch (err) {
return false;
}
}
```
### Example Implementation (Express.js)
```javascript
const express = require('express');
const crypto = require('crypto');
const app = express();
// IMPORTANT: Use express.json() with verify option to get raw body
app.use(express.json({
verify: (req, res, buf) => {
req.rawBody = buf.toString('utf8');
}
}));
app.post('/webhook/jackbox', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const secret = process.env.WEBHOOK_SECRET; // Your webhook secret
// Verify signature
if (!signature || !signature.startsWith('sha256=')) {
return res.status(401).send('Missing or invalid signature');
}
const expectedSignature = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(req.rawBody)
.digest('hex');
// Timing-safe comparison
try {
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
return res.status(401).send('Invalid signature');
}
} catch (err) {
return res.status(401).send('Invalid signature');
}
// Handle the event
if (req.body.event === 'game.added') {
const game = req.body.data.game;
// Send message to Kosmi chat
sendKosmiMessage(`🎮 Coming up next: ${game.title}!`);
console.log(`Announced game: ${game.title} from ${game.pack_name}`);
}
// Always respond with 200 OK
res.status(200).send('OK');
});
function sendKosmiMessage(message) {
// Your Kosmi chat integration here
console.log('Sending to Kosmi:', message);
}
app.listen(3001, () => {
console.log('Webhook receiver listening on port 3001');
});
```
---
## Webhook Management
You can manage webhooks through the API using the following endpoints (all require JWT authentication).
### List All Webhooks
```bash
GET /api/webhooks
Authorization: Bearer YOUR_JWT_TOKEN
```
### Create Webhook
```bash
POST /api/webhooks
Authorization: Bearer YOUR_JWT_TOKEN
Content-Type: application/json
{
"name": "Kosmi Bot",
"url": "http://your-bot-url/webhook/jackbox",
"secret": "your_shared_secret_key",
"events": ["game.added"]
}
```
### Update Webhook
```bash
PATCH /api/webhooks/:id
Authorization: Bearer YOUR_JWT_TOKEN
Content-Type: application/json
{
"enabled": false // Disable webhook
}
```
### Delete Webhook
```bash
DELETE /api/webhooks/:id
Authorization: Bearer YOUR_JWT_TOKEN
```
### Test Webhook
```bash
POST /api/webhooks/test/:id
Authorization: Bearer YOUR_JWT_TOKEN
```
Sends a test `game.added` event to verify your webhook is working.
### View Webhook Logs
```bash
GET /api/webhooks/:id/logs?limit=50
Authorization: Bearer YOUR_JWT_TOKEN
```
Returns recent webhook delivery attempts with status codes and errors.
---
## Testing
### Test Live Voting
```bash
# Get your JWT token first
curl -X POST "http://localhost:5000/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"apiKey": "YOUR_API_KEY"}'
# Send a test vote
curl -X POST "http://localhost:5000/api/votes/live" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "TestUser",
"username": "voter_name",
"vote": "up",
"timestamp": "2025-11-01T20:30:00Z"
}'
"timestamp": "2025-11-01T20:30:00Z",
"ticker": "FBG4"
}
```
### Test Webhooks
The `ticker` field is omitted for `thisgame++/--` votes.
```bash
# Create a webhook
curl -X POST "http://localhost:5000/api/webhooks" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Test Webhook",
"url": "http://localhost:3001/webhook/jackbox",
"secret": "test_secret_123",
"events": ["game.added"]
}'
### Event Transport
# Test the webhook
curl -X POST "http://localhost:5000/api/webhooks/test/1" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
**WebSocket (default)**: Connects to `wss://<api>/api/sessions/live`, authenticates, and subscribes to the active session. Events arrive in real-time with auto-reconnect.
# Check webhook logs
curl -X GET "http://localhost:5000/api/webhooks/1/logs" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
**Webhook (fallback)**: Starts an HTTP server listening for `POST /webhook/jackbox` with HMAC-SHA256 signature verification.
## Configuration
See [JACKBOX_INTEGRATION.md](JACKBOX_INTEGRATION.md) for the full configuration reference.
Minimal setup:
```toml
[jackbox]
Enabled=true
APIURL="https://your-jackbox-api.example.com"
AdminPassword="your_password"
UseWebSocket=true
```
---
## Available Events
Currently supported webhook events:
- `game.added` - Triggered when a game is added to an active session
More events may be added in the future (e.g., `session.started`, `session.ended`, `vote.recorded`).
---
## Security Best Practices
1. **Always verify webhook signatures** - Never trust webhook payloads without verification
2. **Use HTTPS in production** - Webhook URLs should use HTTPS to prevent man-in-the-middle attacks
3. **Keep secrets secure** - Store webhook secrets in environment variables, never in code
4. **Implement rate limiting** - Protect your webhook endpoints from abuse
5. **Log webhook activity** - Keep logs of webhook deliveries for debugging
6. **Use strong secrets** - Generate cryptographically secure random strings for webhook secrets
---
## Troubleshooting
### Votes Not Being Recorded
### Votes not registering
- Check that there's an active session with games played
- Verify the timestamp is within the timeframe of a played game
- Ensure you're not sending duplicate votes within 1 second
- Check API logs for error messages
- Check that Jackbox integration is enabled
- Verify an active session exists
- Look for `"Detected vote from"` in debug logs
- Look for `"Failed to send vote"` errors
### Webhooks Not Being Received
### Announcements not appearing
- Verify your webhook URL is publicly accessible
- Check webhook logs via `/api/webhooks/:id/logs`
- Test with `ngrok` or similar tool if developing locally
- Ensure your webhook endpoint responds with 200 OK
- Check that webhook is enabled in the database
- Check that the Jackbox WebSocket is connected
- Verify the bot is not muted (check logs for `MUTED`)
- Use `!jreconnect` to force a WebSocket reconnection
### Signature Verification Failing
- Ensure you're using the raw request body for signature verification
- Check that the secret matches what's stored in the database
- Verify you're using HMAC-SHA256 algorithm
- Make sure to prefix with "sha256=" when comparing
---
## Support
For issues or questions, contact: cottongin@cottongin.xyz
### `!votes` returns nothing
- An active session must exist
- A game must currently have "playing" status
- The Jackbox integration must be enabled and authenticated

View File

@@ -1,277 +1,160 @@
# Browser-Based Authentication Guide
## Overview
The Kosmi bridge now supports **fully automated email/password authentication** using headless Chrome via chromedp. No manual token extraction needed!
The Kosmi bridge supports **email/password authentication** using headless Chrome (chromedp). This is only needed if you want to connect with a specific Kosmi account instead of anonymous access. The JWT obtained via browser login is cached on disk, so the browser is only launched when needed.
## Quick Start
### 1. Configure Email/Password
Edit `matterbridge.toml`:
```toml
[kosmi.hyperspaceout]
RoomURL="https://app.kosmi.io/room/@hyperspaceout"
RoomURL="https://app.kosmi.io/room/@yourroom"
Email="your-email@example.com"
Password="your-password"
```
### 2. Run the Bot
```bash
./irc-kosmi-relay -conf matterbridge.toml
./matterbridge -conf matterbridge.toml
```
That's it! The bot will:
1. Launch headless Chrome
2. Navigate to Kosmi
3. Log in with your credentials
4. Extract the JWT token from localStorage
5. Use the token for authenticated connections
6. Automatically refresh the token daily (checks for expiry 7 days in advance)
The bot will:
1. Check for a cached token in the data directory
2. If no valid cache exists, launch headless Chrome
3. Navigate to Kosmi and log in with your credentials
4. Extract the JWT from localStorage
5. Cache the token for future use
6. Connect to Kosmi with the authenticated token
## How It Works
## Authentication Modes
### Initial Login
When you start the bot with Email/Password configured:
1. **Browser Launch**: Headless Chrome starts (no visible window)
2. **Navigation**: Goes to https://app.kosmi.io
3. **Login Flow**:
- Clicks "Login" button
- Clicks "Login with Email"
- Fills in email and password
- Submits the form
4. **Token Extraction**: Reads `localStorage.getItem('token')`
5. **Token Parsing**: Extracts expiry time from JWT
6. **Connection**: Uses token for WebSocket authentication
### Option 1: Email/Password (Recommended for Authenticated Access)
### Automatic Token Refresh
- **Daily Check**: Every 24 hours, the bot checks if the token is still valid
- **Expiry Buffer**: Refreshes 7 days before expiration
- **Seamless**: Happens in the background without disconnecting
- **Logging**: You'll see "Checking token expiry..." in debug logs
## Requirements
### System Requirements
- **Chrome/Chromium**: Must be installed on your system
- macOS: Usually pre-installed or via Homebrew: `brew install chromium`
- Linux: `sudo apt install chromium-browser` or `sudo yum install chromium`
- Windows: Download from https://www.chromium.org/getting-involved/download-chromium/
### Go Dependencies
- `github.com/chromedp/chromedp` - Automatically installed with `go get`
## Configuration Options
### Option 1: Email/Password (Recommended)
```toml
Email="your-email@example.com"
Password="your-password"
```
- ✅ Fully automated
- ✅ Auto-refresh
- ✅ No manual intervention
- ⚠️ Requires Chrome/Chromium
- ⚠️ Stores credentials in config file
### Option 2: Manual Token
```toml
Token="eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9..."
```
- ✅ No browser required
- ✅ No credentials in config
- ❌ Manual extraction needed
- ❌ Must update when expired (~1 year)
- Fully automated with token caching
- Browser only launches when cache is missing or expired
- Token refreshes automatically before expiry (7-day buffer)
- Requires Chrome/Chromium installed
### Option 2: Anonymous (Default)
### Option 3: Anonymous
```toml
# Leave both empty
# Leave both empty or omit entirely
Email=""
Password=""
Token=""
```
- ✅ No setup needed
- ❌ Limited permissions
- ❌ Can't access private rooms
- No setup needed, no browser required
- Connects with an anonymous Kosmi identity
- Cannot access private rooms
## Token Caching
Authenticated tokens are cached to avoid launching Chrome on every startup:
- **Docker**: `./data/kosmi_token_cache.json` (via `MATTERBRIDGE_DATA_DIR=/app/data`)
- **Local**: `~/.matterbridge/kosmi_token_cache.json`
On startup, the bridge:
1. Loads the cached token
2. If valid (and not expiring within 7 days), uses it directly -- no browser
3. If expired or expiring soon, performs browser authentication
4. Saves the new token to cache
Kosmi JWT tokens are valid for approximately 1 year.
See [TOKEN_PERSISTENCE.md](TOKEN_PERSISTENCE.md) for full details on caching.
## Requirements
### Chrome/Chromium
Must be installed and in PATH:
```bash
# macOS
brew install --cask chromium
# Ubuntu/Debian
sudo apt install chromium-browser
# Verify
which chromium chromium-browser google-chrome
```
In Docker, Chromium is pre-installed in the image.
## Troubleshooting
### "Browser automation failed"
**Possible causes:**
1. Chrome/Chromium not installed
2. Chrome/Chromium not in PATH
3. Network issues
4. Kosmi website changed
### "Browser automation failed" or "authentication failed"
**Solutions:**
```bash
# Check if Chrome is installed
which chromium || which google-chrome || which chrome
1. Check Chrome/Chromium is installed and accessible
2. Verify credentials by logging in manually at `app.kosmi.io`
3. Delete the cached token to force fresh auth: `rm data/kosmi_token_cache.json`
4. Enable debug logging for detailed browser automation output
# Install Chrome (macOS)
brew install chromium
### Token cache not persisting (Docker)
# Install Chrome (Ubuntu/Debian)
sudo apt install chromium-browser
- Verify the `./data` volume mount exists in `docker-compose.yml`
- Check directory permissions: `ls -la ./data/`
- Confirm `MATTERBRIDGE_DATA_DIR` is set to `/app/data`
# Install Chrome (CentOS/RHEL)
sudo yum install chromium
```
### "No token found in localStorage after login"
**Possible causes:**
1. Login failed (wrong credentials)
2. Kosmi's login flow changed
3. Page didn't fully load
**Solutions:**
- Verify credentials are correct
- Check bot logs for detailed error messages
- Try manual token extraction as fallback
### "Token expired or expiring soon"
This is normal! The bot will automatically refresh the token. If refresh fails:
- Check Chrome is still installed
- Check network connectivity
- Restart the bot to force a fresh login
### Headless Chrome Issues
If you see Chrome-related errors:
### Force token refresh
```bash
# Set environment variable for debugging
export CHROMEDP_DISABLE_GPU=1
# Local
rm ~/.matterbridge/kosmi_token_cache.json
./matterbridge -conf matterbridge.toml
# Or run with visible browser (for debugging)
export CHROMEDP_NO_HEADLESS=1
# Docker
rm ./data/kosmi_token_cache.json
docker-compose restart
```
## Security Considerations
### Credential Storage
- Credentials are stored in **plain text** in `matterbridge.toml`
- Ensure config file has restrictive permissions:
- Credentials are stored in **plain text** in `matterbridge.toml` -- restrict permissions:
```bash
chmod 600 matterbridge.toml
```
- Do not commit config with real credentials to version control
- Consider using environment variables:
```bash
export KOSMI_EMAIL="your-email@example.com"
export KOSMI_PASSWORD="your-password"
```
### Browser Automation
- Headless Chrome runs with minimal privileges
- No data is stored or cached
- Browser closes immediately after token extraction
- Token is kept in memory only
### Token Security
- Tokens are JWT (JSON Web Tokens) signed by Kosmi
- Valid for ~1 year
- Can be revoked by logging out in browser
- Treat tokens like passwords
## Advanced Configuration
### Custom Chrome Path
If Chrome is installed in a non-standard location:
```go
// In browser_auth.go, modify NewContext call:
opts := append(chromedp.DefaultExecAllocatorOptions[:],
chromedp.ExecPath("/path/to/chrome"),
)
ctx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
```
### Timeout Adjustment
If login takes longer than 60 seconds:
```go
// In browser_auth.go, modify timeout:
ctx, cancel = context.WithTimeout(ctx, 120*time.Second)
```
### Refresh Interval
To check token more/less frequently:
```go
// In kosmi.go, modify ticker:
ticker := time.NewTicker(12 * time.Hour) // Check twice daily
```
## Comparison with Manual Token
| Feature | Browser Auth | Manual Token |
|---------|-------------|--------------|
| Setup Complexity | Easy | Medium |
| Automation | Full | None |
| Token Refresh | Automatic | Manual |
| Dependencies | Chrome | None |
| Security | Credentials in config | Token in config |
| Maintenance | Low | Medium |
- Do not commit config files with real credentials to version control
- The cached token file has restricted permissions (`0600`)
- Headless Chrome runs briefly and closes after token extraction
- Tokens can be revoked by logging out in a browser
## Logs to Expect
### Successful Login
### First run (no cache)
```
INFO Using browser automation for email/password authentication
INFO Obtaining authentication token via browser automation...
INFO ✅ Successfully obtained token via browser automation
INFO Token expires in: 365d
INFO ✅ Browser authentication successful
INFO No cached token found, performing authentication...
INFO Authenticating with email/password...
INFO ✅ Authentication successful
INFO 💾 Token cached (expires in 8760h)
INFO Successfully connected to Kosmi
```
### Daily Token Check
### Subsequent runs (cached token)
```
DEBUG Checking token expiry...
DEBUG Token check complete
INFO ✅ Using cached token (expires in 8736h)
INFO Successfully connected to Kosmi
```
### Token Refresh (when expiring)
### Token refresh (near expiry)
```
INFO Token expired or expiring soon, will refresh
INFO Obtaining authentication token via browser automation...
INFO ✅ Successfully obtained token via browser automation
INFO Token expires in: 365d
INFO Cached token expires soon, will refresh
INFO Authenticating with email/password...
INFO ✅ Authentication successful
INFO 💾 Token cached (expires in 8760h)
```
## Migration from Manual Token
If you're currently using manual token:
1. **Add credentials** to config:
```toml
Email="your-email@example.com"
Password="your-password"
```
2. **Remove or comment out Token**:
```toml
#Token="..."
```
3. **Restart the bot**
The bot will automatically switch to browser-based auth!
## Performance Impact
- **Initial Login**: ~5-10 seconds (one-time per start)
- **Token Refresh**: ~5-10 seconds (once per year, or when expiring)
- **Daily Check**: <1ms (just checks expiry time)
- **Memory**: +50-100MB during browser operation (released after)
- **CPU**: Minimal (browser runs briefly)
## Conclusion
Browser-based authentication provides the best balance of:
- ✅ Full automation
- ✅ Reliable token refresh
- ✅ Simple configuration
- ✅ Low maintenance
For production use, this is the **recommended authentication method**.

View File

@@ -5,13 +5,9 @@ Complete guide for deploying the Kosmi-IRC bridge using Docker.
## Quick Start
```bash
# 1. Edit configuration
cp matterbridge.toml.example matterbridge.toml
nano matterbridge.toml
# 2. Build and run
docker-compose up -d
# 3. View logs
docker-compose logs -f
```
@@ -26,24 +22,20 @@ docker-compose logs -f
### 1. Configure the Bridge
Edit `matterbridge.toml` and update these settings:
Copy `matterbridge.toml.example` to `matterbridge.toml` and update these settings:
```toml
[kosmi.hyperspaceout]
RoomURL="https://app.kosmi.io/room/@YOUR_ROOM" # ← Change this
Debug=false
RoomURL="https://app.kosmi.io/room/@YOUR_ROOM"
[irc.libera]
Server="irc.libera.chat:6667" # ← Change to your IRC server
Nick="kosmi-relay" # ← Change your bot's nickname
[irc.zeronode]
Server="irc.libera.chat:6697"
Nick="kosmi-relay"
UseTLS=true
[[gateway.inout]]
account="kosmi.hyperspaceout"
channel="main"
[[gateway.inout]]
account="irc.libera"
channel="#your-channel" # ← Change to your IRC channel
account="irc.zeronode"
channel="#your-channel"
```
### 2. Build the Docker Image
@@ -53,9 +45,10 @@ docker-compose build
```
This will:
- Install Chrome/Chromium in the container
- Build the Matterbridge binary with Kosmi support
- Create an optimized production image
- Use the Go 1.23 Alpine base image
- Install Chromium (used only for email/password authentication)
- Build the Matterbridge binary
- Create a production image
**Build time**: ~5-10 minutes (first time)
@@ -72,89 +65,51 @@ docker-compose up
### 4. Verify It's Working
```bash
# Check container status
docker-compose ps
docker-compose logs -f
```
# View logs
docker-compose logs -f matterbridge
# Look for these messages:
# INFO Successfully connected to Kosmi via Chrome
# INFO Successfully connected to IRC
# INFO Gateway(s) started successfully
Look for:
```
INFO Successfully connected to Kosmi
INFO Connection succeeded (IRC)
INFO Gateway(s) started successfully. Now relaying messages
```
### 5. Test Message Relay
1. **Kosmi IRC**: Send a message in your Kosmi room
- Should appear in IRC as: `[Kosmi] <username> message`
1. **Kosmi --> IRC**: Send a message in your Kosmi room
- Should appear in IRC as: `[kosmi] <username> message`
2. **IRC Kosmi**: Send a message in your IRC channel
- Should appear in Kosmi as: `[IRC] <username> message`
2. **IRC --> Kosmi**: Send a message in your IRC channel
- Should appear in Kosmi as: `[irc] <username> message`
## Docker Commands Reference
### Container Management
```bash
# Start the bridge
docker-compose up -d
# Stop the bridge
docker-compose down
# Restart the bridge
docker-compose restart
# View logs
docker-compose logs -f
# View last 100 lines of logs
docker-compose logs --tail=100
# Check container status
docker-compose ps
# Execute commands in running container
docker-compose exec matterbridge sh
```
### Debugging
```bash
# Enable debug logging (edit docker-compose.yml first)
# Set Debug=true in matterbridge.toml, then:
docker-compose restart
# Check Chrome is installed
docker-compose exec matterbridge which chromium
# Check configuration
docker-compose exec matterbridge cat /app/matterbridge.toml
# Test connectivity
docker-compose exec matterbridge ping -c 3 app.kosmi.io
docker-compose exec matterbridge ping -c 3 irc.libera.chat
docker-compose up -d # Start
docker-compose down # Stop
docker-compose restart # Restart
docker-compose logs -f # Follow logs
docker-compose logs --tail=100 # Last 100 lines
docker-compose ps # Container status
docker-compose exec matterbridge sh # Shell into container
```
### Updating
```bash
# Pull latest code
git pull
# Rebuild image
docker-compose build --no-cache
# Restart with new image
docker-compose up -d
```
## Configuration Options
### docker-compose.yml
## docker-compose.yml Reference
```yaml
version: '3.8'
services:
matterbridge:
build:
@@ -162,241 +117,154 @@ services:
dockerfile: Dockerfile
container_name: kosmi-irc-relay
restart: unless-stopped
command: ["-conf", "/app/matterbridge.toml"]
volumes:
- ./matterbridge.toml:/app/matterbridge.toml:ro
- ./logs:/app/logs
- ./matterbridge.toml:/app/matterbridge.toml:ro,z
- ./logs:/app/logs:z
- ./data:/app/data:z
environment:
- CHROME_BIN=/usr/bin/chromium
- CHROME_PATH=/usr/bin/chromium
- TZ=America/New_York # ← Change to your timezone
security_opt:
- seccomp:unconfined # Required for Chrome
- TZ=America/New_York
- MATTERBRIDGE_DATA_DIR=/app/data
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
```
### Starting Muted
To start with Jackbox announcements suppressed:
```yaml
command: ["-conf", "/app/matterbridge.toml", "-muted"]
```
### Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `CHROME_BIN` | Path to Chrome binary | `/usr/bin/chromium` |
| `CHROME_PATH` | Chrome executable path | `/usr/bin/chromium` |
| `TZ` | Timezone for logs | `America/New_York` |
| `DEBUG` | Enable debug logging | `0` |
| `TZ` | Timezone for log timestamps | `America/New_York` |
| `MATTERBRIDGE_DATA_DIR` | Directory for persistent data (token cache) | `/app/data` |
| `DEBUG` | Set to `1` for debug logging | `0` |
| `CHROME_BIN` | Path to Chrome binary (set in Dockerfile) | `/usr/bin/chromium-browser` |
Note: `CHROME_BIN` and `CHROME_PATH` are set in the Dockerfile. Chromium is installed in the container for email/password authentication. If you only use anonymous access, Chrome is present but unused.
### Volume Mounts
| Host Path | Container Path | Purpose |
|-----------|----------------|---------|
| `./matterbridge.toml` | `/app/matterbridge.toml` | Configuration file (read-only) |
| `./matterbridge.toml` | `/app/matterbridge.toml` | Configuration (read-only) |
| `./logs` | `/app/logs` | Log files (optional) |
| `./data` | `/app/data` | Persistent data: token cache |
The `./data` volume is important for email/password auth -- it stores the cached JWT so the bot doesn't need to re-authenticate on every restart. See [TOKEN_PERSISTENCE.md](TOKEN_PERSISTENCE.md).
## Troubleshooting
### Container Won't Start
**Check logs**:
```bash
docker-compose logs
```
**Common issues**:
- Configuration file syntax error
- Missing `matterbridge.toml`
- Port already in use
Common causes:
- TOML syntax error in `matterbridge.toml`
- Missing configuration file
- Port conflict (if webhook port is exposed)
### Kosmi Connection Fails
**Solution**:
```bash
# Validate TOML syntax
docker run --rm -v $(pwd)/matterbridge.toml:/config.toml alpine sh -c "apk add --no-cache go && go install github.com/pelletier/go-toml/cmd/tomll@latest && tomll /config.toml"
# Check if file exists
ls -la matterbridge.toml
# Check connectivity from inside the container
docker-compose exec matterbridge wget -q -O - https://app.kosmi.io > /dev/null && echo "OK"
```
### Chrome/Chromium Not Found
- Verify `RoomURL` is correct
- Enable debug logging (`Debug=true` in the Kosmi config section)
**Symptoms**:
```
ERROR Chrome binary not found
```
### IRC Connection Fails
**Solution**:
```bash
# Rebuild image
docker-compose build --no-cache
# Verify Chrome is installed
docker-compose run --rm matterbridge which chromium
```
### WebSocket Connection Failed
**Symptoms**:
```
ERROR Failed to connect to Kosmi
ERROR WebSocket connection failed
```
**Solution**:
```bash
# Test network connectivity
docker-compose exec matterbridge ping -c 3 app.kosmi.io
# Check if room URL is correct
docker-compose exec matterbridge cat /app/matterbridge.toml | grep RoomURL
# Enable debug logging
# Edit matterbridge.toml: Debug=true
docker-compose restart
```
### IRC Connection Failed
**Symptoms**:
```
ERROR Failed to connect to IRC
ERROR Connection refused
```
**Solution**:
```bash
# Test IRC connectivity
docker-compose exec matterbridge nc -zv irc.libera.chat 6667
# Check IRC configuration
docker-compose exec matterbridge cat /app/matterbridge.toml | grep -A 10 "\[irc\]"
# Verify nickname isn't already in use
# Try changing Nick in matterbridge.toml
docker-compose exec matterbridge nc -zv irc.libera.chat 6697
```
- Verify server address and port
- Check TLS settings
- Try a different nick if the current one is registered/in use
### Messages Not Relaying
**Symptoms**:
- Container running
- Both bridges connected
- But messages don't appear
1. Confirm both bridges are connected in logs
2. Verify gateway config: Kosmi channel = `"main"`, IRC channel includes `#`
3. Enable debug logging and watch for message routing
### Authentication Issues
If using email/password:
- Delete cached token: `rm ./data/kosmi_token_cache.json`
- Rebuild container: `docker-compose build --no-cache`
- Check Chromium is working: `docker-compose exec matterbridge chromium-browser --version`
## Runtime Operations
### Mute Toggle
Toggle Jackbox announcements without restarting:
**Solution**:
```bash
# Enable debug logging
# Edit matterbridge.toml: Debug=true
docker-compose restart
# Watch logs for message flow
docker-compose logs -f | grep -E "Received|Sending|Forwarding"
# Verify gateway configuration
docker-compose exec matterbridge cat /app/matterbridge.toml | grep -A 20 "\[\[gateway\]\]"
# Check channel names match exactly
# Kosmi channel should be "main"
# IRC channel should include # (e.g., "#your-channel")
docker kill -s SIGUSR1 kosmi-irc-relay
```
### High Memory Usage
See [MUTE_CONTROL.md](MUTE_CONTROL.md) for details.
**Symptoms**:
- Container using >500MB RAM
- System slowdown
### Force Token Refresh
**Solution**:
```bash
# Add memory limits to docker-compose.yml
services:
matterbridge:
mem_limit: 512m
mem_reservation: 256m
# Restart
docker-compose down
rm ./data/kosmi_token_cache.json
docker-compose up -d
```
### Permission Denied Errors
**Symptoms**:
```
ERROR Permission denied writing to /app/logs
```
**Solution**:
```bash
# Create logs directory with correct permissions
mkdir -p logs
chmod 777 logs
# Or run container as root (not recommended)
# Edit docker-compose.yml:
# user: root
```
## Production Deployment
### Using Docker Swarm
### View Token Status
```bash
# Initialize swarm
docker swarm init
# Deploy stack
docker stack deploy -c docker-compose.yml kosmi-relay
# Check status
docker stack services kosmi-relay
# View logs
docker service logs -f kosmi-relay_matterbridge
cat ./data/kosmi_token_cache.json | python3 -m json.tool
```
### Using Kubernetes
## Resource Usage
Create `kosmi-relay.yaml`:
The bridge uses a native WebSocket connection, so resource requirements are modest:
- **Memory**: ~50-100MB typical (lower than browser-based approaches)
- **CPU**: Minimal (event-driven, no polling)
- **Network**: WebSocket to Kosmi + IRC connection
### Memory Limits (Optional)
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: kosmi-irc-relay
spec:
replicas: 1
selector:
matchLabels:
app: kosmi-irc-relay
template:
metadata:
labels:
app: kosmi-irc-relay
spec:
containers:
- name: matterbridge
image: kosmi-irc-relay:latest
volumeMounts:
- name: config
mountPath: /app/matterbridge.toml
subPath: matterbridge.toml
env:
- name: CHROME_BIN
value: /usr/bin/chromium
- name: TZ
value: America/New_York
securityContext:
capabilities:
add:
- SYS_ADMIN # Required for Chrome
volumes:
- name: config
configMap:
name: matterbridge-config
services:
matterbridge:
mem_limit: 256m
mem_reservation: 64m
```
Deploy:
```bash
kubectl create configmap matterbridge-config --from-file=matterbridge.toml
kubectl apply -f kosmi-relay.yaml
## Production Considerations
### Log Rotation
Already configured in `docker-compose.yml`:
```yaml
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
```
### Monitoring
#### Health Check
### Health Check
Add to `docker-compose.yml`:
@@ -408,127 +276,20 @@ services:
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
start_period: 30s
```
#### Prometheus Metrics
### Security
Matterbridge doesn't expose Prometheus metrics by default, but you can monitor:
- Configuration is mounted read-only (`:ro`)
- Token cache directory has restricted access inside the container
- Credentials in `matterbridge.toml` should be protected: `chmod 600 matterbridge.toml`
- Do not commit `matterbridge.toml` with real credentials to version control
```bash
# Container metrics
docker stats kosmi-irc-relay
# Log-based monitoring
docker-compose logs -f | grep -E "ERROR|WARN"
```
### Backup and Restore
```bash
# Backup configuration
cp matterbridge.toml matterbridge.toml.backup
# Backup logs
tar -czf logs-$(date +%Y%m%d).tar.gz logs/
# Restore configuration
cp matterbridge.toml.backup matterbridge.toml
docker-compose restart
```
## Security Best Practices
1. **Run as non-root user** (already configured in Dockerfile)
2. **Use read-only configuration mount**
3. **Limit container resources**
4. **Keep Docker images updated**
5. **Use secrets for sensitive data** (e.g., IRC passwords)
### Using Docker Secrets
```bash
# Create secret
echo "your_irc_password" | docker secret create irc_password -
# Update docker-compose.yml
services:
matterbridge:
secrets:
- irc_password
secrets:
irc_password:
external: true
```
## Performance Tuning
### Resource Limits
```yaml
services:
matterbridge:
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
```
### Chrome Optimization
Add to `docker-compose.yml`:
```yaml
services:
matterbridge:
environment:
- CHROME_FLAGS=--disable-dev-shm-usage --no-sandbox --disable-setuid-sandbox
```
## Next Steps
- ✅ Bridge is running in Docker
- 🔄 Set up monitoring and alerts
- 🔄 Configure log rotation
- 🔄 Set up automatic backups
- 🔄 Add more bridges (Discord, Slack, etc.)
## Getting Help
- Check logs: `docker-compose logs -f`
- Enable debug: Set `Debug=true` in `matterbridge.toml`
- Review `LESSONS_LEARNED.md` for common issues
- Check `QUICK_REFERENCE.md` for troubleshooting tips
## Example: Complete Setup
```bash
# 1. Clone repository
git clone <your-repo> kosmi-irc-relay
cd kosmi-irc-relay
# 2. Edit configuration
nano matterbridge.toml
# Update RoomURL, IRC server, channel
# 3. Build and start
docker-compose up -d
# 4. Watch logs
docker-compose logs -f
# 5. Test by sending messages in both Kosmi and IRC
# 6. If issues, enable debug
nano matterbridge.toml # Set Debug=true
docker-compose restart
docker-compose logs -f
```
That's it! Your Kosmi-IRC bridge is now running in Docker! 🎉
## Further Reading
- [README.md](../../README.md) -- Project overview
- [DOCKER_QUICKSTART.md](DOCKER_QUICKSTART.md) -- 5-minute quick start
- [JACKBOX_INTEGRATION.md](JACKBOX_INTEGRATION.md) -- Jackbox Game Picker setup
- [TOKEN_PERSISTENCE.md](TOKEN_PERSISTENCE.md) -- Token caching details
- [IRC.md](../IRC.md) -- IRC commands and voting

View File

@@ -1,6 +1,6 @@
# Docker Quick Start - 5 Minutes to Running Bridge
# Docker Quick Start -- 5 Minutes to Running Bridge
Get your Kosmi-IRC bridge running in Docker in 5 minutes!
Get your Kosmi-IRC bridge running in Docker in 5 minutes.
## Prerequisites
@@ -13,22 +13,27 @@ Get your Kosmi-IRC bridge running in Docker in 5 minutes!
### 1. Edit Configuration (2 minutes)
Open `matterbridge.toml` and change these 3 things:
```bash
cp matterbridge.toml.example matterbridge.toml
nano matterbridge.toml
```
Change these 3 things:
```toml
[kosmi.hyperspaceout]
RoomURL="https://app.kosmi.io/room/@YOUR_ROOM" # Your Kosmi room
RoomURL="https://app.kosmi.io/room/@YOUR_ROOM" # <-- Your Kosmi room
[irc.libera]
Server="irc.libera.chat:6667" # Your IRC server
Nick="kosmi-relay" # Your bot's nickname
[irc.zeronode]
Server="irc.libera.chat:6697" # <-- Your IRC server
Nick="kosmi-relay" # <-- Your bot's nickname
[[gateway.inout]]
account="irc.libera"
channel="#your-channel" # Your IRC channel
account="irc.zeronode"
channel="#your-channel" # <-- Your IRC channel
```
### 2. Build & Run (2 minutes)
### 2. Build and Run (2 minutes)
```bash
docker-compose up -d
@@ -42,19 +47,15 @@ docker-compose logs -f
Look for:
```
INFO Successfully connected to Kosmi via Chrome
INFO Successfully connected to IRC
INFO Gateway(s) started successfully
INFO Successfully connected to Kosmi
INFO Connection succeeded (IRC)
INFO Gateway(s) started successfully. Now relaying messages
```
### 4. Test It!
### 4. Test It
- Send a message in Kosmi should appear in IRC
- Send a message in IRC should appear in Kosmi
## That's It! 🎉
Your bridge is running!
- Send a message in Kosmi --> should appear in IRC
- Send a message in IRC --> should appear in Kosmi
## Common Commands
@@ -78,52 +79,31 @@ docker-compose build && docker-compose up -d
1. Check your configuration:
```bash
cat matterbridge.toml | grep -E "RoomURL|Server|channel"
grep -E "RoomURL|Server|channel" matterbridge.toml
```
2. Enable debug logging:
- Edit `matterbridge.toml`: Set `Debug=true`
- Edit `matterbridge.toml`: Set `Debug=true` under the appropriate bridge section
- Restart: `docker-compose restart`
- Watch logs: `docker-compose logs -f`
### "Chrome not found"
```bash
# Rebuild image
docker-compose build --no-cache
docker-compose up -d
```
### "Messages not relaying"
1. Check both bridges are connected:
```bash
docker-compose logs | grep -i "connected"
docker-compose logs | grep -i "connected\|succeeded"
```
2. Verify channel names:
- Kosmi channel must be `"main"`
- IRC channel must include `#` (e.g., `"#your-channel"`)
## Persistent Token Cache
If using email/password authentication, the JWT is cached in `./data/` which is mounted into the container. This survives container rebuilds and restarts. See [TOKEN_PERSISTENCE.md](TOKEN_PERSISTENCE.md) for details.
## Need More Help?
- Full guide: See `DOCKER_DEPLOYMENT.md`
- Troubleshooting: See `QUICK_REFERENCE.md`
- Implementation details: See `LESSONS_LEARNED.md`
## Example Output (Success)
```
INFO[...] Starting Matterbridge
INFO[...] Launching headless Chrome for Kosmi connection
INFO[...] Injecting WebSocket interceptor (runs before page load)...
INFO[...] ✓ WebSocket hook confirmed installed
INFO[...] Status: WebSocket connection intercepted
INFO[...] Successfully connected to Kosmi via Chrome
INFO[...] Connecting to IRC server irc.libera.chat:6667
INFO[...] Successfully connected to IRC
INFO[...] Gateway(s) started successfully. Now relaying messages
```
Now send a test message and watch it relay! 🚀
- Full Docker guide: [DOCKER_DEPLOYMENT.md](DOCKER_DEPLOYMENT.md)
- Quick reference: [QUICK_REFERENCE.md](QUICK_REFERENCE.md)
- IRC commands: [IRC.md](../IRC.md)

View File

@@ -1,203 +1,226 @@
# Jackbox Game Picker API Integration
This document describes how the Kosmi/IRC relay integrates with the Jackbox Game Picker API for live voting and game notifications.
This document describes how the Kosmi/IRC relay integrates with the Jackbox Game Picker API for live voting, game announcements, and room code display.
## Features
## Overview
### 1. Vote Detection
The relay automatically detects when users vote on games using the `thisgame++` or `thisgame--` syntax in either Kosmi or IRC chat.
When enabled, the relay:
**How it works:**
- Users type `thisgame++` to upvote the current game
- Users type `thisgame--` to downvote the current game
- Votes are case-insensitive
- The relay filters out relayed messages (messages with `[irc]` or `[kosmi]` prefix) to prevent duplicate votes
- Only votes from actual users in each chat are sent to the API
### 2. Game Notifications
When a new game is added to the Jackbox session via the API, the relay receives a webhook notification and broadcasts it to both Kosmi and IRC chats.
**Example notification:**
```
🎮 Coming up next: Fibbage 4!
```
1. **Detects votes** in chat (`thisgame++`/`thisgame--` and ticker-symbol votes) and submits them to the API
2. **Receives game events** via WebSocket (or webhook fallback) and announces them to all bridges
3. **Responds to `!votes`** with the current game's vote tally
4. **Optionally generates room code images** and uploads them to Kosmi
## Configuration
Add the following section to your `matterbridge.toml`:
Add the `[jackbox]` section to `matterbridge.toml`:
```toml
[jackbox]
# Enable Jackbox integration for vote detection and game notifications
# Enable Jackbox integration
Enabled=true
# Jackbox API URL
APIURL="http://localhost:5000"
APIURL="https://your-jackbox-api.example.com"
# Admin password for API authentication
AdminPassword="your_admin_password_here"
# Admin password for API authentication (exchanged for JWT)
AdminPassword="your_admin_password"
# Webhook server port (for receiving game notifications)
# Use WebSocket for real-time game notifications (recommended, default: true)
# Set to false to use webhook HTTP server instead
UseWebSocket=true
# --- Webhook settings (only if UseWebSocket=false) ---
# Port for the webhook HTTP server
WebhookPort=3001
# HMAC-SHA256 secret for webhook signature verification
WebhookSecret="your_webhook_secret"
# Webhook secret for signature verification
WebhookSecret="your_webhook_secret_here"
# --- Room code image settings ---
# Generate and upload a room code image to Kosmi chat
EnableRoomCodeImage=true
# Seconds to wait before sending the image announcement (default: 0)
RoomCodeImageDelay=28
# Seconds to wait before sending the plaintext room code follow-up (default: 29)
RoomCodePlaintextDelay=22
```
### Configuration Options
### Configuration Reference
- **Enabled**: Set to `true` to enable the integration, `false` to disable
- **APIURL**: The URL of your Jackbox Game Picker API (e.g., `http://localhost:5000`)
- **AdminPassword**: Your API admin password (used to authenticate and get a JWT token)
- **WebhookPort**: Port for the webhook server to listen on (default: 3001)
- **WebhookSecret**: Shared secret for webhook signature verification (must match the secret configured in the API)
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `Enabled` | bool | `false` | Master switch for the integration |
| `APIURL` | string | | Base URL of the Jackbox Game Picker API |
| `AdminPassword` | string | | Admin password, exchanged for a JWT via `POST /api/auth/login` |
| `UseWebSocket` | bool | `true` | Use WebSocket transport for events (recommended) |
| `WebhookPort` | int | `3001` | HTTP listen port for webhook server |
| `WebhookSecret` | string | | HMAC secret for webhook signature verification |
| `EnableRoomCodeImage` | bool | `false` | Generate/upload room code images |
| `RoomCodeImageDelay` | int | `0` | Seconds before image announcement |
| `RoomCodePlaintextDelay` | int | `29` | Seconds before plaintext room code |
## Setup Steps
## Transport: WebSocket vs. Webhook
### 1. Configure the Relay
### WebSocket (Recommended)
Edit `matterbridge.toml` and add the Jackbox configuration section with your API URL, admin password, and webhook secret.
When `UseWebSocket=true`, the relay connects to `wss://<APIURL>/api/sessions/live` and authenticates with its JWT. Events are received in real-time with automatic reconnection.
### 2. Register the Webhook
Handled events:
- `session.started` -- announces game night start
- `game.added` -- announces next game with room code
- `session.ended` -- announces game night end
After starting the relay, register the webhook with the Jackbox API:
No additional setup is required beyond the config above.
### Webhook (Fallback)
When `UseWebSocket=false`, the relay starts an HTTP server on `WebhookPort` and listens for `POST /webhook/jackbox` requests from the API.
You must register the webhook with the API:
```bash
# Get JWT token
curl -X POST "http://localhost:5000/api/auth/login" \
TOKEN=$(curl -s -X POST "$API_URL/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"apiKey": "your_admin_password"}'
-d '{"key": "your_admin_password"}' | jq -r .token)
# Register webhook (use the JWT token from above)
curl -X POST "http://localhost:5000/api/webhooks" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
# Register webhook
curl -X POST "$API_URL/api/webhooks" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Kosmi/IRC Relay",
"url": "http://your-relay-host:3001/webhook/jackbox",
"secret": "your_webhook_secret_here",
"secret": "your_webhook_secret",
"events": ["game.added"]
}'
```
**Important:** Replace `your-relay-host` with the actual hostname or IP address where your relay is running. If the API and relay are on the same machine, you can use `localhost`.
Webhook requests include an `X-Webhook-Signature` header with an HMAC-SHA256 signature for verification.
### 3. Test the Integration
## Vote Detection
#### Test Vote Detection
The relay automatically detects vote patterns in non-relayed messages from both IRC and Kosmi.
1. Start an active session in the Jackbox API with some games played
2. Send a message in Kosmi or IRC: `thisgame++`
3. Check the relay logs for: `Detected vote from <username>: up`
4. Verify the vote was recorded in the API
### Supported Syntax
#### Test Game Notifications
| Pattern | Effect |
|---------|--------|
| `thisgame++` | Upvote currently playing game |
| `thisgame--` | Downvote currently playing game |
| `SYMBOL++` | Upvote game by ticker symbol |
| `SYMBOL--` | Downvote game by ticker symbol |
1. Add a game to the active session via the Jackbox API
2. Both Kosmi and IRC chats should receive a notification: `🎮 Coming up next: <game title>!`
- All patterns are case-insensitive
- `thisgame` votes can appear anywhere in the message
- Ticker symbols are 2-4 alphanumeric characters that map to specific games (see [IRC.md](../IRC.md) for the full ticker table)
- Messages prefixed with `[irc]` or `[kosmi]` are treated as relayed and skip vote detection
Votes are submitted to `POST /api/votes/live` with the voter's username, vote type, timestamp, and optional ticker symbol.
### Deduplication
The API rejects duplicate votes from the same user within 1 second.
## IRC/Kosmi Commands
### `!votes`
Available in both IRC and Kosmi chat. Queries the current game's vote tally and broadcasts the result to all bridges:
```
🗳️ Fibbage 4 • Today: 5👍 2👎 (Score: 3) • All-time: 46👍 3👎 (Score: 43)
```
Requires an active session with a game currently "playing".
### Reconnect Commands (IRC only)
| Command | Effect |
|---------|--------|
| `!jreconnect` | Reconnect the Jackbox WebSocket |
| `!kreconnect` | Reconnect the Kosmi bridge |
| `!reconnect` | Reconnect all non-IRC services |
## Room Code Images
When `EnableRoomCodeImage=true` and a `game.added` event includes a room code, the relay:
1. Generates an image of the room code
2. Uploads it to the Kosmi CDN (`img.kosmi.io`)
3. Sends the image + announcement message after `RoomCodeImageDelay` seconds
4. Sends a plaintext room code follow-up after `RoomCodePlaintextDelay` seconds
If image generation or upload fails, the relay falls back to plaintext-only announcement.
## Mute Control
Jackbox announcements (game start, game added, session end) can be suppressed without stopping the relay:
- Start muted: `./matterbridge -conf matterbridge.toml -muted`
- Toggle at runtime: `kill -SIGUSR1 <pid>` or `docker kill -s SIGUSR1 <container>`
When muted:
- Announcements are suppressed
- Vote detection still works (votes are still sent to the API)
- Normal IRC/Kosmi message relay is unaffected
- `!votes` still works
See [MUTE_CONTROL.md](MUTE_CONTROL.md) for full details.
## How It Works
### Vote Flow
1. User sends a message containing `thisgame++` or `thisgame--` in Kosmi or IRC
2. The bridge detects the vote pattern (case-insensitive)
3. The bridge checks if the message is relayed (has `[irc]` or `[kosmi]` prefix)
4. If not relayed, the bridge extracts the username and vote type
5. The bridge sends the vote to the Jackbox API via HTTP POST to `/api/votes/live`
6. The API records the vote and associates it with the current game based on timestamp
1. User sends a message containing `thisgame++` (or similar) in IRC or Kosmi
2. The bridge detects the vote pattern (skipping relayed messages)
3. The bridge submits `POST /api/votes/live` with username, vote type, and timestamp
4. The API records the vote against the currently playing game
### Notification Flow
### Event Flow (WebSocket)
1. A game is added to an active session in the Jackbox API
2. The API sends a webhook POST request to `http://your-relay-host:3001/webhook/jackbox`
3. The webhook includes an HMAC-SHA256 signature in the `X-Webhook-Signature` header
4. The relay verifies the signature using the configured webhook secret
5. If valid, the relay parses the `game.added` event
6. The relay broadcasts the game announcement to all connected bridges (Kosmi and IRC)
1. The relay authenticates with the Jackbox API and opens a WebSocket
2. When a session starts, the relay subscribes to that session's events
3. `game.added` events trigger game announcements broadcast to all bridges
4. `session.ended` triggers the end-of-night message
## Security
### Authentication
### Webhook Signature Verification
All incoming webhooks are verified using HMAC-SHA256 signatures to ensure they come from the legitimate Jackbox API.
**How it works:**
1. The API computes `HMAC-SHA256(webhook_secret, request_body)`
2. The signature is sent in the `X-Webhook-Signature` header as `sha256=<hex_signature>`
3. The relay computes the expected signature using the same method
4. The relay uses timing-safe comparison to verify the signatures match
5. If verification fails, the webhook is rejected with a 401 Unauthorized response
### JWT Authentication
The relay authenticates with the Jackbox API using the admin password to obtain a JWT token. This token is:
- Cached to avoid re-authentication on every vote
- Automatically refreshed if it expires (detected via 401 response)
- Valid for 24 hours (configurable in the API)
The relay authenticates with `POST /api/auth/login` using the `AdminPassword` to get a JWT. The token is cached in memory and refreshed automatically on 401 responses.
## Troubleshooting
### Votes Not Being Recorded
**Possible causes:**
- No active session in the Jackbox API
- Vote timestamp doesn't match any played game
- Duplicate vote within 1 second
- Authentication failure
- Verify an active session exists with games played
- Check that the vote timestamp matches a played game's timeframe
- Look for `"Detected vote from"` in debug logs
- Look for `"Failed to send vote"` errors
- Confirm the message isn't prefixed with `[irc]`/`[kosmi]` (relayed messages skip vote detection)
**Check:**
1. Relay logs for vote detection: `grep "Detected vote" logs/matterbridge.log`
2. Relay logs for API errors: `grep "Failed to send vote" logs/matterbridge.log`
3. API logs for incoming vote requests
### Announcements Not Appearing
### Webhooks Not Being Received
- Verify `Enabled=true` in config
- Check `APIURL` is accessible and `AdminPassword` is correct
- Look for `"Jackbox WebSocket connected"` in logs (if using WebSocket)
- If using webhooks, verify the webhook is registered and the port is accessible
- Check mute state: look for `"MUTED"` in logs
**Possible causes:**
- Webhook URL is not accessible from the API server
- Webhook not registered in the API
- Signature verification failing
- Firewall blocking the webhook port
### WebSocket Disconnects
**Check:**
1. Verify webhook is registered: `GET /api/webhooks` (with JWT token)
2. Test webhook manually: `POST /api/webhooks/test/:id` (with JWT token)
3. Check webhook logs in API: `GET /api/webhooks/:id/logs` (with JWT token)
4. Verify webhook server is listening: `curl http://localhost:3001/health`
5. Check relay logs for signature verification errors
- The relay auto-reconnects with exponential backoff
- Use `!jreconnect` from IRC to force an immediate reconnection
- Check API server availability
### Authentication Failures
**Possible causes:**
- Incorrect admin password in configuration
- API is not running or not accessible
**Check:**
1. Verify API URL is correct and accessible
2. Test authentication manually:
```bash
curl -X POST "http://localhost:5000/api/auth/login" \
```bash
# Test authentication manually
curl -X POST "$API_URL/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"apiKey": "your_admin_password"}'
```
3. Check relay logs for authentication errors
## Logs
The relay logs all Jackbox-related activity with the `jackbox` prefix:
```
[jackbox] Initializing Jackbox integration...
[jackbox] Successfully authenticated with Jackbox API
[jackbox] Starting Jackbox webhook server on port 3001
[kosmi] Detected vote from Anonymous Llama: up
[jackbox] Vote recorded for Fibbage 4: Anonymous Llama - 5👍 2👎
[jackbox] Broadcasting Jackbox message: 🎮 Coming up next: Quiplash 3!
-d '{"key": "your_admin_password"}'
```
## Disabling the Integration
To disable the Jackbox integration, set `Enabled=false` in the `[jackbox]` section of `matterbridge.toml` and restart the relay.
The relay will continue to function normally for Kosmi ↔ IRC message relay without any Jackbox features.
## Disabling
Set `Enabled=false` in the `[jackbox]` section and restart. The relay continues to function normally for IRC/Kosmi message relay without any Jackbox features.

View File

@@ -1,301 +1,130 @@
# Quick Start Guide
Get the Kosmi-IRC bridge running in minutes!
Get the Kosmi-IRC bridge running in minutes.
## Prerequisites
- Go 1.21 or higher
- Chrome/Chromium browser installed (for headless browser automation)
- Access to a Kosmi room
- Go 1.23 or higher
- Chrome/Chromium (only needed for email/password authentication)
- A Kosmi room URL
- (Optional) IRC server access for full relay
## Step 1: Test Kosmi Connection
First, verify the bridge can connect to Kosmi:
## Step 1: Build the Bridge
```bash
# Build the test program
go build -o test-kosmi ./cmd/test-kosmi
cd irc-kosmi-relay
# Run with your Kosmi room URL
./test-kosmi -room "https://app.kosmi.io/room/@hyperspaceout" -debug
```
You should see output like:
```
INFO[...] Starting Kosmi bridge test
INFO[...] Launching headless Chrome for Kosmi connection
INFO[...] Injecting WebSocket interceptor (runs before page load)...
INFO[...] Navigating to Kosmi room: https://app.kosmi.io/room/@hyperspaceout
INFO[...] ✓ WebSocket hook confirmed installed
INFO[...] Status: WebSocket connection intercepted
INFO[...] Successfully connected to Kosmi via Chrome
INFO[...] Listening for messages... Press Ctrl+C to exit
```
**Test it**: Send a message in the Kosmi room from your browser. You should see it appear in the terminal like:
```
INFO[...] Received message: [00:02:51] username: [Kosmi] <username> your message here
```
## Step 2: Integrate with Full Matterbridge
### Option A: Copy into Existing Matterbridge
If you already have Matterbridge:
```bash
# Navigate to your Matterbridge directory
cd /path/to/matterbridge
# Copy the Kosmi bridge
cp -r /path/to/irc-kosmi-relay/bridge/kosmi bridge/
# Copy the bridge registration
cp /path/to/irc-kosmi-relay/gateway/bridgemap/bkosmi.go gateway/bridgemap/
# Add dependencies
go get github.com/chromedp/chromedp@v0.11.2
go mod tidy
# Download dependencies
go mod download
# Build
go build
go build -o matterbridge .
```
### Option B: Use This Repository
## Step 2: Configure
This repository has a minimal Matterbridge structure. To add IRC support:
Copy and edit the example configuration:
1. Copy IRC bridge from Matterbridge:
```bash
# From the Matterbridge repo
cp -r bridge/irc /path/to/irc-kosmi-relay/bridge/
cp gateway/bridgemap/birc.go /path/to/irc-kosmi-relay/gateway/bridgemap/
```
```bash
cp matterbridge.toml.example matterbridge.toml
nano matterbridge.toml
```
2. Update dependencies:
```bash
cd /path/to/irc-kosmi-relay
go mod tidy
```
## Step 3: Configure
Edit `matterbridge.toml`:
Update these values:
```toml
# Kosmi configuration
[kosmi.hyperspaceout]
RoomURL="https://app.kosmi.io/room/@hyperspaceout"
RoomURL="https://app.kosmi.io/room/@YOUR_ROOM"
# IRC configuration
[irc.libera]
Server="irc.libera.chat:6667"
Nick="kosmi-bot"
UseTLS=false
# Gateway to connect them
[[gateway]]
name="kosmi-irc-relay"
enable=true
[irc.zeronode]
Server="irc.libera.chat:6697"
Nick="your-bot-nick"
UseTLS=true
[[gateway.inout]]
account="kosmi.hyperspaceout"
channel="main"
[[gateway.inout]]
account="irc.libera"
account="irc.zeronode"
channel="#your-channel"
```
**Important**: Replace:
- `https://app.kosmi.io/room/@hyperspaceout` with your Kosmi room URL
- `#your-channel` with your IRC channel
**Important**: The Kosmi channel is always `"main"` (one channel per room). The IRC channel must include `#`.
## Step 4: Run
### Optional: Authenticated Kosmi Access
To use a Kosmi account instead of anonymous access, add credentials:
```toml
[kosmi.hyperspaceout]
RoomURL="https://app.kosmi.io/room/@YOUR_ROOM"
Email="your-email@example.com"
Password="your-password"
```
This requires Chrome/Chromium for the initial browser-based login. The JWT is cached locally for subsequent runs.
## Step 3: Run
```bash
./matterbridge -conf matterbridge.toml
```
Or with debug logging:
```bash
./matterbridge -conf matterbridge.toml -debug
```
## Step 5: Test the Relay
### Expected Output (Success)
1. **Kosmi → IRC**: Send a message in Kosmi. It should appear in IRC as:
```
INFO Running version ...
INFO Connecting to Kosmi
INFO Extracted room ID: @YOUR_ROOM
INFO No credentials provided, using anonymous access
INFO Successfully connected to Kosmi
INFO Connection succeeded (IRC)
INFO Gateway(s) started successfully. Now relaying messages
```
## Step 4: Test the Relay
1. **Kosmi --> IRC**: Send a message in your Kosmi room. It should appear in IRC as:
```
[Kosmi] <username> your message here
[kosmi] <username> your message here
```
2. **IRC Kosmi**: Send a message in IRC. It should appear in Kosmi as:
2. **IRC --> Kosmi**: Send a message in your IRC channel. It should appear in Kosmi as:
```
[IRC] <username> your message here
[irc] <username> your message here
```
## Troubleshooting
### Test program doesn't connect
### Kosmi connection fails
**Check**:
- Is Chrome/Chromium installed and accessible?
- Is the room URL correct?
- Can you access `app.kosmi.io` from your network?
- Try with `-debug` flag for more details
- Verify the room URL is correct (use the full URL from your browser)
- Check network connectivity: `curl -I https://app.kosmi.io`
- Run with `-debug` for detailed WebSocket connection logs
**Solution**:
```bash
# Test Chrome installation
which google-chrome chromium chromium-browser
**Supported room URL formats:**
- `https://app.kosmi.io/room/@roomname`
- `https://app.kosmi.io/room/roomid`
# Test network connectivity
curl -I https://app.kosmi.io
### Authentication issues
# Run with debug
./test-kosmi -room "YOUR_ROOM_URL" -debug
```
- Verify Chrome/Chromium is installed: `which chromium chromium-browser google-chrome`
- Check credentials are correct by logging in manually at `app.kosmi.io`
- Delete the token cache to force a fresh login: `rm -f data/kosmi_token_cache.json`
- See [BROWSER_AUTH_GUIDE.md](BROWSER_AUTH_GUIDE.md) for detailed auth troubleshooting
### Messages not relaying
**Check**:
- Are both bridges connected? Look for "Successfully connected" in logs
- Is the gateway configuration correct?
- Are the channel names correct?
**Solution**:
```bash
# Run with debug to see message flow
./matterbridge -conf matterbridge.toml -debug
# Look for lines like:
# "Received message from Kosmi"
# "Forwarding to Matterbridge"
# "Sending to IRC"
```
### "Room ID extraction failed"
**Check**: Room URL format
**Supported formats**:
- `https://app.kosmi.io/room/@roomname`
- `https://app.kosmi.io/room/roomid`
- `@roomname`
- `roomid`
**Solution**: Use the full URL from your browser's address bar
### Messages not appearing from Kosmi
**Check**:
- Is the WebSocket hook installed? Look for "✓ WebSocket hook confirmed installed"
- Is the WebSocket connection detected? Look for "Status: WebSocket connection intercepted"
- Are messages being captured? Enable debug logging to see message processing
**Solution**:
The bridge uses headless Chrome with a WebSocket interceptor that runs **before page load**. This is critical for capturing messages. The implementation uses `Page.addScriptToEvaluateOnNewDocument` to ensure the hook is installed before any page JavaScript executes.
If messages still aren't appearing:
1. Check Chrome console logs in debug mode
2. Verify the room URL is correct
3. Try sending a test message and watch the debug output
### Cannot send messages to Kosmi
The send functionality uses the headless Chrome instance to inject messages into the Kosmi chat input field.
**To debug**:
1. Enable debug logging with `-debug` flag
2. Look for "Sending message to Kosmi" in logs
3. Check for any JavaScript errors in the browser console logs
- Confirm both bridges are connected (look for success messages in logs)
- Verify channel names match in the gateway configuration
- Enable debug logging to trace message flow
## Next Steps
- **Customize message format**: Edit the format strings in `kosmi.go`
- **Add more bridges**: Matterbridge supports Discord, Slack, Telegram, etc.
- **Set up as a service**: Use systemd or similar to run automatically
- **Monitor logs**: Set up log rotation and monitoring
## Getting Help
- Check `INTEGRATION.md` for detailed integration steps
- Check `README.md` for architecture details
- Enable debug logging for detailed troubleshooting
- Review the chrome extension code in `.examples/` for API details
## Common Use Cases
### Home Server Setup
```toml
# Bridge your Kosmi room with your home IRC server
[kosmi.gamenight]
RoomURL="https://app.kosmi.io/room/@gamenight"
[irc.home]
Server="irc.home.local:6667"
Nick="kosmi-relay"
UseTLS=false
[[gateway]]
name="gamenight"
enable=true
[[gateway.inout]]
account="kosmi.gamenight"
channel="main"
[[gateway.inout]]
account="irc.home"
channel="#gamenight"
```
### Multiple Rooms
```toml
# Bridge multiple Kosmi rooms
[kosmi.room1]
RoomURL="https://app.kosmi.io/room/@room1"
[kosmi.room2]
RoomURL="https://app.kosmi.io/room/@room2"
[irc.libera]
Server="irc.libera.chat:6667"
Nick="kosmi-bot"
# Gateway for room1
[[gateway]]
name="gateway1"
enable=true
[[gateway.inout]]
account="kosmi.room1"
channel="main"
[[gateway.inout]]
account="irc.libera"
channel="#room1"
# Gateway for room2
[[gateway]]
name="gateway2"
enable=true
[[gateway.inout]]
account="kosmi.room2"
channel="main"
[[gateway.inout]]
account="irc.libera"
channel="#room2"
```
## Success!
If you see messages flowing both ways, congratulations! Your Kosmi-IRC bridge is working. 🎉
For advanced configuration and features, see the full documentation in `README.md` and `INTEGRATION.md`.
- [DOCKER_QUICKSTART.md](DOCKER_QUICKSTART.md) -- Deploy with Docker
- [JACKBOX_INTEGRATION.md](JACKBOX_INTEGRATION.md) -- Enable Jackbox Game Picker
- [IRC.md](../IRC.md) -- IRC commands and voting reference
- [MUTE_CONTROL.md](MUTE_CONTROL.md) -- Control Jackbox announcement muting

View File

@@ -1,237 +1,204 @@
# Quick Reference Guide
## Testing the Bridge
## Running the Bridge
### Build and Run Test Program
### Build and Run Locally
```bash
cd /Users/erikfredericks/dev-ai/HSO/irc-kosmi-relay
go build -o test-kosmi ./cmd/test-kosmi
./test-kosmi -room "https://app.kosmi.io/room/@hyperspaceout" -debug
go build -o matterbridge .
./matterbridge -conf matterbridge.toml
```
### Run with Docker
```bash
docker-compose up -d
docker-compose logs -f
```
### Expected Output (Success)
```
INFO Launching headless Chrome for Kosmi connection
INFO Injecting WebSocket interceptor (runs before page load)...
INFO Navigating to Kosmi room: https://app.kosmi.io/room/@hyperspaceout
INFO ✓ WebSocket hook confirmed installed
INFO Status: WebSocket connection intercepted
INFO Successfully connected to Kosmi via Chrome
INFO Listening for messages... Press Ctrl+C to exit
INFO Connecting to Kosmi
INFO Extracted room ID: @yourroom
INFO No credentials provided, using anonymous access
INFO Successfully connected to Kosmi
INFO Connection succeeded (IRC)
INFO Gateway(s) started successfully. Now relaying messages
```
### When Messages Arrive
If using email/password authentication:
```
INFO Processing 1 messages from queue
INFO Received message: [00:02:51] username: [Kosmi] <username> message text
INFO Authenticating with email/password...
INFO ✅ Authentication successful
INFO Successfully connected to Kosmi
```
## Key Status Indicators
| Status Message | Meaning | Action |
|---------------|---------|--------|
| `✓ WebSocket hook confirmed installed` | Hook script is active | ✅ Good |
| `Status: WebSocket connection intercepted` | WebSocket is being captured | ✅ Good |
| `Status: No WebSocket connection detected yet` | Hook missed the WebSocket | ❌ Check injection timing |
| `Processing N messages from queue` | Messages are being captured | ✅ Good |
| Log Message | Meaning |
|-------------|---------|
| `Successfully connected to Kosmi` | Native WebSocket connection established |
| `Connection succeeded (IRC)` | IRC bridge is connected |
| `Gateway(s) started successfully` | All bridges up, relay is active |
| `✅ Using cached token` | Reusing saved JWT (no browser needed) |
| `✅ Authentication successful` | Browser login completed |
| `Kosmi connection lost unexpectedly` | WebSocket dropped, auto-reconnect triggered |
## Common Issues
## IRC Commands
### Issue: "No WebSocket connection detected yet"
| Command | Action |
|---------|--------|
| `!kreconnect` | Reconnect Kosmi bridge |
| `!jreconnect` | Reconnect Jackbox WebSocket |
| `!reconnect` | Reconnect all non-IRC services |
| `!votes` | Show current game vote tally |
**Cause**: WebSocket hook was injected too late
**Fix**: Verify `injectWebSocketHookBeforeLoad()` uses `page.AddScriptToEvaluateOnNewDocument`
### Issue: "Chrome not found"
**Cause**: Chrome/Chromium not installed or not in PATH
**Fix**:
```bash
# macOS
brew install --cask google-chrome
# Ubuntu/Debian
sudo apt install chromium-browser
# Verify installation
which google-chrome chromium chromium-browser
```
### Issue: Messages not appearing
**Cause**: Multiple possibilities
**Debug**:
1. Check for "✓ WebSocket hook confirmed installed" ✓
2. Check for "Status: WebSocket connection intercepted" ✓
3. Enable debug logging: `-debug` flag
4. Send a test message in the Kosmi room
5. Look for "Processing N messages from queue"
See [IRC.md](../IRC.md) for full details including vote syntax and ticker symbols.
## Configuration
### Minimal Test Configuration
### Minimal Configuration
```toml
[kosmi.test]
RoomURL="https://app.kosmi.io/room/@hyperspaceout"
Debug=true
```
[kosmi.myroom]
RoomURL="https://app.kosmi.io/room/@yourroom"
### Full Matterbridge Configuration
```toml
# Kosmi
[kosmi.hyperspaceout]
RoomURL="https://app.kosmi.io/room/@hyperspaceout"
# IRC
[irc.libera]
Server="irc.libera.chat:6667"
[irc.myserver]
Server="irc.libera.chat:6697"
Nick="kosmi-relay"
UseTLS=false
UseTLS=true
# Gateway
[[gateway]]
name="kosmi-irc"
enable=true
[[gateway.inout]]
account="kosmi.hyperspaceout"
account="kosmi.myroom"
channel="main"
[[gateway.inout]]
account="irc.libera"
account="irc.myserver"
channel="#your-channel"
```
### With Authentication
```toml
[kosmi.myroom]
RoomURL="https://app.kosmi.io/room/@yourroom"
Email="your-email@example.com"
Password="your-password"
```
### With Jackbox Integration
```toml
[jackbox]
Enabled=true
APIURL="https://your-jackbox-api.example.com"
AdminPassword="your_password"
UseWebSocket=true
EnableRoomCodeImage=true
```
## Message Format
### Kosmi IRC
### Kosmi --> IRC
```
[Kosmi] <username> message text
[kosmi] <username> message text
```
### IRC Kosmi
### IRC --> Kosmi
```
[IRC] <username> message text
[irc] <username> message text
```
Format is controlled by `RemoteNickFormat` in each bridge's config section.
## File Locations
| File | Purpose |
|------|---------|
| `bridge/kosmi/kosmi.go` | Main bridge implementation |
| `bridge/kosmi/chromedp_client.go` | Headless Chrome client |
| `bridge/kosmi/graphql.go` | GraphQL structures (legacy) |
| `cmd/test-kosmi/main.go` | Standalone test program |
| `matterbridge.toml` | Configuration file |
| `bridge/kosmi/kosmi.go` | Main Kosmi bridge |
| `bridge/kosmi/graphql_ws_client.go` | Native GraphQL WebSocket client |
| `bridge/kosmi/auth.go` | Anonymous login |
| `bridge/kosmi/browser_auth.go` | Chromedp browser login |
| `bridge/kosmi/token_cache.go` | JWT token caching |
| `bridge/irc/handlers.go` | IRC commands and vote detection |
| `bridge/jackbox/client.go` | Jackbox API client |
| `bridge/jackbox/votes.go` | Vote detection logic |
| `matterbridge.toml` | Runtime configuration |
## Key Implementation Details
## Common Issues
### WebSocket Hook Injection
### Kosmi connection fails
**MUST** use `page.AddScriptToEvaluateOnNewDocument` to inject **before page load**:
1. Verify `RoomURL` is correct and the room exists
2. Check network connectivity: `curl -I https://app.kosmi.io`
3. Enable debug logging: `-debug` flag
4. Check logs for WebSocket handshake errors
```go
chromedp.ActionFunc(func(ctx context.Context) error {
_, err := page.AddScriptToEvaluateOnNewDocument(script).Do(ctx)
return err
})
```
### Authentication fails
### Hook Script
1. Verify Chrome/Chromium is installed: `which chromium chromium-browser google-chrome`
2. Check credentials are correct
3. Delete cached token to force fresh login: `rm data/kosmi_token_cache.json`
4. Check for Kosmi UI changes that may break browser automation
Wraps `window.WebSocket` constructor to intercept all WebSocket connections:
### Messages not appearing
```javascript
window.WebSocket = function(url, protocols) {
const socket = new OriginalWebSocket(url, protocols);
// ... interception logic ...
return socket;
};
```
1. Confirm both bridges are connected (check logs)
2. Verify gateway config: Kosmi channel must be `"main"`, IRC channel must have `#`
3. Enable debug logging to trace message routing
4. Check for `RemoteNickFormat` issues
## Debugging Commands
### Jackbox votes not registering
1. Verify Jackbox integration is enabled in config
2. Check that an active session exists with games played
3. Confirm messages don't start with `[irc]` or `[kosmi]` prefix (relayed messages skip vote detection)
## CLI Flags
```bash
# Test Chrome installation
which google-chrome chromium chromium-browser
# Test network connectivity
curl -I https://app.kosmi.io
# Build with verbose output
go build -v -o test-kosmi ./cmd/test-kosmi
# Run with debug logging
./test-kosmi -room "https://app.kosmi.io/room/@hyperspaceout" -debug
# Check for linter errors
go vet ./...
./matterbridge -conf matterbridge.toml # Specify config file
./matterbridge -debug # Enable debug logging
./matterbridge -muted # Start with Jackbox announcements muted
./matterbridge -version # Show version info
```
## Performance Notes
## Mute Toggle
- **Chrome Startup**: ~1-2 seconds
- **Page Load**: ~1-2 seconds
- **Message Latency**: ~100-500ms
- **Memory Usage**: ~100-200MB (Chrome process)
Toggle Jackbox announcements at runtime:
## Security Considerations
```bash
# Local
kill -SIGUSR1 $(pgrep matterbridge)
- Bridge runs Chrome in headless mode (no GUI)
- No credentials stored (anonymous access)
- WebSocket traffic is intercepted in memory only
- Messages are not logged to disk (unless debug logging enabled)
## Production Deployment
### systemd Service Example
```ini
[Unit]
Description=Kosmi-IRC Relay
After=network.target
[Service]
Type=simple
User=matterbridge
WorkingDirectory=/opt/matterbridge
ExecStart=/opt/matterbridge/matterbridge -conf /etc/matterbridge/matterbridge.toml
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
# Docker
docker kill -s SIGUSR1 kosmi-irc-relay
```
### Docker Considerations
## Docker Commands
When running in Docker, ensure:
- Chrome/Chromium is installed in the container
- `--no-sandbox` flag may be needed for Chrome
- Sufficient memory allocation (512MB minimum)
```bash
docker-compose up -d # Start
docker-compose down # Stop
docker-compose restart # Restart
docker-compose logs -f # Follow logs
docker-compose logs --tail=100 # Last 100 lines
docker-compose build --no-cache # Rebuild
```
## Resources
- **Documentation**: See `README.md`, `QUICKSTART.md`, `LESSONS_LEARNED.md`
- **Integration Guide**: See `INTEGRATION.md`
- **Implementation Details**: See `IMPLEMENTATION_SUMMARY.md`
- **ChromeDP Guide**: See `CHROMEDP_IMPLEMENTATION.md`
## Support
For issues:
1. Check this quick reference
2. Review `LESSONS_LEARNED.md` for common patterns
3. Enable debug logging for detailed output
4. Check Chrome console logs in debug mode
## Further Reading
- [README.md](../../README.md) -- Project overview and architecture
- [IRC.md](../IRC.md) -- IRC command and voting reference
- [DOCKER_QUICKSTART.md](DOCKER_QUICKSTART.md) -- 5-minute Docker setup
- [JACKBOX_INTEGRATION.md](JACKBOX_INTEGRATION.md) -- Jackbox Game Picker setup
- [BROWSER_AUTH_GUIDE.md](BROWSER_AUTH_GUIDE.md) -- Email/password authentication
- [MUTE_CONTROL.md](MUTE_CONTROL.md) -- Mute toggle guide

View File

@@ -1,4 +1,4 @@
# Quick Start: Testing Authentication
# Quick Start: Authenticated Kosmi Access
## Step 1: Create Bot Account
@@ -7,88 +7,72 @@
3. Choose a display name (e.g., "HSO Relay Bot")
4. Save the credentials securely
## Step 2: Test with Monitor Script
```bash
cd /Users/erikfredericks/dev-ai/HSO/irc-kosmi-relay
# Run monitor in login mode
./bin/monitor-auth -login
# In the browser that opens:
# 1. Log in with your bot credentials
# 2. Navigate to a room
# 3. Press Ctrl+C to stop
# Review the captured data
cat auth-monitor.log | grep -A 5 "login"
```
## Step 3: Configure Matterbridge
## Step 2: Configure Matterbridge
Edit `matterbridge.toml`:
```toml
[kosmi.hyperspaceout]
RoomURL="https://app.kosmi.io/room/@hyperspaceout"
RoomURL="https://app.kosmi.io/room/@yourroom"
Email="your-bot@example.com"
Password="your-secure-password"
```
## Step 4: Test Connection
## Step 3: Run and Verify
```bash
# Build the bridge
go build
# Run with your config
./matterbridge -conf matterbridge.toml
# Watch the logs for:
# - "Using authenticated connection"
# - "Logged in as: HSO Relay Bot"
# - "Successfully connected to Kosmi"
```
Watch the logs for:
- `"Authenticating with email/password..."` -- browser login starting
- `"✅ Authentication successful"` -- login completed
- `"💾 Token cached"` -- token saved for reuse
- `"Successfully connected to Kosmi"` -- connected to room
On subsequent runs with a cached token:
- `"✅ Using cached token"` -- no browser needed
## Step 4: Test
1. Send a message in the Kosmi room from a browser
2. Verify it appears in your IRC channel
3. Send a message from IRC
4. Verify it appears in Kosmi with the bot's display name
## Verification Checklist
- [ ] Bot account created manually
- [ ] Credentials documented securely
- [ ] Monitor script captured login flow
- [ ] Config file updated with credentials
- [ ] Bot account created at app.kosmi.io
- [ ] Credentials added to `matterbridge.toml`
- [ ] Bridge logs show authenticated connection
- [ ] Bot display name appears correctly in chat
- [ ] Messages relay successfully
- [ ] Messages relay bidirectionally
- [ ] Token cache file exists (`data/kosmi_token_cache.json` or `~/.matterbridge/kosmi_token_cache.json`)
## Troubleshooting
### Wrong account logged in
Check the log for "Logged in as: {name}". If it doesn't match your bot:
- Verify email/password in config
- Check for typos
- Ensure you're using the correct credentials
### Anonymous connection despite credentials
Check that both Email AND Password are set:
Check that both `Email` and `Password` are set and non-empty:
```bash
grep -A 2 "Email=" matterbridge.toml
grep -E "Email|Password" matterbridge.toml
```
### Authentication fails
- Verify Chrome/Chromium is installed
- Test credentials by logging in manually at app.kosmi.io
- Delete cached token to force fresh auth: `rm data/kosmi_token_cache.json`
- Enable debug logging: `-debug`
### Token expired
The bridge should auto-refresh. If not:
- Check logs for "Token refresh failed"
The bridge automatically refreshes tokens expiring within 7 days. If refresh fails:
- Check logs for authentication errors
- Verify credentials are still valid
- Try manual login at app.kosmi.io
- Delete the token cache and restart
## Next Steps
Once authenticated connection works:
- Test reconnection (simulate network failure)
- Monitor for token refresh (wait 24 hours)
- Test with multiple rooms
- Set up as systemd service
See the monitoring script output and logs for detailed information about Kosmi's authentication behavior.
- See [BROWSER_AUTH_GUIDE.md](BROWSER_AUTH_GUIDE.md) for full authentication details
- See [TOKEN_PERSISTENCE.md](TOKEN_PERSISTENCE.md) for token caching behavior

View File

@@ -129,7 +129,7 @@ Potential improvements:
### Build errors
- Ensure all dependencies are installed: `go mod tidy`
- Check Go version: Requires Go 1.19+
- Check Go version: Requires Go 1.23+
- Verify `golang.org/x/image` is available
## References

View File

@@ -1,255 +1,126 @@
# Message Relay Testing Instructions
**Bridge Status**: ✅ **ACTIVE AND READY**
Both Kosmi and IRC are connected. The gateway is relaying messages.
## Current Configuration
- **Kosmi Room**: https://app.kosmi.io/room/@hyperspaceout
- **IRC Server**: irc.zeronode.net:6697
- **IRC Channel**: #cottongin
- **Status**: Both bridges connected and relaying
- **Kosmi Room**: Configured via `RoomURL` in `matterbridge.toml`
- **IRC Server**: Configured via `Server` in `matterbridge.toml`
- **IRC Channel**: Configured via gateway `[[gateway.inout]]`
## Test 1: Kosmi IRC
## Test 1: Kosmi --> IRC
### Steps:
1. Open the Kosmi room in your browser: https://app.kosmi.io/room/@hyperspaceout
2. Type a message in the chat (e.g., "Test message from Kosmi 🚀")
### Steps
1. Open your Kosmi room in a browser
2. Type a test message (e.g., "Test message from Kosmi")
3. Press Enter to send
### Expected Result:
- The message should appear in IRC channel #cottongin
- The logs will show:
```
level=info msg="📨 Received message from Kosmi: ..." prefix=kosmi
level=info msg="Relaying message from kosmi to irc"
```
### Expected Logs
### How to Verify:
Connect to IRC and join #cottongin:
```bash
# Using an IRC client (e.g., irssi, weechat, hexchat)
/connect irc.zeronode.net 6697
/join #cottongin
```
INFO Received message from Kosmi: [<timestamp>] username: message text
DEBUG Forwarding to Matterbridge channel=main account=kosmi.hyperspaceout
```
Or watch the Docker logs:
```bash
docker-compose logs -f | grep -E "(Received|Relaying|Sent)"
### Verification
The message should appear in your IRC channel with the configured `RemoteNickFormat`, typically:
```
[kosmi] <username> Test message from Kosmi
```
---
## Test 2: IRC --> Kosmi
## Test 2: IRC → Kosmi
### Steps
### Steps:
1. Connect to IRC: `irc.zeronode.net:6697`
2. Join channel: `/join #cottongin`
3. Send a message: "Test message from IRC 👋"
1. Connect to your IRC server and join the configured channel
2. Send a test message (e.g., "Test message from IRC")
### Expected Result:
- The message should appear in the Kosmi chat room
- The logs will show:
```
level=info msg="Received message from IRC: ..." prefix=irc
level=info msg="Relaying message from irc to kosmi"
level=info msg="✅ Sent message via Playwright-assisted WebSocket: ..." prefix=kosmi
```
### Expected Logs
### How to Verify:
- Open Kosmi room in browser: https://app.kosmi.io/room/@hyperspaceout
- Check if your IRC message appears in the chat
```
DEBUG <= Sending message from #channel on irc.account to gateway
DEBUG => Sending message to Kosmi
```
---
### Verification
## Watch Logs in Real-Time
The message should appear in the Kosmi room with the configured format, typically:
```
[irc] <username> Test message from IRC
```
## Test 3: Jackbox Vote Detection (if enabled)
### Steps
1. In IRC or Kosmi, type: `thisgame++`
2. Check bot logs
### Expected Logs
```
DEBUG Detected vote from username: up (ticker="")
```
## Test 4: IRC Commands
### Steps
1. In IRC, type: `!votes`
2. Check for broadcast response
### Expected Response (if active session)
```
🗳️ Game Title • Today: X👍 Y👎 (Score: Z) • All-time: X👍 Y👎 (Score: Z)
```
## Watching Logs
```bash
# All logs
docker-compose logs -f
# Only message-related logs
docker-compose logs -f | grep -E "(message|Message|Received|Sent|Relaying)"
# Message-related logs only
docker-compose logs -f | grep -E "(Received|Sending|Forwarding|Detected)"
# Last 50 lines
docker-compose logs --tail=50
```
---
## Success Criteria
## Quick IRC Connection Methods
### Method 1: Web IRC Client
1. Go to: https://web.libera.chat/
2. Connect to: irc.zeronode.net (port 6697, SSL)
3. Join: #cottongin
### Method 2: Command-Line (irssi)
```bash
# Install irssi (if not installed)
brew install irssi # macOS
# or
apt-get install irssi # Linux
# Connect
irssi -c irc.zeronode.net -p 6697
/join #cottongin
```
### Method 3: Command-Line (nc for quick test)
```bash
# Simple netcat connection (read-only)
openssl s_client -connect irc.zeronode.net:6697
# Then type:
NICK testuser
USER testuser 0 * :Test User
JOIN #cottongin
```
---
## What You Should See
### In Docker Logs (Normal Operation):
```
✅ WebSocket established and ready!
✅ Native client fully connected!
Successfully connected to Kosmi
Connection succeeded (IRC)
irc.libera: joining #cottongin
Gateway(s) started successfully. Now relaying messages
```
### When a Message is Relayed (Kosmi → IRC):
```
level=info msg="📨 Received message from Kosmi: username: message text" prefix=kosmi
level=info msg="Handling message from kosmi.hyperspaceout" prefix=router
level=info msg="Relaying message to irc.libera" prefix=router
level=info msg="Sent message to #cottongin" prefix=irc
```
### When a Message is Relayed (IRC → Kosmi):
```
level=info msg="Received message from #cottongin: username: message text" prefix=irc
level=info msg="Handling message from irc.libera" prefix=router
level=info msg="Relaying message to kosmi.hyperspaceout" prefix=router
level=info msg="✅ Sent message via Playwright-assisted WebSocket: message text" prefix=kosmi
```
---
1. Messages sent in Kosmi appear in IRC
2. Messages sent in IRC appear in Kosmi
3. Usernames are displayed correctly with bridge prefix
4. Messages appear within 1-2 seconds
5. No errors in logs during normal relay
## Troubleshooting
### No Messages Appearing
1. **Check bridge is running**:
```bash
docker-compose ps
```
Should show `kosmi-irc-relay` as "Up"
2. **Check logs for errors**:
```bash
docker-compose logs --tail=100
```
3. **Verify connections**:
```bash
docker-compose logs | grep -E "(Connected|connected|joined)"
```
Should show both Kosmi WebSocket and IRC connected
4. **Restart if needed**:
```bash
docker-compose restart
docker-compose logs -f
```
1. Check the container is running: `docker-compose ps`
2. Check for errors: `docker-compose logs --tail=100`
3. Verify both bridges are connected (look for success messages)
4. Restart if needed: `docker-compose restart`
### Messages Only Going One Direction
- **If Kosmi IRC works but IRC Kosmi doesn't**:
- Check Kosmi WebSocket is still connected: `docker-compose logs | grep WebSocket`
- The native client might have disconnected
- Restart: `docker-compose restart`
- **If IRC → Kosmi works but Kosmi → IRC doesn't**:
- Check IRC connection: `docker-compose logs | grep irc`
- Verify IRC authentication
- **Kosmi --> IRC works, IRC --> Kosmi doesn't**: Check Kosmi WebSocket is connected
- **IRC --> Kosmi works, Kosmi --> IRC doesn't**: Check IRC connection status
### Message Formatting Issues
Messages are formatted as:
```
<username> message text
```
Message format is controlled by `RemoteNickFormat` in each bridge's config section. Default: `[{PROTOCOL}] <{NICK}> `
This is configurable in `matterbridge.toml` under the gateway settings.
---
## Success Criteria
✅ **Full Success** means:
1. Message sent in Kosmi appears in IRC #cottongin
2. Message sent in IRC #cottongin appears in Kosmi room
3. Usernames are preserved/displayed correctly
4. Messages appear within 1-2 seconds
5. No errors in logs
---
## Next Steps After Testing
Once both directions work:
1. **Monitor stability** - Let it run for a few hours
2. **Check memory usage**: `docker stats kosmi-irc-relay`
3. **Review message formatting** - Adjust in `matterbridge.toml` if needed
4. **Set up log rotation** - For long-term operation
5. **Consider adding more channels** - Edit `matterbridge.toml`
---
## Quick Commands Reference
## Quick Commands
```bash
# Start in background
docker-compose up -d
# Follow logs
docker-compose logs -f
# Stop
docker-compose down
# Restart
docker-compose restart
# Rebuild
docker-compose up --build -d
# Check status
docker-compose ps
# View resource usage
docker stats kosmi-irc-relay
docker-compose up -d # Start
docker-compose logs -f # Follow logs
docker-compose down # Stop
docker-compose restart # Restart
docker-compose ps # Status
docker stats kosmi-irc-relay # Resource usage
```
---
## Ready to Test!
The bridge is **running and connected**. You can start testing immediately:
1. 🌐 Open: https://app.kosmi.io/room/@hyperspaceout
2. 💬 Send a test message
3. 👀 Watch the logs: `docker-compose logs -f`
4. 🔗 Connect to IRC and verify the message appeared
5. 🔄 Send a message from IRC and check Kosmi
Good luck! 🎉

View File

@@ -8,10 +8,10 @@ The Kosmi bridge now caches JWT authentication tokens to avoid repeated browser
### Token Cache Location
The token cache is stored in a file called `kosmi_token_cache.json` in the following locations:
The token cache is stored in a file called `kosmi_token_cache.json`:
- **Docker (Production)**: `./data/kosmi_token_cache.json` (mounted from your host machine)
- **Local Development**: `~/.matterbridge/kosmi_token_cache.json`
- **Docker**: `./data/kosmi_token_cache.json` (mounted from host via `MATTERBRIDGE_DATA_DIR=/app/data`)
- **Local**: `~/.matterbridge/kosmi_token_cache.json` (or `$MATTERBRIDGE_DATA_DIR/kosmi_token_cache.json` if set)
### Token Cache Structure
@@ -29,9 +29,9 @@ The cache file contains:
### Token Lifecycle
1. **On Startup**: The bridge checks for a cached token
- If found and valid, it uses the cached token (no browser automation needed)
- If expired or expiring within 7 days, it performs fresh authentication
- If not found, it performs fresh authentication
- If found and valid, it uses the cached token (no browser launch needed)
- If expired or expiring within 7 days, it performs fresh browser authentication
- If not found, it performs fresh browser authentication
2. **Token Expiry**: Kosmi JWT tokens expire after 1 year
- The bridge automatically refreshes tokens that expire within 7 days

View File

@@ -119,7 +119,7 @@ func (r *Router) Start() error {
// Start webhook server if Jackbox is enabled
if r.JackboxManager.IsEnabled() {
if err := r.JackboxManager.StartWebhookServer(r.broadcastJackboxMessage); err != nil {
if err := r.JackboxManager.StartWebhookServer(r.broadcastJackboxMessage, r.broadcastJackboxFormattedMessage); err != nil {
r.logger.Errorf("Failed to start Jackbox webhook server: %v", err)
}
}
@@ -274,3 +274,34 @@ func (r *Router) broadcastJackboxMessage(message string) {
}
}
}
// broadcastJackboxFormattedMessage broadcasts per-bridge formatted messages.
// IRC bridges receive ircMsg (with IRC control codes), all others receive plainMsg.
func (r *Router) broadcastJackboxFormattedMessage(ircMsg, plainMsg string) {
if r.JackboxManager != nil && r.JackboxManager.IsMuted() {
r.logger.Debugf("Jackbox formatted message suppressed (muted): %s", plainMsg)
return
}
r.logger.Infof("Broadcasting formatted Jackbox message: %s", plainMsg)
for _, gw := range r.Gateways {
for _, br := range gw.Bridges {
text := plainMsg
if br.Protocol == "irc" {
text = ircMsg
}
msg := config.Message{
Text: " " + text,
Username: "Jackbox",
Account: "jackbox",
Event: config.EventUserAction,
}
if _, err := br.Send(msg); err != nil {
r.logger.Errorf("Failed to send formatted Jackbox message to %s: %v", br.Account, err)
}
}
}
}