@elizaos/plugin-discord 1.0.0-beta.7 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +7 -4
- package/dist/index.d.ts +5 -0
- package/dist/index.js +966 -518
- package/dist/index.js.map +1 -1
- package/package.json +13 -5
package/dist/index.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
1
8
|
// src/index.ts
|
|
2
9
|
import { logger as logger8 } from "@elizaos/core";
|
|
3
10
|
|
|
@@ -134,14 +141,21 @@ var chatWithAttachments = {
|
|
|
134
141
|
return;
|
|
135
142
|
}
|
|
136
143
|
const { objective, attachmentIds } = attachmentData;
|
|
137
|
-
const
|
|
138
|
-
|
|
144
|
+
const conversationLength = runtime.getConversationLength();
|
|
145
|
+
const recentMessages = await runtime.getMemories({
|
|
146
|
+
tableName: "messages",
|
|
147
|
+
roomId: message.roomId,
|
|
148
|
+
count: conversationLength,
|
|
149
|
+
unique: false
|
|
150
|
+
});
|
|
151
|
+
const attachments = recentMessages.filter((msg) => msg.content.attachments && msg.content.attachments.length > 0).flatMap((msg) => msg.content.attachments).filter(
|
|
152
|
+
(attachment) => attachment && (attachmentIds.map((attch) => attch.toLowerCase().slice(0, 5)).includes(attachment.id.toLowerCase().slice(0, 5)) || // or check the other way
|
|
139
153
|
attachmentIds.some((id) => {
|
|
140
154
|
const attachmentId = id.toLowerCase().slice(0, 5);
|
|
141
|
-
return attachment.id.toLowerCase().includes(attachmentId);
|
|
142
|
-
})
|
|
155
|
+
return attachment && attachment.id.toLowerCase().includes(attachmentId);
|
|
156
|
+
}))
|
|
143
157
|
);
|
|
144
|
-
const attachmentsWithText = attachments.map((attachment) => `# ${attachment.title}
|
|
158
|
+
const attachmentsWithText = attachments.filter((attachment) => !!attachment).map((attachment) => `# ${attachment.title}
|
|
145
159
|
${attachment.text}`).join("\n\n");
|
|
146
160
|
let currentSummary = "";
|
|
147
161
|
const chunkSize = 8192;
|
|
@@ -328,6 +342,10 @@ var downloadMedia = {
|
|
|
328
342
|
},
|
|
329
343
|
handler: async (runtime, message, state, _options, callback) => {
|
|
330
344
|
const videoService = runtime.getService(ServiceType.VIDEO);
|
|
345
|
+
if (!videoService) {
|
|
346
|
+
console.error("Video service not found");
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
331
349
|
const mediaUrl = await getMediaUrl(runtime, message, state);
|
|
332
350
|
if (!mediaUrl) {
|
|
333
351
|
console.error("Couldn't get media URL from messages");
|
|
@@ -850,7 +868,14 @@ var transcribeMedia = {
|
|
|
850
868
|
);
|
|
851
869
|
return;
|
|
852
870
|
}
|
|
853
|
-
const
|
|
871
|
+
const conversationLength = runtime.getConversationLength();
|
|
872
|
+
const recentMessages = await runtime.getMemories({
|
|
873
|
+
tableName: "messages",
|
|
874
|
+
roomId: message.roomId,
|
|
875
|
+
count: conversationLength,
|
|
876
|
+
unique: false
|
|
877
|
+
});
|
|
878
|
+
const attachment = recentMessages.filter((msg) => msg.content.attachments && msg.content.attachments.length > 0).flatMap((msg) => msg.content.attachments).find((attachment2) => attachment2?.id.toLowerCase() === attachmentId.toLowerCase());
|
|
854
879
|
if (!attachment) {
|
|
855
880
|
console.error(`Couldn't find attachment with ID ${attachmentId}`);
|
|
856
881
|
await runtime.createMemory(
|
|
@@ -962,7 +987,7 @@ var joinVoice = {
|
|
|
962
987
|
return false;
|
|
963
988
|
}
|
|
964
989
|
const room = state.data.room ?? await runtime.getRoom(message.roomId);
|
|
965
|
-
if (room?.type !== ChannelType2.GROUP) {
|
|
990
|
+
if (room?.type !== ChannelType2.GROUP && room?.type !== ChannelType2.VOICE_GROUP) {
|
|
966
991
|
return false;
|
|
967
992
|
}
|
|
968
993
|
const client = runtime.getService(ServiceType2.DISCORD);
|
|
@@ -975,10 +1000,11 @@ var joinVoice = {
|
|
|
975
1000
|
description: "Join a voice channel to participate in voice chat.",
|
|
976
1001
|
handler: async (runtime, message, state, _options, callback) => {
|
|
977
1002
|
const room = state.data.room ?? await runtime.getRoom(message.roomId);
|
|
1003
|
+
const messageContent = message?.content?.text?.toLowerCase() ?? "";
|
|
978
1004
|
if (!room) {
|
|
979
1005
|
throw new Error("No room found");
|
|
980
1006
|
}
|
|
981
|
-
if (room.type !== ChannelType2.
|
|
1007
|
+
if (room?.type !== ChannelType2.GROUP && room?.type !== ChannelType2.VOICE_GROUP) {
|
|
982
1008
|
return false;
|
|
983
1009
|
}
|
|
984
1010
|
const serverId = room.serverId;
|
|
@@ -997,7 +1023,6 @@ var joinVoice = {
|
|
|
997
1023
|
);
|
|
998
1024
|
const targetChannel = voiceChannels.find((channel) => {
|
|
999
1025
|
const name = channel.name.toLowerCase();
|
|
1000
|
-
const messageContent = message?.content?.text;
|
|
1001
1026
|
const replacedName = name.replace(/[^a-z0-9 ]/g, "");
|
|
1002
1027
|
return name.includes(messageContent) || messageContent.includes(name) || replacedName.includes(messageContent) || messageContent.includes(replacedName);
|
|
1003
1028
|
});
|
|
@@ -1011,7 +1036,8 @@ var joinVoice = {
|
|
|
1011
1036
|
(member2) => createUniqueUuid2(runtime, member2.id) === message.entityId
|
|
1012
1037
|
);
|
|
1013
1038
|
if (member?.voice?.channel) {
|
|
1014
|
-
|
|
1039
|
+
const userVoiceChannel = member.voice.channel;
|
|
1040
|
+
voiceManager.joinChannel(userVoiceChannel);
|
|
1015
1041
|
await runtime.createMemory(
|
|
1016
1042
|
{
|
|
1017
1043
|
entityId: message.entityId,
|
|
@@ -1019,7 +1045,7 @@ var joinVoice = {
|
|
|
1019
1045
|
roomId: message.roomId,
|
|
1020
1046
|
content: {
|
|
1021
1047
|
source: "discord",
|
|
1022
|
-
thought: `I joined the voice channel ${
|
|
1048
|
+
thought: `I joined the voice channel ${userVoiceChannel.name}`,
|
|
1023
1049
|
actions: ["JOIN_VOICE_STARTED"]
|
|
1024
1050
|
},
|
|
1025
1051
|
metadata: {
|
|
@@ -1032,10 +1058,10 @@ var joinVoice = {
|
|
|
1032
1058
|
{
|
|
1033
1059
|
entityId: message.entityId,
|
|
1034
1060
|
agentId: message.agentId,
|
|
1035
|
-
roomId: createUniqueUuid2(runtime,
|
|
1061
|
+
roomId: createUniqueUuid2(runtime, userVoiceChannel.id),
|
|
1036
1062
|
content: {
|
|
1037
1063
|
source: "discord",
|
|
1038
|
-
thought: `I joined the voice channel ${
|
|
1064
|
+
thought: `I joined the voice channel ${userVoiceChannel.name}`,
|
|
1039
1065
|
actions: ["JOIN_VOICE_STARTED"]
|
|
1040
1066
|
},
|
|
1041
1067
|
metadata: {
|
|
@@ -1084,7 +1110,7 @@ You should only respond with the name of the voice channel or none, no commentar
|
|
|
1084
1110
|
roomId: message.roomId,
|
|
1085
1111
|
content: {
|
|
1086
1112
|
source: "discord",
|
|
1087
|
-
thought: `I joined the voice channel ${
|
|
1113
|
+
thought: `I joined the voice channel ${targetChannel2.name}`,
|
|
1088
1114
|
actions: ["JOIN_VOICE_STARTED"]
|
|
1089
1115
|
},
|
|
1090
1116
|
metadata: {
|
|
@@ -1269,7 +1295,7 @@ var leaveVoice = {
|
|
|
1269
1295
|
return false;
|
|
1270
1296
|
}
|
|
1271
1297
|
const room = state.data.room ?? await runtime.getRoom(message.roomId);
|
|
1272
|
-
if (room?.type !== ChannelType3.GROUP) {
|
|
1298
|
+
if (room?.type !== ChannelType3.GROUP && room?.type !== ChannelType3.VOICE_GROUP) {
|
|
1273
1299
|
return false;
|
|
1274
1300
|
}
|
|
1275
1301
|
const isConnectedToVoice = service.client.voice.adapters.size > 0;
|
|
@@ -1281,7 +1307,7 @@ var leaveVoice = {
|
|
|
1281
1307
|
if (!room) {
|
|
1282
1308
|
throw new Error("No room found");
|
|
1283
1309
|
}
|
|
1284
|
-
if (room.type !== ChannelType3.
|
|
1310
|
+
if (room?.type !== ChannelType3.GROUP && room?.type !== ChannelType3.VOICE_GROUP) {
|
|
1285
1311
|
throw new Error("Not a group");
|
|
1286
1312
|
}
|
|
1287
1313
|
const serverId = room.serverId;
|
|
@@ -1517,7 +1543,7 @@ var channelStateProvider = {
|
|
|
1517
1543
|
}
|
|
1518
1544
|
if (message.content.source !== "discord") {
|
|
1519
1545
|
return {
|
|
1520
|
-
data:
|
|
1546
|
+
data: {},
|
|
1521
1547
|
values: {},
|
|
1522
1548
|
text: ""
|
|
1523
1549
|
};
|
|
@@ -1564,7 +1590,24 @@ var channelStateProvider = {
|
|
|
1564
1590
|
text: ""
|
|
1565
1591
|
};
|
|
1566
1592
|
}
|
|
1567
|
-
const guild = discordService.client
|
|
1593
|
+
const guild = discordService.client?.guilds.cache.get(serverId);
|
|
1594
|
+
if (!guild) {
|
|
1595
|
+
console.warn(`Guild not found for serverId: ${serverId}`);
|
|
1596
|
+
return {
|
|
1597
|
+
data: {
|
|
1598
|
+
room,
|
|
1599
|
+
channelType,
|
|
1600
|
+
serverId,
|
|
1601
|
+
channelId
|
|
1602
|
+
},
|
|
1603
|
+
values: {
|
|
1604
|
+
channelType,
|
|
1605
|
+
serverId,
|
|
1606
|
+
channelId
|
|
1607
|
+
},
|
|
1608
|
+
text: ""
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1568
1611
|
serverName = guild.name;
|
|
1569
1612
|
responseText = `${agentName} is currently having a conversation in the channel \`@${channelId} in the server \`${serverName}\` (@${serverId})`;
|
|
1570
1613
|
responseText += `
|
|
@@ -1729,10 +1772,7 @@ import {
|
|
|
1729
1772
|
import fs3 from "node:fs";
|
|
1730
1773
|
import { trimTokens as trimTokens3 } from "@elizaos/core";
|
|
1731
1774
|
import { parseJSONObjectFromText as parseJSONObjectFromText5 } from "@elizaos/core";
|
|
1732
|
-
import {
|
|
1733
|
-
ModelType as ModelType6,
|
|
1734
|
-
ServiceType as ServiceType3
|
|
1735
|
-
} from "@elizaos/core";
|
|
1775
|
+
import { ModelType as ModelType6, ServiceType as ServiceType3 } from "@elizaos/core";
|
|
1736
1776
|
import { Collection } from "discord.js";
|
|
1737
1777
|
import ffmpeg from "fluent-ffmpeg";
|
|
1738
1778
|
async function generateSummary(runtime, text) {
|
|
@@ -1814,7 +1854,7 @@ var AttachmentManager = class {
|
|
|
1814
1854
|
media = await this.processAudioVideoAttachment(attachment);
|
|
1815
1855
|
} else if (attachment.contentType?.startsWith("image/")) {
|
|
1816
1856
|
media = await this.processImageAttachment(attachment);
|
|
1817
|
-
} else if (attachment.contentType?.startsWith("video/") || this.runtime.getService(ServiceType3.VIDEO)
|
|
1857
|
+
} else if (attachment.contentType?.startsWith("video/") || this.runtime.getService(ServiceType3.VIDEO)?.isVideoUrl(attachment.url)) {
|
|
1818
1858
|
media = await this.processVideoAttachment(attachment);
|
|
1819
1859
|
} else {
|
|
1820
1860
|
media = await this.processGenericAttachment(attachment);
|
|
@@ -1852,7 +1892,9 @@ var AttachmentManager = class {
|
|
|
1852
1892
|
text: transcription || "Audio/video content not available"
|
|
1853
1893
|
};
|
|
1854
1894
|
} catch (error) {
|
|
1855
|
-
|
|
1895
|
+
if (error instanceof Error) {
|
|
1896
|
+
console.error(`Error processing audio/video attachment: ${error.message}`);
|
|
1897
|
+
}
|
|
1856
1898
|
return {
|
|
1857
1899
|
id: attachment.id,
|
|
1858
1900
|
url: attachment.url,
|
|
@@ -1907,7 +1949,11 @@ var AttachmentManager = class {
|
|
|
1907
1949
|
try {
|
|
1908
1950
|
const response = await fetch(attachment.url);
|
|
1909
1951
|
const pdfBuffer = await response.arrayBuffer();
|
|
1910
|
-
const
|
|
1952
|
+
const pdfService = this.runtime.getService(ServiceType3.PDF);
|
|
1953
|
+
if (!pdfService) {
|
|
1954
|
+
throw new Error("PDF service not found");
|
|
1955
|
+
}
|
|
1956
|
+
const text = await pdfService.convertPdfToText(Buffer.from(pdfBuffer));
|
|
1911
1957
|
const { title, description } = await generateSummary(this.runtime, text);
|
|
1912
1958
|
return {
|
|
1913
1959
|
id: attachment.id,
|
|
@@ -1918,7 +1964,9 @@ var AttachmentManager = class {
|
|
|
1918
1964
|
text
|
|
1919
1965
|
};
|
|
1920
1966
|
} catch (error) {
|
|
1921
|
-
|
|
1967
|
+
if (error instanceof Error) {
|
|
1968
|
+
console.error(`Error processing PDF attachment: ${error.message}`);
|
|
1969
|
+
}
|
|
1922
1970
|
return {
|
|
1923
1971
|
id: attachment.id,
|
|
1924
1972
|
url: attachment.url,
|
|
@@ -1948,7 +1996,11 @@ var AttachmentManager = class {
|
|
|
1948
1996
|
text
|
|
1949
1997
|
};
|
|
1950
1998
|
} catch (error) {
|
|
1951
|
-
|
|
1999
|
+
if (error instanceof Error) {
|
|
2000
|
+
console.error(`Error processing plaintext attachment: ${error.message}`);
|
|
2001
|
+
} else {
|
|
2002
|
+
console.error(`An unknown error occurred during plaintext attachment processing`);
|
|
2003
|
+
}
|
|
1952
2004
|
return {
|
|
1953
2005
|
id: attachment.id,
|
|
1954
2006
|
url: attachment.url,
|
|
@@ -1982,7 +2034,9 @@ var AttachmentManager = class {
|
|
|
1982
2034
|
text: description || "Image content not available"
|
|
1983
2035
|
};
|
|
1984
2036
|
} catch (error) {
|
|
1985
|
-
|
|
2037
|
+
if (error instanceof Error) {
|
|
2038
|
+
console.error(`Error processing image attachment: ${error.message}`);
|
|
2039
|
+
}
|
|
1986
2040
|
return this.createFallbackImageMedia(attachment);
|
|
1987
2041
|
}
|
|
1988
2042
|
}
|
|
@@ -2011,9 +2065,16 @@ var AttachmentManager = class {
|
|
|
2011
2065
|
async processVideoAttachment(attachment) {
|
|
2012
2066
|
const videoService = this.runtime.getService(ServiceType3.VIDEO);
|
|
2013
2067
|
if (!videoService) {
|
|
2014
|
-
|
|
2068
|
+
return {
|
|
2069
|
+
id: attachment.id,
|
|
2070
|
+
url: attachment.url,
|
|
2071
|
+
title: "Video Attachment (Service Unavailable)",
|
|
2072
|
+
source: "Video",
|
|
2073
|
+
description: "Could not process video attachment because the required service is not available.",
|
|
2074
|
+
text: "Video content not available"
|
|
2075
|
+
};
|
|
2015
2076
|
}
|
|
2016
|
-
if (videoService.isVideoUrl(attachment.url)) {
|
|
2077
|
+
if (typeof videoService.isVideoUrl === "function" && videoService.isVideoUrl(attachment.url)) {
|
|
2017
2078
|
const videoInfo = await videoService.processVideo(attachment.url, this.runtime);
|
|
2018
2079
|
return {
|
|
2019
2080
|
id: attachment.id,
|
|
@@ -2062,43 +2123,103 @@ import {
|
|
|
2062
2123
|
PermissionsBitField,
|
|
2063
2124
|
ThreadChannel
|
|
2064
2125
|
} from "discord.js";
|
|
2065
|
-
function getWavHeader(audioLength, sampleRate, channelCount = 1, bitsPerSample = 16) {
|
|
2066
|
-
const wavHeader = Buffer.alloc(44);
|
|
2067
|
-
wavHeader.write("RIFF", 0);
|
|
2068
|
-
wavHeader.writeUInt32LE(36 + audioLength, 4);
|
|
2069
|
-
wavHeader.write("WAVE", 8);
|
|
2070
|
-
wavHeader.write("fmt ", 12);
|
|
2071
|
-
wavHeader.writeUInt32LE(16, 16);
|
|
2072
|
-
wavHeader.writeUInt16LE(1, 20);
|
|
2073
|
-
wavHeader.writeUInt16LE(channelCount, 22);
|
|
2074
|
-
wavHeader.writeUInt32LE(sampleRate, 24);
|
|
2075
|
-
wavHeader.writeUInt32LE(sampleRate * bitsPerSample * channelCount / 8, 28);
|
|
2076
|
-
wavHeader.writeUInt16LE(bitsPerSample * channelCount / 8, 32);
|
|
2077
|
-
wavHeader.writeUInt16LE(bitsPerSample, 34);
|
|
2078
|
-
wavHeader.write("data", 36);
|
|
2079
|
-
wavHeader.writeUInt32LE(audioLength, 40);
|
|
2080
|
-
return wavHeader;
|
|
2081
|
-
}
|
|
2082
2126
|
var MAX_MESSAGE_LENGTH = 1900;
|
|
2083
|
-
async function sendMessageInChunks(channel, content, _inReplyTo, files) {
|
|
2127
|
+
async function sendMessageInChunks(channel, content, _inReplyTo, files, components) {
|
|
2084
2128
|
const sentMessages = [];
|
|
2085
2129
|
const messages = splitMessage(content);
|
|
2086
2130
|
try {
|
|
2087
2131
|
for (let i = 0; i < messages.length; i++) {
|
|
2088
2132
|
const message = messages[i];
|
|
2089
|
-
if (message.trim().length > 0 || i === messages.length - 1 && files && files.length > 0) {
|
|
2133
|
+
if (message.trim().length > 0 || i === messages.length - 1 && files && files.length > 0 || components) {
|
|
2090
2134
|
const options = {
|
|
2091
2135
|
content: message.trim()
|
|
2092
2136
|
};
|
|
2093
2137
|
if (i === messages.length - 1 && files && files.length > 0) {
|
|
2094
2138
|
options.files = files;
|
|
2095
2139
|
}
|
|
2140
|
+
if (i === messages.length - 1 && components && components.length > 0) {
|
|
2141
|
+
try {
|
|
2142
|
+
const safeStringify = (obj) => {
|
|
2143
|
+
return JSON.stringify(
|
|
2144
|
+
obj,
|
|
2145
|
+
(_, value) => typeof value === "bigint" ? value.toString() : value
|
|
2146
|
+
);
|
|
2147
|
+
};
|
|
2148
|
+
logger3.info(`Components received: ${safeStringify(components)}`);
|
|
2149
|
+
if (!Array.isArray(components)) {
|
|
2150
|
+
logger3.warn("Components is not an array, skipping component processing");
|
|
2151
|
+
} else if (components.length > 0 && components[0] && typeof components[0].toJSON === "function") {
|
|
2152
|
+
options.components = components;
|
|
2153
|
+
} else {
|
|
2154
|
+
const {
|
|
2155
|
+
ActionRowBuilder,
|
|
2156
|
+
ButtonBuilder,
|
|
2157
|
+
StringSelectMenuBuilder
|
|
2158
|
+
} = __require("discord.js");
|
|
2159
|
+
const discordComponents = components.map((row) => {
|
|
2160
|
+
if (!row || typeof row !== "object" || row.type !== 1) {
|
|
2161
|
+
logger3.warn("Invalid component row structure, skipping");
|
|
2162
|
+
return null;
|
|
2163
|
+
}
|
|
2164
|
+
if (row.type === 1) {
|
|
2165
|
+
const actionRow = new ActionRowBuilder();
|
|
2166
|
+
if (!Array.isArray(row.components)) {
|
|
2167
|
+
logger3.warn("Row components is not an array, skipping");
|
|
2168
|
+
return null;
|
|
2169
|
+
}
|
|
2170
|
+
const validComponents = row.components.map((comp) => {
|
|
2171
|
+
if (!comp || typeof comp !== "object") {
|
|
2172
|
+
logger3.warn("Invalid component, skipping");
|
|
2173
|
+
return null;
|
|
2174
|
+
}
|
|
2175
|
+
try {
|
|
2176
|
+
if (comp.type === 2) {
|
|
2177
|
+
return new ButtonBuilder().setCustomId(comp.custom_id).setLabel(comp.label || "").setStyle(comp.style || 1);
|
|
2178
|
+
}
|
|
2179
|
+
if (comp.type === 3) {
|
|
2180
|
+
const selectMenu = new StringSelectMenuBuilder().setCustomId(comp.custom_id).setPlaceholder(comp.placeholder || "Select an option");
|
|
2181
|
+
if (typeof comp.min_values === "number")
|
|
2182
|
+
selectMenu.setMinValues(comp.min_values);
|
|
2183
|
+
if (typeof comp.max_values === "number")
|
|
2184
|
+
selectMenu.setMaxValues(comp.max_values);
|
|
2185
|
+
if (Array.isArray(comp.options)) {
|
|
2186
|
+
selectMenu.addOptions(
|
|
2187
|
+
comp.options.map((option) => ({
|
|
2188
|
+
label: option.label,
|
|
2189
|
+
value: option.value,
|
|
2190
|
+
description: option.description
|
|
2191
|
+
}))
|
|
2192
|
+
);
|
|
2193
|
+
}
|
|
2194
|
+
return selectMenu;
|
|
2195
|
+
}
|
|
2196
|
+
} catch (err) {
|
|
2197
|
+
logger3.error(`Error creating component: ${err}`);
|
|
2198
|
+
return null;
|
|
2199
|
+
}
|
|
2200
|
+
return null;
|
|
2201
|
+
}).filter(Boolean);
|
|
2202
|
+
if (validComponents.length > 0) {
|
|
2203
|
+
actionRow.addComponents(validComponents);
|
|
2204
|
+
return actionRow;
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
return null;
|
|
2208
|
+
}).filter(Boolean);
|
|
2209
|
+
if (discordComponents.length > 0) {
|
|
2210
|
+
options.components = discordComponents;
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
} catch (error) {
|
|
2214
|
+
logger3.error(`Error processing components: ${error}`);
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2096
2217
|
const m = await channel.send(options);
|
|
2097
2218
|
sentMessages.push(m);
|
|
2098
2219
|
}
|
|
2099
2220
|
}
|
|
2100
2221
|
} catch (error) {
|
|
2101
|
-
logger3.error(
|
|
2222
|
+
logger3.error(`Error sending message: ${error}`);
|
|
2102
2223
|
}
|
|
2103
2224
|
return sentMessages;
|
|
2104
2225
|
}
|
|
@@ -2207,6 +2328,9 @@ var MessageManager = class {
|
|
|
2207
2328
|
if (this.runtime.character.settings?.discord?.shouldIgnoreDirectMessages && message.channel.type === DiscordChannelType2.DM) {
|
|
2208
2329
|
return;
|
|
2209
2330
|
}
|
|
2331
|
+
if (this.runtime.character.settings?.discord?.shouldRespondOnlyToMentions && (!this.client.user?.id || !message.mentions.users?.has(this.client.user.id))) {
|
|
2332
|
+
return;
|
|
2333
|
+
}
|
|
2210
2334
|
const entityId = createUniqueUuid4(this.runtime, message.author.id);
|
|
2211
2335
|
const userName = message.author.bot ? `${message.author.username}#${message.author.discriminator}` : message.author.username;
|
|
2212
2336
|
const name = message.author.displayName;
|
|
@@ -2217,6 +2341,9 @@ var MessageManager = class {
|
|
|
2217
2341
|
if (message.guild) {
|
|
2218
2342
|
const guild = await message.guild.fetch();
|
|
2219
2343
|
type = await this.getChannelType(message.channel);
|
|
2344
|
+
if (type === null) {
|
|
2345
|
+
logger4.warn("null channel type, discord message", message);
|
|
2346
|
+
}
|
|
2220
2347
|
serverId = guild.id;
|
|
2221
2348
|
} else {
|
|
2222
2349
|
type = ChannelType7.DM;
|
|
@@ -2230,7 +2357,9 @@ var MessageManager = class {
|
|
|
2230
2357
|
source: "discord",
|
|
2231
2358
|
channelId: message.channel.id,
|
|
2232
2359
|
serverId,
|
|
2233
|
-
type
|
|
2360
|
+
type,
|
|
2361
|
+
worldId: createUniqueUuid4(this.runtime, serverId ?? roomId),
|
|
2362
|
+
worldName: message.guild?.name
|
|
2234
2363
|
});
|
|
2235
2364
|
try {
|
|
2236
2365
|
const canSendResult = canSendMessage(message.channel);
|
|
@@ -2250,6 +2379,22 @@ var MessageManager = class {
|
|
|
2250
2379
|
}
|
|
2251
2380
|
const entityId2 = createUniqueUuid4(this.runtime, message.author.id);
|
|
2252
2381
|
const messageId = createUniqueUuid4(this.runtime, message.id);
|
|
2382
|
+
const channel = message.channel;
|
|
2383
|
+
const startTyping = () => {
|
|
2384
|
+
try {
|
|
2385
|
+
if (channel.sendTyping) {
|
|
2386
|
+
channel.sendTyping();
|
|
2387
|
+
}
|
|
2388
|
+
} catch (err) {
|
|
2389
|
+
logger4.warn("Error sending typing indicator:", err);
|
|
2390
|
+
}
|
|
2391
|
+
};
|
|
2392
|
+
startTyping();
|
|
2393
|
+
const typingInterval = setInterval(startTyping, 8e3);
|
|
2394
|
+
const typingData = {
|
|
2395
|
+
interval: typingInterval,
|
|
2396
|
+
cleared: false
|
|
2397
|
+
};
|
|
2253
2398
|
const newMessage = {
|
|
2254
2399
|
id: messageId,
|
|
2255
2400
|
entityId: entityId2,
|
|
@@ -2264,6 +2409,10 @@ var MessageManager = class {
|
|
|
2264
2409
|
url: message.url,
|
|
2265
2410
|
inReplyTo: message.reference?.messageId ? createUniqueUuid4(this.runtime, message.reference?.messageId) : void 0
|
|
2266
2411
|
},
|
|
2412
|
+
metadata: {
|
|
2413
|
+
entityName: name,
|
|
2414
|
+
type: "message"
|
|
2415
|
+
},
|
|
2267
2416
|
createdAt: message.createdTimestamp
|
|
2268
2417
|
};
|
|
2269
2418
|
const callback = async (content, files) => {
|
|
@@ -2271,37 +2420,54 @@ var MessageManager = class {
|
|
|
2271
2420
|
if (message.id && !content.inReplyTo) {
|
|
2272
2421
|
content.inReplyTo = createUniqueUuid4(this.runtime, message.id);
|
|
2273
2422
|
}
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
const
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2423
|
+
try {
|
|
2424
|
+
const messages = await sendMessageInChunks(
|
|
2425
|
+
channel,
|
|
2426
|
+
content.text ?? "",
|
|
2427
|
+
message.id,
|
|
2428
|
+
files
|
|
2429
|
+
);
|
|
2430
|
+
const memories = [];
|
|
2431
|
+
for (const m of messages) {
|
|
2432
|
+
const actions = content.actions;
|
|
2433
|
+
const memory = {
|
|
2434
|
+
id: createUniqueUuid4(this.runtime, m.id),
|
|
2435
|
+
entityId: this.runtime.agentId,
|
|
2436
|
+
agentId: this.runtime.agentId,
|
|
2437
|
+
content: {
|
|
2438
|
+
...content,
|
|
2439
|
+
actions,
|
|
2440
|
+
inReplyTo: messageId,
|
|
2441
|
+
url: m.url,
|
|
2442
|
+
channelType: type
|
|
2443
|
+
},
|
|
2444
|
+
roomId,
|
|
2445
|
+
createdAt: m.createdTimestamp
|
|
2446
|
+
};
|
|
2447
|
+
memories.push(memory);
|
|
2448
|
+
}
|
|
2449
|
+
for (const m of memories) {
|
|
2450
|
+
await this.runtime.createMemory(m, "messages");
|
|
2451
|
+
}
|
|
2452
|
+
if (typingData.interval && !typingData.cleared) {
|
|
2453
|
+
clearInterval(typingData.interval);
|
|
2454
|
+
typingData.cleared = true;
|
|
2455
|
+
}
|
|
2456
|
+
return memories;
|
|
2457
|
+
} catch (error) {
|
|
2458
|
+
console.error("Error sending message:", error);
|
|
2459
|
+
if (typingData.interval && !typingData.cleared) {
|
|
2460
|
+
clearInterval(typingData.interval);
|
|
2461
|
+
typingData.cleared = true;
|
|
2462
|
+
}
|
|
2463
|
+
return [];
|
|
2301
2464
|
}
|
|
2302
|
-
return memories;
|
|
2303
2465
|
} catch (error) {
|
|
2304
|
-
console.error("Error
|
|
2466
|
+
console.error("Error handling message:", error);
|
|
2467
|
+
if (typingData.interval && !typingData.cleared) {
|
|
2468
|
+
clearInterval(typingData.interval);
|
|
2469
|
+
typingData.cleared = true;
|
|
2470
|
+
}
|
|
2305
2471
|
return [];
|
|
2306
2472
|
}
|
|
2307
2473
|
};
|
|
@@ -2310,6 +2476,12 @@ var MessageManager = class {
|
|
|
2310
2476
|
message: newMessage,
|
|
2311
2477
|
callback
|
|
2312
2478
|
});
|
|
2479
|
+
setTimeout(() => {
|
|
2480
|
+
if (typingData.interval && !typingData.cleared) {
|
|
2481
|
+
clearInterval(typingData.interval);
|
|
2482
|
+
typingData.cleared = true;
|
|
2483
|
+
}
|
|
2484
|
+
}, 500);
|
|
2313
2485
|
} catch (error) {
|
|
2314
2486
|
console.error("Error handling message:", error);
|
|
2315
2487
|
}
|
|
@@ -2356,11 +2528,8 @@ var MessageManager = class {
|
|
|
2356
2528
|
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
|
2357
2529
|
const urls = processedContent.match(urlRegex) || [];
|
|
2358
2530
|
for (const url of urls) {
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
if (!videoService) {
|
|
2362
|
-
throw new Error("Video service not found");
|
|
2363
|
-
}
|
|
2531
|
+
const videoService = this.runtime.getService(ServiceType4.VIDEO);
|
|
2532
|
+
if (videoService?.isVideoUrl(url)) {
|
|
2364
2533
|
const videoInfo = await videoService.processVideo(url, this.runtime);
|
|
2365
2534
|
attachments.push({
|
|
2366
2535
|
id: `youtube-${Date.now()}`,
|
|
@@ -2373,7 +2542,8 @@ var MessageManager = class {
|
|
|
2373
2542
|
} else {
|
|
2374
2543
|
const browserService = this.runtime.getService(ServiceType4.BROWSER);
|
|
2375
2544
|
if (!browserService) {
|
|
2376
|
-
|
|
2545
|
+
logger4.warn("Browser service not found");
|
|
2546
|
+
continue;
|
|
2377
2547
|
}
|
|
2378
2548
|
const { title, description: summary } = await browserService.getPageContent(
|
|
2379
2549
|
url,
|
|
@@ -2432,6 +2602,7 @@ import {
|
|
|
2432
2602
|
ChannelType as ChannelType8,
|
|
2433
2603
|
ModelType as ModelType8,
|
|
2434
2604
|
createUniqueUuid as createUniqueUuid5,
|
|
2605
|
+
getWavHeader,
|
|
2435
2606
|
logger as logger5
|
|
2436
2607
|
} from "@elizaos/core";
|
|
2437
2608
|
import {
|
|
@@ -2561,9 +2732,17 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2561
2732
|
super();
|
|
2562
2733
|
this.client = service.client;
|
|
2563
2734
|
this.runtime = runtime;
|
|
2564
|
-
this.
|
|
2565
|
-
|
|
2566
|
-
|
|
2735
|
+
this.ready = false;
|
|
2736
|
+
if (this.client) {
|
|
2737
|
+
this.client.on("voiceManagerReady", () => {
|
|
2738
|
+
this.setReady(true);
|
|
2739
|
+
});
|
|
2740
|
+
} else {
|
|
2741
|
+
logger5.error(
|
|
2742
|
+
"Discord client is not available in VoiceManager constructor for voiceManagerReady event"
|
|
2743
|
+
);
|
|
2744
|
+
this.ready = false;
|
|
2745
|
+
}
|
|
2567
2746
|
}
|
|
2568
2747
|
/**
|
|
2569
2748
|
* Asynchronously retrieves the type of the channel.
|
|
@@ -2575,6 +2754,11 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2575
2754
|
case DiscordChannelType3.GuildVoice:
|
|
2576
2755
|
case DiscordChannelType3.GuildStageVoice:
|
|
2577
2756
|
return ChannelType8.VOICE_GROUP;
|
|
2757
|
+
default:
|
|
2758
|
+
logger5.error(
|
|
2759
|
+
`getChannelType received unexpected channel type: ${channel.type} for channel ${channel.id}`
|
|
2760
|
+
);
|
|
2761
|
+
throw new Error(`Unexpected channel type encountered: ${channel.type}`);
|
|
2578
2762
|
}
|
|
2579
2763
|
}
|
|
2580
2764
|
/**
|
|
@@ -2605,7 +2789,7 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2605
2789
|
const newChannelId = newState.channelId;
|
|
2606
2790
|
const member = newState.member;
|
|
2607
2791
|
if (!member) return;
|
|
2608
|
-
if (member.id === this.client
|
|
2792
|
+
if (member.id === this.client?.user?.id) {
|
|
2609
2793
|
return;
|
|
2610
2794
|
}
|
|
2611
2795
|
if (oldChannelId === newChannelId) {
|
|
@@ -2639,7 +2823,7 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2639
2823
|
adapterCreator: channel.guild.voiceAdapterCreator,
|
|
2640
2824
|
selfDeaf: false,
|
|
2641
2825
|
selfMute: false,
|
|
2642
|
-
group: this.client
|
|
2826
|
+
group: this.client?.user?.id ?? "default-group"
|
|
2643
2827
|
});
|
|
2644
2828
|
try {
|
|
2645
2829
|
await Promise.race([
|
|
@@ -2715,7 +2899,12 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2715
2899
|
* @returns {VoiceConnection | undefined} The voice connection for the specified guild ID, or undefined if not found.
|
|
2716
2900
|
*/
|
|
2717
2901
|
getVoiceConnection(guildId) {
|
|
2718
|
-
const
|
|
2902
|
+
const userId = this.client?.user?.id;
|
|
2903
|
+
if (!userId) {
|
|
2904
|
+
logger5.error("Client user ID is not available.");
|
|
2905
|
+
return void 0;
|
|
2906
|
+
}
|
|
2907
|
+
const connections = getVoiceConnections(userId);
|
|
2719
2908
|
if (!connections) {
|
|
2720
2909
|
return;
|
|
2721
2910
|
}
|
|
@@ -2740,6 +2929,7 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2740
2929
|
emitClose: true
|
|
2741
2930
|
});
|
|
2742
2931
|
if (!receiveStream || receiveStream.readableLength === 0) {
|
|
2932
|
+
logger5.warn(`[monitorMember] No receiveStream or empty stream for user ${entityId}`);
|
|
2743
2933
|
return;
|
|
2744
2934
|
}
|
|
2745
2935
|
const opusDecoder = new prism.opus.Decoder({
|
|
@@ -2768,7 +2958,11 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2768
2958
|
});
|
|
2769
2959
|
pipeline(receiveStream, opusDecoder, (err) => {
|
|
2770
2960
|
if (err) {
|
|
2771
|
-
logger5.debug(`Opus decoding pipeline error: ${err}`);
|
|
2961
|
+
logger5.debug(`[monitorMember] Opus decoding pipeline error for user ${entityId}: ${err}`);
|
|
2962
|
+
} else {
|
|
2963
|
+
logger5.debug(
|
|
2964
|
+
`[monitorMember] Opus decoding pipeline finished successfully for user ${entityId}`
|
|
2965
|
+
);
|
|
2772
2966
|
}
|
|
2773
2967
|
});
|
|
2774
2968
|
this.streams.set(entityId, opusDecoder);
|
|
@@ -2793,7 +2987,7 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2793
2987
|
opusDecoder.on("error", errorHandler);
|
|
2794
2988
|
opusDecoder.on("close", closeHandler);
|
|
2795
2989
|
receiveStream?.on("close", streamCloseHandler);
|
|
2796
|
-
this.client
|
|
2990
|
+
this.client?.emit("userStream", entityId, name, userName, channel, opusDecoder);
|
|
2797
2991
|
}
|
|
2798
2992
|
/**
|
|
2799
2993
|
* Leaves the specified voice channel and stops monitoring all members in that channel.
|
|
@@ -2808,7 +3002,7 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2808
3002
|
this.connections.delete(channel.id);
|
|
2809
3003
|
}
|
|
2810
3004
|
for (const [memberId, monitorInfo] of this.activeMonitors) {
|
|
2811
|
-
if (monitorInfo.channel.id === channel.id && memberId !== this.client
|
|
3005
|
+
if (monitorInfo.channel.id === channel.id && memberId !== this.client?.user?.id) {
|
|
2812
3006
|
this.stopMonitoringMember(memberId);
|
|
2813
3007
|
}
|
|
2814
3008
|
}
|
|
@@ -2843,8 +3037,10 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2843
3037
|
}
|
|
2844
3038
|
if (this.activeAudioPlayer || this.processingVoice) {
|
|
2845
3039
|
const state = this.userStates.get(entityId);
|
|
2846
|
-
state
|
|
2847
|
-
|
|
3040
|
+
if (state) {
|
|
3041
|
+
state.buffers.length = 0;
|
|
3042
|
+
state.totalLength = 0;
|
|
3043
|
+
}
|
|
2848
3044
|
return;
|
|
2849
3045
|
}
|
|
2850
3046
|
if (this.transcriptionTimeout) {
|
|
@@ -2872,8 +3068,7 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2872
3068
|
* @param {BaseGuildVoiceChannel} channel - The voice channel the user is in.
|
|
2873
3069
|
* @param {Readable} audioStream - The audio stream to monitor.
|
|
2874
3070
|
*/
|
|
2875
|
-
async handleUserStream(
|
|
2876
|
-
const entityId = createUniqueUuid5(this.runtime, userId);
|
|
3071
|
+
async handleUserStream(entityId, name, userName, channel, audioStream) {
|
|
2877
3072
|
logger5.debug(`Starting audio monitor for user: ${entityId}`);
|
|
2878
3073
|
if (!this.userStates.has(entityId)) {
|
|
2879
3074
|
this.userStates.set(entityId, {
|
|
@@ -2925,17 +3120,18 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2925
3120
|
const state = this.userStates.get(entityId);
|
|
2926
3121
|
if (!state || state.buffers.length === 0) return;
|
|
2927
3122
|
try {
|
|
2928
|
-
let
|
|
3123
|
+
let isValidTranscription2 = function(text) {
|
|
2929
3124
|
if (!text || text.includes("[BLANK_AUDIO]")) return false;
|
|
2930
3125
|
return true;
|
|
2931
3126
|
};
|
|
3127
|
+
var isValidTranscription = isValidTranscription2;
|
|
2932
3128
|
const inputBuffer = Buffer.concat(state.buffers, state.totalLength);
|
|
2933
3129
|
state.buffers.length = 0;
|
|
2934
3130
|
state.totalLength = 0;
|
|
2935
3131
|
const wavBuffer = await this.convertOpusToWav(inputBuffer);
|
|
2936
3132
|
logger5.debug("Starting transcription...");
|
|
2937
3133
|
const transcriptionText = await this.runtime.useModel(ModelType8.TRANSCRIPTION, wavBuffer);
|
|
2938
|
-
if (transcriptionText &&
|
|
3134
|
+
if (transcriptionText && isValidTranscription2(transcriptionText)) {
|
|
2939
3135
|
state.transcriptionText += transcriptionText;
|
|
2940
3136
|
}
|
|
2941
3137
|
if (state.transcriptionText.length) {
|
|
@@ -2965,21 +3161,24 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2965
3161
|
return { text: "", actions: ["IGNORE"] };
|
|
2966
3162
|
}
|
|
2967
3163
|
const roomId = createUniqueUuid5(this.runtime, channelId);
|
|
3164
|
+
const uniqueEntityId = createUniqueUuid5(this.runtime, entityId);
|
|
2968
3165
|
const type = await this.getChannelType(channel);
|
|
2969
3166
|
await this.runtime.ensureConnection({
|
|
2970
|
-
entityId,
|
|
3167
|
+
entityId: uniqueEntityId,
|
|
2971
3168
|
roomId,
|
|
2972
3169
|
userName,
|
|
2973
3170
|
name,
|
|
2974
3171
|
source: "discord",
|
|
2975
3172
|
channelId,
|
|
2976
3173
|
serverId: channel.guild.id,
|
|
2977
|
-
type
|
|
3174
|
+
type,
|
|
3175
|
+
worldId: createUniqueUuid5(this.runtime, channel.guild.id),
|
|
3176
|
+
worldName: channel.guild.name
|
|
2978
3177
|
});
|
|
2979
3178
|
const memory = {
|
|
2980
3179
|
id: createUniqueUuid5(this.runtime, `${channelId}-voice-message-${Date.now()}`),
|
|
2981
3180
|
agentId: this.runtime.agentId,
|
|
2982
|
-
entityId,
|
|
3181
|
+
entityId: uniqueEntityId,
|
|
2983
3182
|
roomId,
|
|
2984
3183
|
content: {
|
|
2985
3184
|
text: message,
|
|
@@ -3009,7 +3208,7 @@ var VoiceManager = class extends EventEmitter {
|
|
|
3009
3208
|
createdAt: Date.now()
|
|
3010
3209
|
};
|
|
3011
3210
|
if (responseMemory.content.text?.trim()) {
|
|
3012
|
-
await this.runtime.createMemory(responseMemory);
|
|
3211
|
+
await this.runtime.createMemory(responseMemory, "messages");
|
|
3013
3212
|
const responseStream = await this.runtime.useModel(
|
|
3014
3213
|
ModelType8.TEXT_TO_SPEECH,
|
|
3015
3214
|
content.text
|
|
@@ -3198,6 +3397,13 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3198
3397
|
character;
|
|
3199
3398
|
messageManager;
|
|
3200
3399
|
voiceManager;
|
|
3400
|
+
userSelections = /* @__PURE__ */ new Map();
|
|
3401
|
+
timeouts = [];
|
|
3402
|
+
/**
|
|
3403
|
+
* List of allowed channel IDs (parsed from CHANNEL_IDS env var).
|
|
3404
|
+
* If undefined, all channels are allowed.
|
|
3405
|
+
*/
|
|
3406
|
+
allowedChannelIds;
|
|
3201
3407
|
/**
|
|
3202
3408
|
* Constructor for Discord client.
|
|
3203
3409
|
* Initializes the Discord client with specified intents and partials,
|
|
@@ -3207,6 +3413,11 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3207
3413
|
*/
|
|
3208
3414
|
constructor(runtime) {
|
|
3209
3415
|
super(runtime);
|
|
3416
|
+
this.character = runtime.character;
|
|
3417
|
+
const channelIdsRaw = runtime.getSetting("CHANNEL_IDS");
|
|
3418
|
+
if (channelIdsRaw && channelIdsRaw.trim()) {
|
|
3419
|
+
this.allowedChannelIds = channelIdsRaw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
3420
|
+
}
|
|
3210
3421
|
const token = runtime.getSetting("DISCORD_API_TOKEN");
|
|
3211
3422
|
if (!token || token.trim() === "") {
|
|
3212
3423
|
logger6.warn("Discord API Token not provided - Discord functionality will be unavailable");
|
|
@@ -3232,19 +3443,135 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3232
3443
|
this.runtime = runtime;
|
|
3233
3444
|
this.voiceManager = new VoiceManager(this, runtime);
|
|
3234
3445
|
this.messageManager = new MessageManager(this);
|
|
3235
|
-
this.client.once(Events.ClientReady, this.
|
|
3446
|
+
this.client.once(Events.ClientReady, this.onReady.bind(this));
|
|
3236
3447
|
this.client.login(token).catch((error) => {
|
|
3237
|
-
logger6.error(
|
|
3448
|
+
logger6.error(
|
|
3449
|
+
`Failed to login to Discord: ${error instanceof Error ? error.message : String(error)}`
|
|
3450
|
+
);
|
|
3238
3451
|
this.client = null;
|
|
3239
3452
|
});
|
|
3240
3453
|
this.setupEventListeners();
|
|
3454
|
+
this.registerSendHandler();
|
|
3241
3455
|
} catch (error) {
|
|
3242
|
-
logger6.error(
|
|
3456
|
+
logger6.error(
|
|
3457
|
+
`Error initializing Discord client: ${error instanceof Error ? error.message : String(error)}`
|
|
3458
|
+
);
|
|
3243
3459
|
this.client = null;
|
|
3244
3460
|
}
|
|
3245
3461
|
}
|
|
3462
|
+
static async start(runtime) {
|
|
3463
|
+
const service = new _DiscordService(runtime);
|
|
3464
|
+
return service;
|
|
3465
|
+
}
|
|
3466
|
+
/**
|
|
3467
|
+
* Registers the send handler with the runtime.
|
|
3468
|
+
* @private
|
|
3469
|
+
*/
|
|
3470
|
+
registerSendHandler() {
|
|
3471
|
+
if (this.runtime) {
|
|
3472
|
+
this.runtime.registerSendHandler("discord", this.handleSendMessage.bind(this));
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
/**
|
|
3476
|
+
* The SendHandlerFunction implementation for Discord.
|
|
3477
|
+
* @param {IAgentRuntime} runtime - The runtime instance.
|
|
3478
|
+
* @param {TargetInfo} target - The target information for the message.
|
|
3479
|
+
* @param {Content} content - The content of the message to send.
|
|
3480
|
+
* @returns {Promise<void>} A promise that resolves when the message is sent or rejects on error.
|
|
3481
|
+
* @throws {Error} If the client is not ready, target is invalid, or sending fails.
|
|
3482
|
+
*/
|
|
3483
|
+
async handleSendMessage(runtime, target, content) {
|
|
3484
|
+
if (!this.client?.isReady()) {
|
|
3485
|
+
logger6.error("[Discord SendHandler] Client not ready.");
|
|
3486
|
+
throw new Error("Discord client is not ready.");
|
|
3487
|
+
}
|
|
3488
|
+
if (target.channelId && this.allowedChannelIds && !this.allowedChannelIds.includes(target.channelId)) {
|
|
3489
|
+
logger6.warn(
|
|
3490
|
+
`[Discord SendHandler] Channel ${target.channelId} is not in allowed channels, skipping send.`
|
|
3491
|
+
);
|
|
3492
|
+
return;
|
|
3493
|
+
}
|
|
3494
|
+
let targetChannel = null;
|
|
3495
|
+
try {
|
|
3496
|
+
if (target.channelId) {
|
|
3497
|
+
targetChannel = await this.client.channels.fetch(target.channelId);
|
|
3498
|
+
} else if (target.entityId) {
|
|
3499
|
+
const discordUserId = target.entityId;
|
|
3500
|
+
const user = await this.client.users.fetch(discordUserId);
|
|
3501
|
+
if (user) {
|
|
3502
|
+
targetChannel = await user.dmChannel ?? await user.createDM();
|
|
3503
|
+
}
|
|
3504
|
+
} else {
|
|
3505
|
+
throw new Error("Discord SendHandler requires channelId or entityId.");
|
|
3506
|
+
}
|
|
3507
|
+
if (!targetChannel) {
|
|
3508
|
+
throw new Error(
|
|
3509
|
+
`Could not find target Discord channel/DM for target: ${JSON.stringify(target)}`
|
|
3510
|
+
);
|
|
3511
|
+
}
|
|
3512
|
+
if (targetChannel.isTextBased() && !targetChannel.isVoiceBased()) {
|
|
3513
|
+
if ("send" in targetChannel && typeof targetChannel.send === "function") {
|
|
3514
|
+
if (content.text) {
|
|
3515
|
+
const chunks = this.splitMessage(content.text, 2e3);
|
|
3516
|
+
for (const chunk of chunks) {
|
|
3517
|
+
await targetChannel.send(chunk);
|
|
3518
|
+
}
|
|
3519
|
+
} else {
|
|
3520
|
+
logger6.warn("[Discord SendHandler] No text content provided to send.");
|
|
3521
|
+
}
|
|
3522
|
+
} else {
|
|
3523
|
+
throw new Error(`Target channel ${targetChannel.id} does not have a send method.`);
|
|
3524
|
+
}
|
|
3525
|
+
} else {
|
|
3526
|
+
throw new Error(
|
|
3527
|
+
`Target channel ${targetChannel.id} is not a valid text-based channel for sending messages.`
|
|
3528
|
+
);
|
|
3529
|
+
}
|
|
3530
|
+
} catch (error) {
|
|
3531
|
+
logger6.error(
|
|
3532
|
+
`[Discord SendHandler] Error sending message: ${error instanceof Error ? error.message : String(error)}`,
|
|
3533
|
+
{
|
|
3534
|
+
target,
|
|
3535
|
+
content
|
|
3536
|
+
}
|
|
3537
|
+
);
|
|
3538
|
+
throw error;
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
/**
|
|
3542
|
+
* Helper function to split a string into chunks of a maximum length.
|
|
3543
|
+
*
|
|
3544
|
+
* @param {string} text - The text to split.
|
|
3545
|
+
* @param {number} maxLength - The maximum length of each chunk.
|
|
3546
|
+
* @returns {string[]} An array of text chunks.
|
|
3547
|
+
* @private
|
|
3548
|
+
*/
|
|
3549
|
+
// Helper to split messages
|
|
3550
|
+
splitMessage(text, maxLength) {
|
|
3551
|
+
const chunks = [];
|
|
3552
|
+
let currentChunk = "";
|
|
3553
|
+
const lines = text.split("\n");
|
|
3554
|
+
for (const line of lines) {
|
|
3555
|
+
if (currentChunk.length + line.length + 1 <= maxLength) {
|
|
3556
|
+
currentChunk += (currentChunk ? "\n" : "") + line;
|
|
3557
|
+
} else {
|
|
3558
|
+
if (currentChunk) chunks.push(currentChunk);
|
|
3559
|
+
if (line.length > maxLength) {
|
|
3560
|
+
for (let i = 0; i < line.length; i += maxLength) {
|
|
3561
|
+
chunks.push(line.substring(i, i + maxLength));
|
|
3562
|
+
}
|
|
3563
|
+
currentChunk = "";
|
|
3564
|
+
} else {
|
|
3565
|
+
currentChunk = line;
|
|
3566
|
+
}
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
if (currentChunk) chunks.push(currentChunk);
|
|
3570
|
+
return chunks;
|
|
3571
|
+
}
|
|
3246
3572
|
/**
|
|
3247
|
-
* Set up event listeners for the client
|
|
3573
|
+
* Set up event listeners for the client.
|
|
3574
|
+
* @private
|
|
3248
3575
|
*/
|
|
3249
3576
|
setupEventListeners() {
|
|
3250
3577
|
if (!this.client) {
|
|
@@ -3254,8 +3581,11 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3254
3581
|
if (message.author.id === this.client?.user?.id || message.author.bot) {
|
|
3255
3582
|
return;
|
|
3256
3583
|
}
|
|
3584
|
+
if (this.allowedChannelIds && !this.allowedChannelIds.includes(message.channel.id)) {
|
|
3585
|
+
return;
|
|
3586
|
+
}
|
|
3257
3587
|
try {
|
|
3258
|
-
this.messageManager
|
|
3588
|
+
this.messageManager?.handleMessage(message);
|
|
3259
3589
|
} catch (error) {
|
|
3260
3590
|
logger6.error(`Error handling message: ${error}`);
|
|
3261
3591
|
}
|
|
@@ -3264,6 +3594,9 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3264
3594
|
if (user.id === this.client?.user?.id) {
|
|
3265
3595
|
return;
|
|
3266
3596
|
}
|
|
3597
|
+
if (this.allowedChannelIds && reaction.message.channel && !this.allowedChannelIds.includes(reaction.message.channel.id)) {
|
|
3598
|
+
return;
|
|
3599
|
+
}
|
|
3267
3600
|
try {
|
|
3268
3601
|
await this.handleReactionAdd(reaction, user);
|
|
3269
3602
|
} catch (error) {
|
|
@@ -3274,6 +3607,9 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3274
3607
|
if (user.id === this.client?.user?.id) {
|
|
3275
3608
|
return;
|
|
3276
3609
|
}
|
|
3610
|
+
if (this.allowedChannelIds && reaction.message.channel && !this.allowedChannelIds.includes(reaction.message.channel.id)) {
|
|
3611
|
+
return;
|
|
3612
|
+
}
|
|
3277
3613
|
try {
|
|
3278
3614
|
await this.handleReactionRemove(reaction, user);
|
|
3279
3615
|
} catch (error) {
|
|
@@ -3295,27 +3631,38 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3295
3631
|
}
|
|
3296
3632
|
});
|
|
3297
3633
|
this.client.on("interactionCreate", async (interaction) => {
|
|
3634
|
+
if (this.allowedChannelIds && interaction.channelId && !this.allowedChannelIds.includes(interaction.channelId)) {
|
|
3635
|
+
return;
|
|
3636
|
+
}
|
|
3298
3637
|
try {
|
|
3299
3638
|
await this.handleInteractionCreate(interaction);
|
|
3300
3639
|
} catch (error) {
|
|
3301
3640
|
logger6.error(`Error handling interaction: ${error}`);
|
|
3302
3641
|
}
|
|
3303
3642
|
});
|
|
3643
|
+
this.client.on("userStream", (entityId, name, userName, channel, opusDecoder) => {
|
|
3644
|
+
if (entityId !== this.client?.user?.id) {
|
|
3645
|
+
this.voiceManager?.handleUserStream(entityId, name, userName, channel, opusDecoder);
|
|
3646
|
+
}
|
|
3647
|
+
});
|
|
3304
3648
|
}
|
|
3305
3649
|
/**
|
|
3306
3650
|
* Handles the event when a new member joins a guild.
|
|
3307
3651
|
*
|
|
3308
3652
|
* @param {GuildMember} member - The GuildMember object representing the new member that joined the guild.
|
|
3309
3653
|
* @returns {Promise<void>} - A Promise that resolves once the event handling is complete.
|
|
3654
|
+
* @private
|
|
3310
3655
|
*/
|
|
3311
3656
|
async handleGuildMemberAdd(member) {
|
|
3312
3657
|
logger6.log(`New member joined: ${member.user.username}`);
|
|
3313
3658
|
const guild = member.guild;
|
|
3314
3659
|
const tag = member.user.bot ? `${member.user.username}#${member.user.discriminator}` : member.user.username;
|
|
3660
|
+
const worldId = createUniqueUuid6(this.runtime, guild.id);
|
|
3661
|
+
const entityId = createUniqueUuid6(this.runtime, member.id);
|
|
3315
3662
|
this.runtime.emitEvent([EventType2.ENTITY_JOINED], {
|
|
3316
3663
|
runtime: this.runtime,
|
|
3317
|
-
entityId
|
|
3318
|
-
worldId
|
|
3664
|
+
entityId,
|
|
3665
|
+
worldId,
|
|
3319
3666
|
source: "discord",
|
|
3320
3667
|
metadata: {
|
|
3321
3668
|
originalId: member.id,
|
|
@@ -3327,170 +3674,449 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3327
3674
|
});
|
|
3328
3675
|
this.runtime.emitEvent(["DISCORD_USER_JOINED" /* ENTITY_JOINED */], {
|
|
3329
3676
|
runtime: this.runtime,
|
|
3330
|
-
entityId
|
|
3677
|
+
entityId,
|
|
3678
|
+
worldId,
|
|
3331
3679
|
member,
|
|
3332
3680
|
guild
|
|
3333
3681
|
});
|
|
3334
3682
|
}
|
|
3335
3683
|
/**
|
|
3336
|
-
*
|
|
3337
|
-
*
|
|
3338
|
-
* @
|
|
3339
|
-
* @
|
|
3340
|
-
*
|
|
3684
|
+
* 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.
|
|
3685
|
+
* @param {Guild} guild - The guild that the bot has joined.
|
|
3686
|
+
* @returns {Promise<void>} A promise that resolves when the guild creation is handled.
|
|
3687
|
+
* @private
|
|
3341
3688
|
*/
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3689
|
+
async handleGuildCreate(guild) {
|
|
3690
|
+
logger6.log(`Joined guild ${guild.name}`);
|
|
3691
|
+
const fullGuild = await guild.fetch();
|
|
3692
|
+
this.voiceManager?.scanGuild(guild);
|
|
3693
|
+
const ownerId = createUniqueUuid6(this.runtime, fullGuild.ownerId);
|
|
3694
|
+
const worldId = createUniqueUuid6(this.runtime, fullGuild.id);
|
|
3695
|
+
const standardizedData = {
|
|
3696
|
+
runtime: this.runtime,
|
|
3697
|
+
rooms: await this.buildStandardizedRooms(fullGuild, worldId),
|
|
3698
|
+
users: await this.buildStandardizedUsers(fullGuild),
|
|
3699
|
+
world: {
|
|
3700
|
+
id: worldId,
|
|
3701
|
+
name: fullGuild.name,
|
|
3702
|
+
agentId: this.runtime.agentId,
|
|
3703
|
+
serverId: fullGuild.id,
|
|
3704
|
+
metadata: {
|
|
3705
|
+
ownership: fullGuild.ownerId ? { ownerId } : void 0,
|
|
3706
|
+
roles: {
|
|
3707
|
+
[ownerId]: Role.OWNER
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3710
|
+
},
|
|
3711
|
+
source: "discord"
|
|
3712
|
+
};
|
|
3713
|
+
this.runtime.emitEvent(["DISCORD_WORLD_JOINED" /* WORLD_JOINED */], {
|
|
3714
|
+
runtime: this.runtime,
|
|
3715
|
+
server: fullGuild,
|
|
3716
|
+
source: "discord"
|
|
3717
|
+
});
|
|
3718
|
+
this.runtime.emitEvent([EventType2.WORLD_JOINED], standardizedData);
|
|
3719
|
+
}
|
|
3720
|
+
/**
|
|
3721
|
+
* Handles interactions created by the user, specifically commands and message components.
|
|
3722
|
+
* @param {Interaction} interaction - The interaction object received.
|
|
3723
|
+
* @returns {Promise<void>} A promise that resolves when the interaction is handled.
|
|
3724
|
+
* @private
|
|
3725
|
+
*/
|
|
3726
|
+
async handleInteractionCreate(interaction) {
|
|
3727
|
+
if (interaction.isCommand()) {
|
|
3728
|
+
switch (interaction.commandName) {
|
|
3729
|
+
case "joinchannel":
|
|
3730
|
+
await this.voiceManager?.handleJoinChannelCommand(interaction);
|
|
3731
|
+
break;
|
|
3732
|
+
case "leavechannel":
|
|
3733
|
+
await this.voiceManager?.handleLeaveChannelCommand(interaction);
|
|
3734
|
+
break;
|
|
3735
|
+
}
|
|
3346
3736
|
}
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3737
|
+
if (interaction.isMessageComponent()) {
|
|
3738
|
+
logger6.info(`Received component interaction: ${interaction.customId}`);
|
|
3739
|
+
const userId = interaction.user?.id;
|
|
3740
|
+
const messageId = interaction.message?.id;
|
|
3741
|
+
if (!this.userSelections.has(userId)) {
|
|
3742
|
+
this.userSelections.set(userId, {});
|
|
3743
|
+
}
|
|
3744
|
+
const userSelections = this.userSelections.get(userId);
|
|
3745
|
+
if (!userSelections) {
|
|
3746
|
+
logger6.error(`User selections map unexpectedly missing for user ${userId}`);
|
|
3747
|
+
return;
|
|
3748
|
+
}
|
|
3351
3749
|
try {
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3750
|
+
if (interaction.isStringSelectMenu()) {
|
|
3751
|
+
logger6.info(`Values selected: ${JSON.stringify(interaction.values)}`);
|
|
3752
|
+
logger6.info(
|
|
3753
|
+
`User ${userId} selected values for ${interaction.customId}: ${JSON.stringify(interaction.values)}`
|
|
3754
|
+
);
|
|
3755
|
+
userSelections[messageId] = {
|
|
3756
|
+
...userSelections[messageId],
|
|
3757
|
+
[interaction.customId]: interaction.values
|
|
3758
|
+
};
|
|
3759
|
+
logger6.info(
|
|
3760
|
+
`Current selections for message ${messageId}: ${JSON.stringify(userSelections[messageId])}`
|
|
3761
|
+
);
|
|
3762
|
+
await interaction.deferUpdate();
|
|
3355
3763
|
}
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
}
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3764
|
+
if (interaction.isButton()) {
|
|
3765
|
+
logger6.info("Button interaction detected");
|
|
3766
|
+
logger6.info(`Button pressed by user ${userId}: ${interaction.customId}`);
|
|
3767
|
+
const formSelections = userSelections[messageId] || {};
|
|
3768
|
+
logger6.info(`Form data being submitted: ${JSON.stringify(formSelections)}`);
|
|
3769
|
+
this.runtime.emitEvent(["DISCORD_INTERACTION"], {
|
|
3770
|
+
interaction: {
|
|
3771
|
+
customId: interaction.customId,
|
|
3772
|
+
componentType: interaction.componentType,
|
|
3773
|
+
type: interaction.type,
|
|
3774
|
+
user: userId,
|
|
3775
|
+
messageId,
|
|
3776
|
+
selections: formSelections
|
|
3777
|
+
},
|
|
3778
|
+
source: "discord"
|
|
3363
3779
|
});
|
|
3364
|
-
|
|
3365
|
-
|
|
3780
|
+
delete userSelections[messageId];
|
|
3781
|
+
logger6.info(`Cleared selections for message ${messageId}`);
|
|
3782
|
+
await interaction.deferUpdate();
|
|
3783
|
+
await interaction.followUp({
|
|
3784
|
+
content: "Form submitted successfully!",
|
|
3785
|
+
ephemeral: true
|
|
3786
|
+
});
|
|
3787
|
+
}
|
|
3366
3788
|
} catch (error) {
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
logger6.
|
|
3375
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
3789
|
+
logger6.error(`Error handling component interaction: ${error}`);
|
|
3790
|
+
try {
|
|
3791
|
+
await interaction.followUp({
|
|
3792
|
+
content: "There was an error processing your interaction.",
|
|
3793
|
+
ephemeral: true
|
|
3794
|
+
});
|
|
3795
|
+
} catch (followUpError) {
|
|
3796
|
+
logger6.error(`Error sending follow-up message: ${followUpError}`);
|
|
3376
3797
|
}
|
|
3377
3798
|
}
|
|
3378
3799
|
}
|
|
3379
|
-
throw new Error(
|
|
3380
|
-
`Discord initialization failed after ${maxRetries} attempts. Last error: ${lastError?.message}`
|
|
3381
|
-
);
|
|
3382
3800
|
}
|
|
3383
3801
|
/**
|
|
3384
|
-
*
|
|
3802
|
+
* Builds a standardized list of rooms from Discord guild channels.
|
|
3385
3803
|
*
|
|
3386
|
-
* @param {
|
|
3387
|
-
* @
|
|
3804
|
+
* @param {Guild} guild The guild to build rooms for.
|
|
3805
|
+
* @param {UUID} _worldId The ID of the world to associate with the rooms (currently unused in favor of direct channel to room mapping).
|
|
3806
|
+
* @returns {Promise<any[]>} An array of standardized room objects.
|
|
3807
|
+
* @private
|
|
3388
3808
|
*/
|
|
3389
|
-
|
|
3390
|
-
const
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3809
|
+
async buildStandardizedRooms(guild, _worldId) {
|
|
3810
|
+
const rooms = [];
|
|
3811
|
+
for (const [channelId, channel] of guild.channels.cache) {
|
|
3812
|
+
if (channel.type === DiscordChannelType4.GuildText || channel.type === DiscordChannelType4.GuildVoice) {
|
|
3813
|
+
const roomId = createUniqueUuid6(this.runtime, channelId);
|
|
3814
|
+
let channelType;
|
|
3815
|
+
switch (channel.type) {
|
|
3816
|
+
case DiscordChannelType4.GuildText:
|
|
3817
|
+
channelType = ChannelType9.GROUP;
|
|
3818
|
+
break;
|
|
3819
|
+
case DiscordChannelType4.GuildVoice:
|
|
3820
|
+
channelType = ChannelType9.VOICE_GROUP;
|
|
3821
|
+
break;
|
|
3822
|
+
default:
|
|
3823
|
+
channelType = ChannelType9.GROUP;
|
|
3824
|
+
}
|
|
3825
|
+
let participants = [];
|
|
3826
|
+
if (guild.memberCount < 1e3 && channel.type === DiscordChannelType4.GuildText) {
|
|
3827
|
+
try {
|
|
3828
|
+
participants = Array.from(guild.members.cache.values()).filter(
|
|
3829
|
+
(member) => channel.permissionsFor(member)?.has(PermissionsBitField2.Flags.ViewChannel)
|
|
3830
|
+
).map((member) => createUniqueUuid6(this.runtime, member.id));
|
|
3831
|
+
} catch (error) {
|
|
3832
|
+
logger6.warn(`Failed to get participants for channel ${channel.name}:`, error);
|
|
3833
|
+
}
|
|
3834
|
+
}
|
|
3835
|
+
rooms.push({
|
|
3836
|
+
id: roomId,
|
|
3837
|
+
name: channel.name,
|
|
3838
|
+
type: channelType,
|
|
3839
|
+
channelId: channel.id,
|
|
3840
|
+
participants
|
|
3841
|
+
});
|
|
3842
|
+
}
|
|
3399
3843
|
}
|
|
3844
|
+
return rooms;
|
|
3400
3845
|
}
|
|
3401
3846
|
/**
|
|
3402
|
-
*
|
|
3847
|
+
* Builds a standardized list of users (entities) from Discord guild members.
|
|
3848
|
+
* Implements different strategies based on guild size for performance.
|
|
3403
3849
|
*
|
|
3404
|
-
* @
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
await this.client?.destroy();
|
|
3408
|
-
}
|
|
3409
|
-
/**
|
|
3410
|
-
* Handle the event when the client is ready.
|
|
3411
|
-
* @param {Object} readyClient - The ready client object containing user information.
|
|
3412
|
-
* @param {string} readyClient.user.tag - The username and discriminator of the client user.
|
|
3413
|
-
* @param {string} readyClient.user.id - The user ID of the client.
|
|
3414
|
-
* @returns {Promise<void>}
|
|
3850
|
+
* @param {Guild} guild - The guild from which to build the user list.
|
|
3851
|
+
* @returns {Promise<Entity[]>} A promise that resolves with an array of standardized entity objects.
|
|
3852
|
+
* @private
|
|
3415
3853
|
*/
|
|
3416
|
-
async
|
|
3417
|
-
|
|
3418
|
-
const
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3854
|
+
async buildStandardizedUsers(guild) {
|
|
3855
|
+
const entities = [];
|
|
3856
|
+
const botId = this.client?.user?.id;
|
|
3857
|
+
if (guild.memberCount > 1e3) {
|
|
3858
|
+
logger6.info(
|
|
3859
|
+
`Using optimized user sync for large guild ${guild.name} (${guild.memberCount} members)`
|
|
3860
|
+
);
|
|
3861
|
+
try {
|
|
3862
|
+
for (const [, member] of guild.members.cache) {
|
|
3863
|
+
const tag = member.user.bot ? `${member.user.username}#${member.user.discriminator}` : member.user.username;
|
|
3864
|
+
if (member.id !== botId) {
|
|
3865
|
+
entities.push({
|
|
3866
|
+
id: createUniqueUuid6(this.runtime, member.id),
|
|
3867
|
+
names: Array.from(
|
|
3868
|
+
new Set(
|
|
3869
|
+
[member.user.username, member.displayName, member.user.globalName].filter(
|
|
3870
|
+
Boolean
|
|
3871
|
+
)
|
|
3872
|
+
)
|
|
3873
|
+
),
|
|
3874
|
+
agentId: this.runtime.agentId,
|
|
3875
|
+
metadata: {
|
|
3876
|
+
default: {
|
|
3877
|
+
username: tag,
|
|
3878
|
+
name: member.displayName || member.user.username
|
|
3879
|
+
},
|
|
3880
|
+
discord: member.user.globalName ? {
|
|
3881
|
+
username: tag,
|
|
3882
|
+
name: member.displayName || member.user.username,
|
|
3883
|
+
globalName: member.user.globalName,
|
|
3884
|
+
userId: member.id
|
|
3885
|
+
} : {
|
|
3886
|
+
username: tag,
|
|
3887
|
+
name: member.displayName || member.user.username,
|
|
3888
|
+
userId: member.id
|
|
3889
|
+
}
|
|
3890
|
+
}
|
|
3891
|
+
});
|
|
3431
3892
|
}
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3893
|
+
}
|
|
3894
|
+
if (entities.length < 100) {
|
|
3895
|
+
logger6.info(`Adding online members for ${guild.name}`);
|
|
3896
|
+
const onlineMembers = await guild.members.fetch({ limit: 100 });
|
|
3897
|
+
for (const [, member] of onlineMembers) {
|
|
3898
|
+
if (member.id !== botId) {
|
|
3899
|
+
const entityId = createUniqueUuid6(this.runtime, member.id);
|
|
3900
|
+
if (!entities.some((u) => u.id === entityId)) {
|
|
3901
|
+
const tag = member.user.bot ? `${member.user.username}#${member.user.discriminator}` : member.user.username;
|
|
3902
|
+
entities.push({
|
|
3903
|
+
id: entityId,
|
|
3904
|
+
names: Array.from(
|
|
3905
|
+
new Set(
|
|
3906
|
+
[member.user.username, member.displayName, member.user.globalName].filter(
|
|
3907
|
+
Boolean
|
|
3908
|
+
)
|
|
3909
|
+
)
|
|
3910
|
+
),
|
|
3911
|
+
agentId: this.runtime.agentId,
|
|
3912
|
+
metadata: {
|
|
3913
|
+
default: {
|
|
3914
|
+
username: tag,
|
|
3915
|
+
name: member.displayName || member.user.username
|
|
3916
|
+
},
|
|
3917
|
+
discord: member.user.globalName ? {
|
|
3918
|
+
username: tag,
|
|
3919
|
+
name: member.displayName || member.user.username,
|
|
3920
|
+
globalName: member.user.globalName,
|
|
3921
|
+
userId: member.id
|
|
3922
|
+
} : {
|
|
3923
|
+
username: tag,
|
|
3924
|
+
name: member.displayName || member.user.username,
|
|
3925
|
+
userId: member.id
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3928
|
+
});
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
} catch (error) {
|
|
3934
|
+
logger6.error(`Error fetching members for ${guild.name}:`, error);
|
|
3437
3935
|
}
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3936
|
+
} else {
|
|
3937
|
+
try {
|
|
3938
|
+
let members = guild.members.cache;
|
|
3939
|
+
if (members.size === 0) {
|
|
3940
|
+
members = await guild.members.fetch();
|
|
3941
|
+
}
|
|
3942
|
+
for (const [, member] of members) {
|
|
3943
|
+
if (member.id !== botId) {
|
|
3944
|
+
const tag = member.user.bot ? `${member.user.username}#${member.user.discriminator}` : member.user.username;
|
|
3945
|
+
entities.push({
|
|
3946
|
+
id: createUniqueUuid6(this.runtime, member.id),
|
|
3947
|
+
names: Array.from(
|
|
3948
|
+
new Set(
|
|
3949
|
+
[member.user.username, member.displayName, member.user.globalName].filter(
|
|
3950
|
+
Boolean
|
|
3951
|
+
)
|
|
3952
|
+
)
|
|
3953
|
+
),
|
|
3954
|
+
agentId: this.runtime.agentId,
|
|
3955
|
+
metadata: {
|
|
3956
|
+
default: {
|
|
3957
|
+
username: tag,
|
|
3958
|
+
name: member.displayName || member.user.username
|
|
3959
|
+
},
|
|
3960
|
+
discord: member.user.globalName ? {
|
|
3961
|
+
username: tag,
|
|
3962
|
+
name: member.displayName || member.user.username,
|
|
3963
|
+
globalName: member.user.globalName,
|
|
3964
|
+
userId: member.id
|
|
3965
|
+
} : {
|
|
3966
|
+
username: tag,
|
|
3967
|
+
name: member.displayName || member.user.username,
|
|
3968
|
+
userId: member.id
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3971
|
+
});
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
} catch (error) {
|
|
3975
|
+
logger6.error(`Error fetching members for ${guild.name}:`, error);
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
return entities;
|
|
3471
3979
|
}
|
|
3472
3980
|
/**
|
|
3473
|
-
*
|
|
3474
|
-
*
|
|
3475
|
-
* @
|
|
3476
|
-
* @returns {Promise<
|
|
3981
|
+
* Handles tasks to be performed once the Discord client is fully ready and connected.
|
|
3982
|
+
* This includes fetching guilds, scanning for voice data, and emitting connection events.
|
|
3983
|
+
* @private
|
|
3984
|
+
* @returns {Promise<void>} A promise that resolves when all on-ready tasks are completed.
|
|
3477
3985
|
*/
|
|
3478
|
-
async
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3986
|
+
async onReady() {
|
|
3987
|
+
logger6.log("DISCORD ON READY");
|
|
3988
|
+
const guilds = await this.client?.guilds.fetch();
|
|
3989
|
+
if (!guilds) {
|
|
3990
|
+
logger6.warn("Could not fetch guilds, client might not be ready.");
|
|
3991
|
+
return;
|
|
3992
|
+
}
|
|
3993
|
+
for (const [, guild] of guilds) {
|
|
3994
|
+
const fullGuild = await guild.fetch();
|
|
3995
|
+
await this.voiceManager?.scanGuild(fullGuild);
|
|
3996
|
+
const timeoutId = setTimeout(async () => {
|
|
3997
|
+
try {
|
|
3998
|
+
const fullGuild2 = await guild.fetch();
|
|
3999
|
+
logger6.log("DISCORD SERVER CONNECTED", fullGuild2.name);
|
|
4000
|
+
this.runtime.emitEvent(["DISCORD_SERVER_CONNECTED" /* WORLD_CONNECTED */], {
|
|
4001
|
+
runtime: this.runtime,
|
|
4002
|
+
server: fullGuild2,
|
|
4003
|
+
source: "discord"
|
|
4004
|
+
});
|
|
4005
|
+
const worldId = createUniqueUuid6(this.runtime, fullGuild2.id);
|
|
4006
|
+
const ownerId = createUniqueUuid6(this.runtime, fullGuild2.ownerId);
|
|
4007
|
+
const standardizedData = {
|
|
4008
|
+
name: fullGuild2.name,
|
|
4009
|
+
runtime: this.runtime,
|
|
4010
|
+
rooms: await this.buildStandardizedRooms(fullGuild2, worldId),
|
|
4011
|
+
entities: await this.buildStandardizedUsers(fullGuild2),
|
|
4012
|
+
world: {
|
|
4013
|
+
id: worldId,
|
|
4014
|
+
name: fullGuild2.name,
|
|
4015
|
+
agentId: this.runtime.agentId,
|
|
4016
|
+
serverId: fullGuild2.id,
|
|
4017
|
+
metadata: {
|
|
4018
|
+
ownership: fullGuild2.ownerId ? { ownerId } : void 0,
|
|
4019
|
+
roles: {
|
|
4020
|
+
[ownerId]: Role.OWNER
|
|
4021
|
+
}
|
|
4022
|
+
}
|
|
4023
|
+
},
|
|
4024
|
+
source: "discord"
|
|
4025
|
+
};
|
|
4026
|
+
this.runtime.emitEvent([EventType2.WORLD_CONNECTED], standardizedData);
|
|
4027
|
+
} catch (error) {
|
|
4028
|
+
logger6.error("Error during Discord world connection:", error);
|
|
4029
|
+
}
|
|
4030
|
+
}, 1e3);
|
|
4031
|
+
this.timeouts.push(timeoutId);
|
|
3486
4032
|
}
|
|
4033
|
+
this.client?.emit("voiceManagerReady");
|
|
3487
4034
|
}
|
|
3488
4035
|
/**
|
|
3489
|
-
*
|
|
4036
|
+
* Registers send handlers for the Discord service instance.
|
|
4037
|
+
* This allows the runtime to correctly dispatch messages to this service.
|
|
4038
|
+
* @param {IAgentRuntime} runtime - The agent runtime instance.
|
|
4039
|
+
* @param {DiscordService} serviceInstance - The instance of the DiscordService.
|
|
4040
|
+
* @static
|
|
4041
|
+
*/
|
|
4042
|
+
static registerSendHandlers(runtime, serviceInstance) {
|
|
4043
|
+
if (serviceInstance) {
|
|
4044
|
+
runtime.registerSendHandler(
|
|
4045
|
+
"discord",
|
|
4046
|
+
serviceInstance.handleSendMessage.bind(serviceInstance)
|
|
4047
|
+
);
|
|
4048
|
+
logger6.info("[Discord] Registered send handler.");
|
|
4049
|
+
}
|
|
4050
|
+
}
|
|
4051
|
+
/**
|
|
4052
|
+
* Fetches all members who have access to a specific text channel.
|
|
3490
4053
|
*
|
|
3491
|
-
* @param {
|
|
3492
|
-
* @param {
|
|
3493
|
-
* @returns {
|
|
4054
|
+
* @param {string} channelId - The Discord ID of the text channel.
|
|
4055
|
+
* @param {boolean} [useCache=true] - Whether to prioritize cached data. Defaults to true.
|
|
4056
|
+
* @returns {Promise<Array<{id: string, username: string, displayName: string}>>} A promise that resolves with an array of channel member objects, each containing id, username, and displayName.
|
|
4057
|
+
*/
|
|
4058
|
+
async getTextChannelMembers(channelId, useCache = true) {
|
|
4059
|
+
logger6.info(`Fetching members for text channel ${channelId}, useCache=${useCache}`);
|
|
4060
|
+
try {
|
|
4061
|
+
const channel = await this.client?.channels.fetch(channelId);
|
|
4062
|
+
if (!channel) {
|
|
4063
|
+
logger6.error(`Channel not found: ${channelId}`);
|
|
4064
|
+
return [];
|
|
4065
|
+
}
|
|
4066
|
+
if (channel.type !== DiscordChannelType4.GuildText) {
|
|
4067
|
+
logger6.error(`Channel ${channelId} is not a text channel`);
|
|
4068
|
+
return [];
|
|
4069
|
+
}
|
|
4070
|
+
const guild = channel.guild;
|
|
4071
|
+
if (!guild) {
|
|
4072
|
+
logger6.error(`Channel ${channelId} is not in a guild`);
|
|
4073
|
+
return [];
|
|
4074
|
+
}
|
|
4075
|
+
const useCacheOnly = useCache && guild.memberCount > 1e3;
|
|
4076
|
+
let members;
|
|
4077
|
+
if (useCacheOnly) {
|
|
4078
|
+
logger6.info(
|
|
4079
|
+
`Using cached members for large guild ${guild.name} (${guild.memberCount} members)`
|
|
4080
|
+
);
|
|
4081
|
+
members = guild.members.cache;
|
|
4082
|
+
} else {
|
|
4083
|
+
try {
|
|
4084
|
+
if (useCache && guild.members.cache.size > 0) {
|
|
4085
|
+
logger6.info(`Using cached members (${guild.members.cache.size} members)`);
|
|
4086
|
+
members = guild.members.cache;
|
|
4087
|
+
} else {
|
|
4088
|
+
logger6.info(`Fetching members for guild ${guild.name}`);
|
|
4089
|
+
members = await guild.members.fetch();
|
|
4090
|
+
logger6.info(`Fetched ${members.size} members`);
|
|
4091
|
+
}
|
|
4092
|
+
} catch (error) {
|
|
4093
|
+
logger6.error(`Error fetching members: ${error}`);
|
|
4094
|
+
members = guild.members.cache;
|
|
4095
|
+
logger6.info(`Fallback to cache with ${members.size} members`);
|
|
4096
|
+
}
|
|
4097
|
+
}
|
|
4098
|
+
logger6.info(`Filtering members for access to channel ${channel.name}`);
|
|
4099
|
+
const memberArray = Array.from(members.values());
|
|
4100
|
+
const channelMembers = memberArray.filter((member) => {
|
|
4101
|
+
if (member.user.bot && member.id !== this.client?.user?.id) {
|
|
4102
|
+
return false;
|
|
4103
|
+
}
|
|
4104
|
+
return channel.permissionsFor(member)?.has(PermissionsBitField2.Flags.ViewChannel) ?? false;
|
|
4105
|
+
}).map((member) => ({
|
|
4106
|
+
id: member.id,
|
|
4107
|
+
username: member.user.username,
|
|
4108
|
+
displayName: member.displayName || member.user.username
|
|
4109
|
+
}));
|
|
4110
|
+
logger6.info(`Found ${channelMembers.length} members with access to channel ${channel.name}`);
|
|
4111
|
+
return channelMembers;
|
|
4112
|
+
} catch (error) {
|
|
4113
|
+
logger6.error(`Error fetching channel members: ${error}`);
|
|
4114
|
+
return [];
|
|
4115
|
+
}
|
|
4116
|
+
}
|
|
4117
|
+
/**
|
|
4118
|
+
* Placeholder for handling reaction addition.
|
|
4119
|
+
* @private
|
|
3494
4120
|
*/
|
|
3495
4121
|
async handleReactionAdd(reaction, user) {
|
|
3496
4122
|
try {
|
|
@@ -3527,13 +4153,15 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3527
4153
|
}
|
|
3528
4154
|
const messageContent = reaction.message.content || "";
|
|
3529
4155
|
const truncatedContent = messageContent.length > 50 ? `${messageContent.substring(0, 50)}...` : messageContent;
|
|
3530
|
-
const reactionMessage = `*Added <${emoji}> to: "${truncatedContent}"*`;
|
|
4156
|
+
const reactionMessage = `*Added <${emoji}> to: \\"${truncatedContent}\\"*`;
|
|
3531
4157
|
const userName = reaction.message.author?.username || "unknown";
|
|
3532
4158
|
const name = reaction.message.author?.displayName || userName;
|
|
3533
4159
|
await this.runtime.ensureConnection({
|
|
3534
4160
|
entityId,
|
|
3535
4161
|
roomId,
|
|
3536
4162
|
userName,
|
|
4163
|
+
worldId: createUniqueUuid6(this.runtime, reaction.message.guild?.id ?? roomId),
|
|
4164
|
+
worldName: reaction.message.guild?.name,
|
|
3537
4165
|
name,
|
|
3538
4166
|
source: "discord",
|
|
3539
4167
|
channelId: reaction.message.channel.id,
|
|
@@ -3559,9 +4187,9 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3559
4187
|
const callback = async (content) => {
|
|
3560
4188
|
if (!reaction.message.channel) {
|
|
3561
4189
|
logger6.error("No channel found for reaction message");
|
|
3562
|
-
return;
|
|
4190
|
+
return [];
|
|
3563
4191
|
}
|
|
3564
|
-
await reaction.message.channel.send(content.text);
|
|
4192
|
+
await reaction.message.channel.send(content.text ?? "");
|
|
3565
4193
|
return [];
|
|
3566
4194
|
};
|
|
3567
4195
|
this.runtime.emitEvent(["DISCORD_REACTION_RECEIVED", "REACTION_RECEIVED"], {
|
|
@@ -3574,11 +4202,8 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3574
4202
|
}
|
|
3575
4203
|
}
|
|
3576
4204
|
/**
|
|
3577
|
-
*
|
|
3578
|
-
*
|
|
3579
|
-
* @param {MessageReaction | PartialMessageReaction} reaction - The reaction that was removed.
|
|
3580
|
-
* @param {User | PartialUser} user - The user who removed the reaction.
|
|
3581
|
-
* @returns {Promise<void>} - A Promise that resolves after handling the reaction removal.
|
|
4205
|
+
* Placeholder for handling reaction removal.
|
|
4206
|
+
* @private
|
|
3582
4207
|
*/
|
|
3583
4208
|
async handleReactionRemove(reaction, user) {
|
|
3584
4209
|
try {
|
|
@@ -3597,7 +4222,7 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3597
4222
|
}
|
|
3598
4223
|
const messageContent = reaction.message.content || "";
|
|
3599
4224
|
const truncatedContent = messageContent.length > 50 ? `${messageContent.substring(0, 50)}...` : messageContent;
|
|
3600
|
-
const reactionMessage = `*Removed <${emoji}> from: "${truncatedContent}"*`;
|
|
4225
|
+
const reactionMessage = `*Removed <${emoji}> from: \\"${truncatedContent}\\"*`;
|
|
3601
4226
|
const roomId = createUniqueUuid6(this.runtime, reaction.message.channel.id);
|
|
3602
4227
|
const entityId = createUniqueUuid6(this.runtime, user.id);
|
|
3603
4228
|
const timestamp = Date.now();
|
|
@@ -3611,6 +4236,8 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3611
4236
|
entityId,
|
|
3612
4237
|
roomId,
|
|
3613
4238
|
userName,
|
|
4239
|
+
worldId: createUniqueUuid6(this.runtime, reaction.message.guild?.id ?? roomId),
|
|
4240
|
+
worldName: reaction.message.guild?.name,
|
|
3614
4241
|
name,
|
|
3615
4242
|
source: "discord",
|
|
3616
4243
|
channelId: reaction.message.channel.id,
|
|
@@ -3635,9 +4262,9 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3635
4262
|
const callback = async (content) => {
|
|
3636
4263
|
if (!reaction.message.channel) {
|
|
3637
4264
|
logger6.error("No channel found for reaction message");
|
|
3638
|
-
return;
|
|
4265
|
+
return [];
|
|
3639
4266
|
}
|
|
3640
|
-
await reaction.message.channel.send(content.text);
|
|
4267
|
+
await reaction.message.channel.send(content.text ?? "");
|
|
3641
4268
|
return [];
|
|
3642
4269
|
};
|
|
3643
4270
|
this.runtime.emitEvent(["DISCORD_REACTION_RECEIVED" /* REACTION_RECEIVED */], {
|
|
@@ -3650,260 +4277,40 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3650
4277
|
}
|
|
3651
4278
|
}
|
|
3652
4279
|
/**
|
|
3653
|
-
*
|
|
3654
|
-
*
|
|
3655
|
-
* @returns {Promise<void>}
|
|
4280
|
+
* Stops the Discord service and cleans up resources.
|
|
4281
|
+
* Implements the abstract method from the Service class.
|
|
3656
4282
|
*/
|
|
3657
|
-
async
|
|
3658
|
-
logger6.
|
|
3659
|
-
|
|
3660
|
-
this.
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
rooms: await this.buildStandardizedRooms(fullGuild, worldId),
|
|
3666
|
-
users: await this.buildStandardizedUsers(fullGuild),
|
|
3667
|
-
world: {
|
|
3668
|
-
id: worldId,
|
|
3669
|
-
name: fullGuild.name,
|
|
3670
|
-
agentId: this.runtime.agentId,
|
|
3671
|
-
serverId: fullGuild.id,
|
|
3672
|
-
metadata: {
|
|
3673
|
-
ownership: fullGuild.ownerId ? { ownerId } : void 0,
|
|
3674
|
-
roles: {
|
|
3675
|
-
[ownerId]: Role.OWNER
|
|
3676
|
-
}
|
|
3677
|
-
}
|
|
3678
|
-
},
|
|
3679
|
-
source: "discord"
|
|
3680
|
-
};
|
|
3681
|
-
this.runtime.emitEvent(["DISCORD_WORLD_JOINED" /* WORLD_JOINED */], {
|
|
3682
|
-
runtime: this.runtime,
|
|
3683
|
-
server: fullGuild,
|
|
3684
|
-
source: "discord"
|
|
3685
|
-
});
|
|
3686
|
-
this.runtime.emitEvent([EventType2.WORLD_JOINED], standardizedData);
|
|
3687
|
-
}
|
|
3688
|
-
/**
|
|
3689
|
-
* Handles interactions created by the user, specifically commands.
|
|
3690
|
-
* @param {any} interaction - The interaction object received
|
|
3691
|
-
* @returns {void}
|
|
3692
|
-
*/
|
|
3693
|
-
async handleInteractionCreate(interaction) {
|
|
3694
|
-
if (!interaction.isCommand()) return;
|
|
3695
|
-
switch (interaction.commandName) {
|
|
3696
|
-
case "joinchannel":
|
|
3697
|
-
await this.voiceManager.handleJoinChannelCommand(interaction);
|
|
3698
|
-
break;
|
|
3699
|
-
case "leavechannel":
|
|
3700
|
-
await this.voiceManager.handleLeaveChannelCommand(interaction);
|
|
3701
|
-
break;
|
|
4283
|
+
async stop() {
|
|
4284
|
+
logger6.info("Stopping Discord service...");
|
|
4285
|
+
this.timeouts.forEach(clearTimeout);
|
|
4286
|
+
this.timeouts = [];
|
|
4287
|
+
if (this.client) {
|
|
4288
|
+
await this.client.destroy();
|
|
4289
|
+
this.client = null;
|
|
4290
|
+
logger6.info("Discord client destroyed.");
|
|
3702
4291
|
}
|
|
3703
|
-
|
|
3704
|
-
/**
|
|
3705
|
-
* Builds a standardized list of rooms from Discord guild channels
|
|
3706
|
-
*/
|
|
3707
|
-
/**
|
|
3708
|
-
* Build standardized rooms for a guild based on text and voice channels.
|
|
3709
|
-
*
|
|
3710
|
-
* @param {Guild} guild The guild to build rooms for.
|
|
3711
|
-
* @param {UUID} _worldId The ID of the world to associate with the rooms.
|
|
3712
|
-
* @returns {Promise<any[]>} An array of standardized room objects.
|
|
3713
|
-
*/
|
|
3714
|
-
async buildStandardizedRooms(guild, _worldId) {
|
|
3715
|
-
const rooms = [];
|
|
3716
|
-
for (const [channelId, channel] of guild.channels.cache) {
|
|
3717
|
-
if (channel.type === DiscordChannelType4.GuildText || channel.type === DiscordChannelType4.GuildVoice) {
|
|
3718
|
-
const roomId = createUniqueUuid6(this.runtime, channelId);
|
|
3719
|
-
let channelType;
|
|
3720
|
-
switch (channel.type) {
|
|
3721
|
-
case DiscordChannelType4.GuildText:
|
|
3722
|
-
channelType = ChannelType9.GROUP;
|
|
3723
|
-
break;
|
|
3724
|
-
case DiscordChannelType4.GuildVoice:
|
|
3725
|
-
channelType = ChannelType9.VOICE_GROUP;
|
|
3726
|
-
break;
|
|
3727
|
-
default:
|
|
3728
|
-
channelType = ChannelType9.GROUP;
|
|
3729
|
-
}
|
|
3730
|
-
let participants = [];
|
|
3731
|
-
if (guild.memberCount < 1e3 && channel.type === DiscordChannelType4.GuildText) {
|
|
3732
|
-
try {
|
|
3733
|
-
participants = Array.from(guild.members.cache.values()).filter(
|
|
3734
|
-
(member) => channel.permissionsFor(member)?.has(PermissionsBitField2.Flags.ViewChannel)
|
|
3735
|
-
).map((member) => createUniqueUuid6(this.runtime, member.id));
|
|
3736
|
-
} catch (error) {
|
|
3737
|
-
logger6.warn(`Failed to get participants for channel ${channel.name}:`, error);
|
|
3738
|
-
}
|
|
3739
|
-
}
|
|
3740
|
-
rooms.push({
|
|
3741
|
-
id: roomId,
|
|
3742
|
-
name: channel.name,
|
|
3743
|
-
type: channelType,
|
|
3744
|
-
channelId: channel.id,
|
|
3745
|
-
participants
|
|
3746
|
-
});
|
|
3747
|
-
}
|
|
4292
|
+
if (this.voiceManager) {
|
|
3748
4293
|
}
|
|
3749
|
-
|
|
4294
|
+
logger6.info("Discord service stopped.");
|
|
3750
4295
|
}
|
|
3751
4296
|
/**
|
|
3752
|
-
*
|
|
4297
|
+
* Asynchronously retrieves the type of a given channel.
|
|
4298
|
+
*
|
|
4299
|
+
* @param {Channel} channel - The channel for which to determine the type.
|
|
4300
|
+
* @returns {Promise<ChannelType>} A Promise that resolves with the type of the channel.
|
|
3753
4301
|
*/
|
|
3754
|
-
async
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
entities.push({
|
|
3766
|
-
id: createUniqueUuid6(this.runtime, member.id),
|
|
3767
|
-
names: Array.from(
|
|
3768
|
-
/* @__PURE__ */ new Set([member.user.username, member.displayName, member.user.globalName])
|
|
3769
|
-
),
|
|
3770
|
-
agentId: this.runtime.agentId,
|
|
3771
|
-
metadata: {
|
|
3772
|
-
default: {
|
|
3773
|
-
username: tag,
|
|
3774
|
-
name: member.displayName || member.user.username
|
|
3775
|
-
},
|
|
3776
|
-
discord: member.user.globalName ? {
|
|
3777
|
-
username: tag,
|
|
3778
|
-
name: member.displayName || member.user.username,
|
|
3779
|
-
globalName: member.user.globalName,
|
|
3780
|
-
userId: member.id
|
|
3781
|
-
} : {
|
|
3782
|
-
username: tag,
|
|
3783
|
-
name: member.displayName || member.user.username,
|
|
3784
|
-
userId: member.id
|
|
3785
|
-
}
|
|
3786
|
-
}
|
|
3787
|
-
});
|
|
3788
|
-
}
|
|
3789
|
-
}
|
|
3790
|
-
if (entities.length < 100) {
|
|
3791
|
-
logger6.info(`Adding online members for ${guild.name}`);
|
|
3792
|
-
const onlineMembers = await guild.members.fetch({ limit: 100 });
|
|
3793
|
-
for (const [, member] of onlineMembers) {
|
|
3794
|
-
if (member.id !== botId) {
|
|
3795
|
-
const entityId = createUniqueUuid6(this.runtime, member.id);
|
|
3796
|
-
if (!entities.some((u) => u.id === entityId)) {
|
|
3797
|
-
const tag = member.user.bot ? `${member.user.username}#${member.user.discriminator}` : member.user.username;
|
|
3798
|
-
entities.push({
|
|
3799
|
-
id: entityId,
|
|
3800
|
-
names: Array.from(
|
|
3801
|
-
/* @__PURE__ */ new Set([member.user.username, member.displayName, member.user.globalName])
|
|
3802
|
-
),
|
|
3803
|
-
agentId: this.runtime.agentId,
|
|
3804
|
-
metadata: {
|
|
3805
|
-
default: {
|
|
3806
|
-
username: tag,
|
|
3807
|
-
name: member.displayName || member.user.username
|
|
3808
|
-
},
|
|
3809
|
-
discord: member.user.globalName ? {
|
|
3810
|
-
username: tag,
|
|
3811
|
-
name: member.displayName || member.user.username,
|
|
3812
|
-
globalName: member.user.globalName,
|
|
3813
|
-
userId: member.id
|
|
3814
|
-
} : {
|
|
3815
|
-
username: tag,
|
|
3816
|
-
name: member.displayName || member.user.username,
|
|
3817
|
-
userId: member.id
|
|
3818
|
-
}
|
|
3819
|
-
}
|
|
3820
|
-
});
|
|
3821
|
-
}
|
|
3822
|
-
}
|
|
3823
|
-
}
|
|
3824
|
-
}
|
|
3825
|
-
} catch (error) {
|
|
3826
|
-
logger6.error(`Error fetching members for ${guild.name}:`, error);
|
|
3827
|
-
}
|
|
3828
|
-
} else {
|
|
3829
|
-
try {
|
|
3830
|
-
let members = guild.members.cache;
|
|
3831
|
-
if (members.size === 0) {
|
|
3832
|
-
members = await guild.members.fetch();
|
|
3833
|
-
}
|
|
3834
|
-
for (const [, member] of members) {
|
|
3835
|
-
if (member.id !== botId) {
|
|
3836
|
-
const tag = member.user.bot ? `${member.user.username}#${member.user.discriminator}` : member.user.username;
|
|
3837
|
-
entities.push({
|
|
3838
|
-
id: createUniqueUuid6(this.runtime, member.id),
|
|
3839
|
-
names: Array.from(
|
|
3840
|
-
/* @__PURE__ */ new Set([member.user.username, member.displayName, member.user.globalName])
|
|
3841
|
-
),
|
|
3842
|
-
agentId: this.runtime.agentId,
|
|
3843
|
-
metadata: {
|
|
3844
|
-
default: {
|
|
3845
|
-
username: tag,
|
|
3846
|
-
name: member.displayName || member.user.username
|
|
3847
|
-
},
|
|
3848
|
-
discord: member.user.globalName ? {
|
|
3849
|
-
username: tag,
|
|
3850
|
-
name: member.displayName || member.user.username,
|
|
3851
|
-
globalName: member.user.globalName,
|
|
3852
|
-
userId: member.id
|
|
3853
|
-
} : {
|
|
3854
|
-
username: tag,
|
|
3855
|
-
name: member.displayName || member.user.username,
|
|
3856
|
-
userId: member.id
|
|
3857
|
-
}
|
|
3858
|
-
}
|
|
3859
|
-
});
|
|
3860
|
-
}
|
|
3861
|
-
}
|
|
3862
|
-
} catch (error) {
|
|
3863
|
-
logger6.error(`Error fetching members for ${guild.name}:`, error);
|
|
3864
|
-
}
|
|
3865
|
-
}
|
|
3866
|
-
return entities;
|
|
3867
|
-
}
|
|
3868
|
-
async onReady() {
|
|
3869
|
-
logger6.log("DISCORD ON READY");
|
|
3870
|
-
const guilds = await this.client?.guilds.fetch();
|
|
3871
|
-
for (const [, guild] of guilds) {
|
|
3872
|
-
const fullGuild = await guild.fetch();
|
|
3873
|
-
await this.voiceManager.scanGuild(fullGuild);
|
|
3874
|
-
setTimeout(async () => {
|
|
3875
|
-
const fullGuild2 = await guild.fetch();
|
|
3876
|
-
logger6.log("DISCORD SERVER CONNECTED", fullGuild2.name);
|
|
3877
|
-
this.runtime.emitEvent(["DISCORD_SERVER_CONNECTED" /* WORLD_CONNECTED */], {
|
|
3878
|
-
runtime: this.runtime,
|
|
3879
|
-
server: fullGuild2,
|
|
3880
|
-
source: "discord"
|
|
3881
|
-
});
|
|
3882
|
-
const worldId = createUniqueUuid6(this.runtime, fullGuild2.id);
|
|
3883
|
-
const ownerId = createUniqueUuid6(this.runtime, fullGuild2.ownerId);
|
|
3884
|
-
const standardizedData = {
|
|
3885
|
-
name: fullGuild2.name,
|
|
3886
|
-
runtime: this.runtime,
|
|
3887
|
-
rooms: await this.buildStandardizedRooms(fullGuild2, worldId),
|
|
3888
|
-
entities: await this.buildStandardizedUsers(fullGuild2),
|
|
3889
|
-
world: {
|
|
3890
|
-
id: worldId,
|
|
3891
|
-
name: fullGuild2.name,
|
|
3892
|
-
agentId: this.runtime.agentId,
|
|
3893
|
-
serverId: fullGuild2.id,
|
|
3894
|
-
metadata: {
|
|
3895
|
-
ownership: fullGuild2.ownerId ? { ownerId } : void 0,
|
|
3896
|
-
roles: {
|
|
3897
|
-
[ownerId]: Role.OWNER
|
|
3898
|
-
}
|
|
3899
|
-
}
|
|
3900
|
-
},
|
|
3901
|
-
source: "discord"
|
|
3902
|
-
};
|
|
3903
|
-
this.runtime.emitEvent([EventType2.WORLD_CONNECTED], standardizedData);
|
|
3904
|
-
}, 1e3);
|
|
4302
|
+
async getChannelType(channel) {
|
|
4303
|
+
switch (channel.type) {
|
|
4304
|
+
case DiscordChannelType4.DM:
|
|
4305
|
+
return ChannelType9.DM;
|
|
4306
|
+
case DiscordChannelType4.GuildText:
|
|
4307
|
+
return ChannelType9.GROUP;
|
|
4308
|
+
case DiscordChannelType4.GuildVoice:
|
|
4309
|
+
return ChannelType9.VOICE_GROUP;
|
|
4310
|
+
default:
|
|
4311
|
+
logger6.warn(`Unhandled channel type: ${channel.type}`);
|
|
4312
|
+
return ChannelType9.GROUP;
|
|
3905
4313
|
}
|
|
3906
|
-
this.client?.emit("voiceManagerReady");
|
|
3907
4314
|
}
|
|
3908
4315
|
};
|
|
3909
4316
|
|
|
@@ -3917,11 +4324,12 @@ import {
|
|
|
3917
4324
|
entersState as entersState2
|
|
3918
4325
|
} from "@discordjs/voice";
|
|
3919
4326
|
import { ModelType as ModelType9, logger as logger7 } from "@elizaos/core";
|
|
3920
|
-
import { ChannelType as ChannelType10, Events as Events2 } from "discord.js";
|
|
4327
|
+
import { ChannelType as ChannelType10, Events as Events2, AttachmentBuilder } from "discord.js";
|
|
3921
4328
|
var TEST_IMAGE_URL = "https://github.com/elizaOS/awesome-eliza/blob/main/assets/eliza-logo.jpg?raw=true";
|
|
3922
4329
|
var DiscordTestSuite = class {
|
|
3923
4330
|
name = "discord";
|
|
3924
|
-
discordClient
|
|
4331
|
+
discordClient;
|
|
4332
|
+
// Use definite assignment assertion
|
|
3925
4333
|
tests;
|
|
3926
4334
|
/**
|
|
3927
4335
|
* Constructor for initializing the tests array with test cases to be executed.
|
|
@@ -3967,13 +4375,19 @@ var DiscordTestSuite = class {
|
|
|
3967
4375
|
async testCreatingDiscordClient(runtime) {
|
|
3968
4376
|
try {
|
|
3969
4377
|
this.discordClient = runtime.getService(ServiceType2.DISCORD);
|
|
3970
|
-
if (this.discordClient
|
|
4378
|
+
if (!this.discordClient) {
|
|
4379
|
+
throw new Error("Failed to get DiscordService from runtime.");
|
|
4380
|
+
}
|
|
4381
|
+
if (this.discordClient.client?.isReady()) {
|
|
3971
4382
|
logger7.success("DiscordService is already ready.");
|
|
3972
4383
|
} else {
|
|
3973
4384
|
logger7.info("Waiting for DiscordService to be ready...");
|
|
4385
|
+
if (!this.discordClient.client) {
|
|
4386
|
+
throw new Error("Discord client instance is missing within the service.");
|
|
4387
|
+
}
|
|
3974
4388
|
await new Promise((resolve, reject) => {
|
|
3975
|
-
this.discordClient.client
|
|
3976
|
-
this.discordClient.client
|
|
4389
|
+
this.discordClient.client?.once(Events2.ClientReady, resolve);
|
|
4390
|
+
this.discordClient.client?.once(Events2.Error, reject);
|
|
3977
4391
|
});
|
|
3978
4392
|
}
|
|
3979
4393
|
} catch (error) {
|
|
@@ -3988,6 +4402,7 @@ var DiscordTestSuite = class {
|
|
|
3988
4402
|
* @throws {Error} - If there is an error in executing the slash command test.
|
|
3989
4403
|
*/
|
|
3990
4404
|
async testJoinVoiceSlashCommand(runtime) {
|
|
4405
|
+
if (!this.discordClient) throw new Error("Discord client not initialized.");
|
|
3991
4406
|
try {
|
|
3992
4407
|
await this.waitForVoiceManagerReady(this.discordClient);
|
|
3993
4408
|
const channel = await this.getTestChannel(runtime);
|
|
@@ -4007,10 +4422,13 @@ var DiscordTestSuite = class {
|
|
|
4007
4422
|
logger7.info(`JoinChannel Slash Command Response: ${message}`);
|
|
4008
4423
|
}
|
|
4009
4424
|
};
|
|
4425
|
+
if (!this.discordClient.voiceManager) {
|
|
4426
|
+
throw new Error("VoiceManager is not available on the Discord client.");
|
|
4427
|
+
}
|
|
4010
4428
|
await this.discordClient.voiceManager.handleJoinChannelCommand(fakeJoinInteraction);
|
|
4011
|
-
logger7.success("
|
|
4429
|
+
logger7.success("Join voice slash command test completed successfully.");
|
|
4012
4430
|
} catch (error) {
|
|
4013
|
-
throw new Error(`Error in slash commands test: ${error}`);
|
|
4431
|
+
throw new Error(`Error in join voice slash commands test: ${error}`);
|
|
4014
4432
|
}
|
|
4015
4433
|
}
|
|
4016
4434
|
/**
|
|
@@ -4020,6 +4438,7 @@ var DiscordTestSuite = class {
|
|
|
4020
4438
|
* @returns {Promise<void>} A promise that resolves when the test is complete.
|
|
4021
4439
|
*/
|
|
4022
4440
|
async testLeaveVoiceSlashCommand(runtime) {
|
|
4441
|
+
if (!this.discordClient) throw new Error("Discord client not initialized.");
|
|
4023
4442
|
try {
|
|
4024
4443
|
await this.waitForVoiceManagerReady(this.discordClient);
|
|
4025
4444
|
const channel = await this.getTestChannel(runtime);
|
|
@@ -4034,10 +4453,13 @@ var DiscordTestSuite = class {
|
|
|
4034
4453
|
logger7.info(`LeaveChannel Slash Command Response: ${message}`);
|
|
4035
4454
|
}
|
|
4036
4455
|
};
|
|
4456
|
+
if (!this.discordClient.voiceManager) {
|
|
4457
|
+
throw new Error("VoiceManager is not available on the Discord client.");
|
|
4458
|
+
}
|
|
4037
4459
|
await this.discordClient.voiceManager.handleLeaveChannelCommand(fakeLeaveInteraction);
|
|
4038
|
-
logger7.success("
|
|
4460
|
+
logger7.success("Leave voice slash command test completed successfully.");
|
|
4039
4461
|
} catch (error) {
|
|
4040
|
-
throw new Error(`Error in slash commands test: ${error}`);
|
|
4462
|
+
throw new Error(`Error in leave voice slash commands test: ${error}`);
|
|
4041
4463
|
}
|
|
4042
4464
|
}
|
|
4043
4465
|
/**
|
|
@@ -4046,16 +4468,26 @@ var DiscordTestSuite = class {
|
|
|
4046
4468
|
* @throws {Error} - If voice channel is invalid, voice connection fails to become ready, or no text to speech service found.
|
|
4047
4469
|
*/
|
|
4048
4470
|
async testTextToSpeechPlayback(runtime) {
|
|
4471
|
+
if (!this.discordClient) throw new Error("Discord client not initialized.");
|
|
4049
4472
|
try {
|
|
4050
4473
|
await this.waitForVoiceManagerReady(this.discordClient);
|
|
4051
4474
|
const channel = await this.getTestChannel(runtime);
|
|
4052
4475
|
if (!channel || channel.type !== ChannelType10.GuildVoice) {
|
|
4053
4476
|
throw new Error("Invalid voice channel.");
|
|
4054
4477
|
}
|
|
4478
|
+
if (!this.discordClient.voiceManager) {
|
|
4479
|
+
throw new Error("VoiceManager is not available on the Discord client.");
|
|
4480
|
+
}
|
|
4055
4481
|
await this.discordClient.voiceManager.joinChannel(channel);
|
|
4056
4482
|
const guild = await this.getActiveGuild(this.discordClient);
|
|
4057
4483
|
const guildId = guild.id;
|
|
4484
|
+
if (!this.discordClient.voiceManager) {
|
|
4485
|
+
throw new Error("VoiceManager is not available on the Discord client.");
|
|
4486
|
+
}
|
|
4058
4487
|
const connection = this.discordClient.voiceManager.getVoiceConnection(guildId);
|
|
4488
|
+
if (!connection) {
|
|
4489
|
+
throw new Error(`No voice connection found for guild: ${guildId}`);
|
|
4490
|
+
}
|
|
4059
4491
|
try {
|
|
4060
4492
|
await entersState2(connection, VoiceConnectionStatus2.Ready, 1e4);
|
|
4061
4493
|
logger7.success(`Voice connection is ready in guild: ${guildId}`);
|
|
@@ -4087,9 +4519,14 @@ var DiscordTestSuite = class {
|
|
|
4087
4519
|
* @throws {Error} If there is an error in sending the text message.
|
|
4088
4520
|
*/
|
|
4089
4521
|
async testSendingTextMessage(runtime) {
|
|
4522
|
+
if (!this.discordClient) throw new Error("Discord client not initialized.");
|
|
4090
4523
|
try {
|
|
4091
4524
|
const channel = await this.getTestChannel(runtime);
|
|
4092
|
-
|
|
4525
|
+
if (!channel || !channel.isTextBased()) {
|
|
4526
|
+
throw new Error("Cannot send message to a non-text channel.");
|
|
4527
|
+
}
|
|
4528
|
+
const attachment = new AttachmentBuilder(TEST_IMAGE_URL);
|
|
4529
|
+
await this.sendMessageToChannel(channel, "Testing Message", [attachment]);
|
|
4093
4530
|
} catch (error) {
|
|
4094
4531
|
throw new Error(`Error in sending text message: ${error}`);
|
|
4095
4532
|
}
|
|
@@ -4101,6 +4538,7 @@ var DiscordTestSuite = class {
|
|
|
4101
4538
|
* @returns {Promise<void>} A Promise that resolves once the message is handled.
|
|
4102
4539
|
*/
|
|
4103
4540
|
async testHandlingMessage(runtime) {
|
|
4541
|
+
if (!this.discordClient) throw new Error("Discord client not initialized.");
|
|
4104
4542
|
try {
|
|
4105
4543
|
const channel = await this.getTestChannel(runtime);
|
|
4106
4544
|
const fakeMessage = {
|
|
@@ -4119,9 +4557,12 @@ var DiscordTestSuite = class {
|
|
|
4119
4557
|
reference: null,
|
|
4120
4558
|
attachments: []
|
|
4121
4559
|
};
|
|
4560
|
+
if (!this.discordClient.messageManager) {
|
|
4561
|
+
throw new Error("MessageManager is not available on the Discord client.");
|
|
4562
|
+
}
|
|
4122
4563
|
await this.discordClient.messageManager.handleMessage(fakeMessage);
|
|
4123
4564
|
} catch (error) {
|
|
4124
|
-
throw new Error(`Error in
|
|
4565
|
+
throw new Error(`Error in handling message test: ${error}`);
|
|
4125
4566
|
}
|
|
4126
4567
|
}
|
|
4127
4568
|
// #############################
|
|
@@ -4135,8 +4576,9 @@ var DiscordTestSuite = class {
|
|
|
4135
4576
|
* @throws {Error} If no test channel is found.
|
|
4136
4577
|
*/
|
|
4137
4578
|
async getTestChannel(runtime) {
|
|
4579
|
+
if (!this.discordClient) throw new Error("Discord client not initialized.");
|
|
4138
4580
|
const channelId = this.validateChannelId(runtime);
|
|
4139
|
-
const channel = await this.discordClient.client
|
|
4581
|
+
const channel = await this.discordClient.client?.channels.fetch(channelId);
|
|
4140
4582
|
if (!channel) throw new Error("no test channel found!");
|
|
4141
4583
|
return channel;
|
|
4142
4584
|
}
|
|
@@ -4154,7 +4596,7 @@ var DiscordTestSuite = class {
|
|
|
4154
4596
|
if (!channel || !channel.isTextBased()) {
|
|
4155
4597
|
throw new Error("Channel is not a text-based channel or does not exist.");
|
|
4156
4598
|
}
|
|
4157
|
-
await sendMessageInChunks(channel, messageContent,
|
|
4599
|
+
await sendMessageInChunks(channel, messageContent, "", files);
|
|
4158
4600
|
} catch (error) {
|
|
4159
4601
|
throw new Error(`Error sending message: ${error}`);
|
|
4160
4602
|
}
|
|
@@ -4195,6 +4637,9 @@ var DiscordTestSuite = class {
|
|
|
4195
4637
|
* @throws {Error} If no active voice connection is found for the bot.
|
|
4196
4638
|
*/
|
|
4197
4639
|
async getActiveGuild(discordClient) {
|
|
4640
|
+
if (!discordClient.client) {
|
|
4641
|
+
throw new Error("Discord client instance is missing within the service.");
|
|
4642
|
+
}
|
|
4198
4643
|
const guilds = await discordClient.client.guilds.fetch();
|
|
4199
4644
|
const fullGuilds = await Promise.all(guilds.map((guild) => guild.fetch()));
|
|
4200
4645
|
const activeGuild = fullGuilds.find((g) => g.members.me?.voice.channelId);
|
|
@@ -4214,10 +4659,13 @@ var DiscordTestSuite = class {
|
|
|
4214
4659
|
if (!discordClient) {
|
|
4215
4660
|
throw new Error("Discord client is not initialized.");
|
|
4216
4661
|
}
|
|
4217
|
-
if (!discordClient.voiceManager
|
|
4662
|
+
if (!discordClient.voiceManager) {
|
|
4663
|
+
throw new Error("VoiceManager is not available on the Discord client.");
|
|
4664
|
+
}
|
|
4665
|
+
if (!discordClient.voiceManager?.isReady()) {
|
|
4218
4666
|
await new Promise((resolve, reject) => {
|
|
4219
|
-
discordClient.voiceManager
|
|
4220
|
-
discordClient.voiceManager
|
|
4667
|
+
discordClient.voiceManager?.once("ready", resolve);
|
|
4668
|
+
discordClient.voiceManager?.once("error", reject);
|
|
4221
4669
|
});
|
|
4222
4670
|
}
|
|
4223
4671
|
}
|