a fun bot for the hc slack
at main 3.3 kB view raw
1import { SlackApp } from "slack-edge"; 2 3import { takes } from "./features/index"; 4 5import { t } from "./libs/template"; 6import { blog } from "./libs/Logger"; 7import { version, name } from "../package.json"; 8import { apiRouter, video } from "./features/api"; 9const environment = process.env.NODE_ENV; 10 11import * as Sentry from "@sentry/bun"; 12 13// Check required environment variables 14const requiredVars = [ 15 "SLACK_BOT_TOKEN", 16 "SLACK_SIGNING_SECRET", 17 "SLACK_REVIEW_CHANNEL", 18 "SLACK_LOG_CHANNEL", 19 "SLACK_SPAM_CHANNEL", 20 "SLACK_USER_TOKEN", 21 "API_URL", 22 "SENTRY_DSN", 23] as const; 24const missingVars = requiredVars.filter((varName) => !process.env[varName]); 25 26if (missingVars.length > 0) { 27 throw new Error( 28 `Missing required environment variables: ${missingVars.join(", ")}`, 29 ); 30} 31 32if (process.env.NODE_ENV === "production") { 33 Sentry.init({ 34 dsn: process.env.SENTRY_DSN, 35 environment, 36 release: version, 37 sampleRate: 0.1, 38 }); 39} 40 41console.log( 42 `----------------------------------\n${name} Server\n----------------------------------\n`, 43); 44console.log(`🏗️ Starting ${name}...`); 45console.log("📦 Loading Slack App..."); 46console.log("🔑 Loading environment variables..."); 47 48const slackApp = new SlackApp({ 49 env: { 50 SLACK_BOT_TOKEN: process.env.SLACK_BOT_TOKEN as string, 51 SLACK_SIGNING_SECRET: process.env.SLACK_SIGNING_SECRET as string, 52 SLACK_LOGGING_LEVEL: "INFO", 53 }, 54 startLazyListenerAfterAck: true, 55}); 56const slackClient = slackApp.client; 57 58takes(); 59 60export default { 61 port: process.env.PORT || 3000, 62 development: environment === "dev", 63 async fetch(request: Request) { 64 const url = new URL(request.url); 65 const path = url.pathname.split("/").filter(Boolean)[0] 66 ? `/${url.pathname.split("/").filter(Boolean)[0]}` 67 : "/"; 68 69 // CORS headers to allow all origins 70 const corsHeaders = { 71 "Access-Control-Allow-Origin": "*", 72 "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS", 73 "Access-Control-Allow-Headers": "Content-Type, Authorization", 74 }; 75 76 // Handle preflight OPTIONS request 77 if (request.method === "OPTIONS") { 78 return new Response(null, { 79 status: 204, 80 headers: corsHeaders, 81 }); 82 } 83 84 let response: Response; 85 switch (path) { 86 case "/": 87 response = new Response(`Hello World from ${name}@${version}`); 88 break; 89 case "/health": 90 response = new Response("OK"); 91 break; 92 case "/slack": 93 response = await slackApp.run(request); 94 break; 95 case "/api": 96 response = await apiRouter(url); 97 break; 98 default: 99 response = new Response("404 Not Found", { status: 404 }); 100 } 101 102 // Add CORS headers to all responses 103 const newHeaders = new Headers(response.headers); 104 for (const [key, value] of Object.entries(corsHeaders)) { 105 newHeaders.set(key, value); 106 } 107 108 return new Response(response.body, { 109 status: response.status, 110 statusText: response.statusText, 111 headers: newHeaders, 112 }); 113 }, 114}; 115 116console.log( 117 `🚀 Server Started in ${ 118 Bun.nanoseconds() / 1000000 119 } milliseconds on version: ${version}!\n\n----------------------------------\n`, 120); 121 122blog( 123 t("app.startup", { 124 environment, 125 }), 126 "start", 127 { 128 channel: process.env.SLACK_SPAM_CHANNEL || "", 129 }, 130); 131 132console.log("\n----------------------------------\n"); 133 134export { slackApp, slackClient, version, name, environment };