@karinjs/adapter-qqbot 1.2.10 → 1.3.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 +869 -84
- package/dist/chunk-PYOUSZ6B.js +1501 -0
- package/dist/index.d.ts +499 -0
- package/dist/index.js +100 -0
- package/dist/web.config.d.ts +25 -0
- package/dist/web.config.js +224 -0
- package/package.json +22 -15
- package/config/comment/config.json +0 -3
- package/config/config/config.yaml +0 -53
- package/lib/cli/pr.d.ts +0 -2
- package/lib/cli/pr.js +0 -73
- package/lib/core/adapter/adapter.d.ts +0 -91
- package/lib/core/adapter/adapter.js +0 -144
- package/lib/core/adapter/handler.d.ts +0 -49
- package/lib/core/adapter/handler.js +0 -140
- package/lib/core/adapter/markdown.d.ts +0 -38
- package/lib/core/adapter/markdown.js +0 -220
- package/lib/core/adapter/normal.d.ts +0 -30
- package/lib/core/adapter/normal.js +0 -263
- package/lib/core/api/index.d.ts +0 -167
- package/lib/core/api/index.js +0 -180
- package/lib/core/api/sign.d.ts +0 -22
- package/lib/core/api/sign.js +0 -40
- package/lib/core/api/types.d.ts +0 -236
- package/lib/core/api/types.js +0 -1
- package/lib/core/event/conver.d.ts +0 -10
- package/lib/core/event/conver.js +0 -57
- package/lib/core/event/index.d.ts +0 -9
- package/lib/core/event/index.js +0 -98
- package/lib/core/event/message.d.ts +0 -27
- package/lib/core/event/message.js +0 -95
- package/lib/core/event/notice.d.ts +0 -50
- package/lib/core/event/notice.js +0 -133
- package/lib/core/event/types.d.ts +0 -473
- package/lib/core/event/types.js +0 -1
- package/lib/core/internal/axios.d.ts +0 -25
- package/lib/core/internal/axios.js +0 -46
- package/lib/core/internal/sign.d.ts +0 -22
- package/lib/core/internal/sign.js +0 -40
- package/lib/core/webhook/c2c.d.ts +0 -2
- package/lib/core/webhook/c2c.js +0 -14
- package/lib/core/webhook/http.d.ts +0 -2
- package/lib/core/webhook/http.js +0 -35
- package/lib/core/webhook/index.d.ts +0 -17
- package/lib/core/webhook/index.js +0 -87
- package/lib/core/webhook/ws.d.ts +0 -2
- package/lib/core/webhook/ws.js +0 -79
- package/lib/index.d.ts +0 -1
- package/lib/index.js +0 -5
- package/lib/root.d.ts +0 -5
- package/lib/root.js +0 -9
- package/lib/types/config.d.ts +0 -53
- package/lib/types/config.js +0 -1
- package/lib/utils/common.d.ts +0 -80
- package/lib/utils/common.js +0 -198
- package/lib/utils/config.d.ts +0 -9
- package/lib/utils/config.js +0 -54
- package/lib/utils/dir.d.ts +0 -1
- package/lib/utils/dir.js +0 -1
- package/lib/utils/index.d.ts +0 -4
- package/lib/utils/index.js +0 -4
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import nacl from 'tweetnacl';
|
|
2
|
-
/**
|
|
3
|
-
* 从提供的种子生成密钥对
|
|
4
|
-
* @param seed 用于生成密钥的种子
|
|
5
|
-
*/
|
|
6
|
-
export const generateKeyPair = (seed) => {
|
|
7
|
-
let finalSeed = seed;
|
|
8
|
-
while (finalSeed.length < 32) {
|
|
9
|
-
finalSeed = finalSeed.repeat(2); // 确保种子长度至少为32字节
|
|
10
|
-
}
|
|
11
|
-
finalSeed = finalSeed.slice(0, 32); // 截断为32字节
|
|
12
|
-
const seedBuffer = Buffer.from(finalSeed, 'utf-8');
|
|
13
|
-
const keyPair = nacl.sign.keyPair.fromSeed(seedBuffer);
|
|
14
|
-
return keyPair;
|
|
15
|
-
};
|
|
16
|
-
/**
|
|
17
|
-
* 使用提供的私钥、事件时间戳和明文令牌对消息进行签名
|
|
18
|
-
* @param privateKey 用于签名的私钥
|
|
19
|
-
* @param eventTs 计算签名使用时间戳
|
|
20
|
-
* @param plainToken 需要计算签名的字符串
|
|
21
|
-
* @returns 生成的十六进制格式的签名
|
|
22
|
-
*/
|
|
23
|
-
export const signMessage = (privateKey, eventTs, plainToken) => {
|
|
24
|
-
const message = eventTs + plainToken;
|
|
25
|
-
const messageBytes = Buffer.from(message, 'utf-8');
|
|
26
|
-
const signature = nacl.sign.detached(messageBytes, privateKey);
|
|
27
|
-
return Buffer.from(signature).toString('hex');
|
|
28
|
-
};
|
|
29
|
-
/**
|
|
30
|
-
* 签名
|
|
31
|
-
* @param secret 机器人密钥
|
|
32
|
-
* @param eventTs 计算签名使用时间戳
|
|
33
|
-
* @param plainToken 需要计算签名的字符串
|
|
34
|
-
* @returns 生成的十六进制格式的签名
|
|
35
|
-
*/
|
|
36
|
-
export const sign = (secret, eventTs, plainToken) => {
|
|
37
|
-
const { secretKey } = generateKeyPair(secret);
|
|
38
|
-
const signature = signMessage(secretKey, eventTs, plainToken);
|
|
39
|
-
return signature;
|
|
40
|
-
};
|
package/lib/core/webhook/c2c.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { webhook } from '.';
|
|
2
|
-
import { app } from 'node-karin';
|
|
3
|
-
import { expressAfterInit } from '@/utils/common';
|
|
4
|
-
/** 创建端到端webhook路由 */
|
|
5
|
-
export const createC2CWebhook = () => {
|
|
6
|
-
const c2cRouting = '/webhook/c2c';
|
|
7
|
-
/**
|
|
8
|
-
* api 端到端
|
|
9
|
-
* tx -> bot/webhook
|
|
10
|
-
*/
|
|
11
|
-
app.post(c2cRouting, (req, res) => webhook(req, res, () => true));
|
|
12
|
-
/** 调整中间件顺序 不使用json中间件 */
|
|
13
|
-
expressAfterInit(app._router.stack, c2cRouting, 'post');
|
|
14
|
-
};
|
package/lib/core/webhook/http.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { webhook } from '.';
|
|
2
|
-
import { app, } from 'node-karin';
|
|
3
|
-
import { config } from '@/utils/config';
|
|
4
|
-
import { expressAfterInit, fakeEvent } from '@/utils/common';
|
|
5
|
-
/** 创建http中转webhook路由 */
|
|
6
|
-
export const createHttpWebhook = () => {
|
|
7
|
-
const httpRouting = '/webhook/http';
|
|
8
|
-
/**
|
|
9
|
-
* api 中转
|
|
10
|
-
* tx -> webhook ->bot/http
|
|
11
|
-
*/
|
|
12
|
-
app.post(httpRouting, (req, res) => webhook(req, res, (req, appid) => {
|
|
13
|
-
const auth = req.headers['authorization'];
|
|
14
|
-
if (!auth) {
|
|
15
|
-
fakeEvent(`${appid} 未在头部中找到 Authorization: ${req.socket.remoteAddress}`);
|
|
16
|
-
return false;
|
|
17
|
-
}
|
|
18
|
-
const cfg = config()[appid];
|
|
19
|
-
if (!cfg) {
|
|
20
|
-
fakeEvent(`[配置错误][${appid}] 配置文件中未找到对应的appid,请检查配置文件`);
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
23
|
-
if (cfg.event.type !== 2) {
|
|
24
|
-
fakeEvent(`${appid} http中转服务未启用,请检查配置文件: ${req.socket.remoteAddress}`);
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
if (auth !== cfg.event.httpToken) {
|
|
28
|
-
fakeEvent(`${appid} Authorization 验证失败: ${req.socket.remoteAddress}`);
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
return true;
|
|
32
|
-
}));
|
|
33
|
-
/** 调整中间件顺序 不使用json中间件 */
|
|
34
|
-
expressAfterInit(app._router.stack, httpRouting, 'post');
|
|
35
|
-
};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { Request, Response } from 'express';
|
|
2
|
-
/**
|
|
3
|
-
* webhook
|
|
4
|
-
* @param req 请求
|
|
5
|
-
* @param res 响应
|
|
6
|
-
*/
|
|
7
|
-
export declare const webhook: (req: Request, res: Response, fnc: Function) => Promise<void>;
|
|
8
|
-
/**
|
|
9
|
-
* 判断请求是否合规并返回appid
|
|
10
|
-
* @param req 请求
|
|
11
|
-
* @param res 响应
|
|
12
|
-
*/
|
|
13
|
-
export declare const checkAppid: (req: Request, res: Response) => Promise<{
|
|
14
|
-
appid: string;
|
|
15
|
-
rawBody: string;
|
|
16
|
-
body: Record<string, any>;
|
|
17
|
-
} | undefined>;
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { sign } from '../api/sign';
|
|
2
|
-
import { logger } from 'node-karin';
|
|
3
|
-
import { config } from '@/utils/config';
|
|
4
|
-
import { event, fakeEvent } from '@/utils/common';
|
|
5
|
-
/**
|
|
6
|
-
* webhook
|
|
7
|
-
* @param req 请求
|
|
8
|
-
* @param res 响应
|
|
9
|
-
*/
|
|
10
|
-
export const webhook = async (req, res, fnc) => {
|
|
11
|
-
const data = await checkAppid(req, res);
|
|
12
|
-
if (!data)
|
|
13
|
-
return;
|
|
14
|
-
if (!fnc(req))
|
|
15
|
-
return;
|
|
16
|
-
const api = config()[data.appid];
|
|
17
|
-
if (!api) {
|
|
18
|
-
logger.error(`[配置错误][${data.appid}] 配置文件中未找到对应的appid,请检查配置文件`);
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
if (api.event.type !== 1) {
|
|
22
|
-
logger.error(`[配置错误][${data.appid}] webhook未启用,请检查配置文件`);
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
/** 接口初始化 鉴权回调 */
|
|
26
|
-
if (data.body.op === 13) {
|
|
27
|
-
const eventTs = data.body?.d?.event_ts;
|
|
28
|
-
const plainToken = data.body?.d?.plain_token;
|
|
29
|
-
if (!eventTs || !plainToken) {
|
|
30
|
-
logger.error(`[请求数据错误][${data.appid}] 未找到 event_ts 或 plain_token: ${data.rawBody}`);
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
const signature = sign(api.secret, eventTs, plainToken);
|
|
34
|
-
logger.mark(`[signature][${data.appid}] ${signature}`);
|
|
35
|
-
res.setHeader('Content-Type', 'application/json');
|
|
36
|
-
res.status(200).end(JSON.stringify({ plain_token: plainToken, signature }));
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
/** 非回调事件 进行鉴权验证 */
|
|
40
|
-
const ed25519 = req.headers['x-signature-ed25519'];
|
|
41
|
-
const signature = sign(api.secret, req.headers['x-signature-timestamp'], data.rawBody);
|
|
42
|
-
if (ed25519 !== signature) {
|
|
43
|
-
fakeEvent(`签名验证失败:\nappid: ${data.appid}\ned25519: ${ed25519}\n实际签名: ${signature}\nbody: ${data.rawBody}`);
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
event.emit(data.appid, data.body);
|
|
47
|
-
};
|
|
48
|
-
/**
|
|
49
|
-
* 判断请求是否合规并返回appid
|
|
50
|
-
* @param req 请求
|
|
51
|
-
* @param res 响应
|
|
52
|
-
*/
|
|
53
|
-
export const checkAppid = async (req, res) => {
|
|
54
|
-
let response = true;
|
|
55
|
-
try {
|
|
56
|
-
const ip = req.socket.remoteAddress;
|
|
57
|
-
const appid = req.headers['x-bot-appid'];
|
|
58
|
-
const rawBody = await new Promise((resolve) => {
|
|
59
|
-
const raw = [];
|
|
60
|
-
req.on('data', (chunk) => raw.push(chunk));
|
|
61
|
-
req.on('end', () => resolve(raw.join('')));
|
|
62
|
-
});
|
|
63
|
-
if (!appid) {
|
|
64
|
-
fakeEvent(`未找到 x-bot-appid: ${ip} body: ${rawBody}`);
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
const userAgent = req.headers['user-agent'];
|
|
68
|
-
if (userAgent !== 'QQBot-Callback') {
|
|
69
|
-
fakeEvent(`未找到 User-Agent: ${ip} body: ${rawBody}`);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
const body = JSON.parse(rawBody) || {};
|
|
73
|
-
if (typeof body.op !== 'number') {
|
|
74
|
-
fakeEvent(`非法请求体,未找到 op: ${ip} body: ${rawBody}`);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
else if (body.op === 13) {
|
|
78
|
-
/** op13是计算sign返回 不可以直接返回状态码 */
|
|
79
|
-
response = false;
|
|
80
|
-
}
|
|
81
|
-
return { appid, rawBody, body };
|
|
82
|
-
}
|
|
83
|
-
finally {
|
|
84
|
-
/** 根据文档 返回200或者204都可以 必须在3秒内完成 */
|
|
85
|
-
response && res.status(200).end();
|
|
86
|
-
}
|
|
87
|
-
};
|
package/lib/core/webhook/ws.d.ts
DELETED
package/lib/core/webhook/ws.js
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import WebSocket from 'node-karin/ws';
|
|
2
|
-
import { config } from '@/utils/config';
|
|
3
|
-
import { logger } from 'node-karin';
|
|
4
|
-
import { sign } from '../api/sign';
|
|
5
|
-
import { event, fakeEvent } from '@/utils/common';
|
|
6
|
-
/** 创建ws中转路由 */
|
|
7
|
-
export const createWebSocketWebhook = () => {
|
|
8
|
-
const cfg = config();
|
|
9
|
-
for (const appid of Object.keys(cfg)) {
|
|
10
|
-
const api = cfg[appid];
|
|
11
|
-
if (appid === 'default')
|
|
12
|
-
continue;
|
|
13
|
-
if (api?.event?.type !== 3)
|
|
14
|
-
continue;
|
|
15
|
-
const url = api.event.wsUrl;
|
|
16
|
-
const headers = { 'x-bot-appid': appid, authorization: api.event.wsToken };
|
|
17
|
-
createWebSocket(api, url, headers);
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
const createWebSocket = (cfg, url, headers) => {
|
|
21
|
-
const socket = new WebSocket(url, { headers });
|
|
22
|
-
const appid = headers['x-bot-appid'];
|
|
23
|
-
const close = (isClose = true) => {
|
|
24
|
-
/** 停止监听 */
|
|
25
|
-
socket.removeAllListeners();
|
|
26
|
-
isClose && socket?.close();
|
|
27
|
-
logger.error(`[QQBot][${appid}] WebSocket连接已断开,将在5秒后重连: ${url}`);
|
|
28
|
-
setTimeout(() => createWebSocket(cfg, url, headers), 5000);
|
|
29
|
-
};
|
|
30
|
-
socket.on('close', () => close(false));
|
|
31
|
-
socket.on('error', (error) => { logger.error(error); close(); });
|
|
32
|
-
socket.on('open', () => logger.info(`[QQBot][${appid}] WebSocket连接已打开: ${url}`));
|
|
33
|
-
socket.on('message', (data) => {
|
|
34
|
-
const raw = data.toString();
|
|
35
|
-
const parse = JSON.parse(raw);
|
|
36
|
-
if (parse?.type === 'event') {
|
|
37
|
-
return eventHandler(appid, cfg.secret, raw, parse);
|
|
38
|
-
}
|
|
39
|
-
if (parse?.type === 'sign') {
|
|
40
|
-
signHandler(appid, cfg.secret, raw, parse, socket);
|
|
41
|
-
}
|
|
42
|
-
logger.error(`[QQBot][${appid}] 未知的消息类型: ${raw}`);
|
|
43
|
-
});
|
|
44
|
-
};
|
|
45
|
-
/**
|
|
46
|
-
* 事件处理
|
|
47
|
-
* @param appid 机器人id
|
|
48
|
-
* @param secret 机器人密钥
|
|
49
|
-
* @param raw 原始数据
|
|
50
|
-
* @param parse 解析后的数据
|
|
51
|
-
*/
|
|
52
|
-
const eventHandler = (appid, secret, raw, parse) => {
|
|
53
|
-
if (!parse?.data?.headers || !parse?.data?.headers['x-signature-ed25519'] || !parse?.data?.headers['x-signature-timestamp']) {
|
|
54
|
-
fakeEvent(`未找到签名字段: ${raw}`);
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
const signature = sign(secret, parse.data.headers['x-signature-timestamp'], parse.data.event);
|
|
58
|
-
if (signature !== parse.data.headers['x-signature-ed25519']) {
|
|
59
|
-
logger.error(`[QQBot][${appid}] 签名验证失败: ${raw}`);
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
event.emit(appid, JSON.parse(parse.data.event));
|
|
63
|
-
};
|
|
64
|
-
/**
|
|
65
|
-
* 签名处理
|
|
66
|
-
* @param appid 机器人id
|
|
67
|
-
* @param secret 机器人密钥
|
|
68
|
-
* @param raw 原始数据
|
|
69
|
-
* @param parse 解析后的数据
|
|
70
|
-
* @param socket websocket实例
|
|
71
|
-
*/
|
|
72
|
-
const signHandler = (appid, secret, raw, parse, socket) => {
|
|
73
|
-
if (!parse?.data?.eventTs || !parse?.data?.plainToken) {
|
|
74
|
-
logger.error(`[QQBot][${appid}] 未找到 eventTs 或 plainToken: ${raw}`);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
const signature = sign(secret, parse.data.eventTs, parse.data.plainToken);
|
|
78
|
-
socket.send(JSON.stringify({ echo: parse.echo, data: { signature } }));
|
|
79
|
-
};
|
package/lib/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import '@/core/event/index';
|
package/lib/index.js
DELETED
package/lib/root.d.ts
DELETED
package/lib/root.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import { fileURLToPath } from 'url';
|
|
3
|
-
/** 当前文件的绝对路径 */
|
|
4
|
-
const filePath = fileURLToPath(import.meta.url);
|
|
5
|
-
/** 插件包绝对路径 */
|
|
6
|
-
const dirPath = path.resolve(filePath, '../..');
|
|
7
|
-
/** 插件包的名称 */
|
|
8
|
-
const basename = path.basename(dirPath);
|
|
9
|
-
export { dirPath, basename };
|
package/lib/types/config.d.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/** markdown */
|
|
2
|
-
type Markdown = {
|
|
3
|
-
/** markdown模式 */
|
|
4
|
-
mode: 0 | 1 | 3 | 4 | 5;
|
|
5
|
-
/** 模板ID */
|
|
6
|
-
id: string;
|
|
7
|
-
/** 模板键配置 */
|
|
8
|
-
kv: string[];
|
|
9
|
-
};
|
|
10
|
-
/** `config.yaml` 文件的类型定义 */
|
|
11
|
-
export type Config = Record<string, {
|
|
12
|
-
/** 机器人ID */
|
|
13
|
-
appId: string;
|
|
14
|
-
/** 机器人密钥 */
|
|
15
|
-
secret: string;
|
|
16
|
-
/** 正式环境Api */
|
|
17
|
-
prodApi: string;
|
|
18
|
-
/** 测试环境Api */
|
|
19
|
-
sandboxApi: string;
|
|
20
|
-
/** 调用凭证的Api */
|
|
21
|
-
tokenApi: string;
|
|
22
|
-
/** 开启沙盒环境 */
|
|
23
|
-
sandbox: boolean;
|
|
24
|
-
/** 开启QQ场景能力 */
|
|
25
|
-
qqEnable: boolean;
|
|
26
|
-
/** 开启频道场景能力 */
|
|
27
|
-
guildEnable: boolean;
|
|
28
|
-
/** 频道场景模式 0-公域 1-私域 */
|
|
29
|
-
guildMode: 0 | 1;
|
|
30
|
-
/** 文本中的url转二维码白名单 配置后将不转换这些url为二维码 */
|
|
31
|
-
exclude: string[];
|
|
32
|
-
/** Markdown */
|
|
33
|
-
markdown: Markdown;
|
|
34
|
-
/** 接受到消息后对文本进行表达式处理 */
|
|
35
|
-
regex: {
|
|
36
|
-
/** 表达式 */
|
|
37
|
-
reg: string | RegExp;
|
|
38
|
-
/** 替换 */
|
|
39
|
-
rep: string;
|
|
40
|
-
}[];
|
|
41
|
-
/** 事件接收配置 */
|
|
42
|
-
event: {
|
|
43
|
-
/** 接收方式 0-关闭 1-webhook 2-http 3-ws */
|
|
44
|
-
type: 0 | 1 | 2 | 3;
|
|
45
|
-
/** ws服务器地址 */
|
|
46
|
-
wsUrl: string;
|
|
47
|
-
/** ws服务器鉴权token */
|
|
48
|
-
wsToken: string;
|
|
49
|
-
/** http鉴权token */
|
|
50
|
-
httpToken: string;
|
|
51
|
-
};
|
|
52
|
-
}>;
|
|
53
|
-
export {};
|
package/lib/types/config.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/lib/utils/common.d.ts
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import EventEmitter from 'node:events';
|
|
2
|
-
import { ButtonElement } from 'node-karin';
|
|
3
|
-
/**
|
|
4
|
-
* 事件总线
|
|
5
|
-
*/
|
|
6
|
-
export declare const event: EventEmitter<[never]>;
|
|
7
|
-
/**
|
|
8
|
-
* 睡眠函数
|
|
9
|
-
* @param ms - 毫秒
|
|
10
|
-
*/
|
|
11
|
-
export declare const sleep: (ms: number) => Promise<unknown>;
|
|
12
|
-
/**
|
|
13
|
-
* 生成随机数
|
|
14
|
-
* @param min - 最小值
|
|
15
|
-
* @param max - 最大值
|
|
16
|
-
*/
|
|
17
|
-
export declare const random: (min: number, max: number) => number;
|
|
18
|
-
/**
|
|
19
|
-
* 调整express中间件和路由的顺序,将路由提升到中间件前面
|
|
20
|
-
* @param stack - express实例的stack
|
|
21
|
-
* @param path - 路由名称
|
|
22
|
-
* @param method - 请求方法
|
|
23
|
-
* @param middlewareName - 中间件名称
|
|
24
|
-
*/
|
|
25
|
-
export declare const expressStack: (stack: any[], path: string, method: "use" | "get" | "post" | "put" | "delete", middlewareName: string) => void;
|
|
26
|
-
/**
|
|
27
|
-
* 调整express路由的顺序 使其在`expressInit`之后
|
|
28
|
-
* @param stack - express实例的stack
|
|
29
|
-
* @param path - 路由名称
|
|
30
|
-
* @param method - 请求方法
|
|
31
|
-
*/
|
|
32
|
-
export declare const expressAfterInit: (stack: any[], path: string, method: "use" | "get" | "post" | "put" | "delete") => void;
|
|
33
|
-
/**
|
|
34
|
-
* 虚假事件
|
|
35
|
-
* @param log 日志
|
|
36
|
-
*/
|
|
37
|
-
export declare const fakeEvent: (log: string) => void;
|
|
38
|
-
/**
|
|
39
|
-
* 处理url
|
|
40
|
-
* @param text - 文本
|
|
41
|
-
* @param exclude - 排除的URL
|
|
42
|
-
* @returns url列表
|
|
43
|
-
*/
|
|
44
|
-
export declare const handleUrl: (text: string, exclude?: string[]) => string[];
|
|
45
|
-
/**
|
|
46
|
-
* 生成二维码
|
|
47
|
-
* @param urls - url列表
|
|
48
|
-
* @returns 返回带`base64://`前缀的二维码列表
|
|
49
|
-
*/
|
|
50
|
-
export declare const qrs: (urls: string[]) => Promise<string[]>;
|
|
51
|
-
/**
|
|
52
|
-
* 语音文件转silk
|
|
53
|
-
* @param file 文件路径 base64 buffer
|
|
54
|
-
* @returns silk文件buffer
|
|
55
|
-
*/
|
|
56
|
-
export declare const silkEncode: (file: string | Buffer) => Promise<Buffer>;
|
|
57
|
-
/**
|
|
58
|
-
* 处理文本 提取其中的url转为按钮
|
|
59
|
-
* @param text - 文本
|
|
60
|
-
* @param isC2C - 是否为C2C消息
|
|
61
|
-
*/
|
|
62
|
-
export declare const textToButton: (text: string, isC2C?: boolean) => {
|
|
63
|
-
text: string;
|
|
64
|
-
buttons: ButtonElement[];
|
|
65
|
-
};
|
|
66
|
-
/**
|
|
67
|
-
* 传入一个图片链接,获取图片的宽高
|
|
68
|
-
* @param url - 图片链接
|
|
69
|
-
* @returns 图片的宽高
|
|
70
|
-
*/
|
|
71
|
-
export declare const getImageSize: (url: string) => Promise<{
|
|
72
|
-
width: number;
|
|
73
|
-
height: number;
|
|
74
|
-
}>;
|
|
75
|
-
/**
|
|
76
|
-
* 处理模板文本,对不能直接发送的 Markdown 语法进行转义
|
|
77
|
-
* @param text - 文本
|
|
78
|
-
* @returns 转义后的文本
|
|
79
|
-
*/
|
|
80
|
-
export declare const escapeMarkdown: (text: string) => string;
|
package/lib/utils/common.js
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import qrcode from 'qrcode';
|
|
3
|
-
import size from 'image-size';
|
|
4
|
-
import lodash from 'node-karin/lodash';
|
|
5
|
-
import EventEmitter from 'node:events';
|
|
6
|
-
import GetUrls from '@karinjs/geturls';
|
|
7
|
-
import { encode, isSilk } from 'silk-wasm';
|
|
8
|
-
import { common, ffmpeg, logger, segment, tempPath } from 'node-karin';
|
|
9
|
-
/**
|
|
10
|
-
* 事件总线
|
|
11
|
-
*/
|
|
12
|
-
export const event = new EventEmitter();
|
|
13
|
-
/**
|
|
14
|
-
* 睡眠函数
|
|
15
|
-
* @param ms - 毫秒
|
|
16
|
-
*/
|
|
17
|
-
export const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
18
|
-
/**
|
|
19
|
-
* 生成随机数
|
|
20
|
-
* @param min - 最小值
|
|
21
|
-
* @param max - 最大值
|
|
22
|
-
*/
|
|
23
|
-
export const random = (min, max) => lodash.random(min, max);
|
|
24
|
-
/**
|
|
25
|
-
* 调整express中间件和路由的顺序,将路由提升到中间件前面
|
|
26
|
-
* @param stack - express实例的stack
|
|
27
|
-
* @param path - 路由名称
|
|
28
|
-
* @param method - 请求方法
|
|
29
|
-
* @param middlewareName - 中间件名称
|
|
30
|
-
*/
|
|
31
|
-
export const expressStack = (stack, path, method, middlewareName) => {
|
|
32
|
-
const routeIndex = stack.findIndex((layer) => layer.route?.path === path && layer.route?.methods?.[method]);
|
|
33
|
-
const middlewareIndex = stack.findIndex((layer) => layer.name === middlewareName);
|
|
34
|
-
if (routeIndex !== -1 && middlewareIndex !== -1 && routeIndex > middlewareIndex) {
|
|
35
|
-
const [route] = stack.splice(routeIndex, 1);
|
|
36
|
-
stack.splice(middlewareIndex, 0, route);
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
/**
|
|
40
|
-
* 调整express路由的顺序 使其在`expressInit`之后
|
|
41
|
-
* @param stack - express实例的stack
|
|
42
|
-
* @param path - 路由名称
|
|
43
|
-
* @param method - 请求方法
|
|
44
|
-
*/
|
|
45
|
-
export const expressAfterInit = (stack, path, method) => {
|
|
46
|
-
const routeIndex = stack.findIndex((layer) => layer.route?.path === path && layer.route?.methods?.[method]);
|
|
47
|
-
const initIndex = stack.findIndex((layer) => layer.name === 'expressInit');
|
|
48
|
-
if (routeIndex !== -1 && initIndex !== -1 && (initIndex + 1) < routeIndex) {
|
|
49
|
-
const [route] = stack.splice(routeIndex, 1);
|
|
50
|
-
stack.splice(initIndex + 1, 0, route);
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
/**
|
|
54
|
-
* 虚假事件
|
|
55
|
-
* @param log 日志
|
|
56
|
-
*/
|
|
57
|
-
export const fakeEvent = (log) => {
|
|
58
|
-
logger.fatal(`${logger.red('[QQBot][虚假事件]')} ${log.replace(/\n/g, '\\n')}`);
|
|
59
|
-
};
|
|
60
|
-
/**
|
|
61
|
-
* 处理url
|
|
62
|
-
* @param text - 文本
|
|
63
|
-
* @param exclude - 排除的URL
|
|
64
|
-
* @returns url列表
|
|
65
|
-
*/
|
|
66
|
-
export const handleUrl = (text, exclude = []) => {
|
|
67
|
-
/** 中文不符合url规范 */
|
|
68
|
-
text = text.replace(/[\u4e00-\u9fa5]/g, '|');
|
|
69
|
-
const urls = GetUrls.getUrls(text, {
|
|
70
|
-
exclude,
|
|
71
|
-
/** 去除 WWW */
|
|
72
|
-
stripWWW: false,
|
|
73
|
-
/** 规范化协议 */
|
|
74
|
-
normalizeProtocol: false,
|
|
75
|
-
/** 移除查询参数 */
|
|
76
|
-
removeQueryParameters: false,
|
|
77
|
-
/** 移除唯一斜杠 */
|
|
78
|
-
removeSingleSlash: false,
|
|
79
|
-
/** 查询参数排序 */
|
|
80
|
-
sortQueryParameters: false,
|
|
81
|
-
/** 去除认证信息 */
|
|
82
|
-
stripAuthentication: false,
|
|
83
|
-
/** 去除文本片段 */
|
|
84
|
-
stripTextFragment: false,
|
|
85
|
-
/** 移除末尾斜杠 */
|
|
86
|
-
removeTrailingSlash: false,
|
|
87
|
-
/** 不进行标准处理url */
|
|
88
|
-
normalize: false,
|
|
89
|
-
});
|
|
90
|
-
return urls;
|
|
91
|
-
};
|
|
92
|
-
/**
|
|
93
|
-
* 生成二维码
|
|
94
|
-
* @param urls - url列表
|
|
95
|
-
* @returns 返回带`base64://`前缀的二维码列表
|
|
96
|
-
*/
|
|
97
|
-
export const qrs = async (urls) => {
|
|
98
|
-
const list = await Promise.all(urls.map((url) => qrcode.toDataURL(url)));
|
|
99
|
-
return list.map((item) => `base64://${item.split(',')[1]}`);
|
|
100
|
-
};
|
|
101
|
-
/**
|
|
102
|
-
* 语音文件转silk
|
|
103
|
-
* @param file 文件路径 base64 buffer
|
|
104
|
-
* @returns silk文件buffer
|
|
105
|
-
*/
|
|
106
|
-
export const silkEncode = async (file) => {
|
|
107
|
-
const buffer = await common.buffer(file);
|
|
108
|
-
if (isSilk(buffer))
|
|
109
|
-
return buffer;
|
|
110
|
-
const start = Date.now();
|
|
111
|
-
const root = `${tempPath}/recordToSilk`;
|
|
112
|
-
const temp = `${root}/${start}`;
|
|
113
|
-
const src = `${temp}/${start}`;
|
|
114
|
-
const pcm = `${temp}/${start}.pcm`;
|
|
115
|
-
// const silk = `${temp}/${start}.silk`
|
|
116
|
-
fs.mkdirSync(temp, { recursive: true });
|
|
117
|
-
fs.writeFileSync(src, buffer);
|
|
118
|
-
const { status, error } = await ffmpeg(` -i ${src} -f s16le -ar 48000 -ac 1 ${pcm}`);
|
|
119
|
-
if (!status)
|
|
120
|
-
throw error;
|
|
121
|
-
const pamBuffer = await fs.promises.readFile(pcm);
|
|
122
|
-
const { data, duration } = await encode(pamBuffer, 48000);
|
|
123
|
-
setTimeout(() => {
|
|
124
|
-
// TODO: 缓存silk文件 提升性能
|
|
125
|
-
const ms = logger.yellow(duration + 'ms');
|
|
126
|
-
const end = logger.yellow((Date.now() - start) + 'ms');
|
|
127
|
-
logger.info(`[QQBot] silk文件转码成功 音频时长: ${ms} 转码耗时: ${end}`);
|
|
128
|
-
fs.rmSync(temp, { recursive: true, force: true });
|
|
129
|
-
}, 100);
|
|
130
|
-
return Buffer.from(data);
|
|
131
|
-
};
|
|
132
|
-
/**
|
|
133
|
-
* 处理文本 提取其中的url转为按钮
|
|
134
|
-
* @param text - 文本
|
|
135
|
-
* @param isC2C - 是否为C2C消息
|
|
136
|
-
*/
|
|
137
|
-
export const textToButton = (text, isC2C = false) => {
|
|
138
|
-
text = text.replace(/@everyone/g, 'everyone').replace(/\n/g, '\r');
|
|
139
|
-
if (isC2C)
|
|
140
|
-
text = text.replace(/<qqbot-at-user id=".+" \/>/gm, '').replace(/<@.+>/gm, '');
|
|
141
|
-
const urls = handleUrl(text);
|
|
142
|
-
if (urls.length === 0) {
|
|
143
|
-
return { text, buttons: [] };
|
|
144
|
-
}
|
|
145
|
-
const buttons = [];
|
|
146
|
-
urls.forEach((url, index) => {
|
|
147
|
-
text = text.replace(new RegExp(url, 'g'), `[请点击 按钮${index} 查看]`);
|
|
148
|
-
buttons.push(segment.button({ text: `${index}. ${url}`, link: url }));
|
|
149
|
-
});
|
|
150
|
-
// 最多只有5个长度
|
|
151
|
-
return { text, buttons: buttons.length > 5 ? buttons.slice(0, 5) : buttons };
|
|
152
|
-
};
|
|
153
|
-
/**
|
|
154
|
-
* 传入一个图片链接,获取图片的宽高
|
|
155
|
-
* @param url - 图片链接
|
|
156
|
-
* @returns 图片的宽高
|
|
157
|
-
*/
|
|
158
|
-
export const getImageSize = async (url) => {
|
|
159
|
-
const buffer = await common.buffer(url);
|
|
160
|
-
const { width = 100, height = 100 } = size(buffer);
|
|
161
|
-
return { width, height };
|
|
162
|
-
};
|
|
163
|
-
/**
|
|
164
|
-
* 处理模板文本,对不能直接发送的 Markdown 语法进行转义
|
|
165
|
-
* @param text - 文本
|
|
166
|
-
* @returns 转义后的文本
|
|
167
|
-
*/
|
|
168
|
-
export const escapeMarkdown = (text) => {
|
|
169
|
-
/**
|
|
170
|
-
* @example
|
|
171
|
-
* ```md
|
|
172
|
-
* # 标题
|
|
173
|
-
* **加粗**
|
|
174
|
-
* __下划线加粗__
|
|
175
|
-
* _斜体_
|
|
176
|
-
* *星号斜体*
|
|
177
|
-
* ***加粗斜体***
|
|
178
|
-
* ~~删除线~~
|
|
179
|
-
* ```
|
|
180
|
-
*/
|
|
181
|
-
// 转义标题(仅行首的 #)
|
|
182
|
-
text = text.replace(/^(\s*)#(?!#)/gm, '$1\r#');
|
|
183
|
-
// 转义加粗斜体(排除已经转义的情况)
|
|
184
|
-
text = text.replace(/(?<!\\)\*\*\*(.+?)(?<!\\)\*\*\*/g, '\\*\\*\\*$1\\*\\*\\*');
|
|
185
|
-
// 转义加粗(星号加粗,排除已转义)
|
|
186
|
-
text = text.replace(/(?<!\\)\*\*(.+?)(?<!\\)\*\*/g, '\\*\\*$1\\*\\*');
|
|
187
|
-
// 转义下划线加粗(排除已转义)
|
|
188
|
-
text = text.replace(/(?<!\\)__(.+?)(?<!\\)__/g, '\\__\\__$1\\__\\__');
|
|
189
|
-
// 转义斜体(星号斜体,排除已转义)
|
|
190
|
-
text = text.replace(/(?<!\\)\*(.+?)(?<!\\)\*/g, '\\*$1\\*');
|
|
191
|
-
// 转义斜体(下划线斜体,排除已转义)
|
|
192
|
-
text = text.replace(/(?<!\\)_(.+?)(?<!\\)_/g, '\\_$1\\_');
|
|
193
|
-
// 转义删除线(排除已转义)
|
|
194
|
-
text = text.replace(/(?<!\\)~~(.+?)(?<!\\)~~/g, '\\~\\~$1\\~\\~');
|
|
195
|
-
// 转义其他特殊字符 这里先不管。
|
|
196
|
-
// text = text.replace(/(?<!\\)([\\`*_{}\[\]()#+\-.!])/g, '\\$1')
|
|
197
|
-
return text;
|
|
198
|
-
};
|