@elizaos/plugin-discord 1.0.0-alpha.31 → 1.0.0-alpha.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1126 -1136
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,20 +1,7 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
EventTypes as EventTypes2,
|
|
5
|
-
Role,
|
|
6
|
-
Service,
|
|
7
|
-
createUniqueUuid as createUniqueUuid6,
|
|
8
|
-
logger as logger7
|
|
3
|
+
logger as logger8
|
|
9
4
|
} from "@elizaos/core";
|
|
10
|
-
import {
|
|
11
|
-
ChannelType as DiscordChannelType4,
|
|
12
|
-
Client as DiscordJsClient,
|
|
13
|
-
Events as Events2,
|
|
14
|
-
GatewayIntentBits,
|
|
15
|
-
Partials,
|
|
16
|
-
PermissionsBitField as PermissionsBitField2
|
|
17
|
-
} from "discord.js";
|
|
18
5
|
|
|
19
6
|
// src/actions/chatWithAttachments.ts
|
|
20
7
|
import fs from "node:fs";
|
|
@@ -66,7 +53,7 @@ var getAttachmentIds = async (runtime, _message, state) => {
|
|
|
66
53
|
}
|
|
67
54
|
return null;
|
|
68
55
|
};
|
|
69
|
-
var
|
|
56
|
+
var chatWithAttachments = {
|
|
70
57
|
name: "CHAT_WITH_ATTACHMENTS",
|
|
71
58
|
similes: [
|
|
72
59
|
"CHAT_WITH_ATTACHMENT",
|
|
@@ -298,7 +285,7 @@ ${currentSummary.trim()}
|
|
|
298
285
|
]
|
|
299
286
|
]
|
|
300
287
|
};
|
|
301
|
-
var chatWithAttachments_default =
|
|
288
|
+
var chatWithAttachments_default = chatWithAttachments;
|
|
302
289
|
|
|
303
290
|
// src/actions/downloadMedia.ts
|
|
304
291
|
import {
|
|
@@ -336,7 +323,7 @@ var getMediaUrl = async (runtime, _message, state) => {
|
|
|
336
323
|
}
|
|
337
324
|
return null;
|
|
338
325
|
};
|
|
339
|
-
var
|
|
326
|
+
var downloadMedia = {
|
|
340
327
|
name: "DOWNLOAD_MEDIA",
|
|
341
328
|
similes: [
|
|
342
329
|
"DOWNLOAD_VIDEO",
|
|
@@ -534,7 +521,7 @@ var getDateRange = async (runtime, _message, state) => {
|
|
|
534
521
|
}
|
|
535
522
|
}
|
|
536
523
|
};
|
|
537
|
-
var
|
|
524
|
+
var summarize = {
|
|
538
525
|
name: "SUMMARIZE_CONVERSATION",
|
|
539
526
|
similes: [
|
|
540
527
|
"RECAP",
|
|
@@ -782,7 +769,6 @@ ${currentSummary.trim()}
|
|
|
782
769
|
]
|
|
783
770
|
]
|
|
784
771
|
};
|
|
785
|
-
var summarizeConversation_default = summarizeAction2;
|
|
786
772
|
|
|
787
773
|
// src/actions/transcribeMedia.ts
|
|
788
774
|
import {
|
|
@@ -820,7 +806,7 @@ var getMediaAttachmentId = async (runtime, _message, state) => {
|
|
|
820
806
|
}
|
|
821
807
|
return null;
|
|
822
808
|
};
|
|
823
|
-
var
|
|
809
|
+
var transcribeMedia = {
|
|
824
810
|
name: "TRANSCRIBE_MEDIA",
|
|
825
811
|
similes: [
|
|
826
812
|
"TRANSCRIBE_AUDIO",
|
|
@@ -960,7 +946,6 @@ ${mediaTranscript.trim()}
|
|
|
960
946
|
]
|
|
961
947
|
]
|
|
962
948
|
};
|
|
963
|
-
var transcribeMedia_default = transcribeMediaAction;
|
|
964
949
|
|
|
965
950
|
// src/actions/voiceJoin.ts
|
|
966
951
|
import {
|
|
@@ -980,7 +965,7 @@ var ServiceTypes2 = {
|
|
|
980
965
|
};
|
|
981
966
|
|
|
982
967
|
// src/actions/voiceJoin.ts
|
|
983
|
-
var
|
|
968
|
+
var joinVoice = {
|
|
984
969
|
name: "JOIN_VOICE",
|
|
985
970
|
similes: [
|
|
986
971
|
"JOIN_VOICE",
|
|
@@ -1273,7 +1258,7 @@ import {
|
|
|
1273
1258
|
logger as logger2
|
|
1274
1259
|
} from "@elizaos/core";
|
|
1275
1260
|
import { BaseGuildVoiceChannel } from "discord.js";
|
|
1276
|
-
var
|
|
1261
|
+
var leaveVoice = {
|
|
1277
1262
|
name: "LEAVE_VOICE",
|
|
1278
1263
|
similes: [
|
|
1279
1264
|
"LEAVE_VOICE",
|
|
@@ -1520,1279 +1505,1283 @@ var voiceLeave_default = {
|
|
|
1520
1505
|
]
|
|
1521
1506
|
};
|
|
1522
1507
|
|
|
1523
|
-
// src/
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1508
|
+
// src/providers/channelState.ts
|
|
1509
|
+
import { ChannelType as ChannelType4 } from "@elizaos/core";
|
|
1510
|
+
var channelStateProvider = {
|
|
1511
|
+
name: "channelState",
|
|
1512
|
+
get: async (runtime, message, state) => {
|
|
1513
|
+
const room = state.data?.room ?? await runtime.getRoom(message.roomId);
|
|
1514
|
+
if (!room) {
|
|
1515
|
+
throw new Error("No room found");
|
|
1516
|
+
}
|
|
1517
|
+
if (message.content.source !== "discord") {
|
|
1518
|
+
return {
|
|
1519
|
+
data: null,
|
|
1520
|
+
values: {},
|
|
1521
|
+
text: ""
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
const agentName = state?.agentName || "The agent";
|
|
1525
|
+
const senderName = state?.senderName || "someone";
|
|
1526
|
+
let responseText = "";
|
|
1527
|
+
let channelType = "";
|
|
1528
|
+
let serverName = "";
|
|
1529
|
+
let channelId = "";
|
|
1530
|
+
const serverId = room.serverId;
|
|
1531
|
+
if (room.type === ChannelType4.DM) {
|
|
1532
|
+
channelType = "DM";
|
|
1533
|
+
responseText = `${agentName} is currently in a direct message conversation with ${senderName}. ${agentName} should engage in conversation, should respond to messages that are addressed to them and only ignore messages that seem to not require a response.`;
|
|
1534
|
+
} else {
|
|
1535
|
+
channelType = "GROUP";
|
|
1536
|
+
if (!serverId) {
|
|
1537
|
+
console.error("No server ID found");
|
|
1538
|
+
return {
|
|
1539
|
+
data: {
|
|
1540
|
+
room,
|
|
1541
|
+
channelType
|
|
1542
|
+
},
|
|
1543
|
+
values: {
|
|
1544
|
+
channelType
|
|
1545
|
+
},
|
|
1546
|
+
text: ""
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
channelId = room.channelId;
|
|
1550
|
+
const discordService = runtime.getService(
|
|
1551
|
+
ServiceTypes2.DISCORD
|
|
1552
|
+
);
|
|
1553
|
+
if (!discordService) {
|
|
1554
|
+
console.warn("No discord client found");
|
|
1555
|
+
return {
|
|
1556
|
+
data: {
|
|
1557
|
+
room,
|
|
1558
|
+
channelType,
|
|
1559
|
+
serverId
|
|
1560
|
+
},
|
|
1561
|
+
values: {
|
|
1562
|
+
channelType,
|
|
1563
|
+
serverId
|
|
1564
|
+
},
|
|
1565
|
+
text: ""
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
const guild = discordService.client.guilds.cache.get(serverId);
|
|
1569
|
+
serverName = guild.name;
|
|
1570
|
+
responseText = `${agentName} is currently having a conversation in the channel \`@${channelId} in the server \`${serverName}\` (@${serverId})`;
|
|
1571
|
+
responseText += `
|
|
1572
|
+
${agentName} is in a room with other users and should be self-conscious and only participate when directly addressed or when the conversation is relevant to them.`;
|
|
1573
|
+
}
|
|
1574
|
+
return {
|
|
1575
|
+
data: {
|
|
1576
|
+
room,
|
|
1577
|
+
channelType,
|
|
1578
|
+
serverId,
|
|
1579
|
+
serverName,
|
|
1580
|
+
channelId
|
|
1581
|
+
},
|
|
1582
|
+
values: {
|
|
1583
|
+
channelType,
|
|
1584
|
+
serverName,
|
|
1585
|
+
channelId
|
|
1586
|
+
},
|
|
1587
|
+
text: responseText
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1534
1590
|
};
|
|
1535
|
-
var DISCORD_SERVICE_NAME = "discord";
|
|
1536
1591
|
|
|
1537
|
-
// src/
|
|
1592
|
+
// src/providers/voiceState.ts
|
|
1593
|
+
import { getVoiceConnection } from "@discordjs/voice";
|
|
1594
|
+
import { ChannelType as ChannelType5 } from "@elizaos/core";
|
|
1595
|
+
var voiceStateProvider = {
|
|
1596
|
+
name: "voiceState",
|
|
1597
|
+
get: async (runtime, message, state) => {
|
|
1598
|
+
const room = await runtime.getRoom(message.roomId);
|
|
1599
|
+
if (!room) {
|
|
1600
|
+
throw new Error("No room found");
|
|
1601
|
+
}
|
|
1602
|
+
if (room.type !== ChannelType5.GROUP) {
|
|
1603
|
+
return {
|
|
1604
|
+
data: {
|
|
1605
|
+
isInVoiceChannel: false,
|
|
1606
|
+
room
|
|
1607
|
+
},
|
|
1608
|
+
values: {
|
|
1609
|
+
isInVoiceChannel: "false",
|
|
1610
|
+
roomType: room.type
|
|
1611
|
+
},
|
|
1612
|
+
text: ""
|
|
1613
|
+
};
|
|
1614
|
+
}
|
|
1615
|
+
const serverId = room.serverId;
|
|
1616
|
+
if (!serverId) {
|
|
1617
|
+
throw new Error("No server ID found 10");
|
|
1618
|
+
}
|
|
1619
|
+
const connection = getVoiceConnection(serverId);
|
|
1620
|
+
const agentName = state?.agentName || "The agent";
|
|
1621
|
+
if (!connection) {
|
|
1622
|
+
return {
|
|
1623
|
+
data: {
|
|
1624
|
+
isInVoiceChannel: false,
|
|
1625
|
+
room,
|
|
1626
|
+
serverId
|
|
1627
|
+
},
|
|
1628
|
+
values: {
|
|
1629
|
+
isInVoiceChannel: "false",
|
|
1630
|
+
serverId
|
|
1631
|
+
},
|
|
1632
|
+
text: `${agentName} is not currently in a voice channel`
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
const worldId = room.worldId;
|
|
1636
|
+
const world = await runtime.getWorld(worldId);
|
|
1637
|
+
if (!world) {
|
|
1638
|
+
throw new Error("No world found");
|
|
1639
|
+
}
|
|
1640
|
+
const worldName = world.name;
|
|
1641
|
+
const roomType = room.type;
|
|
1642
|
+
const channelId = room.channelId;
|
|
1643
|
+
const channelName = room.name;
|
|
1644
|
+
if (!channelId) {
|
|
1645
|
+
return {
|
|
1646
|
+
data: {
|
|
1647
|
+
isInVoiceChannel: true,
|
|
1648
|
+
room,
|
|
1649
|
+
serverId,
|
|
1650
|
+
world,
|
|
1651
|
+
connection
|
|
1652
|
+
},
|
|
1653
|
+
values: {
|
|
1654
|
+
isInVoiceChannel: "true",
|
|
1655
|
+
serverId,
|
|
1656
|
+
worldName,
|
|
1657
|
+
roomType
|
|
1658
|
+
},
|
|
1659
|
+
text: `${agentName} is in an invalid voice channel`
|
|
1660
|
+
};
|
|
1661
|
+
}
|
|
1662
|
+
return {
|
|
1663
|
+
data: {
|
|
1664
|
+
isInVoiceChannel: true,
|
|
1665
|
+
room,
|
|
1666
|
+
serverId,
|
|
1667
|
+
world,
|
|
1668
|
+
connection,
|
|
1669
|
+
channelId,
|
|
1670
|
+
channelName
|
|
1671
|
+
},
|
|
1672
|
+
values: {
|
|
1673
|
+
isInVoiceChannel: "true",
|
|
1674
|
+
serverId,
|
|
1675
|
+
worldName,
|
|
1676
|
+
roomType,
|
|
1677
|
+
channelId,
|
|
1678
|
+
channelName
|
|
1679
|
+
},
|
|
1680
|
+
text: `${agentName} is currently in the voice channel: ${channelName} (ID: ${channelId})`
|
|
1681
|
+
};
|
|
1682
|
+
}
|
|
1683
|
+
};
|
|
1684
|
+
|
|
1685
|
+
// src/tests.ts
|
|
1538
1686
|
import {
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1687
|
+
AudioPlayerStatus,
|
|
1688
|
+
NoSubscriberBehavior,
|
|
1689
|
+
VoiceConnectionStatus,
|
|
1690
|
+
createAudioPlayer,
|
|
1691
|
+
createAudioResource,
|
|
1692
|
+
entersState
|
|
1693
|
+
} from "@discordjs/voice";
|
|
1694
|
+
import {
|
|
1695
|
+
ModelTypes as ModelTypes7,
|
|
1543
1696
|
logger as logger4
|
|
1544
1697
|
} from "@elizaos/core";
|
|
1545
|
-
import {
|
|
1546
|
-
ChannelType as DiscordChannelType2
|
|
1547
|
-
} from "discord.js";
|
|
1698
|
+
import { ChannelType as ChannelType7, Events } from "discord.js";
|
|
1548
1699
|
|
|
1549
|
-
// src/
|
|
1550
|
-
import fs3 from "node:fs";
|
|
1551
|
-
import { trimTokens as trimTokens3 } from "@elizaos/core";
|
|
1552
|
-
import { parseJSONObjectFromText as parseJSONObjectFromText5 } from "@elizaos/core";
|
|
1700
|
+
// src/utils.ts
|
|
1553
1701
|
import {
|
|
1554
1702
|
ModelTypes as ModelTypes6,
|
|
1555
|
-
|
|
1703
|
+
logger as logger3,
|
|
1704
|
+
parseJSONObjectFromText as parseJSONObjectFromText5,
|
|
1705
|
+
trimTokens as trimTokens3
|
|
1556
1706
|
} from "@elizaos/core";
|
|
1557
|
-
import {
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
if (parsedResponse?.title && parsedResponse?.summary) {
|
|
1579
|
-
return {
|
|
1580
|
-
title: parsedResponse.title,
|
|
1581
|
-
description: parsedResponse.summary
|
|
1582
|
-
};
|
|
1583
|
-
}
|
|
1584
|
-
return {
|
|
1585
|
-
title: "",
|
|
1586
|
-
description: ""
|
|
1587
|
-
};
|
|
1707
|
+
import {
|
|
1708
|
+
ChannelType as ChannelType6,
|
|
1709
|
+
PermissionsBitField,
|
|
1710
|
+
ThreadChannel
|
|
1711
|
+
} from "discord.js";
|
|
1712
|
+
function getWavHeader(audioLength, sampleRate, channelCount = 1, bitsPerSample = 16) {
|
|
1713
|
+
const wavHeader = Buffer.alloc(44);
|
|
1714
|
+
wavHeader.write("RIFF", 0);
|
|
1715
|
+
wavHeader.writeUInt32LE(36 + audioLength, 4);
|
|
1716
|
+
wavHeader.write("WAVE", 8);
|
|
1717
|
+
wavHeader.write("fmt ", 12);
|
|
1718
|
+
wavHeader.writeUInt32LE(16, 16);
|
|
1719
|
+
wavHeader.writeUInt16LE(1, 20);
|
|
1720
|
+
wavHeader.writeUInt16LE(channelCount, 22);
|
|
1721
|
+
wavHeader.writeUInt32LE(sampleRate, 24);
|
|
1722
|
+
wavHeader.writeUInt32LE(sampleRate * bitsPerSample * channelCount / 8, 28);
|
|
1723
|
+
wavHeader.writeUInt16LE(bitsPerSample * channelCount / 8, 32);
|
|
1724
|
+
wavHeader.writeUInt16LE(bitsPerSample, 34);
|
|
1725
|
+
wavHeader.write("data", 36);
|
|
1726
|
+
wavHeader.writeUInt32LE(audioLength, 40);
|
|
1727
|
+
return wavHeader;
|
|
1588
1728
|
}
|
|
1589
|
-
var
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
async processAttachments(attachments) {
|
|
1606
|
-
const processedAttachments = [];
|
|
1607
|
-
const attachmentCollection = attachments instanceof Collection ? attachments : new Collection(attachments.map((att) => [att.id, att]));
|
|
1608
|
-
for (const [, attachment] of attachmentCollection) {
|
|
1609
|
-
const media = await this.processAttachment(attachment);
|
|
1610
|
-
if (media) {
|
|
1611
|
-
processedAttachments.push(media);
|
|
1729
|
+
var MAX_MESSAGE_LENGTH = 1900;
|
|
1730
|
+
async function sendMessageInChunks(channel, content, _inReplyTo, files) {
|
|
1731
|
+
const sentMessages = [];
|
|
1732
|
+
const messages = splitMessage(content);
|
|
1733
|
+
try {
|
|
1734
|
+
for (let i = 0; i < messages.length; i++) {
|
|
1735
|
+
const message = messages[i];
|
|
1736
|
+
if (message.trim().length > 0 || i === messages.length - 1 && files && files.length > 0) {
|
|
1737
|
+
const options = {
|
|
1738
|
+
content: message.trim()
|
|
1739
|
+
};
|
|
1740
|
+
if (i === messages.length - 1 && files && files.length > 0) {
|
|
1741
|
+
options.files = files;
|
|
1742
|
+
}
|
|
1743
|
+
const m = await channel.send(options);
|
|
1744
|
+
sentMessages.push(m);
|
|
1612
1745
|
}
|
|
1613
1746
|
}
|
|
1614
|
-
|
|
1747
|
+
} catch (error) {
|
|
1748
|
+
logger3.error("Error sending message:", error);
|
|
1749
|
+
}
|
|
1750
|
+
return sentMessages;
|
|
1751
|
+
}
|
|
1752
|
+
function splitMessage(content) {
|
|
1753
|
+
const messages = [];
|
|
1754
|
+
let currentMessage = "";
|
|
1755
|
+
const rawLines = content?.split("\n") || [];
|
|
1756
|
+
const lines = rawLines.flatMap((line) => {
|
|
1757
|
+
const chunks = [];
|
|
1758
|
+
while (line.length > MAX_MESSAGE_LENGTH) {
|
|
1759
|
+
chunks.push(line.slice(0, MAX_MESSAGE_LENGTH));
|
|
1760
|
+
line = line.slice(MAX_MESSAGE_LENGTH);
|
|
1761
|
+
}
|
|
1762
|
+
chunks.push(line);
|
|
1763
|
+
return chunks;
|
|
1764
|
+
});
|
|
1765
|
+
for (const line of lines) {
|
|
1766
|
+
if (currentMessage.length + line.length + 1 > MAX_MESSAGE_LENGTH) {
|
|
1767
|
+
messages.push(currentMessage.trim());
|
|
1768
|
+
currentMessage = "";
|
|
1769
|
+
}
|
|
1770
|
+
currentMessage += `${line}
|
|
1771
|
+
`;
|
|
1615
1772
|
}
|
|
1773
|
+
if (currentMessage.trim().length > 0) {
|
|
1774
|
+
messages.push(currentMessage.trim());
|
|
1775
|
+
}
|
|
1776
|
+
return messages;
|
|
1777
|
+
}
|
|
1778
|
+
function canSendMessage(channel) {
|
|
1779
|
+
if (!channel) {
|
|
1780
|
+
return {
|
|
1781
|
+
canSend: false,
|
|
1782
|
+
reason: "No channel given"
|
|
1783
|
+
};
|
|
1784
|
+
}
|
|
1785
|
+
if (channel.type === ChannelType6.DM) {
|
|
1786
|
+
return {
|
|
1787
|
+
canSend: true,
|
|
1788
|
+
reason: null
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
const botMember = channel.guild?.members.cache.get(channel.client.user.id);
|
|
1792
|
+
if (!botMember) {
|
|
1793
|
+
return {
|
|
1794
|
+
canSend: false,
|
|
1795
|
+
reason: "Not a guild channel or bot member not found"
|
|
1796
|
+
};
|
|
1797
|
+
}
|
|
1798
|
+
const requiredPermissions = [
|
|
1799
|
+
PermissionsBitField.Flags.ViewChannel,
|
|
1800
|
+
PermissionsBitField.Flags.SendMessages,
|
|
1801
|
+
PermissionsBitField.Flags.ReadMessageHistory
|
|
1802
|
+
];
|
|
1803
|
+
if (channel instanceof ThreadChannel) {
|
|
1804
|
+
requiredPermissions.push(PermissionsBitField.Flags.SendMessagesInThreads);
|
|
1805
|
+
}
|
|
1806
|
+
const permissions = channel.permissionsFor(botMember);
|
|
1807
|
+
if (!permissions) {
|
|
1808
|
+
return {
|
|
1809
|
+
canSend: false,
|
|
1810
|
+
reason: "Could not retrieve permissions"
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
const missingPermissions = requiredPermissions.filter(
|
|
1814
|
+
(perm) => !permissions.has(perm)
|
|
1815
|
+
);
|
|
1816
|
+
return {
|
|
1817
|
+
canSend: missingPermissions.length === 0,
|
|
1818
|
+
missingPermissions,
|
|
1819
|
+
reason: missingPermissions.length > 0 ? `Missing permissions: ${missingPermissions.map((p) => String(p)).join(", ")}` : null
|
|
1820
|
+
};
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
// src/tests.ts
|
|
1824
|
+
var TEST_IMAGE_URL = "https://github.com/elizaOS/awesome-eliza/blob/main/assets/eliza-logo.jpg?raw=true";
|
|
1825
|
+
var DiscordTestSuite = class {
|
|
1826
|
+
name = "discord";
|
|
1827
|
+
discordClient = null;
|
|
1828
|
+
tests;
|
|
1616
1829
|
/**
|
|
1617
|
-
*
|
|
1618
|
-
* If the media for the attachment URL is already cached, it will return the cached media.
|
|
1619
|
-
* Otherwise, it will determine the type of attachment (PDF, text, audio, video, image, generic)
|
|
1620
|
-
* and call the corresponding processing method to generate the media object.
|
|
1830
|
+
* Constructor for initializing the tests array with test cases to be executed.
|
|
1621
1831
|
*
|
|
1622
|
-
* @
|
|
1623
|
-
* @
|
|
1832
|
+
* @constructor
|
|
1833
|
+
* @this {TestSuite}
|
|
1624
1834
|
*/
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1835
|
+
constructor() {
|
|
1836
|
+
this.tests = [
|
|
1837
|
+
{
|
|
1838
|
+
name: "Initialize Discord Client",
|
|
1839
|
+
fn: this.testCreatingDiscordClient.bind(this)
|
|
1840
|
+
},
|
|
1841
|
+
{
|
|
1842
|
+
name: "Slash Commands - Join Voice",
|
|
1843
|
+
fn: this.testJoinVoiceSlashCommand.bind(this)
|
|
1844
|
+
},
|
|
1845
|
+
{
|
|
1846
|
+
name: "Voice Playback & TTS",
|
|
1847
|
+
fn: this.testTextToSpeechPlayback.bind(this)
|
|
1848
|
+
},
|
|
1849
|
+
{
|
|
1850
|
+
name: "Send Message with Attachments",
|
|
1851
|
+
fn: this.testSendingTextMessage.bind(this)
|
|
1852
|
+
},
|
|
1853
|
+
{
|
|
1854
|
+
name: "Handle Incoming Messages",
|
|
1855
|
+
fn: this.testHandlingMessage.bind(this)
|
|
1856
|
+
},
|
|
1857
|
+
{
|
|
1858
|
+
name: "Slash Commands - Leave Voice",
|
|
1859
|
+
fn: this.testLeaveVoiceSlashCommand.bind(this)
|
|
1860
|
+
}
|
|
1861
|
+
];
|
|
1647
1862
|
}
|
|
1648
1863
|
/**
|
|
1649
|
-
* Asynchronously
|
|
1650
|
-
*
|
|
1651
|
-
* @
|
|
1864
|
+
* Asynchronously tests the creation of Discord client using the provided runtime.
|
|
1865
|
+
*
|
|
1866
|
+
* @param {IAgentRuntime} runtime - The agent runtime used to obtain the Discord service.
|
|
1867
|
+
* @returns {Promise<void>} - A Promise that resolves once the Discord client is ready.
|
|
1868
|
+
* @throws {Error} - If an error occurs while creating the Discord client.
|
|
1652
1869
|
*/
|
|
1653
|
-
async
|
|
1870
|
+
async testCreatingDiscordClient(runtime) {
|
|
1654
1871
|
try {
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
if (
|
|
1659
|
-
|
|
1660
|
-
} else if (attachment.contentType?.startsWith("video/mp4")) {
|
|
1661
|
-
audioBuffer = await this.extractAudioFromMP4(audioVideoArrayBuffer);
|
|
1872
|
+
this.discordClient = runtime.getService(
|
|
1873
|
+
ServiceTypes2.DISCORD
|
|
1874
|
+
);
|
|
1875
|
+
if (this.discordClient.client.isReady()) {
|
|
1876
|
+
logger4.success("DiscordService is already ready.");
|
|
1662
1877
|
} else {
|
|
1663
|
-
|
|
1878
|
+
logger4.info("Waiting for DiscordService to be ready...");
|
|
1879
|
+
await new Promise((resolve, reject) => {
|
|
1880
|
+
this.discordClient.client.once(Events.ClientReady, resolve);
|
|
1881
|
+
this.discordClient.client.once(Events.Error, reject);
|
|
1882
|
+
});
|
|
1664
1883
|
}
|
|
1665
|
-
const transcription = await this.runtime.useModel(
|
|
1666
|
-
ModelTypes6.TRANSCRIPTION,
|
|
1667
|
-
audioBuffer
|
|
1668
|
-
);
|
|
1669
|
-
const { title, description } = await generateSummary(
|
|
1670
|
-
this.runtime,
|
|
1671
|
-
transcription
|
|
1672
|
-
);
|
|
1673
|
-
return {
|
|
1674
|
-
id: attachment.id,
|
|
1675
|
-
url: attachment.url,
|
|
1676
|
-
title: title || "Audio/Video Attachment",
|
|
1677
|
-
source: attachment.contentType?.startsWith("audio/") ? "Audio" : "Video",
|
|
1678
|
-
description: description || "User-uploaded audio/video attachment which has been transcribed",
|
|
1679
|
-
text: transcription || "Audio/video content not available"
|
|
1680
|
-
};
|
|
1681
1884
|
} catch (error) {
|
|
1682
|
-
|
|
1683
|
-
`Error processing audio/video attachment: ${error.message}`
|
|
1684
|
-
);
|
|
1685
|
-
return {
|
|
1686
|
-
id: attachment.id,
|
|
1687
|
-
url: attachment.url,
|
|
1688
|
-
title: "Audio/Video Attachment",
|
|
1689
|
-
source: attachment.contentType?.startsWith("audio/") ? "Audio" : "Video",
|
|
1690
|
-
description: "An audio/video attachment (transcription failed)",
|
|
1691
|
-
text: `This is an audio/video attachment. File name: ${attachment.name}, Size: ${attachment.size} bytes, Content type: ${attachment.contentType}`
|
|
1692
|
-
};
|
|
1885
|
+
throw new Error(`Error in test creating Discord client: ${error}`);
|
|
1693
1886
|
}
|
|
1694
1887
|
}
|
|
1695
1888
|
/**
|
|
1696
|
-
*
|
|
1889
|
+
* Asynchronously tests the join voice slash command functionality.
|
|
1697
1890
|
*
|
|
1698
|
-
* @param {
|
|
1699
|
-
* @returns {Promise<
|
|
1891
|
+
* @param {IAgentRuntime} runtime - The runtime environment for the agent.
|
|
1892
|
+
* @returns {Promise<void>} - A promise that resolves once the test is complete.
|
|
1893
|
+
* @throws {Error} - If there is an error in executing the slash command test.
|
|
1700
1894
|
*/
|
|
1701
|
-
async
|
|
1702
|
-
const tempMP4File = `temp_${Date.now()}.mp4`;
|
|
1703
|
-
const tempAudioFile = `temp_${Date.now()}.mp3`;
|
|
1895
|
+
async testJoinVoiceSlashCommand(runtime) {
|
|
1704
1896
|
try {
|
|
1705
|
-
|
|
1706
|
-
await
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
}).on("error", (err) => {
|
|
1710
|
-
reject(err);
|
|
1711
|
-
}).run();
|
|
1712
|
-
});
|
|
1713
|
-
const audioData = fs3.readFileSync(tempAudioFile);
|
|
1714
|
-
return audioData;
|
|
1715
|
-
} finally {
|
|
1716
|
-
if (fs3.existsSync(tempMP4File)) {
|
|
1717
|
-
fs3.unlinkSync(tempMP4File);
|
|
1718
|
-
}
|
|
1719
|
-
if (fs3.existsSync(tempAudioFile)) {
|
|
1720
|
-
fs3.unlinkSync(tempAudioFile);
|
|
1897
|
+
await this.waitForVoiceManagerReady(this.discordClient);
|
|
1898
|
+
const channel = await this.getTestChannel(runtime);
|
|
1899
|
+
if (!channel || !channel.isTextBased()) {
|
|
1900
|
+
throw new Error("Invalid test channel for slash command test.");
|
|
1721
1901
|
}
|
|
1902
|
+
const fakeJoinInteraction = {
|
|
1903
|
+
isCommand: () => true,
|
|
1904
|
+
commandName: "joinchannel",
|
|
1905
|
+
options: {
|
|
1906
|
+
get: (name) => name === "channel" ? { value: channel.id } : null
|
|
1907
|
+
},
|
|
1908
|
+
guild: channel.guild,
|
|
1909
|
+
deferReply: async () => {
|
|
1910
|
+
},
|
|
1911
|
+
editReply: async (message) => {
|
|
1912
|
+
logger4.info(`JoinChannel Slash Command Response: ${message}`);
|
|
1913
|
+
}
|
|
1914
|
+
};
|
|
1915
|
+
await this.discordClient.voiceManager.handleJoinChannelCommand(
|
|
1916
|
+
fakeJoinInteraction
|
|
1917
|
+
);
|
|
1918
|
+
logger4.success("Slash command test completed successfully.");
|
|
1919
|
+
} catch (error) {
|
|
1920
|
+
throw new Error(`Error in slash commands test: ${error}`);
|
|
1722
1921
|
}
|
|
1723
1922
|
}
|
|
1724
1923
|
/**
|
|
1725
|
-
*
|
|
1726
|
-
* converting it to text, generating a summary, and returning a Media object
|
|
1727
|
-
* with the extracted information.
|
|
1728
|
-
* If an error occurs during processing, a placeholder Media object is returned
|
|
1729
|
-
* with an error message.
|
|
1924
|
+
* Asynchronously tests the leave voice channel slash command.
|
|
1730
1925
|
*
|
|
1731
|
-
* @param {
|
|
1732
|
-
* @returns {Promise<
|
|
1733
|
-
* the processed PDF attachment.
|
|
1926
|
+
* @param {IAgentRuntime} runtime - The Agent Runtime instance.
|
|
1927
|
+
* @returns {Promise<void>} A promise that resolves when the test is complete.
|
|
1734
1928
|
*/
|
|
1735
|
-
async
|
|
1929
|
+
async testLeaveVoiceSlashCommand(runtime) {
|
|
1736
1930
|
try {
|
|
1737
|
-
|
|
1738
|
-
const
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1931
|
+
await this.waitForVoiceManagerReady(this.discordClient);
|
|
1932
|
+
const channel = await this.getTestChannel(runtime);
|
|
1933
|
+
if (!channel || !channel.isTextBased()) {
|
|
1934
|
+
throw new Error("Invalid test channel for slash command test.");
|
|
1935
|
+
}
|
|
1936
|
+
const fakeLeaveInteraction = {
|
|
1937
|
+
isCommand: () => true,
|
|
1938
|
+
commandName: "leavechannel",
|
|
1939
|
+
guildId: channel.guildId,
|
|
1940
|
+
reply: async (message) => {
|
|
1941
|
+
logger4.info(`LeaveChannel Slash Command Response: ${message}`);
|
|
1942
|
+
}
|
|
1748
1943
|
};
|
|
1944
|
+
await this.discordClient.voiceManager.handleLeaveChannelCommand(
|
|
1945
|
+
fakeLeaveInteraction
|
|
1946
|
+
);
|
|
1947
|
+
logger4.success("Slash command test completed successfully.");
|
|
1749
1948
|
} catch (error) {
|
|
1750
|
-
|
|
1751
|
-
return {
|
|
1752
|
-
id: attachment.id,
|
|
1753
|
-
url: attachment.url,
|
|
1754
|
-
title: "PDF Attachment (conversion failed)",
|
|
1755
|
-
source: "PDF",
|
|
1756
|
-
description: "A PDF document that could not be converted to text",
|
|
1757
|
-
text: `This is a PDF attachment. File name: ${attachment.name}, Size: ${attachment.size} bytes`
|
|
1758
|
-
};
|
|
1949
|
+
throw new Error(`Error in slash commands test: ${error}`);
|
|
1759
1950
|
}
|
|
1760
1951
|
}
|
|
1761
1952
|
/**
|
|
1762
|
-
*
|
|
1763
|
-
* @param {
|
|
1764
|
-
* @
|
|
1953
|
+
* Test Text to Speech playback.
|
|
1954
|
+
* @param {IAgentRuntime} runtime - The Agent Runtime instance.
|
|
1955
|
+
* @throws {Error} - If voice channel is invalid, voice connection fails to become ready, or no text to speech service found.
|
|
1765
1956
|
*/
|
|
1766
|
-
async
|
|
1957
|
+
async testTextToSpeechPlayback(runtime) {
|
|
1767
1958
|
try {
|
|
1768
|
-
|
|
1769
|
-
const
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1959
|
+
await this.waitForVoiceManagerReady(this.discordClient);
|
|
1960
|
+
const channel = await this.getTestChannel(runtime);
|
|
1961
|
+
if (!channel || channel.type !== ChannelType7.GuildVoice) {
|
|
1962
|
+
throw new Error("Invalid voice channel.");
|
|
1963
|
+
}
|
|
1964
|
+
await this.discordClient.voiceManager.joinChannel(channel);
|
|
1965
|
+
const guild = await this.getActiveGuild(this.discordClient);
|
|
1966
|
+
const guildId = guild.id;
|
|
1967
|
+
const connection = this.discordClient.voiceManager.getVoiceConnection(guildId);
|
|
1968
|
+
try {
|
|
1969
|
+
await entersState(connection, VoiceConnectionStatus.Ready, 1e4);
|
|
1970
|
+
logger4.success(`Voice connection is ready in guild: ${guildId}`);
|
|
1971
|
+
} catch (error) {
|
|
1972
|
+
throw new Error(`Voice connection failed to become ready: ${error}`);
|
|
1973
|
+
}
|
|
1974
|
+
let responseStream = null;
|
|
1975
|
+
try {
|
|
1976
|
+
responseStream = await runtime.useModel(
|
|
1977
|
+
ModelTypes7.TEXT_TO_SPEECH,
|
|
1978
|
+
`Hi! I'm ${runtime.character.name}! How are you doing today?`
|
|
1979
|
+
);
|
|
1980
|
+
} catch (_error) {
|
|
1981
|
+
throw new Error("No text to speech service found");
|
|
1982
|
+
}
|
|
1983
|
+
if (!responseStream) {
|
|
1984
|
+
throw new Error("TTS response stream is null or undefined.");
|
|
1985
|
+
}
|
|
1986
|
+
await this.playAudioStream(responseStream, connection);
|
|
1779
1987
|
} catch (error) {
|
|
1780
|
-
|
|
1781
|
-
return {
|
|
1782
|
-
id: attachment.id,
|
|
1783
|
-
url: attachment.url,
|
|
1784
|
-
title: "Plaintext Attachment (retrieval failed)",
|
|
1785
|
-
source: "Plaintext",
|
|
1786
|
-
description: "A plaintext document that could not be retrieved",
|
|
1787
|
-
text: `This is a plaintext attachment. File name: ${attachment.name}, Size: ${attachment.size} bytes`
|
|
1788
|
-
};
|
|
1988
|
+
throw new Error(`Error in TTS playback test: ${error}`);
|
|
1789
1989
|
}
|
|
1790
1990
|
}
|
|
1791
1991
|
/**
|
|
1792
|
-
*
|
|
1793
|
-
* If successful, returns a Media object populated with the details. If unsuccessful, creates a fallback
|
|
1794
|
-
* Media object and logs the error.
|
|
1992
|
+
* Asynchronously tests sending a text message to a specified channel.
|
|
1795
1993
|
*
|
|
1796
|
-
* @param {
|
|
1797
|
-
* @returns {Promise<
|
|
1994
|
+
* @param {IAgentRuntime} runtime - The runtime for the agent.
|
|
1995
|
+
* @returns {Promise<void>} A Promise that resolves when the message is sent successfully.
|
|
1996
|
+
* @throws {Error} If there is an error in sending the text message.
|
|
1798
1997
|
*/
|
|
1799
|
-
async
|
|
1998
|
+
async testSendingTextMessage(runtime) {
|
|
1800
1999
|
try {
|
|
1801
|
-
const
|
|
1802
|
-
|
|
1803
|
-
|
|
2000
|
+
const channel = await this.getTestChannel(runtime);
|
|
2001
|
+
await this.sendMessageToChannel(
|
|
2002
|
+
channel,
|
|
2003
|
+
"Testing Message",
|
|
2004
|
+
[TEST_IMAGE_URL]
|
|
1804
2005
|
);
|
|
1805
|
-
return {
|
|
1806
|
-
id: attachment.id,
|
|
1807
|
-
url: attachment.url,
|
|
1808
|
-
title: title || "Image Attachment",
|
|
1809
|
-
source: "Image",
|
|
1810
|
-
description: description || "An image attachment",
|
|
1811
|
-
text: description || "Image content not available"
|
|
1812
|
-
};
|
|
1813
2006
|
} catch (error) {
|
|
1814
|
-
|
|
1815
|
-
return this.createFallbackImageMedia(attachment);
|
|
2007
|
+
throw new Error(`Error in sending text message: ${error}`);
|
|
1816
2008
|
}
|
|
1817
2009
|
}
|
|
1818
2010
|
/**
|
|
1819
|
-
*
|
|
2011
|
+
* Asynchronously handles sending a test message using the given runtime and mock user data.
|
|
1820
2012
|
*
|
|
1821
|
-
* @param {
|
|
1822
|
-
* @returns {
|
|
1823
|
-
*/
|
|
1824
|
-
createFallbackImageMedia(attachment) {
|
|
1825
|
-
return {
|
|
1826
|
-
id: attachment.id,
|
|
1827
|
-
url: attachment.url,
|
|
1828
|
-
title: "Image Attachment",
|
|
1829
|
-
source: "Image",
|
|
1830
|
-
description: "An image attachment (recognition failed)",
|
|
1831
|
-
text: `This is an image attachment. File name: ${attachment.name}, Size: ${attachment.size} bytes, Content type: ${attachment.contentType}`
|
|
1832
|
-
};
|
|
1833
|
-
}
|
|
1834
|
-
/**
|
|
1835
|
-
* Process a video attachment to extract video information.
|
|
1836
|
-
* @param {Attachment} attachment - The attachment object containing video information.
|
|
1837
|
-
* @returns {Promise<Media>} A promise that resolves to a Media object with video details.
|
|
1838
|
-
* @throws {Error} If video service is not available.
|
|
2013
|
+
* @param {IAgentRuntime} runtime - The agent runtime object.
|
|
2014
|
+
* @returns {Promise<void>} A Promise that resolves once the message is handled.
|
|
1839
2015
|
*/
|
|
1840
|
-
async
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
text: videoInfo.text
|
|
2016
|
+
async testHandlingMessage(runtime) {
|
|
2017
|
+
try {
|
|
2018
|
+
const channel = await this.getTestChannel(runtime);
|
|
2019
|
+
const fakeMessage = {
|
|
2020
|
+
content: `Hello, ${runtime.character.name}! How are you?`,
|
|
2021
|
+
author: {
|
|
2022
|
+
id: "mock-user-id",
|
|
2023
|
+
username: "MockUser",
|
|
2024
|
+
bot: false
|
|
2025
|
+
},
|
|
2026
|
+
channel,
|
|
2027
|
+
id: "mock-message-id",
|
|
2028
|
+
createdTimestamp: Date.now(),
|
|
2029
|
+
mentions: {
|
|
2030
|
+
has: () => false
|
|
2031
|
+
},
|
|
2032
|
+
reference: null,
|
|
2033
|
+
attachments: []
|
|
1859
2034
|
};
|
|
2035
|
+
await this.discordClient.messageManager.handleMessage(fakeMessage);
|
|
2036
|
+
} catch (error) {
|
|
2037
|
+
throw new Error(`Error in sending text message: ${error}`);
|
|
1860
2038
|
}
|
|
1861
|
-
return {
|
|
1862
|
-
id: attachment.id,
|
|
1863
|
-
url: attachment.url,
|
|
1864
|
-
title: "Video Attachment",
|
|
1865
|
-
source: "Video",
|
|
1866
|
-
description: "A video attachment",
|
|
1867
|
-
text: "Video content not available"
|
|
1868
|
-
};
|
|
1869
2039
|
}
|
|
2040
|
+
// #############################
|
|
2041
|
+
// Utility Functions
|
|
2042
|
+
// #############################
|
|
1870
2043
|
/**
|
|
1871
|
-
*
|
|
1872
|
-
*
|
|
1873
|
-
* @
|
|
2044
|
+
* Asynchronously retrieves the test channel associated with the provided runtime.
|
|
2045
|
+
*
|
|
2046
|
+
* @param {IAgentRuntime} runtime - The runtime object containing necessary information.
|
|
2047
|
+
* @returns {Promise<Channel>} The test channel retrieved from the Discord client.
|
|
2048
|
+
* @throws {Error} If no test channel is found.
|
|
1874
2049
|
*/
|
|
1875
|
-
async
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
source: "Generic",
|
|
1881
|
-
description: "A generic attachment",
|
|
1882
|
-
text: "Attachment content not available"
|
|
1883
|
-
};
|
|
1884
|
-
}
|
|
1885
|
-
};
|
|
1886
|
-
|
|
1887
|
-
// src/utils.ts
|
|
1888
|
-
import {
|
|
1889
|
-
ModelTypes as ModelTypes7,
|
|
1890
|
-
logger as logger3,
|
|
1891
|
-
parseJSONObjectFromText as parseJSONObjectFromText6,
|
|
1892
|
-
trimTokens as trimTokens4
|
|
1893
|
-
} from "@elizaos/core";
|
|
1894
|
-
import {
|
|
1895
|
-
ChannelType as ChannelType4,
|
|
1896
|
-
PermissionsBitField,
|
|
1897
|
-
ThreadChannel
|
|
1898
|
-
} from "discord.js";
|
|
1899
|
-
function getWavHeader(audioLength, sampleRate, channelCount = 1, bitsPerSample = 16) {
|
|
1900
|
-
const wavHeader = Buffer.alloc(44);
|
|
1901
|
-
wavHeader.write("RIFF", 0);
|
|
1902
|
-
wavHeader.writeUInt32LE(36 + audioLength, 4);
|
|
1903
|
-
wavHeader.write("WAVE", 8);
|
|
1904
|
-
wavHeader.write("fmt ", 12);
|
|
1905
|
-
wavHeader.writeUInt32LE(16, 16);
|
|
1906
|
-
wavHeader.writeUInt16LE(1, 20);
|
|
1907
|
-
wavHeader.writeUInt16LE(channelCount, 22);
|
|
1908
|
-
wavHeader.writeUInt32LE(sampleRate, 24);
|
|
1909
|
-
wavHeader.writeUInt32LE(sampleRate * bitsPerSample * channelCount / 8, 28);
|
|
1910
|
-
wavHeader.writeUInt16LE(bitsPerSample * channelCount / 8, 32);
|
|
1911
|
-
wavHeader.writeUInt16LE(bitsPerSample, 34);
|
|
1912
|
-
wavHeader.write("data", 36);
|
|
1913
|
-
wavHeader.writeUInt32LE(audioLength, 40);
|
|
1914
|
-
return wavHeader;
|
|
1915
|
-
}
|
|
1916
|
-
var MAX_MESSAGE_LENGTH = 1900;
|
|
1917
|
-
async function sendMessageInChunks(channel, content, _inReplyTo, files) {
|
|
1918
|
-
const sentMessages = [];
|
|
1919
|
-
const messages = splitMessage(content);
|
|
1920
|
-
try {
|
|
1921
|
-
for (let i = 0; i < messages.length; i++) {
|
|
1922
|
-
const message = messages[i];
|
|
1923
|
-
if (message.trim().length > 0 || i === messages.length - 1 && files && files.length > 0) {
|
|
1924
|
-
const options = {
|
|
1925
|
-
content: message.trim()
|
|
1926
|
-
};
|
|
1927
|
-
if (i === messages.length - 1 && files && files.length > 0) {
|
|
1928
|
-
options.files = files;
|
|
1929
|
-
}
|
|
1930
|
-
const m = await channel.send(options);
|
|
1931
|
-
sentMessages.push(m);
|
|
1932
|
-
}
|
|
1933
|
-
}
|
|
1934
|
-
} catch (error) {
|
|
1935
|
-
logger3.error("Error sending message:", error);
|
|
1936
|
-
}
|
|
1937
|
-
return sentMessages;
|
|
1938
|
-
}
|
|
1939
|
-
function splitMessage(content) {
|
|
1940
|
-
const messages = [];
|
|
1941
|
-
let currentMessage = "";
|
|
1942
|
-
const rawLines = content?.split("\n") || [];
|
|
1943
|
-
const lines = rawLines.flatMap((line) => {
|
|
1944
|
-
const chunks = [];
|
|
1945
|
-
while (line.length > MAX_MESSAGE_LENGTH) {
|
|
1946
|
-
chunks.push(line.slice(0, MAX_MESSAGE_LENGTH));
|
|
1947
|
-
line = line.slice(MAX_MESSAGE_LENGTH);
|
|
1948
|
-
}
|
|
1949
|
-
chunks.push(line);
|
|
1950
|
-
return chunks;
|
|
1951
|
-
});
|
|
1952
|
-
for (const line of lines) {
|
|
1953
|
-
if (currentMessage.length + line.length + 1 > MAX_MESSAGE_LENGTH) {
|
|
1954
|
-
messages.push(currentMessage.trim());
|
|
1955
|
-
currentMessage = "";
|
|
1956
|
-
}
|
|
1957
|
-
currentMessage += `${line}
|
|
1958
|
-
`;
|
|
1959
|
-
}
|
|
1960
|
-
if (currentMessage.trim().length > 0) {
|
|
1961
|
-
messages.push(currentMessage.trim());
|
|
1962
|
-
}
|
|
1963
|
-
return messages;
|
|
1964
|
-
}
|
|
1965
|
-
function canSendMessage(channel) {
|
|
1966
|
-
if (!channel) {
|
|
1967
|
-
return {
|
|
1968
|
-
canSend: false,
|
|
1969
|
-
reason: "No channel given"
|
|
1970
|
-
};
|
|
1971
|
-
}
|
|
1972
|
-
if (channel.type === ChannelType4.DM) {
|
|
1973
|
-
return {
|
|
1974
|
-
canSend: true,
|
|
1975
|
-
reason: null
|
|
1976
|
-
};
|
|
1977
|
-
}
|
|
1978
|
-
const botMember = channel.guild?.members.cache.get(channel.client.user.id);
|
|
1979
|
-
if (!botMember) {
|
|
1980
|
-
return {
|
|
1981
|
-
canSend: false,
|
|
1982
|
-
reason: "Not a guild channel or bot member not found"
|
|
1983
|
-
};
|
|
1984
|
-
}
|
|
1985
|
-
const requiredPermissions = [
|
|
1986
|
-
PermissionsBitField.Flags.ViewChannel,
|
|
1987
|
-
PermissionsBitField.Flags.SendMessages,
|
|
1988
|
-
PermissionsBitField.Flags.ReadMessageHistory
|
|
1989
|
-
];
|
|
1990
|
-
if (channel instanceof ThreadChannel) {
|
|
1991
|
-
requiredPermissions.push(PermissionsBitField.Flags.SendMessagesInThreads);
|
|
1992
|
-
}
|
|
1993
|
-
const permissions = channel.permissionsFor(botMember);
|
|
1994
|
-
if (!permissions) {
|
|
1995
|
-
return {
|
|
1996
|
-
canSend: false,
|
|
1997
|
-
reason: "Could not retrieve permissions"
|
|
1998
|
-
};
|
|
1999
|
-
}
|
|
2000
|
-
const missingPermissions = requiredPermissions.filter(
|
|
2001
|
-
(perm) => !permissions.has(perm)
|
|
2002
|
-
);
|
|
2003
|
-
return {
|
|
2004
|
-
canSend: missingPermissions.length === 0,
|
|
2005
|
-
missingPermissions,
|
|
2006
|
-
reason: missingPermissions.length > 0 ? `Missing permissions: ${missingPermissions.map((p) => String(p)).join(", ")}` : null
|
|
2007
|
-
};
|
|
2008
|
-
}
|
|
2009
|
-
|
|
2010
|
-
// src/messages.ts
|
|
2011
|
-
var MessageManager = class {
|
|
2012
|
-
client;
|
|
2013
|
-
runtime;
|
|
2014
|
-
attachmentManager;
|
|
2015
|
-
getChannelType;
|
|
2016
|
-
/**
|
|
2017
|
-
* Constructor for a new instance of MyClass.
|
|
2018
|
-
* @param {any} discordClient - The Discord client object.
|
|
2019
|
-
*/
|
|
2020
|
-
constructor(discordClient) {
|
|
2021
|
-
this.client = discordClient.client;
|
|
2022
|
-
this.runtime = discordClient.runtime;
|
|
2023
|
-
this.attachmentManager = new AttachmentManager(this.runtime);
|
|
2024
|
-
this.getChannelType = discordClient.getChannelType;
|
|
2050
|
+
async getTestChannel(runtime) {
|
|
2051
|
+
const channelId = this.validateChannelId(runtime);
|
|
2052
|
+
const channel = await this.discordClient.client.channels.fetch(channelId);
|
|
2053
|
+
if (!channel) throw new Error("no test channel found!");
|
|
2054
|
+
return channel;
|
|
2025
2055
|
}
|
|
2026
2056
|
/**
|
|
2027
|
-
*
|
|
2057
|
+
* Async function to send a message to a text-based channel.
|
|
2028
2058
|
*
|
|
2029
|
-
* @param {
|
|
2059
|
+
* @param {TextChannel} channel - The text-based channel the message is being sent to.
|
|
2060
|
+
* @param {string} messageContent - The content of the message being sent.
|
|
2061
|
+
* @param {any[]} files - An array of files to include in the message.
|
|
2062
|
+
* @throws {Error} If the channel is not a text-based channel or does not exist.
|
|
2063
|
+
* @throws {Error} If there is an error sending the message.
|
|
2030
2064
|
*/
|
|
2031
|
-
async
|
|
2032
|
-
if (this.runtime.character.settings?.discord?.allowedChannelIds && !this.runtime.character.settings.discord.allowedChannelIds.some(
|
|
2033
|
-
(id) => id === message.channel.id
|
|
2034
|
-
)) {
|
|
2035
|
-
return;
|
|
2036
|
-
}
|
|
2037
|
-
if (message.interaction || message.author.id === this.client.user?.id) {
|
|
2038
|
-
return;
|
|
2039
|
-
}
|
|
2040
|
-
if (this.runtime.character.settings?.discord?.shouldIgnoreBotMessages && message.author?.bot) {
|
|
2041
|
-
return;
|
|
2042
|
-
}
|
|
2043
|
-
if (this.runtime.character.settings?.discord?.shouldIgnoreDirectMessages && message.channel.type === DiscordChannelType2.DM) {
|
|
2044
|
-
return;
|
|
2045
|
-
}
|
|
2046
|
-
const entityId = createUniqueUuid4(this.runtime, message.author.id);
|
|
2047
|
-
const userName = message.author.bot ? `${message.author.username}#${message.author.discriminator}` : message.author.username;
|
|
2048
|
-
const name = message.author.displayName;
|
|
2049
|
-
const channelId = message.channel.id;
|
|
2050
|
-
const roomId = createUniqueUuid4(this.runtime, channelId);
|
|
2051
|
-
let type;
|
|
2052
|
-
let serverId;
|
|
2053
|
-
if (message.guild) {
|
|
2054
|
-
const guild = await message.guild.fetch();
|
|
2055
|
-
type = await this.getChannelType(message.channel);
|
|
2056
|
-
serverId = guild.id;
|
|
2057
|
-
} else {
|
|
2058
|
-
type = ChannelType5.DM;
|
|
2059
|
-
serverId = void 0;
|
|
2060
|
-
}
|
|
2061
|
-
await this.runtime.ensureConnection({
|
|
2062
|
-
entityId,
|
|
2063
|
-
roomId,
|
|
2064
|
-
userName,
|
|
2065
|
-
name,
|
|
2066
|
-
source: "discord",
|
|
2067
|
-
channelId: message.channel.id,
|
|
2068
|
-
serverId,
|
|
2069
|
-
type
|
|
2070
|
-
});
|
|
2065
|
+
async sendMessageToChannel(channel, messageContent, files) {
|
|
2071
2066
|
try {
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
`Cannot send message to channel ${message.channel}`,
|
|
2076
|
-
canSendResult
|
|
2067
|
+
if (!channel || !channel.isTextBased()) {
|
|
2068
|
+
throw new Error(
|
|
2069
|
+
"Channel is not a text-based channel or does not exist."
|
|
2077
2070
|
);
|
|
2078
2071
|
}
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2072
|
+
await sendMessageInChunks(
|
|
2073
|
+
channel,
|
|
2074
|
+
messageContent,
|
|
2075
|
+
null,
|
|
2076
|
+
files
|
|
2082
2077
|
);
|
|
2083
|
-
if (audioAttachments.size > 0) {
|
|
2084
|
-
const processedAudioAttachments = await this.attachmentManager.processAttachments(audioAttachments);
|
|
2085
|
-
attachments.push(...processedAudioAttachments);
|
|
2086
|
-
}
|
|
2087
|
-
if (!processedContent && !attachments?.length) {
|
|
2088
|
-
return;
|
|
2089
|
-
}
|
|
2090
|
-
const entityId2 = createUniqueUuid4(this.runtime, message.author.id);
|
|
2091
|
-
const messageId = createUniqueUuid4(this.runtime, message.id);
|
|
2092
|
-
const newMessage = {
|
|
2093
|
-
id: messageId,
|
|
2094
|
-
entityId: entityId2,
|
|
2095
|
-
agentId: this.runtime.agentId,
|
|
2096
|
-
roomId,
|
|
2097
|
-
content: {
|
|
2098
|
-
// name: name,
|
|
2099
|
-
// userName: userName,
|
|
2100
|
-
text: processedContent || " ",
|
|
2101
|
-
attachments,
|
|
2102
|
-
source: "discord",
|
|
2103
|
-
url: message.url,
|
|
2104
|
-
inReplyTo: message.reference?.messageId ? createUniqueUuid4(this.runtime, message.reference?.messageId) : void 0
|
|
2105
|
-
},
|
|
2106
|
-
createdAt: message.createdTimestamp
|
|
2107
|
-
};
|
|
2108
|
-
const callback = async (content, files) => {
|
|
2109
|
-
try {
|
|
2110
|
-
if (message.id && !content.inReplyTo) {
|
|
2111
|
-
content.inReplyTo = createUniqueUuid4(this.runtime, message.id);
|
|
2112
|
-
}
|
|
2113
|
-
const messages = await sendMessageInChunks(
|
|
2114
|
-
message.channel,
|
|
2115
|
-
content.text,
|
|
2116
|
-
message.id,
|
|
2117
|
-
files
|
|
2118
|
-
);
|
|
2119
|
-
const memories = [];
|
|
2120
|
-
for (const m of messages) {
|
|
2121
|
-
const actions = content.actions;
|
|
2122
|
-
const memory = {
|
|
2123
|
-
id: createUniqueUuid4(this.runtime, m.id),
|
|
2124
|
-
entityId: this.runtime.agentId,
|
|
2125
|
-
agentId: this.runtime.agentId,
|
|
2126
|
-
content: {
|
|
2127
|
-
...content,
|
|
2128
|
-
actions,
|
|
2129
|
-
inReplyTo: messageId,
|
|
2130
|
-
url: m.url,
|
|
2131
|
-
channelType: type
|
|
2132
|
-
},
|
|
2133
|
-
roomId,
|
|
2134
|
-
createdAt: m.createdTimestamp
|
|
2135
|
-
};
|
|
2136
|
-
memories.push(memory);
|
|
2137
|
-
}
|
|
2138
|
-
for (const m of memories) {
|
|
2139
|
-
await this.runtime.getMemoryManager("messages").createMemory(m);
|
|
2140
|
-
}
|
|
2141
|
-
return memories;
|
|
2142
|
-
} catch (error) {
|
|
2143
|
-
console.error("Error sending message:", error);
|
|
2144
|
-
return [];
|
|
2145
|
-
}
|
|
2146
|
-
};
|
|
2147
|
-
this.runtime.emitEvent(["DISCORD_MESSAGE_RECEIVED" /* MESSAGE_RECEIVED */, EventTypes.MESSAGE_RECEIVED], {
|
|
2148
|
-
runtime: this.runtime,
|
|
2149
|
-
message: newMessage,
|
|
2150
|
-
callback
|
|
2151
|
-
});
|
|
2152
2078
|
} catch (error) {
|
|
2153
|
-
|
|
2079
|
+
throw new Error(`Error sending message: ${error}`);
|
|
2154
2080
|
}
|
|
2155
2081
|
}
|
|
2156
2082
|
/**
|
|
2157
|
-
*
|
|
2158
|
-
* processed content and media attachments.
|
|
2083
|
+
* Play an audio stream from a given response stream using the provided VoiceConnection.
|
|
2159
2084
|
*
|
|
2160
|
-
* @param {
|
|
2161
|
-
* @
|
|
2085
|
+
* @param {any} responseStream - The response stream to play as audio.
|
|
2086
|
+
* @param {VoiceConnection} connection - The VoiceConnection to use for playing the audio.
|
|
2087
|
+
* @returns {Promise<void>} - A Promise that resolves when the TTS playback is finished.
|
|
2162
2088
|
*/
|
|
2163
|
-
async
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
processedContent = processedContent.replace(
|
|
2168
|
-
mentionRegex,
|
|
2169
|
-
(match2, entityId) => {
|
|
2170
|
-
const user = message.mentions.users.get(entityId);
|
|
2171
|
-
if (user) {
|
|
2172
|
-
return `${user.username} (@${entityId})`;
|
|
2173
|
-
}
|
|
2174
|
-
return match2;
|
|
2175
|
-
}
|
|
2176
|
-
);
|
|
2177
|
-
const codeBlockRegex = /```([\s\S]*?)```/g;
|
|
2178
|
-
let match;
|
|
2179
|
-
while (match = codeBlockRegex.exec(processedContent)) {
|
|
2180
|
-
const codeBlock = match[1];
|
|
2181
|
-
const lines = codeBlock.split("\n");
|
|
2182
|
-
const title = lines[0];
|
|
2183
|
-
const description = lines.slice(0, 3).join("\n");
|
|
2184
|
-
const attachmentId = `code-${Date.now()}-${Math.floor(
|
|
2185
|
-
Math.random() * 1e3
|
|
2186
|
-
)}`.slice(-5);
|
|
2187
|
-
attachments.push({
|
|
2188
|
-
id: attachmentId,
|
|
2189
|
-
url: "",
|
|
2190
|
-
title: title || "Code Block",
|
|
2191
|
-
source: "Code",
|
|
2192
|
-
description,
|
|
2193
|
-
text: codeBlock
|
|
2194
|
-
});
|
|
2195
|
-
processedContent = processedContent.replace(
|
|
2196
|
-
match[0],
|
|
2197
|
-
`Code Block (${attachmentId})`
|
|
2198
|
-
);
|
|
2199
|
-
}
|
|
2200
|
-
if (message.attachments.size > 0) {
|
|
2201
|
-
attachments = await this.attachmentManager.processAttachments(
|
|
2202
|
-
message.attachments
|
|
2203
|
-
);
|
|
2204
|
-
}
|
|
2205
|
-
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
|
2206
|
-
const urls = processedContent.match(urlRegex) || [];
|
|
2207
|
-
for (const url of urls) {
|
|
2208
|
-
if (this.runtime.getService(ServiceTypes4.VIDEO)?.isVideoUrl(url)) {
|
|
2209
|
-
const videoService = this.runtime.getService(
|
|
2210
|
-
ServiceTypes4.VIDEO
|
|
2211
|
-
);
|
|
2212
|
-
if (!videoService) {
|
|
2213
|
-
throw new Error("Video service not found");
|
|
2214
|
-
}
|
|
2215
|
-
const videoInfo = await videoService.processVideo(url, this.runtime);
|
|
2216
|
-
attachments.push({
|
|
2217
|
-
id: `youtube-${Date.now()}`,
|
|
2218
|
-
url,
|
|
2219
|
-
title: videoInfo.title,
|
|
2220
|
-
source: "YouTube",
|
|
2221
|
-
description: videoInfo.description,
|
|
2222
|
-
text: videoInfo.text
|
|
2223
|
-
});
|
|
2224
|
-
} else {
|
|
2225
|
-
const browserService = this.runtime.getService(
|
|
2226
|
-
ServiceTypes4.BROWSER
|
|
2227
|
-
);
|
|
2228
|
-
if (!browserService) {
|
|
2229
|
-
throw new Error("Browser service not found");
|
|
2230
|
-
}
|
|
2231
|
-
const { title, description: summary } = await browserService.getPageContent(url, this.runtime);
|
|
2232
|
-
attachments.push({
|
|
2233
|
-
id: `webpage-${Date.now()}`,
|
|
2234
|
-
url,
|
|
2235
|
-
title: title || "Web Page",
|
|
2236
|
-
source: "Web",
|
|
2237
|
-
description: summary,
|
|
2238
|
-
text: summary
|
|
2239
|
-
});
|
|
2089
|
+
async playAudioStream(responseStream, connection) {
|
|
2090
|
+
const audioPlayer = createAudioPlayer({
|
|
2091
|
+
behaviors: {
|
|
2092
|
+
noSubscriber: NoSubscriberBehavior.Pause
|
|
2240
2093
|
}
|
|
2241
|
-
}
|
|
2242
|
-
|
|
2094
|
+
});
|
|
2095
|
+
const audioResource = createAudioResource(responseStream);
|
|
2096
|
+
audioPlayer.play(audioResource);
|
|
2097
|
+
connection.subscribe(audioPlayer);
|
|
2098
|
+
logger4.success("TTS playback started successfully.");
|
|
2099
|
+
await new Promise((resolve, reject) => {
|
|
2100
|
+
audioPlayer.once(AudioPlayerStatus.Idle, () => {
|
|
2101
|
+
logger4.info("TTS playback finished.");
|
|
2102
|
+
resolve();
|
|
2103
|
+
});
|
|
2104
|
+
audioPlayer.once("error", (error) => {
|
|
2105
|
+
reject(error);
|
|
2106
|
+
throw new Error(`TTS playback error: ${error}`);
|
|
2107
|
+
});
|
|
2108
|
+
});
|
|
2243
2109
|
}
|
|
2244
2110
|
/**
|
|
2245
|
-
*
|
|
2111
|
+
* Retrieves the active guild where the bot is currently connected to a voice channel.
|
|
2246
2112
|
*
|
|
2247
|
-
* @param {
|
|
2248
|
-
* @returns {Promise<
|
|
2249
|
-
* @throws {Error} If
|
|
2113
|
+
* @param {DiscordService} discordClient The DiscordService instance used to interact with the Discord API.
|
|
2114
|
+
* @returns {Promise<Guild>} The active guild where the bot is currently connected to a voice channel.
|
|
2115
|
+
* @throws {Error} If no active voice connection is found for the bot.
|
|
2250
2116
|
*/
|
|
2251
|
-
async
|
|
2252
|
-
const
|
|
2253
|
-
const
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
}
|
|
2258
|
-
});
|
|
2259
|
-
if (!response.ok) {
|
|
2260
|
-
throw new Error(`Error fetching bot details: ${response.statusText}`);
|
|
2117
|
+
async getActiveGuild(discordClient) {
|
|
2118
|
+
const guilds = await discordClient.client.guilds.fetch();
|
|
2119
|
+
const fullGuilds = await Promise.all(guilds.map((guild) => guild.fetch()));
|
|
2120
|
+
const activeGuild = fullGuilds.find((g) => g.members.me?.voice.channelId);
|
|
2121
|
+
if (!activeGuild) {
|
|
2122
|
+
throw new Error("No active voice connection found for the bot.");
|
|
2261
2123
|
}
|
|
2262
|
-
|
|
2263
|
-
const discriminator = data.discriminator;
|
|
2264
|
-
return data.username + (discriminator ? `#${discriminator}` : "");
|
|
2124
|
+
return activeGuild;
|
|
2265
2125
|
}
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
if (!
|
|
2275
|
-
throw new Error("
|
|
2126
|
+
/**
|
|
2127
|
+
* Waits for the VoiceManager in the Discord client to be ready.
|
|
2128
|
+
*
|
|
2129
|
+
* @param {DiscordService} discordClient - The Discord client to check for VoiceManager readiness.
|
|
2130
|
+
* @throws {Error} If the Discord client is not initialized.
|
|
2131
|
+
* @returns {Promise<void>} A promise that resolves when the VoiceManager is ready.
|
|
2132
|
+
*/
|
|
2133
|
+
async waitForVoiceManagerReady(discordClient) {
|
|
2134
|
+
if (!discordClient) {
|
|
2135
|
+
throw new Error("Discord client is not initialized.");
|
|
2276
2136
|
}
|
|
2277
|
-
if (
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
};
|
|
2137
|
+
if (!discordClient.voiceManager.isReady()) {
|
|
2138
|
+
await new Promise((resolve, reject) => {
|
|
2139
|
+
discordClient.voiceManager.once("ready", resolve);
|
|
2140
|
+
discordClient.voiceManager.once("error", reject);
|
|
2141
|
+
});
|
|
2283
2142
|
}
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
console.error("No server ID found");
|
|
2298
|
-
return {
|
|
2299
|
-
data: {
|
|
2300
|
-
room,
|
|
2301
|
-
channelType
|
|
2302
|
-
},
|
|
2303
|
-
values: {
|
|
2304
|
-
channelType
|
|
2305
|
-
},
|
|
2306
|
-
text: ""
|
|
2307
|
-
};
|
|
2308
|
-
}
|
|
2309
|
-
channelId = room.channelId;
|
|
2310
|
-
const discordService = runtime.getService(
|
|
2311
|
-
ServiceTypes2.DISCORD
|
|
2143
|
+
}
|
|
2144
|
+
/**
|
|
2145
|
+
* Validates the Discord test channel ID by checking if it is set in the runtime or environment variables.
|
|
2146
|
+
* If the test channel ID is not set, an error is thrown.
|
|
2147
|
+
*
|
|
2148
|
+
* @param {IAgentRuntime} runtime The runtime object containing the settings and environment variables.
|
|
2149
|
+
* @returns {string} The validated Discord test channel ID.
|
|
2150
|
+
*/
|
|
2151
|
+
validateChannelId(runtime) {
|
|
2152
|
+
const testChannelId = runtime.getSetting("DISCORD_TEST_CHANNEL_ID") || process.env.DISCORD_TEST_CHANNEL_ID;
|
|
2153
|
+
if (!testChannelId) {
|
|
2154
|
+
throw new Error(
|
|
2155
|
+
"DISCORD_TEST_CHANNEL_ID is not set. Please provide a valid channel ID in the environment variables."
|
|
2312
2156
|
);
|
|
2313
|
-
if (!discordService) {
|
|
2314
|
-
console.warn("No discord client found");
|
|
2315
|
-
return {
|
|
2316
|
-
data: {
|
|
2317
|
-
room,
|
|
2318
|
-
channelType,
|
|
2319
|
-
serverId
|
|
2320
|
-
},
|
|
2321
|
-
values: {
|
|
2322
|
-
channelType,
|
|
2323
|
-
serverId
|
|
2324
|
-
},
|
|
2325
|
-
text: ""
|
|
2326
|
-
};
|
|
2327
|
-
}
|
|
2328
|
-
const guild = discordService.client.guilds.cache.get(serverId);
|
|
2329
|
-
serverName = guild.name;
|
|
2330
|
-
responseText = `${agentName} is currently having a conversation in the channel \`@${channelId} in the server \`${serverName}\` (@${serverId})`;
|
|
2331
|
-
responseText += `
|
|
2332
|
-
${agentName} is in a room with other users and should be self-conscious and only participate when directly addressed or when the conversation is relevant to them.`;
|
|
2333
2157
|
}
|
|
2334
|
-
return
|
|
2335
|
-
data: {
|
|
2336
|
-
room,
|
|
2337
|
-
channelType,
|
|
2338
|
-
serverId,
|
|
2339
|
-
serverName,
|
|
2340
|
-
channelId
|
|
2341
|
-
},
|
|
2342
|
-
values: {
|
|
2343
|
-
channelType,
|
|
2344
|
-
serverName,
|
|
2345
|
-
channelId
|
|
2346
|
-
},
|
|
2347
|
-
text: responseText
|
|
2348
|
-
};
|
|
2158
|
+
return testChannelId;
|
|
2349
2159
|
}
|
|
2350
2160
|
};
|
|
2351
|
-
var channelState_default = channelStateProvider;
|
|
2352
2161
|
|
|
2353
|
-
// src/
|
|
2354
|
-
import {
|
|
2355
|
-
import { ChannelType as
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
},
|
|
2369
|
-
values: {
|
|
2370
|
-
isInVoiceChannel: "false",
|
|
2371
|
-
roomType: room.type
|
|
2372
|
-
},
|
|
2373
|
-
text: ""
|
|
2374
|
-
};
|
|
2375
|
-
}
|
|
2376
|
-
const serverId = room.serverId;
|
|
2377
|
-
if (!serverId) {
|
|
2378
|
-
throw new Error("No server ID found 10");
|
|
2379
|
-
}
|
|
2380
|
-
const connection = getVoiceConnection(serverId);
|
|
2381
|
-
const agentName = state?.agentName || "The agent";
|
|
2382
|
-
if (!connection) {
|
|
2383
|
-
return {
|
|
2384
|
-
data: {
|
|
2385
|
-
isInVoiceChannel: false,
|
|
2386
|
-
room,
|
|
2387
|
-
serverId
|
|
2388
|
-
},
|
|
2389
|
-
values: {
|
|
2390
|
-
isInVoiceChannel: "false",
|
|
2391
|
-
serverId
|
|
2392
|
-
},
|
|
2393
|
-
text: `${agentName} is not currently in a voice channel`
|
|
2394
|
-
};
|
|
2395
|
-
}
|
|
2396
|
-
const worldId = room.worldId;
|
|
2397
|
-
const world = await runtime.getWorld(worldId);
|
|
2398
|
-
if (!world) {
|
|
2399
|
-
throw new Error("No world found");
|
|
2400
|
-
}
|
|
2401
|
-
const worldName = world.name;
|
|
2402
|
-
const roomType = room.type;
|
|
2403
|
-
const channelId = room.channelId;
|
|
2404
|
-
const channelName = room.name;
|
|
2405
|
-
if (!channelId) {
|
|
2406
|
-
return {
|
|
2407
|
-
data: {
|
|
2408
|
-
isInVoiceChannel: true,
|
|
2409
|
-
room,
|
|
2410
|
-
serverId,
|
|
2411
|
-
world,
|
|
2412
|
-
connection
|
|
2413
|
-
},
|
|
2414
|
-
values: {
|
|
2415
|
-
isInVoiceChannel: "true",
|
|
2416
|
-
serverId,
|
|
2417
|
-
worldName,
|
|
2418
|
-
roomType
|
|
2419
|
-
},
|
|
2420
|
-
text: `${agentName} is in an invalid voice channel`
|
|
2421
|
-
};
|
|
2422
|
-
}
|
|
2423
|
-
return {
|
|
2424
|
-
data: {
|
|
2425
|
-
isInVoiceChannel: true,
|
|
2426
|
-
room,
|
|
2427
|
-
serverId,
|
|
2428
|
-
world,
|
|
2429
|
-
connection,
|
|
2430
|
-
channelId,
|
|
2431
|
-
channelName
|
|
2432
|
-
},
|
|
2433
|
-
values: {
|
|
2434
|
-
isInVoiceChannel: "true",
|
|
2435
|
-
serverId,
|
|
2436
|
-
worldName,
|
|
2437
|
-
roomType,
|
|
2438
|
-
channelId,
|
|
2439
|
-
channelName
|
|
2440
|
-
},
|
|
2441
|
-
text: `${agentName} is currently in the voice channel: ${channelName} (ID: ${channelId})`
|
|
2442
|
-
};
|
|
2443
|
-
}
|
|
2162
|
+
// src/service.ts
|
|
2163
|
+
import { ChannelType as ChannelType10, EventTypes as EventTypes2, Role, Service, createUniqueUuid as createUniqueUuid6, logger as logger7 } from "@elizaos/core";
|
|
2164
|
+
import { ChannelType as DiscordChannelType4, Client as DiscordJsClient, Events as Events2, GatewayIntentBits, Partials, PermissionsBitField as PermissionsBitField2 } from "discord.js";
|
|
2165
|
+
|
|
2166
|
+
// src/constants.ts
|
|
2167
|
+
var MESSAGE_CONSTANTS = {
|
|
2168
|
+
MAX_MESSAGES: 10,
|
|
2169
|
+
RECENT_MESSAGE_COUNT: 3,
|
|
2170
|
+
CHAT_HISTORY_COUNT: 5,
|
|
2171
|
+
INTEREST_DECAY_TIME: 5 * 60 * 1e3,
|
|
2172
|
+
// 5 minutes
|
|
2173
|
+
PARTIAL_INTEREST_DECAY: 3 * 60 * 1e3,
|
|
2174
|
+
// 3 minutes
|
|
2175
|
+
DEFAULT_SIMILARITY_THRESHOLD: 0.3,
|
|
2176
|
+
DEFAULT_SIMILARITY_THRESHOLD_FOLLOW_UPS: 0.2
|
|
2444
2177
|
};
|
|
2445
|
-
var
|
|
2178
|
+
var DISCORD_SERVICE_NAME = "discord";
|
|
2446
2179
|
|
|
2447
|
-
// src/
|
|
2180
|
+
// src/messages.ts
|
|
2448
2181
|
import {
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2182
|
+
ChannelType as ChannelType8,
|
|
2183
|
+
EventTypes,
|
|
2184
|
+
ServiceTypes as ServiceTypes4,
|
|
2185
|
+
createUniqueUuid as createUniqueUuid4,
|
|
2186
|
+
logger as logger5
|
|
2187
|
+
} from "@elizaos/core";
|
|
2188
|
+
import {
|
|
2189
|
+
ChannelType as DiscordChannelType2
|
|
2190
|
+
} from "discord.js";
|
|
2191
|
+
|
|
2192
|
+
// src/attachments.ts
|
|
2193
|
+
import fs3 from "node:fs";
|
|
2194
|
+
import { trimTokens as trimTokens4 } from "@elizaos/core";
|
|
2195
|
+
import { parseJSONObjectFromText as parseJSONObjectFromText6 } from "@elizaos/core";
|
|
2456
2196
|
import {
|
|
2457
2197
|
ModelTypes as ModelTypes8,
|
|
2458
|
-
|
|
2198
|
+
ServiceTypes as ServiceTypes3
|
|
2459
2199
|
} from "@elizaos/core";
|
|
2460
|
-
import {
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2200
|
+
import { Collection } from "discord.js";
|
|
2201
|
+
import ffmpeg from "fluent-ffmpeg";
|
|
2202
|
+
async function generateSummary(runtime, text) {
|
|
2203
|
+
text = await trimTokens4(text, 1e5, runtime);
|
|
2204
|
+
const prompt = `Please generate a concise summary for the following text:
|
|
2205
|
+
|
|
2206
|
+
Text: """
|
|
2207
|
+
${text}
|
|
2208
|
+
"""
|
|
2209
|
+
|
|
2210
|
+
Respond with a JSON object in the following format:
|
|
2211
|
+
\`\`\`json
|
|
2212
|
+
{
|
|
2213
|
+
"title": "Generated Title",
|
|
2214
|
+
"summary": "Generated summary and/or description of the text"
|
|
2215
|
+
}
|
|
2216
|
+
\`\`\``;
|
|
2217
|
+
const response = await runtime.useModel(ModelTypes8.TEXT_SMALL, {
|
|
2218
|
+
prompt
|
|
2219
|
+
});
|
|
2220
|
+
const parsedResponse = parseJSONObjectFromText6(response);
|
|
2221
|
+
if (parsedResponse?.title && parsedResponse?.summary) {
|
|
2222
|
+
return {
|
|
2223
|
+
title: parsedResponse.title,
|
|
2224
|
+
description: parsedResponse.summary
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
return {
|
|
2228
|
+
title: "",
|
|
2229
|
+
description: ""
|
|
2230
|
+
};
|
|
2231
|
+
}
|
|
2232
|
+
var AttachmentManager = class {
|
|
2233
|
+
attachmentCache = /* @__PURE__ */ new Map();
|
|
2234
|
+
runtime;
|
|
2466
2235
|
/**
|
|
2467
|
-
* Constructor for
|
|
2236
|
+
* Constructor for creating a new instance of the class.
|
|
2468
2237
|
*
|
|
2469
|
-
* @
|
|
2470
|
-
* @this {TestSuite}
|
|
2238
|
+
* @param {IAgentRuntime} runtime The runtime object to be injected into the instance.
|
|
2471
2239
|
*/
|
|
2472
|
-
constructor() {
|
|
2473
|
-
this.
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
name: "Send Message with Attachments",
|
|
2488
|
-
fn: this.testSendingTextMessage.bind(this)
|
|
2489
|
-
},
|
|
2490
|
-
{
|
|
2491
|
-
name: "Handle Incoming Messages",
|
|
2492
|
-
fn: this.testHandlingMessage.bind(this)
|
|
2493
|
-
},
|
|
2494
|
-
{
|
|
2495
|
-
name: "Slash Commands - Leave Voice",
|
|
2496
|
-
fn: this.testLeaveVoiceSlashCommand.bind(this)
|
|
2240
|
+
constructor(runtime) {
|
|
2241
|
+
this.runtime = runtime;
|
|
2242
|
+
}
|
|
2243
|
+
/**
|
|
2244
|
+
* Processes attachments and returns an array of Media objects.
|
|
2245
|
+
* @param {Collection<string, Attachment> | Attachment[]} attachments - The attachments to be processed
|
|
2246
|
+
* @returns {Promise<Media[]>} - An array of processed Media objects
|
|
2247
|
+
*/
|
|
2248
|
+
async processAttachments(attachments) {
|
|
2249
|
+
const processedAttachments = [];
|
|
2250
|
+
const attachmentCollection = attachments instanceof Collection ? attachments : new Collection(attachments.map((att) => [att.id, att]));
|
|
2251
|
+
for (const [, attachment] of attachmentCollection) {
|
|
2252
|
+
const media = await this.processAttachment(attachment);
|
|
2253
|
+
if (media) {
|
|
2254
|
+
processedAttachments.push(media);
|
|
2497
2255
|
}
|
|
2498
|
-
|
|
2256
|
+
}
|
|
2257
|
+
return processedAttachments;
|
|
2499
2258
|
}
|
|
2500
2259
|
/**
|
|
2501
|
-
*
|
|
2260
|
+
* Processes the provided attachment to generate a media object.
|
|
2261
|
+
* If the media for the attachment URL is already cached, it will return the cached media.
|
|
2262
|
+
* Otherwise, it will determine the type of attachment (PDF, text, audio, video, image, generic)
|
|
2263
|
+
* and call the corresponding processing method to generate the media object.
|
|
2502
2264
|
*
|
|
2503
|
-
* @param
|
|
2504
|
-
* @returns
|
|
2505
|
-
* @throws {Error} - If an error occurs while creating the Discord client.
|
|
2265
|
+
* @param attachment The attachment to process
|
|
2266
|
+
* @returns A promise that resolves to a Media object representing the attachment, or null if the attachment could not be processed
|
|
2506
2267
|
*/
|
|
2507
|
-
async
|
|
2268
|
+
async processAttachment(attachment) {
|
|
2269
|
+
if (this.attachmentCache.has(attachment.url)) {
|
|
2270
|
+
return this.attachmentCache.get(attachment.url);
|
|
2271
|
+
}
|
|
2272
|
+
let media = null;
|
|
2273
|
+
if (attachment.contentType?.startsWith("application/pdf")) {
|
|
2274
|
+
media = await this.processPdfAttachment(attachment);
|
|
2275
|
+
} else if (attachment.contentType?.startsWith("text/plain")) {
|
|
2276
|
+
media = await this.processPlaintextAttachment(attachment);
|
|
2277
|
+
} else if (attachment.contentType?.startsWith("audio/") || attachment.contentType?.startsWith("video/mp4")) {
|
|
2278
|
+
media = await this.processAudioVideoAttachment(attachment);
|
|
2279
|
+
} else if (attachment.contentType?.startsWith("image/")) {
|
|
2280
|
+
media = await this.processImageAttachment(attachment);
|
|
2281
|
+
} else if (attachment.contentType?.startsWith("video/") || this.runtime.getService(ServiceTypes3.VIDEO).isVideoUrl(attachment.url)) {
|
|
2282
|
+
media = await this.processVideoAttachment(attachment);
|
|
2283
|
+
} else {
|
|
2284
|
+
media = await this.processGenericAttachment(attachment);
|
|
2285
|
+
}
|
|
2286
|
+
if (media) {
|
|
2287
|
+
this.attachmentCache.set(attachment.url, media);
|
|
2288
|
+
}
|
|
2289
|
+
return media;
|
|
2290
|
+
}
|
|
2291
|
+
/**
|
|
2292
|
+
* Asynchronously processes an audio or video attachment provided as input and returns a Media object.
|
|
2293
|
+
* @param {Attachment} attachment - The attachment object containing information about the audio/video file.
|
|
2294
|
+
* @returns {Promise<Media>} A Promise that resolves to a Media object representing the processed audio/video attachment.
|
|
2295
|
+
*/
|
|
2296
|
+
async processAudioVideoAttachment(attachment) {
|
|
2508
2297
|
try {
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
if (
|
|
2513
|
-
|
|
2298
|
+
const response = await fetch(attachment.url);
|
|
2299
|
+
const audioVideoArrayBuffer = await response.arrayBuffer();
|
|
2300
|
+
let audioBuffer;
|
|
2301
|
+
if (attachment.contentType?.startsWith("audio/")) {
|
|
2302
|
+
audioBuffer = Buffer.from(audioVideoArrayBuffer);
|
|
2303
|
+
} else if (attachment.contentType?.startsWith("video/mp4")) {
|
|
2304
|
+
audioBuffer = await this.extractAudioFromMP4(audioVideoArrayBuffer);
|
|
2514
2305
|
} else {
|
|
2515
|
-
|
|
2516
|
-
await new Promise((resolve, reject) => {
|
|
2517
|
-
this.discordClient.client.once(Events.ClientReady, resolve);
|
|
2518
|
-
this.discordClient.client.once(Events.Error, reject);
|
|
2519
|
-
});
|
|
2306
|
+
throw new Error("Unsupported audio/video format");
|
|
2520
2307
|
}
|
|
2308
|
+
const transcription = await this.runtime.useModel(
|
|
2309
|
+
ModelTypes8.TRANSCRIPTION,
|
|
2310
|
+
audioBuffer
|
|
2311
|
+
);
|
|
2312
|
+
const { title, description } = await generateSummary(
|
|
2313
|
+
this.runtime,
|
|
2314
|
+
transcription
|
|
2315
|
+
);
|
|
2316
|
+
return {
|
|
2317
|
+
id: attachment.id,
|
|
2318
|
+
url: attachment.url,
|
|
2319
|
+
title: title || "Audio/Video Attachment",
|
|
2320
|
+
source: attachment.contentType?.startsWith("audio/") ? "Audio" : "Video",
|
|
2321
|
+
description: description || "User-uploaded audio/video attachment which has been transcribed",
|
|
2322
|
+
text: transcription || "Audio/video content not available"
|
|
2323
|
+
};
|
|
2521
2324
|
} catch (error) {
|
|
2522
|
-
|
|
2325
|
+
console.error(
|
|
2326
|
+
`Error processing audio/video attachment: ${error.message}`
|
|
2327
|
+
);
|
|
2328
|
+
return {
|
|
2329
|
+
id: attachment.id,
|
|
2330
|
+
url: attachment.url,
|
|
2331
|
+
title: "Audio/Video Attachment",
|
|
2332
|
+
source: attachment.contentType?.startsWith("audio/") ? "Audio" : "Video",
|
|
2333
|
+
description: "An audio/video attachment (transcription failed)",
|
|
2334
|
+
text: `This is an audio/video attachment. File name: ${attachment.name}, Size: ${attachment.size} bytes, Content type: ${attachment.contentType}`
|
|
2335
|
+
};
|
|
2523
2336
|
}
|
|
2524
2337
|
}
|
|
2525
2338
|
/**
|
|
2526
|
-
*
|
|
2339
|
+
* Extracts the audio stream from the provided MP4 data and converts it to MP3 format.
|
|
2527
2340
|
*
|
|
2528
|
-
* @param {
|
|
2529
|
-
* @returns {Promise<
|
|
2530
|
-
* @throws {Error} - If there is an error in executing the slash command test.
|
|
2341
|
+
* @param {ArrayBuffer} mp4Data - The MP4 data to extract audio from
|
|
2342
|
+
* @returns {Promise<Buffer>} - A Promise that resolves with the converted audio data as a Buffer
|
|
2531
2343
|
*/
|
|
2532
|
-
async
|
|
2344
|
+
async extractAudioFromMP4(mp4Data) {
|
|
2345
|
+
const tempMP4File = `temp_${Date.now()}.mp4`;
|
|
2346
|
+
const tempAudioFile = `temp_${Date.now()}.mp3`;
|
|
2533
2347
|
try {
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2348
|
+
fs3.writeFileSync(tempMP4File, Buffer.from(mp4Data));
|
|
2349
|
+
await new Promise((resolve, reject) => {
|
|
2350
|
+
ffmpeg(tempMP4File).outputOptions("-vn").audioCodec("libmp3lame").save(tempAudioFile).on("end", () => {
|
|
2351
|
+
resolve();
|
|
2352
|
+
}).on("error", (err) => {
|
|
2353
|
+
reject(err);
|
|
2354
|
+
}).run();
|
|
2355
|
+
});
|
|
2356
|
+
const audioData = fs3.readFileSync(tempAudioFile);
|
|
2357
|
+
return audioData;
|
|
2358
|
+
} finally {
|
|
2359
|
+
if (fs3.existsSync(tempMP4File)) {
|
|
2360
|
+
fs3.unlinkSync(tempMP4File);
|
|
2361
|
+
}
|
|
2362
|
+
if (fs3.existsSync(tempAudioFile)) {
|
|
2363
|
+
fs3.unlinkSync(tempAudioFile);
|
|
2538
2364
|
}
|
|
2539
|
-
const fakeJoinInteraction = {
|
|
2540
|
-
isCommand: () => true,
|
|
2541
|
-
commandName: "joinchannel",
|
|
2542
|
-
options: {
|
|
2543
|
-
get: (name) => name === "channel" ? { value: channel.id } : null
|
|
2544
|
-
},
|
|
2545
|
-
guild: channel.guild,
|
|
2546
|
-
deferReply: async () => {
|
|
2547
|
-
},
|
|
2548
|
-
editReply: async (message) => {
|
|
2549
|
-
logger5.info(`JoinChannel Slash Command Response: ${message}`);
|
|
2550
|
-
}
|
|
2551
|
-
};
|
|
2552
|
-
await this.discordClient.voiceManager.handleJoinChannelCommand(
|
|
2553
|
-
fakeJoinInteraction
|
|
2554
|
-
);
|
|
2555
|
-
logger5.success("Slash command test completed successfully.");
|
|
2556
|
-
} catch (error) {
|
|
2557
|
-
throw new Error(`Error in slash commands test: ${error}`);
|
|
2558
2365
|
}
|
|
2559
2366
|
}
|
|
2560
2367
|
/**
|
|
2561
|
-
*
|
|
2368
|
+
* Processes a PDF attachment by fetching the PDF file from the specified URL,
|
|
2369
|
+
* converting it to text, generating a summary, and returning a Media object
|
|
2370
|
+
* with the extracted information.
|
|
2371
|
+
* If an error occurs during processing, a placeholder Media object is returned
|
|
2372
|
+
* with an error message.
|
|
2562
2373
|
*
|
|
2563
|
-
* @param {
|
|
2564
|
-
* @returns {Promise<
|
|
2374
|
+
* @param {Attachment} attachment - The PDF attachment to process.
|
|
2375
|
+
* @returns {Promise<Media>} A promise that resolves to a Media object representing
|
|
2376
|
+
* the processed PDF attachment.
|
|
2565
2377
|
*/
|
|
2566
|
-
async
|
|
2378
|
+
async processPdfAttachment(attachment) {
|
|
2567
2379
|
try {
|
|
2568
|
-
await
|
|
2569
|
-
const
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
}
|
|
2380
|
+
const response = await fetch(attachment.url);
|
|
2381
|
+
const pdfBuffer = await response.arrayBuffer();
|
|
2382
|
+
const text = await this.runtime.getService(ServiceTypes3.PDF).convertPdfToText(Buffer.from(pdfBuffer));
|
|
2383
|
+
const { title, description } = await generateSummary(this.runtime, text);
|
|
2384
|
+
return {
|
|
2385
|
+
id: attachment.id,
|
|
2386
|
+
url: attachment.url,
|
|
2387
|
+
title: title || "PDF Attachment",
|
|
2388
|
+
source: "PDF",
|
|
2389
|
+
description: description || "A PDF document",
|
|
2390
|
+
text
|
|
2580
2391
|
};
|
|
2581
|
-
await this.discordClient.voiceManager.handleLeaveChannelCommand(
|
|
2582
|
-
fakeLeaveInteraction
|
|
2583
|
-
);
|
|
2584
|
-
logger5.success("Slash command test completed successfully.");
|
|
2585
2392
|
} catch (error) {
|
|
2586
|
-
|
|
2393
|
+
console.error(`Error processing PDF attachment: ${error.message}`);
|
|
2394
|
+
return {
|
|
2395
|
+
id: attachment.id,
|
|
2396
|
+
url: attachment.url,
|
|
2397
|
+
title: "PDF Attachment (conversion failed)",
|
|
2398
|
+
source: "PDF",
|
|
2399
|
+
description: "A PDF document that could not be converted to text",
|
|
2400
|
+
text: `This is a PDF attachment. File name: ${attachment.name}, Size: ${attachment.size} bytes`
|
|
2401
|
+
};
|
|
2587
2402
|
}
|
|
2588
2403
|
}
|
|
2589
2404
|
/**
|
|
2590
|
-
*
|
|
2591
|
-
* @param {
|
|
2592
|
-
* @
|
|
2405
|
+
* Processes a plaintext attachment by fetching its content, generating a summary, and returning a Media object.
|
|
2406
|
+
* @param {Attachment} attachment - The attachment object to process.
|
|
2407
|
+
* @returns {Promise<Media>} A promise that resolves to a Media object representing the processed plaintext attachment.
|
|
2593
2408
|
*/
|
|
2594
|
-
async
|
|
2409
|
+
async processPlaintextAttachment(attachment) {
|
|
2595
2410
|
try {
|
|
2596
|
-
await
|
|
2597
|
-
const
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
logger5.success(`Voice connection is ready in guild: ${guildId}`);
|
|
2608
|
-
} catch (error) {
|
|
2609
|
-
throw new Error(`Voice connection failed to become ready: ${error}`);
|
|
2610
|
-
}
|
|
2611
|
-
let responseStream = null;
|
|
2612
|
-
try {
|
|
2613
|
-
responseStream = await runtime.useModel(
|
|
2614
|
-
ModelTypes8.TEXT_TO_SPEECH,
|
|
2615
|
-
`Hi! I'm ${runtime.character.name}! How are you doing today?`
|
|
2616
|
-
);
|
|
2617
|
-
} catch (_error) {
|
|
2618
|
-
throw new Error("No text to speech service found");
|
|
2619
|
-
}
|
|
2620
|
-
if (!responseStream) {
|
|
2621
|
-
throw new Error("TTS response stream is null or undefined.");
|
|
2622
|
-
}
|
|
2623
|
-
await this.playAudioStream(responseStream, connection);
|
|
2411
|
+
const response = await fetch(attachment.url);
|
|
2412
|
+
const text = await response.text();
|
|
2413
|
+
const { title, description } = await generateSummary(this.runtime, text);
|
|
2414
|
+
return {
|
|
2415
|
+
id: attachment.id,
|
|
2416
|
+
url: attachment.url,
|
|
2417
|
+
title: title || "Plaintext Attachment",
|
|
2418
|
+
source: "Plaintext",
|
|
2419
|
+
description: description || "A plaintext document",
|
|
2420
|
+
text
|
|
2421
|
+
};
|
|
2624
2422
|
} catch (error) {
|
|
2625
|
-
|
|
2423
|
+
console.error(`Error processing plaintext attachment: ${error.message}`);
|
|
2424
|
+
return {
|
|
2425
|
+
id: attachment.id,
|
|
2426
|
+
url: attachment.url,
|
|
2427
|
+
title: "Plaintext Attachment (retrieval failed)",
|
|
2428
|
+
source: "Plaintext",
|
|
2429
|
+
description: "A plaintext document that could not be retrieved",
|
|
2430
|
+
text: `This is a plaintext attachment. File name: ${attachment.name}, Size: ${attachment.size} bytes`
|
|
2431
|
+
};
|
|
2626
2432
|
}
|
|
2627
2433
|
}
|
|
2628
2434
|
/**
|
|
2629
|
-
*
|
|
2435
|
+
* Process the image attachment by fetching description and title using the IMAGE_DESCRIPTION model.
|
|
2436
|
+
* If successful, returns a Media object populated with the details. If unsuccessful, creates a fallback
|
|
2437
|
+
* Media object and logs the error.
|
|
2630
2438
|
*
|
|
2631
|
-
* @param {
|
|
2632
|
-
* @returns {Promise<
|
|
2633
|
-
* @throws {Error} If there is an error in sending the text message.
|
|
2439
|
+
* @param {Attachment} attachment - The attachment object containing the image details.
|
|
2440
|
+
* @returns {Promise<Media>} A promise that resolves to a Media object.
|
|
2634
2441
|
*/
|
|
2635
|
-
async
|
|
2442
|
+
async processImageAttachment(attachment) {
|
|
2636
2443
|
try {
|
|
2637
|
-
const
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
"Testing Message",
|
|
2641
|
-
[TEST_IMAGE_URL]
|
|
2444
|
+
const { description, title } = await this.runtime.useModel(
|
|
2445
|
+
ModelTypes8.IMAGE_DESCRIPTION,
|
|
2446
|
+
attachment.url
|
|
2642
2447
|
);
|
|
2448
|
+
return {
|
|
2449
|
+
id: attachment.id,
|
|
2450
|
+
url: attachment.url,
|
|
2451
|
+
title: title || "Image Attachment",
|
|
2452
|
+
source: "Image",
|
|
2453
|
+
description: description || "An image attachment",
|
|
2454
|
+
text: description || "Image content not available"
|
|
2455
|
+
};
|
|
2643
2456
|
} catch (error) {
|
|
2644
|
-
|
|
2457
|
+
console.error(`Error processing image attachment: ${error.message}`);
|
|
2458
|
+
return this.createFallbackImageMedia(attachment);
|
|
2645
2459
|
}
|
|
2646
2460
|
}
|
|
2647
2461
|
/**
|
|
2648
|
-
*
|
|
2462
|
+
* Creates a fallback Media object for image attachments that could not be recognized.
|
|
2649
2463
|
*
|
|
2650
|
-
* @param {
|
|
2651
|
-
* @returns {
|
|
2464
|
+
* @param {Attachment} attachment - The attachment object containing image details.
|
|
2465
|
+
* @returns {Media} - The fallback Media object with basic information about the image attachment.
|
|
2652
2466
|
*/
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2467
|
+
createFallbackImageMedia(attachment) {
|
|
2468
|
+
return {
|
|
2469
|
+
id: attachment.id,
|
|
2470
|
+
url: attachment.url,
|
|
2471
|
+
title: "Image Attachment",
|
|
2472
|
+
source: "Image",
|
|
2473
|
+
description: "An image attachment (recognition failed)",
|
|
2474
|
+
text: `This is an image attachment. File name: ${attachment.name}, Size: ${attachment.size} bytes, Content type: ${attachment.contentType}`
|
|
2475
|
+
};
|
|
2476
|
+
}
|
|
2477
|
+
/**
|
|
2478
|
+
* Process a video attachment to extract video information.
|
|
2479
|
+
* @param {Attachment} attachment - The attachment object containing video information.
|
|
2480
|
+
* @returns {Promise<Media>} A promise that resolves to a Media object with video details.
|
|
2481
|
+
* @throws {Error} If video service is not available.
|
|
2482
|
+
*/
|
|
2483
|
+
async processVideoAttachment(attachment) {
|
|
2484
|
+
const videoService = this.runtime.getService(
|
|
2485
|
+
ServiceTypes3.VIDEO
|
|
2486
|
+
);
|
|
2487
|
+
if (!videoService) {
|
|
2488
|
+
throw new Error("Video service not found");
|
|
2489
|
+
}
|
|
2490
|
+
if (videoService.isVideoUrl(attachment.url)) {
|
|
2491
|
+
const videoInfo = await videoService.processVideo(
|
|
2492
|
+
attachment.url,
|
|
2493
|
+
this.runtime
|
|
2494
|
+
);
|
|
2495
|
+
return {
|
|
2496
|
+
id: attachment.id,
|
|
2497
|
+
url: attachment.url,
|
|
2498
|
+
title: videoInfo.title,
|
|
2499
|
+
source: "YouTube",
|
|
2500
|
+
description: videoInfo.description,
|
|
2501
|
+
text: videoInfo.text
|
|
2671
2502
|
};
|
|
2672
|
-
await this.discordClient.messageManager.handleMessage(fakeMessage);
|
|
2673
|
-
} catch (error) {
|
|
2674
|
-
throw new Error(`Error in sending text message: ${error}`);
|
|
2675
2503
|
}
|
|
2504
|
+
return {
|
|
2505
|
+
id: attachment.id,
|
|
2506
|
+
url: attachment.url,
|
|
2507
|
+
title: "Video Attachment",
|
|
2508
|
+
source: "Video",
|
|
2509
|
+
description: "A video attachment",
|
|
2510
|
+
text: "Video content not available"
|
|
2511
|
+
};
|
|
2676
2512
|
}
|
|
2677
|
-
// #############################
|
|
2678
|
-
// Utility Functions
|
|
2679
|
-
// #############################
|
|
2680
2513
|
/**
|
|
2681
|
-
*
|
|
2682
|
-
*
|
|
2683
|
-
* @
|
|
2684
|
-
* @returns {Promise<Channel>} The test channel retrieved from the Discord client.
|
|
2685
|
-
* @throws {Error} If no test channel is found.
|
|
2514
|
+
* Process a generic attachment and return a Media object with specified properties.
|
|
2515
|
+
* @param {Attachment} attachment - The attachment object to process.
|
|
2516
|
+
* @returns {Promise<Media>} A Promise that resolves to a Media object with specified properties.
|
|
2686
2517
|
*/
|
|
2687
|
-
async
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2518
|
+
async processGenericAttachment(attachment) {
|
|
2519
|
+
return {
|
|
2520
|
+
id: attachment.id,
|
|
2521
|
+
url: attachment.url,
|
|
2522
|
+
title: "Generic Attachment",
|
|
2523
|
+
source: "Generic",
|
|
2524
|
+
description: "A generic attachment",
|
|
2525
|
+
text: "Attachment content not available"
|
|
2526
|
+
};
|
|
2692
2527
|
}
|
|
2528
|
+
};
|
|
2529
|
+
|
|
2530
|
+
// src/messages.ts
|
|
2531
|
+
var MessageManager = class {
|
|
2532
|
+
client;
|
|
2533
|
+
runtime;
|
|
2534
|
+
attachmentManager;
|
|
2535
|
+
getChannelType;
|
|
2693
2536
|
/**
|
|
2694
|
-
*
|
|
2537
|
+
* Constructor for a new instance of MyClass.
|
|
2538
|
+
* @param {any} discordClient - The Discord client object.
|
|
2539
|
+
*/
|
|
2540
|
+
constructor(discordClient) {
|
|
2541
|
+
this.client = discordClient.client;
|
|
2542
|
+
this.runtime = discordClient.runtime;
|
|
2543
|
+
this.attachmentManager = new AttachmentManager(this.runtime);
|
|
2544
|
+
this.getChannelType = discordClient.getChannelType;
|
|
2545
|
+
}
|
|
2546
|
+
/**
|
|
2547
|
+
* Handles incoming Discord messages and processes them accordingly.
|
|
2695
2548
|
*
|
|
2696
|
-
* @param {
|
|
2697
|
-
* @param {string} messageContent - The content of the message being sent.
|
|
2698
|
-
* @param {any[]} files - An array of files to include in the message.
|
|
2699
|
-
* @throws {Error} If the channel is not a text-based channel or does not exist.
|
|
2700
|
-
* @throws {Error} If there is an error sending the message.
|
|
2549
|
+
* @param {DiscordMessage} message - The Discord message to be handled
|
|
2701
2550
|
*/
|
|
2702
|
-
async
|
|
2551
|
+
async handleMessage(message) {
|
|
2552
|
+
if (this.runtime.character.settings?.discord?.allowedChannelIds && !this.runtime.character.settings.discord.allowedChannelIds.some(
|
|
2553
|
+
(id) => id === message.channel.id
|
|
2554
|
+
)) {
|
|
2555
|
+
return;
|
|
2556
|
+
}
|
|
2557
|
+
if (message.interaction || message.author.id === this.client.user?.id) {
|
|
2558
|
+
return;
|
|
2559
|
+
}
|
|
2560
|
+
if (this.runtime.character.settings?.discord?.shouldIgnoreBotMessages && message.author?.bot) {
|
|
2561
|
+
return;
|
|
2562
|
+
}
|
|
2563
|
+
if (this.runtime.character.settings?.discord?.shouldIgnoreDirectMessages && message.channel.type === DiscordChannelType2.DM) {
|
|
2564
|
+
return;
|
|
2565
|
+
}
|
|
2566
|
+
const entityId = createUniqueUuid4(this.runtime, message.author.id);
|
|
2567
|
+
const userName = message.author.bot ? `${message.author.username}#${message.author.discriminator}` : message.author.username;
|
|
2568
|
+
const name = message.author.displayName;
|
|
2569
|
+
const channelId = message.channel.id;
|
|
2570
|
+
const roomId = createUniqueUuid4(this.runtime, channelId);
|
|
2571
|
+
let type;
|
|
2572
|
+
let serverId;
|
|
2573
|
+
if (message.guild) {
|
|
2574
|
+
const guild = await message.guild.fetch();
|
|
2575
|
+
type = await this.getChannelType(message.channel);
|
|
2576
|
+
serverId = guild.id;
|
|
2577
|
+
} else {
|
|
2578
|
+
type = ChannelType8.DM;
|
|
2579
|
+
serverId = void 0;
|
|
2580
|
+
}
|
|
2581
|
+
await this.runtime.ensureConnection({
|
|
2582
|
+
entityId,
|
|
2583
|
+
roomId,
|
|
2584
|
+
userName,
|
|
2585
|
+
name,
|
|
2586
|
+
source: "discord",
|
|
2587
|
+
channelId: message.channel.id,
|
|
2588
|
+
serverId,
|
|
2589
|
+
type
|
|
2590
|
+
});
|
|
2703
2591
|
try {
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2592
|
+
const canSendResult = canSendMessage(message.channel);
|
|
2593
|
+
if (!canSendResult.canSend) {
|
|
2594
|
+
return logger5.warn(
|
|
2595
|
+
`Cannot send message to channel ${message.channel}`,
|
|
2596
|
+
canSendResult
|
|
2707
2597
|
);
|
|
2708
2598
|
}
|
|
2709
|
-
await
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
null,
|
|
2713
|
-
files
|
|
2599
|
+
const { processedContent, attachments } = await this.processMessage(message);
|
|
2600
|
+
const audioAttachments = message.attachments.filter(
|
|
2601
|
+
(attachment) => attachment.contentType?.startsWith("audio/")
|
|
2714
2602
|
);
|
|
2603
|
+
if (audioAttachments.size > 0) {
|
|
2604
|
+
const processedAudioAttachments = await this.attachmentManager.processAttachments(audioAttachments);
|
|
2605
|
+
attachments.push(...processedAudioAttachments);
|
|
2606
|
+
}
|
|
2607
|
+
if (!processedContent && !attachments?.length) {
|
|
2608
|
+
return;
|
|
2609
|
+
}
|
|
2610
|
+
const entityId2 = createUniqueUuid4(this.runtime, message.author.id);
|
|
2611
|
+
const messageId = createUniqueUuid4(this.runtime, message.id);
|
|
2612
|
+
const newMessage = {
|
|
2613
|
+
id: messageId,
|
|
2614
|
+
entityId: entityId2,
|
|
2615
|
+
agentId: this.runtime.agentId,
|
|
2616
|
+
roomId,
|
|
2617
|
+
content: {
|
|
2618
|
+
// name: name,
|
|
2619
|
+
// userName: userName,
|
|
2620
|
+
text: processedContent || " ",
|
|
2621
|
+
attachments,
|
|
2622
|
+
source: "discord",
|
|
2623
|
+
url: message.url,
|
|
2624
|
+
inReplyTo: message.reference?.messageId ? createUniqueUuid4(this.runtime, message.reference?.messageId) : void 0
|
|
2625
|
+
},
|
|
2626
|
+
createdAt: message.createdTimestamp
|
|
2627
|
+
};
|
|
2628
|
+
const callback = async (content, files) => {
|
|
2629
|
+
try {
|
|
2630
|
+
if (message.id && !content.inReplyTo) {
|
|
2631
|
+
content.inReplyTo = createUniqueUuid4(this.runtime, message.id);
|
|
2632
|
+
}
|
|
2633
|
+
const messages = await sendMessageInChunks(
|
|
2634
|
+
message.channel,
|
|
2635
|
+
content.text,
|
|
2636
|
+
message.id,
|
|
2637
|
+
files
|
|
2638
|
+
);
|
|
2639
|
+
const memories = [];
|
|
2640
|
+
for (const m of messages) {
|
|
2641
|
+
const actions = content.actions;
|
|
2642
|
+
const memory = {
|
|
2643
|
+
id: createUniqueUuid4(this.runtime, m.id),
|
|
2644
|
+
entityId: this.runtime.agentId,
|
|
2645
|
+
agentId: this.runtime.agentId,
|
|
2646
|
+
content: {
|
|
2647
|
+
...content,
|
|
2648
|
+
actions,
|
|
2649
|
+
inReplyTo: messageId,
|
|
2650
|
+
url: m.url,
|
|
2651
|
+
channelType: type
|
|
2652
|
+
},
|
|
2653
|
+
roomId,
|
|
2654
|
+
createdAt: m.createdTimestamp
|
|
2655
|
+
};
|
|
2656
|
+
memories.push(memory);
|
|
2657
|
+
}
|
|
2658
|
+
for (const m of memories) {
|
|
2659
|
+
await this.runtime.getMemoryManager("messages").createMemory(m);
|
|
2660
|
+
}
|
|
2661
|
+
return memories;
|
|
2662
|
+
} catch (error) {
|
|
2663
|
+
console.error("Error sending message:", error);
|
|
2664
|
+
return [];
|
|
2665
|
+
}
|
|
2666
|
+
};
|
|
2667
|
+
this.runtime.emitEvent(["DISCORD_MESSAGE_RECEIVED" /* MESSAGE_RECEIVED */, EventTypes.MESSAGE_RECEIVED], {
|
|
2668
|
+
runtime: this.runtime,
|
|
2669
|
+
message: newMessage,
|
|
2670
|
+
callback
|
|
2671
|
+
});
|
|
2715
2672
|
} catch (error) {
|
|
2716
|
-
|
|
2673
|
+
console.error("Error handling message:", error);
|
|
2717
2674
|
}
|
|
2718
2675
|
}
|
|
2719
2676
|
/**
|
|
2720
|
-
*
|
|
2677
|
+
* Processes the message content, mentions, code blocks, attachments, and URLs to generate
|
|
2678
|
+
* processed content and media attachments.
|
|
2721
2679
|
*
|
|
2722
|
-
* @param {
|
|
2723
|
-
* @
|
|
2724
|
-
* @returns {Promise<void>} - A Promise that resolves when the TTS playback is finished.
|
|
2680
|
+
* @param {DiscordMessage} message The message to process
|
|
2681
|
+
* @returns {Promise<{ processedContent: string; attachments: Media[] }>} Processed content and media attachments
|
|
2725
2682
|
*/
|
|
2726
|
-
async
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2683
|
+
async processMessage(message) {
|
|
2684
|
+
let processedContent = message.content;
|
|
2685
|
+
let attachments = [];
|
|
2686
|
+
const mentionRegex = /<@!?(\d+)>/g;
|
|
2687
|
+
processedContent = processedContent.replace(
|
|
2688
|
+
mentionRegex,
|
|
2689
|
+
(match2, entityId) => {
|
|
2690
|
+
const user = message.mentions.users.get(entityId);
|
|
2691
|
+
if (user) {
|
|
2692
|
+
return `${user.username} (@${entityId})`;
|
|
2693
|
+
}
|
|
2694
|
+
return match2;
|
|
2730
2695
|
}
|
|
2731
|
-
|
|
2732
|
-
const
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2696
|
+
);
|
|
2697
|
+
const codeBlockRegex = /```([\s\S]*?)```/g;
|
|
2698
|
+
let match;
|
|
2699
|
+
while (match = codeBlockRegex.exec(processedContent)) {
|
|
2700
|
+
const codeBlock = match[1];
|
|
2701
|
+
const lines = codeBlock.split("\n");
|
|
2702
|
+
const title = lines[0];
|
|
2703
|
+
const description = lines.slice(0, 3).join("\n");
|
|
2704
|
+
const attachmentId = `code-${Date.now()}-${Math.floor(
|
|
2705
|
+
Math.random() * 1e3
|
|
2706
|
+
)}`.slice(-5);
|
|
2707
|
+
attachments.push({
|
|
2708
|
+
id: attachmentId,
|
|
2709
|
+
url: "",
|
|
2710
|
+
title: title || "Code Block",
|
|
2711
|
+
source: "Code",
|
|
2712
|
+
description,
|
|
2713
|
+
text: codeBlock
|
|
2744
2714
|
});
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
*
|
|
2750
|
-
* @param {DiscordService} discordClient The DiscordService instance used to interact with the Discord API.
|
|
2751
|
-
* @returns {Promise<Guild>} The active guild where the bot is currently connected to a voice channel.
|
|
2752
|
-
* @throws {Error} If no active voice connection is found for the bot.
|
|
2753
|
-
*/
|
|
2754
|
-
async getActiveGuild(discordClient) {
|
|
2755
|
-
const guilds = await discordClient.client.guilds.fetch();
|
|
2756
|
-
const fullGuilds = await Promise.all(guilds.map((guild) => guild.fetch()));
|
|
2757
|
-
const activeGuild = fullGuilds.find((g) => g.members.me?.voice.channelId);
|
|
2758
|
-
if (!activeGuild) {
|
|
2759
|
-
throw new Error("No active voice connection found for the bot.");
|
|
2715
|
+
processedContent = processedContent.replace(
|
|
2716
|
+
match[0],
|
|
2717
|
+
`Code Block (${attachmentId})`
|
|
2718
|
+
);
|
|
2760
2719
|
}
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
*
|
|
2766
|
-
* @param {DiscordService} discordClient - The Discord client to check for VoiceManager readiness.
|
|
2767
|
-
* @throws {Error} If the Discord client is not initialized.
|
|
2768
|
-
* @returns {Promise<void>} A promise that resolves when the VoiceManager is ready.
|
|
2769
|
-
*/
|
|
2770
|
-
async waitForVoiceManagerReady(discordClient) {
|
|
2771
|
-
if (!discordClient) {
|
|
2772
|
-
throw new Error("Discord client is not initialized.");
|
|
2720
|
+
if (message.attachments.size > 0) {
|
|
2721
|
+
attachments = await this.attachmentManager.processAttachments(
|
|
2722
|
+
message.attachments
|
|
2723
|
+
);
|
|
2773
2724
|
}
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2725
|
+
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
|
2726
|
+
const urls = processedContent.match(urlRegex) || [];
|
|
2727
|
+
for (const url of urls) {
|
|
2728
|
+
if (this.runtime.getService(ServiceTypes4.VIDEO)?.isVideoUrl(url)) {
|
|
2729
|
+
const videoService = this.runtime.getService(
|
|
2730
|
+
ServiceTypes4.VIDEO
|
|
2731
|
+
);
|
|
2732
|
+
if (!videoService) {
|
|
2733
|
+
throw new Error("Video service not found");
|
|
2734
|
+
}
|
|
2735
|
+
const videoInfo = await videoService.processVideo(url, this.runtime);
|
|
2736
|
+
attachments.push({
|
|
2737
|
+
id: `youtube-${Date.now()}`,
|
|
2738
|
+
url,
|
|
2739
|
+
title: videoInfo.title,
|
|
2740
|
+
source: "YouTube",
|
|
2741
|
+
description: videoInfo.description,
|
|
2742
|
+
text: videoInfo.text
|
|
2743
|
+
});
|
|
2744
|
+
} else {
|
|
2745
|
+
const browserService = this.runtime.getService(
|
|
2746
|
+
ServiceTypes4.BROWSER
|
|
2747
|
+
);
|
|
2748
|
+
if (!browserService) {
|
|
2749
|
+
throw new Error("Browser service not found");
|
|
2750
|
+
}
|
|
2751
|
+
const { title, description: summary } = await browserService.getPageContent(url, this.runtime);
|
|
2752
|
+
attachments.push({
|
|
2753
|
+
id: `webpage-${Date.now()}`,
|
|
2754
|
+
url,
|
|
2755
|
+
title: title || "Web Page",
|
|
2756
|
+
source: "Web",
|
|
2757
|
+
description: summary,
|
|
2758
|
+
text: summary
|
|
2759
|
+
});
|
|
2760
|
+
}
|
|
2779
2761
|
}
|
|
2762
|
+
return { processedContent, attachments };
|
|
2780
2763
|
}
|
|
2781
2764
|
/**
|
|
2782
|
-
*
|
|
2783
|
-
* If the test channel ID is not set, an error is thrown.
|
|
2765
|
+
* Asynchronously fetches the bot's username and discriminator from Discord API.
|
|
2784
2766
|
*
|
|
2785
|
-
* @param {
|
|
2786
|
-
* @returns {string}
|
|
2767
|
+
* @param {string} botToken The token of the bot to authenticate the request
|
|
2768
|
+
* @returns {Promise<string>} A promise that resolves with the bot's username and discriminator
|
|
2769
|
+
* @throws {Error} If there is an error while fetching the bot details
|
|
2787
2770
|
*/
|
|
2788
|
-
|
|
2789
|
-
const
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2771
|
+
async fetchBotName(botToken) {
|
|
2772
|
+
const url = "https://discord.com/api/v10/users/@me";
|
|
2773
|
+
const response = await fetch(url, {
|
|
2774
|
+
method: "GET",
|
|
2775
|
+
headers: {
|
|
2776
|
+
Authorization: `Bot ${botToken}`
|
|
2777
|
+
}
|
|
2778
|
+
});
|
|
2779
|
+
if (!response.ok) {
|
|
2780
|
+
throw new Error(`Error fetching bot details: ${response.statusText}`);
|
|
2794
2781
|
}
|
|
2795
|
-
|
|
2782
|
+
const data = await response.json();
|
|
2783
|
+
const discriminator = data.discriminator;
|
|
2784
|
+
return data.username + (discriminator ? `#${discriminator}` : "");
|
|
2796
2785
|
}
|
|
2797
2786
|
};
|
|
2798
2787
|
|
|
@@ -3512,7 +3501,7 @@ var VoiceManager = class extends EventEmitter {
|
|
|
3512
3501
|
console.log(`Joining channel: ${chosenChannel.name}`);
|
|
3513
3502
|
await this.joinChannel(chosenChannel);
|
|
3514
3503
|
} else {
|
|
3515
|
-
|
|
3504
|
+
logger6.debug("Warning: No suitable voice channel found to join.");
|
|
3516
3505
|
}
|
|
3517
3506
|
} catch (error) {
|
|
3518
3507
|
console.error("Error selecting or joining a voice channel:", error);
|
|
@@ -3626,7 +3615,7 @@ var VoiceManager = class extends EventEmitter {
|
|
|
3626
3615
|
}
|
|
3627
3616
|
};
|
|
3628
3617
|
|
|
3629
|
-
// src/
|
|
3618
|
+
// src/service.ts
|
|
3630
3619
|
var DiscordService = class _DiscordService extends Service {
|
|
3631
3620
|
static serviceType = DISCORD_SERVICE_NAME;
|
|
3632
3621
|
capabilityDescription = "The agent is able to send and receive messages on discord";
|
|
@@ -4437,27 +4426,29 @@ var DiscordService = class _DiscordService extends Service {
|
|
|
4437
4426
|
this.client?.emit("voiceManagerReady");
|
|
4438
4427
|
}
|
|
4439
4428
|
};
|
|
4429
|
+
|
|
4430
|
+
// src/index.ts
|
|
4440
4431
|
var discordPlugin = {
|
|
4441
4432
|
name: "discord",
|
|
4442
4433
|
description: "Discord client plugin",
|
|
4443
4434
|
services: [DiscordService],
|
|
4444
4435
|
actions: [
|
|
4445
4436
|
chatWithAttachments_default,
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4437
|
+
downloadMedia,
|
|
4438
|
+
joinVoice,
|
|
4439
|
+
leaveVoice,
|
|
4440
|
+
summarize,
|
|
4441
|
+
transcribeMedia
|
|
4451
4442
|
],
|
|
4452
|
-
providers: [
|
|
4443
|
+
providers: [channelStateProvider, voiceStateProvider],
|
|
4453
4444
|
tests: [new DiscordTestSuite()],
|
|
4454
4445
|
init: async (config, runtime) => {
|
|
4455
4446
|
const token = runtime.getSetting("DISCORD_API_TOKEN");
|
|
4456
4447
|
if (!token || token.trim() === "") {
|
|
4457
|
-
|
|
4448
|
+
logger8.warn(
|
|
4458
4449
|
"Discord API Token not provided - Discord plugin is loaded but will not be functional"
|
|
4459
4450
|
);
|
|
4460
|
-
|
|
4451
|
+
logger8.warn(
|
|
4461
4452
|
"To enable Discord functionality, please provide DISCORD_API_TOKEN in your .eliza/.env file"
|
|
4462
4453
|
);
|
|
4463
4454
|
}
|
|
@@ -4465,7 +4456,6 @@ var discordPlugin = {
|
|
|
4465
4456
|
};
|
|
4466
4457
|
var index_default = discordPlugin;
|
|
4467
4458
|
export {
|
|
4468
|
-
DiscordService,
|
|
4469
4459
|
index_default as default
|
|
4470
4460
|
};
|
|
4471
4461
|
//# sourceMappingURL=index.js.map
|