this repo has no description
1import type { AnyMessageBlock, Block, BlockElement } from "slack-edge"; 2import { channelMappings, userMappings } from "./db"; 3import { slackApp, ircClient } from "./index"; 4 5export function registerCommands() { 6 // Link Slack channel to IRC channel 7 slackApp.command("/irc-bridge-channel", async ({ payload, context }) => { 8 context.respond({ 9 response_type: "ephemeral", 10 text: "Bridge channel command received", 11 blocks: [ 12 { 13 type: "input", 14 block_id: "irc_channel_input", 15 element: { 16 type: "plain_text_input", 17 action_id: "irc_channel", 18 placeholder: { 19 type: "plain_text", 20 text: "#lounge", 21 }, 22 }, 23 label: { 24 type: "plain_text", 25 text: "IRC Channel", 26 }, 27 }, 28 { 29 type: "actions", 30 elements: [ 31 { 32 type: "button", 33 text: { 34 type: "plain_text", 35 text: "Bridge Channel", 36 }, 37 style: "primary", 38 action_id: "bridge_channel_submit", 39 value: payload.channel_id, 40 }, 41 { 42 type: "button", 43 text: { 44 type: "plain_text", 45 text: "Cancel", 46 }, 47 action_id: "cancel", 48 }, 49 ], 50 }, 51 ], 52 replace_original: true, 53 }); 54 }); 55 56 // Handle bridge channel submission 57 slackApp.action("bridge_channel_submit", async ({ payload, context }) => { 58 const stateValues = payload.state?.values; 59 const ircChannel = stateValues?.irc_channel_input?.irc_channel?.value; 60 // @ts-expect-error 61 const slackChannelId = payload.actions?.[0]?.value; 62 if (!context.respond) { 63 return; 64 } 65 66 if (!ircChannel || !ircChannel.startsWith("#")) { 67 context.respond({ 68 response_type: "ephemeral", 69 text: "❌ IRC channel must start with #", 70 replace_original: true, 71 }); 72 return; 73 } 74 75 try { 76 channelMappings.create(slackChannelId, ircChannel); 77 ircClient.join(ircChannel); 78 79 await context.client.conversations.join({ 80 channel: slackChannelId, 81 }); 82 83 console.log( 84 `Created channel mapping: ${slackChannelId} -> ${ircChannel}`, 85 ); 86 87 context.respond({ 88 response_type: "ephemeral", 89 text: `✅ Successfully bridged <#${slackChannelId}> to ${ircChannel}`, 90 replace_original: true, 91 }); 92 } catch (error) { 93 console.error("Error creating channel mapping:", error); 94 context.respond({ 95 response_type: "ephemeral", 96 text: `❌ Failed to bridge channel: ${error}`, 97 replace_original: true, 98 }); 99 } 100 }); 101 102 // Unlink Slack channel from IRC 103 slackApp.command("/irc-unbridge-channel", async ({ payload, context }) => { 104 const slackChannelId = payload.channel_id; 105 const mapping = channelMappings.getBySlackChannel(slackChannelId); 106 107 if (!mapping) { 108 context.respond({ 109 response_type: "ephemeral", 110 text: "❌ This channel is not bridged to IRC", 111 }); 112 return; 113 } 114 115 context.respond({ 116 response_type: "ephemeral", 117 text: "Are you sure you want to remove the bridge to *${mapping.irc_channel}*?", 118 blocks: [ 119 { 120 type: "section", 121 text: { 122 type: "mrkdwn", 123 text: `Are you sure you want to remove the bridge to *${mapping.irc_channel}*?`, 124 }, 125 }, 126 { 127 type: "actions", 128 elements: [ 129 { 130 type: "button", 131 text: { 132 type: "plain_text", 133 text: "Remove Bridge", 134 }, 135 style: "danger", 136 action_id: "unbridge_channel_confirm", 137 value: slackChannelId, 138 }, 139 { 140 type: "button", 141 text: { 142 type: "plain_text", 143 text: "Cancel", 144 }, 145 action_id: "cancel", 146 }, 147 ], 148 }, 149 ], 150 replace_original: true, 151 }); 152 }); 153 154 // Handle unbridge confirmation 155 slackApp.action("unbridge_channel_confirm", async ({ payload, context }) => { 156 // @ts-expect-error 157 const slackChannelId = payload.actions?.[0]?.value; 158 if (!context.respond) return; 159 160 try { 161 const mapping = channelMappings.getBySlackChannel(slackChannelId); 162 if (!mapping) { 163 context.respond({ 164 response_type: "ephemeral", 165 text: "❌ This channel is not bridged to IRC", 166 replace_original: true, 167 }); 168 return; 169 } 170 171 channelMappings.delete(slackChannelId); 172 console.log( 173 `Removed channel mapping: ${slackChannelId} -> ${mapping.irc_channel}`, 174 ); 175 176 context.respond({ 177 response_type: "ephemeral", 178 text: `✅ Removed bridge to ${mapping.irc_channel}`, 179 replace_original: true, 180 }); 181 } catch (error) { 182 console.error("Error removing channel mapping:", error); 183 context.respond({ 184 response_type: "ephemeral", 185 text: `❌ Failed to remove bridge: ${error}`, 186 replace_original: true, 187 }); 188 } 189 }); 190 191 // Link Slack user to IRC nick 192 slackApp.command("/irc-bridge-user", async ({ payload, context }) => { 193 context.respond({ 194 response_type: "ephemeral", 195 text: "Enter your IRC nickname", 196 blocks: [ 197 { 198 type: "input", 199 block_id: "irc_nick_input", 200 element: { 201 type: "plain_text_input", 202 action_id: "irc_nick", 203 placeholder: { 204 type: "plain_text", 205 text: "myircnick", 206 }, 207 }, 208 label: { 209 type: "plain_text", 210 text: "IRC Nickname", 211 }, 212 }, 213 { 214 type: "actions", 215 elements: [ 216 { 217 type: "button", 218 text: { 219 type: "plain_text", 220 text: "Link Account", 221 }, 222 style: "primary", 223 action_id: "bridge_user_submit", 224 value: payload.user_id, 225 }, 226 { 227 type: "button", 228 text: { 229 type: "plain_text", 230 text: "Cancel", 231 }, 232 action_id: "cancel", 233 }, 234 ], 235 }, 236 ], 237 replace_original: true, 238 }); 239 }); 240 241 // Handle bridge user submission 242 slackApp.action("bridge_user_submit", async ({ payload, context }) => { 243 const stateValues = payload.state?.values; 244 const ircNick = stateValues?.irc_nick_input?.irc_nick?.value; 245 // @ts-expect-error 246 const slackUserId = payload.actions?.[0]?.value; 247 if (!context.respond) { 248 return; 249 } 250 251 if (!ircNick) { 252 context.respond({ 253 response_type: "ephemeral", 254 text: "❌ IRC nickname is required", 255 replace_original: true, 256 }); 257 return; 258 } 259 260 try { 261 userMappings.create(slackUserId, ircNick); 262 console.log(`Created user mapping: ${slackUserId} -> ${ircNick}`); 263 264 context.respond({ 265 response_type: "ephemeral", 266 text: `✅ Successfully linked your account to IRC nick: *${ircNick}*`, 267 replace_original: true, 268 }); 269 } catch (error) { 270 console.error("Error creating user mapping:", error); 271 context.respond({ 272 response_type: "ephemeral", 273 text: `❌ Failed to link user: ${error}`, 274 replace_original: true, 275 }); 276 } 277 }); 278 279 // Unlink Slack user from IRC 280 slackApp.command("/irc-unbridge-user", async ({ payload, context }) => { 281 const slackUserId = payload.user_id; 282 const mapping = userMappings.getBySlackUser(slackUserId); 283 284 if (!mapping) { 285 context.respond({ 286 response_type: "ephemeral", 287 text: "❌ You don't have an IRC nick mapping", 288 }); 289 return; 290 } 291 292 context.respond({ 293 response_type: "ephemeral", 294 text: "Are you sure you want to remove your link to IRC nick *${mapping.irc_nick}*?", 295 blocks: [ 296 { 297 type: "section", 298 text: { 299 type: "mrkdwn", 300 text: `Are you sure you want to remove your link to IRC nick *${mapping.irc_nick}*?`, 301 }, 302 }, 303 { 304 type: "actions", 305 elements: [ 306 { 307 type: "button", 308 text: { 309 type: "plain_text", 310 text: "Remove Link", 311 }, 312 style: "danger", 313 action_id: "unbridge_user_confirm", 314 value: slackUserId, 315 }, 316 { 317 type: "button", 318 text: { 319 type: "plain_text", 320 text: "Cancel", 321 }, 322 action_id: "cancel", 323 }, 324 ], 325 }, 326 ], 327 replace_original: true, 328 }); 329 }); 330 331 // Handle unbridge user confirmation 332 slackApp.action("unbridge_user_confirm", async ({ payload, context }) => { 333 // @ts-expect-error 334 const slackUserId = payload.actions?.[0]?.value; 335 if (!context.respond) { 336 return; 337 } 338 339 try { 340 const mapping = userMappings.getBySlackUser(slackUserId); 341 if (!mapping) { 342 context.respond({ 343 response_type: "ephemeral", 344 text: "❌ You don't have an IRC nick mapping", 345 replace_original: true, 346 }); 347 return; 348 } 349 350 userMappings.delete(slackUserId); 351 console.log( 352 `Removed user mapping: ${slackUserId} -> ${mapping.irc_nick}`, 353 ); 354 355 context.respond({ 356 response_type: "ephemeral", 357 text: `✅ Removed link to IRC nick: ${mapping.irc_nick}`, 358 replace_original: true, 359 }); 360 } catch (error) { 361 console.error("Error removing user mapping:", error); 362 context.respond({ 363 response_type: "ephemeral", 364 text: `❌ Failed to remove link: ${error}`, 365 replace_original: true, 366 }); 367 } 368 }); 369 370 // Handle cancel button 371 slackApp.action("cancel", async ({ context }) => { 372 if (!context.respond) return; 373 374 context.respond({ 375 response_type: "ephemeral", 376 delete_original: true, 377 }); 378 }); 379 380 // List channel mappings 381 slackApp.command("/irc-bridge-list", async ({ payload, context }) => { 382 const channelMaps = channelMappings.getAll(); 383 const userMaps = userMappings.getAll(); 384 385 const blocks: AnyMessageBlock[] = [ 386 { 387 type: "header", 388 text: { 389 type: "plain_text", 390 text: "IRC Bridge Status", 391 }, 392 }, 393 { 394 type: "section", 395 text: { 396 type: "mrkdwn", 397 text: "*Channel Bridges:*", 398 }, 399 }, 400 ]; 401 402 if (channelMaps.length === 0) { 403 blocks.push({ 404 type: "section", 405 text: { 406 type: "mrkdwn", 407 text: "_No channel bridges configured_", 408 }, 409 }); 410 } else { 411 for (const map of channelMaps) { 412 blocks.push({ 413 type: "section", 414 text: { 415 type: "mrkdwn", 416 text: `• <#${map.slack_channel_id}> ↔️ *${map.irc_channel}*`, 417 }, 418 }); 419 } 420 } 421 422 blocks.push( 423 { 424 type: "divider", 425 }, 426 { 427 type: "section", 428 text: { 429 type: "mrkdwn", 430 text: "*User Mappings:*", 431 }, 432 }, 433 ); 434 435 if (userMaps.length === 0) { 436 blocks.push({ 437 type: "section", 438 text: { 439 type: "mrkdwn", 440 text: "_No user mappings configured_", 441 }, 442 }); 443 } else { 444 for (const map of userMaps) { 445 blocks.push({ 446 type: "section", 447 text: { 448 type: "mrkdwn", 449 text: `• <@${map.slack_user_id}> ↔️ *${map.irc_nick}*`, 450 }, 451 }); 452 } 453 } 454 455 context.respond({ 456 response_type: "ephemeral", 457 text: "IRC mapping list", 458 blocks, 459 replace_original: true, 460 }); 461 }); 462}