@alemonjs/qq-bot 0.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/README.md +37 -0
- package/lib/api.js +1172 -0
- package/lib/client.js +176 -0
- package/lib/config.js +3 -0
- package/lib/from.js +44 -0
- package/lib/index.js +396 -0
- package/lib/webhook.js +53 -0
- package/package.json +57 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { QQBotAPI } from './api.js';
|
|
2
|
+
import bodyParser from 'koa-bodyparser';
|
|
3
|
+
import Router from 'koa-router';
|
|
4
|
+
import { WebhookAPI } from './webhook.js';
|
|
5
|
+
import Koa from 'koa';
|
|
6
|
+
import { config } from './config.js';
|
|
7
|
+
import { v4 } from 'uuid';
|
|
8
|
+
import { WebSocketServer, WebSocket } from 'ws';
|
|
9
|
+
|
|
10
|
+
class QQBotClient extends QQBotAPI {
|
|
11
|
+
#events = {};
|
|
12
|
+
#app = null;
|
|
13
|
+
#count = 0;
|
|
14
|
+
#client = [];
|
|
15
|
+
#ws = null;
|
|
16
|
+
/**
|
|
17
|
+
* 设置配置
|
|
18
|
+
* @param opstion
|
|
19
|
+
*/
|
|
20
|
+
constructor(opstion) {
|
|
21
|
+
super();
|
|
22
|
+
config.set('secret', opstion.secret);
|
|
23
|
+
config.set('appId', opstion.appId);
|
|
24
|
+
config.set('token', opstion.token);
|
|
25
|
+
config.set('port', opstion.port);
|
|
26
|
+
config.set('ws', opstion.ws);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 注册事件处理程序
|
|
30
|
+
* @param key 事件名称
|
|
31
|
+
* @param val 事件处理函数
|
|
32
|
+
*/
|
|
33
|
+
on(key, val) {
|
|
34
|
+
if (!this.#events[key]) {
|
|
35
|
+
this.#events[key] = [];
|
|
36
|
+
}
|
|
37
|
+
this.#events[key].push(val);
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
*
|
|
42
|
+
* @param cfg
|
|
43
|
+
* @param conversation
|
|
44
|
+
*/
|
|
45
|
+
async connect() {
|
|
46
|
+
this.#app = new Koa();
|
|
47
|
+
this.#app.use(bodyParser());
|
|
48
|
+
const router = new Router();
|
|
49
|
+
const port = config.get('port');
|
|
50
|
+
const cfg = {
|
|
51
|
+
secret: config.get('secret'),
|
|
52
|
+
port: port ? Number(port) : 17157
|
|
53
|
+
};
|
|
54
|
+
const ntqqWebhook = new WebhookAPI({
|
|
55
|
+
secret: 'YI2mWG0lWH2nYJ4qcOAwiUH4reRE1pdR'
|
|
56
|
+
});
|
|
57
|
+
this.#app.use(async (ctx, next) => {
|
|
58
|
+
let rawData = '';
|
|
59
|
+
ctx.req.on('data', chunk => (rawData += chunk));
|
|
60
|
+
ctx.req.on('end', () => (ctx.request.rawBody = rawData));
|
|
61
|
+
await next();
|
|
62
|
+
});
|
|
63
|
+
// 启动服务
|
|
64
|
+
router.post('/webhook', async (ctx) => {
|
|
65
|
+
const sign = ctx.req.headers['x-signature-ed25519'];
|
|
66
|
+
const timestamp = ctx.req.headers['x-signature-timestamp'];
|
|
67
|
+
const rawBody = ctx.request.rawBody;
|
|
68
|
+
const isValid = ntqqWebhook.validSign(timestamp, rawBody, String(sign));
|
|
69
|
+
if (!isValid) {
|
|
70
|
+
ctx.status = 400;
|
|
71
|
+
ctx.body = { msg: 'invalid signature' };
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const body = ctx.request.body;
|
|
75
|
+
if (body.op == 13) {
|
|
76
|
+
ctx.status = 200;
|
|
77
|
+
ctx.body = {
|
|
78
|
+
// 返回明文 token
|
|
79
|
+
plain_token: body.d.plain_token,
|
|
80
|
+
// 生成签名
|
|
81
|
+
signature: ntqqWebhook.getSign(body.d.event_ts, body.d.plain_token)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
else if (body.op == 0) {
|
|
85
|
+
ctx.status = 204;
|
|
86
|
+
console.log('body', body.d);
|
|
87
|
+
// 根据事件类型,处理事件
|
|
88
|
+
for (const event of this.#events[body.t] || []) {
|
|
89
|
+
event(body.d);
|
|
90
|
+
}
|
|
91
|
+
// 也可以分法到客户端。 发送失败需要处理 或清理调
|
|
92
|
+
for (const client of this.#client) {
|
|
93
|
+
try {
|
|
94
|
+
client.ws.send(JSON.stringify(body.d));
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
console.error('send error', error);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
this.#app.use(router.routes());
|
|
103
|
+
this.#app.use(router.allowedMethods());
|
|
104
|
+
// 启动服务
|
|
105
|
+
const server = this.#app.listen(cfg.port, () => {
|
|
106
|
+
console.log('Server running at http://localhost:' + cfg.port + '/webhook');
|
|
107
|
+
});
|
|
108
|
+
// 创建 WebSocketServer 并监听同一个端口
|
|
109
|
+
const wss = new WebSocketServer({ server: server });
|
|
110
|
+
// 处理客户端连接
|
|
111
|
+
wss.on('connection', ws => {
|
|
112
|
+
const clientId = v4();
|
|
113
|
+
ws['clientId'] = clientId;
|
|
114
|
+
console.log(clientId, 'connection');
|
|
115
|
+
this.#client.push({ id: clientId, ws });
|
|
116
|
+
// 处理消息事件
|
|
117
|
+
ws.on('message', (message) => {
|
|
118
|
+
// 拿到消息
|
|
119
|
+
try {
|
|
120
|
+
const body = JSON.parse(message.toString());
|
|
121
|
+
for (const event of this.#events[body.t] || []) {
|
|
122
|
+
event(body);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch (e) {
|
|
126
|
+
console.error('parse error', e);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
// 处理关闭事件
|
|
130
|
+
ws.on('close', () => {
|
|
131
|
+
console.log(`Client ${clientId} disconnected`);
|
|
132
|
+
this.#client = this.#client.filter(client => client.id !== clientId);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
const reconnect = () => {
|
|
136
|
+
const ws = config.get('ws');
|
|
137
|
+
if (!ws)
|
|
138
|
+
return;
|
|
139
|
+
// 使用了ws服务器
|
|
140
|
+
this.#ws = new WebSocket(ws);
|
|
141
|
+
this.#ws.on('open', () => {
|
|
142
|
+
this.#count = 0;
|
|
143
|
+
console.log('ws connected');
|
|
144
|
+
});
|
|
145
|
+
this.#ws.on('message', data => {
|
|
146
|
+
try {
|
|
147
|
+
// 拿到消息
|
|
148
|
+
const body = JSON.parse(data.toString());
|
|
149
|
+
for (const event of this.#events[body.t] || []) {
|
|
150
|
+
event(body);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
console.error('parse error', e);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
this.#ws.on('close', () => {
|
|
158
|
+
console.log('ws closed');
|
|
159
|
+
// 重连5次,超过5次不再重连
|
|
160
|
+
if (this.#count > 5) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
// 23s 后重连
|
|
164
|
+
setTimeout(() => {
|
|
165
|
+
reconnect();
|
|
166
|
+
}, 23000);
|
|
167
|
+
});
|
|
168
|
+
this.#ws.on('error', () => {
|
|
169
|
+
console.log('ws error ');
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
reconnect();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export { QQBotClient };
|
package/lib/config.js
ADDED
package/lib/from.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { existsSync, createReadStream } from 'fs';
|
|
2
|
+
import { Readable, isReadable } from 'stream';
|
|
3
|
+
import { basename } from 'path';
|
|
4
|
+
import { fileTypeFromBuffer, fileTypeFromStream } from 'file-type';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 创建form
|
|
8
|
+
* @param image
|
|
9
|
+
* @param name
|
|
10
|
+
* @returns
|
|
11
|
+
*/
|
|
12
|
+
async function createPicFrom(image, name = 'image.jpg') {
|
|
13
|
+
let picData;
|
|
14
|
+
// 是 string
|
|
15
|
+
if (typeof image === 'string') {
|
|
16
|
+
if (!existsSync(image))
|
|
17
|
+
return false;
|
|
18
|
+
if (!name)
|
|
19
|
+
name = basename(image);
|
|
20
|
+
picData = createReadStream(image);
|
|
21
|
+
// 是 buffer
|
|
22
|
+
}
|
|
23
|
+
else if (Buffer.isBuffer(image)) {
|
|
24
|
+
const { ext } = await fileTypeFromBuffer(image);
|
|
25
|
+
if (!name)
|
|
26
|
+
name = 'file.' + ext;
|
|
27
|
+
picData = new Readable();
|
|
28
|
+
picData.push(image);
|
|
29
|
+
picData.push(null);
|
|
30
|
+
// 是 文件流
|
|
31
|
+
}
|
|
32
|
+
else if (isReadable(image)) {
|
|
33
|
+
const { ext } = await fileTypeFromStream(image);
|
|
34
|
+
if (!name)
|
|
35
|
+
name = 'file.' + ext;
|
|
36
|
+
picData = image;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return { picData, image, name };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { createPicFrom };
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import { defineBot, getConfig, useUserHashKey, OnProcessor } from 'alemonjs';
|
|
2
|
+
import { QQBotClient } from './client.js';
|
|
3
|
+
|
|
4
|
+
const platform = 'qq-bot';
|
|
5
|
+
var index = defineBot(() => {
|
|
6
|
+
const cfg = getConfig();
|
|
7
|
+
const config = cfg.value['qq-bot'];
|
|
8
|
+
if (!config)
|
|
9
|
+
return;
|
|
10
|
+
const client = new QQBotClient({
|
|
11
|
+
secret: config.secret,
|
|
12
|
+
appId: config.appId,
|
|
13
|
+
token: config.token,
|
|
14
|
+
port: config.port,
|
|
15
|
+
ws: config?.ws
|
|
16
|
+
});
|
|
17
|
+
// 连接
|
|
18
|
+
client.connect();
|
|
19
|
+
// 监听消息
|
|
20
|
+
client.on('GROUP_AT_MESSAGE_CREATE', async (event) => {
|
|
21
|
+
const master_key = config?.master_key ?? [];
|
|
22
|
+
const isMaster = master_key.includes(event.author.id);
|
|
23
|
+
const url = `https://q.qlogo.cn/qqapp/${config.app_id}/${event.author.id}/640`;
|
|
24
|
+
const UserAvatar = {
|
|
25
|
+
toBuffer: async () => {
|
|
26
|
+
const arrayBuffer = await fetch(url).then(res => res.arrayBuffer());
|
|
27
|
+
return Buffer.from(arrayBuffer);
|
|
28
|
+
},
|
|
29
|
+
toBase64: async () => {
|
|
30
|
+
const arrayBuffer = await fetch(url).then(res => res.arrayBuffer());
|
|
31
|
+
return Buffer.from(arrayBuffer).toString('base64');
|
|
32
|
+
},
|
|
33
|
+
toURL: async () => {
|
|
34
|
+
return url;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const UserId = event.author.id;
|
|
38
|
+
const UserKey = useUserHashKey({
|
|
39
|
+
Platform: platform,
|
|
40
|
+
UserId: UserId
|
|
41
|
+
});
|
|
42
|
+
// 定义消
|
|
43
|
+
const e = {
|
|
44
|
+
// 事件类型
|
|
45
|
+
Platform: platform,
|
|
46
|
+
// guild
|
|
47
|
+
GuildId: event.group_id,
|
|
48
|
+
ChannelId: event.group_id,
|
|
49
|
+
// 用户Id
|
|
50
|
+
UserId: event.author.id,
|
|
51
|
+
UserKey,
|
|
52
|
+
UserAvatar: UserAvatar,
|
|
53
|
+
IsMaster: isMaster,
|
|
54
|
+
IsBot: false,
|
|
55
|
+
// 格式化数据
|
|
56
|
+
MessageId: event.id,
|
|
57
|
+
MessageText: event.content?.trim(),
|
|
58
|
+
OpenId: event.author.member_openid,
|
|
59
|
+
CreateAt: Date.now(),
|
|
60
|
+
//
|
|
61
|
+
tag: 'GROUP_AT_MESSAGE_CREATE',
|
|
62
|
+
value: null
|
|
63
|
+
};
|
|
64
|
+
// 当访问的时候获取
|
|
65
|
+
Object.defineProperty(e, 'value', {
|
|
66
|
+
get() {
|
|
67
|
+
return event;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
// 处理消息
|
|
71
|
+
OnProcessor(e, 'message.create');
|
|
72
|
+
});
|
|
73
|
+
client.on('C2C_MESSAGE_CREATE', async (event) => {
|
|
74
|
+
const master_key = config?.master_key ?? [];
|
|
75
|
+
const isMaster = master_key.includes(event.author.id);
|
|
76
|
+
const url = `https://q.qlogo.cn/qqapp/${config.app_id}/${event.author.id}/640`;
|
|
77
|
+
const UserAvatar = {
|
|
78
|
+
toBuffer: async () => {
|
|
79
|
+
const arrayBuffer = await fetch(url).then(res => res.arrayBuffer());
|
|
80
|
+
return Buffer.from(arrayBuffer);
|
|
81
|
+
},
|
|
82
|
+
toBase64: async () => {
|
|
83
|
+
const arrayBuffer = await fetch(url).then(res => res.arrayBuffer());
|
|
84
|
+
return Buffer.from(arrayBuffer).toString('base64');
|
|
85
|
+
},
|
|
86
|
+
toURL: async () => {
|
|
87
|
+
return url;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const UserId = event.author.id;
|
|
91
|
+
const UserKey = useUserHashKey({
|
|
92
|
+
Platform: platform,
|
|
93
|
+
UserId: UserId
|
|
94
|
+
});
|
|
95
|
+
// 定义消
|
|
96
|
+
const e = {
|
|
97
|
+
// 事件类型
|
|
98
|
+
Platform: platform,
|
|
99
|
+
// 用户Id
|
|
100
|
+
UserId: event.author.id,
|
|
101
|
+
UserKey,
|
|
102
|
+
UserAvatar: UserAvatar,
|
|
103
|
+
IsMaster: isMaster,
|
|
104
|
+
IsBot: false,
|
|
105
|
+
// 格式化数据
|
|
106
|
+
MessageId: event.id,
|
|
107
|
+
MessageText: event.content?.trim(),
|
|
108
|
+
CreateAt: Date.now(),
|
|
109
|
+
OpenId: '',
|
|
110
|
+
//
|
|
111
|
+
tag: 'GROUP_AT_MESSAGE_CREATE',
|
|
112
|
+
value: null
|
|
113
|
+
};
|
|
114
|
+
// 当访问的时候获取
|
|
115
|
+
Object.defineProperty(e, 'value', {
|
|
116
|
+
get() {
|
|
117
|
+
return event;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
// 处理消息
|
|
121
|
+
OnProcessor(e, 'private.message.create');
|
|
122
|
+
});
|
|
123
|
+
client.on('DIRECT_MESSAGE_CREATE', async (event) => {
|
|
124
|
+
// 屏蔽其他机器人的消息
|
|
125
|
+
if (event?.author?.bot)
|
|
126
|
+
return;
|
|
127
|
+
const master_key = config?.master_key ?? [];
|
|
128
|
+
const isMaster = master_key.includes(event.author.id);
|
|
129
|
+
let msg = event?.content ?? '';
|
|
130
|
+
const UserAvatar = {
|
|
131
|
+
toBuffer: async () => {
|
|
132
|
+
const arrayBuffer = await fetch(event.author.avatar).then(res => res.arrayBuffer());
|
|
133
|
+
return Buffer.from(arrayBuffer);
|
|
134
|
+
},
|
|
135
|
+
toBase64: async () => {
|
|
136
|
+
const arrayBuffer = await fetch(event?.author?.avatar).then(res => res.arrayBuffer());
|
|
137
|
+
return Buffer.from(arrayBuffer).toString('base64');
|
|
138
|
+
},
|
|
139
|
+
toURL: async () => {
|
|
140
|
+
return event?.author?.avatar;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
const UserId = event.author.id;
|
|
144
|
+
const UserKey = useUserHashKey({
|
|
145
|
+
Platform: platform,
|
|
146
|
+
UserId: UserId
|
|
147
|
+
});
|
|
148
|
+
// 定义消
|
|
149
|
+
const e = {
|
|
150
|
+
// 事件类型
|
|
151
|
+
Platform: platform,
|
|
152
|
+
//
|
|
153
|
+
GuildId: event.guild_id,
|
|
154
|
+
ChannelId: event.channel_id,
|
|
155
|
+
// 用户Id
|
|
156
|
+
UserId: event?.author?.id ?? '',
|
|
157
|
+
UserKey,
|
|
158
|
+
UserName: event?.author?.username ?? '',
|
|
159
|
+
UserAvatar: UserAvatar,
|
|
160
|
+
IsMaster: isMaster,
|
|
161
|
+
IsBot: event.author?.bot,
|
|
162
|
+
// message
|
|
163
|
+
MessageId: event.id,
|
|
164
|
+
MessageText: msg,
|
|
165
|
+
OpenId: event.guild_id,
|
|
166
|
+
CreateAt: Date.now(),
|
|
167
|
+
//
|
|
168
|
+
tag: 'AT_MESSAGE_CREATE',
|
|
169
|
+
//
|
|
170
|
+
value: null
|
|
171
|
+
};
|
|
172
|
+
// 当访问的时候获取
|
|
173
|
+
Object.defineProperty(e, 'value', {
|
|
174
|
+
get() {
|
|
175
|
+
return event;
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
// 处理消息
|
|
179
|
+
OnProcessor(e, 'private.message.create');
|
|
180
|
+
});
|
|
181
|
+
// 监听消息
|
|
182
|
+
client.on('AT_MESSAGE_CREATE', async (event) => {
|
|
183
|
+
// 屏蔽其他机器人的消息
|
|
184
|
+
if (event?.author?.bot)
|
|
185
|
+
return;
|
|
186
|
+
const master_key = config?.master_key ?? [];
|
|
187
|
+
const isMaster = master_key.includes(event.author.id);
|
|
188
|
+
let msg = getMessageContent(event);
|
|
189
|
+
const UserAvatar = {
|
|
190
|
+
toBuffer: async () => {
|
|
191
|
+
const arrayBuffer = await fetch(event.author.avatar).then(res => res.arrayBuffer());
|
|
192
|
+
return Buffer.from(arrayBuffer);
|
|
193
|
+
},
|
|
194
|
+
toBase64: async () => {
|
|
195
|
+
const arrayBuffer = await fetch(event?.author?.avatar).then(res => res.arrayBuffer());
|
|
196
|
+
return Buffer.from(arrayBuffer).toString('base64');
|
|
197
|
+
},
|
|
198
|
+
toURL: async () => {
|
|
199
|
+
return event?.author?.avatar;
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
const UserId = event.author.id;
|
|
203
|
+
const UserKey = useUserHashKey({
|
|
204
|
+
Platform: platform,
|
|
205
|
+
UserId: UserId
|
|
206
|
+
});
|
|
207
|
+
// 定义消
|
|
208
|
+
const e = {
|
|
209
|
+
// 事件类型
|
|
210
|
+
Platform: platform,
|
|
211
|
+
GuildId: event.guild_id,
|
|
212
|
+
ChannelId: event.channel_id,
|
|
213
|
+
IsMaster: isMaster,
|
|
214
|
+
// 用户Id
|
|
215
|
+
UserId: event?.author?.id ?? '',
|
|
216
|
+
UserKey,
|
|
217
|
+
UserName: event?.author?.username ?? '',
|
|
218
|
+
UserAvatar: UserAvatar,
|
|
219
|
+
IsBot: event.author?.bot,
|
|
220
|
+
// message
|
|
221
|
+
MessageId: event.id,
|
|
222
|
+
MessageText: msg,
|
|
223
|
+
OpenId: event.guild_id,
|
|
224
|
+
CreateAt: Date.now(),
|
|
225
|
+
//
|
|
226
|
+
tag: 'AT_MESSAGE_CREATE',
|
|
227
|
+
//
|
|
228
|
+
value: null
|
|
229
|
+
};
|
|
230
|
+
// 当访问的时候获取
|
|
231
|
+
Object.defineProperty(e, 'value', {
|
|
232
|
+
get() {
|
|
233
|
+
return event;
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
// 处理消息
|
|
237
|
+
OnProcessor(e, 'message.create');
|
|
238
|
+
});
|
|
239
|
+
/**
|
|
240
|
+
*
|
|
241
|
+
* @param event
|
|
242
|
+
* @returns
|
|
243
|
+
*/
|
|
244
|
+
const getMessageContent = event => {
|
|
245
|
+
let msg = event?.content ?? '';
|
|
246
|
+
// 艾特消息处理
|
|
247
|
+
const at_users = [];
|
|
248
|
+
if (event.mentions) {
|
|
249
|
+
// 去掉@ 转为纯消息
|
|
250
|
+
for (const item of event.mentions) {
|
|
251
|
+
at_users.push({
|
|
252
|
+
id: item.id
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
// 循环删除文本中的at信息并去除前后空格
|
|
256
|
+
at_users.forEach(item => {
|
|
257
|
+
msg = msg.replace(`<@!${item.id}>`, '').trim();
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
return msg;
|
|
261
|
+
};
|
|
262
|
+
// 私域 -
|
|
263
|
+
client.on('MESSAGE_CREATE', async (event) => {
|
|
264
|
+
// 屏蔽其他机器人的消息
|
|
265
|
+
if (event.author?.bot)
|
|
266
|
+
return;
|
|
267
|
+
// 撤回消息
|
|
268
|
+
if (new RegExp(/DELETE$/).test(event.eventType))
|
|
269
|
+
return;
|
|
270
|
+
const master_key = config?.master_key ?? [];
|
|
271
|
+
const UserId = event.author.id;
|
|
272
|
+
const isMaster = master_key.includes(UserId);
|
|
273
|
+
const msg = getMessageContent(event);
|
|
274
|
+
const UserAvatar = {
|
|
275
|
+
toBuffer: async () => {
|
|
276
|
+
const arrayBuffer = await fetch(event.author.avatar).then(res => res.arrayBuffer());
|
|
277
|
+
return Buffer.from(arrayBuffer);
|
|
278
|
+
},
|
|
279
|
+
toBase64: async () => {
|
|
280
|
+
const arrayBuffer = await fetch(event?.author?.avatar).then(res => res.arrayBuffer());
|
|
281
|
+
return Buffer.from(arrayBuffer).toString('base64');
|
|
282
|
+
},
|
|
283
|
+
toURL: async () => {
|
|
284
|
+
return event?.author?.avatar;
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
const UserKey = useUserHashKey({
|
|
288
|
+
Platform: platform,
|
|
289
|
+
UserId: UserId
|
|
290
|
+
});
|
|
291
|
+
// 定义消
|
|
292
|
+
const e = {
|
|
293
|
+
// 事件类型
|
|
294
|
+
Platform: platform,
|
|
295
|
+
//
|
|
296
|
+
GuildId: event.guild_id,
|
|
297
|
+
ChannelId: event.channel_id,
|
|
298
|
+
UserId: event?.author?.id ?? '',
|
|
299
|
+
UserKey,
|
|
300
|
+
UserName: event?.author?.username ?? '',
|
|
301
|
+
UserAvatar: UserAvatar,
|
|
302
|
+
IsMaster: isMaster,
|
|
303
|
+
IsBot: false,
|
|
304
|
+
// message
|
|
305
|
+
MessageId: event.id,
|
|
306
|
+
MessageText: msg,
|
|
307
|
+
OpenId: event.guild_id,
|
|
308
|
+
CreateAt: Date.now(),
|
|
309
|
+
//
|
|
310
|
+
tag: 'AT_MESSAGE_CREATE',
|
|
311
|
+
value: null
|
|
312
|
+
};
|
|
313
|
+
// 当访问的时候获取
|
|
314
|
+
Object.defineProperty(e, 'value', {
|
|
315
|
+
get() {
|
|
316
|
+
return event;
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
// 处理消息
|
|
320
|
+
OnProcessor(e, 'message.create');
|
|
321
|
+
});
|
|
322
|
+
// FRIEND_ADD
|
|
323
|
+
global.client = client;
|
|
324
|
+
return {
|
|
325
|
+
api: {
|
|
326
|
+
use: {
|
|
327
|
+
send: (event, val) => {
|
|
328
|
+
if (val.length < 0)
|
|
329
|
+
return Promise.all([]);
|
|
330
|
+
const content = val
|
|
331
|
+
.filter(item => item.type == 'Link' || item.type == 'Mention' || item.type == 'Text')
|
|
332
|
+
.map(item => {
|
|
333
|
+
if (item.type == 'Link') {
|
|
334
|
+
return `[${item.options?.title ?? item.value}](${item.value})`;
|
|
335
|
+
}
|
|
336
|
+
else if (item.type == 'Mention') {
|
|
337
|
+
if (item.value == 'everyone' ||
|
|
338
|
+
item.value == 'all' ||
|
|
339
|
+
item.value == '' ||
|
|
340
|
+
typeof item.value != 'string') {
|
|
341
|
+
return ``;
|
|
342
|
+
}
|
|
343
|
+
if (item.options?.belong == 'user') {
|
|
344
|
+
return `<@${item.value}>`;
|
|
345
|
+
}
|
|
346
|
+
return '';
|
|
347
|
+
// return `<qqbot-at-everyone />`
|
|
348
|
+
}
|
|
349
|
+
else if (item.type == 'Text') {
|
|
350
|
+
return item.value;
|
|
351
|
+
}
|
|
352
|
+
})
|
|
353
|
+
.join('');
|
|
354
|
+
if (content) {
|
|
355
|
+
return Promise.all([content].map(item => client.groupOpenMessages(event.GuildId, {
|
|
356
|
+
content: item,
|
|
357
|
+
msg_id: event.MessageId,
|
|
358
|
+
msg_type: 0,
|
|
359
|
+
msg_seq: client.getMessageSeq(event.MessageId)
|
|
360
|
+
})));
|
|
361
|
+
}
|
|
362
|
+
const images = val.filter(item => item.type == 'Image').map(item => item.value);
|
|
363
|
+
if (images) {
|
|
364
|
+
return Promise.all(images.map(async (msg) => {
|
|
365
|
+
const file_info = await client
|
|
366
|
+
.postRichMediaByGroup(event.GuildId, {
|
|
367
|
+
file_type: 1,
|
|
368
|
+
file_data: msg.toString('base64')
|
|
369
|
+
})
|
|
370
|
+
.then(res => res?.file_info);
|
|
371
|
+
if (!file_info)
|
|
372
|
+
return Promise.resolve(null);
|
|
373
|
+
return client.groupOpenMessages(event.GuildId, {
|
|
374
|
+
content: '',
|
|
375
|
+
media: {
|
|
376
|
+
file_info
|
|
377
|
+
},
|
|
378
|
+
msg_id: event.MessageId,
|
|
379
|
+
msg_type: 7,
|
|
380
|
+
msg_seq: client.getMessageSeq(event.MessageId)
|
|
381
|
+
});
|
|
382
|
+
}));
|
|
383
|
+
}
|
|
384
|
+
return Promise.all([]);
|
|
385
|
+
},
|
|
386
|
+
mention: async () => {
|
|
387
|
+
// const event = e.value
|
|
388
|
+
const Metions = [];
|
|
389
|
+
return Metions;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
export { index as default, platform };
|
package/lib/webhook.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ed25519 } from '@noble/curves/ed25519';
|
|
2
|
+
|
|
3
|
+
class WebhookAPI {
|
|
4
|
+
config;
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.config = config;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* 验证签名
|
|
10
|
+
* @param ts
|
|
11
|
+
* @param body
|
|
12
|
+
* @param sign
|
|
13
|
+
* @returns
|
|
14
|
+
*/
|
|
15
|
+
validSign(ts, body, sign) {
|
|
16
|
+
const { publicKey } = this.getKey();
|
|
17
|
+
const sig = Buffer.isBuffer(sign) ? sign : Buffer.from(sign, 'hex');
|
|
18
|
+
const httpBody = Buffer.from(body);
|
|
19
|
+
const msg = Buffer.from(ts + httpBody);
|
|
20
|
+
return ed25519.verify(sig, msg, publicKey);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 生成签名
|
|
24
|
+
* @param eventTs
|
|
25
|
+
* @param plainToken
|
|
26
|
+
* @returns
|
|
27
|
+
*/
|
|
28
|
+
getSign(eventTs, plainToken) {
|
|
29
|
+
const { privateKey } = this.getKey();
|
|
30
|
+
const msg = Buffer.from(eventTs + plainToken);
|
|
31
|
+
const signature = Buffer.from(ed25519.sign(msg, privateKey)).toString('hex');
|
|
32
|
+
return signature;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 获取 key
|
|
36
|
+
* @returns
|
|
37
|
+
*/
|
|
38
|
+
getKey() {
|
|
39
|
+
let seed = this.config.secret;
|
|
40
|
+
if (!seed)
|
|
41
|
+
throw new Error("secret not set, can't calc ed25519 key");
|
|
42
|
+
while (seed.length < 32)
|
|
43
|
+
seed = seed.repeat(2); // Ed25519 的种子大小是 32 字节
|
|
44
|
+
seed = seed.slice(0, 32); // 修剪到 32 字节
|
|
45
|
+
const privateKey = Buffer.from(seed);
|
|
46
|
+
return {
|
|
47
|
+
privateKey,
|
|
48
|
+
publicKey: ed25519.getPublicKey(privateKey)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { WebhookAPI };
|