opensentinel 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +283 -0
  3. package/dist/bot-KJ26BG56.js +15 -0
  4. package/dist/bot-KJ26BG56.js.map +1 -0
  5. package/dist/charts-MMXM6BWW.js +241 -0
  6. package/dist/charts-MMXM6BWW.js.map +1 -0
  7. package/dist/chunk-4LVWXUNC.js +1079 -0
  8. package/dist/chunk-4LVWXUNC.js.map +1 -0
  9. package/dist/chunk-4TG2IG5K.js +5249 -0
  10. package/dist/chunk-4TG2IG5K.js.map +1 -0
  11. package/dist/chunk-6DRDKB45.js +251 -0
  12. package/dist/chunk-6DRDKB45.js.map +1 -0
  13. package/dist/chunk-6SNHU3CY.js +123 -0
  14. package/dist/chunk-6SNHU3CY.js.map +1 -0
  15. package/dist/chunk-CI6Q63MM.js +1613 -0
  16. package/dist/chunk-CI6Q63MM.js.map +1 -0
  17. package/dist/chunk-CQ4JURG7.js +57 -0
  18. package/dist/chunk-CQ4JURG7.js.map +1 -0
  19. package/dist/chunk-F6QUZQGI.js +51 -0
  20. package/dist/chunk-F6QUZQGI.js.map +1 -0
  21. package/dist/chunk-GK3E2I7A.js +216 -0
  22. package/dist/chunk-GK3E2I7A.js.map +1 -0
  23. package/dist/chunk-GUBEEYDW.js +211 -0
  24. package/dist/chunk-GUBEEYDW.js.map +1 -0
  25. package/dist/chunk-GVJVEWHI.js +29 -0
  26. package/dist/chunk-GVJVEWHI.js.map +1 -0
  27. package/dist/chunk-HH2HBTQM.js +806 -0
  28. package/dist/chunk-HH2HBTQM.js.map +1 -0
  29. package/dist/chunk-JXUP2X7V.js +129 -0
  30. package/dist/chunk-JXUP2X7V.js.map +1 -0
  31. package/dist/chunk-KHNYJY2Z.js +178 -0
  32. package/dist/chunk-KHNYJY2Z.js.map +1 -0
  33. package/dist/chunk-L3F43VPB.js +652 -0
  34. package/dist/chunk-L3F43VPB.js.map +1 -0
  35. package/dist/chunk-L3PDU3XN.js +803 -0
  36. package/dist/chunk-L3PDU3XN.js.map +1 -0
  37. package/dist/chunk-NSBPE2FW.js +17 -0
  38. package/dist/chunk-NSBPE2FW.js.map +1 -0
  39. package/dist/cli.d.ts +1 -0
  40. package/dist/cli.js +52 -0
  41. package/dist/cli.js.map +1 -0
  42. package/dist/commands/setup.d.ts +9 -0
  43. package/dist/commands/setup.js +374 -0
  44. package/dist/commands/setup.js.map +1 -0
  45. package/dist/commands/start.d.ts +8 -0
  46. package/dist/commands/start.js +27 -0
  47. package/dist/commands/start.js.map +1 -0
  48. package/dist/commands/status.d.ts +8 -0
  49. package/dist/commands/status.js +57 -0
  50. package/dist/commands/status.js.map +1 -0
  51. package/dist/commands/stop.d.ts +8 -0
  52. package/dist/commands/stop.js +37 -0
  53. package/dist/commands/stop.js.map +1 -0
  54. package/dist/commands/utils.d.ts +50 -0
  55. package/dist/commands/utils.js +36 -0
  56. package/dist/commands/utils.js.map +1 -0
  57. package/dist/discord-ZOJFTVTB.js +49 -0
  58. package/dist/discord-ZOJFTVTB.js.map +1 -0
  59. package/dist/imessage-JFRB6EJ7.js +14 -0
  60. package/dist/imessage-JFRB6EJ7.js.map +1 -0
  61. package/dist/lib.d.ts +855 -0
  62. package/dist/lib.js +274 -0
  63. package/dist/lib.js.map +1 -0
  64. package/dist/mcp-LS7Q3Z5W.js +30 -0
  65. package/dist/mcp-LS7Q3Z5W.js.map +1 -0
  66. package/dist/scheduler-EZ7CZMCS.js +42 -0
  67. package/dist/scheduler-EZ7CZMCS.js.map +1 -0
  68. package/dist/signal-T3MCSULM.js +14 -0
  69. package/dist/signal-T3MCSULM.js.map +1 -0
  70. package/dist/slack-N2M4FHAJ.js +54 -0
  71. package/dist/slack-N2M4FHAJ.js.map +1 -0
  72. package/dist/src-K7GASHRH.js +430 -0
  73. package/dist/src-K7GASHRH.js.map +1 -0
  74. package/dist/tools-24GZHYRF.js +16 -0
  75. package/dist/tools-24GZHYRF.js.map +1 -0
  76. package/dist/whatsapp-VCRUPAO5.js +14 -0
  77. package/dist/whatsapp-VCRUPAO5.js.map +1 -0
  78. package/drizzle/0000_chilly_shinobi_shaw.sql +75 -0
  79. package/drizzle/0001_freezing_shape.sql +274 -0
  80. package/drizzle/meta/0000_snapshot.json +529 -0
  81. package/drizzle/meta/0001_snapshot.json +2576 -0
  82. package/drizzle/meta/_journal.json +20 -0
  83. package/package.json +98 -0
