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.
- package/LICENSE +21 -0
- package/README.md +283 -0
- package/dist/bot-KJ26BG56.js +15 -0
- package/dist/bot-KJ26BG56.js.map +1 -0
- package/dist/charts-MMXM6BWW.js +241 -0
- package/dist/charts-MMXM6BWW.js.map +1 -0
- package/dist/chunk-4LVWXUNC.js +1079 -0
- package/dist/chunk-4LVWXUNC.js.map +1 -0
- package/dist/chunk-4TG2IG5K.js +5249 -0
- package/dist/chunk-4TG2IG5K.js.map +1 -0
- package/dist/chunk-6DRDKB45.js +251 -0
- package/dist/chunk-6DRDKB45.js.map +1 -0
- package/dist/chunk-6SNHU3CY.js +123 -0
- package/dist/chunk-6SNHU3CY.js.map +1 -0
- package/dist/chunk-CI6Q63MM.js +1613 -0
- package/dist/chunk-CI6Q63MM.js.map +1 -0
- package/dist/chunk-CQ4JURG7.js +57 -0
- package/dist/chunk-CQ4JURG7.js.map +1 -0
- package/dist/chunk-F6QUZQGI.js +51 -0
- package/dist/chunk-F6QUZQGI.js.map +1 -0
- package/dist/chunk-GK3E2I7A.js +216 -0
- package/dist/chunk-GK3E2I7A.js.map +1 -0
- package/dist/chunk-GUBEEYDW.js +211 -0
- package/dist/chunk-GUBEEYDW.js.map +1 -0
- package/dist/chunk-GVJVEWHI.js +29 -0
- package/dist/chunk-GVJVEWHI.js.map +1 -0
- package/dist/chunk-HH2HBTQM.js +806 -0
- package/dist/chunk-HH2HBTQM.js.map +1 -0
- package/dist/chunk-JXUP2X7V.js +129 -0
- package/dist/chunk-JXUP2X7V.js.map +1 -0
- package/dist/chunk-KHNYJY2Z.js +178 -0
- package/dist/chunk-KHNYJY2Z.js.map +1 -0
- package/dist/chunk-L3F43VPB.js +652 -0
- package/dist/chunk-L3F43VPB.js.map +1 -0
- package/dist/chunk-L3PDU3XN.js +803 -0
- package/dist/chunk-L3PDU3XN.js.map +1 -0
- package/dist/chunk-NSBPE2FW.js +17 -0
- package/dist/chunk-NSBPE2FW.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +52 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/setup.d.ts +9 -0
- package/dist/commands/setup.js +374 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/start.d.ts +8 -0
- package/dist/commands/start.js +27 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/status.d.ts +8 -0
- package/dist/commands/status.js +57 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/stop.d.ts +8 -0
- package/dist/commands/stop.js +37 -0
- package/dist/commands/stop.js.map +1 -0
- package/dist/commands/utils.d.ts +50 -0
- package/dist/commands/utils.js +36 -0
- package/dist/commands/utils.js.map +1 -0
- package/dist/discord-ZOJFTVTB.js +49 -0
- package/dist/discord-ZOJFTVTB.js.map +1 -0
- package/dist/imessage-JFRB6EJ7.js +14 -0
- package/dist/imessage-JFRB6EJ7.js.map +1 -0
- package/dist/lib.d.ts +855 -0
- package/dist/lib.js +274 -0
- package/dist/lib.js.map +1 -0
- package/dist/mcp-LS7Q3Z5W.js +30 -0
- package/dist/mcp-LS7Q3Z5W.js.map +1 -0
- package/dist/scheduler-EZ7CZMCS.js +42 -0
- package/dist/scheduler-EZ7CZMCS.js.map +1 -0
- package/dist/signal-T3MCSULM.js +14 -0
- package/dist/signal-T3MCSULM.js.map +1 -0
- package/dist/slack-N2M4FHAJ.js +54 -0
- package/dist/slack-N2M4FHAJ.js.map +1 -0
- package/dist/src-K7GASHRH.js +430 -0
- package/dist/src-K7GASHRH.js.map +1 -0
- package/dist/tools-24GZHYRF.js +16 -0
- package/dist/tools-24GZHYRF.js.map +1 -0
- package/dist/whatsapp-VCRUPAO5.js +14 -0
- package/dist/whatsapp-VCRUPAO5.js.map +1 -0
- package/drizzle/0000_chilly_shinobi_shaw.sql +75 -0
- package/drizzle/0001_freezing_shape.sql +274 -0
- package/drizzle/meta/0000_snapshot.json +529 -0
- package/drizzle/meta/0001_snapshot.json +2576 -0
- package/drizzle/meta/_journal.json +20 -0
- 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
|