@zhin.js/command 1.0.9 → 1.0.11
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/lib/command-index.js +6 -6
- package/lib/definition.d.ts +28 -3
- package/lib/definition.js +26 -6
- package/package.json +3 -3
- package/src/command-index.ts +13 -11
- package/src/definition.ts +57 -8
package/lib/command-index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SegmentMatcher, TypeMatcherRegistry, } from 'segment-matcher';
|
|
2
|
-
import { createCommandContext, resolveCommandSession, } from './definition.js';
|
|
2
|
+
import { createCommandContext, resolveCommandSession, resolveDynamicParams, } from './definition.js';
|
|
3
3
|
import { permissionHostToken } from '@zhin.js/permission';
|
|
4
4
|
import { toPermissionSubject } from '@zhin.js/permission';
|
|
5
5
|
const segmentFields = {
|
|
@@ -114,8 +114,8 @@ export class CommandIndex {
|
|
|
114
114
|
this.#diagnoseParameter(name);
|
|
115
115
|
throw new Error(`Unknown Command: ${name}`);
|
|
116
116
|
}
|
|
117
|
-
// Host / 无 session:跳过 permit。
|
|
118
|
-
return match.command.slot.definition.execute(createCommandContext(this.snapshot, match.command.slot.owner, args, match.params));
|
|
117
|
+
// Host / 无 session:跳过 permit;无 source 时函数默认值得到空 session。
|
|
118
|
+
return match.command.slot.definition.execute(createCommandContext(this.snapshot, match.command.slot.owner, args, resolveDynamicParams(match.params, undefined)));
|
|
119
119
|
}
|
|
120
120
|
async dispatch(input, source = undefined) {
|
|
121
121
|
const shortcut = this.#matchShortcut(input);
|
|
@@ -123,7 +123,7 @@ export class CommandIndex {
|
|
|
123
123
|
if (!(await this.#permitAllows(shortcut.record, source))) {
|
|
124
124
|
return Object.freeze({ matched: false });
|
|
125
125
|
}
|
|
126
|
-
const value = await shortcut.record.slot.definition.execute(createCommandContext(this.snapshot, shortcut.record.slot.owner, Object.freeze([]), shortcut.params, source, Object.freeze([])));
|
|
126
|
+
const value = await shortcut.record.slot.definition.execute(createCommandContext(this.snapshot, shortcut.record.slot.owner, Object.freeze([]), resolveDynamicParams(shortcut.params, source), source, Object.freeze([])));
|
|
127
127
|
return Object.freeze({
|
|
128
128
|
matched: true,
|
|
129
129
|
command: shortcut.record.name,
|
|
@@ -138,7 +138,7 @@ export class CommandIndex {
|
|
|
138
138
|
return Object.freeze({ matched: false });
|
|
139
139
|
}
|
|
140
140
|
const args = textArgs(match.remaining);
|
|
141
|
-
const value = await match.command.slot.definition.execute(createCommandContext(this.snapshot, match.command.slot.owner, args, match.params, source, match.remaining));
|
|
141
|
+
const value = await match.command.slot.definition.execute(createCommandContext(this.snapshot, match.command.slot.owner, args, resolveDynamicParams(match.params, source), source, match.remaining));
|
|
142
142
|
return Object.freeze({
|
|
143
143
|
matched: true,
|
|
144
144
|
command: match.command.name,
|
|
@@ -370,7 +370,7 @@ function matcherPattern(segments, parameter) {
|
|
|
370
370
|
return `<${parameter.name}:${type}>`;
|
|
371
371
|
return parameter.defaultValue === undefined
|
|
372
372
|
? `[${parameter.name}:${type}]`
|
|
373
|
-
: `[${parameter.name}:${type}=${String(parameter.defaultValue)}]`;
|
|
373
|
+
: `[${parameter.name}:${type}=${typeof parameter.defaultValue === 'function' ? '<dynamic>' : String(parameter.defaultValue)}]`;
|
|
374
374
|
}).join(' ');
|
|
375
375
|
}
|
|
376
376
|
function matcherType(type) {
|
package/lib/definition.d.ts
CHANGED
|
@@ -3,6 +3,26 @@ import { type CapabilityContext } from '@zhin.js/feature-kit';
|
|
|
3
3
|
declare const commandBrand: "zhin.command/1";
|
|
4
4
|
export type CommandParameterType = 'string' | 'number' | 'integer' | 'float' | 'boolean' | 'word' | 'text' | 'mention' | 'image' | 'face' | 'reply' | 'forward' | 'dice' | 'rps';
|
|
5
5
|
export type CommandParameterValue = string | number | boolean | ReadonlyArray<string | number | boolean> | Readonly<Record<string, unknown>> | null;
|
|
6
|
+
/**
|
|
7
|
+
* 可从运行时会话上下文动态解析的参数值。
|
|
8
|
+
*
|
|
9
|
+
* 静态值直接使用;函数值在命令派发时接收 {@link CommandSession},
|
|
10
|
+
* 返回最终的 {@link CommandParameterValue}。适用于 shortcut 预填
|
|
11
|
+
* 和 params.default。
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* defineCommand({
|
|
15
|
+
* params: {
|
|
16
|
+
* user_id: { type: 'string', default: (s) => s.sender?.id ?? '' },
|
|
17
|
+
* },
|
|
18
|
+
* shortcut: {
|
|
19
|
+
* '查看我的信息': { user_id: (s) => s.sender?.id ?? '' },
|
|
20
|
+
* },
|
|
21
|
+
* execute: ({ params }) => `profile:${params.user_id}`,
|
|
22
|
+
* })
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export type CommandDynamicValue = CommandParameterValue | ((session: CommandSession) => CommandParameterValue);
|
|
6
26
|
export declare const commandParameterTypes: ReadonlySet<CommandParameterType>;
|
|
7
27
|
/**
|
|
8
28
|
* Next.js 风格参数声明(`defineCommand({ params: ... })`)。
|
|
@@ -11,7 +31,7 @@ export declare const commandParameterTypes: ReadonlySet<CommandParameterType>;
|
|
|
11
31
|
*/
|
|
12
32
|
export interface CommandParamSchema {
|
|
13
33
|
readonly type: CommandParameterType;
|
|
14
|
-
readonly default?:
|
|
34
|
+
readonly default?: CommandDynamicValue;
|
|
15
35
|
readonly description?: string;
|
|
16
36
|
}
|
|
17
37
|
/** Minimal structural contract shared with canonical IM segments. */
|
|
@@ -24,7 +44,7 @@ export interface CommandSegment {
|
|
|
24
44
|
export interface CommandParameterDefinition {
|
|
25
45
|
readonly name: string;
|
|
26
46
|
readonly type: CommandParameterType;
|
|
27
|
-
readonly defaultValue?:
|
|
47
|
+
readonly defaultValue?: CommandDynamicValue;
|
|
28
48
|
/** `[[name]]` / `[[...name]]` 可选段;缺省按 `defaultValue === undefined` 推断。 */
|
|
29
49
|
readonly optional?: boolean;
|
|
30
50
|
/** `[...name]` / `[[...name]]` 捕获所有段,运行时值为 `string[]`。 */
|
|
@@ -157,7 +177,7 @@ export interface CommandDefinition<TConfig = unknown, TResult = unknown, TInput
|
|
|
157
177
|
* 全局整句快捷方式:触发串(trim 后全文相等)→ 预填 params。
|
|
158
178
|
* 可打破 owner 命名空间。
|
|
159
179
|
*/
|
|
160
|
-
readonly shortcut?: Readonly<Record<string, Readonly<Record<string,
|
|
180
|
+
readonly shortcut?: Readonly<Record<string, Readonly<Record<string, CommandDynamicValue>>>>;
|
|
161
181
|
execute(context: CommandContext<TConfig, TInput>): TResult | Promise<TResult>;
|
|
162
182
|
}
|
|
163
183
|
declare module '@zhin.js/plugin-runtime' {
|
|
@@ -172,6 +192,11 @@ declare module '@zhin.js/plugin-runtime' {
|
|
|
172
192
|
export declare function defineCommand<TConfig = unknown, TResult = unknown, TInput extends CommandMessage = CommandMessage>(definition: Omit<CommandDefinition<TConfig, TResult, TInput>, '$feature' | '$parameter'>): Readonly<CommandDefinition<TConfig, TResult, TInput>>;
|
|
173
193
|
export declare function bindCommandParameter<TConfig, TResult, TInput extends CommandMessage>(definition: CommandDefinition<TConfig, TResult, TInput>, parameter: CommandParameterDefinition | undefined): Readonly<CommandDefinition<TConfig, TResult, TInput>>;
|
|
174
194
|
export declare function parseCommandDefinition(value: unknown): CommandDefinition;
|
|
195
|
+
/**
|
|
196
|
+
* 将动态参数值(可能包含函数)批量解析为静态值。
|
|
197
|
+
* 函数值接收从 `source`(通常是 IM Runtime `Message`)解析出的 {@link CommandSession}。
|
|
198
|
+
*/
|
|
199
|
+
export declare function resolveDynamicParams(params: Readonly<Record<string, CommandDynamicValue>>, source: unknown): Readonly<Record<string, CommandParameterValue>>;
|
|
175
200
|
export declare function createCommandContext(snapshot: RuntimeSnapshot, ownerId: PluginId, args: readonly string[], params?: Readonly<Record<string, CommandParameterValue>>, input?: unknown, segments?: readonly Readonly<CommandSegment>[]): CommandContext;
|
|
176
201
|
/**
|
|
177
202
|
* 从派发来源(通常是 Runtime `Message`)解析入站快捷字段。
|
package/lib/definition.js
CHANGED
|
@@ -100,6 +100,18 @@ export function parseCommandDefinition(value) {
|
|
|
100
100
|
}
|
|
101
101
|
return definition;
|
|
102
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* 将动态参数值(可能包含函数)批量解析为静态值。
|
|
105
|
+
* 函数值接收从 `source`(通常是 IM Runtime `Message`)解析出的 {@link CommandSession}。
|
|
106
|
+
*/
|
|
107
|
+
export function resolveDynamicParams(params, source) {
|
|
108
|
+
const session = resolveCommandSession(source);
|
|
109
|
+
const resolved = {};
|
|
110
|
+
for (const [key, value] of Object.entries(params)) {
|
|
111
|
+
resolved[key] = typeof value === 'function' ? value(session) : value;
|
|
112
|
+
}
|
|
113
|
+
return Object.freeze(resolved);
|
|
114
|
+
}
|
|
103
115
|
export function createCommandContext(snapshot, ownerId, args, params = Object.freeze({}), input = undefined, segments = Object.freeze([])) {
|
|
104
116
|
const context = createCapabilityContext(snapshot, ownerId);
|
|
105
117
|
const session = resolveCommandSession(input);
|
|
@@ -167,10 +179,15 @@ function resolveSender(input, metadata) {
|
|
|
167
179
|
if (isCommandSender(structured)) {
|
|
168
180
|
return freezeSender(structured);
|
|
169
181
|
}
|
|
170
|
-
const
|
|
182
|
+
const $sender = input.$sender;
|
|
183
|
+
const id = input.sender?.id
|
|
184
|
+
|| (typeof $sender?.id === 'string' ? $sender.id : undefined)
|
|
185
|
+
|| firstString(metadata?.user_id, metadata?.userId);
|
|
171
186
|
if (!id)
|
|
172
187
|
return undefined;
|
|
173
|
-
const name = input.sender?.name
|
|
188
|
+
const name = input.sender?.name
|
|
189
|
+
|| (typeof $sender?.name === 'string' ? $sender.name : undefined)
|
|
190
|
+
|| firstString(metadata?.nickname, metadata?.senderName, metadata?.name);
|
|
174
191
|
const role = resolveRoles(input, metadata);
|
|
175
192
|
return Object.freeze({
|
|
176
193
|
id,
|
|
@@ -190,16 +207,19 @@ function resolveRoles(input, metadata) {
|
|
|
190
207
|
if (trimmed && !roles.includes(trimmed))
|
|
191
208
|
roles.push(trimmed);
|
|
192
209
|
};
|
|
210
|
+
// IM Message 的 $sender 携带 enrich 快照(isMaster/isTrusted)和平台群角色(role)
|
|
211
|
+
const $sender = input.$sender;
|
|
212
|
+
if ($sender?.isMaster === true || metadata?.isMaster === true)
|
|
213
|
+
push('master');
|
|
214
|
+
else if ($sender?.isTrusted === true || metadata?.isTrusted === true)
|
|
215
|
+
push('trusted');
|
|
216
|
+
push($sender?.role);
|
|
193
217
|
if (Array.isArray(metadata?.roles)) {
|
|
194
218
|
for (const item of metadata.roles)
|
|
195
219
|
push(item);
|
|
196
220
|
}
|
|
197
221
|
push(metadata?.senderRole);
|
|
198
222
|
push(metadata?.role);
|
|
199
|
-
if (metadata?.isMaster === true)
|
|
200
|
-
push('master');
|
|
201
|
-
if (metadata?.isTrusted === true)
|
|
202
|
-
push('trusted');
|
|
203
223
|
if (roles.length === 0)
|
|
204
224
|
roles.push('user');
|
|
205
225
|
return Object.freeze(roles);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/command",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
4
4
|
"description": "Convention-based Command Feature for Zhin Plugin Runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"segment-matcher": "^1.0.5",
|
|
21
21
|
"@zhin.js/feature-kit": "1.0.8",
|
|
22
|
-
"@zhin.js/
|
|
23
|
-
"@zhin.js/
|
|
22
|
+
"@zhin.js/permission": "1.0.1",
|
|
23
|
+
"@zhin.js/plugin-runtime": "1.1.5"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/node": "^26.1.2",
|
package/src/command-index.ts
CHANGED
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
type CommandParameterType,
|
|
17
17
|
type CommandParameterValue,
|
|
18
18
|
type CommandSegment,
|
|
19
|
+
type CommandDynamicValue,
|
|
20
|
+
resolveDynamicParams,
|
|
19
21
|
} from './definition.js';
|
|
20
22
|
import { permissionHostToken, type PermissionHost } from '@zhin.js/permission';
|
|
21
23
|
import { toPermissionSubject } from '@zhin.js/permission';
|
|
@@ -57,12 +59,12 @@ interface CommandRoute {
|
|
|
57
59
|
|
|
58
60
|
interface ShortcutEntry {
|
|
59
61
|
readonly record: CommandRecord;
|
|
60
|
-
readonly params: Readonly<Record<string,
|
|
62
|
+
readonly params: Readonly<Record<string, CommandDynamicValue>>;
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
interface CommandMatch {
|
|
64
66
|
readonly command: CommandRecord;
|
|
65
|
-
readonly params: Readonly<Record<string,
|
|
67
|
+
readonly params: Readonly<Record<string, CommandDynamicValue>>;
|
|
66
68
|
readonly remaining: readonly Readonly<CommandSegment>[];
|
|
67
69
|
}
|
|
68
70
|
|
|
@@ -197,13 +199,13 @@ export class CommandIndex {
|
|
|
197
199
|
this.#diagnoseParameter(name);
|
|
198
200
|
throw new Error(`Unknown Command: ${name}`);
|
|
199
201
|
}
|
|
200
|
-
// Host / 无 session:跳过 permit。
|
|
202
|
+
// Host / 无 session:跳过 permit;无 source 时函数默认值得到空 session。
|
|
201
203
|
return match.command.slot.definition.execute(
|
|
202
204
|
createCommandContext(
|
|
203
205
|
this.snapshot,
|
|
204
206
|
match.command.slot.owner,
|
|
205
207
|
args,
|
|
206
|
-
match.params,
|
|
208
|
+
resolveDynamicParams(match.params, undefined),
|
|
207
209
|
),
|
|
208
210
|
);
|
|
209
211
|
}
|
|
@@ -222,7 +224,7 @@ export class CommandIndex {
|
|
|
222
224
|
this.snapshot,
|
|
223
225
|
shortcut.record.slot.owner,
|
|
224
226
|
Object.freeze([]),
|
|
225
|
-
shortcut.params,
|
|
227
|
+
resolveDynamicParams(shortcut.params, source),
|
|
226
228
|
source,
|
|
227
229
|
Object.freeze([]),
|
|
228
230
|
),
|
|
@@ -246,7 +248,7 @@ export class CommandIndex {
|
|
|
246
248
|
this.snapshot,
|
|
247
249
|
match.command.slot.owner,
|
|
248
250
|
args,
|
|
249
|
-
match.params,
|
|
251
|
+
resolveDynamicParams(match.params, source),
|
|
250
252
|
source,
|
|
251
253
|
match.remaining,
|
|
252
254
|
),
|
|
@@ -302,7 +304,7 @@ export class CommandIndex {
|
|
|
302
304
|
const result = route.matcher.match(asMatcherSegments(segments));
|
|
303
305
|
if (!result || !hasCommandBoundary(result.remaining)) continue;
|
|
304
306
|
const parameter = route.record.parameter;
|
|
305
|
-
const params: Record<string,
|
|
307
|
+
const params: Record<string, CommandDynamicValue> = { ...result.params };
|
|
306
308
|
if (parameter?.rest) {
|
|
307
309
|
const raw = result.params[parameter.name];
|
|
308
310
|
const coerced = coerceRestValues(parameter, Array.isArray(raw) ? raw : []);
|
|
@@ -390,9 +392,9 @@ function normalizeAliasList(
|
|
|
390
392
|
|
|
391
393
|
function resolveShortcutParams(
|
|
392
394
|
definition: CommandDefinition,
|
|
393
|
-
prefill: Readonly<Record<string,
|
|
395
|
+
prefill: Readonly<Record<string, CommandDynamicValue>>,
|
|
394
396
|
source: string,
|
|
395
|
-
): Record<string,
|
|
397
|
+
): Record<string, CommandDynamicValue> {
|
|
396
398
|
const allowed = new Set<string>();
|
|
397
399
|
const parameter = definition.$parameter;
|
|
398
400
|
if (parameter) allowed.add(parameter.name);
|
|
@@ -408,7 +410,7 @@ function resolveShortcutParams(
|
|
|
408
410
|
}
|
|
409
411
|
}
|
|
410
412
|
|
|
411
|
-
const result: Record<string,
|
|
413
|
+
const result: Record<string, CommandDynamicValue> = { ...prefill };
|
|
412
414
|
|
|
413
415
|
if (parameter) {
|
|
414
416
|
if (result[parameter.name] === undefined) {
|
|
@@ -508,7 +510,7 @@ function matcherPattern(
|
|
|
508
510
|
if (isRequiredParameter(parameter)) return `<${parameter.name}:${type}>`;
|
|
509
511
|
return parameter.defaultValue === undefined
|
|
510
512
|
? `[${parameter.name}:${type}]`
|
|
511
|
-
: `[${parameter.name}:${type}=${String(parameter.defaultValue)}]`;
|
|
513
|
+
: `[${parameter.name}:${type}=${typeof parameter.defaultValue === 'function' ? '<dynamic>' : String(parameter.defaultValue)}]`;
|
|
512
514
|
}).join(' ');
|
|
513
515
|
}
|
|
514
516
|
|
package/src/definition.ts
CHANGED
|
@@ -30,6 +30,29 @@ export type CommandParameterValue =
|
|
|
30
30
|
| Readonly<Record<string, unknown>>
|
|
31
31
|
| null;
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* 可从运行时会话上下文动态解析的参数值。
|
|
35
|
+
*
|
|
36
|
+
* 静态值直接使用;函数值在命令派发时接收 {@link CommandSession},
|
|
37
|
+
* 返回最终的 {@link CommandParameterValue}。适用于 shortcut 预填
|
|
38
|
+
* 和 params.default。
|
|
39
|
+
*
|
|
40
|
+
* ```ts
|
|
41
|
+
* defineCommand({
|
|
42
|
+
* params: {
|
|
43
|
+
* user_id: { type: 'string', default: (s) => s.sender?.id ?? '' },
|
|
44
|
+
* },
|
|
45
|
+
* shortcut: {
|
|
46
|
+
* '查看我的信息': { user_id: (s) => s.sender?.id ?? '' },
|
|
47
|
+
* },
|
|
48
|
+
* execute: ({ params }) => `profile:${params.user_id}`,
|
|
49
|
+
* })
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export type CommandDynamicValue =
|
|
53
|
+
| CommandParameterValue
|
|
54
|
+
| ((session: CommandSession) => CommandParameterValue);
|
|
55
|
+
|
|
33
56
|
export const commandParameterTypes: ReadonlySet<CommandParameterType> = new Set([
|
|
34
57
|
'string',
|
|
35
58
|
'number',
|
|
@@ -54,7 +77,7 @@ export const commandParameterTypes: ReadonlySet<CommandParameterType> = new Set(
|
|
|
54
77
|
*/
|
|
55
78
|
export interface CommandParamSchema {
|
|
56
79
|
readonly type: CommandParameterType;
|
|
57
|
-
readonly default?:
|
|
80
|
+
readonly default?: CommandDynamicValue;
|
|
58
81
|
readonly description?: string;
|
|
59
82
|
}
|
|
60
83
|
|
|
@@ -67,7 +90,7 @@ export interface CommandSegment {
|
|
|
67
90
|
export interface CommandParameterDefinition {
|
|
68
91
|
readonly name: string;
|
|
69
92
|
readonly type: CommandParameterType;
|
|
70
|
-
readonly defaultValue?:
|
|
93
|
+
readonly defaultValue?: CommandDynamicValue;
|
|
71
94
|
/** `[[name]]` / `[[...name]]` 可选段;缺省按 `defaultValue === undefined` 推断。 */
|
|
72
95
|
readonly optional?: boolean;
|
|
73
96
|
/** `[...name]` / `[[...name]]` 捕获所有段,运行时值为 `string[]`。 */
|
|
@@ -212,7 +235,7 @@ export interface CommandDefinition<
|
|
|
212
235
|
* 全局整句快捷方式:触发串(trim 后全文相等)→ 预填 params。
|
|
213
236
|
* 可打破 owner 命名空间。
|
|
214
237
|
*/
|
|
215
|
-
readonly shortcut?: Readonly<Record<string, Readonly<Record<string,
|
|
238
|
+
readonly shortcut?: Readonly<Record<string, Readonly<Record<string, CommandDynamicValue>>>>;
|
|
216
239
|
execute(context: CommandContext<TConfig, TInput>): TResult | Promise<TResult>;
|
|
217
240
|
}
|
|
218
241
|
|
|
@@ -286,7 +309,7 @@ function validateCommandPermit(permit: readonly string[] | undefined): void {
|
|
|
286
309
|
}
|
|
287
310
|
|
|
288
311
|
function validateCommandShortcutShape(
|
|
289
|
-
shortcut: Readonly<Record<string, Readonly<Record<string,
|
|
312
|
+
shortcut: Readonly<Record<string, Readonly<Record<string, CommandDynamicValue>>>> | undefined,
|
|
290
313
|
): void {
|
|
291
314
|
if (shortcut === undefined) return;
|
|
292
315
|
if (!shortcut || typeof shortcut !== 'object' || Array.isArray(shortcut)) {
|
|
@@ -325,6 +348,22 @@ export function parseCommandDefinition(value: unknown): CommandDefinition {
|
|
|
325
348
|
return definition as CommandDefinition;
|
|
326
349
|
}
|
|
327
350
|
|
|
351
|
+
/**
|
|
352
|
+
* 将动态参数值(可能包含函数)批量解析为静态值。
|
|
353
|
+
* 函数值接收从 `source`(通常是 IM Runtime `Message`)解析出的 {@link CommandSession}。
|
|
354
|
+
*/
|
|
355
|
+
export function resolveDynamicParams(
|
|
356
|
+
params: Readonly<Record<string, CommandDynamicValue>>,
|
|
357
|
+
source: unknown,
|
|
358
|
+
): Readonly<Record<string, CommandParameterValue>> {
|
|
359
|
+
const session = resolveCommandSession(source);
|
|
360
|
+
const resolved: Record<string, CommandParameterValue> = {};
|
|
361
|
+
for (const [key, value] of Object.entries(params)) {
|
|
362
|
+
resolved[key] = typeof value === 'function' ? value(session) : value;
|
|
363
|
+
}
|
|
364
|
+
return Object.freeze(resolved);
|
|
365
|
+
}
|
|
366
|
+
|
|
328
367
|
export function createCommandContext(
|
|
329
368
|
snapshot: RuntimeSnapshot,
|
|
330
369
|
ownerId: PluginId,
|
|
@@ -419,10 +458,15 @@ function resolveSender(
|
|
|
419
458
|
return freezeSender(structured);
|
|
420
459
|
}
|
|
421
460
|
|
|
422
|
-
const
|
|
461
|
+
const $sender = (input as { readonly $sender?: Readonly<Record<string, unknown>> }).$sender;
|
|
462
|
+
const id = input.sender?.id
|
|
463
|
+
|| (typeof $sender?.id === 'string' ? $sender.id : undefined)
|
|
464
|
+
|| firstString(metadata?.user_id, metadata?.userId);
|
|
423
465
|
if (!id) return undefined;
|
|
424
466
|
|
|
425
|
-
const name = input.sender?.name
|
|
467
|
+
const name = input.sender?.name
|
|
468
|
+
|| (typeof $sender?.name === 'string' ? $sender.name : undefined)
|
|
469
|
+
|| firstString(metadata?.nickname, metadata?.senderName, metadata?.name);
|
|
426
470
|
const role = resolveRoles(input, metadata);
|
|
427
471
|
|
|
428
472
|
return Object.freeze({
|
|
@@ -446,13 +490,18 @@ function resolveRoles(
|
|
|
446
490
|
if (trimmed && !roles.includes(trimmed)) roles.push(trimmed);
|
|
447
491
|
};
|
|
448
492
|
|
|
493
|
+
// IM Message 的 $sender 携带 enrich 快照(isMaster/isTrusted)和平台群角色(role)
|
|
494
|
+
const $sender = (input as { readonly $sender?: Readonly<Record<string, unknown>> }).$sender;
|
|
495
|
+
|
|
496
|
+
if ($sender?.isMaster === true || metadata?.isMaster === true) push('master');
|
|
497
|
+
else if ($sender?.isTrusted === true || metadata?.isTrusted === true) push('trusted');
|
|
498
|
+
|
|
499
|
+
push($sender?.role);
|
|
449
500
|
if (Array.isArray(metadata?.roles)) {
|
|
450
501
|
for (const item of metadata.roles) push(item);
|
|
451
502
|
}
|
|
452
503
|
push(metadata?.senderRole);
|
|
453
504
|
push(metadata?.role);
|
|
454
|
-
if (metadata?.isMaster === true) push('master');
|
|
455
|
-
if (metadata?.isTrusted === true) push('trusted');
|
|
456
505
|
if (roles.length === 0) roles.push('user');
|
|
457
506
|
return Object.freeze(roles);
|
|
458
507
|
}
|