···
// Join all mapped IRC channels on connect
ircClient.addListener("registered", async () => {
console.log("Connected to IRC server");
// Authenticate with NickServ if password is provided
if (process.env.NICKSERV_PASSWORD && !nickServAuthAttempted) {
nickServAuthAttempted = true;
···
// Handle NickServ notices
+
async (nick: string, to: string, text: string) => {
+
if (nick !== "NickServ") return;
+
console.log(`NickServ: ${text}`);
+
// Check for successful authentication
+
text.includes("You are now identified") ||
+
text.includes("Password accepted")
+
console.log("✓ Successfully authenticated with NickServ");
+
isAuthenticated = true;
+
// Join channels after successful auth
+
const mappings = channelMappings.getAll();
+
for (const mapping of mappings) {
+
ircClient.join(mapping.irc_channel);
+
// Check if nick is not registered
+
text.includes("isn't registered") ||
+
text.includes("not registered")
+
console.log("Nick not registered, registering with NickServ...");
+
if (process.env.NICKSERV_PASSWORD && process.env.NICKSERV_EMAIL) {
+
`REGISTER ${process.env.NICKSERV_PASSWORD} ${process.env.NICKSERV_EMAIL}`,
+
console.error("Cannot register: NICKSERV_EMAIL not configured");
+
// Check for failed authentication
+
text.includes("Invalid password") ||
+
text.includes("Access denied")
+
console.error("✗ NickServ authentication failed: Invalid password");
···
attachments: attachments,
await slackClient.chat.postMessage({
···
console.log(`IRC → Slack: <${nick}> ${text}`);
···
// Handle IRC /me actions
+
async (nick: string, to: string, text: string) => {
+
// Ignore messages from our own bot
+
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);
+
iconUrl = `https://cachet.dunkirk.sh/users/${userMapping.slack_user_id}/r`;
+
iconUrl = getAvatarForNick(nick);
+
// Parse IRC formatting and mentions
+
let messageText = parseIRCFormatting(text);
+
// Find all @mentions and nick: mentions in the IRC message
+
const atMentionPattern = /@(\w+)/g;
+
const nickMentionPattern = /(\w+):/g;
+
const atMentions = Array.from(messageText.matchAll(atMentionPattern));
+
const nickMentions = Array.from(messageText.matchAll(nickMentionPattern));
+
for (const match of atMentions) {
+
const mentionedNick = match[1] as string;
+
const mentionedUserMapping = userMappings.getByIrcNick(mentionedNick);
+
if (mentionedUserMapping) {
+
messageText = messageText.replace(
+
`<@${mentionedUserMapping.slack_user_id}>`,
+
for (const match of nickMentions) {
+
const mentionedNick = match[1] as string;
+
const mentionedUserMapping = userMappings.getByIrcNick(mentionedNick);
+
if (mentionedUserMapping) {
+
messageText = messageText.replace(
+
`<@${mentionedUserMapping.slack_user_id}>:`,
+
// Format as action message with context block
+
const actionText = `${nick} ${messageText}`;
+
await slackClient.chat.postMessage({
+
token: process.env.SLACK_BOT_TOKEN,
+
channel: mapping.slack_channel_id,
+
console.log(`IRC → Slack (action): ${actionText}`);
slackApp.event("message", async ({ payload, context }) => {
···
const mentions = Array.from(messageText.matchAll(mentionRegex));
for (const match of mentions) {
+
const userId = match[1] as string;
+
const displayName = match[3] as string; // The name part after |
// Check if user has a mapped IRC nick
const mentionedUserMapping = userMappings.getBySlackUser(userId);
if (mentionedUserMapping) {
+
messageText = messageText.replace(
+
`@${mentionedUserMapping.irc_nick}`,
} else if (displayName) {
// Use the display name from the mention format <@U123|name>
messageText = messageText.replace(match[0], `@${displayName}`);
···
const response = await fetch(
`https://cachet.dunkirk.sh/users/${userId}`,
tls: { rejectUnauthorized: false },