···101 - IRC mentions (`@nick` or `nick:`) are converted to Slack mentions for mapped users
102 - IRC formatting codes are converted to Slack markdown
103 - IRC `/me` actions are displayed in a context block with the user's avatar
0104- **Slack → IRC**: Messages from mapped Slack channels are sent to their corresponding IRC channels
105 - Slack mentions are converted to mapped IRC nicks, or the display name from `<@U123|name>` format
106 - Slack markdown is converted to IRC formatting codes
107 - File attachments are uploaded to Hack Club CDN and URLs are shared
00108- **User mappings** allow custom IRC nicknames for specific Slack users and enable proper mentions both ways
000000000000109110The bridge ignores its own messages and bot messages to prevent loops.
111
···101 - IRC mentions (`@nick` or `nick:`) are converted to Slack mentions for mapped users
102 - IRC formatting codes are converted to Slack markdown
103 - IRC `/me` actions are displayed in a context block with the user's avatar
104+ - Thread replies: Use `@xxxxx` (5-char thread ID) to reply to a Slack thread from IRC
105- **Slack → IRC**: Messages from mapped Slack channels are sent to their corresponding IRC channels
106 - Slack mentions are converted to mapped IRC nicks, or the display name from `<@U123|name>` format
107 - Slack markdown is converted to IRC formatting codes
108 - File attachments are uploaded to Hack Club CDN and URLs are shared
109+ - Thread messages are prefixed with `@xxxxx` (5-char thread ID) to show they're part of a thread
110+ - First reply in a thread includes a quote of the parent message
111- **User mappings** allow custom IRC nicknames for specific Slack users and enable proper mentions both ways
112+113+#### Thread Support
114+115+The bridge supports Slack threads with a simple IRC-friendly syntax:
116+117+- **Slack → IRC**: Thread messages appear with a `@xxxxx` prefix (5-character thread ID)
118+ - First reply in a thread includes a quote: `<user> @xxxxx > original message`
119+ - Subsequent replies: `<user> @xxxxx message text`
120+- **IRC → Slack**: Reply to a thread by including the thread ID in your message
121+ - Example: `@abc12 this is my reply`
122+ - The bridge removes the `@xxxxx` prefix and sends your message to the correct thread
123+ - Thread IDs are unique per thread and persist across restarts
124125The bridge ignores its own messages and bot messages to prevent loops.
126
+73-3
src/index.ts
···10 convertSlackMentionsToIrc,
11} from "./lib/mentions";
12import { parseIRCFormatting, parseSlackMarkdown } from "./lib/parser";
0000001314const missingEnvVars = [];
15if (!process.env.SLACK_BOT_TOKEN) missingEnvVars.push("SLACK_BOT_TOKEN");
···6970// Register slash commands
71registerCommands();
000007273// Track NickServ authentication state
74let nickServAuthAttempted = false;
···174 // Parse IRC mentions and convert to Slack mentions
175 let messageText = parseIRCFormatting(text);
176000000000000000177 // Extract image URLs from the message
178 const imagePattern =
179 /https?:\/\/[^\s]+\.(?:png|jpg|jpeg|gif|webp|bmp|svg)(?:\?[^\s]*)?/gi;
···198 attachments: attachments,
199 unfurl_links: false,
200 unfurl_media: false,
0201 });
202 } else {
203 await slackClient.chat.postMessage({
···208 icon_url: iconUrl,
209 unfurl_links: true,
210 unfurl_media: true,
0211 });
212 }
213 console.log(`IRC (${to}) → Slack: <${nick}> ${text}`);
···279280// Slack event handlers
281slackApp.event("message", async ({ payload }) => {
282- // Ignore bot messages and threaded messages
283 if (payload.subtype && payload.subtype !== "file_share") return;
284 if (payload.bot_id) return;
285 if (payload.user === botUserId) return;
286- if (payload.thread_ts) return;
287288 // Find IRC channel mapping for this Slack channel
289 const mapping = channelMappings.getBySlackChannel(payload.channel);
···317 // Parse Slack markdown formatting
318 messageText = parseSlackMarkdown(messageText);
319000000000000000000000000000000000000000000320 // Send message only if there's text content
321 if (messageText.trim()) {
322 const message = `<${username}> ${messageText}`;
···331 const data = await uploadToCDN(fileUrls);
332333 for (const file of data.files) {
334- const fileMessage = `<${username}> ${file.deployedUrl}`;
0335 ircClient.say(mapping.irc_channel, fileMessage);
336 console.log(`Slack → IRC (file): ${fileMessage}`);
337 }