@zhin.js/core 1.1.0 → 1.1.3
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/adapter.d.ts +1 -26
- package/lib/adapter.d.ts.map +1 -1
- package/lib/adapter.js +20 -117
- package/lib/adapter.js.map +1 -1
- package/lib/built/adapter-process.d.ts +0 -4
- package/lib/built/adapter-process.d.ts.map +1 -1
- package/lib/built/adapter-process.js +0 -95
- package/lib/built/adapter-process.js.map +1 -1
- package/lib/built/agent-preset.d.ts +2 -0
- package/lib/built/agent-preset.d.ts.map +1 -1
- package/lib/built/agent-preset.js +4 -0
- package/lib/built/agent-preset.js.map +1 -1
- package/lib/built/command.d.ts +4 -0
- package/lib/built/command.d.ts.map +1 -1
- package/lib/built/command.js +6 -0
- package/lib/built/command.js.map +1 -1
- package/lib/built/component.d.ts.map +1 -1
- package/lib/built/component.js +1 -0
- package/lib/built/component.js.map +1 -1
- package/lib/built/dispatcher.d.ts.map +1 -1
- package/lib/built/dispatcher.js +0 -13
- package/lib/built/dispatcher.js.map +1 -1
- package/lib/built/message-filter.d.ts +2 -0
- package/lib/built/message-filter.d.ts.map +1 -1
- package/lib/built/message-filter.js +5 -0
- package/lib/built/message-filter.js.map +1 -1
- package/lib/built/skill.d.ts +11 -0
- package/lib/built/skill.d.ts.map +1 -1
- package/lib/built/skill.js +14 -0
- package/lib/built/skill.js.map +1 -1
- package/lib/built/tool.d.ts +11 -44
- package/lib/built/tool.d.ts.map +1 -1
- package/lib/built/tool.js +14 -353
- package/lib/built/tool.js.map +1 -1
- package/lib/plugin.d.ts +1 -25
- package/lib/plugin.d.ts.map +1 -1
- package/lib/plugin.js +1 -77
- package/lib/plugin.js.map +1 -1
- package/lib/types.d.ts +0 -25
- package/lib/types.d.ts.map +1 -1
- package/package.json +10 -7
- package/CHANGELOG.md +0 -561
- package/REFACTORING_COMPLETE.md +0 -178
- package/REFACTORING_STATUS.md +0 -263
- package/src/adapter.ts +0 -275
- package/src/ai/index.ts +0 -55
- package/src/ai/providers/anthropic.ts +0 -379
- package/src/ai/providers/base.ts +0 -175
- package/src/ai/providers/index.ts +0 -13
- package/src/ai/providers/ollama.ts +0 -302
- package/src/ai/providers/openai.ts +0 -174
- package/src/ai/types.ts +0 -348
- package/src/bot.ts +0 -37
- package/src/built/adapter-process.ts +0 -177
- package/src/built/agent-preset.ts +0 -136
- package/src/built/ai-trigger.ts +0 -259
- package/src/built/command.ts +0 -108
- package/src/built/common-adapter-tools.ts +0 -242
- package/src/built/component.ts +0 -130
- package/src/built/config.ts +0 -335
- package/src/built/cron.ts +0 -156
- package/src/built/database.ts +0 -134
- package/src/built/dispatcher.ts +0 -496
- package/src/built/login-assist.ts +0 -131
- package/src/built/message-filter.ts +0 -390
- package/src/built/permission.ts +0 -151
- package/src/built/schema-feature.ts +0 -190
- package/src/built/skill.ts +0 -221
- package/src/built/tool.ts +0 -948
- package/src/command.ts +0 -87
- package/src/component.ts +0 -565
- package/src/cron.ts +0 -4
- package/src/errors.ts +0 -46
- package/src/feature.ts +0 -7
- package/src/index.ts +0 -53
- package/src/jsx-dev-runtime.ts +0 -2
- package/src/jsx-runtime.ts +0 -12
- package/src/jsx.ts +0 -135
- package/src/message.ts +0 -48
- package/src/models/system-log.ts +0 -20
- package/src/models/user.ts +0 -15
- package/src/notice.ts +0 -98
- package/src/plugin.ts +0 -896
- package/src/prompt.ts +0 -293
- package/src/request.ts +0 -95
- package/src/scheduler/index.ts +0 -19
- package/src/scheduler/scheduler.ts +0 -372
- package/src/scheduler/types.ts +0 -74
- package/src/tool-zod.ts +0 -115
- package/src/types-generator.ts +0 -78
- package/src/types.ts +0 -505
- package/src/utils.ts +0 -227
- package/tests/adapter.test.ts +0 -638
- package/tests/ai/ai-trigger.test.ts +0 -368
- package/tests/ai/providers.integration.test.ts +0 -227
- package/tests/ai/setup.ts +0 -308
- package/tests/ai/tool.test.ts +0 -800
- package/tests/bot.test.ts +0 -151
- package/tests/command.test.ts +0 -737
- package/tests/component-new.test.ts +0 -361
- package/tests/config.test.ts +0 -372
- package/tests/cron.test.ts +0 -82
- package/tests/dispatcher.test.ts +0 -293
- package/tests/errors.test.ts +0 -21
- package/tests/expression-evaluation.test.ts +0 -258
- package/tests/features-builtin.test.ts +0 -191
- package/tests/jsx-runtime.test.ts +0 -45
- package/tests/jsx.test.ts +0 -319
- package/tests/message-filter.test.ts +0 -566
- package/tests/message.test.ts +0 -402
- package/tests/notice.test.ts +0 -198
- package/tests/plugin.test.ts +0 -779
- package/tests/prompt.test.ts +0 -78
- package/tests/redos-protection.test.ts +0 -198
- package/tests/request.test.ts +0 -221
- package/tests/schema.test.ts +0 -248
- package/tests/skill-feature.test.ts +0 -179
- package/tests/test-utils.ts +0 -59
- package/tests/tool-feature.test.ts +0 -254
- package/tests/types.test.ts +0 -162
- package/tests/utils.test.ts +0 -135
- package/tsconfig.json +0 -24
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LoginAssist — 登录辅助生产者-消费者
|
|
3
|
-
*
|
|
4
|
-
* 适配器在需要人为辅助登录时(扫码、短信、滑块等)作为生产者投递待办;
|
|
5
|
-
* Web 控制台、命令行等作为消费者拉取待办并提交结果。
|
|
6
|
-
* 待办未消费前会一直保留,刷新页面后仍可继续消费。
|
|
7
|
-
*
|
|
8
|
-
* 事件:
|
|
9
|
-
* bot.login.pending — 有新待办时触发,payload: PendingLoginTask
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import type { Plugin } from '../plugin.js';
|
|
13
|
-
|
|
14
|
-
// ============================================================================
|
|
15
|
-
// 类型
|
|
16
|
-
// ============================================================================
|
|
17
|
-
|
|
18
|
-
export type LoginAssistType = 'qrcode' | 'sms' | 'slider' | 'device' | 'other';
|
|
19
|
-
|
|
20
|
-
export interface PendingLoginTaskPayload {
|
|
21
|
-
/** 说明文案(如「请扫码登录」) */
|
|
22
|
-
message?: string;
|
|
23
|
-
/** 二维码图片 URL 或 base64(type=qrcode 时) */
|
|
24
|
-
image?: string;
|
|
25
|
-
/** 滑块验证 URL(type=slider 时) */
|
|
26
|
-
url?: string;
|
|
27
|
-
/** 其它扩展字段 */
|
|
28
|
-
[key: string]: unknown;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface PendingLoginTask {
|
|
32
|
-
id: string;
|
|
33
|
-
adapter: string;
|
|
34
|
-
botId: string;
|
|
35
|
-
type: LoginAssistType;
|
|
36
|
-
payload: PendingLoginTaskPayload;
|
|
37
|
-
createdAt: number;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
interface PendingEntry {
|
|
41
|
-
task: PendingLoginTask;
|
|
42
|
-
resolve: (value: string | Record<string, unknown>) => void;
|
|
43
|
-
reject: (err: Error) => void;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// ============================================================================
|
|
47
|
-
// LoginAssist 服务
|
|
48
|
-
// ============================================================================
|
|
49
|
-
|
|
50
|
-
export class LoginAssist {
|
|
51
|
-
private readonly plugin: Plugin;
|
|
52
|
-
private readonly pending = new Map<string, PendingEntry>();
|
|
53
|
-
private idSeq = 0;
|
|
54
|
-
|
|
55
|
-
constructor(plugin: Plugin) {
|
|
56
|
-
this.plugin = plugin;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* 生产者:等待用户输入后 resolve。会发出 bot.login.pending 事件,未消费前可被 listPending 拉取(刷新后可继续消费)。
|
|
61
|
-
*/
|
|
62
|
-
waitForInput(
|
|
63
|
-
adapter: string,
|
|
64
|
-
botId: string,
|
|
65
|
-
type: LoginAssistType,
|
|
66
|
-
payload: PendingLoginTaskPayload = {},
|
|
67
|
-
): Promise<string | Record<string, unknown>> {
|
|
68
|
-
const id = `login-${Date.now()}-${++this.idSeq}`;
|
|
69
|
-
const task: PendingLoginTask = {
|
|
70
|
-
id,
|
|
71
|
-
adapter,
|
|
72
|
-
botId,
|
|
73
|
-
type,
|
|
74
|
-
payload: { message: payload.message ?? '', ...payload },
|
|
75
|
-
createdAt: Date.now(),
|
|
76
|
-
};
|
|
77
|
-
const promise = new Promise<string | Record<string, unknown>>((resolve, reject) => {
|
|
78
|
-
this.pending.set(id, { task, resolve, reject });
|
|
79
|
-
this.plugin.emit('bot.login.pending', task);
|
|
80
|
-
});
|
|
81
|
-
return promise;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* 消费者:提交结果,对应 waitForInput 的 Promise 会 resolve。
|
|
86
|
-
*/
|
|
87
|
-
submit(id: string, value: string | Record<string, unknown>): boolean {
|
|
88
|
-
const entry = this.pending.get(id);
|
|
89
|
-
if (!entry) return false;
|
|
90
|
-
this.pending.delete(id);
|
|
91
|
-
entry.resolve(value);
|
|
92
|
-
return true;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* 消费者:取消某条待办,对应 Promise 会 reject。
|
|
97
|
-
*/
|
|
98
|
-
cancel(id: string, reason = 'cancelled'): boolean {
|
|
99
|
-
const entry = this.pending.get(id);
|
|
100
|
-
if (!entry) return false;
|
|
101
|
-
this.pending.delete(id);
|
|
102
|
-
entry.reject(new Error(reason));
|
|
103
|
-
return true;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* 列出未消费的待办(供 Web/CLI 展示,刷新后仍可拉取同一列表)。
|
|
108
|
-
*/
|
|
109
|
-
listPending(): PendingLoginTask[] {
|
|
110
|
-
return [...this.pending.values()].map((e) => e.task);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
dispose(): void {
|
|
114
|
-
for (const [, entry] of this.pending) {
|
|
115
|
-
entry.reject(new Error('LoginAssist disposed'));
|
|
116
|
-
}
|
|
117
|
-
this.pending.clear();
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// ============================================================================
|
|
122
|
-
// 扩展 Plugin.Contexts
|
|
123
|
-
// ============================================================================
|
|
124
|
-
|
|
125
|
-
declare module '../plugin.js' {
|
|
126
|
-
namespace Plugin {
|
|
127
|
-
interface Contexts {
|
|
128
|
-
loginAssist: LoginAssist;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
@@ -1,390 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MessageFilterFeature — 消息过滤引擎
|
|
3
|
-
*
|
|
4
|
-
* 设计理念:
|
|
5
|
-
* 将过滤规则视为 Feature Item,与命令 (CommandFeature)、权限 (PermissionFeature) 同构,
|
|
6
|
-
* 遵循框架的 add/remove/extensions/toJSON 范式,支持插件级 CRUD 和生命周期自动回收。
|
|
7
|
-
*
|
|
8
|
-
* 核心特性:
|
|
9
|
-
* - 基于优先级的规则引擎(first-match-wins,类似防火墙规则)
|
|
10
|
-
* - 多维匹配:scope / adapter / bot / channel / sender
|
|
11
|
-
* - 支持精确匹配、通配符 `*`、正则 `/pattern/`
|
|
12
|
-
* - 通过 Dispatcher Guardrail 集成,在消息调度第一阶段拦截
|
|
13
|
-
* - 插件通过 `addFilterRule()` 动态注册规则,卸载时自动清理
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { Feature, FeatureJSON } from '../feature.js';
|
|
17
|
-
import { getPlugin } from '../plugin.js';
|
|
18
|
-
import type { Message, MessageType } from '../message.js';
|
|
19
|
-
|
|
20
|
-
// ============================================================================
|
|
21
|
-
// 类型定义
|
|
22
|
-
// ============================================================================
|
|
23
|
-
|
|
24
|
-
/** 过滤动作 */
|
|
25
|
-
export type FilterAction = 'allow' | 'deny';
|
|
26
|
-
|
|
27
|
-
/** 匹配模式:精确字符串 / 正则表达式 */
|
|
28
|
-
export type FilterPattern = string | RegExp;
|
|
29
|
-
|
|
30
|
-
/** 消息作用域 */
|
|
31
|
-
export type FilterScope = MessageType; // 'private' | 'group' | 'channel'
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* 过滤规则
|
|
35
|
-
*
|
|
36
|
-
* 每条规则描述一组匹配条件和对应的动作。
|
|
37
|
-
* 所有条件之间为 AND 关系(必须全部满足),每个条件内部的多个值为 OR 关系(满足任一即可)。
|
|
38
|
-
* 未设置的条件视为匹配所有。
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* // 拒绝特定群的消息
|
|
42
|
-
* { name: 'block-spam', action: 'deny', scopes: ['group'], channels: ['123456'] }
|
|
43
|
-
*
|
|
44
|
-
* // 放行 VIP 用户(高优先级)
|
|
45
|
-
* { name: 'allow-vip', action: 'allow', priority: 100, senders: ['admin001'] }
|
|
46
|
-
*
|
|
47
|
-
* // 正则匹配:拒绝所有 test- 开头的频道
|
|
48
|
-
* { name: 'block-test', action: 'deny', scopes: ['channel'], channels: [/^test-/] }
|
|
49
|
-
*/
|
|
50
|
-
export interface FilterRule {
|
|
51
|
-
/** 规则名称(唯一标识) */
|
|
52
|
-
name: string;
|
|
53
|
-
/** 规则描述 */
|
|
54
|
-
description?: string;
|
|
55
|
-
/** 过滤动作:allow 放行 / deny 拦截 */
|
|
56
|
-
action: FilterAction;
|
|
57
|
-
/** 优先级,数值越大越先匹配(默认 0) */
|
|
58
|
-
priority?: number;
|
|
59
|
-
/** 是否启用(默认 true) */
|
|
60
|
-
enabled?: boolean;
|
|
61
|
-
/** 匹配的消息作用域,未设置则匹配所有 */
|
|
62
|
-
scopes?: FilterScope[];
|
|
63
|
-
/** 匹配的适配器名称 */
|
|
64
|
-
adapters?: FilterPattern[];
|
|
65
|
-
/** 匹配的 Bot 名称 */
|
|
66
|
-
bots?: FilterPattern[];
|
|
67
|
-
/** 匹配的频道/群/会话 ID */
|
|
68
|
-
channels?: FilterPattern[];
|
|
69
|
-
/** 匹配的发送者 ID */
|
|
70
|
-
senders?: FilterPattern[];
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** 过滤检测结果 */
|
|
74
|
-
export interface FilterResult {
|
|
75
|
-
/** 是否允许处理 */
|
|
76
|
-
allowed: boolean;
|
|
77
|
-
/** 匹配到的规则名称(null 表示无规则匹配,走默认策略) */
|
|
78
|
-
matchedRule: string | null;
|
|
79
|
-
/** 结果原因 */
|
|
80
|
-
reason: string;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// ---- 配置类型 ----
|
|
84
|
-
|
|
85
|
-
/** 配置文件中的规则(pattern 均为 string,正则用 /pattern/ 表示) */
|
|
86
|
-
export interface FilterRuleConfig {
|
|
87
|
-
name: string;
|
|
88
|
-
description?: string;
|
|
89
|
-
action: FilterAction;
|
|
90
|
-
priority?: number;
|
|
91
|
-
enabled?: boolean;
|
|
92
|
-
scopes?: FilterScope[];
|
|
93
|
-
adapters?: string[];
|
|
94
|
-
bots?: string[];
|
|
95
|
-
channels?: string[];
|
|
96
|
-
senders?: string[];
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* 消息过滤配置
|
|
101
|
-
*
|
|
102
|
-
* ```yaml
|
|
103
|
-
* message_filter:
|
|
104
|
-
* default_policy: allow
|
|
105
|
-
* rules:
|
|
106
|
-
* - name: block-spam
|
|
107
|
-
* action: deny
|
|
108
|
-
* scopes: [group]
|
|
109
|
-
* channels: ['123456']
|
|
110
|
-
* - name: vip-pass
|
|
111
|
-
* action: allow
|
|
112
|
-
* priority: 100
|
|
113
|
-
* senders: ['admin001']
|
|
114
|
-
* ```
|
|
115
|
-
*/
|
|
116
|
-
export interface MessageFilterConfig {
|
|
117
|
-
/** 默认策略:无规则匹配时的行为(默认 'allow') */
|
|
118
|
-
default_policy?: FilterAction;
|
|
119
|
-
/** 规则列表 */
|
|
120
|
-
rules?: FilterRuleConfig[];
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// ============================================================================
|
|
124
|
-
// Plugin 扩展声明
|
|
125
|
-
// ============================================================================
|
|
126
|
-
|
|
127
|
-
export interface MessageFilterContextExtensions {
|
|
128
|
-
/** 添加过滤规则,返回 dispose 函数 */
|
|
129
|
-
addFilterRule(rule: FilterRule): () => void;
|
|
130
|
-
/** 检测消息是否应被处理 */
|
|
131
|
-
testFilter(message: Message<any>): FilterResult;
|
|
132
|
-
/** 设置默认过滤策略 */
|
|
133
|
-
setDefaultFilterPolicy(policy: FilterAction): void;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
declare module '../plugin.js' {
|
|
137
|
-
namespace Plugin {
|
|
138
|
-
interface Extensions extends MessageFilterContextExtensions {}
|
|
139
|
-
interface Contexts {
|
|
140
|
-
'message-filter': MessageFilterFeature;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// ============================================================================
|
|
146
|
-
// 规则工厂
|
|
147
|
-
// ============================================================================
|
|
148
|
-
|
|
149
|
-
export namespace FilterRules {
|
|
150
|
-
/** 创建拒绝规则 */
|
|
151
|
-
export function deny(name: string, conditions: Omit<FilterRule, 'name' | 'action'>): FilterRule {
|
|
152
|
-
return { ...conditions, name, action: 'deny' };
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/** 创建放行规则 */
|
|
156
|
-
export function allow(name: string, conditions: Omit<FilterRule, 'name' | 'action'>): FilterRule {
|
|
157
|
-
return { ...conditions, name, action: 'allow' };
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* 创建黑名单规则组
|
|
162
|
-
* @returns 一条 deny 规则
|
|
163
|
-
*/
|
|
164
|
-
export function blacklist(
|
|
165
|
-
scope: FilterScope,
|
|
166
|
-
ids: string[],
|
|
167
|
-
name?: string,
|
|
168
|
-
): FilterRule {
|
|
169
|
-
return {
|
|
170
|
-
name: name ?? `${scope}-blacklist`,
|
|
171
|
-
action: 'deny',
|
|
172
|
-
scopes: [scope],
|
|
173
|
-
channels: ids,
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* 创建白名单规则组
|
|
179
|
-
* @returns [allow 规则, deny-catch-all 规则]
|
|
180
|
-
*/
|
|
181
|
-
export function whitelist(
|
|
182
|
-
scope: FilterScope,
|
|
183
|
-
ids: string[],
|
|
184
|
-
name?: string,
|
|
185
|
-
): [FilterRule, FilterRule] {
|
|
186
|
-
const baseName = name ?? `${scope}-whitelist`;
|
|
187
|
-
return [
|
|
188
|
-
{ name: baseName, action: 'allow', scopes: [scope], channels: ids, priority: 1 },
|
|
189
|
-
{ name: `${baseName}-deny-rest`, action: 'deny', scopes: [scope], priority: -100 },
|
|
190
|
-
];
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// ============================================================================
|
|
195
|
-
// Feature 实现
|
|
196
|
-
// ============================================================================
|
|
197
|
-
|
|
198
|
-
export class MessageFilterFeature extends Feature<FilterRule> {
|
|
199
|
-
readonly name = 'message-filter' as const;
|
|
200
|
-
readonly icon = 'Filter';
|
|
201
|
-
readonly desc = '消息过滤';
|
|
202
|
-
|
|
203
|
-
/** 按规则名称索引 */
|
|
204
|
-
readonly byName = new Map<string, FilterRule>();
|
|
205
|
-
|
|
206
|
-
#defaultPolicy: FilterAction = 'allow';
|
|
207
|
-
#sortedCache: FilterRule[] | null = null;
|
|
208
|
-
|
|
209
|
-
constructor(config?: MessageFilterConfig) {
|
|
210
|
-
super();
|
|
211
|
-
if (config) this.#loadConfig(config);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// ---- 公共 API ----
|
|
215
|
-
|
|
216
|
-
/** 默认策略 */
|
|
217
|
-
get defaultPolicy(): FilterAction { return this.#defaultPolicy; }
|
|
218
|
-
set defaultPolicy(policy: FilterAction) { this.#defaultPolicy = policy; }
|
|
219
|
-
|
|
220
|
-
/** 获取按优先级排序的启用规则列表(带缓存) */
|
|
221
|
-
get sortedRules(): FilterRule[] {
|
|
222
|
-
if (!this.#sortedCache) {
|
|
223
|
-
this.#sortedCache = [...this.items]
|
|
224
|
-
.filter(r => r.enabled !== false)
|
|
225
|
-
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
226
|
-
}
|
|
227
|
-
return this.#sortedCache;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/** 按名称查询规则 */
|
|
231
|
-
getRule(name: string): FilterRule | undefined {
|
|
232
|
-
return this.byName.get(name);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* 检测消息是否应该被处理
|
|
237
|
-
*
|
|
238
|
-
* 遍历按优先级排序的规则列表,返回第一个匹配的规则结果。
|
|
239
|
-
* 若无规则匹配,按默认策略决定。
|
|
240
|
-
*/
|
|
241
|
-
test(message: Message<any>): FilterResult {
|
|
242
|
-
for (const rule of this.sortedRules) {
|
|
243
|
-
if (this.#matchRule(rule, message)) {
|
|
244
|
-
return {
|
|
245
|
-
allowed: rule.action === 'allow',
|
|
246
|
-
matchedRule: rule.name,
|
|
247
|
-
reason: `matched rule "${rule.name}" → ${rule.action}`,
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
return {
|
|
252
|
-
allowed: this.#defaultPolicy === 'allow',
|
|
253
|
-
matchedRule: null,
|
|
254
|
-
reason: `no rule matched → default policy: ${this.#defaultPolicy}`,
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// ---- Feature 重写 ----
|
|
259
|
-
|
|
260
|
-
/** 添加规则,维护 byName 索引 */
|
|
261
|
-
add(rule: FilterRule, pluginName: string): () => void {
|
|
262
|
-
this.byName.set(rule.name, rule);
|
|
263
|
-
this.#sortedCache = null;
|
|
264
|
-
return super.add(rule, pluginName);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/** 移除规则,清理 byName 索引 */
|
|
268
|
-
remove(rule: FilterRule, pluginName?: string): boolean {
|
|
269
|
-
this.byName.delete(rule.name);
|
|
270
|
-
this.#sortedCache = null;
|
|
271
|
-
return super.remove(rule, pluginName);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/** 序列化为 JSON(供 Web 控制台展示) */
|
|
275
|
-
toJSON(pluginName?: string): FeatureJSON {
|
|
276
|
-
const list = pluginName ? this.getByPlugin(pluginName) : this.items;
|
|
277
|
-
return {
|
|
278
|
-
name: this.name,
|
|
279
|
-
icon: this.icon,
|
|
280
|
-
desc: this.desc,
|
|
281
|
-
count: list.length,
|
|
282
|
-
items: list.map(r => ({
|
|
283
|
-
name: r.name,
|
|
284
|
-
description: r.description,
|
|
285
|
-
action: r.action,
|
|
286
|
-
priority: r.priority ?? 0,
|
|
287
|
-
enabled: r.enabled !== false,
|
|
288
|
-
scopes: r.scopes,
|
|
289
|
-
adapters: r.adapters?.map(p => p instanceof RegExp ? p.source : p),
|
|
290
|
-
bots: r.bots?.map(p => p instanceof RegExp ? p.source : p),
|
|
291
|
-
channels: r.channels?.map(p => p instanceof RegExp ? p.source : p),
|
|
292
|
-
senders: r.senders?.map(p => p instanceof RegExp ? p.source : p),
|
|
293
|
-
})),
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/** 插件扩展方法 */
|
|
298
|
-
get extensions() {
|
|
299
|
-
const feature = this;
|
|
300
|
-
return {
|
|
301
|
-
addFilterRule(rule: FilterRule) {
|
|
302
|
-
const plugin = getPlugin();
|
|
303
|
-
const dispose = feature.add(rule, plugin.name);
|
|
304
|
-
plugin.recordFeatureContribution(feature.name, rule.name);
|
|
305
|
-
plugin.onDispose(dispose);
|
|
306
|
-
return dispose;
|
|
307
|
-
},
|
|
308
|
-
testFilter(message: Message<any>) {
|
|
309
|
-
return feature.test(message);
|
|
310
|
-
},
|
|
311
|
-
setDefaultFilterPolicy(policy: FilterAction) {
|
|
312
|
-
feature.defaultPolicy = policy;
|
|
313
|
-
},
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// ============================================================================
|
|
318
|
-
// 配置加载
|
|
319
|
-
// ============================================================================
|
|
320
|
-
|
|
321
|
-
#loadConfig(config: MessageFilterConfig): void {
|
|
322
|
-
if (config.default_policy) {
|
|
323
|
-
this.#defaultPolicy = config.default_policy;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
if (config.rules) {
|
|
327
|
-
for (const rc of config.rules) {
|
|
328
|
-
this.add(this.#parseRuleConfig(rc), '__config__');
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// ============================================================================
|
|
334
|
-
// 规则匹配
|
|
335
|
-
// ============================================================================
|
|
336
|
-
|
|
337
|
-
/** 解析配置中的规则字符串为 FilterPattern */
|
|
338
|
-
#parseRuleConfig(rc: FilterRuleConfig): FilterRule {
|
|
339
|
-
return {
|
|
340
|
-
name: rc.name,
|
|
341
|
-
description: rc.description,
|
|
342
|
-
action: rc.action,
|
|
343
|
-
priority: rc.priority,
|
|
344
|
-
enabled: rc.enabled,
|
|
345
|
-
scopes: rc.scopes,
|
|
346
|
-
adapters: rc.adapters?.map(p => this.#parsePattern(p)),
|
|
347
|
-
bots: rc.bots?.map(p => this.#parsePattern(p)),
|
|
348
|
-
channels: rc.channels?.map(p => this.#parsePattern(p)),
|
|
349
|
-
senders: rc.senders?.map(p => this.#parsePattern(p)),
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/** 解析单个 pattern:"/regex/flags" → RegExp,其余为精确字符串 */
|
|
354
|
-
#parsePattern(pattern: string): FilterPattern {
|
|
355
|
-
const match = pattern.match(/^\/(.+)\/([gimsuy]*)$/);
|
|
356
|
-
if (match) {
|
|
357
|
-
return new RegExp(match[1], match[2]);
|
|
358
|
-
}
|
|
359
|
-
return pattern;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
/** 检查规则是否匹配消息(所有条件 AND,条件内 OR) */
|
|
363
|
-
#matchRule(rule: FilterRule, message: Message<any>): boolean {
|
|
364
|
-
if (rule.scopes?.length && !rule.scopes.includes(message.$channel.type as FilterScope)) {
|
|
365
|
-
return false;
|
|
366
|
-
}
|
|
367
|
-
if (rule.adapters?.length && !this.#matchAny(rule.adapters, String(message.$adapter))) {
|
|
368
|
-
return false;
|
|
369
|
-
}
|
|
370
|
-
if (rule.bots?.length && !this.#matchAny(rule.bots, String(message.$bot))) {
|
|
371
|
-
return false;
|
|
372
|
-
}
|
|
373
|
-
if (rule.channels?.length && !this.#matchAny(rule.channels, String(message.$channel.id))) {
|
|
374
|
-
return false;
|
|
375
|
-
}
|
|
376
|
-
if (rule.senders?.length && !this.#matchAny(rule.senders, String(message.$sender.id))) {
|
|
377
|
-
return false;
|
|
378
|
-
}
|
|
379
|
-
return true;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
/** 检查值是否与任一 pattern 匹配 */
|
|
383
|
-
#matchAny(patterns: FilterPattern[], value: string): boolean {
|
|
384
|
-
return patterns.some(p => {
|
|
385
|
-
if (p instanceof RegExp) return p.test(value);
|
|
386
|
-
if (p === '*') return true;
|
|
387
|
-
return p === value;
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
|
-
}
|
package/src/built/permission.ts
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PermissionFeature
|
|
3
|
-
* 权限管理服务,继承自 Feature 抽象类
|
|
4
|
-
*/
|
|
5
|
-
import { Feature, FeatureJSON } from "../feature.js";
|
|
6
|
-
import { getPlugin } from "../plugin.js";
|
|
7
|
-
import type { MaybePromise, RegisteredAdapter, AdapterMessage } from "../types.js";
|
|
8
|
-
import { Message as MessageClass } from "../message.js";
|
|
9
|
-
|
|
10
|
-
export type PermissionItem<T extends RegisteredAdapter = RegisteredAdapter> = {
|
|
11
|
-
name: string | RegExp;
|
|
12
|
-
check: PermissionChecker<T>;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export type PermissionChecker<T extends RegisteredAdapter = RegisteredAdapter> = (
|
|
16
|
-
name: string,
|
|
17
|
-
message: MessageClass<AdapterMessage<T>>
|
|
18
|
-
) => MaybePromise<boolean>;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* PermissionFeature 扩展方法类型
|
|
22
|
-
*/
|
|
23
|
-
export interface PermissionContextExtensions {
|
|
24
|
-
addPermission(permission: PermissionItem): () => void;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
declare module "../plugin.js" {
|
|
28
|
-
namespace Plugin {
|
|
29
|
-
interface Extensions extends PermissionContextExtensions {}
|
|
30
|
-
interface Contexts {
|
|
31
|
-
permission: PermissionFeature;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export class PermissionFeature extends Feature<PermissionItem> {
|
|
37
|
-
readonly name = 'permission' as const;
|
|
38
|
-
readonly icon = 'Shield';
|
|
39
|
-
readonly desc = '权限';
|
|
40
|
-
|
|
41
|
-
constructor() {
|
|
42
|
-
super();
|
|
43
|
-
// 注册默认权限检查器(使用 'built-in' 作为插件名)
|
|
44
|
-
this.add(
|
|
45
|
-
Permissions.define(/^adapter\([^)]+\)$/, (name, message) => {
|
|
46
|
-
return message.$adapter === name.replace(/^adapter\(([^)]+)\)$/, '$1');
|
|
47
|
-
}),
|
|
48
|
-
'__built-in__',
|
|
49
|
-
);
|
|
50
|
-
this.add(
|
|
51
|
-
Permissions.define(/^group\([^)]+\)$/, (name, message) => {
|
|
52
|
-
const match = name.match(/^group\(([^)]+)\)$/);
|
|
53
|
-
if (!match) return false;
|
|
54
|
-
const id = match[1];
|
|
55
|
-
if (message.$channel.type !== 'group') return false;
|
|
56
|
-
if (id === '' || id === '*') return true;
|
|
57
|
-
return message.$channel.id === id;
|
|
58
|
-
}),
|
|
59
|
-
'__built-in__',
|
|
60
|
-
);
|
|
61
|
-
this.add(
|
|
62
|
-
Permissions.define(/^private\([^)]+\)$/, (name, message) => {
|
|
63
|
-
const match = name.match(/^private\(([^)]+)\)$/);
|
|
64
|
-
if (!match) return false;
|
|
65
|
-
const id = match[1];
|
|
66
|
-
if (message.$channel.type !== 'private') return false;
|
|
67
|
-
if (id === '' || id === '*') return true;
|
|
68
|
-
return message.$channel.id === id;
|
|
69
|
-
}),
|
|
70
|
-
'__built-in__',
|
|
71
|
-
);
|
|
72
|
-
this.add(
|
|
73
|
-
Permissions.define(/^channel\([^)]+\)$/, (name, message) => {
|
|
74
|
-
const match = name.match(/^channel\(([^)]+)\)$/);
|
|
75
|
-
if (!match) return false;
|
|
76
|
-
const id = match[1];
|
|
77
|
-
if (message.$channel.type !== 'channel') return false;
|
|
78
|
-
if (id === '' || id === '*') return true;
|
|
79
|
-
return message.$channel.id === id;
|
|
80
|
-
}),
|
|
81
|
-
'__built-in__',
|
|
82
|
-
);
|
|
83
|
-
this.add(
|
|
84
|
-
Permissions.define(/^user\([^)]+\)$/, (name, message) => {
|
|
85
|
-
const match = name.match(/^user\(([^)]+)\)$/);
|
|
86
|
-
if (!match) return false;
|
|
87
|
-
const id = match[1];
|
|
88
|
-
return message.$sender.id === id;
|
|
89
|
-
}),
|
|
90
|
-
'__built-in__',
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* 检查权限
|
|
96
|
-
*/
|
|
97
|
-
async check(name: string, message: MessageClass<AdapterMessage<RegisteredAdapter>>): Promise<boolean> {
|
|
98
|
-
for (const permission of this.items) {
|
|
99
|
-
const passed = await permission.check(name, message);
|
|
100
|
-
if (passed) return true;
|
|
101
|
-
}
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* 序列化为 JSON
|
|
107
|
-
*/
|
|
108
|
-
toJSON(pluginName?: string): FeatureJSON {
|
|
109
|
-
const list = pluginName ? this.getByPlugin(pluginName) : this.items;
|
|
110
|
-
return {
|
|
111
|
-
name: this.name,
|
|
112
|
-
icon: this.icon,
|
|
113
|
-
desc: this.desc,
|
|
114
|
-
count: list.length,
|
|
115
|
-
items: list.map(p => ({
|
|
116
|
-
name: p.name instanceof RegExp ? p.name.source : p.name,
|
|
117
|
-
})),
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* 提供给 Plugin.prototype 的扩展方法
|
|
123
|
-
*/
|
|
124
|
-
get extensions() {
|
|
125
|
-
const feature = this;
|
|
126
|
-
return {
|
|
127
|
-
addPermission(permission: PermissionItem) {
|
|
128
|
-
const plugin = getPlugin();
|
|
129
|
-
const dispose = feature.add(permission, plugin.name);
|
|
130
|
-
const permName = permission.name instanceof RegExp ? permission.name.source : permission.name;
|
|
131
|
-
plugin.recordFeatureContribution(feature.name, permName);
|
|
132
|
-
plugin.onDispose(dispose);
|
|
133
|
-
return dispose;
|
|
134
|
-
},
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export namespace Permissions {
|
|
140
|
-
export function define<T extends RegisteredAdapter = RegisteredAdapter>(
|
|
141
|
-
name: string | RegExp,
|
|
142
|
-
check: PermissionChecker<T>,
|
|
143
|
-
): PermissionItem<T> {
|
|
144
|
-
return { name, check };
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* @deprecated Use PermissionFeature instead
|
|
150
|
-
*/
|
|
151
|
-
export const PermissionService = PermissionFeature;
|