@@ -0,0 +1,806 @@
1
+ import {
2
+ textToSpeech
3
+ } from "./chunk-F6QUZQGI.js";
4
+ import {
5
+ transcribeAudio
6
+ } from "./chunk-GVJVEWHI.js";
7
+ import {
8
+ scheduleReminder
9
+ } from "./chunk-4LVWXUNC.js";
10
+ import {
11
+ chatWithTools
12
+ } from "./chunk-CI6Q63MM.js";
13
+
14
+ // src/inputs/discord/index.ts
15
+ import {
16
+ Client,
17
+ GatewayIntentBits,
18
+ Events,
19
+ REST,
20
+ Routes,
21
+ Collection,
22
+ ChannelType,
23
+ AttachmentBuilder,
24
+ EmbedBuilder
25
+ } from "discord.js";
26
+ import {
27
+ joinVoiceChannel,
28
+ createAudioPlayer,
29
+ createAudioResource,
30
+ AudioPlayerStatus,
31
+ VoiceConnectionStatus,
32
+ entersState
33
+ } from "@discordjs/voice";
34
+ import { Readable } from "stream";
35
+
36
+ // src/inputs/discord/commands.ts
37
+ import {
38
+ SlashCommandBuilder
39
+ } from "discord.js";
40
+ var sessions = /* @__PURE__ */ new Map();
41
+ var MAX_HISTORY = 20;
42
+ function getSession(userId) {
43
+ if (!sessions.has(userId)) {
44
+ sessions.set(userId, []);
45
+ }
46
+ return sessions.get(userId);
47
+ }
48
+ function addToSession(userId, message) {
49
+ const session = getSession(userId);
50
+ session.push(message);
51
+ if (session.length > MAX_HISTORY) {
52
+ sessions.set(userId, session.slice(-MAX_HISTORY));
53
+ }
54
+ }
55
+ function clearSession(userId) {
56
+ sessions.set(userId, []);
57
+ }
58
+ var askCommand = {
59
+ data: new SlashCommandBuilder().setName("ask").setDescription("Ask OpenSentinel a question").addStringOption(
60
+ (option) => option.setName("question").setDescription("Your question for OpenSentinel").setRequired(true)
61
+ ).toJSON(),
62
+ async execute(interaction) {
63
+ const question = interaction.options.getString("question", true);
64
+ const userId = interaction.user.id;
65
+ await interaction.deferReply();
66
+ try {
67
+ addToSession(userId, { role: "user", content: question });
68
+ const response = await chatWithTools(
69
+ getSession(userId),
70
+ `discord:${userId}`
71
+ );
72
+ addToSession(userId, { role: "assistant", content: response.content });
73
+ let finalResponse = response.content;
74
+ if (response.toolsUsed && response.toolsUsed.length > 0) {
75
+ const toolList = [...new Set(response.toolsUsed)].join(", ");
76
+ finalResponse = `*Used: ${toolList}*
77
+
78
+ ${response.content}`;
79
+ }
80
+ if (finalResponse.length > 2e3) {
81
+ const chunks = splitMessage(finalResponse, 2e3);
82
+ await interaction.editReply(chunks[0]);
83
+ for (let i = 1; i < chunks.length; i++) {
84
+ await interaction.followUp(chunks[i]);
85
+ }
86
+ } else {
87
+ await interaction.editReply(finalResponse);
88
+ }
89
+ console.log(
90
+ `[Discord] Processed /ask from ${interaction.user.tag}. Tokens: ${response.inputTokens}/${response.outputTokens}`
91
+ );
92
+ } catch (error) {
93
+ console.error("[Discord] Error processing /ask:", error);
94
+ await interaction.editReply(
95
+ "Sorry, I encountered an error processing your question. Please try again."
96
+ );
97
+ }
98
+ }
99
+ };
100
+ var chatCommand = {
101
+ data: new SlashCommandBuilder().setName("chat").setDescription("Have a conversation with OpenSentinel").addStringOption(
102
+ (option) => option.setName("message").setDescription("Your message").setRequired(true)
103
+ ).toJSON(),
104
+ async execute(interaction) {
105
+ const message = interaction.options.getString("message", true);
106
+ const userId = interaction.user.id;
107
+ await interaction.deferReply();
108
+ try {
109
+ addToSession(userId, { role: "user", content: message });
110
+ const response = await chatWithTools(
111
+ getSession(userId),
112
+ `discord:${userId}`
113
+ );
114
+ addToSession(userId, { role: "assistant", content: response.content });
115
+ let finalResponse = response.content;
116
+ if (response.toolsUsed && response.toolsUsed.length > 0) {
117
+ const toolList = [...new Set(response.toolsUsed)].join(", ");
118
+ finalResponse = `*Used: ${toolList}*
119
+
120
+ ${response.content}`;
121
+ }
122
+ if (finalResponse.length > 2e3) {
123
+ const chunks = splitMessage(finalResponse, 2e3);
124
+ await interaction.editReply(chunks[0]);
125
+ for (let i = 1; i < chunks.length; i++) {
126
+ await interaction.followUp(chunks[i]);
127
+ }
128
+ } else {
129
+ await interaction.editReply(finalResponse);
130
+ }
131
+ console.log(
132
+ `[Discord] Processed /chat from ${interaction.user.tag}. Tokens: ${response.inputTokens}/${response.outputTokens}`
133
+ );
134
+ } catch (error) {
135
+ console.error("[Discord] Error processing /chat:", error);
136
+ await interaction.editReply(
137
+ "Sorry, I encountered an error processing your message. Please try again."
138
+ );
139
+ }
140
+ }
141
+ };
142
+ var clearCommand = {
143
+ data: new SlashCommandBuilder().setName("clear").setDescription("Clear your conversation history with OpenSentinel").toJSON(),
144
+ async execute(interaction) {
145
+ clearSession(interaction.user.id);
146
+ await interaction.reply({
147
+ content: "Conversation history cleared.",
148
+ ephemeral: true
149
+ });
150
+ console.log(`[Discord] Cleared history for ${interaction.user.tag}`);
151
+ }
152
+ };
153
+ var remindCommand = {
154
+ data: new SlashCommandBuilder().setName("remind").setDescription("Set a reminder").addIntegerOption(
155
+ (option) => option.setName("time").setDescription("Time amount").setRequired(true).setMinValue(1)
156
+ ).addStringOption(
157
+ (option) => option.setName("unit").setDescription("Time unit").setRequired(true).addChoices(
158
+ { name: "seconds", value: "s" },
159
+ { name: "minutes", value: "m" },
160
+ { name: "hours", value: "h" }
161
+ )
162
+ ).addStringOption(
163
+ (option) => option.setName("message").setDescription("Reminder message").setRequired(true)
164
+ ).toJSON(),
165
+ async execute(interaction) {
166
+ const time = interaction.options.getInteger("time", true);
167
+ const unit = interaction.options.getString("unit", true);
168
+ const message = interaction.options.getString("message", true);
169
+ const multipliers = {
170
+ s: 1e3,
171
+ m: 60 * 1e3,
172
+ h: 60 * 60 * 1e3
173
+ };
174
+ const delayMs = time * multipliers[unit];
175
+ try {
176
+ await scheduleReminder(
177
+ message,
178
+ delayMs,
179
+ `discord:${interaction.channelId}:${interaction.user.id}`
180
+ );
181
+ const timeStr = unit === "s" ? "seconds" : unit === "m" ? "minutes" : "hours";
182
+ await interaction.reply({
183
+ content: `Reminder set for ${time} ${timeStr}: "${message}"`,
184
+ ephemeral: true
185
+ });
186
+ console.log(
187
+ `[Discord] Reminder set by ${interaction.user.tag}: ${time}${unit} - "${message}"`
188
+ );
189
+ } catch (error) {
190
+ console.error("[Discord] Error setting reminder:", error);
191
+ await interaction.reply({
192
+ content: "Sorry, I couldn't set the reminder. Please try again.",
193
+ ephemeral: true
194
+ });
195
+ }
196
+ }
197
+ };
198
+ var statusCommand = {
199
+ data: new SlashCommandBuilder().setName("status").setDescription("Check OpenSentinel status and capabilities").toJSON(),
200
+ async execute(interaction) {
201
+ const session = getSession(interaction.user.id);
202
+ const historyCount = session.length;
203
+ await interaction.reply({
204
+ content: `**OpenSentinel Status**
205
+
206
+ Bot: Online
207
+ Your conversation history: ${historyCount} messages
208
+
209
+ **Capabilities:**
210
+ - Chat and answer questions using Claude AI
211
+ - Execute shell commands
212
+ - Read and write files
213
+ - Search the web
214
+ - Remember important information
215
+ - Set reminders
216
+ - Voice channel support (join, speak)
217
+
218
+ Use \`/help\` for available commands.`,
219
+ ephemeral: true
220
+ });
221
+ }
222
+ };
223
+ var helpCommand = {
224
+ data: new SlashCommandBuilder().setName("help").setDescription("Show available OpenSentinel commands").toJSON(),
225
+ async execute(interaction) {
226
+ await interaction.reply({
227
+ content: `**OpenSentinel Commands**
228
+
229
+ \`/ask <question>\` - Ask a single question
230
+ \`/chat <message>\` - Continue a conversation
231
+ \`/clear\` - Clear your conversation history
232
+ \`/remind <time> <unit> <message>\` - Set a reminder
233
+ \`/status\` - Check bot status
234
+ \`/voice join\` - Join your voice channel
235
+ \`/voice leave\` - Leave the voice channel
236
+ \`/voice speak <text>\` - Speak text in voice channel
237
+ \`/help\` - Show this help message
238
+
239
+ **Tips:**
240
+ - Use \`/chat\` for multi-turn conversations with context
241
+ - Use \`/ask\` for quick one-off questions
242
+ - DM me directly to chat without slash commands`,
243
+ ephemeral: true
244
+ });
245
+ }
246
+ };
247
+ var voiceCommand = {
248
+ data: new SlashCommandBuilder().setName("voice").setDescription("Voice channel operations").addSubcommand(
249
+ (subcommand) => subcommand.setName("join").setDescription("Join your current voice channel")
250
+ ).addSubcommand(
251
+ (subcommand) => subcommand.setName("leave").setDescription("Leave the voice channel")
252
+ ).addSubcommand(
253
+ (subcommand) => subcommand.setName("speak").setDescription("Speak text in the voice channel").addStringOption(
254
+ (option) => option.setName("text").setDescription("Text to speak").setRequired(true)
255
+ )
256
+ ).toJSON(),
257
+ async execute(interaction) {
258
+ const subcommand = interaction.options.getSubcommand();
259
+ await interaction.reply({
260
+ content: `Voice command received: ${subcommand}. Voice handling is managed by the Discord bot.`,
261
+ ephemeral: true
262
+ });
263
+ }
264
+ };
265
+ var slashCommands = [
266
+ askCommand,
267
+ chatCommand,
268
+ clearCommand,
269
+ remindCommand,
270
+ statusCommand,
271
+ helpCommand,
272
+ voiceCommand
273
+ ];
274
+ function getCommandData() {
275
+ return slashCommands.map((cmd) => cmd.data);
276
+ }
277
+ function getCommand(name) {
278
+ return slashCommands.find(
279
+ (cmd) => cmd.data.name === name
280
+ );
281
+ }
282
+ function splitMessage(text, maxLength) {
283
+ const chunks = [];
284
+ let remaining = text;
285
+ while (remaining.length > 0) {
286
+ if (remaining.length <= maxLength) {
287
+ chunks.push(remaining);
288
+ break;
289
+ }
290
+ let breakPoint = remaining.lastIndexOf("\n", maxLength);
291
+ if (breakPoint === -1 || breakPoint < maxLength / 2) {
292
+ breakPoint = remaining.lastIndexOf(" ", maxLength);
293
+ }
294
+ if (breakPoint === -1 || breakPoint < maxLength / 2) {
295
+ breakPoint = maxLength;
296
+ }
297
+ chunks.push(remaining.slice(0, breakPoint));
298
+ remaining = remaining.slice(breakPoint).trim();
299
+ }
300
+ return chunks;
301
+ }
302
+
303
+ // src/inputs/discord/index.ts
304
+ var DiscordBot = class {
305
+ client;
306
+ config;
307
+ rest;
308
+ voiceConnections = /* @__PURE__ */ new Map();
309
+ commandCollection;
310
+ isReady = false;
311
+ constructor(config) {
312
+ this.config = config;
313
+ this.client = new Client({
314
+ intents: [
315
+ GatewayIntentBits.Guilds,
316
+ GatewayIntentBits.GuildMessages,
317
+ GatewayIntentBits.DirectMessages,
318
+ GatewayIntentBits.GuildVoiceStates,
319
+ GatewayIntentBits.MessageContent
320
+ // Privileged - must be enabled in Developer Portal
321
+ // GatewayIntentBits.GuildMembers, // Optional - enable for role-based authorization
322
+ ]
323
+ });
324
+ this.rest = new REST({ version: "10" }).setToken(config.token);
325
+ this.commandCollection = new Collection();
326
+ for (const command of slashCommands) {
327
+ this.commandCollection.set(command.data.name, command);
328
+ }
329
+ this.setupEventHandlers();
330
+ }
331
+ /**
332
+ * Set up Discord event handlers
333
+ */
334
+ setupEventHandlers() {
335
+ this.client.once(Events.ClientReady, (readyClient) => {
336
+ this.isReady = true;
337
+ console.log(`[Discord] Bot ready as ${readyClient.user.tag}`);
338
+ console.log(
339
+ `[Discord] Connected to ${readyClient.guilds.cache.size} guild(s)`
340
+ );
341
+ });
342
+ this.client.on(Events.InteractionCreate, async (interaction) => {
343
+ await this.handleInteraction(interaction);
344
+ });
345
+ this.client.on(Events.MessageCreate, async (message) => {
346
+ await this.handleMessage(message);
347
+ });
348
+ this.client.on(Events.VoiceStateUpdate, async (oldState, newState) => {
349
+ await this.handleVoiceStateUpdate(oldState, newState);
350
+ });
351
+ this.client.on(Events.Error, (error) => {
352
+ console.error("[Discord] Client error:", error);
353
+ });
354
+ this.client.on(Events.Warn, (warning) => {
355
+ console.warn("[Discord] Warning:", warning);
356
+ });
357
+ }
358
+ /**
359
+ * Handle slash command interactions
360
+ */
361
+ async handleInteraction(interaction) {
362
+ if (!interaction.isChatInputCommand()) return;
363
+ if (!this.isUserAuthorized(interaction.user.id, interaction.member)) {
364
+ await interaction.reply({
365
+ content: "You are not authorized to use this bot.",
366
+ ephemeral: true
367
+ });
368
+ return;
369
+ }
370
+ const command = this.commandCollection.get(interaction.commandName);
371
+ if (!command) {
372
+ console.warn(`[Discord] Unknown command: ${interaction.commandName}`);
373
+ return;
374
+ }
375
+ if (interaction.commandName === "voice") {
376
+ await this.handleVoiceCommand(interaction);
377
+ return;
378
+ }
379
+ try {
380
+ await command.execute(interaction);
381
+ } catch (error) {
382
+ console.error(
383
+ `[Discord] Error executing command ${interaction.commandName}:`,
384
+ error
385
+ );
386
+ const errorMessage = "There was an error executing this command.";
387
+ if (interaction.replied || interaction.deferred) {
388
+ await interaction.followUp({ content: errorMessage, ephemeral: true });
389
+ } else {
390
+ await interaction.reply({ content: errorMessage, ephemeral: true });
391
+ }
392
+ }
393
+ }
394
+ /**
395
+ * Handle voice slash commands
396
+ */
397
+ async handleVoiceCommand(interaction) {
398
+ if (!interaction.isChatInputCommand()) return;
399
+ const subcommand = interaction.options.getSubcommand();
400
+ switch (subcommand) {
401
+ case "join":
402
+ await this.joinVoiceChannel(interaction);
403
+ break;
404
+ case "leave":
405
+ await this.leaveVoiceChannel(interaction);
406
+ break;
407
+ case "speak":
408
+ await this.speakInVoiceChannel(interaction);
409
+ break;
410
+ default:
411
+ await interaction.reply({
412
+ content: "Unknown voice subcommand.",
413
+ ephemeral: true
414
+ });
415
+ }
416
+ }
417
+ /**
418
+ * Join a voice channel
419
+ */
420
+ async joinVoiceChannel(interaction) {
421
+ if (!interaction.isChatInputCommand() || !interaction.guild) {
422
+ return;
423
+ }
424
+ const member = interaction.member;
425
+ const voiceChannel = member.voice.channel;
426
+ if (!voiceChannel) {
427
+ await interaction.reply({
428
+ content: "You need to be in a voice channel for me to join.",
429
+ ephemeral: true
430
+ });
431
+ return;
432
+ }
433
+ try {
434
+ const connection = joinVoiceChannel({
435
+ channelId: voiceChannel.id,
436
+ guildId: interaction.guild.id,
437
+ adapterCreator: interaction.guild.voiceAdapterCreator
438
+ });
439
+ const player = createAudioPlayer();
440
+ connection.subscribe(player);
441
+ await entersState(connection, VoiceConnectionStatus.Ready, 3e4);
442
+ this.voiceConnections.set(interaction.guild.id, {
443
+ connection,
444
+ player,
445
+ guildId: interaction.guild.id,
446
+ channelId: voiceChannel.id
447
+ });
448
+ await interaction.reply({
449
+ content: `Joined ${voiceChannel.name}!`,
450
+ ephemeral: true
451
+ });
452
+ console.log(
453
+ `[Discord] Joined voice channel: ${voiceChannel.name} in ${interaction.guild.name}`
454
+ );
455
+ } catch (error) {
456
+ console.error("[Discord] Error joining voice channel:", error);
457
+ await interaction.reply({
458
+ content: "Failed to join the voice channel. Please try again.",
459
+ ephemeral: true
460
+ });
461
+ }
462
+ }
463
+ /**
464
+ * Leave a voice channel
465
+ */
466
+ async leaveVoiceChannel(interaction) {
467
+ if (!interaction.isChatInputCommand() || !interaction.guild) {
468
+ return;
469
+ }
470
+ const voiceState = this.voiceConnections.get(interaction.guild.id);
471
+ if (!voiceState) {
472
+ await interaction.reply({
473
+ content: "I'm not in a voice channel.",
474
+ ephemeral: true
475
+ });
476
+ return;
477
+ }
478
+ try {
479
+ voiceState.player.stop();
480
+ voiceState.connection.destroy();
481
+ this.voiceConnections.delete(interaction.guild.id);
482
+ await interaction.reply({
483
+ content: "Left the voice channel.",
484
+ ephemeral: true
485
+ });
486
+ console.log(
487
+ `[Discord] Left voice channel in ${interaction.guild.name}`
488
+ );
489
+ } catch (error) {
490
+ console.error("[Discord] Error leaving voice channel:", error);
491
+ await interaction.reply({
492
+ content: "Failed to leave the voice channel.",
493
+ ephemeral: true
494
+ });
495
+ }
496
+ }
497
+ /**
498
+ * Speak text in a voice channel using TTS
499
+ */
500
+ async speakInVoiceChannel(interaction) {
501
+ if (!interaction.isChatInputCommand() || !interaction.guild) {
502
+ return;
503
+ }
504
+ const voiceState = this.voiceConnections.get(interaction.guild.id);
505
+ if (!voiceState) {
506
+ await interaction.reply({
507
+ content: "I need to be in a voice channel first. Use `/voice join`.",
508
+ ephemeral: true
509
+ });
510
+ return;
511
+ }
512
+ const text = interaction.options.getString("text", true);
513
+ await interaction.deferReply({ ephemeral: true });
514
+ try {
515
+ const audioBuffer = await textToSpeech(text);
516
+ if (!audioBuffer) {
517
+ await interaction.editReply("Failed to generate speech. TTS service may be unavailable.");
518
+ return;
519
+ }
520
+ const stream = Readable.from(audioBuffer);
521
+ const resource = createAudioResource(stream);
522
+ voiceState.player.play(resource);
523
+ await new Promise((resolve) => {
524
+ voiceState.player.once(AudioPlayerStatus.Idle, () => resolve());
525
+ });
526
+ await interaction.editReply(`Spoke: "${text}"`);
527
+ console.log(
528
+ `[Discord] Spoke in voice channel: "${text.substring(0, 50)}..."`
529
+ );
530
+ } catch (error) {
531
+ console.error("[Discord] Error speaking in voice channel:", error);
532
+ await interaction.editReply(
533
+ "Failed to speak in the voice channel. Please try again."
534
+ );
535
+ }
536
+ }
537
+ /**
538
+ * Handle incoming messages (DMs and @mentions)
539
+ */
540
+ async handleMessage(message) {
541
+ if (message.author.bot) return;
542
+ const isDM = message.channel.type === ChannelType.DM;
543
+ const isMentioned = message.mentions.has(this.client.user.id);
544
+ if (!isDM && !isMentioned) return;
545
+ if (isDM && !this.config.allowDMs) return;
546
+ if (!isDM && !this.config.allowChannels) return;
547
+ if (!this.isUserAuthorized(message.author.id, message.member)) return;
548
+ let content = message.content;
549
+ if (isMentioned) {
550
+ content = content.replace(/<@!?\d+>/g, "").trim();
551
+ }
552
+ if (!content && message.attachments.size === 0) return;
553
+ const userId = message.author.id;
554
+ try {
555
+ if ("sendTyping" in message.channel) {
556
+ await message.channel.sendTyping();
557
+ }
558
+ let processedContent = content;
559
+ if (message.attachments.size > 0) {
560
+ const attachmentDescriptions = await this.processAttachments(message);
561
+ if (attachmentDescriptions.length > 0) {
562
+ processedContent += "\n\n[Attachments:\n" + attachmentDescriptions.join("\n") + "]";
563
+ }
564
+ }
565
+ addToSession(userId, { role: "user", content: processedContent });
566
+ const response = await chatWithTools(
567
+ getSession(userId),
568
+ `discord:${userId}`,
569
+ async () => {
570
+ if ("sendTyping" in message.channel) {
571
+ await message.channel.sendTyping();
572
+ }
573
+ }
574
+ );
575
+ addToSession(userId, { role: "assistant", content: response.content });
576
+ let finalResponse = response.content;
577
+ if (response.toolsUsed && response.toolsUsed.length > 0) {
578
+ const toolList = [...new Set(response.toolsUsed)].join(", ");
579
+ finalResponse = `*Used: ${toolList}*
580
+
581
+ ${response.content}`;
582
+ }
583
+ if (finalResponse.length > 2e3) {
584
+ const chunks = splitMessage(finalResponse, 2e3);
585
+ for (const chunk of chunks) {
586
+ await message.reply(chunk);
587
+ }
588
+ } else {
589
+ await message.reply(finalResponse);
590
+ }
591
+ console.log(
592
+ `[Discord] Processed message from ${message.author.tag}. Tokens: ${response.inputTokens}/${response.outputTokens}` + (response.toolsUsed ? ` Tools: ${response.toolsUsed.join(", ")}` : "")
593
+ );
594
+ } catch (error) {
595
+ console.error("[Discord] Error processing message:", error);
596
+ await message.reply(
597
+ "Sorry, I encountered an error processing your message. Please try again."
598
+ );
599
+ }
600
+ }
601
+ /**
602
+ * Process message attachments
603
+ */
604
+ async processAttachments(message) {
605
+ const descriptions = [];
606
+ for (const [, attachment] of message.attachments) {
607
+ const contentType = attachment.contentType || "";
608
+ const fileName = attachment.name || "unknown";
609
+ if (contentType.startsWith("audio/")) {
610
+ try {
611
+ const response = await fetch(attachment.url);
612
+ const audioBuffer = await response.arrayBuffer();
613
+ const transcription = await transcribeAudio(Buffer.from(audioBuffer));
614
+ if (transcription) {
615
+ descriptions.push(`Audio transcription (${fileName}): "${transcription}"`);
616
+ } else {
617
+ descriptions.push(`Audio file: ${fileName} (could not transcribe)`);
618
+ }
619
+ } catch (error) {
620
+ console.error("[Discord] Error processing audio attachment:", error);
621
+ descriptions.push(`Audio file: ${fileName} (error processing)`);
622
+ }
623
+ } else if (contentType.startsWith("text/")) {
624
+ try {
625
+ const response = await fetch(attachment.url);
626
+ const text = await response.text();
627
+ const preview = text.length > 1e3 ? text.substring(0, 1e3) + "..." : text;
628
+ descriptions.push(`Text file (${fileName}):
629
+ ${preview}`);
630
+ } catch (error) {
631
+ descriptions.push(`Text file: ${fileName} (could not read)`);
632
+ }
633
+ } else {
634
+ descriptions.push(
635
+ `File: ${fileName} (${contentType || "unknown type"}, ${attachment.size} bytes)`
636
+ );
637
+ }
638
+ }
639
+ return descriptions;
640
+ }
641
+ /**
642
+ * Handle voice state updates
643
+ */
644
+ async handleVoiceStateUpdate(oldState, newState) {
645
+ if (oldState.member?.id === this.client.user?.id && !newState.channelId && oldState.channelId) {
646
+ const guildId = oldState.guild?.id || oldState.guildId;
647
+ const voiceState = this.voiceConnections.get(guildId);
648
+ if (voiceState) {
649
+ voiceState.player.stop();
650
+ this.voiceConnections.delete(guildId);
651
+ console.log(`[Discord] Bot disconnected from voice in ${oldState.guild?.name || guildId}`);
652
+ }
653
+ }
654
+ }
655
+ /**
656
+ * Check if a user is authorized to use the bot
657
+ */
658
+ isUserAuthorized(userId, member) {
659
+ if (!this.config.allowedUserIds?.length && !this.config.allowedRoleIds?.length) {
660
+ return true;
661
+ }
662
+ if (this.config.allowedUserIds?.includes(userId)) {
663
+ return true;
664
+ }
665
+ if (member && this.config.allowedRoleIds?.length) {
666
+ try {
667
+ for (const roleId of this.config.allowedRoleIds) {
668
+ if (member.roles?.cache?.has(roleId)) {
669
+ return true;
670
+ }
671
+ }
672
+ } catch {
673
+ }
674
+ }
675
+ return false;
676
+ }
677
+ /**
678
+ * Register slash commands with Discord
679
+ */
680
+ async registerCommands() {
681
+ const commands = getCommandData();
682
+ try {
683
+ console.log("[Discord] Registering slash commands...");
684
+ if (this.config.guildId) {
685
+ await this.rest.put(
686
+ Routes.applicationGuildCommands(
687
+ this.config.clientId,
688
+ this.config.guildId
689
+ ),
690
+ { body: commands }
691
+ );
692
+ console.log(
693
+ `[Discord] Registered ${commands.length} guild commands for guild ${this.config.guildId}`
694
+ );
695
+ } else {
696
+ await this.rest.put(Routes.applicationCommands(this.config.clientId), {
697
+ body: commands
698
+ });
699
+ console.log(
700
+ `[Discord] Registered ${commands.length} global commands`
701
+ );
702
+ }
703
+ } catch (error) {
704
+ console.error("[Discord] Error registering commands:", error);
705
+ throw error;
706
+ }
707
+ }
708
+ /**
709
+ * Start the Discord bot
710
+ */
711
+ async start() {
712
+ console.log("[Discord] Starting bot...");
713
+ await this.registerCommands();
714
+ await this.client.login(this.config.token);
715
+ }
716
+ /**
717
+ * Stop the Discord bot
718
+ */
719
+ async stop() {
720
+ console.log("[Discord] Stopping bot...");
721
+ for (const [guildId, voiceState] of this.voiceConnections) {
722
+ voiceState.player.stop();
723
+ voiceState.connection.destroy();
724
+ this.voiceConnections.delete(guildId);
725
+ }
726
+ this.client.destroy();
727
+ this.isReady = false;
728
+ console.log("[Discord] Bot stopped");
729
+ }
730
+ /**
731
+ * Get the Discord client
732
+ */
733
+ getClient() {
734
+ return this.client;
735
+ }
736
+ /**
737
+ * Check if bot is ready
738
+ */
739
+ ready() {
740
+ return this.isReady;
741
+ }
742
+ /**
743
+ * Send a message to a channel
744
+ */
745
+ async sendMessage(channelId, content) {
746
+ const channel = await this.client.channels.fetch(channelId);
747
+ if (channel?.isTextBased() && "send" in channel) {
748
+ await channel.send(content);
749
+ }
750
+ }
751
+ /**
752
+ * Send a message with an embed
753
+ */
754
+ async sendEmbed(channelId, title, description, color) {
755
+ const channel = await this.client.channels.fetch(channelId);
756
+ if (channel?.isTextBased() && "send" in channel) {
757
+ const embed = new EmbedBuilder().setTitle(title).setDescription(description).setColor(color || 5793266).setTimestamp();
758
+ await channel.send({ embeds: [embed] });
759
+ }
760
+ }
761
+ /**
762
+ * Send a file to a channel
763
+ */
764
+ async sendFile(channelId, buffer, filename, content) {
765
+ const channel = await this.client.channels.fetch(channelId);
766
+ if (channel?.isTextBased() && "send" in channel) {
767
+ const attachment = new AttachmentBuilder(buffer, { name: filename });
768
+ await channel.send({
769
+ content,
770
+ files: [attachment]
771
+ });
772
+ }
773
+ }
774
+ };
775
+ function createDiscordBot(config) {
776
+ return new DiscordBot(config);
777
+ }
778
+ var discord_default = {
779
+ createDiscordBot,
780
+ DiscordBot,
781
+ slashCommands,
782
+ getCommandData,
783
+ getCommand
784
+ };
785
+
786
+ export {
787
+ sessions,
788
+ getSession,
789
+ addToSession,
790
+ clearSession,
791
+ askCommand,
792
+ chatCommand,
793
+ clearCommand,
794
+ remindCommand,
795
+ statusCommand,
796
+ helpCommand,
797
+ voiceCommand,
798
+ slashCommands,
799
+ getCommandData,
800
+ getCommand,
801
+ splitMessage,
802
+ DiscordBot,
803
+ createDiscordBot,
804
+ discord_default
805
+ };
806
+ //# sourceMappingURL=chunk-HH2HBTQM.js.map