@nextclaw/channel-runtime 0.1.7 → 0.1.8

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 CHANGED
@@ -64,6 +64,9 @@ declare class DiscordChannel extends BaseChannel<Config["channels"]["discord"]>
64
64
  send(msg: OutboundMessage): Promise<void>;
65
65
  private handleIncoming;
66
66
  private resolveProxyAgent;
67
+ private resolveAccountId;
68
+ private isAllowedByPolicy;
69
+ private resolveMentionState;
67
70
  private resolveInboundAttachment;
68
71
  private startTyping;
69
72
  private stopTyping;
@@ -192,6 +195,8 @@ declare class TelegramChannel extends BaseChannel<Config["channels"]["telegram"]
192
195
  private sessionManager?;
193
196
  name: string;
194
197
  private bot;
198
+ private botUserId;
199
+ private botUsername;
195
200
  private readonly typingController;
196
201
  private transcriber;
197
202
  constructor(config: Config["channels"]["telegram"], bus: MessageBus, groqApiKey?: string, sessionManager?: SessionManager | undefined);
@@ -202,6 +207,9 @@ declare class TelegramChannel extends BaseChannel<Config["channels"]["telegram"]
202
207
  private dispatchToBus;
203
208
  private startTyping;
204
209
  private stopTyping;
210
+ private resolveAccountId;
211
+ private isAllowedByPolicy;
212
+ private resolveMentionState;
205
213
  }
206
214
 
207
215
  declare class WeComChannel extends BaseChannel<Config["channels"]["wecom"]> {
package/dist/index.js CHANGED
@@ -316,7 +316,12 @@ var DiscordChannel = class extends BaseChannel {
316
316
  }
317
317
  const senderId = message.author.id;
318
318
  const channelId = message.channelId;
319
- if (!this.isAllowed(senderId)) {
319
+ const isGroup = Boolean(message.guildId);
320
+ if (!this.isAllowedByPolicy({ senderId, channelId, isGroup })) {
321
+ return;
322
+ }
323
+ const mentionState = this.resolveMentionState({ message, selfUserId, channelId, isGroup });
324
+ if (mentionState.requireMention && !mentionState.wasMentioned) {
320
325
  return;
321
326
  }
322
327
  const contentParts = [];
@@ -358,8 +363,16 @@ var DiscordChannel = class extends BaseChannel {
358
363
  attachments,
359
364
  metadata: {
360
365
  message_id: message.id,
366
+ channel_id: channelId,
361
367
  guild_id: message.guildId,
362
368
  reply_to: replyTo,
369
+ account_id: this.resolveAccountId(),
370
+ accountId: this.resolveAccountId(),
371
+ is_group: isGroup,
372
+ peer_kind: isGroup ? "channel" : "direct",
373
+ peer_id: isGroup ? channelId : senderId,
374
+ was_mentioned: mentionState.wasMentioned,
375
+ require_mention: mentionState.requireMention,
363
376
  ...attachmentIssues.length ? { attachment_issues: attachmentIssues } : {}
364
377
  }
365
378
  });
@@ -378,6 +391,62 @@ var DiscordChannel = class extends BaseChannel {
378
391
  return null;
379
392
  }
380
393
  }
