@nextclaw/channel-runtime 0.4.0 → 0.4.2

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
@@ -97,25 +97,6 @@ declare class EmailChannel extends BaseChannel<Config["channels"]["email"]> {
97
97
  private fetchNewMessages;
98
98
  }
99
99
 
100
- declare class FeishuChannel extends BaseChannel<Config["channels"]["feishu"]> {
101
- name: string;
102
- private clients;
103
- private processedMessageIds;
104
- private processedSet;
105
- constructor(config: Config["channels"]["feishu"], bus: MessageBus);
106
- start(): Promise<void>;
107
- stop(): Promise<void>;
108
- send(msg: OutboundMessage): Promise<void>;
109
- private handleIncoming;
110
- private isDuplicate;
111
- private addReaction;
112
- private resolveOutboundAccount;
113
- private isAllowedByPolicy;
114
- private resolveMentionState;
115
- private convertResource;
116
- private buildInboundPayload;
117
- }
118
-
119
100
  declare class MochatChannel extends BaseChannel<Config["channels"]["mochat"]> {
120
101
  name: string;
121
102
  private socket;
@@ -309,11 +290,6 @@ declare const BUILTIN_CHANNEL_RUNTIMES: {
309
290
  readonly isEnabled: (config: Config) => boolean;
310
291
  readonly createChannel: (context: BuiltinChannelCreateContext) => DiscordChannel;
311
292
  };
312
- readonly feishu: {
313
- readonly id: "feishu";
314
- readonly isEnabled: (config: Config) => boolean;
315
- readonly createChannel: (context: BuiltinChannelCreateContext) => FeishuChannel;
316
- };
317
293
  readonly mochat: {
318
294
  readonly id: "mochat";
319
295
  readonly isEnabled: (config: Config) => boolean;
@@ -350,4 +326,4 @@ declare const BUILTIN_CHANNEL_PLUGIN_IDS: BuiltinChannelId[];
350
326
  declare function listBuiltinChannelRuntimes(): BuiltinChannelRuntime[];
351
327
  declare function resolveBuiltinChannelRuntime(channelId: string): BuiltinChannelRuntime;
352
328
 
353
- export { BUILTIN_CHANNEL_PLUGIN_IDS, type BuiltinChannelId, type BuiltinChannelRuntime, DingTalkChannel, DiscordChannel, EmailChannel, FeishuChannel, MochatChannel, QQChannel, SlackChannel, TelegramChannel, WeComChannel, WhatsAppChannel, listBuiltinChannelRuntimes, resolveBuiltinChannelRuntime };
329
+ export { BUILTIN_CHANNEL_PLUGIN_IDS, type BuiltinChannelId, type BuiltinChannelRuntime, DingTalkChannel, DiscordChannel, EmailChannel, MochatChannel, QQChannel, SlackChannel, TelegramChannel, WeComChannel, WhatsAppChannel, listBuiltinChannelRuntimes, resolveBuiltinChannelRuntime };
package/dist/index.js CHANGED
@@ -1264,347 +1264,6 @@ var EmailChannel = class extends BaseChannel {
1264
1264
  }
1265
1265
  };
1266
1266
 
1267
- // src/channels/feishu-message-support.ts
1268
- var TABLE_RE = /((?:^[ \t]*\|.+\|[ \t]*\n)(?:^[ \t]*\|[-:\s|]+\|[ \t]*\n)(?:^[ \t]*\|.+\|[ \t]*\n?)+)/gm;
1269
- function extractSenderInfo(sender) {
1270
- const senderIdObj = sender.sender_id ?? {};
1271
- const senderOpenId = senderIdObj.open_id || sender.open_id || "";
1272
- const senderUserId = senderIdObj.user_id || sender.user_id || "";
1273
- const senderUnionId = senderIdObj.union_id || sender.union_id || "";
1274
- return {
1275
- senderId: senderOpenId || senderUserId || senderUnionId || "",
1276
- senderType: sender.sender_type ?? sender.senderType,
1277
- senderOpenId: senderOpenId || void 0,
1278
- senderUserId: senderUserId || void 0,
1279
- senderUnionId: senderUnionId || void 0
1280
- };
1281
- }
1282
- function extractMessageInfo(message) {
1283
- const chatId = message.chat_id ?? "";
1284
- const chatType = message.chat_type ?? "";
1285
- return {
1286
- chatId,
1287
- chatType,
1288
- isGroup: chatType === "group",
1289
- msgType: message.msg_type ?? message.message_type ?? "",
1290
- messageId: message.message_id ?? "",
1291
- rawContent: typeof message.content === "string" ? message.content : ""
1292
- };
1293
- }
1294
- function extractMentions(root, message) {
1295
- if (Array.isArray(root.mentions)) {
1296
- return root.mentions;
1297
- }
1298
- if (Array.isArray(message.mentions)) {
1299
- return message.mentions;
1300
- }
1301
- return [];
1302
- }
1303
- function buildInboundMetadata(params) {
1304
- return {
1305
- account_id: params.accountId,
1306
- accountId: params.accountId,
1307
- message_id: params.messageInfo.messageId,
1308
- chat_id: params.messageInfo.chatId,
1309
- chat_type: params.messageInfo.chatType,
1310
- msg_type: params.messageInfo.msgType,
1311
- is_group: params.messageInfo.isGroup,
1312
- peer_kind: params.messageInfo.isGroup ? "group" : "direct",
1313
- peer_id: params.messageInfo.isGroup ? params.messageInfo.chatId : params.senderInfo.senderId,
1314
- sender_open_id: params.senderInfo.senderOpenId,
1315
- sender_user_id: params.senderInfo.senderUserId,
1316
- sender_union_id: params.senderInfo.senderUnionId,
1317
- was_mentioned: params.mentionState.wasMentioned,
1318
- require_mention: params.mentionState.requireMention
1319
- };
1320
- }
1321
- function inferFeishuResourceMimeType(resourceType) {
1322
- if (resourceType === "image" || resourceType === "sticker") {
1323
- return "image/*";
1324
- }
1325
- if (resourceType === "audio") {
1326
- return "audio/*";
1327
- }
1328
- return void 0;
1329
- }
1330
- function buildFeishuCardElements(content) {
1331
- const elements = [];
1332
- let lastEnd = 0;
1333
- for (const match of content.matchAll(TABLE_RE)) {
1334
- const start = match.index ?? 0;
1335
- const tableText = match[1] ?? "";
1336
- const before = content.slice(lastEnd, start).trim();
1337
- if (before) {
1338
- elements.push({ tag: "markdown", content: before });
1339
- }
1340
- elements.push(parseMarkdownTable(tableText) ?? { tag: "markdown", content: tableText });
1341
- lastEnd = start + tableText.length;
1342
- }
1343
- const remaining = content.slice(lastEnd).trim();
1344
- if (remaining) {
1345
- elements.push({ tag: "markdown", content: remaining });
1346
- }
1347
- if (!elements.length) {
1348
- elements.push({ tag: "markdown", content });
1349
- }
1350
- return elements;
1351
- }
1352
- function parseMarkdownTable(tableText) {
1353
- const lines = tableText.trim().split("\n").map((line) => line.trim()).filter(Boolean);
1354
- if (lines.length < 3) {
1355
- return null;
1356
- }
1357
- const split = (line) => line.replace(/^\|+|\|+$/g, "").split("|").map((item) => item.trim());
1358
- const headers = split(lines[0]);
1359
- const rows = lines.slice(2).map(split);
1360
- return {
1361
- tag: "table",
1362
- page_size: rows.length + 1,
1363
- columns: headers.map((header, index) => ({
1364
- tag: "column",
1365
- name: `c${index}`,
1366
- display_name: header,
1367
- width: "auto"
1368
- })),
1369
- rows: rows.map((row) => {
1370
- const values = {};
1371
- headers.forEach((_, index) => {
1372
- values[`c${index}`] = row[index] ?? "";
1373
- });
1374
- return values;
1375
- })
1376
- };
1377
- }
1378
-
1379
- // src/channels/feishu.ts
1380
- import {
1381
- buildFeishuConvertContext,
1382
- convertFeishuMessageContent,
1383
- getDefaultFeishuAccountId,
1384
- getEnabledFeishuAccounts,
1385
- LarkClient
1386
- } from "@nextclaw/feishu-core";
1387
- function isRecord(value) {
1388
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1389
- }
1390
- var FeishuChannel = class extends BaseChannel {
1391
- name = "feishu";
1392
- clients = /* @__PURE__ */ new Map();
1393
- processedMessageIds = [];
1394
- processedSet = /* @__PURE__ */ new Set();
1395
- constructor(config, bus) {
1396
- super(config, bus);
1397
- }
1398
- async start() {
1399
- const accounts = getEnabledFeishuAccounts(this.config);
1400
- if (accounts.length === 0) {
1401
- throw new Error("Feishu appId/appSecret not configured");
1402
- }
1403
- this.running = true;
1404
- for (const account of accounts) {
1405
- const client = LarkClient.fromAccount(account);
1406
- const activeAccount = {
1407
- accountId: account.accountId,
1408
- client
1409
- };
1410
- const probe = await client.probe();
1411
- if (probe.ok) {
1412
- activeAccount.botOpenId = probe.botOpenId;
1413
- activeAccount.botName = probe.botName;
1414
- }
1415
- client.startWebsocket(async (data) => {
1416
- await this.handleIncoming(account.accountId, data);
1417
- });
1418
- this.clients.set(account.accountId, activeAccount);
1419
- }
1420
- }
1421
- async stop() {
1422
- this.running = false;
1423
- for (const account of this.clients.values()) {
1424
- account.client.closeWebsocket();
1425
- }
1426
- this.clients.clear();
1427
- }
1428
- async send(msg) {
1429
- const account = this.resolveOutboundAccount(msg.metadata);
1430
- if (!account) {
1431
- return;
1432
- }
1433
- const receiveIdType = msg.chatId.startsWith("oc_") ? "chat_id" : "open_id";
1434
- const elements = buildFeishuCardElements(msg.content ?? "");
1435
- const card = {
1436
- config: { wide_screen_mode: true },
1437
- elements
1438
- };
1439
- const content = JSON.stringify(card);
1440
- await account.client.sendInteractiveCard({
1441
- receiveId: msg.chatId,
1442
- receiveIdType,
1443
- content
1444
- });
1445
- }
1446
- async handleIncoming(accountId, data) {
1447
- const account = this.clients.get(accountId);
1448
- if (!account) {
1449
- return;
1450
- }
1451
- const root = isRecord(data.event) ? data.event : data;
1452
- const message = root.message ?? data.message ?? {};
1453
- const sender = root.sender ?? message.sender ?? data.sender ?? {};
1454
- const senderInfo = extractSenderInfo(sender);
1455
- if (senderInfo.senderType === "bot") {
1456
- return;
1457
- }
1458
- const messageInfo = extractMessageInfo(message);
1459
- if (!senderInfo.senderId || !messageInfo.chatId) {
1460
- return;
1461
- }
1462
- if (!this.isAllowedByPolicy({ senderId: senderInfo.senderId, chatId: messageInfo.chatId, isGroup: messageInfo.isGroup })) {
1463
- return;
1464
- }
1465
- if (messageInfo.messageId && this.isDuplicate(`${accountId}:${messageInfo.messageId}`)) {
1466
- return;
1467
- }
1468
- if (messageInfo.messageId) {
1469
- await this.addReaction(account, messageInfo.messageId, "THUMBSUP");
1470
- }
1471
- const mentions = extractMentions(root, message);
1472
- const mentionState = this.resolveMentionState({
1473
- account,
1474
- mentions,
1475
- chatId: messageInfo.chatId,
1476
- isGroup: messageInfo.isGroup,
1477
- rawContent: messageInfo.rawContent
1478
- });
1479
- if (mentionState.requireMention && !mentionState.wasMentioned) {
1480
- return;
1481
- }
1482
- const payload = this.buildInboundPayload(account, messageInfo, mentions);
1483
- if (!payload) {
1484
- return;
1485
- }
1486
- await this.handleMessage({
1487
- senderId: senderInfo.senderId,
1488
- // Always route by Feishu chat_id so DM/group sessions are stable.
1489
- chatId: messageInfo.chatId,
1490
- content: payload.content,
1491
- attachments: payload.attachments,
1492
- metadata: buildInboundMetadata({
1493
- accountId,
1494
- messageInfo,
1495
- senderInfo,
1496
- mentionState
1497
- })
1498
- });
1499
- }
1500
- isDuplicate(messageId) {
1501
- if (this.processedSet.has(messageId)) {
1502
- return true;
1503
- }
1504
- this.processedSet.add(messageId);
1505
- this.processedMessageIds.push(messageId);
1506
- if (this.processedMessageIds.length > 1e3) {
1507
- const removed = this.processedMessageIds.splice(0, 500);
1508
- for (const id of removed) {
1509
- this.processedSet.delete(id);
1510
- }
1511
- }
1512
- return false;
1513
- }
1514
- async addReaction(account, messageId, emojiType) {
1515
- try {
1516
- await account.client.addReaction(messageId, emojiType);
1517
- } catch {
1518
- }
1519
- }
1520
- resolveOutboundAccount(metadata) {
1521
- const accountId = typeof metadata?.accountId === "string" ? metadata.accountId : typeof metadata?.account_id === "string" ? metadata.account_id : getDefaultFeishuAccountId(this.config);
1522
- return this.clients.get(accountId) ?? this.clients.get(getDefaultFeishuAccountId(this.config)) ?? null;
1523
- }
1524
- isAllowedByPolicy(params) {
1525
- if (!params.isGroup) {
1526
- if (this.config.dmPolicy === "disabled") {
1527
- return false;
1528
- }
1529
- if (this.config.dmPolicy === "allowlist" || this.config.dmPolicy === "pairing") {
1530
- return this.isAllowed(params.senderId);
1531
- }
1532
- const allowFrom = this.config.allowFrom ?? [];
1533
- return allowFrom.length === 0 || allowFrom.includes("*") || this.isAllowed(params.senderId);
1534
- }
1535
- if (this.config.groupPolicy === "disabled") {
1536
- return false;
1537
- }
1538
- if (this.config.groupPolicy === "allowlist") {
1539
- const allowFrom = this.config.groupAllowFrom ?? [];
1540
- return allowFrom.includes("*") || allowFrom.includes(params.chatId);
1541
- }
1542
- return true;
1543
- }
1544
- resolveMentionState(params) {
1545
- if (!params.isGroup) {
1546
- return { wasMentioned: false, requireMention: false };
1547
- }
1548
- const groupRule = this.config.groups?.[params.chatId] ?? this.config.groups?.["*"];
1549
- const requireMention = groupRule?.requireMention ?? this.config.requireMention ?? false;
1550
- if (!requireMention) {
1551
- return { wasMentioned: false, requireMention: false };
1552
- }
1553
- const patterns = [...this.config.mentionPatterns ?? [], ...groupRule?.mentionPatterns ?? []].map((pattern) => pattern.trim()).filter(Boolean);
1554
- const rawText = params.rawContent.toLowerCase();
1555
- const mentionedByPattern = patterns.some((pattern) => {
1556
- try {
1557
- return new RegExp(pattern, "i").test(rawText);
1558
- } catch {
1559
- return rawText.includes(pattern.toLowerCase());
1560
- }
1561
- });
1562
- const mentionedByIds = params.mentions.some((entry) => {
1563
- if (!entry || typeof entry !== "object") {
1564
- return false;
1565
- }
1566
- const mention = entry;
1567
- const openId = (typeof mention.open_id === "string" ? mention.open_id : "") || (mention.id && typeof mention.id === "object" && "open_id" in mention.id ? mention.id.open_id ?? "" : typeof mention.id === "string" ? mention.id : "");
1568
- const name = typeof mention.name === "string" ? mention.name : "";
1569
- return openId === params.account.botOpenId || (params.account.botName ? name === params.account.botName : false);
1570
- });
1571
- return {
1572
- wasMentioned: mentionedByPattern || mentionedByIds,
1573
- requireMention
1574
- };
1575
- }
1576
- convertResource(resource) {
1577
- return {
1578
- id: resource.fileKey,
1579
- name: resource.fileName,
1580
- source: "feishu",
1581
- status: "remote-only",
1582
- mimeType: inferFeishuResourceMimeType(resource.type),
1583
- url: resource.fileKey
1584
- };
1585
- }
1586
- buildInboundPayload(account, messageInfo, mentions) {
1587
- const converted = convertFeishuMessageContent(
1588
- messageInfo.rawContent,
1589
- messageInfo.msgType,
1590
- buildFeishuConvertContext({
1591
- mentions,
1592
- stripBotMentions: true,
1593
- botOpenId: account.botOpenId,
1594
- botName: account.botName
1595
- })
1596
- );
1597
- const content = converted.content.trim();
1598
- if (!content) {
1599
- return null;
1600
- }
1601
- return {
1602
- content,
1603
- attachments: converted.resources.map((resource) => this.convertResource(resource))
1604
- };
1605
- }
1606
- };
1607
-
1608
1267
  // src/channels/mochat.ts
1609
1268
  import { io } from "socket.io-client";
1610
1269
  import { fetch as fetch3 } from "undici";
@@ -4270,11 +3929,6 @@ var BUILTIN_CHANNEL_RUNTIMES = {
4270
3929
  isEnabled: (config) => config.channels.discord.enabled,
4271
3930
  createChannel: (context) => new DiscordChannel(context.config.channels.discord, context.bus, context.sessionManager, context.config)
4272
3931
  },
4273
- feishu: {
4274
- id: "feishu",
4275
- isEnabled: (config) => config.channels.feishu.enabled,
4276
- createChannel: (context) => new FeishuChannel(context.config.channels.feishu, context.bus)
4277
- },
4278
3932
  mochat: {
4279
3933
  id: "mochat",
4280
3934
  isEnabled: (config) => config.channels.mochat.enabled,
@@ -4324,7 +3978,6 @@ export {
4324
3978
  DingTalkChannel,
4325
3979
  DiscordChannel,
4326
3980
  EmailChannel,
4327
- FeishuChannel,
4328
3981
  MochatChannel,
4329
3982
  QQChannel,
4330
3983
  SlackChannel,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/channel-runtime",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "private": false,
5
5
  "description": "Runtime implementations for NextClaw builtin channel plugins.",
6
6
  "type": "module",
@@ -28,8 +28,7 @@
28
28
  "undici": "^6.21.0",
29
29
  "ws": "^8.18.0",
30
30
  "socket.io-msgpack-parser": "^3.0.2",
31
- "@nextclaw/core": "0.11.0",
32
- "@nextclaw/feishu-core": "0.2.0"
31
+ "@nextclaw/core": "0.11.1"
33
32
  },
34
33
  "devDependencies": {
35
34
  "@types/mailparser": "^3.4.6",