@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.
- package/CHANGELOG.md +9 -0
- package/LICENSE +21 -0
- package/README.md +295 -74
- package/lib/adapter.d.ts +39 -0
- package/lib/adapter.d.ts.map +1 -0
- package/{dist → lib}/adapter.js +20 -2
- package/lib/adapter.js.map +1 -0
- package/lib/app.d.ts +115 -0
- package/lib/app.d.ts.map +1 -0
- package/{dist → lib}/app.js +148 -78
- package/lib/app.js.map +1 -0
- package/lib/bot.d.ts +31 -0
- package/lib/bot.d.ts.map +1 -0
- package/lib/command.d.ts +32 -0
- package/lib/command.d.ts.map +1 -0
- package/lib/command.js +46 -0
- package/lib/command.js.map +1 -0
- package/lib/component.d.ts +107 -0
- package/lib/component.d.ts.map +1 -0
- package/lib/component.js +273 -0
- package/lib/component.js.map +1 -0
- package/{dist → lib}/config.d.ts.map +1 -1
- package/{dist → lib}/config.js +6 -9
- package/lib/config.js.map +1 -0
- package/lib/cron.d.ts +81 -0
- package/lib/cron.d.ts.map +1 -0
- package/lib/cron.js +159 -0
- package/lib/cron.js.map +1 -0
- package/lib/errors.d.ts +165 -0
- package/lib/errors.d.ts.map +1 -0
- package/lib/errors.js +306 -0
- package/lib/errors.js.map +1 -0
- package/lib/index.d.ts +15 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +17 -0
- package/lib/index.js.map +1 -0
- package/lib/message.d.ts +44 -0
- package/lib/message.d.ts.map +1 -0
- package/lib/message.js +11 -0
- package/lib/message.js.map +1 -0
- package/lib/plugin.d.ts +50 -0
- package/lib/plugin.d.ts.map +1 -0
- package/lib/plugin.js +170 -0
- package/lib/plugin.js.map +1 -0
- package/lib/prompt.d.ts +116 -0
- package/lib/prompt.d.ts.map +1 -0
- package/lib/prompt.js +240 -0
- package/lib/prompt.js.map +1 -0
- package/lib/schema.d.ts +83 -0
- package/lib/schema.d.ts.map +1 -0
- package/lib/schema.js +245 -0
- package/lib/schema.js.map +1 -0
- package/{dist → lib}/types-generator.d.ts.map +1 -1
- package/{dist → lib}/types-generator.js +6 -3
- package/lib/types-generator.js.map +1 -0
- package/lib/types.d.ts +119 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/utils.d.ts +52 -0
- package/lib/utils.d.ts.map +1 -0
- package/lib/utils.js +338 -0
- package/lib/utils.js.map +1 -0
- package/package.json +15 -9
- package/src/adapter.ts +25 -9
- package/src/app.ts +363 -258
- package/src/bot.ts +29 -8
- package/src/command.ts +50 -0
- package/src/component.ts +318 -0
- package/src/config.ts +9 -12
- package/src/cron.ts +176 -0
- package/src/errors.ts +365 -0
- package/src/index.ts +16 -13
- package/src/message.ts +44 -0
- package/src/plugin.ts +148 -66
- package/src/prompt.ts +290 -0
- package/src/schema.ts +273 -0
- package/src/types-generator.ts +7 -3
- package/src/types.ts +77 -30
- package/src/utils.ts +312 -0
- package/tests/adapter.test.ts +36 -22
- package/tests/app.test.ts +30 -0
- package/tests/command.test.ts +545 -0
- package/tests/component.test.ts +656 -0
- package/tests/config.test.ts +1 -1
- package/tests/errors.test.ts +311 -0
- package/tests/message.test.ts +402 -0
- package/tests/plugin.test.ts +275 -143
- package/tests/utils.test.ts +80 -0
- package/tsconfig.json +3 -4
- package/dist/adapter.d.ts +0 -22
- package/dist/adapter.d.ts.map +0 -1
- package/dist/adapter.js.map +0 -1
- package/dist/app.d.ts +0 -69
- package/dist/app.d.ts.map +0 -1
- package/dist/app.js.map +0 -1
- package/dist/bot.d.ts +0 -9
- package/dist/bot.d.ts.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/index.d.ts +0 -9
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -12
- package/dist/index.js.map +0 -1
- package/dist/logger.d.ts +0 -3
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js +0 -3
- package/dist/logger.js.map +0 -1
- package/dist/plugin.d.ts +0 -41
- package/dist/plugin.d.ts.map +0 -1
- package/dist/plugin.js +0 -95
- package/dist/plugin.js.map +0 -1
- package/dist/types-generator.js.map +0 -1
- package/dist/types.d.ts +0 -69
- package/dist/types.d.ts.map +0 -1
- package/src/logger.ts +0 -3
- package/tests/logger.test.ts +0 -170
- package/tsconfig.tsbuildinfo +0 -1
- /package/{dist → lib}/bot.js +0 -0
- /package/{dist → lib}/bot.js.map +0 -0
- /package/{dist → lib}/config.d.ts +0 -0
- /package/{dist → lib}/types-generator.d.ts +0 -0
- /package/{dist → lib}/types.js +0 -0
- /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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
7
|
+
/**
|
|
8
|
+
* 类型定义文件:包含适配器、消息、数据库、配置等核心类型声明。
|
|
9
|
+
* 协作者可通过本文件了解各主要数据结构的用途与关系。
|
|
10
|
+
*/
|
|
11
|
+
declare module '@zhin.js/types'{
|
|
12
|
+
interface GlobalContext extends RegisteredAdapters{}
|
|
6
13
|
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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, '&')
|
|
60
|
+
.replace(/</g, '<')
|
|
61
|
+
.replace(/>/g, '>')
|
|
62
|
+
.replace(/"/g, '"')
|
|
63
|
+
.replace(/'/g, ''') as T;
|
|
64
|
+
}
|
|
65
|
+
export function unescape<T>(text: T): T {
|
|
66
|
+
if (typeof text !== 'string') return text;
|
|
67
|
+
return text
|
|
68
|
+
.replace(/</g, '<')
|
|
69
|
+
.replace(/>/g, '>')
|
|
70
|
+
.replace(/"/g, '"')
|
|
71
|
+
.replace(/'/g, "'")
|
|
72
|
+
.replace(/&/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
|
+
}
|
package/tests/adapter.test.ts
CHANGED
|
@@ -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,
|
|
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
|
|
16
|
-
this
|
|
22
|
+
async $disconnect(): Promise<void> {
|
|
23
|
+
this.$connected = false
|
|
17
24
|
}
|
|
18
25
|
|
|
19
|
-
async
|
|
20
|
-
this
|
|
26
|
+
async $sendMessage(): Promise<void> {
|
|
27
|
+
if (!this.$connected) throw new Error('机器人未连接')
|
|
21
28
|
}
|
|
22
29
|
|
|
23
|
-
|
|
24
|
-
|
|
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')
|
|
72
|
-
expect(adapter.bots.get('test-bot-2')
|
|
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,
|
|
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
|
|
156
|
-
this
|
|
169
|
+
async $disconnect(): Promise<void> {
|
|
170
|
+
this.$connected = false
|
|
157
171
|
}
|
|
158
172
|
|
|
159
|
-
async
|
|
160
|
-
this
|
|
173
|
+
async $sendMessage(): Promise<void> {
|
|
174
|
+
if (!this.$connected) throw new Error('机器人未连接')
|
|
161
175
|
}
|
|
162
176
|
|
|
163
|
-
|
|
164
|
-
|
|
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
|
|
184
|
-
expect(bot
|
|
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('消息处理测试', () => {
|