hotcat-bot-qq 0.0.1 → 0.1.1

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/README.md CHANGED
@@ -19,7 +19,8 @@ bun add hotcat-bot-qq
19
19
  ## 快速开始
20
20
 
21
21
  ```ts
22
- import { BotClient, Message } from 'hotcat-bot-qq'
22
+ import { BotClient } from './botClient'
23
+ import { Message } from './message'
23
24
 
24
25
  const bot = new BotClient({
25
26
  baseUrl: 'ws://localhost:8082/onebot/v11/ws/',
@@ -39,14 +40,13 @@ bot.event.message.onGroupMessage(async (bot, event) => {
39
40
 
40
41
  - **类型安全** — 完整的 TypeScript 类型推导,封装 napcat 全部 API
41
42
  - **消息段构建** — `Message` 类支持文本、图片、表情、文件等所有消息类型
42
- - **彩色日志** — 内置 `BotConsole` 控制台日志,自动解析 CQ 码
43
43
  - **事件系统** — `BotEvent` 按类别分组暴露 message/messageSent/request/notice 事件
44
+ - **定时任务** — `BotScheduler` 支持 cron 表达式、固定间隔、指定时间
45
+ - **插件系统** — 继承 `PluginBase` 即可开发插件,支持热重载、目录监听自动加载,`scan` + `watch` 零配置接入
44
46
 
45
47
  ## 文档
46
48
 
47
- 完整文档 → [hotcat-bot-qq.docs.chcat1320.top](https://hotcat-bot-qq.docs.chcat1320.top)
48
-
49
- 快速开始 → [docs/guide/getting-started.md](docs/guide/getting-started.md)
49
+ 完整文档 → [框架开发文档](https://hotcat-bot-qq.docs.chcat1320.top)
50
50
 
51
51
  ## 协议
52
52
 
@@ -1,33 +1,35 @@
1
- import { NCWebsocketOptions } from 'node-napcat-ts';
2
1
  import { BotApi } from './botApi';
3
2
  import { BotEvent } from './botEvent';
3
+ import { BotScheduler } from './botScheduler';
4
+ /** Bot 配置 */
5
+ export interface BotConfig {
6
+ baseUrl: string;
7
+ accessToken?: string;
8
+ reconnection?: {
9
+ enable?: boolean;
10
+ attempts?: number;
11
+ delay?: number;
12
+ };
13
+ apiTimeout?: number;
14
+ }
4
15
  /**
5
16
  * Bot 客户端,封装连接、启动流程及 bot 自身信息。
6
17
  *
7
18
  * @example
8
- * ```ts
9
19
  * const bot = new BotClient({
10
- baseUrl: 'ws://localhost:8082/onebot/v11/ws/',
11
- accessToken: 'yourAccessToken',
12
- reconnection: {
13
- enable: true,
14
- attempts: 10,
15
- delay: 5000,
16
- },
17
- });
18
- * await bot.start();
19
- * ```
20
+ * baseUrl: 'ws://localhost:8082/onebot/v11/ws/',
21
+ * accessToken: 'yourAccessToken',
22
+ * })
23
+ * await bot.start()
20
24
  */
21
25
  export declare class BotClient {
22
- /** API 调用入口 */
23
26
  api: BotApi;
24
- /** 事件监听入口 */
25
27
  event: BotEvent;
26
- /** 登录后 bot 自身昵称 */
28
+ scheduler: BotScheduler;
29
+ plugin: import('./botPlugin').BotPluginSystem;
27
30
  nickname: string | null;
28
- /** 登录后 bot 自身 QQ 号 */
29
31
  id: number | null;
30
- constructor(config: NCWebsocketOptions);
32
+ constructor(config: BotConfig);
31
33
  /**
32
34
  * 连接 napcat 并初始化事件监听,启动成功后自动填充 {@link nickname} 和 {@link id}。
33
35
  */
package/dist/botClient.js CHANGED
@@ -1,47 +1,49 @@
1
1
  import { BotApi } from './botApi';
2
2
  import { BotConsole } from './botConsole';
3
3
  import { BotEvent } from './botEvent';
4
+ import { BotScheduler } from './botScheduler';
4
5
  /**
5
6
  * Bot 客户端,封装连接、启动流程及 bot 自身信息。
6
7
  *
7
8
  * @example
8
- * ```ts
9
9
  * const bot = new BotClient({
10
- baseUrl: 'ws://localhost:8082/onebot/v11/ws/',
11
- accessToken: 'yourAccessToken',
12
- reconnection: {
13
- enable: true,
14
- attempts: 10,
15
- delay: 5000,
16
- },
17
- });
18
- * await bot.start();
19
- * ```
10
+ * baseUrl: 'ws://localhost:8082/onebot/v11/ws/',
11
+ * accessToken: 'yourAccessToken',
12
+ * })
13
+ * await bot.start()
20
14
  */
21
15
  export class BotClient {
22
16
  constructor(config) {
23
- /** API 调用入口 */
24
17
  Object.defineProperty(this, "api", {
25
18
  enumerable: true,
26
19
  configurable: true,
27
20
  writable: true,
28
21
  value: void 0
29
22
  });
30
- /** 事件监听入口 */
31
23
  Object.defineProperty(this, "event", {
32
24
  enumerable: true,
33
25
  configurable: true,
34
26
  writable: true,
35
27
  value: void 0
36
28
  });
37
- /** 登录后 bot 自身昵称 */
29
+ Object.defineProperty(this, "scheduler", {
30
+ enumerable: true,
31
+ configurable: true,
32
+ writable: true,
33
+ value: void 0
34
+ });
35
+ Object.defineProperty(this, "plugin", {
36
+ enumerable: true,
37
+ configurable: true,
38
+ writable: true,
39
+ value: void 0
40
+ });
38
41
  Object.defineProperty(this, "nickname", {
39
42
  enumerable: true,
40
43
  configurable: true,
41
44
  writable: true,
42
45
  value: void 0
43
46
  });
44
- /** 登录后 bot 自身 QQ 号 */
45
47
  Object.defineProperty(this, "id", {
46
48
  enumerable: true,
47
49
  configurable: true,
@@ -51,6 +53,7 @@ export class BotClient {
51
53
  this.api = new BotApi(config);
52
54
  this.api.client = this;
53
55
  this.event = new BotEvent(this.api, this);
56
+ this.scheduler = new BotScheduler();
54
57
  this.nickname = null;
55
58
  this.id = null;
56
59
  }
@@ -59,6 +62,8 @@ export class BotClient {
59
62
  */
60
63
  async start() {
61
64
  await this.api.connect();
65
+ const { BotPluginSystem } = await import('./botPlugin');
66
+ this.plugin = new BotPluginSystem(this.api, this);
62
67
  this.api.napcat.on('meta_event.lifecycle.connect', () => {
63
68
  new BotConsole('system', 'napcat服务器连接成功').log();
64
69
  });
@@ -71,5 +76,7 @@ export class BotClient {
71
76
  catch (e) {
72
77
  new BotConsole('error', e instanceof Error ? e.message : JSON.stringify(e)).log();
73
78
  }
79
+ await this.plugin.scan('./plugins');
80
+ this.plugin.watch('./plugins');
74
81
  }
75
82
  }
@@ -0,0 +1,68 @@
1
+ import type { BotApi } from './botApi';
2
+ import type { BotClient } from './botClient';
3
+ import { PluginBase, PluginConstructor, PluginMeta } from './plugin';
4
+ /**
5
+ * 插件管理器,通过 `bot.plugin.xxx()` 访问。
6
+ *
7
+ * @example
8
+ * bot.plugin.register('test', TestPlugin, { name: 'test', version: '1.0.0' })
9
+ * await bot.plugin.load('test')
10
+ *
11
+ * // 自动扫描并监视
12
+ * await bot.plugin.scan('./plugins')
13
+ * bot.plugin.watch('./plugins')
14
+ */
15
+ export declare class BotPluginSystem {
16
+ private api;
17
+ private client;
18
+ private registry;
19
+ private instances;
20
+ private watcher;
21
+ private watchDir;
22
+ private watched;
23
+ constructor(api: BotApi, client: BotClient);
24
+ /**
25
+ * 注册插件(不加载)
26
+ * @param name - 唯一标识,重复注册抛错
27
+ * @param ctor - 插件类,必须实现 static create()
28
+ * @param meta - 插件元数据
29
+ */
30
+ register(name: string, ctor: PluginConstructor, meta: PluginMeta): void;
31
+ /**
32
+ * 加载插件
33
+ * @param name - 已注册的插件名
34
+ */
35
+ load(name: string): Promise<void>;
36
+ /**
37
+ * 卸载插件
38
+ * @param name - 已加载的插件名
39
+ */
40
+ unload(name: string): Promise<void>;
41
+ /**
42
+ * 热重载:先卸载再加载
43
+ * @param name - 已加载的插件名
44
+ */
45
+ reload(name: string): Promise<void>;
46
+ /** 获取已加载的插件实例 */
47
+ get(name: string): PluginBase | undefined;
48
+ /** 返回所有已注册的插件名列表 */
49
+ list(): string[];
50
+ /** 返回所有已加载的插件名列表 */
51
+ loaded(): string[];
52
+ /**
53
+ * 扫描目录,自动注册并加载所有插件
54
+ * @param dir - 插件目录路径
55
+ * @param autoLoad - 是否自动加载,默认 true
56
+ * @returns 加载成功的插件名列表
57
+ */
58
+ scan(dir: string, autoLoad?: boolean): Promise<string[]>;
59
+ /**
60
+ * 开始监听目录,自动加载新增插件、卸载已删除插件
61
+ * @param dir - 插件目录路径
62
+ * @param interval - 轮询间隔(ms),默认 5000
63
+ */
64
+ watch(dir: string, interval?: number): void;
65
+ /** 停止目录监听 */
66
+ unwatch(): void;
67
+ private scanOne;
68
+ }
@@ -0,0 +1,243 @@
1
+ import { BotConsole } from './botConsole';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ /**
5
+ * 插件管理器,通过 `bot.plugin.xxx()` 访问。
6
+ *
7
+ * @example
8
+ * bot.plugin.register('test', TestPlugin, { name: 'test', version: '1.0.0' })
9
+ * await bot.plugin.load('test')
10
+ *
11
+ * // 自动扫描并监视
12
+ * await bot.plugin.scan('./plugins')
13
+ * bot.plugin.watch('./plugins')
14
+ */
15
+ export class BotPluginSystem {
16
+ constructor(api, client) {
17
+ Object.defineProperty(this, "api", {
18
+ enumerable: true,
19
+ configurable: true,
20
+ writable: true,
21
+ value: void 0
22
+ });
23
+ Object.defineProperty(this, "client", {
24
+ enumerable: true,
25
+ configurable: true,
26
+ writable: true,
27
+ value: void 0
28
+ });
29
+ Object.defineProperty(this, "registry", {
30
+ enumerable: true,
31
+ configurable: true,
32
+ writable: true,
33
+ value: new Map()
34
+ });
35
+ Object.defineProperty(this, "instances", {
36
+ enumerable: true,
37
+ configurable: true,
38
+ writable: true,
39
+ value: new Map()
40
+ });
41
+ Object.defineProperty(this, "watcher", {
42
+ enumerable: true,
43
+ configurable: true,
44
+ writable: true,
45
+ value: null
46
+ });
47
+ Object.defineProperty(this, "watchDir", {
48
+ enumerable: true,
49
+ configurable: true,
50
+ writable: true,
51
+ value: null
52
+ });
53
+ Object.defineProperty(this, "watched", {
54
+ enumerable: true,
55
+ configurable: true,
56
+ writable: true,
57
+ value: new Set()
58
+ });
59
+ this.api = api;
60
+ this.client = client;
61
+ }
62
+ /**
63
+ * 注册插件(不加载)
64
+ * @param name - 唯一标识,重复注册抛错
65
+ * @param ctor - 插件类,必须实现 static create()
66
+ * @param meta - 插件元数据
67
+ */
68
+ register(name, ctor, meta) {
69
+ if (this.registry.has(name)) {
70
+ throw new Error(`插件 "${name}" 已注册`);
71
+ }
72
+ this.registry.set(name, { ctor, meta });
73
+ new BotConsole('system', `插件 "${meta.name}" 已注册`).log();
74
+ }
75
+ /**
76
+ * 加载插件
77
+ * @param name - 已注册的插件名
78
+ */
79
+ async load(name) {
80
+ if (this.instances.has(name)) {
81
+ throw new Error(`插件 "${name}" 已加载`);
82
+ }
83
+ const record = this.registry.get(name);
84
+ if (!record) {
85
+ throw new Error(`插件 "${name}" 未注册`);
86
+ }
87
+ const instance = record.ctor.create(this.api, this.client, record.meta);
88
+ await instance.load();
89
+ this.instances.set(name, instance);
90
+ new BotConsole('system', `插件 "${record.meta.name}" 已加载`).log();
91
+ }
92
+ /**
93
+ * 卸载插件
94
+ * @param name - 已加载的插件名
95
+ */
96
+ async unload(name) {
97
+ const instance = this.instances.get(name);
98
+ if (!instance) {
99
+ throw new Error(`插件 "${name}" 未加载`);
100
+ }
101
+ await instance.unload();
102
+ this.instances.delete(name);
103
+ new BotConsole('system', `插件 "${name}" 已卸载`).log();
104
+ }
105
+ /**
106
+ * 热重载:先卸载再加载
107
+ * @param name - 已加载的插件名
108
+ */
109
+ async reload(name) {
110
+ await this.unload(name);
111
+ await this.load(name);
112
+ }
113
+ /** 获取已加载的插件实例 */
114
+ get(name) {
115
+ return this.instances.get(name);
116
+ }
117
+ /** 返回所有已注册的插件名列表 */
118
+ list() {
119
+ return Array.from(this.registry.keys());
120
+ }
121
+ /** 返回所有已加载的插件名列表 */
122
+ loaded() {
123
+ return Array.from(this.instances.keys());
124
+ }
125
+ /**
126
+ * 扫描目录,自动注册并加载所有插件
127
+ * @param dir - 插件目录路径
128
+ * @param autoLoad - 是否自动加载,默认 true
129
+ * @returns 加载成功的插件名列表
130
+ */
131
+ async scan(dir, autoLoad = true) {
132
+ const loaded = [];
133
+ if (!fs.existsSync(dir))
134
+ return loaded;
135
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
136
+ for (const entry of entries) {
137
+ if (!entry.isDirectory())
138
+ continue;
139
+ const name = entry.name;
140
+ const indexPath = path.join(dir, name, 'index.ts');
141
+ if (!fs.existsSync(indexPath))
142
+ continue;
143
+ try {
144
+ const mod = await import(path.resolve(indexPath));
145
+ const ctor = mod[name.charAt(0).toUpperCase() + name.slice(1) + 'Plugin'] || mod.default || Object.values(mod).find((v) => typeof v === 'function' && v.create);
146
+ if (!ctor || typeof ctor.create !== 'function') {
147
+ new BotConsole('error', `插件目录 "${name}" 未导出符合规范的插件类`).log();
148
+ continue;
149
+ }
150
+ const meta = ctor.meta || {
151
+ name,
152
+ version: '0.0.0',
153
+ };
154
+ this.register(name, ctor, meta);
155
+ if (autoLoad) {
156
+ await this.load(name);
157
+ loaded.push(name);
158
+ }
159
+ }
160
+ catch (e) {
161
+ new BotConsole('error', `加载插件 "${name}" 失败: ${e.message || e}`).log();
162
+ }
163
+ }
164
+ return loaded;
165
+ }
166
+ /**
167
+ * 开始监听目录,自动加载新增插件、卸载已删除插件
168
+ * @param dir - 插件目录路径
169
+ * @param interval - 轮询间隔(ms),默认 5000
170
+ */
171
+ watch(dir, interval = 5000) {
172
+ if (this.watcher) {
173
+ new BotConsole('error', '已在监听中,请先调用 unwatch()').log();
174
+ return;
175
+ }
176
+ this.watchDir = path.resolve(dir);
177
+ if (!fs.existsSync(this.watchDir)) {
178
+ new BotConsole('error', `目录不存在: ${this.watchDir}`).log();
179
+ return;
180
+ }
181
+ this.watched = new Set(this.list());
182
+ new BotConsole('system', `开始监听插件目录: ${this.watchDir}`).log();
183
+ this.watcher = setInterval(async () => {
184
+ if (!fs.existsSync(this.watchDir))
185
+ return;
186
+ const entries = fs.readdirSync(this.watchDir, { withFileTypes: true });
187
+ const current = new Set();
188
+ for (const entry of entries) {
189
+ if (!entry.isDirectory())
190
+ continue;
191
+ const name = entry.name;
192
+ const indexPath = path.join(this.watchDir, name, 'index.ts');
193
+ if (!fs.existsSync(indexPath))
194
+ continue;
195
+ current.add(name);
196
+ if (!this.watched.has(name)) {
197
+ await this.scanOne(this.watchDir, name);
198
+ }
199
+ }
200
+ for (const name of Array.from(this.watched)) {
201
+ if (!current.has(name) && this.registry.has(name)) {
202
+ try {
203
+ if (this.instances.has(name)) {
204
+ await this.unload(name);
205
+ }
206
+ this.registry.delete(name);
207
+ new BotConsole('system', `插件 "${name}" 目录已删除,已移除`).log();
208
+ }
209
+ catch { }
210
+ }
211
+ }
212
+ this.watched = current;
213
+ }, interval);
214
+ }
215
+ /** 停止目录监听 */
216
+ unwatch() {
217
+ if (this.watcher) {
218
+ clearInterval(this.watcher);
219
+ this.watcher = null;
220
+ this.watchDir = null;
221
+ new BotConsole('system', '已停止插件目录监听').log();
222
+ }
223
+ }
224
+ async scanOne(dir, name) {
225
+ const indexPath = path.join(dir, name, 'index.ts');
226
+ try {
227
+ const mod = await import(indexPath);
228
+ const ctor = mod[name.charAt(0).toUpperCase() + name.slice(1) + 'Plugin'] || mod.default || Object.values(mod).find((v) => typeof v === 'function' && v.create);
229
+ if (!ctor || typeof ctor.create !== 'function')
230
+ return;
231
+ const meta = ctor.meta || {
232
+ name,
233
+ version: '0.0.0',
234
+ };
235
+ this.register(name, ctor, meta);
236
+ await this.load(name);
237
+ new BotConsole('system', `检测到新插件 "${name}",已自动加载`).log();
238
+ }
239
+ catch (e) {
240
+ new BotConsole('error', `自动加载插件 "${name}" 失败: ${e.message || e}`).log();
241
+ }
242
+ }
243
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * 定时任务调度器,支持 cron、间隔、指定时间三种模式。
3
+ *
4
+ * cron('0 8 * * *', fn) 每天 8:00
5
+ * every('5m', fn) 每5分钟
6
+ * at('08:00', fn) 每天 8:00
7
+ */
8
+ export declare class BotScheduler {
9
+ private jobs;
10
+ private idCounter;
11
+ /**
12
+ * cron 表达式定时
13
+ * @param expr - cron 表达式,如 `'0 8 * * *'`(每天 8:00)
14
+ * @param fn - 回调函数
15
+ * @returns job id,可用于 cancel
16
+ */
17
+ cron(expr: string, fn: () => void): number;
18
+ /**
19
+ * 间隔重复执行
20
+ * @param time - 间隔,支持数字(ms)或字符串 `'5s'` `'10m'` `'1h'` `'2d'`
21
+ * @param fn - 回调函数
22
+ * @returns job id
23
+ */
24
+ every(time: number | string, fn: () => void): number;
25
+ /**
26
+ * 指定时间执行(一次性或每日重复)
27
+ * @param time - 字符串 `'08:00'`(每日)/ `'2026-08-01 12:00'`(一次性),或 Date
28
+ * @param fn - 回调函数
29
+ * @returns job id
30
+ */
31
+ at(time: string | Date, fn: () => void): number;
32
+ /**
33
+ * 取消指定任务
34
+ * @param id - cron/every/at 返回的 id
35
+ */
36
+ cancel(id: number): void;
37
+ /** 取消全部任务 */
38
+ cancelAll(): void;
39
+ private parseTime;
40
+ private timeToCron;
41
+ }
@@ -0,0 +1,146 @@
1
+ import * as schedule from 'node-schedule';
2
+ /**
3
+ * 定时任务调度器,支持 cron、间隔、指定时间三种模式。
4
+ *
5
+ * cron('0 8 * * *', fn) 每天 8:00
6
+ * every('5m', fn) 每5分钟
7
+ * at('08:00', fn) 每天 8:00
8
+ */
9
+ export class BotScheduler {
10
+ constructor() {
11
+ Object.defineProperty(this, "jobs", {
12
+ enumerable: true,
13
+ configurable: true,
14
+ writable: true,
15
+ value: new Map()
16
+ });
17
+ Object.defineProperty(this, "idCounter", {
18
+ enumerable: true,
19
+ configurable: true,
20
+ writable: true,
21
+ value: 0
22
+ });
23
+ }
24
+ /**
25
+ * cron 表达式定时
26
+ * @param expr - cron 表达式,如 `'0 8 * * *'`(每天 8:00)
27
+ * @param fn - 回调函数
28
+ * @returns job id,可用于 cancel
29
+ */
30
+ cron(expr, fn) {
31
+ const id = ++this.idCounter;
32
+ const job = schedule.scheduleJob(expr, () => {
33
+ try {
34
+ fn();
35
+ }
36
+ catch { }
37
+ });
38
+ if (job)
39
+ this.jobs.set(id, job);
40
+ return id;
41
+ }
42
+ /**
43
+ * 间隔重复执行
44
+ * @param time - 间隔,支持数字(ms)或字符串 `'5s'` `'10m'` `'1h'` `'2d'`
45
+ * @param fn - 回调函数
46
+ * @returns job id
47
+ */
48
+ every(time, fn) {
49
+ const ms = typeof time === 'number' ? time : this.parseTime(time);
50
+ const id = ++this.idCounter;
51
+ const timer = setInterval(() => {
52
+ try {
53
+ fn();
54
+ }
55
+ catch { }
56
+ }, ms);
57
+ this.jobs.set(id, timer);
58
+ return id;
59
+ }
60
+ /**
61
+ * 指定时间执行(一次性或每日重复)
62
+ * @param time - 字符串 `'08:00'`(每日)/ `'2026-08-01 12:00'`(一次性),或 Date
63
+ * @param fn - 回调函数
64
+ * @returns job id
65
+ */
66
+ at(time, fn) {
67
+ const id = ++this.idCounter;
68
+ if (typeof time === 'string' && time.includes('-')) {
69
+ const target = new Date(time);
70
+ if (isNaN(target.getTime()))
71
+ return id;
72
+ const ms = target.getTime() - Date.now();
73
+ if (ms <= 0) {
74
+ try {
75
+ fn();
76
+ }
77
+ catch { }
78
+ return id;
79
+ }
80
+ const timer = setTimeout(() => { try {
81
+ fn();
82
+ }
83
+ catch { } }, ms);
84
+ this.jobs.set(id, timer);
85
+ return id;
86
+ }
87
+ if (typeof time === 'string') {
88
+ return this.cron(this.timeToCron(time), fn);
89
+ }
90
+ const ms = time.getTime() - Date.now();
91
+ if (ms <= 0) {
92
+ try {
93
+ fn();
94
+ }
95
+ catch { }
96
+ return id;
97
+ }
98
+ const timer = setTimeout(() => { try {
99
+ fn();
100
+ }
101
+ catch { } }, ms);
102
+ this.jobs.set(id, timer);
103
+ return id;
104
+ }
105
+ /**
106
+ * 取消指定任务
107
+ * @param id - cron/every/at 返回的 id
108
+ */
109
+ cancel(id) {
110
+ const job = this.jobs.get(id);
111
+ if (!job)
112
+ return;
113
+ if ('cancel' in job) {
114
+ job.cancel();
115
+ }
116
+ else {
117
+ clearInterval(job);
118
+ }
119
+ this.jobs.delete(id);
120
+ }
121
+ /** 取消全部任务 */
122
+ cancelAll() {
123
+ for (const id of Array.from(this.jobs.keys())) {
124
+ this.cancel(id);
125
+ }
126
+ }
127
+ parseTime(t) {
128
+ const m = t.match(/^(\d+)\s*(s|sec|secs|second|seconds|m|min|mins|minute|minutes|h|hr|hrs|hour|hours|d|day|days)$/i);
129
+ if (!m)
130
+ return 0;
131
+ const v = parseInt(m[1]);
132
+ switch (m[2][0].toLowerCase()) {
133
+ case 's': return v * 1000;
134
+ case 'm': return v * 60000;
135
+ case 'h': return v * 3600000;
136
+ case 'd': return v * 86400000;
137
+ default: return 0;
138
+ }
139
+ }
140
+ timeToCron(t) {
141
+ const m = t.match(/^(\d{1,2}):(\d{2})$/);
142
+ if (!m)
143
+ return '';
144
+ return `${m[2]} ${m[1]} * * *`;
145
+ }
146
+ }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,11 @@
1
1
  export { BotClient } from './botClient';
2
+ export type { BotConfig } from './botClient';
2
3
  export { BotApi } from './botApi';
3
4
  export { BotEvent, MessageEvent, MessageSent, RequestEvent, NoticeEvent } from './botEvent';
5
+ export { BotScheduler } from './botScheduler';
6
+ export { BotPluginSystem } from './botPlugin';
4
7
  export { BotConsole, colors, parseCQ, formatMessage } from './botConsole';
5
8
  export type { BotConsoleType, PrivateMessage, PrivateMessageSelf, RequestGroup, NoticeEvent as NoticeEventType } from './botConsole';
6
9
  export { Message } from './message';
10
+ export { PluginBase } from './plugin';
11
+ export type { PluginMeta, PluginConstructor } from './plugin';
package/dist/index.js CHANGED
@@ -1,5 +1,8 @@
1
1
  export { BotClient } from './botClient';
2
2
  export { BotApi } from './botApi';
3
3
  export { BotEvent, MessageEvent, MessageSent, RequestEvent, NoticeEvent } from './botEvent';
4
+ export { BotScheduler } from './botScheduler';
5
+ export { BotPluginSystem } from './botPlugin';
4
6
  export { BotConsole, colors, parseCQ, formatMessage } from './botConsole';
5
7
  export { Message } from './message';
8
+ export { PluginBase } from './plugin';
@@ -0,0 +1,27 @@
1
+ import type { BotApi } from './botApi';
2
+ import type { BotClient } from './botClient';
3
+ /** 插件元数据 */
4
+ export interface PluginMeta {
5
+ name: string;
6
+ version: string;
7
+ description?: string;
8
+ author?: string;
9
+ }
10
+ /** 所有插件必须继承此类 */
11
+ export declare abstract class PluginBase {
12
+ protected api: BotApi;
13
+ protected bot: BotClient;
14
+ protected meta: PluginMeta;
15
+ /** 子类重写:声明插件元数据 */
16
+ static meta: PluginMeta;
17
+ constructor(api: BotApi, bot: BotClient, meta?: PluginMeta);
18
+ /** 插件加载时调用:注册事件、启动定时任务、初始化资源 */
19
+ abstract load(): Promise<void>;
20
+ /** 插件卸载时调用:移除事件监听、取消定时任务、释放资源 */
21
+ abstract unload(): Promise<void>;
22
+ }
23
+ /** 插件模块必须导出的构造函数签名 */
24
+ export type PluginConstructor = {
25
+ create(api: BotApi, bot: BotClient, meta?: PluginMeta): PluginBase;
26
+ meta?: PluginMeta;
27
+ };
package/dist/plugin.js ADDED
@@ -0,0 +1,34 @@
1
+ /** 所有插件必须继承此类 */
2
+ export class PluginBase {
3
+ constructor(api, bot, meta) {
4
+ Object.defineProperty(this, "api", {
5
+ enumerable: true,
6
+ configurable: true,
7
+ writable: true,
8
+ value: void 0
9
+ });
10
+ Object.defineProperty(this, "bot", {
11
+ enumerable: true,
12
+ configurable: true,
13
+ writable: true,
14
+ value: void 0
15
+ });
16
+ Object.defineProperty(this, "meta", {
17
+ enumerable: true,
18
+ configurable: true,
19
+ writable: true,
20
+ value: void 0
21
+ });
22
+ this.api = api;
23
+ this.bot = bot;
24
+ const Ctor = this.constructor;
25
+ this.meta = meta || Ctor.meta;
26
+ }
27
+ }
28
+ /** 子类重写:声明插件元数据 */
29
+ Object.defineProperty(PluginBase, "meta", {
30
+ enumerable: true,
31
+ configurable: true,
32
+ writable: true,
33
+ value: { name: '', version: '0.0.0' }
34
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hotcat-bot-qq",
3
- "version": "0.0.1",
3
+ "version": "0.1.1",
4
4
  "description": "基于 node-napcat-ts 的 TypeScript QQ 机器人框架",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -29,10 +29,24 @@
29
29
  "./botEvent": {
30
30
  "types": "./dist/botEvent.d.ts",
31
31
  "default": "./dist/botEvent.js"
32
+ },
33
+ "./botScheduler": {
34
+ "types": "./dist/botScheduler.d.ts",
35
+ "default": "./dist/botScheduler.js"
36
+ },
37
+ "./botPlugin": {
38
+ "types": "./dist/botPlugin.d.ts",
39
+ "default": "./dist/botPlugin.js"
40
+ },
41
+ "./plugin": {
42
+ "types": "./dist/plugin.d.ts",
43
+ "default": "./dist/plugin.js"
32
44
  }
33
45
  },
34
46
  "files": [
35
47
  "dist",
48
+ "plugins/hotCatPlugin",
49
+ "scripts/postinstall.mjs",
36
50
  "LICENSE",
37
51
  "README.md",
38
52
  "logo.png"
@@ -40,6 +54,7 @@
40
54
  "scripts": {
41
55
  "build": "tsc -p tsconfig.build.json",
42
56
  "test": "echo \"Error: no test specified\" && exit 1",
57
+ "postinstall": "node scripts/postinstall.mjs || bun scripts/postinstall.mjs || echo skip",
43
58
  "prepublishOnly": "bun run build",
44
59
  "docs:dev": "vitepress dev docs",
45
60
  "docs:build": "vitepress build docs",
@@ -61,6 +76,7 @@
61
76
  },
62
77
  "dependencies": {
63
78
  "node-napcat-ts": "^0.4.25",
79
+ "node-schedule": "^2.1.1",
64
80
  "@types/node": "^26.1.2"
65
81
  },
66
82
  "devDependencies": {
@@ -0,0 +1,64 @@
1
+ import { PluginBase } from 'hotcat-bot-qq/plugin'
2
+ import { BotApi } from 'hotcat-bot-qq/botApi'
3
+ import { BotClient } from 'hotcat-bot-qq/botClient'
4
+ import { Message } from 'hotcat-bot-qq/message'
5
+ import * as os from 'os'
6
+
7
+ export class HotCatPlugin extends PluginBase {
8
+ static meta = {
9
+ name: 'hotCatPlugin',
10
+ version: '0.1.1',
11
+ description: 'HotCat 默认插件(#hotCat 指令查询 bot 状态)',
12
+ author: 'CHCAT1320',
13
+ }
14
+
15
+ private startTime = 0
16
+ private enabled = false
17
+
18
+ static create(api: BotApi, bot: BotClient) {
19
+ return new HotCatPlugin(api, bot)
20
+ }
21
+
22
+ async load() {
23
+ this.startTime = Date.now()
24
+ this.enabled = true
25
+ this.bot.event.message.onGroupMessage(this.onGroupMsg)
26
+ }
27
+
28
+ async unload() {
29
+ this.enabled = false
30
+ }
31
+
32
+ private onGroupMsg = async (_bot: BotClient, event: any) => {
33
+ if (!this.enabled) return
34
+ const msg = event.raw_message.trim()
35
+
36
+ if (msg === '#hotCat' || msg === '#hotcat') {
37
+ const uptime = this.formatUptime(Date.now() - this.startTime)
38
+ const platform = os.platform()
39
+ const arch = os.arch()
40
+
41
+ await this.api.sendGroupMessage(event.group_id,
42
+ Message.reply(event.message_id),
43
+ Message.text(
44
+ `HotCat 信息\n` +
45
+ `版本:${this.meta.version}\n` +
46
+ `平台:${platform} ${arch}\n` +
47
+ `运行时间:${uptime}`
48
+ )
49
+ )
50
+ }
51
+ }
52
+
53
+ private formatUptime(ms: number): string {
54
+ const s = Math.floor(ms / 1000)
55
+ const d = Math.floor(s / 86400)
56
+ const h = Math.floor((s % 86400) / 3600)
57
+ const m = Math.floor((s % 3600) / 60)
58
+ const parts: string[] = []
59
+ if (d > 0) parts.push(`${d}天`)
60
+ if (h > 0) parts.push(`${h}小时`)
61
+ if (m > 0) parts.push(`${m}分钟`)
62
+ return parts.length > 0 ? parts.join(' ') : '小于1分钟'
63
+ }
64
+ }
@@ -0,0 +1,19 @@
1
+ import { existsSync, mkdirSync, cpSync } from 'fs'
2
+ import { join, dirname } from 'path'
3
+ import { fileURLToPath } from 'url'
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url))
6
+ const src = join(__dirname, '..', 'plugins', 'hotCatPlugin')
7
+ const dest = join(process.cwd(), 'plugins', 'hotCatPlugin')
8
+
9
+ if (existsSync(dest)) {
10
+ console.log('[hotCatBot] 插件 hotCatPlugin 已存在,跳过复制')
11
+ process.exit(0)
12
+ }
13
+
14
+ if (!existsSync(join(process.cwd(), 'plugins'))) {
15
+ mkdirSync(join(process.cwd(), 'plugins'), { recursive: true })
16
+ }
17
+
18
+ cpSync(src, dest, { recursive: true })
19
+ console.log('[hotCatBot] 默认插件 hotCatPlugin 已复制到 ./plugins/hotCatPlugin/')