@alemonjs/qq-bot 0.0.3 → 0.0.4

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 CHANGED
@@ -26,10 +26,11 @@ qq-bot:
26
26
  qq-group-bot:
27
27
  master_key:
28
28
  - ''
29
- # 默认(请使用nginx进行代理)
29
+ # 默认 推荐nginx进行代理 http://localhost:17157/webhook
30
30
  port: 17157
31
- # 如果在服务器也启动了机器人,可以进行互相调用
32
- ws: 'ws://...'
31
+ # 当配置ws的时候,会连接另一台hook机器人的消息。不会再启动本地端口。
32
+ # 推荐nginx进行代理 http://localhost:17157/
33
+ ws: 'wss://xxxx/ws'
33
34
  # 频道沙盒
34
35
  sandbox: false
35
36
  ```
package/lib/client.js CHANGED
@@ -1,218 +1,210 @@
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';
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
9
 
10
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
- if (opstion.secret)
23
- config.set('secret', opstion.secret);
24
- if (opstion.app_id)
25
- config.set('app_id', opstion.app_id);
26
- if (opstion.token)
27
- config.set('token', opstion.token);
28
- if (opstion.port)
29
- config.set('port', opstion.port);
30
- if (opstion.ws)
31
- config.set('ws', opstion.ws);
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
+ if (opstion.secret) config.set('secret', opstion.secret)
23
+ if (opstion.app_id) config.set('app_id', opstion.app_id)
24
+ if (opstion.token) config.set('token', opstion.token)
25
+ if (opstion.port) config.set('port', opstion.port)
26
+ if (opstion.ws) 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] = []
32
36
  }
33
- /**
34
- * 注册事件处理程序
35
- * @param key 事件名称
36
- * @param val 事件处理函数
37
- */
38
- on(key, val) {
39
- if (!this.#events[key]) {
40
- this.#events[key] = [];
41
- }
42
- this.#events[key].push(val);
43
- return this;
44
- }
45
- /**
46
- * 定时鉴权
47
- * @param cfg
48
- * @returns
49
- */
50
- async #setTimeoutBotConfig() {
51
- const callBack = async () => {
52
- const app_id = config.get('app_id');
53
- if (!app_id)
54
- return;
55
- const secret = config.get('secret');
56
- if (!secret)
57
- return;
58
- // 发送请求
59
- const data = await this.getAuthentication(app_id, secret).then(res => res.data);
60
- config.set('access_token', data.access_token);
61
- console.info('refresh', data.expires_in, 's');
62
- setTimeout(callBack, data.expires_in * 1000);
63
- };
64
- await callBack();
37
+ this.#events[key].push(val)
38
+ return this
39
+ }
40
+ /**
41
+ * 定时鉴权
42
+ * @param cfg
43
+ * @returns
44
+ */
45
+ async #setTimeoutBotConfig() {
46
+ const callBack = async () => {
47
+ const app_id = config.get('app_id')
48
+ if (!app_id) return
49
+ const secret = config.get('secret')
50
+ if (!secret) return
51
+ // 发送请求
52
+ const data = await this.getAuthentication(app_id, secret).then(res => res.data)
53
+ config.set('access_token', data.access_token)
54
+ console.info('refresh', data.expires_in, 's')
55
+ setTimeout(callBack, data.expires_in * 1000)
65
56
  }
66
- /**
67
- *
68
- * @param cfg
69
- * @param conversation
70
- */
71
- connect() {
72
- try {
73
- this.#setTimeoutBotConfig();
74
- this.#app = new Koa();
75
- this.#app.use(bodyParser());
76
- const router = new Router();
77
- const port = config.get('port');
78
- const secret = config.get('secret');
79
- const cfg = {
80
- secret: secret ?? '',
81
- port: port ? Number(port) : 17157
82
- };
83
- const ntqqWebhook = new WebhookAPI({
84
- secret: cfg.secret
85
- });
86
- this.#app.use(async (ctx, next) => {
87
- let rawData = '';
88
- ctx.req.on('data', chunk => (rawData += chunk));
89
- ctx.req.on('end', () => (ctx.request.rawBody = rawData));
90
- await next();
91
- });
92
- // 启动服务
93
- router.post('/webhook', async (ctx) => {
94
- const sign = ctx.req.headers['x-signature-ed25519'];
95
- const timestamp = ctx.req.headers['x-signature-timestamp'];
96
- const rawBody = ctx.request.rawBody;
97
- const isValid = ntqqWebhook.validSign(timestamp, rawBody, String(sign));
98
- if (!isValid) {
99
- ctx.status = 400;
100
- ctx.body = { msg: 'invalid signature' };
101
- return;
102
- }
103
- const body = ctx.request.body;
104
- if (body.op == 13) {
105
- ctx.status = 200;
106
- ctx.body = {
107
- // 返回明文 token
108
- plain_token: body.d.plain_token,
109
- // 生成签名
110
- signature: ntqqWebhook.getSign(body.d.event_ts, body.d.plain_token)
111
- };
112
- }
113
- else if (body.op == 0) {
114
- ctx.status = 204;
115
- // 根据事件类型,处理事件
116
- for (const event of this.#events[body.t] || []) {
117
- event(body.d);
118
- }
119
- // 也可以分法到客户端。 发送失败需要处理 或清理调
120
- for (const client of this.#client) {
121
- try {
122
- client.ws.send(JSON.stringify(body.d));
123
- }
124
- catch (e) {
125
- this.#error(e);
126
- }
127
- }
128
- }
129
- });
130
- this.#app.use(router.routes());
131
- this.#app.use(router.allowedMethods());
132
- // 启动服务
133
- const server = this.#app.listen(cfg.port, () => {
134
- console.log('Server running at http://localhost:' + cfg.port + '/webhook');
135
- });
136
- // 创建 WebSocketServer 并监听同一个端口
137
- const wss = new WebSocketServer({ server: server });
138
- // 处理客户端连接
139
- wss.on('connection', ws => {
140
- const clientId = v4();
141
- ws['clientId'] = clientId;
142
- console.log(clientId, 'connection');
143
- this.#client.push({ id: clientId, ws });
144
- // 处理消息事件
145
- ws.on('message', (message) => {
146
- // 拿到消息
147
- try {
148
- const body = JSON.parse(message.toString());
149
- for (const event of this.#events[body.t] || []) {
150
- event(body);
151
- }
152
- }
153
- catch (e) {
154
- this.#error(e);
155
- }
156
- });
157
- // 处理关闭事件
158
- ws.on('close', () => {
159
- console.log(`Client ${clientId} disconnected`);
160
- this.#client = this.#client.filter(client => client.id !== clientId);
161
- });
162
- });
163
- const reconnect = () => {
164
- const ws = config.get('ws');
165
- if (!ws)
166
- return;
167
- // 使用了ws服务器
168
- this.#ws = new WebSocket(ws);
169
- this.#ws.on('open', () => {
170
- this.#count = 0;
171
- console.log('ws connected');
172
- });
173
- this.#ws.on('message', data => {
174
- try {
175
- // 拿到消息
176
- const body = JSON.parse(data.toString());
177
- for (const event of this.#events[body.t] || []) {
178
- event(body);
179
- }
180
- }
181
- catch (e) {
182
- this.#error(e);
183
- }
184
- });
185
- this.#ws.on('close', () => {
186
- console.log('ws closed');
187
- // 重连5次,超过5次不再重连
188
- if (this.#count > 5)
189
- return;
190
- // 23s 后重连
191
- setTimeout(() => {
192
- reconnect();
193
- }, 23000);
194
- });
195
- this.#ws.on('error', e => {
196
- this.#error(e);
197
- });
198
- };
199
- reconnect();
200
- }
201
- catch (e) {
202
- this.#error(e);
57
+ await callBack()
58
+ }
59
+ /**
60
+ *
61
+ * @param cfg
62
+ * @param conversation
63
+ */
64
+ connect() {
65
+ try {
66
+ const ws = config.get('ws')
67
+ if (!ws) {
68
+ this.#setTimeoutBotConfig()
69
+ this.#app = new Koa()
70
+ this.#app.use(bodyParser())
71
+ const router = new Router()
72
+ const port = config.get('port')
73
+ const secret = config.get('secret')
74
+ const cfg = {
75
+ secret: secret ?? '',
76
+ port: port ? Number(port) : 17157
203
77
  }
204
- }
205
- /**
206
- *
207
- * @param error
208
- */
209
- #error(error) {
210
- if (this.#events['ERROR']) {
211
- for (const event of this.#events['ERROR'] || []) {
212
- event(error);
78
+ const ntqqWebhook = new WebhookAPI({
79
+ secret: cfg.secret
80
+ })
81
+ this.#app.use(async (ctx, next) => {
82
+ let rawData = ''
83
+ ctx.req.on('data', chunk => (rawData += chunk))
84
+ ctx.req.on('end', () => (ctx.request.rawBody = rawData))
85
+ await next()
86
+ })
87
+ // 启动服务
88
+ router.post('/webhook', async ctx => {
89
+ const sign = ctx.req.headers['x-signature-ed25519']
90
+ const timestamp = ctx.req.headers['x-signature-timestamp']
91
+ const rawBody = ctx.request.rawBody
92
+ const isValid = ntqqWebhook.validSign(timestamp, rawBody, String(sign))
93
+ if (!isValid) {
94
+ ctx.status = 400
95
+ ctx.body = { msg: 'invalid signature' }
96
+ return
97
+ }
98
+ const body = ctx.request.body
99
+ if (body.op == 13) {
100
+ ctx.status = 200
101
+ ctx.body = {
102
+ // 返回明文 token
103
+ plain_token: body.d.plain_token,
104
+ // 生成签名
105
+ signature: ntqqWebhook.getSign(body.d.event_ts, body.d.plain_token)
213
106
  }
214
- }
107
+ } else if (body.op == 0) {
108
+ ctx.status = 204
109
+ // 根据事件类型,处理事件
110
+ for (const event of this.#events[body.t] || []) {
111
+ event(body.d)
112
+ }
113
+ const access_token = config.get('access_token')
114
+ // 也可以分法到客户端。 发送失败需要处理 或清理调
115
+ for (const client of this.#client) {
116
+ try {
117
+ if (access_token) body['access_token'] = access_token
118
+ client.ws.send(JSON.stringify(body))
119
+ } catch (e) {
120
+ this.#error(e)
121
+ }
122
+ }
123
+ }
124
+ })
125
+ this.#app.use(router.routes())
126
+ this.#app.use(router.allowedMethods())
127
+ // 启动服务
128
+ const server = this.#app.listen(cfg.port, () => {
129
+ console.log('Server running at http://localhost:' + cfg.port + '/webhook')
130
+ })
131
+ // 创建 WebSocketServer 并监听同一个端口
132
+ const wss = new WebSocketServer({ server: server })
133
+ // 处理客户端连接
134
+ wss.on('connection', ws => {
135
+ const clientId = v4()
136
+ ws['clientId'] = clientId
137
+ console.log(clientId, 'connection')
138
+ this.#client.push({ id: clientId, ws })
139
+ // 处理消息事件
140
+ ws.on('message', message => {
141
+ // 拿到消息
142
+ try {
143
+ const body = JSON.parse(message.toString())
144
+ for (const event of this.#events[body.t] || []) {
145
+ event(body.d)
146
+ }
147
+ } catch (e) {
148
+ this.#error(e)
149
+ }
150
+ })
151
+ // 处理关闭事件
152
+ ws.on('close', () => {
153
+ console.log(`${clientId} disconnected`)
154
+ this.#client = this.#client.filter(client => client.id !== clientId)
155
+ })
156
+ })
157
+ }
158
+ const reconnect = () => {
159
+ if (!ws) return
160
+ // 使用了ws服务器
161
+ this.#ws = new WebSocket(ws)
162
+ this.#ws.on('open', () => {
163
+ this.#count = 0
164
+ console.log('ws connected')
165
+ })
166
+ this.#ws.on('message', data => {
167
+ try {
168
+ // 拿到消息
169
+ const body = JSON.parse(data.toString())
170
+ const access_token = body['access_token']
171
+ if (access_token) config.set('access_token', access_token)
172
+ for (const event of this.#events[body.t] || []) {
173
+ event(body.d)
174
+ }
175
+ } catch (e) {
176
+ this.#error(e)
177
+ }
178
+ })
179
+ this.#ws.on('close', () => {
180
+ console.log('ws closed')
181
+ // 重连5次,超过5次不再重连
182
+ if (this.#count > 5) return
183
+ // 23s 后重连
184
+ setTimeout(() => {
185
+ reconnect()
186
+ }, 23000)
187
+ })
188
+ this.#ws.on('error', e => {
189
+ this.#error(e)
190
+ })
191
+ }
192
+ reconnect()
193
+ } catch (e) {
194
+ this.#error(e)
195
+ }
196
+ }
197
+ /**
198
+ *
199
+ * @param error
200
+ */
201
+ #error(error) {
202
+ if (this.#events['ERROR']) {
203
+ for (const event of this.#events['ERROR'] || []) {
204
+ event(error)
205
+ }
215
206
  }
207
+ }
216
208
  }
217
209
 
218
- export { QQBotClient };
210
+ export { QQBotClient }
package/lib/index.js CHANGED
@@ -1,6 +1,14 @@
1
1
  import { defineBot, getConfigValue, useUserHashKey, OnProcessor } from 'alemonjs';
2
2
  import { QQBotClient } from './client.js';
3
3
 
4
+ const client = new Proxy({}, {
5
+ get: (_, prop) => {
6
+ if (prop in global.client) {
7
+ return global.client[prop];
8
+ }
9
+ return undefined;
10
+ }
11
+ });
4
12
  const platform = 'qq-bot';
5
13
  var index = defineBot(() => {
6
14
  const value = getConfigValue();
@@ -319,7 +327,6 @@ var index = defineBot(() => {
319
327
  client.on('ERROR', console.error);
320
328
  // FRIEND_ADD
321
329
  global.client = client;
322
- //
323
330
  return {
324
331
  api: {
325
332
  use: {
@@ -461,4 +468,4 @@ var index = defineBot(() => {
461
468
  };
462
469
  });
463
470
 
464
- export { index as default, platform };
471
+ export { client, index as default, platform };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alemonjs/qq-bot",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "qq-bot",
5
5
  "author": "lemonade",
6
6
  "license": "MIT",