weaver-work-cli 0.1.4 → 0.1.6
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/internal/e10/auth/commands.js +4 -1
- package/dist/internal/e10/request-runtime.js +3 -2
- package/dist/shortcuts/index.js +4 -0
- package/dist/shortcuts/meeting/continuation.js +70 -0
- package/dist/shortcuts/meeting/errors.js +63 -0
- package/dist/shortcuts/meeting/host.js +167 -0
- package/dist/shortcuts/meeting/index.js +123 -0
- package/dist/shortcuts/meeting/manifest.js +64 -0
- package/dist/shortcuts/meeting/operations/catalogs.js +58 -0
- package/dist/shortcuts/meeting/operations/change.js +242 -0
- package/dist/shortcuts/meeting/operations/conflicts.js +142 -0
- package/dist/shortcuts/meeting/operations/create.js +349 -0
- package/dist/shortcuts/meeting/operations/env.js +191 -0
- package/dist/shortcuts/meeting/operations/lifecycle.js +139 -0
- package/dist/shortcuts/meeting/operations/meetings.js +73 -0
- package/dist/shortcuts/meeting/operations/receipts.js +195 -0
- package/dist/shortcuts/meeting/operations/registry.js +47 -0
- package/dist/shortcuts/meeting/operations/rooms.js +132 -0
- package/dist/shortcuts/meeting/operations/shared.js +112 -0
- package/dist/shortcuts/meeting/operations/signs.js +41 -0
- package/dist/shortcuts/meeting/operations/types.js +25 -0
- package/dist/shortcuts/meeting/operations.js +19 -0
- package/dist/shortcuts/yimiaoban/continuation.js +70 -0
- package/dist/shortcuts/yimiaoban/errors.js +61 -0
- package/dist/shortcuts/yimiaoban/host.js +469 -0
- package/dist/shortcuts/yimiaoban/index.js +271 -0
- package/dist/shortcuts/yimiaoban/manifest.js +28 -0
- package/dist/shortcuts/yimiaoban/operations/file-user.js +288 -0
- package/dist/shortcuts/yimiaoban/operations/group-read.js +477 -0
- package/dist/shortcuts/yimiaoban/operations/group-write.js +591 -0
- package/dist/shortcuts/yimiaoban/operations/msg-parse.js +429 -0
- package/dist/shortcuts/yimiaoban/operations/msg-sync.js +519 -0
- package/dist/shortcuts/yimiaoban/operations/msg-write.js +593 -0
- package/dist/shortcuts/yimiaoban/operations/person.js +102 -0
- package/dist/shortcuts/yimiaoban/operations/registry.js +38 -0
- package/dist/shortcuts/yimiaoban/operations/session.js +437 -0
- package/dist/shortcuts/yimiaoban/operations/shared.js +236 -0
- package/dist/shortcuts/yimiaoban/operations/types.js +27 -0
- package/dist/shortcuts/yimiaoban/operations/write-util.js +78 -0
- package/dist/shortcuts/yimiaoban/operations.js +8 -0
- package/docs/_catalog.md +6 -0
- package/docs/meeting.md +48 -0
- package/docs/yimiaoban.md +86 -0
- package/package.json +1 -1
- package/skill-template/business-info.json +4 -1
- package/skill-template/domains/meeting.md +3 -0
- package/skill-template/domains/yimiaoban.md +21 -0
- package/skills/weaver-e10-calendar/SKILL.md +1 -0
- package/skills/weaver-e10-esb/SKILL.md +1 -0
- package/skills/weaver-e10-esb/references/source-manifest.json +3 -3
- package/skills/weaver-e10-hrm/SKILL.md +1 -0
- package/skills/weaver-e10-jiuchuanhui/SKILL.md +1 -0
- package/skills/weaver-e10-jucailin/SKILL.md +1 -0
- package/skills/weaver-e10-mail/SKILL.md +1 -0
- package/skills/weaver-e10-meeting/SKILL.md +106 -0
- package/skills/weaver-e10-meeting/product.json +8 -0
- package/skills/weaver-e10-meeting/references/meeting-change.md +67 -0
- package/skills/weaver-e10-meeting/references/meeting-create.md +52 -0
- package/skills/weaver-e10-meeting/references/meeting-lifecycle.md +57 -0
- package/skills/weaver-e10-meeting/references/meeting-queries.md +61 -0
- package/skills/weaver-e10-meeting/references/meeting-receipts-signs.md +48 -0
- package/skills/weaver-e10-meeting/references/meeting-update.md +7 -0
- package/skills/weaver-e10-meeting/references/source-manifest.json +50 -0
- package/skills/weaver-e10-plan/SKILL.md +1 -0
- package/skills/weaver-e10-plan/references/source-manifest.json +3 -3
- package/skills/weaver-e10-qiyecheng/SKILL.md +1 -0
- package/skills/weaver-e10-qiyecheng/references/source-manifest.json +3 -3
- package/skills/weaver-e10-shared/SKILL.md +4 -0
- package/skills/weaver-e10-skill-maker/SKILL.md +1 -0
- package/skills/weaver-e10-skill-maker/references/weaver-skill-style.md +1 -1
- package/skills/weaver-e10-wenshuding/SKILL.md +1 -0
- package/skills/weaver-e10-wenshuding/references/source-manifest.json +3 -3
- package/skills/weaver-e10-yepiaotong/references/source-manifest.json +5 -5
- package/skills/weaver-e10-yimiaoban/SKILL.md +104 -0
- package/skills/weaver-e10-yimiaoban/product.json +8 -0
- package/skills/weaver-e10-yimiaoban/references/ding-write.md +68 -0
- package/skills/weaver-e10-yimiaoban/references/error-codes.md +85 -0
- package/skills/weaver-e10-yimiaoban/references/field-resolution.md +46 -0
- package/skills/weaver-e10-yimiaoban/references/file-user-i18n.md +54 -0
- package/skills/weaver-e10-yimiaoban/references/group-read.md +55 -0
- package/skills/weaver-e10-yimiaoban/references/group-write.md +72 -0
- package/skills/weaver-e10-yimiaoban/references/msg-read.md +59 -0
- package/skills/weaver-e10-yimiaoban/references/msg-write.md +64 -0
- package/skills/weaver-e10-yimiaoban/references/person.md +44 -0
- package/skills/weaver-e10-yimiaoban/references/safety-boundaries.md +61 -0
- package/skills/weaver-e10-yimiaoban/references/session-sysmsg.md +47 -0
- package/skills/weaver-e10-yimiaoban/references/source-manifest.json +485 -0
- package/skills/weaver-e10-ziguanjia/SKILL.md +1 -0
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { mkdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { dirname, extname, resolve } from 'node:path';
|
|
5
|
+
import { requireE10Session } from '../../internal/e10/context.js';
|
|
6
|
+
import { getActiveProfile } from '../../internal/e10/auth/session.js';
|
|
7
|
+
import { E10ApiError, createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
|
|
8
|
+
import { ymbAuthError, ymbNetworkError, YmbCliError, ymbValidationError } from './errors.js';
|
|
9
|
+
export const IM_PREFIX = '/api/em/msg/executeIm';
|
|
10
|
+
function inferProfile(ctx) {
|
|
11
|
+
const options = ctx.getGlobalOptions();
|
|
12
|
+
return options.profile || getActiveProfile();
|
|
13
|
+
}
|
|
14
|
+
export function assertPath(requestPath) {
|
|
15
|
+
if (typeof requestPath !== 'string' || !requestPath.startsWith('/') || requestPath.startsWith('//')) {
|
|
16
|
+
throw ymbValidationError('path_invalid', '请求路径必须是相对绝对路径');
|
|
17
|
+
}
|
|
18
|
+
if (/^https?:/i.test(requestPath) || /[\r\n]/.test(requestPath) || requestPath.includes('/../')) {
|
|
19
|
+
throw ymbValidationError('path_invalid', '请求路径不允许跨域或路径逃逸');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function formBody(form) {
|
|
23
|
+
if (!form)
|
|
24
|
+
return undefined;
|
|
25
|
+
const params = new URLSearchParams();
|
|
26
|
+
for (const [key, value] of Object.entries(form)) {
|
|
27
|
+
if (value === undefined || value === null)
|
|
28
|
+
continue;
|
|
29
|
+
params.set(key, typeof value === 'string' ? value : JSON.stringify(value));
|
|
30
|
+
}
|
|
31
|
+
return params;
|
|
32
|
+
}
|
|
33
|
+
function mapRuntimeNetworkError(error) {
|
|
34
|
+
if (error instanceof E10ApiError && error.type === 'network') {
|
|
35
|
+
throw ymbNetworkError(error.subtype, error.message, {
|
|
36
|
+
retryable: error.retryable,
|
|
37
|
+
details: error.details,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
function guessMime(name) {
|
|
43
|
+
const ext = extname(String(name || '')).toLowerCase();
|
|
44
|
+
const map = {
|
|
45
|
+
'.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png', '.gif': 'image/gif',
|
|
46
|
+
'.webp': 'image/webp', '.bmp': 'image/bmp', '.svg': 'image/svg+xml',
|
|
47
|
+
'.pdf': 'application/pdf', '.txt': 'text/plain', '.md': 'text/markdown',
|
|
48
|
+
'.doc': 'application/msword',
|
|
49
|
+
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
50
|
+
'.xls': 'application/vnd.ms-excel',
|
|
51
|
+
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
52
|
+
'.ppt': 'application/vnd.ms-powerpoint',
|
|
53
|
+
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
54
|
+
'.zip': 'application/zip', '.rar': 'application/x-rar-compressed',
|
|
55
|
+
'.mp4': 'video/mp4', '.mp3': 'audio/mpeg',
|
|
56
|
+
};
|
|
57
|
+
return map[ext] || 'application/octet-stream';
|
|
58
|
+
}
|
|
59
|
+
async function md5File(filePath) {
|
|
60
|
+
const hash = createHash('md5');
|
|
61
|
+
const { createReadStream } = await import('node:fs');
|
|
62
|
+
const stream = createReadStream(filePath);
|
|
63
|
+
for await (const chunk of stream)
|
|
64
|
+
hash.update(chunk);
|
|
65
|
+
return hash.digest('hex');
|
|
66
|
+
}
|
|
67
|
+
export function createYmbHost(ctx, dependencies = {}) {
|
|
68
|
+
let e10Session = null;
|
|
69
|
+
let sessionContext = null;
|
|
70
|
+
let selfIdentity = null;
|
|
71
|
+
const personCache = new Map();
|
|
72
|
+
const i18nCache = new Map();
|
|
73
|
+
const requireSession = dependencies.requireSession || requireE10Session;
|
|
74
|
+
async function ensureSession() {
|
|
75
|
+
if (!e10Session) {
|
|
76
|
+
e10Session = await requireSession(ctx);
|
|
77
|
+
sessionContext = {
|
|
78
|
+
baseUrl: e10Session.getBaseUrl(),
|
|
79
|
+
profile: inferProfile(ctx),
|
|
80
|
+
userId: e10Session.userId,
|
|
81
|
+
tenantKey: e10Session.tenantKey,
|
|
82
|
+
eteamsId: e10Session.eteamsId,
|
|
83
|
+
agentType: e10Session.agentType,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return e10Session;
|
|
87
|
+
}
|
|
88
|
+
async function getSession() {
|
|
89
|
+
await ensureSession();
|
|
90
|
+
return sessionContext;
|
|
91
|
+
}
|
|
92
|
+
async function runtime() {
|
|
93
|
+
return createE10RequestRuntime(ctx, await ensureSession(), {
|
|
94
|
+
interceptors: dependencies.interceptors,
|
|
95
|
+
fetch: dependencies.fetch,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
async function request(request) {
|
|
99
|
+
const { method, path, query, body, operation, form, multipart, headers: extraHeaders = {},
|
|
100
|
+
// 与源 skill 一致:普通 JSON 请求 15 秒超时即视为失败(超时不做重试)
|
|
101
|
+
timeoutMs = 15_000, } = request;
|
|
102
|
+
assertPath(path);
|
|
103
|
+
const resolvedMethod = method || (body === undefined && form === undefined && multipart === undefined ? 'GET' : 'POST');
|
|
104
|
+
let response;
|
|
105
|
+
try {
|
|
106
|
+
response = await (await runtime()).requestJson({
|
|
107
|
+
module: 'yimiaoban',
|
|
108
|
+
operation,
|
|
109
|
+
method: resolvedMethod,
|
|
110
|
+
path,
|
|
111
|
+
query,
|
|
112
|
+
body: multipart === undefined ? body : undefined,
|
|
113
|
+
form: multipart === undefined && form === undefined ? undefined : formBody(form ?? {}),
|
|
114
|
+
formData: multipart,
|
|
115
|
+
headers: { 'X-Requested-With': 'XMLHttpRequest', ...extraHeaders },
|
|
116
|
+
timeoutMs,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
mapRuntimeNetworkError(error);
|
|
121
|
+
}
|
|
122
|
+
if (response.status === 401 || response.status === 302) {
|
|
123
|
+
throw ymbAuthError('session_expired', 'E10 登录态已失效,请先向用户确认 E10 登录域名,再运行 weaver-work-cli auth login --base-url <域名> 完成登录', { httpStatus: response.status });
|
|
124
|
+
}
|
|
125
|
+
if (response.parseError) {
|
|
126
|
+
if (response.status < 200 || response.status >= 300) {
|
|
127
|
+
throw new YmbCliError('api', 'http_error', `泛微接口 HTTP ${response.status}`, {
|
|
128
|
+
code: response.status,
|
|
129
|
+
details: { status: response.status },
|
|
130
|
+
exitCode: 1,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
throw ymbNetworkError('protocol', `接口返回不是合法 JSON(HTTP ${response.status})`, { retryable: false });
|
|
134
|
+
}
|
|
135
|
+
if (response.status < 200 || response.status >= 300) {
|
|
136
|
+
throw new YmbCliError('api', 'http_error', `泛微接口 HTTP ${response.status}`, {
|
|
137
|
+
code: response.status,
|
|
138
|
+
details: { status: response.status },
|
|
139
|
+
exitCode: 1,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return response.value;
|
|
143
|
+
}
|
|
144
|
+
async function requestBinary(url, headers = {}) {
|
|
145
|
+
const session = await ensureSession();
|
|
146
|
+
const controller = new AbortController();
|
|
147
|
+
const timer = setTimeout(() => controller.abort(), 60_000);
|
|
148
|
+
try {
|
|
149
|
+
const resp = await (dependencies.fetch || globalThis.fetch)(url, {
|
|
150
|
+
headers: {
|
|
151
|
+
accept: 'application/json, text/plain, */*',
|
|
152
|
+
cookie: session.cookieHeader(),
|
|
153
|
+
eteamsid: session.eteamsId,
|
|
154
|
+
'user-agent': session.uaHeader(),
|
|
155
|
+
origin: session.getBaseUrl(),
|
|
156
|
+
...headers,
|
|
157
|
+
},
|
|
158
|
+
redirect: 'manual',
|
|
159
|
+
signal: controller.signal,
|
|
160
|
+
});
|
|
161
|
+
if (resp.status === 401 || resp.status === 302) {
|
|
162
|
+
throw ymbAuthError('session_expired', '登录态已失效:下载需有效登录态,请重新登录后重试');
|
|
163
|
+
}
|
|
164
|
+
if (resp.status !== 200) {
|
|
165
|
+
throw new YmbCliError('api', 'download_failed', `下载失败 HTTP ${resp.status}`, {
|
|
166
|
+
code: resp.status,
|
|
167
|
+
exitCode: 1,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return Buffer.from(await resp.arrayBuffer());
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
if (error instanceof YmbCliError)
|
|
174
|
+
throw error;
|
|
175
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
176
|
+
throw ymbNetworkError('transport', `下载失败:${message}`, { retryable: true });
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
clearTimeout(timer);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/** teamsCheck:解析当前登录人的 imUid/imCid/name(IM uid 语义 = 员工 id)。 */
|
|
183
|
+
async function getSelfIdentity() {
|
|
184
|
+
if (selfIdentity)
|
|
185
|
+
return selfIdentity;
|
|
186
|
+
await ensureSession();
|
|
187
|
+
const base = sessionContext?.baseUrl || '';
|
|
188
|
+
if (!base)
|
|
189
|
+
throw ymbAuthError('self_identity_unavailable', '缺少 baseUrl,请先登录');
|
|
190
|
+
const res = await request({
|
|
191
|
+
method: 'POST',
|
|
192
|
+
path: `/api/baseserver/layout/teamsCheck?clientType=not_xinchuang&client=WEB&domainName=${encodeURIComponent(base)}`,
|
|
193
|
+
body: {},
|
|
194
|
+
operation: 'yimiaoban.self.identity',
|
|
195
|
+
});
|
|
196
|
+
const cur = res?.currentUser || res?.data?.currentUser || {};
|
|
197
|
+
const uid = String(cur.imUid || sessionContext?.userId || '');
|
|
198
|
+
const cid = String(cur.imCid || '');
|
|
199
|
+
const name = cur.name || cur.realName || cur.userName || '';
|
|
200
|
+
if (!uid || !cid) {
|
|
201
|
+
throw ymbAuthError('self_identity_incomplete', `teamsCheck 未返回完整 IM 身份(imUid=${uid || '-'}, imCid=${cid || '-'}),请确认登录态后重试`);
|
|
202
|
+
}
|
|
203
|
+
selfIdentity = { uid, cid, name };
|
|
204
|
+
return selfIdentity;
|
|
205
|
+
}
|
|
206
|
+
/** 文件两段式上传:preUploadCheck → valid/upload。 */
|
|
207
|
+
async function uploadFile(filePath, options = {}) {
|
|
208
|
+
const abs = resolve(filePath);
|
|
209
|
+
if (!existsSync(abs))
|
|
210
|
+
throw ymbValidationError('file_not_found', `文件不存在: ${abs}`);
|
|
211
|
+
const st = await stat(abs);
|
|
212
|
+
if (!st.isFile())
|
|
213
|
+
throw ymbValidationError('file_invalid', `不是文件: ${abs}`);
|
|
214
|
+
const name = options.name || filePath.split(/[\\/]/).pop() || 'file';
|
|
215
|
+
const size = st.size;
|
|
216
|
+
const md5 = await md5File(abs);
|
|
217
|
+
const data = await readFile(abs);
|
|
218
|
+
const mime = guessMime(name);
|
|
219
|
+
const permission = options.permission !== undefined ? String(options.permission) : '3';
|
|
220
|
+
const shareUsers = options.shareUsers !== undefined ? String(options.shareUsers) : '';
|
|
221
|
+
const shareGroups = options.shareGroups !== undefined ? String(options.shareGroups) : '';
|
|
222
|
+
const lastModified = Math.round(st.mtimeMs);
|
|
223
|
+
const base = sessionContext?.baseUrl || (await getSession()).baseUrl;
|
|
224
|
+
const uploadHeaders = { devtype: '5', langtype: 'zh_CN', timezoneoffset: String(new Date().getTimezoneOffset()) };
|
|
225
|
+
// 第 1 步:预检
|
|
226
|
+
const preForm = new FormData();
|
|
227
|
+
const preFields = {
|
|
228
|
+
module: 'im',
|
|
229
|
+
size: String(size),
|
|
230
|
+
name,
|
|
231
|
+
lastModified: String(lastModified),
|
|
232
|
+
lastModifiedDate: String(lastModified),
|
|
233
|
+
MD5: md5,
|
|
234
|
+
permission,
|
|
235
|
+
shareUsers,
|
|
236
|
+
shareGroups,
|
|
237
|
+
shareExternals: '',
|
|
238
|
+
};
|
|
239
|
+
for (const [k, v] of Object.entries(preFields))
|
|
240
|
+
preForm.append(k, v);
|
|
241
|
+
const preRes = await request({
|
|
242
|
+
method: 'POST',
|
|
243
|
+
path: '/api/file/preUploadCheck',
|
|
244
|
+
multipart: preForm,
|
|
245
|
+
headers: uploadHeaders,
|
|
246
|
+
operation: 'yimiaoban.file.upload.check',
|
|
247
|
+
timeoutMs: 15_000,
|
|
248
|
+
});
|
|
249
|
+
const preData = preRes?.data || {};
|
|
250
|
+
if (!preData.preUploadId) {
|
|
251
|
+
throw new YmbCliError('api', 'upload_check_failed', `preUploadCheck 失败`, { details: preRes, exitCode: 1 });
|
|
252
|
+
}
|
|
253
|
+
const preUploadId = String(preData.preUploadId);
|
|
254
|
+
const uploadType = preData.uploadType || 'common';
|
|
255
|
+
// 第 2 步:实传
|
|
256
|
+
const upForm = new FormData();
|
|
257
|
+
upForm.append('data', new Blob([data], { type: mime }), name);
|
|
258
|
+
for (const [k, v] of Object.entries({ module: 'im', permission, shareUsers, shareGroups, shareExternals: '', uploadType, preUploadId, MD5: md5 })) {
|
|
259
|
+
upForm.append(k, v);
|
|
260
|
+
}
|
|
261
|
+
const upRes = await request({
|
|
262
|
+
method: 'POST',
|
|
263
|
+
path: '/api/file/valid/upload',
|
|
264
|
+
multipart: upForm,
|
|
265
|
+
headers: uploadHeaders,
|
|
266
|
+
operation: 'yimiaoban.file.upload.do',
|
|
267
|
+
timeoutMs: 60_000,
|
|
268
|
+
});
|
|
269
|
+
const upData = upRes?.data || {};
|
|
270
|
+
if (!upData.fileObj || (!upData.fileObj.id && !upData.fileObj.fileid)) {
|
|
271
|
+
throw new YmbCliError('api', 'upload_failed', `valid/upload 失败`, { details: upRes, exitCode: 1 });
|
|
272
|
+
}
|
|
273
|
+
return upData.fileObj;
|
|
274
|
+
}
|
|
275
|
+
/** hrm getEmployeeByIds:uid 列表批量(一次最多 100,自动分批)。 */
|
|
276
|
+
async function resolveUsersByIds(ids) {
|
|
277
|
+
const unique = [...new Set(ids.map((x) => String(x)).filter(Boolean))];
|
|
278
|
+
const miss = unique.filter((id) => !personCache.has(id));
|
|
279
|
+
const result = {};
|
|
280
|
+
for (const id of unique) {
|
|
281
|
+
const hit = personCache.get(id);
|
|
282
|
+
if (hit)
|
|
283
|
+
result[id] = hit;
|
|
284
|
+
}
|
|
285
|
+
for (let i = 0; i < miss.length; i += 100) {
|
|
286
|
+
const batch = miss.slice(i, i + 100);
|
|
287
|
+
const res = await request({
|
|
288
|
+
method: 'POST',
|
|
289
|
+
path: '/api/hrm/common/getEmployeeByIds',
|
|
290
|
+
body: { userIdList: batch, externalIdList: [] },
|
|
291
|
+
headers: { devtype: '5' },
|
|
292
|
+
operation: 'yimiaoban.person.resolveByIds',
|
|
293
|
+
timeoutMs: 15_000,
|
|
294
|
+
});
|
|
295
|
+
const list = res?.data?.userList;
|
|
296
|
+
if (Array.isArray(list)) {
|
|
297
|
+
for (const u of list) {
|
|
298
|
+
const brief = {
|
|
299
|
+
uid: String(u.id ?? ''),
|
|
300
|
+
name: String(u.name ?? ''),
|
|
301
|
+
cid: String(u.imCid ?? u.cid ?? ''),
|
|
302
|
+
jobNum: String(u.jobNum ?? u.job_num ?? ''),
|
|
303
|
+
department: String(u.department ?? ''),
|
|
304
|
+
departmentId: String(u.departmentId ?? u.department_id ?? ''),
|
|
305
|
+
subCompany: String(u.subCompany ?? u.subcompany ?? ''),
|
|
306
|
+
position: String(u.position ?? ''),
|
|
307
|
+
mobile: String(u.mobile ?? ''),
|
|
308
|
+
};
|
|
309
|
+
if (brief.uid) {
|
|
310
|
+
personCache.set(brief.uid, brief);
|
|
311
|
+
result[brief.uid] = brief;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return result;
|
|
317
|
+
}
|
|
318
|
+
/** hrm 员工浏览:按姓名/工号/手机/邮箱 模糊(in 包含)查询。 */
|
|
319
|
+
async function searchEmployeeByName(name) {
|
|
320
|
+
const baseForm = {
|
|
321
|
+
browserMultiple: true, authWecahtBook: false,
|
|
322
|
+
wxDepNeedConvertCount: 0, wxEmpNeedConvertCount: 0, wxTotalNeedConvertCount: 0,
|
|
323
|
+
};
|
|
324
|
+
const formDatas = {
|
|
325
|
+
status: [{ statusSelect: ['normal'] }],
|
|
326
|
+
subcompany: [{ subcompanyOpt: 'in' }], department: [{ departmentOpt: 'in' }],
|
|
327
|
+
role: [{ roleOpt: 'in' }], position: [{ positionOpt: 'in' }], superior: [{ superiorOpt: 'in' }],
|
|
328
|
+
};
|
|
329
|
+
if (/@/.test(name))
|
|
330
|
+
formDatas.email = [{ emailOpt: 'in', emailInput: name }];
|
|
331
|
+
else if (/^\d{11}$/.test(name))
|
|
332
|
+
formDatas.mobile = [{ mobileOpt: 'in', mobileInput: name }];
|
|
333
|
+
else if (/^\d+$/.test(name) || /^[A-Za-z][A-Za-z0-9]*\d[A-Za-z0-9]*$/.test(name))
|
|
334
|
+
formDatas.job_num = [{ job_numOpt: 'in', job_numInput: name }];
|
|
335
|
+
else
|
|
336
|
+
formDatas.username = [{ usernameOpt: 'in', usernameInput: name }];
|
|
337
|
+
const out = [];
|
|
338
|
+
let current = 1;
|
|
339
|
+
const pageSize = 200;
|
|
340
|
+
for (;;) {
|
|
341
|
+
const res = await request({
|
|
342
|
+
method: 'POST',
|
|
343
|
+
path: '/api/hrm/common/browser/data/resource',
|
|
344
|
+
body: { ...baseForm, cmd: 'all', formDatas, quickSearchDatas: {}, type: 'and', current, pageSize, containsNoneAccount: false, containsPartTimeEmployee: false, containsDls: false },
|
|
345
|
+
operation: 'yimiaoban.person.search',
|
|
346
|
+
timeoutMs: 15_000,
|
|
347
|
+
});
|
|
348
|
+
const rows = res?.data?.data;
|
|
349
|
+
if (!Array.isArray(rows))
|
|
350
|
+
break;
|
|
351
|
+
for (const r of rows) {
|
|
352
|
+
if (!r?.name)
|
|
353
|
+
continue;
|
|
354
|
+
const brief = {
|
|
355
|
+
uid: String(r.id ?? ''),
|
|
356
|
+
name: String(r.name),
|
|
357
|
+
cid: String(r.imCid ?? r.cid ?? ''),
|
|
358
|
+
jobNum: String(r.jobNum ?? r.job_num ?? ''),
|
|
359
|
+
department: String(r.department ?? ''),
|
|
360
|
+
departmentId: String(r.departmentId ?? r.department_id ?? ''),
|
|
361
|
+
subCompany: String(r.subCompany ?? r.subcompany ?? ''),
|
|
362
|
+
position: String(r.position ?? ''),
|
|
363
|
+
mobile: String(r.mobile ?? ''),
|
|
364
|
+
};
|
|
365
|
+
if (brief.uid)
|
|
366
|
+
personCache.set(brief.uid, brief);
|
|
367
|
+
out.push(brief);
|
|
368
|
+
}
|
|
369
|
+
if (rows.length < pageSize)
|
|
370
|
+
break;
|
|
371
|
+
current++;
|
|
372
|
+
if (current > 5)
|
|
373
|
+
break; // 防超长翻页(服务端通常一次返回全部命中)
|
|
374
|
+
}
|
|
375
|
+
return out;
|
|
376
|
+
}
|
|
377
|
+
/** i18n 标签翻译:POST body 为 id 字符串数组。 */
|
|
378
|
+
async function fetchI18nLabels(ids) {
|
|
379
|
+
const result = {};
|
|
380
|
+
const unique = [...new Set(ids.map((x) => String(x)).filter((x) => /^\d+$/.test(x)))];
|
|
381
|
+
const miss = unique.filter((id) => !i18nCache.has(id));
|
|
382
|
+
for (const id of unique) {
|
|
383
|
+
const hit = i18nCache.get(id);
|
|
384
|
+
if (hit !== undefined)
|
|
385
|
+
result[id] = hit;
|
|
386
|
+
}
|
|
387
|
+
if (miss.length === 0)
|
|
388
|
+
return result;
|
|
389
|
+
try {
|
|
390
|
+
const res = await request({
|
|
391
|
+
method: 'POST',
|
|
392
|
+
path: '/api/bs/i18n/labelmanage/getHtmlLabelNames',
|
|
393
|
+
body: miss,
|
|
394
|
+
operation: 'yimiaoban.i18n.labels',
|
|
395
|
+
timeoutMs: 15_000,
|
|
396
|
+
});
|
|
397
|
+
const data = res?.data || {};
|
|
398
|
+
for (const id of miss) {
|
|
399
|
+
if (data[id] !== undefined && data[id] !== null) {
|
|
400
|
+
const text = String(data[id]);
|
|
401
|
+
i18nCache.set(id, text);
|
|
402
|
+
result[id] = text;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
catch {
|
|
407
|
+
// 翻译失败不抛错,调用方回退原样
|
|
408
|
+
}
|
|
409
|
+
return result;
|
|
410
|
+
}
|
|
411
|
+
/** 构造媒体(图片/视频)预览 URL(绝对地址;调用前确保会话就绪,避免返回相对路径)。 */
|
|
412
|
+
async function buildPreviewUrl(options) {
|
|
413
|
+
await ensureSession();
|
|
414
|
+
const kind = options.kind === 'video' ? 'video' : 'img';
|
|
415
|
+
const payload = { tag: kind === 'video' ? '1' : '0', msgid: String(options.msgid) };
|
|
416
|
+
if (options.groupId !== undefined && options.groupId !== null && String(options.groupId) !== '') {
|
|
417
|
+
payload.flag = 1;
|
|
418
|
+
payload.group_id = String(options.groupId);
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
payload.flag = 0;
|
|
422
|
+
payload.user = { cid: String(options.user?.cid ?? ''), uid: String(options.user?.uid ?? '') };
|
|
423
|
+
}
|
|
424
|
+
const weappAuthStr = Buffer.from(JSON.stringify(payload), 'utf-8').toString('base64');
|
|
425
|
+
const params = new URLSearchParams();
|
|
426
|
+
params.set('fileId', String(options.fileId));
|
|
427
|
+
params.set('type', kind === 'video' ? 'video' : 'imgs');
|
|
428
|
+
if (kind !== 'video')
|
|
429
|
+
params.set('imgFormat', options.imgFormat || 'small');
|
|
430
|
+
params.set('authModule', 'im');
|
|
431
|
+
params.set('weappAuthStr', weappAuthStr);
|
|
432
|
+
return `${(sessionContext?.baseUrl || '').replace(/\/+$/, '')}/api/file/preview?${params.toString()}`;
|
|
433
|
+
}
|
|
434
|
+
/** 构造媒体下载 URL(/api/file/remotedownload/{fileId}/im/true;绝对地址)。 */
|
|
435
|
+
async function buildDownloadUrl(options) {
|
|
436
|
+
await ensureSession();
|
|
437
|
+
const kind = options.kind === 'video' ? 'video' : 'img';
|
|
438
|
+
const payload = { tag: kind === 'video' ? '1' : '0', msgid: String(options.msgid) };
|
|
439
|
+
if (options.groupId !== undefined && options.groupId !== null && String(options.groupId) !== '') {
|
|
440
|
+
payload.flag = 1;
|
|
441
|
+
payload.group_id = String(options.groupId);
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
payload.flag = 0;
|
|
445
|
+
payload.user = { cid: String(options.user?.cid ?? ''), uid: String(options.user?.uid ?? '') };
|
|
446
|
+
}
|
|
447
|
+
const weappAuthStr = Buffer.from(JSON.stringify(payload), 'utf-8').toString('base64');
|
|
448
|
+
const params = new URLSearchParams();
|
|
449
|
+
params.set('type', kind === 'video' ? 'video' : 'imgs');
|
|
450
|
+
if (kind !== 'video')
|
|
451
|
+
params.set('imgFormat', options.imgFormat || 'small');
|
|
452
|
+
params.set('authModule', 'im');
|
|
453
|
+
params.set('weappAuthStr', weappAuthStr);
|
|
454
|
+
return `${(sessionContext?.baseUrl || '').replace(/\/+$/, '')}/api/file/remotedownload/${String(options.fileId)}/im/true?${params.toString()}`;
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
request,
|
|
458
|
+
requestBinary,
|
|
459
|
+
getSession,
|
|
460
|
+
getSelfIdentity,
|
|
461
|
+
uploadFile,
|
|
462
|
+
resolveUsersByIds,
|
|
463
|
+
searchEmployeeByName,
|
|
464
|
+
fetchI18nLabels,
|
|
465
|
+
buildPreviewUrl,
|
|
466
|
+
buildDownloadUrl,
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
export { mkdir as ensureDir, dirname, writeFile };
|