@zhin.js/core 1.0.3 → 1.0.5
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/CHANGELOG.md +12 -0
- package/lib/app.d.ts +7 -0
- package/lib/app.d.ts.map +1 -1
- package/lib/app.js +17 -3
- package/lib/app.js.map +1 -1
- package/lib/command.d.ts +4 -6
- package/lib/command.d.ts.map +1 -1
- package/lib/command.js +17 -9
- package/lib/command.js.map +1 -1
- package/lib/models/user.d.ts +10 -0
- package/lib/models/user.d.ts.map +1 -0
- package/lib/models/user.js +8 -0
- package/lib/models/user.js.map +1 -0
- package/lib/permissions.d.ts +20 -0
- package/lib/permissions.d.ts.map +1 -0
- package/lib/permissions.js +66 -0
- package/lib/permissions.js.map +1 -0
- package/lib/plugin.d.ts +4 -0
- package/lib/plugin.d.ts.map +1 -1
- package/lib/plugin.js +12 -5
- package/lib/plugin.js.map +1 -1
- package/lib/types.d.ts +1 -0
- package/lib/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/app.ts +35 -22
- package/src/command.ts +17 -9
- package/src/models/user.ts +15 -0
- package/src/permissions.ts +62 -0
- package/src/plugin.ts +13 -5
- package/src/types.ts +2 -1
- package/tests/command.test.ts +136 -40
- package/tests/plugin.test.ts +2 -2
package/src/app.ts
CHANGED
|
@@ -19,20 +19,22 @@ import { fileURLToPath } from "url";
|
|
|
19
19
|
import { generateEnvTypes } from "./types-generator.js";
|
|
20
20
|
import logger, { setName } from "@zhin.js/logger";
|
|
21
21
|
import { sleep } from "./utils.js";
|
|
22
|
-
|
|
22
|
+
import { PermissionChecker, Permissions } from "./permissions.js";
|
|
23
23
|
// 创建静态logger用于配置加载等静态操作
|
|
24
24
|
setName("Zhin");
|
|
25
25
|
import { MessageMiddleware, Plugin } from "./plugin.js";
|
|
26
26
|
import { Adapter } from "./adapter";
|
|
27
27
|
import { MessageCommand } from "./command";
|
|
28
28
|
import { Component } from "./component";
|
|
29
|
-
import { RelatedDatabase,DocumentDatabase,KeyValueDatabase,Schema,Registry} from "@zhin.js/database";
|
|
29
|
+
import { RelatedDatabase, DocumentDatabase, KeyValueDatabase, Schema, Registry } from "@zhin.js/database";
|
|
30
30
|
import { DatabaseLogTransport } from "./log-transport.js";
|
|
31
31
|
import { SystemLog, SystemLogSchema } from "./models/system-log.js";
|
|
32
|
+
import { User, UserSchema } from './models/user.js'
|
|
32
33
|
import { addTransport, removeTransport } from "@zhin.js/logger";
|
|
33
|
-
declare module "@zhin.js/types"{
|
|
34
|
-
interface Models{
|
|
35
|
-
SystemLog:SystemLog;
|
|
34
|
+
declare module "@zhin.js/types" {
|
|
35
|
+
interface Models {
|
|
36
|
+
SystemLog: SystemLog;
|
|
37
|
+
User: User
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
|
|
@@ -46,8 +48,10 @@ declare module "@zhin.js/types"{
|
|
|
46
48
|
export class App extends HMR<Plugin> {
|
|
47
49
|
static currentPlugin: Plugin;
|
|
48
50
|
private config: AppConfig;
|
|
51
|
+
middlewares: MessageMiddleware[] = [];
|
|
49
52
|
adapters: string[] = [];
|
|
50
|
-
database?: RelatedDatabase<any,Models
|
|
53
|
+
database?: RelatedDatabase<any, Models> | DocumentDatabase<any, Models> | KeyValueDatabase<any, Models>;
|
|
54
|
+
permissions: Permissions = new Permissions(this);
|
|
51
55
|
private logTransport?: DatabaseLogTransport;
|
|
52
56
|
/**
|
|
53
57
|
* 构造函数:初始化应用,加载配置,注册全局异常处理
|
|
@@ -77,15 +81,17 @@ export class App extends HMR<Plugin> {
|
|
|
77
81
|
super("Zhin", {
|
|
78
82
|
logger,
|
|
79
83
|
dirs: finalConfig.plugin_dirs || [],
|
|
80
|
-
extensions: new Set([".js", ".ts",".jsx",".tsx"]),
|
|
84
|
+
extensions: new Set([".js", ".ts", ".jsx", ".tsx"]),
|
|
81
85
|
debug: finalConfig.debug,
|
|
82
86
|
});
|
|
83
87
|
this.on("message.send", this.sendMessage.bind(this));
|
|
84
88
|
process.on("uncaughtException", (e) => {
|
|
85
|
-
|
|
89
|
+
const args=e instanceof Error ? [e.message,{stack:e.stack}] : [e];
|
|
90
|
+
this.logger.error(...args);
|
|
86
91
|
});
|
|
87
92
|
process.on("unhandledRejection", (e) => {
|
|
88
|
-
|
|
93
|
+
const args=e instanceof Error ? [e.message,{stack:e.stack}] : [e];
|
|
94
|
+
this.logger.error(...args);
|
|
89
95
|
});
|
|
90
96
|
this.config = finalConfig;
|
|
91
97
|
}
|
|
@@ -103,6 +109,9 @@ export class App extends HMR<Plugin> {
|
|
|
103
109
|
bots: [],
|
|
104
110
|
debug: false,
|
|
105
111
|
};
|
|
112
|
+
middleware(middleware: MessageMiddleware) {
|
|
113
|
+
this.middlewares.push(middleware)
|
|
114
|
+
}
|
|
106
115
|
/**
|
|
107
116
|
* 发送消息到指定适配器和机器人
|
|
108
117
|
* @param options 消息发送参数(包含 context、bot、内容等)
|
|
@@ -119,7 +128,7 @@ export class App extends HMR<Plugin> {
|
|
|
119
128
|
);
|
|
120
129
|
return bot.$sendMessage(options);
|
|
121
130
|
}
|
|
122
|
-
async recallMessage(adapter_name:string,bot_name:string,id:string){
|
|
131
|
+
async recallMessage(adapter_name: string, bot_name: string, id: string) {
|
|
123
132
|
const adapter = this.getContext<Adapter>(adapter_name);
|
|
124
133
|
if (!adapter)
|
|
125
134
|
throw new Error(`can't find adapter for name ${adapter_name}`);
|
|
@@ -188,14 +197,15 @@ export class App extends HMR<Plugin> {
|
|
|
188
197
|
|
|
189
198
|
this.logger.info("App configuration updated", this.config);
|
|
190
199
|
}
|
|
191
|
-
get schemas(){
|
|
200
|
+
get schemas() {
|
|
192
201
|
return this.dependencyList.reduce((result, plugin) => {
|
|
193
202
|
plugin.schemas.forEach((schema, name) => {
|
|
194
203
|
result.set(name, schema);
|
|
195
204
|
});
|
|
196
205
|
return result;
|
|
197
|
-
}, new Map<string,Schema<any>>([
|
|
198
|
-
['SystemLog', SystemLogSchema]
|
|
206
|
+
}, new Map<string, Schema<any>>([
|
|
207
|
+
['SystemLog', SystemLogSchema],
|
|
208
|
+
['User', UserSchema]
|
|
199
209
|
]));
|
|
200
210
|
}
|
|
201
211
|
/** 使用插件 */
|
|
@@ -211,15 +221,15 @@ export class App extends HMR<Plugin> {
|
|
|
211
221
|
this.use(pluginName);
|
|
212
222
|
}
|
|
213
223
|
await sleep(200);
|
|
214
|
-
const schemas:Record<string,Schema
|
|
224
|
+
const schemas: Record<string, Schema> = {};
|
|
215
225
|
for (const [name, schema] of this.schemas) {
|
|
216
|
-
schemas[name]=schema;
|
|
226
|
+
schemas[name] = schema;
|
|
217
227
|
}
|
|
218
228
|
if (this.config.database) {
|
|
219
|
-
this.database=Registry.create((this.config.database as any).dialect,this.config.database,schemas);
|
|
229
|
+
this.database = Registry.create((this.config.database as any).dialect, this.config.database, schemas);
|
|
220
230
|
await this.database?.start();
|
|
221
231
|
this.logger.info(`database init success`);
|
|
222
|
-
|
|
232
|
+
|
|
223
233
|
// 初始化日志传输器
|
|
224
234
|
this.logTransport = new DatabaseLogTransport(this);
|
|
225
235
|
addTransport(this.logTransport);
|
|
@@ -227,7 +237,7 @@ export class App extends HMR<Plugin> {
|
|
|
227
237
|
} else {
|
|
228
238
|
this.logger.info(`database not configured, skipping database init`);
|
|
229
239
|
}
|
|
230
|
-
this.dispatch("database.ready",this.database);
|
|
240
|
+
this.dispatch("database.ready", this.database);
|
|
231
241
|
// 等待所有插件就绪
|
|
232
242
|
await this.waitForReady();
|
|
233
243
|
this.logger.info("started successfully");
|
|
@@ -237,14 +247,14 @@ export class App extends HMR<Plugin> {
|
|
|
237
247
|
/** 停止App */
|
|
238
248
|
async stop(): Promise<void> {
|
|
239
249
|
this.logger.info("Stopping app...");
|
|
240
|
-
|
|
250
|
+
|
|
241
251
|
// 停止日志清理任务并移除日志传输器
|
|
242
252
|
if (this.logTransport) {
|
|
243
253
|
this.logTransport.stopCleanup();
|
|
244
254
|
removeTransport(this.logTransport);
|
|
245
255
|
this.logger.info("database log transport removed");
|
|
246
256
|
}
|
|
247
|
-
|
|
257
|
+
|
|
248
258
|
// 销毁所有插件
|
|
249
259
|
this.dispose();
|
|
250
260
|
|
|
@@ -315,7 +325,10 @@ export function defineModel<T extends Record<string, any>>(
|
|
|
315
325
|
const plugin = usePlugin();
|
|
316
326
|
return plugin.defineModel(name, schema);
|
|
317
327
|
}
|
|
318
|
-
|
|
328
|
+
export function addPermit<T extends RegisteredAdapter>(name: string | RegExp, checker: PermissionChecker<T>) {
|
|
329
|
+
const plugin = usePlugin()
|
|
330
|
+
return plugin.addPermit(name, checker)
|
|
331
|
+
}
|
|
319
332
|
/** 获取当前插件实例 */
|
|
320
333
|
export function usePlugin(): Plugin {
|
|
321
334
|
const hmr = HMR.currentHMR;
|
|
@@ -371,7 +384,7 @@ export function addMiddleware(middleware: MessageMiddleware): void {
|
|
|
371
384
|
const plugin = usePlugin();
|
|
372
385
|
plugin.addMiddleware(middleware);
|
|
373
386
|
}
|
|
374
|
-
export function onDatabaseReady(callback: (database: RelatedDatabase<any,Models
|
|
387
|
+
export function onDatabaseReady(callback: (database: RelatedDatabase<any, Models> | DocumentDatabase<any, Models> | KeyValueDatabase<any, Models>) => PromiseLike<void>) {
|
|
375
388
|
const plugin = usePlugin();
|
|
376
389
|
if (plugin.app.database?.isStarted) callback(plugin.app.database);
|
|
377
390
|
plugin.on("database.ready", callback);
|
package/src/command.ts
CHANGED
|
@@ -3,6 +3,8 @@ import {AdapterMessage, SendContent} from "./types.js";
|
|
|
3
3
|
import {RegisteredAdapters} from "@zhin.js/types";
|
|
4
4
|
import type {Message} from "./message.js";
|
|
5
5
|
import {MaybePromise} from "@zhin.js/types";
|
|
6
|
+
import { Plugin } from "./plugin.js";
|
|
7
|
+
import { PluginError } from "./errors.js";
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* MessageCommand类:命令系统核心,基于segment-matcher实现。
|
|
@@ -10,15 +12,8 @@ import {MaybePromise} from "@zhin.js/types";
|
|
|
10
12
|
*/
|
|
11
13
|
export class MessageCommand<T extends keyof RegisteredAdapters=keyof RegisteredAdapters> extends SegmentMatcher{
|
|
12
14
|
#callbacks:MessageCommand.Callback<T>[]=[];
|
|
15
|
+
#permissions:string[]=[];
|
|
13
16
|
#checkers:MessageCommand.Checker<T>[]=[]
|
|
14
|
-
/**
|
|
15
|
-
* 限定命令作用域(适配器名)
|
|
16
|
-
* @param scopes 适配器名列表
|
|
17
|
-
*/
|
|
18
|
-
scope<R extends T>(...scopes:R[]):MessageCommand<R>{
|
|
19
|
-
this.#checkers.push((m)=>(scopes as string[]).includes(m.$adapter))
|
|
20
|
-
return this as MessageCommand<R>
|
|
21
|
-
}
|
|
22
17
|
/**
|
|
23
18
|
* 注册命令回调
|
|
24
19
|
* @param callback 命令处理函数
|
|
@@ -27,12 +22,25 @@ export class MessageCommand<T extends keyof RegisteredAdapters=keyof RegisteredA
|
|
|
27
22
|
this.#callbacks.push(callback)
|
|
28
23
|
return this as MessageCommand<T>;
|
|
29
24
|
}
|
|
25
|
+
permit(...permissions:string[]){
|
|
26
|
+
this.#permissions.push(...permissions)
|
|
27
|
+
return this as MessageCommand<T>;
|
|
28
|
+
}
|
|
30
29
|
/**
|
|
31
30
|
* 处理消息,自动匹配命令并执行回调
|
|
32
31
|
* @param message 消息对象
|
|
32
|
+
* @param plugin 插件实例
|
|
33
33
|
* @returns 命令返回内容或undefined
|
|
34
34
|
*/
|
|
35
|
-
async handle(message:Message<AdapterMessage<T
|
|
35
|
+
async handle(message:Message<AdapterMessage<T>>,plugin:Plugin):Promise<SendContent|undefined>{
|
|
36
|
+
for(const permission of this.#permissions){
|
|
37
|
+
const permit=plugin.getPermit(permission)
|
|
38
|
+
if(!permit) {
|
|
39
|
+
throw new PluginError(`权限 ${permission} 不存在`,plugin.name)
|
|
40
|
+
}
|
|
41
|
+
const result=await permit.check(permission,message)
|
|
42
|
+
if(!result) return;
|
|
43
|
+
}
|
|
36
44
|
for(const check of this.#checkers){
|
|
37
45
|
const result=await check(message)
|
|
38
46
|
if(!result) return;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Schema } from '@zhin.js/database'
|
|
2
|
+
export interface User{
|
|
3
|
+
id:string
|
|
4
|
+
name?:string
|
|
5
|
+
password?:string
|
|
6
|
+
third_part:string[];
|
|
7
|
+
permissions?:string[]
|
|
8
|
+
}
|
|
9
|
+
export const UserSchema:Schema<User>={
|
|
10
|
+
id:{type:"text",nullable:false},
|
|
11
|
+
name:{type:'text',nullable:true},
|
|
12
|
+
password:{type:'text',nullable:true},
|
|
13
|
+
third_part:{type:'json',default:[]},
|
|
14
|
+
permissions:{type:'json',default:[]}
|
|
15
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { MaybePromise } from "@zhin.js/types";
|
|
2
|
+
import { RegisteredAdapter } from "./types.js";
|
|
3
|
+
import { Message } from "./message.js";
|
|
4
|
+
import { AdapterMessage } from "./types.js";
|
|
5
|
+
import { App } from "./app.js";
|
|
6
|
+
export type PermissionItem<T extends RegisteredAdapter = RegisteredAdapter> = {
|
|
7
|
+
name: string | RegExp
|
|
8
|
+
check: PermissionChecker<T>
|
|
9
|
+
}
|
|
10
|
+
export type PermissionChecker<T extends RegisteredAdapter = RegisteredAdapter> = (name: string, message: Message<AdapterMessage<T>>) => MaybePromise<boolean>
|
|
11
|
+
export class Permissions extends Array<PermissionItem>{
|
|
12
|
+
constructor(private readonly app: App) {
|
|
13
|
+
super();
|
|
14
|
+
this.add(Permissions.define(/^adapter\([^)]+\)$/, (name, message) => {
|
|
15
|
+
return message.$adapter === name.replace(/^adapter\(([^)]+)\)$/, '$1');
|
|
16
|
+
}));
|
|
17
|
+
this.add(Permissions.define(/^group\([^)]+\)$/, (name, message) => {
|
|
18
|
+
const match=name.match(/^group\(([^)]+)\)$/);
|
|
19
|
+
if(!match) return false;
|
|
20
|
+
const id=match[1];
|
|
21
|
+
if(message.$channel.type !== 'group') return false;
|
|
22
|
+
if(id===''||id==='*') return true;
|
|
23
|
+
return message.$channel.id === id;
|
|
24
|
+
}));
|
|
25
|
+
this.add(Permissions.define(/^private\([^)]+\)$/, (name, message) => {
|
|
26
|
+
const match=name.match(/^private\(([^)]+)\)$/);
|
|
27
|
+
if(!match) return false;
|
|
28
|
+
const id=match[1];
|
|
29
|
+
if(message.$channel.type !== 'private') return false;
|
|
30
|
+
if(id===''||id==='*') return true;
|
|
31
|
+
return message.$channel.id === id;
|
|
32
|
+
}));
|
|
33
|
+
this.add(Permissions.define(/^channel\([^)]+\)$/, (name, message) => {
|
|
34
|
+
const match=name.match(/^channel\(([^)]+)\)$/);
|
|
35
|
+
if(!match) return false;
|
|
36
|
+
const id=match[1];
|
|
37
|
+
if(message.$channel.type !== 'channel') return false;
|
|
38
|
+
if(id===''||id==='*') return true;
|
|
39
|
+
return message.$channel.id === id;
|
|
40
|
+
}));
|
|
41
|
+
this.add(Permissions.define(/^user\([^)]+\)$/,(name,message)=>{
|
|
42
|
+
const match=name.match(/^channel\(([^)]+)\)$/);
|
|
43
|
+
if(!match) return false;
|
|
44
|
+
const id=match[1];
|
|
45
|
+
return message.$sender.id===id;
|
|
46
|
+
}))
|
|
47
|
+
}
|
|
48
|
+
add(permission: PermissionItem) {
|
|
49
|
+
this.push(permission);
|
|
50
|
+
}
|
|
51
|
+
get(name: string): PermissionItem | undefined {
|
|
52
|
+
return this.app.dependencyList.reduce((result,dep)=>{
|
|
53
|
+
result.push(...dep.permissions)
|
|
54
|
+
return result;
|
|
55
|
+
},[...this]).find(p => new RegExp(p.name).test(name));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export namespace Permissions{
|
|
59
|
+
export function define<T extends RegisteredAdapter = RegisteredAdapter>(name: string | RegExp, check: PermissionChecker<T>): PermissionItem<T> {
|
|
60
|
+
return { name, check };
|
|
61
|
+
}
|
|
62
|
+
}
|
package/src/plugin.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import {MaybePromise} from '@zhin.js/types'
|
|
7
7
|
import {AdapterMessage, BeforeSendHandler, RegisteredAdapter, SendOptions} from "./types.js";
|
|
8
|
+
import { PermissionItem,PermissionChecker } from './permissions.js';
|
|
8
9
|
import {Message} from './message.js'
|
|
9
10
|
import {Dependency, Logger,} from "@zhin.js/hmr";
|
|
10
11
|
import {App} from "./app";
|
|
@@ -31,6 +32,7 @@ export type MessageMiddleware<P extends RegisteredAdapter=RegisteredAdapter> = (
|
|
|
31
32
|
export class Plugin extends Dependency<Plugin> {
|
|
32
33
|
middlewares: MessageMiddleware<RegisteredAdapter>[] = [];
|
|
33
34
|
components: Map<string, Component<any>> = new Map();
|
|
35
|
+
permissions: PermissionItem<RegisteredAdapter>[]=[];
|
|
34
36
|
schemas: Map<string,Schema<any>>=new Map();
|
|
35
37
|
commands:MessageCommand[]=[];
|
|
36
38
|
crons:Cron[]=[];
|
|
@@ -48,7 +50,7 @@ export class Plugin extends Dependency<Plugin> {
|
|
|
48
50
|
// 注册命令处理为默认中间件
|
|
49
51
|
this.addMiddleware(async (message,next)=>{
|
|
50
52
|
for(const command of this.commands){
|
|
51
|
-
const result=await command.handle(message);
|
|
53
|
+
const result=await command.handle(message,this);
|
|
52
54
|
if(result) message.$reply(result);
|
|
53
55
|
}
|
|
54
56
|
return next()
|
|
@@ -90,20 +92,26 @@ export class Plugin extends Dependency<Plugin> {
|
|
|
90
92
|
try {
|
|
91
93
|
await message.$reply('抱歉,处理您的消息时出现了错误。')
|
|
92
94
|
} catch (replyError) {
|
|
93
|
-
// 静默处理回复错误,避免错误循环
|
|
94
|
-
// console.error 已替换为注释
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
+
addPermit<T extends RegisteredAdapter>(name:string|RegExp,check:PermissionChecker<T>){
|
|
99
|
+
this.permissions.push({name,check});
|
|
100
|
+
return this;
|
|
101
|
+
}
|
|
102
|
+
getPermit<T extends RegisteredAdapter>(name:string):PermissionItem<T>|undefined{
|
|
103
|
+
return this.app.permissions.get(name);
|
|
104
|
+
}
|
|
98
105
|
cron(cronExpression:string,callback:()=>void){
|
|
99
106
|
const cronJob = new Cron(cronExpression,callback);
|
|
100
107
|
this.crons.push(cronJob);
|
|
101
108
|
return this;
|
|
102
109
|
}
|
|
103
110
|
async #runMiddlewares(message: Message, index: number): Promise<void> {
|
|
104
|
-
|
|
111
|
+
const middlewareList=[...this.app.middlewares,...this.middlewares]
|
|
112
|
+
if (index >= middlewareList.length) return
|
|
105
113
|
|
|
106
|
-
const middleware =
|
|
114
|
+
const middleware = middlewareList[index]
|
|
107
115
|
|
|
108
116
|
try {
|
|
109
117
|
await middleware(message, () => this.#runMiddlewares(message, index + 1))
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {MaybePromise,RegisteredAdapters}from '@zhin.js/types'
|
|
2
|
-
import {MessageChannel} from "./message.js";
|
|
2
|
+
import {MessageChannel,Message} from "./message.js";
|
|
3
3
|
import {Adapter} from "./adapter.js";
|
|
4
4
|
import {Bot,BotConfig} from "./bot.js";
|
|
5
5
|
import { Databases,Registry } from "@zhin.js/database";
|
|
@@ -57,6 +57,7 @@ export type SendContent=MaybeArray<string|MessageElement>
|
|
|
57
57
|
export interface MessageSender{
|
|
58
58
|
id: string;
|
|
59
59
|
name?: string;
|
|
60
|
+
permissions?:string[]
|
|
60
61
|
}
|
|
61
62
|
/**
|
|
62
63
|
* 通用字典类型
|