@zhin.js/core 1.0.0

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/README.md +159 -0
  2. package/dist/adapter.d.ts +22 -0
  3. package/dist/adapter.d.ts.map +1 -0
  4. package/dist/adapter.js +67 -0
  5. package/dist/adapter.js.map +1 -0
  6. package/dist/app.d.ts +69 -0
  7. package/dist/app.d.ts.map +1 -0
  8. package/dist/app.js +307 -0
  9. package/dist/app.js.map +1 -0
  10. package/dist/bot.d.ts +9 -0
  11. package/dist/bot.d.ts.map +1 -0
  12. package/dist/bot.js +2 -0
  13. package/dist/bot.js.map +1 -0
  14. package/dist/config.d.ts +24 -0
  15. package/dist/config.d.ts.map +1 -0
  16. package/dist/config.js +242 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/index.d.ts +9 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +12 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/logger.d.ts +3 -0
  23. package/dist/logger.d.ts.map +1 -0
  24. package/dist/logger.js +3 -0
  25. package/dist/logger.js.map +1 -0
  26. package/dist/plugin.d.ts +41 -0
  27. package/dist/plugin.d.ts.map +1 -0
  28. package/dist/plugin.js +95 -0
  29. package/dist/plugin.js.map +1 -0
  30. package/dist/types-generator.d.ts +6 -0
  31. package/dist/types-generator.d.ts.map +1 -0
  32. package/dist/types-generator.js +69 -0
  33. package/dist/types-generator.js.map +1 -0
  34. package/dist/types.d.ts +69 -0
  35. package/dist/types.d.ts.map +1 -0
  36. package/dist/types.js +2 -0
  37. package/dist/types.js.map +1 -0
  38. package/package.json +29 -0
  39. package/src/adapter.ts +69 -0
  40. package/src/app.ts +339 -0
  41. package/src/bot.ts +9 -0
  42. package/src/config.ts +276 -0
  43. package/src/index.ts +14 -0
  44. package/src/logger.ts +3 -0
  45. package/src/plugin.ts +122 -0
  46. package/src/types-generator.ts +74 -0
  47. package/src/types.ts +74 -0
  48. package/tests/adapter.test.ts +187 -0
  49. package/tests/app.test.ts +207 -0
  50. package/tests/bot.test.ts +132 -0
  51. package/tests/config.test.ts +328 -0
  52. package/tests/logger.test.ts +170 -0
  53. package/tests/plugin.test.ts +226 -0
  54. package/tests/test-utils.ts +59 -0
  55. package/tests/types.test.ts +162 -0
  56. package/tsconfig.json +25 -0
  57. package/tsconfig.tsbuildinfo +1 -0
