···
import * as irc from "irc";
import { SlackApp } from "slack-edge";
import { version } from "../package.json";
+
import { registerCommands } from "./commands";
import { channelMappings, userMappings } from "./db";
+
import { parseIRCFormatting, parseSlackMarkdown } from "./parser";
import type { CachetUser } from "./types";
const missingEnvVars = [];
if (!process.env.SLACK_BOT_TOKEN) missingEnvVars.push("SLACK_BOT_TOKEN");
if (!process.env.SLACK_SIGNING_SECRET)
+
missingEnvVars.push("SLACK_SIGNING_SECRET");
if (!process.env.ADMINS) missingEnvVars.push("ADMINS");
if (!process.env.IRC_NICK) missingEnvVars.push("IRC_NICK");
if (missingEnvVars.length > 0) {
+
`Missing required environment variables: ${missingEnvVars.join(", ")}`,
const slackApp = new SlackApp({
+
SLACK_BOT_TOKEN: process.env.SLACK_BOT_TOKEN as string,
+
SLACK_SIGNING_SECRET: process.env.SLACK_SIGNING_SECRET as string,
+
SLACK_LOGGING_LEVEL: "INFO",
+
startLazyListenerAfterAck: true,
const slackClient = slackApp.client;
let botUserId: string | undefined;
+
token: process.env.SLACK_BOT_TOKEN,
+
botUserId = result.user_id;
+
console.log(`Bot user ID: ${botUserId}`);
const ircClient = new irc.Client(
+
process.env.IRC_NICK || "slackbridge",
+
userName: process.env.IRC_NICK,
+
realName: "Slack IRC Bridge",
// Clean up IRC connection on hot reload or exit
process.on("beforeExit", () => {
+
ircClient.disconnect("Reloading", () => {
+
console.log("IRC client disconnected");
+
// Register slash commands
// Join all mapped IRC channels on connect
ircClient.addListener("registered", async () => {
+
console.log("Connected to IRC server");
+
const mappings = channelMappings.getAll();
+
for (const mapping of mappings) {
+
ircClient.join(mapping.irc_channel);
ircClient.addListener("join", (channel: string, nick: string) => {
+
if (nick === process.env.IRC_NICK) {
+
console.log(`Joined IRC channel: ${channel}`);
+
async (nick: string, to: string, text: string) => {
+
// Ignore messages from our own bot (with or without numbers suffix)
+
const botNickPattern = new RegExp(`^${process.env.IRC_NICK}\\d*$`);
+
if (botNickPattern.test(nick)) return;
+
if (nick === "****") return;
+
// Find Slack channel mapping for this IRC channel
+
const mapping = channelMappings.getByIrcChannel(to);
+
// Check if this IRC nick is mapped to a Slack user
+
const userMapping = userMappings.getByIrcNick(nick);
+
const displayName = `${nick} <irc>`;
+
let iconUrl: string | undefined;
+
iconUrl = `https://cachet.dunkirk.sh/users/${userMapping.slack_user_id}/r`;
+
console.error("Error fetching user info:", error);
+
await slackClient.chat.postMessage({
+
token: process.env.SLACK_BOT_TOKEN,
+
channel: mapping.slack_channel_id,
+
text: parseIRCFormatting(text),
+
console.log(`IRC → Slack: <${nick}> ${text}`);
+
console.error("Error posting to Slack:", error);
ircClient.addListener("error", (error: string) => {
+
console.error("IRC error:", error);
slackApp.event("message", async ({ payload }) => {
+
if (payload.subtype) return;
+
if (payload.bot_id) return;
+
if (payload.user === botUserId) return;
+
// Find IRC channel mapping for this Slack channel
+
const mapping = channelMappings.getBySlackChannel(payload.channel);
+
`No IRC channel mapping found for Slack channel ${payload.channel}`,
+
slackClient.conversations.leave({
+
channel: payload.channel,
+
const userInfo = await slackClient.users.info({
+
token: process.env.SLACK_BOT_TOKEN,
+
// Check for user mapping, otherwise use Slack name
+
const userMapping = userMappings.getBySlackUser(payload.user);
+
userMapping?.irc_nick ||
+
userInfo.user?.real_name ||
+
// Parse Slack mentions and replace with display names
+
let messageText = payload.text;
+
const mentionRegex = /<@(U[A-Z0-9]+)>/g;
+
const mentions = Array.from(messageText.matchAll(mentionRegex));
+
for (const match of mentions) {
+
const userId = match[1];
+
const response = await fetch(
+
`https://cachet.dunkirk.sh/users/${userId}`,
+
const data = (await response.json()) as CachetUser;
+
messageText = messageText.replace(match[0], `@${data.displayName}`);
+
console.error(`Error fetching user ${userId} from cachet:`, error);
+
// Parse Slack markdown formatting
+
messageText = parseSlackMarkdown(messageText);
+
const message = `<${username}> ${messageText}`;
+
ircClient.say(mapping.irc_channel, message);
+
console.log(`Slack → IRC: ${message}`);
+
console.error("Error handling Slack message:", error);
+
port: process.env.PORT || 3000,
+
async fetch(request: Request) {
+
const url = new URL(request.url);
+
const path = url.pathname;
+
return new Response(`Hello World from irc-slack-bridge@${version}`);
+
return new Response("OK");
+
return slackApp.run(request);
+
return new Response("404 Not Found", { status: 404 });
+
`🚀 Server Started in ${Bun.nanoseconds() / 1000000} milliseconds on version: ${version}!\n\n----------------------------------\n`,
+
`Connecting to IRC: irc.hackclub.com:6667 as ${process.env.IRC_NICK}`,
console.log(`Channel mappings: ${channelMappings.getAll().length}`);
console.log(`User mappings: ${userMappings.getAll().length}`);
+
export { slackApp, slackClient, ircClient };