@zhin.js/core 1.0.0 → 1.0.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.
Files changed (121) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +21 -0
  3. package/README.md +295 -74
  4. package/lib/adapter.d.ts +39 -0
  5. package/lib/adapter.d.ts.map +1 -0
  6. package/{dist → lib}/adapter.js +20 -2
  7. package/lib/adapter.js.map +1 -0
  8. package/lib/app.d.ts +115 -0
  9. package/lib/app.d.ts.map +1 -0
  10. package/{dist → lib}/app.js +148 -78
  11. package/lib/app.js.map +1 -0
  12. package/lib/bot.d.ts +31 -0
  13. package/lib/bot.d.ts.map +1 -0
  14. package/lib/command.d.ts +32 -0
  15. package/lib/command.d.ts.map +1 -0
  16. package/lib/command.js +46 -0
  17. package/lib/command.js.map +1 -0
  18. package/lib/component.d.ts +107 -0
  19. package/lib/component.d.ts.map +1 -0
  20. package/lib/component.js +273 -0
  21. package/lib/component.js.map +1 -0
  22. package/{dist → lib}/config.d.ts.map +1 -1
  23. package/{dist → lib}/config.js +6 -9
  24. package/lib/config.js.map +1 -0
  25. package/lib/cron.d.ts +81 -0
  26. package/lib/cron.d.ts.map +1 -0
  27. package/lib/cron.js +159 -0
  28. package/lib/cron.js.map +1 -0
  29. package/lib/errors.d.ts +165 -0
  30. package/lib/errors.d.ts.map +1 -0
  31. package/lib/errors.js +306 -0
  32. package/lib/errors.js.map +1 -0
  33. package/lib/index.d.ts +15 -0
  34. package/lib/index.d.ts.map +1 -0
  35. package/lib/index.js +17 -0
  36. package/lib/index.js.map +1 -0
  37. package/lib/message.d.ts +44 -0
  38. package/lib/message.d.ts.map +1 -0
  39. package/lib/message.js +11 -0
  40. package/lib/message.js.map +1 -0
  41. package/lib/plugin.d.ts +50 -0
  42. package/lib/plugin.d.ts.map +1 -0
  43. package/lib/plugin.js +170 -0
  44. package/lib/plugin.js.map +1 -0
  45. package/lib/prompt.d.ts +116 -0
  46. package/lib/prompt.d.ts.map +1 -0
  47. package/lib/prompt.js +240 -0
  48. package/lib/prompt.js.map +1 -0
  49. package/lib/schema.d.ts +83 -0
  50. package/lib/schema.d.ts.map +1 -0
  51. package/lib/schema.js +245 -0
  52. package/lib/schema.js.map +1 -0
  53. package/{dist → lib}/types-generator.d.ts.map +1 -1
  54. package/{dist → lib}/types-generator.js +6 -3
  55. package/lib/types-generator.js.map +1 -0
  56. package/lib/types.d.ts +119 -0
  57. package/lib/types.d.ts.map +1 -0
  58. package/lib/utils.d.ts +52 -0
  59. package/lib/utils.d.ts.map +1 -0
  60. package/lib/utils.js +338 -0
  61. package/lib/utils.js.map +1 -0
  62. package/package.json +15 -9
  63. package/src/adapter.ts +25 -9
  64. package/src/app.ts +363 -258
  65. package/src/bot.ts +29 -8
  66. package/src/command.ts +50 -0
  67. package/src/component.ts +318 -0
  68. package/src/config.ts +9 -12
  69. package/src/cron.ts +176 -0
  70. package/src/errors.ts +365 -0
  71. package/src/index.ts +16 -13
  72. package/src/message.ts +44 -0
  73. package/src/plugin.ts +148 -66
  74. package/src/prompt.ts +290 -0
  75. package/src/schema.ts +273 -0
  76. package/src/types-generator.ts +7 -3
  77. package/src/types.ts +77 -30
  78. package/src/utils.ts +312 -0
  79. package/tests/adapter.test.ts +36 -22
  80. package/tests/app.test.ts +30 -0
  81. package/tests/command.test.ts +545 -0
  82. package/tests/component.test.ts +656 -0
  83. package/tests/config.test.ts +1 -1
  84. package/tests/errors.test.ts +311 -0
  85. package/tests/message.test.ts +402 -0
  86. package/tests/plugin.test.ts +275 -143
  87. package/tests/utils.test.ts +80 -0
  88. package/tsconfig.json +3 -4
  89. package/dist/adapter.d.ts +0 -22
  90. package/dist/adapter.d.ts.map +0 -1
  91. package/dist/adapter.js.map +0 -1
  92. package/dist/app.d.ts +0 -69
  93. package/dist/app.d.ts.map +0 -1
  94. package/dist/app.js.map +0 -1
  95. package/dist/bot.d.ts +0 -9
  96. package/dist/bot.d.ts.map +0 -1
  97. package/dist/config.js.map +0 -1
  98. package/dist/index.d.ts +0 -9
  99. package/dist/index.d.ts.map +0 -1
  100. package/dist/index.js +0 -12
  101. package/dist/index.js.map +0 -1
  102. package/dist/logger.d.ts +0 -3
  103. package/dist/logger.d.ts.map +0 -1
  104. package/dist/logger.js +0 -3
  105. package/dist/logger.js.map +0 -1
  106. package/dist/plugin.d.ts +0 -41
  107. package/dist/plugin.d.ts.map +0 -1
  108. package/dist/plugin.js +0 -95
  109. package/dist/plugin.js.map +0 -1
  110. package/dist/types-generator.js.map +0 -1
  111. package/dist/types.d.ts +0 -69
  112. package/dist/types.d.ts.map +0 -1
  113. package/src/logger.ts +0 -3
  114. package/tests/logger.test.ts +0 -170
  115. package/tsconfig.tsbuildinfo +0 -1
  116. /package/{dist → lib}/bot.js +0 -0
  117. /package/{dist → lib}/bot.js.map +0 -0
  118. /package/{dist → lib}/config.d.ts +0 -0
  119. /package/{dist → lib}/types-generator.d.ts +0 -0
  120. /package/{dist → lib}/types.js +0 -0
  121. /package/{dist → lib}/types.js.map +0 -0
