wechaty-web-panel 0.2.12

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.
Files changed (56) hide show
  1. package/.eslintignore +4 -0
  2. package/.eslintrc.js +14 -0
  3. package/.idea/inspectionProfiles/Project_Default.xml +6 -0
  4. package/.idea/misc.xml +6 -0
  5. package/.idea/modules.xml +8 -0
  6. package/.idea/vcs.xml +6 -0
  7. package/.idea/wechaty-web-panel.iml +12 -0
  8. package/.prettierrc +8 -0
  9. package/LICENSE +21 -0
  10. package/README.md +175 -0
  11. package/doc/img/async.png +0 -0
  12. package/doc/img/event.png +0 -0
  13. package/doc/img/everyday.png +0 -0
  14. package/doc/img/func.jpeg +0 -0
  15. package/doc/img/group.jpeg +0 -0
  16. package/doc/img/index.png +0 -0
  17. package/doc/img/material.png +0 -0
  18. package/doc/img/news.jpeg +0 -0
  19. package/doc/img/qrcode-s.png +0 -0
  20. package/doc/img/qrcode.png +0 -0
  21. package/doc/img/roomasync.png +0 -0
  22. package/doc/img/schedule.png +0 -0
  23. package/doc/img/user-center.png +0 -0
  24. package/package.json +67 -0
  25. package/src/common/aiDb.js +23 -0
  26. package/src/common/configDb.js +86 -0
  27. package/src/common/index.js +321 -0
  28. package/src/common/reply.js +24 -0
  29. package/src/common/roomAvatarDb.js +24 -0
  30. package/src/common/userDb.js +24 -0
  31. package/src/handlers/on-error.js +13 -0
  32. package/src/handlers/on-friend.js +45 -0
  33. package/src/handlers/on-heartbeat.js +12 -0
  34. package/src/handlers/on-login.js +35 -0
  35. package/src/handlers/on-logout.js +11 -0
  36. package/src/handlers/on-message.js +127 -0
  37. package/src/handlers/on-ready.js +18 -0
  38. package/src/handlers/on-roomjoin.js +36 -0
  39. package/src/handlers/on-roomleave.js +6 -0
  40. package/src/handlers/on-roomtopic.js +5 -0
  41. package/src/handlers/on-scan.js +16 -0
  42. package/src/index.js +45 -0
  43. package/src/lib/index.js +816 -0
  44. package/src/lib/nedb.js +87 -0
  45. package/src/proxy/aibotk.js +351 -0
  46. package/src/proxy/api.js +582 -0
  47. package/src/proxy/config.js +9 -0
  48. package/src/proxy/mqtt.js +91 -0
  49. package/src/proxy/superagent.js +117 -0
  50. package/src/proxy/tencent.js +40 -0
  51. package/src/service/event-dispatch-service.js +147 -0
  52. package/src/service/msg-filter-service.js +76 -0
  53. package/src/service/msg-filters.js +175 -0
  54. package/src/service/room-async-service.js +395 -0
  55. package/src/task/index.js +233 -0
  56. package/verpub.config.js +6 -0
