@m1heng-clawd/feishu 0.1.10 → 0.1.12
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 +280 -7
- package/index.ts +6 -6
- package/package.json +13 -3
- package/skills/feishu-doc/SKILL.md +64 -2
- package/skills/feishu-task/SKILL.md +210 -0
- package/src/bitable-tools/index.ts +1 -0
- package/src/bot.ts +182 -71
- package/src/channel.ts +2 -0
- package/src/config-schema.ts +4 -0
- package/src/doc-tools/actions.ts +341 -0
- package/src/doc-tools/common.ts +33 -0
- package/src/doc-tools/index.ts +2 -0
- package/src/doc-tools/register.ts +90 -0
- package/src/{doc-schema.ts → doc-tools/schemas.ts} +39 -1
- package/src/{docx.ts → doc-write-service.ts} +204 -351
- package/src/drive-tools/actions.ts +182 -0
- package/src/drive-tools/common.ts +18 -0
- package/src/drive-tools/index.ts +2 -0
- package/src/drive-tools/register.ts +71 -0
- package/src/{drive-schema.ts → drive-tools/schemas.ts} +2 -1
- package/src/media.ts +5 -3
- package/src/onboarding.ts +14 -4
- package/src/perm-tools/actions.ts +111 -0
- package/src/perm-tools/common.ts +18 -0
- package/src/perm-tools/index.ts +2 -0
- package/src/perm-tools/register.ts +65 -0
- package/src/policy.ts +13 -0
- package/src/reply-dispatcher.ts +6 -4
- package/src/send.ts +33 -2
- package/src/task-tools/actions.ts +466 -13
- package/src/task-tools/constants.ts +13 -0
- package/src/task-tools/index.ts +1 -0
- package/src/task-tools/register.ts +175 -13
- package/src/task-tools/schemas.ts +433 -4
- package/src/text/markdown-links.ts +104 -0
- package/src/types.ts +1 -0
- package/src/wiki-tools/actions.ts +166 -0
- package/src/wiki-tools/common.ts +18 -0
- package/src/wiki-tools/index.ts +2 -0
- package/src/wiki-tools/register.ts +66 -0
- package/src/bitable.ts +0 -1
- package/src/drive.ts +0 -264
- package/src/perm.ts +0 -165
- package/src/task.ts +0 -1
- package/src/wiki.ts +0 -223
- /package/src/{perm-schema.ts → perm-tools/schemas.ts} +0 -0
- /package/src/{wiki-schema.ts → wiki-tools/schemas.ts} +0 -0
package/src/send.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { FeishuSendResult, ResolvedFeishuAccount } from "./types.js";
|
|
|
3
3
|
import type { MentionTarget } from "./mention.js";
|
|
4
4
|
import { buildMentionedMessage, buildMentionedCardContent } from "./mention.js";
|
|
5
5
|
import { createFeishuClient } from "./client.js";
|
|
6
|
+
import { normalizeFeishuMarkdownLinks } from "./text/markdown-links.js";
|
|
6
7
|
import { resolveReceiveIdType, normalizeFeishuTarget } from "./targets.js";
|
|
7
8
|
import { getFeishuRuntime } from "./runtime.js";
|
|
8
9
|
import { resolveFeishuAccount } from "./accounts.js";
|
|
@@ -71,6 +72,31 @@ export async function getMessageFeishu(params: {
|
|
|
71
72
|
const parsed = JSON.parse(content);
|
|
72
73
|
if (item.msg_type === "text" && parsed.text) {
|
|
73
74
|
content = parsed.text;
|
|
75
|
+
} else if (parsed.content || parsed.elements) {
|
|
76
|
+
// Extract plain text from rich text (post) or interactive (card) format.
|
|
77
|
+
// Both use nested arrays: Array<Array<{tag, text?, href?, ...}>>
|
|
78
|
+
const blocks = parsed.content ?? parsed.elements ?? [];
|
|
79
|
+
const lines: string[] = [];
|
|
80
|
+
for (const paragraph of blocks) {
|
|
81
|
+
if (!Array.isArray(paragraph)) continue;
|
|
82
|
+
const line = paragraph
|
|
83
|
+
.map((node: { tag?: string; text?: string; href?: string }) => {
|
|
84
|
+
if (node.tag === "text") return node.text ?? "";
|
|
85
|
+
if (node.tag === "a") return node.text ?? node.href ?? "";
|
|
86
|
+
if (node.tag === "at") return "";
|
|
87
|
+
if (node.tag === "img") return "[图片]";
|
|
88
|
+
return node.text ?? "";
|
|
89
|
+
})
|
|
90
|
+
.join("");
|
|
91
|
+
if (line.trim()) lines.push(line);
|
|
92
|
+
}
|
|
93
|
+
const extracted = (parsed.title ? parsed.title + "\n" : "") + lines.join("\n");
|
|
94
|
+
// Filter out Feishu's degraded card placeholder text
|
|
95
|
+
if (extracted.trim() && !extracted.includes("请升级至最新版本客户端")) {
|
|
96
|
+
content = extracted;
|
|
97
|
+
} else if (extracted.includes("请升级至最新版本客户端")) {
|
|
98
|
+
content = "[卡片消息]";
|
|
99
|
+
}
|
|
74
100
|
}
|
|
75
101
|
} catch {
|
|
76
102
|
// Keep raw content if parsing fails
|
|
@@ -147,7 +173,9 @@ export async function sendMessageFeishu(params: SendFeishuMessageParams): Promis
|
|
|
147
173
|
if (mentions && mentions.length > 0) {
|
|
148
174
|
rawText = buildMentionedMessage(mentions, rawText);
|
|
149
175
|
}
|
|
150
|
-
const messageText =
|
|
176
|
+
const messageText = normalizeFeishuMarkdownLinks(
|
|
177
|
+
getFeishuRuntime().channel.text.convertMarkdownTables(rawText, tableMode),
|
|
178
|
+
);
|
|
151
179
|
|
|
152
180
|
const { content, msgType } = buildFeishuPostMessagePayload({ messageText });
|
|
153
181
|
|
|
@@ -317,6 +345,7 @@ export async function sendMarkdownCardFeishu(params: {
|
|
|
317
345
|
if (mentions && mentions.length > 0) {
|
|
318
346
|
cardText = buildMentionedCardContent(mentions, text);
|
|
319
347
|
}
|
|
348
|
+
cardText = normalizeFeishuMarkdownLinks(cardText);
|
|
320
349
|
const card = buildMarkdownCard(cardText);
|
|
321
350
|
return sendCardFeishu({ cfg, to, card, replyToMessageId, accountId });
|
|
322
351
|
}
|
|
@@ -342,7 +371,9 @@ export async function editMessageFeishu(params: {
|
|
|
342
371
|
cfg,
|
|
343
372
|
channel: "feishu",
|
|
344
373
|
});
|
|
345
|
-
const messageText =
|
|
374
|
+
const messageText = normalizeFeishuMarkdownLinks(
|
|
375
|
+
getFeishuRuntime().channel.text.convertMarkdownTables(text ?? "", tableMode),
|
|
376
|
+
);
|
|
346
377
|
|
|
347
378
|
const { content, msgType } = buildFeishuPostMessagePayload({ messageText });
|
|
348
379
|
|
|
@@ -1,23 +1,42 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
1
4
|
import type { TaskClient } from "./common.js";
|
|
2
|
-
import
|
|
5
|
+
import {
|
|
6
|
+
TASKLIST_UPDATE_FIELD_VALUES,
|
|
7
|
+
TASK_UPDATE_FIELD_VALUES,
|
|
8
|
+
type AddTaskToTasklistParams,
|
|
9
|
+
type AddTasklistMembersParams,
|
|
10
|
+
type CreateTasklistParams,
|
|
11
|
+
CreateSubtaskParams,
|
|
3
12
|
CreateTaskParams,
|
|
13
|
+
DeleteTaskAttachmentParams,
|
|
14
|
+
GetTaskAttachmentParams,
|
|
4
15
|
GetTaskParams,
|
|
16
|
+
type GetTasklistParams,
|
|
17
|
+
ListTaskAttachmentsParams,
|
|
18
|
+
type ListTasklistsParams,
|
|
19
|
+
type RemoveTaskFromTasklistParams,
|
|
20
|
+
type RemoveTasklistMembersParams,
|
|
5
21
|
TaskUpdateTask,
|
|
22
|
+
type TasklistPatchTasklist,
|
|
23
|
+
UploadTaskAttachmentParams,
|
|
24
|
+
type UpdateTasklistParams,
|
|
6
25
|
UpdateTaskParams,
|
|
7
26
|
} from "./schemas.js";
|
|
27
|
+
import {
|
|
28
|
+
DEFAULT_TASK_ATTACHMENT_FILENAME,
|
|
29
|
+
DEFAULT_TASK_ATTACHMENT_MAX_BYTES,
|
|
30
|
+
BYTES_PER_MEGABYTE,
|
|
31
|
+
HEX_RADIX,
|
|
32
|
+
RANDOM_TOKEN_PREFIX_LENGTH,
|
|
33
|
+
SIZE_DISPLAY_FRACTION_DIGITS,
|
|
34
|
+
} from "./constants.js";
|
|
35
|
+
import { getFeishuRuntime } from "../runtime.js";
|
|
8
36
|
import { runTaskApiCall } from "./common.js";
|
|
9
37
|
|
|
10
|
-
const SUPPORTED_PATCH_FIELDS = new Set<
|
|
11
|
-
|
|
12
|
-
"description",
|
|
13
|
-
"due",
|
|
14
|
-
"start",
|
|
15
|
-
"extra",
|
|
16
|
-
"completed_at",
|
|
17
|
-
"repeat_rule",
|
|
18
|
-
"mode",
|
|
19
|
-
"is_milestone",
|
|
20
|
-
]);
|
|
38
|
+
const SUPPORTED_PATCH_FIELDS = new Set<string>(TASK_UPDATE_FIELD_VALUES);
|
|
39
|
+
const SUPPORTED_TASKLIST_PATCH_FIELDS = new Set<string>(TASKLIST_UPDATE_FIELD_VALUES);
|
|
21
40
|
|
|
22
41
|
function omitUndefined<T extends Record<string, unknown>>(obj: T): T {
|
|
23
42
|
return Object.fromEntries(
|
|
@@ -27,7 +46,24 @@ function omitUndefined<T extends Record<string, unknown>>(obj: T): T {
|
|
|
27
46
|
|
|
28
47
|
function inferUpdateFields(task: TaskUpdateTask): string[] {
|
|
29
48
|
return Object.keys(task).filter((field) =>
|
|
30
|
-
SUPPORTED_PATCH_FIELDS.has(field
|
|
49
|
+
SUPPORTED_PATCH_FIELDS.has(field),
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function ensureSupportedUpdateFields(
|
|
54
|
+
updateFields: string[],
|
|
55
|
+
supported: Set<string>,
|
|
56
|
+
resource: "task" | "tasklist",
|
|
57
|
+
) {
|
|
58
|
+
const invalid = updateFields.filter((field) => !supported.has(field));
|
|
59
|
+
if (invalid.length > 0) {
|
|
60
|
+
throw new Error(`unsupported ${resource} update_fields: ${invalid.join(", ")}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function inferTasklistUpdateFields(tasklist: TasklistPatchTasklist): string[] {
|
|
65
|
+
return Object.keys(tasklist).filter((field) =>
|
|
66
|
+
SUPPORTED_TASKLIST_PATCH_FIELDS.has(field),
|
|
31
67
|
);
|
|
32
68
|
}
|
|
33
69
|
|
|
@@ -51,6 +87,96 @@ function formatTask(task: Record<string, unknown> | undefined) {
|
|
|
51
87
|
};
|
|
52
88
|
}
|
|
53
89
|
|
|
90
|
+
function formatTasklist(tasklist: Record<string, unknown> | undefined) {
|
|
91
|
+
if (!tasklist) return undefined;
|
|
92
|
+
return {
|
|
93
|
+
guid: tasklist.guid,
|
|
94
|
+
name: tasklist.name,
|
|
95
|
+
creator: tasklist.creator,
|
|
96
|
+
owner: tasklist.owner,
|
|
97
|
+
members: tasklist.members,
|
|
98
|
+
url: tasklist.url,
|
|
99
|
+
created_at: tasklist.created_at,
|
|
100
|
+
updated_at: tasklist.updated_at,
|
|
101
|
+
archive_msec: tasklist.archive_msec,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function formatAttachment(attachment: Record<string, unknown> | undefined) {
|
|
106
|
+
if (!attachment) return undefined;
|
|
107
|
+
return {
|
|
108
|
+
guid: attachment.guid,
|
|
109
|
+
file_token: attachment.file_token,
|
|
110
|
+
name: attachment.name,
|
|
111
|
+
size: attachment.size,
|
|
112
|
+
uploader: attachment.uploader,
|
|
113
|
+
is_cover: attachment.is_cover,
|
|
114
|
+
uploaded_at: attachment.uploaded_at,
|
|
115
|
+
url: attachment.url,
|
|
116
|
+
resource: attachment.resource,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function sanitizeUploadFilename(input: string) {
|
|
121
|
+
const base = path.basename(input.trim());
|
|
122
|
+
return base.length > 0 ? base : DEFAULT_TASK_ATTACHMENT_FILENAME;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function ensureUploadableLocalFile(filePath: string, maxBytes: number) {
|
|
126
|
+
let stat: fs.Stats;
|
|
127
|
+
try {
|
|
128
|
+
stat = await fs.promises.stat(filePath);
|
|
129
|
+
} catch {
|
|
130
|
+
throw new Error(`file_path not found: ${filePath}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!stat.isFile()) {
|
|
134
|
+
throw new Error(`file_path is not a regular file: ${filePath}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (stat.size > maxBytes) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
`file_path exceeds ${(maxBytes / BYTES_PER_MEGABYTE).toFixed(SIZE_DISPLAY_FRACTION_DIGITS)}MB limit: ${filePath}`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function saveBufferToTempFile(buffer: Buffer, fileName: string) {
|
|
145
|
+
const safeName = sanitizeUploadFilename(fileName);
|
|
146
|
+
const tempPath = path.join(
|
|
147
|
+
os.tmpdir(),
|
|
148
|
+
`feishu-task-attachment-${Date.now()}-${Math.random().toString(HEX_RADIX).slice(RANDOM_TOKEN_PREFIX_LENGTH)}-${safeName}`,
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
await fs.promises.writeFile(tempPath, buffer);
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
tempPath,
|
|
155
|
+
cleanup: async () => {
|
|
156
|
+
await fs.promises.unlink(tempPath).catch(() => undefined);
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function downloadToTempFile(fileUrl: string, filename: string | undefined, maxBytes: number) {
|
|
162
|
+
const loaded = await getFeishuRuntime().media.loadWebMedia(fileUrl, {
|
|
163
|
+
maxBytes,
|
|
164
|
+
optimizeImages: false,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const parsedPath = (() => {
|
|
168
|
+
try {
|
|
169
|
+
return new URL(fileUrl).pathname;
|
|
170
|
+
} catch {
|
|
171
|
+
return "";
|
|
172
|
+
}
|
|
173
|
+
})();
|
|
174
|
+
|
|
175
|
+
const fallbackName = path.basename(parsedPath) || DEFAULT_TASK_ATTACHMENT_FILENAME;
|
|
176
|
+
const preferredName = filename?.trim() ? filename : loaded.fileName ?? fallbackName;
|
|
177
|
+
return saveBufferToTempFile(loaded.buffer, preferredName);
|
|
178
|
+
}
|
|
179
|
+
|
|
54
180
|
export async function createTask(client: TaskClient, params: CreateTaskParams) {
|
|
55
181
|
const res = await runTaskApiCall("task.v2.task.create", () =>
|
|
56
182
|
client.task.v2.task.create({
|
|
@@ -78,6 +204,68 @@ export async function createTask(client: TaskClient, params: CreateTaskParams) {
|
|
|
78
204
|
};
|
|
79
205
|
}
|
|
80
206
|
|
|
207
|
+
export async function createSubtask(client: TaskClient, params: CreateSubtaskParams) {
|
|
208
|
+
const res = await runTaskApiCall("task.v2.taskSubtask.create", () =>
|
|
209
|
+
client.task.v2.taskSubtask.create({
|
|
210
|
+
path: { task_guid: params.task_guid },
|
|
211
|
+
data: omitUndefined({
|
|
212
|
+
summary: params.summary,
|
|
213
|
+
description: params.description,
|
|
214
|
+
due: params.due,
|
|
215
|
+
start: params.start,
|
|
216
|
+
extra: params.extra,
|
|
217
|
+
completed_at: params.completed_at,
|
|
218
|
+
members: params.members,
|
|
219
|
+
repeat_rule: params.repeat_rule,
|
|
220
|
+
tasklists: params.tasklists,
|
|
221
|
+
mode: params.mode,
|
|
222
|
+
is_milestone: params.is_milestone,
|
|
223
|
+
}),
|
|
224
|
+
params: omitUndefined({
|
|
225
|
+
user_id_type: params.user_id_type,
|
|
226
|
+
}),
|
|
227
|
+
}),
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
subtask: formatTask((res.data?.subtask ?? undefined) as Record<string, unknown> | undefined),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export async function createTasklist(client: TaskClient, params: CreateTasklistParams) {
|
|
236
|
+
const res = await runTaskApiCall("task.v2.tasklist.create", () =>
|
|
237
|
+
client.task.v2.tasklist.create({
|
|
238
|
+
data: omitUndefined({
|
|
239
|
+
name: params.name,
|
|
240
|
+
members: params.members,
|
|
241
|
+
archive_tasklist: params.archive_tasklist,
|
|
242
|
+
}),
|
|
243
|
+
params: omitUndefined({
|
|
244
|
+
user_id_type: params.user_id_type,
|
|
245
|
+
}),
|
|
246
|
+
}),
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
tasklist: formatTasklist(
|
|
251
|
+
(res.data?.tasklist ?? undefined) as Record<string, unknown> | undefined,
|
|
252
|
+
),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export async function deleteTaskAttachment(client: TaskClient, params: DeleteTaskAttachmentParams) {
|
|
257
|
+
await runTaskApiCall("task.v2.attachment.delete", () =>
|
|
258
|
+
client.task.v2.attachment.delete({
|
|
259
|
+
path: { attachment_guid: params.attachment_guid },
|
|
260
|
+
}),
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
success: true,
|
|
265
|
+
attachment_guid: params.attachment_guid,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
81
269
|
export async function deleteTask(client: TaskClient, taskGuid: string) {
|
|
82
270
|
await runTaskApiCall("task.v2.task.delete", () =>
|
|
83
271
|
client.task.v2.task.delete({
|
|
@@ -91,6 +279,19 @@ export async function deleteTask(client: TaskClient, taskGuid: string) {
|
|
|
91
279
|
};
|
|
92
280
|
}
|
|
93
281
|
|
|
282
|
+
export async function deleteTasklist(client: TaskClient, tasklistGuid: string) {
|
|
283
|
+
await runTaskApiCall("task.v2.tasklist.delete", () =>
|
|
284
|
+
client.task.v2.tasklist.delete({
|
|
285
|
+
path: { tasklist_guid: tasklistGuid },
|
|
286
|
+
}),
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
success: true,
|
|
291
|
+
tasklist_guid: tasklistGuid,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
94
295
|
export async function getTask(client: TaskClient, params: GetTaskParams) {
|
|
95
296
|
const res = await runTaskApiCall("task.v2.task.get", () =>
|
|
96
297
|
client.task.v2.task.get({
|
|
@@ -106,10 +307,91 @@ export async function getTask(client: TaskClient, params: GetTaskParams) {
|
|
|
106
307
|
};
|
|
107
308
|
}
|
|
108
309
|
|
|
310
|
+
export async function getTasklist(client: TaskClient, params: GetTasklistParams) {
|
|
311
|
+
const res = await runTaskApiCall("task.v2.tasklist.get", () =>
|
|
312
|
+
client.task.v2.tasklist.get({
|
|
313
|
+
path: { tasklist_guid: params.tasklist_guid },
|
|
314
|
+
params: omitUndefined({
|
|
315
|
+
user_id_type: params.user_id_type,
|
|
316
|
+
}),
|
|
317
|
+
}),
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
tasklist: formatTasklist(
|
|
322
|
+
(res.data?.tasklist ?? undefined) as Record<string, unknown> | undefined,
|
|
323
|
+
),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export async function listTasklists(client: TaskClient, params: ListTasklistsParams) {
|
|
328
|
+
const res = await runTaskApiCall("task.v2.tasklist.list", () =>
|
|
329
|
+
client.task.v2.tasklist.list({
|
|
330
|
+
params: omitUndefined({
|
|
331
|
+
page_size: params.page_size,
|
|
332
|
+
page_token: params.page_token,
|
|
333
|
+
user_id_type: params.user_id_type,
|
|
334
|
+
}),
|
|
335
|
+
}),
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
const items = (res.data?.items ?? []) as Record<string, unknown>[];
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
items: items.map((item) => formatTasklist(item)),
|
|
342
|
+
page_token: res.data?.page_token,
|
|
343
|
+
has_more: res.data?.has_more,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export async function getTaskAttachment(client: TaskClient, params: GetTaskAttachmentParams) {
|
|
348
|
+
const res = await runTaskApiCall("task.v2.attachment.get", () =>
|
|
349
|
+
client.task.v2.attachment.get({
|
|
350
|
+
path: { attachment_guid: params.attachment_guid },
|
|
351
|
+
params: omitUndefined({
|
|
352
|
+
user_id_type: params.user_id_type,
|
|
353
|
+
}),
|
|
354
|
+
}),
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
attachment: formatAttachment(
|
|
359
|
+
(res.data?.attachment ?? undefined) as Record<string, unknown> | undefined,
|
|
360
|
+
),
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export async function listTaskAttachments(client: TaskClient, params: ListTaskAttachmentsParams) {
|
|
365
|
+
const res = await runTaskApiCall("task.v2.attachment.list", () =>
|
|
366
|
+
client.task.v2.attachment.list({
|
|
367
|
+
params: omitUndefined({
|
|
368
|
+
resource_type: "task",
|
|
369
|
+
resource_id: params.task_guid,
|
|
370
|
+
page_size: params.page_size,
|
|
371
|
+
page_token: params.page_token,
|
|
372
|
+
updated_mesc: params.updated_mesc,
|
|
373
|
+
user_id_type: params.user_id_type,
|
|
374
|
+
}),
|
|
375
|
+
}),
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
const items = (res.data?.items ?? []) as Record<string, unknown>[];
|
|
379
|
+
|
|
380
|
+
return {
|
|
381
|
+
items: items.map((item) => formatAttachment(item)),
|
|
382
|
+
page_token: res.data?.page_token,
|
|
383
|
+
has_more: res.data?.has_more,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
109
387
|
export async function updateTask(client: TaskClient, params: UpdateTaskParams) {
|
|
110
388
|
const task = omitUndefined(params.task as Record<string, unknown>) as TaskUpdateTask;
|
|
111
389
|
const updateFields = params.update_fields?.length ? params.update_fields : inferUpdateFields(task);
|
|
112
390
|
|
|
391
|
+
if (params.update_fields?.length) {
|
|
392
|
+
ensureSupportedUpdateFields(updateFields, SUPPORTED_PATCH_FIELDS, "task");
|
|
393
|
+
}
|
|
394
|
+
|
|
113
395
|
if (Object.keys(task).length === 0) {
|
|
114
396
|
throw new Error("task update payload is empty");
|
|
115
397
|
}
|
|
@@ -135,3 +417,174 @@ export async function updateTask(client: TaskClient, params: UpdateTaskParams) {
|
|
|
135
417
|
update_fields: updateFields,
|
|
136
418
|
};
|
|
137
419
|
}
|
|
420
|
+
|
|
421
|
+
export async function addTaskToTasklist(client: TaskClient, params: AddTaskToTasklistParams) {
|
|
422
|
+
const res = await runTaskApiCall("task.v2.task.add_tasklist", () =>
|
|
423
|
+
client.task.v2.task.addTasklist({
|
|
424
|
+
path: { task_guid: params.task_guid },
|
|
425
|
+
data: omitUndefined({
|
|
426
|
+
tasklist_guid: params.tasklist_guid,
|
|
427
|
+
section_guid: params.section_guid,
|
|
428
|
+
}),
|
|
429
|
+
params: omitUndefined({
|
|
430
|
+
user_id_type: params.user_id_type,
|
|
431
|
+
}),
|
|
432
|
+
}),
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
return {
|
|
436
|
+
task: formatTask((res.data?.task ?? undefined) as Record<string, unknown> | undefined),
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export async function removeTaskFromTasklist(
|
|
441
|
+
client: TaskClient,
|
|
442
|
+
params: RemoveTaskFromTasklistParams,
|
|
443
|
+
) {
|
|
444
|
+
const res = await runTaskApiCall("task.v2.task.remove_tasklist", () =>
|
|
445
|
+
client.task.v2.task.removeTasklist({
|
|
446
|
+
path: { task_guid: params.task_guid },
|
|
447
|
+
data: omitUndefined({
|
|
448
|
+
tasklist_guid: params.tasklist_guid,
|
|
449
|
+
}),
|
|
450
|
+
params: omitUndefined({
|
|
451
|
+
user_id_type: params.user_id_type,
|
|
452
|
+
}),
|
|
453
|
+
}),
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
return {
|
|
457
|
+
task: formatTask((res.data?.task ?? undefined) as Record<string, unknown> | undefined),
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export async function updateTasklist(client: TaskClient, params: UpdateTasklistParams) {
|
|
462
|
+
const tasklist = omitUndefined(params.tasklist as Record<string, unknown>) as TasklistPatchTasklist;
|
|
463
|
+
const updateFields = params.update_fields?.length
|
|
464
|
+
? params.update_fields
|
|
465
|
+
: inferTasklistUpdateFields(tasklist);
|
|
466
|
+
|
|
467
|
+
if (params.update_fields?.length) {
|
|
468
|
+
ensureSupportedUpdateFields(updateFields, SUPPORTED_TASKLIST_PATCH_FIELDS, "tasklist");
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (Object.keys(tasklist).length === 0) {
|
|
472
|
+
throw new Error("tasklist update payload is empty");
|
|
473
|
+
}
|
|
474
|
+
if (updateFields.length === 0) {
|
|
475
|
+
throw new Error("no valid update_fields provided or inferred from tasklist payload");
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const res = await runTaskApiCall("task.v2.tasklist.patch", () =>
|
|
479
|
+
client.task.v2.tasklist.patch({
|
|
480
|
+
path: { tasklist_guid: params.tasklist_guid },
|
|
481
|
+
data: omitUndefined({
|
|
482
|
+
tasklist,
|
|
483
|
+
update_fields: updateFields,
|
|
484
|
+
origin_owner_to_role: params.origin_owner_to_role,
|
|
485
|
+
}),
|
|
486
|
+
params: omitUndefined({
|
|
487
|
+
user_id_type: params.user_id_type,
|
|
488
|
+
}),
|
|
489
|
+
}),
|
|
490
|
+
);
|
|
491
|
+
|
|
492
|
+
return {
|
|
493
|
+
tasklist: formatTasklist(
|
|
494
|
+
(res.data?.tasklist ?? undefined) as Record<string, unknown> | undefined,
|
|
495
|
+
),
|
|
496
|
+
update_fields: updateFields,
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
export async function addTasklistMembers(client: TaskClient, params: AddTasklistMembersParams) {
|
|
501
|
+
const res = await runTaskApiCall("task.v2.tasklist.addMembers", () =>
|
|
502
|
+
client.task.v2.tasklist.addMembers({
|
|
503
|
+
path: { tasklist_guid: params.tasklist_guid },
|
|
504
|
+
data: {
|
|
505
|
+
members: params.members,
|
|
506
|
+
},
|
|
507
|
+
params: omitUndefined({
|
|
508
|
+
user_id_type: params.user_id_type,
|
|
509
|
+
}),
|
|
510
|
+
}),
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
return {
|
|
514
|
+
tasklist: formatTasklist(
|
|
515
|
+
(res.data?.tasklist ?? undefined) as Record<string, unknown> | undefined,
|
|
516
|
+
),
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
export async function removeTasklistMembers(
|
|
521
|
+
client: TaskClient,
|
|
522
|
+
params: RemoveTasklistMembersParams,
|
|
523
|
+
) {
|
|
524
|
+
const res = await runTaskApiCall("task.v2.tasklist.removeMembers", () =>
|
|
525
|
+
client.task.v2.tasklist.removeMembers({
|
|
526
|
+
path: { tasklist_guid: params.tasklist_guid },
|
|
527
|
+
data: {
|
|
528
|
+
members: params.members,
|
|
529
|
+
},
|
|
530
|
+
params: omitUndefined({
|
|
531
|
+
user_id_type: params.user_id_type,
|
|
532
|
+
}),
|
|
533
|
+
}),
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
return {
|
|
537
|
+
tasklist: formatTasklist(
|
|
538
|
+
(res.data?.tasklist ?? undefined) as Record<string, unknown> | undefined,
|
|
539
|
+
),
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
export async function uploadTaskAttachment(
|
|
544
|
+
client: TaskClient,
|
|
545
|
+
params: UploadTaskAttachmentParams,
|
|
546
|
+
options?: { maxBytes?: number },
|
|
547
|
+
) {
|
|
548
|
+
const maxBytes =
|
|
549
|
+
typeof options?.maxBytes === "number" && options.maxBytes > 0
|
|
550
|
+
? options.maxBytes
|
|
551
|
+
: DEFAULT_TASK_ATTACHMENT_MAX_BYTES;
|
|
552
|
+
|
|
553
|
+
let tempCleanup: (() => Promise<void>) | undefined;
|
|
554
|
+
let filePath: string;
|
|
555
|
+
|
|
556
|
+
if ("file_path" in params) {
|
|
557
|
+
filePath = params.file_path;
|
|
558
|
+
await ensureUploadableLocalFile(filePath, maxBytes);
|
|
559
|
+
} else {
|
|
560
|
+
const download = await downloadToTempFile(params.file_url, params.filename, maxBytes);
|
|
561
|
+
filePath = download.tempPath;
|
|
562
|
+
tempCleanup = download.cleanup;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
try {
|
|
566
|
+
const res = await runTaskApiCall("task.v2.attachment.upload", async () => {
|
|
567
|
+
const data = await client.task.v2.attachment.upload({
|
|
568
|
+
data: {
|
|
569
|
+
resource_type: "task",
|
|
570
|
+
resource_id: params.task_guid,
|
|
571
|
+
file: fs.createReadStream(filePath),
|
|
572
|
+
},
|
|
573
|
+
params: omitUndefined({
|
|
574
|
+
user_id_type: params.user_id_type,
|
|
575
|
+
}),
|
|
576
|
+
});
|
|
577
|
+
return { code: 0, data } as { code: number; data: typeof data };
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
const items = (res.data?.items ?? []) as Record<string, unknown>[];
|
|
581
|
+
|
|
582
|
+
return {
|
|
583
|
+
items: items.map((item) => formatAttachment(item)),
|
|
584
|
+
};
|
|
585
|
+
} finally {
|
|
586
|
+
if (tempCleanup) {
|
|
587
|
+
await tempCleanup();
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const BYTES_PER_MEGABYTE = 1024 * 1024;
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_TASK_MEDIA_MAX_MB = 30;
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_TASK_ATTACHMENT_MAX_BYTES = DEFAULT_TASK_MEDIA_MAX_MB * BYTES_PER_MEGABYTE;
|
|
6
|
+
|
|
7
|
+
export const DEFAULT_TASK_ATTACHMENT_FILENAME = "attachment";
|
|
8
|
+
|
|
9
|
+
export const HEX_RADIX = 16;
|
|
10
|
+
|
|
11
|
+
export const RANDOM_TOKEN_PREFIX_LENGTH = 2;
|
|
12
|
+
|
|
13
|
+
export const SIZE_DISPLAY_FRACTION_DIGITS = 0;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { registerFeishuTaskTools } from "./register.js";
|