You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by as...@apache.org on 2026/10/12 01:08:15 UTC

(maka) 01/04: fix(bots): apply channel sender settings before routing

This is an automated email from the ASF dual-hosted git repository.

Astro-Han pushed a commit to branch fix/channels-settings-roundtrip
in repository https://gitbox.apache.org/repos/asf/maka.git

commit e23717932f3344afefc70014406bfd1fe818a5a0
Author: AstroHan <le...@outlook.com>
AuthorDate: Sun Oct 11 22:28:27 2026 +1000

    fix(bots): apply channel sender settings before routing
    
    Generated-by: Codex
---
 .../bot-incoming-session-lifecycle.test.ts         | 21 ++++++++--
 .../src/bots/__tests__/base-adapter.test.ts        | 48 +++++++++++++++++-----
 .../src/bots/__tests__/bot-user-allowlist.test.ts  | 39 ------------------
 .../src/bots/__tests__/discord-bridge.test.ts      | 35 ++++++++++++++++
 .../src/bots/__tests__/im-channel-mapping.test.ts  | 39 ------------------
 packages/runtime/src/bots/base-adapter.ts          |  5 ++-
 packages/runtime/src/bots/dingtalk-bridge.ts       |  4 +-
 packages/runtime/src/bots/discord-bridge.ts        |  3 +-
 packages/runtime/src/bots/feishu-bridge.ts         | 19 ++-------
 packages/runtime/src/bots/qq-bridge.ts             |  3 +-
 packages/runtime/src/bots/telegram-bridge.ts       | 47 +++++++--------------
 packages/runtime/src/bots/wechat-bridge.ts         |  5 +--
 packages/runtime/src/bots/wecom-bridge.ts          | 12 ++----
 13 files changed, 121 insertions(+), 159 deletions(-)

diff --git a/apps/desktop/src/main/__tests__/bot-incoming-session-lifecycle.test.ts b/apps/desktop/src/main/__tests__/bot-incoming-session-lifecycle.test.ts
index e1594d270d..db37edaedf 100644
--- a/apps/desktop/src/main/__tests__/bot-incoming-session-lifecycle.test.ts
+++ b/apps/desktop/src/main/__tests__/bot-incoming-session-lifecycle.test.ts
@@ -19,7 +19,8 @@
 
 import assert from 'node:assert/strict';
 import { describe, test } from 'node:test';
-import type { BotIncomingMessage, BotRegistry } from '@maka/runtime/bots';
+import { BaseBotAdapter, type BotIncomingMessage, type BotRegistry } from '@maka/runtime/bots';
+import { createDefaultBotChannel } from '@maka/core/bot-chat-settings';
 import { createBotIncomingMainService } from '../bot-incoming-main.js';
 import { BotSessionUnavailableError } from '../bot-session-adapter.js';
 import { waitFor as pollFor } from '@maka/core/test-only/async-primitives';
@@ -31,6 +32,12 @@ async function waitFor(predicate: () => boolean): Promise<void> {
   });
 }
 
