@qihoo/tuitui-openclaw-channel 1.0.13 → 1.0.14

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/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { OpenClawPluginApi } from 'openclaw/plugin-sdk';
2
- import { emptyPluginConfigSchema } from 'openclaw/plugin-sdk';
2
+ import { emptyPluginConfigSchema } from 'openclaw/plugin-sdk/core';
3
3
  import { createTuiTuiChannelPlugin } from './src/channel';
4
4
  import { registerTuituiTools } from './src/tools';
5
5
  import { CHANNEL_ID, CHANNEL_NAME } from './src/const';
@@ -8,7 +8,7 @@ import { id } from './openclaw.plugin.json';
8
8
  const plugin = {
9
9
  id, // plugin id not is CHANNEL_ID
10
10
  name: CHANNEL_NAME,
11
- description: `${CHANNEL_NAME} chat integration for OpenClaw via webhook`,
11
+ description: `${CHANNEL_NAME} chat integration for OpenClaw via WebSocket`,
12
12
  configSchema: emptyPluginConfigSchema(),
13
13
  register(api: OpenClawPluginApi) {
14
14
  console.log(`[${CHANNEL_ID}] Plugin.register Before.`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qihoo/tuitui-openclaw-channel",
3
- "version": "1.0.13",
3
+ "version": "1.0.14",
4
4
  "maintainers": [
5
5
  {
6
6
  "name": "huzunjie",
@@ -17,6 +17,9 @@
17
17
  ],
18
18
  "description": "TuiTui channel plugin for OpenClaw",
19
19
  "type": "module",
20
+ "peerDependencies": {
21
+ "openclaw": ">=2026.3.13"
22
+ },
20
23
  "dependencies": {
21
24
  "@sinclair/typebox": "^0.34.48",
22
25
  "ws": "^8.13.0"
package/src/accounts.ts CHANGED
@@ -1,9 +1,7 @@
1
- import { parseAllowFroms } from './utils';
2
- import { capabilities, configSchema, baseFildsDefault } from './confs';
3
- import { CHANNEL_ID,} from "./const";
4
- import { DEFAULT_ACCOUNT_ID,} from 'openclaw/plugin-sdk';
5
-
6
- const isEnabled = (val: any) => val === undefined || !!val;
1
+ import { parseAllowFroms, isEnabled } from './utils';
2
+ import { baseFildsDefault } from './confs';
3
+ import { CHANNEL_ID } from './const';
4
+ import { DEFAULT_ACCOUNT_ID } from 'openclaw/plugin-sdk/account-id';
7
5
 
8
6
  export const resolveAccount = (cfg: any, accountId?: string | null) => {
9
7
  const channelConfig = cfg?.channels?.[CHANNEL_ID] || {};
@@ -21,5 +19,6 @@ export const resolveAccount = (cfg: any, accountId?: string | null) => {
21
19
  groupAllowFrom: parseAllowFroms(acct?.groupAllowFrom || baseFildsDefault.groupAllowFrom),
22
20
  requireMention: isEnabled(acct?.requireMention ?? baseFildsDefault.requireMention),
23
21
  channelContext: acct?.channelContext || baseFildsDefault.channelContext,
22
+ emojiReaction: isEnabled(acct?.emojiReaction ?? baseFildsDefault.emojiReaction),
24
23
  };
25
24
  };
package/src/channel.ts CHANGED
@@ -5,11 +5,8 @@
5
5
  * Supports single chat (text, image, voice, file) and group chat with @mentions.
6
6
  */
7
7
  import WebSocket from 'ws';
8
- import {
9
- DEFAULT_ACCOUNT_ID,
10
- setAccountEnabledInConfigSection,
11
- deleteAccountFromConfigSection,
12
- } from 'openclaw/plugin-sdk';
8
+ import { DEFAULT_ACCOUNT_ID } from 'openclaw/plugin-sdk/account-id';
9
+ import { setAccountEnabledInConfigSection, deleteAccountFromConfigSection } from 'openclaw/plugin-sdk/core';
13
10
  import { CHANNEL_ID, CHANNEL_NAME } from "./const";
14
11
  import {
15
12
  checkAccount,
@@ -21,9 +18,8 @@ import {
21
18
  import { handleInboundMessage } from './inbound';
22
19
  import { capabilities, configSchema, baseFildsDefault } from './confs';
23
20
  import { resolveAccount } from "./accounts"
21
+ import { isEnabled } from './utils';
24
22
 
25
-
26
- const isEnabled = (val: any) => val === undefined || !!val;
27
23
  const isConfigured = (account: any)=> !!(account?.appId && account?.appSecret);
28
24
 
29
25
  const wsReadyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'] as const;
package/src/confs.ts CHANGED
@@ -42,7 +42,10 @@ const baseFields = {
42
42
  default: 'channel',
43
43
  enum: ['channel', 'thread']
44
44
  },
45
-
45
+ emojiReaction: {
46
+ type: 'boolean',
47
+ default: true,
48
+ },
46
49
  };
47
50
  const baseFieldsKeys = Object.keys(baseFields);
48
51
  export const baseFildsDefault = {} as Record<string, any>;
@@ -86,7 +89,7 @@ const fieldsUiHints = {
86
89
  advanced: true,
87
90
  },
88
91
  groupAllowFrom: {
89
- help: '群组/团队白名单-群ID和团队ID(仅在 groupPolicy=allowlist 生效)',
92
+ help: '群组/团队白名单-包含群ID、团队ID或频道ID(仅在 groupPolicy=allowlist 生效)',
90
93
  order: 14,
91
94
  advanced: true,
92
95
  },
@@ -95,9 +98,14 @@ const fieldsUiHints = {
95
98
  order: 15,
96
99
  advanced: true,
97
100
  },
101
+ emojiReaction: {
102
+ help: '在收到消息后,大模型给出反应结果前,先对原消息发送一个”收到“的表情回复。',
103
+ order: 16,
104
+ advanced: true,
105
+ },
98
106
  };
99
107
 
100
- /* 多账户管理 - 暂不开放,先保留代码逻辑
108
+ /* 多账户管理 */
101
109
  const accountsFieldsUiHints = {} as Record<string, any>;
102
110
  function isValidObjKey(key: string | number | symbol , object: object): key is keyof typeof object {
103
111
  return key in object;
@@ -105,7 +113,6 @@ function isValidObjKey(key: string | number | symbol , object: object): key is k
105
113
  for (const k in fieldsUiHints) {
106
114
  if (isValidObjKey(k, fieldsUiHints)) accountsFieldsUiHints[`accounts.*.${k}` as string] = fieldsUiHints[k];
107
115
  }
108
- */
109
116
 
110
117
  export const configSchema = {
111
118
  schema: {
@@ -113,7 +120,7 @@ export const configSchema = {
113
120
  additionalProperties: false,
114
121
  properties: {
115
122
  ...fields,
116
- /* 基础配置信息中,准备 accounts 配置支持多账户管理,以备后续允许用户配置多个推推机器人账号
123
+ /* 基础配置信息中,准备 accounts 配置支持多账户管理 */
117
124
  accounts: {
118
125
  type: 'object',
119
126
  additionalProperties: {
@@ -121,17 +128,17 @@ export const configSchema = {
121
128
  additionalProperties: false,
122
129
  properties: { ...fields },
123
130
  },
124
- }, */
131
+ },
125
132
  },
126
133
  },
127
134
  uiHints: {
128
135
  ...fieldsUiHints,
129
- /* 基础配置信息中,准备 accounts 配置支持多账户管理,以备后续允许用户配置多个推推机器人账号
136
+ /* 基础配置信息中,准备 accounts 配置支持多账户管理 */
130
137
  accounts: {
131
138
  help: 'Accounts(多账户配置)',
132
139
  order: 30,
133
140
  advanced: true
134
141
  },
135
- ...accountsFieldsUiHints, */
142
+ ...accountsFieldsUiHints,
136
143
  },
137
144
  };
package/src/inbound.ts CHANGED
@@ -48,6 +48,7 @@ export interface InboundAccount {
48
48
  groupAllowFrom: string[];
49
49
  requireMention: boolean;
50
50
  channelContext: string;
51
+ emojiReaction: boolean;
51
52
  }
52
53
  export interface InboundHandlerOptions {
53
54
  json: any
@@ -308,7 +309,13 @@ const parseAndVerifyPayload: Record<string, Function> = {
308
309
  return false;
309
310
  }
310
311
 
311
- if (!groupAllowFrom.includes(String(team_id))) {
312
+ if (!groupAllowFrom.includes(String(team_id)) && !groupAllowFrom.includes(String(channel_id)) ) {
313
+ if (!msgData.at_me) {
314
+ // 解决这个case:requireMention=false时,团队里发任意帖子都会回复加白,搞得没法用
315
+ // 要求必须@才触发回复加白
316
+ log?.info?.(`[${CHANNEL_ID}] AccountId: ${accountId}, ignore teams post (not mentioned)`);
317
+ return false;
318
+ }
312
319
  if (needPairingThrottle(accountId, chatId)) {
313
320
  log?.info?.(`[${CHANNEL_ID}] AccountId: ${accountId}, teams pairing throttled for teamsChatId=${chatId}`);
314
321
  return false;
@@ -317,7 +324,7 @@ const parseAndVerifyPayload: Record<string, Function> = {
317
324
  account,
318
325
  chatId,
319
326
  payload.chatType,
320
- `当前openclaw(AccountId: ${accountId})群聊/团队策略为白名单,需要主人在群白名单(Group Allow From)增加当前团队ID:\n${team_id}`,
327
+ `当前openclaw(AccountId: ${accountId})群聊/团队策略为白名单,需要主人在群白名单(Group Allow From)增加当前团队ID: ${team_id} 或者频道ID: ${channel_id} 如果配置团队ID指当前团队下所有频道都可以使用,如果配置频道ID则仅此频道可使用`,
321
328
  'tuitui.groupPolicy.reply',
322
329
  );
323
330
  return false;
@@ -369,8 +376,10 @@ export async function handleInboundMessage({ json, account, apiRuntime, log }: I
369
376
  return;
370
377
  }
371
378
 
372
- // 因为回复较慢,先回复一个表情
373
- await tuituiEmojiReaction(account, payload.chatId, payload.chatType, payload.msgId, '收到');
379
+ if (account.emojiReaction) {
380
+ // 因为回复较慢,先回复一个表情
381
+ await tuituiEmojiReaction(account, payload.chatId, payload.chatType, payload.msgId, '收到');
382
+ }
374
383
 
375
384
  // 路由判断
376
385
  const routeSenderFrom = payload.tuituiAccount || payload.tuituiUid || 'unknown';
@@ -403,6 +412,6 @@ export async function handleInboundMessage({ json, account, apiRuntime, log }: I
403
412
  if (payload.mediaUrls?.length) ctx.MediaUrls = payload.mediaUrls;
404
413
  if (payload.replyToId) ctx.ReplyToId = payload.replyToId;
405
414
 
406
- log?.info?.(`[${CHANNEL_ID}] AccountId: ${accountId}, handleInboundMessage dispatchReply payload:`, JSON.stringify(payload, null, ' '));
415
+ log?.info?.(`[${CHANNEL_ID}] AccountId: ${accountId}, handleInboundMessage dispatchReply ctx: ${JSON.stringify(ctx, null, ' ')}`);
407
416
  dispatchReply(ctx, cfg, account, payload, apiRuntime, log);
408
417
  }
package/src/outbound.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { readFileSync, existsSync, statSync } from 'node:fs';
2
2
  import { basename } from 'node:path';
3
- import { fetchWithSsrFGuard } from 'openclaw/plugin-sdk';
3
+ import { fetchWithSsrFGuard } from 'openclaw/plugin-sdk/tlon';
4
4
  import { CHANNEL_ID } from "./const";
5
5
 
6
6
  import type {
package/src/tools.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
2
- import type { OpenClawPluginToolContext } from "openclaw/plugin-sdk";
3
- import { DEFAULT_ACCOUNT_ID} from 'openclaw/plugin-sdk';
2
+ import type { OpenClawPluginToolContext } from "openclaw/plugin-sdk/core";
4
3
  import { CHANNEL_ID} from "./const";
5
4
  import { resolveAccount } from "./accounts"
6
- import { Type, type Static } from "@sinclair/typebox";
5
+ import { Type } from "@sinclair/typebox";
7
6
 
8
7
  import {CHAT_TYPE_DIRECT,CHAT_TYPE_GROUP,CHAT_TYPE_CHANNEL,ChatType, getChatRecord} from "./outbound"
9
8
 
@@ -66,13 +65,13 @@ const tuituiToolFactory = (ctx: OpenClawPluginToolContext) => {
66
65
  label: "tuitui IM",
67
66
  description: "推推(tuitui) 聊天记录获取,可查询群聊和私聊的聊天记录。\n\n",
68
67
  parameters: tuitui_im_get_messages_schema,
69
- execute: async (_toolCallId, params) => {
68
+ execute: async (_toolCallId: any, params: any) => {
70
69
  if(messageChannel != CHANNEL_ID) {
71
70
  console.log(`tuitui_im_get_messages(): bad channel ${messageChannel}`);
72
71
  return
73
72
  }
74
73
  console.log(`tuitui_im_get_messages(): agentAccountId: ${agentAccountId}, sessionKey: ${sessionKey}`, params);
75
- return await tuitui_im_get_messages(config, agentAccountId, params);
74
+ return await tuitui_im_get_messages(config, String(agentAccountId), params);
76
75
  },
77
76
  };
78
77
  };
package/src/utils.ts CHANGED
@@ -2,4 +2,6 @@
2
2
  export const parseAllowFroms = (allowFrom: any) : string[] => {
3
3
  const arr = Array.isArray(allowFrom) ? allowFrom : [];
4
4
  return arr.filter((v: any) => !!v).map((v: any) => String(v).toLowerCase().trim());
5
- }
5
+ }
6
+
7
+ export const isEnabled = (val: any) => val === undefined || !!val;