@nextclaw/channel-runtime 0.1.7 → 0.1.9
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 +11 -0
- package/dist/index.js +212 -14
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ declare abstract class BaseChannel<TConfig extends Record<string, unknown>> {
|
|
|
30
30
|
abstract start(): Promise<void>;
|
|
31
31
|
abstract stop(): Promise<void>;
|
|
32
32
|
abstract send(msg: OutboundMessage): Promise<void>;
|
|
33
|
+
handleControlMessage(_msg: OutboundMessage): Promise<boolean>;
|
|
33
34
|
isAllowed(senderId: string): boolean;
|
|
34
35
|
protected handleMessage(params: {
|
|
35
36
|
senderId: string;
|
|
@@ -61,9 +62,13 @@ declare class DiscordChannel extends BaseChannel<Config["channels"]["discord"]>
|
|
|
61
62
|
constructor(config: Config["channels"]["discord"], bus: MessageBus);
|
|
62
63
|
start(): Promise<void>;
|
|
63
64
|
stop(): Promise<void>;
|
|
65
|
+
handleControlMessage(msg: OutboundMessage): Promise<boolean>;
|
|
64
66
|
send(msg: OutboundMessage): Promise<void>;
|
|
65
67
|
private handleIncoming;
|
|
66
68
|
private resolveProxyAgent;
|
|
69
|
+
private resolveAccountId;
|
|
70
|
+
private isAllowedByPolicy;
|
|
71
|
+
private resolveMentionState;
|
|
67
72
|
private resolveInboundAttachment;
|
|
68
73
|
private startTyping;
|
|
69
74
|
private stopTyping;
|
|
@@ -192,16 +197,22 @@ declare class TelegramChannel extends BaseChannel<Config["channels"]["telegram"]
|
|
|
192
197
|
private sessionManager?;
|
|
193
198
|
name: string;
|
|
194
199
|
private bot;
|
|
200
|
+
private botUserId;
|
|
201
|
+
private botUsername;
|
|
195
202
|
private readonly typingController;
|
|
196
203
|
private transcriber;
|
|
197
204
|
constructor(config: Config["channels"]["telegram"], bus: MessageBus, groqApiKey?: string, sessionManager?: SessionManager | undefined);
|
|
198
205
|
start(): Promise<void>;
|
|
199
206
|
stop(): Promise<void>;
|
|
207
|
+
handleControlMessage(msg: OutboundMessage): Promise<boolean>;
|
|
200
208
|
send(msg: OutboundMessage): Promise<void>;
|
|
201
209
|
private handleIncoming;
|
|
202
210
|
private dispatchToBus;
|
|
203
211
|
private startTyping;
|
|
204
212
|
private stopTyping;
|
|
213
|
+
private resolveAccountId;
|
|
214
|
+
private isAllowedByPolicy;
|
|
215
|
+
private resolveMentionState;
|
|
205
216
|
}
|
|
206
217
|
|
|
207
218
|
declare class WeComChannel extends BaseChannel<Config["channels"]["wecom"]> {
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,9 @@ var BaseChannel = class {
|
|
|
5
5
|
this.bus = bus;
|
|
6
6
|
}
|
|
7
7
|
running = false;
|
|
8
|
+
async handleControlMessage(_msg) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
8
11
|
isAllowed(senderId) {
|
|
9
12
|
const allowList = this.config.allowFrom ?? [];
|
|
10
13
|
if (!allowList.length) {
|
|
@@ -224,10 +227,11 @@ var ChannelTypingController = class {
|
|
|
224
227
|
};
|
|
225
228
|
|
|
226
229
|
// src/channels/discord.ts
|
|
230
|
+
import { isTypingStopControlMessage } from "@nextclaw/core";
|
|
227
231
|
var DEFAULT_MEDIA_MAX_MB = 8;
|
|
228
232
|
var MEDIA_FETCH_TIMEOUT_MS = 15e3;
|
|
229
|
-
var TYPING_HEARTBEAT_MS =
|
|
230
|
-
var TYPING_AUTO_STOP_MS =
|
|
233
|
+
var TYPING_HEARTBEAT_MS = 6e3;
|
|
234
|
+
var TYPING_AUTO_STOP_MS = 12e4;
|
|
231
235
|
var DISCORD_TEXT_LIMIT = 2e3;
|
|
232
236
|
var DISCORD_MAX_LINES_PER_MESSAGE = 17;
|
|
233
237
|
var FENCE_RE = /^( {0,3})(`{3,}|~{3,})(.*)$/;
|
|
@@ -278,7 +282,18 @@ var DiscordChannel = class extends BaseChannel {
|
|
|
278
282
|
this.client = null;
|
|
279
283
|
}
|
|
280
284
|
}
|
|
285
|
+
async handleControlMessage(msg) {
|
|
286
|
+
if (!isTypingStopControlMessage(msg)) {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
this.stopTyping(msg.chatId);
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
281
292
|
async send(msg) {
|
|
293
|
+
if (isTypingStopControlMessage(msg)) {
|
|
294
|
+
this.stopTyping(msg.chatId);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
282
297
|
if (!this.client) {
|
|
283
298
|
return;
|
|
284
299
|
}
|
|
@@ -316,7 +331,12 @@ var DiscordChannel = class extends BaseChannel {
|
|
|
316
331
|
}
|
|
317
332
|
const senderId = message.author.id;
|
|
318
333
|
const channelId = message.channelId;
|
|
319
|
-
|
|
334
|
+
const isGroup = Boolean(message.guildId);
|
|
335
|
+
if (!this.isAllowedByPolicy({ senderId, channelId, isGroup })) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const mentionState = this.resolveMentionState({ message, selfUserId, channelId, isGroup });
|
|
339
|
+
if (mentionState.requireMention && !mentionState.wasMentioned) {
|
|
320
340
|
return;
|
|
321
341
|
}
|
|
322
342
|
const contentParts = [];
|
|
@@ -358,13 +378,22 @@ var DiscordChannel = class extends BaseChannel {
|
|
|
358
378
|
attachments,
|
|
359
379
|
metadata: {
|
|
360
380
|
message_id: message.id,
|
|
381
|
+
channel_id: channelId,
|
|
361
382
|
guild_id: message.guildId,
|
|
362
383
|
reply_to: replyTo,
|
|
384
|
+
account_id: this.resolveAccountId(),
|
|
385
|
+
accountId: this.resolveAccountId(),
|
|
386
|
+
is_group: isGroup,
|
|
387
|
+
peer_kind: isGroup ? "channel" : "direct",
|
|
388
|
+
peer_id: isGroup ? channelId : senderId,
|
|
389
|
+
was_mentioned: mentionState.wasMentioned,
|
|
390
|
+
require_mention: mentionState.requireMention,
|
|
363
391
|
...attachmentIssues.length ? { attachment_issues: attachmentIssues } : {}
|
|
364
392
|
}
|
|
365
393
|
});
|
|
366
|
-
}
|
|
394
|
+
} catch (error) {
|
|
367
395
|
this.stopTyping(channelId);
|
|
396
|
+
throw error;
|
|
368
397
|
}
|
|
369
398
|
}
|
|
370
399
|
resolveProxyAgent() {
|
|
@@ -378,6 +407,62 @@ var DiscordChannel = class extends BaseChannel {
|
|
|
378
407
|
return null;
|
|
379
408
|
}
|
|
380
409
|
}
|
|
410
|
+
resolveAccountId() {
|
|
411
|
+
const accountId = this.config.accountId?.trim();
|
|
412
|
+
return accountId || "default";
|
|
413
|
+
}
|
|
414
|
+
isAllowedByPolicy(params) {
|
|
415
|
+
if (!params.isGroup) {
|
|
416
|
+
if (this.config.dmPolicy === "disabled") {
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
const allowFrom = this.config.allowFrom ?? [];
|
|
420
|
+
if (this.config.dmPolicy === "allowlist" || this.config.dmPolicy === "pairing") {
|
|
421
|
+
return this.isAllowed(params.senderId);
|
|
422
|
+
}
|
|
423
|
+
if (allowFrom.includes("*")) {
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
return allowFrom.length === 0 ? true : this.isAllowed(params.senderId);
|
|
427
|
+
}
|
|
428
|
+
if (this.config.groupPolicy === "disabled") {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
if (this.config.groupPolicy === "allowlist") {
|
|
432
|
+
const allowFrom = this.config.groupAllowFrom ?? [];
|
|
433
|
+
return allowFrom.includes("*") || allowFrom.includes(params.channelId);
|
|
434
|
+
}
|
|
435
|
+
return true;
|
|
436
|
+
}
|
|
437
|
+
resolveMentionState(params) {
|
|
438
|
+
if (!params.isGroup) {
|
|
439
|
+
return { wasMentioned: false, requireMention: false };
|
|
440
|
+
}
|
|
441
|
+
const groups = this.config.groups ?? {};
|
|
442
|
+
const groupRule = groups[params.channelId] ?? groups["*"];
|
|
443
|
+
const requireMention = groupRule?.requireMention ?? this.config.requireMention ?? false;
|
|
444
|
+
if (!requireMention) {
|
|
445
|
+
return { wasMentioned: false, requireMention: false };
|
|
446
|
+
}
|
|
447
|
+
const patterns = [
|
|
448
|
+
...this.config.mentionPatterns ?? [],
|
|
449
|
+
...groupRule?.mentionPatterns ?? []
|
|
450
|
+
].map((pattern) => pattern.trim()).filter(Boolean);
|
|
451
|
+
const content = params.message.content ?? "";
|
|
452
|
+
const wasMentionedByUserRef = Boolean(params.selfUserId) && params.message.mentions.users.has(params.selfUserId ?? "");
|
|
453
|
+
const wasMentionedByText = Boolean(params.selfUserId) && (content.includes(`<@${params.selfUserId}>`) || content.includes(`<@!${params.selfUserId}>`));
|
|
454
|
+
const wasMentionedByPattern = patterns.some((pattern) => {
|
|
455
|
+
try {
|
|
456
|
+
return new RegExp(pattern, "i").test(content);
|
|
457
|
+
} catch {
|
|
458
|
+
return content.toLowerCase().includes(pattern.toLowerCase());
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
return {
|
|
462
|
+
wasMentioned: wasMentionedByUserRef || wasMentionedByText || wasMentionedByPattern,
|
|
463
|
+
requireMention
|
|
464
|
+
};
|
|
465
|
+
}
|
|
381
466
|
async resolveInboundAttachment(params) {
|
|
382
467
|
const { attachment, mediaDir, maxBytes, proxy } = params;
|
|
383
468
|
const id = attachment.id;
|
|
@@ -2373,8 +2458,9 @@ var GroqTranscriptionProvider = class {
|
|
|
2373
2458
|
// src/channels/telegram.ts
|
|
2374
2459
|
import { join as join3 } from "path";
|
|
2375
2460
|
import { mkdirSync as mkdirSync3 } from "fs";
|
|
2376
|
-
|
|
2377
|
-
var
|
|
2461
|
+
import { isTypingStopControlMessage as isTypingStopControlMessage2 } from "@nextclaw/core";
|
|
2462
|
+
var TYPING_HEARTBEAT_MS2 = 6e3;
|
|
2463
|
+
var TYPING_AUTO_STOP_MS2 = 12e4;
|
|
2378
2464
|
var BOT_COMMANDS = [
|
|
2379
2465
|
{ command: "start", description: "Start the bot" },
|
|
2380
2466
|
{ command: "reset", description: "Reset conversation history" },
|
|
@@ -2395,6 +2481,8 @@ var TelegramChannel = class extends BaseChannel {
|
|
|
2395
2481
|
}
|
|
2396
2482
|
name = "telegram";
|
|
2397
2483
|
bot = null;
|
|
2484
|
+
botUserId = null;
|
|
2485
|
+
botUsername = null;
|
|
2398
2486
|
typingController;
|
|
2399
2487
|
transcriber;
|
|
2400
2488
|
async start() {
|
|
@@ -2407,6 +2495,14 @@ var TelegramChannel = class extends BaseChannel {
|
|
|
2407
2495
|
options.request = { proxy: this.config.proxy };
|
|
2408
2496
|
}
|
|
2409
2497
|
this.bot = new TelegramBot(this.config.token, options);
|
|
2498
|
+
try {
|
|
2499
|
+
const me = await this.bot.getMe();
|
|
2500
|
+
this.botUserId = me.id;
|
|
2501
|
+
this.botUsername = me.username ?? null;
|
|
2502
|
+
} catch {
|
|
2503
|
+
this.botUserId = null;
|
|
2504
|
+
this.botUsername = null;
|
|
2505
|
+
}
|
|
2410
2506
|
this.bot.onText(/^\/start$/, async (msg) => {
|
|
2411
2507
|
await this.bot?.sendMessage(
|
|
2412
2508
|
msg.chat.id,
|
|
@@ -2432,12 +2528,31 @@ Just send me a text message to chat!`;
|
|
|
2432
2528
|
await this.bot?.sendMessage(msg.chat.id, "\u26A0\uFE0F Session management is not available.");
|
|
2433
2529
|
return;
|
|
2434
2530
|
}
|
|
2435
|
-
const
|
|
2436
|
-
const
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2531
|
+
const accountId = this.resolveAccountId();
|
|
2532
|
+
const candidates = this.sessionManager.listSessions().filter((entry) => {
|
|
2533
|
+
const metadata = entry.metadata ?? {};
|
|
2534
|
+
const lastChannel = typeof metadata.last_channel === "string" ? metadata.last_channel : "";
|
|
2535
|
+
const lastTo = typeof metadata.last_to === "string" ? metadata.last_to : "";
|
|
2536
|
+
const lastAccountId = typeof metadata.last_account_id === "string" ? metadata.last_account_id : typeof metadata.last_accountId === "string" ? metadata.last_accountId : "default";
|
|
2537
|
+
return lastChannel === this.name && lastTo === chatId && lastAccountId === accountId;
|
|
2538
|
+
}).map((entry) => String(entry.key ?? "")).filter(Boolean);
|
|
2539
|
+
let totalCleared = 0;
|
|
2540
|
+
for (const key of candidates) {
|
|
2541
|
+
const session = this.sessionManager.getIfExists(key);
|
|
2542
|
+
if (!session) {
|
|
2543
|
+
continue;
|
|
2544
|
+
}
|
|
2545
|
+
totalCleared += session.messages.length;
|
|
2546
|
+
this.sessionManager.clear(session);
|
|
2547
|
+
this.sessionManager.save(session);
|
|
2548
|
+
}
|
|
2549
|
+
if (candidates.length === 0) {
|
|
2550
|
+
const legacySession = this.sessionManager.getOrCreate(`${this.name}:${chatId}`);
|
|
2551
|
+
totalCleared = legacySession.messages.length;
|
|
2552
|
+
this.sessionManager.clear(legacySession);
|
|
2553
|
+
this.sessionManager.save(legacySession);
|
|
2554
|
+
}
|
|
2555
|
+
await this.bot?.sendMessage(msg.chat.id, `\u{1F504} Conversation history cleared (${totalCleared} messages).`);
|
|
2441
2556
|
});
|
|
2442
2557
|
this.bot.on("message", async (msg) => {
|
|
2443
2558
|
if (!msg.text && !msg.caption && !msg.photo && !msg.voice && !msg.audio && !msg.document) {
|
|
@@ -2467,7 +2582,18 @@ Just send me a text message to chat!`;
|
|
|
2467
2582
|
this.bot = null;
|
|
2468
2583
|
}
|
|
2469
2584
|
}
|
|
2585
|
+
async handleControlMessage(msg) {
|
|
2586
|
+
if (!isTypingStopControlMessage2(msg)) {
|
|
2587
|
+
return false;
|
|
2588
|
+
}
|
|
2589
|
+
this.stopTyping(msg.chatId);
|
|
2590
|
+
return true;
|
|
2591
|
+
}
|
|
2470
2592
|
async send(msg) {
|
|
2593
|
+
if (isTypingStopControlMessage2(msg)) {
|
|
2594
|
+
this.stopTyping(msg.chatId);
|
|
2595
|
+
return;
|
|
2596
|
+
}
|
|
2471
2597
|
if (!this.bot) {
|
|
2472
2598
|
return;
|
|
2473
2599
|
}
|
|
@@ -2498,6 +2624,14 @@ Just send me a text message to chat!`;
|
|
|
2498
2624
|
return;
|
|
2499
2625
|
}
|
|
2500
2626
|
const chatId = String(message.chat.id);
|
|
2627
|
+
const isGroup = message.chat.type !== "private";
|
|
2628
|
+
if (!this.isAllowedByPolicy({ senderId: String(sender.id), chatId, isGroup })) {
|
|
2629
|
+
return;
|
|
2630
|
+
}
|
|
2631
|
+
const mentionState = this.resolveMentionState({ message, chatId, isGroup });
|
|
2632
|
+
if (mentionState.requireMention && !mentionState.wasMentioned) {
|
|
2633
|
+
return;
|
|
2634
|
+
}
|
|
2501
2635
|
let senderId = String(sender.id);
|
|
2502
2636
|
if (sender.username) {
|
|
2503
2637
|
senderId = `${senderId}|${sender.username}`;
|
|
@@ -2546,10 +2680,17 @@ Just send me a text message to chat!`;
|
|
|
2546
2680
|
first_name: sender.firstName,
|
|
2547
2681
|
sender_type: sender.type,
|
|
2548
2682
|
is_bot: sender.isBot,
|
|
2549
|
-
is_group:
|
|
2683
|
+
is_group: isGroup,
|
|
2684
|
+
account_id: this.resolveAccountId(),
|
|
2685
|
+
accountId: this.resolveAccountId(),
|
|
2686
|
+
peer_kind: isGroup ? "group" : "direct",
|
|
2687
|
+
peer_id: isGroup ? chatId : String(sender.id),
|
|
2688
|
+
was_mentioned: mentionState.wasMentioned,
|
|
2689
|
+
require_mention: mentionState.requireMention
|
|
2550
2690
|
});
|
|
2551
|
-
}
|
|
2691
|
+
} catch (error) {
|
|
2552
2692
|
this.stopTyping(chatId);
|
|
2693
|
+
throw error;
|
|
2553
2694
|
}
|
|
2554
2695
|
}
|
|
2555
2696
|
async dispatchToBus(senderId, chatId, content, attachments, metadata) {
|
|
@@ -2561,6 +2702,63 @@ Just send me a text message to chat!`;
|
|
|
2561
2702
|
stopTyping(chatId) {
|
|
2562
2703
|
this.typingController.stop(chatId);
|
|
2563
2704
|
}
|
|
2705
|
+
resolveAccountId() {
|
|
2706
|
+
const accountId = this.config.accountId?.trim();
|
|
2707
|
+
return accountId || "default";
|
|
2708
|
+
}
|
|
2709
|
+
isAllowedByPolicy(params) {
|
|
2710
|
+
if (!params.isGroup) {
|
|
2711
|
+
if (this.config.dmPolicy === "disabled") {
|
|
2712
|
+
return false;
|
|
2713
|
+
}
|
|
2714
|
+
const allowFrom = this.config.allowFrom ?? [];
|
|
2715
|
+
if (this.config.dmPolicy === "allowlist" || this.config.dmPolicy === "pairing") {
|
|
2716
|
+
return this.isAllowed(params.senderId);
|
|
2717
|
+
}
|
|
2718
|
+
if (allowFrom.includes("*")) {
|
|
2719
|
+
return true;
|
|
2720
|
+
}
|
|
2721
|
+
return allowFrom.length === 0 ? true : this.isAllowed(params.senderId);
|
|
2722
|
+
}
|
|
2723
|
+
if (this.config.groupPolicy === "disabled") {
|
|
2724
|
+
return false;
|
|
2725
|
+
}
|
|
2726
|
+
if (this.config.groupPolicy === "allowlist") {
|
|
2727
|
+
const allowFrom = this.config.groupAllowFrom ?? [];
|
|
2728
|
+
return allowFrom.includes("*") || allowFrom.includes(params.chatId);
|
|
2729
|
+
}
|
|
2730
|
+
return true;
|
|
2731
|
+
}
|
|
2732
|
+
resolveMentionState(params) {
|
|
2733
|
+
if (!params.isGroup) {
|
|
2734
|
+
return { wasMentioned: false, requireMention: false };
|
|
2735
|
+
}
|
|
2736
|
+
const groups = this.config.groups ?? {};
|
|
2737
|
+
const groupRule = groups[params.chatId] ?? groups["*"];
|
|
2738
|
+
const requireMention = groupRule?.requireMention ?? this.config.requireMention ?? false;
|
|
2739
|
+
if (!requireMention) {
|
|
2740
|
+
return { wasMentioned: false, requireMention: false };
|
|
2741
|
+
}
|
|
2742
|
+
const content = `${params.message.text ?? ""}
|
|
2743
|
+
${params.message.caption ?? ""}`.trim();
|
|
2744
|
+
const patterns = [
|
|
2745
|
+
...this.config.mentionPatterns ?? [],
|
|
2746
|
+
...groupRule?.mentionPatterns ?? []
|
|
2747
|
+
].map((pattern) => pattern.trim()).filter(Boolean);
|
|
2748
|
+
const usernameMentioned = this.botUsername ? content.includes(`@${this.botUsername}`) : false;
|
|
2749
|
+
const replyToBot = Boolean(this.botUserId) && Boolean(params.message.reply_to_message?.from) && params.message.reply_to_message?.from?.id === this.botUserId;
|
|
2750
|
+
const patternMentioned = patterns.some((pattern) => {
|
|
2751
|
+
try {
|
|
2752
|
+
return new RegExp(pattern, "i").test(content);
|
|
2753
|
+
} catch {
|
|
2754
|
+
return content.toLowerCase().includes(pattern.toLowerCase());
|
|
2755
|
+
}
|
|
2756
|
+
});
|
|
2757
|
+
return {
|
|
2758
|
+
wasMentioned: usernameMentioned || replyToBot || patternMentioned,
|
|
2759
|
+
requireMention
|
|
2760
|
+
};
|
|
2761
|
+
}
|
|
2564
2762
|
};
|
|
2565
2763
|
function resolveSender(message) {
|
|
2566
2764
|
if (message.from) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/channel-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
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.23",
|
|
19
19
|
"@slack/socket-mode": "^1.3.3",
|
|
20
20
|
"@slack/web-api": "^7.6.0",
|
|
21
21
|
"dingtalk-stream": "^2.1.4",
|