IRC <> Slack bridge#
This is a little bot in active development to bridge slack and irc for Hackclub!
How do I hack on it?#
Development#
This is written in typescript so pretty easy to get started!
bun install
bun dev
To run tests:
bun test
Slack App Setup#
- Go to api.slack.com/apps and create a new app
- Choose "From an app manifest"
- Copy the contents of
slack-manifest.yamland paste it - Install the app to your workspace
- Copy the "Bot User OAuth Token" (starts with
xoxb-) and "Signing Secret" - Invite the bot to your desired Slack channel:
/invite @IRC Bridge
Environment Setup#
Make a .env file with the following:
# Slack Configuration
SLACK_BOT_TOKEN=xoxb-your-bot-token-here
SLACK_SIGNING_SECRET=your-signing-secret-here
# Slack workspace URL (for admin API calls)
SLACK_API_URL=https://hackclub.enterprise.slack.com
# Optional: For channel manager permission checks
SLACK_USER_COOKIE=your-slack-cookie-here
SLACK_USER_TOKEN=your-user-token-here
# Optional: Enable Cachet API for user lookups (recommended for better performance)
CACHET_ENABLED=true
# IRC Configuration
IRC_NICK=slackbridge
NICKSERV_PASSWORD=your-nickserv-password-here
NICKSERV_EMAIL=your-email@example.com
# Admin users (comma-separated Slack user IDs)
ADMINS=U1234567890,U0987654321
# Hack Club CDN Token (for file uploads)
CDN_TOKEN=your-cdn-token-here
# Server Configuration (optional)
PORT=3000
# Note: Channel and user mappings are now stored in the SQLite database (bridge.db)
# Use the API or database tools to manage mappings
See .env.example for a template.
Slash Commands#
The bridge provides interactive slash commands for managing mappings:
/irc-bridge-channel- Bridge current Slack channel to an IRC channel/irc-unbridge-channel- Remove bridge from current channel/irc-bridge-user- Link your Slack account to an IRC nickname/irc-unbridge-user- Remove your IRC nickname link/irc-bridge-list- List all channel and user bridges
Managing Channel and User Mappings#
Channel and user mappings are stored in a SQLite database (bridge.db). You can manage them through:
Using Bun REPL:
bun repl
> import { channelMappings, userMappings } from "./src/lib/db"
> channelMappings.create("C1234567890", "#general")
> userMappings.create("U1234567890", "myircnick")
> channelMappings.getAll()
Using SQLite directly:
bun:sqlite bridge.db
sqlite> SELECT * FROM channel_mappings;
sqlite> INSERT INTO channel_mappings (slack_channel_id, irc_channel) VALUES ('C1234567890', '#general');
How it works#
The bridge connects to irc.hackclub.com:6667 (no TLS) and forwards messages bidirectionally based on channel mappings:
- NickServ Authentication: If
NICKSERV_PASSWORDis configured, the bridge authenticates on connect- Waits for NickServ confirmation before joining channels
- Auto-registers the nick if not registered (requires
NICKSERV_EMAIL) - Prevents "No external channel messages" errors by ensuring proper authentication
- IRC → Slack: Messages from mapped IRC channels appear in their corresponding Slack channels
- Image URLs are automatically displayed as inline attachments
- IRC mentions (
@nickornick:) are converted to Slack mentions for mapped users - IRC formatting codes are converted to Slack markdown
- IRC
/meactions are displayed in a context block with the user's avatar - Thread replies: Use
@xxxxx(5-char thread ID) to reply to a Slack thread from IRC
- Slack → IRC: Messages from mapped Slack channels are sent to their corresponding IRC channels
- User display names: Uses name from Slack event if available, otherwise Cachet API (if
CACHET_ENABLED=true), then Slack API fallback - All lookups are cached locally (1 hour TTL) to reduce API calls
- Slack mentions are converted to mapped IRC nicks, or looked up via the above priority
- Slack markdown is converted to IRC formatting codes
- File attachments are uploaded to Hack Club CDN and URLs are shared
- Thread messages are prefixed with
@xxxxx(5-char thread ID) to show they're part of a thread - First reply in a thread includes a quote of the parent message
- User display names: Uses name from Slack event if available, otherwise Cachet API (if
- User mappings allow custom IRC nicknames for specific Slack users and enable proper mentions both ways
- Permissions: Only channel creators, channel managers, or global admins can bridge/unbridge channels
Thread Support#
The bridge supports Slack threads with a simple IRC-friendly syntax:
- Slack → IRC: Thread messages appear with a
@xxxxxprefix (5-character thread ID)- First reply in a thread includes a quote:
<user> @xxxxx > original message - Subsequent replies:
<user> @xxxxx message text
- First reply in a thread includes a quote:
- IRC → Slack: Reply to a thread by including the thread ID in your message
- Example:
@abc12 this is my reply - The bridge removes the
@xxxxxprefix and sends your message to the correct thread - Thread IDs are unique per thread and persist across restarts (stored in SQLite)
- Example:
The bridge ignores its own messages and bot messages to prevent loops.
Architecture#
The bridge consists of several modules:
src/index.ts- Main application entry point with IRC/Slack event handlerssrc/commands.ts- Slash command handlers for managing bridgessrc/lib/db.ts- SQLite database layer for channel/user/thread mappingssrc/lib/parser.ts- Bidirectional message formatting conversion (IRC ↔ Slack)src/lib/mentions.ts- User mention conversion with Cachet integrationsrc/lib/threads.ts- Thread tracking and ID generationsrc/lib/user-cache.ts- Cached Slack user info lookups (1-hour TTL)src/lib/permissions.ts- Channel management permission checkssrc/lib/avatars.ts- Stable avatar URL generation for IRC userssrc/lib/cdn.ts- File upload integration with Hack Club CDNsrc/lib/cachet.ts- User profile lookups from Cachet API
Testing#
The project includes comprehensive unit tests covering all core functionality:
bun test
Tests cover:
- Message format parsing (IRC ↔ Slack)
- User mention conversion
- Thread ID generation and tracking
- User info caching
- Database operations
- Avatar generation
If you want to report an issue the main repo is the tangled repo and the github is just a mirror.
© 2025-present Kieran Klukas