openclaw-extension-typex 1.0.19 → 1.0.21
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/README.md +8 -0
- package/dist/agent-tools-send.d.ts +50 -0
- package/dist/agent-tools-send.js +353 -0
- package/dist/client/accounts.d.ts +1 -1
- package/dist/client/accounts.js +10 -10
- package/dist/client/client.d.ts +48 -8
- package/dist/client/client.js +303 -115
- package/dist/client/config.d.ts +3 -2
- package/dist/client/domain.d.ts +7 -0
- package/dist/client/domain.js +9 -0
- package/dist/client/message.d.ts +2 -1
- package/dist/client/message.js +203 -61
- package/dist/client/monitor.d.ts +2 -1
- package/dist/client/monitor.js +36 -7
- package/dist/client/outbound.d.ts +1 -1
- package/dist/client/outbound.js +49 -4
- package/dist/client/runtime.d.ts +1 -1
- package/dist/client/send.d.ts +8 -0
- package/dist/client/send.js +11 -1
- package/dist/client/types.d.ts +17 -1
- package/dist/config-schema.d.ts +4 -2
- package/dist/config-schema.js +4 -4
- package/dist/directory.d.ts +24 -0
- package/dist/directory.js +94 -0
- package/dist/index.d.ts +178 -130
- package/dist/index.js +10 -9
- package/dist/normalize.js +7 -2
- package/dist/onboarding.d.ts +2 -2
- package/dist/onboarding.js +2 -2
- package/dist/plugin.d.ts +65 -22
- package/dist/plugin.js +81 -18
- package/dist/setup-entry.d.ts +181 -0
- package/dist/setup-entry.js +5 -0
- package/dist/types.d.ts +1 -1
- package/openclaw.plugin.json +4 -3
- package/package.json +26 -6
package/README.md
CHANGED
|
@@ -18,3 +18,11 @@ Onboarding: select TypeX and confirm the install prompt to fetch the plugin auto
|
|
|
18
18
|
|
|
19
19
|
Once the actual TypeX provider is implemented in the core `openclaw` repo, you can extend
|
|
20
20
|
this plugin to wire outbound messaging and gateway/runtime logic.
|
|
21
|
+
|
|
22
|
+
## Local checks
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
git config core.hooksPath .githooks
|
|
26
|
+
npm run commitlint -- "feat(typex): add delivery flow"
|
|
27
|
+
npm run verify:domain
|
|
28
|
+
```
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ChannelAgentTool } from "openclaw/plugin-sdk/channel-contract";
|
|
2
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core";
|
|
3
|
+
type ResolvedPeerTarget = {
|
|
4
|
+
kind: "chat";
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
matchedBy: "feed";
|
|
8
|
+
} | {
|
|
9
|
+
kind: "user";
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
matchedBy: "contact";
|
|
13
|
+
};
|
|
14
|
+
type ResolvedGroupMember = {
|
|
15
|
+
id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
};
|
|
18
|
+
type ExecuteTypeXSendByNameParams = {
|
|
19
|
+
cfg?: OpenClawConfig;
|
|
20
|
+
recipient: string;
|
|
21
|
+
message?: string;
|
|
22
|
+
mediaPath?: string;
|
|
23
|
+
mediaPaths?: string[];
|
|
24
|
+
accountId?: string | null;
|
|
25
|
+
};
|
|
26
|
+
type ExecuteTypeXSendInGroupParams = {
|
|
27
|
+
cfg?: OpenClawConfig;
|
|
28
|
+
chatId: string;
|
|
29
|
+
memberName: string;
|
|
30
|
+
message?: string;
|
|
31
|
+
mediaPath?: string;
|
|
32
|
+
mediaPaths?: string[];
|
|
33
|
+
accountId?: string | null;
|
|
34
|
+
};
|
|
35
|
+
export declare function executeTypeXSendByName(params: ExecuteTypeXSendByNameParams): Promise<{
|
|
36
|
+
target: ResolvedPeerTarget;
|
|
37
|
+
sent: string[];
|
|
38
|
+
}>;
|
|
39
|
+
export declare function executeTypeXSendInGroup(params: ExecuteTypeXSendInGroupParams): Promise<{
|
|
40
|
+
chatId: string;
|
|
41
|
+
member: ResolvedGroupMember;
|
|
42
|
+
sent: string[];
|
|
43
|
+
}>;
|
|
44
|
+
export declare function createTypeXSendByNameTool(params: {
|
|
45
|
+
cfg?: OpenClawConfig;
|
|
46
|
+
}): ChannelAgentTool;
|
|
47
|
+
export declare function createTypeXSendInGroupTool(params: {
|
|
48
|
+
cfg?: OpenClawConfig;
|
|
49
|
+
}): ChannelAgentTool;
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.executeTypeXSendByName = executeTypeXSendByName;
|
|
4
|
+
exports.executeTypeXSendInGroup = executeTypeXSendInGroup;
|
|
5
|
+
exports.createTypeXSendByNameTool = createTypeXSendByNameTool;
|
|
6
|
+
exports.createTypeXSendInGroupTool = createTypeXSendInGroupTool;
|
|
7
|
+
const promises_1 = require("node:fs/promises");
|
|
8
|
+
const node_path_1 = require("node:path");
|
|
9
|
+
const account_id_1 = require("openclaw/plugin-sdk/account-id");
|
|
10
|
+
const client_js_1 = require("./client/client.js");
|
|
11
|
+
const types_js_1 = require("./client/types.js");
|
|
12
|
+
const TYPEX_IMAGE_SEND_MSG_TYPE = 2;
|
|
13
|
+
const TYPEX_SEND_BY_NAME_SCHEMA = {
|
|
14
|
+
type: "object",
|
|
15
|
+
properties: {
|
|
16
|
+
recipient: { type: "string", minLength: 1 },
|
|
17
|
+
message: { type: "string" },
|
|
18
|
+
mediaPath: { type: "string" },
|
|
19
|
+
accountId: { type: "string" },
|
|
20
|
+
},
|
|
21
|
+
required: ["recipient"],
|
|
22
|
+
additionalProperties: false,
|
|
23
|
+
};
|
|
24
|
+
const TYPEX_SEND_IN_GROUP_SCHEMA = {
|
|
25
|
+
type: "object",
|
|
26
|
+
properties: {
|
|
27
|
+
chatId: { type: "string", minLength: 1, description: "当前 TypeX 群聊 chat_id,可传 chat:123 形式。" },
|
|
28
|
+
memberName: { type: "string", minLength: 1 },
|
|
29
|
+
message: { type: "string" },
|
|
30
|
+
mediaPath: { type: "string" },
|
|
31
|
+
accountId: { type: "string" },
|
|
32
|
+
},
|
|
33
|
+
required: ["chatId", "memberName"],
|
|
34
|
+
additionalProperties: false,
|
|
35
|
+
};
|
|
36
|
+
function normalizeName(value) {
|
|
37
|
+
return (value ?? "").trim().toLowerCase().replace(/\s+/g, "");
|
|
38
|
+
}
|
|
39
|
+
function normalizeChatId(value) {
|
|
40
|
+
return value.trim().replace(/^chat:/i, "").replace(/^group:/i, "");
|
|
41
|
+
}
|
|
42
|
+
function resolveTypeXClient(cfg, accountId) {
|
|
43
|
+
const typexCfg = (cfg?.channels?.["openclaw-extension-typex"] ?? {});
|
|
44
|
+
const resolvedAccountId = accountId?.trim() || account_id_1.DEFAULT_ACCOUNT_ID;
|
|
45
|
+
const client = (0, client_js_1.getTypeXClient)(resolvedAccountId, { typexCfg });
|
|
46
|
+
return { client, accountId: resolvedAccountId };
|
|
47
|
+
}
|
|
48
|
+
function dedupeByKey(items, getKey) {
|
|
49
|
+
const seen = new Set();
|
|
50
|
+
const result = [];
|
|
51
|
+
for (const item of items) {
|
|
52
|
+
const key = getKey(item).trim();
|
|
53
|
+
if (!key || seen.has(key)) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
seen.add(key);
|
|
57
|
+
result.push(item);
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
function filterMembersByName(items, query) {
|
|
62
|
+
const normalizedQuery = normalizeName(query);
|
|
63
|
+
const exact = items.filter((item) => normalizeName(item.name) === normalizedQuery);
|
|
64
|
+
if (exact.length > 0) {
|
|
65
|
+
return exact;
|
|
66
|
+
}
|
|
67
|
+
return items.filter((item) => normalizeName(item.name).includes(normalizedQuery));
|
|
68
|
+
}
|
|
69
|
+
function filterMembersMentionedInSentence(items, sentence) {
|
|
70
|
+
const normalizedSentence = normalizeName(sentence);
|
|
71
|
+
if (!normalizedSentence) {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
return items.filter((item) => {
|
|
75
|
+
const normalizedMemberName = normalizeName(item.name);
|
|
76
|
+
return normalizedMemberName.length > 0 && normalizedSentence.includes(normalizedMemberName);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
async function resolvePeerTargetByName(params) {
|
|
80
|
+
const { client } = resolveTypeXClient(params.cfg, params.accountId);
|
|
81
|
+
if (client.mode !== "user") {
|
|
82
|
+
throw new Error("typex_send_by_name 需要使用 TypeX user 账号。");
|
|
83
|
+
}
|
|
84
|
+
const contacts = await client.searchContactsByName(params.recipient);
|
|
85
|
+
const uniqueMatchingContacts = dedupeByKey(contacts
|
|
86
|
+
.map((contact) => ({
|
|
87
|
+
friendId: String(contact.friend_id ?? ""),
|
|
88
|
+
name: String(contact.name ?? contact.friend_id ?? ""),
|
|
89
|
+
}))
|
|
90
|
+
.filter((entry) => entry.friendId), (entry) => entry.friendId);
|
|
91
|
+
if (uniqueMatchingContacts.length === 1) {
|
|
92
|
+
return {
|
|
93
|
+
kind: "user",
|
|
94
|
+
id: uniqueMatchingContacts[0].friendId,
|
|
95
|
+
name: uniqueMatchingContacts[0].name,
|
|
96
|
+
matchedBy: "contact",
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (uniqueMatchingContacts.length > 1) {
|
|
100
|
+
throw new Error(`找到多个名为 ${params.recipient} 的联系人,请说得更具体一点。`);
|
|
101
|
+
}
|
|
102
|
+
const feeds = await client.searchFeedsByName(params.recipient);
|
|
103
|
+
const uniqueMatchingFeeds = dedupeByKey(feeds
|
|
104
|
+
.map((feed) => ({
|
|
105
|
+
chatId: String(feed.chat_id ?? ""),
|
|
106
|
+
name: String(feed.name ?? feed.chat_id ?? ""),
|
|
107
|
+
}))
|
|
108
|
+
.filter((entry) => entry.chatId), (entry) => entry.chatId);
|
|
109
|
+
if (uniqueMatchingFeeds.length === 1) {
|
|
110
|
+
return {
|
|
111
|
+
kind: "chat",
|
|
112
|
+
id: uniqueMatchingFeeds[0].chatId,
|
|
113
|
+
name: uniqueMatchingFeeds[0].name,
|
|
114
|
+
matchedBy: "feed",
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (uniqueMatchingFeeds.length > 1) {
|
|
118
|
+
throw new Error(`找到多个名为 ${params.recipient} 的会话,请说得更具体一点。`);
|
|
119
|
+
}
|
|
120
|
+
throw new Error(`没有找到名为 ${params.recipient} 的会话或联系人。`);
|
|
121
|
+
}
|
|
122
|
+
async function buildUploadedMediaPayload(params) {
|
|
123
|
+
const { client } = resolveTypeXClient(params.cfg, params.accountId);
|
|
124
|
+
const filePath = params.mediaPath.trim();
|
|
125
|
+
const fileName = (0, node_path_1.basename)(filePath);
|
|
126
|
+
const buffer = await (0, promises_1.readFile)(filePath);
|
|
127
|
+
const ext = (0, node_path_1.extname)(fileName).toLowerCase();
|
|
128
|
+
const isImage = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"].includes(ext);
|
|
129
|
+
const fileType = isImage ? "image" : "application";
|
|
130
|
+
const upload = client.mode === "bot"
|
|
131
|
+
? await client.uploadResource(fileName, fileType, buffer, params.chatId)
|
|
132
|
+
: await client.uploadUserResource(fileName, fileType, buffer, params.chatId);
|
|
133
|
+
if (isImage) {
|
|
134
|
+
return {
|
|
135
|
+
msgType: TYPEX_IMAGE_SEND_MSG_TYPE,
|
|
136
|
+
content: {
|
|
137
|
+
object_url: upload.address || upload.objectKey,
|
|
138
|
+
thumb_url: upload.address || upload.objectKey,
|
|
139
|
+
width: upload.width || 800,
|
|
140
|
+
height: upload.height || 600,
|
|
141
|
+
},
|
|
142
|
+
kindLabel: "image",
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
msgType: types_js_1.TypeXMessageEnum.file,
|
|
147
|
+
content: {
|
|
148
|
+
object_url: upload.address || upload.objectKey,
|
|
149
|
+
file_name: fileName,
|
|
150
|
+
file_size: buffer.length,
|
|
151
|
+
file_type: "application/octet-stream",
|
|
152
|
+
},
|
|
153
|
+
kindLabel: "file",
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function formatToolTextResult(text, details) {
|
|
157
|
+
return {
|
|
158
|
+
content: [{ type: "text", text }],
|
|
159
|
+
details,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
async function executeTypeXSendByName(params) {
|
|
163
|
+
const recipient = String(params.recipient ?? "").trim();
|
|
164
|
+
const message = String(params.message ?? "").trim();
|
|
165
|
+
const mediaPaths = [
|
|
166
|
+
...((params.mediaPaths ?? []).map((entry) => String(entry ?? "").trim()).filter(Boolean)),
|
|
167
|
+
...([params.mediaPath].map((entry) => String(entry ?? "").trim()).filter(Boolean)),
|
|
168
|
+
];
|
|
169
|
+
if (!recipient) {
|
|
170
|
+
throw new Error("recipient 不能为空。");
|
|
171
|
+
}
|
|
172
|
+
if (!message && mediaPaths.length === 0) {
|
|
173
|
+
throw new Error("message 和 mediaPath 至少要提供一个。");
|
|
174
|
+
}
|
|
175
|
+
const target = await resolvePeerTargetByName({
|
|
176
|
+
cfg: params.cfg,
|
|
177
|
+
recipient,
|
|
178
|
+
accountId: params.accountId,
|
|
179
|
+
});
|
|
180
|
+
const { client, accountId } = resolveTypeXClient(params.cfg, params.accountId);
|
|
181
|
+
const sent = [];
|
|
182
|
+
for (const mediaPath of mediaPaths) {
|
|
183
|
+
if (target.kind !== "chat") {
|
|
184
|
+
throw new Error("按联系人直发图片/文件需要已有会话 chat_id;当前只找到了联系人,没有可上传资源的会话。");
|
|
185
|
+
}
|
|
186
|
+
const uploaded = await buildUploadedMediaPayload({
|
|
187
|
+
cfg: params.cfg,
|
|
188
|
+
accountId,
|
|
189
|
+
chatId: target.id,
|
|
190
|
+
mediaPath,
|
|
191
|
+
});
|
|
192
|
+
await client.sendDelegatedChatMessage(target.id, uploaded.content, uploaded.msgType);
|
|
193
|
+
sent.push(uploaded.kindLabel);
|
|
194
|
+
}
|
|
195
|
+
if (message) {
|
|
196
|
+
if (target.kind === "chat") {
|
|
197
|
+
await client.sendDelegatedChatMessage(target.id, { text: message }, types_js_1.TypeXMessageEnum.text);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
await client.sendDelegatedContactMessage(target.id, { text: message }, types_js_1.TypeXMessageEnum.text);
|
|
201
|
+
}
|
|
202
|
+
sent.push("text");
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
target,
|
|
206
|
+
sent,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
async function executeTypeXSendInGroup(params) {
|
|
210
|
+
const rawChatId = String(params.chatId ?? "").trim();
|
|
211
|
+
const memberName = String(params.memberName ?? "").trim();
|
|
212
|
+
const message = String(params.message ?? "").trim();
|
|
213
|
+
const mediaPaths = [
|
|
214
|
+
...((params.mediaPaths ?? []).map((entry) => String(entry ?? "").trim()).filter(Boolean)),
|
|
215
|
+
...([params.mediaPath].map((entry) => String(entry ?? "").trim()).filter(Boolean)),
|
|
216
|
+
];
|
|
217
|
+
if (!rawChatId || !memberName) {
|
|
218
|
+
throw new Error("chatId 和 memberName 不能为空。");
|
|
219
|
+
}
|
|
220
|
+
if (!message && mediaPaths.length === 0) {
|
|
221
|
+
throw new Error("message 和 mediaPath 至少要提供一个。");
|
|
222
|
+
}
|
|
223
|
+
const chatId = normalizeChatId(rawChatId);
|
|
224
|
+
const member = await resolveGroupMemberByName({
|
|
225
|
+
cfg: params.cfg,
|
|
226
|
+
chatId,
|
|
227
|
+
memberName,
|
|
228
|
+
accountId: params.accountId,
|
|
229
|
+
});
|
|
230
|
+
const { client, accountId } = resolveTypeXClient(params.cfg, params.accountId);
|
|
231
|
+
const sent = [];
|
|
232
|
+
for (const mediaPath of mediaPaths) {
|
|
233
|
+
const uploaded = await buildUploadedMediaPayload({
|
|
234
|
+
cfg: params.cfg,
|
|
235
|
+
accountId,
|
|
236
|
+
chatId,
|
|
237
|
+
mediaPath,
|
|
238
|
+
});
|
|
239
|
+
await client.sendBotGroupMessage(chatId, uploaded.content, uploaded.msgType, {
|
|
240
|
+
atUserIds: [member.id],
|
|
241
|
+
atMentions: [{ id: member.id, name: member.name }],
|
|
242
|
+
});
|
|
243
|
+
sent.push(uploaded.kindLabel);
|
|
244
|
+
}
|
|
245
|
+
if (message) {
|
|
246
|
+
await client.sendBotGroupMessage(chatId, message, types_js_1.TypeXMessageEnum.text, {
|
|
247
|
+
atUserIds: [member.id],
|
|
248
|
+
atMentions: [{ id: member.id, name: member.name }],
|
|
249
|
+
});
|
|
250
|
+
sent.push("text");
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
chatId,
|
|
254
|
+
member,
|
|
255
|
+
sent,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function createTypeXSendByNameTool(params) {
|
|
259
|
+
return {
|
|
260
|
+
label: "TypeX Send By Name",
|
|
261
|
+
name: "typex_send_by_name",
|
|
262
|
+
description: "当用户在 TypeX 单聊里明确要求“发给某人/转给某人”时,用这个工具。它会按名字查找 TypeX 会话或联系人,并以当前登录的 user 身份代发文本或本地图片/文件。",
|
|
263
|
+
parameters: TYPEX_SEND_BY_NAME_SCHEMA,
|
|
264
|
+
execute: async (_toolCallId, rawArgs) => {
|
|
265
|
+
const args = rawArgs;
|
|
266
|
+
const recipient = String(args.recipient ?? "").trim();
|
|
267
|
+
const message = String(args.message ?? "").trim();
|
|
268
|
+
const mediaPath = String(args.mediaPath ?? "").trim();
|
|
269
|
+
if (!recipient) {
|
|
270
|
+
throw new Error("recipient 不能为空。");
|
|
271
|
+
}
|
|
272
|
+
if (!message && !mediaPath) {
|
|
273
|
+
throw new Error("message 和 mediaPath 至少要提供一个。");
|
|
274
|
+
}
|
|
275
|
+
const { target, sent } = await executeTypeXSendByName({
|
|
276
|
+
cfg: params.cfg,
|
|
277
|
+
recipient,
|
|
278
|
+
message,
|
|
279
|
+
mediaPath,
|
|
280
|
+
accountId: args.accountId,
|
|
281
|
+
});
|
|
282
|
+
return formatToolTextResult(`已向 ${target.name || recipient} 发送 ${sent.join(" + ")}。`, {
|
|
283
|
+
ok: true,
|
|
284
|
+
channel: "openclaw-extension-typex",
|
|
285
|
+
tool: "typex_send_by_name",
|
|
286
|
+
targetKind: target.kind,
|
|
287
|
+
targetId: target.id,
|
|
288
|
+
matchedBy: target.matchedBy,
|
|
289
|
+
sent,
|
|
290
|
+
});
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
async function resolveGroupMemberByName(params) {
|
|
295
|
+
const { client } = resolveTypeXClient(params.cfg, params.accountId);
|
|
296
|
+
if (client.mode !== "bot") {
|
|
297
|
+
throw new Error("typex_send_in_group 需要使用 TypeX bot 账号。");
|
|
298
|
+
}
|
|
299
|
+
const members = dedupeByKey((await client.listGroupMembers(params.chatId))
|
|
300
|
+
.map((member) => ({
|
|
301
|
+
id: String(member.user_id ?? ""),
|
|
302
|
+
name: String(member.name ?? member.user_id ?? ""),
|
|
303
|
+
}))
|
|
304
|
+
.filter((entry) => entry.id), (entry) => entry.id);
|
|
305
|
+
const directMatches = filterMembersByName(members, params.memberName).filter((entry) => entry.id);
|
|
306
|
+
const matches = directMatches.length > 0
|
|
307
|
+
? directMatches
|
|
308
|
+
: filterMembersMentionedInSentence(members, params.memberName).filter((entry) => entry.id);
|
|
309
|
+
if (matches.length === 1) {
|
|
310
|
+
return matches[0];
|
|
311
|
+
}
|
|
312
|
+
if (matches.length > 1) {
|
|
313
|
+
throw new Error(`当前群里有多个成员匹配 ${params.memberName},请说得更具体一点。`);
|
|
314
|
+
}
|
|
315
|
+
throw new Error(`当前群里没有找到名为 ${params.memberName} 的成员。`);
|
|
316
|
+
}
|
|
317
|
+
function createTypeXSendInGroupTool(params) {
|
|
318
|
+
return {
|
|
319
|
+
label: "TypeX Send In Group",
|
|
320
|
+
name: "typex_send_in_group",
|
|
321
|
+
description: "当用户在 TypeX 群聊里要求 bot 给某个群成员发消息时,用这个工具。它会在当前群里按名字找成员,并以 bot 身份在该群中 @ 对方发送文本或本地图片/文件。",
|
|
322
|
+
parameters: TYPEX_SEND_IN_GROUP_SCHEMA,
|
|
323
|
+
execute: async (_toolCallId, rawArgs) => {
|
|
324
|
+
const args = rawArgs;
|
|
325
|
+
const rawChatId = String(args.chatId ?? "").trim();
|
|
326
|
+
const memberName = String(args.memberName ?? "").trim();
|
|
327
|
+
const message = String(args.message ?? "").trim();
|
|
328
|
+
const mediaPath = String(args.mediaPath ?? "").trim();
|
|
329
|
+
if (!rawChatId || !memberName) {
|
|
330
|
+
throw new Error("chatId 和 memberName 不能为空。");
|
|
331
|
+
}
|
|
332
|
+
if (!message && !mediaPath) {
|
|
333
|
+
throw new Error("message 和 mediaPath 至少要提供一个。");
|
|
334
|
+
}
|
|
335
|
+
const { chatId, member, sent } = await executeTypeXSendInGroup({
|
|
336
|
+
cfg: params.cfg,
|
|
337
|
+
chatId: rawChatId,
|
|
338
|
+
memberName,
|
|
339
|
+
message,
|
|
340
|
+
mediaPath,
|
|
341
|
+
accountId: args.accountId,
|
|
342
|
+
});
|
|
343
|
+
return formatToolTextResult(`已在群 ${chatId} 中发送给 ${member.name || memberName}:${sent.join(" + ")}。`, {
|
|
344
|
+
ok: true,
|
|
345
|
+
channel: "openclaw-extension-typex",
|
|
346
|
+
tool: "typex_send_in_group",
|
|
347
|
+
chatId,
|
|
348
|
+
memberId: member.id,
|
|
349
|
+
sent,
|
|
350
|
+
});
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
1
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core";
|
|
2
2
|
import type { TypeXAccountConfig } from "../types.js";
|
|
3
3
|
export type TypeXTokenSource = "config" | "file" | "env" | "none";
|
|
4
4
|
export type ResolvedTypeXAccount = {
|
package/dist/client/accounts.js
CHANGED
|
@@ -7,7 +7,7 @@ exports.listTypeXAccountIds = listTypeXAccountIds;
|
|
|
7
7
|
exports.resolveDefaultTypeXAccountId = resolveDefaultTypeXAccountId;
|
|
8
8
|
exports.resolveTypeXAccount = resolveTypeXAccount;
|
|
9
9
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
|
-
const
|
|
10
|
+
const account_id_1 = require("openclaw/plugin-sdk/account-id");
|
|
11
11
|
function readFileIfExists(filePath) {
|
|
12
12
|
if (!filePath) {
|
|
13
13
|
return undefined;
|
|
@@ -28,8 +28,8 @@ function resolveAccountConfig(cfg, accountId) {
|
|
|
28
28
|
if (direct) {
|
|
29
29
|
return direct;
|
|
30
30
|
}
|
|
31
|
-
const normalized = (0,
|
|
32
|
-
const matchKey = Object.keys(accounts).find((key) => (0,
|
|
31
|
+
const normalized = (0, account_id_1.normalizeAccountId)(accountId);
|
|
32
|
+
const matchKey = Object.keys(accounts).find((key) => (0, account_id_1.normalizeAccountId)(key) === normalized);
|
|
33
33
|
return matchKey ? accounts[matchKey] : undefined;
|
|
34
34
|
}
|
|
35
35
|
function mergeTypeXAccountConfig(cfg, accountId) {
|
|
@@ -55,29 +55,29 @@ function listTypeXAccountIds(cfg) {
|
|
|
55
55
|
const baseConfigured = Boolean(typexCfg?.appId?.trim() && (typexCfg?.appSecret?.trim() || Boolean(typexCfg?.appSecretFile)));
|
|
56
56
|
const envConfigured = Boolean(process.env.TYPEX_APP_ID?.trim() && process.env.TYPEX_APP_SECRET?.trim());
|
|
57
57
|
if (baseConfigured || envConfigured) {
|
|
58
|
-
ids.add(
|
|
58
|
+
ids.add(account_id_1.DEFAULT_ACCOUNT_ID);
|
|
59
59
|
}
|
|
60
60
|
if (accounts) {
|
|
61
61
|
for (const id of Object.keys(accounts)) {
|
|
62
|
-
ids.add((0,
|
|
62
|
+
ids.add((0, account_id_1.normalizeAccountId)(id));
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
return Array.from(ids);
|
|
66
66
|
}
|
|
67
67
|
function resolveDefaultTypeXAccountId(cfg) {
|
|
68
68
|
const ids = listTypeXAccountIds(cfg);
|
|
69
|
-
if (ids.includes(
|
|
70
|
-
return
|
|
69
|
+
if (ids.includes(account_id_1.DEFAULT_ACCOUNT_ID)) {
|
|
70
|
+
return account_id_1.DEFAULT_ACCOUNT_ID;
|
|
71
71
|
}
|
|
72
|
-
return ids[0] ??
|
|
72
|
+
return ids[0] ?? account_id_1.DEFAULT_ACCOUNT_ID;
|
|
73
73
|
}
|
|
74
74
|
function resolveTypeXAccount(params) {
|
|
75
|
-
const accountId = (0,
|
|
75
|
+
const accountId = (0, account_id_1.normalizeAccountId)(params.accountId);
|
|
76
76
|
const baseEnabled = params.cfg.channels?.['openclaw-extension-typex']?.enabled !== false;
|
|
77
77
|
const merged = mergeTypeXAccountConfig(params.cfg, accountId);
|
|
78
78
|
const accountEnabled = merged.enabled !== false;
|
|
79
79
|
const enabled = baseEnabled && accountEnabled;
|
|
80
|
-
const allowEnv = accountId ===
|
|
80
|
+
const allowEnv = accountId === account_id_1.DEFAULT_ACCOUNT_ID;
|
|
81
81
|
const envAppId = allowEnv ? process.env.TYPEX_APP_ID?.trim() : undefined;
|
|
82
82
|
const envAppSecret = allowEnv ? process.env.TYPEX_APP_SECRET?.trim() : undefined;
|
|
83
83
|
const appId = merged.appId?.trim() || envAppId || "";
|
package/dist/client/client.d.ts
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
import { TypeXMessageEnum, type TypeXClientOptions, type TypeXMessageEntry } from "./types.js";
|
|
1
|
+
import { TypeXMessageEnum, type TypeXClientOptions, type TypeXContactSearchEntry, type TypeXFeedSearchEntry, type TypeXGroupMemberEntry, type TypeXMessageEntry } from "./types.js";
|
|
2
|
+
type TypeXSendOptions = {
|
|
3
|
+
replyMsgId?: string;
|
|
4
|
+
receiverId?: string;
|
|
5
|
+
isDelegate?: boolean;
|
|
6
|
+
atUserIds?: string[];
|
|
7
|
+
atMentions?: Array<{
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
}>;
|
|
11
|
+
};
|
|
2
12
|
export declare class TypeXClient {
|
|
3
13
|
private options;
|
|
4
14
|
private accessToken?;
|
|
@@ -7,16 +17,22 @@ export declare class TypeXClient {
|
|
|
7
17
|
get mode(): "user" | "bot";
|
|
8
18
|
getAccessToken(): Promise<string>;
|
|
9
19
|
getCurUserId(): Promise<string>;
|
|
20
|
+
private getAuthHeaders;
|
|
21
|
+
private postJson;
|
|
10
22
|
fetchQrcodeUrl(): Promise<any>;
|
|
11
23
|
checkLoginStatus(qrcodeId: string): Promise<boolean>;
|
|
24
|
+
private executeSendMessage;
|
|
25
|
+
sendUserChatMessage(chatId: string, content: string | object, msgType?: TypeXMessageEnum): Promise<any>;
|
|
26
|
+
sendDelegatedChatMessage(chatId: string, content: string | object, msgType?: TypeXMessageEnum): Promise<any>;
|
|
27
|
+
sendDelegatedContactMessage(receiverId: string, content: string | object, msgType?: TypeXMessageEnum): Promise<any>;
|
|
28
|
+
sendBotGroupMessage(chatId: string, content: string | object, msgType?: TypeXMessageEnum, options?: Pick<TypeXSendOptions, "replyMsgId" | "atUserIds" | "atMentions">): Promise<any>;
|
|
12
29
|
/**
|
|
13
|
-
*
|
|
14
|
-
* @param to chat_id to send to
|
|
15
|
-
* @param content message text or object
|
|
30
|
+
* Compatibility wrapper. Prefer the explicit methods above for new call sites.
|
|
16
31
|
*/
|
|
17
|
-
sendMessage(to: string, content: string | object, msgType?: TypeXMessageEnum, options?:
|
|
18
|
-
|
|
19
|
-
|
|
32
|
+
sendMessage(to: string, content: string | object, msgType?: TypeXMessageEnum, options?: TypeXSendOptions): Promise<any>;
|
|
33
|
+
searchFeedsByName(name: string): Promise<TypeXFeedSearchEntry[]>;
|
|
34
|
+
searchContactsByName(name: string): Promise<TypeXContactSearchEntry[]>;
|
|
35
|
+
listGroupMembers(chatId: string): Promise<TypeXGroupMemberEntry[]>;
|
|
20
36
|
/**
|
|
21
37
|
* Upload resource for the robot to send.
|
|
22
38
|
* @param fileName Name of the file
|
|
@@ -25,6 +41,7 @@ export declare class TypeXClient {
|
|
|
25
41
|
* @param chatId Optional chat_id
|
|
26
42
|
*/
|
|
27
43
|
uploadResource(fileName: string, fileType: "image" | "audio" | "video" | "application", fileContent: Buffer | Blob, chatId?: string): Promise<any>;
|
|
44
|
+
uploadUserResource(fileName: string, fileType: "image" | "audio" | "video" | "application", fileContent: Buffer | Blob, chatId: string): Promise<any>;
|
|
28
45
|
/**
|
|
29
46
|
* Fetch messages. Dispatches to user or bot endpoint based on mode.
|
|
30
47
|
*/
|
|
@@ -33,7 +50,6 @@ export declare class TypeXClient {
|
|
|
33
50
|
private fetchUserMessages;
|
|
34
51
|
/**
|
|
35
52
|
* Pull messages for a bot account (Bearer token auth).
|
|
36
|
-
* TODO: replace /open/bot/message with the actual endpoint path once confirmed.
|
|
37
53
|
*/
|
|
38
54
|
private fetchBotMessages;
|
|
39
55
|
/**
|
|
@@ -48,5 +64,29 @@ export declare class TypeXClient {
|
|
|
48
64
|
buffer: Buffer;
|
|
49
65
|
mimeType: string;
|
|
50
66
|
} | null>;
|
|
67
|
+
/**
|
|
68
|
+
* Search feeds by name (User mode)
|
|
69
|
+
*/
|
|
70
|
+
fetchFeedsByName(name: string): Promise<Array<{
|
|
71
|
+
id: string;
|
|
72
|
+
name: string;
|
|
73
|
+
}>>;
|
|
74
|
+
/**
|
|
75
|
+
* Search contacts by name (User mode)
|
|
76
|
+
*/
|
|
77
|
+
fetchContactsByName(name: string): Promise<Array<{
|
|
78
|
+
id: string;
|
|
79
|
+
name: string;
|
|
80
|
+
alias?: string;
|
|
81
|
+
}>>;
|
|
82
|
+
/**
|
|
83
|
+
* Search group members by name (Bot mode)
|
|
84
|
+
*/
|
|
85
|
+
fetchGroupMembersByName(name: string): Promise<Array<{
|
|
86
|
+
id: string;
|
|
87
|
+
name: string;
|
|
88
|
+
group_alias?: string;
|
|
89
|
+
}>>;
|
|
51
90
|
}
|
|
52
91
|
export declare function getTypeXClient(accountId?: string, manualOptions?: TypeXClientOptions): TypeXClient;
|
|
92
|
+
export {};
|