@zhin.js/core 1.0.2 → 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.
Files changed (57) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/lib/app.d.ts +16 -1
  3. package/lib/app.d.ts.map +1 -1
  4. package/lib/app.js +38 -1
  5. package/lib/app.js.map +1 -1
  6. package/lib/bot.d.ts +4 -2
  7. package/lib/bot.d.ts.map +1 -1
  8. package/lib/command.d.ts +6 -7
  9. package/lib/command.d.ts.map +1 -1
  10. package/lib/command.js +17 -9
  11. package/lib/command.js.map +1 -1
  12. package/lib/jsx-dev-runtime.d.ts +3 -0
  13. package/lib/jsx-dev-runtime.d.ts.map +1 -0
  14. package/lib/jsx-dev-runtime.js +3 -0
  15. package/lib/jsx-dev-runtime.js.map +1 -0
  16. package/lib/log-transport.d.ts +37 -0
  17. package/lib/log-transport.d.ts.map +1 -0
  18. package/lib/log-transport.js +136 -0
  19. package/lib/log-transport.js.map +1 -0
  20. package/lib/message.d.ts +1 -1
  21. package/lib/message.d.ts.map +1 -1
  22. package/lib/models/system-log.d.ts +11 -0
  23. package/lib/models/system-log.d.ts.map +1 -0
  24. package/lib/models/system-log.js +9 -0
  25. package/lib/models/system-log.js.map +1 -0
  26. package/lib/models/user.d.ts +10 -0
  27. package/lib/models/user.d.ts.map +1 -0
  28. package/lib/models/user.js +8 -0
  29. package/lib/models/user.js.map +1 -0
  30. package/lib/permissions.d.ts +20 -0
  31. package/lib/permissions.d.ts.map +1 -0
  32. package/lib/permissions.js +66 -0
  33. package/lib/permissions.js.map +1 -0
  34. package/lib/plugin.d.ts +6 -1
  35. package/lib/plugin.d.ts.map +1 -1
  36. package/lib/plugin.js +22 -5
  37. package/lib/plugin.js.map +1 -1
  38. package/lib/prompt.d.ts.map +1 -1
  39. package/lib/prompt.js +3 -2
  40. package/lib/prompt.js.map +1 -1
  41. package/lib/types.d.ts +11 -14
  42. package/lib/types.d.ts.map +1 -1
  43. package/package.json +7 -3
  44. package/src/app.ts +56 -12
  45. package/src/bot.ts +5 -3
  46. package/src/command.ts +19 -10
  47. package/src/jsx-dev-runtime.ts +2 -0
  48. package/src/log-transport.ts +163 -0
  49. package/src/message.ts +1 -1
  50. package/src/models/system-log.ts +20 -0
  51. package/src/models/user.ts +15 -0
  52. package/src/permissions.ts +62 -0
  53. package/src/plugin.ts +28 -6
  54. package/src/prompt.ts +3 -2
  55. package/src/types.ts +12 -13
  56. package/tests/command.test.ts +136 -40
  57. package/tests/plugin.test.ts +2 -2
package/src/command.ts CHANGED
@@ -1,7 +1,10 @@
1
1
  import {MatchResult, SegmentMatcher} from "segment-matcher";
2
- import {AdapterMessage, RegisteredAdapters, SendContent} from "./types.js";
2
+ import {AdapterMessage, SendContent} from "./types.js";
3
+ import {RegisteredAdapters} from "@zhin.js/types";
3
4
  import type {Message} from "./message.js";
4
5
  import {MaybePromise} from "@zhin.js/types";
6
+ import { Plugin } from "./plugin.js";
7
+ import { PluginError } from "./errors.js";
5
8
 
6
9
  /**
7
10
  * MessageCommand类:命令系统核心,基于segment-matcher实现。
@@ -9,15 +12,8 @@ import {MaybePromise} from "@zhin.js/types";
9
12
  */
