@zhin.js/core 1.0.3 → 1.0.4
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 +6 -0
- package/lib/app.d.ts +7 -0
- package/lib/app.d.ts.map +1 -1
- package/lib/app.js +13 -1
- 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 +31 -20
- 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,7 +81,7 @@ 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));
|
|
@@ -103,6 +107,9 @@ export class App extends HMR<Plugin> {
|
|
|
103
107
|
bots: [],
|
|
104
108
|
debug: false,
|
|
105
109
|
};
|
|
110
|
+
middleware(middleware: MessageMiddleware) {
|
|
111
|
+
this.middlewares.push(middleware)
|
|
112
|
+
}
|
|
106
113
|
/**
|
|
107
114
|
* 发送消息到指定适配器和机器人
|
|
108
115
|
* @param options 消息发送参数(包含 context、bot、内容等)
|
|
@@ -119,7 +126,7 @@ export class App extends HMR<Plugin> {
|
|
|
119
126
|
);
|
|
120
127
|
return bot.$sendMessage(options);
|
|
121
128
|
}
|
|
122
|
-
async recallMessage(adapter_name:string,bot_name:string,id:string){
|
|
129
|
+
async recallMessage(adapter_name: string, bot_name: string, id: string) {
|
|
123
130
|
const adapter = this.getContext<Adapter>(adapter_name);
|
|
124
131
|
if (!adapter)
|
|
125
132
|
throw new Error(`can't find adapter for name ${adapter_name}`);
|
|
@@ -188,14 +195,15 @@ export class App extends HMR<Plugin> {
|
|
|
188
195
|
|
|
189
196
|
this.logger.info("App configuration updated", this.config);
|
|
190
197
|
}
|
|
191
|
-
get schemas(){
|
|
198
|
+
get schemas() {
|
|
192
199
|
return this.dependencyList.reduce((result, plugin) => {
|
|
193
200
|
plugin.schemas.forEach((schema, name) => {
|
|
194
201
|
result.set(name, schema);
|
|
195
202
|
});
|
|
196
203
|
return result;
|
|
197
|
-
}, new Map<string,Schema<any>>([
|
|
198
|
-
['SystemLog', SystemLogSchema]
|
|
204
|
+
}, new Map<string, Schema<any>>([
|
|
205
|
+
['SystemLog', SystemLogSchema],
|
|
206
|
+
['User', UserSchema]
|
|
199
207
|
]));
|
|
200
208
|
}
|
|
201
209
|
/** 使用插件 */
|
|
@@ -211,15 +219,15 @@ export class App extends HMR<Plugin> {
|
|
|
211
219
|
this.use(pluginName);
|
|
212
220
|
}
|
|
213
221
|
await sleep(200);
|
|
214
|
-
const schemas:Record<string,Schema
|
|
222
|
+
const schemas: Record<string, Schema> = {};
|
|
215
223
|
for (const [name, schema] of this.schemas) {
|
|
216
|
-
schemas[name]=schema;
|
|
224
|
+
schemas[name] = schema;
|
|
217
225
|
}
|
|
218
226
|
if (this.config.database) {
|
|
219
|
-
this.database=Registry.create((this.config.database as any).dialect,this.config.database,schemas);
|
|
227
|
+
this.database = Registry.create((this.config.database as any).dialect, this.config.database, schemas);
|
|
220
228
|
await this.database?.start();
|
|
221
229
|
this.logger.info(`database init success`);
|
|
222
|
-
|
|
230
|
+
|
|
223
231
|
// 初始化日志传输器
|
|
224
232
|
this.logTransport = new DatabaseLogTransport(this);
|
|
225
233
|
addTransport(this.logTransport);
|
|
@@ -227,7 +235,7 @@ export class App extends HMR<Plugin> {
|
|
|
227
235
|
} else {
|
|
228
236
|
this.logger.info(`database not configured, skipping database init`);
|
|
229
237
|
}
|
|
230
|
-
this.dispatch("database.ready",this.database);
|
|
238
|
+
this.dispatch("database.ready", this.database);
|
|
231
239
|
// 等待所有插件就绪
|
|
232
240
|
await this.waitForReady();
|
|
233
241
|
this.logger.info("started successfully");
|
|
@@ -237,14 +245,14 @@ export class App extends HMR<Plugin> {
|
|
|
237
245
|
/** 停止App */
|
|
238
246
|
async stop(): Promise<void> {
|
|
239
247
|
this.logger.info("Stopping app...");
|
|
240
|
-
|
|
248
|
+
|
|
241
249
|
// 停止日志清理任务并移除日志传输器
|
|
242
250
|
if (this.logTransport) {
|
|
243
251
|
this.logTransport.stopCleanup();
|
|
244
252
|
removeTransport(this.logTransport);
|
|
245
253
|
this.logger.info("database log transport removed");
|
|
246
254
|
}
|
|
247
|
-
|
|
255
|
+
|
|
248
256
|
// 销毁所有插件
|
|
249
257
|
this.dispose();
|
|
250
258
|
|
|
@@ -315,7 +323,10 @@ export function defineModel<T extends Record<string, any>>(
|
|
|
315
323
|
const plugin = usePlugin();
|
|
316
324
|
return plugin.defineModel(name, schema);
|
|
317
325
|
}
|
|
318
|
-
|
|
326
|
+
export function addPermit<T extends RegisteredAdapter>(name: string | RegExp, checker: PermissionChecker<T>) {
|
|
327
|
+
const plugin = usePlugin()
|
|
328
|
+
return plugin.addPermit(name, checker)
|
|
329
|
+
}
|
|
319
330
|
/** 获取当前插件实例 */
|
|
320
331
|
export function usePlugin(): Plugin {
|
|
321
332
|
const hmr = HMR.currentHMR;
|
|
@@ -371,7 +382,7 @@ export function addMiddleware(middleware: MessageMiddleware): void {
|
|
|
371
382
|
const plugin = usePlugin();
|
|
372
383
|
plugin.addMiddleware(middleware);
|
|
373
384
|
}
|
|
374
|
-
export function onDatabaseReady(callback: (database: RelatedDatabase<any,Models
|
|
385
|
+
export function onDatabaseReady(callback: (database: RelatedDatabase<any, Models> | DocumentDatabase<any, Models> | KeyValueDatabase<any, Models>) => PromiseLike<void>) {
|
|
375
386
|
const plugin = usePlugin();
|
|
376
387
|
if (plugin.app.database?.isStarted) callback(plugin.app.database);
|
|
377
388
|
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
|
* 通用字典类型
|