a fun bot for the hc slack
at v0.0.1 7.0 kB view raw
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;