10
13
  export class MessageCommand<T extends keyof RegisteredAdapters=keyof RegisteredAdapters> extends SegmentMatcher{
11
14
  #callbacks:MessageCommand.Callback<T>[]=[];
15
+ #permissions:string[]=[];
12
16
  #checkers:MessageCommand.Checker<T>[]=[]
13
- /**
14
- * 限定命令作用域(适配器名)
15
- * @param scopes 适配器名列表
16
- */
17
- scope<R extends T>(...scopes:R[]):MessageCommand<R>{
18
- this.#checkers.push((m)=>(scopes as string[]).includes(m.$adapter))
19
- return this as MessageCommand<R>
20
- }
21
17
  /**
22
18
  * 注册命令回调
23
19
  * @param callback 命令处理函数
@@ -26,12 +22,25 @@ export class MessageCommand<T extends keyof RegisteredAdapters=keyof RegisteredA
26
22
  this.#callbacks.push(callback)
27
23
  return this as MessageCommand<T>;
28
24
  }
25
+ permit(...permissions:string[]){
26
+ this.#permissions.push(...permissions)
27
+ return this as MessageCommand<T>;
28
+ }
29
29
  /**
30
30
  * 处理消息,自动匹配命令并执行回调
31
31
  * @param message 消息对象
32
+ * @param plugin 插件实例
32
33
  * @returns 命令返回内容或undefined
33
34
  */
34
- async handle(message:Message<AdapterMessage<T>>):Promise<SendContent|undefined>{
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
+ }
35
44
  for(const check of this.#checkers){
36
45
  const result=await check(message)
37
46
  if(!result) return;
@@ -0,0 +1,2 @@
1
+ export * from './jsx-runtime.js'
2
+ export {jsx as jsxDEV} from './jsx.js'
@@ -0,0 +1,163 @@
1
+ import { LogTransport } from '@zhin.js/logger'
2
+ import { App } from './app.js'
3
+
4
+ /**
5
+ * 数据库日志传输器
6
+ * 将日志存储到数据库,并自动清理旧日志
7
+ */
8
+ export class DatabaseLogTransport implements LogTransport {
9
+ private app: App
10
+ private stripAnsiRegex = /\x1b\[[0-9;]*m/g
11
+ private cleanupTimer?: NodeJS.Timeout
12
+ private maxDays: number
13
+ private maxRecords: number
14
+ private cleanupInterval: number
15
+
16
+ constructor(app: App) {
17
+ this.app = app
18
+
19
+ // 从配置读取日志清理策略
20
+ const logConfig = app['config']?.log || {}
21
+ this.maxDays = logConfig.maxDays || 7 // 默认保留 7 天
22
+ this.maxRecords = logConfig.maxRecords || 10000 // 默认最多 10000 条
23
+ this.cleanupInterval = logConfig.cleanupInterval || 24 // 默认 24 小时清理一次
24
+
25
+ // 启动定时清理
26
+ this.startCleanup()
27
+ }
28
+
29
+ /**
30
+ * 启动定时清理任务
31
+ */
32
+ private startCleanup(): void {
33
+ // 立即执行一次清理
34
+ this.cleanupOldLogs().catch(err => {
35
+ this.app.logger.error('[DatabaseLogTransport] Initial cleanup failed:', err.message)
36
+ })
37
+
38
+ // 设置定时任务
39
+ this.cleanupTimer = setInterval(() => {
40
+ this.cleanupOldLogs().catch(err => {
41
+ this.app.logger.error('[DatabaseLogTransport] Scheduled cleanup failed:', err.message)
42
+ })
43
+ }, this.cleanupInterval * 60 * 60 * 1000) // 转换为毫秒
44
+ }
45
+
46
+ /**
47
+ * 清理旧日志
48
+ */
49
+ private async cleanupOldLogs(): Promise<void> {
50
+ if (!this.app.database) {
51
+ return
52
+ }
53
+
54
+ try {
55
+ const LogModel = this.app.database.model('SystemLog')
56
+ if (!LogModel) {
57
+ return
58
+ }
59
+
60
+ // 1. 按时间清理:删除超过 maxDays 天的日志
61
+ const cutoffDate = new Date()
62
+ cutoffDate.setDate(cutoffDate.getDate() - this.maxDays)
63
+
64
+ const deletedByDate = await LogModel
65
+ .delete({ timestamp: { $lt: cutoffDate } })
66
+
67
+ // 2. 按数量清理:如果日志总数超过 maxRecords,删除最旧的
68
+ const total = await LogModel.select()
69
+ const totalCount = total.length
70
+
71
+ if (totalCount > this.maxRecords) {
72
+ const excessCount = totalCount - this.maxRecords
73
+
74
+ // 查找最旧的 excessCount 条日志的 ID
75
+ const oldestLogs = await LogModel
76
+ .select('id','timestamp')
77
+ .orderBy('timestamp', 'ASC')
78
+ .limit(excessCount)
79
+
80
+ const idsToDelete = oldestLogs.map((log: any) => log.id)
81
+
82
+ if (idsToDelete.length > 0) {
83
+ await LogModel
84
+ .delete({ id: { $in: idsToDelete } })
85
+ }
86
+ }
87
+
88
+ this.app.logger.info(
89
+ `[DatabaseLogTransport] Log cleanup completed. ` +
90
+ `Deleted ${deletedByDate || 0} logs older than ${this.maxDays} days. ` +
91
+ `Current total: ${Math.max(0, totalCount - (deletedByDate || 0))} logs.`
92
+ )
93
+ } catch (error) {
94
+ // 静默处理错误
95
+ this.app.logger.error('[DatabaseLogTransport] Cleanup error:', (error as Error).message)
96
+ }
97
+ }
98
+
99
+ /**
100
+ * 停止清理任务
101
+ */
102
+ public stopCleanup(): void {
103
+ if (this.cleanupTimer) {
104
+ clearInterval(this.cleanupTimer)
105
+ this.cleanupTimer = undefined
106
+ }
107
+ }
108
+
109
+ /**
110
+ * 移除 ANSI 颜色代码
111
+ */
112
+ private stripAnsi(str: string): string {
113
+ return str.replace(this.stripAnsiRegex, '')
114
+ }
115
+
116
+ write(message: string): void {
117
+ // 移除 ANSI 颜色代码
118
+ const cleanMessage = this.stripAnsi(message)
119
+
120
+ // 解析日志消息
121
+ // 格式: [09-08 04:07:55.852] [INFO] [MyApp]: message
122
+ const logRegex = /\[[\d-]+ [\d:.]+\] \[(\w+)\] \[([^\]]+)\]: ([\s\S]+)/
123
+ const match = cleanMessage.match(logRegex)
124
+
125
+ if (match) {
126
+ const [, level, name, msg] = match
127
+ const source = name.split(':')[0] // 取第一部分作为 source
128
+
129
+ // 异步存储到数据库,不阻塞日志输出
130
+ this.saveToDatabase(level.toLowerCase(), name, msg.trim(), source).catch(err => {
131
+ // 避免日志存储失败导致应用崩溃
132
+ console.error('[DatabaseLogTransport] Failed to save log:', err.message)
133
+ })
134
+ }
135
+ }
136
+
137
+ /**
138
+ * 保存日志到数据库
139
+ */
140
+ private async saveToDatabase(level: string, name: string, message: string, source: string): Promise<void> {
141
+ if (!this.app.database) {
142
+ return // 没有数据库则跳过
143
+ }
144
+
145
+ try {
146
+ const LogModel = this.app.database.model('SystemLog')
147
+ if (!LogModel) {
148
+ return // 模型不存在则跳过
149
+ }
150
+
151
+ await LogModel.create({
152
+ level,
153
+ name,
154
+ message,
155
+ source,
156
+ timestamp: new Date()
157
+ })
158
+ } catch (error) {
159
+ // 静默处理错误,避免干扰主流程
160
+ }
161
+ }
162
+ }
163
+
package/src/message.ts CHANGED
@@ -28,7 +28,7 @@ export interface MessageBase {
28
28
  $bot:string
29
29
  $content: MessageElement[];
30
30
  $sender: MessageSender;
31
- $reply(content:SendContent,quote?:boolean|string):Promise<void>
31
+ $reply(content:SendContent,quote?:boolean|string):Promise<string>
32
32
  $channel: MessageChannel;
33
33
  $timestamp: number;
34
34
  $raw: string;
@@ -0,0 +1,20 @@
1
+ import { Schema } from '@zhin.js/database'
2
+
3
+ export interface SystemLog {
4
+ id?: number
5
+ level: string
6
+ name: string
7
+ message: string
8
+ source: string
9
+ timestamp: Date
10
+ }
11
+
12
+ export const SystemLogSchema:Schema<SystemLog>={
13
+ id: { type: 'integer', autoIncrement: true, primary: true },
14
+ level: { type: 'text', nullable: false },
15
+ name: { type: 'text', nullable: false },
16
+ message: { type: 'text', nullable: false },
17
+ source: { type: 'text', nullable: false },
18
+ timestamp: { type: 'date', nullable: false }
19
+ }
20
+
@@ -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";
@@ -29,8 +30,9 @@ export type MessageMiddleware<P extends RegisteredAdapter=RegisteredAdapter> = (
29
30
  * 支持命令注册、中间件、组件、定时任务、模型等。
30
31
  */
31
32
  export class Plugin extends Dependency<Plugin> {
32
- middlewares: MessageMiddleware<any>[] = [];
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
- if (index >= this.middlewares.length) return
111
+ const middlewareList=[...this.app.middlewares,...this.middlewares]
112
+ if (index >= middlewareList.length) return
105
113
 
106
- const middleware = this.middlewares[index]
114
+ const middleware = middlewareList[index]
107
115
 
108
116
  try {
109
117
  await middleware(message, () => this.#runMiddlewares(message, index + 1))
@@ -178,6 +186,20 @@ export class Plugin extends Dependency<Plugin> {
178
186
  throw messageError
179
187
  }
180
188
  }
189
+ recallMessage(adapter:string,bot:string,id:string){
190
+ try{
191
+ this.app.recallMessage(adapter,bot,id)
192
+ }catch(error){
193
+ const messageError = new MessageError(
194
+ `撤回消息失败: ${(error as Error).message}`,
195
+ id,
196
+ undefined,
197
+ { originalError: error }
198
+ )
199
+ errorManager.handle(messageError)
200
+ throw messageError
201
+ }
202
+ }
181
203
 
182
204
  /** 销毁插件 */
183
205
  dispose(): void {
package/src/prompt.ts CHANGED
@@ -27,8 +27,8 @@ export class Prompt<P extends RegisteredAdapter> {
27
27
  * @param config 提问配置
28
28
  */
29
29
  private prompt<T = any>(config: Prompt.Config<T>) {
30
- return new Promise<T>((resolve, reject) => {
31
- this.event.$reply(config.tips);
30
+ return new Promise<T>(async (resolve, reject) => {
31
+ const id = await this.event.$reply(config.tips);
32
32
  this.middleware(
33
33
  input => {
34
34
  if (input instanceof Error) {
@@ -37,6 +37,7 @@ export class Prompt<P extends RegisteredAdapter> {
37
37
  else reject(input);
38
38
  return;
39
39
  }
40
+ this.plugin.recallMessage(this.event.$adapter,this.event.$bot,id);
40
41
  resolve(config.format(input));
41
42
  },
42
43
  config.timeout,
package/src/types.ts CHANGED
@@ -1,21 +1,10 @@
1
- import {MaybePromise}from '@zhin.js/types'
2
- import {MessageChannel} from "./message.js";
1
+ import {MaybePromise,RegisteredAdapters}from '@zhin.js/types'
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";
6
6
  import { MessageComponent } from "./message.js";
7
7
 
8
- /**
9
- * 类型定义文件:包含适配器、消息、数据库、配置等核心类型声明。
10
- * 协作者可通过本文件了解各主要数据结构的用途与关系。
11
- */
12
- declare module '@zhin.js/types'{
13
- interface GlobalContext extends RegisteredAdapters{}
14
- }
15
- /**
16
- * 所有已注册适配器的类型映射(key为适配器名,value为Adapter实例)
17
- */
18
- export interface RegisteredAdapters extends Record<string, Adapter>{}
19
8
  /**
20
9
  * 数据库配置类型,支持多种数据库驱动
21
10
  */
@@ -68,6 +57,7 @@ export type SendContent=MaybeArray<string|MessageElement>
68
57
  export interface MessageSender{
69
58
  id: string;
70
59
  name?: string;
60
+ permissions?:string[]
71
61
  }
72
62
  /**
73
63
  * 通用字典类型
@@ -109,6 +99,15 @@ export interface AppConfig {
109
99
  disable_dependencies?: string[];
110
100
  /** 是否启用调试模式 */
111
101
  debug?: boolean;
102
+ /** 日志配置 */
103
+ log?: {
104
+ /** 最大日志保留天数,默认 7 天 */
105
+ maxDays?: number;
106
+ /** 最大日志条数,默认 10000 条 */
107
+ maxRecords?: number;
108
+ /** 自动清理间隔(小时),默认 24 小时 */
109
+ cleanupInterval?: number;
110
+ };
112
111
  }
113
112
  /**
114
113
  * defineConfig辅助类型,支持函数式/对象式配置