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}