alemonjs 2.1.68 → 2.1.70

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,12 @@
1
+ export type FallbackHint = {
2
+ matched: boolean;
3
+ message?: string;
4
+ suggestedKey?: string;
5
+ };
6
+ export type FallbackSuggestOptions = {
7
+ suggest?: boolean;
8
+ maxDistance?: number;
9
+ minInputLength?: number;
10
+ allowPrefixMatch?: boolean;
11
+ };
12
+ export declare function checkFallbackHint(messageText: string | undefined, routeKeys: string[], options?: FallbackSuggestOptions): FallbackHint;
@@ -0,0 +1,85 @@
1
+ import { parseMessageText } from './parser.js';
2
+
3
+ function levenshteinDistance(a, b) {
4
+ const rows = a.length + 1;
5
+ const cols = b.length + 1;
6
+ const matrix = Array.from({ length: rows }, () => Array(cols).fill(0));
7
+ for (let row = 0; row < rows; row += 1) {
8
+ matrix[row][0] = row;
9
+ }
10
+ for (let col = 0; col < cols; col += 1) {
11
+ matrix[0][col] = col;
12
+ }
13
+ for (let row = 1; row < rows; row += 1) {
14
+ for (let col = 1; col < cols; col += 1) {
15
+ const cost = a[row - 1] === b[col - 1] ? 0 : 1;
16
+ matrix[row][col] = Math.min(matrix[row - 1][col] + 1, matrix[row][col - 1] + 1, matrix[row - 1][col - 1] + cost);
17
+ }
18
+ }
19
+ return matrix[a.length][b.length];
20
+ }
21
+ function normalizeForCompare(text) {
22
+ return text.replace(/^([!!/##])\s*/, '').trim();
23
+ }
24
+ function getAdaptiveMaxDistance(input) {
25
+ if (input.length <= 2) {
26
+ return 1;
27
+ }
28
+ if (input.length <= 4) {
29
+ return 2;
30
+ }
31
+ return 3;
32
+ }
33
+ function shouldAcceptSuggestion(input, candidate, distance, options = {}) {
34
+ if (input === candidate) {
35
+ return false;
36
+ }
37
+ if (options.allowPrefixMatch !== false && (candidate.startsWith(input) || input.startsWith(candidate))) {
38
+ return true;
39
+ }
40
+ const maxDistance = typeof options.maxDistance === 'number' ? options.maxDistance : getAdaptiveMaxDistance(input);
41
+ return distance <= maxDistance;
42
+ }
43
+ function checkFallbackHint(messageText, routeKeys, options = {}) {
44
+ if (options.suggest === false) {
45
+ return { matched: false };
46
+ }
47
+ const parsed = parseMessageText(messageText);
48
+ if (!parsed) {
49
+ return { matched: false };
50
+ }
51
+ const oneKey = parsed.tokens[0] ?? '';
52
+ const twoKey = parsed.tokens.length >= 2 ? `${parsed.tokens[0]} ${parsed.tokens[1]}` : '';
53
+ const attemptedKey = twoKey || oneKey;
54
+ if (!attemptedKey) {
55
+ return { matched: false };
56
+ }
57
+ if (typeof options.minInputLength === 'number' && attemptedKey.length < options.minInputLength) {
58
+ return { matched: false };
59
+ }
60
+ let bestMatch = null;
61
+ for (const routeKey of routeKeys) {
62
+ const normalizedKey = normalizeForCompare(routeKey);
63
+ const distance = levenshteinDistance(attemptedKey, normalizedKey);
64
+ if (!bestMatch || distance < bestMatch.distance) {
65
+ bestMatch = {
66
+ key: routeKey,
67
+ distance
68
+ };
69
+ }
70
+ }
71
+ if (!bestMatch) {
72
+ return { matched: false };
73
+ }
74
+ const normalizedBestKey = normalizeForCompare(bestMatch.key);
75
+ if (!shouldAcceptSuggestion(attemptedKey, normalizedBestKey, bestMatch.distance, options)) {
76
+ return { matched: false };
77
+ }
78
+ return {
79
+ matched: true,
80
+ suggestedKey: bestMatch.key,
81
+ message: `你输入的内容更接近指令 \`${bestMatch.key}\`,请改用这个指令。`
82
+ };
83
+ }
84
+
85
+ export { checkFallbackHint };
@@ -0,0 +1,7 @@
1
+ export { Router } from './dsl';
2
+ export type { RouterOptions } from './dsl';
3
+ export { checkFallbackHint } from './fallback';
4
+ export { normalizeRoutePath, parseMessageText } from './parser';
5
+ export type { AppMatchResult, RegisteredRoute, RouteDefinition, RouteHandlerConfig, RouteImporter } from './types';
6
+ export { validateRouteArgs } from './validator';
7
+ export type { RouteArgSchema, RouteRangeValue, RouteSchema, RouteSchemaValue, RouteValidationResult, SchemaValueType } from './validator';
@@ -0,0 +1,4 @@
1
+ export { Router } from './dsl.js';
2
+ export { checkFallbackHint } from './fallback.js';
3
+ export { normalizeRoutePath, parseMessageText } from './parser.js';
4
+ export { validateRouteArgs } from './validator.js';
@@ -0,0 +1,8 @@
1
+ export type ParsedMessage = {
2
+ prefix: string;
3
+ rawText: string;
4
+ normalizedText: string;
5
+ tokens: string[];
6
+ };
7
+ export declare function parseMessageText(messageText?: string): ParsedMessage | null;
8
+ export declare function normalizeRoutePath(path: string): string;
@@ -0,0 +1,24 @@
1
+ const COMMAND_PREFIX_REGEXP = /^([!!/##])\s*/;
2
+ function parseMessageText(messageText) {
3
+ const rawText = String(messageText ?? '').trim();
4
+ if (!rawText) {
5
+ return null;
6
+ }
7
+ const prefixMatch = rawText.match(COMMAND_PREFIX_REGEXP);
8
+ const prefix = prefixMatch?.[1] ?? '';
9
+ const normalizedText = rawText.replace(COMMAND_PREFIX_REGEXP, '').trim();
10
+ if (!normalizedText) {
11
+ return null;
12
+ }
13
+ return {
14
+ prefix,
15
+ rawText,
16
+ normalizedText,
17
+ tokens: normalizedText.split(/\s+/).filter(Boolean)
18
+ };
19
+ }
20
+ function normalizeRoutePath(path) {
21
+ return path.replace(COMMAND_PREFIX_REGEXP, '').trim();
22
+ }
23
+
24
+ export { normalizeRoutePath, parseMessageText };
@@ -0,0 +1,102 @@
1
+ import type { RouteSchema, RouteSchemaValue, RouteValidationResult } from './validator';
2
+ export type RouteParams = Record<string, RouteSchemaValue | undefined>;
3
+ export type RouteContext = {
4
+ key: string;
5
+ text: string;
6
+ rawArgs: string[];
7
+ parsedArgs: RouteSchemaValue[];
8
+ params: RouteParams;
9
+ };
10
+ export type RouteTextRule = {
11
+ prefixes?: readonly string[];
12
+ stripPrefix?: boolean;
13
+ allowBare?: boolean;
14
+ };
15
+ export type RouteTextConfig<P extends string = string> = RouteTextRule & {
16
+ byPlatform?: Partial<Record<P, RouteTextRule>>;
17
+ };
18
+ export type RouteImporter = () => Promise<unknown>;
19
+ export type RouteNext = () => Promise<void> | undefined;
20
+ export type RouteExecutable = (event: Record<string, unknown>, next: RouteNext) => Promise<boolean | undefined> | boolean | undefined;
21
+ export type RouteHandlerConfig<P extends string = string, E extends string = string> = {
22
+ path?: string;
23
+ events?: readonly E[];
24
+ platforms?: readonly P[];
25
+ schema?: RouteSchema;
26
+ };
27
+ export type RouteGroupConditions<P extends string = string, E extends string = string> = {
28
+ path?: string;
29
+ events?: readonly E[];
30
+ platforms?: readonly P[];
31
+ routeText?: RouteTextConfig<P>;
32
+ keyPolicy?: {
33
+ maxWords?: 1 | 2;
34
+ };
35
+ duplicateKey?: 'ignore' | 'warn' | 'throw';
36
+ fallback?: {
37
+ suggest?: boolean;
38
+ maxDistance?: number;
39
+ minInputLength?: number;
40
+ allowPrefixMatch?: boolean;
41
+ };
42
+ redispatch?: {
43
+ maxDepth?: number;
44
+ };
45
+ };
46
+ export type RouteResConfig<P extends string = string, E extends string = string> = {
47
+ events?: readonly E[];
48
+ platforms?: readonly P[];
49
+ regular?: RegExp;
50
+ };
51
+ export type RouteDefinition<P extends string = string, E extends string = string> = string | RouteHandlerConfig<P, E>;
52
+ export type RouteDefinitions<P extends string = string, E extends string = string> = RouteDefinition<P, E> | RouteDefinition<P, E>[];
53
+ export type NormalizedRouteConfig<P extends string = string, E extends string = string> = {
54
+ path?: string;
55
+ events?: readonly E[];
56
+ platforms?: readonly P[];
57
+ schema?: RouteSchema;
58
+ };
59
+ export type RegisteredRoute<P extends string = string, E extends string = string> = {
60
+ config: NormalizedRouteConfig<P, E> & {
61
+ path: string;
62
+ };
63
+ scopeId: string;
64
+ keyLength: 1 | 2;
65
+ importers: RouteImporter[];
66
+ inheritedImporters: RouteImporter[];
67
+ };
68
+ export type RegisteredRes<P extends string = string, E extends string = string> = {
69
+ config: RouteResConfig<P, E>;
70
+ importers: RouteImporter[];
71
+ };
72
+ export type AppMatchResult = {
73
+ matched: false;
74
+ } | {
75
+ matched: true;
76
+ route: RegisteredRoute;
77
+ eventName: string;
78
+ normalizedCommand: string;
79
+ rawArgs: string[];
80
+ parsedArgs: RouteSchemaValue[];
81
+ validation: RouteValidationResult;
82
+ matchedPath: string;
83
+ importerCount: number;
84
+ importerLabels: string[];
85
+ };
86
+ export type DispatchStopReason = 'unmatched' | 'validation_failed' | 'handler_returned_false' | 'handler_completed' | 'redispatch_limit' | 'unsupported_handler';
87
+ export type AppDispatchResult = {
88
+ matched: boolean;
89
+ stopped: boolean;
90
+ reason: DispatchStopReason;
91
+ eventName: string;
92
+ commandKey?: string;
93
+ matchedPath?: string;
94
+ normalizedCommand?: string;
95
+ rawArgs: string[];
96
+ parsedArgs: RouteSchemaValue[];
97
+ params?: RouteParams;
98
+ importerCount: number;
99
+ importerLabels: string[];
100
+ validation?: RouteValidationResult;
101
+ rewrittenMessageText?: string;
102
+ };
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,50 @@
1
+ export type RouteRangeValue = {
2
+ start: number;
3
+ end: number;
4
+ };
5
+ export type SchemaValueType = 'string' | 'number' | 'enum' | 'range' | 'rest';
6
+ export type RouteSchemaValue = string | number | RouteRangeValue;
7
+ export type RouteRuleValidatorContext = {
8
+ commandPath?: string;
9
+ argName: string;
10
+ argIndex: number;
11
+ displayIndex: number;
12
+ rawArgs: string[];
13
+ parsedArgs: RouteSchemaValue[];
14
+ rawValue: string;
15
+ };
16
+ export type RouteRule = {
17
+ required?: boolean;
18
+ type?: SchemaValueType;
19
+ enum?: string[];
20
+ pattern?: RegExp;
21
+ min?: number;
22
+ max?: number;
23
+ separators?: string[];
24
+ normalizeMap?: Record<string, string>;
25
+ message?: string;
26
+ validator?: (value: RouteSchemaValue, context: RouteRuleValidatorContext) => string | undefined;
27
+ };
28
+ export type RouteArgSchema = {
29
+ name: string;
30
+ defaultValue?: RouteSchemaValue;
31
+ rules?: RouteRule[];
32
+ };
33
+ export type RouteSchema = {
34
+ args?: RouteArgSchema[];
35
+ usage?: string;
36
+ messages?: {
37
+ tooFewArgs?: string;
38
+ tooManyArgs?: string;
39
+ };
40
+ };
41
+ export type RouteValidationResult = {
42
+ valid: true;
43
+ parsedArgs: RouteSchemaValue[];
44
+ } | {
45
+ valid: false;
46
+ error: string;
47
+ usage?: string;
48
+ };
49
+ export declare function validateRouteArgs(rawArgs: string[], schema?: RouteSchema): RouteValidationResult;
50
+ export declare function validateRouteArgsForCommand(commandPath: string | undefined, rawArgs: string[], schema?: RouteSchema): RouteValidationResult;
@@ -0,0 +1,306 @@
1
+ function getRequiredRule(arg) {
2
+ return arg?.rules?.find(rule => rule.required);
3
+ }
4
+ function getRestRule(arg) {
5
+ return arg?.rules?.find(rule => rule.type === 'rest');
6
+ }
7
+ function getMinArgs(schema) {
8
+ if (!schema) {
9
+ return 0;
10
+ }
11
+ return (schema.args ?? []).filter(arg => Boolean(getRequiredRule(arg))).length;
12
+ }
13
+ function getMaxArgs(schema) {
14
+ if (!schema) {
15
+ return undefined;
16
+ }
17
+ const args = schema.args ?? [];
18
+ if (args.some(arg => Boolean(getRestRule(arg)))) {
19
+ return undefined;
20
+ }
21
+ return args.length > 0 ? args.length : undefined;
22
+ }
23
+ function parseRange(rawValue, rule) {
24
+ const separators = rule.separators ?? ['-', '-'];
25
+ const separator = separators.find(item => rawValue.includes(item));
26
+ if (!separator) {
27
+ return null;
28
+ }
29
+ const [rawStart = '', rawEnd = ''] = rawValue.split(separator).map(item => item.trim());
30
+ const start = Number(rawStart);
31
+ const end = Number(rawEnd);
32
+ if (!rawStart || !rawEnd || Number.isNaN(start) || Number.isNaN(end)) {
33
+ return null;
34
+ }
35
+ return { start, end };
36
+ }
37
+ function validateTypedRule(rawValue, rule, context, usage) {
38
+ const messagePrefix = `参数${context.displayIndex}`;
39
+ const normalizedValue = rule.normalizeMap?.[rawValue] ?? rawValue;
40
+ if (rule.pattern) {
41
+ rule.pattern.lastIndex = 0;
42
+ if (!rule.pattern.test(normalizedValue)) {
43
+ return {
44
+ valid: false,
45
+ error: rule.message ?? `${messagePrefix}格式不正确`,
46
+ usage
47
+ };
48
+ }
49
+ }
50
+ if (!rule.type || rule.type === 'string') {
51
+ const value = String(normalizedValue);
52
+ if (rule.validator) {
53
+ const customError = rule.validator(value, { ...context, rawValue: normalizedValue });
54
+ if (customError) {
55
+ return {
56
+ valid: false,
57
+ error: customError,
58
+ usage
59
+ };
60
+ }
61
+ }
62
+ return {
63
+ valid: true,
64
+ parsedArgs: [value]
65
+ };
66
+ }
67
+ if (rule.type === 'number') {
68
+ const value = Number(normalizedValue);
69
+ if (Number.isNaN(value)) {
70
+ return {
71
+ valid: false,
72
+ error: rule.message ?? `${messagePrefix}必须是数字`,
73
+ usage
74
+ };
75
+ }
76
+ if (typeof rule.min === 'number' && value < rule.min) {
77
+ return {
78
+ valid: false,
79
+ error: rule.message ?? `${messagePrefix}不能小于${rule.min}`,
80
+ usage
81
+ };
82
+ }
83
+ if (typeof rule.max === 'number' && value > rule.max) {
84
+ return {
85
+ valid: false,
86
+ error: rule.message ?? `${messagePrefix}不能大于${rule.max}`,
87
+ usage
88
+ };
89
+ }
90
+ if (rule.validator) {
91
+ const customError = rule.validator(value, { ...context, rawValue: normalizedValue });
92
+ if (customError) {
93
+ return {
94
+ valid: false,
95
+ error: customError,
96
+ usage
97
+ };
98
+ }
99
+ }
100
+ return {
101
+ valid: true,
102
+ parsedArgs: [value]
103
+ };
104
+ }
105
+ if (rule.type === 'enum') {
106
+ const enumValues = rule.enum ?? [];
107
+ if (!enumValues.includes(normalizedValue)) {
108
+ return {
109
+ valid: false,
110
+ error: rule.message ?? `${messagePrefix}只能是 ${enumValues.join(' / ')}`,
111
+ usage
112
+ };
113
+ }
114
+ if (rule.validator) {
115
+ const customError = rule.validator(normalizedValue, { ...context, rawValue: normalizedValue });
116
+ if (customError) {
117
+ return {
118
+ valid: false,
119
+ error: customError,
120
+ usage
121
+ };
122
+ }
123
+ }
124
+ return {
125
+ valid: true,
126
+ parsedArgs: [normalizedValue]
127
+ };
128
+ }
129
+ if (rule.type === 'range') {
130
+ const value = parseRange(normalizedValue, rule);
131
+ if (!value) {
132
+ return {
133
+ valid: false,
134
+ error: rule.message ?? `${messagePrefix}必须是区间,例如 1-10`,
135
+ usage
136
+ };
137
+ }
138
+ if (value.start > value.end) {
139
+ return {
140
+ valid: false,
141
+ error: rule.message ?? `${messagePrefix}的起点不能大于终点`,
142
+ usage
143
+ };
144
+ }
145
+ if (typeof rule.min === 'number' && (value.start < rule.min || value.end < rule.min)) {
146
+ return {
147
+ valid: false,
148
+ error: rule.message ?? `${messagePrefix}不能小于${rule.min}`,
149
+ usage
150
+ };
151
+ }
152
+ if (typeof rule.max === 'number' && (value.start > rule.max || value.end > rule.max)) {
153
+ return {
154
+ valid: false,
155
+ error: rule.message ?? `${messagePrefix}不能大于${rule.max}`,
156
+ usage
157
+ };
158
+ }
159
+ if (rule.validator) {
160
+ const customError = rule.validator(value, { ...context, rawValue: normalizedValue });
161
+ if (customError) {
162
+ return {
163
+ valid: false,
164
+ error: customError,
165
+ usage
166
+ };
167
+ }
168
+ }
169
+ return {
170
+ valid: true,
171
+ parsedArgs: [value]
172
+ };
173
+ }
174
+ if (rule.type === 'rest') {
175
+ const value = String(normalizedValue);
176
+ if (rule.validator) {
177
+ const customError = rule.validator(value, { ...context, rawValue: normalizedValue });
178
+ if (customError) {
179
+ return {
180
+ valid: false,
181
+ error: customError,
182
+ usage
183
+ };
184
+ }
185
+ }
186
+ return {
187
+ valid: true,
188
+ parsedArgs: [value]
189
+ };
190
+ }
191
+ return {
192
+ valid: true,
193
+ parsedArgs: [String(normalizedValue)]
194
+ };
195
+ }
196
+ function validateArgValue(commandPath, arg, rawValue, rawArgs, parsedArgs, argIndex, displayIndex, usage) {
197
+ const rules = arg.rules ?? [];
198
+ const context = {
199
+ commandPath,
200
+ argName: arg.name,
201
+ argIndex,
202
+ displayIndex,
203
+ rawArgs,
204
+ parsedArgs,
205
+ rawValue
206
+ };
207
+ for (const rule of rules) {
208
+ const result = validateTypedRule(rawValue, rule, context, usage);
209
+ if (!result.valid) {
210
+ return result;
211
+ }
212
+ if (rule.type) {
213
+ return result;
214
+ }
215
+ }
216
+ return {
217
+ valid: true,
218
+ parsedArgs: [rawValue]
219
+ };
220
+ }
221
+ function validateRouteArgs(rawArgs, schema) {
222
+ return validateRouteArgsForCommand(undefined, rawArgs, schema);
223
+ }
224
+ function validateRouteArgsForCommand(commandPath, rawArgs, schema) {
225
+ const minArgs = getMinArgs(schema);
226
+ const maxArgs = getMaxArgs(schema);
227
+ const usage = schema?.usage;
228
+ if (rawArgs.length < minArgs) {
229
+ return {
230
+ valid: false,
231
+ error: schema?.messages?.tooFewArgs ?? `至少需要 ${minArgs} 个参数`,
232
+ usage
233
+ };
234
+ }
235
+ if (typeof maxArgs === 'number' && rawArgs.length > maxArgs) {
236
+ return {
237
+ valid: false,
238
+ error: schema?.messages?.tooManyArgs ?? `最多需要 ${maxArgs} 个参数`,
239
+ usage
240
+ };
241
+ }
242
+ const args = schema?.args ?? [];
243
+ const parsedArgs = [];
244
+ for (let index = 0; index < args.length; index += 1) {
245
+ const arg = args[index];
246
+ const displayIndex = index + 1;
247
+ const restRule = getRestRule(arg);
248
+ if (restRule) {
249
+ const rawValue = rawArgs.slice(index).join(' ').trim();
250
+ const requiredRule = getRequiredRule(arg);
251
+ if (!rawValue) {
252
+ if (requiredRule) {
253
+ return {
254
+ valid: false,
255
+ error: requiredRule.message ?? `参数${displayIndex}是必填的`,
256
+ usage
257
+ };
258
+ }
259
+ if (arg.defaultValue !== undefined) {
260
+ parsedArgs.push(arg.defaultValue);
261
+ }
262
+ continue;
263
+ }
264
+ const result = validateTypedRule(rawValue, restRule, {
265
+ commandPath,
266
+ argName: arg.name,
267
+ argIndex: index,
268
+ displayIndex,
269
+ rawArgs,
270
+ parsedArgs,
271
+ rawValue
272
+ }, usage);
273
+ if (!result.valid) {
274
+ return result;
275
+ }
276
+ parsedArgs.push(...result.parsedArgs);
277
+ break;
278
+ }
279
+ const rawValue = rawArgs[index];
280
+ if (rawValue === undefined) {
281
+ const requiredRule = getRequiredRule(arg);
282
+ if (requiredRule) {
283
+ return {
284
+ valid: false,
285
+ error: requiredRule.message ?? `参数${displayIndex}是必填的`,
286
+ usage
287
+ };
288
+ }
289
+ if (arg.defaultValue !== undefined) {
290
+ parsedArgs.push(arg.defaultValue);
291
+ }
292
+ continue;
293
+ }
294
+ const result = validateArgValue(commandPath, arg, rawValue, rawArgs, parsedArgs, index, displayIndex, usage);
295
+ if (!result.valid) {
296
+ return result;
297
+ }
298
+ parsedArgs.push(...result.parsedArgs);
299
+ }
300
+ return {
301
+ valid: true,
302
+ parsedArgs
303
+ };
304
+ }
305
+
306
+ export { validateRouteArgs, validateRouteArgsForCommand };
package/lib/index.js CHANGED
@@ -46,5 +46,9 @@ export { registerAppDir, scheduleCancel, scheduleCancelAll, scheduleCancelByApp,
46
46
  export { createEventValue, createSelects, onSelects, onState, unChildren, unState, useState } from './app/event-utils.js';
47
47
  export { MessageDirect, createDataFormat, format, getMessageIntent, sendToChannel, sendToUser } from './app/message-api.js';
48
48
  export { Format, FormatButtonGroup, FormatMarkDown, FormatSelect } from './app/message-format.js';
49
+ export { Router } from './app/router/dsl.js';
50
+ export { checkFallbackHint } from './app/router/fallback.js';
51
+ export { normalizeRoutePath, parseMessageText } from './app/router/parser.js';
52
+ export { validateRouteArgs } from './app/router/validator.js';
49
53
  export { start } from './main.js';
50
54
  export { Attachment, Audio, BT, Button, Image, ImageFile, ImageURL, Link, MD, Markdown, MarkdownOriginal, Mention, Text, Video } from './app/message-format-old.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alemonjs",
3
- "version": "2.1.68",
3
+ "version": "2.1.70",
4
4
  "description": "bot script",
5
5
  "author": "lemonade",
6
6
  "license": "MIT",