weaver-work-cli 0.1.6 → 0.1.7
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 +14 -9
- package/dist/cmd/skills/index.js +60 -0
- package/dist/internal/e10/auth/commands.js +44 -31
- package/dist/internal/e10/auth/session.js +44 -3
- package/dist/internal/e10/auth/xiaoe.js +363 -11
- package/dist/internal/e10/context.js +4 -2
- package/dist/internal/skills/detector.js +42 -0
- package/dist/internal/skills/install.js +50 -3
- package/dist/shortcuts/archive/render/search-format.js +12 -3
- package/dist/shortcuts/ebuilder-form/continuation.js +70 -0
- package/dist/shortcuts/ebuilder-form/errors.js +54 -0
- package/dist/shortcuts/ebuilder-form/host.js +1238 -0
- package/dist/shortcuts/ebuilder-form/index.js +108 -0
- package/dist/shortcuts/ebuilder-form/manifest.js +347 -0
- package/dist/shortcuts/ebuilder-form/operations/read.js +157 -0
- package/dist/shortcuts/ebuilder-form/operations/registry.js +56 -0
- package/dist/shortcuts/ebuilder-form/operations/shared.js +132 -0
- package/dist/shortcuts/ebuilder-form/operations/types.js +19 -0
- package/dist/shortcuts/ebuilder-form/operations/write.js +277 -0
- package/dist/shortcuts/ebuilder-form/operations.js +8 -0
- package/dist/shortcuts/ehr/operations/salary.js +2 -140
- package/dist/shortcuts/im/continuation.js +70 -0
- package/dist/shortcuts/{yimiaoban → im}/errors.js +14 -14
- package/dist/shortcuts/{yimiaoban → im}/host.js +26 -26
- package/dist/shortcuts/{yimiaoban → im}/index.js +42 -42
- package/dist/shortcuts/{yimiaoban → im}/manifest.js +7 -6
- package/dist/shortcuts/{yimiaoban → im}/operations/file-user.js +54 -41
- package/dist/shortcuts/{yimiaoban → im}/operations/group-read.js +46 -44
- package/dist/shortcuts/{yimiaoban → im}/operations/group-write.js +113 -66
- package/dist/shortcuts/{yimiaoban → im}/operations/msg-sync.js +164 -141
- package/dist/shortcuts/{yimiaoban → im}/operations/msg-write.js +47 -47
- package/dist/shortcuts/{yimiaoban → im}/operations/person.js +10 -10
- package/dist/shortcuts/{yimiaoban → im}/operations/registry.js +10 -8
- package/dist/shortcuts/{yimiaoban → im}/operations/session.js +82 -83
- package/dist/shortcuts/im/operations/shared.js +442 -0
- package/dist/shortcuts/{yimiaoban → im}/operations/types.js +1 -1
- package/dist/shortcuts/im/operations/user-remind.js +201 -0
- package/dist/shortcuts/{yimiaoban → im}/operations/write-util.js +7 -7
- package/dist/shortcuts/{yimiaoban → im}/operations.js +3 -3
- package/dist/shortcuts/index.js +10 -2
- package/dist/shortcuts/invoice/manifest.js +113 -10
- package/dist/shortcuts/invoice/operations/import.js +39 -14
- package/dist/shortcuts/invoice/operations/reim.js +38 -18
- package/dist/shortcuts/invoice/operations/shared.js +35 -6
- package/dist/shortcuts/jiuchuanhui/manifest.js +3 -3
- package/dist/shortcuts/jiuchuanhui/operations/registry.js +2 -2
- package/dist/shortcuts/{yimiaoban → okr}/continuation.js +10 -10
- package/dist/shortcuts/okr/errors.js +70 -0
- package/dist/shortcuts/okr/host.js +169 -0
- package/dist/shortcuts/okr/index.js +155 -0
- package/dist/shortcuts/okr/manifest.js +70 -0
- package/dist/shortcuts/okr/operations/link.js +305 -0
- package/dist/shortcuts/okr/operations/read.js +481 -0
- package/dist/shortcuts/okr/operations/registry.js +31 -0
- package/dist/shortcuts/okr/operations/shared.js +381 -0
- package/dist/shortcuts/okr/operations/types.js +88 -0
- package/dist/shortcuts/okr/operations/write.js +838 -0
- package/dist/shortcuts/okr/operations.js +8 -0
- package/dist/shortcuts/project/continuation.js +70 -0
- package/dist/shortcuts/project/errors.js +53 -0
- package/dist/shortcuts/project/host.js +160 -0
- package/dist/shortcuts/project/index.js +106 -0
- package/dist/shortcuts/project/manifest.js +36 -0
- package/dist/shortcuts/project/operations/read.js +551 -0
- package/dist/shortcuts/project/operations/registry.js +31 -0
- package/dist/shortcuts/project/operations/shared.js +68 -0
- package/dist/shortcuts/project/operations/types.js +39 -0
- package/dist/shortcuts/project/operations/write.js +266 -0
- package/dist/shortcuts/project/operations.js +8 -0
- package/dist/shortcuts/workflow/continuation.js +508 -0
- package/dist/shortcuts/workflow/endpoints.js +149 -0
- package/dist/shortcuts/workflow/errors.js +112 -0
- package/dist/shortcuts/workflow/host.js +224 -0
- package/dist/shortcuts/workflow/index.js +116 -0
- package/dist/shortcuts/workflow/manifest.js +30 -0
- package/dist/shortcuts/workflow/operations/form.js +4159 -0
- package/dist/shortcuts/workflow/operations/read.js +3778 -0
- package/dist/shortcuts/workflow/operations/registry.js +33 -0
- package/dist/shortcuts/workflow/operations/shared.js +167 -0
- package/dist/shortcuts/workflow/operations/types.js +44 -0
- package/dist/shortcuts/workflow/operations/write.js +1991 -0
- package/dist/shortcuts/workflow/operations.js +119 -0
- package/dist/shortcuts/workflow/state.js +182 -0
- package/docs/SKILL.md +5 -3
- package/docs/_catalog.md +10 -1
- package/docs/agent-invoice.md +2 -2
- package/docs/agent-skill-install.md +2 -2
- package/docs/e10-auth.md +3 -1
- package/docs/ebuilder-form.md +38 -0
- package/docs/im.md +104 -0
- package/docs/invoice.md +5 -5
- package/docs/okr.md +122 -0
- package/docs/operation-manual.md +10 -7
- package/docs/project.md +69 -0
- package/docs/skill-review-2026-09-19.md +68 -0
- package/package.json +3 -2
- package/scripts/package-skill.mjs +38 -11
- package/scripts/sync-connector-skills.mjs +172 -0
- package/skill-template/business-info.json +357 -16
- package/skill-template/domains/ebuilder-form.md +5 -0
- package/skill-template/domains/okr.md +18 -0
- package/skill-template/domains/shijingran.md +3 -0
- package/skill-template/domains/skill-maker.md +4 -4
- package/skill-template/domains/workflow.md +22 -0
- package/skill-template/domains/yepiaotong.md +4 -2
- package/skill-template/domains/yimiaoban.md +13 -10
- package/skill-template/product.json +5 -0
- package/skill-template/skill-template.md +1 -0
- package/skills/weaver-e10-calendar/SKILL.md +5 -1
- package/skills/weaver-e10-calendar/references/calendar-query.md +7 -0
- package/skills/weaver-e10-calendar/references/source-manifest.json +314 -32
- package/skills/weaver-e10-ebuilder-form/SKILL.md +123 -0
- package/skills/weaver-e10-ebuilder-form/references/context-routing.md +90 -0
- package/skills/weaver-e10-ebuilder-form/references/custom-api.md +86 -0
- package/skills/weaver-e10-ebuilder-form/references/disabled-capabilities.md +50 -0
- package/skills/weaver-e10-ebuilder-form/references/fields-browser.md +100 -0
- package/skills/weaver-e10-ebuilder-form/references/list-nlist.md +91 -0
- package/skills/weaver-e10-ebuilder-form/references/openapi-read.md +74 -0
- package/skills/weaver-e10-ebuilder-form/references/openapi-write.md +86 -0
- package/skills/weaver-e10-ebuilder-form/references/source-manifest.json +107 -0
- package/skills/weaver-e10-esb/SKILL.md +1 -1
- package/skills/weaver-e10-esb/references/source-manifest.json +15 -14
- package/skills/weaver-e10-hrm/SKILL.md +1 -1
- package/skills/weaver-e10-hrm/references/source-manifest.json +357 -38
- package/skills/weaver-e10-jiuchuanhui/SKILL.md +22 -4
- package/skills/weaver-e10-jiuchuanhui/references/clue.md +4 -2
- package/skills/weaver-e10-jiuchuanhui/references/contact.md +1 -1
- package/skills/weaver-e10-jiuchuanhui/references/customer.md +3 -3
- package/skills/weaver-e10-jiuchuanhui/references/disabled-capabilities.md +1 -1
- package/skills/weaver-e10-jiuchuanhui/references/discovery-config.md +10 -10
- package/skills/weaver-e10-jiuchuanhui/references/general-helpers.md +3 -3
- package/skills/weaver-e10-jiuchuanhui/references/sale.md +7 -7
- package/skills/weaver-e10-jiuchuanhui/references/source-manifest.json +3 -3
- package/skills/weaver-e10-jucailin/SKILL.md +2 -2
- package/skills/weaver-e10-mail/SKILL.md +22 -6
- package/skills/weaver-e10-meeting/SKILL.md +22 -1
- package/skills/weaver-e10-okr/SKILL.md +139 -0
- package/skills/weaver-e10-okr/references/okr-align.md +59 -0
- package/skills/weaver-e10-okr/references/okr-keyresult.md +68 -0
- package/skills/weaver-e10-okr/references/okr-link-routing.md +67 -0
- package/skills/weaver-e10-okr/references/okr-query.md +105 -0
- package/skills/weaver-e10-okr/references/okr-write.md +94 -0
- package/skills/weaver-e10-okr/references/source-manifest.json +672 -0
- package/skills/weaver-e10-plan/SKILL.md +3 -3
- package/skills/weaver-e10-plan/references/plan-report-write.md +1 -1
- package/skills/weaver-e10-plan/references/source-manifest.json +20 -20
- package/skills/weaver-e10-qiyecheng/SKILL.md +21 -1
- package/skills/weaver-e10-qiyecheng/references/source-manifest.json +17 -17
- package/skills/weaver-e10-shared/SKILL.md +79 -11
- package/skills/weaver-e10-shared/references/e10-auth-and-session.md +41 -8
- package/skills/weaver-e10-shared/references/non-interactive-environment.md +78 -0
- package/skills/weaver-e10-shared/references/weaver-e10-installation.md +39 -0
- package/skills/weaver-e10-shijingran/SKILL.md +83 -0
- package/skills/weaver-e10-shijingran/references/field-resolution.md +105 -0
- package/skills/weaver-e10-shijingran/references/operations.md +154 -0
- package/skills/weaver-e10-shijingran/references/source-manifest.json +1380 -0
- package/skills/weaver-e10-shijingran/references/write-operations.md +90 -0
- package/skills/weaver-e10-skill-maker/SKILL.md +26 -9
- package/skills/weaver-e10-skill-maker/references/business-skill-generation.md +39 -8
- package/skills/weaver-e10-skill-maker/references/detector-validation.md +25 -8
- package/skills/weaver-e10-skill-maker/references/module-cli-generation.md +66 -5
- package/skills/weaver-e10-skill-maker/references/module-gap-audit-and-repair.md +86 -0
- package/skills/weaver-e10-skill-maker/references/source-update-detection.md +59 -5
- package/skills/weaver-e10-skill-maker/references/weaver-skill-style.md +16 -8
- package/skills/weaver-e10-wenshuding/SKILL.md +28 -4
- package/skills/weaver-e10-wenshuding/references/archive-search.md +14 -0
- package/skills/weaver-e10-wenshuding/references/source-manifest.json +27 -26
- package/skills/weaver-e10-workflow/SKILL.md +250 -0
- package/skills/weaver-e10-workflow/references/source-manifest.json +1062 -0
- package/skills/weaver-e10-workflow/references/workflow-agent-entry.md +96 -0
- package/skills/weaver-e10-workflow/references/workflow-batch.md +275 -0
- package/skills/weaver-e10-workflow/references/workflow-create.md +182 -0
- package/skills/weaver-e10-workflow/references/workflow-edit.md +106 -0
- package/skills/weaver-e10-workflow/references/workflow-memory.md +165 -0
- package/skills/weaver-e10-workflow/references/workflow-operate.md +153 -0
- package/skills/weaver-e10-workflow/references/workflow-query.md +821 -0
- package/skills/weaver-e10-workflow/references/workflow-share.md +117 -0
- package/skills/weaver-e10-workflow/references/workflow-view.md +189 -0
- package/skills/weaver-e10-yepiaotong/SKILL.md +28 -28
- package/skills/weaver-e10-yepiaotong/references/invoice-disabled-capabilities.md +11 -5
- package/skills/weaver-e10-yepiaotong/references/invoice-import.md +14 -8
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing-examples.md +9 -43
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing.md +8 -0
- package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +13 -17
- package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +6 -7
- package/skills/weaver-e10-yepiaotong/references/reim-examples.md +2 -2
- package/skills/weaver-e10-yepiaotong/references/source-manifest.json +1155 -553
- package/skills/weaver-e10-yimiaoban/SKILL.md +56 -38
- package/skills/weaver-e10-yimiaoban/references/ding-write.md +8 -8
- package/skills/weaver-e10-yimiaoban/references/error-codes.md +16 -0
- package/skills/weaver-e10-yimiaoban/references/field-resolution.md +10 -10
- package/skills/weaver-e10-yimiaoban/references/file-user-i18n.md +12 -12
- package/skills/weaver-e10-yimiaoban/references/group-read.md +16 -16
- package/skills/weaver-e10-yimiaoban/references/group-write.md +17 -15
- package/skills/weaver-e10-yimiaoban/references/msg-read.md +21 -17
- package/skills/weaver-e10-yimiaoban/references/msg-write.md +10 -10
- package/skills/weaver-e10-yimiaoban/references/person.md +6 -6
- package/skills/weaver-e10-yimiaoban/references/safety-boundaries.md +15 -11
- package/skills/weaver-e10-yimiaoban/references/session-sysmsg.md +11 -10
- package/skills/weaver-e10-yimiaoban/references/source-manifest.json +2164 -154
- package/skills/weaver-e10-yimiaoban/references/user-remind.md +75 -0
- package/skills/weaver-e10-ziguanjia/SKILL.md +4 -2
- package/skills/weaver-e10-ziguanjia/references/source-manifest.json +99 -29
- package/tools/weaver-skill-detector-1.0.13/SKILL.md +253 -0
- package/tools/weaver-skill-detector-1.0.13/_meta.json +6 -0
- package/tools/weaver-skill-detector-1.0.13/references/checklist.md +298 -0
- package/tools/weaver-skill-detector-1.0.13/scripts/validate_skill.py +4190 -0
- package/dist/shortcuts/yimiaoban/operations/shared.js +0 -236
- package/docs/yimiaoban.md +0 -86
- package/skills/weaver-e10-calendar/product.json +0 -8
- package/skills/weaver-e10-esb/product.json +0 -8
- package/skills/weaver-e10-hrm/product.json +0 -8
- package/skills/weaver-e10-jiuchuanhui/product.json +0 -8
- package/skills/weaver-e10-jucailin/product.json +0 -8
- package/skills/weaver-e10-mail/product.json +0 -8
- package/skills/weaver-e10-meeting/product.json +0 -8
- package/skills/weaver-e10-plan/product.json +0 -8
- package/skills/weaver-e10-qiyecheng/product.json +0 -8
- package/skills/weaver-e10-skill-maker/product.json +0 -8
- package/skills/weaver-e10-wenshuding/product.json +0 -8
- package/skills/weaver-e10-yepiaotong/product.json +0 -8
- package/skills/weaver-e10-yepiaotong/references/invoice-red.md +0 -261
- package/skills/weaver-e10-yimiaoban/product.json +0 -8
- package/skills/weaver-e10-ziguanjia/product.json +0 -8
- /package/dist/shortcuts/{yimiaoban → im}/operations/msg-parse.js +0 -0
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
import { imValidationError, imBusinessError } from '../errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* 分页约定(与源 skill `common.js` 一致):
|
|
4
|
+
* - `DEFAULT_PAGE_SIZE`:每页默认 50 条;
|
|
5
|
+
* - `MAX_PAGE_SIZE`:单次请求最多 50 条(`num` 超过会被截断);
|
|
6
|
+
* - `MAX_AUTO_PAGES`:有界区间自动循环翻页的页数安全上限(防止把历史一次性拉完);
|
|
7
|
+
* - `AUTO_PAGE_INTERVAL_MS`:自动翻页时相邻两次请求的间隔,避免高频打服务端。
|
|
8
|
+
*/
|
|
9
|
+
export const DEFAULT_PAGE_SIZE = 50;
|
|
10
|
+
export const MAX_PAGE_SIZE = 50;
|
|
11
|
+
export const MAX_AUTO_PAGES = 100;
|
|
12
|
+
export const AUTO_PAGE_INTERVAL_MS = 60;
|
|
13
|
+
/** 时间范围收敛上限:显式时间范围跨度超过 7 天时,收敛为该范围「最后 7 个自然日」。 */
|
|
14
|
+
export const TIME_RANGE_MAX_DAYS = 7;
|
|
15
|
+
const SECONDS_PER_DAY = 24 * 3600;
|
|
16
|
+
/**
|
|
17
|
+
* 禁止出现在输入对象中的键:
|
|
18
|
+
* - 原型污染防护:`__proto__`/`constructor`/`prototype`
|
|
19
|
+
* - 操作人红线:`user`/`sender`/`operator`(操作者由会话 eteamsid 识别,请求体一律不传)
|
|
20
|
+
*/
|
|
21
|
+
const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype', 'user', 'sender', 'operator']);
|
|
22
|
+
export function asObject(value) {
|
|
23
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
24
|
+
throw imValidationError('input_not_object', '输入必须是 JSON 对象');
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
export function assertNoForbiddenKeys(input) {
|
|
29
|
+
for (const key of Object.keys(input)) {
|
|
30
|
+
if (FORBIDDEN_KEYS.has(key)) {
|
|
31
|
+
throw imValidationError('forbidden_key', `输入包含非法字段:${key}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** 校验 uid/cid 非空且非 0(易秒办红线:uid/cid 不得为 0)。 */
|
|
36
|
+
export function assertNonZeroId(value, field) {
|
|
37
|
+
if (value === undefined || value === null || value === '') {
|
|
38
|
+
throw imValidationError('id_required', `${field} 必填`);
|
|
39
|
+
}
|
|
40
|
+
const text = String(value).trim();
|
|
41
|
+
if (!/^\d+$/.test(text) || text === '0') {
|
|
42
|
+
throw imValidationError('id_invalid', `${field} 必须是非 0 数字 ID`);
|
|
43
|
+
}
|
|
44
|
+
return text;
|
|
45
|
+
}
|
|
46
|
+
/** 拆分逗号分隔 id 列表(uint64 按字符串保留),去空。 */
|
|
47
|
+
export function splitIds(value, field) {
|
|
48
|
+
if (value === undefined || value === null || value === '') {
|
|
49
|
+
throw imValidationError('id_list_required', `${field} 必填`);
|
|
50
|
+
}
|
|
51
|
+
const list = String(value).split(',').map((s) => s.trim()).filter(Boolean);
|
|
52
|
+
if (list.length === 0) {
|
|
53
|
+
throw imValidationError('id_list_empty', `${field} 不能为空`);
|
|
54
|
+
}
|
|
55
|
+
for (const item of list) {
|
|
56
|
+
if (!/^\d+$/.test(item) || item === '0') {
|
|
57
|
+
throw imValidationError('id_invalid', `${field} 含非法 id(必须非 0 数字):${item}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return list;
|
|
61
|
+
}
|
|
62
|
+
/** 无符号整数转换(uint32 等),拒绝小数/负数。 */
|
|
63
|
+
export function toUint(value, field) {
|
|
64
|
+
if (value === undefined || value === null || value === '')
|
|
65
|
+
return undefined;
|
|
66
|
+
const numeric = Number(value);
|
|
67
|
+
if (!Number.isInteger(numeric) || numeric < 0) {
|
|
68
|
+
throw imValidationError('number_invalid', `${field} 必须是非负整数`);
|
|
69
|
+
}
|
|
70
|
+
return numeric;
|
|
71
|
+
}
|
|
72
|
+
/** 正整数转换。 */
|
|
73
|
+
export function toPositiveInt(value, field) {
|
|
74
|
+
if (value === undefined || value === null || value === '')
|
|
75
|
+
return undefined;
|
|
76
|
+
const numeric = Number(value);
|
|
77
|
+
if (!Number.isInteger(numeric) || numeric <= 0) {
|
|
78
|
+
throw imValidationError('number_invalid', `${field} 必须是正整数`);
|
|
79
|
+
}
|
|
80
|
+
return numeric;
|
|
81
|
+
}
|
|
82
|
+
/** 移除值为 undefined 的键。 */
|
|
83
|
+
export function compact(body) {
|
|
84
|
+
for (const key of Object.keys(body)) {
|
|
85
|
+
if (body[key] === undefined)
|
|
86
|
+
delete body[key];
|
|
87
|
+
}
|
|
88
|
+
return body;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 旧字段别名归一化(遵循 CLI 规范):
|
|
92
|
+
* - 只把 `aliases` 里登记的旧字段映射到 canonical 字段;
|
|
93
|
+
* - canonical 与旧字段同时出现且取值不一致 → 结构化校验错误(禁止任一方静默覆盖);
|
|
94
|
+
* - 归一化后删除旧字段,保证后续 host/请求体只看到 canonical 字段。
|
|
95
|
+
*/
|
|
96
|
+
export function normalizeAliases(input, aliases) {
|
|
97
|
+
const out = { ...input };
|
|
98
|
+
for (const [canonical, oldKeys] of Object.entries(aliases)) {
|
|
99
|
+
for (const oldKey of oldKeys) {
|
|
100
|
+
if (!(oldKey in out))
|
|
101
|
+
continue;
|
|
102
|
+
const oldValue = out[oldKey];
|
|
103
|
+
if (oldValue === undefined || oldValue === null || oldValue === '') {
|
|
104
|
+
delete out[oldKey];
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const canonicalValue = out[canonical];
|
|
108
|
+
const hasCanonical = canonicalValue !== undefined && canonicalValue !== null && canonicalValue !== '';
|
|
109
|
+
if (hasCanonical && JSON.stringify(canonicalValue) !== JSON.stringify(oldValue)) {
|
|
110
|
+
throw imValidationError('alias_conflict', `字段 ${canonical} 与旧字段 ${oldKey} 同时出现且取值不一致,请只保留一个`);
|
|
111
|
+
}
|
|
112
|
+
if (!hasCanonical)
|
|
113
|
+
out[canonical] = oldValue;
|
|
114
|
+
delete out[oldKey];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
/** 时间(unix 秒)→ 消息 id 最小/最大值(fwutils MakeMinAndMaxIdByTm)。返回字符串(超 JS 安全整数)。 */
|
|
120
|
+
export function makeMinMaxIdByTm(tmSec) {
|
|
121
|
+
if (!tmSec || tmSec <= 0)
|
|
122
|
+
return { min: '0', max: '0' };
|
|
123
|
+
const base = BigInt(Math.floor(tmSec)) * 1000000000n;
|
|
124
|
+
return { min: base.toString(), max: (base + 999999999n).toString() };
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* 解析时间参数为 unix 秒(按本地时区):
|
|
128
|
+
* - 10 位纯数字:视为 unix 秒
|
|
129
|
+
* - YYYY-MM-DD:isEnd=false → 当天 00:00:00;isEnd=true → 当天 23:59:59
|
|
130
|
+
* - YYYY-MM-DD HH:mm[:ss]
|
|
131
|
+
*/
|
|
132
|
+
export function parseTimeToSec(arg, isEnd) {
|
|
133
|
+
if (arg === undefined || arg === null || arg === '')
|
|
134
|
+
return 0;
|
|
135
|
+
const s = String(arg).trim();
|
|
136
|
+
if (/^\d{10}$/.test(s))
|
|
137
|
+
return Number(s);
|
|
138
|
+
const m = s.match(/^(\d{4})-(\d{1,2})-(\d{1,2})(?:[ T](\d{1,2}):(\d{1,2})(?::(\d{1,2}))?)?$/);
|
|
139
|
+
if (!m) {
|
|
140
|
+
throw imValidationError('time_invalid', `无法解析时间参数: ${s}(支持 unix 秒 / YYYY-MM-DD / YYYY-MM-DD HH:mm:ss)`);
|
|
141
|
+
}
|
|
142
|
+
const hh = m[4] !== undefined ? Number(m[4]) : (isEnd ? 23 : 0);
|
|
143
|
+
const mm = m[5] !== undefined ? Number(m[5]) : (isEnd ? 59 : 0);
|
|
144
|
+
const ss = m[6] !== undefined ? Number(m[6]) : (isEnd ? 59 : 0);
|
|
145
|
+
return Math.floor(new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]), hh, mm, ss).getTime() / 1000);
|
|
146
|
+
}
|
|
147
|
+
/** 当天 00:00:00 / 23:59:59 的 unix 秒(本地时区)。 */
|
|
148
|
+
export function todayRange() {
|
|
149
|
+
const now = new Date();
|
|
150
|
+
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0);
|
|
151
|
+
const end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59);
|
|
152
|
+
return { fromSec: Math.floor(start.getTime() / 1000), toSec: Math.floor(end.getTime() / 1000) };
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* 时间范围收敛(强制约定,与源 skill `clampTimeRangeTo7Days` 一致):
|
|
156
|
+
* 显式时间范围跨度 > 7 天时,**自动缩短为该范围最后 7 个自然日**(起点对齐到终点所在日 00:00:00 往前 6 天)。
|
|
157
|
+
* - 「拉一年的消息」→ 最近 7 天;「拉上个月的消息」→ 上个月最后 7 天;
|
|
158
|
+
* - 跨度 ≤ 7 天原样返回;
|
|
159
|
+
* - 只给起点 → 终点按「现在」计算;只给终点 → 起点按「终点前 7 天」计算。
|
|
160
|
+
*/
|
|
161
|
+
export function clampTimeRangeTo7Days(fromSec, toSec) {
|
|
162
|
+
let f = Number(fromSec) > 0 ? Number(fromSec) : 0;
|
|
163
|
+
let t = Number(toSec) > 0 ? Number(toSec) : 0;
|
|
164
|
+
if (f <= 0 && t <= 0) {
|
|
165
|
+
return { fromSec: f, toSec: t, shrunk: false, origFromSec: f, origToSec: t };
|
|
166
|
+
}
|
|
167
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
168
|
+
if (t <= 0)
|
|
169
|
+
t = nowSec;
|
|
170
|
+
if (f <= 0)
|
|
171
|
+
f = t - TIME_RANGE_MAX_DAYS * SECONDS_PER_DAY;
|
|
172
|
+
const maxSpan = TIME_RANGE_MAX_DAYS * SECONDS_PER_DAY;
|
|
173
|
+
if (t - f > maxSpan) {
|
|
174
|
+
const d = new Date(t * 1000);
|
|
175
|
+
const start = new Date(d.getFullYear(), d.getMonth(), d.getDate() - (TIME_RANGE_MAX_DAYS - 1), 0, 0, 0);
|
|
176
|
+
return {
|
|
177
|
+
fromSec: Math.floor(start.getTime() / 1000),
|
|
178
|
+
toSec: t,
|
|
179
|
+
shrunk: true,
|
|
180
|
+
origFromSec: f,
|
|
181
|
+
origToSec: t,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return { fromSec: f, toSec: t, shrunk: false, origFromSec: f, origToSec: t };
|
|
185
|
+
}
|
|
186
|
+
/** unix 秒 → `YYYY-MM-DD HH:mm:ss`(本地时区,用于口径与收敛提示)。 */
|
|
187
|
+
export function formatSec(sec) {
|
|
188
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
189
|
+
const d = new Date(Number(sec) * 1000);
|
|
190
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
191
|
+
}
|
|
192
|
+
/** 时间范围收敛提示文案(Agent 展示结果时必须原样转述给用户)。 */
|
|
193
|
+
export function timeRangeShrinkNotice(clamp) {
|
|
194
|
+
return `⚠️ 指定时间范围超过 ${TIME_RANGE_MAX_DAYS} 天,已自动缩短为该范围最后 ${TIME_RANGE_MAX_DAYS} 天:`
|
|
195
|
+
+ `${formatSec(clamp.origFromSec)} ~ ${formatSec(clamp.origToSec)}`
|
|
196
|
+
+ ` → ${formatSec(clamp.fromSec)} ~ ${formatSec(clamp.toSec)}`
|
|
197
|
+
+ `(如需更早历史,请分段指定 ≤ ${TIME_RANGE_MAX_DAYS} 天的时间范围)`;
|
|
198
|
+
}
|
|
199
|
+
/** 时间展示统一格式:YYYY/M/D HH:MM:SS(unix 秒输入)。 */
|
|
200
|
+
export function formatFullTime(ts) {
|
|
201
|
+
if (ts === undefined || ts === null || ts === '')
|
|
202
|
+
return '-';
|
|
203
|
+
const d = new Date(Number(ts) * 1000);
|
|
204
|
+
if (Number.isNaN(d.getTime()))
|
|
205
|
+
return '-';
|
|
206
|
+
const p = (n) => String(n).padStart(2, '0');
|
|
207
|
+
return `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
|
208
|
+
}
|
|
209
|
+
/** 校验枚举取值。 */
|
|
210
|
+
export function assertEnum(value, allowed, field) {
|
|
211
|
+
const text = typeof value === 'string' ? value : String(value);
|
|
212
|
+
if (!allowed.includes(text)) {
|
|
213
|
+
throw imValidationError('enum_invalid', `${field} 取值必须是 ${allowed.join(' / ')} 之一`);
|
|
214
|
+
}
|
|
215
|
+
return text;
|
|
216
|
+
}
|
|
217
|
+
/** 从 IM 响应 JSON 中提取 actionMsg(路径多样兼容)。 */
|
|
218
|
+
export function findActionMsg(value) {
|
|
219
|
+
const candidate = value?.data?.actionMsg ?? value?.actionMsg;
|
|
220
|
+
if (candidate === undefined || candidate === null)
|
|
221
|
+
return null;
|
|
222
|
+
return {
|
|
223
|
+
code: candidate.code !== undefined ? candidate.code : 0,
|
|
224
|
+
message: candidate.message ?? candidate.msg ?? '',
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/** 判断 IM 业务是否成功(actionMsg.code === 0)。失败抛 business_error。 */
|
|
228
|
+
export function assertImOk(value, actionLabel) {
|
|
229
|
+
const msg = findActionMsg(value);
|
|
230
|
+
if (msg === null) {
|
|
231
|
+
// 无 actionMsg 时回退顶层 code===200 && status 判定(sendDing 等路径)
|
|
232
|
+
if (Number(value?.code) === 200 && value?.status)
|
|
233
|
+
return;
|
|
234
|
+
throw imBusinessError('business_error', `${actionLabel}失败:缺少 actionMsg`, {
|
|
235
|
+
details: { raw: value },
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
if (Number(msg.code) === 0)
|
|
239
|
+
return;
|
|
240
|
+
const message = msg.message || `${actionLabel}失败(actionMsg.code=${msg.code})`;
|
|
241
|
+
throw imBusinessError('action_failed', message, { code: msg.code });
|
|
242
|
+
}
|
|
243
|
+
/** 取出响应 data 主体(兼容 data.data.data / data.data 等路径)。 */
|
|
244
|
+
export function unwrapInner(value) {
|
|
245
|
+
return value?.data?.data ?? value?.data;
|
|
246
|
+
}
|
|
247
|
+
/** 群成员条目判定(成员行至少含 uid 或 user 身份)。 */
|
|
248
|
+
function isMemberRow(value) {
|
|
249
|
+
return Boolean(value) && typeof value === 'object' && (value.uid !== undefined || value.user !== undefined);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* 从 getGroupUsers 响应主体提取成员数组。
|
|
253
|
+
* 兼容服务端历史形态:`users` / `members` / `datas`(含 `datas[].datas` 嵌套),
|
|
254
|
+
* 避免因响应包裹层不同导致「群内没有可接收成员」这类误判。
|
|
255
|
+
*/
|
|
256
|
+
export function extractGroupMembers(inner) {
|
|
257
|
+
if (!inner || typeof inner !== 'object')
|
|
258
|
+
return [];
|
|
259
|
+
for (const key of ['users', 'members']) {
|
|
260
|
+
const list = inner[key];
|
|
261
|
+
if (Array.isArray(list) && list.some(isMemberRow))
|
|
262
|
+
return list;
|
|
263
|
+
}
|
|
264
|
+
const datas = Array.isArray(inner.datas) ? inner.datas : [];
|
|
265
|
+
if (datas.some(isMemberRow))
|
|
266
|
+
return datas;
|
|
267
|
+
const nested = [];
|
|
268
|
+
for (const group of datas) {
|
|
269
|
+
if (Array.isArray(group?.datas))
|
|
270
|
+
nested.push(...group.datas);
|
|
271
|
+
}
|
|
272
|
+
return nested;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* 当前登录用户 uid(易秒办 IM uid = E10 登录 userId)。
|
|
276
|
+
* 仅用于展示层识别「我」,请求体依旧不携带任何操作人字段。
|
|
277
|
+
*/
|
|
278
|
+
export async function resolveAuthUid(host) {
|
|
279
|
+
try {
|
|
280
|
+
const session = await host.getSession();
|
|
281
|
+
return str(session?.userId);
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
return '';
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
/** 消息页最小 ser_msgid(`hasMore` 时作为下一页 `start` 锚点;服务端返回含边界那条,需按 msgid 去重)。 */
|
|
288
|
+
export function minMsgId(messages) {
|
|
289
|
+
let min = null;
|
|
290
|
+
for (const message of messages) {
|
|
291
|
+
const id = str(message?.msgid);
|
|
292
|
+
if (!/^\d+$/.test(id))
|
|
293
|
+
continue;
|
|
294
|
+
if (min === null || BigInt(id) < BigInt(min))
|
|
295
|
+
min = id;
|
|
296
|
+
}
|
|
297
|
+
return min ?? '';
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* 倒序分页拉取(与源 skill `common.js pagedPull` 一致):
|
|
301
|
+
* - `autoLoop=true`(有界区间 `end != 0`)→ 自动循环翻页直到区间内拉完再返回,受 MAX_AUTO_PAGES 页数上限保护;
|
|
302
|
+
* - `autoLoop=false`(无界区间 `end = 0`,如 `latest:true`)→ 只拉一页,未拉完时 `hasMore=true`。
|
|
303
|
+
* 翻页约定:下一页 `start` = 本页最小消息 id;服务端返回「id ≤ start」且包含边界那条,因此按消息 id 去重。
|
|
304
|
+
*/
|
|
305
|
+
export async function pullPaged(options) {
|
|
306
|
+
const maxNum = Math.min(options.num > 0 ? options.num : DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE);
|
|
307
|
+
const idOf = options.idOf ?? ((row) => str(row?.ser_msgid));
|
|
308
|
+
const endBig = /^\d+$/.test(String(options.end ?? '')) ? BigInt(String(options.end)) : 0n;
|
|
309
|
+
const rows = [];
|
|
310
|
+
const seen = new Set();
|
|
311
|
+
let curStart = options.start;
|
|
312
|
+
let pages = 0;
|
|
313
|
+
let lastStart = '';
|
|
314
|
+
let exhausted = false;
|
|
315
|
+
let hasMore = false;
|
|
316
|
+
let cappedAtMaxPages = false;
|
|
317
|
+
for (;;) {
|
|
318
|
+
const page = await options.requestOnePage(curStart, options.end, maxNum);
|
|
319
|
+
pages += 1;
|
|
320
|
+
if (!page || page.length === 0) {
|
|
321
|
+
exhausted = true;
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
let added = 0;
|
|
325
|
+
let minId = '';
|
|
326
|
+
for (const row of page) {
|
|
327
|
+
const id = idOf(row);
|
|
328
|
+
if (!/^\d+$/.test(id) || seen.has(id))
|
|
329
|
+
continue;
|
|
330
|
+
seen.add(id);
|
|
331
|
+
rows.push(row);
|
|
332
|
+
added += 1;
|
|
333
|
+
if (minId === '' || BigInt(id) < BigInt(minId))
|
|
334
|
+
minId = id;
|
|
335
|
+
}
|
|
336
|
+
if (added === 0 || minId === '') {
|
|
337
|
+
exhausted = true;
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
lastStart = minId;
|
|
341
|
+
curStart = minId;
|
|
342
|
+
if (endBig > 0n && BigInt(curStart) <= endBig) {
|
|
343
|
+
exhausted = true;
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
if (!options.autoLoop) {
|
|
347
|
+
// 单页模式:本页拉满即保守认为还有更旧消息(未拉满说明区间内已无更多)
|
|
348
|
+
if (page.length >= maxNum)
|
|
349
|
+
hasMore = endBig > 0n ? BigInt(curStart) > endBig : true;
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
if (pages >= MAX_AUTO_PAGES) {
|
|
353
|
+
hasMore = true;
|
|
354
|
+
cappedAtMaxPages = true;
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
if (AUTO_PAGE_INTERVAL_MS > 0) {
|
|
358
|
+
await new Promise((resolve) => setTimeout(resolve, AUTO_PAGE_INTERVAL_MS));
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return { rows, pages, lastStart, exhausted, hasMore, autoPaged: options.autoLoop, cappedAtMaxPages };
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* 分页对外摘要:自动/单页口径、页数、下一页锚点与去重/继续提示。
|
|
365
|
+
* `hasMore=true` 时把 `nextStart` 作为下一次调用的 `start`(保持同一 `end`/时间范围)。
|
|
366
|
+
*/
|
|
367
|
+
export function paginationMeta(result, num) {
|
|
368
|
+
return {
|
|
369
|
+
pageSize: num,
|
|
370
|
+
pages: result.pages,
|
|
371
|
+
autoPaged: result.autoPaged,
|
|
372
|
+
maxAutoPages: MAX_AUTO_PAGES,
|
|
373
|
+
exhausted: result.exhausted,
|
|
374
|
+
hasMore: result.hasMore,
|
|
375
|
+
...(result.hasMore && result.lastStart
|
|
376
|
+
? {
|
|
377
|
+
nextStart: result.lastStart,
|
|
378
|
+
resumeHint: '需要更早数据时,用相同范围再次调用并传 start=nextStart(服务端会重复返回边界那条,已按消息 id 去重)',
|
|
379
|
+
}
|
|
380
|
+
: {}),
|
|
381
|
+
...(result.cappedAtMaxPages
|
|
382
|
+
? {
|
|
383
|
+
cappedAtMaxPages: true,
|
|
384
|
+
cappedNote: `已达单次自动翻页上限 ${MAX_AUTO_PAGES} 页,区间内仍有更旧数据,用 nextStart 继续`,
|
|
385
|
+
}
|
|
386
|
+
: {}),
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
/** 时间范围收敛的对外摘要(Agent 必须把 notice 原样转述给用户)。 */
|
|
390
|
+
export function rangeShrinkMeta(clamp) {
|
|
391
|
+
return {
|
|
392
|
+
shrunk: true,
|
|
393
|
+
maxDays: TIME_RANGE_MAX_DAYS,
|
|
394
|
+
originalFrom: formatSec(clamp.origFromSec),
|
|
395
|
+
originalTo: formatSec(clamp.origToSec),
|
|
396
|
+
from: formatSec(clamp.fromSec),
|
|
397
|
+
to: formatSec(clamp.toSec),
|
|
398
|
+
notice: timeRangeShrinkNotice(clamp),
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
/** 消息按消息 id 从新到旧排序(与源 skill 展示口径一致:最新在前)。 */
|
|
402
|
+
export function sortMessagesNewestFirst(messages) {
|
|
403
|
+
return messages.sort((a, b) => {
|
|
404
|
+
const ai = str(a.msgid);
|
|
405
|
+
const bi = str(b.msgid);
|
|
406
|
+
const aOk = /^\d+$/.test(ai);
|
|
407
|
+
const bOk = /^\d+$/.test(bi);
|
|
408
|
+
if (!aOk && !bOk)
|
|
409
|
+
return 0;
|
|
410
|
+
if (!aOk)
|
|
411
|
+
return 1;
|
|
412
|
+
if (!bOk)
|
|
413
|
+
return -1;
|
|
414
|
+
return BigInt(bi) === BigInt(ai) ? 0 : (BigInt(bi) > BigInt(ai) ? 1 : -1);
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
/** 枚举值转成 string(可能为字符串数字)。 */
|
|
418
|
+
export function str(value) {
|
|
419
|
+
if (value === undefined || value === null)
|
|
420
|
+
return '';
|
|
421
|
+
return String(value);
|
|
422
|
+
}
|
|
423
|
+
/** 解析消息 JSON 字符串或对象。 */
|
|
424
|
+
export function parseMsg(value) {
|
|
425
|
+
if (!value)
|
|
426
|
+
return null;
|
|
427
|
+
if (typeof value === 'string') {
|
|
428
|
+
try {
|
|
429
|
+
return JSON.parse(value);
|
|
430
|
+
}
|
|
431
|
+
catch {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (typeof value === 'object')
|
|
436
|
+
return value;
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* 媒体文件上传所需文件大小上限(消息图片/文件常规场景按服务端约束执行)。
|
|
441
|
+
*/
|
|
442
|
+
export const MAX_UPLOAD_BYTES = 1024 * 1024 * 200;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const READ = 'read';
|
|
2
2
|
export const WRITE = 'write';
|
|
3
|
-
export function
|
|
3
|
+
export function imSuccess(action, data, meta) {
|
|
4
4
|
const payload = { ok: true, action, data };
|
|
5
5
|
if (meta && Object.keys(meta).length > 0)
|
|
6
6
|
payload.meta = meta;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { imValidationError } from '../errors.js';
|
|
2
|
+
import { schemaOf, WRITE, } from './types.js';
|
|
3
|
+
import { assertImOk, str, toUint, unwrapInner } from './shared.js';
|
|
4
|
+
import { makeWritePair } from './write-util.js';
|
|
5
|
+
/**
|
|
6
|
+
* 消息免打扰设置(`POST /api/em/msg/executeIm/user/setMsgRemind`)。
|
|
7
|
+
*
|
|
8
|
+
* 服务端契约(weaver-im-api `PathSetMsgRemind` + `MsgRemindInfo`):
|
|
9
|
+
* { "user": {cid,uid}, "data": { trans_id, dev, remind: MsgRemindInfo } }
|
|
10
|
+
*
|
|
11
|
+
* 本 operation 按会话维度设置提醒状态:
|
|
12
|
+
* 1 单聊会话 → `user`(对方 cid/uid)
|
|
13
|
+
* 2 群聊会话 → `group_id`
|
|
14
|
+
* 4 系统会话 → `group`(系统消息分组 id)
|
|
15
|
+
* `reminder`:0=消息接收提醒;1=消息接收但不提醒(免打扰)。
|
|
16
|
+
*/
|
|
17
|
+
const SESSION_TYPE_NAME = {
|
|
18
|
+
1: '单聊会话',
|
|
19
|
+
2: '群聊会话',
|
|
20
|
+
4: '系统会话',
|
|
21
|
+
};
|
|
22
|
+
const REMINDER_NAME = {
|
|
23
|
+
0: '消息接收提醒',
|
|
24
|
+
1: '消息接收但不提醒(免打扰)',
|
|
25
|
+
};
|
|
26
|
+
const USER_SESSION_TYPES = new Set([1]);
|
|
27
|
+
const GROUP_ID_SESSION_TYPES = new Set([2]);
|
|
28
|
+
/** 只读回查目标名称(失败不影响签发,仅用于摘要可读性)。 */
|
|
29
|
+
async function resolveTargetLabel(plan, env) {
|
|
30
|
+
try {
|
|
31
|
+
if (GROUP_ID_SESSION_TYPES.has(plan.sessionType) && plan.groupId) {
|
|
32
|
+
const res = await env.host.request({
|
|
33
|
+
method: 'POST',
|
|
34
|
+
path: '/api/em/msg/executeIm/group/queryGroupInfo',
|
|
35
|
+
body: { trans_id: 0, type: 0, mask: 0, group_id: [plan.groupId], flag: 0 },
|
|
36
|
+
operation: 'im.user.setRemind.plan.group',
|
|
37
|
+
});
|
|
38
|
+
const info = (unwrapInner(res)?.datas || [])[0];
|
|
39
|
+
const name = str(info?.name);
|
|
40
|
+
return name ? `群「${name}」(${plan.groupId})` : `群 ${plan.groupId}`;
|
|
41
|
+
}
|
|
42
|
+
if (USER_SESSION_TYPES.has(plan.sessionType) && plan.user) {
|
|
43
|
+
const map = await env.host.resolveUsersByIds([plan.user.uid]);
|
|
44
|
+
const name = map[plan.user.uid]?.name;
|
|
45
|
+
return name ? `与「${name}」的会话(${plan.user.uid})` : `与 ${plan.user.uid} 的会话`;
|
|
46
|
+
}
|
|
47
|
+
if (plan.sessionType === 4 && plan.group !== undefined) {
|
|
48
|
+
const res = await env.host.request({
|
|
49
|
+
method: 'POST',
|
|
50
|
+
path: '/api/em/msg/executeIm/system-msg/syncGroupType',
|
|
51
|
+
body: { gids: [plan.group], tids: [], lang: 'zh_CN', showdel: 1 },
|
|
52
|
+
operation: 'im.user.setRemind.plan.sysgroup',
|
|
53
|
+
});
|
|
54
|
+
const groupName = str((unwrapInner(res)?.groups || [])[0]?.groupName);
|
|
55
|
+
const base = groupName ? `系统消息分组「${groupName}」(${plan.group})` : `系统消息分组 ${plan.group}`;
|
|
56
|
+
return base;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// 回查失败:仅用 id 描述目标
|
|
61
|
+
}
|
|
62
|
+
if (plan.groupId)
|
|
63
|
+
return `群 ${plan.groupId}`;
|
|
64
|
+
if (plan.user)
|
|
65
|
+
return `与 ${plan.user.uid} 的会话`;
|
|
66
|
+
if (plan.group !== undefined)
|
|
67
|
+
return `系统消息分组 ${plan.group}`;
|
|
68
|
+
return '会话免打扰设置';
|
|
69
|
+
}
|
|
70
|
+
const userSetRemindPair = makeWritePair({
|
|
71
|
+
action: 'im.user.setRemind',
|
|
72
|
+
async plan(args, env) {
|
|
73
|
+
const sessionType = toUint(args.sessionType, 'sessionType');
|
|
74
|
+
if (sessionType === undefined || SESSION_TYPE_NAME[sessionType] === undefined) {
|
|
75
|
+
throw imValidationError('session_type_invalid', 'sessionType 必填且仅支持会话维度:1 单聊会话、2 群聊会话、4 系统会话');
|
|
76
|
+
}
|
|
77
|
+
const reminder = toUint(args.reminder, 'reminder');
|
|
78
|
+
if (reminder === undefined || REMINDER_NAME[reminder] === undefined) {
|
|
79
|
+
throw imValidationError('reminder_invalid', 'reminder 必填且仅支持 0 消息接收提醒 / 1 消息接收但不提醒(免打扰)');
|
|
80
|
+
}
|
|
81
|
+
const plan = { sessionType, reminder };
|
|
82
|
+
if (USER_SESSION_TYPES.has(sessionType)) {
|
|
83
|
+
const uid = str(args.toUid);
|
|
84
|
+
const cid = str(args.toCid);
|
|
85
|
+
if (!uid || !cid || uid === '0' || cid === '0') {
|
|
86
|
+
throw imValidationError('target_required', `sessionType=${sessionType}(${SESSION_TYPE_NAME[sessionType]})需要 toUid + toCid`);
|
|
87
|
+
}
|
|
88
|
+
plan.user = { cid, uid };
|
|
89
|
+
}
|
|
90
|
+
else if (GROUP_ID_SESSION_TYPES.has(sessionType)) {
|
|
91
|
+
const groupId = str(args.groupId);
|
|
92
|
+
if (!groupId || groupId === '0') {
|
|
93
|
+
throw imValidationError('target_required', `sessionType=${sessionType}(${SESSION_TYPE_NAME[sessionType]})需要 groupId`);
|
|
94
|
+
}
|
|
95
|
+
plan.groupId = groupId;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
const group = toUint(args.group, 'group');
|
|
99
|
+
if (group === undefined || group <= 0) {
|
|
100
|
+
throw imValidationError('target_required', 'sessionType=4(系统会话)需要 group(系统消息分组 id)');
|
|
101
|
+
}
|
|
102
|
+
plan.group = group;
|
|
103
|
+
}
|
|
104
|
+
const target = await resolveTargetLabel(plan, env);
|
|
105
|
+
return {
|
|
106
|
+
summary: {
|
|
107
|
+
action: '设置消息免打扰',
|
|
108
|
+
target,
|
|
109
|
+
sessionType: plan.sessionType,
|
|
110
|
+
sessionTypeName: SESSION_TYPE_NAME[plan.sessionType],
|
|
111
|
+
reminder: plan.reminder,
|
|
112
|
+
reminderName: REMINDER_NAME[plan.reminder],
|
|
113
|
+
...(plan.groupId ? { groupId: plan.groupId } : {}),
|
|
114
|
+
...(plan.user ? { toUid: plan.user.uid, toCid: plan.user.cid } : {}),
|
|
115
|
+
...(plan.group !== undefined ? { group: plan.group } : {}),
|
|
116
|
+
note: '仅修改当前登录人自己的会话提醒设置;reminder=0 接收并提醒,1 接收但不提醒(免打扰)。',
|
|
117
|
+
},
|
|
118
|
+
snapshot: plan,
|
|
119
|
+
};
|
|
120
|
+
},
|
|
121
|
+
async execute(plan, env) {
|
|
122
|
+
// 该接口在请求体里携带「修改者」,只能由 CLI 用当前登录人身份填充,调用方不可指定。
|
|
123
|
+
const self = await env.host.getSelfIdentity();
|
|
124
|
+
const remind = {
|
|
125
|
+
session_type: plan.sessionType,
|
|
126
|
+
reminder: plan.reminder,
|
|
127
|
+
};
|
|
128
|
+
if (plan.user)
|
|
129
|
+
remind.user = { cid: plan.user.cid, uid: plan.user.uid };
|
|
130
|
+
if (plan.groupId)
|
|
131
|
+
remind.group_id = plan.groupId;
|
|
132
|
+
if (plan.group !== undefined)
|
|
133
|
+
remind.group = plan.group;
|
|
134
|
+
const res = await env.host.request({
|
|
135
|
+
method: 'POST',
|
|
136
|
+
path: '/api/em/msg/executeIm/user/setMsgRemind',
|
|
137
|
+
body: {
|
|
138
|
+
user: { cid: self.cid, uid: self.uid },
|
|
139
|
+
data: { trans_id: 0, dev: 4, remind },
|
|
140
|
+
},
|
|
141
|
+
operation: 'im.user.setRemind',
|
|
142
|
+
});
|
|
143
|
+
assertImOk(res, '设置消息免打扰');
|
|
144
|
+
return {
|
|
145
|
+
sessionType: plan.sessionType,
|
|
146
|
+
sessionTypeName: SESSION_TYPE_NAME[plan.sessionType],
|
|
147
|
+
reminder: plan.reminder,
|
|
148
|
+
reminderName: REMINDER_NAME[plan.reminder],
|
|
149
|
+
...(plan.groupId ? { groupId: plan.groupId } : {}),
|
|
150
|
+
...(plan.user ? { toUid: plan.user.uid } : {}),
|
|
151
|
+
...(plan.group !== undefined ? { group: plan.group } : {}),
|
|
152
|
+
};
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
const prepareSchema = {
|
|
156
|
+
...schemaOf({
|
|
157
|
+
sessionType: {
|
|
158
|
+
type: 'integer',
|
|
159
|
+
enum: [1, 2, 4],
|
|
160
|
+
description: '会话类型(必填,仅会话维度):1 单聊会话,2 群聊会话,4 系统会话',
|
|
161
|
+
},
|
|
162
|
+
reminder: {
|
|
163
|
+
type: 'integer',
|
|
164
|
+
enum: [0, 1],
|
|
165
|
+
description: '提醒值(必填):0 消息接收提醒,1 消息接收但不提醒(免打扰)',
|
|
166
|
+
},
|
|
167
|
+
toUid: { type: 'string', pattern: '^\\d+$', description: 'sessionType=1(单聊会话)时的对方 uid' },
|
|
168
|
+
toCid: { type: 'string', pattern: '^\\d+$', description: 'sessionType=1(单聊会话)时的对方 cid' },
|
|
169
|
+
groupId: { type: 'string', pattern: '^\\d+$', description: 'sessionType=2(群聊会话)时的群 id' },
|
|
170
|
+
group: { type: 'integer', minimum: 1, description: 'sessionType=4(系统会话)时的系统消息分组 id' },
|
|
171
|
+
}, { required: ['sessionType', 'reminder'], description: '会话免打扰设置(单聊会话 / 群聊会话 / 系统会话)' }),
|
|
172
|
+
};
|
|
173
|
+
export const userRemindOperations = [
|
|
174
|
+
{
|
|
175
|
+
name: 'im.user.setRemind.prepare',
|
|
176
|
+
resource: 'user',
|
|
177
|
+
method: 'user.setRemind.prepare',
|
|
178
|
+
action: 'im.user.setRemind.prepare',
|
|
179
|
+
summary: '设置消息免打扰(prepare:校验目标 + 只读回查目标名称 + 签发确认)',
|
|
180
|
+
capability: 'user',
|
|
181
|
+
risk: 'read-before-write',
|
|
182
|
+
requiresConfirmation: true,
|
|
183
|
+
inputSchema: prepareSchema,
|
|
184
|
+
handler: userSetRemindPair.prepare,
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: 'im.user.setRemind.apply',
|
|
188
|
+
resource: 'user',
|
|
189
|
+
method: 'user.setRemind.apply',
|
|
190
|
+
action: 'im.user.setRemind.apply',
|
|
191
|
+
summary: '设置消息免打扰(apply:需 confirm:true + continuation)',
|
|
192
|
+
capability: 'user',
|
|
193
|
+
risk: WRITE,
|
|
194
|
+
requiresConfirmation: true,
|
|
195
|
+
inputSchema: schemaOf({
|
|
196
|
+
confirm: { type: 'boolean', description: '必须为 true' },
|
|
197
|
+
continuation: { type: 'string', minLength: 1 },
|
|
198
|
+
}, { required: ['confirm', 'continuation'], description: '设置消息免打扰(apply)' }),
|
|
199
|
+
handler: userSetRemindPair.apply,
|
|
200
|
+
},
|
|
201
|
+
];
|