@elizaos/plugin-discord 1.0.0-beta.8 → 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/README.md +7 -4
- package/dist/index.d.ts +5 -0
- package/dist/index.js +951 -498
- package/dist/index.js.map +1 -1
- package/package.json +12 -7
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,
|
|
@@ -2063,25 +2124,102 @@ import {
|
|
|
2063
2124
|
ThreadChannel
|
|
2064
2125
|
} from "discord.js";
|
|
2065
2126
|
var MAX_MESSAGE_LENGTH = 1900;
|
|
2066
|
-
async function sendMessageInChunks(channel, content, _inReplyTo, files) {
|
|
2127
|
+
async function sendMessageInChunks(channel, content, _inReplyTo, files, components) {
|
|
2067
2128
|
const sentMessages = [];
|
|
2068
2129
|
const messages = splitMessage(content);
|
|
2069
2130
|
try {
|
|
2070
2131
|
for (let i = 0; i < messages.length; i++) {
|
|
2071
2132
|
const message = messages[i];
|
|
2072
|
-
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) {
|
|
2073
2134
|
const options = {
|
|
2074
2135
|
content: message.trim()
|
|
2075
2136
|
};
|
|
2076
2137
|
if (i === messages.length - 1 && files && files.length > 0) {
|
|
2077
2138
|
options.files = files;
|
|
2078
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
|
+
}
|
|
2079
2217
|
const m = await channel.send(options);
|
|
2080
2218
|
sentMessages.push(m);
|
|
2081
2219
|
}
|
|
2082
2220
|
}
|
|
2083
2221
|
} catch (error) {
|
|
2084
|
-
logger3.error(
|
|
2222
|
+
logger3.error(`Error sending message: ${error}`);
|
|
2085
2223
|
}
|
|
2086
2224
|
return sentMessages;
|
|
2087
2225
|
}
|
|
@@ -2190,7 +2328,7 @@ var MessageManager = class {
|
|
|
2190
2328
|
if (this.runtime.character.settings?.discord?.shouldIgnoreDirectMessages && message.channel.type === DiscordChannelType2.DM) {
|
|
2191
2329
|
return;
|
|
2192
2330
|
}
|
|
2193
|
-
if (this.runtime.character.settings?.discord?.shouldRespondOnlyToMentions && !message.mentions.users?.has(this.client.user
|
|
2331
|
+
if (this.runtime.character.settings?.discord?.shouldRespondOnlyToMentions && (!this.client.user?.id || !message.mentions.users?.has(this.client.user.id))) {
|
|
2194
2332
|
return;
|
|
2195
2333
|
}
|
|
2196
2334
|
const entityId = createUniqueUuid4(this.runtime, message.author.id);
|
|
@@ -2203,6 +2341,9 @@ var MessageManager = class {
|
|
|
2203
2341
|
if (message.guild) {
|
|
2204
2342
|
const guild = await message.guild.fetch();
|
|
2205
2343
|
type = await this.getChannelType(message.channel);
|
|
2344
|
+
if (type === null) {
|
|
2345
|
+
logger4.warn("null channel type, discord message", message);
|
|
2346
|
+
}
|
|
2206
2347
|
serverId = guild.id;
|
|
2207
2348
|
} else {
|
|
2208
2349
|
type = ChannelType7.DM;
|
|
@@ -2216,7 +2357,9 @@ var MessageManager = class {
|
|
|
2216
2357
|
source: "discord",
|
|
2217
2358
|
channelId: message.channel.id,
|
|
2218
2359
|
serverId,
|
|
2219
|
-
type
|
|
2360
|
+
type,
|
|
2361
|
+
worldId: createUniqueUuid4(this.runtime, serverId ?? roomId),
|
|
2362
|
+
worldName: message.guild?.name
|
|
2220
2363
|
});
|
|
2221
2364
|
try {
|
|
2222
2365
|
const canSendResult = canSendMessage(message.channel);
|
|
@@ -2236,6 +2379,22 @@ var MessageManager = class {
|
|
|
2236
2379
|
}
|
|
2237
2380
|
const entityId2 = createUniqueUuid4(this.runtime, message.author.id);
|
|
2238
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
|
+
};
|
|
2239
2398
|
const newMessage = {
|
|
2240
2399
|
id: messageId,
|
|
2241
2400
|
entityId: entityId2,
|
|
@@ -2250,6 +2409,10 @@ var MessageManager = class {
|
|
|
2250
2409
|
url: message.url,
|
|
2251
2410
|
inReplyTo: message.reference?.messageId ? createUniqueUuid4(this.runtime, message.reference?.messageId) : void 0
|
|
2252
2411
|
},
|
|
2412
|
+
metadata: {
|
|
2413
|
+
entityName: name,
|
|
2414
|
+
type: "message"
|
|
2415
|
+
},
|
|
2253
2416
|
createdAt: message.createdTimestamp
|
|
2254
2417
|
};
|
|
2255
2418
|
const callback = async (content, files) => {
|
|
@@ -2257,37 +2420,54 @@ var MessageManager = class {
|
|
|
2257
2420
|
if (message.id && !content.inReplyTo) {
|
|
2258
2421
|
content.inReplyTo = createUniqueUuid4(this.runtime, message.id);
|
|
2259
2422
|
}
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
const
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
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 [];
|
|
2287
2464
|
}
|
|
2288
|
-
return memories;
|
|
2289
2465
|
} catch (error) {
|
|
2290
|
-
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
|
+
}
|
|
2291
2471
|
return [];
|
|
2292
2472
|
}
|
|
2293
2473
|
};
|
|
@@ -2296,6 +2476,12 @@ var MessageManager = class {
|
|
|
2296
2476
|
message: newMessage,
|
|
2297
2477
|
callback
|
|
2298
2478
|
});
|
|
2479
|
+
setTimeout(() => {
|
|
2480
|
+
if (typingData.interval && !typingData.cleared) {
|
|
2481
|
+
clearInterval(typingData.interval);
|
|
2482
|
+
typingData.cleared = true;
|
|
2483
|
+
}
|
|
2484
|
+
}, 500);
|
|
2299
2485
|
} catch (error) {
|
|
2300
2486
|
console.error("Error handling message:", error);
|
|
2301
2487
|
}
|
|
@@ -2342,11 +2528,8 @@ var MessageManager = class {
|
|
|
2342
2528
|
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
|
2343
2529
|
const urls = processedContent.match(urlRegex) || [];
|
|
2344
2530
|
for (const url of urls) {
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
if (!videoService) {
|
|
2348
|
-
throw new Error("Video service not found");
|
|
2349
|
-
}
|
|
2531
|
+
const videoService = this.runtime.getService(ServiceType4.VIDEO);
|
|
2532
|
+
if (videoService?.isVideoUrl(url)) {
|
|
2350
2533
|
const videoInfo = await videoService.processVideo(url, this.runtime);
|
|
2351
2534
|
attachments.push({
|
|
2352
2535
|
id: `youtube-${Date.now()}`,
|
|
@@ -2359,7 +2542,8 @@ var MessageManager = class {
|
|
|
2359
2542
|
} else {
|
|
2360
2543
|
const browserService = this.runtime.getService(ServiceType4.BROWSER);
|
|
2361
2544
|
if (!browserService) {
|
|
2362
|
-
|
|
2545
|
+
logger4.warn("Browser service not found");
|
|
2546
|
+
continue;
|
|
2363
2547
|
}
|
|
2364
2548
|
const { title, description: summary } = await browserService.getPageContent(
|
|
2365
2549
|
url,
|
|
@@ -2548,9 +2732,17 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2548
2732
|
super();
|
|
2549
2733
|
this.client = service.client;
|
|
2550
2734
|
this.runtime = runtime;
|
|
2551
|
-
this.
|
|
2552
|
-
|
|
2553
|
-
|
|
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
|
+
}
|
|
2554
2746
|
}
|
|
2555
2747
|
/**
|
|
2556
2748
|
* Asynchronously retrieves the type of the channel.
|
|
@@ -2562,6 +2754,11 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2562
2754
|
case DiscordChannelType3.GuildVoice:
|
|
2563
2755
|
case DiscordChannelType3.GuildStageVoice:
|
|
2564
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}`);
|
|
2565
2762
|
}
|
|
2566
2763
|
}
|
|
2567
2764
|
/**
|
|
@@ -2592,7 +2789,7 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2592
2789
|
const newChannelId = newState.channelId;
|
|
2593
2790
|
const member = newState.member;
|
|
2594
2791
|
if (!member) return;
|
|
2595
|
-
if (member.id === this.client
|
|
2792
|
+
if (member.id === this.client?.user?.id) {
|
|
2596
2793
|
return;
|
|
2597
2794
|
}
|
|
2598
2795
|
if (oldChannelId === newChannelId) {
|
|
@@ -2626,7 +2823,7 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2626
2823
|
adapterCreator: channel.guild.voiceAdapterCreator,
|
|
2627
2824
|
selfDeaf: false,
|
|
2628
2825
|
selfMute: false,
|
|
2629
|
-
group: this.client
|
|
2826
|
+
group: this.client?.user?.id ?? "default-group"
|
|
2630
2827
|
});
|
|
2631
2828
|
try {
|
|
2632
2829
|
await Promise.race([
|
|
@@ -2702,7 +2899,12 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2702
2899
|
* @returns {VoiceConnection | undefined} The voice connection for the specified guild ID, or undefined if not found.
|
|
2703
2900
|
*/
|
|
2704
2901
|
getVoiceConnection(guildId) {
|
|
2705
|
-
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);
|
|
2706
2908
|
if (!connections) {
|
|
2707
2909
|
return;
|
|
2708
2910
|
}
|
|
@@ -2727,6 +2929,7 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2727
2929
|
emitClose: true
|
|
2728
2930
|
});
|
|
2729
2931
|
if (!receiveStream || receiveStream.readableLength === 0) {
|
|
2932
|
+
logger5.warn(`[monitorMember] No receiveStream or empty stream for user ${entityId}`);
|
|
2730
2933
|
return;
|
|
2731
2934
|
}
|
|
2732
2935
|
const opusDecoder = new prism.opus.Decoder({
|
|
@@ -2755,7 +2958,11 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2755
2958
|
});
|
|
2756
2959
|
pipeline(receiveStream, opusDecoder, (err) => {
|
|
2757
2960
|
if (err) {
|
|
2758
|
-
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
|
+
);
|
|
2759
2966
|
}
|
|
2760
2967
|
});
|
|
2761
2968
|
this.streams.set(entityId, opusDecoder);
|
|
@@ -2780,7 +2987,7 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2780
2987
|
opusDecoder.on("error", errorHandler);
|
|
2781
2988
|
opusDecoder.on("close", closeHandler);
|
|
2782
2989
|
receiveStream?.on("close", streamCloseHandler);
|
|
2783
|
-
this.client
|
|
2990
|
+
this.client?.emit("userStream", entityId, name, userName, channel, opusDecoder);
|
|
2784
2991
|
}
|
|
2785
2992
|
/**
|
|
2786
2993
|
* Leaves the specified voice channel and stops monitoring all members in that channel.
|
|
@@ -2795,7 +3002,7 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2795
3002
|
this.connections.delete(channel.id);
|
|
2796
3003
|
}
|
|
2797
3004
|
for (const [memberId, monitorInfo] of this.activeMonitors) {
|
|
2798
|
-
if (monitorInfo.channel.id === channel.id && memberId !== this.client
|
|
3005
|
+
if (monitorInfo.channel.id === channel.id && memberId !== this.client?.user?.id) {
|
|
2799
3006
|
this.stopMonitoringMember(memberId);
|
|
2800
3007
|
}
|
|
2801
3008
|
}
|
|
@@ -2830,8 +3037,10 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2830
3037
|
}
|
|
2831
3038
|
if (this.activeAudioPlayer || this.processingVoice) {
|
|
2832
3039
|
const state = this.userStates.get(entityId);
|
|
2833
|
-
state
|
|
2834
|
-
|
|
3040
|
+
if (state) {
|
|
3041
|
+
state.buffers.length = 0;
|
|
3042
|
+
state.totalLength = 0;
|
|
3043
|
+
}
|
|
2835
3044
|
return;
|
|
2836
3045
|
}
|
|
2837
3046
|
if (this.transcriptionTimeout) {
|
|
@@ -2911,17 +3120,18 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2911
3120
|
const state = this.userStates.get(entityId);
|
|
2912
3121
|
if (!state || state.buffers.length === 0) return;
|
|
2913
3122
|
try {
|
|
2914
|
-
let
|
|
3123
|
+
let isValidTranscription2 = function(text) {
|
|
2915
3124
|
if (!text || text.includes("[BLANK_AUDIO]")) return false;
|
|
2916
3125
|
return true;
|
|
2917
3126
|
};
|
|
3127
|
+
var isValidTranscription = isValidTranscription2;
|
|
2918
3128
|
const inputBuffer = Buffer.concat(state.buffers, state.totalLength);
|
|
2919
3129
|
state.buffers.length = 0;
|
|
2920
3130
|
state.totalLength = 0;
|
|
2921
3131
|
const wavBuffer = await this.convertOpusToWav(inputBuffer);
|
|
2922
3132
|
logger5.debug("Starting transcription...");
|
|
2923
3133
|
const transcriptionText = await this.runtime.useModel(ModelType8.TRANSCRIPTION, wavBuffer);
|
|
2924
|
-
if (transcriptionText &&
|
|
3134
|
+
if (transcriptionText && isValidTranscription2(transcriptionText)) {
|
|
2925
3135
|
state.transcriptionText += transcriptionText;
|
|
2926
3136
|
}
|
|
2927
3137
|
if (state.transcriptionText.length) {
|
|
@@ -2961,7 +3171,9 @@ var VoiceManager = class extends EventEmitter {
|
|
|
2961
3171
|
source: "discord",
|
|
2962
3172
|
channelId,
|
|
2963
3173
|
serverId: channel.guild.id,
|
|
2964
|
-
type
|
|
3174
|
+
type,
|
|
3175
|
+
worldId: createUniqueUuid5(this.runtime, channel.guild.id),
|
|
3176
|
+
worldName: channel.guild.name
|
|
2965
3177
|
});
|
|
2966
3178
|
const memory = {
|
|
2967
3179
|
id: createUniqueUuid5(this.runtime, `${channelId}-voice-message-${Date.now()}`),
|
|
@@ -3185,6 +3397,13 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3185
3397
|
character;
|
|
3186
3398
|
messageManager;
|
|
3187
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;
|
|
3188
3407
|
/**
|
|
3189
3408
|
* Constructor for Discord client.
|
|
3190
3409
|
* Initializes the Discord client with specified intents and partials,
|
|
@@ -3194,6 +3413,11 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3194
3413
|
*/
|
|
3195
3414
|
constructor(runtime) {
|
|
3196
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
|
+
}
|
|
3197
3421
|
const token = runtime.getSetting("DISCORD_API_TOKEN");
|
|
3198
3422
|
if (!token || token.trim() === "") {
|
|
3199
3423
|
logger6.warn("Discord API Token not provided - Discord functionality will be unavailable");
|
|
@@ -3219,19 +3443,135 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3219
3443
|
this.runtime = runtime;
|
|
3220
3444
|
this.voiceManager = new VoiceManager(this, runtime);
|
|
3221
3445
|
this.messageManager = new MessageManager(this);
|
|
3222
|
-
this.client.once(Events.ClientReady, this.
|
|
3446
|
+
this.client.once(Events.ClientReady, this.onReady.bind(this));
|
|
3223
3447
|
this.client.login(token).catch((error) => {
|
|
3224
|
-
logger6.error(
|
|
3448
|
+
logger6.error(
|
|
3449
|
+
`Failed to login to Discord: ${error instanceof Error ? error.message : String(error)}`
|
|
3450
|
+
);
|
|
3225
3451
|
this.client = null;
|
|
3226
3452
|
});
|
|
3227
3453
|
this.setupEventListeners();
|
|
3454
|
+
this.registerSendHandler();
|
|
3228
3455
|
} catch (error) {
|
|
3229
|
-
logger6.error(
|
|
3456
|
+
logger6.error(
|
|
3457
|
+
`Error initializing Discord client: ${error instanceof Error ? error.message : String(error)}`
|
|
3458
|
+
);
|
|
3230
3459
|
this.client = null;
|
|
3231
3460
|
}
|
|
3232
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
|
+
}
|
|
3233
3572
|
/**
|
|
3234
|
-
* Set up event listeners for the client
|
|
3573
|
+
* Set up event listeners for the client.
|
|
3574
|
+
* @private
|
|
3235
3575
|
*/
|
|
3236
3576
|
setupEventListeners() {
|
|
3237
3577
|
if (!this.client) {
|
|
@@ -3241,8 +3581,11 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3241
3581
|
if (message.author.id === this.client?.user?.id || message.author.bot) {
|
|
3242
3582
|
return;
|
|
3243
3583
|
}
|
|
3584
|
+
if (this.allowedChannelIds && !this.allowedChannelIds.includes(message.channel.id)) {
|
|
3585
|
+
return;
|
|
3586
|
+
}
|
|
3244
3587
|
try {
|
|
3245
|
-
this.messageManager
|
|
3588
|
+
this.messageManager?.handleMessage(message);
|
|
3246
3589
|
} catch (error) {
|
|
3247
3590
|
logger6.error(`Error handling message: ${error}`);
|
|
3248
3591
|
}
|
|
@@ -3251,6 +3594,9 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3251
3594
|
if (user.id === this.client?.user?.id) {
|
|
3252
3595
|
return;
|
|
3253
3596
|
}
|
|
3597
|
+
if (this.allowedChannelIds && reaction.message.channel && !this.allowedChannelIds.includes(reaction.message.channel.id)) {
|
|
3598
|
+
return;
|
|
3599
|
+
}
|
|
3254
3600
|
try {
|
|
3255
3601
|
await this.handleReactionAdd(reaction, user);
|
|
3256
3602
|
} catch (error) {
|
|
@@ -3261,6 +3607,9 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3261
3607
|
if (user.id === this.client?.user?.id) {
|
|
3262
3608
|
return;
|
|
3263
3609
|
}
|
|
3610
|
+
if (this.allowedChannelIds && reaction.message.channel && !this.allowedChannelIds.includes(reaction.message.channel.id)) {
|
|
3611
|
+
return;
|
|
3612
|
+
}
|
|
3264
3613
|
try {
|
|
3265
3614
|
await this.handleReactionRemove(reaction, user);
|
|
3266
3615
|
} catch (error) {
|
|
@@ -3282,6 +3631,9 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3282
3631
|
}
|
|
3283
3632
|
});
|
|
3284
3633
|
this.client.on("interactionCreate", async (interaction) => {
|
|
3634
|
+
if (this.allowedChannelIds && interaction.channelId && !this.allowedChannelIds.includes(interaction.channelId)) {
|
|
3635
|
+
return;
|
|
3636
|
+
}
|
|
3285
3637
|
try {
|
|
3286
3638
|
await this.handleInteractionCreate(interaction);
|
|
3287
3639
|
} catch (error) {
|
|
@@ -3290,7 +3642,7 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3290
3642
|
});
|
|
3291
3643
|
this.client.on("userStream", (entityId, name, userName, channel, opusDecoder) => {
|
|
3292
3644
|
if (entityId !== this.client?.user?.id) {
|
|
3293
|
-
this.voiceManager
|
|
3645
|
+
this.voiceManager?.handleUserStream(entityId, name, userName, channel, opusDecoder);
|
|
3294
3646
|
}
|
|
3295
3647
|
});
|
|
3296
3648
|
}
|
|
@@ -3299,6 +3651,7 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3299
3651
|
*
|
|
3300
3652
|
* @param {GuildMember} member - The GuildMember object representing the new member that joined the guild.
|
|
3301
3653
|
* @returns {Promise<void>} - A Promise that resolves once the event handling is complete.
|
|
3654
|
+
* @private
|
|
3302
3655
|
*/
|
|
3303
3656
|
async handleGuildMemberAdd(member) {
|
|
3304
3657
|
logger6.log(`New member joined: ${member.user.username}`);
|
|
@@ -3328,164 +3681,442 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3328
3681
|
});
|
|
3329
3682
|
}
|
|
3330
3683
|
/**
|
|
3331
|
-
*
|
|
3332
|
-
*
|
|
3333
|
-
* @
|
|
3334
|
-
* @
|
|
3335
|
-
*
|
|
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
|
|
3336
3688
|
*/
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
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
|
+
}
|
|
3341
3736
|
}
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
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
|
+
}
|
|
3346
3749
|
try {
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
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();
|
|
3350
3763
|
}
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
}
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
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"
|
|
3358
3779
|
});
|
|
3359
|
-
|
|
3360
|
-
|
|
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
|
+
}
|
|
3361
3788
|
} catch (error) {
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
logger6.
|
|
3370
|
-
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}`);
|
|
3371
3797
|
}
|
|
3372
3798
|
}
|
|
3373
3799
|
}
|
|
3374
|
-
throw new Error(
|
|
3375
|
-
`Discord initialization failed after ${maxRetries} attempts. Last error: ${lastError?.message}`
|
|
3376
|
-
);
|
|
3377
3800
|
}
|
|
3378
3801
|
/**
|
|
3379
|
-
*
|
|
3802
|
+
* Builds a standardized list of rooms from Discord guild channels.
|
|
3380
3803
|
*
|
|
3381
|
-
* @param {
|
|
3382
|
-
* @
|
|
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
|
|
3383
3808
|
*/
|
|
3384
|
-
|
|
3385
|
-
const
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
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
|
+
}
|
|
3394
3843
|
}
|
|
3844
|
+
return rooms;
|
|
3395
3845
|
}
|
|
3396
3846
|
/**
|
|
3397
|
-
*
|
|
3847
|
+
* Builds a standardized list of users (entities) from Discord guild members.
|
|
3848
|
+
* Implements different strategies based on guild size for performance.
|
|
3398
3849
|
*
|
|
3399
|
-
* @
|
|
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
|
|
3400
3853
|
*/
|
|
3401
|
-
async
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
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
|
-
|
|
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
|
+
});
|
|
3892
|
+
}
|
|
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);
|
|
3935
|
+
}
|
|
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;
|
|
3979
|
+
}
|
|
3467
3980
|
/**
|
|
3468
|
-
*
|
|
3469
|
-
*
|
|
3470
|
-
* @
|
|
3471
|
-
* @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.
|
|
3472
3985
|
*/
|
|
3473
|
-
async
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
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);
|
|
3481
4032
|
}
|
|
4033
|
+
this.client?.emit("voiceManagerReady");
|
|
3482
4034
|
}
|
|
3483
4035
|
/**
|
|
3484
|
-
*
|
|
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.
|
|
3485
4053
|
*
|
|
3486
|
-
* @param {
|
|
3487
|
-
* @param {
|
|
3488
|
-
* @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
|
|
3489
4120
|
*/
|
|
3490
4121
|
async handleReactionAdd(reaction, user) {
|
|
3491
4122
|
try {
|
|
@@ -3522,13 +4153,15 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3522
4153
|
}
|
|
3523
4154
|
const messageContent = reaction.message.content || "";
|
|
3524
4155
|
const truncatedContent = messageContent.length > 50 ? `${messageContent.substring(0, 50)}...` : messageContent;
|
|
3525
|
-
const reactionMessage = `*Added <${emoji}> to: "${truncatedContent}"*`;
|
|
4156
|
+
const reactionMessage = `*Added <${emoji}> to: \\"${truncatedContent}\\"*`;
|
|
3526
4157
|
const userName = reaction.message.author?.username || "unknown";
|
|
3527
4158
|
const name = reaction.message.author?.displayName || userName;
|
|
3528
4159
|
await this.runtime.ensureConnection({
|
|
3529
4160
|
entityId,
|
|
3530
4161
|
roomId,
|
|
3531
4162
|
userName,
|
|
4163
|
+
worldId: createUniqueUuid6(this.runtime, reaction.message.guild?.id ?? roomId),
|
|
4164
|
+
worldName: reaction.message.guild?.name,
|
|
3532
4165
|
name,
|
|
3533
4166
|
source: "discord",
|
|
3534
4167
|
channelId: reaction.message.channel.id,
|
|
@@ -3554,9 +4187,9 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3554
4187
|
const callback = async (content) => {
|
|
3555
4188
|
if (!reaction.message.channel) {
|
|
3556
4189
|
logger6.error("No channel found for reaction message");
|
|
3557
|
-
return;
|
|
4190
|
+
return [];
|
|
3558
4191
|
}
|
|
3559
|
-
await reaction.message.channel.send(content.text);
|
|
4192
|
+
await reaction.message.channel.send(content.text ?? "");
|
|
3560
4193
|
return [];
|
|
3561
4194
|
};
|
|
3562
4195
|
this.runtime.emitEvent(["DISCORD_REACTION_RECEIVED", "REACTION_RECEIVED"], {
|
|
@@ -3569,11 +4202,8 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3569
4202
|
}
|
|
3570
4203
|
}
|
|
3571
4204
|
/**
|
|
3572
|
-
*
|
|
3573
|
-
*
|
|
3574
|
-
* @param {MessageReaction | PartialMessageReaction} reaction - The reaction that was removed.
|
|
3575
|
-
* @param {User | PartialUser} user - The user who removed the reaction.
|
|
3576
|
-
* @returns {Promise<void>} - A Promise that resolves after handling the reaction removal.
|
|
4205
|
+
* Placeholder for handling reaction removal.
|
|
4206
|
+
* @private
|
|
3577
4207
|
*/
|
|
3578
4208
|
async handleReactionRemove(reaction, user) {
|
|
3579
4209
|
try {
|
|
@@ -3592,7 +4222,7 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3592
4222
|
}
|
|
3593
4223
|
const messageContent = reaction.message.content || "";
|
|
3594
4224
|
const truncatedContent = messageContent.length > 50 ? `${messageContent.substring(0, 50)}...` : messageContent;
|
|
3595
|
-
const reactionMessage = `*Removed <${emoji}> from: "${truncatedContent}"*`;
|
|
4225
|
+
const reactionMessage = `*Removed <${emoji}> from: \\"${truncatedContent}\\"*`;
|
|
3596
4226
|
const roomId = createUniqueUuid6(this.runtime, reaction.message.channel.id);
|
|
3597
4227
|
const entityId = createUniqueUuid6(this.runtime, user.id);
|
|
3598
4228
|
const timestamp = Date.now();
|
|
@@ -3606,6 +4236,8 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3606
4236
|
entityId,
|
|
3607
4237
|
roomId,
|
|
3608
4238
|
userName,
|
|
4239
|
+
worldId: createUniqueUuid6(this.runtime, reaction.message.guild?.id ?? roomId),
|
|
4240
|
+
worldName: reaction.message.guild?.name,
|
|
3609
4241
|
name,
|
|
3610
4242
|
source: "discord",
|
|
3611
4243
|
channelId: reaction.message.channel.id,
|
|
@@ -3630,9 +4262,9 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3630
4262
|
const callback = async (content) => {
|
|
3631
4263
|
if (!reaction.message.channel) {
|
|
3632
4264
|
logger6.error("No channel found for reaction message");
|
|
3633
|
-
return;
|
|
4265
|
+
return [];
|
|
3634
4266
|
}
|
|
3635
|
-
await reaction.message.channel.send(content.text);
|
|
4267
|
+
await reaction.message.channel.send(content.text ?? "");
|
|
3636
4268
|
return [];
|
|
3637
4269
|
};
|
|
3638
4270
|
this.runtime.emitEvent(["DISCORD_REACTION_RECEIVED" /* REACTION_RECEIVED */], {
|
|
@@ -3645,260 +4277,40 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
3645
4277
|
}
|
|
3646
4278
|
}
|
|
3647
4279
|
/**
|
|
3648
|
-
*
|
|
3649
|
-
*
|
|
3650
|
-
* @returns {Promise<void>}
|
|
4280
|
+
* Stops the Discord service and cleans up resources.
|
|
4281
|
+
* Implements the abstract method from the Service class.
|
|
3651
4282
|
*/
|
|
3652
|
-
async
|
|
3653
|
-
logger6.
|
|
3654
|
-
|
|
3655
|
-
this.
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
rooms: await this.buildStandardizedRooms(fullGuild, worldId),
|
|
3661
|
-
users: await this.buildStandardizedUsers(fullGuild),
|
|
3662
|
-
world: {
|
|
3663
|
-
id: worldId,
|
|
3664
|
-
name: fullGuild.name,
|
|
3665
|
-
agentId: this.runtime.agentId,
|
|
3666
|
-
serverId: fullGuild.id,
|
|
3667
|
-
metadata: {
|
|
3668
|
-
ownership: fullGuild.ownerId ? { ownerId } : void 0,
|
|
3669
|
-
roles: {
|
|
3670
|
-
[ownerId]: Role.OWNER
|
|
3671
|
-
}
|
|
3672
|
-
}
|
|
3673
|
-
},
|
|
3674
|
-
source: "discord"
|
|
3675
|
-
};
|
|
3676
|
-
this.runtime.emitEvent(["DISCORD_WORLD_JOINED" /* WORLD_JOINED */], {
|
|
3677
|
-
runtime: this.runtime,
|
|
3678
|
-
server: fullGuild,
|
|
3679
|
-
source: "discord"
|
|
3680
|
-
});
|
|
3681
|
-
this.runtime.emitEvent([EventType2.WORLD_JOINED], standardizedData);
|
|
3682
|
-
}
|
|
3683
|
-
/**
|
|
3684
|
-
* Handles interactions created by the user, specifically commands.
|
|
3685
|
-
* @param {any} interaction - The interaction object received
|
|
3686
|
-
* @returns {void}
|
|
3687
|
-
*/
|
|
3688
|
-
async handleInteractionCreate(interaction) {
|
|
3689
|
-
if (!interaction.isCommand()) return;
|
|
3690
|
-
switch (interaction.commandName) {
|
|
3691
|
-
case "joinchannel":
|
|
3692
|
-
await this.voiceManager.handleJoinChannelCommand(interaction);
|
|
3693
|
-
break;
|
|
3694
|
-
case "leavechannel":
|
|
3695
|
-
await this.voiceManager.handleLeaveChannelCommand(interaction);
|
|
3696
|
-
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.");
|
|
3697
4291
|
}
|
|
3698
|
-
|
|
3699
|
-
/**
|
|
3700
|
-
* Builds a standardized list of rooms from Discord guild channels
|
|
3701
|
-
*/
|
|
3702
|
-
/**
|
|
3703
|
-
* Build standardized rooms for a guild based on text and voice channels.
|
|
3704
|
-
*
|
|
3705
|
-
* @param {Guild} guild The guild to build rooms for.
|
|
3706
|
-
* @param {UUID} _worldId The ID of the world to associate with the rooms.
|
|
3707
|
-
* @returns {Promise<any[]>} An array of standardized room objects.
|
|
3708
|
-
*/
|
|
3709
|
-
async buildStandardizedRooms(guild, _worldId) {
|
|
3710
|
-
const rooms = [];
|
|
3711
|
-
for (const [channelId, channel] of guild.channels.cache) {
|
|
3712
|
-
if (channel.type === DiscordChannelType4.GuildText || channel.type === DiscordChannelType4.GuildVoice) {
|
|
3713
|
-
const roomId = createUniqueUuid6(this.runtime, channelId);
|
|
3714
|
-
let channelType;
|
|
3715
|
-
switch (channel.type) {
|
|
3716
|
-
case DiscordChannelType4.GuildText:
|
|
3717
|
-
channelType = ChannelType9.GROUP;
|
|
3718
|
-
break;
|
|
3719
|
-
case DiscordChannelType4.GuildVoice:
|
|
3720
|
-
channelType = ChannelType9.VOICE_GROUP;
|
|
3721
|
-
break;
|
|
3722
|
-
default:
|
|
3723
|
-
channelType = ChannelType9.GROUP;
|
|
3724
|
-
}
|
|
3725
|
-
let participants = [];
|
|
3726
|
-
if (guild.memberCount < 1e3 && channel.type === DiscordChannelType4.GuildText) {
|
|
3727
|
-
try {
|
|
3728
|
-
participants = Array.from(guild.members.cache.values()).filter(
|
|
3729
|
-
(member) => channel.permissionsFor(member)?.has(PermissionsBitField2.Flags.ViewChannel)
|
|
3730
|
-
).map((member) => createUniqueUuid6(this.runtime, member.id));
|
|
3731
|
-
} catch (error) {
|
|
3732
|
-
logger6.warn(`Failed to get participants for channel ${channel.name}:`, error);
|
|
3733
|
-
}
|
|
3734
|
-
}
|
|
3735
|
-
rooms.push({
|
|
3736
|
-
id: roomId,
|
|
3737
|
-
name: channel.name,
|
|
3738
|
-
type: channelType,
|
|
3739
|
-
channelId: channel.id,
|
|
3740
|
-
participants
|
|
3741
|
-
});
|
|
3742
|
-
}
|
|
4292
|
+
if (this.voiceManager) {
|
|
3743
4293
|
}
|
|
3744
|
-
|
|
4294
|
+
logger6.info("Discord service stopped.");
|
|
3745
4295
|
}
|
|
3746
4296
|
/**
|
|
3747
|
-
*
|
|
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.
|
|
3748
4301
|
*/
|
|
3749
|
-
async
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
entities.push({
|
|
3761
|
-
id: createUniqueUuid6(this.runtime, member.id),
|
|
3762
|
-
names: Array.from(
|
|
3763
|
-
/* @__PURE__ */ new Set([member.user.username, member.displayName, member.user.globalName])
|
|
3764
|
-
),
|
|
3765
|
-
agentId: this.runtime.agentId,
|
|
3766
|
-
metadata: {
|
|
3767
|
-
default: {
|
|
3768
|
-
username: tag,
|
|
3769
|
-
name: member.displayName || member.user.username
|
|
3770
|
-
},
|
|
3771
|
-
discord: member.user.globalName ? {
|
|
3772
|
-
username: tag,
|
|
3773
|
-
name: member.displayName || member.user.username,
|
|
3774
|
-
globalName: member.user.globalName,
|
|
3775
|
-
userId: member.id
|
|
3776
|
-
} : {
|
|
3777
|
-
username: tag,
|
|
3778
|
-
name: member.displayName || member.user.username,
|
|
3779
|
-
userId: member.id
|
|
3780
|
-
}
|
|
3781
|
-
}
|
|
3782
|
-
});
|
|
3783
|
-
}
|
|
3784
|
-
}
|
|
3785
|
-
if (entities.length < 100) {
|
|
3786
|
-
logger6.info(`Adding online members for ${guild.name}`);
|
|
3787
|
-
const onlineMembers = await guild.members.fetch({ limit: 100 });
|
|
3788
|
-
for (const [, member] of onlineMembers) {
|
|
3789
|
-
if (member.id !== botId) {
|
|
3790
|
-
const entityId = createUniqueUuid6(this.runtime, member.id);
|
|
3791
|
-
if (!entities.some((u) => u.id === entityId)) {
|
|
3792
|
-
const tag = member.user.bot ? `${member.user.username}#${member.user.discriminator}` : member.user.username;
|
|
3793
|
-
entities.push({
|
|
3794
|
-
id: entityId,
|
|
3795
|
-
names: Array.from(
|
|
3796
|
-
/* @__PURE__ */ new Set([member.user.username, member.displayName, member.user.globalName])
|
|
3797
|
-
),
|
|
3798
|
-
agentId: this.runtime.agentId,
|
|
3799
|
-
metadata: {
|
|
3800
|
-
default: {
|
|
3801
|
-
username: tag,
|
|
3802
|
-
name: member.displayName || member.user.username
|
|
3803
|
-
},
|
|
3804
|
-
discord: member.user.globalName ? {
|
|
3805
|
-
username: tag,
|
|
3806
|
-
name: member.displayName || member.user.username,
|
|
3807
|
-
globalName: member.user.globalName,
|
|
3808
|
-
userId: member.id
|
|
3809
|
-
} : {
|
|
3810
|
-
username: tag,
|
|
3811
|
-
name: member.displayName || member.user.username,
|
|
3812
|
-
userId: member.id
|
|
3813
|
-
}
|
|
3814
|
-
}
|
|
3815
|
-
});
|
|
3816
|
-
}
|
|
3817
|
-
}
|
|
3818
|
-
}
|
|
3819
|
-
}
|
|
3820
|
-
} catch (error) {
|
|
3821
|
-
logger6.error(`Error fetching members for ${guild.name}:`, error);
|
|
3822
|
-
}
|
|
3823
|
-
} else {
|
|
3824
|
-
try {
|
|
3825
|
-
let members = guild.members.cache;
|
|
3826
|
-
if (members.size === 0) {
|
|
3827
|
-
members = await guild.members.fetch();
|
|
3828
|
-
}
|
|
3829
|
-
for (const [, member] of members) {
|
|
3830
|
-
if (member.id !== botId) {
|
|
3831
|
-
const tag = member.user.bot ? `${member.user.username}#${member.user.discriminator}` : member.user.username;
|
|
3832
|
-
entities.push({
|
|
3833
|
-
id: createUniqueUuid6(this.runtime, member.id),
|
|
3834
|
-
names: Array.from(
|
|
3835
|
-
/* @__PURE__ */ new Set([member.user.username, member.displayName, member.user.globalName])
|
|
3836
|
-
),
|
|
3837
|
-
agentId: this.runtime.agentId,
|
|
3838
|
-
metadata: {
|
|
3839
|
-
default: {
|
|
3840
|
-
username: tag,
|
|
3841
|
-
name: member.displayName || member.user.username
|
|
3842
|
-
},
|
|
3843
|
-
discord: member.user.globalName ? {
|
|
3844
|
-
username: tag,
|
|
3845
|
-
name: member.displayName || member.user.username,
|
|
3846
|
-
globalName: member.user.globalName,
|
|
3847
|
-
userId: member.id
|
|
3848
|
-
} : {
|
|
3849
|
-
username: tag,
|
|
3850
|
-
name: member.displayName || member.user.username,
|
|
3851
|
-
userId: member.id
|
|
3852
|
-
}
|
|
3853
|
-
}
|
|
3854
|
-
});
|
|
3855
|
-
}
|
|
3856
|
-
}
|
|
3857
|
-
} catch (error) {
|
|
3858
|
-
logger6.error(`Error fetching members for ${guild.name}:`, error);
|
|
3859
|
-
}
|
|
3860
|
-
}
|
|
3861
|
-
return entities;
|
|
3862
|
-
}
|
|
3863
|
-
async onReady() {
|
|
3864
|
-
logger6.log("DISCORD ON READY");
|
|
3865
|
-
const guilds = await this.client?.guilds.fetch();
|
|
3866
|
-
for (const [, guild] of guilds) {
|
|
3867
|
-
const fullGuild = await guild.fetch();
|
|
3868
|
-
await this.voiceManager.scanGuild(fullGuild);
|
|
3869
|
-
setTimeout(async () => {
|
|
3870
|
-
const fullGuild2 = await guild.fetch();
|
|
3871
|
-
logger6.log("DISCORD SERVER CONNECTED", fullGuild2.name);
|
|
3872
|
-
this.runtime.emitEvent(["DISCORD_SERVER_CONNECTED" /* WORLD_CONNECTED */], {
|
|
3873
|
-
runtime: this.runtime,
|
|
3874
|
-
server: fullGuild2,
|
|
3875
|
-
source: "discord"
|
|
3876
|
-
});
|
|
3877
|
-
const worldId = createUniqueUuid6(this.runtime, fullGuild2.id);
|
|
3878
|
-
const ownerId = createUniqueUuid6(this.runtime, fullGuild2.ownerId);
|
|
3879
|
-
const standardizedData = {
|
|
3880
|
-
name: fullGuild2.name,
|
|
3881
|
-
runtime: this.runtime,
|
|
3882
|
-
rooms: await this.buildStandardizedRooms(fullGuild2, worldId),
|
|
3883
|
-
entities: await this.buildStandardizedUsers(fullGuild2),
|
|
3884
|
-
world: {
|
|
3885
|
-
id: worldId,
|
|
3886
|
-
name: fullGuild2.name,
|
|
3887
|
-
agentId: this.runtime.agentId,
|
|
3888
|
-
serverId: fullGuild2.id,
|
|
3889
|
-
metadata: {
|
|
3890
|
-
ownership: fullGuild2.ownerId ? { ownerId } : void 0,
|
|
3891
|
-
roles: {
|
|
3892
|
-
[ownerId]: Role.OWNER
|
|
3893
|
-
}
|
|
3894
|
-
}
|
|
3895
|
-
},
|
|
3896
|
-
source: "discord"
|
|
3897
|
-
};
|
|
3898
|
-
this.runtime.emitEvent([EventType2.WORLD_CONNECTED], standardizedData);
|
|
3899
|
-
}, 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;
|
|
3900
4313
|
}
|
|
3901
|
-
this.client?.emit("voiceManagerReady");
|
|
3902
4314
|
}
|
|
3903
4315
|
};
|
|
3904
4316
|
|
|
@@ -3912,11 +4324,12 @@ import {
|
|
|
3912
4324
|
entersState as entersState2
|
|
3913
4325
|
} from "@discordjs/voice";
|
|
3914
4326
|
import { ModelType as ModelType9, logger as logger7 } from "@elizaos/core";
|
|
3915
|
-
import { ChannelType as ChannelType10, Events as Events2 } from "discord.js";
|
|
4327
|
+
import { ChannelType as ChannelType10, Events as Events2, AttachmentBuilder } from "discord.js";
|
|
3916
4328
|
var TEST_IMAGE_URL = "https://github.com/elizaOS/awesome-eliza/blob/main/assets/eliza-logo.jpg?raw=true";
|
|
3917
4329
|
var DiscordTestSuite = class {
|
|
3918
4330
|
name = "discord";
|
|
3919
|
-
discordClient
|
|
4331
|
+
discordClient;
|
|
4332
|
+
// Use definite assignment assertion
|
|
3920
4333
|
tests;
|
|
3921
4334
|
/**
|
|
3922
4335
|
* Constructor for initializing the tests array with test cases to be executed.
|
|
@@ -3962,13 +4375,19 @@ var DiscordTestSuite = class {
|
|
|
3962
4375
|
async testCreatingDiscordClient(runtime) {
|
|
3963
4376
|
try {
|
|
3964
4377
|
this.discordClient = runtime.getService(ServiceType2.DISCORD);
|
|
3965
|
-
if (this.discordClient
|
|
4378
|
+
if (!this.discordClient) {
|
|
4379
|
+
throw new Error("Failed to get DiscordService from runtime.");
|
|
4380
|
+
}
|
|
4381
|
+
if (this.discordClient.client?.isReady()) {
|
|
3966
4382
|
logger7.success("DiscordService is already ready.");
|
|
3967
4383
|
} else {
|
|
3968
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
|
+
}
|
|
3969
4388
|
await new Promise((resolve, reject) => {
|
|
3970
|
-
this.discordClient.client
|
|
3971
|
-
this.discordClient.client
|
|
4389
|
+
this.discordClient.client?.once(Events2.ClientReady, resolve);
|
|
4390
|
+
this.discordClient.client?.once(Events2.Error, reject);
|
|
3972
4391
|
});
|
|
3973
4392
|
}
|
|
3974
4393
|
} catch (error) {
|
|
@@ -3983,6 +4402,7 @@ var DiscordTestSuite = class {
|
|
|
3983
4402
|
* @throws {Error} - If there is an error in executing the slash command test.
|
|
3984
4403
|
*/
|
|
3985
4404
|
async testJoinVoiceSlashCommand(runtime) {
|
|
4405
|
+
if (!this.discordClient) throw new Error("Discord client not initialized.");
|
|
3986
4406
|
try {
|
|
3987
4407
|
await this.waitForVoiceManagerReady(this.discordClient);
|
|
3988
4408
|
const channel = await this.getTestChannel(runtime);
|
|
@@ -4002,10 +4422,13 @@ var DiscordTestSuite = class {
|
|
|
4002
4422
|
logger7.info(`JoinChannel Slash Command Response: ${message}`);
|
|
4003
4423
|
}
|
|
4004
4424
|
};
|
|
4425
|
+
if (!this.discordClient.voiceManager) {
|
|
4426
|
+
throw new Error("VoiceManager is not available on the Discord client.");
|
|
4427
|
+
}
|
|
4005
4428
|
await this.discordClient.voiceManager.handleJoinChannelCommand(fakeJoinInteraction);
|
|
4006
|
-
logger7.success("
|
|
4429
|
+
logger7.success("Join voice slash command test completed successfully.");
|
|
4007
4430
|
} catch (error) {
|
|
4008
|
-
throw new Error(`Error in slash commands test: ${error}`);
|
|
4431
|
+
throw new Error(`Error in join voice slash commands test: ${error}`);
|
|
4009
4432
|
}
|
|
4010
4433
|
}
|
|
4011
4434
|
/**
|
|
@@ -4015,6 +4438,7 @@ var DiscordTestSuite = class {
|
|
|
4015
4438
|
* @returns {Promise<void>} A promise that resolves when the test is complete.
|
|
4016
4439
|
*/
|
|
4017
4440
|
async testLeaveVoiceSlashCommand(runtime) {
|
|
4441
|
+
if (!this.discordClient) throw new Error("Discord client not initialized.");
|
|
4018
4442
|
try {
|
|
4019
4443
|
await this.waitForVoiceManagerReady(this.discordClient);
|
|
4020
4444
|
const channel = await this.getTestChannel(runtime);
|
|
@@ -4029,10 +4453,13 @@ var DiscordTestSuite = class {
|
|
|
4029
4453
|
logger7.info(`LeaveChannel Slash Command Response: ${message}`);
|
|
4030
4454
|
}
|
|
4031
4455
|
};
|
|
4456
|
+
if (!this.discordClient.voiceManager) {
|
|
4457
|
+
throw new Error("VoiceManager is not available on the Discord client.");
|
|
4458
|
+
}
|
|
4032
4459
|
await this.discordClient.voiceManager.handleLeaveChannelCommand(fakeLeaveInteraction);
|
|
4033
|
-
logger7.success("
|
|
4460
|
+
logger7.success("Leave voice slash command test completed successfully.");
|
|
4034
4461
|
} catch (error) {
|
|
4035
|
-
throw new Error(`Error in slash commands test: ${error}`);
|
|
4462
|
+
throw new Error(`Error in leave voice slash commands test: ${error}`);
|
|
4036
4463
|
}
|
|
4037
4464
|
}
|
|
4038
4465
|
/**
|
|
@@ -4041,16 +4468,26 @@ var DiscordTestSuite = class {
|
|
|
4041
4468
|
* @throws {Error} - If voice channel is invalid, voice connection fails to become ready, or no text to speech service found.
|
|
4042
4469
|
*/
|
|
4043
4470
|
async testTextToSpeechPlayback(runtime) {
|
|
4471
|
+
if (!this.discordClient) throw new Error("Discord client not initialized.");
|
|
4044
4472
|
try {
|
|
4045
4473
|
await this.waitForVoiceManagerReady(this.discordClient);
|
|
4046
4474
|
const channel = await this.getTestChannel(runtime);
|
|
4047
4475
|
if (!channel || channel.type !== ChannelType10.GuildVoice) {
|
|
4048
4476
|
throw new Error("Invalid voice channel.");
|
|
4049
4477
|
}
|
|
4478
|
+
if (!this.discordClient.voiceManager) {
|
|
4479
|
+
throw new Error("VoiceManager is not available on the Discord client.");
|
|
4480
|
+
}
|
|
4050
4481
|
await this.discordClient.voiceManager.joinChannel(channel);
|
|
4051
4482
|
const guild = await this.getActiveGuild(this.discordClient);
|
|
4052
4483
|
const guildId = guild.id;
|
|
4484
|
+
if (!this.discordClient.voiceManager) {
|
|
4485
|
+
throw new Error("VoiceManager is not available on the Discord client.");
|
|
4486
|
+
}
|
|
4053
4487
|
const connection = this.discordClient.voiceManager.getVoiceConnection(guildId);
|
|
4488
|
+
if (!connection) {
|
|
4489
|
+
throw new Error(`No voice connection found for guild: ${guildId}`);
|
|
4490
|
+
}
|
|
4054
4491
|
try {
|
|
4055
4492
|
await entersState2(connection, VoiceConnectionStatus2.Ready, 1e4);
|
|
4056
4493
|
logger7.success(`Voice connection is ready in guild: ${guildId}`);
|
|
@@ -4082,9 +4519,14 @@ var DiscordTestSuite = class {
|
|
|
4082
4519
|
* @throws {Error} If there is an error in sending the text message.
|
|
4083
4520
|
*/
|
|
4084
4521
|
async testSendingTextMessage(runtime) {
|
|
4522
|
+
if (!this.discordClient) throw new Error("Discord client not initialized.");
|
|
4085
4523
|
try {
|
|
4086
4524
|
const channel = await this.getTestChannel(runtime);
|
|
4087
|
-
|
|
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]);
|
|
4088
4530
|
} catch (error) {
|
|
4089
4531
|
throw new Error(`Error in sending text message: ${error}`);
|
|
4090
4532
|
}
|
|
@@ -4096,6 +4538,7 @@ var DiscordTestSuite = class {
|
|
|
4096
4538
|
* @returns {Promise<void>} A Promise that resolves once the message is handled.
|
|
4097
4539
|
*/
|
|
4098
4540
|
async testHandlingMessage(runtime) {
|
|
4541
|
+
if (!this.discordClient) throw new Error("Discord client not initialized.");
|
|
4099
4542
|
try {
|
|
4100
4543
|
const channel = await this.getTestChannel(runtime);
|
|
4101
4544
|
const fakeMessage = {
|
|
@@ -4114,9 +4557,12 @@ var DiscordTestSuite = class {
|
|
|
4114
4557
|
reference: null,
|
|
4115
4558
|
attachments: []
|
|
4116
4559
|
};
|
|
4560
|
+
if (!this.discordClient.messageManager) {
|
|
4561
|
+
throw new Error("MessageManager is not available on the Discord client.");
|
|
4562
|
+
}
|
|
4117
4563
|
await this.discordClient.messageManager.handleMessage(fakeMessage);
|
|
4118
4564
|
} catch (error) {
|
|
4119
|
-
throw new Error(`Error in
|
|
4565
|
+
throw new Error(`Error in handling message test: ${error}`);
|
|
4120
4566
|
}
|
|
4121
4567
|
}
|
|
4122
4568
|
// #############################
|
|
@@ -4130,8 +4576,9 @@ var DiscordTestSuite = class {
|
|
|
4130
4576
|
* @throws {Error} If no test channel is found.
|
|
4131
4577
|
*/
|
|
4132
4578
|
async getTestChannel(runtime) {
|
|
4579
|
+
if (!this.discordClient) throw new Error("Discord client not initialized.");
|
|
4133
4580
|
const channelId = this.validateChannelId(runtime);
|
|
4134
|
-
const channel = await this.discordClient.client
|
|
4581
|
+
const channel = await this.discordClient.client?.channels.fetch(channelId);
|
|
4135
4582
|
if (!channel) throw new Error("no test channel found!");
|
|
4136
4583
|
return channel;
|
|
4137
4584
|
}
|
|
@@ -4149,7 +4596,7 @@ var DiscordTestSuite = class {
|
|
|
4149
4596
|
if (!channel || !channel.isTextBased()) {
|
|
4150
4597
|
throw new Error("Channel is not a text-based channel or does not exist.");
|
|
4151
4598
|
}
|
|
4152
|
-
await sendMessageInChunks(channel, messageContent,
|
|
4599
|
+
await sendMessageInChunks(channel, messageContent, "", files);
|
|
4153
4600
|
} catch (error) {
|
|
4154
4601
|
throw new Error(`Error sending message: ${error}`);
|
|
4155
4602
|
}
|
|
@@ -4190,6 +4637,9 @@ var DiscordTestSuite = class {
|
|
|
4190
4637
|
* @throws {Error} If no active voice connection is found for the bot.
|
|
4191
4638
|
*/
|
|
4192
4639
|
async getActiveGuild(discordClient) {
|
|
4640
|
+
if (!discordClient.client) {
|
|
4641
|
+
throw new Error("Discord client instance is missing within the service.");
|
|
4642
|
+
}
|
|
4193
4643
|
const guilds = await discordClient.client.guilds.fetch();
|
|
4194
4644
|
const fullGuilds = await Promise.all(guilds.map((guild) => guild.fetch()));
|
|
4195
4645
|
const activeGuild = fullGuilds.find((g) => g.members.me?.voice.channelId);
|
|
@@ -4209,10 +4659,13 @@ var DiscordTestSuite = class {
|
|
|
4209
4659
|
if (!discordClient) {
|
|
4210
4660
|
throw new Error("Discord client is not initialized.");
|
|
4211
4661
|
}
|
|
4212
|
-
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()) {
|
|
4213
4666
|
await new Promise((resolve, reject) => {
|
|
4214
|
-
discordClient.voiceManager
|
|
4215
|
-
discordClient.voiceManager
|
|
4667
|
+
discordClient.voiceManager?.once("ready", resolve);
|
|
4668
|
+
discordClient.voiceManager?.once("error", reject);
|
|
4216
4669
|
});
|
|
4217
4670
|
}
|
|
4218
4671
|
}
|