a fun bot for the hc slack
at main 9.5 kB view raw
1import { slackApp, slackClient } from "../../../index"; 2import { db } from "../../../libs/db"; 3import { eq } from "drizzle-orm"; 4import { users as usersTable } from "../../../libs/schema"; 5import { 6 fetchRecentProjectKeys, 7 getHackatimeName, 8 HACKATIME_VERSIONS, 9 type HackatimeVersion, 10} from "../../../libs/hackatime"; 11import { deployToHackClubCDN } from "../../../libs/cdn"; 12import { getCategoryLabel } from "../../../libs/categories"; 13import type { Project } from "../../api/routes/projects"; 14 15export async function handleSettings( 16 triggerID: string, 17 user: string, 18 prefill = false, 19) { 20 let initialValues: { 21 project_name: string; 22 project_description: string; 23 project_category: Project["projectCategory"]; 24 repo_link: string | undefined; 25 demo_link: string | undefined; 26 hackatime_version: HackatimeVersion; 27 hackatime_keys: string[]; 28 } = { 29 project_name: "", 30 project_description: "", 31 project_category: "other", 32 repo_link: undefined, 33 demo_link: undefined, 34 hackatime_version: "v1", 35 hackatime_keys: [], 36 }; 37 38 if (prefill) { 39 try { 40 // Check if user already has a project in the database 41 const existingUser = ( 42 await db 43 .select() 44 .from(usersTable) 45 .where(eq(usersTable.id, user)) 46 )[0]; 47 48 if (existingUser) { 49 initialValues = { 50 project_name: existingUser.projectName, 51 project_description: existingUser.projectDescription, 52 project_category: 53 existingUser.projectCategory && 54 [ 55 "hardware", 56 "hardware_software", 57 "website", 58 "app", 59 "game", 60 "art_design", 61 "other", 62 ].includes(existingUser.projectCategory) 63 ? (existingUser.projectCategory as Project["projectCategory"]) 64 : "other", 65 repo_link: existingUser.repoLink || undefined, 66 demo_link: existingUser.demoLink || undefined, 67 hackatime_version: 68 existingUser.hackatimeVersion as HackatimeVersion, 69 hackatime_keys: existingUser.hackatimeKeys 70 ? JSON.parse(existingUser.hackatimeKeys) 71 : [], 72 }; 73 } 74 } catch (error) { 75 console.error("Error prefilling form:", error); 76 } 77 } 78 79 const hackatimeKeys = await fetchRecentProjectKeys( 80 user, 81 10, 82 initialValues.hackatime_version as HackatimeVersion, 83 ); 84 85 await slackClient.views.open({ 86 trigger_id: triggerID, 87 view: { 88 type: "modal", 89 title: { 90 type: "plain_text", 91 text: "Setup Project", 92 }, 93 submit: { 94 type: "plain_text", 95 text: "Submit", 96 }, 97 callback_id: "takes_setup_submit", 98 blocks: [ 99 { 100 type: "input", 101 block_id: "project_name", 102 label: { 103 type: "plain_text", 104 text: "Project Name", 105 }, 106 element: { 107 type: "plain_text_input", 108 action_id: "project_name_input", 109 initial_value: initialValues.project_name, 110 placeholder: { 111 type: "plain_text", 112 text: "Enter your project name", 113 }, 114 }, 115 }, 116 { 117 type: "input", 118 block_id: "project_description", 119 label: { 120 type: "plain_text", 121 text: "Project Description", 122 }, 123 element: { 124 type: "plain_text_input", 125 action_id: "project_description_input", 126 multiline: true, 127 initial_value: initialValues.project_description, 128 placeholder: { 129 type: "plain_text", 130 text: "Describe your project", 131 }, 132 }, 133 }, 134 { 135 type: "input", 136 block_id: "project_category", 137 label: { 138 type: "plain_text", 139 text: "Project Category", 140 }, 141 element: { 142 type: "static_select", 143 action_id: "project_category_input", 144 initial_option: initialValues.project_category 145 ? { 146 text: { 147 type: "plain_text", 148 text: getCategoryLabel( 149 initialValues.project_category, 150 ), 151 }, 152 value: initialValues.project_category, 153 } 154 : undefined, 155 placeholder: { 156 type: "plain_text", 157 text: "Select a project category", 158 }, 159 options: [ 160 { 161 text: { 162 type: "plain_text", 163 text: "Hardware", 164 }, 165 value: "hardware", 166 }, 167 { 168 text: { 169 type: "plain_text", 170 text: "Hardware + Software", 171 }, 172 value: "hardware_software", 173 }, 174 { 175 text: { 176 type: "plain_text", 177 text: "Website", 178 }, 179 value: "website", 180 }, 181 { 182 text: { 183 type: "plain_text", 184 text: "App", 185 }, 186 value: "app", 187 }, 188 { 189 text: { 190 type: "plain_text", 191 text: "Game", 192 }, 193 value: "game", 194 }, 195 { 196 text: { 197 type: "plain_text", 198 text: "Art & Design", 199 }, 200 value: "art_design", 201 }, 202 { 203 text: { 204 type: "plain_text", 205 text: "Other", 206 }, 207 value: "other", 208 }, 209 ], 210 }, 211 }, 212 { 213 type: "input", 214 block_id: "project_banner", 215 label: { 216 type: "plain_text", 217 text: `Banner Image${prefill ? " (this will replace your current banner)" : ""}`, 218 }, 219 element: { 220 type: "file_input", 221 action_id: "project_banner_input", 222 }, 223 optional: true, 224 }, 225 { 226 type: "input", 227 block_id: "repo_link", 228 optional: false, 229 label: { 230 type: "plain_text", 231 text: "Repository Link", 232 }, 233 element: { 234 type: "plain_text_input", 235 action_id: "repo_link_input", 236 initial_value: initialValues.repo_link, 237 placeholder: { 238 type: "plain_text", 239 text: "Add a link to your repository", 240 }, 241 }, 242 }, 243 { 244 type: "input", 245 block_id: "demo_link", 246 optional: true, 247 label: { 248 type: "plain_text", 249 text: "Demo Link", 250 }, 251 element: { 252 type: "plain_text_input", 253 action_id: "demo_link_input", 254 initial_value: initialValues.demo_link, 255 placeholder: { 256 type: "plain_text", 257 text: "Optional: Add a link to your demo", 258 }, 259 }, 260 }, 261 { 262 type: "input", 263 block_id: "hackatime_version", 264 label: { 265 type: "plain_text", 266 text: "Hackatime Version", 267 }, 268 element: { 269 type: "static_select", 270 action_id: "hackatime_version_input", 271 initial_option: { 272 text: { 273 type: "plain_text", 274 text: getHackatimeName( 275 initialValues.hackatime_version, 276 ), 277 }, 278 value: initialValues.hackatime_version, 279 }, 280 options: Object.values(HACKATIME_VERSIONS).map((v) => ({ 281 text: { 282 type: "plain_text", 283 text: v.name, 284 }, 285 value: v.id, 286 })), 287 }, 288 }, 289 hackatimeKeys.length > 0 290 ? { 291 type: "input", 292 block_id: "project_keys", 293 label: { 294 type: "plain_text", 295 text: "Project Keys", 296 }, 297 element: { 298 type: "multi_static_select", 299 action_id: "project_keys_input", 300 initial_options: 301 initialValues.hackatime_keys.length === 0 302 ? undefined 303 : initialValues.hackatime_keys.map( 304 (key) => ({ 305 text: { 306 type: "plain_text", 307 text: key, 308 }, 309 value: key, 310 }), 311 ), 312 options: hackatimeKeys.map((key) => ({ 313 text: { 314 type: "plain_text", 315 text: key, 316 }, 317 value: key, 318 })), 319 }, 320 } 321 : { 322 type: "section", 323 text: { 324 text: "You don't have any hackatime projects. Go setup hackatime with `/hackatime`", 325 type: "mrkdwn", 326 }, 327 }, 328 ], 329 }, 330 }); 331} 332 333export async function setupSubmitListener() { 334 slackApp.view("takes_setup_submit", async ({ payload, context }) => { 335 if (payload.type !== "view_submission") return; 336 const values = payload.view.state.values; 337 const userId = payload.user.id; 338 339 const file = values.project_banner?.project_banner_input?.files?.[0] 340 ?.url_private_download as string; 341 342 const hackatimeKeys = JSON.stringify( 343 values.project_keys?.project_keys_input?.selected_options?.map( 344 (option) => option.value, 345 ) || [], 346 ); 347 348 try { 349 const projectBannerUrl = file 350 ? await deployToHackClubCDN([file]).then( 351 (res) => res.files[0]?.deployedUrl, 352 ) 353 : undefined; 354 355 const hackatimeVersion = values.hackatime_version 356 ?.hackatime_version_input?.selected_option 357 ?.value as HackatimeVersion; 358 359 await db 360 .insert(usersTable) 361 .values({ 362 id: userId, 363 projectName: values.project_name?.project_name_input?.value, 364 projectDescription: 365 values.project_description?.project_description_input 366 ?.value, 367 projectCategory: 368 values.project_category?.project_category_input 369 ?.selected_option?.value, 370 projectBannerUrl, 371 repoLink: values.repo_link?.repo_link_input?.value, 372 demoLink: values.demo_link?.demo_link_input?.value, 373 hackatimeVersion, 374 hackatimeKeys, 375 }) 376 .onConflictDoUpdate({ 377 target: usersTable.id, 378 set: { 379 projectName: 380 values.project_name?.project_name_input?.value, 381 projectDescription: 382 values.project_description 383 ?.project_description_input?.value, 384 projectBannerUrl, 385 repoLink: values.repo_link?.repo_link_input?.value, 386 demoLink: values.demo_link?.demo_link_input?.value, 387 hackatimeVersion, 388 hackatimeKeys, 389 }, 390 }); 391 } catch (error) { 392 console.error("Error processing file:", error); 393 throw error; 394 } 395 }); 396}