-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.js
More file actions
355 lines (323 loc) · 9.71 KB
/
app.js
File metadata and controls
355 lines (323 loc) · 9.71 KB
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
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
import https from "https";
import { query, where, getDocs, orderBy } from "firebase/firestore";
import { App } from "@slack/bolt";
import { db } from "./firebase.js";
import { collection, addDoc, Timestamp } from "firebase/firestore";
import { render } from "@react-email/render";
import { Resend } from "resend";
import React from "react";
import WelcomeEmail from "./welcome-email.jsx";
const translations = [
{
title: "American Standard Version",
abbreviation: "asv",
},
{
title: "Bible in Basic English",
abbreviation: "bbe",
},
{
title: "King James Version",
abbreviation: "kjv",
},
];
// Initializes your app with your bot token and app token
const app = new App({
token: process.env.SLACK_BOT_TOKEN,
socketMode: true,
appToken: process.env.SLACK_APP_TOKEN,
});
// Listens to incoming messages that contain "hello"
app.message("hello", async ({ message, say }) => {
console.log("Message received:", message);
await say({
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `Hey there <@${message.user}>!`,
},
accessory: {
type: "button",
text: {
type: "plain_text",
text: "Click Me",
},
action_id: "button_click",
},
},
],
text: `Hey there <@${message.user}>!`,
});
});
app.message(/^prayer\s*requests$/i, async ({ message, say }) => {
try {
const q = query(
collection(db, "prayer-requests"),
where("user", "==", message.user),
orderBy("timestamp", "desc")
);
const querySnapshot = await getDocs(q);
if (querySnapshot.empty) {
await say("You have no prayer requests.");
return;
}
let response = "*Your Prayer Requests:*\n";
Array.from(querySnapshot.docs).forEach((doc, idx) => {
const data = doc.data();
const date = data.timestamp?.toDate?.().toLocaleString?.() || "";
response += `*${idx + 1}.* _${date}_\n> ${data.text}\n`;
});
await say(response);
} catch (error) {
console.error("Error fetching prayer requests:", error);
await say("Sorry, there was an error fetching your prayer requests.");
}
});
const GEMINI_API_KEY = process.env.GEMINI_API_KEY;
const MODEL_NAME = "gemini-2.5-flash-lite";
const GEMINI_API_URL = `https://generativelanguage.googleapis.com/v1/models/${MODEL_NAME}:generateContent`;
app.message(/^explain:\s*(.+)/i, async ({ message, context, say }) => {
const userQuestion = context.matches[1]?.trim();
if (!userQuestion) {
await say('Please provide a question after "explain:".');
return;
}
if (!GEMINI_API_KEY) {
await say("Gemini API key is not configured.");
return;
}
const prompt =
"You are a Bible expert. Only answer questions about the Bible. " +
"Give a very short answer and include scripture(s) (max 400 characters total length).\n" +
`Question: ${userQuestion}`;
// body for Gemini v1 generateContent
const body = JSON.stringify({
contents: [
{
role: "user",
parts: [{ text: prompt }],
},
],
});
// GEMINI_API_URL should now look like:
// `https://generativelanguage.googleapis.com/v1/models/${MODEL_NAME}:generateContent`
const url = `${GEMINI_API_URL}?key=${GEMINI_API_KEY}`;
const options = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
};
let fallbackText = "Sorry, there was an error getting an explanation.";
try {
const answer = await new Promise((resolve) => {
const req = https.request(url, options, (res) => {
let data = "";
res.on("data", (chunk) => {
data += chunk;
});
res.on("end", () => {
try {
const result = JSON.parse(data);
// handle error payloads gracefully
if (result.error) {
console.error("Gemini API error payload:", result.error);
return resolve(null);
}
const text =
result.candidates?.[0]?.content?.parts?.[0]?.text ??
result.candidates?.[0]?.output_text;
resolve(text || null);
} catch (e) {
console.error("Gemini parse error:", e, data);
resolve(null);
}
});
});
req.on("error", (err) => {
console.error("Gemini API error:", err);
resolve(null);
});
req.write(body);
req.end();
});
if (answer) {
await say(answer);
// Save to Firestore Questions collection
try {
await addDoc(collection(db, "Questions"), {
user: message.user,
question: userQuestion,
response: answer,
timestamp: Timestamp.now(),
});
} catch (err) {
console.error("Error saving question to Firestore:", err);
}
} else {
await say("No answer received from Gemini.");
}
} catch (error) {
console.error("Gemini API error:", error);
await say(fallbackText);
}
});
app.event("team_join", async ({ event, client }) => {
try {
await client.chat.postMessage({
channel: "C06LD5BGUP7",
text: `Everyone welcome <@${event.user.id}> to the community! 🎉`,
});
const userInfo = await client.users.info({
user: event.user.id,
});
console.log("Full user profile:", JSON.stringify(userInfo.user.profile, null, 2));
console.log("Email found:", userInfo.user.profile.email);
console.log("First name:", userInfo.user.profile.first_name);
const email = userInfo.user.profile.email;
const firstName = userInfo.user.profile.first_name || "there";
const html = await render(React.createElement(WelcomeEmail, { name: firstName }));
const resend = new Resend(process.env.RESEND_API_KEY);
if (email) {
await resend.emails.send({
from: "Debugging Disciples <info@debuggingdisciples.org>",
to: email,
subject: "Welcome to Debugging Disciples 🙌",
html: html,
});
console.log("Email sent to:", email);
}
} catch (error) {
console.error("Error sending welcome message on team_join:", error);
}
});
app.message(/^bible:\s*(.+)/i, async ({ message, context, say }) => {
const verse = context.matches[1]?.trim();
if (!verse) {
await say(
'Please provide a verse after "bible:". For example: bible: John 3:16'
);
return;
}
const translation = "asv";
await sendBibleVerses({ verse, translation, say });
});
async function sendBibleVerses({
verse,
translation,
say,
client,
channel,
messageTs,
}) {
const url = `https://bible-api.com/${encodeURIComponent(
verse
)}?translation=${translation}`;
try {
const res = await fetch(url);
if (!res.ok) {
await say("Sorry, I could not fetch that verse.");
return;
}
const data = await res.json();
if (
!data.verses ||
!Array.isArray(data.verses) ||
data.verses.length === 0
) {
await say("No verses found for that reference.");
return;
}
// Use the verses array as specified
const first = data.verses[0];
const last = data.verses[data.verses.length - 1];
let verseRange = first.verse;
if (data.verses.length > 1) {
verseRange = `${first.verse}-${last.verse}`;
}
const title = `*${first.book_name} ${first.chapter
}:${verseRange} (${translation.toUpperCase()})*`;
const versesText = data.verses
.map((v) => `*${v.verse}*: ${v.text.trim()}`)
.join("\n");
// Build translation buttons
const buttons = translations.map((t) => ({
type: "button",
text: {
type: "plain_text",
text: t.title,
},
value: JSON.stringify({ verse, translation: t.abbreviation }),
action_id: `bible_translation_${t.abbreviation}`,
}));
const blocks = [
{
type: "section",
text: { type: "mrkdwn", text: `${title}\n${versesText}` },
},
{ type: "actions", elements: buttons },
];
// Only use client/chat.update for button actions, otherwise always use say
if (client && channel && messageTs) {
await client.chat.update({
channel,
ts: messageTs,
blocks,
text: `${title}\n${versesText}`,
});
} else {
await say({ blocks, text: `${title}\n${versesText}` });
}
} catch (error) {
console.error("Error fetching Bible verse:", error);
await say("Sorry, there was an error fetching the Bible verse.");
}
}
// Add action listeners for translation buttons
translations.forEach((t) => {
app.action(
`bible_translation_${t.abbreviation}`,
async ({ body, ack, client }) => {
await ack();
const { verse, translation } = JSON.parse(body.actions[0].value);
await sendBibleVerses({
verse,
translation,
client,
channel: body.channel.id,
messageTs: body.message.ts,
});
}
);
});
// Listen for messages starting with "pray4me:"
app.message(/^pray4me:\s*(.+)/i, async ({ message, context, say }) => {
const prayerText = context.matches[1]?.trim();
if (!prayerText) {
await say('Please provide a prayer request after "pray4me:".');
return;
}
try {
await addDoc(collection(db, "prayer-requests"), {
user: message.user,
text: prayerText,
timestamp: Timestamp.now(),
});
await say("🙏 Your prayer request has been received.");
} catch (error) {
console.error("Error adding prayer request:", error);
await say("Sorry, there was an error saving your prayer request.");
}
});
app.action("button_click", async ({ body, ack, say }) => {
// Acknowledge the action
await ack();
await say(`<@${body.user.id}> clicked the button`);
});
(async () => {
// Start your app
await app.start();
app.logger.info("⚡️ Bolt app is running!");
})();