@nextclaw/channel-runtime 0.1.15 → 0.1.17
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.d.ts +9 -1
- package/dist/index.js +132 -7
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -56,15 +56,23 @@ declare class DingTalkChannel extends BaseChannel<Config["channels"]["dingtalk"]
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
declare class DiscordChannel extends BaseChannel<Config["channels"]["discord"]> {
|
|
59
|
+
private sessionManager?;
|
|
60
|
+
private coreConfig?;
|
|
59
61
|
name: string;
|
|
60
62
|
private client;
|
|
61
63
|
private readonly typingController;
|
|
62
|
-
|
|
64
|
+
private readonly commandRegistry;
|
|
65
|
+
constructor(config: Config["channels"]["discord"], bus: MessageBus, sessionManager?: SessionManager | undefined, coreConfig?: Config | undefined);
|
|
63
66
|
start(): Promise<void>;
|
|
64
67
|
stop(): Promise<void>;
|
|
65
68
|
handleControlMessage(msg: OutboundMessage): Promise<boolean>;
|
|
66
69
|
send(msg: OutboundMessage): Promise<void>;
|
|
67
70
|
private handleIncoming;
|
|
71
|
+
private handleInteraction;
|
|
72
|
+
private handleSlashCommand;
|
|
73
|
+
private replyInteraction;
|
|
74
|
+
private registerSlashCommands;
|
|
75
|
+
private buildSlashCommandPayloads;
|
|
68
76
|
private resolveProxyAgent;
|
|
69
77
|
private resolveAccountId;
|
|
70
78
|
private isAllowedByPolicy;
|
package/dist/index.js
CHANGED
|
@@ -176,7 +176,10 @@ import {
|
|
|
176
176
|
Client,
|
|
177
177
|
GatewayIntentBits,
|
|
178
178
|
Partials,
|
|
179
|
-
MessageFlags
|
|
179
|
+
MessageFlags,
|
|
180
|
+
REST,
|
|
181
|
+
Routes,
|
|
182
|
+
ApplicationCommandOptionType
|
|
180
183
|
} from "discord.js";
|
|
181
184
|
import { ProxyAgent, fetch as fetch2 } from "undici";
|
|
182
185
|
import { join } from "path";
|
|
@@ -227,7 +230,7 @@ var ChannelTypingController = class {
|
|
|
227
230
|
};
|
|
228
231
|
|
|
229
232
|
// src/channels/discord.ts
|
|
230
|
-
import { isTypingStopControlMessage } from "@nextclaw/core";
|
|
233
|
+
import { CommandRegistry, isTypingStopControlMessage } from "@nextclaw/core";
|
|
231
234
|
var DEFAULT_MEDIA_MAX_MB = 8;
|
|
232
235
|
var MEDIA_FETCH_TIMEOUT_MS = 15e3;
|
|
233
236
|
var TYPING_HEARTBEAT_MS = 6e3;
|
|
@@ -237,12 +240,13 @@ var DISCORD_MAX_LINES_PER_MESSAGE = 17;
|
|
|
237
240
|
var STREAM_EDIT_MIN_INTERVAL_MS = 600;
|
|
238
241
|
var STREAM_MAX_UPDATES_PER_MESSAGE = 40;
|
|
239
242
|
var FENCE_RE = /^( {0,3})(`{3,}|~{3,})(.*)$/;
|
|
243
|
+
var SLASH_GUILD_THRESHOLD = 10;
|
|
240
244
|
var DiscordChannel = class extends BaseChannel {
|
|
241
|
-
|
|
242
|
-
client = null;
|
|
243
|
-
typingController;
|
|
244
|
-
constructor(config, bus) {
|
|
245
|
+
constructor(config, bus, sessionManager, coreConfig) {
|
|
245
246
|
super(config, bus);
|
|
247
|
+
this.sessionManager = sessionManager;
|
|
248
|
+
this.coreConfig = coreConfig;
|
|
249
|
+
this.commandRegistry = this.coreConfig ? new CommandRegistry(this.coreConfig, this.sessionManager) : null;
|
|
246
250
|
this.typingController = new ChannelTypingController({
|
|
247
251
|
heartbeatMs: TYPING_HEARTBEAT_MS,
|
|
248
252
|
autoStopMs: TYPING_AUTO_STOP_MS,
|
|
@@ -259,6 +263,10 @@ var DiscordChannel = class extends BaseChannel {
|
|
|
259
263
|
}
|
|
260
264
|
});
|
|
261
265
|
}
|
|
266
|
+
name = "discord";
|
|
267
|
+
client = null;
|
|
268
|
+
typingController;
|
|
269
|
+
commandRegistry;
|
|
262
270
|
async start() {
|
|
263
271
|
if (!this.config.token) {
|
|
264
272
|
throw new Error("Discord token not configured");
|
|
@@ -270,10 +278,14 @@ var DiscordChannel = class extends BaseChannel {
|
|
|
270
278
|
});
|
|
271
279
|
this.client.on("ready", () => {
|
|
272
280
|
console.log("Discord bot connected");
|
|
281
|
+
void this.registerSlashCommands();
|
|
273
282
|
});
|
|
274
283
|
this.client.on("messageCreate", async (message) => {
|
|
275
284
|
await this.handleIncoming(message);
|
|
276
285
|
});
|
|
286
|
+
this.client.on("interactionCreate", async (interaction) => {
|
|
287
|
+
await this.handleInteraction(interaction);
|
|
288
|
+
});
|
|
277
289
|
await this.client.login(this.config.token);
|
|
278
290
|
}
|
|
279
291
|
async stop() {
|
|
@@ -409,6 +421,97 @@ var DiscordChannel = class extends BaseChannel {
|
|
|
409
421
|
throw error;
|
|
410
422
|
}
|
|
411
423
|
}
|
|
424
|
+
async handleInteraction(interaction) {
|
|
425
|
+
if (!interaction.isChatInputCommand()) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
await this.handleSlashCommand(interaction);
|
|
429
|
+
}
|
|
430
|
+
async handleSlashCommand(interaction) {
|
|
431
|
+
if (!this.commandRegistry) {
|
|
432
|
+
await this.replyInteraction(interaction, "Slash commands are not available.", true);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
const channelId = interaction.channelId;
|
|
436
|
+
if (!channelId) {
|
|
437
|
+
await this.replyInteraction(interaction, "Slash commands are not available in this channel.", true);
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const senderId = interaction.user.id;
|
|
441
|
+
const isGroup = Boolean(interaction.guildId);
|
|
442
|
+
if (!this.isAllowedByPolicy({ senderId, channelId, isGroup })) {
|
|
443
|
+
await this.replyInteraction(interaction, "You are not authorized to use commands here.", true);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
const args = {};
|
|
447
|
+
for (const option of interaction.options.data) {
|
|
448
|
+
if (typeof option.name === "string" && option.value !== void 0) {
|
|
449
|
+
args[option.name] = option.value;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
try {
|
|
453
|
+
const result = await this.commandRegistry.execute(interaction.commandName, args, {
|
|
454
|
+
channel: this.name,
|
|
455
|
+
chatId: channelId,
|
|
456
|
+
senderId,
|
|
457
|
+
sessionKey: `${this.name}:${channelId}`
|
|
458
|
+
});
|
|
459
|
+
await this.replyInteraction(interaction, result.content, result.ephemeral ?? true);
|
|
460
|
+
} catch (error) {
|
|
461
|
+
await this.replyInteraction(interaction, "Command failed to execute.", true);
|
|
462
|
+
console.error(`Discord slash command error: ${String(error)}`);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
async replyInteraction(interaction, content, ephemeral) {
|
|
466
|
+
const payload = { content, ephemeral };
|
|
467
|
+
if (interaction.deferred || interaction.replied) {
|
|
468
|
+
await interaction.followUp(payload);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
await interaction.reply(payload);
|
|
472
|
+
}
|
|
473
|
+
async registerSlashCommands() {
|
|
474
|
+
if (!this.client || !this.commandRegistry) {
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
const appId = this.client.application?.id ?? this.client.user?.id;
|
|
478
|
+
if (!appId) {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
const commands = this.buildSlashCommandPayloads();
|
|
482
|
+
if (!commands.length) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
const rest = new REST({ version: "10" }).setToken(this.config.token);
|
|
486
|
+
let guildIds = [];
|
|
487
|
+
try {
|
|
488
|
+
const guilds = await this.client.guilds.fetch();
|
|
489
|
+
guildIds = [...guilds.keys()];
|
|
490
|
+
} catch (error) {
|
|
491
|
+
console.error(`Failed to fetch Discord guild list: ${String(error)}`);
|
|
492
|
+
}
|
|
493
|
+
try {
|
|
494
|
+
if (guildIds.length > 0 && guildIds.length <= SLASH_GUILD_THRESHOLD) {
|
|
495
|
+
for (const guildId of guildIds) {
|
|
496
|
+
await rest.put(Routes.applicationGuildCommands(appId, guildId), { body: commands });
|
|
497
|
+
}
|
|
498
|
+
console.log(`Discord slash commands registered for ${guildIds.length} guild(s).`);
|
|
499
|
+
} else {
|
|
500
|
+
await rest.put(Routes.applicationCommands(appId), { body: commands });
|
|
501
|
+
console.log("Discord slash commands registered globally.");
|
|
502
|
+
}
|
|
503
|
+
} catch (error) {
|
|
504
|
+
console.error(`Failed to register Discord slash commands: ${String(error)}`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
buildSlashCommandPayloads() {
|
|
508
|
+
const specs = this.commandRegistry?.listSlashCommands() ?? [];
|
|
509
|
+
return specs.map((spec) => ({
|
|
510
|
+
name: spec.name,
|
|
511
|
+
description: spec.description,
|
|
512
|
+
options: mapCommandOptions(spec.options)
|
|
513
|
+
}));
|
|
514
|
+
}
|
|
412
515
|
resolveProxyAgent() {
|
|
413
516
|
const proxy = this.config.proxy?.trim();
|
|
414
517
|
if (!proxy) {
|
|
@@ -610,6 +713,28 @@ var DiscordChannel = class extends BaseChannel {
|
|
|
610
713
|
this.typingController.stop(channelId);
|
|
611
714
|
}
|
|
612
715
|
};
|
|
716
|
+
function mapCommandOptions(options) {
|
|
717
|
+
if (!options || options.length === 0) {
|
|
718
|
+
return void 0;
|
|
719
|
+
}
|
|
720
|
+
return options.map((option) => ({
|
|
721
|
+
name: option.name,
|
|
722
|
+
description: option.description,
|
|
723
|
+
type: mapCommandOptionType(option.type),
|
|
724
|
+
required: option.required ?? false
|
|
725
|
+
}));
|
|
726
|
+
}
|
|
727
|
+
function mapCommandOptionType(type) {
|
|
728
|
+
switch (type) {
|
|
729
|
+
case "boolean":
|
|
730
|
+
return ApplicationCommandOptionType.Boolean;
|
|
731
|
+
case "number":
|
|
732
|
+
return ApplicationCommandOptionType.Number;
|
|
733
|
+
case "string":
|
|
734
|
+
default:
|
|
735
|
+
return ApplicationCommandOptionType.String;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
613
738
|
function sanitizeAttachmentName(name) {
|
|
614
739
|
return name.replace(/[\\/:*?"<>|]/g, "_");
|
|
615
740
|
}
|
|
@@ -3453,7 +3578,7 @@ var BUILTIN_CHANNEL_RUNTIMES = {
|
|
|
3453
3578
|
discord: {
|
|
3454
3579
|
id: "discord",
|
|
3455
3580
|
isEnabled: (config) => config.channels.discord.enabled,
|
|
3456
|
-
createChannel: (context) => new DiscordChannel(context.config.channels.discord, context.bus)
|
|
3581
|
+
createChannel: (context) => new DiscordChannel(context.config.channels.discord, context.bus, context.sessionManager, context.config)
|
|
3457
3582
|
},
|
|
3458
3583
|
feishu: {
|
|
3459
3584
|
id: "feishu",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/channel-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Runtime implementations for NextClaw builtin channel plugins.",
|
|
6
6
|
"type": "module",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
],
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@larksuiteoapi/node-sdk": "^1.58.0",
|
|
18
|
-
"@nextclaw/core": "^0.6.
|
|
18
|
+
"@nextclaw/core": "^0.6.32",
|
|
19
19
|
"@slack/socket-mode": "^1.3.3",
|
|
20
20
|
"@slack/web-api": "^7.6.0",
|
|
21
21
|
"dingtalk-stream": "^2.1.4",
|