a fun bot for the hc slack
1import { slackClient } from "../index";
2import Bottleneck from "bottleneck";
3import Queue from "./queue";
4import colors from "colors";
5import * as Sentry from "@sentry/bun";
6import type {
7 ChatPostMessageRequest,
8 ChatPostMessageResponse,
9} from "slack-edge";
10
11// Create a rate limiter with Bottleneck
12const limiter = new Bottleneck({
13 minTime: 1000, // 1 second between each request
14});
15
16const messageQueue = new Queue();
17
18async function sendMessage(
19 message: ChatPostMessageRequest,
20): Promise<ChatPostMessageResponse> {
21 try {
22 return await limiter.schedule(() =>
23 slackClient.chat.postMessage(message),
24 );
25 } catch (error) {
26 Sentry.captureException(error, {
27 extra: { channel: message.channel, text: message.text },
28 tags: { type: "slack_message_error" },
29 });
30 console.error("Failed to send Slack message:", error);
31 throw error;
32 }
33}
34
35async function slog(
36 logMessage: string,
37 location?: {
38 thread_ts?: string;
39 channel: string;
40 },
41): Promise<void> {
42 try {
43 const channel = location?.channel || process.env.SLACK_LOG_CHANNEL;
44
45 if (!channel) {
46 throw new Error("No Slack channel specified for logging");
47 }
48
49 const message: ChatPostMessageRequest = {
50 channel,
51 thread_ts: location?.thread_ts,
52 text: logMessage.substring(0, 2500),
53 blocks: [
54 {
55 type: "section",
56 text: {
57 type: "mrkdwn",
58 text: logMessage
59 .split("\n")
60 .map((a) => `> ${a}`)
61 .join("\n"),
62 },
63 },
64 {
65 type: "context",
66 elements: [
67 {
68 type: "mrkdwn",
69 text: `${new Date().toString()}`,
70 },
71 ],
72 },
73 ],
74 };
75
76 messageQueue.enqueue(() => sendMessage(message));
77 } catch (error) {
78 Sentry.captureException(error, {
79 extra: { logMessage, location, channel: location?.channel },
80 tags: { type: "slog_error" },
81 });
82 console.error("Failed to queue Slack log message:", error);
83 }
84}
85
86type LogType = "info" | "start" | "cron" | "error";
87
88type LogMetadata = {
89 error?: Error;
90 context?: string;
91 additional?: Record<string, unknown>;
92};
93
94export async function clog(
95 logMessage: string,
96 type: LogType,
97 metadata?: LogMetadata,
98): Promise<void> {
99 const timestamp = new Date().toISOString();
100 const formattedMessage = `[${timestamp}] ${logMessage}`;
101
102 switch (type) {
103 case "info":
104 console.log(colors.blue(formattedMessage));
105 break;
106 case "start":
107 console.log(colors.green(formattedMessage));
108 break;
109 case "cron":
110 console.log(colors.magenta(`[CRON]: ${formattedMessage}`));
111 break;
112 case "error": {
113 const errorMessage = colors.red.bold(
114 `Yo <@S0790GPRA48> deres an error \n\n [ERROR]: ${formattedMessage}`,
115 );
116 console.error(errorMessage);
117 break;
118 }
119 default:
120 console.log(formattedMessage);
121 }
122}
123
124export async function blog(
125 logMessage: string,
126 type: LogType,
127 location?: {
128 thread_ts?: string;
129 channel: string;
130 },
131 metadata?: LogMetadata,
132): Promise<void> {
133 try {
134 await Promise.all([
135 slog(logMessage, location),
136 clog(logMessage, type, metadata),
137 ]);
138 } catch (error) {
139 console.error("Failed to log message:", error);
140 Sentry.captureException(error, {
141 extra: { logMessage, type, location, metadata },
142 tags: { type: "blog_error" },
143 });
144 }
145}
146
147export { clog as default, slog };