package/src/config.ts ADDED
@@ -0,0 +1,276 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+ import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
5
+ import { parse as parseToml } from 'toml';
6
+ import { config as loadDotenv } from 'dotenv';
7
+ import type { AppConfig,DefineConfig } from './types.js';
8
+
9
+ export interface ConfigOptions {
10
+ configPath?: string;
11
+ envPath?: string;
12
+ envOverride?: boolean;
13
+ }
14
+
15
+ /**
16
+ * 支持的配置文件格式
17
+ */
18
+ export type ConfigFormat = 'json' | 'yaml' | 'yml' | 'toml' | 'js' | 'ts';
19
+
20
+ /**
21
+ * 环境变量替换正则表达式,支持默认值语法: ${VAR_NAME:-default_value}
22
+ */
23
+ const ENV_VAR_REGEX = /\$\{([^}]+)\}/g;
24
+
25
+ /**
26
+ * 替换字符串中的环境变量
27
+ */
28
+ function replaceEnvVars(str: string): string {
29
+ return str.replace(ENV_VAR_REGEX, (match, content) => {
30
+ // 解析环境变量名和默认值
31
+ const colonIndex = content.indexOf(':-');
32
+ let envName: string;
33
+ let defaultValue: string | undefined;
34
+
35
+ if (colonIndex !== -1) {
36
+ // 格式: VAR_NAME:-default_value
37
+ envName = content.slice(0, colonIndex);
38
+ defaultValue = content.slice(colonIndex + 2);
39
+ } else {
40
+ // 格式: VAR_NAME
41
+ envName = content;
42
+ defaultValue = undefined;
43
+ }
44
+
45
+ const envValue = process.env[envName];
46
+
47
+ if (envValue !== undefined) {
48
+ return envValue;
49
+ } else if (defaultValue !== undefined) {
50
+ return defaultValue;
51
+ } else {
52
+ console.warn(`环境变量 ${envName} 未定义,保持原值`);
53
+ return match;
54
+ }
55
+ });
56
+ }
57
+
58
+ /**
59
+ * 递归替换对象中的环境变量
60
+ */
61
+ function replaceEnvVarsInObject(obj: any): any {
62
+ if (typeof obj === 'string') {
63
+ return replaceEnvVars(obj);
64
+ }
65
+
66
+ if (Array.isArray(obj)) {
67
+ return obj.map(item => replaceEnvVarsInObject(item));
68
+ }
69
+
70
+ if (obj && typeof obj === 'object') {
71
+ for (const [key, value] of Object.entries(obj)) {
72
+ obj[key] = replaceEnvVarsInObject(value);
73
+ }
74
+ return obj;
75
+ }
76
+
77
+ return obj;
78
+ }
79
+
80
+ /**
81
+ * 根据文件扩展名解析配置文件
82
+ */
83
+ async function parseConfigFile(content: string, format: ConfigFormat, filePath?: string): Promise<any> {
84
+ try {
85
+ switch (format) {
86
+ case 'json':
87
+ return JSON.parse(content);
88
+ case 'yaml':
89
+ case 'yml':
90
+ return parseYaml(content);
91
+ case 'toml':
92
+ return parseToml(content);
93
+ case 'js':
94
+ case 'ts':
95
+ if (!filePath) {
96
+ throw new Error('解析 JS/TS 配置文件需要提供文件路径');
97
+ }
98
+ // 使用动态导入加载 JS/TS 模块
99
+ const fileUrl = pathToFileURL(path.resolve(filePath)).href;
100
+ const module = await import(fileUrl);
101
+ // 支持 ES 模块的 default 导出和 CommonJS 模块
102
+ const result = module.default || module;
103
+ if(typeof result === 'function') return await result((process.env||{}) as Record<string,string>);
104
+ return result;
105
+ default:
106
+ throw new Error(`不支持的配置文件格式: ${format}`);
107
+ }
108
+ } catch (error) {
109
+ throw new Error(`解析配置文件失败: ${error}`);
110
+ }
111
+ }
112
+
113
+ /**
114
+ * 获取配置文件格式
115
+ */
116
+ function getConfigFormat(filePath: string): ConfigFormat {
117
+ const ext = path.extname(filePath).slice(1).toLowerCase();
118
+ if (!['json', 'yaml', 'yml', 'toml', 'js', 'ts'].includes(ext)) {
119
+ throw new Error(`不支持的配置文件格式: ${ext}`);
120
+ }
121
+ return ext as ConfigFormat;
122
+ }
123
+
124
+ /**
125
+ * 查找配置文件
126
+ */
127
+ function findConfigFile(cwd: string = process.cwd()): string | null {
128
+ const configNames = [
129
+ // 优先查找 zhin.config.* 格式
130
+ 'zhin.config.yaml',
131
+ 'zhin.config.yml',
132
+ 'zhin.config.json',
133
+ 'zhin.config.toml',
134
+ 'zhin.config.js',
135
+ 'zhin.config.ts',
136
+ // 然后查找 config.* 格式
137
+ 'config.yaml',
138
+ 'config.yml',
139
+ 'config.json',
140
+ 'config.toml',
141
+ 'config.js',
142
+ 'config.ts'
143
+ ];
144
+
145
+ for (const name of configNames) {
146
+ const filePath = path.join(cwd, name);
147
+ if (fs.existsSync(filePath)) {
148
+ return filePath;
149
+ }
150
+ }
151
+
152
+ return null;
153
+ }
154
+ export function defineConfig<T extends DefineConfig<AppConfig>>(config: T): T {
155
+ return config;
156
+ }
157
+ /**
158
+ * 加载配置文件
159
+ */
160
+ export async function loadConfig(options: ConfigOptions = {}): Promise<[string,AppConfig]> {
161
+ const { configPath, envPath, envOverride = true } = options;
162
+
163
+ // 加载环境变量
164
+ if (envPath) {
165
+ loadDotenv({ path: envPath, override: envOverride });
166
+ } else {
167
+ // 尝试加载默认的 .env 文件
168
+ const defaultEnvPath = path.join(process.cwd(), '.env');
169
+ if (fs.existsSync(defaultEnvPath)) {
170
+ loadDotenv({ path: defaultEnvPath, override: envOverride });
171
+ }
172
+ }
173
+
174
+ // 确定配置文件路径
175
+ let finalConfigPath: string;
176
+
177
+ if (configPath) {
178
+ if (!fs.existsSync(configPath)) {
179
+ throw new Error(`配置文件不存在: ${configPath}`);
180
+ }
181
+ finalConfigPath = configPath;
182
+ } else {
183
+ const foundPath = findConfigFile();
184
+ if (!foundPath) {
185
+ throw new Error('未找到配置文件,支持的文件名: zhin.config.[yaml|yml|json|toml|js|ts] 或 config.[yaml|yml|json|toml|js|ts]');
186
+ }
187
+ finalConfigPath = foundPath;
188
+ }
189
+
190
+ // 读取并解析配置文件
191
+ const format = getConfigFormat(finalConfigPath);
192
+ let rawConfig: any;
193
+
194
+ if (format === 'js' || format === 'ts') {
195
+ // JS/TS 文件不需要读取内容,直接解析
196
+ rawConfig = await parseConfigFile('', format, finalConfigPath);
197
+ } else {
198
+ const content = fs.readFileSync(finalConfigPath, 'utf-8');
199
+ rawConfig = await parseConfigFile(content, format, finalConfigPath);
200
+ }
201
+
202
+ // 替换环境变量
203
+ const config = replaceEnvVarsInObject(rawConfig) as AppConfig;
204
+
205
+ // 验证配置
206
+ validateConfig(config);
207
+
208
+ return [finalConfigPath,config];
209
+ }
210
+
211
+ /**
212
+ * 验证配置文件结构
213
+ */
214
+ function validateConfig(config: any): void {
215
+ if (!config) {
216
+ throw new Error('配置文件不能为空');
217
+ }
218
+
219
+ if (!config.bots || !Array.isArray(config.bots)) {
220
+ throw new Error('配置文件必须包含 bots 数组');
221
+ }
222
+
223
+
224
+ for (const [index, bot] of config.bots.entries()) {
225
+ if (!bot.name) {
226
+ throw new Error(`机器人 ${index} 缺少 name 字段`);
227
+ }
228
+
229
+ if (!bot.context) {
230
+ throw new Error(`机器人 ${bot.name} 缺少 context 字段`);
231
+ }
232
+ }
233
+ }
234
+
235
+ /**
236
+ * 保存配置文件
237
+ */
238
+ export function saveConfig(config: AppConfig, filePath: string): void {
239
+ const format = getConfigFormat(filePath);
240
+ let content: string;
241
+
242
+ switch (format) {
243
+ case 'json':
244
+ content = JSON.stringify(config, null, 2);
245
+ break;
246
+ case 'yaml':
247
+ case 'yml':
248
+ content = stringifyYaml(config, { indent: 2 });
249
+ break;
250
+ case 'toml':
251
+ // toml 库没有 stringify 方法,我们需要手动实现或使用其他库
252
+ throw new Error('暂不支持保存 TOML 格式的配置文件');
253
+ default:
254
+ throw new Error(`不支持的配置文件格式: ${format}`);
255
+ }
256
+
257
+ fs.writeFileSync(filePath, content, 'utf-8');
258
+ }
259
+
260
+ /**
261
+ * 创建默认配置
262
+ */
263
+ export function createDefaultConfig(format: ConfigFormat = 'yaml'): AppConfig {
264
+ return {
265
+ bots: [
266
+ {
267
+ name: 'onebot11',
268
+ context: 'onebot11',
269
+ url: '${ONEBOT_URL:-ws://localhost:8080}',
270
+ access_token: '${ONEBOT_ACCESS_TOKEN:-}'
271
+ }
272
+ ],
273
+ plugin_dirs: ['./src/plugins', 'node_modules'],
274
+ plugins: []
275
+ };
276
+ }
package/src/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ // ============================================================================
2
+ // Zhin Bot Framework - HMR Edition
3
+ // ============================================================================
4
+
5
+ // 核心系统
6
+ export type { Bot } from './bot.js';
7
+ export { loadConfig, saveConfig, createDefaultConfig } from './config.js';
8
+ export * from './types.js';
9
+ export * from './config.js';
10
+ export * from './adapter.js'
11
+ export * from './plugin.js';
12
+ // HMR Bot系统 (主要API)
13
+ export * from './app.js';
14
+ export * from '@zhin.js/hmr';
package/src/logger.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { ConsoleLogger } from "@zhin.js/hmr";
2
+
3
+ export const logger = new ConsoleLogger('[Core]', process.env.NODE_ENV === 'development');
package/src/plugin.ts ADDED
@@ -0,0 +1,122 @@
1
+ // ============================================================================
2
+ // 插件类型定义
3
+ // ============================================================================
4
+
5
+
6
+ import {MaybePromise} from '@zhin.js/types'
7
+ import { Message, BeforeSendHandler, SendOptions} from "./types";
8
+ import {Dependency, Logger,} from "@zhin.js/hmr";
9
+ import {App} from "./app";
10
+
11
+ /** 消息中间件函数 */
12
+ export type MessageMiddleware = (message: Message, next: () => Promise<void>) => MaybePromise<void>;
13
+
14
+ /** 事件监听器函数 */
15
+ export type EventListener<T = any> = (data: T) => void | Promise<void>;
16
+
17
+ /** 定时任务配置 */
18
+ export interface CronJob {
19
+ name: string;
20
+ schedule: string; // cron 表达式
21
+ handler: () => void | Promise<void>;
22
+ enabled?: boolean;
23
+ }
24
+
25
+
26
+ // ============================================================================
27
+ // Plugin 类
28
+ // ============================================================================
29
+
30
+ /**
31
+ * 插件类:继承自Dependency,提供机器人特定功能
32
+ */
33
+ export class Plugin extends Dependency<Plugin> {
34
+ middlewares: MessageMiddleware[] = [];
35
+ eventListeners = new Map<string, EventListener[]>();
36
+ cronJobs = new Map<string, CronJob>();
37
+
38
+ #logger?:Logger
39
+ constructor(parent: Dependency<Plugin>, name: string, filePath: string) {
40
+ super(parent, name, filePath);
41
+ this.on('message.receive',this.#handleMessage.bind(this))
42
+ }
43
+ #handleMessage(message:Message){
44
+ const next=async (index:number)=>{
45
+ if(!this.middlewares[index]) return
46
+ const middleware=this.middlewares[index]
47
+ middleware(message,()=>next(index+1))
48
+ }
49
+ next(0)
50
+ }
51
+ beforeSend(handler:BeforeSendHandler){
52
+ this.before('message.send',handler)
53
+ }
54
+ before(event:string,listener:(...args:any[])=>any){
55
+ this.on(`before-${event}`,listener)
56
+ }
57
+ /** 获取所属的App实例 */
58
+ get app(): App {
59
+ return this.parent as App;
60
+ }
61
+ get logger(): Logger {
62
+ if(this.#logger) return this.#logger
63
+ const names = [this.name];
64
+ let temp=this as Dependency<Plugin>
65
+ while(temp.parent){
66
+ names.unshift(temp.parent.name)
67
+ temp=temp.parent
68
+ }
69
+ return this.#logger=this.app.getLogger(...names)
70
+ }
71
+
72
+ /** 添加中间件 */
73
+ addMiddleware(middleware: MessageMiddleware): void {
74
+ this.middlewares.push(middleware);
75
+ this.dispatch('middleware.add',middleware)
76
+ }
77
+
78
+ /** 添加事件监听器 */
79
+ addEventListener<T = any>(event: string, listener: EventListener<T>): void {
80
+ if (!this.eventListeners.has(event)) {
81
+ this.eventListeners.set(event, []);
82
+ }
83
+ this.eventListeners.get(event)!.push(listener);
84
+ this.dispatch('listener.add',event,listener)
85
+ }
86
+
87
+ /** 添加定时任务 */
88
+ addCronJob(job: CronJob): void {
89
+ this.cronJobs.set(job.name, job);
90
+ this.dispatch('cron-job.add',job)
91
+ }
92
+ /** 发送消息 */
93
+ async sendMessage(options:SendOptions): Promise<void> {
94
+ await this.app.sendMessage(options);
95
+ }
96
+
97
+ /** 销毁插件 */
98
+ dispose(): void {
99
+ // 移除所有中间件
100
+ for (const middleware of this.middlewares) {
101
+ this.dispatch('middleware.remove', middleware)
102
+ }
103
+ this.middlewares = []
104
+
105
+ // 移除所有事件监听器
106
+ for (const [event, listeners] of this.eventListeners) {
107
+ for (const listener of listeners) {
108
+ this.dispatch('listener.remove', event, listener)
109
+ }
110
+ }
111
+ this.eventListeners.clear()
112
+
113
+ // 移除所有定时任务
114
+ for (const [name, job] of this.cronJobs) {
115
+ this.dispatch('cron-job.remove', job)
116
+ }
117
+ this.cronJobs.clear()
118
+
119
+ // 调用父类的dispose方法
120
+ super.dispose()
121
+ }
122
+ }
@@ -0,0 +1,74 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { logger } from './logger.js';
4
+
5
+ /**
6
+ * 更新 tsconfig.json 的类型声明
7
+ * @param cwd 项目根目录
8
+ */
9
+ export async function generateEnvTypes(cwd: string): Promise<void> {
10
+ try {
11
+ // 基础类型集合
12
+ const types = new Set(['@zhin.js/types']);
13
+
14
+ // 检查 package.json 中的依赖
15
+ const pkgPath = path.join(cwd, 'package.json');
16
+ if (fs.existsSync(pkgPath)) {
17
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
18
+ const allDeps = {
19
+ ...(pkg.dependencies || {}),
20
+ ...(pkg.devDependencies || {})
21
+ };
22
+
23
+ // 检查所有 @zhin.js/ 开头的包
24
+ for (const [name] of Object.entries(allDeps)) {
25
+ if (name.startsWith('@zhin.js/') || name === 'zhin.js') {
26
+ try {
27
+ const depPkgPath = path.join(cwd, 'node_modules', name, 'package.json');
28
+ if (fs.existsSync(depPkgPath)) {
29
+ const depPkg = JSON.parse(fs.readFileSync(depPkgPath, 'utf-8'));
30
+ if (depPkg.types || depPkg.typings) {
31
+ types.add(name);
32
+ }
33
+ }
34
+ } catch (err) {
35
+ // 如果读取失败,跳过这个包
36
+ continue;
37
+ }
38
+ }
39
+ }
40
+ }
41
+
42
+ // 更新或创建 tsconfig.json
43
+ const tsconfigPath = path.join(cwd, 'tsconfig.json');
44
+ let tsconfig:Record<string,any> = {};
45
+
46
+ // 读取现有的 tsconfig.json
47
+ if (fs.existsSync(tsconfigPath)) {
48
+ try {
49
+ tsconfig = JSON.parse(fs.readFileSync(tsconfigPath, 'utf-8'));
50
+ } catch (err) {
51
+ console.error(err)
52
+ logger.warn('⚠️ Failed to parse tsconfig.json, creating new one');
53
+ }
54
+ }
55
+
56
+ // 确保 compilerOptions 存在
57
+ if (!tsconfig.compilerOptions) {
58
+ tsconfig.compilerOptions = {};
59
+ }
60
+
61
+ // 合并现有的 types
62
+ const existingTypes = tsconfig.compilerOptions.types || [];
63
+ const allTypes = new Set([...existingTypes, ...types]);
64
+
65
+ // 更新 types 字段
66
+ tsconfig.compilerOptions.types = Array.from(allTypes);
67
+
68
+ // 写入文件
69
+ fs.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2), 'utf-8');
70
+ logger.info('✅ Updated TypeScript types configuration');
71
+ } catch (error) {
72
+ logger.warn('⚠️ Failed to update TypeScript types:', error);
73
+ }
74
+ }
package/src/types.ts ADDED
@@ -0,0 +1,74 @@
1
+ import {MaybePromise}from '@zhin.js/types'
2
+
3
+ export interface MessageSegment<T extends keyof Segment=keyof Segment> {
4
+ type: T;
5
+ data: Segment[T];
6
+ }
7
+ export interface Segment{
8
+ [key:string]:Record<string, any>
9
+ any:Record<string, any>
10
+ }
11
+ export type MaybeArray<T>=T|T[]
12
+ export type SendContent=MaybeArray<string|MessageSegment>
13
+ export interface MessageSender{
14
+ id: string;
15
+ name?: string;
16
+ }
17
+ export type MessageComponent<T extends object>=(props:T&{children:SendContent})=>MaybePromise<SendContent>
18
+ export interface MessageChannel{
19
+ id: string;
20
+ type: 'group' | 'private' | 'channel';
21
+ }
22
+ export interface Message {
23
+ id: string;
24
+ adapter:string
25
+ bot:string
26
+ content: MessageSegment[];
27
+ sender: MessageSender;
28
+ reply(content:SendContent,quote?:boolean|string):Promise<void>
29
+ channel: MessageChannel;
30
+ timestamp: number;
31
+ raw: string;
32
+ }
33
+
34
+ export interface User {
35
+ user_id: string;
36
+ nickname: string;
37
+ card?: string;
38
+ role?: string;
39
+ }
40
+
41
+ export interface Group {
42
+ group_id: string;
43
+ group_name: string;
44
+ member_count: number;
45
+ }
46
+
47
+ export interface BotConfig {
48
+ name: string;
49
+ context: string;
50
+ [key: string]: any;
51
+ }
52
+
53
+
54
+
55
+ export interface AppConfig {
56
+ /** 机器人配置列表 */
57
+ bots?: BotConfig[];
58
+ /** 插件目录列表,默认为 ['./plugins', 'node_modules'] */
59
+ plugin_dirs?: string[];
60
+ /** 需要加载的插件列表 */
61
+ plugins?: string[];
62
+ /** 禁用的依赖列表 */
63
+ disable_dependencies?: string[];
64
+ /** 是否启用调试模式 */
65
+ debug?: boolean;
66
+ }
67
+ export type DefineConfig<T> = T | ((env:Record<string,string>)=>MaybePromise<T>);
68
+
69
+ export interface SendOptions extends MessageChannel{
70
+ context:string
71
+ bot:string
72
+ content:SendContent
73
+ }
74
+ export type BeforeSendHandler=(options:SendOptions)=>MaybePromise<SendOptions|void>