a fun bot for the hc slack
1import { slackApp, slackClient } from "../index";
2import { db } from "../libs/db";
3import { takes as takesTable } from "../libs/schema";
4import { eq, and } from "drizzle-orm";
5import { prettyPrintTime } from "../libs/time";
6
7const upload = async () => {
8 slackApp.anyMessage(async ({ payload }) => {
9 try {
10 if (payload.subtype !== "file_share") return;
11 const user = payload.user;
12
13 if (!user) return;
14
15 const takesNeedUpload = await db
16 .select()
17 .from(takesTable)
18 .where(
19 and(
20 eq(takesTable.userId, payload.user as string),
21 eq(takesTable.ts, payload.thread_ts as string),
22 eq(takesTable.status, "waitingUpload"),
23 ),
24 );
25
26 if (takesNeedUpload.length === 0) return;
27
28 const take = takesNeedUpload[0];
29
30 if (!payload.files || !take) return;
31
32 const file = payload.files[0];
33
34 if (!file || !file.id || !file.thumb_video || !file.mp4) {
35 await slackClient.reactions.add({
36 channel: payload.channel,
37 timestamp: payload.ts as string,
38 name: "no",
39 });
40
41 slackClient.chat.postMessage({
42 channel: payload.channel,
43 thread_ts: payload.thread_ts,
44 text: "that's not a video file? 🤔",
45 });
46 return;
47 }
48
49 const fileres = await slackClient.files.sharedPublicURL({
50 file: file.id,
51 token: process.env.SLACK_USER_TOKEN,
52 });
53
54 const fetchRes = await fetch(
55 fileres.file?.permalink_public as string,
56 );
57 const html = await fetchRes.text();
58 const match = html.match(/src="([^"]*\.mp4[^"]*)"/);
59 const takePublicUrl = match?.[1];
60
61 await db
62 .update(takesTable)
63 .set({
64 status: "uploaded",
65 takeUploadedAt: new Date(),
66 takeUrl: takePublicUrl,
67 takeThumbUrl: file?.thumb_video,
68 })
69 .where(eq(takesTable.id, take.id));
70
71 await slackClient.reactions.add({
72 channel: payload.channel,
73 timestamp: payload.ts as string,
74 name: "fire",
75 });
76
77 await slackClient.chat.postMessage({
78 channel: payload.channel,
79 thread_ts: payload.thread_ts,
80 text: ":video_camera: uploaded! leme send this to the team for review real quick",
81 blocks: [
82 {
83 type: "section",
84 text: {
85 type: "mrkdwn",
86 text: ":video_camera: uploaded! leme send this to the team for review real quick",
87 },
88 },
89 {
90 type: "divider",
91 },
92 {
93 type: "context",
94 elements: [
95 {
96 type: "mrkdwn",
97 text: `take by <@${user}> for \`${prettyPrintTime(take.durationMinutes * 60000)}\` working on: *${take.description}*`,
98 },
99 ],
100 },
101 ],
102 });
103
104 await slackClient.chat.postMessage({
105 channel: process.env.SLACK_REVIEW_CHANNEL || "",
106 text: "",
107 blocks: [
108 {
109 type: "section",
110 text: {
111 type: "mrkdwn",
112 text: `:video_camera: new take uploaded by <@${user}> for \`${prettyPrintTime(take.durationMinutes * 60000)}\` working on: *${take.description}*`,
113 },
114 },
115 {
116 type: "divider",
117 },
118 {
119 type: "video",
120 video_url: `${process.env.API_URL}/video/${take.id}`,
121 title_url: `${process.env.API_URL}/video/${take.id}`,
122 title: {
123 type: "plain_text",
124 text: `take on ${take.takeUploadedAt?.toISOString()}`,
125 },
126 thumbnail_url: `https://cachet.dunkirk.sh/users/${payload.user}/r`,
127 alt_text: `take on ${take.takeUploadedAt?.toISOString()}`,
128 },
129 {
130 type: "divider",
131 },
132 {
133 type: "actions",
134 elements: [
135 {
136 type: "static_select",
137 placeholder: {
138 type: "plain_text",
139 text: "Select multiplier",
140 },
141 options: [
142 {
143 text: {
144 type: "plain_text",
145 text: "0.5x",
146 },
147 value: "0.5",
148 },
149 {
150 text: {
151 type: "plain_text",
152 text: "1x",
153 },
154 value: "1",
155 },
156 {
157 text: {
158 type: "plain_text",
159 text: "1.25x",
160 },
161 value: "1.25",
162 },
163 {
164 text: {
165 type: "plain_text",
166 text: "1.5x",
167 },
168 value: "1.5",
169 },
170 {
171 text: {
172 type: "plain_text",
173 text: "2x",
174 },
175 value: "2",
176 },
177 {
178 text: {
179 type: "plain_text",
180 text: "3x",
181 },
182 value: "2.5",
183 },
184 ],
185 action_id: "select_multiplier",
186 },
187 {
188 type: "button",
189 text: {
190 type: "plain_text",
191 text: "approve",
192 },
193 style: "primary",
194 value: take.id,
195 action_id: "approve",
196 },
197 {
198 type: "button",
199 text: {
200 type: "plain_text",
201 text: "reject",
202 },
203 style: "danger",
204 value: take.id,
205 action_id: "reject",
206 },
207 ],
208 },
209 {
210 type: "divider",
211 },
212 {
213 type: "context",
214 elements: [
215 {
216 type: "mrkdwn",
217 text: `take by <@${user}> for \`${prettyPrintTime(take.durationMinutes * 60000)}\` working on: *${take.description}*`,
218 },
219 ],
220 },
221 ],
222 });
223 } catch (error) {
224 console.error("Error handling file message:", error);
225 }
226 });
227
228 slackApp.action("select_multiplier", async () => {});
229
230 slackApp.action("approve", async ({ payload, context }) => {
231 const multiplier = Object.values(payload.state.values)[0]
232 ?.select_multiplier?.selected_option?.value;
233 // @ts-expect-error
234 const takeId = payload.actions[0]?.value;
235
236 const take = await db
237 .select()
238 .from(takesTable)
239 .where(eq(takesTable.id, takeId));
240 if (take.length === 0) {
241 return;
242 }
243 await db
244 .update(takesTable)
245 .set({
246 status: "approved",
247 multiplier: multiplier,
248 })
249 .where(eq(takesTable.id, takeId));
250
251 await slackClient.chat.postMessage({
252 channel: payload.user.id,
253 thread_ts: take[0]?.ts as string,
254 text: `take approved with multiplier \`${multiplier}\` so you have earned *${Number(((take[0]?.durationMinutes as number) * Number(multiplier)) / 60).toFixed(1)} takes*!`,
255 });
256
257 // delete the message from the review channel
258 if (context.respond)
259 await context.respond({
260 delete_original: true,
261 });
262 });
263
264 slackApp.action("reject", async ({ payload, context }) => {
265 // @ts-expect-error
266 const takeId = payload.actions[0]?.value;
267
268 const take = await db
269 .select()
270 .from(takesTable)
271 .where(eq(takesTable.id, takeId));
272 if (take.length === 0) {
273 return;
274 }
275 await db
276 .update(takesTable)
277 .set({
278 status: "rejected",
279 multiplier: "0",
280 })
281 .where(eq(takesTable.id, takeId));
282
283 await slackClient.chat.postMessage({
284 channel: payload.user.id,
285 thread_ts: take[0]?.ts as string,
286 text: "take rejected :(",
287 });
288
289 // delete the message from the review channel
290 if (context.respond)
291 await context.respond({
292 delete_original: true,
293 });
294 });
295};
296
297export default upload;