@qihoo/tuitui-openclaw-channel 1.0.31 → 1.0.33
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/package.json +1 -1
- package/src/chat_base.ts +28 -8
- package/src/chat_record.ts +20 -1
- package/src/filespace.ts +26 -21
- package/src/inbound.ts +47 -6
- package/src/outbound.ts +9 -0
- package/src/robot_api.ts +37 -28
- package/src/robot_helper.ts +7 -0
- package/src/tools.ts +9 -1
package/package.json
CHANGED
package/src/chat_base.ts
CHANGED
|
@@ -33,18 +33,38 @@ export function teamsParseChatId(chatId: string): TuiTuiTeamsTarget {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
export
|
|
37
|
-
|
|
36
|
+
export interface TuiTuiSessionKeyInfo {
|
|
37
|
+
chat_type: ChatType,
|
|
38
|
+
channel_id: string;
|
|
39
|
+
thread_id: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 暂时只支持频道(后续可以完善)
|
|
43
|
+
// 格式 agent:coder:tuitui:channel:123:thread:456
|
|
44
|
+
export function parseSessionKey(str: string): TuiTuiSessionKeyInfo {
|
|
45
|
+
const info: TuiTuiSessionKeyInfo = { chat_type: CHAT_TYPE_DIRECT, channel_id: '', thread_id: '' };
|
|
46
|
+
|
|
38
47
|
if (!str.includes('tuitui:channel:')) {
|
|
39
|
-
|
|
48
|
+
return info;
|
|
40
49
|
}
|
|
41
|
-
|
|
50
|
+
|
|
51
|
+
info.chat_type = CHAT_TYPE_CHANNEL;
|
|
52
|
+
|
|
42
53
|
const parts = str.split(':');
|
|
43
54
|
const channelIndex = parts.findIndex(part => part === 'channel');
|
|
44
|
-
|
|
45
55
|
if (channelIndex !== -1 && parts[channelIndex + 1]) {
|
|
46
|
-
|
|
56
|
+
info.channel_id = parts[channelIndex + 1];
|
|
47
57
|
}
|
|
48
|
-
|
|
49
|
-
|
|
58
|
+
|
|
59
|
+
const threadIndex = parts.findIndex(part => part === 'thread');
|
|
60
|
+
if (threadIndex !== -1 && parts[threadIndex + 1]) {
|
|
61
|
+
info.thread_id = parts[threadIndex + 1];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return info;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function parseChannelIdBySessionKey(str: string): string {
|
|
68
|
+
const info = parseSessionKey(str);
|
|
69
|
+
return info.channel_id;
|
|
50
70
|
}
|
package/src/chat_record.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { CHANNEL_ID } from "./const";
|
|
2
2
|
import {CHAT_TYPE_DIRECT,CHAT_TYPE_GROUP,CHAT_TYPE_CHANNEL,ChatType, teamsParseChatId, guessChatType, teamsBuildChatId} from "./chat_base"
|
|
3
3
|
import { tuituiRobotApi } from "./robot_api"
|
|
4
|
+
import { parseSessionKey } from "./chat_base"
|
|
5
|
+
import {getChannelInfoByChannelId} from "./robot_helper"
|
|
4
6
|
import type { TuiTuiMessageData} from './types';
|
|
5
7
|
|
|
6
8
|
export interface TuiTuiChatRecordMessage {
|
|
@@ -236,6 +238,7 @@ export async function getChannelInfoById(account: any, channel_id: string): Prom
|
|
|
236
238
|
}
|
|
237
239
|
|
|
238
240
|
interface TeamsPostChainItem {
|
|
241
|
+
from_uid: string;
|
|
239
242
|
post_id: string;
|
|
240
243
|
time: string;
|
|
241
244
|
last_reply_time: string; // 只有主贴有最后回帖时间,所有的回帖更新的都是主贴的属性
|
|
@@ -261,6 +264,7 @@ function parsePost(item: any): TeamsPostChainItem[] {
|
|
|
261
264
|
const posts: TeamsPostChainItem[] = [];
|
|
262
265
|
|
|
263
266
|
posts.push({
|
|
267
|
+
from_uid: topic.from_uid ?? '',
|
|
264
268
|
post_id: topic.post_id ?? '',
|
|
265
269
|
time: topic.create_time ?? '',
|
|
266
270
|
last_reply_time: topic.last_reply_time ?? '',
|
|
@@ -271,6 +275,7 @@ function parsePost(item: any): TeamsPostChainItem[] {
|
|
|
271
275
|
|
|
272
276
|
for (const post of [...replyList].reverse()) {
|
|
273
277
|
posts.push({
|
|
278
|
+
from_uid: post.from_uid ?? '',
|
|
274
279
|
post_id: post.post_id ?? '',
|
|
275
280
|
time: post.create_time ?? '',
|
|
276
281
|
last_reply_time : '',
|
|
@@ -365,12 +370,26 @@ async function getPostChain(
|
|
|
365
370
|
|
|
366
371
|
const datas = data.datas ?? {};
|
|
367
372
|
|
|
368
|
-
const posts = parsePost(datas)
|
|
373
|
+
const posts = parsePost(datas)
|
|
369
374
|
|
|
370
375
|
console.log(`[${CHANNEL_ID}] getPostChain result: ${posts.length} posts`, posts);
|
|
371
376
|
return posts;
|
|
372
377
|
}
|
|
373
378
|
|
|
379
|
+
export async function getPostChainBySessionKey(
|
|
380
|
+
account: any,
|
|
381
|
+
sessionKey: string,
|
|
382
|
+
): Promise<TeamsPostChainItem[]> {
|
|
383
|
+
const session_info = parseSessionKey(sessionKey);
|
|
384
|
+
|
|
385
|
+
const channelInfo = await getChannelInfoByChannelId(account, session_info.channel_id);
|
|
386
|
+
if (!channelInfo) {
|
|
387
|
+
throw new Error(`无法获取频道信息`);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return await getPostChain(account, channelInfo.team_id, session_info.channel_id, session_info.thread_id);
|
|
391
|
+
}
|
|
392
|
+
|
|
374
393
|
export async function getPostChainByChatId(
|
|
375
394
|
account: any,
|
|
376
395
|
chatId: string,
|
package/src/filespace.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CHANNEL_ID } from "./const";
|
|
2
|
-
import { tuituiRobotApi,
|
|
3
|
-
import {
|
|
2
|
+
import { tuituiRobotApi, tuituiRobotUpload} from "./robot_api"
|
|
3
|
+
import {CHAT_TYPE_CHANNEL, parseSessionKey} from "./chat_base"
|
|
4
|
+
import {getChannelInfoByChannelId} from "./robot_helper"
|
|
4
5
|
|
|
5
6
|
export const NODE_TYPE_DIR = '1';
|
|
6
7
|
export const NODE_TYPE_FILE = '2';
|
|
@@ -51,29 +52,21 @@ export function flattenFileSpaceList(list: any[]): FileSpaceItem[] {
|
|
|
51
52
|
}));
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
async function getChannelInfoBySessionKey(account: any, sessionKey: string) {
|
|
57
|
-
const channel_id = parseChannelIdBySessionKey(sessionKey);
|
|
58
|
-
if(!channel_id) {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const payload = {channel_id: channel_id};
|
|
63
|
-
const body = await tuituiRobotApi(account, '/teams/channel/info', payload);
|
|
64
|
-
return body?.datas?.info;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
55
|
export async function file_space_list(
|
|
68
56
|
account: any,
|
|
69
57
|
sessionKey: string
|
|
70
58
|
): Promise<any> {
|
|
71
59
|
|
|
72
|
-
const
|
|
73
|
-
if
|
|
60
|
+
const session_info = parseSessionKey(sessionKey);
|
|
61
|
+
if(session_info.chat_type != CHAT_TYPE_CHANNEL) {
|
|
74
62
|
throw new Error(`私聊、群聊会话不支持共享空间。仅团队频道支持。`);
|
|
75
63
|
}
|
|
76
64
|
|
|
65
|
+
const channelInfo = await getChannelInfoByChannelId(account, session_info.channel_id);
|
|
66
|
+
if (!channelInfo) {
|
|
67
|
+
throw new Error(`无法获取频道信息`);
|
|
68
|
+
}
|
|
69
|
+
|
|
77
70
|
const payload = {
|
|
78
71
|
space_id: channelInfo.team_id,
|
|
79
72
|
space_type: SPACE_TYPE_TEAM,
|
|
@@ -81,6 +74,8 @@ export async function file_space_list(
|
|
|
81
74
|
|
|
82
75
|
const data = await tuituiRobotApi(account, '/file_space/node/list', payload);
|
|
83
76
|
|
|
77
|
+
//console.log(JSON.stringify(data));
|
|
78
|
+
|
|
84
79
|
const list = data?.datas?.list;
|
|
85
80
|
const flat_list = flattenFileSpaceList(list);
|
|
86
81
|
|
|
@@ -98,7 +93,8 @@ export async function file_space_list(
|
|
|
98
93
|
async function ensureFolderPath(
|
|
99
94
|
account: any,
|
|
100
95
|
spaceId: string,
|
|
101
|
-
folderPath: string
|
|
96
|
+
folderPath: string,
|
|
97
|
+
source: string
|
|
102
98
|
): Promise<string> {
|
|
103
99
|
// 标准化路径
|
|
104
100
|
const normalizedPath = folderPath.startsWith('/') ? folderPath : '/' + folderPath;
|
|
@@ -151,6 +147,7 @@ async function ensureFolderPath(
|
|
|
151
147
|
node_type: NODE_TYPE_DIR,
|
|
152
148
|
name: folderName,
|
|
153
149
|
parent_id: currentParentId,
|
|
150
|
+
source: source,
|
|
154
151
|
};
|
|
155
152
|
|
|
156
153
|
const createResult = await tuituiRobotApi(account, '/file_space/node/add', createFolderPayload);
|
|
@@ -184,11 +181,18 @@ export async function file_space_add(
|
|
|
184
181
|
cloud_filepath: string,
|
|
185
182
|
url_or_localpath: string,
|
|
186
183
|
): Promise<any> {
|
|
187
|
-
const
|
|
188
|
-
if
|
|
184
|
+
const session_info = parseSessionKey(sessionKey);
|
|
185
|
+
if(session_info.chat_type != CHAT_TYPE_CHANNEL) {
|
|
189
186
|
throw new Error(`私聊、群聊会话不支持共享空间。仅团队频道支持。`);
|
|
190
187
|
}
|
|
191
188
|
|
|
189
|
+
const channelInfo = await getChannelInfoByChannelId(account, session_info.channel_id);
|
|
190
|
+
if (!channelInfo) {
|
|
191
|
+
throw new Error(`无法获取频道信息`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const source = session_info.thread_id;
|
|
195
|
+
|
|
192
196
|
// 1. 解析云路径,分离目录和文件名
|
|
193
197
|
const normalizedPath = cloud_filepath.startsWith('/')
|
|
194
198
|
? cloud_filepath
|
|
@@ -201,7 +205,7 @@ export async function file_space_add(
|
|
|
201
205
|
: '';
|
|
202
206
|
|
|
203
207
|
// 2. 确保目标目录存在,获取 parent_id
|
|
204
|
-
const parentId = await ensureFolderPath(account, channelInfo.team_id, folderPath);
|
|
208
|
+
const parentId = await ensureFolderPath(account, channelInfo.team_id, folderPath, source);
|
|
205
209
|
|
|
206
210
|
// 3. 上传文件获取 fid
|
|
207
211
|
const isImage = /^data:image\//i.test(url_or_localpath) || /\.(jpg|jpeg|png|gif)(?:$|[?#])/i.test(url_or_localpath);
|
|
@@ -219,6 +223,7 @@ export async function file_space_add(
|
|
|
219
223
|
name: filename,
|
|
220
224
|
fid: fid,
|
|
221
225
|
parent_id: parentId,
|
|
226
|
+
source: source,
|
|
222
227
|
};
|
|
223
228
|
|
|
224
229
|
const body = await tuituiRobotApi(account, '/file_space/node/add', filePayload);
|
package/src/inbound.ts
CHANGED
|
@@ -19,11 +19,13 @@ import {
|
|
|
19
19
|
sendPageMsg,
|
|
20
20
|
sendMediaMsg,
|
|
21
21
|
get_announcement,
|
|
22
|
+
get_team_members,
|
|
22
23
|
} from "./outbound";
|
|
23
24
|
import { parseChatMessageBody } from './inbound_body_parse';
|
|
24
25
|
import { parseAllowFroms } from './utils';
|
|
25
26
|
import { addUnmentionedHistory, popUnmentionedHistories } from "./histories";
|
|
26
27
|
import { StringDeduplicator } from "./deduplicator"
|
|
28
|
+
import { getPostChainByChatId } from "./chat_record"
|
|
27
29
|
|
|
28
30
|
// 会话上下文注入排重
|
|
29
31
|
let _session_ctx_injected = new StringDeduplicator(1000, 3600);
|
|
@@ -177,19 +179,52 @@ async function sendSingleChatPairingMsg(account: any, payload: any, log: any, ap
|
|
|
177
179
|
}
|
|
178
180
|
}
|
|
179
181
|
|
|
180
|
-
|
|
181
|
-
async function
|
|
182
|
-
if(!
|
|
182
|
+
//tuituiAccounts列表中是否存在至少1个允许私聊的用户(私聊白名单、配对、或者open)
|
|
183
|
+
async function isAllowOneOfAccount(tuituiAccounts: string[], apiRuntime: any, account: InboundAccount) {
|
|
184
|
+
if(!tuituiAccounts) return false;
|
|
183
185
|
const { accountId, allowFrom, dmPolicy } = account;
|
|
184
186
|
if (dmPolicy === 'open') return true;
|
|
185
187
|
|
|
186
|
-
|
|
188
|
+
// 是否包含任意一个元素
|
|
189
|
+
if (tuituiAccounts.some(item => allowFrom.includes(item))) return true;
|
|
190
|
+
|
|
187
191
|
try {
|
|
188
|
-
storeAllowFrom = parseAllowFroms(
|
|
192
|
+
let storeAllowFrom = parseAllowFroms(
|
|
189
193
|
await apiRuntime?.channel?.pairing?.readAllowFromStore?.({ channel: CHANNEL_ID, accountId })
|
|
190
194
|
);
|
|
195
|
+
if (tuituiAccounts.some(item => storeAllowFrom.includes(item))) return true;
|
|
191
196
|
} catch {}
|
|
192
|
-
return
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function isAllowAccount(tuituiAccount: string, apiRuntime: any, account: InboundAccount) {
|
|
201
|
+
return await isAllowOneOfAccount([tuituiAccount], apiRuntime, account);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function isChannelCoordAuthorized(tuituiAccount: string, apiRuntime: any, account: InboundAccount, team_id:string, chat_id:string) {
|
|
205
|
+
// 协调员身份校验
|
|
206
|
+
if (tuituiAccount != "bot-FiwwCeDw" && tuituiAccount != "bot-sUwUeknd") return false;
|
|
207
|
+
|
|
208
|
+
// 当前 thread 中有白名单用户发帖,则协调员继承该权限
|
|
209
|
+
const [members, posts] = await Promise.all([
|
|
210
|
+
get_team_members(account, team_id),
|
|
211
|
+
getPostChainByChatId(account, chat_id),
|
|
212
|
+
]);
|
|
213
|
+
if (!members?.length || !posts?.length) return false;
|
|
214
|
+
|
|
215
|
+
const from_uids = posts.map((p: any) => p.from_uid).filter(Boolean);
|
|
216
|
+
if (!from_uids.length) return false;
|
|
217
|
+
|
|
218
|
+
//console.log("from_uids", from_uids);
|
|
219
|
+
|
|
220
|
+
// 将 uid 映射到 user_account
|
|
221
|
+
const from_accounts: string[] = members
|
|
222
|
+
.filter((m: any) => from_uids.includes(m.uid) && m.account)
|
|
223
|
+
.map((m: any) => String(m.account));
|
|
224
|
+
if (!from_accounts.length) return false;
|
|
225
|
+
|
|
226
|
+
//console.log("from_accounts", from_accounts);
|
|
227
|
+
return await isAllowOneOfAccount(from_accounts, apiRuntime, account);
|
|
193
228
|
}
|
|
194
229
|
|
|
195
230
|
|
|
@@ -386,6 +421,12 @@ async function parse_teams_post(payload: ChatPayload, msgData: any, account: Inb
|
|
|
386
421
|
log?.info?.(`[${CHANNEL_ID}] AccountId: ${accountId}, ignore teams post (not mentioned)`);
|
|
387
422
|
return false;
|
|
388
423
|
}
|
|
424
|
+
|
|
425
|
+
// 频道中允许协调员@自己(且帖子里有白名单用户发帖)
|
|
426
|
+
if (await isChannelCoordAuthorized(tuituiAccount, apiRuntime, account, team_id, chatId)) {
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
429
|
+
|
|
389
430
|
if (needPairingThrottle(accountId, chatId)) {
|
|
390
431
|
log?.info?.(`[${CHANNEL_ID}] AccountId: ${accountId}, teams pairing throttled for teamsChatId=${chatId}`);
|
|
391
432
|
return false;
|
package/src/outbound.ts
CHANGED
|
@@ -234,3 +234,12 @@ export async function get_announcement(account: any, id: any, id_is_session: boo
|
|
|
234
234
|
const announcement = body?.datas?.info?.announcement;
|
|
235
235
|
return announcement;
|
|
236
236
|
}
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
export async function get_team_members(account: any, team_id: string): Promise<any> {
|
|
240
|
+
const payload = {team_id: team_id};
|
|
241
|
+
const body = await tuituiRobotApi(account, '/teams/member/list', payload);
|
|
242
|
+
const members = body?.datas?.members;
|
|
243
|
+
//console.log("info", members);
|
|
244
|
+
return members;
|
|
245
|
+
}
|
package/src/robot_api.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { readFileSync, existsSync, statSync } from 'node:fs';
|
|
2
2
|
import { basename } from 'node:path';
|
|
3
|
-
import { fetchWithSsrFGuard } from 'openclaw/plugin-sdk/tlon';
|
|
4
3
|
import { CHANNEL_ID } from "./const";
|
|
5
4
|
import { TUITUI_SSRF_POLICY, getTuituiApiHost } from "./env"
|
|
6
5
|
|
|
@@ -19,6 +18,8 @@ export function checkAccount(account: any, ctxTips: string = 'send text') {
|
|
|
19
18
|
export async function tuituiRobotApi(account: any, api: string, payload: any, log: boolean = true) {
|
|
20
19
|
checkAccount(account);
|
|
21
20
|
|
|
21
|
+
const startTime = Date.now(); // 记录开始时间
|
|
22
|
+
|
|
22
23
|
if (log) {
|
|
23
24
|
console.log(`[${CHANNEL_ID}] ${api} request`, payload);
|
|
24
25
|
}
|
|
@@ -32,8 +33,8 @@ export async function tuituiRobotApi(account: any, api: string, payload: any, lo
|
|
|
32
33
|
fetch_fun = _fetchForm;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
const { response, release } = await fetch_fun(url, payload);
|
|
36
36
|
try {
|
|
37
|
+
const response = await fetch_fun(url, payload);
|
|
37
38
|
const bodyText = await response.text();
|
|
38
39
|
|
|
39
40
|
if (!response.ok) {
|
|
@@ -50,36 +51,26 @@ export async function tuituiRobotApi(account: any, api: string, payload: any, lo
|
|
|
50
51
|
console.error(`[${CHANNEL_ID}] ${api} error:`, err);
|
|
51
52
|
throw err;
|
|
52
53
|
} finally {
|
|
53
|
-
|
|
54
|
+
if (log) {
|
|
55
|
+
const endTime = Date.now();
|
|
56
|
+
const duration = endTime - startTime;
|
|
57
|
+
console.log(`[${CHANNEL_ID}] ${api} response took ${duration}ms`);
|
|
58
|
+
}
|
|
54
59
|
}
|
|
55
60
|
}
|
|
56
61
|
|
|
57
|
-
function
|
|
58
|
-
return
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
function _fetchJson(url: string, json: any, auditCtx: string = "tuitui.api.call"): Promise<any> {
|
|
64
|
-
return _fetch({
|
|
65
|
-
url,
|
|
66
|
-
init: {
|
|
67
|
-
method: 'POST',
|
|
68
|
-
headers: { 'Content-Type': 'application/json' },
|
|
69
|
-
body: JSON.stringify(json),
|
|
70
|
-
},
|
|
71
|
-
auditCtx
|
|
62
|
+
function _fetchJson(url: string, json: any): Promise<Response> {
|
|
63
|
+
return fetch(url, {
|
|
64
|
+
method: 'POST',
|
|
65
|
+
headers: { 'Content-Type': 'application/json' },
|
|
66
|
+
body: JSON.stringify(json),
|
|
72
67
|
});
|
|
73
68
|
}
|
|
74
69
|
|
|
75
|
-
function _fetchForm(url: string, form: any
|
|
76
|
-
return
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
method: 'POST',
|
|
80
|
-
body: form,
|
|
81
|
-
},
|
|
82
|
-
auditCtx
|
|
70
|
+
function _fetchForm(url: string, form: any): Promise<Response> {
|
|
71
|
+
return fetch(url, {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
body: form,
|
|
83
74
|
});
|
|
84
75
|
}
|
|
85
76
|
|
|
@@ -89,10 +80,26 @@ function _fetchForm(url: string, form: any, auditCtx: string = "tuitui.api.call"
|
|
|
89
80
|
* @returns Object with buffer, filename and content type
|
|
90
81
|
*/
|
|
91
82
|
export async function downloadUrl(url: string): Promise<{ buffer: ArrayBuffer; filename: string; contentType: string }> {
|
|
92
|
-
|
|
83
|
+
console.log(`[${CHANNEL_ID}] downloadUrl prepair`);
|
|
84
|
+
|
|
85
|
+
let fetchWithSsrFGuard: any;
|
|
86
|
+
try {
|
|
87
|
+
fetchWithSsrFGuard = (await import('openclaw/plugin-sdk/ssrf-runtime')).fetchWithSsrFGuard;
|
|
88
|
+
} catch {
|
|
89
|
+
try {
|
|
90
|
+
fetchWithSsrFGuard = (await import('openclaw/plugin-sdk/tlon')).fetchWithSsrFGuard;
|
|
91
|
+
} catch {
|
|
92
|
+
throw new Error(`[${CHANNEL_ID}] fetchWithSsrFGuard() API import failed`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
console.log(`[${CHANNEL_ID}] downloadUrl start ${url}`);
|
|
97
|
+
|
|
98
|
+
const { response, release } = await fetchWithSsrFGuard({
|
|
99
|
+
policy: TUITUI_SSRF_POLICY,
|
|
93
100
|
url,
|
|
94
101
|
init: { method: 'GET' },
|
|
95
|
-
|
|
102
|
+
auditContext: "tuitui.download",
|
|
96
103
|
});
|
|
97
104
|
|
|
98
105
|
try {
|
|
@@ -120,6 +127,8 @@ export async function downloadUrl(url: string): Promise<{ buffer: ArrayBuffer; f
|
|
|
120
127
|
if (match) filename = decodeURIComponent(match[1]);
|
|
121
128
|
}
|
|
122
129
|
|
|
130
|
+
console.log(`[${CHANNEL_ID}] downloadUrl ok`);
|
|
131
|
+
|
|
123
132
|
return { buffer, filename, contentType };
|
|
124
133
|
} finally {
|
|
125
134
|
await release();
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { tuituiRobotApi} from "./robot_api"
|
|
2
|
+
|
|
3
|
+
export async function getChannelInfoByChannelId(account: any, channel_id: string) {
|
|
4
|
+
const payload = {channel_id: channel_id};
|
|
5
|
+
const body = await tuituiRobotApi(account, '/teams/channel/info', payload);
|
|
6
|
+
return body?.datas?.info;
|
|
7
|
+
}
|
package/src/tools.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
|
|
2
2
|
import type { OpenClawPluginToolContext } from "openclaw/plugin-sdk/core";
|
|
3
3
|
import { resolveAccount } from "./accounts"
|
|
4
4
|
import { Type } from "@sinclair/typebox";
|
|
5
|
-
|
|
5
|
+
import { CHANNEL_ID } from './const';
|
|
6
6
|
import {sendTextMsg, get_announcement} from "./outbound"
|
|
7
7
|
import {CHAT_TYPE_DIRECT,CHAT_TYPE_GROUP,CHAT_TYPE_CHANNEL,guessChatType, teamsBuildChatId} from "./chat_base"
|
|
8
8
|
import {getChatRecord, getChannelInfoById} from "./chat_record"
|
|
@@ -162,6 +162,14 @@ export function registerTuituiTools(api: OpenClawPluginApi) {
|
|
|
162
162
|
api.logger.debug?.("tuitui: Registered tool: No config available");
|
|
163
163
|
return;
|
|
164
164
|
}
|
|
165
|
+
|
|
166
|
+
const channels = api.config.channels || {};
|
|
167
|
+
const currChannel = channels[CHANNEL_ID] || {};
|
|
168
|
+
if (!currChannel?.appId && !currChannel?.accounts) {
|
|
169
|
+
// 无推推配置时,不需要注册工具防止污染上下文
|
|
170
|
+
// api.logger.info?.(`tuitui: ignore Registered tools`);
|
|
171
|
+
return
|
|
172
|
+
}
|
|
165
173
|
|
|
166
174
|
api.registerTool(tuitui_im_get_messages_factory);
|
|
167
175
|
api.registerTool(tuitui_send_channel_post_factory);
|