e2505df6730235e02a7334accdd1d8c8f97cb40d1cd57519f31b37cf82101eef1// Manages persistent Discord bot connections for gateway input channels.
2// One bot client per unique bot token. Multiple channels can share a bot.
3
4import log from "../../seed/log.js";
5
6let Client, GatewayIntentBits;
7let discordLoaded = false;
8
9async function loadDiscord() {
10 if (discordLoaded) return;
11 try {
12 const discordjs = await import("discord.js");
13 Client = discordjs.Client;
14 GatewayIntentBits = discordjs.GatewayIntentBits;
15 discordLoaded = true;
16 } catch {
17 throw new Error(
18 "discord.js package not installed. Run: npm install discord.js",
19 );
20 }
21}
22
23// tokenHash -> { client, channelMap: Map<channelId, { discordChannelId, rootId }> }
24const activeBots = new Map();
25
26function hashToken(token) {
27 // Use last 10 chars as key (enough to distinguish, avoids storing full token in memory map key)
28 return token.slice(-10);
29}
30
31export async function connectBot(
32 botToken,
33 channelId,
34 discordChannelId,
35 rootId,
36) {
37 await loadDiscord();
38
39 const key = hashToken(botToken);
40 const entry = activeBots.get(key);
41
42 if (entry) {
43 // Bot already connected, just add this channel to its map
44 entry.channelMap.set(channelId, { discordChannelId, rootId });
45 log.debug("GatewayDiscord",
46 `Bot manager: added channel ${channelId} to existing bot (${entry.channelMap.size} channels)`,
47 );
48 return;
49 }
50
51 // Create new bot client
52 const client = new Client({
53 intents: [
54 GatewayIntentBits.Guilds,
55 GatewayIntentBits.GuildMessages,
56 GatewayIntentBits.MessageContent,
57 ],
58 });
59
60 const channelMap = new Map();
61 channelMap.set(channelId, { discordChannelId, rootId });
62
63 // Message handler
64 client.on("messageCreate", async (message) => {
65 // Ignore only our own replies (prevents self-reply loops)
66 if (message.author.id === client.user?.id) return;
67
68 // Find which gateway channel(s) are listening on this Discord channel
69 for (const [gwChannelId, config] of channelMap) {
70 if (message.channel.id !== config.discordChannelId) continue;
71
72 const senderName =
73 message.author.displayName || message.author.username || "Unknown";
74 const senderPlatformId = message.author.id;
75 const messageText = message.content;
76
77 if (!messageText || !messageText.trim()) continue;
78
79 log.debug("GatewayDiscord",
80 `Bot: message on gw channel ${gwChannelId} from ${senderName}: "${messageText.slice(0, 80)}"`,
81 );
82
83 try {
84 // Process via gateway core
85 const { getExtension } = await import("../loader.js");
86 const gateway = getExtension("gateway");
87 if (!gateway?.exports?.processGatewayMessage) {
88 log.error("GatewayDiscord", "Gateway core not loaded");
89 continue;
90 }
91
92 const result = await gateway.exports.processGatewayMessage(gwChannelId, {
93 senderName,
94 senderPlatformId,
95 messageText,
96 });
97
98 // Send reply back to the same Discord channel if input-output
99 if (result.reply) {
100 let replyText = result.reply;
101 if (replyText.length > 2000) {
102 replyText = replyText.slice(0, 1997) + "...";
103 }
104 await message.channel.send(replyText);
105 }
106 } catch (err) {
107 log.error("GatewayDiscord",
108 `Bot: error processing message for channel ${gwChannelId}:`,
109 err.message,
110 );
111 }
112 }
113 });
114
115 client.on("error", (err) => {
116 log.error("GatewayDiscord", "Bot client error:", err.message);
117 });
118
119 client.on("warn", (msg) => {
120 log.warn("GatewayDiscord", "Bot warning:", msg);
121 });
122
123 // Login
124 try {
125 await client.login(botToken);
126 log.verbose("GatewayDiscord",
127 `Bot manager: bot connected as ${client.user?.tag}, ${channelMap.size} channel(s)`,
128 );
129 } catch (err) {
130 log.error("GatewayDiscord", "Bot manager: login failed:", err.message);
131 throw new Error("Discord bot login failed: " + err.message);
132 }
133
134 activeBots.set(key, { client, channelMap, botToken });
135}
136
137export async function disconnectBot(channelId) {
138 for (const [key, entry] of activeBots) {
139 if (entry.channelMap.has(channelId)) {
140 entry.channelMap.delete(channelId);
141 log.debug("GatewayDiscord",
142 `Bot manager: removed channel ${channelId} (${entry.channelMap.size} remaining)`,
143 );
144
145 // If no more channels, destroy the bot client
146 if (entry.channelMap.size === 0) {
147 try {
148 entry.client.destroy();
149 } catch (err) { log.debug("GatewayDiscord", "bot client destroy failed:", err.message); }
150 activeBots.delete(key);
151 log.verbose("GatewayDiscord",
152 "Bot manager: bot client destroyed (no channels left)",
153 );
154 }
155 return;
156 }
157 }
158}
159
160export async function disconnectAllBots() {
161 for (const [key, entry] of activeBots) {
162 try {
163 entry.client.destroy();
164 } catch (err) { log.debug("GatewayDiscord", "bot client destroy failed during shutdown:", err.message); }
165 }
166 activeBots.clear();
167 log.verbose("GatewayDiscord", "Bot manager: all bots disconnected");
168}
169
170export function getActiveBotCount() {
171 return activeBots.size;
172}
173
174/**
175 * Scan DB for enabled Discord input channels and connect their bots.
176 * Called on server startup.
177 */
178export async function startupScan() {
179 try {
180 const { getExtension } = await import("../loader.js");
181 const gatewayExt = getExtension("gateway");
182 const GatewayChannel = gatewayExt?.exports?.GatewayChannel;
183 const getChannelWithSecrets = gatewayExt?.exports?.getChannelWithSecrets;
184
185 const channels = await GatewayChannel.find({
186 type: "discord",
187 direction: { $in: ["input", "input-output"] },
188 enabled: true,
189 }).lean();
190
191 if (channels.length === 0) {
192 log.debug("GatewayDiscord", "Bot manager: no Discord input channels to connect");
193 return;
194 }
195
196 log.verbose("GatewayDiscord",
197 `Bot manager: found ${channels.length} Discord input channel(s) to connect`,
198 );
199
200 for (const channel of channels) {
201 try {
202 const full = await getChannelWithSecrets(channel._id);
203 if (!full?.config?.decryptedSecrets?.botToken) {
204 log.error("GatewayDiscord",
205 `Bot manager: no bot token for channel ${channel._id}`,
206 );
207 continue;
208 }
209
210 await connectBot(
211 full.config.decryptedSecrets.botToken,
212 channel._id,
213 channel.config.metadata.discordChannelId,
214 channel.rootId,
215 );
216 } catch (err) {
217 log.error("GatewayDiscord",
218 `Bot manager: failed to connect channel ${channel._id}:`,
219 err.message,
220 );
221 }
222 }
223 } catch (err) {
224 log.error("GatewayDiscord", "Bot manager: startup scan failed:", err.message);
225 }
226}
2271// Discord channel type handler.
2// Registered with gateway core during init.
3
4import log from "../../seed/log.js";
5
6// ─────────────────────────────────────────────────────────────────────────
7// VALIDATION
8// ─────────────────────────────────────────────────────────────────────────
9
10function validateDiscordWebhookUrl(webhookUrl) {
11 try {
12 const parsed = new URL(webhookUrl);
13 if (
14 !parsed.hostname.endsWith("discord.com") &&
15 !parsed.hostname.endsWith("discordapp.com")
16 ) {
17 throw new Error("Discord webhook URL must be a discord.com URL");
18 }
19 } catch (err) {
20 if (err.message.includes("discord")) throw err;
21 throw new Error("Invalid Discord webhook URL");
22 }
23}
24
25function validateConfig(config, direction) {
26 const hasInput = direction === "input" || direction === "input-output";
27 const hasOutput = direction === "output" || direction === "input-output";
28
29 if (hasInput) {
30 // Discord input requires bot token + channel ID (bot connects via gateway)
31 if (!config.botToken || typeof config.botToken !== "string") {
32 throw new Error("Discord input channel requires a botToken");
33 }
34 if (
35 !config.discordChannelId ||
36 typeof config.discordChannelId !== "string"
37 ) {
38 throw new Error("Discord input channel requires a discordChannelId");
39 }
40 // Webhook URL optional for input-output (used for output side)
41 if (hasOutput && config.webhookUrl) {
42 validateDiscordWebhookUrl(config.webhookUrl);
43 }
44 } else {
45 // Output-only: webhook URL required
46 if (!config.webhookUrl || typeof config.webhookUrl !== "string") {
47 throw new Error("Discord channel requires a webhookUrl");
48 }
49 validateDiscordWebhookUrl(config.webhookUrl);
50 }
51}
52
53function buildEncryptedConfig(config, direction) {
54 const hasInput = direction === "input" || direction === "input-output";
55 let secrets;
56 let metadata = {};
57
58 if (hasInput) {
59 // Bot token for input, optionally webhook for output side
60 secrets = { botToken: config.botToken };
61 if (config.webhookUrl) secrets.webhookUrl = config.webhookUrl;
62 metadata = {
63 discordChannelId: config.discordChannelId,
64 guildId: config.guildId || null,
65 };
66 } else {
67 secrets = { webhookUrl: config.webhookUrl };
68 }
69
70 return {
71 secrets,
72 metadata,
73 displayIdentifier: config.displayIdentifier || null,
74 };
75}
76
77// ─────────────────────────────────────────────────────────────────────────
78// SENDER
79// ─────────────────────────────────────────────────────────────────────────
80
81async function send(secrets, metadata, notification) {
82 let content = `**${notification.title}**\n\n${notification.content}`;
83 if (content.length > 2000) {
84 content = content.slice(0, 1997) + "...";
85 }
86
87 if (secrets.webhookUrl) {
88 // Output via webhook
89 const res = await fetch(secrets.webhookUrl, {
90 method: "POST",
91 headers: { "Content-Type": "application/json" },
92 body: JSON.stringify({ content }),
93 });
94
95 if (!res.ok) {
96 const body = await res.text();
97 throw new Error(`Discord webhook error ${res.status}: ${body}`);
98 }
99 } else if (secrets.botToken && metadata.discordChannelId) {
100 // Input channel, send via bot API
101 const res = await fetch(`https://discord.com/api/v10/channels/${metadata.discordChannelId}/messages`, {
102 method: "POST",
103 headers: {
104 "Content-Type": "application/json",
105 "Authorization": `Bot ${secrets.botToken}`,
106 },
107 body: JSON.stringify({ content }),
108 });
109
110 if (!res.ok) {
111 const body = await res.text();
112 throw new Error(`Discord bot API error ${res.status}: ${body}`);
113 }
114 } else {
115 throw new Error("Discord channel has no webhookUrl or botToken configured");
116 }
117}
118
119// ─────────────────────────────────────────────────────────────────────────
120// INPUT LIFECYCLE
121// ─────────────────────────────────────────────────────────────────────────
122
123async function registerInput(channel, secrets) {
124 const { connectBot } = await import("./botManager.js");
125 await connectBot(
126 secrets.botToken,
127 channel._id,
128 channel.config.metadata.discordChannelId,
129 channel.rootId,
130 );
131 log.verbose("GatewayDiscord", `Discord bot connected for channel ${channel._id}`);
132}
133
134async function unregisterInput(channel, secrets) {
135 try {
136 const { disconnectBot } = await import("./botManager.js");
137 await disconnectBot(channel._id);
138 log.verbose("GatewayDiscord", `Discord bot disconnected for channel ${channel._id}`);
139 } catch (err) {
140 log.error("GatewayDiscord", `Failed to disconnect Discord bot for ${channel._id}:`, err.message);
141 }
142}
143
144// ─────────────────────────────────────────────────────────────────────────
145// EXPORT HANDLER
146// ─────────────────────────────────────────────────────────────────────────
147
148export default {
149 allowedDirections: ["input", "output", "input-output"],
150 requiredTiers: ["standard", "premium"],
151 validateConfig,
152 buildEncryptedConfig,
153 send,
154 registerInput,
155 unregisterInput,
156};
1571import log from "../../seed/log.js";
2import handler from "./handler.js";
3import { getExtension } from "../loader.js";
4import { startupScan, disconnectAllBots } from "./botManager.js";
5
6export async function init(core) {
7 const gateway = getExtension("gateway");
8 if (!gateway?.exports?.registerChannelType) {
9 throw new Error("gateway-discord requires the gateway extension to be loaded first");
10 }
11
12 gateway.exports.registerChannelType("discord", handler);
13 log.verbose("GatewayDiscord", "Registered discord channel type");
14
15 return {
16 jobs: [
17 {
18 name: "gateway-discord-bots",
19 start: () => { startupScan(); },
20 stop: () => { disconnectAllBots(); },
21 },
22 ],
23 };
24}
251export default {
2 name: "gateway-discord",
3 version: "1.0.1",
4 builtFor: "treeos-connect",
5 description:
6 "Registers the Discord channel type with the gateway core, enabling trees to send and " +
7 "receive messages through Discord servers. Output channels post notifications to a " +
8 "Discord channel via webhook URL. Input channels connect a persistent Discord bot that " +
9 "listens for messages in a specific channel and routes them through the gateway's AI " +
10 "pipeline. Input-output channels do both: the bot listens for messages, processes them " +
11 "through the tree orchestrator, and replies directly in the Discord channel." +
12 "\n\n" +
13 "The bot manager maintains persistent Discord.js client connections, one per unique bot " +
14 "token. Multiple gateway channels can share a single bot if they use the same token, " +
15 "with each channel mapped to a different Discord channel ID. On server startup, the " +
16 "manager scans the database for all enabled Discord input channels, decrypts their bot " +
17 "tokens, and connects their bots automatically. When a channel is disabled or deleted, " +
18 "the bot is removed from the channel map. When no channels remain on a bot, the client " +
19 "is destroyed to free resources. Bot messages from the bot's own user ID are ignored to " +
20 "prevent self-reply loops." +
21 "\n\n" +
22 "Output uses the Discord webhook API, which requires no bot and no persistent connection. " +
23 "Messages are formatted with bold titles and truncated to Discord's 2000 character limit. " +
24 "For input-output channels that also need to send outbound notifications, the handler " +
25 "can use either a webhook URL or the bot API directly, falling through based on what is " +
26 "configured. Input channels require Standard or Premium tier subscriptions, enforced by " +
27 "the gateway core's tier check system. Config validation ensures webhook URLs point to " +
28 "discord.com or discordapp.com domains, and input channels provide both a bot token and " +
29 "a Discord channel ID.",
30
31 npm: ["discord.js@^14.25.1"],
32
33 needs: {
34 models: ["Node", "User"],
35 extensions: ["gateway"],
36 },
37
38 optional: {},
39
40 provides: {
41 models: {},
42 routes: false,
43 tools: false,
44 jobs: false,
45 orchestrator: false,
46 energyActions: {},
47 sessionTypes: {},
48 env: [],
49 cli: [],
50
51 hooks: {
52 fires: [],
53 listens: [],
54 },
55 },
56};
571{
2 "name": "treeos-ext-gateway-discord",
3 "version": "1.0.0",
4 "lockfileVersion": 3,
5 "requires": true,
6 "packages": {
7 "": {
8 "name": "treeos-ext-gateway-discord",
9 "version": "1.0.0",
10 "dependencies": {
11 "discord.js": "^14.25.1"
12 }
13 },
14 "node_modules/@discordjs/builders": {
15 "version": "1.14.0",
16 "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.14.0.tgz",
17 "integrity": "sha512-7pVKxVWkeLUtrTo9nTYkjRcJk0Hlms6lYervXAD7E7+K5lil9ms2JrEB1TalMiHvQMh7h1HJZ4fCJa0/vHpl4w==",
18 "license": "Apache-2.0",
19 "dependencies": {
20 "@discordjs/formatters": "^0.6.2",
21 "@discordjs/util": "^1.2.0",
22 "@sapphire/shapeshift": "^4.0.0",
23 "discord-api-types": "^0.38.40",
24 "fast-deep-equal": "^3.1.3",
25 "ts-mixer": "^6.0.4",
26 "tslib": "^2.6.3"
27 },
28 "engines": {
29 "node": ">=16.11.0"
30 },
31 "funding": {
32 "url": "https://github.com/discordjs/discord.js?sponsor"
33 }
34 },
35 "node_modules/@discordjs/collection": {
36 "version": "1.5.3",
37 "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz",
38 "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==",
39 "license": "Apache-2.0",
40 "engines": {
41 "node": ">=16.11.0"
42 }
43 },
44 "node_modules/@discordjs/formatters": {
45 "version": "0.6.2",
46 "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.2.tgz",
47 "integrity": "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==",
48 "license": "Apache-2.0",
49 "dependencies": {
50 "discord-api-types": "^0.38.33"
51 },
52 "engines": {
53 "node": ">=16.11.0"
54 },
55 "funding": {
56 "url": "https://github.com/discordjs/discord.js?sponsor"
57 }
58 },
59 "node_modules/@discordjs/rest": {
60 "version": "2.6.1",
61 "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.1.tgz",
62 "integrity": "sha512-wwQdgjeaoYFiaG+atbqx6aJDpqW7JHAo0HrQkBTbYzM3/PJ3GweQIpgElNcGZ26DCUOXMyawYd0YF7vtr+fZXg==",
63 "license": "Apache-2.0",
64 "dependencies": {
65 "@discordjs/collection": "^2.1.1",
66 "@discordjs/util": "^1.2.0",
67 "@sapphire/async-queue": "^1.5.3",
68 "@sapphire/snowflake": "^3.5.5",
69 "@vladfrangu/async_event_emitter": "^2.4.6",
70 "discord-api-types": "^0.38.40",
71 "magic-bytes.js": "^1.13.0",
72 "tslib": "^2.6.3",
73 "undici": "6.24.1"
74 },
75 "engines": {
76 "node": ">=18"
77 },
78 "funding": {
79 "url": "https://github.com/discordjs/discord.js?sponsor"
80 }
81 },
82 "node_modules/@discordjs/rest/node_modules/@discordjs/collection": {
83 "version": "2.1.1",
84 "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz",
85 "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==",
86 "license": "Apache-2.0",
87 "engines": {
88 "node": ">=18"
89 },
90 "funding": {
91 "url": "https://github.com/discordjs/discord.js?sponsor"
92 }
93 },
94 "node_modules/@discordjs/rest/node_modules/@sapphire/snowflake": {
95 "version": "3.5.5",
96 "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.5.tgz",
97 "integrity": "sha512-xzvBr1Q1c4lCe7i6sRnrofxeO1QTP/LKQ6A6qy0iB4x5yfiSfARMEQEghojzTNALDTcv8En04qYNIco9/K9eZQ==",
98 "license": "MIT",
99 "engines": {
100 "node": ">=v14.0.0",
101 "npm": ">=7.0.0"
102 }
103 },
104 "node_modules/@discordjs/rest/node_modules/undici": {
105 "version": "6.24.1",
106 "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz",
107 "integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==",
108 "license": "MIT",
109 "engines": {
110 "node": ">=18.17"
111 }
112 },
113 "node_modules/@discordjs/util": {
114 "version": "1.2.0",
115 "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz",
116 "integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==",
117 "license": "Apache-2.0",
118 "dependencies": {
119 "discord-api-types": "^0.38.33"
120 },
121 "engines": {
122 "node": ">=18"
123 },
124 "funding": {
125 "url": "https://github.com/discordjs/discord.js?sponsor"
126 }
127 },
128 "node_modules/@discordjs/ws": {
129 "version": "1.2.3",
130 "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz",
131 "integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==",
132 "license": "Apache-2.0",
133 "dependencies": {
134 "@discordjs/collection": "^2.1.0",
135 "@discordjs/rest": "^2.5.1",
136 "@discordjs/util": "^1.1.0",
137 "@sapphire/async-queue": "^1.5.2",
138 "@types/ws": "^8.5.10",
139 "@vladfrangu/async_event_emitter": "^2.2.4",
140 "discord-api-types": "^0.38.1",
141 "tslib": "^2.6.2",
142 "ws": "^8.17.0"
143 },
144 "engines": {
145 "node": ">=16.11.0"
146 },
147 "funding": {
148 "url": "https://github.com/discordjs/discord.js?sponsor"
149 }
150 },
151 "node_modules/@discordjs/ws/node_modules/@discordjs/collection": {
152 "version": "2.1.1",
153 "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz",
154 "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==",
155 "license": "Apache-2.0",
156 "engines": {
157 "node": ">=18"
158 },
159 "funding": {
160 "url": "https://github.com/discordjs/discord.js?sponsor"
161 }
162 },
163 "node_modules/@sapphire/async-queue": {
164 "version": "1.5.5",
165 "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz",
166 "integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==",
167 "license": "MIT",
168 "engines": {
169 "node": ">=v14.0.0",
170 "npm": ">=7.0.0"
171 }
172 },
173 "node_modules/@sapphire/shapeshift": {
174 "version": "4.0.0",
175 "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz",
176 "integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==",
177 "license": "MIT",
178 "dependencies": {
179 "fast-deep-equal": "^3.1.3",
180 "lodash": "^4.17.21"
181 },
182 "engines": {
183 "node": ">=v16"
184 }
185 },
186 "node_modules/@sapphire/snowflake": {
187 "version": "3.5.3",
188 "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz",
189 "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==",
190 "license": "MIT",
191 "engines": {
192 "node": ">=v14.0.0",
193 "npm": ">=7.0.0"
194 }
195 },
196 "node_modules/@types/node": {
197 "version": "25.5.0",
198 "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz",
199 "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
200 "license": "MIT",
201 "dependencies": {
202 "undici-types": "~7.18.0"
203 }
204 },
205 "node_modules/@types/ws": {
206 "version": "8.18.1",
207 "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
208 "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
209 "license": "MIT",
210 "dependencies": {
211 "@types/node": "*"
212 }
213 },
214 "node_modules/@vladfrangu/async_event_emitter": {
215 "version": "2.4.7",
216 "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz",
217 "integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==",
218 "license": "MIT",
219 "engines": {
220 "node": ">=v14.0.0",
221 "npm": ">=7.0.0"
222 }
223 },
224 "node_modules/discord-api-types": {
225 "version": "0.38.42",
226 "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.42.tgz",
227 "integrity": "sha512-qs1kya7S84r5RR8m9kgttywGrmmoHaRifU1askAoi+wkoSefLpZP6aGXusjNw5b0jD3zOg3LTwUa3Tf2iHIceQ==",
228 "license": "MIT",
229 "workspaces": [
230 "scripts/actions/documentation"
231 ]
232 },
233 "node_modules/discord.js": {
234 "version": "14.25.1",
235 "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.25.1.tgz",
236 "integrity": "sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g==",
237 "license": "Apache-2.0",
238 "dependencies": {
239 "@discordjs/builders": "^1.13.0",
240 "@discordjs/collection": "1.5.3",
241 "@discordjs/formatters": "^0.6.2",
242 "@discordjs/rest": "^2.6.0",
243 "@discordjs/util": "^1.2.0",
244 "@discordjs/ws": "^1.2.3",
245 "@sapphire/snowflake": "3.5.3",
246 "discord-api-types": "^0.38.33",
247 "fast-deep-equal": "3.1.3",
248 "lodash.snakecase": "4.1.1",
249 "magic-bytes.js": "^1.10.0",
250 "tslib": "^2.6.3",
251 "undici": "6.21.3"
252 },
253 "engines": {
254 "node": ">=18"
255 },
256 "funding": {
257 "url": "https://github.com/discordjs/discord.js?sponsor"
258 }
259 },
260 "node_modules/fast-deep-equal": {
261 "version": "3.1.3",
262 "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
263 "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
264 "license": "MIT"
265 },
266 "node_modules/lodash": {
267 "version": "4.17.23",
268 "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
269 "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
270 "license": "MIT"
271 },
272 "node_modules/lodash.snakecase": {
273 "version": "4.1.1",
274 "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
275 "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==",
276 "license": "MIT"
277 },
278 "node_modules/magic-bytes.js": {
279 "version": "1.13.0",
280 "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.13.0.tgz",
281 "integrity": "sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg==",
282 "license": "MIT"
283 },
284 "node_modules/ts-mixer": {
285 "version": "6.0.4",
286 "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz",
287 "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==",
288 "license": "MIT"
289 },
290 "node_modules/tslib": {
291 "version": "2.8.1",
292 "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
293 "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
294 "license": "0BSD"
295 },
296 "node_modules/undici": {
297 "version": "6.21.3",
298 "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
299 "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==",
300 "license": "MIT",
301 "engines": {
302 "node": ">=18.17"
303 }
304 },
305 "node_modules/undici-types": {
306 "version": "7.18.2",
307 "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
308 "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
309 "license": "MIT"
310 },
311 "node_modules/ws": {
312 "version": "8.20.0",
313 "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
314 "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==",
315 "license": "MIT",
316 "engines": {
317 "node": ">=10.0.0"
318 },
319 "peerDependencies": {
320 "bufferutil": "^4.0.1",
321 "utf-8-validate": ">=5.0.2"
322 },
323 "peerDependenciesMeta": {
324 "bufferutil": {
325 "optional": true
326 },
327 "utf-8-validate": {
328 "optional": true
329 }
330 }
331 }
332 }
333}
3341{
2 "type": "module",
3 "name": "treeos-ext-gateway-discord",
4 "version": "1.0.0",
5 "private": true,
6 "dependencies": {
7 "discord.js": "^14.25.1"
8 }
9}
| Version | Published | Downloads |
|---|---|---|
| 1.0.1 | 38d ago | 0 |
| 1.0.0 | 48d ago | 0 |
treeos ext star gateway-discord
Post comments from the CLI: treeos ext comment gateway-discord "your comment"
Max 3 comments per extension. One star and one flag per user.
Loading comments...