@nextclaw/channel-plugin-feishu 0.2.29-beta.0 → 0.2.29-beta.1
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/dist/index.d.ts +23 -0
- package/dist/index.js +45 -0
- package/dist/src/accounts.js +141 -0
- package/dist/src/app-scope-checker.js +36 -0
- package/dist/src/async.js +34 -0
- package/dist/src/auth-errors.js +72 -0
- package/dist/src/bitable.js +495 -0
- package/dist/src/bot.d.ts +35 -0
- package/dist/src/bot.js +941 -0
- package/dist/src/calendar-calendar.js +54 -0
- package/dist/src/calendar-event-attendee.js +98 -0
- package/dist/src/calendar-event.js +193 -0
- package/dist/src/calendar-freebusy.js +40 -0
- package/dist/src/calendar-shared.js +23 -0
- package/dist/src/calendar.js +16 -0
- package/dist/src/card-action.js +49 -0
- package/dist/src/channel.d.ts +7 -0
- package/dist/src/channel.js +413 -0
- package/dist/src/chat-schema.js +25 -0
- package/dist/src/chat.js +87 -0
- package/dist/src/client.d.ts +16 -0
- package/dist/src/client.js +112 -0
- package/dist/src/config-schema.d.ts +357 -0
- package/dist/src/dedup.js +126 -0
- package/dist/src/device-flow.js +109 -0
- package/dist/src/directory.js +101 -0
- package/dist/src/doc-schema.js +148 -0
- package/dist/src/docx-batch-insert.js +104 -0
- package/dist/src/docx-color-text.js +80 -0
- package/dist/src/docx-table-ops.js +197 -0
- package/dist/src/docx.js +858 -0
- package/dist/src/domains.js +14 -0
- package/dist/src/drive-schema.js +41 -0
- package/dist/src/drive.js +126 -0
- package/dist/src/dynamic-agent.js +93 -0
- package/dist/src/external-keys.js +13 -0
- package/dist/src/feishu-fetch.js +12 -0
- package/dist/src/identity.js +92 -0
- package/dist/src/lark-ticket.js +11 -0
- package/dist/src/media.d.ts +75 -0
- package/dist/src/media.js +304 -0
- package/dist/src/mention.d.ts +52 -0
- package/dist/src/mention.js +82 -0
- package/dist/src/monitor.account.d.ts +1 -0
- package/dist/src/monitor.account.js +393 -0
- package/dist/src/monitor.d.ts +11 -0
- package/dist/src/monitor.js +58 -0
- package/dist/src/monitor.startup.js +24 -0
- package/dist/src/monitor.state.d.ts +1 -0
- package/dist/src/monitor.state.js +80 -0
- package/dist/src/monitor.transport.js +167 -0
- package/dist/src/nextclaw-sdk/account-id.js +15 -0
- package/dist/src/nextclaw-sdk/core-channel.js +150 -0
- package/dist/src/nextclaw-sdk/core-pairing.js +151 -0
- package/dist/src/nextclaw-sdk/dedupe.js +164 -0
- package/dist/src/nextclaw-sdk/feishu.d.ts +1 -0
- package/dist/src/nextclaw-sdk/feishu.js +14 -0
- package/dist/src/nextclaw-sdk/history.js +69 -0
- package/dist/src/nextclaw-sdk/network-body.js +180 -0
- package/dist/src/nextclaw-sdk/network-fetch.js +63 -0
- package/dist/src/nextclaw-sdk/network-webhook.js +126 -0
- package/dist/src/nextclaw-sdk/network.js +4 -0
- package/dist/src/nextclaw-sdk/runtime-store.js +21 -0
- package/dist/src/nextclaw-sdk/secrets-config.js +65 -0
- package/dist/src/nextclaw-sdk/secrets-core.d.ts +1 -0
- package/dist/src/nextclaw-sdk/secrets-core.js +68 -0
- package/dist/src/nextclaw-sdk/secrets-prompt.js +193 -0
- package/dist/src/nextclaw-sdk/secrets.d.ts +1 -0
- package/dist/src/nextclaw-sdk/secrets.js +4 -0
- package/dist/src/nextclaw-sdk/types.d.ts +242 -0
- package/dist/src/oauth.js +171 -0
- package/dist/src/onboarding.js +381 -0
- package/dist/src/outbound.js +150 -0
- package/dist/src/perm-schema.js +49 -0
- package/dist/src/perm.js +90 -0
- package/dist/src/policy.js +61 -0
- package/dist/src/post.js +160 -0
- package/dist/src/probe.d.ts +11 -0
- package/dist/src/probe.js +85 -0
- package/dist/src/raw-request.js +24 -0
- package/dist/src/reactions.d.ts +67 -0
- package/dist/src/reactions.js +91 -0
- package/dist/src/reply-dispatcher.js +250 -0
- package/dist/src/runtime.js +5 -0
- package/dist/src/secret-input.js +3 -0
- package/dist/src/send-result.js +12 -0
- package/dist/src/send-target.js +22 -0
- package/dist/src/send.d.ts +51 -0
- package/dist/src/send.js +265 -0
- package/dist/src/sheets-shared.js +193 -0
- package/dist/src/sheets.js +95 -0
- package/dist/src/streaming-card.js +263 -0
- package/dist/src/targets.js +39 -0
- package/dist/src/task-comment.js +76 -0
- package/dist/src/task-shared.js +13 -0
- package/dist/src/task-subtask.js +79 -0
- package/dist/src/task-task.js +144 -0
- package/dist/src/task-tasklist.js +136 -0
- package/dist/src/task.js +16 -0
- package/dist/src/token-store.js +154 -0
- package/dist/src/tool-account.js +65 -0
- package/dist/src/tool-result.js +18 -0
- package/dist/src/tool-scopes.js +62 -0
- package/dist/src/tools-config.js +30 -0
- package/dist/src/types.d.ts +43 -0
- package/dist/src/typing.js +145 -0
- package/dist/src/uat-client.js +102 -0
- package/dist/src/user-tool-client.js +132 -0
- package/dist/src/user-tool-helpers.js +110 -0
- package/dist/src/user-tool-result.js +10 -0
- package/dist/src/wiki-schema.js +45 -0
- package/dist/src/wiki.js +144 -0
- package/package.json +8 -4
- package/index.ts +0 -75
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/domains.ts
|
|
2
|
+
function openPlatformDomain(domain) {
|
|
3
|
+
return domain === "lark" ? "https://open.larksuite.com" : "https://open.feishu.cn";
|
|
4
|
+
}
|
|
5
|
+
function wwwDomain(domain) {
|
|
6
|
+
return domain === "lark" ? "https://www.larksuite.com" : "https://www.feishu.cn";
|
|
7
|
+
}
|
|
8
|
+
function resolveDomainUrl(domain) {
|
|
9
|
+
if (domain === "feishu") return "https://open.feishu.cn";
|
|
10
|
+
if (domain === "lark") return "https://open.larksuite.com";
|
|
11
|
+
return domain.replace(/\/+$/, "");
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { openPlatformDomain, resolveDomainUrl, wwwDomain };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
//#region src/drive-schema.ts
|
|
3
|
+
const FileType = Type.Union([
|
|
4
|
+
Type.Literal("doc"),
|
|
5
|
+
Type.Literal("docx"),
|
|
6
|
+
Type.Literal("sheet"),
|
|
7
|
+
Type.Literal("bitable"),
|
|
8
|
+
Type.Literal("folder"),
|
|
9
|
+
Type.Literal("file"),
|
|
10
|
+
Type.Literal("mindnote"),
|
|
11
|
+
Type.Literal("shortcut")
|
|
12
|
+
]);
|
|
13
|
+
const FeishuDriveSchema = Type.Union([
|
|
14
|
+
Type.Object({
|
|
15
|
+
action: Type.Literal("list"),
|
|
16
|
+
folder_token: Type.Optional(Type.String({ description: "Folder token (optional, omit for root directory)" }))
|
|
17
|
+
}),
|
|
18
|
+
Type.Object({
|
|
19
|
+
action: Type.Literal("info"),
|
|
20
|
+
file_token: Type.String({ description: "File or folder token" }),
|
|
21
|
+
type: FileType
|
|
22
|
+
}),
|
|
23
|
+
Type.Object({
|
|
24
|
+
action: Type.Literal("create_folder"),
|
|
25
|
+
name: Type.String({ description: "Folder name" }),
|
|
26
|
+
folder_token: Type.Optional(Type.String({ description: "Parent folder token (optional, omit for root)" }))
|
|
27
|
+
}),
|
|
28
|
+
Type.Object({
|
|
29
|
+
action: Type.Literal("move"),
|
|
30
|
+
file_token: Type.String({ description: "File token to move" }),
|
|
31
|
+
type: FileType,
|
|
32
|
+
folder_token: Type.String({ description: "Target folder token" })
|
|
33
|
+
}),
|
|
34
|
+
Type.Object({
|
|
35
|
+
action: Type.Literal("delete"),
|
|
36
|
+
file_token: Type.String({ description: "File token to delete" }),
|
|
37
|
+
type: FileType
|
|
38
|
+
})
|
|
39
|
+
]);
|
|
40
|
+
//#endregion
|
|
41
|
+
export { FeishuDriveSchema };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { jsonToolResult, toolExecutionErrorResult, unknownToolActionResult } from "./tool-result.js";
|
|
2
|
+
import { createFeishuToolClient, resolveRegisteredFeishuToolsConfig } from "./tool-account.js";
|
|
3
|
+
import { FeishuDriveSchema } from "./drive-schema.js";
|
|
4
|
+
//#region src/drive.ts
|
|
5
|
+
async function getRootFolderToken(client) {
|
|
6
|
+
const domain = client.domain ?? "https://open.feishu.cn";
|
|
7
|
+
const res = await client.httpInstance.get(`${domain}/open-apis/drive/explorer/v2/root_folder/meta`);
|
|
8
|
+
if (res.code !== 0) throw new Error(res.msg ?? "Failed to get root folder");
|
|
9
|
+
const token = res.data?.token;
|
|
10
|
+
if (!token) throw new Error("Root folder token not found");
|
|
11
|
+
return token;
|
|
12
|
+
}
|
|
13
|
+
async function listFolder(client, folderToken) {
|
|
14
|
+
const validFolderToken = folderToken && folderToken !== "0" ? folderToken : void 0;
|
|
15
|
+
const res = await client.drive.file.list({ params: validFolderToken ? { folder_token: validFolderToken } : {} });
|
|
16
|
+
if (res.code !== 0) throw new Error(res.msg);
|
|
17
|
+
return {
|
|
18
|
+
files: res.data?.files?.map((f) => ({
|
|
19
|
+
token: f.token,
|
|
20
|
+
name: f.name,
|
|
21
|
+
type: f.type,
|
|
22
|
+
url: f.url,
|
|
23
|
+
created_time: f.created_time,
|
|
24
|
+
modified_time: f.modified_time,
|
|
25
|
+
owner_id: f.owner_id
|
|
26
|
+
})) ?? [],
|
|
27
|
+
next_page_token: res.data?.next_page_token
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
async function getFileInfo(client, fileToken, folderToken) {
|
|
31
|
+
const res = await client.drive.file.list({ params: folderToken ? { folder_token: folderToken } : {} });
|
|
32
|
+
if (res.code !== 0) throw new Error(res.msg);
|
|
33
|
+
const file = res.data?.files?.find((f) => f.token === fileToken);
|
|
34
|
+
if (!file) throw new Error(`File not found: ${fileToken}`);
|
|
35
|
+
return {
|
|
36
|
+
token: file.token,
|
|
37
|
+
name: file.name,
|
|
38
|
+
type: file.type,
|
|
39
|
+
url: file.url,
|
|
40
|
+
created_time: file.created_time,
|
|
41
|
+
modified_time: file.modified_time,
|
|
42
|
+
owner_id: file.owner_id
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async function createFolder(client, name, folderToken) {
|
|
46
|
+
let effectiveToken = folderToken && folderToken !== "0" ? folderToken : "0";
|
|
47
|
+
if (effectiveToken === "0") try {
|
|
48
|
+
effectiveToken = await getRootFolderToken(client);
|
|
49
|
+
} catch {}
|
|
50
|
+
const res = await client.drive.file.createFolder({ data: {
|
|
51
|
+
name,
|
|
52
|
+
folder_token: effectiveToken
|
|
53
|
+
} });
|
|
54
|
+
if (res.code !== 0) throw new Error(res.msg);
|
|
55
|
+
return {
|
|
56
|
+
token: res.data?.token,
|
|
57
|
+
url: res.data?.url
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
async function moveFile(client, fileToken, type, folderToken) {
|
|
61
|
+
const res = await client.drive.file.move({
|
|
62
|
+
path: { file_token: fileToken },
|
|
63
|
+
data: {
|
|
64
|
+
type,
|
|
65
|
+
folder_token: folderToken
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
if (res.code !== 0) throw new Error(res.msg);
|
|
69
|
+
return {
|
|
70
|
+
success: true,
|
|
71
|
+
task_id: res.data?.task_id
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
async function deleteFile(client, fileToken, type) {
|
|
75
|
+
const res = await client.drive.file.delete({
|
|
76
|
+
path: { file_token: fileToken },
|
|
77
|
+
params: { type }
|
|
78
|
+
});
|
|
79
|
+
if (res.code !== 0) throw new Error(res.msg);
|
|
80
|
+
return {
|
|
81
|
+
success: true,
|
|
82
|
+
task_id: res.data?.task_id
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function registerFeishuDriveTools(api) {
|
|
86
|
+
if (!api.config) {
|
|
87
|
+
api.logger.debug?.("feishu_drive: No config available, skipping drive tools");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (!resolveRegisteredFeishuToolsConfig(api.config).drive) {
|
|
91
|
+
api.logger.debug?.("feishu_drive: drive tool disabled in config");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
api.registerTool((ctx) => {
|
|
95
|
+
const defaultAccountId = ctx.agentAccountId;
|
|
96
|
+
return {
|
|
97
|
+
name: "feishu_drive",
|
|
98
|
+
label: "Feishu Drive",
|
|
99
|
+
description: "Feishu cloud storage operations. Actions: list, info, create_folder, move, delete",
|
|
100
|
+
parameters: FeishuDriveSchema,
|
|
101
|
+
async execute(_toolCallId, params) {
|
|
102
|
+
const p = params;
|
|
103
|
+
try {
|
|
104
|
+
const client = createFeishuToolClient({
|
|
105
|
+
api,
|
|
106
|
+
executeParams: p,
|
|
107
|
+
defaultAccountId
|
|
108
|
+
});
|
|
109
|
+
switch (p.action) {
|
|
110
|
+
case "list": return jsonToolResult(await listFolder(client, p.folder_token));
|
|
111
|
+
case "info": return jsonToolResult(await getFileInfo(client, p.file_token));
|
|
112
|
+
case "create_folder": return jsonToolResult(await createFolder(client, p.name, p.folder_token));
|
|
113
|
+
case "move": return jsonToolResult(await moveFile(client, p.file_token, p.type, p.folder_token));
|
|
114
|
+
case "delete": return jsonToolResult(await deleteFile(client, p.file_token, p.type));
|
|
115
|
+
default: return unknownToolActionResult(p.action);
|
|
116
|
+
}
|
|
117
|
+
} catch (err) {
|
|
118
|
+
return toolExecutionErrorResult(err);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}, { name: "feishu_drive" });
|
|
123
|
+
api.logger.info?.(`feishu_drive: Registered feishu_drive tool`);
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
126
|
+
export { registerFeishuDriveTools };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
//#region src/dynamic-agent.ts
|
|
5
|
+
/**
|
|
6
|
+
* Check if a dynamic agent should be created for a DM user and create it if needed.
|
|
7
|
+
* This creates a unique agent instance with its own workspace for each DM user.
|
|
8
|
+
*/
|
|
9
|
+
async function maybeCreateDynamicAgent(params) {
|
|
10
|
+
const { cfg, runtime, senderOpenId, dynamicCfg, log } = params;
|
|
11
|
+
const existingBindings = cfg.bindings ?? [];
|
|
12
|
+
if (existingBindings.some((b) => b.match?.channel === "feishu" && b.match?.peer?.kind === "direct" && b.match?.peer?.id === senderOpenId)) return {
|
|
13
|
+
created: false,
|
|
14
|
+
updatedCfg: cfg
|
|
15
|
+
};
|
|
16
|
+
if (dynamicCfg.maxAgents !== void 0) {
|
|
17
|
+
if ((cfg.agents?.list ?? []).filter((a) => a.id.startsWith("feishu-")).length >= dynamicCfg.maxAgents) {
|
|
18
|
+
log(`feishu: maxAgents limit (${dynamicCfg.maxAgents}) reached, not creating agent for ${senderOpenId}`);
|
|
19
|
+
return {
|
|
20
|
+
created: false,
|
|
21
|
+
updatedCfg: cfg
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const agentId = `feishu-${senderOpenId}`;
|
|
26
|
+
if ((cfg.agents?.list ?? []).find((a) => a.id === agentId)) {
|
|
27
|
+
log(`feishu: agent "${agentId}" exists, adding missing binding for ${senderOpenId}`);
|
|
28
|
+
const updatedCfg = {
|
|
29
|
+
...cfg,
|
|
30
|
+
bindings: [...existingBindings, {
|
|
31
|
+
agentId,
|
|
32
|
+
match: {
|
|
33
|
+
channel: "feishu",
|
|
34
|
+
peer: {
|
|
35
|
+
kind: "direct",
|
|
36
|
+
id: senderOpenId
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}]
|
|
40
|
+
};
|
|
41
|
+
await runtime.config.writeConfigFile(updatedCfg);
|
|
42
|
+
return {
|
|
43
|
+
created: true,
|
|
44
|
+
updatedCfg,
|
|
45
|
+
agentId
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const workspaceTemplate = dynamicCfg.workspaceTemplate ?? "~/.openclaw/workspace-{agentId}";
|
|
49
|
+
const agentDirTemplate = dynamicCfg.agentDirTemplate ?? "~/.openclaw/agents/{agentId}/agent";
|
|
50
|
+
const workspace = resolveUserPath(workspaceTemplate.replace("{userId}", senderOpenId).replace("{agentId}", agentId));
|
|
51
|
+
const agentDir = resolveUserPath(agentDirTemplate.replace("{userId}", senderOpenId).replace("{agentId}", agentId));
|
|
52
|
+
log(`feishu: creating dynamic agent "${agentId}" for user ${senderOpenId}`);
|
|
53
|
+
log(` workspace: ${workspace}`);
|
|
54
|
+
log(` agentDir: ${agentDir}`);
|
|
55
|
+
await fs.promises.mkdir(workspace, { recursive: true });
|
|
56
|
+
await fs.promises.mkdir(agentDir, { recursive: true });
|
|
57
|
+
const updatedCfg = {
|
|
58
|
+
...cfg,
|
|
59
|
+
agents: {
|
|
60
|
+
...cfg.agents,
|
|
61
|
+
list: [...cfg.agents?.list ?? [], {
|
|
62
|
+
id: agentId,
|
|
63
|
+
workspace,
|
|
64
|
+
agentDir
|
|
65
|
+
}]
|
|
66
|
+
},
|
|
67
|
+
bindings: [...existingBindings, {
|
|
68
|
+
agentId,
|
|
69
|
+
match: {
|
|
70
|
+
channel: "feishu",
|
|
71
|
+
peer: {
|
|
72
|
+
kind: "direct",
|
|
73
|
+
id: senderOpenId
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}]
|
|
77
|
+
};
|
|
78
|
+
await runtime.config.writeConfigFile(updatedCfg);
|
|
79
|
+
return {
|
|
80
|
+
created: true,
|
|
81
|
+
updatedCfg,
|
|
82
|
+
agentId
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Resolve a path that may start with ~ to the user's home directory.
|
|
87
|
+
*/
|
|
88
|
+
function resolveUserPath(p) {
|
|
89
|
+
if (p.startsWith("~/")) return path.join(os.homedir(), p.slice(2));
|
|
90
|
+
return p;
|
|
91
|
+
}
|
|
92
|
+
//#endregion
|
|
93
|
+
export { maybeCreateDynamicAgent };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/external-keys.ts
|
|
2
|
+
const CONTROL_CHARS_RE = /[\u0000-\u001f\u007f]/;
|
|
3
|
+
const MAX_EXTERNAL_KEY_LENGTH = 512;
|
|
4
|
+
function normalizeFeishuExternalKey(value) {
|
|
5
|
+
if (typeof value !== "string") return;
|
|
6
|
+
const normalized = value.trim();
|
|
7
|
+
if (!normalized || normalized.length > MAX_EXTERNAL_KEY_LENGTH) return;
|
|
8
|
+
if (CONTROL_CHARS_RE.test(normalized)) return;
|
|
9
|
+
if (normalized.includes("/") || normalized.includes("\\") || normalized.includes("..")) return;
|
|
10
|
+
return normalized;
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { normalizeFeishuExternalKey };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/feishu-fetch.ts
|
|
2
|
+
const FEISHU_USER_AGENT = "nextclaw-feishu-plugin/0.2";
|
|
3
|
+
function feishuFetch(url, init) {
|
|
4
|
+
const headers = new Headers(init?.headers);
|
|
5
|
+
if (!headers.has("User-Agent")) headers.set("User-Agent", FEISHU_USER_AGENT);
|
|
6
|
+
return fetch(url, {
|
|
7
|
+
...init,
|
|
8
|
+
headers
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { feishuFetch };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { StringEnum, assertLarkOk, createToolContext, handleInvokeError, json, registerTool } from "./user-tool-helpers.js";
|
|
2
|
+
import { resolveRegisteredFeishuToolsConfig } from "./tool-account.js";
|
|
3
|
+
import { Type } from "@sinclair/typebox";
|
|
4
|
+
//#region src/identity.ts
|
|
5
|
+
const GetUserSchema = Type.Object({
|
|
6
|
+
user_id: Type.Optional(Type.String({ description: "用户 ID(如 ou_xxx)。不传时获取当前消息发送者本人信息。" })),
|
|
7
|
+
user_id_type: Type.Optional(StringEnum([
|
|
8
|
+
"open_id",
|
|
9
|
+
"union_id",
|
|
10
|
+
"user_id"
|
|
11
|
+
]))
|
|
12
|
+
});
|
|
13
|
+
const SearchUserSchema = Type.Object({
|
|
14
|
+
query: Type.String({ description: "搜索关键词,可匹配姓名、邮箱、手机号等。" }),
|
|
15
|
+
page_size: Type.Optional(Type.Integer({
|
|
16
|
+
description: "分页大小,默认 20,最大 200。",
|
|
17
|
+
minimum: 1,
|
|
18
|
+
maximum: 200
|
|
19
|
+
})),
|
|
20
|
+
page_token: Type.Optional(Type.String({ description: "翻页 token。" }))
|
|
21
|
+
});
|
|
22
|
+
function registerFeishuIdentityTools(api) {
|
|
23
|
+
if (!api.config) return;
|
|
24
|
+
if (!resolveRegisteredFeishuToolsConfig(api.config).identity) return;
|
|
25
|
+
registerFeishuGetUserTool(api);
|
|
26
|
+
registerFeishuSearchUserTool(api);
|
|
27
|
+
}
|
|
28
|
+
function registerFeishuGetUserTool(api) {
|
|
29
|
+
const { toolClient, log } = createToolContext(api, "feishu_get_user");
|
|
30
|
+
registerTool(api, {
|
|
31
|
+
name: "feishu_get_user",
|
|
32
|
+
label: "Feishu: Get User",
|
|
33
|
+
description: "获取飞书用户信息。不传 user_id 时默认获取当前消息发送者本人信息;传 user_id 时获取指定用户信息。",
|
|
34
|
+
parameters: GetUserSchema,
|
|
35
|
+
async execute(_toolCallId, params) {
|
|
36
|
+
const payload = params;
|
|
37
|
+
try {
|
|
38
|
+
const client = toolClient();
|
|
39
|
+
if (!payload.user_id) {
|
|
40
|
+
log.info("fetching current user info");
|
|
41
|
+
const response = await client.invoke("feishu_get_user.default", (sdk, opts) => sdk.authen.userInfo.get({}, opts), { as: "user" });
|
|
42
|
+
assertLarkOk(response);
|
|
43
|
+
return json({ user: response.data });
|
|
44
|
+
}
|
|
45
|
+
log.info(`fetching user ${payload.user_id}`);
|
|
46
|
+
const response = await client.invoke("feishu_get_user.default", (sdk, opts) => sdk.contact.user.get({
|
|
47
|
+
path: { user_id: payload.user_id },
|
|
48
|
+
params: { user_id_type: payload.user_id_type ?? "open_id" }
|
|
49
|
+
}, opts), { as: "user" });
|
|
50
|
+
assertLarkOk(response);
|
|
51
|
+
return json({ user: response.data?.user });
|
|
52
|
+
} catch (error) {
|
|
53
|
+
return handleInvokeError(error, api);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}, { name: "feishu_get_user" });
|
|
57
|
+
}
|
|
58
|
+
function registerFeishuSearchUserTool(api) {
|
|
59
|
+
const { toolClient, log } = createToolContext(api, "feishu_search_user");
|
|
60
|
+
registerTool(api, {
|
|
61
|
+
name: "feishu_search_user",
|
|
62
|
+
label: "Feishu: Search User",
|
|
63
|
+
description: "搜索飞书员工信息,返回姓名、部门、open_id 等结果。",
|
|
64
|
+
parameters: SearchUserSchema,
|
|
65
|
+
async execute(_toolCallId, params) {
|
|
66
|
+
const payload = params;
|
|
67
|
+
try {
|
|
68
|
+
const client = toolClient();
|
|
69
|
+
log.info(`search query="${payload.query}"`);
|
|
70
|
+
const response = await client.invokeByPath("feishu_search_user.default", "/open-apis/search/v1/user", {
|
|
71
|
+
method: "GET",
|
|
72
|
+
query: {
|
|
73
|
+
query: payload.query,
|
|
74
|
+
page_size: String(payload.page_size ?? 20),
|
|
75
|
+
...payload.page_token ? { page_token: payload.page_token } : {}
|
|
76
|
+
},
|
|
77
|
+
as: "user"
|
|
78
|
+
});
|
|
79
|
+
assertLarkOk(response);
|
|
80
|
+
return json({
|
|
81
|
+
users: response.data?.users ?? [],
|
|
82
|
+
has_more: response.data?.has_more ?? false,
|
|
83
|
+
page_token: response.data?.page_token
|
|
84
|
+
});
|
|
85
|
+
} catch (error) {
|
|
86
|
+
return handleInvokeError(error, api);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}, { name: "feishu_search_user" });
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
export { registerFeishuIdentityTools };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
//#region src/lark-ticket.ts
|
|
3
|
+
const store = new AsyncLocalStorage();
|
|
4
|
+
function withTicket(ticket, fn) {
|
|
5
|
+
return store.run(ticket, fn);
|
|
6
|
+
}
|
|
7
|
+
function getTicket() {
|
|
8
|
+
return store.getStore();
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { getTicket, withTicket };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { ClawdbotConfig } from "./nextclaw-sdk/types.js";
|
|
2
|
+
//#region src/media.d.ts
|
|
3
|
+
type UploadImageResult = {
|
|
4
|
+
imageKey: string;
|
|
5
|
+
};
|
|
6
|
+
type UploadFileResult = {
|
|
7
|
+
fileKey: string;
|
|
8
|
+
};
|
|
9
|
+
type SendMediaResult = {
|
|
10
|
+
messageId: string;
|
|
11
|
+
chatId: string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Upload an image to Feishu and get an image_key for sending.
|
|
15
|
+
* Supports: JPEG, PNG, WEBP, GIF, TIFF, BMP, ICO
|
|
16
|
+
*/
|
|
17
|
+
declare function uploadImageFeishu(params: {
|
|
18
|
+
cfg: ClawdbotConfig;
|
|
19
|
+
image: Buffer | string;
|
|
20
|
+
imageType?: "message" | "avatar";
|
|
21
|
+
accountId?: string;
|
|
22
|
+
}): Promise<UploadImageResult>;
|
|
23
|
+
/**
|
|
24
|
+
* Upload a file to Feishu and get a file_key for sending.
|
|
25
|
+
* Max file size: 30MB
|
|
26
|
+
*/
|
|
27
|
+
declare function uploadFileFeishu(params: {
|
|
28
|
+
cfg: ClawdbotConfig;
|
|
29
|
+
file: Buffer | string;
|
|
30
|
+
fileName: string;
|
|
31
|
+
fileType: "opus" | "mp4" | "pdf" | "doc" | "xls" | "ppt" | "stream";
|
|
32
|
+
duration?: number;
|
|
33
|
+
accountId?: string;
|
|
34
|
+
}): Promise<UploadFileResult>;
|
|
35
|
+
/**
|
|
36
|
+
* Send an image message using an image_key
|
|
37
|
+
*/
|
|
38
|
+
declare function sendImageFeishu(params: {
|
|
39
|
+
cfg: ClawdbotConfig;
|
|
40
|
+
to: string;
|
|
41
|
+
imageKey: string;
|
|
42
|
+
replyToMessageId?: string;
|
|
43
|
+
replyInThread?: boolean;
|
|
44
|
+
accountId?: string;
|
|
45
|
+
}): Promise<SendMediaResult>;
|
|
46
|
+
/**
|
|
47
|
+
* Send a file message using a file_key
|
|
48
|
+
*/
|
|
49
|
+
declare function sendFileFeishu(params: {
|
|
50
|
+
cfg: ClawdbotConfig;
|
|
51
|
+
to: string;
|
|
52
|
+
fileKey: string; /** Use "audio" for audio, "media" for video (mp4), "file" for documents */
|
|
53
|
+
msgType?: "file" | "audio" | "media";
|
|
54
|
+
replyToMessageId?: string;
|
|
55
|
+
replyInThread?: boolean;
|
|
56
|
+
accountId?: string;
|
|
57
|
+
}): Promise<SendMediaResult>;
|
|
58
|
+
/**
|
|
59
|
+
* Upload and send media (image or file) from URL, local path, or buffer.
|
|
60
|
+
* When mediaUrl is a local path, mediaLocalRoots (from core outbound context)
|
|
61
|
+
* must be passed so loadWebMedia allows the path (post CVE-2026-26321).
|
|
62
|
+
*/
|
|
63
|
+
declare function sendMediaFeishu(params: {
|
|
64
|
+
cfg: ClawdbotConfig;
|
|
65
|
+
to: string;
|
|
66
|
+
mediaUrl?: string;
|
|
67
|
+
mediaBuffer?: Buffer;
|
|
68
|
+
fileName?: string;
|
|
69
|
+
replyToMessageId?: string;
|
|
70
|
+
replyInThread?: boolean;
|
|
71
|
+
accountId?: string; /** Allowed roots for local path reads; required for local filePath to work. */
|
|
72
|
+
mediaLocalRoots?: readonly string[];
|
|
73
|
+
}): Promise<SendMediaResult>;
|
|
74
|
+
//#endregion
|
|
75
|
+
export { sendFileFeishu, sendImageFeishu, sendMediaFeishu, uploadFileFeishu, uploadImageFeishu };
|