@yanhaidao/wecom 2.3.13 → 2.3.141

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.
@@ -0,0 +1,1002 @@
1
+ import type { ResolvedAgentAccount } from "../../types/index.js";
2
+ import { getAccessToken } from "../../transport/agent-api/core.js";
3
+ import { wecomFetch } from "../../http.js";
4
+ import { resolveWecomEgressProxyUrlFromNetwork } from "../../config/index.js";
5
+ import { LIMITS } from "../../types/constants.js";
6
+ import {
7
+ BatchUpdateDocResponse,
8
+ GetDocContentResponse,
9
+ Node,
10
+ UpdateRequest
11
+ } from "./types.js";
12
+
13
+ function readString(value: unknown): string {
14
+ const trimmed = String(value ?? "").trim();
15
+ return trimmed || "";
16
+ }
17
+
18
+ function normalizeDocType(docType: unknown): 3 | 4 | 10 {
19
+ if (docType === 3 || docType === "3") return 3;
20
+ if (docType === 4 || docType === "4") return 4;
21
+ if (docType === 10 || docType === "10" || docType === 5 || docType === "5") return 10;
22
+ const normalized = readString(docType).toLowerCase();
23
+ if (!normalized || normalized === "doc") return 3;
24
+ if (normalized === "spreadsheet" || normalized === "sheet" || normalized === "table") return 4;
25
+ if (normalized === "smart_table" || normalized === "smarttable") return 10;
26
+ throw new Error(`Unsupported WeCom docType: ${String(docType)}`);
27
+ }
28
+
29
+ function mapDocTypeLabel(docType: 3 | 4 | 10): string {
30
+ if (docType === 10) return "smart_table";
31
+ if (docType === 4) return "spreadsheet";
32
+ return "doc";
33
+ }
34
+
35
+ function isRecord(value: unknown): value is Record<string, unknown> {
36
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
37
+ }
38
+
39
+ function readObject(value: unknown): Record<string, unknown> {
40
+ return isRecord(value) ? value : {};
41
+ }
42
+
43
+ function readArray(value: unknown): unknown[] {
44
+ return Array.isArray(value) ? value : [];
45
+ }
46
+
47
+ export interface DocMemberEntry {
48
+ userid?: string;
49
+ partyid?: string;
50
+ tagid?: string;
51
+ /**
52
+ * 权限位:1-查看,2-编辑,7-管理
53
+ * 只有“智能表格”才支持读写权限(auth=2)?
54
+ * 实际上企微文档现在也支持设置协作者权限了。
55
+ */
56
+ auth?: number;
57
+ }
58
+
59
+ function normalizeDocMemberEntry(value: unknown): DocMemberEntry | null {
60
+ if (typeof value === "string" || typeof value === "number") {
61
+ const userid = readString(value);
62
+ return userid ? { userid } : null;
63
+ }
64
+ if (!isRecord(value)) return null;
65
+ const entry: DocMemberEntry = { ...value } as DocMemberEntry;
66
+ if (!readString(entry.userid) && readString(value.userId)) {
67
+ entry.userid = readString(value.userId);
68
+ }
69
+ if (!readString(entry.userid) && !readString(entry.partyid) && !readString(entry.tagid)) {
70
+ return null;
71
+ }
72
+ if (readString(entry.userid)) entry.userid = readString(entry.userid);
73
+ if (readString(entry.partyid)) entry.partyid = readString(entry.partyid);
74
+ if (readString(entry.tagid)) entry.tagid = readString(entry.tagid);
75
+ if (entry.auth !== undefined) entry.auth = Number(entry.auth);
76
+ return entry;
77
+ }
78
+
79
+ function normalizeDocMemberEntryList(values: unknown): DocMemberEntry[] {
80
+ return readArray(values).map(normalizeDocMemberEntry).filter((v): v is DocMemberEntry => v !== null);
81
+ }
82
+
83
+ function buildDocMemberAuthRequest(params: {
84
+ docId: string;
85
+ viewers?: unknown;
86
+ collaborators?: unknown;
87
+ removeViewers?: unknown;
88
+ removeCollaborators?: unknown;
89
+ authLevel?: number; // Default auth level for new members if not specified in entry
90
+ }): Record<string, unknown> {
91
+ const { docId, viewers, collaborators, removeViewers, removeCollaborators, authLevel } = params;
92
+ const payload: Record<string, unknown> = {
93
+ docid: readString(docId),
94
+ };
95
+ if (!payload.docid) throw new Error("docId required");
96
+
97
+ const normalizedViewers = normalizeDocMemberEntryList(viewers).map(v => ({ ...v, auth: v.auth ?? authLevel ?? 1 }));
98
+ const normalizedCollaborators = normalizeDocMemberEntryList(collaborators).map(v => ({ ...v, auth: v.auth ?? authLevel ?? 2 }));
99
+ const normalizedRemovedViewers = normalizeDocMemberEntryList(removeViewers);
100
+ const normalizedRemovedCollaborators = normalizeDocMemberEntryList(removeCollaborators);
101
+
102
+ if (normalizedViewers.length > 0) payload.update_file_member_list = normalizedViewers;
103
+ if (normalizedCollaborators.length > 0) payload.update_co_auth_list = normalizedCollaborators;
104
+ if (normalizedRemovedViewers.length > 0) payload.del_file_member_list = normalizedRemovedViewers;
105
+ if (normalizedRemovedCollaborators.length > 0) payload.del_co_auth_list = normalizedRemovedCollaborators;
106
+
107
+ if (
108
+ !payload.update_doc_member_list &&
109
+ !payload.update_co_auth_list &&
110
+ !payload.del_doc_member_list &&
111
+ !payload.del_co_auth_list
112
+ ) {
113
+ throw new Error("at least one viewer/collaborator change is required");
114
+ }
115
+
116
+ return payload;
117
+ }
118
+
119
+ async function parseJsonResponse(res: Response, actionLabel: string): Promise<any> {
120
+ let payload: any = null;
121
+ try {
122
+ payload = await res.json();
123
+ } catch {
124
+ if (!res.ok) {
125
+ throw new Error(`WeCom ${actionLabel} failed: HTTP ${res.status}`);
126
+ }
127
+ throw new Error(`WeCom ${actionLabel} failed: invalid JSON response`);
128
+ }
129
+ if (!payload || typeof payload !== "object") {
130
+ throw new Error(`WeCom ${actionLabel} failed: empty response`);
131
+ }
132
+ if (!res.ok) {
133
+ throw new Error(`WeCom ${actionLabel} failed: HTTP ${res.status} ${JSON.stringify(payload)}`);
134
+ }
135
+ if (Array.isArray(payload)) {
136
+ const failedItem = payload.find((item) => Number(item?.errcode ?? 0) !== 0);
137
+ if (failedItem) {
138
+ throw new Error(
139
+ `WeCom ${actionLabel} failed: ${String(failedItem?.errmsg || "unknown error")} (errcode ${String(failedItem?.errcode)})`,
140
+ );
141
+ }
142
+ return payload;
143
+ }
144
+ if (Number(payload.errcode ?? 0) !== 0) {
145
+ throw new Error(
146
+ `WeCom ${actionLabel} failed: ${String(payload.errmsg || "unknown error")} (errcode ${String(payload.errcode)})`,
147
+ );
148
+ }
149
+ return payload;
150
+ }
151
+
152
+ export class WecomDocClient {
153
+ private async postWecomDocApi(params: {
154
+ path: string;
155
+ actionLabel: string;
156
+ agent: ResolvedAgentAccount;
157
+ body: Record<string, unknown> | unknown[];
158
+ }): Promise<any> {
159
+ const { path, actionLabel, agent, body } = params;
160
+
161
+ const token = await getAccessToken(agent);
162
+ const url = `https://qyapi.weixin.qq.com${path}?access_token=${encodeURIComponent(token)}`;
163
+ const proxyUrl = resolveWecomEgressProxyUrlFromNetwork(agent.network);
164
+
165
+ let lastErr: any;
166
+ for (let attempt = 1; attempt <= 3; attempt++) {
167
+ try {
168
+ const res = await wecomFetch(url, {
169
+ method: "POST",
170
+ headers: {
171
+ "content-type": "application/json",
172
+ },
173
+ body: JSON.stringify(body ?? {}),
174
+ }, { proxyUrl, timeoutMs: LIMITS.REQUEST_TIMEOUT_MS });
175
+
176
+ return await parseJsonResponse(res, actionLabel);
177
+ } catch (err) {
178
+ lastErr = err;
179
+ if (attempt < 3) {
180
+ await new Promise(r => setTimeout(r, 1000));
181
+ }
182
+ }
183
+ }
184
+ throw lastErr;
185
+ }
186
+
187
+ async createDoc(params: { agent: ResolvedAgentAccount; docName: string; docType?: unknown; spaceId?: string; fatherId?: string; adminUsers?: string[] }) {
188
+ const { agent, docName, docType, spaceId, fatherId, adminUsers } = params;
189
+ const normalizedDocType = normalizeDocType(docType);
190
+ const payload: Record<string, unknown> = {
191
+ doc_type: normalizedDocType,
192
+ doc_name: readString(docName),
193
+ };
194
+ if (!payload.doc_name) throw new Error("docName required");
195
+ const normalizedSpaceId = readString(spaceId);
196
+ const normalizedFatherId = readString(fatherId);
197
+ if (normalizedSpaceId) payload.spaceid = normalizedSpaceId;
198
+ if (normalizedFatherId) payload.fatherid = normalizedFatherId;
199
+ const normalizedAdminUsers = Array.isArray(adminUsers)
200
+ ? adminUsers.map((item) => readString(item)).filter(Boolean)
201
+ : [];
202
+ if (normalizedAdminUsers.length > 0) {
203
+ payload.admin_users = normalizedAdminUsers;
204
+ }
205
+ const json = await this.postWecomDocApi({
206
+ path: "/cgi-bin/wedoc/create_doc",
207
+ actionLabel: "create_doc",
208
+ agent,
209
+ body: payload,
210
+ });
211
+ return {
212
+ raw: json,
213
+ docId: readString(json.docid),
214
+ url: readString(json.url),
215
+ docType: normalizedDocType,
216
+ docTypeLabel: mapDocTypeLabel(normalizedDocType),
217
+ };
218
+ }
219
+
220
+ async renameDoc(params: { agent: ResolvedAgentAccount; docId: string; newName: string }) {
221
+ const { agent, docId, newName } = params;
222
+ const payload = {
223
+ docid: readString(docId),
224
+ new_name: readString(newName),
225
+ };
226
+ if (!payload.docid) throw new Error("docId required");
227
+ if (!payload.new_name) throw new Error("newName required");
228
+ const json = await this.postWecomDocApi({
229
+ path: "/cgi-bin/wedoc/rename_doc",
230
+ actionLabel: "rename_doc",
231
+ agent,
232
+ body: payload,
233
+ });
234
+ return {
235
+ raw: json,
236
+ docId: payload.docid,
237
+ newName: payload.new_name,
238
+ };
239
+ }
240
+
241
+ async copyDoc(params: { agent: ResolvedAgentAccount; docId: string; newName?: string; spaceId?: string; fatherId?: string }) {
242
+ const { agent, docId, newName, spaceId, fatherId } = params;
243
+ const payload: Record<string, unknown> = {
244
+ docid: readString(docId),
245
+ };
246
+ if (!payload.docid) throw new Error("docId required");
247
+ if (newName) payload.new_name = readString(newName);
248
+ if (spaceId) payload.spaceid = readString(spaceId);
249
+ if (fatherId) payload.fatherid = readString(fatherId);
250
+
251
+ const json = await this.postWecomDocApi({
252
+ path: "/cgi-bin/wedoc/smartsheet/copy",
253
+ actionLabel: "copy_smartsheet",
254
+ agent,
255
+ body: payload,
256
+ });
257
+ return {
258
+ raw: json,
259
+ docId: readString(json.docid),
260
+ url: readString(json.url),
261
+ };
262
+ }
263
+
264
+ async getDocBaseInfo(params: { agent: ResolvedAgentAccount; docId: string }) {
265
+ const { agent, docId } = params;
266
+ const normalizedDocId = readString(docId);
267
+ if (!normalizedDocId) throw new Error("docId required");
268
+ const json = await this.postWecomDocApi({
269
+ path: "/cgi-bin/wedoc/get_doc_base_info",
270
+ actionLabel: "get_doc_base_info",
271
+ agent,
272
+ body: { docid: normalizedDocId },
273
+ });
274
+ return {
275
+ raw: json,
276
+ info: json.doc_base_info && typeof json.doc_base_info === "object" ? json.doc_base_info : {},
277
+ };
278
+ }
279
+
280
+ async shareDoc(params: { agent: ResolvedAgentAccount; docId: string }) {
281
+ const { agent, docId } = params;
282
+ const normalizedDocId = readString(docId);
283
+ if (!normalizedDocId) throw new Error("docId required");
284
+ const json = await this.postWecomDocApi({
285
+ path: "/cgi-bin/wedoc/doc_share",
286
+ actionLabel: "doc_share",
287
+ agent,
288
+ body: { docid: normalizedDocId },
289
+ });
290
+ return {
291
+ raw: json,
292
+ shareUrl: readString(json.share_url),
293
+ };
294
+ }
295
+
296
+ async getDocAuth(params: { agent: ResolvedAgentAccount; docId: string }) {
297
+ const { agent, docId } = params;
298
+ const normalizedDocId = readString(docId);
299
+ if (!normalizedDocId) throw new Error("docId required");
300
+ const json = await this.postWecomDocApi({
301
+ path: "/cgi-bin/wedoc/doc_get_auth",
302
+ actionLabel: "doc_get_auth",
303
+ agent,
304
+ body: { docid: normalizedDocId },
305
+ });
306
+ return {
307
+ raw: json,
308
+ accessRule: json.access_rule && typeof json.access_rule === "object" ? json.access_rule : {},
309
+ secureSetting: json.secure_setting && typeof json.secure_setting === "object" ? json.secure_setting : {},
310
+ docMembers: Array.isArray(json.doc_member_list) ? json.doc_member_list : [],
311
+ coAuthList: Array.isArray(json.co_auth_list) ? json.co_auth_list : [],
312
+ };
313
+ }
314
+
315
+ async deleteDoc(params: { agent: ResolvedAgentAccount; docId?: string; formId?: string }) {
316
+ const { agent, docId, formId } = params;
317
+ const payload: Record<string, string> = {};
318
+ const normalizedDocId = readString(docId);
319
+ const normalizedFormId = readString(formId);
320
+ if (normalizedDocId) payload.docid = normalizedDocId;
321
+ if (normalizedFormId) payload.formid = normalizedFormId;
322
+ if (!payload.docid && !payload.formid) {
323
+ throw new Error("docId or formId required");
324
+ }
325
+ const json = await this.postWecomDocApi({
326
+ path: "/cgi-bin/wedoc/del_doc",
327
+ actionLabel: "del_doc",
328
+ agent,
329
+ body: payload,
330
+ });
331
+ return {
332
+ raw: json,
333
+ docId: payload.docid || "",
334
+ formId: payload.formid || "",
335
+ };
336
+ }
337
+
338
+ async setDocJoinRule(params: { agent: ResolvedAgentAccount; docId: string; request: any }) {
339
+ const { agent, docId, request } = params;
340
+ const payload = {
341
+ ...readObject(request),
342
+ };
343
+ payload.docid = readString(docId || payload.docid);
344
+ if (!payload.docid) throw new Error("docId required");
345
+ const json = await this.postWecomDocApi({
346
+ path: "/cgi-bin/wedoc/mod_doc_join_rule",
347
+ actionLabel: "mod_doc_join_rule",
348
+ agent,
349
+ body: payload,
350
+ });
351
+ return {
352
+ raw: json,
353
+ docId: payload.docid as string,
354
+ };
355
+ }
356
+
357
+ async setDocMemberAuth(params: { agent: ResolvedAgentAccount; docId: string; request: any }) {
358
+ const { agent, docId, request } = params;
359
+ const payload = {
360
+ ...readObject(request),
361
+ };
362
+ payload.docid = readString(docId || payload.docid);
363
+ if (!payload.docid) throw new Error("docId required");
364
+ const json = await this.postWecomDocApi({
365
+ path: "/cgi-bin/wedoc/mod_doc_member",
366
+ actionLabel: "mod_doc_member",
367
+ agent,
368
+ body: payload,
369
+ });
370
+ return {
371
+ raw: json,
372
+ docId: payload.docid as string,
373
+ };
374
+ }
375
+
376
+ async grantDocAccess(params: {
377
+ agent: ResolvedAgentAccount;
378
+ docId: string;
379
+ viewers?: unknown;
380
+ collaborators?: unknown;
381
+ removeViewers?: unknown;
382
+ removeCollaborators?: unknown;
383
+ authLevel?: number;
384
+ }) {
385
+ const { agent, docId, viewers, collaborators, removeViewers, removeCollaborators, authLevel } = params;
386
+ const payload = buildDocMemberAuthRequest({
387
+ docId,
388
+ viewers,
389
+ collaborators,
390
+ removeViewers,
391
+ removeCollaborators,
392
+ authLevel,
393
+ });
394
+ const result = await this.setDocMemberAuth({
395
+ agent,
396
+ docId: payload.docid as string,
397
+ request: payload,
398
+ });
399
+ return {
400
+ ...result,
401
+ addedViewerCount: (payload.update_file_member_list as any[])?.length ?? 0,
402
+ addedCollaboratorCount: (payload.update_co_auth_list as any[])?.length ?? 0,
403
+ removedViewerCount: (payload.del_file_member_list as any[])?.length ?? 0,
404
+ removedCollaboratorCount: (payload.del_co_auth_list as any[])?.length ?? 0,
405
+ };
406
+ }
407
+
408
+ async addDocCollaborators(params: { agent: ResolvedAgentAccount; docId: string; collaborators: unknown; auth?: number }) {
409
+ const { agent, docId, collaborators, auth } = params;
410
+ return this.grantDocAccess({
411
+ agent,
412
+ docId,
413
+ collaborators,
414
+ authLevel: auth ?? 2, // Default to edit/read-write for collaborators
415
+ });
416
+ }
417
+
418
+ async setDocSafetySetting(params: { agent: ResolvedAgentAccount; docId: string; request: any }) {
419
+ const { agent, docId, request } = params;
420
+ const payload = {
421
+ ...readObject(request),
422
+ };
423
+ payload.docid = readString(docId || payload.docid);
424
+ if (!payload.docid) throw new Error("docId required");
425
+ const json = await this.postWecomDocApi({
426
+ path: "/cgi-bin/wedoc/mod_doc_safty_setting",
427
+ actionLabel: "mod_doc_safty_setting",
428
+ agent,
429
+ body: payload,
430
+ });
431
+ return {
432
+ raw: json,
433
+ docId: payload.docid as string,
434
+ };
435
+ }
436
+
437
+ async createCollect(params: { agent: ResolvedAgentAccount; formInfo: any; spaceId?: string; fatherId?: string }) {
438
+ const { agent, formInfo, spaceId, fatherId } = params;
439
+ const payload: Record<string, unknown> = {
440
+ form_info: readObject(formInfo),
441
+ };
442
+ if (Object.keys(payload.form_info as object).length === 0) {
443
+ throw new Error("formInfo required");
444
+ }
445
+ const normalizedSpaceId = readString(spaceId);
446
+ const normalizedFatherId = readString(fatherId);
447
+ if (normalizedSpaceId) payload.spaceid = normalizedSpaceId;
448
+ if (normalizedFatherId) payload.fatherid = normalizedFatherId;
449
+ const json = await this.postWecomDocApi({
450
+ path: "/cgi-bin/wedoc/create_collect",
451
+ actionLabel: "create_collect",
452
+ agent,
453
+ body: payload,
454
+ });
455
+ return {
456
+ raw: json,
457
+ formId: readString(json.formid),
458
+ title: readString((payload.form_info as any).form_title),
459
+ };
460
+ }
461
+
462
+ async modifyCollect(params: { agent: ResolvedAgentAccount; oper: string; formId: string; formInfo: any }) {
463
+ const { agent, oper, formId, formInfo } = params;
464
+ const payload = {
465
+ oper: readString(oper),
466
+ formid: readString(formId),
467
+ form_info: readObject(formInfo),
468
+ };
469
+ if (!payload.oper) throw new Error("oper required");
470
+ if (!payload.formid) throw new Error("formId required");
471
+ if (Object.keys(payload.form_info).length === 0) {
472
+ throw new Error("formInfo required");
473
+ }
474
+ const json = await this.postWecomDocApi({
475
+ path: "/cgi-bin/wedoc/modify_collect",
476
+ actionLabel: "modify_collect",
477
+ agent,
478
+ body: payload,
479
+ });
480
+ return {
481
+ raw: json,
482
+ formId: payload.formid,
483
+ oper: payload.oper,
484
+ title: readString((payload.form_info as any).form_title),
485
+ };
486
+ }
487
+
488
+ async getFormInfo(params: { agent: ResolvedAgentAccount; formId: string }) {
489
+ const { agent, formId } = params;
490
+ const normalizedFormId = readString(formId);
491
+ if (!normalizedFormId) throw new Error("formId required");
492
+ const json = await this.postWecomDocApi({
493
+ path: "/cgi-bin/wedoc/get_form_info",
494
+ actionLabel: "get_form_info",
495
+ agent,
496
+ body: { formid: normalizedFormId },
497
+ });
498
+ return {
499
+ raw: json,
500
+ formInfo: readObject(json.form_info),
501
+ };
502
+ }
503
+
504
+ async getFormAnswer(params: { agent: ResolvedAgentAccount; repeatedId: string; answerIds?: unknown[] }) {
505
+ const { agent, repeatedId, answerIds } = params;
506
+ const normalizedRepeatedId = readString(repeatedId);
507
+ if (!normalizedRepeatedId) throw new Error("repeatedId required");
508
+ const normalizedAnswerIds = Array.isArray(answerIds)
509
+ ? answerIds
510
+ .map((item) => Number(item))
511
+ .filter((item) => Number.isFinite(item))
512
+ : [];
513
+ const payload: Record<string, unknown> = {
514
+ repeated_id: normalizedRepeatedId,
515
+ };
516
+ if (normalizedAnswerIds.length > 0) {
517
+ payload.answer_ids = normalizedAnswerIds;
518
+ }
519
+ const json = await this.postWecomDocApi({
520
+ path: "/cgi-bin/wedoc/get_form_answer",
521
+ actionLabel: "get_form_answer",
522
+ agent,
523
+ body: payload,
524
+ });
525
+ const answer = readObject(json.answer);
526
+ return {
527
+ raw: json,
528
+ answer,
529
+ answerList: readArray((answer as any).answer_list),
530
+ };
531
+ }
532
+
533
+ async getFormStatistic(params: { agent: ResolvedAgentAccount; requests: unknown[] }) {
534
+ const { agent, requests } = params;
535
+ const payload = Array.isArray(requests)
536
+ ? requests.map((item) => readObject(item)).filter((item) => Object.keys(item).length > 0)
537
+ : [];
538
+ if (payload.length === 0) {
539
+ throw new Error("requests required");
540
+ }
541
+ const json = await this.postWecomDocApi({
542
+ path: "/cgi-bin/wedoc/get_form_statistic",
543
+ actionLabel: "get_form_statistic",
544
+ agent,
545
+ body: { requests: payload },
546
+ });
547
+ const statisticList = readArray(json.statistic_list);
548
+ return {
549
+ raw: json,
550
+ items: statisticList,
551
+ successCount: statisticList.filter((item: any) => Number(item?.errcode ?? 0) === 0).length,
552
+ };
553
+ }
554
+
555
+ // --- Content Operations (New) ---
556
+
557
+ async getDocContent(params: { agent: ResolvedAgentAccount; docId: string }) {
558
+ const { agent, docId } = params;
559
+ const json = await this.postWecomDocApi({
560
+ path: "/cgi-bin/wedoc/document/get",
561
+ actionLabel: "get_doc_content",
562
+ agent,
563
+ body: { docid: readString(docId) },
564
+ }) as GetDocContentResponse;
565
+
566
+ // Ensure structure strictly matches official API: { version: number, document: Node }
567
+ return {
568
+ raw: json,
569
+ version: json.version,
570
+ document: json.document
571
+ };
572
+ }
573
+
574
+ async updateDocContent(params: { agent: ResolvedAgentAccount; docId: string; requests: UpdateRequest[]; version?: number }) {
575
+ const { agent, docId, requests, version } = params;
576
+
577
+ // Validate requests structure basic check
578
+ const requestList = readArray(requests);
579
+ if (requestList.length === 0) {
580
+ throw new Error("requests list cannot be empty");
581
+ }
582
+
583
+ const body: Record<string, unknown> = {
584
+ docid: readString(docId),
585
+ requests: requestList,
586
+ };
587
+ // version is optional but recommended for concurrency control
588
+ if (version !== undefined && version !== null) {
589
+ body.version = Number(version);
590
+ }
591
+
592
+ const json = await this.postWecomDocApi({
593
+ path: "/cgi-bin/wedoc/document/batch_update",
594
+ actionLabel: "update_doc_content",
595
+ agent,
596
+ body,
597
+ }) as BatchUpdateDocResponse;
598
+ return { raw: json };
599
+ }
600
+
601
+ // --- Spreadsheet Operations ---
602
+
603
+ async getSheetProperties(params: { agent: ResolvedAgentAccount; docId: string }) {
604
+ const { agent, docId } = params;
605
+ const normalizedDocId = readString(docId);
606
+ if (!normalizedDocId) throw new Error("docId required");
607
+ const json = await this.postWecomDocApi({
608
+ path: "/cgi-bin/wedoc/spreadsheet/get_sheet_properties",
609
+ actionLabel: "get_sheet_properties",
610
+ agent,
611
+ body: { docid: normalizedDocId },
612
+ });
613
+ return {
614
+ raw: json,
615
+ properties:
616
+ (Array.isArray(json.properties) && json.properties) ||
617
+ (Array.isArray(json.sheet_properties) && json.sheet_properties) ||
618
+ (Array.isArray(json.sheet_list) && json.sheet_list) ||
619
+ [],
620
+ };
621
+ }
622
+
623
+ async modDocMemberNotifiedScope(params: { agent: ResolvedAgentAccount; docId: string; notified_scope_type: number; notified_member_list?: any[] }) {
624
+ const { agent, docId, notified_scope_type, notified_member_list } = params;
625
+ const json = await this.postWecomDocApi({
626
+ path: "/cgi-bin/wedoc/mod_doc_member_notified_scope",
627
+ actionLabel: "mod_doc_member_notified_scope",
628
+ agent,
629
+ body: { docid: readString(docId), notified_scope_type, notified_member_list },
630
+ });
631
+ return json;
632
+ }
633
+
634
+ async editSheetData(params: {
635
+ agent: ResolvedAgentAccount;
636
+ docId: string;
637
+ sheetId: string;
638
+ startRow?: number;
639
+ startColumn?: number;
640
+ gridData?: any;
641
+ }) {
642
+ const { agent, docId, sheetId, startRow = 0, startColumn = 0, gridData } = params;
643
+
644
+ // Validate required docId
645
+ const normalizedDocId = readString(docId);
646
+ if (!normalizedDocId) {
647
+ throw new Error('docId is required');
648
+ }
649
+
650
+ // Validate required sheetId
651
+ const normalizedSheetId = readString(sheetId);
652
+ if (!normalizedSheetId) {
653
+ throw new Error('sheetId is required');
654
+ }
655
+
656
+ // Build GridData per official API
657
+ // gridData.rows[i].values[j] must be: {cell_value: {text} | {link: {text, url}}, cell_format?: {...}}
658
+ const finalGridData = {
659
+ start_row: startRow,
660
+ start_column: startColumn,
661
+ rows: (gridData?.rows || []).map((row: any) => ({
662
+ values: (row.values || []).map((cell: any) => {
663
+ // If already CellData format, use as-is
664
+ if (cell && typeof cell === 'object' && cell.cell_value) {
665
+ return cell;
666
+ }
667
+ // Otherwise wrap primitive as CellValue
668
+ return { cell_value: { text: String(cell ?? '') } };
669
+ })
670
+ }))
671
+ };
672
+
673
+ // Build batch_update request per official API
674
+ const body = {
675
+ docid: normalizedDocId,
676
+ requests: [{
677
+ update_range_request: {
678
+ sheet_id: normalizedSheetId,
679
+ grid_data: finalGridData
680
+ }
681
+ }]
682
+ };
683
+
684
+ const json = await this.postWecomDocApi({
685
+ path: "/cgi-bin/wedoc/spreadsheet/batch_update",
686
+ actionLabel: "spreadsheet_batch_update",
687
+ agent, body,
688
+ });
689
+ return { raw: json, docId: body.docid as string };
690
+ }
691
+
692
+ /**
693
+ * Build CellFormat object per official API
694
+ */
695
+ private buildCellFormat(formatData: any): any {
696
+ const textFormat: any = {};
697
+
698
+ // Font properties
699
+ if (formatData.font != null) {
700
+ textFormat.font = String(formatData.font);
701
+ }
702
+ if (formatData.font_size != null) {
703
+ textFormat.font_size = Math.min(72, Math.max(1, Number(formatData.font_size)));
704
+ }
705
+ if (formatData.bold != null) {
706
+ textFormat.bold = Boolean(formatData.bold);
707
+ }
708
+ if (formatData.italic != null) {
709
+ textFormat.italic = Boolean(formatData.italic);
710
+ }
711
+ if (formatData.strikethrough != null) {
712
+ textFormat.strikethrough = Boolean(formatData.strikethrough);
713
+ }
714
+ if (formatData.underline != null) {
715
+ textFormat.underline = Boolean(formatData.underline);
716
+ }
717
+
718
+ // Color (RGBA)
719
+ if (formatData.color != null && typeof formatData.color === "object") {
720
+ const color = formatData.color;
721
+ textFormat.color = {
722
+ red: Math.min(255, Math.max(0, Number(color.red ?? 0))),
723
+ green: Math.min(255, Math.max(0, Number(color.green ?? 0))),
724
+ blue: Math.min(255, Math.max(0, Number(color.blue ?? 0))),
725
+ alpha: Math.min(255, Math.max(0, Number(color.alpha ?? 255)))
726
+ };
727
+ }
728
+
729
+ // Return empty object if no format properties
730
+ if (Object.keys(textFormat).length === 0) {
731
+ return null;
732
+ }
733
+
734
+ return { text_format: textFormat };
735
+ }
736
+
737
+ async getSheetData(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string; range: string }) {
738
+ const { agent, docId, sheetId, range } = params;
739
+ const body = { docid: readString(docId), sheet_id: readString(sheetId), range: readString(range) };
740
+ const json = await this.postWecomDocApi({
741
+ path: "/cgi-bin/wedoc/spreadsheet/get_sheet_range_data",
742
+ actionLabel: "get_sheet_range_data",
743
+ agent, body,
744
+ });
745
+ return { raw: json, data: json };
746
+ }
747
+
748
+ async modifySheetProperties(params: { agent: ResolvedAgentAccount; docId: string; requests: unknown[] }) {
749
+ const { agent, docId, requests } = params;
750
+ const json = await this.postWecomDocApi({
751
+ path: "/cgi-bin/wedoc/spreadsheet/batch_update",
752
+ actionLabel: "spreadsheet_batch_update",
753
+ agent, body: { docid: readString(docId), requests: readArray(requests) },
754
+ });
755
+ return { raw: json, docId: docId };
756
+ }
757
+
758
+ // --- Smart Table Operations ---
759
+
760
+ async smartTableOperate(params: { agent: ResolvedAgentAccount; docId: string; operation: string; bodyData: any }) {
761
+ const { agent, docId, operation, bodyData } = params;
762
+ const body = { docid: readString(docId), ...readObject(bodyData) };
763
+ const path = `/cgi-bin/wedoc/smartsheet/${operation}`;
764
+ const json = await this.postWecomDocApi({
765
+ path,
766
+ actionLabel: `smartsheet_${operation}`,
767
+ agent, body,
768
+ });
769
+ return { raw: json, docId };
770
+ }
771
+
772
+ async smartTableGetSheets(params: { agent: ResolvedAgentAccount; docId: string }) {
773
+ const { agent, docId } = params;
774
+ const json = await this.postWecomDocApi({
775
+ path: "/cgi-bin/wedoc/smartsheet/get_sheet",
776
+ actionLabel: "smartsheet_get_sheet",
777
+ agent,
778
+ body: { docid: readString(docId) },
779
+ });
780
+ return {
781
+ raw: json,
782
+ sheets: readArray(json.sheet_list),
783
+ };
784
+ }
785
+
786
+ async smartTableAddSheet(params: { agent: ResolvedAgentAccount; docId: string; title: string; index?: number }) {
787
+ const { agent, docId, title, index } = params;
788
+ return this.smartTableOperate({ agent, docId, operation: "add_sheet", bodyData: { properties: { title, index } } });
789
+ }
790
+
791
+ async smartTableDelSheet(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string }) {
792
+ const { agent, docId, sheetId } = params;
793
+ return this.smartTableOperate({ agent, docId, operation: "delete_sheet", bodyData: { sheet_id: sheetId } });
794
+ }
795
+
796
+ async smartTableUpdateSheet(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string; title: string }) {
797
+ const { agent, docId, sheetId, title } = params;
798
+ return this.smartTableOperate({ agent, docId, operation: "update_sheet", bodyData: { properties: { sheet_id: sheetId, title } } });
799
+ }
800
+ async smartTableAddView(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string; view_title: string; view_type: string; property_gantt?: any; property_calendar?: any }) {
801
+ const { agent, docId, sheetId, view_title, view_type, property_gantt, property_calendar } = params;
802
+ return this.smartTableOperate({ agent, docId, operation: "add_view", bodyData: { sheet_id: sheetId, view_title, view_type, property_gantt, property_calendar } });
803
+ }
804
+
805
+ async smartTableUpdateView(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string; view_id: string; view_title?: string; property_gantt?: any; property_calendar?: any }) {
806
+ const { agent, docId, sheetId, view_id, view_title, property_gantt, property_calendar } = params;
807
+ return this.smartTableOperate({ agent, docId, operation: "update_view", bodyData: { sheet_id: sheetId, view_id, view_title, property_gantt, property_calendar } });
808
+ }
809
+
810
+ async smartTableDelView(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string; view_ids: string[] }) {
811
+ const { agent, docId, sheetId, view_ids } = params;
812
+ return this.smartTableOperate({ agent, docId, operation: "delete_views", bodyData: { sheet_id: sheetId, view_ids } });
813
+ }
814
+
815
+ async smartTableAddFields(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string; fields: any[] }) {
816
+ const { agent, docId, sheetId, fields } = params;
817
+ return this.smartTableOperate({ agent, docId, operation: "add_fields", bodyData: { sheet_id: sheetId, fields } });
818
+ }
819
+
820
+ async smartTableDelFields(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string; field_ids: string[] }) {
821
+ const { agent, docId, sheetId, field_ids } = params;
822
+ return this.smartTableOperate({ agent, docId, operation: "delete_fields", bodyData: { sheet_id: sheetId, field_ids } });
823
+ }
824
+
825
+ async smartTableUpdateFields(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string; fields: any[] }) {
826
+ const { agent, docId, sheetId, fields } = params;
827
+ return this.smartTableOperate({ agent, docId, operation: "update_fields", bodyData: { sheet_id: sheetId, fields } });
828
+ }
829
+
830
+ async smartTableAddGroup(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string; name: string; children?: string[] }) {
831
+ const { agent, docId, sheetId, name, children } = params;
832
+ return this.smartTableOperate({ agent, docId, operation: "add_field_group", bodyData: { sheet_id: sheetId, name, children } });
833
+ }
834
+
835
+ async smartTableDelGroup(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string; field_group_id: string }) {
836
+ const { agent, docId, sheetId, field_group_id } = params;
837
+ return this.smartTableOperate({ agent, docId, operation: "delete_field_group", bodyData: { sheet_id: sheetId, field_group_id } });
838
+ }
839
+
840
+ async smartTableUpdateGroup(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string; field_group_id: string; name?: string; children?: string[] }) {
841
+ const { agent, docId, sheetId, field_group_id, name, children } = params;
842
+ return this.smartTableOperate({ agent, docId, operation: "update_field_group", bodyData: { sheet_id: sheetId, field_group_id, name, children } });
843
+ }
844
+
845
+ async smartTableGetGroups(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string }) {
846
+ const { agent, docId, sheetId } = params;
847
+ return this.smartTableOperate({ agent, docId, operation: "get_field_groups", bodyData: { sheet_id: sheetId } });
848
+ }
849
+
850
+ async smartTableAddExternalRecords(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string; records: any[] }) {
851
+ const { agent, docId, sheetId, records } = params;
852
+ return this.smartTableOperate({ agent, docId, operation: "add_external_records", bodyData: { sheet_id: sheetId, records } });
853
+ }
854
+
855
+ async smartTableUpdateExternalRecords(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string; records: any[] }) {
856
+ const { agent, docId, sheetId, records } = params;
857
+ return this.smartTableOperate({ agent, docId, operation: "update_external_records", bodyData: { sheet_id: sheetId, records } });
858
+ }
859
+
860
+ async smartTableAddRecords(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string; records: any[] }) {
861
+ const { agent, docId, sheetId, records } = params;
862
+ return this.smartTableOperate({ agent, docId, operation: "add_records", bodyData: { sheet_id: sheetId, records } });
863
+ }
864
+
865
+ async smartTableUpdateRecords(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string; records: any[] }) {
866
+ const { agent, docId, sheetId, records } = params;
867
+ return this.smartTableOperate({ agent, docId, operation: "update_records", bodyData: { sheet_id: sheetId, records } });
868
+ }
869
+
870
+ async smartTableDelRecords(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string; record_ids: string[] }) {
871
+ const { agent, docId, sheetId, record_ids } = params;
872
+ return this.smartTableOperate({ agent, docId, operation: "delete_records", bodyData: { sheet_id: sheetId, record_ids } });
873
+ }
874
+
875
+ async smartTableGetRecords(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string; record_ids?: string[]; offset?: number; limit?: number }) {
876
+ const { agent, docId, sheetId, record_ids, offset, limit } = params;
877
+ return this.smartTableOperate({ agent, docId, operation: "get_records", bodyData: { sheet_id: sheetId, record_ids, offset, limit } });
878
+ }
879
+
880
+ // --- Smartsheet Content Permissions ---
881
+
882
+ async smartTableGetSheetPriv(params: { agent: ResolvedAgentAccount; docId: string; type: number; rule_id_list?: number[] }) {
883
+ const { agent, docId, type, rule_id_list } = params;
884
+ const json = await this.postWecomDocApi({
885
+ path: "/cgi-bin/wedoc/smartsheet/content_priv/get_sheet_priv",
886
+ actionLabel: "smartsheet_get_sheet_priv",
887
+ agent,
888
+ body: { docid: readString(docId), type, rule_id_list },
889
+ });
890
+ return { raw: json };
891
+ }
892
+
893
+ async smartTableUpdateSheetPriv(params: { agent: ResolvedAgentAccount; docId: string; type: number; rule_id?: number; name?: string; priv_list: any[] }) {
894
+ const { agent, docId, type, rule_id, name, priv_list } = params;
895
+ const body: any = { docid: readString(docId), type, priv_list };
896
+ if (rule_id !== undefined) body.rule_id = rule_id;
897
+ if (name !== undefined) body.name = name;
898
+
899
+ const json = await this.postWecomDocApi({
900
+ path: "/cgi-bin/wedoc/smartsheet/content_priv/update_sheet_priv",
901
+ actionLabel: "smartsheet_update_sheet_priv",
902
+ agent,
903
+ body,
904
+ });
905
+ return { raw: json };
906
+ }
907
+
908
+ async smartTableCreateRule(params: { agent: ResolvedAgentAccount; docId: string; name: string }) {
909
+ const { agent, docId, name } = params;
910
+ const json = await this.postWecomDocApi({
911
+ path: "/cgi-bin/wedoc/smartsheet/content_priv/create_rule",
912
+ actionLabel: "smartsheet_create_rule",
913
+ agent,
914
+ body: { docid: readString(docId), name },
915
+ });
916
+ return { raw: json, rule_id: json.rule_id };
917
+ }
918
+
919
+ async smartTableModRuleMember(params: { agent: ResolvedAgentAccount; docId: string; rule_id: number; add_member_range?: any; del_member_range?: any }) {
920
+ const { agent, docId, rule_id, add_member_range, del_member_range } = params;
921
+ const body: any = { docid: readString(docId), rule_id };
922
+ if (add_member_range) body.add_member_range = add_member_range;
923
+ if (del_member_range) body.del_member_range = del_member_range;
924
+
925
+ const json = await this.postWecomDocApi({
926
+ path: "/cgi-bin/wedoc/smartsheet/content_priv/mod_rule_member",
927
+ actionLabel: "smartsheet_mod_rule_member",
928
+ agent,
929
+ body,
930
+ });
931
+ return { raw: json };
932
+ }
933
+
934
+ async smartTableDeleteRule(params: { agent: ResolvedAgentAccount; docId: string; rule_id_list: number[] }) {
935
+ const { agent, docId, rule_id_list } = params;
936
+ const json = await this.postWecomDocApi({
937
+ path: "/cgi-bin/wedoc/smartsheet/content_priv/delete_rule",
938
+ actionLabel: "smartsheet_delete_rule",
939
+ agent,
940
+ body: { docid: readString(docId), rule_id_list },
941
+ });
942
+ return { raw: json };
943
+ }
944
+
945
+ // --- Advanced Account Management ---
946
+
947
+ async assignDocAdvancedAccount(params: { agent: ResolvedAgentAccount; userid_list: string[] }) {
948
+ const { agent, userid_list } = params;
949
+ return this.postWecomDocApi({
950
+ path: "/cgi-bin/meeting/vip/submit_batch_add_job",
951
+ actionLabel: "assign_advanced_account",
952
+ agent,
953
+ body: { userid_list },
954
+ });
955
+ }
956
+
957
+ async cancelDocAdvancedAccount(params: { agent: ResolvedAgentAccount; userid_list: string[] }) {
958
+ const { agent, userid_list } = params;
959
+ return this.postWecomDocApi({
960
+ path: "/cgi-bin/meeting/vip/submit_batch_del_job",
961
+ actionLabel: "cancel_advanced_account",
962
+ agent,
963
+ body: { userid_list },
964
+ });
965
+ }
966
+
967
+ async getDocAdvancedAccountList(params: { agent: ResolvedAgentAccount; offset?: number; limit?: number }) {
968
+ const { agent, offset, limit } = params;
969
+ return this.postWecomDocApi({
970
+ path: "/cgi-bin/meeting/vip/get_vip_user_list",
971
+ actionLabel: "get_advanced_account_list",
972
+ agent,
973
+ body: { cursor: offset ? String(offset) : undefined, limit: limit ?? 100 },
974
+ });
975
+ }
976
+
977
+ // --- Material Management ---
978
+
979
+ async uploadDocImage(params: { agent: ResolvedAgentAccount; docId: string; base64_content: string }) {
980
+ const { agent, docId, base64_content } = params;
981
+ const normalizedDocId = readString(docId);
982
+ if (!normalizedDocId) throw new Error("docId required");
983
+
984
+ const json = await this.postWecomDocApi({
985
+ path: "/cgi-bin/wedoc/image_upload",
986
+ actionLabel: "upload_doc_image",
987
+ agent,
988
+ body: {
989
+ docid: normalizedDocId,
990
+ base64_content: base64_content
991
+ }
992
+ });
993
+
994
+ return {
995
+ raw: json,
996
+ url: readString(json.url),
997
+ height: json.height,
998
+ width: json.width,
999
+ size: json.size
1000
+ };
1001
+ }
1002
+ }