a fun bot for the hc slack
1import { environment, slackApp } from "../../../index";
2import handleHelp from "../handlers/help";
3import { handleHistory } from "../handlers/history";
4import handlePause from "../handlers/pause";
5import handleResume from "../handlers/resume";
6import handleStart from "../handlers/start";
7import handleStatus from "../handlers/status";
8import handleStop from "../handlers/stop";
9import { getActiveTake, getPausedTake } from "../services/database";
10import {
11 checkActiveSessions,
12 expirePausedSessions,
13} from "../services/notifications";
14import type { MessageResponse } from "../types";
15import { getDescriptionBlocks, getEditDescriptionBlocks } from "../ui/blocks";
16import * as Sentry from "@sentry/bun";
17import { blog } from "../../../libs/Logger";
18
19export default function setupCommands() {
20 // Main command handler
21 slackApp.command(
22 environment === "dev" ? "/takes-dev" : "/takes",
23 async ({ payload, context }): Promise<void> => {
24 try {
25 const userId = payload.user_id;
26 const channelId = payload.channel_id;
27 const text = payload.text || "";
28 const args = text.trim().split(/\s+/);
29 let subcommand = args[0]?.toLowerCase() || "";
30
31 // Check for active takes session
32 const activeTake = await getActiveTake(userId);
33
34 // Check for paused session if no active one
35 const pausedTakeCheck =
36 activeTake.length === 0 ? await getPausedTake(userId) : [];
37
38 // Run checks for expired or about-to-expire sessions
39 await expirePausedSessions();
40 await checkActiveSessions();
41
42 // Default to status if we have an active or paused session and no command specified
43 if (
44 subcommand === "" &&
45 (activeTake.length > 0 || pausedTakeCheck.length > 0)
46 ) {
47 subcommand = "status";
48 } else if (subcommand === "") {
49 subcommand = "help";
50 }
51
52 let response: MessageResponse | undefined;
53
54 // Special handling for start command to show modal
55 if (subcommand === "start" && !activeTake.length) {
56 response = getDescriptionBlocks();
57 }
58
59 // Route to the appropriate handler function
60 switch (subcommand) {
61 case "start": {
62 if (args.length < 2) {
63 response = getDescriptionBlocks();
64 break;
65 }
66
67 const descriptionInput = args.slice(1).join(" ");
68
69 if (!descriptionInput.trim()) {
70 response = getDescriptionBlocks(
71 "Please enter a note for your session.",
72 );
73 break;
74 }
75
76 response = await handleStart(
77 userId,
78 channelId,
79 descriptionInput,
80 );
81 break;
82 }
83 case "pause":
84 response = await handlePause(userId);
85 break;
86 case "resume":
87 response = await handleResume(userId);
88 break;
89 case "stop":
90 response = await handleStop(userId, args);
91 break;
92 case "edit":
93 response = getEditDescriptionBlocks(
94 activeTake[0]?.description || "",
95 );
96 break;
97 case "status":
98 response = await handleStatus(userId);
99 break;
100 case "history":
101 response = await handleHistory(userId);
102 break;
103 case "help":
104 response = await handleHelp();
105 break;
106 default:
107 response = await handleHelp();
108 break;
109 }
110
111 if (!response) {
112 throw new Error("No response received from handler");
113 }
114
115 if (context.respond) {
116 await context.respond(response);
117 }
118 } catch (error) {
119 if (error instanceof Error)
120 blog(
121 `Error in \`${payload.command}\` command: ${error.message}`,
122 "error",
123 );
124
125 // Capture the error in Sentry
126 Sentry.captureException(error, {
127 extra: {
128 command: payload.command,
129 userId: payload.user_id,
130 channelId: payload.channel_id,
131 text: payload.text,
132 },
133 });
134
135 // Respond with error message to user
136 if (context.respond) {
137 await context.respond({
138 text: "An error occurred while processing your request. Please be patent while we try to put out the fire.",
139 response_type: "ephemeral",
140 });
141 }
142 }
143 },
144 );
145}