···
import type { Block, SlackAPIClient } from "slack-edge";
export interface SlackMessage {
···
private slack: SlackAPIClient;
private isProcessing = false;
17
+
private rateLimitDelay = 1000; // 1 message per second per channel
18
+
private channelLastMessageTime: Map<string, number> = new Map();
19
+
private totalMessageCount = 0;
20
+
private messageCountResetTime = 0;
21
+
private backoffDelay = 1000;
22
+
private maxBackoff = 30000; // 30 seconds
constructor(slackClient: SlackAPIClient, dbPath = "slack-queue.db") {
this.slack = slackClient;
this.db = new Database(dbPath);
28
+
this.processQueue();
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
35
+
channel TEXT NOT NULL,
timestamp INTEGER NOT NULL,
···
async enqueue(message: SlackMessage): Promise<void> {
const stmt = this.db.prepare(`
42
-
INSERT INTO messages (userId, channelId, blocks, text, timestamp, status)
43
-
VALUES (?, ?, ?, ?, ?, ?)
47
+
INSERT INTO messages (channel, blocks, text, timestamp, status)
48
+
VALUES (?, ?, ?, ?, ?)
47
-
message.userId ?? null,
48
-
message.channelId ?? null,
52
+
message.channel ?? null,
JSON.stringify(message.blocks) ?? null,
···
64
+
private async sleep(ms: number): Promise<void> {
65
+
return new Promise((resolve) => setTimeout(resolve, ms));
68
+
private async sendWithRateLimit(
69
+
message: SlackMessage & { id: number },
71
+
const now = Date.now();
73
+
// Check per-minute total limit
74
+
if (now - this.messageCountResetTime >= 60000) {
75
+
this.totalMessageCount = 0;
76
+
this.messageCountResetTime = now;
79
+
if (this.totalMessageCount >= 350) {
80
+
const waitTime = 60000 - (now - this.messageCountResetTime);
81
+
await this.sleep(waitTime);
82
+
this.totalMessageCount = 0;
83
+
this.messageCountResetTime = Date.now();
86
+
// Check per-channel rate limit
87
+
const channelLastTime =
88
+
this.channelLastMessageTime.get(message.channel) || 0;
89
+
const timeSinceLastChannelMessage = now - channelLastTime;
91
+
if (timeSinceLastChannelMessage < this.rateLimitDelay) {
92
+
await this.sleep(this.rateLimitDelay - timeSinceLastChannelMessage);
95
+
let currentBackoff = this.backoffDelay;
97
+
const maxAttempts = 3;
99
+
while (attempts < maxAttempts) {
101
+
await this.slack.chat.postMessage({
102
+
channel: message.channel,
103
+
blocks: JSON.parse(message.blocks as unknown as string) ?? undefined,
104
+
text: message.text,
107
+
this.channelLastMessageTime.set(message.channel, Date.now());
108
+
this.totalMessageCount++;
114
+
SET status = 'sent'
123
+
`Error sending message (attempt ${attempts + 1}/${maxAttempts})`,
128
+
if (attempts === maxAttempts) {
133
+
SET status = 'failed'
141
+
await this.sleep(currentBackoff);
142
+
currentBackoff = Math.min(currentBackoff * 2, this.maxBackoff);
private async processQueue() {
if (this.isProcessing) return;
this.isProcessing = true;
···
71
-
SELECT * FROM messages
72
-
WHERE status = 'pending'
158
+
SELECT * FROM messages
159
+
WHERE status = 'pending'
.all(this.batchSize) as (SlackMessage & { id: number })[];
78
-
console.log(messages);
if (messages.length === 0) break;
82
-
messages.map(async (message) => {
84
-
if (message.channelId) {
85
-
await this.slack.chat.postMessage({
86
-
channel: message.channelId,
88
-
JSON.parse(message.blocks as unknown as string) ??
94
-
} else if (message.userId) {
95
-
await this.slack.chat.postMessage({
96
-
channel: message.userId,
98
-
JSON.parse(message.blocks as unknown as string) ??
100
-
text: message.text,
104
-
console.log("Message sent successfully");
110
-
SET status = 'sent'
116
-
console.error("Error sending message", error);
121
-
SET status = 'failed'
167
+
// Process messages sequentially to maintain rate limiting
168
+
for (const message of messages) {
169
+
await this.sendWithRateLimit(message);
this.isProcessing = false;
···
140
-
DELETE FROM messages
141
-
WHERE timestamp < ? AND status != 'pending'
182
+
DELETE FROM messages
183
+
WHERE timestamp < ? AND status != 'pending'
···
151
-
SELECT COUNT(*) as count
153
-
WHERE status = 'pending'
193
+
SELECT COUNT(*) as count
195
+
WHERE status = 'pending'
return (result as { count: number }).count;