+class IncomingAdapter extends BaseBotAdapter {
+  async start(): Promise<void> {}
+  async stop(): Promise<void> {}
+  receive(message: BotIncomingMessage): void { this.emitIncomingMessage(message); }
+}
+
 describe('bot session lifecycle bindings', () => {
   test('rebinds a conversation after its archived session rejects a send', async () => {
     const created: string[] = [];
@@ -127,7 +134,7 @@ describe('bot session lifecycle bindings', () => {
   });
 });
 
-test('WorkHub routing bypasses Bot Session creation, explore preparation and reset commands', async () => {
+test('channel admission precedes WorkHub routing, Bot Session creation and reset commands', async () => {
   let routed = 0;
   let closed = false;
   const unexpected = async (): Promise<never> => { throw new Error('Bot Session must not be used'); };
@@ -139,7 +146,15 @@ test('WorkHub routing bypasses Bot Session creation, explore preparation and res
       async close() { closed = true; },
     },
   });
-  await service.handleBotIncomingMessage({ platform: 'qq', userId: 'user', userName: 'User', chatId: 'chat', isGroup: false, receivedAt: 1, text: '重置', sourceMessageId: 'source' });
+  const adapter = new IncomingAdapter('qq', { ...createDefaultBotChannel('qq'), allowedUserIds: ['user'] });
+  const pending: Promise<void>[] = [];
+  adapter.on('message', (message: BotIncomingMessage) => pending.push(service.handleBotIncomingMessage(message)));
+  const message: BotIncomingMessage = { platform: 'qq', userId: 'outsider', userName: 'User', chatId: 'chat', isGroup: false, receivedAt: 1, text: '重置', sourceMessageId: 'source' };
+  adapter.receive(message);
+  await Promise.all(pending);
+  assert.equal(routed, 0);
+  adapter.receive({ ...message, userId: 'user' });
+  await Promise.all(pending);
   assert.equal(routed, 1);
   await service.close();
   assert.equal(closed, true);
diff --git a/packages/runtime/src/bots/__tests__/base-adapter.test.ts b/packages/runtime/src/bots/__tests__/base-adapter.test.ts
index b2a4afb9b6..51b1d08cdc 100644
--- a/packages/runtime/src/bots/__tests__/base-adapter.test.ts
+++ b/packages/runtime/src/bots/__tests__/base-adapter.test.ts
@@ -19,7 +19,7 @@
 
 import assert from 'node:assert/strict';
 import { describe, test } from 'node:test';
-import { createDefaultBotChannel } from '@maka/core/settings';
+import { BOT_PROVIDERS, createDefaultBotChannel } from '@maka/core/settings';
 import {
   BaseBotAdapter,
   botReadinessFromSettings,
@@ -77,15 +77,43 @@ describe('BaseBotAdapter', () => {
     assert.equal(adapter.getStatus().readiness, 'configured');
   });
 
-  test('does NOT restart when only allowedUserIds changes (runtime filter, not connection parameter)', () => {
-    const base = createDefaultBotChannel('telegram');
-    assert.equal(
-      botSettingsRequireRestart(
-        { ...base, allowedUserIds: ['123'] },
-        { ...base, allowedUserIds: undefined },
-      ),
-      false,
-    );
+  test('admits only listed senders in every channel and applies policy updates without restarting', () => {
+    for (const platform of BOT_PROVIDERS) {
+      const settings = { ...createDefaultBotChannel(platform), allowedUserIds: ['owner'] };
+      const adapter = new TestAdapter(platform, settings);
+      const messages: BotIncomingMessage[] = [];
+      adapter.on('message', (message) => messages.push(message));
+      const incoming = (userId: string, receivedAt: number): BotIncomingMessage => ({
+        platform,
+        userId,
+        userName: 'owner',
+        chatId: 'owner',
+        isGroup: true,
+        text: 'hello',
+        sourceMessageId: String(receivedAt),
+        receivedAt,
+      });
+      const owner = incoming('owner', 1);
+      adapter.publish(owner);
+      for (const userId of ['outsider', 'owner-extra', '']) adapter.publish(incoming(userId, 2));
+      assert.deepEqual(messages, [owner], platform);
+      assert.equal(adapter.getStatus().lastEventAt, 1, platform);
+
+      assert.deepEqual(adapter.updateSettings({ ...settings, allowedUserIds: ['outsider'] }), {
+        needsRestart: false,
+      });
+      adapter.publish(incoming('owner', 3));
+      const nextOwner = incoming('outsider', 4);
+      adapter.publish(nextOwner);
+      assert.deepEqual(messages, [owner, nextOwner], platform);
+
+      for (const allowedUserIds of [[], undefined]) {
+        adapter.updateSettings({ ...settings, allowedUserIds });
+        const unrestricted = incoming('any-user', messages.length + 5);
+        adapter.publish(unrestricted);
+        assert.equal(messages.at(-1), unrestricted, platform);
+      }
+    }
   });
 
   test('derives readiness only from current credential facts', () => {
diff --git a/packages/runtime/src/bots/__tests__/bot-user-allowlist.test.ts b/packages/runtime/src/bots/__tests__/bot-user-allowlist.test.ts
deleted file mode 100644
index 1f6cc8d2cc..0000000000
--- a/packages/runtime/src/bots/__tests__/bot-user-allowlist.test.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { strict as assert } from 'node:assert';
-import { describe, it } from 'node:test';
-
-import { __TEST__ } from '../telegram-bridge.js';
-
-const { isAllowedUser } = __TEST__;
-
-describe('isAllowedUser', () => {
-  it('defaults open and otherwise requires an exact user-id match', () => {
-    const cases: Array<[string[] | undefined, string, boolean]> = [
-      [undefined, '12345', true],
-      [[], '12345', true],
-      [['12345', '67890'], '67890', true],
-      [['1234567890'], '123', false],
-    ];
-    for (const [allowlist, userId, expected] of cases) {
-      assert.equal(isAllowedUser(allowlist, userId), expected, `${allowlist}:${userId}`);
-    }
-  });
-});
diff --git a/packages/runtime/src/bots/__tests__/discord-bridge.test.ts b/packages/runtime/src/bots/__tests__/discord-bridge.test.ts
index d7579ef3f1..fdc97c0dca 100644
--- a/packages/runtime/src/bots/__tests__/discord-bridge.test.ts
+++ b/packages/runtime/src/bots/__tests__/discord-bridge.test.ts
@@ -20,6 +20,7 @@
 import { strict as assert } from 'node:assert';
 import { describe, it } from 'node:test';
 import { createDefaultBotChannel } from '@maka/core/settings';
+import type { BotIncomingMessage } from '../types.js';
 
 import { DiscordBotBridge, __TEST__ } from '../discord-bridge.js';
 
@@ -48,6 +49,40 @@ class ResetTrackingDiscordBridge extends DiscordBotBridge {
 }
 
 describe('resume gateway url lifecycle', () => {
+  it('drops unlisted gateway senders in direct and group messages', () => {
+    const settings = { ...createDefaultBotChannel('discord'), allowedUserIds: ['owner'] };
+    const bridge = new ResetTrackingDiscordBridge('discord', settings);
+    const messages: BotIncomingMessage[] = [];
+    const statuses: unknown[] = [];
+    bridge.on('message', (message) => messages.push(message));
+    bridge.on('statusChange', (status) => statuses.push(status));
+    for (const guild_id of [undefined, 'guild']) {
+      for (const userId of ['outsider', 'owner']) {
+        bridge.receive(
+          JSON.stringify({
+            op: 0,
+            t: 'MESSAGE_CREATE',
+            d: {
+              id: `${guild_id}:${userId}`,
+              channel_id: 'chat',
+              guild_id,
+              content: 'hello',
+              author: { id: userId, username: 'owner' },
+            },
+          }),
+        );
+      }
+    }
+    assert.deepEqual(
+      messages.map((message) => [message.userId, message.isGroup]),
+      [
+        ['owner', false],
+        ['owner', true],
+      ],
+    );
+    assert.equal(statuses.length, 2);
+  });
+
   it('clears resumeGatewayUrl when the session is dropped', () => {
     const bridge = new ResetTrackingDiscordBridge('discord', {
       ...createDefaultBotChannel('discord'),
diff --git a/packages/runtime/src/bots/__tests__/im-channel-mapping.test.ts b/packages/runtime/src/bots/__tests__/im-channel-mapping.test.ts
index f03adfe7f7..db6cfb6478 100644
--- a/packages/runtime/src/bots/__tests__/im-channel-mapping.test.ts
+++ b/packages/runtime/src/bots/__tests__/im-channel-mapping.test.ts
@@ -55,29 +55,6 @@ describe('IM channel event mapping', () => {
     assert.equal(JSON.stringify(event).includes('must-not-cross'), false);
   });
 
-  it('drops Feishu group messages outside the local allowlist', () => {
-    // PR1197 review: the Lark SDK dmAllowlist is DM-only, so a group message
-    // from an unauthorized sender must be dropped by the bridge's own local
-    // allowlist. Empty/absent list allows all (asserted implicitly by the
-    // mapping test above, which passes no allowlist and returns an event).
-    const message = {
-      messageId: 'om_2',
-      chatId: 'oc_2',
-      chatType: 'group',
-      senderId: 'ou_blocked',
-      senderName: 'Mallory',
-      content: 'hello',
-      rawContentType: 'text',
-      resources: [],
-      mentions: [],
-      mentionAll: false,
-      mentionedBot: true,
-      createTime: 123,
-      raw: {},
-    } satisfies NormalizedMessage;
-    assert.equal(feishuMessageToEvent(message, 456, ['ou_allowed']), null);
-  });
-
   it('maps a WeCom direct text message to the sender conversation', () => {
     const frame = {
       headers: { req_id: 'req_1' },
@@ -102,22 +79,6 @@ describe('IM channel event mapping', () => {
     });
   });
 
-  it('drops WeCom messages outside the local allowlist', () => {
-    const frame = {
-      headers: { req_id: 'req_2' },
-      body: {
-        msgid: 'msg_2',
-        aibotid: 'bot_1',
-        chatid: 'group_1',
-        chattype: 'group',
-        from: { userid: 'blocked_user' },
-        msgtype: 'text',
-        text: { content: 'question' },
-      },
-    } as WsFrame<TextMessage>;
-    assert.equal(wecomTextFrameToEvent(frame, 789, ['allowed_user']), null);
-  });
-
   it('force-closes a Feishu raw socket even when the channel handshake never completed', async () => {
     const calls: string[] = [];
     await closeLarkChannel({
diff --git a/packages/runtime/src/bots/base-adapter.ts b/packages/runtime/src/bots/base-adapter.ts
index 112c74cb3e..d368dbff3b 100644
--- a/packages/runtime/src/bots/base-adapter.ts
+++ b/packages/runtime/src/bots/base-adapter.ts
@@ -72,9 +72,12 @@ export abstract class BaseBotAdapter extends EventEmitter implements BotBridge {
     return { needsRestart };
   }
 
-  protected emitIncomingMessage(message: BotIncomingMessage): void {
+  protected emitIncomingMessage(message: BotIncomingMessage): boolean {
+    const allowedUserIds = this.settings.allowedUserIds;
+    if (allowedUserIds?.length && !allowedUserIds.includes(message.userId)) return false;
     this.lastEventAt = message.receivedAt;
     this.emit('message', message);
+    return true;
   }
 
   protected emitStatusChange(): void {
diff --git a/packages/runtime/src/bots/dingtalk-bridge.ts b/packages/runtime/src/bots/dingtalk-bridge.ts
index e2655a8ed9..87f77b159e 100644
--- a/packages/runtime/src/bots/dingtalk-bridge.ts
+++ b/packages/runtime/src/bots/dingtalk-bridge.ts
@@ -432,9 +432,7 @@ export class DingTalkBotBridge extends WsBridgeBase implements SendCapable {
       }
       if (payload) {
         const event = dingTalkPayloadToEvent(payload, Date.now());
-        if (event) {
-          this.lastEventAt = event.receivedAt;
-          this.emitIncomingMessage(event);
+        if (event && this.emitIncomingMessage(event)) {
           this.emitStatusChange();
         }
       }
diff --git a/packages/runtime/src/bots/discord-bridge.ts b/packages/runtime/src/bots/discord-bridge.ts
index c24526e92c..6e210c2731 100644
--- a/packages/runtime/src/bots/discord-bridge.ts
+++ b/packages/runtime/src/bots/discord-bridge.ts
@@ -297,8 +297,7 @@ export class DiscordBotBridge extends GatewayBridgeBase implements SendCapable {
     if (type === 'MESSAGE_CREATE') {
       const event = discordMessageToEvent(d as DiscordMessagePayload, Date.now());
       if (!event) return;
-      this.lastEventAt = event.receivedAt;
-      this.emitIncomingMessage(event);
+      if (!this.emitIncomingMessage(event)) return;
       this.emitStatusChange();
       return;
     }
diff --git a/packages/runtime/src/bots/feishu-bridge.ts b/packages/runtime/src/bots/feishu-bridge.ts
index 82fe9553f0..ccf4a6dd55 100644
--- a/packages/runtime/src/bots/feishu-bridge.ts
+++ b/packages/runtime/src/bots/feishu-bridge.ts
@@ -41,20 +41,8 @@ export async function closeLarkChannel(channel: ClosableLarkChannel): Promise<vo
   await channel.disconnect().catch(() => {});
 }
 
-export function feishuMessageToEvent(
-  message: NormalizedMessage,
-  receivedAt: number,
-  allowedUserIds?: ReadonlyArray<string>,
-) {
+export function feishuMessageToEvent(message: NormalizedMessage, receivedAt: number) {
   if (!message.content.trim() || !message.senderId || !message.chatId) return null;
-  // PR-BOT-USER-ALLOWLIST-0 / PR1197 review: the Lark SDK policy gate only
-  // enforces the allowlist for DMs (PolicyGate.evaluateGroup checks
-  // groupAllowlist + requireMention, and this bridge sets requireMention:false),
-  // so group messages from unauthorized senders slip past the SDK. Mirror the
-  // WeCom/Telegram local check: an empty/absent list allows all, otherwise the
-  // sender must be listed. Dropped silently — no bounce that would let scanners
-  // enumerate the policy.
-  if (allowedUserIds?.length && !allowedUserIds.includes(message.senderId)) return null;
   return {
     platform: 'feishu' as const,
     userId: message.senderId,
@@ -194,12 +182,11 @@ export class FeishuBotBridge extends BaseBotAdapter implements SendCapable {
     this.unsubscribe.push(
       channel.on('message', (message) => {
         const receivedAt = Date.now();
-        const event = feishuMessageToEvent(message, receivedAt, this.settings.allowedUserIds);
+        const event = feishuMessageToEvent(message, receivedAt);
         if (!event || this.channel !== channel) return;
-        this.lastEventAt = receivedAt;
+        if (!this.emitIncomingMessage(event)) return;
         this.readiness = 'operational';
         this.reason = undefined;
-        this.emitIncomingMessage(event);
         this.emitStatusChange();
       }),
       channel.on('error', (error) => {
diff --git a/packages/runtime/src/bots/qq-bridge.ts b/packages/runtime/src/bots/qq-bridge.ts
index a2c431f84b..2171dc3ed3 100644
--- a/packages/runtime/src/bots/qq-bridge.ts
+++ b/packages/runtime/src/bots/qq-bridge.ts
@@ -393,8 +393,7 @@ export class QQBotBridge extends GatewayBridgeBase implements SendCapable {
       event = qqC2CMessageToEvent(d as QQC2CMessagePayload, receivedAt);
     }
     if (!event) return;
-    this.lastEventAt = event.receivedAt;
-    this.emitIncomingMessage(event);
+    if (!this.emitIncomingMessage(event)) return;
     this.emitStatusChange();
   }
 
diff --git a/packages/runtime/src/bots/telegram-bridge.ts b/packages/runtime/src/bots/telegram-bridge.ts
index e3437d6242..526ea1fc23 100644
--- a/packages/runtime/src/bots/telegram-bridge.ts
+++ b/packages/runtime/src/bots/telegram-bridge.ts
@@ -133,18 +133,6 @@ function normalizeTelegramPrivateChatId(value: string): number | undefined {
   return Number.isSafeInteger(numeric) ? numeric : undefined;
 }
 
-/**
- * PR-BOT-USER-ALLOWLIST-0: runtime allowlist gate. `undefined` or empty
- * means no restriction; any other set is enforced as exact match against
- * the platform-native user id. The settings normalize layer already
- * trims, dedups, and caps the persisted array, so this function only
- * has to do membership.
- */
-function isAllowedUser(allowedUserIds: ReadonlyArray<string> | undefined, userId: string): boolean {
-  if (!allowedUserIds || allowedUserIds.length === 0) return true;
-  return allowedUserIds.includes(userId);
-}
-
 /**
  * PR-BOT-NON-TEXT-MESSAGE-ACK-0 (external bot research): map a Telegram
  * `message` object to a stable {@link BotAttachmentKind} so the handler
@@ -246,7 +234,6 @@ export const __TEST__ = {
   buildTelegramSendBody,
   normalizeTelegramReplyToMessageId,
   normalizeTelegramPrivateChatId,
-  isAllowedUser,
   classifyTelegramSendResponse,
   createTelegramReplyStream,
   telegramDraftId,
@@ -452,27 +439,23 @@ export class TelegramBotBridge extends BaseBotAdapter implements SendCapable {
   private handleTelegramMessage(message: any): void {
     if (!message?.from) return;
     const userId = String(message.from.id);
-    // PR-BOT-USER-ALLOWLIST-0: drop unauthorized senders silently when an
-    // allowlist is configured. No bounce reply — that would let scanners
-    // enumerate the policy by toggling IDs. Status fields are NOT updated
-    // for dropped messages so the bridge's `lastEventAt` continues to
-    // reflect authentic activity from authorized users only.
-    if (!isAllowedUser(this.settings.allowedUserIds, userId)) return;
-    this.lastEventAt = Date.now();
+    const attachmentKind = telegramAttachmentKind(message);
+    if (
+      !this.emitIncomingMessage({
+        platform: 'telegram',
+        userId,
+        userName: message.from.username ?? message.from.first_name ?? userId,
+        chatId: String(message.chat?.id ?? ''),
+        isGroup: message.chat?.type === 'group' || message.chat?.type === 'supergroup',
+        text: message.text ?? message.caption ?? '',
+        sourceMessageId: String(message.message_id ?? ''),
+        receivedAt: Date.now(),
+        ...(attachmentKind ? { attachmentKind } : {}),
+      })
+    )
+      return;
     this.readiness = 'operational';
     this.reason = undefined;
-    const attachmentKind = telegramAttachmentKind(message);
-    this.emitIncomingMessage({
-      platform: 'telegram',
-      userId,
-      userName: message.from.username ?? message.from.first_name ?? userId,
-      chatId: String(message.chat?.id ?? ''),
-      isGroup: message.chat?.type === 'group' || message.chat?.type === 'supergroup',
-      text: message.text ?? message.caption ?? '',
-      sourceMessageId: String(message.message_id ?? ''),
-      receivedAt: this.lastEventAt,
-      ...(attachmentKind ? { attachmentKind } : {}),
-    });
     this.emitStatusChange();
   }
 
diff --git a/packages/runtime/src/bots/wechat-bridge.ts b/packages/runtime/src/bots/wechat-bridge.ts
index ab9fc63d53..28c61f9387 100644
--- a/packages/runtime/src/bots/wechat-bridge.ts
+++ b/packages/runtime/src/bots/wechat-bridge.ts
@@ -227,9 +227,9 @@ export class WechatBridge extends BaseBotAdapter implements SendCapable {
           cursor.sinceEpochSeconds,
           Math.floor(event.receivedAt / 1000),
         );
+        if (!this.emitIncomingMessage(event)) continue;
         this.readiness = 'operational';
         this.reason = undefined;
-        this.emitIncomingMessage(event);
         this.emitStatusChange();
       }
       return true;
@@ -274,10 +274,9 @@ export class WechatBridge extends BaseBotAdapter implements SendCapable {
         for (const message of messages) {
           const event = mapWechatIlinkMessage(message);
           if (!event) continue;
+          if (!this.emitIncomingMessage(event)) continue;
           this.readiness = 'operational';
           this.reason = undefined;
-          this.lastEventAt = Date.now();
-          this.emitIncomingMessage(event);
           this.emitStatusChange();
         }
         if (this.running && messages.length === 0) await sleep(800);
diff --git a/packages/runtime/src/bots/wecom-bridge.ts b/packages/runtime/src/bots/wecom-bridge.ts
index 07cf6ddf5d..150740d0fd 100644
--- a/packages/runtime/src/bots/wecom-bridge.ts
+++ b/packages/runtime/src/bots/wecom-bridge.ts
@@ -40,16 +40,11 @@ const quietLogger = {
   error() {},
 };
 
-export function wecomTextFrameToEvent(
-  frame: WsFrame<TextMessage>,
-  receivedAt: number,
-  allowedUserIds?: ReadonlyArray<string>,
-) {
+export function wecomTextFrameToEvent(frame: WsFrame<TextMessage>, receivedAt: number) {
   const body = frame.body;
   if (!body || body.msgtype !== 'text' || typeof body.text?.content !== 'string') return null;
   const userId = body.from?.userid;
   if (typeof userId !== 'string' || !userId) return null;
-  if (allowedUserIds?.length && !allowedUserIds.includes(userId)) return null;
   const isGroup = body.chattype === 'group';
   const chatId = isGroup ? body.chatid : userId;
   if (typeof chatId !== 'string' || !chatId) return null;
@@ -199,12 +194,11 @@ export class WeComBotBridge extends BaseBotAdapter implements SendCapable {
   private wire(client: WSClient, botId: string): void {
     client.on('message.text', (frame) => {
       const receivedAt = Date.now();
-      const event = wecomTextFrameToEvent(frame, receivedAt, this.settings.allowedUserIds);
+      const event = wecomTextFrameToEvent(frame, receivedAt);
       if (!event || this.client !== client) return;
-      this.lastEventAt = receivedAt;
+      if (!this.emitIncomingMessage(event)) return;
       this.readiness = 'operational';
       this.reason = undefined;
-      this.emitIncomingMessage(event);
       this.emitStatusChange();
     });
     client.on('authenticated', () => {


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org