@max1874/feishu 0.1.7 → 0.2.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/package.json +1 -1
- package/src/docx.ts +262 -8
package/package.json
CHANGED
package/src/docx.ts
CHANGED
|
@@ -232,6 +232,101 @@ async function processImages(
|
|
|
232
232
|
return processed;
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
+
// ============ Wiki Functions ============
|
|
236
|
+
|
|
237
|
+
type WikiNodeInfo = {
|
|
238
|
+
node_token: string;
|
|
239
|
+
obj_token: string;
|
|
240
|
+
obj_type: string;
|
|
241
|
+
space_id: string;
|
|
242
|
+
title?: string;
|
|
243
|
+
parent_node_token?: string;
|
|
244
|
+
node_type?: string;
|
|
245
|
+
origin_space_id?: string;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
/** Get wiki node info by token (from /wiki/XXX URL) */
|
|
249
|
+
async function getWikiNode(client: Lark.Client, wikiToken: string): Promise<WikiNodeInfo> {
|
|
250
|
+
const res = await client.wiki.space.getNode({
|
|
251
|
+
params: { token: wikiToken },
|
|
252
|
+
});
|
|
253
|
+
if (res.code !== 0) throw new Error(res.msg);
|
|
254
|
+
const node = res.data?.node;
|
|
255
|
+
if (!node) throw new Error("Wiki node not found");
|
|
256
|
+
return {
|
|
257
|
+
node_token: node.node_token ?? wikiToken,
|
|
258
|
+
obj_token: node.obj_token ?? "",
|
|
259
|
+
obj_type: node.obj_type ?? "",
|
|
260
|
+
space_id: node.space_id ?? "",
|
|
261
|
+
title: node.title,
|
|
262
|
+
parent_node_token: node.parent_node_token,
|
|
263
|
+
node_type: node.node_type,
|
|
264
|
+
origin_space_id: node.origin_space_id,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** Read wiki page content (resolves to underlying docx) */
|
|
269
|
+
async function readWiki(client: Lark.Client, wikiToken: string) {
|
|
270
|
+
// 1. Get wiki node info to find the underlying document
|
|
271
|
+
const node = await getWikiNode(client, wikiToken);
|
|
272
|
+
|
|
273
|
+
if (node.obj_type !== "docx") {
|
|
274
|
+
return {
|
|
275
|
+
node,
|
|
276
|
+
error: `Wiki node is of type '${node.obj_type}', only 'docx' is supported for reading content. Use the obj_token with the appropriate API.`,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 2. Read the underlying docx content
|
|
281
|
+
const docContent = await readDoc(client, node.obj_token);
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
wiki_token: wikiToken,
|
|
285
|
+
title: node.title,
|
|
286
|
+
obj_type: node.obj_type,
|
|
287
|
+
obj_token: node.obj_token,
|
|
288
|
+
space_id: node.space_id,
|
|
289
|
+
...docContent,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** List wiki spaces */
|
|
294
|
+
async function listWikiSpaces(client: Lark.Client) {
|
|
295
|
+
const res = await client.wiki.space.list({});
|
|
296
|
+
if (res.code !== 0) throw new Error(res.msg);
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
spaces:
|
|
300
|
+
res.data?.items?.map((s) => ({
|
|
301
|
+
space_id: s.space_id,
|
|
302
|
+
name: s.name,
|
|
303
|
+
description: s.description,
|
|
304
|
+
type: s.space_type === "team" ? "team" : "personal",
|
|
305
|
+
visibility: s.visibility,
|
|
306
|
+
})) ?? [],
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** List wiki nodes under a parent */
|
|
311
|
+
async function listWikiNodes(client: Lark.Client, spaceId: string, parentNodeToken?: string) {
|
|
312
|
+
const res = await client.wiki.spaceNode.list({
|
|
313
|
+
path: { space_id: spaceId },
|
|
314
|
+
params: { parent_node_token: parentNodeToken },
|
|
315
|
+
});
|
|
316
|
+
if (res.code !== 0) throw new Error(res.msg);
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
nodes:
|
|
320
|
+
res.data?.items?.map((n) => ({
|
|
321
|
+
node_token: n.node_token,
|
|
322
|
+
obj_token: n.obj_token,
|
|
323
|
+
obj_type: n.obj_type,
|
|
324
|
+
title: n.title,
|
|
325
|
+
has_child: n.has_child,
|
|
326
|
+
})) ?? [],
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
235
330
|
// ============ Actions ============
|
|
236
331
|
|
|
237
332
|
// Block types that are NOT included in rawContent (plain text) output
|
|
@@ -278,16 +373,45 @@ async function readDoc(client: Lark.Client, docToken: string) {
|
|
|
278
373
|
};
|
|
279
374
|
}
|
|
280
375
|
|
|
281
|
-
|
|
376
|
+
/** Set document permission to allow tenant members to edit */
|
|
377
|
+
async function setDocPermissionTenantEditable(client: Lark.Client, docToken: string) {
|
|
378
|
+
const res = await client.drive.permissionPublic.patch({
|
|
379
|
+
path: { token: docToken },
|
|
380
|
+
params: { type: "docx" },
|
|
381
|
+
data: {
|
|
382
|
+
link_share_entity: "tenant_editable",
|
|
383
|
+
share_entity: "same_tenant",
|
|
384
|
+
},
|
|
385
|
+
});
|
|
386
|
+
if (res.code !== 0) throw new Error(res.msg);
|
|
387
|
+
return res.data;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export type DocPermission = "private" | "tenant_editable";
|
|
391
|
+
|
|
392
|
+
async function createDoc(
|
|
393
|
+
client: Lark.Client,
|
|
394
|
+
title: string,
|
|
395
|
+
folderToken?: string,
|
|
396
|
+
permission?: DocPermission,
|
|
397
|
+
) {
|
|
282
398
|
const res = await client.docx.document.create({
|
|
283
399
|
data: { title, folder_token: folderToken },
|
|
284
400
|
});
|
|
285
401
|
if (res.code !== 0) throw new Error(res.msg);
|
|
286
402
|
const doc = res.data?.document;
|
|
403
|
+
const docId = doc?.document_id;
|
|
404
|
+
|
|
405
|
+
// Set permission if requested
|
|
406
|
+
if (permission === "tenant_editable" && docId) {
|
|
407
|
+
await setDocPermissionTenantEditable(client, docId);
|
|
408
|
+
}
|
|
409
|
+
|
|
287
410
|
return {
|
|
288
|
-
document_id:
|
|
411
|
+
document_id: docId,
|
|
289
412
|
title: doc?.title,
|
|
290
|
-
url: `https://feishu.cn/docx/${
|
|
413
|
+
url: `https://feishu.cn/docx/${docId}`,
|
|
414
|
+
permission: permission ?? "private",
|
|
291
415
|
};
|
|
292
416
|
}
|
|
293
417
|
|
|
@@ -455,6 +579,12 @@ const DocTokenSchema = Type.Object({
|
|
|
455
579
|
const CreateDocSchema = Type.Object({
|
|
456
580
|
title: Type.String({ description: "Document title" }),
|
|
457
581
|
folder_token: Type.Optional(Type.String({ description: "Target folder token (optional)" })),
|
|
582
|
+
permission: Type.Optional(
|
|
583
|
+
Type.Union([Type.Literal("private"), Type.Literal("tenant_editable")], {
|
|
584
|
+
description:
|
|
585
|
+
"Document permission: 'private' (default) or 'tenant_editable' (organization members can edit via link)",
|
|
586
|
+
}),
|
|
587
|
+
),
|
|
458
588
|
});
|
|
459
589
|
|
|
460
590
|
const WriteDocSchema = Type.Object({
|
|
@@ -489,6 +619,25 @@ const FolderTokenSchema = Type.Object({
|
|
|
489
619
|
folder_token: Type.String({ description: "Folder token" }),
|
|
490
620
|
});
|
|
491
621
|
|
|
622
|
+
const SetPermissionSchema = Type.Object({
|
|
623
|
+
doc_token: Type.String({ description: "Document token" }),
|
|
624
|
+
permission: Type.Union([Type.Literal("private"), Type.Literal("tenant_editable")], {
|
|
625
|
+
description: "'private' or 'tenant_editable' (organization members can edit via link)",
|
|
626
|
+
}),
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
// Wiki Schemas
|
|
630
|
+
const WikiTokenSchema = Type.Object({
|
|
631
|
+
wiki_token: Type.String({ description: "Wiki token (extract from URL /wiki/XXX)" }),
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
const WikiSpaceIdSchema = Type.Object({
|
|
635
|
+
space_id: Type.String({ description: "Wiki space ID" }),
|
|
636
|
+
parent_node_token: Type.Optional(
|
|
637
|
+
Type.String({ description: "Parent node token (optional, for listing children)" }),
|
|
638
|
+
),
|
|
639
|
+
});
|
|
640
|
+
|
|
492
641
|
// ============ Tool Registration ============
|
|
493
642
|
|
|
494
643
|
export function registerFeishuDocTools(api: OpenClawPluginApi) {
|
|
@@ -525,12 +674,17 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
|
|
|
525
674
|
{
|
|
526
675
|
name: "feishu_doc_create",
|
|
527
676
|
label: "Feishu Doc Create",
|
|
528
|
-
description:
|
|
677
|
+
description:
|
|
678
|
+
"Create a new empty Feishu document. Use permission='tenant_editable' to allow organization members to edit via link.",
|
|
529
679
|
parameters: CreateDocSchema,
|
|
530
680
|
async execute(_toolCallId, params) {
|
|
531
|
-
const { title, folder_token } = params as {
|
|
681
|
+
const { title, folder_token, permission } = params as {
|
|
682
|
+
title: string;
|
|
683
|
+
folder_token?: string;
|
|
684
|
+
permission?: DocPermission;
|
|
685
|
+
};
|
|
532
686
|
try {
|
|
533
|
-
const result = await createDoc(getClient(), title, folder_token);
|
|
687
|
+
const result = await createDoc(getClient(), title, folder_token, permission);
|
|
534
688
|
return json(result);
|
|
535
689
|
} catch (err) {
|
|
536
690
|
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
@@ -687,7 +841,107 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
|
|
|
687
841
|
{ name: "feishu_folder_list" },
|
|
688
842
|
);
|
|
689
843
|
|
|
690
|
-
// Tool 10:
|
|
844
|
+
// Tool 10: feishu_doc_set_permission
|
|
845
|
+
api.registerTool(
|
|
846
|
+
{
|
|
847
|
+
name: "feishu_doc_set_permission",
|
|
848
|
+
label: "Feishu Doc Set Permission",
|
|
849
|
+
description:
|
|
850
|
+
"Set document sharing permission. Use 'tenant_editable' to allow organization members to edit via link.",
|
|
851
|
+
parameters: SetPermissionSchema,
|
|
852
|
+
async execute(_toolCallId, params) {
|
|
853
|
+
const { doc_token, permission } = params as { doc_token: string; permission: DocPermission };
|
|
854
|
+
try {
|
|
855
|
+
if (permission === "tenant_editable") {
|
|
856
|
+
await setDocPermissionTenantEditable(getClient(), doc_token);
|
|
857
|
+
return json({ success: true, permission: "tenant_editable" });
|
|
858
|
+
} else {
|
|
859
|
+
// Reset to private (only owner can access)
|
|
860
|
+
const client = getClient();
|
|
861
|
+
const res = await client.drive.permissionPublic.patch({
|
|
862
|
+
path: { token: doc_token },
|
|
863
|
+
params: { type: "docx" },
|
|
864
|
+
data: {
|
|
865
|
+
link_share_entity: "closed",
|
|
866
|
+
share_entity: "only_full_access",
|
|
867
|
+
},
|
|
868
|
+
});
|
|
869
|
+
if (res.code !== 0) throw new Error(res.msg);
|
|
870
|
+
return json({ success: true, permission: "private" });
|
|
871
|
+
}
|
|
872
|
+
} catch (err) {
|
|
873
|
+
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
874
|
+
}
|
|
875
|
+
},
|
|
876
|
+
},
|
|
877
|
+
{ name: "feishu_doc_set_permission" },
|
|
878
|
+
);
|
|
879
|
+
|
|
880
|
+
// Tool 11: feishu_wiki_read
|
|
881
|
+
api.registerTool(
|
|
882
|
+
{
|
|
883
|
+
name: "feishu_wiki_read",
|
|
884
|
+
label: "Feishu Wiki Read",
|
|
885
|
+
description:
|
|
886
|
+
"Read content from a Feishu wiki page. Extract wiki_token from URL /wiki/XXX. Returns wiki metadata and document content if the underlying type is docx.",
|
|
887
|
+
parameters: WikiTokenSchema,
|
|
888
|
+
async execute(_toolCallId, params) {
|
|
889
|
+
const { wiki_token } = params as { wiki_token: string };
|
|
890
|
+
try {
|
|
891
|
+
const result = await readWiki(getClient(), wiki_token);
|
|
892
|
+
return json(result);
|
|
893
|
+
} catch (err) {
|
|
894
|
+
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
895
|
+
}
|
|
896
|
+
},
|
|
897
|
+
},
|
|
898
|
+
{ name: "feishu_wiki_read" },
|
|
899
|
+
);
|
|
900
|
+
|
|
901
|
+
// Tool 12: feishu_wiki_spaces
|
|
902
|
+
api.registerTool(
|
|
903
|
+
{
|
|
904
|
+
name: "feishu_wiki_spaces",
|
|
905
|
+
label: "Feishu Wiki Spaces",
|
|
906
|
+
description: "List available wiki spaces (knowledge bases) the app has access to.",
|
|
907
|
+
parameters: Type.Object({}),
|
|
908
|
+
async execute() {
|
|
909
|
+
try {
|
|
910
|
+
const result = await listWikiSpaces(getClient());
|
|
911
|
+
return json(result);
|
|
912
|
+
} catch (err) {
|
|
913
|
+
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
914
|
+
}
|
|
915
|
+
},
|
|
916
|
+
},
|
|
917
|
+
{ name: "feishu_wiki_spaces" },
|
|
918
|
+
);
|
|
919
|
+
|
|
920
|
+
// Tool 13: feishu_wiki_nodes
|
|
921
|
+
api.registerTool(
|
|
922
|
+
{
|
|
923
|
+
name: "feishu_wiki_nodes",
|
|
924
|
+
label: "Feishu Wiki Nodes",
|
|
925
|
+
description:
|
|
926
|
+
"List wiki nodes (pages) in a space. Use parent_node_token to list children of a specific node.",
|
|
927
|
+
parameters: WikiSpaceIdSchema,
|
|
928
|
+
async execute(_toolCallId, params) {
|
|
929
|
+
const { space_id, parent_node_token } = params as {
|
|
930
|
+
space_id: string;
|
|
931
|
+
parent_node_token?: string;
|
|
932
|
+
};
|
|
933
|
+
try {
|
|
934
|
+
const result = await listWikiNodes(getClient(), space_id, parent_node_token);
|
|
935
|
+
return json(result);
|
|
936
|
+
} catch (err) {
|
|
937
|
+
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
938
|
+
}
|
|
939
|
+
},
|
|
940
|
+
},
|
|
941
|
+
{ name: "feishu_wiki_nodes" },
|
|
942
|
+
);
|
|
943
|
+
|
|
944
|
+
// Tool 14: feishu_app_scopes
|
|
691
945
|
api.registerTool(
|
|
692
946
|
{
|
|
693
947
|
name: "feishu_app_scopes",
|
|
@@ -707,5 +961,5 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
|
|
|
707
961
|
{ name: "feishu_app_scopes" },
|
|
708
962
|
);
|
|
709
963
|
|
|
710
|
-
api.logger.info?.(`feishu_doc: Registered
|
|
964
|
+
api.logger.info?.(`feishu_doc: Registered 14 document/wiki tools`);
|
|
711
965
|
}
|