@zhin.js/core 1.0.16 → 1.0.18
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 +19 -0
- package/REFACTORING_COMPLETE.md +178 -0
- package/REFACTORING_STATUS.md +263 -0
- package/lib/adapter.d.ts +44 -19
- package/lib/adapter.d.ts.map +1 -1
- package/lib/adapter.js +81 -50
- package/lib/adapter.js.map +1 -1
- package/lib/bot.d.ts +7 -12
- package/lib/bot.d.ts.map +1 -1
- package/lib/built/adapter-process.d.ts +36 -0
- package/lib/built/adapter-process.d.ts.map +1 -0
- package/lib/built/adapter-process.js +77 -0
- package/lib/built/adapter-process.js.map +1 -0
- package/lib/built/command.d.ts +46 -0
- package/lib/built/command.d.ts.map +1 -0
- package/lib/built/command.js +54 -0
- package/lib/built/command.js.map +1 -0
- package/lib/built/component.d.ts +42 -0
- package/lib/built/component.d.ts.map +1 -0
- package/lib/built/component.js +66 -0
- package/lib/built/component.js.map +1 -0
- package/lib/built/config.d.ts +31 -0
- package/lib/built/config.d.ts.map +1 -0
- package/lib/built/config.js +141 -0
- package/lib/built/config.js.map +1 -0
- package/lib/built/cron.d.ts +53 -0
- package/lib/built/cron.d.ts.map +1 -0
- package/lib/built/cron.js +79 -0
- package/lib/built/cron.js.map +1 -0
- package/lib/built/database.d.ts +17 -0
- package/lib/built/database.d.ts.map +1 -0
- package/lib/built/database.js +28 -0
- package/lib/built/database.js.map +1 -0
- package/lib/{permissions.d.ts → built/permission.d.ts} +5 -10
- package/lib/built/permission.d.ts.map +1 -0
- package/lib/{permissions.js → built/permission.js} +11 -10
- package/lib/built/permission.js.map +1 -0
- package/lib/command.d.ts +18 -7
- package/lib/command.d.ts.map +1 -1
- package/lib/command.js +36 -15
- package/lib/command.js.map +1 -1
- package/lib/component.d.ts +1 -1
- package/lib/component.d.ts.map +1 -1
- package/lib/component.js.map +1 -1
- package/lib/cron.d.ts +4 -12
- package/lib/cron.d.ts.map +1 -1
- package/lib/cron.js +33 -64
- package/lib/cron.js.map +1 -1
- package/lib/index.d.ts +11 -3
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +14 -4
- package/lib/index.js.map +1 -1
- package/lib/jsx-runtime.d.ts +2 -2
- package/lib/jsx.d.ts +2 -3
- package/lib/jsx.d.ts.map +1 -1
- package/lib/jsx.js.map +1 -1
- package/lib/message.d.ts +4 -7
- package/lib/message.d.ts.map +1 -1
- package/lib/message.js.map +1 -1
- package/lib/plugin.d.ts +164 -51
- package/lib/plugin.d.ts.map +1 -1
- package/lib/plugin.js +520 -137
- package/lib/plugin.js.map +1 -1
- package/lib/prompt.d.ts +1 -1
- package/lib/prompt.d.ts.map +1 -1
- package/lib/prompt.js +2 -1
- package/lib/prompt.js.map +1 -1
- package/lib/types.d.ts +33 -33
- package/lib/types.d.ts.map +1 -1
- package/lib/utils.d.ts +16 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +166 -66
- package/lib/utils.js.map +1 -1
- package/package.json +17 -11
- package/src/adapter.ts +131 -80
- package/src/bot.ts +8 -13
- package/src/built/adapter-process.ts +77 -0
- package/src/built/command.ts +102 -0
- package/src/built/component.ts +111 -0
- package/src/built/config.ts +126 -0
- package/src/built/cron.ts +140 -0
- package/src/built/database.ts +38 -0
- package/src/{permissions.ts → built/permission.ts} +9 -12
- package/src/command.ts +48 -20
- package/src/component.ts +2 -3
- package/src/cron.ts +35 -70
- package/src/index.ts +15 -5
- package/src/jsx.ts +2 -3
- package/src/message.ts +3 -4
- package/src/plugin.ts +671 -184
- package/src/prompt.ts +4 -3
- package/src/types.ts +41 -35
- package/src/utils.ts +418 -296
- package/test/minimal-bot.ts +31 -0
- package/test/stress-test.ts +123 -0
- package/tests/command.test.ts +124 -44
- package/ASYNC-JSX-SUPPORT.md +0 -173
- package/lib/app.d.ts +0 -191
- package/lib/app.d.ts.map +0 -1
- package/lib/app.js +0 -604
- package/lib/app.js.map +0 -1
- package/lib/config.d.ts +0 -54
- package/lib/config.d.ts.map +0 -1
- package/lib/config.js +0 -308
- package/lib/config.js.map +0 -1
- package/lib/log-transport.d.ts +0 -37
- package/lib/log-transport.d.ts.map +0 -1
- package/lib/log-transport.js +0 -136
- package/lib/log-transport.js.map +0 -1
- package/lib/permissions.d.ts.map +0 -1
- package/lib/permissions.js.map +0 -1
- package/src/app.ts +0 -772
- package/src/config.ts +0 -397
- package/src/log-transport.ts +0 -163
- package/tests/app.test.ts +0 -265
- package/tests/permissions.test.ts +0 -358
- package/tests/plugin.test.ts +0 -234
- package/tests/prompt.test.ts +0 -223
package/src/adapter.ts
CHANGED
|
@@ -1,92 +1,143 @@
|
|
|
1
|
-
import {Bot} from "./bot.js";
|
|
2
|
-
import {Plugin} from "./plugin.js";
|
|
1
|
+
import { Bot } from "./bot.js";
|
|
2
|
+
import { Plugin } from "./plugin.js";
|
|
3
|
+
import { EventEmitter } from "events";
|
|
4
|
+
import { Message } from "./message.js";
|
|
5
|
+
import { BeforeSendHandler, SendOptions } from "./types.js";
|
|
6
|
+
import { segment } from "./utils.js";
|
|
3
7
|
/**
|
|
4
8
|
* Adapter类:适配器抽象,管理多平台Bot实例。
|
|
5
9
|
* 负责根据配置启动/关闭各平台机器人,统一异常处理。
|
|
6
10
|
*/
|
|
7
|
-
export class Adapter<R extends Bot=Bot>{
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
if(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
11
|
+
export abstract class Adapter<R extends Bot = Bot> extends EventEmitter<Adapter.Lifecycle> {
|
|
12
|
+
/** 当前适配器下所有Bot实例,key为bot名称 */
|
|
13
|
+
public bots: Map<string, R> = new Map<string, R>();
|
|
14
|
+
/**
|
|
15
|
+
* 构造函数
|
|
16
|
+
* @param name 适配器名称(如 'process'、'qq' 等)
|
|
17
|
+
* @param botFactory Bot工厂函数或构造器
|
|
18
|
+
*/
|
|
19
|
+
constructor(
|
|
20
|
+
public plugin: Plugin,
|
|
21
|
+
public name: keyof Plugin.Contexts,
|
|
22
|
+
public config: Adapter.BotConfig<R>[]
|
|
23
|
+
) {
|
|
24
|
+
super();
|
|
25
|
+
this.on('call.recallMessage', async(bot_id, id) => {
|
|
26
|
+
const bot = this.bots.get(bot_id);
|
|
27
|
+
if(!bot) throw new Error(`Bot ${bot_id} not found`);
|
|
28
|
+
this.logger.info(`${bot_id} recall ${id}`);
|
|
29
|
+
await bot.$recallMessage(id);
|
|
30
|
+
})
|
|
31
|
+
this.on('call.sendMessage', async (bot_id:string,options:SendOptions) => {
|
|
32
|
+
const fns=this.plugin.root.listeners('before.sendMessage') as BeforeSendHandler[];
|
|
33
|
+
for(const fn of fns){
|
|
34
|
+
const result=await fn(options);
|
|
35
|
+
if(result) options=result;
|
|
36
|
+
}
|
|
37
|
+
const bot = this.bots.get(bot_id);
|
|
38
|
+
if(!bot) throw new Error(`Bot ${bot_id} not found`);
|
|
39
|
+
this.logger.info(`${bot_id} send ${options.type}(${options.id}):${segment.raw(options.content)}`);
|
|
40
|
+
return await bot.$sendMessage(options);
|
|
41
|
+
});
|
|
42
|
+
this.on('message.receive', (message) => {
|
|
43
|
+
this.logger.info(`${message.$bot} recv ${message.$channel.type}(${message.$channel.id}):${segment.raw(message.$content)}`);
|
|
44
|
+
this.plugin?.middleware(message, async ()=>{});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
abstract createBot(config: Adapter.BotConfig<R>): R;
|
|
48
|
+
get logger() {
|
|
49
|
+
if(!this.plugin) throw new Error("Adapter is not associated with any plugin");
|
|
50
|
+
return this.plugin.logger;
|
|
51
|
+
}
|
|
52
|
+
binding(plugin: Plugin) {
|
|
53
|
+
this.plugin = plugin;
|
|
54
|
+
}
|
|
55
|
+
async start() {
|
|
56
|
+
this.plugin.root.adapters.push(this.name);
|
|
57
|
+
if (!this.config?.length) return;
|
|
49
58
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
59
|
+
for (const config of this.config) {
|
|
60
|
+
const bot = this.createBot(config);
|
|
61
|
+
await bot.$connect();
|
|
62
|
+
this.logger.debug(`bot ${bot.$id} of adapter ${this.name} connected`);
|
|
63
|
+
this.bots.set(bot.$id, bot);
|
|
55
64
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
65
|
+
this.logger.debug(`adapter ${this.name} started`);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 停止适配器,断开并移除所有Bot实例
|
|
69
|
+
* @param plugin 所属插件实例
|
|
70
|
+
*/
|
|
71
|
+
async stop() {
|
|
72
|
+
try {
|
|
73
|
+
for (const [id, bot] of this.bots) {
|
|
61
74
|
try {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
await bot.$disconnect()
|
|
65
|
-
plugin.logger.info(`bot ${name} of adapter ${this.name} disconnected`)
|
|
66
|
-
this.bots.delete(name)
|
|
67
|
-
} catch (error) {
|
|
68
|
-
// 如果断开连接失败,确保错误正确传播
|
|
69
|
-
throw error
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
plugin.logger.info(`adapter ${this.name} stopped`)
|
|
75
|
+
await bot.$disconnect();
|
|
76
|
+
this.logger.debug(`bot ${id} of adapter ${this.name} disconnected`);
|
|
73
77
|
} catch (error) {
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
// 如果断开连接失败,确保错误正确传播
|
|
79
|
+
throw error;
|
|
76
80
|
}
|
|
81
|
+
}
|
|
82
|
+
// 清理 bots Map
|
|
83
|
+
this.bots.clear();
|
|
84
|
+
|
|
85
|
+
// 从 adapters 数组中移除
|
|
86
|
+
const idx = this.plugin.root.adapters.indexOf(this.name);
|
|
87
|
+
if (idx !== -1) {
|
|
88
|
+
this.plugin.root.adapters.splice(idx, 1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 移除所有事件监听器
|
|
92
|
+
this.removeAllListeners();
|
|
93
|
+
|
|
94
|
+
this.logger.info(`adapter ${this.name} stopped`);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
// 确保错误正确传播
|
|
97
|
+
throw error;
|
|
77
98
|
}
|
|
99
|
+
}
|
|
78
100
|
}
|
|
101
|
+
export interface Adapters {}
|
|
79
102
|
export namespace Adapter {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
103
|
+
export type Factory<R extends Adapter = Adapter> = {
|
|
104
|
+
new (
|
|
105
|
+
plugin: Plugin,
|
|
106
|
+
name: string,
|
|
107
|
+
config: Adapter.BotConfig<Adapter.InferBot<R>>[]
|
|
108
|
+
):R
|
|
109
|
+
};
|
|
110
|
+
export interface Lifecycle {
|
|
111
|
+
'message.receive': [Message];
|
|
112
|
+
'message.private.receive': [Message];
|
|
113
|
+
'message.group.receive': [Message];
|
|
114
|
+
'message.channel.receive': [Message];
|
|
115
|
+
'call.recallMessage': [string, string];
|
|
116
|
+
'call.sendMessage': [string, SendOptions];
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* 适配器工厂注册表
|
|
120
|
+
* 灵感来源于 zhinjs/next 的 Adapter.Registry
|
|
121
|
+
*/
|
|
122
|
+
export const Registry = new Map<string, Factory>();
|
|
123
|
+
export type InferBot<R extends Adapter=Adapter> = R extends Adapter<infer T>
|
|
124
|
+
? T
|
|
125
|
+
: never;
|
|
126
|
+
export type BotConfig<T extends Bot> = T extends Bot<infer R> ? R : never;
|
|
127
|
+
export type BotMessage<T extends Bot> = T extends Bot<infer _L, infer R>
|
|
128
|
+
? R
|
|
129
|
+
: never;
|
|
130
|
+
/**
|
|
131
|
+
* 注册适配器工厂
|
|
132
|
+
*
|
|
133
|
+
* @param name 适配器名称
|
|
134
|
+
* @param factory 适配器工厂函数
|
|
135
|
+
* @example
|
|
136
|
+
* ```typescript
|
|
137
|
+
* Adapter.register('icqq', IcqqAdapter);
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
export function register(name: string, factory: Factory) {
|
|
141
|
+
Registry.set(name, factory);
|
|
142
|
+
}
|
|
143
|
+
}
|
package/src/bot.ts
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import type { SendOptions } from "./types.js";
|
|
2
2
|
import { Message } from "./message.js";
|
|
3
|
+
import { Adapter, Adapters } from "./adapter.js";
|
|
3
4
|
/**
|
|
4
5
|
* Bot接口:所有平台机器人需实现的统一接口。
|
|
5
6
|
* 负责消息格式化、连接、断开、消息发送等。
|
|
6
7
|
* @template M 消息类型
|
|
7
8
|
* @template T 配置类型
|
|
8
9
|
*/
|
|
9
|
-
export interface Bot<
|
|
10
|
+
export interface Bot<Config extends object= {},Event extends object = {}> {
|
|
11
|
+
$id:string
|
|
10
12
|
/** 机器人配置 */
|
|
11
|
-
$config:
|
|
13
|
+
$config: Config;
|
|
12
14
|
/** 是否已连接 */
|
|
13
|
-
$connected
|
|
15
|
+
$connected: boolean;
|
|
14
16
|
/** 格式化平台消息为标准Message结构 */
|
|
15
|
-
$formatMessage(
|
|
17
|
+
$formatMessage(event: Event): Message<Event>;
|
|
16
18
|
/** 连接机器人 */
|
|
17
19
|
$connect(): Promise<void>;
|
|
18
20
|
/** 断开机器人 */
|
|
@@ -23,12 +25,5 @@ export interface Bot<M extends object = {}, T extends Bot.Config = Bot.Config> {
|
|
|
23
25
|
$sendMessage(options: SendOptions): Promise<string>;
|
|
24
26
|
}
|
|
25
27
|
export namespace Bot {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
*/
|
|
29
|
-
export interface Config {
|
|
30
|
-
context: string;
|
|
31
|
-
name: string;
|
|
32
|
-
[key: string]: any;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
28
|
+
export type Config =Adapter.BotConfig<Adapters[keyof Adapters]>
|
|
29
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Adapter, Plugin, Bot, SendContent, SendOptions, MessageBase, Message, segment } from "@zhin.js/core";
|
|
2
|
+
export class ProcessBot implements Bot<{},{content:string,ts:number}>{
|
|
3
|
+
$id=`${process.pid}`;
|
|
4
|
+
get logger() {
|
|
5
|
+
return this.adapter.logger;
|
|
6
|
+
}
|
|
7
|
+
$connected=false;
|
|
8
|
+
constructor(public adapter: ProcessAdapter, public $config={}) {
|
|
9
|
+
this.$listenInput=this.$listenInput.bind(this);
|
|
10
|
+
}
|
|
11
|
+
$listenInput:(data:Buffer<ArrayBufferLike>)=>void=function (this:ProcessBot,data){
|
|
12
|
+
const content = data.toString().trim();
|
|
13
|
+
if (content) {
|
|
14
|
+
this.adapter.emit('message.receive', this.$formatMessage({content,ts:Date.now()}));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async connect() {
|
|
18
|
+
}
|
|
19
|
+
async disconnect() {
|
|
20
|
+
}
|
|
21
|
+
$formatMessage(event: {content:string,ts:number}): Message<{content:string,ts:number}> {
|
|
22
|
+
const base:MessageBase={
|
|
23
|
+
$id: `${event.ts}`,
|
|
24
|
+
$adapter: 'process',
|
|
25
|
+
$bot: `${process.pid}`,
|
|
26
|
+
$sender: {
|
|
27
|
+
id: `${process.pid}`,
|
|
28
|
+
name: process.title,
|
|
29
|
+
},
|
|
30
|
+
$channel: {
|
|
31
|
+
id: `${process.pid}`,
|
|
32
|
+
type: 'private',
|
|
33
|
+
},
|
|
34
|
+
$content: [{type:'text',data:{text:event.content}}],
|
|
35
|
+
$raw: event.content,
|
|
36
|
+
$timestamp: event.ts,
|
|
37
|
+
$recall: async () => {
|
|
38
|
+
await this.$recallMessage(base.$id)
|
|
39
|
+
},
|
|
40
|
+
$reply: async (content: SendContent) => {
|
|
41
|
+
return await this.$sendMessage({
|
|
42
|
+
context: 'process',
|
|
43
|
+
bot: base.$bot,
|
|
44
|
+
content,
|
|
45
|
+
id: base.$id,
|
|
46
|
+
type: base.$channel.type,
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
return Message.from(event, base);
|
|
51
|
+
}
|
|
52
|
+
async $recallMessage(id: string) {
|
|
53
|
+
}
|
|
54
|
+
async $sendMessage(options: SendOptions) {
|
|
55
|
+
return `${Date.now()}`;
|
|
56
|
+
}
|
|
57
|
+
async $connect(): Promise<void> {
|
|
58
|
+
process.stdin.on('data', this.$listenInput);
|
|
59
|
+
this.$connected = true;
|
|
60
|
+
}
|
|
61
|
+
async $disconnect() {
|
|
62
|
+
process.stdin.removeListener('data', this.$listenInput);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export class ProcessAdapter extends Adapter<ProcessBot>{
|
|
66
|
+
constructor(plugin: Plugin) {
|
|
67
|
+
super(plugin, 'process', [{}]);
|
|
68
|
+
}
|
|
69
|
+
createBot(): ProcessBot {
|
|
70
|
+
return new ProcessBot(this);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
declare module '@zhin.js/core'{
|
|
74
|
+
interface RegisteredAdapters{
|
|
75
|
+
process:ProcessAdapter
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Context
|
|
3
|
+
* 管理所有插件注册的命令
|
|
4
|
+
*/
|
|
5
|
+
import { MessageCommand } from "../command.js";
|
|
6
|
+
import { Message } from "../message.js";
|
|
7
|
+
import { Context, Plugin, getPlugin } from "../plugin.js";
|
|
8
|
+
import type { RegisteredAdapter, AdapterMessage } from "../types.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* CommandContext 扩展方法类型
|
|
12
|
+
*/
|
|
13
|
+
export interface CommandContextExtensions {
|
|
14
|
+
/** 添加命令 */
|
|
15
|
+
addCommand<T extends RegisteredAdapter>(command: MessageCommand<T>): () => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// 扩展 Plugin 接口
|
|
19
|
+
declare module "../plugin.js" {
|
|
20
|
+
namespace Plugin {
|
|
21
|
+
interface Extensions extends CommandContextExtensions {}
|
|
22
|
+
interface Contexts {
|
|
23
|
+
command: CommandService;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 命令服务数据
|
|
30
|
+
*/
|
|
31
|
+
export interface CommandService {
|
|
32
|
+
/** 命令列表(保持顺序) */
|
|
33
|
+
readonly items: MessageCommand<RegisteredAdapter>[];
|
|
34
|
+
/** 按 pattern 索引 */
|
|
35
|
+
readonly byName: Map<string, MessageCommand<RegisteredAdapter>>;
|
|
36
|
+
/** 添加命令 */
|
|
37
|
+
add(command: MessageCommand<RegisteredAdapter>, pluginName: string): () => void;
|
|
38
|
+
/** 移除命令 */
|
|
39
|
+
remove(command: MessageCommand<RegisteredAdapter>): boolean;
|
|
40
|
+
/** 按名称获取 */
|
|
41
|
+
get(pattern: string): MessageCommand<RegisteredAdapter> | undefined;
|
|
42
|
+
/** 处理消息 */
|
|
43
|
+
handle(message: Message<AdapterMessage<RegisteredAdapter>>, plugin: Plugin): Promise<any>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 创建命令 Context
|
|
48
|
+
*/
|
|
49
|
+
export function createCommandService(): Context<'command', CommandContextExtensions> {
|
|
50
|
+
const items: MessageCommand<RegisteredAdapter>[] = [];
|
|
51
|
+
const byName = new Map<string, MessageCommand<RegisteredAdapter>>();
|
|
52
|
+
const pluginMap = new Map<MessageCommand<RegisteredAdapter>, string>();
|
|
53
|
+
|
|
54
|
+
const value: CommandService = {
|
|
55
|
+
items,
|
|
56
|
+
byName,
|
|
57
|
+
|
|
58
|
+
add(command, pluginName) {
|
|
59
|
+
items.push(command);
|
|
60
|
+
byName.set(command.pattern, command);
|
|
61
|
+
pluginMap.set(command, pluginName);
|
|
62
|
+
return () => value.remove(command);
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
remove(command) {
|
|
66
|
+
const index = items.indexOf(command);
|
|
67
|
+
if (index !== -1) {
|
|
68
|
+
items.splice(index, 1);
|
|
69
|
+
byName.delete(command.pattern);
|
|
70
|
+
pluginMap.delete(command);
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
get(pattern) {
|
|
77
|
+
return byName.get(pattern);
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
async handle(message, plugin) {
|
|
81
|
+
for (const command of items) {
|
|
82
|
+
const result = await command.handle(message, plugin);
|
|
83
|
+
if (result) return result;
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
name: 'command',
|
|
91
|
+
description: '命令服务',
|
|
92
|
+
value,
|
|
93
|
+
extensions: {
|
|
94
|
+
addCommand<T extends RegisteredAdapter>(command: MessageCommand<T>) {
|
|
95
|
+
const plugin = getPlugin();
|
|
96
|
+
const dispose = value.add(command as MessageCommand<RegisteredAdapter>, plugin.name);
|
|
97
|
+
plugin.onDispose(dispose);
|
|
98
|
+
return dispose;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component Context
|
|
3
|
+
* 管理所有插件注册的组件
|
|
4
|
+
*/
|
|
5
|
+
import { Component, renderComponents } from "../component.js";
|
|
6
|
+
import { SendOptions, MaybePromise } from "../types.js";
|
|
7
|
+
import { Context, Plugin, getPlugin } from "../plugin.js";
|
|
8
|
+
|
|
9
|
+
type Listener = (options: SendOptions) => MaybePromise<SendOptions>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* ComponentContext 扩展方法类型
|
|
13
|
+
*/
|
|
14
|
+
export interface ComponentContextExtensions {
|
|
15
|
+
/** 添加组件 */
|
|
16
|
+
addComponent<T extends Component<any>>(component: T): () => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 扩展 Plugin 接口
|
|
20
|
+
declare module "../plugin.js" {
|
|
21
|
+
namespace Plugin {
|
|
22
|
+
interface Extensions extends ComponentContextExtensions {}
|
|
23
|
+
interface Contexts {
|
|
24
|
+
component: ComponentService;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 组件服务数据
|
|
31
|
+
*/
|
|
32
|
+
export interface ComponentService {
|
|
33
|
+
/** 按名称索引 */
|
|
34
|
+
readonly byName: Map<string, Component<any>>;
|
|
35
|
+
/** 添加组件 */
|
|
36
|
+
add(component: Component<any>, pluginName: string): () => void;
|
|
37
|
+
/** 获取所有组件名称 */
|
|
38
|
+
getAllNames(): string[];
|
|
39
|
+
/** 移除组件 */
|
|
40
|
+
remove(component: Component<any>): boolean;
|
|
41
|
+
/** 按名称获取 */
|
|
42
|
+
get(name: string): Component<any> | undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 创建组件 Context
|
|
47
|
+
*/
|
|
48
|
+
export function createComponentService(): Context<'component', ComponentContextExtensions> {
|
|
49
|
+
const byName = new Map<string, Component<any>>();
|
|
50
|
+
const pluginMap = new Map<Component<any>, string>();
|
|
51
|
+
let listener: Listener | undefined;
|
|
52
|
+
let rootPlugin: Plugin | undefined;
|
|
53
|
+
|
|
54
|
+
const value: ComponentService = {
|
|
55
|
+
byName,
|
|
56
|
+
|
|
57
|
+
add(component, pluginName) {
|
|
58
|
+
byName.set(component.name, component);
|
|
59
|
+
pluginMap.set(component, pluginName);
|
|
60
|
+
return () => value.remove(component);
|
|
61
|
+
},
|
|
62
|
+
getAllNames() {
|
|
63
|
+
return Array.from(byName.keys());
|
|
64
|
+
},
|
|
65
|
+
remove(component) {
|
|
66
|
+
if (byName.has(component.name)) {
|
|
67
|
+
byName.delete(component.name);
|
|
68
|
+
pluginMap.delete(component);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
get(name) {
|
|
75
|
+
return byName.get(name);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
name: 'component',
|
|
81
|
+
description: '组件服务',
|
|
82
|
+
value,
|
|
83
|
+
|
|
84
|
+
mounted(plugin: Plugin) {
|
|
85
|
+
rootPlugin = plugin;
|
|
86
|
+
// 创建消息渲染监听器
|
|
87
|
+
listener = (options: SendOptions) => {
|
|
88
|
+
return renderComponents(byName, options);
|
|
89
|
+
};
|
|
90
|
+
plugin.root.on('before.sendMessage', listener);
|
|
91
|
+
return value;
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
dispose() {
|
|
95
|
+
if (listener && rootPlugin) {
|
|
96
|
+
rootPlugin.root.off('before.sendMessage', listener);
|
|
97
|
+
listener = undefined;
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
extensions: {
|
|
102
|
+
addComponent<T extends Component<any>>(component: T) {
|
|
103
|
+
const plugin = getPlugin();
|
|
104
|
+
const dispose = value.add(component, plugin.name);
|
|
105
|
+
plugin.onDispose(dispose);
|
|
106
|
+
return dispose;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { stringify as stringifyYaml,parse as parseYaml } from "yaml";
|
|
4
|
+
import { Schema } from "@zhin.js/schema";
|
|
5
|
+
export class ConfigLoader<T extends object>{
|
|
6
|
+
#data: T;
|
|
7
|
+
get data(): T {
|
|
8
|
+
return this.#proxy(this.#data,this);
|
|
9
|
+
}
|
|
10
|
+
get raw(): T {
|
|
11
|
+
return this.#data;
|
|
12
|
+
}
|
|
13
|
+
get extension() {
|
|
14
|
+
return path.extname(this.filename).toLowerCase();
|
|
15
|
+
}
|
|
16
|
+
constructor(public filename: string, public initial:T, public schema?: Schema<T>) {
|
|
17
|
+
this.#data = this.initial;
|
|
18
|
+
}
|
|
19
|
+
#proxy<R extends object>(data: R, loader: ConfigLoader<T>) {
|
|
20
|
+
return new Proxy(data, {
|
|
21
|
+
get(target, prop, receiver) {
|
|
22
|
+
const result= Reflect.get(target, prop, receiver);
|
|
23
|
+
if(result instanceof Object && result!==null) return loader.#proxy(result,loader);
|
|
24
|
+
if(typeof result==='string') {
|
|
25
|
+
if(result.startsWith('\\${') && result.endsWith('}')) return result.slice(1);
|
|
26
|
+
if(/^\$\{(.*)\}$/.test(result)){
|
|
27
|
+
const content = result.slice(2, -1);
|
|
28
|
+
const [key, ...rest] = content.split(':');
|
|
29
|
+
const defaultValue = rest.length > 0 ? rest.join(':') : undefined;
|
|
30
|
+
return process.env[key] ?? defaultValue ?? (loader.initial as any)[key] ?? result;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
},
|
|
35
|
+
set(target, prop, value, receiver) {
|
|
36
|
+
const result= Reflect.set(target, prop, value, receiver);
|
|
37
|
+
loader.save(loader.filename);
|
|
38
|
+
return result;
|
|
39
|
+
},
|
|
40
|
+
deleteProperty(target, prop) {
|
|
41
|
+
const result= Reflect.deleteProperty(target, prop);
|
|
42
|
+
loader.save(loader.filename);
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
load() {
|
|
48
|
+
const fullPath=path.resolve(process.cwd(), this.filename);
|
|
49
|
+
if (!fs.existsSync(fullPath)) {
|
|
50
|
+
this.save(fullPath);
|
|
51
|
+
}
|
|
52
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
53
|
+
let rawConfig: any;
|
|
54
|
+
switch (this.extension) {
|
|
55
|
+
case ".json":
|
|
56
|
+
rawConfig = JSON.parse(content);
|
|
57
|
+
break;
|
|
58
|
+
case ".yaml":
|
|
59
|
+
case ".yml":
|
|
60
|
+
rawConfig = parseYaml(content);
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
if(this.schema){
|
|
64
|
+
this.#data = this.schema(rawConfig || this.initial) as T;
|
|
65
|
+
}else{
|
|
66
|
+
this.#data = rawConfig as T;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
save(fullPath: string) {
|
|
70
|
+
switch (this.extension) {
|
|
71
|
+
case ".json":
|
|
72
|
+
fs.writeFileSync(fullPath, JSON.stringify(this.#data, null, 2));
|
|
73
|
+
break;
|
|
74
|
+
case ".yaml":
|
|
75
|
+
case ".yml":
|
|
76
|
+
fs.writeFileSync(fullPath, stringifyYaml(this.#data));
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export namespace ConfigLoader{
|
|
82
|
+
export const supportedExtensions = [".json", ".yaml", ".yml"];
|
|
83
|
+
export function load<T extends object>(filename: string, initial?:T, schema?: Schema<T>) {
|
|
84
|
+
const result = new ConfigLoader<T>(filename, initial??{} as T, schema);
|
|
85
|
+
result.load();
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export class ConfigService{
|
|
90
|
+
configs: Map<string, ConfigLoader<any>> = new Map();
|
|
91
|
+
constructor() {
|
|
92
|
+
}
|
|
93
|
+
load<T extends object>(filename: string, initial?:Partial<T>, schema?: Schema<T>) {
|
|
94
|
+
const ext = path.extname(filename).toLowerCase();
|
|
95
|
+
if (!ConfigLoader.supportedExtensions.includes(ext)) {
|
|
96
|
+
throw new Error(`不支持的配置文件格式: ${ext}`);
|
|
97
|
+
}
|
|
98
|
+
const config = ConfigLoader.load(filename, initial as T, schema);
|
|
99
|
+
this.configs.set(filename, config);
|
|
100
|
+
return config;
|
|
101
|
+
}
|
|
102
|
+
get<T extends object>(filename: string, initial?:Partial<T>, schema?: Schema<T>): T {
|
|
103
|
+
if(!this.configs.has(filename)) this.load(filename, initial, schema);
|
|
104
|
+
const config = this.configs.get(filename);
|
|
105
|
+
if(!config) throw new Error(`配置文件 ${filename} 未加载`);
|
|
106
|
+
return config.data as T;
|
|
107
|
+
}
|
|
108
|
+
getRaw<T extends object>(filename: string, initial?:Partial<T>, schema?: Schema<T>): T {
|
|
109
|
+
if(!this.configs.has(filename)) this.load(filename, initial, schema);
|
|
110
|
+
const config = this.configs.get(filename);
|
|
111
|
+
if(!config) throw new Error(`配置文件 ${filename} 未加载`);
|
|
112
|
+
return config.raw as T;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* 更新配置文件内容
|
|
116
|
+
* @param filename 配置文件名
|
|
117
|
+
* @param data 新的配置数据
|
|
118
|
+
*/
|
|
119
|
+
set<T extends object>(filename: string, data: T): void {
|
|
120
|
+
const config = this.configs.get(filename);
|
|
121
|
+
if(!config) throw new Error(`配置文件 ${filename} 未加载`);
|
|
122
|
+
// 直接更新内部数据(会触发 Proxy 的 set 拦截器并自动保存)
|
|
123
|
+
Object.assign(config.raw, data);
|
|
124
|
+
config.save(path.resolve(process.cwd(), filename));
|
|
125
|
+
}
|
|
126
|
+
}
|