package/src/types.ts CHANGED
@@ -1,36 +1,79 @@
1
1
  import {MaybePromise}from '@zhin.js/types'
2
+ import {MessageChannel} from "./message.js";
3
+ import {Adapter} from "./adapter.js";
4
+ import {Bot,BotConfig} from "./bot.js";
5
+ import { Databases,Registry } from "@zhin.js/database";
2
6
 
3
- export interface MessageSegment<T extends keyof Segment=keyof Segment> {
4
- type: T;
5
- data: Segment[T];
7
+ /**
8
+ * 类型定义文件:包含适配器、消息、数据库、配置等核心类型声明。
9
+ * 协作者可通过本文件了解各主要数据结构的用途与关系。
10
+ */
11
+ declare module '@zhin.js/types'{
12
+ interface GlobalContext extends RegisteredAdapters{}
6
13
  }
7
- export interface Segment{
8
- [key:string]:Record<string, any>
9
- any:Record<string, any>
14
+ /**
15
+ * 所有已注册适配器的类型映射(key为适配器名,value为Adapter实例)
16
+ */
17
+ export interface RegisteredAdapters extends Record<string, Adapter>{}
18
+ /**
19
+ * 数据库配置类型,支持多种数据库驱动
20
+ */
21
+ export type DatabaseConfig<T extends keyof Databases=keyof Databases>={
22
+ dialect:T
23
+ } & Registry.Config<Databases[T]>
24
+ /**
25
+ * 获取对象所有value类型
26
+ */
27
+ export type ObjectItem<T extends object>=T[keyof T]
28
+ /**
29
+ * 已注册适配器名类型
30
+ */
31
+ export type RegisteredAdapter=keyof RegisteredAdapters
32
+ /**
33
+ * 指定适配器的消息类型
34
+ */
35
+ export type AdapterMessage<T extends keyof RegisteredAdapters=keyof RegisteredAdapters>=RegisteredAdapters[T] extends Adapter<infer R>?BotMessage<R>:{}
36
+ /**
37
+ * 指定适配器的配置类型
38
+ */
39
+ export type AdapterConfig<T extends keyof RegisteredAdapters=keyof RegisteredAdapters>=RegisteredAdapters[T] extends Adapter<infer R>?PlatformConfig<R>:BotConfig
40
+ /**
41
+ * Bot实例的配置类型
42
+ */
43
+ export type PlatformConfig<T>=T extends Bot<infer L,infer R>?R:BotConfig
44
+ /**
45
+ * Bot实例的消息类型
46
+ */
47
+ export type BotMessage<T extends Bot>=T extends Bot<infer R>?R:{}
48
+ /**
49
+ * 消息段结构,支持 text/image/at/face 等类型
50
+ */
51
+ export interface MessageSegment {
52
+ type: string;
53
+ data: Record<string, any>;
10
54
  }
55
+ /**
56
+ * 单个或数组类型
57
+ */
11
58
  export type MaybeArray<T>=T|T[]
59
+ /**
60
+ * 消息发送内容类型
61
+ */
12
62
  export type SendContent=MaybeArray<string|MessageSegment>
63
+ /**
64
+ * 消息发送者信息
65
+ */
13
66
  export interface MessageSender{
14
67
  id: string;
15
68
  name?: string;
16
69
  }
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
-
70
+ /**
71
+ * 通用字典类型
72
+ */
73
+ export type Dict<V=any,K extends string|symbol=string>=Record<K, V>;
74
+ /**
75
+ * 用户信息结构
76
+ */
34
77
  export interface User {
35
78
  user_id: string;
36
79
  nickname: string;
@@ -38,23 +81,24 @@ export interface User {
38
81
  role?: string;
39
82
  }
40
83
 
84
+ /**
85
+ * 群组信息结构
86
+ */
41
87
  export interface Group {
42
88
  group_id: string;
43
89
  group_name: string;
44
90
  member_count: number;
45
91
  }
46
92
 
47
- export interface BotConfig {
48
- name: string;
49
- context: string;
50
- [key: string]: any;
51
- }
52
-
53
-
54
93
 
94
+ /**
95
+ * App配置类型,涵盖机器人、数据库、插件、调试等
96
+ */
55
97
  export interface AppConfig {
56
98
  /** 机器人配置列表 */
57
99
  bots?: BotConfig[];
100
+ /** 数据库配置列表 */
101
+ database?: DatabaseConfig;
58
102
  /** 插件目录列表,默认为 ['./plugins', 'node_modules'] */
59
103
  plugin_dirs?: string[];
60
104
  /** 需要加载的插件列表 */
@@ -64,6 +108,9 @@ export interface AppConfig {
64
108
  /** 是否启用调试模式 */
65
109
  debug?: boolean;
66
110
  }
111
+ /**
112
+ * defineConfig辅助类型,支持函数式/对象式配置
113
+ */
67
114
  export type DefineConfig<T> = T | ((env:Record<string,string>)=>MaybePromise<T>);
68
115
 
69
116
  export interface SendOptions extends MessageChannel{
package/src/utils.ts ADDED
@@ -0,0 +1,312 @@
1
+ import {Dict, MessageSegment, SendContent} from "./types";
2
+
3
+ export function getValueWithRuntime(template: string, ctx: Dict) {
4
+ const result = evaluate(template, ctx);
5
+ if (result === `return(${template})`) return undefined;
6
+ return result;
7
+ }
8
+ export const evaluate = <S, T = any>(exp: string, context: S) => {
9
+ const result = execute<S, T>(`return(${exp})`, context);
10
+ // 如果结果是原始表达式,说明访问被阻止,返回 undefined
11
+ if (result === `return(${exp})`) return undefined;
12
+ return result;
13
+ };
14
+ const evalCache: Record<string, Function> = Object.create(null);
15
+ export const execute = <S, T = any>(exp: string, context: S):T => {
16
+ const fn = evalCache[exp] || (evalCache[exp] = toFunction(exp));
17
+ context={
18
+ ...context,
19
+ process:undefined,
20
+ global:undefined,
21
+ Buffer:undefined,
22
+ crypto:undefined
23
+ }
24
+ try {
25
+ return fn.apply(context, [context]);
26
+ } catch {
27
+ return exp as T;
28
+ }
29
+ };
30
+
31
+ const toFunction = (exp: string): Function => {
32
+ try {
33
+ return new Function(`$data`, `with($data){${exp}}`);
34
+ } catch {
35
+ return () => {};
36
+ }
37
+ };
38
+ export function compiler(template: string, ctx: Dict) {
39
+ const matched = [...template.matchAll(/\${([^}]*?)}/g)];
40
+ for (const item of matched) {
41
+ const tpl = item[1];
42
+ let value = getValueWithRuntime(tpl, ctx);
43
+ if (value === tpl) continue;
44
+ if (typeof value !== 'string') value = JSON.stringify(value, null, 2);
45
+ template = template.replace(`\${${item[1]}}`, value);
46
+ }
47
+ return template;
48
+ }
49
+ export function segment<T extends object>(type:string,data:T){
50
+ return {
51
+ type,
52
+ data
53
+ }
54
+ }
55
+ export namespace segment{
56
+ export function escape<T>(text: T): T {
57
+ if (typeof text !== 'string') return text;
58
+ return text
59
+ .replace(/&/g, '&amp;')
60
+ .replace(/</g, '&lt;')
61
+ .replace(/>/g, '&gt;')
62
+ .replace(/"/g, '&quot;')
63
+ .replace(/'/g, '&#39;') as T;
64
+ }
65
+ export function unescape<T>(text: T): T {
66
+ if (typeof text !== 'string') return text;
67
+ return text
68
+ .replace(/&lt;/g, '<')
69
+ .replace(/&gt;/g, '>')
70
+ .replace(/&quot;/g, '"')
71
+ .replace(/&#39;/g, "'")
72
+ .replace(/&amp;/g, '&') as T;
73
+ }
74
+ export function text(text:string){
75
+ return segment('text',{text});
76
+ }
77
+ export function face(id:string,text?:string){
78
+ return segment('face',{id,text});
79
+ }
80
+ export function from(content: SendContent): SendContent {
81
+ if (!Array.isArray(content)) content=[content];
82
+ const toString=(template:string|MessageSegment)=>{
83
+ if(typeof template!=='string') return [template]
84
+ template=unescape(template);
85
+ const result: MessageSegment[] = [];
86
+ const closingReg = /<(\S+)(\s[^>]+)?\/>/;
87
+ const twinningReg = /<(\S+)(\s[^>]+)?>([\s\S]*?)<\/\1>/;
88
+ while (template.length) {
89
+ const [_, type, attrStr = '', child = ''] = template.match(twinningReg) || template.match(closingReg) || [];
90
+ if (!type) break;
91
+ const isClosing = closingReg.test(template);
92
+ const matched = isClosing ? `<${type}${attrStr}/>` : `<${type}${attrStr}>${child}</${type}>`;
93
+ const index = template.indexOf(matched);
94
+ const prevText = template.slice(0, index);
95
+ if (prevText)
96
+ result.push({
97
+ type: 'text',
98
+ data: {
99
+ text: unescape(prevText),
100
+ },
101
+ });
102
+ template = template.slice(index + matched.length);
103
+ const attrArr = [...attrStr.matchAll(/\s([^=]+)(?=(?=="([^"]+)")|(?=='([^']+)'))/g)];
104
+ const data = Object.fromEntries(
105
+ attrArr.map(([source, key, v1, v2]) => {
106
+ const value = v1 || v2;
107
+ try {
108
+ return [key, JSON.parse(unescape(value))];
109
+ } catch {
110
+ return [key, unescape(value)];
111
+ }
112
+ }),
113
+ );
114
+ if (child) {
115
+ data.message = toString(child).map(({ type, data }) => ({ type, ...data }));
116
+ }
117
+ result.push({
118
+ type: type,
119
+ data,
120
+ });
121
+ }
122
+ if (template.length) {
123
+ result.push({
124
+ type: 'text',
125
+ data: {
126
+ text: unescape(template),
127
+ },
128
+ });
129
+ }
130
+ return result;
131
+ }
132
+ return content.reduce((result,item)=>{
133
+ result.push(...toString(item))
134
+ return result;
135
+ },[] as MessageSegment[])
136
+ }
137
+ export function raw(content:SendContent){
138
+ if(!Array.isArray(content)) content=[content]
139
+ return content.map(item=>{
140
+ if(typeof item==='string') return item
141
+ const {type,data}=item
142
+ if(type==='text') return data.text
143
+ return data.text?`{${type}}(${data.text})`:`{${type}}`;
144
+ }).join('')
145
+ }
146
+ export function toString(content:SendContent){
147
+ if(!Array.isArray(content)) content=[content]
148
+ return content.map(item=>{
149
+ if(typeof item==='string') return item
150
+ const {type,data}=item
151
+ if(type==='text') return data.text
152
+ return `<${type} ${Object.keys(data).map(key=>`${key}='${escape(JSON.stringify(data[key]))}'`).join(' ')}/>`
153
+ }).join('')
154
+ }
155
+ }
156
+
157
+ export function remove<T>(list: T[], fn: (item: T) => boolean): void;
158
+ export function remove<T>(list: T[], item: T): void;
159
+ export function remove<T>(list: T[], arg: T | ((item: T) => boolean)) {
160
+ const index =
161
+ typeof arg === 'function' && !list.every(item => typeof item === 'function')
162
+ ? list.findIndex(arg as (item: T) => boolean)
163
+ : list.indexOf(arg as T);
164
+ if (index !== -1) list.splice(index, 1);
165
+ }
166
+ export function isEmpty<T>(item:T){
167
+ if(Array.isArray(item)) return item.length===0
168
+ if(typeof item==='object'){
169
+ if(!item) return true
170
+ return Reflect.ownKeys(item).length===0
171
+ }
172
+ return false
173
+ }
174
+
175
+ export namespace Time {
176
+ export const millisecond = 1;
177
+ export const second = 1000;
178
+ export const minute = second * 60;
179
+ export const hour = minute * 60;
180
+ export const day = hour * 24;
181
+ export const week = day * 7;
182
+
183
+ let timezoneOffset = new Date().getTimezoneOffset();
184
+
185
+ export function setTimezoneOffset(offset: number) {
186
+ timezoneOffset = offset;
187
+ }
188
+
189
+ export function getTimezoneOffset() {
190
+ return timezoneOffset;
191
+ }
192
+
193
+ export function getDateNumber(date: number | Date = new Date(), offset?: number) {
194
+ if (typeof date === 'number') date = new Date(date);
195
+ if (offset === undefined) offset = timezoneOffset;
196
+ return Math.floor((date.valueOf() / minute - offset) / 1440);
197
+ }
198
+
199
+ export function fromDateNumber(value: number, offset?: number) {
200
+ const date = new Date(value * day);
201
+ if (offset === undefined) offset = timezoneOffset;
202
+ return new Date(+date + offset * minute);
203
+ }
204
+
205
+ const numeric = /\d+(?:\.\d+)?/.source;
206
+ const timeRegExp = new RegExp(
207
+ `^${['w(?:eek(?:s)?)?', 'd(?:ay(?:s)?)?', 'h(?:our(?:s)?)?', 'm(?:in(?:ute)?(?:s)?)?', 's(?:ec(?:ond)?(?:s)?)?']
208
+ .map(unit => `(${numeric}${unit})?`)
209
+ .join('')}$`,
210
+ );
211
+
212
+ export function parseTime(source: string) {
213
+ const capture = timeRegExp.exec(source);
214
+ if (!capture) return 0;
215
+ return (
216
+ (parseFloat(capture[1]) * week || 0) +
217
+ (parseFloat(capture[2]) * day || 0) +
218
+ (parseFloat(capture[3]) * hour || 0) +
219
+ (parseFloat(capture[4]) * minute || 0) +
220
+ (parseFloat(capture[5]) * second || 0)
221
+ );
222
+ }
223
+
224
+ export function parseDate(date: string) {
225
+ const parsed = parseTime(date);
226
+ if (parsed) {
227
+ date = (Date.now() + parsed) as any;
228
+ } else if (/^\d{1,2}(:\d{1,2}){1,2}$/.test(date)) {
229
+ date = `${new Date().toLocaleDateString()}-${date}`;
230
+ } else if (/^\d{1,2}-\d{1,2}-\d{1,2}(:\d{1,2}){1,2}$/.test(date)) {
231
+ date = `${new Date().getFullYear()}-${date}`;
232
+ }
233
+ return date ? new Date(date) : new Date();
234
+ }
235
+
236
+ export function formatTimeShort(ms: number) {
237
+ const abs = Math.abs(ms);
238
+ if (abs >= day - hour / 2) {
239
+ return Math.round(ms / day) + 'd';
240
+ } else if (abs >= hour - minute / 2) {
241
+ return Math.round(ms / hour) + 'h';
242
+ } else if (abs >= minute - second / 2) {
243
+ return Math.round(ms / minute) + 'm';
244
+ } else if (abs >= second) {
245
+ return Math.round(ms / second) + 's';
246
+ }
247
+ return ms + 'ms';
248
+ }
249
+
250
+ export function formatTime(ms: number) {
251
+ let result: string;
252
+ if (ms >= day - hour / 2) {
253
+ ms += hour / 2;
254
+ result = Math.floor(ms / day) + ' 天';
255
+ if (ms % day > hour) {
256
+ result += ` ${Math.floor((ms % day) / hour)} 小时`;
257
+ }
258
+ } else if (ms >= hour - minute / 2) {
259
+ ms += minute / 2;
260
+ result = Math.floor(ms / hour) + ' 小时';
261
+ if (ms % hour > minute) {
262
+ result += ` ${Math.floor((ms % hour) / minute)} 分钟`;
263
+ }
264
+ } else if (ms >= minute - second / 2) {
265
+ ms += second / 2;
266
+ result = Math.floor(ms / minute) + ' 分钟';
267
+ if (ms % minute > second) {
268
+ result += ` ${Math.floor((ms % minute) / second)} 秒`;
269
+ }
270
+ } else {
271
+ result = Math.round(ms / second) + ' 秒';
272
+ }
273
+ return result;
274
+ }
275
+
276
+ const dayMap = ['日', '一', '二', '三', '四', '五', '六'];
277
+
278
+ function toDigits(source: number, length = 2) {
279
+ return source.toString().padStart(length, '0');
280
+ }
281
+
282
+ export function template(template: string, time = new Date()) {
283
+ return template
284
+ .replace('yyyy', time.getFullYear().toString())
285
+ .replace('yy', time.getFullYear().toString().slice(2))
286
+ .replace('MM', toDigits(time.getMonth() + 1))
287
+ .replace('dd', toDigits(time.getDate()))
288
+ .replace('hh', toDigits(time.getHours()))
289
+ .replace('mm', toDigits(time.getMinutes()))
290
+ .replace('ss', toDigits(time.getSeconds()))
291
+ .replace('SSS', toDigits(time.getMilliseconds(), 3));
292
+ }
293
+
294
+ function toHourMinute(time: Date) {
295
+ return `${toDigits(time.getHours())}:${toDigits(time.getMinutes())}`;
296
+ }
297
+
298
+ export function formatTimeInterval(time: Date, interval?: number) {
299
+ if (!interval) {
300
+ return template('yyyy-MM-dd hh:mm:ss', time);
301
+ } else if (interval === day) {
302
+ return `每天 ${toHourMinute(time)}`;
303
+ } else if (interval === week) {
304
+ return `每周${dayMap[time.getDay()]} ${toHourMinute(time)}`;
305
+ } else {
306
+ return `${template('yyyy-MM-dd hh:mm:ss', time)} 起每隔 ${formatTime(interval)}`;
307
+ }
308
+ }
309
+ }
310
+ export function sleep(ms: number) {
311
+ return new Promise(resolve => setTimeout(resolve, ms));
312
+ }
@@ -8,20 +8,27 @@ import type { BotConfig } from '../src/types'
8
8
  describe('适配器类测试', () => {
9
9
  // 创建测试用的Bot类
10
10
  class TestBot implements Bot {
11
- connected = false
11
+ $connected = false
12
+ $config: BotConfig
12
13
 
13
- constructor(public plugin: Plugin, public config: BotConfig) {}
14
+ constructor(public plugin: Plugin, config: BotConfig) {
15
+ this.$config = config
16
+ }
17
+
18
+ async $connect(): Promise<void> {
19
+ this.$connected = true
20
+ }
14
21
 
15
- async connect(): Promise<void> {
16
- this.connected = true
22
+ async $disconnect(): Promise<void> {
23
+ this.$connected = false
17
24
  }
18
25
 
19
- async disconnect(): Promise<void> {
20
- this.connected = false
26
+ async $sendMessage(): Promise<void> {
27
+ if (!this.$connected) throw new Error('机器人未连接')
21
28
  }
22
29
 
23
- async sendMessage(): Promise<void> {
24
- if (!this.connected) throw new Error('机器人未连接')
30
+ $formatMessage(message: any): any {
31
+ return message
25
32
  }
26
33
  }
27
34
 
@@ -68,8 +75,8 @@ describe('适配器类测试', () => {
68
75
  expect(adapter.bots.size).toBe(2)
69
76
  expect(adapter.bots.get('test-bot-1')).toBeDefined()
70
77
  expect(adapter.bots.get('test-bot-2')).toBeDefined()
71
- expect(adapter.bots.get('test-bot-1')?.connected).toBe(true)
72
- expect(adapter.bots.get('test-bot-2')?.connected).toBe(true)
78
+ expect(adapter.bots.get('test-bot-1')?.$connected).toBe(true)
79
+ expect(adapter.bots.get('test-bot-2')?.$connected).toBe(true)
73
80
 
74
81
  expect(loggerSpy).toHaveBeenCalledWith('bot test-bot-1 of adapter test-adapter connected')
75
82
  expect(loggerSpy).toHaveBeenCalledWith('bot test-bot-2 of adapter test-adapter connected')
@@ -106,7 +113,7 @@ describe('适配器类测试', () => {
106
113
  describe('错误处理测试', () => {
107
114
  it('应该处理机器人连接失败', async () => {
108
115
  class FailingBot extends TestBot {
109
- async connect(): Promise<void> {
116
+ async $connect(): Promise<void> {
110
117
  throw new Error('连接失败')
111
118
  }
112
119
  }
@@ -123,7 +130,7 @@ describe('适配器类测试', () => {
123
130
 
124
131
  it('应该处理机器人断开连接失败', async () => {
125
132
  class FailingBot extends TestBot {
126
- async disconnect(): Promise<void> {
133
+ async $disconnect(): Promise<void> {
127
134
  throw new Error('断开连接失败')
128
135
  }
129
136
  }
@@ -148,20 +155,27 @@ describe('适配器类测试', () => {
148
155
  }
149
156
 
150
157
  class ExtendedBot implements Bot<ExtendedBotConfig> {
151
- connected = false
158
+ $connected = false
159
+ $config: ExtendedBotConfig
152
160
 
153
- constructor(public plugin: Plugin, public config: ExtendedBotConfig) {}
161
+ constructor(public plugin: Plugin, config: ExtendedBotConfig) {
162
+ this.$config = config
163
+ }
164
+
165
+ async $connect(): Promise<void> {
166
+ this.$connected = true
167
+ }
154
168
 
155
- async connect(): Promise<void> {
156
- this.connected = true
169
+ async $disconnect(): Promise<void> {
170
+ this.$connected = false
157
171
  }
158
172
 
159
- async disconnect(): Promise<void> {
160
- this.connected = false
173
+ async $sendMessage(): Promise<void> {
174
+ if (!this.$connected) throw new Error('机器人未连接')
161
175
  }
162
176
 
163
- async sendMessage(): Promise<void> {
164
- if (!this.connected) throw new Error('机器人未连接')
177
+ $formatMessage(message: any): any {
178
+ return message
165
179
  }
166
180
  }
167
181
 
@@ -180,8 +194,8 @@ describe('适配器类测试', () => {
180
194
 
181
195
  const bot = extendedAdapter.bots.get('extended-bot')
182
196
  expect(bot).toBeDefined()
183
- expect(bot?.config.token).toBe('test-token')
184
- expect(bot?.config.platform).toBe('test-platform')
197
+ expect(bot?.$config.token).toBe('test-token')
198
+ expect(bot?.$config.platform).toBe('test-platform')
185
199
  })
186
200
  })
187
201
  })
package/tests/app.test.ts CHANGED
@@ -137,6 +137,36 @@ describe('App类测试', () => {
137
137
  it('当上下文不存在时应该抛出错误', () => {
138
138
  expect(() => app.getContext('non-existent')).toThrow("can't find Context of non-existent")
139
139
  })
140
+
141
+ it('应该正确设置和获取上下文描述', async () => {
142
+ // 创建测试插件
143
+ const plugin = app.createDependency('test-plugin-desc', 'test-plugin-desc.ts')
144
+
145
+ // 注册带描述的上下文
146
+ const context = {
147
+ name: 'test-context',
148
+ description: '这是一个测试上下文,用于验证描述字段功能',
149
+ mounted: () => ({ testValue: 'test' }),
150
+ dispose: () => {}
151
+ }
152
+ plugin.register(context)
153
+
154
+ // 等待插件挂载
155
+ await plugin.mounted()
156
+
157
+ // 使用上下文来验证功能
158
+ plugin.useContext('test-context', () => {
159
+ // 验证上下文可以正常获取(先测试基本功能)
160
+ const retrievedContext = app.getContext('test-context')
161
+ expect(retrievedContext).toEqual({ testValue: 'test' })
162
+
163
+ // 验证上下文列表包含描述信息
164
+ const contextList = app.contextList
165
+ const testContext = contextList.find(ctx => ctx.name === 'test-context')
166
+ expect(testContext).toBeDefined()
167
+ expect(testContext?.description).toBe('这是一个测试上下文,用于验证描述字段功能')
168
+ })
169
+ })
140
170
  })
141
171
 
142
172
  describe('消息处理测试', () => {