koishi-plugin-msg-router 1.3.0 → 1.4.2
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/index.d.ts +17 -0
- package/lib/index.js +157 -16
- package/package.json +4 -4
- package/readme.md +29 -6
package/lib/index.d.ts
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
import { Context, Schema } from 'koishi';
|
|
2
2
|
export declare const name = "msg-router";
|
|
3
|
+
export type CommandScope = 'all' | 'group' | 'private';
|
|
4
|
+
export interface CommandAccessConfig {
|
|
5
|
+
permissions: string[];
|
|
6
|
+
hidden: boolean;
|
|
7
|
+
maxUsage: number;
|
|
8
|
+
minInterval: number;
|
|
9
|
+
scope: CommandScope;
|
|
10
|
+
platforms: string[];
|
|
11
|
+
}
|
|
3
12
|
export interface CommandMappingConfig {
|
|
4
13
|
enabled: boolean;
|
|
5
14
|
source: string;
|
|
6
15
|
target: string;
|
|
7
16
|
description: string;
|
|
17
|
+
permissions: string[];
|
|
18
|
+
hidden: boolean;
|
|
19
|
+
maxUsage: number;
|
|
20
|
+
minInterval: number;
|
|
21
|
+
scope: CommandScope;
|
|
22
|
+
platforms: string[];
|
|
8
23
|
}
|
|
9
24
|
export interface ClientRouteConfig {
|
|
10
25
|
name: string;
|
|
@@ -45,6 +60,8 @@ export interface Config {
|
|
|
45
60
|
enableCommandTranslation: boolean;
|
|
46
61
|
commandDeclarationAction: string;
|
|
47
62
|
maxDeclaredCommands: number;
|
|
63
|
+
allowDynamicCommandPermissions: boolean;
|
|
64
|
+
minimumDynamicAuthority: number;
|
|
48
65
|
clientRoutes: ClientRouteConfig[];
|
|
49
66
|
serverRoutes: ServerRouteConfig[];
|
|
50
67
|
}
|
package/lib/index.js
CHANGED
|
@@ -19,6 +19,16 @@ const CommonRouteConfig = {
|
|
|
19
19
|
source: koishi_1.Schema.string().required().description('用户侧需要接管的指令名,例如:天气'),
|
|
20
20
|
target: koishi_1.Schema.string().required().description('发送给后端的目标指令名,例如:weather'),
|
|
21
21
|
description: koishi_1.Schema.string().default('').description('显示在 Koishi 指令系统中的说明,可留空'),
|
|
22
|
+
permissions: koishi_1.Schema.array(koishi_1.Schema.string()).role('perms').default(['authority:1']).description('Koishi 原生权限规则,例如 authority:3'),
|
|
23
|
+
hidden: koishi_1.Schema.boolean().default(false).description('是否在帮助菜单中隐藏,需要启用 help 插件'),
|
|
24
|
+
maxUsage: koishi_1.Schema.natural().default(0).description('每名用户每天最多调用次数,0 表示不限;需要 rate-limit 和数据库'),
|
|
25
|
+
minInterval: koishi_1.Schema.natural().default(0).description('同一用户连续调用的最小间隔,单位毫秒;0 表示不限'),
|
|
26
|
+
scope: koishi_1.Schema.union([
|
|
27
|
+
koishi_1.Schema.const('all').description('群聊和私聊'),
|
|
28
|
+
koishi_1.Schema.const('group').description('仅群聊'),
|
|
29
|
+
koishi_1.Schema.const('private').description('仅私聊'),
|
|
30
|
+
]).default('all').description('指令可用的会话范围'),
|
|
31
|
+
platforms: koishi_1.Schema.array(koishi_1.Schema.string()).default([]).description('允许使用的平台列表,例如 qq;留空表示不限制'),
|
|
22
32
|
})).role('table').default([]).description('手动指令转译表。映射可以独立于 commands 使用;同名映射会覆盖本路由 commands 中的直通规则'),
|
|
23
33
|
action: koishi_1.Schema.string().default('msg_router.forward_command').description('兼容配置字段;消息事件当前按 OneBot v11 标准直接推送'),
|
|
24
34
|
timeout: koishi_1.Schema.number().default(10000).description('单次请求超时时间,单位毫秒'),
|
|
@@ -59,6 +69,14 @@ exports.Config = koishi_1.Schema.object({
|
|
|
59
69
|
.max(1000)
|
|
60
70
|
.default(100)
|
|
61
71
|
.description('每条路由最多允许后端动态声明的指令数量'),
|
|
72
|
+
allowDynamicCommandPermissions: koishi_1.Schema.boolean()
|
|
73
|
+
.default(false)
|
|
74
|
+
.description('允许后端声明 permissions 权限规则。关闭时动态指令统一使用 minimumDynamicAuthority,避免后端意外开放敏感指令'),
|
|
75
|
+
minimumDynamicAuthority: koishi_1.Schema.natural()
|
|
76
|
+
.min(0)
|
|
77
|
+
.max(4)
|
|
78
|
+
.default(1)
|
|
79
|
+
.description('动态指令允许的最低权限等级;后端声明低于此等级的 authority 权限时会拒绝该指令'),
|
|
62
80
|
debug: koishi_1.Schema.boolean().default(false).description('输出 WebSocket 收发内容、指令匹配和消息解析等调试日志'),
|
|
63
81
|
clientRoutes: koishi_1.Schema.array(ClientRouteConfig)
|
|
64
82
|
.role('table')
|
|
@@ -128,6 +146,9 @@ function extractQQMarkdown(value) {
|
|
|
128
146
|
return {
|
|
129
147
|
markdown: { content },
|
|
130
148
|
fallback,
|
|
149
|
+
keyboard: markdownSource.keyboard && typeof markdownSource.keyboard === 'object'
|
|
150
|
+
? markdownSource.keyboard
|
|
151
|
+
: undefined,
|
|
131
152
|
remaining,
|
|
132
153
|
};
|
|
133
154
|
}
|
|
@@ -431,11 +452,13 @@ class RouteRuntime {
|
|
|
431
452
|
? {
|
|
432
453
|
msg_type: 2,
|
|
433
454
|
markdown: qq.markdown,
|
|
455
|
+
...(qq.keyboard ? { keyboard: qq.keyboard } : {}),
|
|
434
456
|
...(recentMessageId ? { msg_id: recentMessageId } : {}),
|
|
435
457
|
}
|
|
436
458
|
: {
|
|
437
459
|
content: ' ',
|
|
438
460
|
markdown: qq.markdown,
|
|
461
|
+
...(qq.keyboard ? { keyboard: qq.keyboard } : {}),
|
|
439
462
|
...(recentMessageId ? { msg_id: recentMessageId } : {}),
|
|
440
463
|
};
|
|
441
464
|
const response = isPrivate
|
|
@@ -908,6 +931,14 @@ function apply(ctx, config) {
|
|
|
908
931
|
const logger = ctx.logger(exports.name);
|
|
909
932
|
const runtimes = new Map();
|
|
910
933
|
const registered = new Map();
|
|
934
|
+
const defaultCommandOptions = () => ({
|
|
935
|
+
permissions: ['authority:1'],
|
|
936
|
+
hidden: false,
|
|
937
|
+
maxUsage: 0,
|
|
938
|
+
minInterval: 0,
|
|
939
|
+
scope: 'all',
|
|
940
|
+
platforms: [],
|
|
941
|
+
});
|
|
911
942
|
const normalizeCommandName = (value) => String(value ?? '')
|
|
912
943
|
.trim()
|
|
913
944
|
.replace(/^\/+/, '')
|
|
@@ -916,6 +947,59 @@ function apply(ctx, config) {
|
|
|
916
947
|
const isValidCommandName = (value) => {
|
|
917
948
|
return !!value && value.length <= 100 && !/[\s[\]<>]/.test(value);
|
|
918
949
|
};
|
|
950
|
+
const normalizeCommandOptions = (raw, dynamic) => {
|
|
951
|
+
const minimumAuthority = Math.min(4, Math.max(0, config.minimumDynamicAuthority ?? 1));
|
|
952
|
+
let permissions = Array.isArray(raw?.permissions)
|
|
953
|
+
? raw.permissions.map(String).map(item => item.trim()).filter(Boolean).slice(0, 20)
|
|
954
|
+
: [];
|
|
955
|
+
if (dynamic && !config.allowDynamicCommandPermissions) {
|
|
956
|
+
permissions = [`authority:${minimumAuthority}`];
|
|
957
|
+
}
|
|
958
|
+
else if (!permissions.length) {
|
|
959
|
+
permissions = [dynamic ? `authority:${minimumAuthority}` : 'authority:1'];
|
|
960
|
+
}
|
|
961
|
+
if (dynamic) {
|
|
962
|
+
let hasAuthorityPermission = false;
|
|
963
|
+
for (const permission of permissions) {
|
|
964
|
+
const match = /^authority:(\d+)$/.exec(permission);
|
|
965
|
+
if (match) {
|
|
966
|
+
hasAuthorityPermission = true;
|
|
967
|
+
if (Number(match[1]) < minimumAuthority) {
|
|
968
|
+
throw new Error(`permission ${permission} is below minimum authority:${minimumAuthority}`);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
if (!hasAuthorityPermission)
|
|
973
|
+
permissions.push(`authority:${minimumAuthority}`);
|
|
974
|
+
}
|
|
975
|
+
const requestedScope = String(raw?.scope ?? 'all');
|
|
976
|
+
const scope = requestedScope === 'group' || requestedScope === 'private'
|
|
977
|
+
? requestedScope
|
|
978
|
+
: 'all';
|
|
979
|
+
const platforms = Array.isArray(raw?.platforms)
|
|
980
|
+
? [...new Set(raw.platforms.map((item) => String(item).trim().toLowerCase()).filter(Boolean))].slice(0, 20)
|
|
981
|
+
: [];
|
|
982
|
+
return {
|
|
983
|
+
permissions,
|
|
984
|
+
hidden: raw?.hidden === true,
|
|
985
|
+
maxUsage: Math.min(1_000_000, Math.max(0, Math.floor(Number(raw?.maxUsage) || 0))),
|
|
986
|
+
minInterval: Math.min(86_400_000, Math.max(0, Math.floor(Number(raw?.minInterval) || 0))),
|
|
987
|
+
scope,
|
|
988
|
+
platforms,
|
|
989
|
+
};
|
|
990
|
+
};
|
|
991
|
+
const matchesCommandOptions = (session, options) => {
|
|
992
|
+
if (options.platforms.length && !options.platforms.includes(String(session?.platform ?? '').toLowerCase()))
|
|
993
|
+
return false;
|
|
994
|
+
const isPrivate = session?.isDirect === true || (session?.guildId == null
|
|
995
|
+
&& session?.channelId != null
|
|
996
|
+
&& String(session.channelId) === String(session?.userId ?? ''));
|
|
997
|
+
if (options.scope === 'private' && !isPrivate)
|
|
998
|
+
return false;
|
|
999
|
+
if (options.scope === 'group' && isPrivate)
|
|
1000
|
+
return false;
|
|
1001
|
+
return true;
|
|
1002
|
+
};
|
|
919
1003
|
const parseDeclarations = (params) => {
|
|
920
1004
|
const source = params?.commands;
|
|
921
1005
|
if (Array.isArray(source))
|
|
@@ -925,18 +1009,45 @@ function apply(ctx, config) {
|
|
|
925
1009
|
}
|
|
926
1010
|
throw new Error('params.commands must be an array or an object map');
|
|
927
1011
|
};
|
|
928
|
-
const registerCommand = (runtime, sourceName, targetName, dynamic, description) => {
|
|
1012
|
+
const registerCommand = (runtime, sourceName, targetName, dynamic, description, options = defaultCommandOptions()) => {
|
|
929
1013
|
const existing = registered.get(sourceName);
|
|
930
1014
|
if (existing) {
|
|
931
1015
|
if (existing.runtime !== runtime)
|
|
932
1016
|
return 'already owned by another msg-router route';
|
|
1017
|
+
if (!dynamic) {
|
|
1018
|
+
existing.dynamic = false;
|
|
1019
|
+
existing.baseTarget = targetName;
|
|
1020
|
+
existing.baseOptions = options;
|
|
1021
|
+
}
|
|
933
1022
|
existing.target = targetName;
|
|
1023
|
+
existing.options = options;
|
|
1024
|
+
Object.assign(existing.command.config, {
|
|
1025
|
+
permissions: options.permissions,
|
|
1026
|
+
hidden: options.hidden,
|
|
1027
|
+
maxUsage: options.maxUsage,
|
|
1028
|
+
minInterval: options.minInterval,
|
|
1029
|
+
});
|
|
1030
|
+
ctx.emit('command-updated', existing.command);
|
|
934
1031
|
return;
|
|
935
1032
|
}
|
|
936
1033
|
if (ctx.$commander.get(sourceName))
|
|
937
1034
|
return 'already registered by another Koishi plugin';
|
|
938
|
-
const entry = {
|
|
939
|
-
|
|
1035
|
+
const entry = {
|
|
1036
|
+
runtime,
|
|
1037
|
+
target: targetName,
|
|
1038
|
+
dynamic,
|
|
1039
|
+
command: null,
|
|
1040
|
+
options,
|
|
1041
|
+
baseTarget: targetName,
|
|
1042
|
+
baseOptions: options,
|
|
1043
|
+
};
|
|
1044
|
+
const commandCtx = ctx.intersect(session => matchesCommandOptions(session, entry.options));
|
|
1045
|
+
const command = commandCtx.command(`${sourceName} [content:text]`, description || `由 msg-router 动态路由到 ${targetName}`, {
|
|
1046
|
+
permissions: options.permissions,
|
|
1047
|
+
hidden: options.hidden,
|
|
1048
|
+
maxUsage: options.maxUsage,
|
|
1049
|
+
minInterval: options.minInterval,
|
|
1050
|
+
}).action(async ({ session }, content) => {
|
|
940
1051
|
if (config.debug) {
|
|
941
1052
|
logger.debug(`command trigger: source=${sourceName} target=${entry.target} content=${JSON.stringify(content ?? '')}`);
|
|
942
1053
|
}
|
|
@@ -969,12 +1080,21 @@ function apply(ctx, config) {
|
|
|
969
1080
|
rejected.push({ name: sourceName, reason: 'invalid target command' });
|
|
970
1081
|
continue;
|
|
971
1082
|
}
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1083
|
+
try {
|
|
1084
|
+
declarations.set(sourceName, {
|
|
1085
|
+
name: sourceName,
|
|
1086
|
+
target: targetName,
|
|
1087
|
+
description: raw.description == null ? undefined : String(raw.description).slice(0, 200),
|
|
1088
|
+
enabled: raw.enabled !== false,
|
|
1089
|
+
options: normalizeCommandOptions(raw, true),
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
catch (error) {
|
|
1093
|
+
rejected.push({
|
|
1094
|
+
name: sourceName,
|
|
1095
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
978
1098
|
}
|
|
979
1099
|
const limit = Math.max(1, config.maxDeclaredCommands || 100);
|
|
980
1100
|
const resultingDynamic = new Set([...registered]
|
|
@@ -994,11 +1114,24 @@ function apply(ctx, config) {
|
|
|
994
1114
|
const removed = [];
|
|
995
1115
|
if (params?.replace !== false) {
|
|
996
1116
|
for (const [sourceName, entry] of [...registered]) {
|
|
997
|
-
if (entry.runtime !== runtime ||
|
|
1117
|
+
if (entry.runtime !== runtime || declarations.has(sourceName))
|
|
998
1118
|
continue;
|
|
999
|
-
entry.
|
|
1000
|
-
|
|
1001
|
-
|
|
1119
|
+
if (entry.dynamic) {
|
|
1120
|
+
entry.command.dispose();
|
|
1121
|
+
registered.delete(sourceName);
|
|
1122
|
+
removed.push(sourceName);
|
|
1123
|
+
}
|
|
1124
|
+
else {
|
|
1125
|
+
entry.target = entry.baseTarget;
|
|
1126
|
+
entry.options = entry.baseOptions;
|
|
1127
|
+
Object.assign(entry.command.config, {
|
|
1128
|
+
permissions: entry.options.permissions,
|
|
1129
|
+
hidden: entry.options.hidden,
|
|
1130
|
+
maxUsage: entry.options.maxUsage,
|
|
1131
|
+
minInterval: entry.options.minInterval,
|
|
1132
|
+
});
|
|
1133
|
+
ctx.emit('command-updated', entry.command);
|
|
1134
|
+
}
|
|
1002
1135
|
}
|
|
1003
1136
|
}
|
|
1004
1137
|
const declared = [];
|
|
@@ -1011,11 +1144,19 @@ function apply(ctx, config) {
|
|
|
1011
1144
|
removed.push(item.name);
|
|
1012
1145
|
}
|
|
1013
1146
|
else if (existing?.runtime === runtime) {
|
|
1014
|
-
existing.target =
|
|
1147
|
+
existing.target = existing.baseTarget;
|
|
1148
|
+
existing.options = existing.baseOptions;
|
|
1149
|
+
Object.assign(existing.command.config, {
|
|
1150
|
+
permissions: existing.options.permissions,
|
|
1151
|
+
hidden: existing.options.hidden,
|
|
1152
|
+
maxUsage: existing.options.maxUsage,
|
|
1153
|
+
minInterval: existing.options.minInterval,
|
|
1154
|
+
});
|
|
1155
|
+
ctx.emit('command-updated', existing.command);
|
|
1015
1156
|
}
|
|
1016
1157
|
continue;
|
|
1017
1158
|
}
|
|
1018
|
-
const reason = registerCommand(runtime, item.name, item.target, true, item.description);
|
|
1159
|
+
const reason = registerCommand(runtime, item.name, item.target, true, item.description, item.options);
|
|
1019
1160
|
if (reason) {
|
|
1020
1161
|
rejected.push({ name: item.name, reason });
|
|
1021
1162
|
}
|
|
@@ -1076,7 +1217,7 @@ function apply(ctx, config) {
|
|
|
1076
1217
|
logger.warn(`skip invalid command mapping target ${JSON.stringify(mapping.target)} from route ${route.name}`);
|
|
1077
1218
|
continue;
|
|
1078
1219
|
}
|
|
1079
|
-
const reason = registerCommand(runtime, sourceName, targetName, false, mapping.description);
|
|
1220
|
+
const reason = registerCommand(runtime, sourceName, targetName, false, mapping.description, normalizeCommandOptions(mapping, false));
|
|
1080
1221
|
if (reason) {
|
|
1081
1222
|
logger.warn(`skip command mapping ${sourceName} -> ${targetName} from route ${route.name}: ${reason}`);
|
|
1082
1223
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-msg-router",
|
|
3
|
-
"description": "Koishi OneBot
|
|
4
|
-
"version": "1.
|
|
3
|
+
"description": "Koishi OneBot 指令路由:支持手动/动态转译、权限与频率控制、双向 WebSocket 和 QQ Markdown",
|
|
4
|
+
"version": "1.4.2",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
},
|
|
35
35
|
"koishi": {
|
|
36
36
|
"description": {
|
|
37
|
-
"zh": "通过 OneBot
|
|
38
|
-
"en": "
|
|
37
|
+
"zh": "通过 OneBot 手动或动态声明并转译指定指令,可限制权限、频率、会话及平台,不影响其他指令,支持正反向 WebSocket 和 QQ Markdown",
|
|
38
|
+
"en": "Maps selected commands over OneBot with access controls, forward/reverse WebSocket, and QQ Markdown"
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
}
|
package/readme.md
CHANGED
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
一个面向 Koishi 的 OneBot v11 指令中转路由插件。插件仅接管配置中指定的指令,将会话转换为 OneBot v11 消息事件并通过 WebSocket 交给外部后端处理;未配置的指令保持 Koishi 原有处理流程,不受影响。
|
|
6
6
|
|
|
7
|
-
后端可以通过标准 OneBot API 动作向群聊或私聊回发文本、图片、语音等消息。针对 QQ 官方适配器,插件还支持原生 Markdown
|
|
7
|
+
后端可以通过标准 OneBot API 动作向群聊或私聊回发文本、图片、语音等消息。针对 QQ 官方适配器,插件还支持原生 Markdown 文本和 Markdown 键盘,并在不支持 Markdown 的平台上自动降级为普通文本。
|
|
8
8
|
|
|
9
9
|
## 主要用途
|
|
10
10
|
|
|
11
11
|
- **按需接管指令**:每条路由独立配置指令列表,只转发明确指定的指令
|
|
12
12
|
- **手动或动态转译**:可以在配置页维护指令映射,也可以由后端通过接口动态声明
|
|
13
|
+
- **Koishi 指令控制**:映射可配置权限、帮助菜单隐藏、调用频率、群聊/私聊范围和平台限制
|
|
13
14
|
- **OneBot v11 兼容**:向后端推送标准消息事件,并处理常用 OneBot API 动作
|
|
14
15
|
- **双向 WebSocket**:支持插件主动连接后端,也支持插件监听并等待后端连接
|
|
15
16
|
- **多路由配置**:不同指令可以连接不同后端,分别设置地址、鉴权和连接参数
|
|
@@ -26,6 +27,8 @@
|
|
|
26
27
|
- `enableCommandTranslation`:是否允许把对外指令转译为不同的后端目标指令
|
|
27
28
|
- `commandDeclarationAction`:动态声明接口的 action,默认 `msg_router.declare_commands`
|
|
28
29
|
- `maxDeclaredCommands`:每条路由最多允许动态声明的指令数量,默认 `100`
|
|
30
|
+
- `allowDynamicCommandPermissions`:是否允许后端自行声明权限规则,默认关闭
|
|
31
|
+
- `minimumDynamicAuthority`:动态指令的最低权限等级,默认 `1`;后端不得声明更低的 `authority` 权限
|
|
29
32
|
- `debug`:是否输出 WebSocket 收发、指令匹配和消息解析等调试信息
|
|
30
33
|
|
|
31
34
|
### 正向 WS 路由列表
|
|
@@ -177,10 +180,16 @@
|
|
|
177
180
|
| `source` | 用户实际执行、插件需要接管的指令 | `天气` |
|
|
178
181
|
| `target` | 转发给后端的目标指令 | `weather` |
|
|
179
182
|
| `description` | Koishi 指令系统中显示的说明 | `查询天气` |
|
|
183
|
+
| `permissions` | Koishi 权限规则 | `["authority:2"]` |
|
|
184
|
+
| `hidden` | 是否从帮助菜单隐藏 | `false` |
|
|
185
|
+
| `maxUsage` | 每名用户每天最多调用次数,`0` 不限制 | `20` |
|
|
186
|
+
| `minInterval` | 同一用户连续调用的最小间隔(毫秒),`0` 不限制 | `3000` |
|
|
187
|
+
| `scope` | `all`、`group` 或 `private` | `group` |
|
|
188
|
+
| `platforms` | 允许的平台;空数组不限制 | `["qq"]` |
|
|
180
189
|
|
|
181
190
|
配置后,用户执行 `天气 上海`,后端会收到 `weather 上海`。映射可以独立使用,不需要再把 `天气` 重复填写到 `commands`;如果两处都填写,`commandMappings` 中的目标指令优先。单条映射可以使用 `enabled` 独立关闭。
|
|
182
191
|
|
|
183
|
-
全局关闭 `enableCommandTranslation`
|
|
192
|
+
全局关闭 `enableCommandTranslation` 后,手动映射和动态声明仍会注册源指令,但不再替换指令名。`permissions` 使用 Koishi 原生权限服务;`hidden` 需要 help 插件,`maxUsage` 和 `minInterval` 需要 rate-limit 插件及数据库服务才能生效。
|
|
184
193
|
|
|
185
194
|
### 动态声明与指令转译接口
|
|
186
195
|
|
|
@@ -197,7 +206,13 @@
|
|
|
197
206
|
{
|
|
198
207
|
"name": "天气",
|
|
199
208
|
"target": "weather",
|
|
200
|
-
"description": "查询天气"
|
|
209
|
+
"description": "查询天气",
|
|
210
|
+
"permissions": ["authority:2"],
|
|
211
|
+
"hidden": false,
|
|
212
|
+
"maxUsage": 20,
|
|
213
|
+
"minInterval": 3000,
|
|
214
|
+
"scope": "group",
|
|
215
|
+
"platforms": ["qq"]
|
|
201
216
|
},
|
|
202
217
|
{
|
|
203
218
|
"name": "菜单",
|
|
@@ -233,13 +248,16 @@
|
|
|
233
248
|
- 字符串声明(例如 `"ping"`)表示不转译,源指令和目标指令相同
|
|
234
249
|
- `replace` 默认为 `true`,会移除该路由之前动态声明、但本次未再次声明的指令
|
|
235
250
|
- `replace: false` 表示增量更新;单项设置 `enabled: false` 可以移除动态指令
|
|
251
|
+
- 动态声明支持与手动映射相同的 `permissions`、`hidden`、`maxUsage`、`minInterval`、`scope` 和 `platforms`
|
|
252
|
+
- 默认不允许后端决定权限,动态指令统一采用 `minimumDynamicAuthority`;只有显式开启 `allowDynamicCommandPermissions` 后才读取后端提交的 `permissions`
|
|
253
|
+
- 即使允许动态权限,低于 `minimumDynamicAuthority` 的 `authority` 规则仍会被拒绝;只声明自定义权限时,插件也会自动附加最低 `authority` 规则
|
|
236
254
|
- 关闭 `enableCommandTranslation` 后仍允许动态声明,但会忽略 `target`,按原指令转发
|
|
237
255
|
- 插件不会覆盖其他 Koishi 插件已经注册的指令,冲突项会出现在 `rejected` 中
|
|
238
256
|
- 只配置动态声明、不填写静态 `commands` 的路由也会启动并等待后端声明
|
|
239
257
|
|
|
240
|
-
### QQ Markdown
|
|
258
|
+
### QQ Markdown 与键盘
|
|
241
259
|
|
|
242
|
-
后端通过 `send_group_msg`、`send_private_msg` 或 `send_msg` 回发消息时,可以使用 QQ 原生 Markdown 文本。插件在 QQ 官方适配器上会直接提交 `markdown.content`;在其他平台上会把同一内容作为普通文本降级发送。
|
|
260
|
+
后端通过 `send_group_msg`、`send_private_msg` 或 `send_msg` 回发消息时,可以使用 QQ 原生 Markdown 文本。插件在 QQ 官方适配器上会直接提交 `markdown.content`,并透传 `keyboard`;在其他平台上会把同一内容作为普通文本降级发送。
|
|
243
261
|
|
|
244
262
|
此功能可通过配置页面的 `enableQQMarkdown` 开关控制。关闭后不会调用 QQ 原生 Markdown 接口,而是把 `content` 或 `fallback_text` 作为普通消息发送。
|
|
245
263
|
|
|
@@ -253,7 +271,12 @@
|
|
|
253
271
|
"type": "markdown",
|
|
254
272
|
"data": {
|
|
255
273
|
"content": "# 标题\n**加粗内容**\n[查看详情](https://example.com)",
|
|
256
|
-
"fallback_text": "标题\n加粗内容\nhttps://example.com"
|
|
274
|
+
"fallback_text": "标题\n加粗内容\nhttps://example.com",
|
|
275
|
+
"keyboard": {
|
|
276
|
+
"content": {
|
|
277
|
+
"rows": []
|
|
278
|
+
}
|
|
279
|
+
}
|
|
257
280
|
}
|
|
258
281
|
}
|
|
259
282
|
]
|