this repo has no description
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: move everything to lib and add some more libs

+125 -117
+2 -2
src/commands.ts
··· 1 1 import type { AnyMessageBlock } from "slack-edge"; 2 - import { channelMappings, userMappings } from "./db"; 2 + import { channelMappings, userMappings } from "./lib/db"; 3 3 import { ircClient, slackApp } from "./index"; 4 - import { canManageChannel } from "./permissions"; 4 + import { canManageChannel } from "./lib/permissions"; 5 5 6 6 export function registerCommands() { 7 7 // Link Slack channel to IRC channel
src/db.ts src/lib/db.ts
+10 -115
src/index.ts
··· 2 2 import { SlackApp } from "slack-edge"; 3 3 import { version } from "../package.json"; 4 4 import { registerCommands } from "./commands"; 5 - import { channelMappings, userMappings } from "./db"; 5 + import { channelMappings, userMappings } from "./lib/db"; 6 + import { getAvatarForNick } from "./lib/avatars"; 6 7 import { uploadToCDN } from "./lib/cdn"; 7 - import { parseIRCFormatting, parseSlackMarkdown } from "./parser"; 8 - import type { CachetUser } from "./types"; 9 - 10 - // Default profile pictures for unmapped IRC users 11 - const DEFAULT_AVATARS = [ 12 - "https://hc-cdn.hel1.your-objectstorage.com/s/v3/4183627c4d26c56c915e104a8a7374f43acd1733_pfp__1_.png", 13 - "https://hc-cdn.hel1.your-objectstorage.com/s/v3/389b1e6bd4248a7e5dd88e14c1adb8eb01267080_pfp__2_.png", 14 - "https://hc-cdn.hel1.your-objectstorage.com/s/v3/03011a5e59548191de058f33ccd1d1cb1d64f2a0_pfp__3_.png", 15 - "https://hc-cdn.hel1.your-objectstorage.com/s/v3/f9c57b88fbd4633114c1864bcc2968db555dbd2a_pfp__4_.png", 16 - "https://hc-cdn.hel1.your-objectstorage.com/s/v3/e61a8cabee5a749588125242747b65122fb94205_pfp.png", 17 - ]; 18 - 19 - // Hash function for stable avatar selection 20 - function getAvatarForNick(nick: string): string { 21 - let hash = 0; 22 - for (let i = 0; i < nick.length; i++) { 23 - hash = (hash << 5) - hash + nick.charCodeAt(i); 24 - hash = hash & hash; // Convert to 32bit integer 25 - } 26 - return DEFAULT_AVATARS[Math.abs(hash) % DEFAULT_AVATARS.length] as string; 27 - } 8 + import { 9 + convertIrcMentionsToSlack, 10 + convertSlackMentionsToIrc, 11 + } from "./lib/mentions"; 12 + import { parseIRCFormatting, parseSlackMarkdown } from "./lib/parser"; 28 13 29 14 const missingEnvVars = []; 30 15 if (!process.env.SLACK_BOT_TOKEN) missingEnvVars.push("SLACK_BOT_TOKEN"); ··· 194 179 /https?:\/\/[^\s]+\.(?:png|jpg|jpeg|gif|webp|bmp|svg)(?:\?[^\s]*)?/gi; 195 180 const imageUrls = Array.from(messageText.matchAll(imagePattern)); 196 181 197 - // Find all @mentions and nick: mentions in the IRC message 198 - const atMentionPattern = /@(\w+)/g; 199 - const nickMentionPattern = /(\w+):/g; 200 - 201 - const atMentions = Array.from(messageText.matchAll(atMentionPattern)); 202 - const nickMentions = Array.from(messageText.matchAll(nickMentionPattern)); 203 - 204 - for (const match of atMentions) { 205 - const mentionedNick = match[1] as string; 206 - const mentionedUserMapping = userMappings.getByIrcNick(mentionedNick); 207 - if (mentionedUserMapping) { 208 - messageText = messageText.replace( 209 - match[0], 210 - `<@${mentionedUserMapping.slack_user_id}>`, 211 - ); 212 - } 213 - } 214 - 215 - for (const match of nickMentions) { 216 - const mentionedNick = match[1] as string; 217 - const mentionedUserMapping = userMappings.getByIrcNick(mentionedNick); 218 - if (mentionedUserMapping) { 219 - messageText = messageText.replace( 220 - match[0], 221 - `<@${mentionedUserMapping.slack_user_id}>:`, 222 - ); 223 - } 224 - } 182 + messageText = convertIrcMentionsToSlack(messageText); 225 183 226 184 try { 227 185 // If there are image URLs, send them as attachments ··· 288 246 289 247 // Parse IRC formatting and mentions 290 248 let messageText = parseIRCFormatting(text); 291 - 292 - // Find all @mentions and nick: mentions in the IRC message 293 - const atMentionPattern = /@(\w+)/g; 294 - const nickMentionPattern = /(\w+):/g; 295 - 296 - const atMentions = Array.from(messageText.matchAll(atMentionPattern)); 297 - const nickMentions = Array.from(messageText.matchAll(nickMentionPattern)); 298 - 299 - for (const match of atMentions) { 300 - const mentionedNick = match[1] as string; 301 - const mentionedUserMapping = userMappings.getByIrcNick(mentionedNick); 302 - if (mentionedUserMapping) { 303 - messageText = messageText.replace( 304 - match[0], 305 - `<@${mentionedUserMapping.slack_user_id}>`, 306 - ); 307 - } 308 - } 309 - 310 - for (const match of nickMentions) { 311 - const mentionedNick = match[1] as string; 312 - const mentionedUserMapping = userMappings.getByIrcNick(mentionedNick); 313 - if (mentionedUserMapping) { 314 - messageText = messageText.replace( 315 - match[0], 316 - `<@${mentionedUserMapping.slack_user_id}>:`, 317 - ); 318 - } 319 - } 249 + messageText = convertIrcMentionsToSlack(messageText); 320 250 321 251 // Format as action message with context block 322 252 const actionText = `${nick} ${messageText}`; ··· 382 312 "Unknown"; 383 313 384 314 // Parse Slack mentions and replace with IRC nicks or display names 385 - let messageText = payload.text; 386 - const mentionRegex = /<@(U[A-Z0-9]+)(\|([^>]+))?>/g; 387 - const mentions = Array.from(messageText.matchAll(mentionRegex)); 388 - 389 - for (const match of mentions) { 390 - const userId = match[1] as string; 391 - const displayName = match[3] as string; // The name part after | 392 - 393 - // Check if user has a mapped IRC nick 394 - const mentionedUserMapping = userMappings.getBySlackUser(userId); 395 - if (mentionedUserMapping) { 396 - messageText = messageText.replace( 397 - match[0], 398 - `@${mentionedUserMapping.irc_nick}`, 399 - ); 400 - } else if (displayName) { 401 - // Use the display name from the mention format <@U123|name> 402 - messageText = messageText.replace(match[0], `@${displayName}`); 403 - } else { 404 - // Fallback to Cachet lookup 405 - try { 406 - const response = await fetch( 407 - `https://cachet.dunkirk.sh/users/${userId}`, 408 - { 409 - tls: { rejectUnauthorized: false }, 410 - }, 411 - ); 412 - if (response.ok) { 413 - const data = (await response.json()) as CachetUser; 414 - messageText = messageText.replace(match[0], `@${data.displayName}`); 415 - } 416 - } catch (error) { 417 - console.error(`Error fetching user ${userId} from cachet:`, error); 418 - } 419 - } 420 - } 315 + let messageText = await convertSlackMentionsToIrc(payload.text); 421 316 422 317 // Parse Slack markdown formatting 423 318 messageText = parseSlackMarkdown(messageText);
+19
src/lib/avatars.ts
··· 1 + const DEFAULT_AVATARS = [ 2 + "https://hc-cdn.hel1.your-objectstorage.com/s/v3/4183627c4d26c56c915e104a8a7374f43acd1733_pfp__1_.png", 3 + "https://hc-cdn.hel1.your-objectstorage.com/s/v3/389b1e6bd4248a7e5dd88e14c1adb8eb01267080_pfp__2_.png", 4 + "https://hc-cdn.hel1.your-objectstorage.com/s/v3/03011a5e59548191de058f33ccd1d1cb1d64f2a0_pfp__3_.png", 5 + "https://hc-cdn.hel1.your-objectstorage.com/s/v3/f9c57b88fbd4633114c1864bcc2968db555dbd2a_pfp__4_.png", 6 + "https://hc-cdn.hel1.your-objectstorage.com/s/v3/e61a8cabee5a749588125242747b65122fb94205_pfp.png", 7 + ]; 8 + 9 + /** 10 + * Returns a stable avatar URL for an IRC nick based on hash 11 + */ 12 + export function getAvatarForNick(nick: string): string { 13 + let hash = 0; 14 + for (let i = 0; i < nick.length; i++) { 15 + hash = (hash << 5) - hash + nick.charCodeAt(i); 16 + hash = hash & hash; // Convert to 32bit integer 17 + } 18 + return DEFAULT_AVATARS[Math.abs(hash) % DEFAULT_AVATARS.length] as string; 19 + }
+21
src/lib/cachet.ts
··· 1 + import type { CachetUser } from "../types"; 2 + 3 + /** 4 + * Fetches user information from Cachet API 5 + */ 6 + export async function getCachetUser( 7 + userId: string, 8 + ): Promise<CachetUser | null> { 9 + try { 10 + const response = await fetch(`https://cachet.dunkirk.sh/users/${userId}`, { 11 + tls: { rejectUnauthorized: false }, 12 + }); 13 + if (response.ok) { 14 + return (await response.json()) as CachetUser; 15 + } 16 + return null; 17 + } catch (error) { 18 + console.error(`Error fetching user ${userId} from cachet:`, error); 19 + return null; 20 + } 21 + }
+73
src/lib/mentions.ts
··· 1 + import { userMappings } from "./db"; 2 + import { getCachetUser } from "./cachet"; 3 + 4 + /** 5 + * Converts IRC @mentions and nick: mentions to Slack user mentions 6 + */ 7 + export function convertIrcMentionsToSlack(messageText: string): string { 8 + let result = messageText; 9 + 10 + // Find all @mentions and nick: mentions in the IRC message 11 + const atMentionPattern = /@(\w+)/g; 12 + const nickMentionPattern = /(\w+):/g; 13 + 14 + const atMentions = Array.from(result.matchAll(atMentionPattern)); 15 + const nickMentions = Array.from(result.matchAll(nickMentionPattern)); 16 + 17 + for (const match of atMentions) { 18 + const mentionedNick = match[1] as string; 19 + const mentionedUserMapping = userMappings.getByIrcNick(mentionedNick); 20 + if (mentionedUserMapping) { 21 + result = result.replace( 22 + match[0], 23 + `<@${mentionedUserMapping.slack_user_id}>`, 24 + ); 25 + } 26 + } 27 + 28 + for (const match of nickMentions) { 29 + const mentionedNick = match[1] as string; 30 + const mentionedUserMapping = userMappings.getByIrcNick(mentionedNick); 31 + if (mentionedUserMapping) { 32 + result = result.replace( 33 + match[0], 34 + `<@${mentionedUserMapping.slack_user_id}>:`, 35 + ); 36 + } 37 + } 38 + 39 + return result; 40 + } 41 + 42 + /** 43 + * Converts Slack user mentions to IRC @mentions, with Cachet fallback 44 + */ 45 + export async function convertSlackMentionsToIrc( 46 + messageText: string, 47 + ): Promise<string> { 48 + let result = messageText; 49 + const mentionRegex = /<@(U[A-Z0-9]+)(\|([^>]+))?>/g; 50 + const mentions = Array.from(result.matchAll(mentionRegex)); 51 + 52 + for (const match of mentions) { 53 + const userId = match[1] as string; 54 + const displayName = match[3] as string; // The name part after | 55 + 56 + // Check if user has a mapped IRC nick 57 + const mentionedUserMapping = userMappings.getBySlackUser(userId); 58 + if (mentionedUserMapping) { 59 + result = result.replace(match[0], `@${mentionedUserMapping.irc_nick}`); 60 + } else if (displayName) { 61 + // Use the display name from the mention format <@U123|name> 62 + result = result.replace(match[0], `@${displayName}`); 63 + } else { 64 + // Fallback to Cachet lookup 65 + const data = await getCachetUser(userId); 66 + if (data) { 67 + result = result.replace(match[0], `@${data.displayName}`); 68 + } 69 + } 70 + } 71 + 72 + return result; 73 + }
src/parser.ts src/lib/parser.ts
src/permissions.ts src/lib/permissions.ts