@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 +2 -2
- package/package.json +4 -1
- package/src/accounts.ts +5 -6
- package/src/channel.ts +3 -7
- package/src/confs.ts +15 -8
- package/src/inbound.ts +14 -5
- package/src/outbound.ts +1 -1
- package/src/tools.ts +4 -5
- package/src/utils.ts +3 -1
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
|
|
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.
|
|
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 {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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
|
-
|
|
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: '
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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;
|