@onebots/core 1.0.0 → 1.0.5
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/lib/__tests__/integration.test.js +2 -2
- package/lib/account.d.ts +4 -1
- package/lib/account.js +4 -2
- package/lib/adapter.d.ts +76 -1
- package/lib/adapter.js +36 -5
- package/lib/base-app.d.ts +16 -0
- package/lib/base-app.js +120 -45
- package/lib/circuit-breaker.d.ts +94 -0
- package/lib/circuit-breaker.js +206 -0
- package/lib/config-validator.d.ts +6 -0
- package/lib/config-validator.js +15 -4
- package/lib/connection-pool.d.ts +68 -0
- package/lib/connection-pool.js +202 -0
- package/lib/db.d.ts +5 -0
- package/lib/db.js +17 -2
- package/lib/index.d.ts +5 -0
- package/lib/index.js +6 -0
- package/lib/metrics.d.ts +80 -0
- package/lib/metrics.js +201 -0
- package/lib/middleware/index.d.ts +8 -0
- package/lib/middleware/index.js +8 -0
- package/lib/middleware/metrics-collector.d.ts +9 -0
- package/lib/middleware/metrics-collector.js +64 -0
- package/lib/middleware/rate-limit.d.ts +32 -0
- package/lib/middleware/rate-limit.js +149 -0
- package/lib/middleware/security-audit.d.ts +33 -0
- package/lib/middleware/security-audit.js +223 -0
- package/lib/middleware/token-manager.d.ts +73 -0
- package/lib/middleware/token-manager.js +186 -0
- package/lib/middleware/token-validator.d.ts +42 -0
- package/lib/middleware/token-validator.js +198 -0
- package/lib/registry.d.ts +27 -0
- package/lib/registry.js +40 -0
- package/lib/timestamp.d.ts +42 -0
- package/lib/timestamp.js +72 -0
- package/lib/utils.d.ts +2 -1
- package/lib/utils.js +11 -19
- package/package.json +13 -6
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 访问令牌验证中间件
|
|
3
|
+
* 支持多种令牌格式和验证方式
|
|
4
|
+
*/
|
|
5
|
+
import { createLogger } from '../logger.js';
|
|
6
|
+
import { logInvalidToken, logAuthFailure } from './security-audit.js';
|
|
7
|
+
import crypto from 'crypto';
|
|
8
|
+
const logger = createLogger('TokenValidator');
|
|
9
|
+
/**
|
|
10
|
+
* 创建令牌验证中间件
|
|
11
|
+
*/
|
|
12
|
+
export function createTokenValidator(config = {}) {
|
|
13
|
+
const { tokenName = 'access_token', fromHeader = true, fromQuery = true, validator, required = true, errorMessage = 'Invalid or missing access token', } = config;
|
|
14
|
+
return async (ctx, next) => {
|
|
15
|
+
// 从多个位置提取令牌
|
|
16
|
+
let token;
|
|
17
|
+
if (fromHeader) {
|
|
18
|
+
// 从 Authorization header 获取
|
|
19
|
+
const authHeader = ctx.get('authorization');
|
|
20
|
+
if (authHeader) {
|
|
21
|
+
// 支持 Bearer token 格式
|
|
22
|
+
const match = authHeader.match(/^Bearer\s+(.+)$/i);
|
|
23
|
+
if (match) {
|
|
24
|
+
token = match[1];
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
token = authHeader;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (!token && fromQuery) {
|
|
32
|
+
// 从 query 参数获取
|
|
33
|
+
token = ctx.query[tokenName];
|
|
34
|
+
}
|
|
35
|
+
// 检查令牌是否存在
|
|
36
|
+
if (!token) {
|
|
37
|
+
if (required) {
|
|
38
|
+
logInvalidToken(ctx);
|
|
39
|
+
ctx.status = 401;
|
|
40
|
+
ctx.body = {
|
|
41
|
+
error: 'Unauthorized',
|
|
42
|
+
message: errorMessage,
|
|
43
|
+
};
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
// 可选令牌,继续执行
|
|
48
|
+
return next();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// 验证令牌
|
|
52
|
+
let isValid = true;
|
|
53
|
+
if (validator) {
|
|
54
|
+
try {
|
|
55
|
+
isValid = await validator(token, ctx);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
logger.error('Token validation error', {
|
|
59
|
+
error: error.message,
|
|
60
|
+
path: ctx.path,
|
|
61
|
+
});
|
|
62
|
+
isValid = false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
// 默认验证:检查令牌不为空
|
|
67
|
+
isValid = token.length > 0;
|
|
68
|
+
}
|
|
69
|
+
if (!isValid) {
|
|
70
|
+
logInvalidToken(ctx, token);
|
|
71
|
+
ctx.status = 401;
|
|
72
|
+
ctx.body = {
|
|
73
|
+
error: 'Unauthorized',
|
|
74
|
+
message: errorMessage,
|
|
75
|
+
};
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// 将令牌存储到 context 中,供后续使用
|
|
79
|
+
ctx.state.token = token;
|
|
80
|
+
await next();
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* 创建基于配置的令牌验证器
|
|
85
|
+
* 从配置中读取 access_token 列表进行验证
|
|
86
|
+
*/
|
|
87
|
+
export function createConfigTokenValidator(expectedTokens) {
|
|
88
|
+
const tokenSet = new Set(expectedTokens.filter(Boolean));
|
|
89
|
+
return createTokenValidator({
|
|
90
|
+
validator: (token) => {
|
|
91
|
+
return tokenSet.has(token);
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* HMAC 签名验证
|
|
97
|
+
*/
|
|
98
|
+
export function createHMACValidator(secret, algorithm = 'sha256') {
|
|
99
|
+
return async (ctx, next) => {
|
|
100
|
+
const signature = ctx.get('x-signature') || ctx.query.signature;
|
|
101
|
+
if (!signature) {
|
|
102
|
+
logAuthFailure(ctx, 'Missing signature');
|
|
103
|
+
ctx.status = 401;
|
|
104
|
+
ctx.body = {
|
|
105
|
+
error: 'Unauthorized',
|
|
106
|
+
message: 'Missing signature',
|
|
107
|
+
};
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// 获取请求体
|
|
111
|
+
const body = ctx.request.body || ctx.request.rawBody || '';
|
|
112
|
+
const bodyString = typeof body === 'string' ? body : JSON.stringify(body);
|
|
113
|
+
// 计算 HMAC
|
|
114
|
+
const hmac = crypto.createHmac(algorithm, secret);
|
|
115
|
+
hmac.update(bodyString);
|
|
116
|
+
const expectedSignature = hmac.digest('hex');
|
|
117
|
+
// 使用时间安全比较
|
|
118
|
+
const isValid = crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature));
|
|
119
|
+
if (!isValid) {
|
|
120
|
+
logAuthFailure(ctx, 'Invalid signature');
|
|
121
|
+
ctx.status = 401;
|
|
122
|
+
ctx.body = {
|
|
123
|
+
error: 'Unauthorized',
|
|
124
|
+
message: 'Invalid signature',
|
|
125
|
+
};
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
await next();
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* 创建带令牌管理的验证器
|
|
133
|
+
* 支持令牌过期检查和自动刷新
|
|
134
|
+
*/
|
|
135
|
+
export function createManagedTokenValidator(tokenManager, config = {}) {
|
|
136
|
+
const baseValidator = createTokenValidator(config);
|
|
137
|
+
return async (ctx, next) => {
|
|
138
|
+
const token = ctx.get('authorization')?.replace(/^Bearer\s+/i, '') ||
|
|
139
|
+
ctx.query[config.tokenName || 'access_token'];
|
|
140
|
+
if (!token) {
|
|
141
|
+
if (config.required !== false) {
|
|
142
|
+
logInvalidToken(ctx);
|
|
143
|
+
ctx.status = 401;
|
|
144
|
+
ctx.body = {
|
|
145
|
+
error: 'Unauthorized',
|
|
146
|
+
message: config.errorMessage || 'Invalid or missing access token',
|
|
147
|
+
};
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
return next();
|
|
151
|
+
}
|
|
152
|
+
// 验证令牌
|
|
153
|
+
const validation = tokenManager.validateToken(token);
|
|
154
|
+
if (!validation.valid) {
|
|
155
|
+
if (validation.expired) {
|
|
156
|
+
logInvalidToken(ctx, token);
|
|
157
|
+
ctx.status = 401;
|
|
158
|
+
ctx.body = {
|
|
159
|
+
error: 'Unauthorized',
|
|
160
|
+
message: 'Token expired',
|
|
161
|
+
code: 'TOKEN_EXPIRED',
|
|
162
|
+
};
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
logInvalidToken(ctx, token);
|
|
167
|
+
ctx.status = 401;
|
|
168
|
+
ctx.body = {
|
|
169
|
+
error: 'Unauthorized',
|
|
170
|
+
message: config.errorMessage || 'Invalid access token',
|
|
171
|
+
};
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// 将令牌信息存储到 context
|
|
176
|
+
ctx.state.token = token;
|
|
177
|
+
ctx.state.tokenInfo = validation.info;
|
|
178
|
+
await next();
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* 组合多个验证器
|
|
183
|
+
*/
|
|
184
|
+
export function combineValidators(...validators) {
|
|
185
|
+
return async (ctx, next) => {
|
|
186
|
+
for (const validator of validators) {
|
|
187
|
+
await validator(ctx, async () => {
|
|
188
|
+
// 空函数,用于链式调用
|
|
189
|
+
});
|
|
190
|
+
// 如果验证失败,响应已设置,直接返回
|
|
191
|
+
if (ctx.status === 401) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// 所有验证通过,继续执行
|
|
196
|
+
await next();
|
|
197
|
+
};
|
|
198
|
+
}
|
package/lib/registry.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Protocol } from "./protocol.js";
|
|
|
2
2
|
import { Adapter } from "./adapter.js";
|
|
3
3
|
import { BaseApp } from "./base-app.js";
|
|
4
4
|
import { Account } from "./account.js";
|
|
5
|
+
import type { Schema } from "./config-validator.js";
|
|
5
6
|
/**
|
|
6
7
|
* Protocol Registry
|
|
7
8
|
* Manages registration and retrieval of protocol implementations
|
|
@@ -9,6 +10,7 @@ import { Account } from "./account.js";
|
|
|
9
10
|
export declare class ProtocolRegistry {
|
|
10
11
|
private static protocols;
|
|
11
12
|
private static metadata;
|
|
13
|
+
private static schemas;
|
|
12
14
|
/**
|
|
13
15
|
* Register a protocol implementation
|
|
14
16
|
* @param name Protocol name (e.g., 'onebot', 'milky', 'satori')
|
|
@@ -17,6 +19,18 @@ export declare class ProtocolRegistry {
|
|
|
17
19
|
* @param metadata Optional protocol metadata
|
|
18
20
|
*/
|
|
19
21
|
static register(name: string, version: string, factory: Protocol.Factory, metadata?: Partial<Protocol.Metadata>): void;
|
|
22
|
+
/**
|
|
23
|
+
* Register a protocol config schema (key format: name.version)
|
|
24
|
+
*/
|
|
25
|
+
static registerSchema(key: string, schema: Schema): void;
|
|
26
|
+
/**
|
|
27
|
+
* Get a protocol config schema by key
|
|
28
|
+
*/
|
|
29
|
+
static getSchema(key: string): Schema | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Get all protocol config schemas
|
|
32
|
+
*/
|
|
33
|
+
static getAllSchemas(): Record<string, Schema>;
|
|
20
34
|
/**
|
|
21
35
|
* Get a protocol factory
|
|
22
36
|
* @param name Protocol name
|
|
@@ -63,6 +77,7 @@ export declare class ProtocolRegistry {
|
|
|
63
77
|
export declare class AdapterRegistry {
|
|
64
78
|
private static adapters;
|
|
65
79
|
private static metadata;
|
|
80
|
+
private static schemas;
|
|
66
81
|
/**
|
|
67
82
|
* Register an adapter implementation
|
|
68
83
|
* @param name Adapter name/platform (e.g., 'wechat', 'dingtalk', 'qq')
|
|
@@ -70,6 +85,18 @@ export declare class AdapterRegistry {
|
|
|
70
85
|
* @param metadata Optional adapter metadata
|
|
71
86
|
*/
|
|
72
87
|
static register(name: string, factory: Adapter.Factory, metadata?: Partial<Adapter.Metadata>): void;
|
|
88
|
+
/**
|
|
89
|
+
* Register an adapter config schema
|
|
90
|
+
*/
|
|
91
|
+
static registerSchema(name: string, schema: Schema): void;
|
|
92
|
+
/**
|
|
93
|
+
* Get an adapter config schema
|
|
94
|
+
*/
|
|
95
|
+
static getSchema(name: string): Schema | undefined;
|
|
96
|
+
/**
|
|
97
|
+
* Get all adapter config schemas
|
|
98
|
+
*/
|
|
99
|
+
static getAllSchemas(): Record<string, Schema>;
|
|
73
100
|
/**
|
|
74
101
|
* Get an adapter factory
|
|
75
102
|
* @param name Adapter name/platform
|
package/lib/registry.js
CHANGED
|
@@ -7,6 +7,7 @@ import { Adapter } from "./adapter.js";
|
|
|
7
7
|
export class ProtocolRegistry {
|
|
8
8
|
static protocols = new Map();
|
|
9
9
|
static metadata = new Map();
|
|
10
|
+
static schemas = new Map();
|
|
10
11
|
/**
|
|
11
12
|
* Register a protocol implementation
|
|
12
13
|
* @param name Protocol name (e.g., 'onebot', 'milky', 'satori')
|
|
@@ -36,6 +37,24 @@ export class ProtocolRegistry {
|
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Register a protocol config schema (key format: name.version)
|
|
42
|
+
*/
|
|
43
|
+
static registerSchema(key, schema) {
|
|
44
|
+
this.schemas.set(key, schema);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get a protocol config schema by key
|
|
48
|
+
*/
|
|
49
|
+
static getSchema(key) {
|
|
50
|
+
return this.schemas.get(key);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Get all protocol config schemas
|
|
54
|
+
*/
|
|
55
|
+
static getAllSchemas() {
|
|
56
|
+
return Object.fromEntries(this.schemas.entries());
|
|
57
|
+
}
|
|
39
58
|
/**
|
|
40
59
|
* Get a protocol factory
|
|
41
60
|
* @param name Protocol name
|
|
@@ -122,6 +141,7 @@ export class ProtocolRegistry {
|
|
|
122
141
|
static clear() {
|
|
123
142
|
this.protocols.clear();
|
|
124
143
|
this.metadata.clear();
|
|
144
|
+
this.schemas.clear();
|
|
125
145
|
}
|
|
126
146
|
}
|
|
127
147
|
/**
|
|
@@ -131,6 +151,7 @@ export class ProtocolRegistry {
|
|
|
131
151
|
export class AdapterRegistry {
|
|
132
152
|
static adapters = new Map();
|
|
133
153
|
static metadata = new Map();
|
|
154
|
+
static schemas = new Map();
|
|
134
155
|
/**
|
|
135
156
|
* Register an adapter implementation
|
|
136
157
|
* @param name Adapter name/platform (e.g., 'wechat', 'dingtalk', 'qq')
|
|
@@ -151,6 +172,24 @@ export class AdapterRegistry {
|
|
|
151
172
|
});
|
|
152
173
|
}
|
|
153
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Register an adapter config schema
|
|
177
|
+
*/
|
|
178
|
+
static registerSchema(name, schema) {
|
|
179
|
+
this.schemas.set(name, schema);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get an adapter config schema
|
|
183
|
+
*/
|
|
184
|
+
static getSchema(name) {
|
|
185
|
+
return this.schemas.get(name);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Get all adapter config schemas
|
|
189
|
+
*/
|
|
190
|
+
static getAllSchemas() {
|
|
191
|
+
return Object.fromEntries(this.schemas.entries());
|
|
192
|
+
}
|
|
154
193
|
/**
|
|
155
194
|
* Get an adapter factory
|
|
156
195
|
* @param name Adapter name/platform
|
|
@@ -208,5 +247,6 @@ export class AdapterRegistry {
|
|
|
208
247
|
static clear() {
|
|
209
248
|
this.adapters.clear();
|
|
210
249
|
this.metadata.clear();
|
|
250
|
+
this.schemas.clear();
|
|
211
251
|
}
|
|
212
252
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 统一时间戳工具:各平台有的返回 Unix **秒**、有的返回 **毫秒**,CommonEvent 等统一用 **毫秒**。
|
|
3
|
+
*/
|
|
4
|
+
/** 启发式阈值:大于等于该值视为已是毫秒(约对应 2001-09-09 之后的毫秒时间戳) */
|
|
5
|
+
export declare const UNIX_TIMESTAMP_MS_MIN = 10000000000;
|
|
6
|
+
/**
|
|
7
|
+
* 平台明确给出 **Unix 秒**(如微信公众号 CreateTime、飞书 create_time)时,转为事件用的 **毫秒**。
|
|
8
|
+
*
|
|
9
|
+
* @param seconds - 秒级时间戳(number / 数字字符串)
|
|
10
|
+
* @param options.fallbackMs - 空值、非有限数或 ≤0 时使用的毫秒时间,默认 `Date.now()`
|
|
11
|
+
*/
|
|
12
|
+
export declare function unixSecondsToEventMs(seconds: unknown, options?: {
|
|
13
|
+
fallbackMs?: number;
|
|
14
|
+
}): number;
|
|
15
|
+
/**
|
|
16
|
+
* 平台明确给出 **Unix 毫秒** 时,校验并取整;无效时使用 fallback。
|
|
17
|
+
*/
|
|
18
|
+
export declare function unixMillisToEventMs(ms: unknown, options?: {
|
|
19
|
+
fallbackMs?: number;
|
|
20
|
+
}): number;
|
|
21
|
+
/**
|
|
22
|
+
* 不确定单位时使用:**大于等于 {@link UNIX_TIMESTAMP_MS_MIN} 视为毫秒**,否则视为秒。
|
|
23
|
+
* 适用于部分文档不清或中间层混传的场景。
|
|
24
|
+
*/
|
|
25
|
+
export declare function coerceUnixToEventMs(value: unknown, options?: {
|
|
26
|
+
fallbackMs?: number;
|
|
27
|
+
}): number;
|
|
28
|
+
/**
|
|
29
|
+
* 需要 **Unix 秒** 的字段(如部分协议里的 message `time`)时使用。
|
|
30
|
+
*
|
|
31
|
+
* @param seconds - 秒级时间戳
|
|
32
|
+
* @param options.fallbackSec - 无效时的秒级时间,默认当前 `Date.now()/1000` 取整
|
|
33
|
+
*/
|
|
34
|
+
export declare function toUnixSeconds(seconds: unknown, options?: {
|
|
35
|
+
fallbackSec?: number;
|
|
36
|
+
}): number;
|
|
37
|
+
/**
|
|
38
|
+
* 平台返回 **ISO 8601 字符串**或其它 `Date` 可解析格式(如 QQ 开放消息 `timestamp`)时,转为事件用的 **毫秒**。
|
|
39
|
+
*/
|
|
40
|
+
export declare function dateLikeToEventMs(value: unknown, options?: {
|
|
41
|
+
fallbackMs?: number;
|
|
42
|
+
}): number;
|
package/lib/timestamp.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 统一时间戳工具:各平台有的返回 Unix **秒**、有的返回 **毫秒**,CommonEvent 等统一用 **毫秒**。
|
|
3
|
+
*/
|
|
4
|
+
/** 启发式阈值:大于等于该值视为已是毫秒(约对应 2001-09-09 之后的毫秒时间戳) */
|
|
5
|
+
export const UNIX_TIMESTAMP_MS_MIN = 10_000_000_000;
|
|
6
|
+
/**
|
|
7
|
+
* 平台明确给出 **Unix 秒**(如微信公众号 CreateTime、飞书 create_time)时,转为事件用的 **毫秒**。
|
|
8
|
+
*
|
|
9
|
+
* @param seconds - 秒级时间戳(number / 数字字符串)
|
|
10
|
+
* @param options.fallbackMs - 空值、非有限数或 ≤0 时使用的毫秒时间,默认 `Date.now()`
|
|
11
|
+
*/
|
|
12
|
+
export function unixSecondsToEventMs(seconds, options) {
|
|
13
|
+
const fallback = options?.fallbackMs ?? Date.now();
|
|
14
|
+
if (seconds == null || seconds === "")
|
|
15
|
+
return fallback;
|
|
16
|
+
const n = typeof seconds === "string" ? parseFloat(seconds) : Number(seconds);
|
|
17
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
18
|
+
return fallback;
|
|
19
|
+
return Math.floor(n * 1000);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 平台明确给出 **Unix 毫秒** 时,校验并取整;无效时使用 fallback。
|
|
23
|
+
*/
|
|
24
|
+
export function unixMillisToEventMs(ms, options) {
|
|
25
|
+
const fallback = options?.fallbackMs ?? Date.now();
|
|
26
|
+
if (ms == null || ms === "")
|
|
27
|
+
return fallback;
|
|
28
|
+
const n = typeof ms === "string" ? parseFloat(ms) : Number(ms);
|
|
29
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
30
|
+
return fallback;
|
|
31
|
+
return Math.floor(n);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 不确定单位时使用:**大于等于 {@link UNIX_TIMESTAMP_MS_MIN} 视为毫秒**,否则视为秒。
|
|
35
|
+
* 适用于部分文档不清或中间层混传的场景。
|
|
36
|
+
*/
|
|
37
|
+
export function coerceUnixToEventMs(value, options) {
|
|
38
|
+
const fallback = options?.fallbackMs ?? Date.now();
|
|
39
|
+
if (value == null || value === "")
|
|
40
|
+
return fallback;
|
|
41
|
+
const n = typeof value === "string" ? parseFloat(value) : Number(value);
|
|
42
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
43
|
+
return fallback;
|
|
44
|
+
if (n >= UNIX_TIMESTAMP_MS_MIN)
|
|
45
|
+
return Math.floor(n);
|
|
46
|
+
return Math.floor(n * 1000);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 需要 **Unix 秒** 的字段(如部分协议里的 message `time`)时使用。
|
|
50
|
+
*
|
|
51
|
+
* @param seconds - 秒级时间戳
|
|
52
|
+
* @param options.fallbackSec - 无效时的秒级时间,默认当前 `Date.now()/1000` 取整
|
|
53
|
+
*/
|
|
54
|
+
export function toUnixSeconds(seconds, options) {
|
|
55
|
+
const fallback = options?.fallbackSec ?? Math.floor(Date.now() / 1000);
|
|
56
|
+
if (seconds == null || seconds === "")
|
|
57
|
+
return fallback;
|
|
58
|
+
const n = typeof seconds === "string" ? parseFloat(seconds) : Number(seconds);
|
|
59
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
60
|
+
return fallback;
|
|
61
|
+
return Math.floor(n);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 平台返回 **ISO 8601 字符串**或其它 `Date` 可解析格式(如 QQ 开放消息 `timestamp`)时,转为事件用的 **毫秒**。
|
|
65
|
+
*/
|
|
66
|
+
export function dateLikeToEventMs(value, options) {
|
|
67
|
+
const fallback = options?.fallbackMs ?? Date.now();
|
|
68
|
+
if (value == null || value === "")
|
|
69
|
+
return fallback;
|
|
70
|
+
const t = new Date(value).getTime();
|
|
71
|
+
return Number.isFinite(t) ? t : fallback;
|
|
72
|
+
}
|
package/lib/utils.d.ts
CHANGED
|
@@ -11,8 +11,9 @@ export declare function omit<T, K extends keyof T>(source: T, keys?: Iterable<K>
|
|
|
11
11
|
* 将驼峰命名替换为下划线分割命名
|
|
12
12
|
* @param name
|
|
13
13
|
* @returns
|
|
14
|
-
* @todo 是否应该改名 ToUnderLine()?
|
|
15
14
|
*/
|
|
15
|
+
export declare function toUnderLine(name: string): string;
|
|
16
|
+
/** @deprecated Use {@link toUnderLine} instead. */
|
|
16
17
|
export declare function toLine<T extends string>(name: T): string;
|
|
17
18
|
export interface Class<T = any> {
|
|
18
19
|
new (...args: any[]): T;
|
package/lib/utils.js
CHANGED
|
@@ -64,25 +64,14 @@ export function transformObj(obj, callback) {
|
|
|
64
64
|
}
|
|
65
65
|
// 深拷贝
|
|
66
66
|
export function deepClone(obj) {
|
|
67
|
-
|
|
68
|
-
return obj;
|
|
69
|
-
|
|
67
|
+
try {
|
|
68
|
+
return structuredClone(obj);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// structuredClone does not support all types (e.g. functions, DOM nodes).
|
|
72
|
+
// Return the original reference as a best-effort fallback.
|
|
70
73
|
return obj;
|
|
71
|
-
//判断拷贝的obj是对象还是数组
|
|
72
|
-
if (Array.isArray(obj))
|
|
73
|
-
return obj.map(item => deepClone(item));
|
|
74
|
-
const objClone = {};
|
|
75
|
-
for (const key in obj) {
|
|
76
|
-
if (obj.hasOwnProperty(key)) {
|
|
77
|
-
if (obj[key] && typeof obj[key] === "object") {
|
|
78
|
-
objClone[key] = deepClone(obj[key]);
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
objClone[key] = obj[key];
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
74
|
}
|
|
85
|
-
return objClone;
|
|
86
75
|
}
|
|
87
76
|
export function pick(source, keys, forced) {
|
|
88
77
|
if (!keys)
|
|
@@ -107,11 +96,14 @@ export function omit(source, keys) {
|
|
|
107
96
|
* 将驼峰命名替换为下划线分割命名
|
|
108
97
|
* @param name
|
|
109
98
|
* @returns
|
|
110
|
-
* @todo 是否应该改名 ToUnderLine()?
|
|
111
99
|
*/
|
|
112
|
-
export function
|
|
100
|
+
export function toUnderLine(name) {
|
|
113
101
|
return name.replace(/([A-Z])/g, "_$1").toLowerCase();
|
|
114
102
|
}
|
|
103
|
+
/** @deprecated Use {@link toUnderLine} instead. */
|
|
104
|
+
export function toLine(name) {
|
|
105
|
+
return toUnderLine(name);
|
|
106
|
+
}
|
|
115
107
|
export function Mixin(base, ...classes) {
|
|
116
108
|
classes.forEach(ctr => {
|
|
117
109
|
Object.getOwnPropertyNames(ctr.prototype).forEach(name => {
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onebots/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "IMHelper 核心抽象层,提供适配器、协议、账号等基础接口和类型",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/index.d.ts",
|
|
8
8
|
"engines": {
|
|
9
|
-
"node": ">=
|
|
9
|
+
"node": ">=24"
|
|
10
10
|
},
|
|
11
11
|
"exports": {
|
|
12
12
|
".": {
|
|
@@ -30,15 +30,17 @@
|
|
|
30
30
|
"license": "MIT",
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/koa": "^3.0.1",
|
|
33
|
-
"@types/node": "^22.7.3",
|
|
34
|
-
"@types/koa-basic-auth": "^2.0.6",
|
|
35
33
|
"@types/koa__router": "^12.0.5",
|
|
34
|
+
"@types/koa-basic-auth": "^2.0.6",
|
|
35
|
+
"@types/koa-static": "^4.0.4",
|
|
36
|
+
"@types/js-yaml": "^4.0.9",
|
|
37
|
+
"@types/node": "^24.0.0",
|
|
36
38
|
"@types/ws": "^8.5.3",
|
|
37
39
|
"tsc-alias": "latest",
|
|
38
40
|
"tsconfig-paths": "latest",
|
|
39
41
|
"tsx": "latest",
|
|
40
|
-
"typescript": "
|
|
41
|
-
"
|
|
42
|
+
"typescript": "5.9.3",
|
|
43
|
+
"wechat": "latest"
|
|
42
44
|
},
|
|
43
45
|
"files": [
|
|
44
46
|
"/lib/**/*.js",
|
|
@@ -50,10 +52,15 @@
|
|
|
50
52
|
"koa": "^3.1.1",
|
|
51
53
|
"koa-basic-auth": "^4.0.0",
|
|
52
54
|
"koa-body": "^7.0.0",
|
|
55
|
+
"koa-static": "^5.0.0",
|
|
53
56
|
"log4js": "^6.5.2",
|
|
54
57
|
"reflect-metadata": "^0.1.13",
|
|
55
58
|
"ws": "^8.16.0"
|
|
56
59
|
},
|
|
60
|
+
"repository": {
|
|
61
|
+
"type": "git",
|
|
62
|
+
"url": "git+https://github.com/lc-cn/onebots.git"
|
|
63
|
+
},
|
|
57
64
|
"scripts": {
|
|
58
65
|
"start": "node .",
|
|
59
66
|
"build": "rm -f *.tsbuildinfo && tsc --project tsconfig.json && tsc-alias -p tsconfig.json",
|