-
Notifications
You must be signed in to change notification settings - Fork 0
/
birthday-service.js
301 lines (266 loc) · 8.96 KB
/
birthday-service.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
const cron = require('node-cron');
const { db, statements } = require('./database');
const Anthropic = require('@anthropic-ai/sdk');
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
// Birthday channel ID where messages will be posted
const BIRTHDAY_CHANNEL = process.env.BIRTHDAY_CHANNEL;
// Description message prompts
const descriptionPrompts = [
"What makes them unique and essential?",
"What's something you admire about them?",
"What's their defining feature?",
"What makes them amazing to work with?",
"How would you describe them in one word?",
"What's their superpower?"
];
// Function to get a random description prompt
const getRandomPrompt = () => {
const randomIndex = Math.floor(Math.random() * descriptionPrompts.length);
return descriptionPrompts[randomIndex];
};
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
function chunk(array, size) {
const chunks = [];
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
}
async function generateBirthdayPoem(descriptions) {
try {
// Format descriptions into a single string
const descriptionsText = descriptions
.map(desc => desc.message)
.join('\n');
// Create the prompt for Claude
const prompt = `Based on these descriptions of some from their colleagues:\n\n${descriptionsText}\n\nWrite a warm, personal, and fun birthday poem that incorporates these qualities and characteristics. The poem should be light-hearted and celebratory. Just return the poem and no introduction or other text. Format it with line breaks.`;
// Call Claude API
const response = await anthropic.messages.create({
model: 'claude-3-sonnet-20240229',
max_tokens: 150,
temperature: 0.7,
messages: [{
role: 'user',
content: prompt
}]
});
// Extract the poem from the response
const poem = response.content[0].text.trim();
return poem;
} catch (error) {
console.error('Error generating poem with Claude:', error);
// Return a fallback poem if Claude API fails
return "Here's to another year of joy and cheer,\nWith colleagues who hold you ever so dear.\nYour presence makes our workplace bright,\nHappy birthday, may your day be just right!";
}
}
const generateBirthdayCollectionBlocks = (celebrantId) => {
const descriptionPrompt = getRandomPrompt();
return [
{
type: "section",
text: {
type: "mrkdwn",
text: `Hey! :birthday: *<@${celebrantId}>* has a birthday coming up in 7 days!`
}
},
{
type: "input",
block_id: "message_input_block",
element: {
type: "plain_text_input",
action_id: "message_input",
multiline: true,
placeholder: {
type: "plain_text",
text: "Type your birthday message here..."
}
},
label: {
type: "plain_text",
text: "Your Birthday Message"
}
},
{
type: "input",
block_id: "media_input_block",
element: {
type: "plain_text_input",
action_id: "media_input",
placeholder: {
type: "plain_text",
text: "Paste a URL to a GIF or image..."
}
},
label: {
type: "plain_text",
text: "Optional: Add Media (Hint: Use /giphy to search for a GIF and copy the URL)"
},
optional: true
},
{
type: "divider"
},
{
type: "input",
block_id: "description_input_block",
element: {
type: "plain_text_input",
action_id: "description_input",
multiline: true,
placeholder: {
type: "plain_text",
text: `${descriptionPrompt}`
}
},
label: {
type: "plain_text",
text: "Describe Them"
},
optional: true
},
{
type: "actions",
block_id: "submit_block",
elements: [
{
type: "button",
text: {
type: "plain_text",
text: "Submit",
emoji: true
},
action_id: "submit_birthday_content",
value: `${celebrantId}`
}
]
}
]
}
async function triggerBirthdayCollection(client, celebrantId) {
try {
const exists = statements.checkUserExists.get(celebrantId);
if (!exists.count) {
throw new Error('User does not exist in birthdays table');
}
const today = new Date().toISOString().split('T')[0];
const birthday = statements.getBirthday.get(celebrantId);
if (birthday.last_notification_date === today) {
console.log(`Already sent notifications for ${celebrantId} today`);
return;
}
const result = await client.users.list();
const users = result.members.filter(user =>
!user.is_bot &&
!user.deleted &&
!user.is_restricted &&
!user.is_ultra_restricted &&
user.id !== celebrantId &&
user.id !== 'USLACKBOT' &&
user.name !== celebrantId
);
const userBatches = chunk(users, 10);
console.log(`Attempting to send messages to ${users.length} users in ${userBatches.length} batches`);
for (const batch of userBatches) {
// Process each batch with a delay between batches
await Promise.all(batch.map(async (user) => {
try {
// Try to open a DM channel first
try {
const conversationResponse = await client.conversations.open({
users: user.id
});
if (!conversationResponse.ok) {
console.log(`Cannot open DM with user ${user.id}`);
return;
}
} catch (dmError) {
console.log(`Error opening DM with user ${user.id}:`, dmError);
return;
}
console.log(`Sending birthday message collection to ${user.id}`);
await client.chat.postMessage({
channel: user.id,
text: `Birthday message collection for <@${celebrantId}>`,
blocks: generateBirthdayCollectionBlocks(celebrantId)
});
} catch (error) {
console.error(`Error sending birthday collection message to ${user.id}:`, error);
}
}));
// Add delay between batches to prevent rate limiting
await delay(2500);
}
console.log(`Updating last notification date for ${celebrantId}`);
statements.updateLastNotificationDate.run(celebrantId);
} catch (error) {
console.error('Error triggering birthday collection:', error);
}
}
async function postBirthdayThread(client, celebrantId) {
try {
const mainPost = await client.chat.postMessage({
channel: BIRTHDAY_CHANNEL,
text: `Happy Birthday <@${celebrantId}>! 🎂`,
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `:birthday: *Happy Birthday <@${celebrantId}>!* :balloon:\n\nYour colleagues have some special messages for you! Check out the thread below. :arrow_down:`
}
}
]
});
console.log(`Getting descriptions for ${celebrantId}`);
// Get all descriptions and combine them into one message
const descriptions = statements.getDescriptionMessages.all(celebrantId);
if (descriptions.length > 0) {
// Generate poem from descriptions
console.log(`Generating poem for ${celebrantId}`);
const poem = await generateBirthdayPoem(descriptions);
// Post the poem first in the thread
console.log(`Posting poem for ${celebrantId}`);
await client.chat.postMessage({
channel: BIRTHDAY_CHANNEL,
thread_ts: mainPost.ts,
text: "*A special birthday poem for you:*\n\n" + poem + "\n\n:birthday: :sparkles: :cake:"
});
// Post the descriptions
let descriptionMessage = "*Here's what your colleagues say about you:*\n\n";
for (const desc of descriptions) {
descriptionMessage += `• ${desc.message} _- ${desc.sender_name}_\n\n`;
}
await client.chat.postMessage({
channel: BIRTHDAY_CHANNEL,
thread_ts: mainPost.ts,
text: descriptionMessage
});
// Mark descriptions as sent
statements.markDescriptionMessagesAsSent.run(celebrantId);
}
console.log(`Getting birthday messages for ${celebrantId}`);
const messages = statements.getBirthdayMessages.all(celebrantId);
for (const message of messages) {
console.log(`Posting birthday message for ${celebrantId} from ${message.sender_name}`);
let text = `${message.sender_name} says:\n${message.message}`;
if (message.media_url) {
text += `<${message.media_url}|.>`;
}
await client.chat.postMessage({
channel: BIRTHDAY_CHANNEL,
thread_ts: mainPost.ts,
text: text
});
}
// Mark messages as sent
statements.markMessagesAsSent.run(celebrantId);
} catch (error) {
console.error('Error posting birthday thread:', error);
}
}
module.exports = {
postBirthdayThread,
triggerBirthdayCollection
};