@@ -0,0 +1,816 @@
1
+ const Crypto = require('crypto')
2
+ const schedule = require('node-schedule')
3
+ const fs = require('fs')
4
+ const { MCanvas, MImage } = require('mcanvas')
5
+ const { log } = require('wechaty')
6
+ const { addRoom, getRoom } = require('../common/roomAvatarDb')
7
+
8
+ /**
9
+ * 设置定时器
10
+ * @param {*} date 日期
11
+ * @param {*} callback 回调
12
+ */
13
+ //其他规则见 https://www.npmjs.com/package/node-schedule
14
+ // 规则参数讲解 *代表通配符
15
+ //
16
+ // * * * * * *
17
+ // ┬ ┬ ┬ ┬ ┬ ┬
18
+ // │ │ │ │ │ |
19
+ // │ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun)
20
+ // │ │ │ │ └───── month (1 - 12)
21
+ // │ │ │ └────────── day of month (1 - 31)
22
+ // │ │ └─────────────── hour (0 - 23)
23
+ // │ └──────────────────── minute (0 - 59)
24
+ // └───────────────────────── second (0 - 59, OPTIONAL)
25
+
26
+ // 每分钟的第30秒触发: '30 * * * * *'
27
+ //
28
+ // 每小时的1分30秒触发 :'30 1 * * * *'
29
+ //
30
+ // 每天的凌晨1点1分30秒触发 :'30 1 1 * * *'
31
+ //
32
+ // 每月的1日1点1分30秒触发 :'30 1 1 1 * *'
33
+ //
34
+ // 每周1的1点1分30秒触发 :'30 1 1 * * 1'
35
+
36
+ function setLocalSchedule(date, callback, name) {
37
+ if (name) {
38
+ schedule.scheduleJob(name, { rule: date, tz: 'Asia/Shanghai' }, callback)
39
+ } else {
40
+ schedule.scheduleJob({ rule: date, tz: 'Asia/Shanghai' }, callback)
41
+ }
42
+ }
43
+
44
+ // 取消任务
45
+ function cancelLocalSchedule(name) {
46
+ schedule.cancelJob(name)
47
+ }
48
+
49
+ // 取消指定任务
50
+ function cancelAllSchedule(type) {
51
+ for (let i in schedule.scheduledJobs) {
52
+ if (i.includes(type)) {
53
+ cancelLocalSchedule(i)
54
+ }
55
+ }
56
+ }
57
+
58
+ /**
59
+ * 获取所有定时任务的job名
60
+ *
61
+ */
62
+ function getAllSchedule() {
63
+ for (let i in schedule.scheduledJobs) {
64
+ console.log(i)
65
+ }
66
+ }
67
+
68
+ /**
69
+ * 延时函数
70
+ * @param {*} ms 毫秒
71
+ */
72
+ async function delay(ms) {
73
+ return new Promise((resolve) => setTimeout(resolve, ms))
74
+ }
75
+
76
+ /**
77
+ * 读取文件
78
+ */
79
+ const loadFile = {
80
+ data: null,
81
+ mtime: '',
82
+ fetch(file) {
83
+ try {
84
+ let mtime = fs.statSync(file).mtime
85
+ if (!this.data || mtime - this.mtime !== 0) {
86
+ console.log('Reload task file: ' + mtime)
87
+ this.data = JSON.parse(fs.readFileSync(file))
88
+ this.mtime = +mtime
89
+ }
90
+ } catch (e) {
91
+ console.log(e)
92
+ }
93
+ return this.data
94
+ },
95
+ }
96
+
97
+ /**
98
+ * 获取周几
99
+ * @param {*} date 日期
100
+ */
101
+ function getDay(date) {
102
+ var date2 = new Date()
103
+ var date1 = new Date(date)
104
+ var iDays = parseInt(Math.abs(date2.getTime() - date1.getTime()) / 1000 / 60 / 60 / 24)
105
+ return iDays
106
+ }
107
+
108
+ /**
109
+ * 格式化日期
110
+ * @param {*} date
111
+ * @returns 例:2019-9-10 13:13:04 星期一
112
+ */
113
+ function formatDate(date) {
114
+ var tempDate = new Date(date)
115
+ var year = tempDate.getFullYear()
116
+ var month = tempDate.getMonth() + 1
117
+ var day = tempDate.getDate()
118
+ var hour = tempDate.getHours()
119
+ var min = tempDate.getMinutes()
120
+ var second = tempDate.getSeconds()
121
+ var week = tempDate.getDay()
122
+ var str = ''
123
+ if (week === 0) {
124
+ str = '星期日'
125
+ } else if (week === 1) {
126
+ str = '星期一'
127
+ } else if (week === 2) {
128
+ str = '星期二'
129
+ } else if (week === 3) {
130
+ str = '星期三'
131
+ } else if (week === 4) {
132
+ str = '星期四'
133
+ } else if (week === 5) {
134
+ str = '星期五'
135
+ } else if (week === 6) {
136
+ str = '星期六'
137
+ }
138
+ if (hour < 10) {
139
+ hour = '0' + hour
140
+ }
141
+ if (min < 10) {
142
+ min = '0' + min
143
+ }
144
+ if (second < 10) {
145
+ second = '0' + second
146
+ }
147
+ return year + '-' + month + '-' + day + ' ' + hour + ':' + min + ' ' + str
148
+ }
149
+
150
+ /**
151
+ * 获取今天日期
152
+ * @returns 2019-7-19
153
+ */
154
+ function getToday() {
155
+ const date = new Date()
156
+ let year = date.getFullYear()
157
+ let month = date.getMonth() + 1
158
+ let day = date.getDate()
159
+ return year + '-' + month + '-' + day + ' '
160
+ }
161
+
162
+ /**
163
+ * 转换定时日期格式
164
+ * @param {*} time
165
+ * @returns 0 12 15 * * * 每天下午3点12分
166
+ */
167
+ function convertTime(time) {
168
+ let array = time.split(':')
169
+ return '0 ' + array[1] + ' ' + array[0] + ' * * *'
170
+ }
171
+
172
+ //
173
+ /**
174
+ * 判断日期时间格式是否正确
175
+ * @param {*} str 日期
176
+ * @returns {boolean}
177
+ */
178
+ function isRealDate(str) {
179
+ var reg = /^(\d+)-(\d{1,2})-(\d{1,2}) (\d{1,2}):(\d{1,2})$/
180
+ var r = str.match(reg)
181
+ if (r == null) return false
182
+ r[2] = r[2] - 1
183
+ var d = new Date(r[1], r[2], r[3], r[4], r[5])
184
+ if (d.getFullYear() != r[1]) return false
185
+ if (d.getMonth() != r[2]) return false
186
+ if (d.getDate() != r[3]) return false
187
+ if (d.getHours() != r[4]) return false
188
+ if (d.getMinutes() != r[5]) return false
189
+ return true
190
+ }
191
+
192
+ /**
193
+ * 获取星座的英文
194
+ * @param {*} msg
195
+ */
196
+ function getConstellation(astro) {
197
+ if (astro.includes('白羊座')) {
198
+ return 'aries'
199
+ }
200
+ if (astro.includes('金牛座')) {
201
+ return 'taurus'
202
+ }
203
+ if (astro.includes('双子座')) {
204
+ return 'gemini'
205
+ }
206
+ if (astro.includes('巨蟹座') || astro.includes('钜蟹座')) {
207
+ return 'cancer'
208
+ }
209
+ if (astro.includes('狮子座')) {
210
+ return 'leo'
211
+ }
212
+ if (astro.includes('处女座')) {
213
+ return 'virgo'
214
+ }
215
+ if (astro.includes('天平座') || astro.includes('天秤座') || astro.includes('天瓶座') || astro.includes('天枰座')) {
216
+ return 'libra'
217
+ }
218
+ if (astro.includes('天蝎座')) {
219
+ return 'scorpio'
220
+ }
221
+ if (astro.includes('射手座')) {
222
+ return 'sagittarius'
223
+ }
224
+ if (astro.includes('射手座')) {
225
+ return 'sagittarius'
226
+ }
227
+ if (astro.includes('摩羯座')) {
228
+ return 'capricorn'
229
+ }
230
+ if (astro.includes('水瓶座')) {
231
+ return 'aquarius'
232
+ }
233
+ if (astro.includes('双鱼座')) {
234
+ return 'pisces'
235
+ }
236
+ }
237
+
238
+ /**
239
+ * 返回指定范围的随机整数
240
+ * @param {*} min
241
+ * @param {*} max
242
+ */
243
+ function randomRange(min, max) {
244
+ // min最小值,max最大值
245
+ return Math.floor(Math.random() * (max - min)) + min
246
+ }
247
+
248
+ /**
249
+ * 写入文件内容
250
+ * @param fpath
251
+ * @param encoding
252
+ * @returns {Promise<unknown>}
253
+ */
254
+ async function writeFile(fpath, encoding) {
255
+ return new Promise(function (resolve, reject) {
256
+ fs.writeFile(fpath, encoding, function (err, content) {
257
+ if (err) {
258
+ reject(err)
259
+ } else {
260
+ resolve(content)
261
+ }
262
+ })
263
+ })
264
+ }
265
+
266
+ /**
267
+ * 解析响应数据
268
+ * @param {*} content 内容
269
+ */
270
+ function parseBody(content) {
271
+ if (!content) return
272
+ return JSON.parse(content.text)
273
+ }
274
+
275
+ /**
276
+ * MD5加密
277
+ * @return {string}
278
+ */
279
+ function MD5(str) {
280
+ return Crypto.createHash('md5').update(str).digest('hex')
281
+ }
282
+
283
+ /**
284
+ * 对象内容按照字母排序
285
+ * @param obj
286
+ */
287
+ function objKeySort(obj) {
288
+ const newkey = Object.keys(obj).sort()
289
+ const newObj = {}
290
+ for (let i = 0; i < newkey.length; i++) {
291
+ newObj[newkey[i]] = obj[newkey[i]]
292
+ }
293
+ return newObj
294
+ }
295
+
296
+ /**
297
+ * 根据排序后的数据返回url参数
298
+ * @param datas
299
+ * @returns {string}
300
+ */
301
+ function getQueryString(datas) {
302
+ const data = objKeySort(datas)
303
+ let url = ''
304
+ if (typeof data === 'undefined' || data == null || typeof data !== 'object') {
305
+ return ''
306
+ }
307
+ for (var k in data) {
308
+ const string = typeof data[k] === 'object' ? JSON.stringify(data[k]) : data[k]
309
+ url += (url.indexOf('=') !== -1 ? '&' : '') + k + '=' + string
310
+ }
311
+ return url
312
+ }
313
+
314
+ /**
315
+ * 获取MD5加密后的Sign
316
+ * @param secret
317
+ * @param query
318
+ * @returns {string}
319
+ */
320
+ function getSign(secret, query) {
321
+ const stringSignTemp = `${query}&ApiSecret=${secret}`
322
+ return MD5(stringSignTemp).toUpperCase()
323
+ }
324
+
325
+ /**
326
+ * 生成n位随机数
327
+ * @param n
328
+ * @returns {string}
329
+ */
330
+ function rndNum(n) {
331
+ let rnd = ''
332
+ for (let i = 0; i < n; i++) {
333
+ rnd += Math.floor(Math.random() * 10)
334
+ }
335
+ return rnd
336
+ }
337
+
338
+ /**
339
+ * 生成加密后的对象
340
+ * @param apiKey
341
+ * @param apiSecret
342
+ * @param params
343
+ * @returns {{apiKey: *, nonce: *, timestamp: *}}
344
+ */
345
+ function getFormatQuery(apiKey, apiSecret, params = {}) {
346
+ const query = {
347
+ apiKey: apiKey,
348
+ timestamp: new Date().getTime(),
349
+ nonce: rndNum(3),
350
+ ...params,
351
+ }
352
+ const sign = getSign(getQueryString(query), apiSecret)
353
+ query.sign = sign
354
+ return query
355
+ }
356
+
357
+ /**
358
+ * 生成回复内容
359
+ * @param type 内容类型
360
+ * @param content 内容
361
+ * @param url 链接
362
+ * @returns {[{type: *, content: *, url: *}]}
363
+ */
364
+ function msgArr(type = 1, content = '', url = '') {
365
+ let obj = { type: type, content: content, url: url }
366
+ return [obj]
367
+ }
368
+
369
+ /**
370
+ * 设置提醒内容解析
371
+ * @param {*} keywordArray 分词后内容
372
+ * @param name
373
+ */
374
+ function contentDistinguish(keywordArray, name) {
375
+ let scheduleObj = {}
376
+ let today = getToday()
377
+ scheduleObj.setter = name // 设置定时任务的用户
378
+ scheduleObj.subscribe = keywordArray[1] === '我' ? name : keywordArray[1] // 定时任务接收者
379
+ if (keywordArray[2] === '每天') {
380
+ // 判断是否属于循环任务
381
+ console.log('已设置每日定时任务')
382
+ scheduleObj.isLoop = true
383
+ if (keywordArray[3].includes(':') || keywordArray[3].includes(':')) {
384
+ let time = keywordArray[3].replace(':', ':')
385
+ scheduleObj.time = convertTime(time)
386
+ } else {
387
+ scheduleObj.time = ''
388
+ }
389
+ scheduleObj.content = scheduleObj.setter === scheduleObj.subscribe ? `亲爱的${scheduleObj.subscribe},温馨提醒:${keywordArray[4].replace('我', '你')}` : `亲爱的${scheduleObj.subscribe},${scheduleObj.setter}委托我提醒你,${keywordArray[4].replace('我', '你')}`
390
+ } else if (keywordArray[2] && keywordArray[2].includes('-')) {
391
+ console.log('已设置指定日期时间任务')
392
+ scheduleObj.isLoop = false
393
+ scheduleObj.time = keywordArray[2] + ' ' + keywordArray[3].replace(':', ':')
394
+ scheduleObj.content = scheduleObj.setter === scheduleObj.subscribe ? `亲爱的${scheduleObj.subscribe},温馨提醒:${keywordArray[4].replace('我', '你')}` : `亲爱的${scheduleObj.subscribe},${scheduleObj.setter}委托我提醒你,${keywordArray[4].replace('我', '你')}`
395
+ } else {
396
+ console.log('已设置当天任务')
397
+ scheduleObj.isLoop = false
398
+ scheduleObj.time = today + keywordArray[2].replace(':', ':')
399
+ scheduleObj.content = scheduleObj.setter === scheduleObj.subscribe ? `亲爱的${scheduleObj.subscribe},温馨提醒:${keywordArray[3].replace('我', '你')}` : `亲爱的${scheduleObj.subscribe},${scheduleObj.setter}委托我提醒你,${keywordArray[3].replace('我', '你')}`
400
+ }
401
+ return scheduleObj
402
+ }
403
+
404
+ //过滤联系人
405
+ /**
406
+ * @param {*} param1
407
+ * name:名字
408
+ * alias:别名
409
+ * friend : 1是朋友 2不是朋友
410
+ * type : 1个人 2 公众号 3 未知
411
+ * gender : 1 男 2 女
412
+ * province : 省份
413
+ * city :城市
414
+ * address:地址
415
+ */
416
+ function filterContacts(contacts, query) {
417
+ let { name, alias, friend, type, gender, province, city, address } = query
418
+ return contacts.filter((item) => {
419
+ let arr = []
420
+ let { payload } = item
421
+ if (friend) {
422
+ let bool = Number(friend) === 1 ? true : false
423
+ arr.push(bool === payload.friend)
424
+ }
425
+ name && arr.push(payload.name.indexOf(name) >= 0)
426
+ alias && arr.push(payload.alias.indexOf(alias) >= 0)
427
+ type && arr.push(Number(type) === payload.type)
428
+ gender && arr.push(Number(gender) === payload.gender)
429
+ province && arr.push(payload.province.indexOf(province) >= 0)
430
+ city && arr.push(payload.city.indexOf(city) >= 0)
431
+ address && arr.push(payload.address.indexOf(address) >= 0)
432
+ return arr.indexOf(false) < 0
433
+ })
434
+ }
435
+
436
+ /**
437
+ * 格式化联系人
438
+ * @param {*} data
439
+ */
440
+ function formatContacts(data) {
441
+ let arr = data.map(function (item) {
442
+ // const file = await item.avatar()
443
+ // let avatar = await file.toBase64(file.name, true);
444
+ let payload = item.payload
445
+ return {
446
+ id: payload.id,
447
+ name: payload.name,
448
+ gender: payload.gender === 0 ? '无' : payload.gender === 1 ? '男' : '女',
449
+ alias: payload.alias,
450
+ friend: payload.friend ? '是' : '否',
451
+ star: payload.star ? '是' : '否',
452
+ type: payload.type === 1 ? '个人' : payload.type === 2 ? '公众号' : '未知',
453
+ signature: payload.signature,
454
+ province: payload.province,
455
+ city: payload.city,
456
+ address: payload.address,
457
+ }
458
+ })
459
+ return arr
460
+ }
461
+
462
+ /**
463
+ * 函数节流
464
+ * @param fn
465
+ * @param wait
466
+ * @returns {Function}
467
+ */
468
+ function throttle(fn, wait) {
469
+ var timer = null
470
+ return function () {
471
+ var context = this
472
+ var args = arguments
473
+ if (!timer) {
474
+ timer = setTimeout(function () {
475
+ fn.apply(context, args)
476
+ timer = null
477
+ }, wait)
478
+ }
479
+ }
480
+ }
481
+
482
+ /**
483
+ * @return {string}
484
+ */
485
+ function Base64Encode(str) {
486
+ return Buffer.from(str).toString('base64')
487
+ }
488
+
489
+ /**
490
+ * @return {string}
491
+ */
492
+ function Base64Decode(str) {
493
+ return Buffer.from(str, 'base64').toString('ascii')
494
+ }
495
+
496
+ /**
497
+ * 数组拆分
498
+ * @param {array} array 数组
499
+ * @param {*} subGroupLength 每个数组长度
500
+ */
501
+ function groupArray(array, subGroupLength) {
502
+ let index = 0
503
+ let newArray = []
504
+ while (index < array.length) {
505
+ newArray.push(array.slice(index, (index += subGroupLength)))
506
+ }
507
+ return newArray
508
+ }
509
+
510
+ /**
511
+ * 获取群用户列表
512
+ * @param {*}} room
513
+ * @param {*} name
514
+ */
515
+ async function getRoomAvatarList(room, name) {
516
+ const members = await room.memberAll()
517
+ let res = []
518
+ console.log('正在努力获取群成员信息...')
519
+ for (let i of members) {
520
+ let member = i.payload
521
+ try {
522
+ const avatar = await i.avatar()
523
+ if (avatar.mimeType) {
524
+ const base64 = member.weixin ? member.avatar : await avatar.toDataURL()
525
+ let obj = {
526
+ img: base64,
527
+ name: member.name,
528
+ }
529
+ res.push(obj)
530
+ }
531
+ } catch (error) {
532
+ console.log(`获取${member.name}头像失败, 头像文件格式错误,不影响群合影生成`)
533
+ continue
534
+ }
535
+ }
536
+ const say = res.splice(
537
+ res.findIndex((e) => e.name === name),
538
+ 1
539
+ )
540
+ res.unshift(say[0])
541
+ console.log('获取群成员信息完成...')
542
+ return res
543
+ }
544
+
545
+ /**
546
+ * 设置中心位置
547
+ */
548
+ function setFirstAvatr(list, name) {
549
+ const temp = list
550
+ const say = temp.splice(
551
+ temp.findIndex((e) => e.name === name),
552
+ 1
553
+ )
554
+ temp.unshift(say[0])
555
+ return temp
556
+ }
557
+
558
+ /**
559
+ * 获取群头像列表
560
+ * @param {*} roomObj
561
+ * @param {*} roomName
562
+ * @param {*} name
563
+ */
564
+ async function getRoomAvatar(roomObj, roomName, name) {
565
+ let memberList = []
566
+ const room = await getRoom(roomName) // 先获取缓存中是否存在已经获取的头像
567
+ if (room && room.list) {
568
+ memberList = room.list
569
+ } else {
570
+ const list = await getRoomAvatarList(roomObj, name)
571
+ const obj = { name: roomName, list }
572
+ await addRoom(obj)
573
+ memberList = list
574
+ }
575
+ console.log('准备绘制...')
576
+ const list = setFirstAvatr(memberList, name)
577
+ return list
578
+ }
579
+
580
+ /**
581
+ * 头像处理
582
+ * @param {*}} list
583
+ * @param {*} size
584
+ */
585
+ async function cropImg(list, size = 74) {
586
+ try {
587
+ const arr = []
588
+ for (const i of list) {
589
+ try {
590
+ if (i.img) {
591
+ const im = new MImage(i.img)
592
+ im.compress({
593
+ quality: 1,
594
+ width: size,
595
+ height: size,
596
+ }).crop({
597
+ radius: size,
598
+ })
599
+ const bas64 = await im.draw({ type: 'png' })
600
+ arr.push({ img: bas64 })
601
+ }
602
+ } catch (error) {
603
+ console.log('处理头像失败', error)
604
+ continue
605
+ }
606
+ }
607
+ return arr
608
+ } catch (error) {
609
+ console.log('处理头像错误error', error)
610
+ }
611
+ }
612
+
613
+ /**
614
+ *
615
+ * @param {*} t 头像数组
616
+ * @param {*} param1
617
+ */
618
+ function __beforePatternCircle(t, { size = 120, space = 24, circleSpace = 24, centerSizeMargin = 0 }) {
619
+ let i, r, n, s, o, c, l, p, u, g, m, d, h, f, v, b, w, y, C, _, k, I, x, S, j, q, D, z, P
620
+ for (u in ((i = size), (r = space), (n = circleSpace), (s = centerSizeMargin), (o = 1), (c = 0), (l = []), (p = r), t)) {
621
+ if (u > 0 && ((g = i / 2), (m = (i + n) * o), (m += s), (d = (2 * Math.PI * m) / (2 * g + r)), (d = Math.floor(d)), u > 0 && c === d - 1 ? (o++, (c = 0), l.push(d)) : c++, (h = t.length - 1 + ''), u === h && ((f = c), (v = c * i), c < d))) {
622
+ for (C in ((b = 0), (w = 0), (y = []), l)) {
623
+ C < l.length &&
624
+ ((_ = l[C] * r),
625
+ _ >= i + r &&
626
+ ((b += _),
627
+ (w += l[C]),
628
+ y.push({
629
+ level: C,
630
+ spaceUnit: _,
631
+ })))
632
+ }
633
+ v <= b && ((k = w + f === 0 ? 0 : (b - v) / (w + f)), (p = k))
634
+ }
635
+ }
636
+ for (j in ((I = 1), (x = 0), (S = 0), t)) j > 0 && ((q = i / 2), (D = (i + n) * I), (D += s), (z = (2 * Math.PI * D) / (2 * q + p)), (z = Math.floor(z)), t.length - j < z && ((P = t.length - 1 + ''), j === P && (S = 360 / (x + 1))), j > 0 && x === z - 1 ? (I++, (x = 0)) : x++)
637
+ return {
638
+ newSpace: p,
639
+ newRate: S,
640
+ lastCircleNumber: I,
641
+ }
642
+ }
643
+
644
+ function patternCircle(mc, t, info, i) {
645
+ let r, n, s, g, d, h, f, v, b, w, y, C, _, k, I, x, S, j, q
646
+ r = i.x
647
+ n = i.y
648
+ const o = i.centerSize || i.size
649
+ s = i.space
650
+ const c = o > i.size ? (o - i.size) / 2 : 0
651
+ const l = i.backWid
652
+ const p = i.backHei
653
+ const u = l / 2 // x中间坐标
654
+ g = p / 2 // y中间坐标
655
+ g = i.marginBottom ? p - i.marginBottom : g
656
+ r = u - i.size / 2
657
+ n = g - i.size / 2
658
+ const m = s
659
+ for (w in ((d = info), (s = d.newSpace), (h = d.newRate), (f = d.lastCircleNumber), (v = 1), (b = 0), t)) {
660
+ w > 0 &&
661
+ ((y = i.size / 2),
662
+ (C = (i.size + m) * v),
663
+ (C += c),
664
+ (_ = (2 * Math.PI * C) / (2 * y + s)),
665
+ (_ = Math.floor(_)),
666
+ (k = 360 / _),
667
+ f === v && (k = h),
668
+ (I = ((2 * Math.PI) / 360) * k * b),
669
+ (x = u + Math.sin(I + (2 * Math.PI * 270) / 360) * C),
670
+ (S = g - Math.cos(I + (2 * Math.PI * 270) / 360) * C),
671
+ (r = x - y),
672
+ (n = S - y),
673
+ w > 0 && b === _ - 1 ? (v++, (b = 0)) : b++),
674
+ (j = i.size),
675
+ parseInt(w) === 0 && ((j = o), (r = u - o / 2), (n = g - o / 2)),
676
+ (q = t[w].img)
677
+ mc.add(q, {
678
+ width: j,
679
+ pos: {
680
+ x: r,
681
+ y: n,
682
+ scale: 1,
683
+ },
684
+ })
685
+ }
686
+ return mc
687
+ }
688
+
689
+ /**
690
+ * 绘制标题
691
+ * @param {*} mc
692
+ * @param {*} title
693
+ * @param {*} titleInfo
694
+ */
695
+
696
+ function drawTitle(mc, title, titleInfo) {
697
+ mc.text(title, {
698
+ align: 'center',
699
+ width: '100%',
700
+ normalStyle: {
701
+ color: titleInfo.color,
702
+ lineheight: titleInfo.fontSize + 10,
703
+ font: `${titleInfo.fontSize}px`,
704
+ },
705
+ pos: {
706
+ x: 0,
707
+ y: titleInfo.top,
708
+ },
709
+ })
710
+ }
711
+
712
+ async function generateRoomImg(list, options) {
713
+ const { sizeInfo, titleInfo, background, roomName } = options
714
+ console.log('群合影生成中...')
715
+ list = await cropImg(list, sizeInfo.size)
716
+ const initOptions = {
717
+ title: titleInfo.title || roomName,
718
+ centerSize: sizeInfo.centerSize,
719
+ space: sizeInfo.space,
720
+ circleSpace: sizeInfo.space,
721
+ size: sizeInfo.size,
722
+ x: 0,
723
+ y: 0,
724
+ backWid: sizeInfo.width,
725
+ backHei: sizeInfo.height,
726
+ marginBottom: sizeInfo.bottom, // 距离底部高度
727
+ }
728
+ const mc = new MCanvas({
729
+ width: initOptions.backWid,
730
+ height: initOptions.backHei,
731
+ backgroundColor: '#ffffff',
732
+ })
733
+ mc.background(background, {
734
+ type: 'contain',
735
+ })
736
+ const info = __beforePatternCircle(list, initOptions)
737
+ patternCircle(mc, list, info, initOptions)
738
+ drawTitle(mc, initOptions.title, titleInfo)
739
+ const base64 = await mc.draw({ type: 'jpg', quality: 1 })
740
+ console.log('群合影生成成功!')
741
+ // var base64Data = base64.replace(/^data:image\/\w+;base64,/, '')
742
+ // var dataBuffer = Buffer.from(base64Data, 'base64')
743
+ // fs.writeFile('image.jpg', dataBuffer, function (err) {
744
+ // if (err) {
745
+ // console.log(err)
746
+ // } else {
747
+ // console.log('保存成功!')
748
+ // }
749
+ // })
750
+ return base64
751
+ }
752
+
753
+ async function generateAvatar(avatar) {
754
+ let mc = new MCanvas({
755
+ width: 880,
756
+ height: 880,
757
+ backgroundColor: '#ffffff',
758
+ })
759
+ mc.add(avatar, {
760
+ width: 810,
761
+ pos: {
762
+ x: 35,
763
+ y: 35,
764
+ scale: 1,
765
+ },
766
+ })
767
+ mc.add('http://image.xkboke.com/hat.png', {
768
+ width: 880,
769
+ pos: {
770
+ x: 0,
771
+ y: 0,
772
+ scale: 1,
773
+ },
774
+ })
775
+ const base64 = await mc.draw({ type: 'jpg', quality: 1 })
776
+ // var base64Data = base64.replace(/^data:image\/\w+;base64,/, '')
777
+ // var dataBuffer = Buffer.from(base64Data, 'base64')
778
+ // fs.writeFile('avatar.jpg', dataBuffer, function (err) {
779
+ // if (err) {
780
+ // console.log(err)
781
+ // } else {
782
+ // console.log('保存成功!')
783
+ // }
784
+ // })
785
+ return base64
786
+ }
787
+
788
+ module.exports = {
789
+ Base64Encode,
790
+ Base64Decode,
791
+ setLocalSchedule,
792
+ parseBody,
793
+ delay,
794
+ getToday,
795
+ convertTime,
796
+ getDay,
797
+ formatDate,
798
+ isRealDate,
799
+ getConstellation,
800
+ randomRange,
801
+ writeFile,
802
+ MD5,
803
+ getFormatQuery,
804
+ contentDistinguish,
805
+ msgArr,
806
+ throttle,
807
+ formatContacts,
808
+ filterContacts,
809
+ cancelAllSchedule,
810
+ getAllSchedule,
811
+ groupArray,
812
+ getRoomAvatarList,
813
+ generateRoomImg,
814
+ getRoomAvatar,
815
+ generateAvatar,
816
+ }