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.
- package/.eslintignore +4 -0
- package/.eslintrc.js +14 -0
- package/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/.idea/misc.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/.idea/wechaty-web-panel.iml +12 -0
- package/.prettierrc +8 -0
- package/LICENSE +21 -0
- package/README.md +175 -0
- package/doc/img/async.png +0 -0
- package/doc/img/event.png +0 -0
- package/doc/img/everyday.png +0 -0
- package/doc/img/func.jpeg +0 -0
- package/doc/img/group.jpeg +0 -0
- package/doc/img/index.png +0 -0
- package/doc/img/material.png +0 -0
- package/doc/img/news.jpeg +0 -0
- package/doc/img/qrcode-s.png +0 -0
- package/doc/img/qrcode.png +0 -0
- package/doc/img/roomasync.png +0 -0
- package/doc/img/schedule.png +0 -0
- package/doc/img/user-center.png +0 -0
- package/package.json +67 -0
- package/src/common/aiDb.js +23 -0
- package/src/common/configDb.js +86 -0
- package/src/common/index.js +321 -0
- package/src/common/reply.js +24 -0
- package/src/common/roomAvatarDb.js +24 -0
- package/src/common/userDb.js +24 -0
- package/src/handlers/on-error.js +13 -0
- package/src/handlers/on-friend.js +45 -0
- package/src/handlers/on-heartbeat.js +12 -0
- package/src/handlers/on-login.js +35 -0
- package/src/handlers/on-logout.js +11 -0
- package/src/handlers/on-message.js +127 -0
- package/src/handlers/on-ready.js +18 -0
- package/src/handlers/on-roomjoin.js +36 -0
- package/src/handlers/on-roomleave.js +6 -0
- package/src/handlers/on-roomtopic.js +5 -0
- package/src/handlers/on-scan.js +16 -0
- package/src/index.js +45 -0
- package/src/lib/index.js +816 -0
- package/src/lib/nedb.js +87 -0
- package/src/proxy/aibotk.js +351 -0
- package/src/proxy/api.js +582 -0
- package/src/proxy/config.js +9 -0
- package/src/proxy/mqtt.js +91 -0
- package/src/proxy/superagent.js +117 -0
- package/src/proxy/tencent.js +40 -0
- package/src/service/event-dispatch-service.js +147 -0
- package/src/service/msg-filter-service.js +76 -0
- package/src/service/msg-filters.js +175 -0
- package/src/service/room-async-service.js +395 -0
- package/src/task/index.js +233 -0
- package/verpub.config.js +6 -0
package/src/lib/index.js
ADDED
|
@@ -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
|
+
}
|