@elizaos/plugin-discord 1.0.0-alpha.4 → 1.0.0-alpha.41
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/dist/index.js +1672 -1159
- package/dist/index.js.map +1 -1
- package/package.json +13 -14
- package/dist/index.d.ts +0 -91
package/dist/index.js
CHANGED
|
@@ -1,29 +1,17 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
createUniqueUuid as createUniqueUuid6,
|
|
5
|
-
logger as logger7,
|
|
6
|
-
Role,
|
|
7
|
-
Service
|
|
3
|
+
logger as logger8
|
|
8
4
|
} from "@elizaos/core";
|
|
9
|
-
import {
|
|
10
|
-
ChannelType as DiscordChannelType4,
|
|
11
|
-
Client as DiscordJsClient,
|
|
12
|
-
Events as Events2,
|
|
13
|
-
GatewayIntentBits,
|
|
14
|
-
Partials,
|
|
15
|
-
PermissionsBitField as PermissionsBitField2
|
|
16
|
-
} from "discord.js";
|
|
17
5
|
|
|
18
6
|
// src/actions/chatWithAttachments.ts
|
|
7
|
+
import fs from "node:fs";
|
|
19
8
|
import {
|
|
20
9
|
ChannelType,
|
|
21
|
-
composePrompt,
|
|
22
10
|
ModelTypes,
|
|
11
|
+
composePromptFromState,
|
|
23
12
|
parseJSONObjectFromText,
|
|
24
13
|
trimTokens
|
|
25
14
|
} from "@elizaos/core";
|
|
26
|
-
import * as fs from "node:fs";
|
|
27
15
|
var summarizationTemplate = `# Summarized so far (we are adding to this)
|
|
28
16
|
{{currentSummary}}
|
|
29
17
|
|
|
@@ -49,7 +37,7 @@ Your response must be formatted as a JSON block with this structure:
|
|
|
49
37
|
\`\`\`
|
|
50
38
|
`;
|
|
51
39
|
var getAttachmentIds = async (runtime, _message, state) => {
|
|
52
|
-
const prompt =
|
|
40
|
+
const prompt = composePromptFromState({
|
|
53
41
|
state,
|
|
54
42
|
template: attachmentIdsTemplate
|
|
55
43
|
});
|
|
@@ -65,7 +53,7 @@ var getAttachmentIds = async (runtime, _message, state) => {
|
|
|
65
53
|
}
|
|
66
54
|
return null;
|
|
67
55
|
};
|
|
68
|
-
var
|
|
56
|
+
var chatWithAttachments = {
|
|
69
57
|
name: "CHAT_WITH_ATTACHMENTS",
|
|
70
58
|
similes: [
|
|
71
59
|
"CHAT_WITH_ATTACHMENT",
|
|
@@ -86,7 +74,7 @@ var summarizeAction = {
|
|
|
86
74
|
],
|
|
87
75
|
description: "Answer a user request informed by specific attachments based on their IDs. If a user asks to chat with a PDF, or wants more specific information about a link or video or anything else they've attached, this is the action to use.",
|
|
88
76
|
validate: async (_runtime, message, _state) => {
|
|
89
|
-
const room = await _runtime.
|
|
77
|
+
const room = await _runtime.getRoom(message.roomId);
|
|
90
78
|
if (room?.type !== ChannelType.GROUP) {
|
|
91
79
|
return false;
|
|
92
80
|
}
|
|
@@ -166,7 +154,7 @@ ${attachment.text}`).join("\n\n");
|
|
|
166
154
|
chunkSize,
|
|
167
155
|
runtime
|
|
168
156
|
);
|
|
169
|
-
const prompt =
|
|
157
|
+
const prompt = composePromptFromState({
|
|
170
158
|
state,
|
|
171
159
|
// make sure it fits, we can pad the tokens a bit
|
|
172
160
|
// Get the model's tokenizer based on the current model being used
|
|
@@ -213,7 +201,7 @@ ${currentSummary.trim()}
|
|
|
213
201
|
});
|
|
214
202
|
await fs.promises.writeFile(summaryFilename, currentSummary, "utf8");
|
|
215
203
|
console.log("File written successfully");
|
|
216
|
-
await runtime.
|
|
204
|
+
await runtime.setCache(summaryFilename, currentSummary);
|
|
217
205
|
console.log("Cache set operation completed");
|
|
218
206
|
await callback(
|
|
219
207
|
{
|
|
@@ -297,14 +285,14 @@ ${currentSummary.trim()}
|
|
|
297
285
|
]
|
|
298
286
|
]
|
|
299
287
|
};
|
|
300
|
-
var chatWithAttachments_default =
|
|
288
|
+
var chatWithAttachments_default = chatWithAttachments;
|
|
301
289
|
|
|
302
290
|
// src/actions/downloadMedia.ts
|
|
303
291
|
import {
|
|
304
|
-
composePrompt as composePrompt2,
|
|
305
292
|
ModelTypes as ModelTypes2,
|
|
306
|
-
|
|
307
|
-
|
|
293
|
+
ServiceTypes,
|
|
294
|
+
composePromptFromState as composePromptFromState2,
|
|
295
|
+
parseJSONObjectFromText as parseJSONObjectFromText2
|
|
308
296
|
} from "@elizaos/core";
|
|
309
297
|
var mediaUrlTemplate = `# Messages we are searching for a media URL
|
|
310
298
|
{{recentMessages}}
|
|
@@ -320,7 +308,7 @@ Your response must be formatted as a JSON block with this structure:
|
|
|
320
308
|
\`\`\`
|
|
321
309
|
`;
|
|
322
310
|
var getMediaUrl = async (runtime, _message, state) => {
|
|
323
|
-
const prompt =
|
|
311
|
+
const prompt = composePromptFromState2({
|
|
324
312
|
state,
|
|
325
313
|
template: mediaUrlTemplate
|
|
326
314
|
});
|
|
@@ -335,7 +323,7 @@ var getMediaUrl = async (runtime, _message, state) => {
|
|
|
335
323
|
}
|
|
336
324
|
return null;
|
|
337
325
|
};
|
|
338
|
-
var
|
|
326
|
+
var downloadMedia = {
|
|
339
327
|
name: "DOWNLOAD_MEDIA",
|
|
340
328
|
similes: [
|
|
341
329
|
"DOWNLOAD_VIDEO",
|
|
@@ -453,15 +441,15 @@ var downloadMedia_default = {
|
|
|
453
441
|
};
|
|
454
442
|
|
|
455
443
|
// src/actions/summarizeConversation.ts
|
|
444
|
+
import fs2 from "node:fs";
|
|
456
445
|
import {
|
|
457
|
-
composePrompt as composePrompt3,
|
|
458
|
-
getEntityDetails,
|
|
459
446
|
ModelTypes as ModelTypes3,
|
|
447
|
+
composePromptFromState as composePromptFromState3,
|
|
448
|
+
getEntityDetails,
|
|
460
449
|
parseJSONObjectFromText as parseJSONObjectFromText3,
|
|
461
450
|
splitChunks,
|
|
462
451
|
trimTokens as trimTokens2
|
|
463
452
|
} from "@elizaos/core";
|
|
464
|
-
import * as fs2 from "node:fs";
|
|
465
453
|
var summarizationTemplate2 = `# Summarized so far (we are adding to this)
|
|
466
454
|
{{currentSummary}}
|
|
467
455
|
|
|
@@ -490,7 +478,7 @@ Your response must be formatted as a JSON block with this structure:
|
|
|
490
478
|
\`\`\`
|
|
491
479
|
`;
|
|
492
480
|
var getDateRange = async (runtime, _message, state) => {
|
|
493
|
-
const prompt =
|
|
481
|
+
const prompt = composePromptFromState3({
|
|
494
482
|
state,
|
|
495
483
|
template: dateRangeTemplate
|
|
496
484
|
});
|
|
@@ -533,7 +521,7 @@ var getDateRange = async (runtime, _message, state) => {
|
|
|
533
521
|
}
|
|
534
522
|
}
|
|
535
523
|
};
|
|
536
|
-
var
|
|
524
|
+
var summarize = {
|
|
537
525
|
name: "SUMMARIZE_CONVERSATION",
|
|
538
526
|
similes: [
|
|
539
527
|
"RECAP",
|
|
@@ -656,7 +644,7 @@ ${attachments}`;
|
|
|
656
644
|
chunkSize + 500,
|
|
657
645
|
runtime
|
|
658
646
|
);
|
|
659
|
-
const prompt =
|
|
647
|
+
const prompt = composePromptFromState3({
|
|
660
648
|
state,
|
|
661
649
|
// make sure it fits, we can pad the tokens a bit
|
|
662
650
|
template
|
|
@@ -695,7 +683,7 @@ ${currentSummary.trim()}
|
|
|
695
683
|
} else if (currentSummary.trim()) {
|
|
696
684
|
const summaryDir = "cache";
|
|
697
685
|
const summaryFilename = `${summaryDir}/conversation_summary_${Date.now()}`;
|
|
698
|
-
await runtime.
|
|
686
|
+
await runtime.setCache(summaryFilename, currentSummary);
|
|
699
687
|
await fs2.promises.mkdir(summaryDir, { recursive: true });
|
|
700
688
|
await fs2.promises.writeFile(summaryFilename, currentSummary, "utf8");
|
|
701
689
|
await callback(
|
|
@@ -781,12 +769,11 @@ ${currentSummary.trim()}
|
|
|
781
769
|
]
|
|
782
770
|
]
|
|
783
771
|
};
|
|
784
|
-
var summarizeConversation_default = summarizeAction2;
|
|
785
772
|
|
|
786
773
|
// src/actions/transcribeMedia.ts
|
|
787
774
|
import {
|
|
788
|
-
composePrompt as composePrompt4,
|
|
789
775
|
ModelTypes as ModelTypes4,
|
|
776
|
+
composePromptFromState as composePromptFromState4,
|
|
790
777
|
parseJSONObjectFromText as parseJSONObjectFromText4
|
|
791
778
|
} from "@elizaos/core";
|
|
792
779
|
var mediaAttachmentIdTemplate = `# Messages we are transcribing
|
|
@@ -803,7 +790,7 @@ Your response must be formatted as a JSON block with this structure:
|
|
|
803
790
|
\`\`\`
|
|
804
791
|
`;
|
|
805
792
|
var getMediaAttachmentId = async (runtime, _message, state) => {
|
|
806
|
-
const prompt =
|
|
793
|
+
const prompt = composePromptFromState4({
|
|
807
794
|
state,
|
|
808
795
|
template: mediaAttachmentIdTemplate
|
|
809
796
|
});
|
|
@@ -819,7 +806,7 @@ var getMediaAttachmentId = async (runtime, _message, state) => {
|
|
|
819
806
|
}
|
|
820
807
|
return null;
|
|
821
808
|
};
|
|
822
|
-
var
|
|
809
|
+
var transcribeMedia = {
|
|
823
810
|
name: "TRANSCRIBE_MEDIA",
|
|
824
811
|
similes: [
|
|
825
812
|
"TRANSCRIBE_AUDIO",
|
|
@@ -913,7 +900,7 @@ ${mediaTranscript.trim()}
|
|
|
913
900
|
await callback(callbackData);
|
|
914
901
|
} else if (callbackData.text) {
|
|
915
902
|
const transcriptFilename = `content/transcript_${Date.now()}`;
|
|
916
|
-
await runtime.
|
|
903
|
+
await runtime.setCache(transcriptFilename, callbackData.text);
|
|
917
904
|
await callback(
|
|
918
905
|
{
|
|
919
906
|
...callbackData,
|
|
@@ -959,15 +946,14 @@ ${mediaTranscript.trim()}
|
|
|
959
946
|
]
|
|
960
947
|
]
|
|
961
948
|
};
|
|
962
|
-
var transcribeMedia_default = transcribeMediaAction;
|
|
963
949
|
|
|
964
950
|
// src/actions/voiceJoin.ts
|
|
965
951
|
import {
|
|
966
952
|
ChannelType as ChannelType2,
|
|
967
|
-
|
|
953
|
+
ModelTypes as ModelTypes5,
|
|
954
|
+
composePromptFromState as composePromptFromState5,
|
|
968
955
|
createUniqueUuid as createUniqueUuid2,
|
|
969
|
-
logger
|
|
970
|
-
ModelTypes as ModelTypes5
|
|
956
|
+
logger
|
|
971
957
|
} from "@elizaos/core";
|
|
972
958
|
import {
|
|
973
959
|
ChannelType as DiscordChannelType
|
|
@@ -979,7 +965,7 @@ var ServiceTypes2 = {
|
|
|
979
965
|
};
|
|
980
966
|
|
|
981
967
|
// src/actions/voiceJoin.ts
|
|
982
|
-
var
|
|
968
|
+
var joinVoice = {
|
|
983
969
|
name: "JOIN_VOICE",
|
|
984
970
|
similes: [
|
|
985
971
|
"JOIN_VOICE",
|
|
@@ -993,7 +979,7 @@ var voiceJoin_default = {
|
|
|
993
979
|
if (message.content.source !== "discord") {
|
|
994
980
|
return false;
|
|
995
981
|
}
|
|
996
|
-
const room = state.data.room ?? await runtime.
|
|
982
|
+
const room = state.data.room ?? await runtime.getRoom(message.roomId);
|
|
997
983
|
if (room?.type !== ChannelType2.GROUP) {
|
|
998
984
|
return false;
|
|
999
985
|
}
|
|
@@ -1006,7 +992,7 @@ var voiceJoin_default = {
|
|
|
1006
992
|
},
|
|
1007
993
|
description: "Join a voice channel to participate in voice chat.",
|
|
1008
994
|
handler: async (runtime, message, state, _options, callback) => {
|
|
1009
|
-
const room = state.data.room ?? await runtime.
|
|
995
|
+
const room = state.data.room ?? await runtime.getRoom(message.roomId);
|
|
1010
996
|
if (!room) {
|
|
1011
997
|
throw new Error("No room found");
|
|
1012
998
|
}
|
|
@@ -1090,7 +1076,7 @@ You should only respond with the name of the voice channel or none, no commentar
|
|
|
1090
1076
|
userMessage: message.content.text,
|
|
1091
1077
|
voiceChannels: voiceChannels.map((channel) => channel.name).join("\n")
|
|
1092
1078
|
};
|
|
1093
|
-
const prompt =
|
|
1079
|
+
const prompt = composePromptFromState5({
|
|
1094
1080
|
template: messageTemplate,
|
|
1095
1081
|
state: guessState
|
|
1096
1082
|
});
|
|
@@ -1272,7 +1258,7 @@ import {
|
|
|
1272
1258
|
logger as logger2
|
|
1273
1259
|
} from "@elizaos/core";
|
|
1274
1260
|
import { BaseGuildVoiceChannel } from "discord.js";
|
|
1275
|
-
var
|
|
1261
|
+
var leaveVoice = {
|
|
1276
1262
|
name: "LEAVE_VOICE",
|
|
1277
1263
|
similes: [
|
|
1278
1264
|
"LEAVE_VOICE",
|
|
@@ -1291,7 +1277,7 @@ var voiceLeave_default = {
|
|
|
1291
1277
|
logger2.error("Discord client not found");
|
|
1292
1278
|
return false;
|
|
1293
1279
|
}
|
|
1294
|
-
const room = state.data.room ?? await runtime.
|
|
1280
|
+
const room = state.data.room ?? await runtime.getRoom(message.roomId);
|
|
1295
1281
|
if (room?.type !== ChannelType3.GROUP) {
|
|
1296
1282
|
return false;
|
|
1297
1283
|
}
|
|
@@ -1300,7 +1286,7 @@ var voiceLeave_default = {
|
|
|
1300
1286
|
},
|
|
1301
1287
|
description: "Leave the current voice channel.",
|
|
1302
1288
|
handler: async (runtime, message, _state, _options) => {
|
|
1303
|
-
const room = await runtime.
|
|
1289
|
+
const room = await runtime.getRoom(message.roomId);
|
|
1304
1290
|
if (!room) {
|
|
1305
1291
|
throw new Error("No room found");
|
|
1306
1292
|
}
|
|
@@ -1519,6 +1505,187 @@ var voiceLeave_default = {
|
|
|
1519
1505
|
]
|
|
1520
1506
|
};
|
|
1521
1507
|
|
|
1508
|
+
// src/providers/channelState.ts
|
|
1509
|
+
import { ChannelType as ChannelType4 } from "@elizaos/core";
|
|
1510
|
+
var channelStateProvider = {
|
|
1511
|
+
name: "channelState",
|
|
1512
|
+
get: async (runtime, message, state) => {
|
|
1513
|
+
const room = state.data?.room ?? await runtime.getRoom(message.roomId);
|
|
1514
|
+
if (!room) {
|
|
1515
|
+
throw new Error("No room found");
|
|
1516
|
+
}
|
|
1517
|
+
if (message.content.source !== "discord") {
|
|
1518
|
+
return {
|
|
1519
|
+
data: null,
|
|
1520
|
+
values: {},
|
|
1521
|
+
text: ""
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
const agentName = state?.agentName || "The agent";
|
|
1525
|
+
const senderName = state?.senderName || "someone";
|
|
1526
|
+
let responseText = "";
|
|
1527
|
+
let channelType = "";
|
|
1528
|
+
let serverName = "";
|
|
1529
|
+
let channelId = "";
|
|
1530
|
+
const serverId = room.serverId;
|
|
1531
|
+
if (room.type === ChannelType4.DM) {
|
|
1532
|
+
channelType = "DM";
|
|
1533
|
+
responseText = `${agentName} is currently in a direct message conversation with ${senderName}. ${agentName} should engage in conversation, should respond to messages that are addressed to them and only ignore messages that seem to not require a response.`;
|
|
1534
|
+
} else {
|
|
1535
|
+
channelType = "GROUP";
|
|
1536
|
+
if (!serverId) {
|
|
1537
|
+
console.error("No server ID found");
|
|
1538
|
+
return {
|
|
1539
|
+
data: {
|
|
1540
|
+
room,
|
|
1541
|
+
channelType
|
|
1542
|
+
},
|
|
1543
|
+
values: {
|
|
1544
|
+
channelType
|
|
1545
|
+
},
|
|
1546
|
+
text: ""
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
channelId = room.channelId;
|
|
1550
|
+
const discordService = runtime.getService(
|
|
1551
|
+
ServiceTypes2.DISCORD
|
|
1552
|
+
);
|
|
1553
|
+
if (!discordService) {
|
|
1554
|
+
console.warn("No discord client found");
|
|
1555
|
+
return {
|
|
1556
|
+
data: {
|
|
1557
|
+
room,
|
|
1558
|
+
channelType,
|
|
1559
|
+
serverId
|
|
1560
|
+
},
|
|
1561
|
+
values: {
|
|
1562
|
+
channelType,
|
|
1563
|
+
serverId
|
|
1564
|
+
},
|
|
1565
|
+
text: ""
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
const guild = discordService.client.guilds.cache.get(serverId);
|
|
1569
|
+
serverName = guild.name;
|
|
1570
|
+
responseText = `${agentName} is currently having a conversation in the channel \`@${channelId} in the server \`${serverName}\` (@${serverId})`;
|
|
1571
|
+
responseText += `
|
|
1572
|
+
${agentName} is in a room with other users and should be self-conscious and only participate when directly addressed or when the conversation is relevant to them.`;
|
|
1573
|
+
}
|
|
1574
|
+
return {
|
|
1575
|
+
data: {
|
|
1576
|
+
room,
|
|
1577
|
+
channelType,
|
|
1578
|
+
serverId,
|
|
1579
|
+
serverName,
|
|
1580
|
+
channelId
|
|
1581
|
+
},
|
|
1582
|
+
values: {
|
|
1583
|
+
channelType,
|
|
1584
|
+
serverName,
|
|
1585
|
+
channelId
|
|
1586
|
+
},
|
|
1587
|
+
text: responseText
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
};
|
|
1591
|
+
|
|
1592
|
+
// src/providers/voiceState.ts
|
|
1593
|
+
import { getVoiceConnection } from "@discordjs/voice";
|
|
1594
|
+
import { ChannelType as ChannelType5 } from "@elizaos/core";
|
|
1595
|
+
var voiceStateProvider = {
|
|
1596
|
+
name: "voiceState",
|
|
1597
|
+
get: async (runtime, message, state) => {
|
|
1598
|
+
const room = await runtime.getRoom(message.roomId);
|
|
1599
|
+
if (!room) {
|
|
1600
|
+
throw new Error("No room found");
|
|
1601
|
+
}
|
|
1602
|
+
if (room.type !== ChannelType5.GROUP) {
|
|
1603
|
+
return {
|
|
1604
|
+
data: {
|
|
1605
|
+
isInVoiceChannel: false,
|
|
1606
|
+
room
|
|
1607
|
+
},
|
|
1608
|
+
values: {
|
|
1609
|
+
isInVoiceChannel: "false",
|
|
1610
|
+
roomType: room.type
|
|
1611
|
+
},
|
|
1612
|
+
text: ""
|
|
1613
|
+
};
|
|
1614
|
+
}
|
|
1615
|
+
const serverId = room.serverId;
|
|
1616
|
+
if (!serverId) {
|
|
1617
|
+
throw new Error("No server ID found 10");
|
|
1618
|
+
}
|
|
1619
|
+
const connection = getVoiceConnection(serverId);
|
|
1620
|
+
const agentName = state?.agentName || "The agent";
|
|
1621
|
+
if (!connection) {
|
|
1622
|
+
return {
|
|
1623
|
+
data: {
|
|
1624
|
+
isInVoiceChannel: false,
|
|
1625
|
+
room,
|
|
1626
|
+
serverId
|
|
1627
|
+
},
|
|
1628
|
+
values: {
|
|
1629
|
+
isInVoiceChannel: "false",
|
|
1630
|
+
serverId
|
|
1631
|
+
},
|
|
1632
|
+
text: `${agentName} is not currently in a voice channel`
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
const worldId = room.worldId;
|
|
1636
|
+
const world = await runtime.getWorld(worldId);
|
|
1637
|
+
if (!world) {
|
|
1638
|
+
throw new Error("No world found");
|
|
1639
|
+
}
|
|
1640
|
+
const worldName = world.name;
|
|
1641
|
+
const roomType = room.type;
|
|
1642
|
+
const channelId = room.channelId;
|
|
1643
|
+
const channelName = room.name;
|
|
1644
|
+
if (!channelId) {
|
|
1645
|
+
return {
|
|
1646
|
+
data: {
|
|
1647
|
+
isInVoiceChannel: true,
|
|
1648
|
+
room,
|
|
1649
|
+
serverId,
|
|
1650
|
+
world,
|
|
1651
|
+
connection
|
|
1652
|
+
},
|
|
1653
|
+
values: {
|
|
1654
|
+
isInVoiceChannel: "true",
|
|
1655
|
+
serverId,
|
|
1656
|
+
worldName,
|
|
1657
|
+
roomType
|
|
1658
|
+
},
|
|
1659
|
+
text: `${agentName} is in an invalid voice channel`
|
|
1660
|
+
};
|
|
1661
|
+
}
|
|
1662
|
+
return {
|
|
1663
|
+
data: {
|
|
1664
|
+
isInVoiceChannel: true,
|
|
1665
|
+
room,
|
|
1666
|
+
serverId,
|
|
1667
|
+
world,
|
|
1668
|
+
connection,
|
|
1669
|
+
channelId,
|
|
1670
|
+
channelName
|
|
1671
|
+
},
|
|
1672
|
+
values: {
|
|
1673
|
+
isInVoiceChannel: "true",
|
|
1674
|
+
serverId,
|
|
1675
|
+
worldName,
|
|
1676
|
+
roomType,
|
|
1677
|
+
channelId,
|
|
1678
|
+
channelName
|
|
1679
|
+
},
|
|
1680
|
+
text: `${agentName} is currently in the voice channel: ${channelName} (ID: ${channelId})`
|
|
1681
|
+
};
|
|
1682
|
+
}
|
|
1683
|
+
};
|
|
1684
|
+
|
|
1685
|
+
// src/service.ts
|
|
1686
|
+
import { ChannelType as ChannelType9, EventTypes as EventTypes2, Role, Service, createUniqueUuid as createUniqueUuid6, logger as logger6 } from "@elizaos/core";
|
|
1687
|
+
import { ChannelType as DiscordChannelType4, Client as DiscordJsClient, Events, GatewayIntentBits, Partials, PermissionsBitField as PermissionsBitField2 } from "discord.js";
|
|
1688
|
+
|
|
1522
1689
|
// src/constants.ts
|
|
1523
1690
|
var MESSAGE_CONSTANTS = {
|
|
1524
1691
|
MAX_MESSAGES: 10,
|
|
@@ -1535,16 +1702,18 @@ var DISCORD_SERVICE_NAME = "discord";
|
|
|
1535
1702
|
|
|
1536
1703
|
// src/messages.ts
|
|
1537
1704
|
import {
|
|
1538
|
-
ChannelType as
|
|
1705
|
+
ChannelType as ChannelType7,
|
|
1706
|
+
EventTypes,
|
|
1707
|
+
ServiceTypes as ServiceTypes4,
|
|
1539
1708
|
createUniqueUuid as createUniqueUuid4,
|
|
1540
|
-
logger as logger4
|
|
1541
|
-
ServiceTypes as ServiceTypes4
|
|
1709
|
+
logger as logger4
|
|
1542
1710
|
} from "@elizaos/core";
|
|
1543
1711
|
import {
|
|
1544
1712
|
ChannelType as DiscordChannelType2
|
|
1545
1713
|
} from "discord.js";
|
|
1546
1714
|
|
|
1547
1715
|
// src/attachments.ts
|
|
1716
|
+
import fs3 from "node:fs";
|
|
1548
1717
|
import { trimTokens as trimTokens3 } from "@elizaos/core";
|
|
1549
1718
|
import { parseJSONObjectFromText as parseJSONObjectFromText5 } from "@elizaos/core";
|
|
1550
1719
|
import {
|
|
@@ -1553,7 +1722,6 @@ import {
|
|
|
1553
1722
|
} from "@elizaos/core";
|
|
1554
1723
|
import { Collection } from "discord.js";
|
|
1555
1724
|
import ffmpeg from "fluent-ffmpeg";
|
|
1556
|
-
import fs3 from "node:fs";
|
|
1557
1725
|
async function generateSummary(runtime, text) {
|
|
1558
1726
|
text = await trimTokens3(text, 1e5, runtime);
|
|
1559
1727
|
const prompt = `Please generate a concise summary for the following text:
|
|
@@ -1587,9 +1755,19 @@ async function generateSummary(runtime, text) {
|
|
|
1587
1755
|
var AttachmentManager = class {
|
|
1588
1756
|
attachmentCache = /* @__PURE__ */ new Map();
|
|
1589
1757
|
runtime;
|
|
1758
|
+
/**
|
|
1759
|
+
* Constructor for creating a new instance of the class.
|
|
1760
|
+
*
|
|
1761
|
+
* @param {IAgentRuntime} runtime The runtime object to be injected into the instance.
|
|
1762
|
+
*/
|
|
1590
1763
|
constructor(runtime) {
|
|
1591
1764
|
this.runtime = runtime;
|
|
1592
1765
|
}
|
|
1766
|
+
/**
|
|
1767
|
+
* Processes attachments and returns an array of Media objects.
|
|
1768
|
+
* @param {Collection<string, Attachment> | Attachment[]} attachments - The attachments to be processed
|
|
1769
|
+
* @returns {Promise<Media[]>} - An array of processed Media objects
|
|
1770
|
+
*/
|
|
1593
1771
|
async processAttachments(attachments) {
|
|
1594
1772
|
const processedAttachments = [];
|
|
1595
1773
|
const attachmentCollection = attachments instanceof Collection ? attachments : new Collection(attachments.map((att) => [att.id, att]));
|
|
@@ -1601,6 +1779,15 @@ var AttachmentManager = class {
|
|
|
1601
1779
|
}
|
|
1602
1780
|
return processedAttachments;
|
|
1603
1781
|
}
|
|
1782
|
+
/**
|
|
1783
|
+
* Processes the provided attachment to generate a media object.
|
|
1784
|
+
* If the media for the attachment URL is already cached, it will return the cached media.
|
|
1785
|
+
* Otherwise, it will determine the type of attachment (PDF, text, audio, video, image, generic)
|
|
1786
|
+
* and call the corresponding processing method to generate the media object.
|
|
1787
|
+
*
|
|
1788
|
+
* @param attachment The attachment to process
|
|
1789
|
+
* @returns A promise that resolves to a Media object representing the attachment, or null if the attachment could not be processed
|
|
1790
|
+
*/
|
|
1604
1791
|
async processAttachment(attachment) {
|
|
1605
1792
|
if (this.attachmentCache.has(attachment.url)) {
|
|
1606
1793
|
return this.attachmentCache.get(attachment.url);
|
|
@@ -1624,6 +1811,11 @@ var AttachmentManager = class {
|
|
|
1624
1811
|
}
|
|
1625
1812
|
return media;
|
|
1626
1813
|
}
|
|
1814
|
+
/**
|
|
1815
|
+
* Asynchronously processes an audio or video attachment provided as input and returns a Media object.
|
|
1816
|
+
* @param {Attachment} attachment - The attachment object containing information about the audio/video file.
|
|
1817
|
+
* @returns {Promise<Media>} A Promise that resolves to a Media object representing the processed audio/video attachment.
|
|
1818
|
+
*/
|
|
1627
1819
|
async processAudioVideoAttachment(attachment) {
|
|
1628
1820
|
try {
|
|
1629
1821
|
const response = await fetch(attachment.url);
|
|
@@ -1666,6 +1858,12 @@ var AttachmentManager = class {
|
|
|
1666
1858
|
};
|
|
1667
1859
|
}
|
|
1668
1860
|
}
|
|
1861
|
+
/**
|
|
1862
|
+
* Extracts the audio stream from the provided MP4 data and converts it to MP3 format.
|
|
1863
|
+
*
|
|
1864
|
+
* @param {ArrayBuffer} mp4Data - The MP4 data to extract audio from
|
|
1865
|
+
* @returns {Promise<Buffer>} - A Promise that resolves with the converted audio data as a Buffer
|
|
1866
|
+
*/
|
|
1669
1867
|
async extractAudioFromMP4(mp4Data) {
|
|
1670
1868
|
const tempMP4File = `temp_${Date.now()}.mp4`;
|
|
1671
1869
|
const tempAudioFile = `temp_${Date.now()}.mp3`;
|
|
@@ -1689,6 +1887,17 @@ var AttachmentManager = class {
|
|
|
1689
1887
|
}
|
|
1690
1888
|
}
|
|
1691
1889
|
}
|
|
1890
|
+
/**
|
|
1891
|
+
* Processes a PDF attachment by fetching the PDF file from the specified URL,
|
|
1892
|
+
* converting it to text, generating a summary, and returning a Media object
|
|
1893
|
+
* with the extracted information.
|
|
1894
|
+
* If an error occurs during processing, a placeholder Media object is returned
|
|
1895
|
+
* with an error message.
|
|
1896
|
+
*
|
|
1897
|
+
* @param {Attachment} attachment - The PDF attachment to process.
|
|
1898
|
+
* @returns {Promise<Media>} A promise that resolves to a Media object representing
|
|
1899
|
+
* the processed PDF attachment.
|
|
1900
|
+
*/
|
|
1692
1901
|
async processPdfAttachment(attachment) {
|
|
1693
1902
|
try {
|
|
1694
1903
|
const response = await fetch(attachment.url);
|
|
@@ -1715,6 +1924,11 @@ var AttachmentManager = class {
|
|
|
1715
1924
|
};
|
|
1716
1925
|
}
|
|
1717
1926
|
}
|
|
1927
|
+
/**
|
|
1928
|
+
* Processes a plaintext attachment by fetching its content, generating a summary, and returning a Media object.
|
|
1929
|
+
* @param {Attachment} attachment - The attachment object to process.
|
|
1930
|
+
* @returns {Promise<Media>} A promise that resolves to a Media object representing the processed plaintext attachment.
|
|
1931
|
+
*/
|
|
1718
1932
|
async processPlaintextAttachment(attachment) {
|
|
1719
1933
|
try {
|
|
1720
1934
|
const response = await fetch(attachment.url);
|
|
@@ -1740,6 +1954,14 @@ var AttachmentManager = class {
|
|
|
1740
1954
|
};
|
|
1741
1955
|
}
|
|
1742
1956
|
}
|
|
1957
|
+
/**
|
|
1958
|
+
* Process the image attachment by fetching description and title using the IMAGE_DESCRIPTION model.
|
|
1959
|
+
* If successful, returns a Media object populated with the details. If unsuccessful, creates a fallback
|
|
1960
|
+
* Media object and logs the error.
|
|
1961
|
+
*
|
|
1962
|
+
* @param {Attachment} attachment - The attachment object containing the image details.
|
|
1963
|
+
* @returns {Promise<Media>} A promise that resolves to a Media object.
|
|
1964
|
+
*/
|
|
1743
1965
|
async processImageAttachment(attachment) {
|
|
1744
1966
|
try {
|
|
1745
1967
|
const { description, title } = await this.runtime.useModel(
|
|
@@ -1759,6 +1981,12 @@ var AttachmentManager = class {
|
|
|
1759
1981
|
return this.createFallbackImageMedia(attachment);
|
|
1760
1982
|
}
|
|
1761
1983
|
}
|
|
1984
|
+
/**
|
|
1985
|
+
* Creates a fallback Media object for image attachments that could not be recognized.
|
|
1986
|
+
*
|
|
1987
|
+
* @param {Attachment} attachment - The attachment object containing image details.
|
|
1988
|
+
* @returns {Media} - The fallback Media object with basic information about the image attachment.
|
|
1989
|
+
*/
|
|
1762
1990
|
createFallbackImageMedia(attachment) {
|
|
1763
1991
|
return {
|
|
1764
1992
|
id: attachment.id,
|
|
@@ -1769,6 +1997,12 @@ var AttachmentManager = class {
|
|
|
1769
1997
|
text: `This is an image attachment. File name: ${attachment.name}, Size: ${attachment.size} bytes, Content type: ${attachment.contentType}`
|
|
1770
1998
|
};
|
|
1771
1999
|
}
|
|
2000
|
+
/**
|
|
2001
|
+
* Process a video attachment to extract video information.
|
|
2002
|
+
* @param {Attachment} attachment - The attachment object containing video information.
|
|
2003
|
+
* @returns {Promise<Media>} A promise that resolves to a Media object with video details.
|
|
2004
|
+
* @throws {Error} If video service is not available.
|
|
2005
|
+
*/
|
|
1772
2006
|
async processVideoAttachment(attachment) {
|
|
1773
2007
|
const videoService = this.runtime.getService(
|
|
1774
2008
|
ServiceTypes3.VIDEO
|
|
@@ -1799,6 +2033,11 @@ var AttachmentManager = class {
|
|
|
1799
2033
|
text: "Video content not available"
|
|
1800
2034
|
};
|
|
1801
2035
|
}
|
|
2036
|
+
/**
|
|
2037
|
+
* Process a generic attachment and return a Media object with specified properties.
|
|
2038
|
+
* @param {Attachment} attachment - The attachment object to process.
|
|
2039
|
+
* @returns {Promise<Media>} A Promise that resolves to a Media object with specified properties.
|
|
2040
|
+
*/
|
|
1802
2041
|
async processGenericAttachment(attachment) {
|
|
1803
2042
|
return {
|
|
1804
2043
|
id: attachment.id,
|
|
@@ -1815,11 +2054,11 @@ var AttachmentManager = class {
|
|
|
1815
2054
|
import {
|
|
1816
2055
|
ModelTypes as ModelTypes7,
|
|
1817
2056
|
logger as logger3,
|
|
1818
|
-
|
|
1819
|
-
|
|
2057
|
+
parseJSONObjectFromText as parseJSONObjectFromText6,
|
|
2058
|
+
trimTokens as trimTokens4
|
|
1820
2059
|
} from "@elizaos/core";
|
|
1821
2060
|
import {
|
|
1822
|
-
ChannelType as
|
|
2061
|
+
ChannelType as ChannelType6,
|
|
1823
2062
|
PermissionsBitField,
|
|
1824
2063
|
ThreadChannel
|
|
1825
2064
|
} from "discord.js";
|
|
@@ -1896,7 +2135,7 @@ function canSendMessage(channel) {
|
|
|
1896
2135
|
reason: "No channel given"
|
|
1897
2136
|
};
|
|
1898
2137
|
}
|
|
1899
|
-
if (channel.type ===
|
|
2138
|
+
if (channel.type === ChannelType6.DM) {
|
|
1900
2139
|
return {
|
|
1901
2140
|
canSend: true,
|
|
1902
2141
|
reason: null
|
|
@@ -1940,12 +2179,21 @@ var MessageManager = class {
|
|
|
1940
2179
|
runtime;
|
|
1941
2180
|
attachmentManager;
|
|
1942
2181
|
getChannelType;
|
|
2182
|
+
/**
|
|
2183
|
+
* Constructor for a new instance of MyClass.
|
|
2184
|
+
* @param {any} discordClient - The Discord client object.
|
|
2185
|
+
*/
|
|
1943
2186
|
constructor(discordClient) {
|
|
1944
2187
|
this.client = discordClient.client;
|
|
1945
2188
|
this.runtime = discordClient.runtime;
|
|
1946
2189
|
this.attachmentManager = new AttachmentManager(this.runtime);
|
|
1947
2190
|
this.getChannelType = discordClient.getChannelType;
|
|
1948
2191
|
}
|
|
2192
|
+
/**
|
|
2193
|
+
* Handles incoming Discord messages and processes them accordingly.
|
|
2194
|
+
*
|
|
2195
|
+
* @param {DiscordMessage} message - The Discord message to be handled
|
|
2196
|
+
*/
|
|
1949
2197
|
async handleMessage(message) {
|
|
1950
2198
|
if (this.runtime.character.settings?.discord?.allowedChannelIds && !this.runtime.character.settings.discord.allowedChannelIds.some(
|
|
1951
2199
|
(id) => id === message.channel.id
|
|
@@ -1973,7 +2221,7 @@ var MessageManager = class {
|
|
|
1973
2221
|
type = await this.getChannelType(message.channel);
|
|
1974
2222
|
serverId = guild.id;
|
|
1975
2223
|
} else {
|
|
1976
|
-
type =
|
|
2224
|
+
type = ChannelType7.DM;
|
|
1977
2225
|
serverId = void 0;
|
|
1978
2226
|
}
|
|
1979
2227
|
await this.runtime.ensureConnection({
|
|
@@ -2062,7 +2310,7 @@ var MessageManager = class {
|
|
|
2062
2310
|
return [];
|
|
2063
2311
|
}
|
|
2064
2312
|
};
|
|
2065
|
-
this.runtime.emitEvent(["DISCORD_MESSAGE_RECEIVED"
|
|
2313
|
+
this.runtime.emitEvent(["DISCORD_MESSAGE_RECEIVED" /* MESSAGE_RECEIVED */, EventTypes.MESSAGE_RECEIVED], {
|
|
2066
2314
|
runtime: this.runtime,
|
|
2067
2315
|
message: newMessage,
|
|
2068
2316
|
callback
|
|
@@ -2071,6 +2319,13 @@ var MessageManager = class {
|
|
|
2071
2319
|
console.error("Error handling message:", error);
|
|
2072
2320
|
}
|
|
2073
2321
|
}
|
|
2322
|
+
/**
|
|
2323
|
+
* Processes the message content, mentions, code blocks, attachments, and URLs to generate
|
|
2324
|
+
* processed content and media attachments.
|
|
2325
|
+
*
|
|
2326
|
+
* @param {DiscordMessage} message The message to process
|
|
2327
|
+
* @returns {Promise<{ processedContent: string; attachments: Media[] }>} Processed content and media attachments
|
|
2328
|
+
*/
|
|
2074
2329
|
async processMessage(message) {
|
|
2075
2330
|
let processedContent = message.content;
|
|
2076
2331
|
let attachments = [];
|
|
@@ -2152,6 +2407,13 @@ var MessageManager = class {
|
|
|
2152
2407
|
}
|
|
2153
2408
|
return { processedContent, attachments };
|
|
2154
2409
|
}
|
|
2410
|
+
/**
|
|
2411
|
+
* Asynchronously fetches the bot's username and discriminator from Discord API.
|
|
2412
|
+
*
|
|
2413
|
+
* @param {string} botToken The token of the bot to authenticate the request
|
|
2414
|
+
* @returns {Promise<string>} A promise that resolves with the bot's username and discriminator
|
|
2415
|
+
* @throws {Error} If there is an error while fetching the bot details
|
|
2416
|
+
*/
|
|
2155
2417
|
async fetchBotName(botToken) {
|
|
2156
2418
|
const url = "https://discord.com/api/v10/users/@me";
|
|
2157
2419
|
const response = await fetch(url, {
|
|
@@ -2169,526 +2431,103 @@ var MessageManager = class {
|
|
|
2169
2431
|
}
|
|
2170
2432
|
};
|
|
2171
2433
|
|
|
2172
|
-
// src/
|
|
2173
|
-
import {
|
|
2174
|
-
|
|
2175
|
-
name: "channelState",
|
|
2176
|
-
get: async (runtime, message, state) => {
|
|
2177
|
-
const room = state.data?.room ?? await runtime.getDatabaseAdapter().getRoom(message.roomId);
|
|
2178
|
-
if (!room) {
|
|
2179
|
-
throw new Error("No room found");
|
|
2180
|
-
}
|
|
2181
|
-
if (message.content.source !== "discord") {
|
|
2182
|
-
return {
|
|
2183
|
-
data: null,
|
|
2184
|
-
values: {},
|
|
2185
|
-
text: ""
|
|
2186
|
-
};
|
|
2187
|
-
}
|
|
2188
|
-
const agentName = state?.agentName || "The agent";
|
|
2189
|
-
const senderName = state?.senderName || "someone";
|
|
2190
|
-
let responseText = "";
|
|
2191
|
-
let channelType = "";
|
|
2192
|
-
let serverName = "";
|
|
2193
|
-
let channelId = "";
|
|
2194
|
-
const serverId = room.serverId;
|
|
2195
|
-
if (room.type === ChannelType6.DM) {
|
|
2196
|
-
channelType = "DM";
|
|
2197
|
-
responseText = `${agentName} is currently in a direct message conversation with ${senderName}. ${agentName} should engage in conversation, should respond to messages that are addressed to them and only ignore messages that seem to not require a response.`;
|
|
2198
|
-
} else {
|
|
2199
|
-
channelType = "GROUP";
|
|
2200
|
-
if (!serverId) {
|
|
2201
|
-
console.error("No server ID found");
|
|
2202
|
-
return {
|
|
2203
|
-
data: {
|
|
2204
|
-
room,
|
|
2205
|
-
channelType
|
|
2206
|
-
},
|
|
2207
|
-
values: {
|
|
2208
|
-
channelType
|
|
2209
|
-
},
|
|
2210
|
-
text: ""
|
|
2211
|
-
};
|
|
2212
|
-
}
|
|
2213
|
-
channelId = room.channelId;
|
|
2214
|
-
const discordService = runtime.getService(
|
|
2215
|
-
ServiceTypes2.DISCORD
|
|
2216
|
-
);
|
|
2217
|
-
if (!discordService) {
|
|
2218
|
-
console.warn("No discord client found");
|
|
2219
|
-
return {
|
|
2220
|
-
data: {
|
|
2221
|
-
room,
|
|
2222
|
-
channelType,
|
|
2223
|
-
serverId
|
|
2224
|
-
},
|
|
2225
|
-
values: {
|
|
2226
|
-
channelType,
|
|
2227
|
-
serverId
|
|
2228
|
-
},
|
|
2229
|
-
text: ""
|
|
2230
|
-
};
|
|
2231
|
-
}
|
|
2232
|
-
const guild = discordService.client.guilds.cache.get(serverId);
|
|
2233
|
-
serverName = guild.name;
|
|
2234
|
-
responseText = `${agentName} is currently having a conversation in the channel \`@${channelId} in the server \`${serverName}\` (@${serverId})`;
|
|
2235
|
-
responseText += `
|
|
2236
|
-
${agentName} is in a room with other users and should be self-conscious and only participate when directly addressed or when the conversation is relevant to them.`;
|
|
2237
|
-
}
|
|
2238
|
-
return {
|
|
2239
|
-
data: {
|
|
2240
|
-
room,
|
|
2241
|
-
channelType,
|
|
2242
|
-
serverId,
|
|
2243
|
-
serverName,
|
|
2244
|
-
channelId
|
|
2245
|
-
},
|
|
2246
|
-
values: {
|
|
2247
|
-
channelType,
|
|
2248
|
-
serverName,
|
|
2249
|
-
channelId
|
|
2250
|
-
},
|
|
2251
|
-
text: responseText
|
|
2252
|
-
};
|
|
2253
|
-
}
|
|
2254
|
-
};
|
|
2255
|
-
var channelState_default = channelStateProvider;
|
|
2256
|
-
|
|
2257
|
-
// src/providers/voiceState.ts
|
|
2258
|
-
import { getVoiceConnection } from "@discordjs/voice";
|
|
2259
|
-
import { ChannelType as ChannelType7 } from "@elizaos/core";
|
|
2260
|
-
var voiceStateProvider = {
|
|
2261
|
-
name: "voiceState",
|
|
2262
|
-
get: async (runtime, message, state) => {
|
|
2263
|
-
const room = await runtime.getDatabaseAdapter().getRoom(message.roomId);
|
|
2264
|
-
if (!room) {
|
|
2265
|
-
throw new Error("No room found");
|
|
2266
|
-
}
|
|
2267
|
-
if (room.type !== ChannelType7.GROUP) {
|
|
2268
|
-
return {
|
|
2269
|
-
data: {
|
|
2270
|
-
isInVoiceChannel: false,
|
|
2271
|
-
room
|
|
2272
|
-
},
|
|
2273
|
-
values: {
|
|
2274
|
-
isInVoiceChannel: "false",
|
|
2275
|
-
roomType: room.type
|
|
2276
|
-
},
|
|
2277
|
-
text: ""
|
|
2278
|
-
};
|
|
2279
|
-
}
|
|
2280
|
-
const serverId = room.serverId;
|
|
2281
|
-
if (!serverId) {
|
|
2282
|
-
throw new Error("No server ID found 10");
|
|
2283
|
-
}
|
|
2284
|
-
const connection = getVoiceConnection(serverId);
|
|
2285
|
-
const agentName = state?.agentName || "The agent";
|
|
2286
|
-
if (!connection) {
|
|
2287
|
-
return {
|
|
2288
|
-
data: {
|
|
2289
|
-
isInVoiceChannel: false,
|
|
2290
|
-
room,
|
|
2291
|
-
serverId
|
|
2292
|
-
},
|
|
2293
|
-
values: {
|
|
2294
|
-
isInVoiceChannel: "false",
|
|
2295
|
-
serverId
|
|
2296
|
-
},
|
|
2297
|
-
text: `${agentName} is not currently in a voice channel`
|
|
2298
|
-
};
|
|
2299
|
-
}
|
|
2300
|
-
const worldId = room.worldId;
|
|
2301
|
-
const world = await runtime.getDatabaseAdapter().getWorld(worldId);
|
|
2302
|
-
if (!world) {
|
|
2303
|
-
throw new Error("No world found");
|
|
2304
|
-
}
|
|
2305
|
-
const worldName = world.name;
|
|
2306
|
-
const roomType = room.type;
|
|
2307
|
-
const channelId = room.channelId;
|
|
2308
|
-
const channelName = room.name;
|
|
2309
|
-
if (!channelId) {
|
|
2310
|
-
return {
|
|
2311
|
-
data: {
|
|
2312
|
-
isInVoiceChannel: true,
|
|
2313
|
-
room,
|
|
2314
|
-
serverId,
|
|
2315
|
-
world,
|
|
2316
|
-
connection
|
|
2317
|
-
},
|
|
2318
|
-
values: {
|
|
2319
|
-
isInVoiceChannel: "true",
|
|
2320
|
-
serverId,
|
|
2321
|
-
worldName,
|
|
2322
|
-
roomType
|
|
2323
|
-
},
|
|
2324
|
-
text: `${agentName} is in an invalid voice channel`
|
|
2325
|
-
};
|
|
2326
|
-
}
|
|
2327
|
-
return {
|
|
2328
|
-
data: {
|
|
2329
|
-
isInVoiceChannel: true,
|
|
2330
|
-
room,
|
|
2331
|
-
serverId,
|
|
2332
|
-
world,
|
|
2333
|
-
connection,
|
|
2334
|
-
channelId,
|
|
2335
|
-
channelName
|
|
2336
|
-
},
|
|
2337
|
-
values: {
|
|
2338
|
-
isInVoiceChannel: "true",
|
|
2339
|
-
serverId,
|
|
2340
|
-
worldName,
|
|
2341
|
-
roomType,
|
|
2342
|
-
channelId,
|
|
2343
|
-
channelName
|
|
2344
|
-
},
|
|
2345
|
-
text: `${agentName} is currently in the voice channel: ${channelName} (ID: ${channelId})`
|
|
2346
|
-
};
|
|
2347
|
-
}
|
|
2348
|
-
};
|
|
2349
|
-
var voiceState_default = voiceStateProvider;
|
|
2350
|
-
|
|
2351
|
-
// src/tests.ts
|
|
2434
|
+
// src/voice.ts
|
|
2435
|
+
import { EventEmitter } from "node:events";
|
|
2436
|
+
import { pipeline } from "node:stream";
|
|
2352
2437
|
import {
|
|
2353
|
-
AudioPlayerStatus,
|
|
2354
2438
|
NoSubscriberBehavior,
|
|
2439
|
+
StreamType,
|
|
2355
2440
|
VoiceConnectionStatus,
|
|
2356
2441
|
createAudioPlayer,
|
|
2357
2442
|
createAudioResource,
|
|
2358
|
-
entersState
|
|
2443
|
+
entersState,
|
|
2444
|
+
getVoiceConnections,
|
|
2445
|
+
joinVoiceChannel
|
|
2359
2446
|
} from "@discordjs/voice";
|
|
2360
2447
|
import {
|
|
2448
|
+
ChannelType as ChannelType8,
|
|
2361
2449
|
ModelTypes as ModelTypes8,
|
|
2450
|
+
createUniqueUuid as createUniqueUuid5,
|
|
2362
2451
|
logger as logger5
|
|
2363
2452
|
} from "@elizaos/core";
|
|
2364
|
-
import {
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
name: "Handle Incoming Messages",
|
|
2390
|
-
fn: this.testHandlingMessage.bind(this)
|
|
2391
|
-
},
|
|
2392
|
-
{
|
|
2393
|
-
name: "Slash Commands - Leave Voice",
|
|
2394
|
-
fn: this.testLeaveVoiceSlashCommand.bind(this)
|
|
2453
|
+
import {
|
|
2454
|
+
ChannelType as DiscordChannelType3
|
|
2455
|
+
} from "discord.js";
|
|
2456
|
+
import prism from "prism-media";
|
|
2457
|
+
var DECODE_FRAME_SIZE = 1024;
|
|
2458
|
+
var DECODE_SAMPLE_RATE = 16e3;
|
|
2459
|
+
var AudioMonitor = class {
|
|
2460
|
+
readable;
|
|
2461
|
+
buffers = [];
|
|
2462
|
+
maxSize;
|
|
2463
|
+
lastFlagged = -1;
|
|
2464
|
+
ended = false;
|
|
2465
|
+
/**
|
|
2466
|
+
* Constructs an AudioMonitor instance.
|
|
2467
|
+
* @param {Readable} readable - The readable stream to monitor for audio data.
|
|
2468
|
+
* @param {number} maxSize - The maximum size of the audio buffer.
|
|
2469
|
+
* @param {function} onStart - The callback function to be called when audio starts.
|
|
2470
|
+
* @param {function} callback - The callback function to process audio data.
|
|
2471
|
+
*/
|
|
2472
|
+
constructor(readable, maxSize, onStart, callback) {
|
|
2473
|
+
this.readable = readable;
|
|
2474
|
+
this.maxSize = maxSize;
|
|
2475
|
+
this.readable.on("data", (chunk) => {
|
|
2476
|
+
if (this.lastFlagged < 0) {
|
|
2477
|
+
this.lastFlagged = this.buffers.length;
|
|
2395
2478
|
}
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
this.discordClient = runtime.getService(
|
|
2401
|
-
ServiceTypes2.DISCORD
|
|
2479
|
+
this.buffers.push(chunk);
|
|
2480
|
+
const currentSize = this.buffers.reduce(
|
|
2481
|
+
(acc, cur) => acc + cur.length,
|
|
2482
|
+
0
|
|
2402
2483
|
);
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
logger5.info("Waiting for DiscordService to be ready...");
|
|
2407
|
-
await new Promise((resolve, reject) => {
|
|
2408
|
-
this.discordClient.client.once(Events.ClientReady, resolve);
|
|
2409
|
-
this.discordClient.client.once(Events.Error, reject);
|
|
2410
|
-
});
|
|
2484
|
+
while (currentSize > this.maxSize) {
|
|
2485
|
+
this.buffers.shift();
|
|
2486
|
+
this.lastFlagged--;
|
|
2411
2487
|
}
|
|
2412
|
-
}
|
|
2413
|
-
|
|
2414
|
-
|
|
2488
|
+
});
|
|
2489
|
+
this.readable.on("end", () => {
|
|
2490
|
+
logger5.log("AudioMonitor ended");
|
|
2491
|
+
this.ended = true;
|
|
2492
|
+
if (this.lastFlagged < 0) return;
|
|
2493
|
+
callback(this.getBufferFromStart());
|
|
2494
|
+
this.lastFlagged = -1;
|
|
2495
|
+
});
|
|
2496
|
+
this.readable.on("speakingStopped", () => {
|
|
2497
|
+
if (this.ended) return;
|
|
2498
|
+
logger5.log("Speaking stopped");
|
|
2499
|
+
if (this.lastFlagged < 0) return;
|
|
2500
|
+
callback(this.getBufferFromStart());
|
|
2501
|
+
});
|
|
2502
|
+
this.readable.on("speakingStarted", () => {
|
|
2503
|
+
if (this.ended) return;
|
|
2504
|
+
onStart();
|
|
2505
|
+
logger5.log("Speaking started");
|
|
2506
|
+
this.reset();
|
|
2507
|
+
});
|
|
2415
2508
|
}
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
isCommand: () => true,
|
|
2425
|
-
commandName: "joinchannel",
|
|
2426
|
-
options: {
|
|
2427
|
-
get: (name) => name === "channel" ? { value: channel.id } : null
|
|
2428
|
-
},
|
|
2429
|
-
guild: channel.guild,
|
|
2430
|
-
deferReply: async () => {
|
|
2431
|
-
},
|
|
2432
|
-
editReply: async (message) => {
|
|
2433
|
-
logger5.info(`JoinChannel Slash Command Response: ${message}`);
|
|
2434
|
-
}
|
|
2435
|
-
};
|
|
2436
|
-
await this.discordClient.voiceManager.handleJoinChannelCommand(
|
|
2437
|
-
fakeJoinInteraction
|
|
2438
|
-
);
|
|
2439
|
-
logger5.success("Slash command test completed successfully.");
|
|
2440
|
-
} catch (error) {
|
|
2441
|
-
throw new Error(`Error in slash commands test: ${error}`);
|
|
2442
|
-
}
|
|
2443
|
-
}
|
|
2444
|
-
async testLeaveVoiceSlashCommand(runtime) {
|
|
2445
|
-
try {
|
|
2446
|
-
await this.waitForVoiceManagerReady(this.discordClient);
|
|
2447
|
-
const channel = await this.getTestChannel(runtime);
|
|
2448
|
-
if (!channel || !channel.isTextBased()) {
|
|
2449
|
-
throw new Error("Invalid test channel for slash command test.");
|
|
2450
|
-
}
|
|
2451
|
-
const fakeLeaveInteraction = {
|
|
2452
|
-
isCommand: () => true,
|
|
2453
|
-
commandName: "leavechannel",
|
|
2454
|
-
guildId: channel.guildId,
|
|
2455
|
-
reply: async (message) => {
|
|
2456
|
-
logger5.info(`LeaveChannel Slash Command Response: ${message}`);
|
|
2457
|
-
}
|
|
2458
|
-
};
|
|
2459
|
-
await this.discordClient.voiceManager.handleLeaveChannelCommand(
|
|
2460
|
-
fakeLeaveInteraction
|
|
2461
|
-
);
|
|
2462
|
-
logger5.success("Slash command test completed successfully.");
|
|
2463
|
-
} catch (error) {
|
|
2464
|
-
throw new Error(`Error in slash commands test: ${error}`);
|
|
2465
|
-
}
|
|
2466
|
-
}
|
|
2467
|
-
async testTextToSpeechPlayback(runtime) {
|
|
2468
|
-
try {
|
|
2469
|
-
await this.waitForVoiceManagerReady(this.discordClient);
|
|
2470
|
-
const channel = await this.getTestChannel(runtime);
|
|
2471
|
-
if (!channel || channel.type !== ChannelType8.GuildVoice) {
|
|
2472
|
-
throw new Error("Invalid voice channel.");
|
|
2473
|
-
}
|
|
2474
|
-
await this.discordClient.voiceManager.joinChannel(channel);
|
|
2475
|
-
const guild = await this.getActiveGuild(this.discordClient);
|
|
2476
|
-
const guildId = guild.id;
|
|
2477
|
-
const connection = this.discordClient.voiceManager.getVoiceConnection(guildId);
|
|
2478
|
-
try {
|
|
2479
|
-
await entersState(connection, VoiceConnectionStatus.Ready, 1e4);
|
|
2480
|
-
logger5.success(`Voice connection is ready in guild: ${guildId}`);
|
|
2481
|
-
} catch (error) {
|
|
2482
|
-
throw new Error(`Voice connection failed to become ready: ${error}`);
|
|
2483
|
-
}
|
|
2484
|
-
let responseStream = null;
|
|
2485
|
-
try {
|
|
2486
|
-
responseStream = await runtime.useModel(
|
|
2487
|
-
ModelTypes8.TEXT_TO_SPEECH,
|
|
2488
|
-
`Hi! I'm ${runtime.character.name}! How are you doing today?`
|
|
2489
|
-
);
|
|
2490
|
-
} catch (_error) {
|
|
2491
|
-
throw new Error("No text to speech service found");
|
|
2492
|
-
}
|
|
2493
|
-
if (!responseStream) {
|
|
2494
|
-
throw new Error("TTS response stream is null or undefined.");
|
|
2495
|
-
}
|
|
2496
|
-
await this.playAudioStream(responseStream, connection);
|
|
2497
|
-
} catch (error) {
|
|
2498
|
-
throw new Error(`Error in TTS playback test: ${error}`);
|
|
2499
|
-
}
|
|
2500
|
-
}
|
|
2501
|
-
async testSendingTextMessage(runtime) {
|
|
2502
|
-
try {
|
|
2503
|
-
const channel = await this.getTestChannel(runtime);
|
|
2504
|
-
await this.sendMessageToChannel(
|
|
2505
|
-
channel,
|
|
2506
|
-
"Testing Message",
|
|
2507
|
-
[TEST_IMAGE_URL]
|
|
2508
|
-
);
|
|
2509
|
-
} catch (error) {
|
|
2510
|
-
throw new Error(`Error in sending text message: ${error}`);
|
|
2511
|
-
}
|
|
2512
|
-
}
|
|
2513
|
-
async testHandlingMessage(runtime) {
|
|
2514
|
-
try {
|
|
2515
|
-
const channel = await this.getTestChannel(runtime);
|
|
2516
|
-
const fakeMessage = {
|
|
2517
|
-
content: `Hello, ${runtime.character.name}! How are you?`,
|
|
2518
|
-
author: {
|
|
2519
|
-
id: "mock-user-id",
|
|
2520
|
-
username: "MockUser",
|
|
2521
|
-
bot: false
|
|
2522
|
-
},
|
|
2523
|
-
channel,
|
|
2524
|
-
id: "mock-message-id",
|
|
2525
|
-
createdTimestamp: Date.now(),
|
|
2526
|
-
mentions: {
|
|
2527
|
-
has: () => false
|
|
2528
|
-
},
|
|
2529
|
-
reference: null,
|
|
2530
|
-
attachments: []
|
|
2531
|
-
};
|
|
2532
|
-
await this.discordClient.messageManager.handleMessage(fakeMessage);
|
|
2533
|
-
} catch (error) {
|
|
2534
|
-
throw new Error(`Error in sending text message: ${error}`);
|
|
2535
|
-
}
|
|
2536
|
-
}
|
|
2537
|
-
// #############################
|
|
2538
|
-
// Utility Functions
|
|
2539
|
-
// #############################
|
|
2540
|
-
async getTestChannel(runtime) {
|
|
2541
|
-
const channelId = this.validateChannelId(runtime);
|
|
2542
|
-
const channel = await this.discordClient.client.channels.fetch(channelId);
|
|
2543
|
-
if (!channel) throw new Error("no test channel found!");
|
|
2544
|
-
return channel;
|
|
2545
|
-
}
|
|
2546
|
-
async sendMessageToChannel(channel, messageContent, files) {
|
|
2547
|
-
try {
|
|
2548
|
-
if (!channel || !channel.isTextBased()) {
|
|
2549
|
-
throw new Error(
|
|
2550
|
-
"Channel is not a text-based channel or does not exist."
|
|
2551
|
-
);
|
|
2552
|
-
}
|
|
2553
|
-
await sendMessageInChunks(
|
|
2554
|
-
channel,
|
|
2555
|
-
messageContent,
|
|
2556
|
-
null,
|
|
2557
|
-
files
|
|
2558
|
-
);
|
|
2559
|
-
} catch (error) {
|
|
2560
|
-
throw new Error(`Error sending message: ${error}`);
|
|
2561
|
-
}
|
|
2562
|
-
}
|
|
2563
|
-
async playAudioStream(responseStream, connection) {
|
|
2564
|
-
const audioPlayer = createAudioPlayer({
|
|
2565
|
-
behaviors: {
|
|
2566
|
-
noSubscriber: NoSubscriberBehavior.Pause
|
|
2567
|
-
}
|
|
2568
|
-
});
|
|
2569
|
-
const audioResource = createAudioResource(responseStream);
|
|
2570
|
-
audioPlayer.play(audioResource);
|
|
2571
|
-
connection.subscribe(audioPlayer);
|
|
2572
|
-
logger5.success("TTS playback started successfully.");
|
|
2573
|
-
await new Promise((resolve, reject) => {
|
|
2574
|
-
audioPlayer.once(AudioPlayerStatus.Idle, () => {
|
|
2575
|
-
logger5.info("TTS playback finished.");
|
|
2576
|
-
resolve();
|
|
2577
|
-
});
|
|
2578
|
-
audioPlayer.once("error", (error) => {
|
|
2579
|
-
reject(error);
|
|
2580
|
-
throw new Error(`TTS playback error: ${error}`);
|
|
2581
|
-
});
|
|
2582
|
-
});
|
|
2583
|
-
}
|
|
2584
|
-
async getActiveGuild(discordClient) {
|
|
2585
|
-
const guilds = await discordClient.client.guilds.fetch();
|
|
2586
|
-
const fullGuilds = await Promise.all(guilds.map((guild) => guild.fetch()));
|
|
2587
|
-
const activeGuild = fullGuilds.find((g) => g.members.me?.voice.channelId);
|
|
2588
|
-
if (!activeGuild) {
|
|
2589
|
-
throw new Error("No active voice connection found for the bot.");
|
|
2590
|
-
}
|
|
2591
|
-
return activeGuild;
|
|
2592
|
-
}
|
|
2593
|
-
async waitForVoiceManagerReady(discordClient) {
|
|
2594
|
-
if (!discordClient) {
|
|
2595
|
-
throw new Error("Discord client is not initialized.");
|
|
2596
|
-
}
|
|
2597
|
-
if (!discordClient.voiceManager.isReady()) {
|
|
2598
|
-
await new Promise((resolve, reject) => {
|
|
2599
|
-
discordClient.voiceManager.once("ready", resolve);
|
|
2600
|
-
discordClient.voiceManager.once("error", reject);
|
|
2601
|
-
});
|
|
2602
|
-
}
|
|
2603
|
-
}
|
|
2604
|
-
validateChannelId(runtime) {
|
|
2605
|
-
const testChannelId = runtime.getSetting("DISCORD_TEST_CHANNEL_ID") || process.env.DISCORD_TEST_CHANNEL_ID;
|
|
2606
|
-
if (!testChannelId) {
|
|
2607
|
-
throw new Error(
|
|
2608
|
-
"DISCORD_TEST_CHANNEL_ID is not set. Please provide a valid channel ID in the environment variables."
|
|
2609
|
-
);
|
|
2610
|
-
}
|
|
2611
|
-
return testChannelId;
|
|
2612
|
-
}
|
|
2613
|
-
};
|
|
2614
|
-
|
|
2615
|
-
// src/voice.ts
|
|
2616
|
-
import {
|
|
2617
|
-
NoSubscriberBehavior as NoSubscriberBehavior2,
|
|
2618
|
-
StreamType,
|
|
2619
|
-
VoiceConnectionStatus as VoiceConnectionStatus2,
|
|
2620
|
-
createAudioPlayer as createAudioPlayer2,
|
|
2621
|
-
createAudioResource as createAudioResource2,
|
|
2622
|
-
entersState as entersState2,
|
|
2623
|
-
getVoiceConnections,
|
|
2624
|
-
joinVoiceChannel
|
|
2625
|
-
} from "@discordjs/voice";
|
|
2626
|
-
import {
|
|
2627
|
-
ChannelType as ChannelType9,
|
|
2628
|
-
ModelTypes as ModelTypes9,
|
|
2629
|
-
createUniqueUuid as createUniqueUuid5,
|
|
2630
|
-
logger as logger6
|
|
2631
|
-
} from "@elizaos/core";
|
|
2632
|
-
import {
|
|
2633
|
-
ChannelType as DiscordChannelType3
|
|
2634
|
-
} from "discord.js";
|
|
2635
|
-
import { EventEmitter } from "node:events";
|
|
2636
|
-
import { pipeline } from "node:stream";
|
|
2637
|
-
import prism from "prism-media";
|
|
2638
|
-
var DECODE_FRAME_SIZE = 1024;
|
|
2639
|
-
var DECODE_SAMPLE_RATE = 16e3;
|
|
2640
|
-
var AudioMonitor = class {
|
|
2641
|
-
readable;
|
|
2642
|
-
buffers = [];
|
|
2643
|
-
maxSize;
|
|
2644
|
-
lastFlagged = -1;
|
|
2645
|
-
ended = false;
|
|
2646
|
-
constructor(readable, maxSize, onStart, callback) {
|
|
2647
|
-
this.readable = readable;
|
|
2648
|
-
this.maxSize = maxSize;
|
|
2649
|
-
this.readable.on("data", (chunk) => {
|
|
2650
|
-
if (this.lastFlagged < 0) {
|
|
2651
|
-
this.lastFlagged = this.buffers.length;
|
|
2652
|
-
}
|
|
2653
|
-
this.buffers.push(chunk);
|
|
2654
|
-
const currentSize = this.buffers.reduce(
|
|
2655
|
-
(acc, cur) => acc + cur.length,
|
|
2656
|
-
0
|
|
2657
|
-
);
|
|
2658
|
-
while (currentSize > this.maxSize) {
|
|
2659
|
-
this.buffers.shift();
|
|
2660
|
-
this.lastFlagged--;
|
|
2661
|
-
}
|
|
2662
|
-
});
|
|
2663
|
-
this.readable.on("end", () => {
|
|
2664
|
-
logger6.log("AudioMonitor ended");
|
|
2665
|
-
this.ended = true;
|
|
2666
|
-
if (this.lastFlagged < 0) return;
|
|
2667
|
-
callback(this.getBufferFromStart());
|
|
2668
|
-
this.lastFlagged = -1;
|
|
2669
|
-
});
|
|
2670
|
-
this.readable.on("speakingStopped", () => {
|
|
2671
|
-
if (this.ended) return;
|
|
2672
|
-
logger6.log("Speaking stopped");
|
|
2673
|
-
if (this.lastFlagged < 0) return;
|
|
2674
|
-
callback(this.getBufferFromStart());
|
|
2675
|
-
});
|
|
2676
|
-
this.readable.on("speakingStarted", () => {
|
|
2677
|
-
if (this.ended) return;
|
|
2678
|
-
onStart();
|
|
2679
|
-
logger6.log("Speaking started");
|
|
2680
|
-
this.reset();
|
|
2681
|
-
});
|
|
2682
|
-
}
|
|
2683
|
-
stop() {
|
|
2684
|
-
this.readable.removeAllListeners("data");
|
|
2685
|
-
this.readable.removeAllListeners("end");
|
|
2686
|
-
this.readable.removeAllListeners("speakingStopped");
|
|
2687
|
-
this.readable.removeAllListeners("speakingStarted");
|
|
2509
|
+
/**
|
|
2510
|
+
* Stops listening to "data", "end", "speakingStopped", and "speakingStarted" events on the readable stream.
|
|
2511
|
+
*/
|
|
2512
|
+
stop() {
|
|
2513
|
+
this.readable.removeAllListeners("data");
|
|
2514
|
+
this.readable.removeAllListeners("end");
|
|
2515
|
+
this.readable.removeAllListeners("speakingStopped");
|
|
2516
|
+
this.readable.removeAllListeners("speakingStarted");
|
|
2688
2517
|
}
|
|
2518
|
+
/**
|
|
2519
|
+
* Check if the item is flagged.
|
|
2520
|
+
* @returns {boolean} True if the item was flagged, false otherwise.
|
|
2521
|
+
*/
|
|
2689
2522
|
isFlagged() {
|
|
2690
2523
|
return this.lastFlagged >= 0;
|
|
2691
2524
|
}
|
|
2525
|
+
/**
|
|
2526
|
+
* Returns a Buffer containing all buffers starting from the last flagged index.
|
|
2527
|
+
* If the last flagged index is less than 0, returns null.
|
|
2528
|
+
*
|
|
2529
|
+
* @returns {Buffer | null} The concatenated Buffer or null
|
|
2530
|
+
*/
|
|
2692
2531
|
getBufferFromFlag() {
|
|
2693
2532
|
if (this.lastFlagged < 0) {
|
|
2694
2533
|
return null;
|
|
@@ -2696,14 +2535,26 @@ var AudioMonitor = class {
|
|
|
2696
2535
|
const buffer = Buffer.concat(this.buffers.slice(this.lastFlagged));
|
|
2697
2536
|
return buffer;
|
|
2698
2537
|
}
|
|
2538
|
+
/**
|
|
2539
|
+
* Concatenates all buffers in the array and returns a single buffer.
|
|
2540
|
+
*
|
|
2541
|
+
* @returns {Buffer} The concatenated buffer from the start.
|
|
2542
|
+
*/
|
|
2699
2543
|
getBufferFromStart() {
|
|
2700
2544
|
const buffer = Buffer.concat(this.buffers);
|
|
2701
2545
|
return buffer;
|
|
2702
2546
|
}
|
|
2547
|
+
/**
|
|
2548
|
+
* Resets the buffers array and sets lastFlagged to -1.
|
|
2549
|
+
*/
|
|
2703
2550
|
reset() {
|
|
2704
2551
|
this.buffers = [];
|
|
2705
2552
|
this.lastFlagged = -1;
|
|
2706
2553
|
}
|
|
2554
|
+
/**
|
|
2555
|
+
* Check if the object has ended.
|
|
2556
|
+
* @returns {boolean} Returns true if the object has ended; false otherwise.
|
|
2557
|
+
*/
|
|
2707
2558
|
isEnded() {
|
|
2708
2559
|
return this.ended;
|
|
2709
2560
|
}
|
|
@@ -2719,6 +2570,12 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2719
2570
|
connections = /* @__PURE__ */ new Map();
|
|
2720
2571
|
activeMonitors = /* @__PURE__ */ new Map();
|
|
2721
2572
|
ready;
|
|
2573
|
+
/**
|
|
2574
|
+
* Constructor for initializing a new instance of the class.
|
|
2575
|
+
*
|
|
2576
|
+
* @param {DiscordService} service - The Discord service to use.
|
|
2577
|
+
* @param {IAgentRuntime} runtime - The runtime for the agent.
|
|
2578
|
+
*/
|
|
2722
2579
|
constructor(service, runtime) {
|
|
2723
2580
|
super();
|
|
2724
2581
|
this.client = service.client;
|
|
@@ -2727,21 +2584,41 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2727
2584
|
this.setReady(true);
|
|
2728
2585
|
});
|
|
2729
2586
|
}
|
|
2587
|
+
/**
|
|
2588
|
+
* Asynchronously retrieves the type of the channel.
|
|
2589
|
+
* @param {Channel} channel - The channel to get the type for.
|
|
2590
|
+
* @returns {Promise<ChannelType>} The type of the channel.
|
|
2591
|
+
*/
|
|
2730
2592
|
async getChannelType(channel) {
|
|
2731
2593
|
switch (channel.type) {
|
|
2732
2594
|
case DiscordChannelType3.GuildVoice:
|
|
2733
2595
|
case DiscordChannelType3.GuildStageVoice:
|
|
2734
|
-
return
|
|
2596
|
+
return ChannelType8.VOICE_GROUP;
|
|
2735
2597
|
}
|
|
2736
2598
|
}
|
|
2599
|
+
/**
|
|
2600
|
+
* Set the ready status of the VoiceManager.
|
|
2601
|
+
* @param {boolean} status - The status to set.
|
|
2602
|
+
*/
|
|
2737
2603
|
setReady(status) {
|
|
2738
2604
|
this.ready = status;
|
|
2739
2605
|
this.emit("ready");
|
|
2740
|
-
|
|
2606
|
+
logger5.debug(`VoiceManager is now ready: ${this.ready}`);
|
|
2741
2607
|
}
|
|
2608
|
+
/**
|
|
2609
|
+
* Check if the object is ready.
|
|
2610
|
+
*
|
|
2611
|
+
* @returns {boolean} True if the object is ready, false otherwise.
|
|
2612
|
+
*/
|
|
2742
2613
|
isReady() {
|
|
2743
2614
|
return this.ready;
|
|
2744
2615
|
}
|
|
2616
|
+
/**
|
|
2617
|
+
* Handle voice state update event.
|
|
2618
|
+
* @param {VoiceState} oldState - The old voice state of the member.
|
|
2619
|
+
* @param {VoiceState} newState - The new voice state of the member.
|
|
2620
|
+
* @returns {void}
|
|
2621
|
+
*/
|
|
2745
2622
|
async handleVoiceStateUpdate(oldState, newState) {
|
|
2746
2623
|
const oldChannelId = oldState.channelId;
|
|
2747
2624
|
const newChannelId = newState.channelId;
|
|
@@ -2763,6 +2640,10 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2763
2640
|
);
|
|
2764
2641
|
}
|
|
2765
2642
|
}
|
|
2643
|
+
/**
|
|
2644
|
+
* Joins a voice channel and sets up the necessary connection and event listeners.
|
|
2645
|
+
* @param {BaseGuildVoiceChannel} channel - The voice channel to join
|
|
2646
|
+
*/
|
|
2766
2647
|
async joinChannel(channel) {
|
|
2767
2648
|
const oldConnection = this.getVoiceConnection(channel.guildId);
|
|
2768
2649
|
if (oldConnection) {
|
|
@@ -2784,38 +2665,38 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2784
2665
|
});
|
|
2785
2666
|
try {
|
|
2786
2667
|
await Promise.race([
|
|
2787
|
-
|
|
2788
|
-
|
|
2668
|
+
entersState(connection, VoiceConnectionStatus.Ready, 2e4),
|
|
2669
|
+
entersState(connection, VoiceConnectionStatus.Signalling, 2e4)
|
|
2789
2670
|
]);
|
|
2790
|
-
|
|
2671
|
+
logger5.log(
|
|
2791
2672
|
`Voice connection established in state: ${connection.state.status}`
|
|
2792
2673
|
);
|
|
2793
2674
|
connection.on("stateChange", async (oldState, newState) => {
|
|
2794
|
-
|
|
2675
|
+
logger5.log(
|
|
2795
2676
|
`Voice connection state changed from ${oldState.status} to ${newState.status}`
|
|
2796
2677
|
);
|
|
2797
|
-
if (newState.status ===
|
|
2798
|
-
|
|
2678
|
+
if (newState.status === VoiceConnectionStatus.Disconnected) {
|
|
2679
|
+
logger5.log("Handling disconnection...");
|
|
2799
2680
|
try {
|
|
2800
2681
|
await Promise.race([
|
|
2801
|
-
|
|
2802
|
-
|
|
2682
|
+
entersState(connection, VoiceConnectionStatus.Signalling, 5e3),
|
|
2683
|
+
entersState(connection, VoiceConnectionStatus.Connecting, 5e3)
|
|
2803
2684
|
]);
|
|
2804
|
-
|
|
2685
|
+
logger5.log("Reconnecting to channel...");
|
|
2805
2686
|
} catch (e) {
|
|
2806
|
-
|
|
2687
|
+
logger5.log(`Disconnection confirmed - cleaning up...${e}`);
|
|
2807
2688
|
connection.destroy();
|
|
2808
2689
|
this.connections.delete(channel.id);
|
|
2809
2690
|
}
|
|
2810
|
-
} else if (newState.status ===
|
|
2691
|
+
} else if (newState.status === VoiceConnectionStatus.Destroyed) {
|
|
2811
2692
|
this.connections.delete(channel.id);
|
|
2812
|
-
} else if (!this.connections.has(channel.id) && (newState.status ===
|
|
2693
|
+
} else if (!this.connections.has(channel.id) && (newState.status === VoiceConnectionStatus.Ready || newState.status === VoiceConnectionStatus.Signalling)) {
|
|
2813
2694
|
this.connections.set(channel.id, connection);
|
|
2814
2695
|
}
|
|
2815
2696
|
});
|
|
2816
2697
|
connection.on("error", (error) => {
|
|
2817
|
-
|
|
2818
|
-
|
|
2698
|
+
logger5.log("Voice connection error:", error);
|
|
2699
|
+
logger5.log("Connection error - will attempt to recover...");
|
|
2819
2700
|
});
|
|
2820
2701
|
this.connections.set(channel.id, connection);
|
|
2821
2702
|
const me = channel.guild.members.me;
|
|
@@ -2824,7 +2705,7 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2824
2705
|
await me.voice.setDeaf(false);
|
|
2825
2706
|
await me.voice.setMute(false);
|
|
2826
2707
|
} catch (error) {
|
|
2827
|
-
|
|
2708
|
+
logger5.log("Failed to modify voice state:", error);
|
|
2828
2709
|
}
|
|
2829
2710
|
}
|
|
2830
2711
|
connection.receiver.speaking.on("start", async (entityId) => {
|
|
@@ -2848,12 +2729,17 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2848
2729
|
}
|
|
2849
2730
|
});
|
|
2850
2731
|
} catch (error) {
|
|
2851
|
-
|
|
2732
|
+
logger5.log("Failed to establish voice connection:", error);
|
|
2852
2733
|
connection.destroy();
|
|
2853
2734
|
this.connections.delete(channel.id);
|
|
2854
2735
|
throw error;
|
|
2855
2736
|
}
|
|
2856
2737
|
}
|
|
2738
|
+
/**
|
|
2739
|
+
* Retrieves the voice connection for a given guild ID.
|
|
2740
|
+
* @param {string} guildId - The ID of the guild to get the voice connection for.
|
|
2741
|
+
* @returns {VoiceConnection | undefined} The voice connection for the specified guild ID, or undefined if not found.
|
|
2742
|
+
*/
|
|
2857
2743
|
getVoiceConnection(guildId) {
|
|
2858
2744
|
const connections = getVoiceConnections(this.client.user.id);
|
|
2859
2745
|
if (!connections) {
|
|
@@ -2864,6 +2750,12 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2864
2750
|
);
|
|
2865
2751
|
return connection;
|
|
2866
2752
|
}
|
|
2753
|
+
/**
|
|
2754
|
+
* Monitor a member's audio stream for volume activity and speaking thresholds.
|
|
2755
|
+
*
|
|
2756
|
+
* @param {GuildMember} member - The member whose audio stream is being monitored.
|
|
2757
|
+
* @param {BaseGuildVoiceChannel} channel - The voice channel in which the member is connected.
|
|
2758
|
+
*/
|
|
2867
2759
|
async monitorMember(member, channel) {
|
|
2868
2760
|
const entityId = member?.id;
|
|
2869
2761
|
const userName = member?.user?.username;
|
|
@@ -2944,6 +2836,12 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2944
2836
|
opusDecoder
|
|
2945
2837
|
);
|
|
2946
2838
|
}
|
|
2839
|
+
/**
|
|
2840
|
+
* Leaves the specified voice channel and stops monitoring all members in that channel.
|
|
2841
|
+
* If there is an active connection in the channel, it will be destroyed.
|
|
2842
|
+
*
|
|
2843
|
+
* @param {BaseGuildVoiceChannel} channel - The voice channel to leave.
|
|
2844
|
+
*/
|
|
2947
2845
|
leaveChannel(channel) {
|
|
2948
2846
|
const connection = this.connections.get(channel.id);
|
|
2949
2847
|
if (connection) {
|
|
@@ -2957,6 +2855,10 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2957
2855
|
}
|
|
2958
2856
|
console.log(`Left voice channel: ${channel.name} (${channel.id})`);
|
|
2959
2857
|
}
|
|
2858
|
+
/**
|
|
2859
|
+
* Stop monitoring a specific member by their member ID.
|
|
2860
|
+
* @param {string} memberId - The ID of the member to stop monitoring.
|
|
2861
|
+
*/
|
|
2960
2862
|
stopMonitoringMember(memberId) {
|
|
2961
2863
|
const monitorInfo = this.activeMonitors.get(memberId);
|
|
2962
2864
|
if (monitorInfo) {
|
|
@@ -2966,10 +2868,18 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2966
2868
|
console.log(`Stopped monitoring user ${memberId}`);
|
|
2967
2869
|
}
|
|
2968
2870
|
}
|
|
2871
|
+
/**
|
|
2872
|
+
* Asynchronously debounces the process transcription function to prevent rapid execution.
|
|
2873
|
+
*
|
|
2874
|
+
* @param {UUID} entityId - The ID of the entity related to the transcription.
|
|
2875
|
+
* @param {string} name - The name of the entity for transcription.
|
|
2876
|
+
* @param {string} userName - The username of the user initiating the transcription.
|
|
2877
|
+
* @param {BaseGuildVoiceChannel} channel - The voice channel where the transcription is happening.
|
|
2878
|
+
*/
|
|
2969
2879
|
async debouncedProcessTranscription(entityId, name, userName, channel) {
|
|
2970
2880
|
const DEBOUNCE_TRANSCRIPTION_THRESHOLD = 1500;
|
|
2971
2881
|
if (this.activeAudioPlayer?.state?.status === "idle") {
|
|
2972
|
-
|
|
2882
|
+
logger5.log("Cleaning up idle audio player.");
|
|
2973
2883
|
this.cleanupAudioPlayer(this.activeAudioPlayer);
|
|
2974
2884
|
}
|
|
2975
2885
|
if (this.activeAudioPlayer || this.processingVoice) {
|
|
@@ -3000,6 +2910,15 @@ var VoiceManager = class extends EventEmitter {
|
|
|
3000
2910
|
}
|
|
3001
2911
|
}, DEBOUNCE_TRANSCRIPTION_THRESHOLD);
|
|
3002
2912
|
}
|
|
2913
|
+
/**
|
|
2914
|
+
* Handle user audio stream for monitoring purposes.
|
|
2915
|
+
*
|
|
2916
|
+
* @param {UUID} userId - The unique identifier of the user.
|
|
2917
|
+
* @param {string} name - The name of the user.
|
|
2918
|
+
* @param {string} userName - The username of the user.
|
|
2919
|
+
* @param {BaseGuildVoiceChannel} channel - The voice channel the user is in.
|
|
2920
|
+
* @param {Readable} audioStream - The audio stream to monitor.
|
|
2921
|
+
*/
|
|
3003
2922
|
async handleUserStream(userId, name, userName, channel, audioStream) {
|
|
3004
2923
|
const entityId = createUniqueUuid5(this.runtime, userId);
|
|
3005
2924
|
console.log(`Starting audio monitor for user: ${entityId}`);
|
|
@@ -3039,6 +2958,16 @@ var VoiceManager = class extends EventEmitter {
|
|
|
3039
2958
|
}
|
|
3040
2959
|
);
|
|
3041
2960
|
}
|
|
2961
|
+
/**
|
|
2962
|
+
* Process the transcription of audio data for a user.
|
|
2963
|
+
*
|
|
2964
|
+
* @param {UUID} entityId - The unique ID of the user entity.
|
|
2965
|
+
* @param {string} channelId - The ID of the channel where the transcription is taking place.
|
|
2966
|
+
* @param {BaseGuildVoiceChannel} channel - The voice channel where the user is speaking.
|
|
2967
|
+
* @param {string} name - The name of the user.
|
|
2968
|
+
* @param {string} userName - The username of the user.
|
|
2969
|
+
* @returns {Promise<void>}
|
|
2970
|
+
*/
|
|
3042
2971
|
async processTranscription(entityId, channelId, channel, name, userName) {
|
|
3043
2972
|
const state = this.userStates.get(entityId);
|
|
3044
2973
|
if (!state || state.buffers.length === 0) return;
|
|
@@ -3053,7 +2982,7 @@ var VoiceManager = class extends EventEmitter {
|
|
|
3053
2982
|
const wavBuffer = await this.convertOpusToWav(inputBuffer);
|
|
3054
2983
|
console.log("Starting transcription...");
|
|
3055
2984
|
const transcriptionText = await this.runtime.useModel(
|
|
3056
|
-
|
|
2985
|
+
ModelTypes8.TRANSCRIPTION,
|
|
3057
2986
|
wavBuffer
|
|
3058
2987
|
);
|
|
3059
2988
|
if (transcriptionText && isValidTranscription(transcriptionText)) {
|
|
@@ -3076,6 +3005,17 @@ var VoiceManager = class extends EventEmitter {
|
|
|
3076
3005
|
console.error(`Error transcribing audio for user ${entityId}:`, error);
|
|
3077
3006
|
}
|
|
3078
3007
|
}
|
|
3008
|
+
/**
|
|
3009
|
+
* Handles a voice message received in a Discord channel.
|
|
3010
|
+
*
|
|
3011
|
+
* @param {string} message - The message content.
|
|
3012
|
+
* @param {UUID} entityId - The entity ID associated with the message.
|
|
3013
|
+
* @param {string} channelId - The ID of the Discord channel where the message was received.
|
|
3014
|
+
* @param {BaseGuildVoiceChannel} channel - The Discord channel where the message was received.
|
|
3015
|
+
* @param {string} name - The name associated with the message.
|
|
3016
|
+
* @param {string} userName - The user name associated with the message.
|
|
3017
|
+
* @returns {Promise<{text: string, actions: string[]}>} Object containing the resulting text and actions.
|
|
3018
|
+
*/
|
|
3079
3019
|
async handleMessage(message, entityId, channelId, channel, name, userName) {
|
|
3080
3020
|
try {
|
|
3081
3021
|
if (!message || message.trim() === "" || message.length < 3) {
|
|
@@ -3134,7 +3074,7 @@ var VoiceManager = class extends EventEmitter {
|
|
|
3134
3074
|
if (responseMemory.content.text?.trim()) {
|
|
3135
3075
|
await this.runtime.getMemoryManager("messages").createMemory(responseMemory);
|
|
3136
3076
|
const responseStream = await this.runtime.useModel(
|
|
3137
|
-
|
|
3077
|
+
ModelTypes8.TEXT_TO_SPEECH,
|
|
3138
3078
|
content.text
|
|
3139
3079
|
);
|
|
3140
3080
|
if (responseStream) {
|
|
@@ -3159,6 +3099,12 @@ var VoiceManager = class extends EventEmitter {
|
|
|
3159
3099
|
console.error("Error processing voice message:", error);
|
|
3160
3100
|
}
|
|
3161
3101
|
}
|
|
3102
|
+
/**
|
|
3103
|
+
* Asynchronously converts an Opus audio Buffer to a WAV audio Buffer.
|
|
3104
|
+
*
|
|
3105
|
+
* @param {Buffer} pcmBuffer - The Opus audio Buffer to convert to WAV.
|
|
3106
|
+
* @returns {Promise<Buffer>} A Promise that resolves with the converted WAV audio Buffer.
|
|
3107
|
+
*/
|
|
3162
3108
|
async convertOpusToWav(pcmBuffer) {
|
|
3163
3109
|
try {
|
|
3164
3110
|
const wavHeader = getWavHeader(pcmBuffer.length, DECODE_SAMPLE_RATE);
|
|
@@ -3169,6 +3115,11 @@ var VoiceManager = class extends EventEmitter {
|
|
|
3169
3115
|
throw error;
|
|
3170
3116
|
}
|
|
3171
3117
|
}
|
|
3118
|
+
/**
|
|
3119
|
+
* Scans the given Discord guild to select a suitable voice channel to join.
|
|
3120
|
+
*
|
|
3121
|
+
* @param {Guild} guild The Discord guild to scan for voice channels.
|
|
3122
|
+
*/
|
|
3172
3123
|
async scanGuild(guild) {
|
|
3173
3124
|
let chosenChannel = null;
|
|
3174
3125
|
try {
|
|
@@ -3196,12 +3147,19 @@ var VoiceManager = class extends EventEmitter {
|
|
|
3196
3147
|
console.log(`Joining channel: ${chosenChannel.name}`);
|
|
3197
3148
|
await this.joinChannel(chosenChannel);
|
|
3198
3149
|
} else {
|
|
3199
|
-
|
|
3150
|
+
logger5.debug("Warning: No suitable voice channel found to join.");
|
|
3200
3151
|
}
|
|
3201
3152
|
} catch (error) {
|
|
3202
3153
|
console.error("Error selecting or joining a voice channel:", error);
|
|
3203
3154
|
}
|
|
3204
3155
|
}
|
|
3156
|
+
/**
|
|
3157
|
+
* Play an audio stream for a given entity ID.
|
|
3158
|
+
*
|
|
3159
|
+
* @param {UUID} entityId - The ID of the entity to play the audio for.
|
|
3160
|
+
* @param {Readable} audioStream - The audio stream to play.
|
|
3161
|
+
* @returns {void}
|
|
3162
|
+
*/
|
|
3205
3163
|
async playAudioStream(entityId, audioStream) {
|
|
3206
3164
|
const connection = this.connections.get(entityId);
|
|
3207
3165
|
if (connection == null) {
|
|
@@ -3209,15 +3167,15 @@ var VoiceManager = class extends EventEmitter {
|
|
|
3209
3167
|
return;
|
|
3210
3168
|
}
|
|
3211
3169
|
this.cleanupAudioPlayer(this.activeAudioPlayer);
|
|
3212
|
-
const audioPlayer =
|
|
3170
|
+
const audioPlayer = createAudioPlayer({
|
|
3213
3171
|
behaviors: {
|
|
3214
|
-
noSubscriber:
|
|
3172
|
+
noSubscriber: NoSubscriberBehavior.Pause
|
|
3215
3173
|
}
|
|
3216
3174
|
});
|
|
3217
3175
|
this.activeAudioPlayer = audioPlayer;
|
|
3218
3176
|
connection.subscribe(audioPlayer);
|
|
3219
3177
|
const audioStartTime = Date.now();
|
|
3220
|
-
const resource =
|
|
3178
|
+
const resource = createAudioResource(audioStream, {
|
|
3221
3179
|
inputType: StreamType.Arbitrary
|
|
3222
3180
|
});
|
|
3223
3181
|
audioPlayer.play(resource);
|
|
@@ -3234,6 +3192,12 @@ var VoiceManager = class extends EventEmitter {
|
|
|
3234
3192
|
}
|
|
3235
3193
|
);
|
|
3236
3194
|
}
|
|
3195
|
+
/**
|
|
3196
|
+
* Cleans up the provided audio player by stopping it, removing all listeners,
|
|
3197
|
+
* and resetting the active audio player if it matches the provided player.
|
|
3198
|
+
*
|
|
3199
|
+
* @param {AudioPlayer} audioPlayer - The audio player to be cleaned up.
|
|
3200
|
+
*/
|
|
3237
3201
|
cleanupAudioPlayer(audioPlayer) {
|
|
3238
3202
|
if (!audioPlayer) return;
|
|
3239
3203
|
audioPlayer.stop();
|
|
@@ -3242,6 +3206,12 @@ var VoiceManager = class extends EventEmitter {
|
|
|
3242
3206
|
this.activeAudioPlayer = null;
|
|
3243
3207
|
}
|
|
3244
3208
|
}
|
|
3209
|
+
/**
|
|
3210
|
+
* Asynchronously handles the join channel command in an interaction.
|
|
3211
|
+
*
|
|
3212
|
+
* @param {any} interaction - The interaction object representing the user's input.
|
|
3213
|
+
* @returns {Promise<void>} - A promise that resolves once the join channel command is handled.
|
|
3214
|
+
*/
|
|
3245
3215
|
async handleJoinChannelCommand(interaction) {
|
|
3246
3216
|
try {
|
|
3247
3217
|
await interaction.deferReply();
|
|
@@ -3269,6 +3239,12 @@ var VoiceManager = class extends EventEmitter {
|
|
|
3269
3239
|
await interaction.editReply("Failed to join the voice channel.").catch(console.error);
|
|
3270
3240
|
}
|
|
3271
3241
|
}
|
|
3242
|
+
/**
|
|
3243
|
+
* Handles the leave channel command by destroying the voice connection if it exists.
|
|
3244
|
+
*
|
|
3245
|
+
* @param {any} interaction The interaction object representing the command invocation.
|
|
3246
|
+
* @returns {void}
|
|
3247
|
+
*/
|
|
3272
3248
|
async handleLeaveChannelCommand(interaction) {
|
|
3273
3249
|
const connection = this.getVoiceConnection(interaction.guildId);
|
|
3274
3250
|
if (!connection) {
|
|
@@ -3285,7 +3261,7 @@ var VoiceManager = class extends EventEmitter {
|
|
|
3285
3261
|
}
|
|
3286
3262
|
};
|
|
3287
3263
|
|
|
3288
|
-
// src/
|
|
3264
|
+
// src/service.ts
|
|
3289
3265
|
var DiscordService = class _DiscordService extends Service {
|
|
3290
3266
|
static serviceType = DISCORD_SERVICE_NAME;
|
|
3291
3267
|
capabilityDescription = "The agent is able to send and receive messages on discord";
|
|
@@ -3293,49 +3269,91 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3293
3269
|
character;
|
|
3294
3270
|
messageManager;
|
|
3295
3271
|
voiceManager;
|
|
3272
|
+
/**
|
|
3273
|
+
* Constructor for Discord client.
|
|
3274
|
+
* Initializes the Discord client with specified intents and partials,
|
|
3275
|
+
* sets up event listeners, and ensures all servers exist.
|
|
3276
|
+
*
|
|
3277
|
+
* @param {IAgentRuntime} runtime - The AgentRuntime instance
|
|
3278
|
+
*/
|
|
3296
3279
|
constructor(runtime) {
|
|
3297
3280
|
super(runtime);
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3281
|
+
const token = runtime.getSetting("DISCORD_API_TOKEN");
|
|
3282
|
+
if (!token || token.trim() === "") {
|
|
3283
|
+
logger6.warn(
|
|
3284
|
+
"Discord API Token not provided - Discord functionality will be unavailable"
|
|
3285
|
+
);
|
|
3286
|
+
this.client = null;
|
|
3287
|
+
return;
|
|
3288
|
+
}
|
|
3289
|
+
try {
|
|
3290
|
+
this.client = new DiscordJsClient({
|
|
3291
|
+
intents: [
|
|
3292
|
+
GatewayIntentBits.Guilds,
|
|
3293
|
+
GatewayIntentBits.GuildMembers,
|
|
3294
|
+
GatewayIntentBits.GuildPresences,
|
|
3295
|
+
GatewayIntentBits.DirectMessages,
|
|
3296
|
+
GatewayIntentBits.GuildVoiceStates,
|
|
3297
|
+
GatewayIntentBits.MessageContent,
|
|
3298
|
+
GatewayIntentBits.GuildMessages,
|
|
3299
|
+
GatewayIntentBits.DirectMessageTyping,
|
|
3300
|
+
GatewayIntentBits.GuildMessageTyping,
|
|
3301
|
+
GatewayIntentBits.GuildMessageReactions
|
|
3302
|
+
],
|
|
3303
|
+
partials: [
|
|
3304
|
+
Partials.Channel,
|
|
3305
|
+
Partials.Message,
|
|
3306
|
+
Partials.User,
|
|
3307
|
+
Partials.Reaction
|
|
3308
|
+
]
|
|
3309
|
+
});
|
|
3310
|
+
this.runtime = runtime;
|
|
3311
|
+
this.voiceManager = new VoiceManager(this, runtime);
|
|
3312
|
+
this.messageManager = new MessageManager(this);
|
|
3313
|
+
this.client.once(Events.ClientReady, this.onClientReady.bind(this));
|
|
3314
|
+
this.client.login(token).catch((error) => {
|
|
3315
|
+
logger6.error(`Failed to login to Discord: ${error.message}`);
|
|
3316
|
+
this.client = null;
|
|
3317
|
+
});
|
|
3318
|
+
this.setupEventListeners();
|
|
3319
|
+
const ensureAllServersExist = async (runtime2) => {
|
|
3320
|
+
const guilds = await this.client.guilds.fetch();
|
|
3321
|
+
for (const [, guild] of guilds) {
|
|
3322
|
+
await this.ensureAllChannelsExist(runtime2, guild);
|
|
3323
|
+
}
|
|
3324
|
+
};
|
|
3325
|
+
ensureAllServersExist(this.runtime);
|
|
3326
|
+
} catch (error) {
|
|
3327
|
+
logger6.error(`Error initializing Discord client: ${error.message}`);
|
|
3328
|
+
this.client = null;
|
|
3329
|
+
}
|
|
3332
3330
|
}
|
|
3331
|
+
/**
|
|
3332
|
+
* Ensures that all channels exist in the database for a given guild.
|
|
3333
|
+
* @param {IAgentRuntime} runtime - The agent runtime object.
|
|
3334
|
+
* @param {OAuth2Guild} guild - The OAuth2Guild object for which channels need to be ensured.
|
|
3335
|
+
* @returns {Promise<void>} - A Promise that resolves once all channels are ensured.
|
|
3336
|
+
*/
|
|
3333
3337
|
async ensureAllChannelsExist(runtime, guild) {
|
|
3334
|
-
|
|
3335
|
-
|
|
3338
|
+
let guildObj;
|
|
3339
|
+
let guildChannels;
|
|
3340
|
+
let retries = 3;
|
|
3341
|
+
while (retries > 0) {
|
|
3342
|
+
try {
|
|
3343
|
+
guildObj = await guild.fetch();
|
|
3344
|
+
guildChannels = await guild.fetch();
|
|
3345
|
+
break;
|
|
3346
|
+
} catch (error) {
|
|
3347
|
+
retries--;
|
|
3348
|
+
if (retries === 0) {
|
|
3349
|
+
throw error;
|
|
3350
|
+
}
|
|
3351
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
3352
|
+
}
|
|
3353
|
+
}
|
|
3336
3354
|
for (const [, channel] of guildChannels.channels.cache) {
|
|
3337
3355
|
const roomId = createUniqueUuid6(this.runtime, channel.id);
|
|
3338
|
-
const room = await runtime.
|
|
3356
|
+
const room = await runtime.getRoom(roomId);
|
|
3339
3357
|
if (room) {
|
|
3340
3358
|
continue;
|
|
3341
3359
|
}
|
|
@@ -3352,595 +3370,1090 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3352
3370
|
[ownerId]: Role.OWNER
|
|
3353
3371
|
}
|
|
3354
3372
|
}
|
|
3355
|
-
});
|
|
3356
|
-
await runtime.ensureRoomExists({
|
|
3357
|
-
id: roomId,
|
|
3358
|
-
name: channel.name,
|
|
3359
|
-
source: "discord",
|
|
3360
|
-
type:
|
|
3361
|
-
channelId: channel.id,
|
|
3362
|
-
serverId: guild.id,
|
|
3363
|
-
worldId
|
|
3364
|
-
});
|
|
3373
|
+
});
|
|
3374
|
+
await runtime.ensureRoomExists({
|
|
3375
|
+
id: roomId,
|
|
3376
|
+
name: channel.name,
|
|
3377
|
+
source: "discord",
|
|
3378
|
+
type: ChannelType9.GROUP,
|
|
3379
|
+
channelId: channel.id,
|
|
3380
|
+
serverId: guild.id,
|
|
3381
|
+
worldId
|
|
3382
|
+
});
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
/**
|
|
3386
|
+
* Set up event listeners for the client
|
|
3387
|
+
*/
|
|
3388
|
+
setupEventListeners() {
|
|
3389
|
+
if (!this.client) {
|
|
3390
|
+
return;
|
|
3391
|
+
}
|
|
3392
|
+
this.client.on("messageCreate", (message) => {
|
|
3393
|
+
if (message.author.id === this.client?.user?.id || message.author.bot) {
|
|
3394
|
+
return;
|
|
3395
|
+
}
|
|
3396
|
+
try {
|
|
3397
|
+
this.messageManager.handleMessage(message);
|
|
3398
|
+
} catch (error) {
|
|
3399
|
+
logger6.error(`Error handling message: ${error}`);
|
|
3400
|
+
}
|
|
3401
|
+
});
|
|
3402
|
+
this.client.on("messageReactionAdd", async (reaction, user) => {
|
|
3403
|
+
if (user.id === this.client?.user?.id) {
|
|
3404
|
+
return;
|
|
3405
|
+
}
|
|
3406
|
+
try {
|
|
3407
|
+
await this.handleReactionAdd(reaction, user);
|
|
3408
|
+
} catch (error) {
|
|
3409
|
+
logger6.error(`Error handling reaction add: ${error}`);
|
|
3410
|
+
}
|
|
3411
|
+
});
|
|
3412
|
+
this.client.on("messageReactionRemove", async (reaction, user) => {
|
|
3413
|
+
if (user.id === this.client?.user?.id) {
|
|
3414
|
+
return;
|
|
3415
|
+
}
|
|
3416
|
+
try {
|
|
3417
|
+
await this.handleReactionRemove(reaction, user);
|
|
3418
|
+
} catch (error) {
|
|
3419
|
+
logger6.error(`Error handling reaction remove: ${error}`);
|
|
3420
|
+
}
|
|
3421
|
+
});
|
|
3422
|
+
this.client.on("guildCreate", async (guild) => {
|
|
3423
|
+
try {
|
|
3424
|
+
await this.handleGuildCreate(guild);
|
|
3425
|
+
} catch (error) {
|
|
3426
|
+
logger6.error(`Error handling guild create: ${error}`);
|
|
3427
|
+
}
|
|
3428
|
+
});
|
|
3429
|
+
this.client.on("guildMemberAdd", async (member) => {
|
|
3430
|
+
try {
|
|
3431
|
+
await this.handleGuildMemberAdd(member);
|
|
3432
|
+
} catch (error) {
|
|
3433
|
+
logger6.error(`Error handling guild member add: ${error}`);
|
|
3434
|
+
}
|
|
3435
|
+
});
|
|
3436
|
+
this.client.on("interactionCreate", async (interaction) => {
|
|
3437
|
+
try {
|
|
3438
|
+
await this.handleInteractionCreate(interaction);
|
|
3439
|
+
} catch (error) {
|
|
3440
|
+
logger6.error(`Error handling interaction: ${error}`);
|
|
3441
|
+
}
|
|
3442
|
+
});
|
|
3443
|
+
}
|
|
3444
|
+
/**
|
|
3445
|
+
* Handles the event when a new member joins a guild.
|
|
3446
|
+
*
|
|
3447
|
+
* @param {GuildMember} member - The GuildMember object representing the new member that joined the guild.
|
|
3448
|
+
* @returns {Promise<void>} - A Promise that resolves once the event handling is complete.
|
|
3449
|
+
*/
|
|
3450
|
+
async handleGuildMemberAdd(member) {
|
|
3451
|
+
logger6.log(`New member joined: ${member.user.username}`);
|
|
3452
|
+
const guild = member.guild;
|
|
3453
|
+
const tag = member.user.bot ? `${member.user.username}#${member.user.discriminator}` : member.user.username;
|
|
3454
|
+
this.runtime.emitEvent([EventTypes2.ENTITY_JOINED], {
|
|
3455
|
+
runtime: this.runtime,
|
|
3456
|
+
entityId: createUniqueUuid6(this.runtime, member.id),
|
|
3457
|
+
worldId: createUniqueUuid6(this.runtime, guild.id),
|
|
3458
|
+
source: "discord",
|
|
3459
|
+
metadata: {
|
|
3460
|
+
originalId: member.id,
|
|
3461
|
+
username: tag,
|
|
3462
|
+
displayName: member.displayName || member.user.username,
|
|
3463
|
+
roles: member.roles.cache.map((r) => r.name),
|
|
3464
|
+
joinedAt: member.joinedAt?.getTime()
|
|
3465
|
+
}
|
|
3466
|
+
});
|
|
3467
|
+
this.runtime.emitEvent(["DISCORD_USER_JOINED" /* ENTITY_JOINED */], {
|
|
3468
|
+
runtime: this.runtime,
|
|
3469
|
+
entityId: createUniqueUuid6(this.runtime, member.id),
|
|
3470
|
+
member,
|
|
3471
|
+
guild
|
|
3472
|
+
});
|
|
3473
|
+
}
|
|
3474
|
+
/**
|
|
3475
|
+
*
|
|
3476
|
+
* Start the Discord service
|
|
3477
|
+
* @param {IAgentRuntime} runtime - The runtime for the agent
|
|
3478
|
+
* @returns {Promise<DiscordService>} A promise that resolves to a DiscordService instance
|
|
3479
|
+
*
|
|
3480
|
+
*/
|
|
3481
|
+
static async start(runtime) {
|
|
3482
|
+
const token = runtime.getSetting("DISCORD_API_TOKEN");
|
|
3483
|
+
if (!token || token.trim() === "") {
|
|
3484
|
+
throw new Error("Discord API Token not provided");
|
|
3485
|
+
}
|
|
3486
|
+
const maxRetries = 5;
|
|
3487
|
+
let retryCount = 0;
|
|
3488
|
+
let lastError = null;
|
|
3489
|
+
while (retryCount < maxRetries) {
|
|
3490
|
+
try {
|
|
3491
|
+
const service = new _DiscordService(runtime);
|
|
3492
|
+
if (!service.client) {
|
|
3493
|
+
throw new Error("Failed to initialize Discord client");
|
|
3494
|
+
}
|
|
3495
|
+
await new Promise((resolve, reject) => {
|
|
3496
|
+
const timeout = setTimeout(() => {
|
|
3497
|
+
reject(new Error("Discord client ready timeout"));
|
|
3498
|
+
}, 3e4);
|
|
3499
|
+
service.client?.once("ready", () => {
|
|
3500
|
+
clearTimeout(timeout);
|
|
3501
|
+
resolve();
|
|
3502
|
+
});
|
|
3503
|
+
});
|
|
3504
|
+
return service;
|
|
3505
|
+
} catch (error) {
|
|
3506
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
3507
|
+
logger6.error(`Discord initialization attempt ${retryCount + 1} failed: ${lastError.message}`);
|
|
3508
|
+
retryCount++;
|
|
3509
|
+
if (retryCount < maxRetries) {
|
|
3510
|
+
const delay = 2 ** retryCount * 1e3;
|
|
3511
|
+
logger6.info(`Retrying Discord initialization in ${delay / 1e3} seconds...`);
|
|
3512
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
3513
|
+
}
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
throw new Error(`Discord initialization failed after ${maxRetries} attempts. Last error: ${lastError?.message}`);
|
|
3517
|
+
}
|
|
3518
|
+
/**
|
|
3519
|
+
* Stops the Discord client associated with the given runtime.
|
|
3520
|
+
*
|
|
3521
|
+
* @param {IAgentRuntime} runtime - The runtime associated with the Discord client.
|
|
3522
|
+
* @returns {void}
|
|
3523
|
+
*/
|
|
3524
|
+
static async stop(runtime) {
|
|
3525
|
+
const client = runtime.getService(DISCORD_SERVICE_NAME);
|
|
3526
|
+
if (!client) {
|
|
3527
|
+
logger6.error("DiscordService not found");
|
|
3528
|
+
return;
|
|
3529
|
+
}
|
|
3530
|
+
try {
|
|
3531
|
+
await client.stop();
|
|
3532
|
+
} catch (e) {
|
|
3533
|
+
logger6.error("client-discord instance stop err", e);
|
|
3534
|
+
}
|
|
3535
|
+
}
|
|
3536
|
+
/**
|
|
3537
|
+
* Asynchronously stops the client by destroying it.
|
|
3538
|
+
*
|
|
3539
|
+
* @returns {Promise<void>}
|
|
3540
|
+
*/
|
|
3541
|
+
async stop() {
|
|
3542
|
+
await this.client?.destroy();
|
|
3543
|
+
}
|
|
3544
|
+
/**
|
|
3545
|
+
* Handle the event when the client is ready.
|
|
3546
|
+
* @param {Object} readyClient - The ready client object containing user information.
|
|
3547
|
+
* @param {string} readyClient.user.tag - The username and discriminator of the client user.
|
|
3548
|
+
* @param {string} readyClient.user.id - The user ID of the client.
|
|
3549
|
+
* @returns {Promise<void>}
|
|
3550
|
+
*/
|
|
3551
|
+
async onClientReady(readyClient) {
|
|
3552
|
+
logger6.success(`DISCORD: Logged in as ${readyClient.user?.tag}`);
|
|
3553
|
+
const commands = [
|
|
3554
|
+
{
|
|
3555
|
+
name: "joinchannel",
|
|
3556
|
+
description: "Join a voice channel",
|
|
3557
|
+
options: [
|
|
3558
|
+
{
|
|
3559
|
+
name: "channel",
|
|
3560
|
+
type: 7,
|
|
3561
|
+
// CHANNEL type
|
|
3562
|
+
description: "The voice channel to join",
|
|
3563
|
+
required: true,
|
|
3564
|
+
channel_types: [2]
|
|
3565
|
+
// GuildVoice type
|
|
3566
|
+
}
|
|
3567
|
+
]
|
|
3568
|
+
},
|
|
3569
|
+
{
|
|
3570
|
+
name: "leavechannel",
|
|
3571
|
+
description: "Leave the current voice channel"
|
|
3572
|
+
}
|
|
3573
|
+
];
|
|
3574
|
+
try {
|
|
3575
|
+
await this.client?.application?.commands.set(commands);
|
|
3576
|
+
logger6.success("DISCORD: Slash commands registered");
|
|
3577
|
+
} catch (error) {
|
|
3578
|
+
console.error("Error registering slash commands:", error);
|
|
3579
|
+
}
|
|
3580
|
+
const requiredPermissions = [
|
|
3581
|
+
// Text Permissions
|
|
3582
|
+
PermissionsBitField2.Flags.ViewChannel,
|
|
3583
|
+
PermissionsBitField2.Flags.SendMessages,
|
|
3584
|
+
PermissionsBitField2.Flags.SendMessagesInThreads,
|
|
3585
|
+
PermissionsBitField2.Flags.CreatePrivateThreads,
|
|
3586
|
+
PermissionsBitField2.Flags.CreatePublicThreads,
|
|
3587
|
+
PermissionsBitField2.Flags.EmbedLinks,
|
|
3588
|
+
PermissionsBitField2.Flags.AttachFiles,
|
|
3589
|
+
PermissionsBitField2.Flags.AddReactions,
|
|
3590
|
+
PermissionsBitField2.Flags.UseExternalEmojis,
|
|
3591
|
+
PermissionsBitField2.Flags.UseExternalStickers,
|
|
3592
|
+
PermissionsBitField2.Flags.MentionEveryone,
|
|
3593
|
+
PermissionsBitField2.Flags.ManageMessages,
|
|
3594
|
+
PermissionsBitField2.Flags.ReadMessageHistory,
|
|
3595
|
+
// Voice Permissions
|
|
3596
|
+
PermissionsBitField2.Flags.Connect,
|
|
3597
|
+
PermissionsBitField2.Flags.Speak,
|
|
3598
|
+
PermissionsBitField2.Flags.UseVAD,
|
|
3599
|
+
PermissionsBitField2.Flags.PrioritySpeaker
|
|
3600
|
+
].reduce((a, b) => a | b, 0n);
|
|
3601
|
+
logger6.success("Use this URL to add the bot to your server:");
|
|
3602
|
+
logger6.success(
|
|
3603
|
+
`https://discord.com/api/oauth2/authorize?client_id=${readyClient.user?.id}&permissions=${requiredPermissions}&scope=bot%20applications.commands`
|
|
3604
|
+
);
|
|
3605
|
+
await this.onReady();
|
|
3606
|
+
}
|
|
3607
|
+
/**
|
|
3608
|
+
* Asynchronously retrieves the type of a given channel.
|
|
3609
|
+
*
|
|
3610
|
+
* @param {Channel} channel - The channel for which to determine the type.
|
|
3611
|
+
* @returns {Promise<ChannelType>} A Promise that resolves with the type of the channel.
|
|
3612
|
+
*/
|
|
3613
|
+
async getChannelType(channel) {
|
|
3614
|
+
switch (channel.type) {
|
|
3615
|
+
case DiscordChannelType4.DM:
|
|
3616
|
+
return ChannelType9.DM;
|
|
3617
|
+
case DiscordChannelType4.GuildText:
|
|
3618
|
+
return ChannelType9.GROUP;
|
|
3619
|
+
case DiscordChannelType4.GuildVoice:
|
|
3620
|
+
return ChannelType9.VOICE_GROUP;
|
|
3621
|
+
}
|
|
3622
|
+
}
|
|
3623
|
+
/**
|
|
3624
|
+
* Handles the addition of a reaction on a message.
|
|
3625
|
+
*
|
|
3626
|
+
* @param {MessageReaction | PartialMessageReaction} reaction The reaction that was added.
|
|
3627
|
+
* @param {User | PartialUser} user The user who added the reaction.
|
|
3628
|
+
* @returns {void}
|
|
3629
|
+
*/
|
|
3630
|
+
async handleReactionAdd(reaction, user) {
|
|
3631
|
+
try {
|
|
3632
|
+
logger6.log("Reaction added");
|
|
3633
|
+
if (!reaction || !user) {
|
|
3634
|
+
logger6.warn("Invalid reaction or user");
|
|
3635
|
+
return;
|
|
3636
|
+
}
|
|
3637
|
+
let emoji = reaction.emoji.name;
|
|
3638
|
+
if (!emoji && reaction.emoji.id) {
|
|
3639
|
+
emoji = `<:${reaction.emoji.name}:${reaction.emoji.id}>`;
|
|
3640
|
+
}
|
|
3641
|
+
if (reaction.partial) {
|
|
3642
|
+
try {
|
|
3643
|
+
await reaction.fetch();
|
|
3644
|
+
} catch (error) {
|
|
3645
|
+
logger6.error("Failed to fetch partial reaction:", error);
|
|
3646
|
+
return;
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
const timestamp = Date.now();
|
|
3650
|
+
const roomId = createUniqueUuid6(
|
|
3651
|
+
this.runtime,
|
|
3652
|
+
reaction.message.channel.id
|
|
3653
|
+
);
|
|
3654
|
+
const entityId = createUniqueUuid6(this.runtime, user.id);
|
|
3655
|
+
const reactionUUID = createUniqueUuid6(
|
|
3656
|
+
this.runtime,
|
|
3657
|
+
`${reaction.message.id}-${user.id}-${emoji}-${timestamp}`
|
|
3658
|
+
);
|
|
3659
|
+
if (!entityId || !roomId) {
|
|
3660
|
+
logger6.error("Invalid user ID or room ID", {
|
|
3661
|
+
entityId,
|
|
3662
|
+
roomId
|
|
3663
|
+
});
|
|
3664
|
+
return;
|
|
3665
|
+
}
|
|
3666
|
+
const messageContent = reaction.message.content || "";
|
|
3667
|
+
const truncatedContent = messageContent.length > 50 ? `${messageContent.substring(0, 50)}...` : messageContent;
|
|
3668
|
+
const reactionMessage = `*Added <${emoji}> to: "${truncatedContent}"*`;
|
|
3669
|
+
const userName = reaction.message.author?.username || "unknown";
|
|
3670
|
+
const name = reaction.message.author?.displayName || userName;
|
|
3671
|
+
await this.runtime.ensureConnection({
|
|
3672
|
+
entityId,
|
|
3673
|
+
roomId,
|
|
3674
|
+
userName,
|
|
3675
|
+
name,
|
|
3676
|
+
source: "discord",
|
|
3677
|
+
channelId: reaction.message.channel.id,
|
|
3678
|
+
serverId: reaction.message.guild?.id,
|
|
3679
|
+
type: await this.getChannelType(reaction.message.channel)
|
|
3680
|
+
});
|
|
3681
|
+
const inReplyTo = createUniqueUuid6(this.runtime, reaction.message.id);
|
|
3682
|
+
const memory = {
|
|
3683
|
+
id: reactionUUID,
|
|
3684
|
+
entityId,
|
|
3685
|
+
agentId: this.runtime.agentId,
|
|
3686
|
+
content: {
|
|
3687
|
+
// name,
|
|
3688
|
+
// userName,
|
|
3689
|
+
text: reactionMessage,
|
|
3690
|
+
source: "discord",
|
|
3691
|
+
inReplyTo,
|
|
3692
|
+
channelType: await this.getChannelType(
|
|
3693
|
+
reaction.message.channel
|
|
3694
|
+
)
|
|
3695
|
+
},
|
|
3696
|
+
roomId,
|
|
3697
|
+
createdAt: timestamp
|
|
3698
|
+
};
|
|
3699
|
+
const callback = async (content) => {
|
|
3700
|
+
if (!reaction.message.channel) {
|
|
3701
|
+
logger6.error("No channel found for reaction message");
|
|
3702
|
+
return;
|
|
3703
|
+
}
|
|
3704
|
+
await reaction.message.channel.send(content.text);
|
|
3705
|
+
return [];
|
|
3706
|
+
};
|
|
3707
|
+
this.runtime.emitEvent(
|
|
3708
|
+
["DISCORD_REACTION_RECEIVED", "REACTION_RECEIVED"],
|
|
3709
|
+
{
|
|
3710
|
+
runtime: this.runtime,
|
|
3711
|
+
message: memory,
|
|
3712
|
+
callback
|
|
3713
|
+
}
|
|
3714
|
+
);
|
|
3715
|
+
} catch (error) {
|
|
3716
|
+
logger6.error("Error handling reaction:", error);
|
|
3717
|
+
}
|
|
3718
|
+
}
|
|
3719
|
+
/**
|
|
3720
|
+
* Handles the removal of a reaction on a message.
|
|
3721
|
+
*
|
|
3722
|
+
* @param {MessageReaction | PartialMessageReaction} reaction - The reaction that was removed.
|
|
3723
|
+
* @param {User | PartialUser} user - The user who removed the reaction.
|
|
3724
|
+
* @returns {Promise<void>} - A Promise that resolves after handling the reaction removal.
|
|
3725
|
+
*/
|
|
3726
|
+
async handleReactionRemove(reaction, user) {
|
|
3727
|
+
try {
|
|
3728
|
+
logger6.log("Reaction removed");
|
|
3729
|
+
let emoji = reaction.emoji.name;
|
|
3730
|
+
if (!emoji && reaction.emoji.id) {
|
|
3731
|
+
emoji = `<:${reaction.emoji.name}:${reaction.emoji.id}>`;
|
|
3732
|
+
}
|
|
3733
|
+
if (reaction.partial) {
|
|
3734
|
+
try {
|
|
3735
|
+
await reaction.fetch();
|
|
3736
|
+
} catch (error) {
|
|
3737
|
+
logger6.error(
|
|
3738
|
+
"Something went wrong when fetching the message:",
|
|
3739
|
+
error
|
|
3740
|
+
);
|
|
3741
|
+
return;
|
|
3742
|
+
}
|
|
3743
|
+
}
|
|
3744
|
+
const messageContent = reaction.message.content || "";
|
|
3745
|
+
const truncatedContent = messageContent.length > 50 ? `${messageContent.substring(0, 50)}...` : messageContent;
|
|
3746
|
+
const reactionMessage = `*Removed <${emoji}> from: "${truncatedContent}"*`;
|
|
3747
|
+
const roomId = createUniqueUuid6(
|
|
3748
|
+
this.runtime,
|
|
3749
|
+
reaction.message.channel.id
|
|
3750
|
+
);
|
|
3751
|
+
const entityId = createUniqueUuid6(this.runtime, user.id);
|
|
3752
|
+
const timestamp = Date.now();
|
|
3753
|
+
const reactionUUID = createUniqueUuid6(
|
|
3754
|
+
this.runtime,
|
|
3755
|
+
`${reaction.message.id}-${user.id}-${emoji}-${timestamp}`
|
|
3756
|
+
);
|
|
3757
|
+
const userName = reaction.message.author?.username || "unknown";
|
|
3758
|
+
const name = reaction.message.author?.displayName || userName;
|
|
3759
|
+
await this.runtime.ensureConnection({
|
|
3760
|
+
entityId,
|
|
3761
|
+
roomId,
|
|
3762
|
+
userName,
|
|
3763
|
+
name,
|
|
3764
|
+
source: "discord",
|
|
3765
|
+
channelId: reaction.message.channel.id,
|
|
3766
|
+
serverId: reaction.message.guild?.id,
|
|
3767
|
+
type: await this.getChannelType(reaction.message.channel)
|
|
3768
|
+
});
|
|
3769
|
+
const memory = {
|
|
3770
|
+
id: reactionUUID,
|
|
3771
|
+
entityId,
|
|
3772
|
+
agentId: this.runtime.agentId,
|
|
3773
|
+
content: {
|
|
3774
|
+
// name,
|
|
3775
|
+
// userName,
|
|
3776
|
+
text: reactionMessage,
|
|
3777
|
+
source: "discord",
|
|
3778
|
+
inReplyTo: createUniqueUuid6(this.runtime, reaction.message.id),
|
|
3779
|
+
channelType: await this.getChannelType(
|
|
3780
|
+
reaction.message.channel
|
|
3781
|
+
)
|
|
3782
|
+
},
|
|
3783
|
+
roomId,
|
|
3784
|
+
createdAt: Date.now()
|
|
3785
|
+
};
|
|
3786
|
+
const callback = async (content) => {
|
|
3787
|
+
if (!reaction.message.channel) {
|
|
3788
|
+
logger6.error("No channel found for reaction message");
|
|
3789
|
+
return;
|
|
3790
|
+
}
|
|
3791
|
+
await reaction.message.channel.send(content.text);
|
|
3792
|
+
return [];
|
|
3793
|
+
};
|
|
3794
|
+
this.runtime.emitEvent(["DISCORD_REACTION_RECEIVED" /* REACTION_RECEIVED */], {
|
|
3795
|
+
runtime: this.runtime,
|
|
3796
|
+
message: memory,
|
|
3797
|
+
callback
|
|
3798
|
+
});
|
|
3799
|
+
} catch (error) {
|
|
3800
|
+
logger6.error("Error handling reaction removal:", error);
|
|
3801
|
+
}
|
|
3802
|
+
}
|
|
3803
|
+
/**
|
|
3804
|
+
* Handles the event when the bot joins a guild. It logs the guild name, fetches additional information about the guild, scans the guild for voice data, creates standardized world data structure, generates unique IDs, and emits events to the runtime.
|
|
3805
|
+
* @param {Guild} guild - The guild that the bot has joined.
|
|
3806
|
+
* @returns {Promise<void>}
|
|
3807
|
+
*/
|
|
3808
|
+
async handleGuildCreate(guild) {
|
|
3809
|
+
logger6.log(`Joined guild ${guild.name}`);
|
|
3810
|
+
const fullGuild = await guild.fetch();
|
|
3811
|
+
this.voiceManager.scanGuild(guild);
|
|
3812
|
+
const ownerId = createUniqueUuid6(this.runtime, fullGuild.ownerId);
|
|
3813
|
+
const worldId = createUniqueUuid6(this.runtime, fullGuild.id);
|
|
3814
|
+
const standardizedData = {
|
|
3815
|
+
runtime: this.runtime,
|
|
3816
|
+
rooms: await this.buildStandardizedRooms(fullGuild, worldId),
|
|
3817
|
+
users: await this.buildStandardizedUsers(fullGuild),
|
|
3818
|
+
world: {
|
|
3819
|
+
id: worldId,
|
|
3820
|
+
name: fullGuild.name,
|
|
3821
|
+
agentId: this.runtime.agentId,
|
|
3822
|
+
serverId: fullGuild.id,
|
|
3823
|
+
metadata: {
|
|
3824
|
+
ownership: fullGuild.ownerId ? { ownerId } : void 0,
|
|
3825
|
+
roles: {
|
|
3826
|
+
[ownerId]: Role.OWNER
|
|
3827
|
+
}
|
|
3828
|
+
}
|
|
3829
|
+
},
|
|
3830
|
+
source: "discord"
|
|
3831
|
+
};
|
|
3832
|
+
this.runtime.emitEvent(["DISCORD_WORLD_JOINED" /* WORLD_JOINED */], {
|
|
3833
|
+
runtime: this.runtime,
|
|
3834
|
+
server: fullGuild,
|
|
3835
|
+
source: "discord"
|
|
3836
|
+
});
|
|
3837
|
+
this.runtime.emitEvent([EventTypes2.WORLD_JOINED], standardizedData);
|
|
3838
|
+
}
|
|
3839
|
+
/**
|
|
3840
|
+
* Handles interactions created by the user, specifically commands.
|
|
3841
|
+
* @param {any} interaction - The interaction object received
|
|
3842
|
+
* @returns {void}
|
|
3843
|
+
*/
|
|
3844
|
+
async handleInteractionCreate(interaction) {
|
|
3845
|
+
if (!interaction.isCommand()) return;
|
|
3846
|
+
switch (interaction.commandName) {
|
|
3847
|
+
case "joinchannel":
|
|
3848
|
+
await this.voiceManager.handleJoinChannelCommand(interaction);
|
|
3849
|
+
break;
|
|
3850
|
+
case "leavechannel":
|
|
3851
|
+
await this.voiceManager.handleLeaveChannelCommand(interaction);
|
|
3852
|
+
break;
|
|
3853
|
+
}
|
|
3854
|
+
}
|
|
3855
|
+
/**
|
|
3856
|
+
* Builds a standardized list of rooms from Discord guild channels
|
|
3857
|
+
*/
|
|
3858
|
+
/**
|
|
3859
|
+
* Build standardized rooms for a guild based on text and voice channels.
|
|
3860
|
+
*
|
|
3861
|
+
* @param {Guild} guild The guild to build rooms for.
|
|
3862
|
+
* @param {UUID} _worldId The ID of the world to associate with the rooms.
|
|
3863
|
+
* @returns {Promise<any[]>} An array of standardized room objects.
|
|
3864
|
+
*/
|
|
3865
|
+
async buildStandardizedRooms(guild, _worldId) {
|
|
3866
|
+
const rooms = [];
|
|
3867
|
+
for (const [channelId, channel] of guild.channels.cache) {
|
|
3868
|
+
if (channel.type === DiscordChannelType4.GuildText || channel.type === DiscordChannelType4.GuildVoice) {
|
|
3869
|
+
const roomId = createUniqueUuid6(this.runtime, channelId);
|
|
3870
|
+
let channelType;
|
|
3871
|
+
switch (channel.type) {
|
|
3872
|
+
case DiscordChannelType4.GuildText:
|
|
3873
|
+
channelType = ChannelType9.GROUP;
|
|
3874
|
+
break;
|
|
3875
|
+
case DiscordChannelType4.GuildVoice:
|
|
3876
|
+
channelType = ChannelType9.VOICE_GROUP;
|
|
3877
|
+
break;
|
|
3878
|
+
default:
|
|
3879
|
+
channelType = ChannelType9.GROUP;
|
|
3880
|
+
}
|
|
3881
|
+
let participants = [];
|
|
3882
|
+
if (guild.memberCount < 1e3 && channel.type === DiscordChannelType4.GuildText) {
|
|
3883
|
+
try {
|
|
3884
|
+
participants = Array.from(guild.members.cache.values()).filter(
|
|
3885
|
+
(member) => channel.permissionsFor(member)?.has(PermissionsBitField2.Flags.ViewChannel)
|
|
3886
|
+
).map((member) => createUniqueUuid6(this.runtime, member.id));
|
|
3887
|
+
} catch (error) {
|
|
3888
|
+
logger6.warn(
|
|
3889
|
+
`Failed to get participants for channel ${channel.name}:`,
|
|
3890
|
+
error
|
|
3891
|
+
);
|
|
3892
|
+
}
|
|
3893
|
+
}
|
|
3894
|
+
rooms.push({
|
|
3895
|
+
id: roomId,
|
|
3896
|
+
name: channel.name,
|
|
3897
|
+
type: channelType,
|
|
3898
|
+
channelId: channel.id,
|
|
3899
|
+
participants
|
|
3900
|
+
});
|
|
3901
|
+
}
|
|
3902
|
+
}
|
|
3903
|
+
return rooms;
|
|
3904
|
+
}
|
|
3905
|
+
/**
|
|
3906
|
+
* Builds a standardized list of users from Discord guild members
|
|
3907
|
+
*/
|
|
3908
|
+
async buildStandardizedUsers(guild) {
|
|
3909
|
+
const entities = [];
|
|
3910
|
+
const botId = this.client?.user?.id;
|
|
3911
|
+
if (guild.memberCount > 1e3) {
|
|
3912
|
+
logger6.info(
|
|
3913
|
+
`Using optimized user sync for large guild ${guild.name} (${guild.memberCount} members)`
|
|
3914
|
+
);
|
|
3915
|
+
try {
|
|
3916
|
+
for (const [, member] of guild.members.cache) {
|
|
3917
|
+
const tag = member.user.bot ? `${member.user.username}#${member.user.discriminator}` : member.user.username;
|
|
3918
|
+
if (member.id !== botId) {
|
|
3919
|
+
entities.push({
|
|
3920
|
+
id: createUniqueUuid6(this.runtime, member.id),
|
|
3921
|
+
names: Array.from(
|
|
3922
|
+
/* @__PURE__ */ new Set([
|
|
3923
|
+
member.user.username,
|
|
3924
|
+
member.displayName,
|
|
3925
|
+
member.user.globalName
|
|
3926
|
+
])
|
|
3927
|
+
),
|
|
3928
|
+
agentId: this.runtime.agentId,
|
|
3929
|
+
metadata: {
|
|
3930
|
+
default: {
|
|
3931
|
+
username: tag,
|
|
3932
|
+
name: member.displayName || member.user.username
|
|
3933
|
+
},
|
|
3934
|
+
discord: member.user.globalName ? {
|
|
3935
|
+
username: tag,
|
|
3936
|
+
name: member.displayName || member.user.username,
|
|
3937
|
+
globalName: member.user.globalName,
|
|
3938
|
+
userId: member.id
|
|
3939
|
+
} : {
|
|
3940
|
+
username: tag,
|
|
3941
|
+
name: member.displayName || member.user.username,
|
|
3942
|
+
userId: member.id
|
|
3943
|
+
}
|
|
3944
|
+
}
|
|
3945
|
+
});
|
|
3946
|
+
}
|
|
3947
|
+
}
|
|
3948
|
+
if (entities.length < 100) {
|
|
3949
|
+
logger6.info(`Adding online members for ${guild.name}`);
|
|
3950
|
+
const onlineMembers = await guild.members.fetch({ limit: 100 });
|
|
3951
|
+
for (const [, member] of onlineMembers) {
|
|
3952
|
+
if (member.id !== botId) {
|
|
3953
|
+
const entityId = createUniqueUuid6(this.runtime, member.id);
|
|
3954
|
+
if (!entities.some((u) => u.id === entityId)) {
|
|
3955
|
+
const tag = member.user.bot ? `${member.user.username}#${member.user.discriminator}` : member.user.username;
|
|
3956
|
+
entities.push({
|
|
3957
|
+
id: entityId,
|
|
3958
|
+
names: Array.from(
|
|
3959
|
+
/* @__PURE__ */ new Set([
|
|
3960
|
+
member.user.username,
|
|
3961
|
+
member.displayName,
|
|
3962
|
+
member.user.globalName
|
|
3963
|
+
])
|
|
3964
|
+
),
|
|
3965
|
+
agentId: this.runtime.agentId,
|
|
3966
|
+
metadata: {
|
|
3967
|
+
default: {
|
|
3968
|
+
username: tag,
|
|
3969
|
+
name: member.displayName || member.user.username
|
|
3970
|
+
},
|
|
3971
|
+
discord: member.user.globalName ? {
|
|
3972
|
+
username: tag,
|
|
3973
|
+
name: member.displayName || member.user.username,
|
|
3974
|
+
globalName: member.user.globalName,
|
|
3975
|
+
userId: member.id
|
|
3976
|
+
} : {
|
|
3977
|
+
username: tag,
|
|
3978
|
+
name: member.displayName || member.user.username,
|
|
3979
|
+
userId: member.id
|
|
3980
|
+
}
|
|
3981
|
+
}
|
|
3982
|
+
});
|
|
3983
|
+
}
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3986
|
+
}
|
|
3987
|
+
} catch (error) {
|
|
3988
|
+
logger6.error(`Error fetching members for ${guild.name}:`, error);
|
|
3989
|
+
}
|
|
3990
|
+
} else {
|
|
3991
|
+
try {
|
|
3992
|
+
let members = guild.members.cache;
|
|
3993
|
+
if (members.size === 0) {
|
|
3994
|
+
members = await guild.members.fetch();
|
|
3995
|
+
}
|
|
3996
|
+
for (const [, member] of members) {
|
|
3997
|
+
if (member.id !== botId) {
|
|
3998
|
+
const tag = member.user.bot ? `${member.user.username}#${member.user.discriminator}` : member.user.username;
|
|
3999
|
+
entities.push({
|
|
4000
|
+
id: createUniqueUuid6(this.runtime, member.id),
|
|
4001
|
+
names: Array.from(
|
|
4002
|
+
/* @__PURE__ */ new Set([
|
|
4003
|
+
member.user.username,
|
|
4004
|
+
member.displayName,
|
|
4005
|
+
member.user.globalName
|
|
4006
|
+
])
|
|
4007
|
+
),
|
|
4008
|
+
agentId: this.runtime.agentId,
|
|
4009
|
+
metadata: {
|
|
4010
|
+
default: {
|
|
4011
|
+
username: tag,
|
|
4012
|
+
name: member.displayName || member.user.username
|
|
4013
|
+
},
|
|
4014
|
+
discord: member.user.globalName ? {
|
|
4015
|
+
username: tag,
|
|
4016
|
+
name: member.displayName || member.user.username,
|
|
4017
|
+
globalName: member.user.globalName,
|
|
4018
|
+
userId: member.id
|
|
4019
|
+
} : {
|
|
4020
|
+
username: tag,
|
|
4021
|
+
name: member.displayName || member.user.username,
|
|
4022
|
+
userId: member.id
|
|
4023
|
+
}
|
|
4024
|
+
}
|
|
4025
|
+
});
|
|
4026
|
+
}
|
|
4027
|
+
}
|
|
4028
|
+
} catch (error) {
|
|
4029
|
+
logger6.error(`Error fetching members for ${guild.name}:`, error);
|
|
4030
|
+
}
|
|
3365
4031
|
}
|
|
4032
|
+
return entities;
|
|
3366
4033
|
}
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
this.client.
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
username: tag,
|
|
3405
|
-
displayName: member.displayName || member.user.username
|
|
3406
|
-
},
|
|
3407
|
-
serverId: guild.id,
|
|
3408
|
-
channelId: null,
|
|
3409
|
-
// No specific channel for server joins
|
|
3410
|
-
channelType: ChannelType10.WORLD,
|
|
3411
|
-
source: "discord"
|
|
3412
|
-
});
|
|
3413
|
-
this.runtime.emitEvent("DISCORD_USER_JOINED", {
|
|
3414
|
-
runtime: this.runtime,
|
|
3415
|
-
entityId: createUniqueUuid6(this.runtime, member.id),
|
|
3416
|
-
member,
|
|
3417
|
-
guild
|
|
3418
|
-
});
|
|
3419
|
-
}
|
|
3420
|
-
static async start(runtime) {
|
|
3421
|
-
const client = new _DiscordService(runtime);
|
|
3422
|
-
return client;
|
|
3423
|
-
}
|
|
3424
|
-
static async stop(runtime) {
|
|
3425
|
-
const client = runtime.getService(DISCORD_SERVICE_NAME);
|
|
3426
|
-
if (!client) {
|
|
3427
|
-
logger7.error("DiscordService not found");
|
|
3428
|
-
return;
|
|
3429
|
-
}
|
|
3430
|
-
try {
|
|
3431
|
-
await client.stop();
|
|
3432
|
-
} catch (e) {
|
|
3433
|
-
logger7.error("client-discord instance stop err", e);
|
|
4034
|
+
async onReady() {
|
|
4035
|
+
logger6.log("DISCORD ON READY");
|
|
4036
|
+
const guilds = await this.client?.guilds.fetch();
|
|
4037
|
+
for (const [, guild] of guilds) {
|
|
4038
|
+
const fullGuild = await guild.fetch();
|
|
4039
|
+
await this.voiceManager.scanGuild(fullGuild);
|
|
4040
|
+
setTimeout(async () => {
|
|
4041
|
+
const fullGuild2 = await guild.fetch();
|
|
4042
|
+
logger6.log("DISCORD SERVER CONNECTED", fullGuild2.name);
|
|
4043
|
+
this.runtime.emitEvent(["DISCORD_SERVER_CONNECTED" /* WORLD_CONNECTED */], {
|
|
4044
|
+
runtime: this.runtime,
|
|
4045
|
+
server: fullGuild2,
|
|
4046
|
+
source: "discord"
|
|
4047
|
+
});
|
|
4048
|
+
const worldId = createUniqueUuid6(this.runtime, fullGuild2.id);
|
|
4049
|
+
const ownerId = createUniqueUuid6(this.runtime, fullGuild2.ownerId);
|
|
4050
|
+
const standardizedData = {
|
|
4051
|
+
name: fullGuild2.name,
|
|
4052
|
+
runtime: this.runtime,
|
|
4053
|
+
rooms: await this.buildStandardizedRooms(fullGuild2, worldId),
|
|
4054
|
+
entities: await this.buildStandardizedUsers(fullGuild2),
|
|
4055
|
+
world: {
|
|
4056
|
+
id: worldId,
|
|
4057
|
+
name: fullGuild2.name,
|
|
4058
|
+
agentId: this.runtime.agentId,
|
|
4059
|
+
serverId: fullGuild2.id,
|
|
4060
|
+
metadata: {
|
|
4061
|
+
ownership: fullGuild2.ownerId ? { ownerId } : void 0,
|
|
4062
|
+
roles: {
|
|
4063
|
+
[ownerId]: Role.OWNER
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
},
|
|
4067
|
+
source: "discord"
|
|
4068
|
+
};
|
|
4069
|
+
this.runtime.emitEvent([EventTypes2.WORLD_CONNECTED], standardizedData);
|
|
4070
|
+
}, 1e3);
|
|
3434
4071
|
}
|
|
4072
|
+
this.client?.emit("voiceManagerReady");
|
|
3435
4073
|
}
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
4074
|
+
};
|
|
4075
|
+
|
|
4076
|
+
// src/tests.ts
|
|
4077
|
+
import {
|
|
4078
|
+
AudioPlayerStatus,
|
|
4079
|
+
NoSubscriberBehavior as NoSubscriberBehavior2,
|
|
4080
|
+
VoiceConnectionStatus as VoiceConnectionStatus2,
|
|
4081
|
+
createAudioPlayer as createAudioPlayer2,
|
|
4082
|
+
createAudioResource as createAudioResource2,
|
|
4083
|
+
entersState as entersState2
|
|
4084
|
+
} from "@discordjs/voice";
|
|
4085
|
+
import {
|
|
4086
|
+
ModelTypes as ModelTypes9,
|
|
4087
|
+
logger as logger7
|
|
4088
|
+
} from "@elizaos/core";
|
|
4089
|
+
import { ChannelType as ChannelType10, Events as Events2 } from "discord.js";
|
|
4090
|
+
var TEST_IMAGE_URL = "https://github.com/elizaOS/awesome-eliza/blob/main/assets/eliza-logo.jpg?raw=true";
|
|
4091
|
+
var DiscordTestSuite = class {
|
|
4092
|
+
name = "discord";
|
|
4093
|
+
discordClient = null;
|
|
4094
|
+
tests;
|
|
4095
|
+
/**
|
|
4096
|
+
* Constructor for initializing the tests array with test cases to be executed.
|
|
4097
|
+
*
|
|
4098
|
+
* @constructor
|
|
4099
|
+
* @this {TestSuite}
|
|
4100
|
+
*/
|
|
4101
|
+
constructor() {
|
|
4102
|
+
this.tests = [
|
|
3442
4103
|
{
|
|
3443
|
-
name: "
|
|
3444
|
-
|
|
3445
|
-
options: [
|
|
3446
|
-
{
|
|
3447
|
-
name: "channel",
|
|
3448
|
-
type: 7,
|
|
3449
|
-
// CHANNEL type
|
|
3450
|
-
description: "The voice channel to join",
|
|
3451
|
-
required: true,
|
|
3452
|
-
channel_types: [2]
|
|
3453
|
-
// GuildVoice type
|
|
3454
|
-
}
|
|
3455
|
-
]
|
|
4104
|
+
name: "Initialize Discord Client",
|
|
4105
|
+
fn: this.testCreatingDiscordClient.bind(this)
|
|
3456
4106
|
},
|
|
3457
4107
|
{
|
|
3458
|
-
name: "
|
|
3459
|
-
|
|
4108
|
+
name: "Slash Commands - Join Voice",
|
|
4109
|
+
fn: this.testJoinVoiceSlashCommand.bind(this)
|
|
4110
|
+
},
|
|
4111
|
+
{
|
|
4112
|
+
name: "Voice Playback & TTS",
|
|
4113
|
+
fn: this.testTextToSpeechPlayback.bind(this)
|
|
4114
|
+
},
|
|
4115
|
+
{
|
|
4116
|
+
name: "Send Message with Attachments",
|
|
4117
|
+
fn: this.testSendingTextMessage.bind(this)
|
|
4118
|
+
},
|
|
4119
|
+
{
|
|
4120
|
+
name: "Handle Incoming Messages",
|
|
4121
|
+
fn: this.testHandlingMessage.bind(this)
|
|
4122
|
+
},
|
|
4123
|
+
{
|
|
4124
|
+
name: "Slash Commands - Leave Voice",
|
|
4125
|
+
fn: this.testLeaveVoiceSlashCommand.bind(this)
|
|
3460
4126
|
}
|
|
3461
4127
|
];
|
|
4128
|
+
}
|
|
4129
|
+
/**
|
|
4130
|
+
* Asynchronously tests the creation of Discord client using the provided runtime.
|
|
4131
|
+
*
|
|
4132
|
+
* @param {IAgentRuntime} runtime - The agent runtime used to obtain the Discord service.
|
|
4133
|
+
* @returns {Promise<void>} - A Promise that resolves once the Discord client is ready.
|
|
4134
|
+
* @throws {Error} - If an error occurs while creating the Discord client.
|
|
4135
|
+
*/
|
|
4136
|
+
async testCreatingDiscordClient(runtime) {
|
|
3462
4137
|
try {
|
|
3463
|
-
|
|
3464
|
-
|
|
4138
|
+
this.discordClient = runtime.getService(
|
|
4139
|
+
ServiceTypes2.DISCORD
|
|
4140
|
+
);
|
|
4141
|
+
if (this.discordClient.client.isReady()) {
|
|
4142
|
+
logger7.success("DiscordService is already ready.");
|
|
4143
|
+
} else {
|
|
4144
|
+
logger7.info("Waiting for DiscordService to be ready...");
|
|
4145
|
+
await new Promise((resolve, reject) => {
|
|
4146
|
+
this.discordClient.client.once(Events2.ClientReady, resolve);
|
|
4147
|
+
this.discordClient.client.once(Events2.Error, reject);
|
|
4148
|
+
});
|
|
4149
|
+
}
|
|
3465
4150
|
} catch (error) {
|
|
3466
|
-
|
|
4151
|
+
throw new Error(`Error in test creating Discord client: ${error}`);
|
|
3467
4152
|
}
|
|
3468
|
-
const requiredPermissions = [
|
|
3469
|
-
// Text Permissions
|
|
3470
|
-
PermissionsBitField2.Flags.ViewChannel,
|
|
3471
|
-
PermissionsBitField2.Flags.SendMessages,
|
|
3472
|
-
PermissionsBitField2.Flags.SendMessagesInThreads,
|
|
3473
|
-
PermissionsBitField2.Flags.CreatePrivateThreads,
|
|
3474
|
-
PermissionsBitField2.Flags.CreatePublicThreads,
|
|
3475
|
-
PermissionsBitField2.Flags.EmbedLinks,
|
|
3476
|
-
PermissionsBitField2.Flags.AttachFiles,
|
|
3477
|
-
PermissionsBitField2.Flags.AddReactions,
|
|
3478
|
-
PermissionsBitField2.Flags.UseExternalEmojis,
|
|
3479
|
-
PermissionsBitField2.Flags.UseExternalStickers,
|
|
3480
|
-
PermissionsBitField2.Flags.MentionEveryone,
|
|
3481
|
-
PermissionsBitField2.Flags.ManageMessages,
|
|
3482
|
-
PermissionsBitField2.Flags.ReadMessageHistory,
|
|
3483
|
-
// Voice Permissions
|
|
3484
|
-
PermissionsBitField2.Flags.Connect,
|
|
3485
|
-
PermissionsBitField2.Flags.Speak,
|
|
3486
|
-
PermissionsBitField2.Flags.UseVAD,
|
|
3487
|
-
PermissionsBitField2.Flags.PrioritySpeaker
|
|
3488
|
-
].reduce((a, b) => a | b, 0n);
|
|
3489
|
-
logger7.success("Use this URL to add the bot to your server:");
|
|
3490
|
-
logger7.success(
|
|
3491
|
-
`https://discord.com/api/oauth2/authorize?client_id=${readyClient.user?.id}&permissions=${requiredPermissions}&scope=bot%20applications.commands`
|
|
3492
|
-
);
|
|
3493
|
-
await this.onReady();
|
|
3494
4153
|
}
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
4154
|
+
/**
|
|
4155
|
+
* Asynchronously tests the join voice slash command functionality.
|
|
4156
|
+
*
|
|
4157
|
+
* @param {IAgentRuntime} runtime - The runtime environment for the agent.
|
|
4158
|
+
* @returns {Promise<void>} - A promise that resolves once the test is complete.
|
|
4159
|
+
* @throws {Error} - If there is an error in executing the slash command test.
|
|
4160
|
+
*/
|
|
4161
|
+
async testJoinVoiceSlashCommand(runtime) {
|
|
4162
|
+
try {
|
|
4163
|
+
await this.waitForVoiceManagerReady(this.discordClient);
|
|
4164
|
+
const channel = await this.getTestChannel(runtime);
|
|
4165
|
+
if (!channel || !channel.isTextBased()) {
|
|
4166
|
+
throw new Error("Invalid test channel for slash command test.");
|
|
4167
|
+
}
|
|
4168
|
+
const fakeJoinInteraction = {
|
|
4169
|
+
isCommand: () => true,
|
|
4170
|
+
commandName: "joinchannel",
|
|
4171
|
+
options: {
|
|
4172
|
+
get: (name) => name === "channel" ? { value: channel.id } : null
|
|
4173
|
+
},
|
|
4174
|
+
guild: channel.guild,
|
|
4175
|
+
deferReply: async () => {
|
|
4176
|
+
},
|
|
4177
|
+
editReply: async (message) => {
|
|
4178
|
+
logger7.info(`JoinChannel Slash Command Response: ${message}`);
|
|
4179
|
+
}
|
|
4180
|
+
};
|
|
4181
|
+
await this.discordClient.voiceManager.handleJoinChannelCommand(
|
|
4182
|
+
fakeJoinInteraction
|
|
4183
|
+
);
|
|
4184
|
+
logger7.success("Slash command test completed successfully.");
|
|
4185
|
+
} catch (error) {
|
|
4186
|
+
throw new Error(`Error in slash commands test: ${error}`);
|
|
4187
|
+
}
|
|
4188
|
+
}
|
|
4189
|
+
/**
|
|
4190
|
+
* Asynchronously tests the leave voice channel slash command.
|
|
4191
|
+
*
|
|
4192
|
+
* @param {IAgentRuntime} runtime - The Agent Runtime instance.
|
|
4193
|
+
* @returns {Promise<void>} A promise that resolves when the test is complete.
|
|
4194
|
+
*/
|
|
4195
|
+
async testLeaveVoiceSlashCommand(runtime) {
|
|
4196
|
+
try {
|
|
4197
|
+
await this.waitForVoiceManagerReady(this.discordClient);
|
|
4198
|
+
const channel = await this.getTestChannel(runtime);
|
|
4199
|
+
if (!channel || !channel.isTextBased()) {
|
|
4200
|
+
throw new Error("Invalid test channel for slash command test.");
|
|
4201
|
+
}
|
|
4202
|
+
const fakeLeaveInteraction = {
|
|
4203
|
+
isCommand: () => true,
|
|
4204
|
+
commandName: "leavechannel",
|
|
4205
|
+
guildId: channel.guildId,
|
|
4206
|
+
reply: async (message) => {
|
|
4207
|
+
logger7.info(`LeaveChannel Slash Command Response: ${message}`);
|
|
4208
|
+
}
|
|
4209
|
+
};
|
|
4210
|
+
await this.discordClient.voiceManager.handleLeaveChannelCommand(
|
|
4211
|
+
fakeLeaveInteraction
|
|
4212
|
+
);
|
|
4213
|
+
logger7.success("Slash command test completed successfully.");
|
|
4214
|
+
} catch (error) {
|
|
4215
|
+
throw new Error(`Error in slash commands test: ${error}`);
|
|
3503
4216
|
}
|
|
3504
4217
|
}
|
|
3505
|
-
|
|
4218
|
+
/**
|
|
4219
|
+
* Test Text to Speech playback.
|
|
4220
|
+
* @param {IAgentRuntime} runtime - The Agent Runtime instance.
|
|
4221
|
+
* @throws {Error} - If voice channel is invalid, voice connection fails to become ready, or no text to speech service found.
|
|
4222
|
+
*/
|
|
4223
|
+
async testTextToSpeechPlayback(runtime) {
|
|
3506
4224
|
try {
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
4225
|
+
await this.waitForVoiceManagerReady(this.discordClient);
|
|
4226
|
+
const channel = await this.getTestChannel(runtime);
|
|
4227
|
+
if (!channel || channel.type !== ChannelType10.GuildVoice) {
|
|
4228
|
+
throw new Error("Invalid voice channel.");
|
|
3511
4229
|
}
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
4230
|
+
await this.discordClient.voiceManager.joinChannel(channel);
|
|
4231
|
+
const guild = await this.getActiveGuild(this.discordClient);
|
|
4232
|
+
const guildId = guild.id;
|
|
4233
|
+
const connection = this.discordClient.voiceManager.getVoiceConnection(guildId);
|
|
4234
|
+
try {
|
|
4235
|
+
await entersState2(connection, VoiceConnectionStatus2.Ready, 1e4);
|
|
4236
|
+
logger7.success(`Voice connection is ready in guild: ${guildId}`);
|
|
4237
|
+
} catch (error) {
|
|
4238
|
+
throw new Error(`Voice connection failed to become ready: ${error}`);
|
|
3515
4239
|
}
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
4240
|
+
let responseStream = null;
|
|
4241
|
+
try {
|
|
4242
|
+
responseStream = await runtime.useModel(
|
|
4243
|
+
ModelTypes9.TEXT_TO_SPEECH,
|
|
4244
|
+
`Hi! I'm ${runtime.character.name}! How are you doing today?`
|
|
4245
|
+
);
|
|
4246
|
+
} catch (_error) {
|
|
4247
|
+
throw new Error("No text to speech service found");
|
|
3523
4248
|
}
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
this.runtime,
|
|
3527
|
-
reaction.message.channel.id
|
|
3528
|
-
);
|
|
3529
|
-
const entityId = createUniqueUuid6(this.runtime, user.id);
|
|
3530
|
-
const reactionUUID = createUniqueUuid6(
|
|
3531
|
-
this.runtime,
|
|
3532
|
-
`${reaction.message.id}-${user.id}-${emoji}-${timestamp}`
|
|
3533
|
-
);
|
|
3534
|
-
if (!entityId || !roomId) {
|
|
3535
|
-
logger7.error("Invalid user ID or room ID", {
|
|
3536
|
-
entityId,
|
|
3537
|
-
roomId
|
|
3538
|
-
});
|
|
3539
|
-
return;
|
|
4249
|
+
if (!responseStream) {
|
|
4250
|
+
throw new Error("TTS response stream is null or undefined.");
|
|
3540
4251
|
}
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
4252
|
+
await this.playAudioStream(responseStream, connection);
|
|
4253
|
+
} catch (error) {
|
|
4254
|
+
throw new Error(`Error in TTS playback test: ${error}`);
|
|
4255
|
+
}
|
|
4256
|
+
}
|
|
4257
|
+
/**
|
|
4258
|
+
* Asynchronously tests sending a text message to a specified channel.
|
|
4259
|
+
*
|
|
4260
|
+
* @param {IAgentRuntime} runtime - The runtime for the agent.
|
|
4261
|
+
* @returns {Promise<void>} A Promise that resolves when the message is sent successfully.
|
|
4262
|
+
* @throws {Error} If there is an error in sending the text message.
|
|
4263
|
+
*/
|
|
4264
|
+
async testSendingTextMessage(runtime) {
|
|
4265
|
+
try {
|
|
4266
|
+
const channel = await this.getTestChannel(runtime);
|
|
4267
|
+
await this.sendMessageToChannel(
|
|
4268
|
+
channel,
|
|
4269
|
+
"Testing Message",
|
|
4270
|
+
[TEST_IMAGE_URL]
|
|
4271
|
+
);
|
|
4272
|
+
} catch (error) {
|
|
4273
|
+
throw new Error(`Error in sending text message: ${error}`);
|
|
4274
|
+
}
|
|
4275
|
+
}
|
|
4276
|
+
/**
|
|
4277
|
+
* Asynchronously handles sending a test message using the given runtime and mock user data.
|
|
4278
|
+
*
|
|
4279
|
+
* @param {IAgentRuntime} runtime - The agent runtime object.
|
|
4280
|
+
* @returns {Promise<void>} A Promise that resolves once the message is handled.
|
|
4281
|
+
*/
|
|
4282
|
+
async testHandlingMessage(runtime) {
|
|
4283
|
+
try {
|
|
4284
|
+
const channel = await this.getTestChannel(runtime);
|
|
4285
|
+
const fakeMessage = {
|
|
4286
|
+
content: `Hello, ${runtime.character.name}! How are you?`,
|
|
4287
|
+
author: {
|
|
4288
|
+
id: "mock-user-id",
|
|
4289
|
+
username: "MockUser",
|
|
4290
|
+
bot: false
|
|
3570
4291
|
},
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
await reaction.message.channel.send(content.text);
|
|
3580
|
-
return [];
|
|
4292
|
+
channel,
|
|
4293
|
+
id: "mock-message-id",
|
|
4294
|
+
createdTimestamp: Date.now(),
|
|
4295
|
+
mentions: {
|
|
4296
|
+
has: () => false
|
|
4297
|
+
},
|
|
4298
|
+
reference: null,
|
|
4299
|
+
attachments: []
|
|
3581
4300
|
};
|
|
3582
|
-
this.
|
|
3583
|
-
["DISCORD_REACTION_RECEIVED", "REACTION_RECEIVED"],
|
|
3584
|
-
{
|
|
3585
|
-
runtime: this.runtime,
|
|
3586
|
-
message: memory,
|
|
3587
|
-
callback
|
|
3588
|
-
}
|
|
3589
|
-
);
|
|
4301
|
+
await this.discordClient.messageManager.handleMessage(fakeMessage);
|
|
3590
4302
|
} catch (error) {
|
|
3591
|
-
|
|
4303
|
+
throw new Error(`Error in sending text message: ${error}`);
|
|
3592
4304
|
}
|
|
3593
4305
|
}
|
|
3594
|
-
|
|
4306
|
+
// #############################
|
|
4307
|
+
// Utility Functions
|
|
4308
|
+
// #############################
|
|
4309
|
+
/**
|
|
4310
|
+
* Asynchronously retrieves the test channel associated with the provided runtime.
|
|
4311
|
+
*
|
|
4312
|
+
* @param {IAgentRuntime} runtime - The runtime object containing necessary information.
|
|
4313
|
+
* @returns {Promise<Channel>} The test channel retrieved from the Discord client.
|
|
4314
|
+
* @throws {Error} If no test channel is found.
|
|
4315
|
+
*/
|
|
4316
|
+
async getTestChannel(runtime) {
|
|
4317
|
+
const channelId = this.validateChannelId(runtime);
|
|
4318
|
+
const channel = await this.discordClient.client.channels.fetch(channelId);
|
|
4319
|
+
if (!channel) throw new Error("no test channel found!");
|
|
4320
|
+
return channel;
|
|
4321
|
+
}
|
|
4322
|
+
/**
|
|
4323
|
+
* Async function to send a message to a text-based channel.
|
|
4324
|
+
*
|
|
4325
|
+
* @param {TextChannel} channel - The text-based channel the message is being sent to.
|
|
4326
|
+
* @param {string} messageContent - The content of the message being sent.
|
|
4327
|
+
* @param {any[]} files - An array of files to include in the message.
|
|
4328
|
+
* @throws {Error} If the channel is not a text-based channel or does not exist.
|
|
4329
|
+
* @throws {Error} If there is an error sending the message.
|
|
4330
|
+
*/
|
|
4331
|
+
async sendMessageToChannel(channel, messageContent, files) {
|
|
3595
4332
|
try {
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
}
|
|
3601
|
-
if (reaction.partial) {
|
|
3602
|
-
try {
|
|
3603
|
-
await reaction.fetch();
|
|
3604
|
-
} catch (error) {
|
|
3605
|
-
logger7.error(
|
|
3606
|
-
"Something went wrong when fetching the message:",
|
|
3607
|
-
error
|
|
3608
|
-
);
|
|
3609
|
-
return;
|
|
3610
|
-
}
|
|
4333
|
+
if (!channel || !channel.isTextBased()) {
|
|
4334
|
+
throw new Error(
|
|
4335
|
+
"Channel is not a text-based channel or does not exist."
|
|
4336
|
+
);
|
|
3611
4337
|
}
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
reaction.message.channel.id
|
|
3618
|
-
);
|
|
3619
|
-
const entityId = createUniqueUuid6(this.runtime, user.id);
|
|
3620
|
-
const timestamp = Date.now();
|
|
3621
|
-
const reactionUUID = createUniqueUuid6(
|
|
3622
|
-
this.runtime,
|
|
3623
|
-
`${reaction.message.id}-${user.id}-${emoji}-${timestamp}`
|
|
4338
|
+
await sendMessageInChunks(
|
|
4339
|
+
channel,
|
|
4340
|
+
messageContent,
|
|
4341
|
+
null,
|
|
4342
|
+
files
|
|
3624
4343
|
);
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
4344
|
+
} catch (error) {
|
|
4345
|
+
throw new Error(`Error sending message: ${error}`);
|
|
4346
|
+
}
|
|
4347
|
+
}
|
|
4348
|
+
/**
|
|
4349
|
+
* Play an audio stream from a given response stream using the provided VoiceConnection.
|
|
4350
|
+
*
|
|
4351
|
+
* @param {any} responseStream - The response stream to play as audio.
|
|
4352
|
+
* @param {VoiceConnection} connection - The VoiceConnection to use for playing the audio.
|
|
4353
|
+
* @returns {Promise<void>} - A Promise that resolves when the TTS playback is finished.
|
|
4354
|
+
*/
|
|
4355
|
+
async playAudioStream(responseStream, connection) {
|
|
4356
|
+
const audioPlayer = createAudioPlayer2({
|
|
4357
|
+
behaviors: {
|
|
4358
|
+
noSubscriber: NoSubscriberBehavior2.Pause
|
|
4359
|
+
}
|
|
4360
|
+
});
|
|
4361
|
+
const audioResource = createAudioResource2(responseStream);
|
|
4362
|
+
audioPlayer.play(audioResource);
|
|
4363
|
+
connection.subscribe(audioPlayer);
|
|
4364
|
+
logger7.success("TTS playback started successfully.");
|
|
4365
|
+
await new Promise((resolve, reject) => {
|
|
4366
|
+
audioPlayer.once(AudioPlayerStatus.Idle, () => {
|
|
4367
|
+
logger7.info("TTS playback finished.");
|
|
4368
|
+
resolve();
|
|
3636
4369
|
});
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
agentId: this.runtime.agentId,
|
|
3641
|
-
content: {
|
|
3642
|
-
// name,
|
|
3643
|
-
// userName,
|
|
3644
|
-
text: reactionMessage,
|
|
3645
|
-
source: "discord",
|
|
3646
|
-
inReplyTo: createUniqueUuid6(this.runtime, reaction.message.id),
|
|
3647
|
-
channelType: await this.getChannelType(
|
|
3648
|
-
reaction.message.channel
|
|
3649
|
-
)
|
|
3650
|
-
},
|
|
3651
|
-
roomId,
|
|
3652
|
-
createdAt: Date.now()
|
|
3653
|
-
};
|
|
3654
|
-
const callback = async (content) => {
|
|
3655
|
-
if (!reaction.message.channel) {
|
|
3656
|
-
logger7.error("No channel found for reaction message");
|
|
3657
|
-
return;
|
|
3658
|
-
}
|
|
3659
|
-
await reaction.message.channel.send(content.text);
|
|
3660
|
-
return [];
|
|
3661
|
-
};
|
|
3662
|
-
this.runtime.emitEvent(["DISCORD_REACTION_EVENT", "REACTION_RECEIVED"], {
|
|
3663
|
-
runtime: this.runtime,
|
|
3664
|
-
message: memory,
|
|
3665
|
-
callback
|
|
4370
|
+
audioPlayer.once("error", (error) => {
|
|
4371
|
+
reject(error);
|
|
4372
|
+
throw new Error(`TTS playback error: ${error}`);
|
|
3666
4373
|
});
|
|
3667
|
-
} catch (error) {
|
|
3668
|
-
logger7.error("Error handling reaction removal:", error);
|
|
3669
|
-
}
|
|
3670
|
-
}
|
|
3671
|
-
async handleGuildCreate(guild) {
|
|
3672
|
-
logger7.log(`Joined guild ${guild.name}`);
|
|
3673
|
-
const fullGuild = await guild.fetch();
|
|
3674
|
-
this.voiceManager.scanGuild(guild);
|
|
3675
|
-
const ownerId = createUniqueUuid6(this.runtime, fullGuild.ownerId);
|
|
3676
|
-
const worldId = createUniqueUuid6(this.runtime, fullGuild.id);
|
|
3677
|
-
const standardizedData = {
|
|
3678
|
-
runtime: this.runtime,
|
|
3679
|
-
rooms: await this.buildStandardizedRooms(fullGuild, worldId),
|
|
3680
|
-
users: await this.buildStandardizedUsers(fullGuild),
|
|
3681
|
-
world: {
|
|
3682
|
-
id: worldId,
|
|
3683
|
-
name: fullGuild.name,
|
|
3684
|
-
agentId: this.runtime.agentId,
|
|
3685
|
-
serverId: fullGuild.id,
|
|
3686
|
-
metadata: {
|
|
3687
|
-
ownership: fullGuild.ownerId ? { ownerId } : void 0,
|
|
3688
|
-
roles: {
|
|
3689
|
-
[ownerId]: Role.OWNER
|
|
3690
|
-
}
|
|
3691
|
-
}
|
|
3692
|
-
},
|
|
3693
|
-
source: "discord"
|
|
3694
|
-
};
|
|
3695
|
-
this.runtime.emitEvent(["DISCORD_SERVER_JOINED"], {
|
|
3696
|
-
runtime: this.runtime,
|
|
3697
|
-
server: fullGuild,
|
|
3698
|
-
source: "discord"
|
|
3699
4374
|
});
|
|
3700
|
-
this.runtime.emitEvent(["SERVER_JOINED"], standardizedData);
|
|
3701
4375
|
}
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
4376
|
+
/**
|
|
4377
|
+
* Retrieves the active guild where the bot is currently connected to a voice channel.
|
|
4378
|
+
*
|
|
4379
|
+
* @param {DiscordService} discordClient The DiscordService instance used to interact with the Discord API.
|
|
4380
|
+
* @returns {Promise<Guild>} The active guild where the bot is currently connected to a voice channel.
|
|
4381
|
+
* @throws {Error} If no active voice connection is found for the bot.
|
|
4382
|
+
*/
|
|
4383
|
+
async getActiveGuild(discordClient) {
|
|
4384
|
+
const guilds = await discordClient.client.guilds.fetch();
|
|
4385
|
+
const fullGuilds = await Promise.all(guilds.map((guild) => guild.fetch()));
|
|
4386
|
+
const activeGuild = fullGuilds.find((g) => g.members.me?.voice.channelId);
|
|
4387
|
+
if (!activeGuild) {
|
|
4388
|
+
throw new Error("No active voice connection found for the bot.");
|
|
3711
4389
|
}
|
|
4390
|
+
return activeGuild;
|
|
3712
4391
|
}
|
|
3713
4392
|
/**
|
|
3714
|
-
*
|
|
4393
|
+
* Waits for the VoiceManager in the Discord client to be ready.
|
|
4394
|
+
*
|
|
4395
|
+
* @param {DiscordService} discordClient - The Discord client to check for VoiceManager readiness.
|
|
4396
|
+
* @throws {Error} If the Discord client is not initialized.
|
|
4397
|
+
* @returns {Promise<void>} A promise that resolves when the VoiceManager is ready.
|
|
3715
4398
|
*/
|
|
3716
|
-
async
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
break;
|
|
3726
|
-
case DiscordChannelType4.GuildVoice:
|
|
3727
|
-
channelType = ChannelType10.VOICE_GROUP;
|
|
3728
|
-
break;
|
|
3729
|
-
default:
|
|
3730
|
-
channelType = ChannelType10.GROUP;
|
|
3731
|
-
}
|
|
3732
|
-
let participants = [];
|
|
3733
|
-
if (guild.memberCount < 1e3 && channel.type === DiscordChannelType4.GuildText) {
|
|
3734
|
-
try {
|
|
3735
|
-
participants = Array.from(guild.members.cache.values()).filter(
|
|
3736
|
-
(member) => channel.permissionsFor(member)?.has(PermissionsBitField2.Flags.ViewChannel)
|
|
3737
|
-
).map((member) => createUniqueUuid6(this.runtime, member.id));
|
|
3738
|
-
} catch (error) {
|
|
3739
|
-
logger7.warn(
|
|
3740
|
-
`Failed to get participants for channel ${channel.name}:`,
|
|
3741
|
-
error
|
|
3742
|
-
);
|
|
3743
|
-
}
|
|
3744
|
-
}
|
|
3745
|
-
rooms.push({
|
|
3746
|
-
id: roomId,
|
|
3747
|
-
name: channel.name,
|
|
3748
|
-
type: channelType,
|
|
3749
|
-
channelId: channel.id,
|
|
3750
|
-
participants
|
|
3751
|
-
});
|
|
3752
|
-
}
|
|
4399
|
+
async waitForVoiceManagerReady(discordClient) {
|
|
4400
|
+
if (!discordClient) {
|
|
4401
|
+
throw new Error("Discord client is not initialized.");
|
|
4402
|
+
}
|
|
4403
|
+
if (!discordClient.voiceManager.isReady()) {
|
|
4404
|
+
await new Promise((resolve, reject) => {
|
|
4405
|
+
discordClient.voiceManager.once("ready", resolve);
|
|
4406
|
+
discordClient.voiceManager.once("error", reject);
|
|
4407
|
+
});
|
|
3753
4408
|
}
|
|
3754
|
-
return rooms;
|
|
3755
4409
|
}
|
|
3756
4410
|
/**
|
|
3757
|
-
*
|
|
4411
|
+
* Validates the Discord test channel ID by checking if it is set in the runtime or environment variables.
|
|
4412
|
+
* If the test channel ID is not set, an error is thrown.
|
|
4413
|
+
*
|
|
4414
|
+
* @param {IAgentRuntime} runtime The runtime object containing the settings and environment variables.
|
|
4415
|
+
* @returns {string} The validated Discord test channel ID.
|
|
3758
4416
|
*/
|
|
3759
|
-
|
|
3760
|
-
const
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
`Using optimized user sync for large guild ${guild.name} (${guild.memberCount} members)`
|
|
4417
|
+
validateChannelId(runtime) {
|
|
4418
|
+
const testChannelId = runtime.getSetting("DISCORD_TEST_CHANNEL_ID") || process.env.DISCORD_TEST_CHANNEL_ID;
|
|
4419
|
+
if (!testChannelId) {
|
|
4420
|
+
throw new Error(
|
|
4421
|
+
"DISCORD_TEST_CHANNEL_ID is not set. Please provide a valid channel ID in the environment variables."
|
|
3765
4422
|
);
|
|
3766
|
-
try {
|
|
3767
|
-
for (const [, member] of guild.members.cache) {
|
|
3768
|
-
const tag = member.user.bot ? `${member.user.username}#${member.user.discriminator}` : member.user.username;
|
|
3769
|
-
if (member.id !== botId) {
|
|
3770
|
-
entities.push({
|
|
3771
|
-
id: createUniqueUuid6(this.runtime, member.id),
|
|
3772
|
-
names: Array.from(
|
|
3773
|
-
/* @__PURE__ */ new Set([
|
|
3774
|
-
member.user.username,
|
|
3775
|
-
member.displayName,
|
|
3776
|
-
member.user.globalName
|
|
3777
|
-
])
|
|
3778
|
-
),
|
|
3779
|
-
agentId: this.runtime.agentId,
|
|
3780
|
-
metadata: {
|
|
3781
|
-
default: {
|
|
3782
|
-
username: tag,
|
|
3783
|
-
name: member.displayName || member.user.username
|
|
3784
|
-
},
|
|
3785
|
-
discord: member.user.globalName ? {
|
|
3786
|
-
username: tag,
|
|
3787
|
-
name: member.displayName || member.user.username,
|
|
3788
|
-
globalName: member.user.globalName,
|
|
3789
|
-
userId: member.id
|
|
3790
|
-
} : {
|
|
3791
|
-
username: tag,
|
|
3792
|
-
name: member.displayName || member.user.username,
|
|
3793
|
-
userId: member.id
|
|
3794
|
-
}
|
|
3795
|
-
}
|
|
3796
|
-
});
|
|
3797
|
-
}
|
|
3798
|
-
}
|
|
3799
|
-
if (entities.length < 100) {
|
|
3800
|
-
logger7.info(`Adding online members for ${guild.name}`);
|
|
3801
|
-
const onlineMembers = await guild.members.fetch({ limit: 100 });
|
|
3802
|
-
for (const [, member] of onlineMembers) {
|
|
3803
|
-
if (member.id !== botId) {
|
|
3804
|
-
const entityId = createUniqueUuid6(this.runtime, member.id);
|
|
3805
|
-
if (!entities.some((u) => u.id === entityId)) {
|
|
3806
|
-
const tag = member.user.bot ? `${member.user.username}#${member.user.discriminator}` : member.user.username;
|
|
3807
|
-
entities.push({
|
|
3808
|
-
id: entityId,
|
|
3809
|
-
names: Array.from(
|
|
3810
|
-
/* @__PURE__ */ new Set([
|
|
3811
|
-
member.user.username,
|
|
3812
|
-
member.displayName,
|
|
3813
|
-
member.user.globalName
|
|
3814
|
-
])
|
|
3815
|
-
),
|
|
3816
|
-
agentId: this.runtime.agentId,
|
|
3817
|
-
metadata: {
|
|
3818
|
-
default: {
|
|
3819
|
-
username: tag,
|
|
3820
|
-
name: member.displayName || member.user.username
|
|
3821
|
-
},
|
|
3822
|
-
discord: member.user.globalName ? {
|
|
3823
|
-
username: tag,
|
|
3824
|
-
name: member.displayName || member.user.username,
|
|
3825
|
-
globalName: member.user.globalName,
|
|
3826
|
-
userId: member.id
|
|
3827
|
-
} : {
|
|
3828
|
-
username: tag,
|
|
3829
|
-
name: member.displayName || member.user.username,
|
|
3830
|
-
userId: member.id
|
|
3831
|
-
}
|
|
3832
|
-
}
|
|
3833
|
-
});
|
|
3834
|
-
}
|
|
3835
|
-
}
|
|
3836
|
-
}
|
|
3837
|
-
}
|
|
3838
|
-
} catch (error) {
|
|
3839
|
-
logger7.error(`Error fetching members for ${guild.name}:`, error);
|
|
3840
|
-
}
|
|
3841
|
-
} else {
|
|
3842
|
-
try {
|
|
3843
|
-
let members = guild.members.cache;
|
|
3844
|
-
if (members.size === 0) {
|
|
3845
|
-
members = await guild.members.fetch();
|
|
3846
|
-
}
|
|
3847
|
-
for (const [, member] of members) {
|
|
3848
|
-
if (member.id !== botId) {
|
|
3849
|
-
const tag = member.user.bot ? `${member.user.username}#${member.user.discriminator}` : member.user.username;
|
|
3850
|
-
entities.push({
|
|
3851
|
-
id: createUniqueUuid6(this.runtime, member.id),
|
|
3852
|
-
names: Array.from(
|
|
3853
|
-
/* @__PURE__ */ new Set([
|
|
3854
|
-
member.user.username,
|
|
3855
|
-
member.displayName,
|
|
3856
|
-
member.user.globalName
|
|
3857
|
-
])
|
|
3858
|
-
),
|
|
3859
|
-
agentId: this.runtime.agentId,
|
|
3860
|
-
metadata: {
|
|
3861
|
-
default: {
|
|
3862
|
-
username: tag,
|
|
3863
|
-
name: member.displayName || member.user.username
|
|
3864
|
-
},
|
|
3865
|
-
discord: member.user.globalName ? {
|
|
3866
|
-
username: tag,
|
|
3867
|
-
name: member.displayName || member.user.username,
|
|
3868
|
-
globalName: member.user.globalName,
|
|
3869
|
-
userId: member.id
|
|
3870
|
-
} : {
|
|
3871
|
-
username: tag,
|
|
3872
|
-
name: member.displayName || member.user.username,
|
|
3873
|
-
userId: member.id
|
|
3874
|
-
}
|
|
3875
|
-
}
|
|
3876
|
-
});
|
|
3877
|
-
}
|
|
3878
|
-
}
|
|
3879
|
-
} catch (error) {
|
|
3880
|
-
logger7.error(`Error fetching members for ${guild.name}:`, error);
|
|
3881
|
-
}
|
|
3882
|
-
}
|
|
3883
|
-
return entities;
|
|
3884
|
-
}
|
|
3885
|
-
async onReady() {
|
|
3886
|
-
logger7.log("DISCORD ON READY");
|
|
3887
|
-
const guilds = await this.client.guilds.fetch();
|
|
3888
|
-
for (const [, guild] of guilds) {
|
|
3889
|
-
const fullGuild = await guild.fetch();
|
|
3890
|
-
await this.voiceManager.scanGuild(fullGuild);
|
|
3891
|
-
setTimeout(async () => {
|
|
3892
|
-
const fullGuild2 = await guild.fetch();
|
|
3893
|
-
logger7.log("DISCORD SERVER CONNECTED", fullGuild2.name);
|
|
3894
|
-
this.runtime.emitEvent(["DISCORD_SERVER_CONNECTED"], {
|
|
3895
|
-
runtime: this.runtime,
|
|
3896
|
-
server: fullGuild2,
|
|
3897
|
-
source: "discord"
|
|
3898
|
-
});
|
|
3899
|
-
const worldId = createUniqueUuid6(this.runtime, fullGuild2.id);
|
|
3900
|
-
const ownerId = createUniqueUuid6(this.runtime, fullGuild2.ownerId);
|
|
3901
|
-
const standardizedData = {
|
|
3902
|
-
name: fullGuild2.name,
|
|
3903
|
-
runtime: this.runtime,
|
|
3904
|
-
rooms: await this.buildStandardizedRooms(fullGuild2, worldId),
|
|
3905
|
-
users: await this.buildStandardizedUsers(fullGuild2),
|
|
3906
|
-
world: {
|
|
3907
|
-
id: worldId,
|
|
3908
|
-
name: fullGuild2.name,
|
|
3909
|
-
agentId: this.runtime.agentId,
|
|
3910
|
-
serverId: fullGuild2.id,
|
|
3911
|
-
metadata: {
|
|
3912
|
-
ownership: fullGuild2.ownerId ? { ownerId } : void 0,
|
|
3913
|
-
roles: {
|
|
3914
|
-
[ownerId]: Role.OWNER
|
|
3915
|
-
}
|
|
3916
|
-
}
|
|
3917
|
-
},
|
|
3918
|
-
source: "discord"
|
|
3919
|
-
};
|
|
3920
|
-
this.runtime.emitEvent(["SERVER_CONNECTED"], standardizedData);
|
|
3921
|
-
}, 1e3);
|
|
3922
4423
|
}
|
|
3923
|
-
|
|
4424
|
+
return testChannelId;
|
|
3924
4425
|
}
|
|
3925
4426
|
};
|
|
4427
|
+
|
|
4428
|
+
// src/index.ts
|
|
3926
4429
|
var discordPlugin = {
|
|
3927
4430
|
name: "discord",
|
|
3928
4431
|
description: "Discord client plugin",
|
|
3929
4432
|
services: [DiscordService],
|
|
3930
4433
|
actions: [
|
|
3931
4434
|
chatWithAttachments_default,
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
4435
|
+
downloadMedia,
|
|
4436
|
+
joinVoice,
|
|
4437
|
+
leaveVoice,
|
|
4438
|
+
summarize,
|
|
4439
|
+
transcribeMedia
|
|
3937
4440
|
],
|
|
3938
|
-
providers: [
|
|
3939
|
-
tests: [new DiscordTestSuite()]
|
|
4441
|
+
providers: [channelStateProvider, voiceStateProvider],
|
|
4442
|
+
tests: [new DiscordTestSuite()],
|
|
4443
|
+
init: async (config, runtime) => {
|
|
4444
|
+
const token = runtime.getSetting("DISCORD_API_TOKEN");
|
|
4445
|
+
if (!token || token.trim() === "") {
|
|
4446
|
+
logger8.warn(
|
|
4447
|
+
"Discord API Token not provided - Discord plugin is loaded but will not be functional"
|
|
4448
|
+
);
|
|
4449
|
+
logger8.warn(
|
|
4450
|
+
"To enable Discord functionality, please provide DISCORD_API_TOKEN in your .eliza/.env file"
|
|
4451
|
+
);
|
|
4452
|
+
}
|
|
4453
|
+
}
|
|
3940
4454
|
};
|
|
3941
4455
|
var index_default = discordPlugin;
|
|
3942
4456
|
export {
|
|
3943
|
-
DiscordService,
|
|
3944
4457
|
index_default as default
|
|
3945
4458
|
};
|
|
3946
4459
|
//# sourceMappingURL=index.js.map
|