@qihoo/tuitui-openclaw-channel 1.0.13 → 1.0.15
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 +16 -9
- package/src/histories.ts +69 -0
- package/src/inbound.ts +46 -10
- package/src/outbound.ts +2 -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.15",
|
|
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
|
@@ -39,10 +39,13 @@ const baseFields = {
|
|
|
39
39
|
},
|
|
40
40
|
channelContext: {
|
|
41
41
|
type: 'string',
|
|
42
|
-
default: '
|
|
42
|
+
default: 'thread',
|
|
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/histories.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_GROUP_HISTORY_LIMIT,
|
|
3
|
+
recordPendingHistoryEntryIfEnabled,
|
|
4
|
+
type HistoryEntry,
|
|
5
|
+
} from "openclaw/plugin-sdk/reply-history";
|
|
6
|
+
|
|
7
|
+
// 每个 accountId 独立的 group history map,防止多账户冲突
|
|
8
|
+
const accountGroupHistories = new Map<string, Map<string, HistoryEntry[]>>();
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 记录没有被@,被忽略的群消息到对应 account 的 pending history 中
|
|
12
|
+
*/
|
|
13
|
+
export async function addUnmentionedHistory(apiRuntime, accountId: string, chatId: string, tuituiUserName: any, tuituiAccount: string, text: any, timestamp :any) {
|
|
14
|
+
const cfg = await apiRuntime.config.loadConfig();
|
|
15
|
+
const senderDesc = tuituiUserName ? `${tuituiUserName} (${tuituiAccount})` : tuituiAccount;
|
|
16
|
+
|
|
17
|
+
const entry: HistoryEntry = {
|
|
18
|
+
sender: senderDesc,
|
|
19
|
+
body: text,
|
|
20
|
+
timestamp: timestamp ? Number(timestamp) : Date.now(),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// 为每个 accountId 维护独立的 group history map
|
|
24
|
+
let groupHistories = accountGroupHistories.get(accountId);
|
|
25
|
+
if (!groupHistories) {
|
|
26
|
+
groupHistories = new Map<string, HistoryEntry[]>();
|
|
27
|
+
accountGroupHistories.set(accountId, groupHistories);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const historyLimit = resolveHistoryLimit(cfg);
|
|
31
|
+
|
|
32
|
+
// 这个API内部没啥神秘的,只是帮你维护map变量。openclaw本身不持有这些数据。
|
|
33
|
+
recordPendingHistoryEntryIfEnabled({
|
|
34
|
+
historyMap: groupHistories,
|
|
35
|
+
historyKey: chatId,
|
|
36
|
+
limit: historyLimit,
|
|
37
|
+
entry: entry,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 获取指定 account 和群组的 pending history,格式化为 ctx.InboundHistory 所需的格式
|
|
43
|
+
*/
|
|
44
|
+
export function popUnmentionedHistories(accountId: string, chatId: string): {sender: string, body: string, timestamp?: number}[] {
|
|
45
|
+
const groupHistories = accountGroupHistories.get(accountId);
|
|
46
|
+
if (!groupHistories) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
const his = groupHistories.get(chatId);
|
|
50
|
+
if (!his) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
groupHistories.delete(chatId);
|
|
55
|
+
|
|
56
|
+
return his.map((entry) => ({
|
|
57
|
+
sender: entry.sender,
|
|
58
|
+
body: entry.body,
|
|
59
|
+
timestamp: entry.timestamp,
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 从配置中解析 history limit
|
|
65
|
+
* dashboard->messages->groupChat->historyLimit
|
|
66
|
+
*/
|
|
67
|
+
export function resolveHistoryLimit(cfg: any): number {
|
|
68
|
+
return cfg.messages?.groupChat?.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT;
|
|
69
|
+
}
|
package/src/inbound.ts
CHANGED
|
@@ -23,6 +23,10 @@ import {
|
|
|
23
23
|
teamsParseChatId,
|
|
24
24
|
} from "./outbound";
|
|
25
25
|
import { parseAllowFroms } from './utils';
|
|
26
|
+
import {
|
|
27
|
+
addUnmentionedHistory,
|
|
28
|
+
popUnmentionedHistories,
|
|
29
|
+
} from "./histories";
|
|
26
30
|
|
|
27
31
|
/** 子函数共享的可变上下文,子函数直接修改字段,外层读取结果 */
|
|
28
32
|
interface ChatPayload {
|
|
@@ -30,12 +34,14 @@ interface ChatPayload {
|
|
|
30
34
|
chatId: string | undefined;
|
|
31
35
|
text: string | undefined;
|
|
32
36
|
groupName: string | undefined;
|
|
37
|
+
channelName: string | undefined;
|
|
33
38
|
mediaUrls: string[] | undefined;
|
|
34
39
|
replyToId: string | undefined; // 引用ID
|
|
35
40
|
msgId?: string; // 消息ID
|
|
36
41
|
tuituiAccount: string;
|
|
37
42
|
tuituiUid: string | undefined;
|
|
38
43
|
tuituiUserName: string | undefined;
|
|
44
|
+
timestamp: Number | undefined;
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
export interface InboundAccount {
|
|
@@ -48,6 +54,7 @@ export interface InboundAccount {
|
|
|
48
54
|
groupAllowFrom: string[];
|
|
49
55
|
requireMention: boolean;
|
|
50
56
|
channelContext: string;
|
|
57
|
+
emojiReaction: boolean;
|
|
51
58
|
}
|
|
52
59
|
export interface InboundHandlerOptions {
|
|
53
60
|
json: any
|
|
@@ -226,6 +233,7 @@ const parseAndVerifyPayload: Record<string, Function> = {
|
|
|
226
233
|
payload.chatId = chatId;
|
|
227
234
|
payload.groupName = msgData.group_name;
|
|
228
235
|
payload.msgId = msgData.msgid;
|
|
236
|
+
payload.text = buildMessageBody(msgData);
|
|
229
237
|
log?.debug?.(
|
|
230
238
|
`[${CHANNEL_ID}] AccountId: ${accountId}, inbound group_chat:
|
|
231
239
|
tuituiAccount=${payload.tuituiAccount},
|
|
@@ -248,7 +256,8 @@ const parseAndVerifyPayload: Record<string, Function> = {
|
|
|
248
256
|
}
|
|
249
257
|
|
|
250
258
|
if (!msgData.at_me && account.requireMention) {
|
|
251
|
-
log?.info?.(`[${CHANNEL_ID}] AccountId: ${accountId}, ignore group message (not mentioned)`);
|
|
259
|
+
log?.info?.(`[${CHANNEL_ID}] AccountId: ${accountId}, ignore group message (not mentioned), add to history key: ${chatId}`);
|
|
260
|
+
await addUnmentionedHistory(apiRuntime, accountId, chatId, payload.tuituiUserName, payload.tuituiAccount, payload.text, payload.timestamp);
|
|
252
261
|
return false;
|
|
253
262
|
}
|
|
254
263
|
|
|
@@ -267,19 +276,20 @@ const parseAndVerifyPayload: Record<string, Function> = {
|
|
|
267
276
|
return false;
|
|
268
277
|
}
|
|
269
278
|
|
|
270
|
-
|
|
279
|
+
|
|
271
280
|
|
|
272
281
|
return true;
|
|
273
282
|
},
|
|
274
283
|
teams_post_create: async (payload: ChatPayload, msgData: any, account: InboundAccount, apiRuntime: any, log: any): Promise<boolean> => {
|
|
275
284
|
const { accountId, groupPolicy, groupAllowFrom } = account;
|
|
276
285
|
payload.chatType = CHAT_TYPE_CHANNEL;
|
|
277
|
-
const { team_id, channel_id, parent_id, post_id, content } = msgData;
|
|
286
|
+
const { team_id, channel_id, parent_id, post_id, content, team_name, channel_name } = msgData;
|
|
278
287
|
const thread_id = (parent_id && parent_id != "0")?parent_id: post_id;
|
|
279
288
|
const chatId = teamsBuildChatId(team_id, channel_id, thread_id);
|
|
280
289
|
payload.chatId = chatId;
|
|
281
290
|
payload.msgId = post_id;
|
|
282
291
|
payload.text = content;
|
|
292
|
+
payload.channelName = channel_name;
|
|
283
293
|
payload.replyToId = "";
|
|
284
294
|
log?.debug?.(
|
|
285
295
|
`[${CHANNEL_ID}] AccountId: ${accountId}, inbound teams:
|
|
@@ -304,11 +314,18 @@ const parseAndVerifyPayload: Record<string, Function> = {
|
|
|
304
314
|
}
|
|
305
315
|
|
|
306
316
|
if (!msgData.at_me && account.requireMention) {
|
|
307
|
-
log?.info?.(`[${CHANNEL_ID}] AccountId: ${accountId}, ignore teams post (not mentioned)`);
|
|
317
|
+
log?.info?.(`[${CHANNEL_ID}] AccountId: ${accountId}, ignore teams post (not mentioned), add to history key: ${chatId}`);
|
|
318
|
+
await addUnmentionedHistory(apiRuntime, accountId, chatId, payload.tuituiUserName, payload.tuituiAccount, payload.text, payload.timestamp);
|
|
308
319
|
return false;
|
|
309
320
|
}
|
|
310
321
|
|
|
311
|
-
if (!groupAllowFrom.includes(String(team_id))) {
|
|
322
|
+
if (!groupAllowFrom.includes(String(team_id)) && !groupAllowFrom.includes(String(channel_id)) ) {
|
|
323
|
+
if (!msgData.at_me) {
|
|
324
|
+
// 解决这个case:requireMention=false时,团队里发任意帖子都会回复加白,搞得没法用
|
|
325
|
+
// 要求必须@才触发回复加白
|
|
326
|
+
log?.info?.(`[${CHANNEL_ID}] AccountId: ${accountId}, ignore teams post (not mentioned)`);
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
312
329
|
if (needPairingThrottle(accountId, chatId)) {
|
|
313
330
|
log?.info?.(`[${CHANNEL_ID}] AccountId: ${accountId}, teams pairing throttled for teamsChatId=${chatId}`);
|
|
314
331
|
return false;
|
|
@@ -317,7 +334,7 @@ const parseAndVerifyPayload: Record<string, Function> = {
|
|
|
317
334
|
account,
|
|
318
335
|
chatId,
|
|
319
336
|
payload.chatType,
|
|
320
|
-
`当前openclaw(AccountId: ${accountId})群聊/团队策略为白名单,需要主人在群白名单(Group Allow From)增加当前团队ID
|
|
337
|
+
`当前openclaw(AccountId: ${accountId})群聊/团队策略为白名单,需要主人在群白名单(Group Allow From)增加当前团队ID: ${team_id} 或者频道ID: ${channel_id} 如果配置团队ID指当前团队下所有频道都可以使用,如果配置频道ID则仅此频道可使用`,
|
|
321
338
|
'tuitui.groupPolicy.reply',
|
|
322
339
|
);
|
|
323
340
|
return false;
|
|
@@ -349,11 +366,13 @@ export async function handleInboundMessage({ json, account, apiRuntime, log }: I
|
|
|
349
366
|
chatId: undefined,
|
|
350
367
|
text: undefined,
|
|
351
368
|
groupName: undefined,
|
|
369
|
+
channelName: undefined,
|
|
352
370
|
mediaUrls: getMediaUrls(msgData),
|
|
353
371
|
replyToId: ref?.is_me && ref?.msgid ? ref.msgid : undefined,
|
|
354
372
|
tuituiAccount: msg.user_account || "",
|
|
355
373
|
tuituiUid: msg.uid || "",
|
|
356
374
|
tuituiUserName: msg.user_name || "",
|
|
375
|
+
timestamp: msg.timestamp ? parseInt(msg.timestamp, 10)*1000 : undefined
|
|
357
376
|
};
|
|
358
377
|
const wsEvent = json.body.event;
|
|
359
378
|
// 按event类型,标准化并校验基础数据
|
|
@@ -369,8 +388,10 @@ export async function handleInboundMessage({ json, account, apiRuntime, log }: I
|
|
|
369
388
|
return;
|
|
370
389
|
}
|
|
371
390
|
|
|
372
|
-
|
|
373
|
-
|
|
391
|
+
if (account.emojiReaction) {
|
|
392
|
+
// 因为回复较慢,先回复一个表情
|
|
393
|
+
await tuituiEmojiReaction(account, payload.chatId, payload.chatType, payload.msgId, '收到');
|
|
394
|
+
}
|
|
374
395
|
|
|
375
396
|
// 路由判断
|
|
376
397
|
const routeSenderFrom = payload.tuituiAccount || payload.tuituiUid || 'unknown';
|
|
@@ -379,6 +400,14 @@ export async function handleInboundMessage({ json, account, apiRuntime, log }: I
|
|
|
379
400
|
const sessionKey = getSessionKey(cfg, payload, account, apiRuntime);
|
|
380
401
|
log?.info?.(`[${CHANNEL_ID}] AccountId: ${accountId}, dispatching to agent session=${sessionKey}, chatType: ${payload.chatType}, chatId ${payload.chatId}, routeSenderFrom: ${routeSenderFrom}`);
|
|
381
402
|
|
|
403
|
+
if(payload.text?.startsWith("/new") || payload.text?.endsWith("/new")) {
|
|
404
|
+
// 清理agent上下文时,同时清空历史
|
|
405
|
+
popUnmentionedHistories(accountId, payload.chatId);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// 字段意义 参考 openclaw 代码
|
|
409
|
+
// src\auto-reply\reply\inbound-meta.ts
|
|
410
|
+
// buildInboundUserContextPrefix()
|
|
382
411
|
const ctx: any = {
|
|
383
412
|
Body: payload.text! || ' ',
|
|
384
413
|
From: String(routeSenderFrom),
|
|
@@ -392,17 +421,24 @@ export async function handleInboundMessage({ json, account, apiRuntime, log }: I
|
|
|
392
421
|
Surface: CHANNEL_ID,
|
|
393
422
|
Provider: CHANNEL_ID,
|
|
394
423
|
SenderName: payload.tuituiUserName,
|
|
395
|
-
|
|
424
|
+
SenderId: payload.tuituiAccount,
|
|
425
|
+
SenderUsername: payload.tuituiAccount,
|
|
396
426
|
UserAccount: payload.tuituiAccount,
|
|
427
|
+
Timestamp: payload.timestamp,
|
|
397
428
|
CommandAuthorized: true, // 允许 /new 等内置命令
|
|
398
429
|
};
|
|
399
430
|
if (payload.chatType == CHAT_TYPE_GROUP && payload.chatId) {
|
|
400
431
|
ctx.GroupSubject = payload.groupName;
|
|
401
432
|
ctx.GroupId = payload.chatId;
|
|
433
|
+
ctx.InboundHistory = popUnmentionedHistories(accountId, payload.chatId);
|
|
434
|
+
}
|
|
435
|
+
if (payload.chatType == CHAT_TYPE_CHANNEL) {
|
|
436
|
+
ctx.GroupChannel = payload.channelName;
|
|
437
|
+
ctx.InboundHistory = popUnmentionedHistories(accountId, payload.chatId);
|
|
402
438
|
}
|
|
403
439
|
if (payload.mediaUrls?.length) ctx.MediaUrls = payload.mediaUrls;
|
|
404
440
|
if (payload.replyToId) ctx.ReplyToId = payload.replyToId;
|
|
405
441
|
|
|
406
|
-
log?.info?.(`[${CHANNEL_ID}] AccountId: ${accountId}, handleInboundMessage dispatchReply
|
|
442
|
+
log?.info?.(`[${CHANNEL_ID}] AccountId: ${accountId}, handleInboundMessage dispatchReply ctx: ${JSON.stringify(ctx, null, ' ')}`);
|
|
407
443
|
dispatchReply(ctx, cfg, account, payload, apiRuntime, log);
|
|
408
444
|
}
|
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 {
|
|
@@ -491,6 +491,7 @@ export async function sendMediaMsg(
|
|
|
491
491
|
atList?: string[],
|
|
492
492
|
): Promise<void> {
|
|
493
493
|
if (!chatId) return console.error(`[${CHANNEL_ID}] sendMediaMsg Error ${auditCtx}: Missing "target"`);
|
|
494
|
+
console.log(`[${CHANNEL_ID}] sendMediaMsg ${chatType} ${chatId} ${auditCtx} uploading`);
|
|
494
495
|
// Check if mediaUrl looks like an image
|
|
495
496
|
const isImage = /^data:image\//i.test(mediaUrl) || /\.(jpg|jpeg|png|gif)(?:$|[?#])/i.test(mediaUrl);
|
|
496
497
|
const mediaType = isImage ? 'image' : 'file';
|
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;
|