394
+ resolveAccountId() {
395
+ const accountId = this.config.accountId?.trim();
396
+ return accountId || "default";
397
+ }
398
+ isAllowedByPolicy(params) {
399
+ if (!params.isGroup) {
400
+ if (this.config.dmPolicy === "disabled") {
401
+ return false;
402
+ }
403
+ const allowFrom = this.config.allowFrom ?? [];
404
+ if (this.config.dmPolicy === "allowlist" || this.config.dmPolicy === "pairing") {
405
+ return this.isAllowed(params.senderId);
406
+ }
407
+ if (allowFrom.includes("*")) {
408
+ return true;
409
+ }
410
+ return allowFrom.length === 0 ? true : this.isAllowed(params.senderId);
411
+ }
412
+ if (this.config.groupPolicy === "disabled") {
413
+ return false;
414
+ }
415
+ if (this.config.groupPolicy === "allowlist") {
416
+ const allowFrom = this.config.groupAllowFrom ?? [];
417
+ return allowFrom.includes("*") || allowFrom.includes(params.channelId);
418
+ }
419
+ return true;
420
+ }
421
+ resolveMentionState(params) {
422
+ if (!params.isGroup) {
423
+ return { wasMentioned: false, requireMention: false };
424
+ }
425
+ const groups = this.config.groups ?? {};
426
+ const groupRule = groups[params.channelId] ?? groups["*"];
427
+ const requireMention = groupRule?.requireMention ?? this.config.requireMention ?? false;
428
+ if (!requireMention) {
429
+ return { wasMentioned: false, requireMention: false };
430
+ }
431
+ const patterns = [
432
+ ...this.config.mentionPatterns ?? [],
433
+ ...groupRule?.mentionPatterns ?? []
434
+ ].map((pattern) => pattern.trim()).filter(Boolean);
435
+ const content = params.message.content ?? "";
436
+ const wasMentionedByUserRef = Boolean(params.selfUserId) && params.message.mentions.users.has(params.selfUserId ?? "");
437
+ const wasMentionedByText = Boolean(params.selfUserId) && (content.includes(`<@${params.selfUserId}>`) || content.includes(`<@!${params.selfUserId}>`));
438
+ const wasMentionedByPattern = patterns.some((pattern) => {
439
+ try {
440
+ return new RegExp(pattern, "i").test(content);
441
+ } catch {
442
+ return content.toLowerCase().includes(pattern.toLowerCase());
443
+ }
444
+ });
445
+ return {
446
+ wasMentioned: wasMentionedByUserRef || wasMentionedByText || wasMentionedByPattern,
447
+ requireMention
448
+ };
449
+ }
381
450
  async resolveInboundAttachment(params) {
382
451
  const { attachment, mediaDir, maxBytes, proxy } = params;
383
452
  const id = attachment.id;
@@ -2395,6 +2464,8 @@ var TelegramChannel = class extends BaseChannel {
2395
2464
  }
2396
2465
  name = "telegram";
2397
2466
  bot = null;
2467
+ botUserId = null;
2468
+ botUsername = null;
2398
2469
  typingController;
2399
2470
  transcriber;
2400
2471
  async start() {
@@ -2407,6 +2478,14 @@ var TelegramChannel = class extends BaseChannel {
2407
2478
  options.request = { proxy: this.config.proxy };
2408
2479
  }
2409
2480
  this.bot = new TelegramBot(this.config.token, options);
2481
+ try {
2482
+ const me = await this.bot.getMe();
2483
+ this.botUserId = me.id;
2484
+ this.botUsername = me.username ?? null;
2485
+ } catch {
2486
+ this.botUserId = null;
2487
+ this.botUsername = null;
2488
+ }
2410
2489
  this.bot.onText(/^\/start$/, async (msg) => {
2411
2490
  await this.bot?.sendMessage(
2412
2491
  msg.chat.id,
@@ -2432,12 +2511,31 @@ Just send me a text message to chat!`;
2432
2511
  await this.bot?.sendMessage(msg.chat.id, "\u26A0\uFE0F Session management is not available.");
2433
2512
  return;
2434
2513
  }
2435
- const sessionKey = `${this.name}:${chatId}`;
2436
- const session = this.sessionManager.getOrCreate(sessionKey);
2437
- const count = session.messages.length;
2438
- this.sessionManager.clear(session);
2439
- this.sessionManager.save(session);
2440
- await this.bot?.sendMessage(msg.chat.id, `\u{1F504} Conversation history cleared (${count} messages).`);
2514
+ const accountId = this.resolveAccountId();
2515
+ const candidates = this.sessionManager.listSessions().filter((entry) => {
2516
+ const metadata = entry.metadata ?? {};
2517
+ const lastChannel = typeof metadata.last_channel === "string" ? metadata.last_channel : "";
2518
+ const lastTo = typeof metadata.last_to === "string" ? metadata.last_to : "";
2519
+ const lastAccountId = typeof metadata.last_account_id === "string" ? metadata.last_account_id : typeof metadata.last_accountId === "string" ? metadata.last_accountId : "default";
2520
+ return lastChannel === this.name && lastTo === chatId && lastAccountId === accountId;
2521
+ }).map((entry) => String(entry.key ?? "")).filter(Boolean);
2522
+ let totalCleared = 0;
2523
+ for (const key of candidates) {
2524
+ const session = this.sessionManager.getIfExists(key);
2525
+ if (!session) {
2526
+ continue;
2527
+ }
2528
+ totalCleared += session.messages.length;
2529
+ this.sessionManager.clear(session);
2530
+ this.sessionManager.save(session);
2531
+ }
2532
+ if (candidates.length === 0) {
2533
+ const legacySession = this.sessionManager.getOrCreate(`${this.name}:${chatId}`);
2534
+ totalCleared = legacySession.messages.length;
2535
+ this.sessionManager.clear(legacySession);
2536
+ this.sessionManager.save(legacySession);
2537
+ }
2538
+ await this.bot?.sendMessage(msg.chat.id, `\u{1F504} Conversation history cleared (${totalCleared} messages).`);
2441
2539
  });
2442
2540
  this.bot.on("message", async (msg) => {
2443
2541
  if (!msg.text && !msg.caption && !msg.photo && !msg.voice && !msg.audio && !msg.document) {
@@ -2498,6 +2596,14 @@ Just send me a text message to chat!`;
2498
2596
  return;
2499
2597
  }
2500
2598
  const chatId = String(message.chat.id);
2599
+ const isGroup = message.chat.type !== "private";
2600
+ if (!this.isAllowedByPolicy({ senderId: String(sender.id), chatId, isGroup })) {
2601
+ return;
2602
+ }
2603
+ const mentionState = this.resolveMentionState({ message, chatId, isGroup });
2604
+ if (mentionState.requireMention && !mentionState.wasMentioned) {
2605
+ return;
2606
+ }
2501
2607
  let senderId = String(sender.id);
2502
2608
  if (sender.username) {
2503
2609
  senderId = `${senderId}|${sender.username}`;
@@ -2546,7 +2652,13 @@ Just send me a text message to chat!`;
2546
2652
  first_name: sender.firstName,
2547
2653
  sender_type: sender.type,
2548
2654
  is_bot: sender.isBot,
2549
- is_group: message.chat.type !== "private"
2655
+ is_group: isGroup,
2656
+ account_id: this.resolveAccountId(),
2657
+ accountId: this.resolveAccountId(),
2658
+ peer_kind: isGroup ? "group" : "direct",
2659
+ peer_id: isGroup ? chatId : String(sender.id),
2660
+ was_mentioned: mentionState.wasMentioned,
2661
+ require_mention: mentionState.requireMention
2550
2662
  });
2551
2663
  } finally {
2552
2664
  this.stopTyping(chatId);
@@ -2561,6 +2673,63 @@ Just send me a text message to chat!`;
2561
2673
  stopTyping(chatId) {
2562
2674
  this.typingController.stop(chatId);
2563
2675
  }
2676
+ resolveAccountId() {
2677
+ const accountId = this.config.accountId?.trim();
2678
+ return accountId || "default";
2679
+ }
2680
+ isAllowedByPolicy(params) {
2681
+ if (!params.isGroup) {
2682
+ if (this.config.dmPolicy === "disabled") {
2683
+ return false;
2684
+ }
2685
+ const allowFrom = this.config.allowFrom ?? [];
2686
+ if (this.config.dmPolicy === "allowlist" || this.config.dmPolicy === "pairing") {
2687
+ return this.isAllowed(params.senderId);
2688
+ }
2689
+ if (allowFrom.includes("*")) {
2690
+ return true;
2691
+ }
2692
+ return allowFrom.length === 0 ? true : this.isAllowed(params.senderId);
2693
+ }
2694
+ if (this.config.groupPolicy === "disabled") {
2695
+ return false;
2696
+ }
2697
+ if (this.config.groupPolicy === "allowlist") {
2698
+ const allowFrom = this.config.groupAllowFrom ?? [];
2699
+ return allowFrom.includes("*") || allowFrom.includes(params.chatId);
2700
+ }
2701
+ return true;
2702
+ }
2703
+ resolveMentionState(params) {
2704
+ if (!params.isGroup) {
2705
+ return { wasMentioned: false, requireMention: false };
2706
+ }
2707
+ const groups = this.config.groups ?? {};
2708
+ const groupRule = groups[params.chatId] ?? groups["*"];
2709
+ const requireMention = groupRule?.requireMention ?? this.config.requireMention ?? false;
2710
+ if (!requireMention) {
2711
+ return { wasMentioned: false, requireMention: false };
2712
+ }
2713
+ const content = `${params.message.text ?? ""}
2714
+ ${params.message.caption ?? ""}`.trim();
2715
+ const patterns = [
2716
+ ...this.config.mentionPatterns ?? [],
2717
+ ...groupRule?.mentionPatterns ?? []
2718
+ ].map((pattern) => pattern.trim()).filter(Boolean);
2719
+ const usernameMentioned = this.botUsername ? content.includes(`@${this.botUsername}`) : false;
2720
+ const replyToBot = Boolean(this.botUserId) && Boolean(params.message.reply_to_message?.from) && params.message.reply_to_message?.from?.id === this.botUserId;
2721
+ const patternMentioned = patterns.some((pattern) => {
2722
+ try {
2723
+ return new RegExp(pattern, "i").test(content);
2724
+ } catch {
2725
+ return content.toLowerCase().includes(pattern.toLowerCase());
2726
+ }
2727
+ });
2728
+ return {
2729
+ wasMentioned: usernameMentioned || replyToBot || patternMentioned,
2730
+ requireMention
2731
+ };
2732
+ }
2564
2733
  };
2565
2734
  function resolveSender(message) {
2566
2735
  if (message.from) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/channel-runtime",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
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.21",
18
+ "@nextclaw/core": "^0.6.22",
19
19
  "@slack/socket-mode": "^1.3.3",
20
20
  "@slack/web-api": "^7.6.0",
21
21
  "dingtalk-stream": "^2.1.4",