wechaty-puppet-matrix 0.0.21 → 0.0.23
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/dist/cjs/src/matrix/cache-manager.d.ts +2 -2
- package/dist/cjs/src/matrix/cache-manager.d.ts.map +1 -1
- package/dist/cjs/src/matrix/cache-manager.js +4 -0
- package/dist/cjs/src/puppet-matrix.d.ts.map +1 -1
- package/dist/cjs/src/puppet-matrix.js +17 -6
- package/dist/esm/src/matrix/cache-manager.d.ts +2 -2
- package/dist/esm/src/matrix/cache-manager.d.ts.map +1 -1
- package/dist/esm/src/matrix/cache-manager.js +4 -0
- package/dist/esm/src/puppet-matrix.d.ts.map +1 -1
- package/dist/esm/src/puppet-matrix.js +17 -6
- package/package.json +2 -2
- package/src/matrix/cache-manager.ts +7 -3
- package/src/puppet-matrix.ts +20 -6
- package/src/puppet-matrix.ts~ +1674 -0
|
@@ -0,0 +1,1674 @@
|
|
|
1
|
+
import { log } from '@juzi/wechaty-puppet'
|
|
2
|
+
import * as PUPPET from '@juzi/wechaty-puppet'
|
|
3
|
+
import type { FileBoxInterface } from 'file-box'
|
|
4
|
+
import {
|
|
5
|
+
FileBox,
|
|
6
|
+
FileBoxType,
|
|
7
|
+
} from 'file-box'
|
|
8
|
+
import type { ContactPayload, MessagePayload } from './engine-schema.js'
|
|
9
|
+
import type { FileBoxMetadataMessage } from './matrix/types.js'
|
|
10
|
+
import Client from './matrix/service/request.js'
|
|
11
|
+
import { getUnixTime } from 'date-fns'
|
|
12
|
+
|
|
13
|
+
import { delay } from './matrix/utils/index.js'
|
|
14
|
+
import path from 'path'
|
|
15
|
+
// 参考
|
|
16
|
+
import { CacheManager, RoomMemberMap } from './matrix/cache-manager.js'
|
|
17
|
+
import { isIMContactId, isRoomId } from './matrix/utils/is-type.js'
|
|
18
|
+
import { AppMessagePayload, parseAppmsgMessagePayload } from './matrix/messages/message-appmsg.js'
|
|
19
|
+
import { parseMiniProgramMessagePayload } from './matrix/messages/message-miniprogram.js'
|
|
20
|
+
import { parseEmotionMessagePayload } from './matrix/messages/message-emotion.js'
|
|
21
|
+
import { ImageMessagePayload, parseImageMessagePayload } from './matrix/messages/message-image.js'
|
|
22
|
+
import { parseAudioMessagePayload, AudioMessagePayload } from './matrix/messages/message-audio.js'
|
|
23
|
+
import { parseVideoMessagePayload, VideoMessagePayload } from './matrix/messages/message-video.js'
|
|
24
|
+
import { parseLocationMessagePayload } from './matrix/messages/message-location.js'
|
|
25
|
+
import { CachedPromiseFunc } from './matrix/utils/cached-promise.js'
|
|
26
|
+
import { engineMessageToWechaty } from './matrix/schema-mapper/message.js'
|
|
27
|
+
import { engineContactToWechaty } from './matrix/schema-mapper/contact.js'
|
|
28
|
+
import {
|
|
29
|
+
chatRoomMemberToContact,
|
|
30
|
+
engineRoomMemberToWechaty,
|
|
31
|
+
engineRoomToWechaty,
|
|
32
|
+
} from './matrix/schema-mapper/room.js'
|
|
33
|
+
import { parseEvent, EventType } from './matrix/events/mod.js'
|
|
34
|
+
import { addRunningPuppet, removeRunningPuppet } from './cleanup.js'
|
|
35
|
+
import { packageJson } from './package-json.js'
|
|
36
|
+
import { JsonToXml } from './matrix/utils/xml-to-json.js'
|
|
37
|
+
|
|
38
|
+
const VERSION = packageJson.version || '0.0.0'
|
|
39
|
+
const PRE = '[PuppetMatrix]'
|
|
40
|
+
const SEARCH_CONTACT_PREFIX = '$search$-'
|
|
41
|
+
const STRANGER_SUFFIX = '@stranger'
|
|
42
|
+
|
|
43
|
+
export type PuppetEngineOptions = PUPPET.PuppetOptions & {
|
|
44
|
+
proxyId?: string,
|
|
45
|
+
token?: string,
|
|
46
|
+
engine?: any
|
|
47
|
+
maxGetQrcoderTimes: number
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
class PuppetMatrix extends PUPPET.Puppet {
|
|
51
|
+
|
|
52
|
+
private _cacheMgr?: CacheManager
|
|
53
|
+
private _client?: Client
|
|
54
|
+
private _self?: ContactPayload | undefined
|
|
55
|
+
private _qrcodeTicket: string | null | undefined
|
|
56
|
+
private _printVersion: boolean = true
|
|
57
|
+
private _heartBeatTimer?: ReturnType<typeof setTimeout>
|
|
58
|
+
private _qrcodeInterval?: ReturnType<typeof setInterval> | null
|
|
59
|
+
private _verifyInterval?: ReturnType<typeof setInterval> | null
|
|
60
|
+
private _qrcodeStatuasInterval?: ReturnType<typeof setInterval> | null
|
|
61
|
+
private _getQrcodeTimes: number
|
|
62
|
+
public static override readonly VERSION = VERSION
|
|
63
|
+
/**
|
|
64
|
+
* UUIDify:
|
|
65
|
+
* We need to clone a FileBox
|
|
66
|
+
* to set uuid loader/saver with this grpc client
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
constructor (public override options: PuppetEngineOptions = {} as PuppetEngineOptions) {
|
|
70
|
+
super(options)
|
|
71
|
+
|
|
72
|
+
this.options.engine = Client
|
|
73
|
+
this._getQrcodeTimes = 0
|
|
74
|
+
this.options.maxGetQrcoderTimes = 20
|
|
75
|
+
|
|
76
|
+
if (!this.options.token) {
|
|
77
|
+
const token = process.env['WECHATY_PUPPET_MATRIX_TOKEN'] || ''
|
|
78
|
+
if (token) {
|
|
79
|
+
this.options.token = token
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!this.options.proxyId) {
|
|
84
|
+
const proxyId = process.env['WECHATY_PUPPET_MATRIX_PROXYID'] || ''
|
|
85
|
+
if (proxyId) {
|
|
86
|
+
this.options.proxyId = proxyId
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
public get client () {
|
|
92
|
+
return this._client
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
override async onStart (): Promise<void> {
|
|
96
|
+
log.verbose(PRE, 'onStart()')
|
|
97
|
+
await this._startClient()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 启动监听
|
|
102
|
+
* @private
|
|
103
|
+
*/
|
|
104
|
+
private async _startClient () {
|
|
105
|
+
if (!this._client) {
|
|
106
|
+
this._client = await this.options.engine.create(this.options)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
await this._startPuppetHeart(true)
|
|
110
|
+
if (this._client) {
|
|
111
|
+
this._client.on('login', this.wrapAsync(async ({ robotInfo } : { robotInfo: ContactPayload }) => {
|
|
112
|
+
log.info(PRE, `login success: ${robotInfo.name}`)
|
|
113
|
+
await this.onLogin(robotInfo)
|
|
114
|
+
}))
|
|
115
|
+
this._client.on('message', this.wrapAsync(async (message:MessagePayload) => {
|
|
116
|
+
await this._onPushMessage(message)
|
|
117
|
+
}))
|
|
118
|
+
this._client.on('contact', this.wrapAsync(async ({ contactInfo }:{ friendShip:PUPPET.payloads.FriendshipReceive, contactInfo: ContactPayload, type: PUPPET.types.Friendship }) => {
|
|
119
|
+
await this._cacheMgr!.setContact(contactInfo.wxid, contactInfo)
|
|
120
|
+
await this._cacheMgr?.setFriendshipRawPayload(contactInfo.wxid, {
|
|
121
|
+
id: contactInfo.wxid,
|
|
122
|
+
contactId: contactInfo.wxid,
|
|
123
|
+
timestamp: getUnixTime(Date.now()),
|
|
124
|
+
type: PUPPET.types.Friendship.Confirm,
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
this.emit('friendship', {
|
|
128
|
+
friendshipId: contactInfo.wxid,
|
|
129
|
+
type: PUPPET.types.Friendship.Confirm,
|
|
130
|
+
})
|
|
131
|
+
}))
|
|
132
|
+
|
|
133
|
+
this._client.on('update-contacts', this.wrapAsync(async (contactList: ContactPayload[]) => {
|
|
134
|
+
for (const contact of contactList) {
|
|
135
|
+
await this._onPushContact(contact)
|
|
136
|
+
}
|
|
137
|
+
}))
|
|
138
|
+
this._client.on('room-leave', this.wrapAsync(async ({ roomId, leaveIds }: { roomId: string, leaveIds: string[] }) => {
|
|
139
|
+
const payload = {
|
|
140
|
+
roomId,
|
|
141
|
+
removeeIdList: leaveIds,
|
|
142
|
+
removerId: this._self?.wxid,
|
|
143
|
+
timestamp: getUnixTime(Date.now()),
|
|
144
|
+
}
|
|
145
|
+
for (const leaveId of leaveIds) {
|
|
146
|
+
await this._cacheMgr?.deleteSingleRoomMember(roomId, leaveId)
|
|
147
|
+
const roomInfo = await this._cacheMgr?.getRoom(roomId)
|
|
148
|
+
if (roomInfo && roomInfo.chatroommemberList) {
|
|
149
|
+
roomInfo.chatroommemberList = roomInfo.chatroommemberList.filter(member => member !== leaveId)
|
|
150
|
+
await this._cacheMgr?.setRoom(roomId, roomInfo)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
this.emit('room-leave', payload)
|
|
155
|
+
}))
|
|
156
|
+
this._client.on('room-join', this.wrapAsync(async ({ roomId, inviterId, joinIds, joinUsers }: { roomId: string, inviterId: string, joinIds: string[], joinUsers: ContactPayload[] }) => {
|
|
157
|
+
const payload = {
|
|
158
|
+
inviteeIdList : joinIds,
|
|
159
|
+
inviterId,
|
|
160
|
+
roomId,
|
|
161
|
+
timestamp : getUnixTime(Date.now()),
|
|
162
|
+
}
|
|
163
|
+
for (const joinUser of joinUsers) {
|
|
164
|
+
await this._cacheMgr?.updateSingleRoomMember(roomId, joinUser)
|
|
165
|
+
const roomInfo = await this._cacheMgr?.getRoom(roomId)
|
|
166
|
+
if (roomInfo && roomInfo.chatroommemberList) {
|
|
167
|
+
roomInfo.chatroommemberList.push(joinUser.wxid)
|
|
168
|
+
await this._cacheMgr?.setRoom(roomId, roomInfo)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
this.emit('room-join', payload)
|
|
172
|
+
}))
|
|
173
|
+
this._client.on('logout', this.wrapAsync(async (message: string) => {
|
|
174
|
+
log.info(PRE, 'Logout event: %s', message)
|
|
175
|
+
await this.logout(message)
|
|
176
|
+
}))
|
|
177
|
+
|
|
178
|
+
this._client.on('verify-code', this.wrapAsync(async ({ ticket, uuid } : {ticket: string, uuid: string, status: number}) => {
|
|
179
|
+
log.info(PRE, 'verify-code: %s, %s', ticket, uuid)
|
|
180
|
+
this._qrcodeTicket = ticket
|
|
181
|
+
if (!this._verifyInterval) {
|
|
182
|
+
this._verifyInterval = setInterval(() => {
|
|
183
|
+
this.emit('verify-code', {
|
|
184
|
+
id: uuid,
|
|
185
|
+
scene: 1,
|
|
186
|
+
status: 1,
|
|
187
|
+
message: '',
|
|
188
|
+
} as any)
|
|
189
|
+
}, 5000)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
}))
|
|
193
|
+
}
|
|
194
|
+
addRunningPuppet(this)
|
|
195
|
+
void this.checkIsLogin()
|
|
196
|
+
|
|
197
|
+
if (this._printVersion) {
|
|
198
|
+
// only print once
|
|
199
|
+
this._printVersion = false
|
|
200
|
+
log.info(`
|
|
201
|
+
============================================================
|
|
202
|
+
Welcome to Wechaty Matrix puppet!
|
|
203
|
+
|
|
204
|
+
- puppet-Matrix version: ${VERSION}
|
|
205
|
+
============================================================
|
|
206
|
+
`)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private async checkIsLogin () {
|
|
211
|
+
const info = await this._client?.getSelfInfo() as ContactPayload
|
|
212
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
213
|
+
if (info) {
|
|
214
|
+
log.info(PRE, `login success: ${info.name}`)
|
|
215
|
+
this.options.proxyId = ''
|
|
216
|
+
this._getQrcodeTimes = 0
|
|
217
|
+
await this._client?.setBridgeId('')
|
|
218
|
+
await this.onLogin(info)
|
|
219
|
+
} else {
|
|
220
|
+
await this._client?.setBridgeId(this.options.proxyId)
|
|
221
|
+
if (this._qrcodeInterval) {
|
|
222
|
+
clearInterval(this._qrcodeInterval)
|
|
223
|
+
this._qrcodeInterval = null
|
|
224
|
+
}
|
|
225
|
+
await this._getQrcode()
|
|
226
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
227
|
+
this._qrcodeInterval = setInterval(async () => {
|
|
228
|
+
await this._getQrcode()
|
|
229
|
+
}, 200000)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private getQrcodeTicket (authurl: string):string {
|
|
234
|
+
if (!authurl || !authurl.startsWith('http')) return ''
|
|
235
|
+
const url = new URL(authurl)
|
|
236
|
+
const searchParams = new URLSearchParams(url.search.slice(1))
|
|
237
|
+
return searchParams.get('ticket') || ''
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private async _getQrcode () {
|
|
241
|
+
const qrcode = await this._client?.getQrcode()
|
|
242
|
+
if (qrcode === 'stop') {
|
|
243
|
+
this.options.proxyId = ''
|
|
244
|
+
|
|
245
|
+
if (this._qrcodeInterval) {
|
|
246
|
+
clearInterval(this._qrcodeInterval)
|
|
247
|
+
this._qrcodeInterval = null
|
|
248
|
+
}
|
|
249
|
+
if (this._qrcodeStatuasInterval) {
|
|
250
|
+
clearInterval(this._qrcodeStatuasInterval)
|
|
251
|
+
this._qrcodeStatuasInterval = null
|
|
252
|
+
}
|
|
253
|
+
return
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (qrcode) {
|
|
257
|
+
if (qrcode.isLogin) {
|
|
258
|
+
await this.checkIsLogin()
|
|
259
|
+
return
|
|
260
|
+
}
|
|
261
|
+
if (this._qrcodeStatuasInterval) {
|
|
262
|
+
clearInterval(this._qrcodeStatuasInterval)
|
|
263
|
+
this._qrcodeStatuasInterval = null
|
|
264
|
+
}
|
|
265
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
266
|
+
this._qrcodeStatuasInterval = setInterval(async () => {
|
|
267
|
+
const scanStatus = await this._client?.getQrcodeStatus() as { status: number, uuid: string, authUrl?: string }
|
|
268
|
+
if (scanStatus.status === 4 || scanStatus.status === 3) {
|
|
269
|
+
await this.checkIsLogin()
|
|
270
|
+
}
|
|
271
|
+
if (scanStatus.uuid === qrcode.uuid) {
|
|
272
|
+
|
|
273
|
+
if (scanStatus.authUrl) {
|
|
274
|
+
this._qrcodeTicket = this.getQrcodeTicket(scanStatus.authUrl)
|
|
275
|
+
if (!this._verifyInterval) {
|
|
276
|
+
this._verifyInterval = setInterval(() => {
|
|
277
|
+
this.emit('verify-code', {
|
|
278
|
+
id: scanStatus.uuid,
|
|
279
|
+
scene: 1,
|
|
280
|
+
status: 1,
|
|
281
|
+
message: '',
|
|
282
|
+
} as any)
|
|
283
|
+
}, 5000)
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
await this.onQrCodeEvent(scanStatus.status, qrcode.qrcodeUrl)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
}, 8000)
|
|
290
|
+
} else {
|
|
291
|
+
if (this._getQrcodeTimes && this._getQrcodeTimes > 5) {
|
|
292
|
+
log.info(PRE, '由于多次请求二维码失败,已停止请求,请检查你的网络环境,重启容器后再试。')
|
|
293
|
+
return
|
|
294
|
+
}
|
|
295
|
+
this._getQrcodeTimes = this._getQrcodeTimes + 1
|
|
296
|
+
|
|
297
|
+
let tempTimer: any = setTimeout(() => {
|
|
298
|
+
void this._getQrcode()
|
|
299
|
+
tempTimer && clearTimeout(tempTimer)
|
|
300
|
+
tempTimer = null
|
|
301
|
+
}, 5000)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
override async enterVerifyCode (id: string, code: string): Promise<void> {
|
|
306
|
+
log.verbose(PRE, 'enterVerifyCode(%s, %s)', id, code)
|
|
307
|
+
if (this._qrcodeTicket) {
|
|
308
|
+
await this._client?.verifyLoginUrl(this._qrcodeTicket, code)
|
|
309
|
+
return
|
|
310
|
+
}
|
|
311
|
+
log.error(PRE, 'enterVerifyCode(%s, %s) error, no qrcodeTicket', id, code)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private async onQrCodeEvent (status: number, url: string) {
|
|
315
|
+
const ScanStatusName = {
|
|
316
|
+
[PUPPET.types.ScanStatus.Unknown]: 'Unknown',
|
|
317
|
+
[PUPPET.types.ScanStatus.Cancel]: 'Cancel',
|
|
318
|
+
[PUPPET.types.ScanStatus.Waiting]: 'Waiting',
|
|
319
|
+
[PUPPET.types.ScanStatus.Scanned]: 'Scanned',
|
|
320
|
+
[PUPPET.types.ScanStatus.Confirmed]: 'Confirmed',
|
|
321
|
+
[PUPPET.types.ScanStatus.Timeout]: 'Timeout',
|
|
322
|
+
}
|
|
323
|
+
let scanStatus: PUPPET.types.ScanStatus = PUPPET.types.ScanStatus.Unknown
|
|
324
|
+
|
|
325
|
+
switch (status) {
|
|
326
|
+
case 0:
|
|
327
|
+
scanStatus = PUPPET.types.ScanStatus.Waiting
|
|
328
|
+
break
|
|
329
|
+
case 1:
|
|
330
|
+
scanStatus = PUPPET.types.ScanStatus.Scanned
|
|
331
|
+
break
|
|
332
|
+
case 2:
|
|
333
|
+
scanStatus = PUPPET.types.ScanStatus.Confirmed
|
|
334
|
+
break
|
|
335
|
+
case 3:
|
|
336
|
+
scanStatus = PUPPET.types.ScanStatus.Cancel
|
|
337
|
+
break
|
|
338
|
+
case 4:
|
|
339
|
+
scanStatus = PUPPET.types.ScanStatus.Timeout
|
|
340
|
+
break
|
|
341
|
+
}
|
|
342
|
+
log.silly(
|
|
343
|
+
PRE,
|
|
344
|
+
`scan event, status: ${ScanStatusName[scanStatus]}${url ? ', with qrcode: ' + url : ''}`,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
this.emit('scan', {
|
|
348
|
+
qrcode: url,
|
|
349
|
+
status: scanStatus,
|
|
350
|
+
})
|
|
351
|
+
// 如果被取消 或者 超时 重新拉取二维码
|
|
352
|
+
if (status === 4 || status === 3) {
|
|
353
|
+
await this.checkIsLogin()
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// 登录
|
|
358
|
+
private async onLogin (user: ContactPayload):Promise<void> {
|
|
359
|
+
try {
|
|
360
|
+
// create cache manager firstly
|
|
361
|
+
if (!this._client) {
|
|
362
|
+
this._client = await this.options.engine.create(this.options)
|
|
363
|
+
}
|
|
364
|
+
if (!this._cacheMgr) {
|
|
365
|
+
this._cacheMgr = new CacheManager(user.wxid)
|
|
366
|
+
await this._cacheMgr.init()
|
|
367
|
+
await this._cacheMgr!.setContact(user.wxid, user)
|
|
368
|
+
}
|
|
369
|
+
if (this._verifyInterval) {
|
|
370
|
+
clearInterval(this._verifyInterval)
|
|
371
|
+
this._verifyInterval = null
|
|
372
|
+
}
|
|
373
|
+
if (this._qrcodeInterval) {
|
|
374
|
+
clearInterval(this._qrcodeInterval)
|
|
375
|
+
this._qrcodeInterval = null
|
|
376
|
+
}
|
|
377
|
+
if (this._qrcodeStatuasInterval) {
|
|
378
|
+
clearInterval(this._qrcodeStatuasInterval)
|
|
379
|
+
this._qrcodeStatuasInterval = null
|
|
380
|
+
}
|
|
381
|
+
this._self = user
|
|
382
|
+
|
|
383
|
+
await super.login(user.wxid)
|
|
384
|
+
void this._client?.pushLoginNotify()
|
|
385
|
+
await this.ready()
|
|
386
|
+
} catch (e) {
|
|
387
|
+
log.error('error login', e)
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
public async ready (): Promise<void> {
|
|
392
|
+
try {
|
|
393
|
+
await this._client?.getContactList(1, 100)
|
|
394
|
+
await delay(2000)
|
|
395
|
+
await this._client?.getGroupList(1, 100)
|
|
396
|
+
await delay(2000)
|
|
397
|
+
await this._client?.getOfficeList(1, 100)
|
|
398
|
+
|
|
399
|
+
log.silly(PRE, 'on ready')
|
|
400
|
+
|
|
401
|
+
setTimeout(() => {
|
|
402
|
+
this.emit('ready', {
|
|
403
|
+
data: 'ready',
|
|
404
|
+
})
|
|
405
|
+
}, 10000)
|
|
406
|
+
} catch (e) {
|
|
407
|
+
log.error('ready error', e)
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
public async onStop (): Promise<void> {
|
|
412
|
+
await this._stopClient()
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
private async _stopClient (): Promise<void> {
|
|
416
|
+
this.__currentUserId = undefined
|
|
417
|
+
this.__currentUserId = undefined
|
|
418
|
+
if (this._cacheMgr) {
|
|
419
|
+
log.info(PRE, 'colse cache')
|
|
420
|
+
await this._cacheMgr.close()
|
|
421
|
+
this._cacheMgr = undefined
|
|
422
|
+
}
|
|
423
|
+
removeRunningPuppet(this)
|
|
424
|
+
this._stopPuppetHeart()
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// 登出
|
|
428
|
+
override async logout (reason?: string): Promise<void> {
|
|
429
|
+
if (!this.isLoggedIn) {
|
|
430
|
+
return
|
|
431
|
+
}
|
|
432
|
+
await this._client?.logout()
|
|
433
|
+
|
|
434
|
+
await super.logout(reason)
|
|
435
|
+
void this.checkIsLogin()
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
override ding (data?: string): void {
|
|
439
|
+
const eventDongPayload = {
|
|
440
|
+
data: data ? data! : 'ding-dong',
|
|
441
|
+
}
|
|
442
|
+
this.emit('dong', eventDongPayload)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
override async refreshQRCode (): Promise<void> {
|
|
446
|
+
log.verbose(PRE, 'refreshQRCode(%s)')
|
|
447
|
+
await this.checkIsLogin()
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
public async stopRefreshQRCode (): Promise<void> {
|
|
451
|
+
log.verbose(PRE, 'stopRefreshQRCode(%s)')
|
|
452
|
+
if (this._qrcodeInterval) {
|
|
453
|
+
clearInterval(this._qrcodeInterval)
|
|
454
|
+
this._qrcodeInterval = null
|
|
455
|
+
}
|
|
456
|
+
if (this._qrcodeStatuasInterval) {
|
|
457
|
+
clearInterval(this._qrcodeStatuasInterval)
|
|
458
|
+
this._qrcodeStatuasInterval = null
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/****************************************************************************
|
|
463
|
+
* contact
|
|
464
|
+
***************************************************************************/
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
*
|
|
468
|
+
* ContactSelf
|
|
469
|
+
*
|
|
470
|
+
*
|
|
471
|
+
*/
|
|
472
|
+
// 设置自己的昵称 暂不支持
|
|
473
|
+
override async contactSelfName (name: string): Promise<void> {
|
|
474
|
+
return PUPPET.throwUnsupportedError(name)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// 获取自己的二维码
|
|
478
|
+
override async contactSelfQRCode (): Promise<string> {
|
|
479
|
+
return this._client?.getUserQrcode() || ''
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// 设置自己的签名 暂不支持
|
|
483
|
+
override async contactSelfSignature (signature: string): Promise<void> {
|
|
484
|
+
return PUPPET.throwUnsupportedError(signature)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// 获取用户的手机号 暂不支持
|
|
488
|
+
override async contactPhone (contactId: string, phoneList: string[]): Promise<void> {
|
|
489
|
+
return PUPPET.throwUnsupportedError(contactId, phoneList)
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// 查询或设置用户备注
|
|
493
|
+
override contactAlias (contactId: string) : Promise<string>
|
|
494
|
+
override contactAlias (contactId: string, alias: string | null): Promise<void>
|
|
495
|
+
|
|
496
|
+
override async contactAlias (contactId : string, alias? : string | null): Promise<string | void> {
|
|
497
|
+
const contact = await this.contactRawPayload(contactId)
|
|
498
|
+
if (alias) {
|
|
499
|
+
// contact is stranger, set alias in cache, to update after user is added
|
|
500
|
+
if (contact) {
|
|
501
|
+
if (contact.wxid.indexOf(STRANGER_SUFFIX) !== -1) {
|
|
502
|
+
await this._cacheMgr!.setContactStrangerAlias(contact.wxid, alias)
|
|
503
|
+
|
|
504
|
+
// to suppress warning: 15:31:06 WARN Contact alias(asd3) sync with server fail: set(asd3) is not equal to get()
|
|
505
|
+
if (contactId.startsWith(SEARCH_CONTACT_PREFIX)) {
|
|
506
|
+
const searchContact = await this._cacheMgr?.getContactSearch(contactId)
|
|
507
|
+
if (searchContact) {
|
|
508
|
+
searchContact.remark = alias
|
|
509
|
+
await this._cacheMgr!.setContactSearch(contactId, searchContact)
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
} else {
|
|
513
|
+
await this._client?.setContactAlias(contactId, alias)
|
|
514
|
+
contact.remark = alias
|
|
515
|
+
await this._updateContactCache(contact)
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
} else {
|
|
519
|
+
return contact && contact.remark
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// 获取用户头像
|
|
524
|
+
override async contactAvatar (contactId: string) : Promise<FileBoxInterface>
|
|
525
|
+
override async contactAvatar (contactId: string, file: FileBoxInterface) : Promise<void>
|
|
526
|
+
|
|
527
|
+
override async contactAvatar (contactId: string, file?: FileBoxInterface) : Promise<void | FileBoxInterface> {
|
|
528
|
+
if (file) {
|
|
529
|
+
return PUPPET.throwUnsupportedError('set avatar is not unsupported')
|
|
530
|
+
}
|
|
531
|
+
const contact = await this.contactRawPayload(contactId)
|
|
532
|
+
if (contact && contact.avatar) {
|
|
533
|
+
return FileBox.fromUrl(contact.avatar, { name: `avatar-${contactId}.jpg` })
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// 获取用户列表
|
|
538
|
+
override async contactList (): Promise<string[]> {
|
|
539
|
+
return this._cacheMgr!.getContactIds()
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// 公司备注 暂不支持
|
|
543
|
+
override async contactCorporationRemark (contactId: string, corporationRemark: string | null) {
|
|
544
|
+
return PUPPET.throwUnsupportedError(contactId, corporationRemark)
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// 其他备注 暂不支持
|
|
548
|
+
override async contactDescription (contactId: string, description: string | null) {
|
|
549
|
+
return PUPPET.throwUnsupportedError(contactId, description)
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// 删除联系人
|
|
553
|
+
override async contactDelete (contactId: string): Promise<void> {
|
|
554
|
+
const contact = await this._refreshContact(contactId)
|
|
555
|
+
if (contact && contact.isFriend === 2) {
|
|
556
|
+
log.warn(`can not delete contact which is not a friend:: ${contactId}`)
|
|
557
|
+
return
|
|
558
|
+
}
|
|
559
|
+
await this._client?.removeContact(contactId)
|
|
560
|
+
await this._refreshContact(contactId, 2)
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// 添加标签 暂不支持
|
|
564
|
+
override async tagContactTagAdd (tagIds: string[], contactIds: string[]): Promise<void> {
|
|
565
|
+
return PUPPET.throwUnsupportedError(tagIds, contactIds)
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// 删除用户标签 暂不支持
|
|
569
|
+
override async tagContactTagRemove (tagIds: string[], contactIds: string[]): Promise<void> {
|
|
570
|
+
return PUPPET.throwUnsupportedError(tagIds, contactIds)
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// 删除标签
|
|
574
|
+
override async tagTagDelete (tagIdList: string[]) : Promise<void> {
|
|
575
|
+
return PUPPET.throwUnsupportedError(tagIdList)
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// 获取用户标签
|
|
579
|
+
override async tagTagContactList (contactId?: string) : Promise<string[]> {
|
|
580
|
+
return PUPPET.throwUnsupportedError(contactId)
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/****************************************************************************
|
|
584
|
+
* friendship
|
|
585
|
+
***************************************************************************/
|
|
586
|
+
/**
|
|
587
|
+
* 通过好友请求
|
|
588
|
+
* @param friendshipId
|
|
589
|
+
*/
|
|
590
|
+
override async friendshipAccept (friendshipId : string): Promise<void> {
|
|
591
|
+
const friendship: PUPPET.payloads.FriendshipReceive = (await this.friendshipRawPayload(
|
|
592
|
+
friendshipId,
|
|
593
|
+
)) as PUPPET.payloads.FriendshipReceive
|
|
594
|
+
const userName = friendship.contactId
|
|
595
|
+
|
|
596
|
+
// FIXME: workaround to make accept enterprise account work. can be done in a better way
|
|
597
|
+
if (isIMContactId(userName)) {
|
|
598
|
+
await this._refreshContact(userName)
|
|
599
|
+
}
|
|
600
|
+
await this._client?.confirmFriendship(userName, friendship.scene, friendship.ticket)
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* 主动添加好友
|
|
605
|
+
* @param contactId
|
|
606
|
+
* @param option
|
|
607
|
+
*/
|
|
608
|
+
override async friendshipAdd (contactId: string, option?: PUPPET.types.FriendshipAddOptions): Promise<void> {
|
|
609
|
+
let stranger: number | undefined
|
|
610
|
+
let ticket: string
|
|
611
|
+
let addContactScene
|
|
612
|
+
const cachedContactSearch = await this._cacheMgr!.getContactSearch(contactId)
|
|
613
|
+
// 通过陌生人查找用户 手机或者qq
|
|
614
|
+
if (cachedContactSearch) {
|
|
615
|
+
stranger = cachedContactSearch.isFriend || undefined
|
|
616
|
+
ticket = cachedContactSearch.ticket || ''
|
|
617
|
+
addContactScene = cachedContactSearch.scene
|
|
618
|
+
} else {
|
|
619
|
+
// 通过wxid 查找
|
|
620
|
+
const contactPayload = await this.contactRawPayload(contactId)
|
|
621
|
+
const contactAlias = contactPayload?.remark
|
|
622
|
+
if (!contactAlias) {
|
|
623
|
+
// add contact from room,
|
|
624
|
+
const roomIds = await this._findRoomIdForWxid(contactId)
|
|
625
|
+
if (!roomIds.length) {
|
|
626
|
+
throw new Error(`Can not find room for contact while adding friendship: ${contactId}`)
|
|
627
|
+
}
|
|
628
|
+
const res = await this._client?.searchContact([ contactId ]) as ContactPayload | undefined
|
|
629
|
+
if (res) {
|
|
630
|
+
await this._updateContactCache(res)
|
|
631
|
+
}
|
|
632
|
+
addContactScene = '14' // 1=qq 3=微信号 6=单向添加 10和13=通讯录 14=群聊 15=手机号 17=名片 30=扫一扫
|
|
633
|
+
}
|
|
634
|
+
const res = await this.contactRawPayload(contactId)
|
|
635
|
+
if (res?.isFriend === 1) {
|
|
636
|
+
throw new Error(`contact:${contactId} is already a friend`)
|
|
637
|
+
}
|
|
638
|
+
// 通过wxid加好友
|
|
639
|
+
ticket = ''
|
|
640
|
+
addContactScene = '6' // 1=qq 3=微信号 6=单向添加 10和13=通讯录 14=群聊 15=手机号 17=名片 30=扫一扫
|
|
641
|
+
}
|
|
642
|
+
let hello: string | undefined
|
|
643
|
+
if (stranger === 1) {
|
|
644
|
+
throw new Error(`contact:${contactId} is already a friend`)
|
|
645
|
+
}
|
|
646
|
+
if (option) {
|
|
647
|
+
if (typeof option === 'string') {
|
|
648
|
+
hello = option
|
|
649
|
+
} else {
|
|
650
|
+
hello = (option as any).hello
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
await this._client?.addFriendByWxid({ content: hello, ticket, scene: addContactScene, wxid: contactId })
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* 根据手机号查询好友
|
|
658
|
+
* @param phone
|
|
659
|
+
*/
|
|
660
|
+
override async friendshipSearchPhone (phone: string): Promise<null | string> {
|
|
661
|
+
return this._friendshipSearch(phone, '15')
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* 根据qq号查询好友
|
|
666
|
+
* @param qq
|
|
667
|
+
*/
|
|
668
|
+
override async friendshipSearchHandle (qq: string): Promise<null | string> {
|
|
669
|
+
return this._friendshipSearch(qq, '1')
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* 陌生人查询
|
|
674
|
+
* @param id
|
|
675
|
+
* @param scene 场景值 1=qq 3=微信号 6=单向添加 10和13=通讯录 14=群聊 15=手机号 17=名片 30=扫一扫
|
|
676
|
+
* @private
|
|
677
|
+
*/
|
|
678
|
+
private async _friendshipSearch (id: string, scene?:string): Promise<null | string> {
|
|
679
|
+
const cachedContactSearch = await this._cacheMgr!.getContactSearch(id)
|
|
680
|
+
if (cachedContactSearch) {
|
|
681
|
+
return id
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
const res = await this._client?.searchStranger(id)
|
|
685
|
+
const searchId = `${SEARCH_CONTACT_PREFIX}${id}`
|
|
686
|
+
if (res) {
|
|
687
|
+
await this._cacheMgr!.setContactSearch(searchId, { ...res, scene })
|
|
688
|
+
}
|
|
689
|
+
return searchId
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* 根据wxid 查询群id
|
|
694
|
+
* @param wxid
|
|
695
|
+
* @private
|
|
696
|
+
*/
|
|
697
|
+
private async _findRoomIdForWxid (wxid: string): Promise<string[]> {
|
|
698
|
+
const ret = []
|
|
699
|
+
|
|
700
|
+
const roomIds = (await this._cacheMgr?.getRoomIds()) || []
|
|
701
|
+
for (const roomId of roomIds) {
|
|
702
|
+
const roomMember = await this._cacheMgr?.getRoomMember(roomId)
|
|
703
|
+
if (!roomMember) {
|
|
704
|
+
continue
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
const roomMemberIds = Object.keys(roomMember)
|
|
708
|
+
if (roomMemberIds.indexOf(wxid) !== -1) {
|
|
709
|
+
ret.push(roomId)
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
return ret
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/****************************************************************************
|
|
717
|
+
* get message payload
|
|
718
|
+
***************************************************************************/
|
|
719
|
+
// 名片
|
|
720
|
+
override async messageContact (messageId: string): Promise<string> {
|
|
721
|
+
log.verbose('PuppetWeChat', 'messageContact(%s)', messageId)
|
|
722
|
+
return PUPPET.throwUnsupportedError(messageId)
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
override async messageChannel (
|
|
726
|
+
messageId: string,
|
|
727
|
+
): Promise<PUPPET.payloads.Channel> {
|
|
728
|
+
log.verbose('PuppetService', 'messageChannel(%s)', messageId)
|
|
729
|
+
|
|
730
|
+
const rawPayload = await this.messageRawPayload(messageId)
|
|
731
|
+
const payload = await this.messageRawPayloadParser(rawPayload)
|
|
732
|
+
|
|
733
|
+
if (payload.type !== PUPPET.types.Message.Channel) {
|
|
734
|
+
throw new Error('Can not get channel from no Channel payload')
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// FIXME: thumb may not in appPayload.thumburl, but in appPayload.appAttachPayload
|
|
738
|
+
const appPayload = await parseAppmsgMessagePayload(rawPayload.msg)
|
|
739
|
+
const channelPayload = {
|
|
740
|
+
avatar: appPayload.channel?.avatar || '',
|
|
741
|
+
coverUrl: appPayload.channel?.mediaList?.media?.coverUrl || '',
|
|
742
|
+
desc: appPayload.channel?.desc || '',
|
|
743
|
+
extras: '',
|
|
744
|
+
feedType: appPayload.channel?.feedType || PUPPET.types.ChannelFeed.VIDEO,
|
|
745
|
+
nickname: appPayload.channel?.nickname || '',
|
|
746
|
+
thumbUrl: appPayload.channel?.mediaList?.media?.thumbUrl || '',
|
|
747
|
+
url: appPayload.channel?.mediaList?.media?.url || '',
|
|
748
|
+
objectId: appPayload.channel?.objectId || '',
|
|
749
|
+
objectNonceId: appPayload.channel?.objectNonceId || '',
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
return channelPayload
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// 文件消息
|
|
756
|
+
override async messageFile (messageId: string): Promise<FileBoxInterface> {
|
|
757
|
+
const messagePayload: MessagePayload = await this.messageRawPayload(messageId)
|
|
758
|
+
const message: PUPPET.payloads.Message = await this.messageRawPayloadParser(messagePayload)
|
|
759
|
+
switch (message.type) {
|
|
760
|
+
// 图片
|
|
761
|
+
case PUPPET.types.Message.Image:
|
|
762
|
+
return this._getMessageImageFileBox(messageId, messagePayload)
|
|
763
|
+
case PUPPET.types.Message.Audio:
|
|
764
|
+
return this._getMessageAudioFileBox(messageId, messagePayload)
|
|
765
|
+
case PUPPET.types.Message.Video: {
|
|
766
|
+
return this._getMessageVideoFileBox(messageId, messagePayload)
|
|
767
|
+
}
|
|
768
|
+
case PUPPET.types.Message.Attachment:
|
|
769
|
+
return this._getMessageFileFileBox(messageId, messagePayload)
|
|
770
|
+
case PUPPET.types.Message.Emoticon: {
|
|
771
|
+
const emotionPayload = await parseEmotionMessagePayload(messagePayload)
|
|
772
|
+
const emoticonBox = FileBox.fromUrl(emotionPayload.cdnurl, { name: `message-${messageId}-emoticon.jpg` })
|
|
773
|
+
|
|
774
|
+
emoticonBox.metadata = {
|
|
775
|
+
payload: emotionPayload,
|
|
776
|
+
type: 'emoticon',
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
return emoticonBox
|
|
780
|
+
}
|
|
781
|
+
case PUPPET.types.Message.MiniProgram:
|
|
782
|
+
return PUPPET.throwUnsupportedError(messageId)
|
|
783
|
+
case PUPPET.types.Message.Url:
|
|
784
|
+
return PUPPET.throwUnsupportedError(messageId)
|
|
785
|
+
default:
|
|
786
|
+
throw new Error(`Can not get file for message: ${messageId}`)
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* 解析图片消息
|
|
792
|
+
* @param messageId
|
|
793
|
+
*/
|
|
794
|
+
override async messageImage (messageId: string): Promise<FileBoxInterface> {
|
|
795
|
+
const messagePayload: MessagePayload = await this.messageRawPayload(messageId)
|
|
796
|
+
return this._getMessageImageFileBox(messageId, messagePayload)
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* 解析小程序
|
|
801
|
+
* @param messageId
|
|
802
|
+
*/
|
|
803
|
+
override async messageMiniProgram (messageId: string): Promise<PUPPET.payloads.MiniProgram> {
|
|
804
|
+
const messagePayload = await this.messageRawPayload(messageId)
|
|
805
|
+
const message = await this.messageRawPayloadParser(messagePayload)
|
|
806
|
+
|
|
807
|
+
if (message.type !== PUPPET.types.Message.MiniProgram) {
|
|
808
|
+
throw new Error('message is not mini program, can not get MiniProgramPayload')
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
return parseMiniProgramMessagePayload(messagePayload)
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* 解析h5链接
|
|
816
|
+
* @param messageId
|
|
817
|
+
*/
|
|
818
|
+
override async messageUrl (messageId: string) : Promise<PUPPET.payloads.UrlLink> {
|
|
819
|
+
const rawPayload = await this.messageRawPayload(messageId)
|
|
820
|
+
const payload = await this.messageRawPayloadParser(rawPayload)
|
|
821
|
+
|
|
822
|
+
if (payload.type !== PUPPET.types.Message.Url) {
|
|
823
|
+
throw new Error('Can not get url from non url payload')
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// FIXME: thumb may not in appPayload.thumburl, but in appPayload.appAttachPayload
|
|
827
|
+
const appPayload = await parseAppmsgMessagePayload(rawPayload.msg)
|
|
828
|
+
return {
|
|
829
|
+
description: appPayload.des,
|
|
830
|
+
thumbnailUrl: appPayload.thumburl,
|
|
831
|
+
title: appPayload.title,
|
|
832
|
+
url: appPayload.url,
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* 解析h5链接
|
|
838
|
+
* @param messageId
|
|
839
|
+
*/
|
|
840
|
+
override async messageLocation (messageId: string) : Promise<PUPPET.payloads.Location> {
|
|
841
|
+
const rawPayload = await this.messageRawPayload(messageId)
|
|
842
|
+
const payload = await this.messageRawPayloadParser(rawPayload)
|
|
843
|
+
|
|
844
|
+
if (payload.type !== PUPPET.types.Message.Location) {
|
|
845
|
+
throw new Error('Can not get location from non location payload')
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// FIXME: thumb may not in appPayload.thumburl, but in appPayload.appAttachPayload
|
|
849
|
+
const locationPayload = await parseLocationMessagePayload(rawPayload.msg) as PUPPET.payloads.Location
|
|
850
|
+
return locationPayload
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
/****************************************************************************
|
|
854
|
+
* send message
|
|
855
|
+
***************************************************************************/
|
|
856
|
+
// 发送名片
|
|
857
|
+
override async messageSendContact (toUserId: string, contactId: string): Promise<void | string> {
|
|
858
|
+
log.verbose('PuppetWeChat', 'messageSend("%s", %s)', toUserId, contactId)
|
|
859
|
+
const contactPayload = await this.contactRawPayload(contactId)
|
|
860
|
+
const xmlObj = {
|
|
861
|
+
msg: {
|
|
862
|
+
antispamticket: '', // TODO 需要考虑怎么设置
|
|
863
|
+
bigheadimgurl: contactPayload?.avatar,
|
|
864
|
+
brandFlags: '0',
|
|
865
|
+
certflag: '0',
|
|
866
|
+
city: contactPayload?.city,
|
|
867
|
+
fullpy: contactPayload?.name,
|
|
868
|
+
imagestatus: '3',
|
|
869
|
+
nickname: contactPayload?.name,
|
|
870
|
+
province: contactPayload?.province,
|
|
871
|
+
regionCode: 'CN_Shanghai',
|
|
872
|
+
scene: '17',
|
|
873
|
+
sex: contactPayload?.sex,
|
|
874
|
+
smallheadimgurl: contactPayload?.avatar,
|
|
875
|
+
username: contactPayload?.ticket,
|
|
876
|
+
},
|
|
877
|
+
}
|
|
878
|
+
const xml = JsonToXml(xmlObj)
|
|
879
|
+
const res = await this._client?.sendContactCard(toUserId, xml, this._self?.wxid)
|
|
880
|
+
if (res) {
|
|
881
|
+
await this._cacheMgr?.setMessage(res.id, res)
|
|
882
|
+
return res.id
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// 发送文件
|
|
887
|
+
override async messageSendFile (conversationId: string, fileBox: FileBoxInterface): Promise<string | void> {
|
|
888
|
+
const metadata: FileBoxMetadataMessage = fileBox.metadata as FileBoxMetadataMessage
|
|
889
|
+
|
|
890
|
+
const fileType
|
|
891
|
+
= fileBox.mediaType && fileBox.mediaType !== 'application/octet-stream'
|
|
892
|
+
? fileBox.mediaType
|
|
893
|
+
: path.extname(fileBox.name)
|
|
894
|
+
|
|
895
|
+
// @ts-ignore
|
|
896
|
+
const fileUrl = fileBox.remoteUrl || ''
|
|
897
|
+
if (metadata.type === 'emoticon') {
|
|
898
|
+
PUPPET.throwUnsupportedError(conversationId, fileBox)
|
|
899
|
+
} else if (fileBox.mediaType.startsWith('image/')) {
|
|
900
|
+
if (fileUrl) {
|
|
901
|
+
const msgInfo = await this._client?.sendUrlImg(conversationId, fileUrl, this._self?.wxid)
|
|
902
|
+
|
|
903
|
+
if (msgInfo) {
|
|
904
|
+
await this._cacheMgr?.setMessage(msgInfo.id, msgInfo)
|
|
905
|
+
return msgInfo.id
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
}
|
|
909
|
+
} else if (fileBox.mediaType === 'audio/silk') {
|
|
910
|
+
PUPPET.throwUnsupportedError(conversationId, fileBox)
|
|
911
|
+
} else if (fileType.includes('video/mp4') || fileType.includes('.mp4')) {
|
|
912
|
+
const msgInfo = await this._client?.sendVideo(conversationId, fileUrl, this._self?.wxid)
|
|
913
|
+
if (msgInfo) {
|
|
914
|
+
await this._cacheMgr?.setMessage(msgInfo.id, msgInfo)
|
|
915
|
+
return msgInfo.id
|
|
916
|
+
}
|
|
917
|
+
} else {
|
|
918
|
+
if (fileUrl) {
|
|
919
|
+
const msgInfo = await this._client?.sendCloudFile(conversationId, fileUrl, this._self?.wxid)
|
|
920
|
+
if (msgInfo) {
|
|
921
|
+
await this._cacheMgr?.setMessage(msgInfo.id, msgInfo)
|
|
922
|
+
return msgInfo.id
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// 发送小程序
|
|
929
|
+
override async messageSendMiniProgram (toUserName: string, mpPayload: PUPPET.payloads.MiniProgram): Promise<void | string> {
|
|
930
|
+
const miniProgram = {
|
|
931
|
+
appid: mpPayload.appid,
|
|
932
|
+
contactId: toUserName,
|
|
933
|
+
content: mpPayload.description,
|
|
934
|
+
username: mpPayload.username,
|
|
935
|
+
pagePath: mpPayload.pagePath,
|
|
936
|
+
thumbUrl: mpPayload.thumbUrl,
|
|
937
|
+
iconUrl: mpPayload.iconUrl,
|
|
938
|
+
title: mpPayload.title,
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
if (!mpPayload.thumbUrl) {
|
|
942
|
+
log.warn(PRE, 'no thumb image found while sending mimi program')
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
const res = await this._client?.sendMiniProgram(miniProgram, this._self?.wxid)
|
|
946
|
+
if (res) {
|
|
947
|
+
await this._cacheMgr?.setMessage(res.id, res)
|
|
948
|
+
return res.id
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// 发送文字
|
|
953
|
+
override async messageSendText (conversationId: string, text: string, mentionIdList?: string[]): Promise<string | void> {
|
|
954
|
+
if (mentionIdList && mentionIdList.length) {
|
|
955
|
+
const mentionContact = await Promise.all(mentionIdList.map(async (mentionId) => {
|
|
956
|
+
const contact = await this.contactRawPayload(mentionId)
|
|
957
|
+
if (contact) {
|
|
958
|
+
return '@' + (contact.alias || contact.name)
|
|
959
|
+
}
|
|
960
|
+
return '@' + (mentionId === '@all' ? 'all' : '')
|
|
961
|
+
}))
|
|
962
|
+
const mentionText = mentionContact.join(String.fromCharCode(0x2005))
|
|
963
|
+
console.log('metionContact********', mentionText)
|
|
964
|
+
text = text.replaceAll(mentionText, '').trim()
|
|
965
|
+
const msgInfo = await this._client?.sendAtText(conversationId, text, mentionIdList, this._self?.wxid)
|
|
966
|
+
if (msgInfo) {
|
|
967
|
+
await this._cacheMgr?.setMessage(msgInfo.id, msgInfo)
|
|
968
|
+
return msgInfo.id
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
const msgInfo = await this._client?.sendText(conversationId, text, this._self?.wxid)
|
|
973
|
+
|
|
974
|
+
if (msgInfo) {
|
|
975
|
+
await this._cacheMgr?.setMessage(msgInfo.id, msgInfo)
|
|
976
|
+
return msgInfo.id
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// 发送h5链接
|
|
981
|
+
override async messageSendUrl (conversationId: string, urlLinkPayload: PUPPET.payloads.UrlLink): Promise<string | void> {
|
|
982
|
+
const urlCard = {
|
|
983
|
+
contactId: conversationId,
|
|
984
|
+
content: urlLinkPayload.description,
|
|
985
|
+
jumpUrl: urlLinkPayload.url,
|
|
986
|
+
imageUrl: urlLinkPayload.thumbnailUrl,
|
|
987
|
+
title: urlLinkPayload.title,
|
|
988
|
+
}
|
|
989
|
+
if (!urlLinkPayload.thumbnailUrl) {
|
|
990
|
+
log.warn(PRE, 'no thumb image found while sending mimi program')
|
|
991
|
+
}
|
|
992
|
+
const msgInfo = await this._client?.sendShareCard(urlCard, this._self?.wxid)
|
|
993
|
+
|
|
994
|
+
if (msgInfo) {
|
|
995
|
+
await this._cacheMgr?.setMessage(msgInfo.id, msgInfo)
|
|
996
|
+
return msgInfo.id
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* 确认收款
|
|
1002
|
+
*/
|
|
1003
|
+
override async messageSendPost (conversationId: string, postPayload: PUPPET.payloads.Post): Promise<void> {
|
|
1004
|
+
const msgType = postPayload.sayableList[0] as PUPPET.payloads.Sayable
|
|
1005
|
+
if (msgType.type !== 'Text') {
|
|
1006
|
+
throw new Error(`Wrong Post!!! please check your Post payload to make sure it right ${conversationId}`)
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
/**
|
|
1011
|
+
* 消息撤回 暂不支持
|
|
1012
|
+
* @param messageId
|
|
1013
|
+
*/
|
|
1014
|
+
override async messageRecall (messageId: string): Promise<boolean> {
|
|
1015
|
+
return PUPPET.throwUnsupportedError(messageId)
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
/**
|
|
1019
|
+
* 消息转发
|
|
1020
|
+
* @param toUserName
|
|
1021
|
+
* @param messageId
|
|
1022
|
+
*/
|
|
1023
|
+
override async messageForward (toUserName: string, messageId: string): Promise<void> {
|
|
1024
|
+
const messagePayload = await this.messageRawPayload(messageId)
|
|
1025
|
+
const message = await this.messageRawPayloadParser(messagePayload)
|
|
1026
|
+
|
|
1027
|
+
switch (message.type) {
|
|
1028
|
+
case PUPPET.types.Message.Text:
|
|
1029
|
+
await this.messageSendText(toUserName, message.text!)
|
|
1030
|
+
break
|
|
1031
|
+
|
|
1032
|
+
case PUPPET.types.Message.Image: {
|
|
1033
|
+
const imageFileBox = await this.messageImage(messageId)
|
|
1034
|
+
await this.messageSendFile(toUserName, imageFileBox)
|
|
1035
|
+
break
|
|
1036
|
+
}
|
|
1037
|
+
case PUPPET.types.Message.Audio: {
|
|
1038
|
+
const audioFileBox = await this.messageFile(messageId)
|
|
1039
|
+
await this.messageSendFile(toUserName, audioFileBox)
|
|
1040
|
+
break
|
|
1041
|
+
}
|
|
1042
|
+
case PUPPET.types.Message.Video: {
|
|
1043
|
+
const videoFileBox = await this.messageFile(messageId)
|
|
1044
|
+
await this.messageSendFile(toUserName, videoFileBox)
|
|
1045
|
+
break
|
|
1046
|
+
}
|
|
1047
|
+
default:
|
|
1048
|
+
throw new Error(`Message forwarding is unsupported for messageId:${messageId}, type:${message.type}`)
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
/****************************************************************************
|
|
1053
|
+
* room
|
|
1054
|
+
***************************************************************************/
|
|
1055
|
+
|
|
1056
|
+
// 拉人进群
|
|
1057
|
+
override async roomAdd (roomId : string, contactId : string): Promise<void> {
|
|
1058
|
+
let type:number = 1 // 1 直接拉 2 发送邀请链接 人数超过40需要对方同意
|
|
1059
|
+
if (roomId) {
|
|
1060
|
+
const ret = await this.roomRawPayload(roomId)
|
|
1061
|
+
if (ret && ret.memberNum && ret.memberNum > 38) {
|
|
1062
|
+
type = 2
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
await this._client?.inviteToGroup(roomId, contactId, type)
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
// 获取群头像
|
|
1069
|
+
override async roomAvatar (roomId: string): Promise<FileBoxInterface> {
|
|
1070
|
+
const chatroom = await this.roomRawPayload(roomId)
|
|
1071
|
+
if (chatroom && chatroom.avatar) {
|
|
1072
|
+
return FileBox.fromUrl(chatroom.avatar)
|
|
1073
|
+
} else {
|
|
1074
|
+
// return dummy FileBox object
|
|
1075
|
+
return FileBox.fromBuffer(Buffer.from(new ArrayBuffer(0)), 'room-avatar.jpg')
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
// 创建群聊
|
|
1080
|
+
override async roomCreate (
|
|
1081
|
+
contactIdList : string[],
|
|
1082
|
+
topic : string,
|
|
1083
|
+
): Promise<string> {
|
|
1084
|
+
return this._client?.creatRoom(contactIdList, topic) || ''
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// 删除群聊 暂不支持
|
|
1088
|
+
override async roomDel (
|
|
1089
|
+
roomId : string,
|
|
1090
|
+
contactId : string,
|
|
1091
|
+
): Promise<void> {
|
|
1092
|
+
return PUPPET.throwUnsupportedError(roomId, contactId)
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
// 获取群聊列表
|
|
1096
|
+
override async roomList (): Promise<string[]> {
|
|
1097
|
+
return this._cacheMgr!.getRoomIds()
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// 获取群二维码 暂不支持
|
|
1101
|
+
override async roomQRCode (roomId: string): Promise<string> {
|
|
1102
|
+
return PUPPET.throwUnsupportedError(roomId)
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// 机器人退出群聊
|
|
1106
|
+
override async roomQuit (roomId: string): Promise<void> {
|
|
1107
|
+
return this._client?.roomQuit(roomId)
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
override async roomTopic (roomId: string) : Promise<string>
|
|
1111
|
+
override async roomTopic (roomId: string, topic: string) : Promise<void>
|
|
1112
|
+
|
|
1113
|
+
// 修改群名称
|
|
1114
|
+
override async roomTopic (
|
|
1115
|
+
roomId : string,
|
|
1116
|
+
topic? : string,
|
|
1117
|
+
): Promise<void | string> {
|
|
1118
|
+
await this._client?.setGroupName(roomId, topic)
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
override async roomAnnounce (roomId: string) : Promise<string>
|
|
1122
|
+
override async roomAnnounce (roomId: string, text: string) : Promise<void>
|
|
1123
|
+
|
|
1124
|
+
// 修改群公告
|
|
1125
|
+
override async roomAnnounce (roomId: string, text?: string) : Promise<void | string> {
|
|
1126
|
+
if (text !== undefined) {
|
|
1127
|
+
await this._client?.sendAnnouncement(roomId, text)
|
|
1128
|
+
}
|
|
1129
|
+
return ''
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// 获取群成员列表
|
|
1133
|
+
override async roomMemberList (roomId: string) : Promise<string[]> {
|
|
1134
|
+
const roomMemberMap = await this._getRoomMemberList(roomId)
|
|
1135
|
+
return Object.values(roomMemberMap).map((m) => m.wxid)
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
// 接受群邀请 暂不支持
|
|
1139
|
+
override async roomInvitationAccept (roomInvitationId: string): Promise<void> {
|
|
1140
|
+
return PUPPET.throwUnsupportedError(roomInvitationId)
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/****************************************************************************
|
|
1144
|
+
* RawPayload section
|
|
1145
|
+
***************************************************************************/
|
|
1146
|
+
|
|
1147
|
+
// 解析联系人信息格式化为Wechaty 格式
|
|
1148
|
+
override async contactRawPayloadParser (payload: ContactPayload): Promise<PUPPET.payloads.Contact> {
|
|
1149
|
+
return engineContactToWechaty(payload)
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
// 获取联系人信息 原格式
|
|
1153
|
+
override async contactRawPayload (id: string): Promise<ContactPayload | undefined> {
|
|
1154
|
+
log.silly(PRE, 'contactRawPayload(%s) @ %s', id, this)
|
|
1155
|
+
if (id.startsWith(SEARCH_CONTACT_PREFIX)) {
|
|
1156
|
+
const searchContact = await this._cacheMgr?.getContactSearch(id)
|
|
1157
|
+
return searchContact
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
let ret = await this._cacheMgr!.getContact(id)
|
|
1161
|
+
if (!ret) {
|
|
1162
|
+
ret = await CachedPromiseFunc(`contactRawPayload-${id}`, async () => {
|
|
1163
|
+
const contact = await this._refreshContact(id)
|
|
1164
|
+
return contact
|
|
1165
|
+
})
|
|
1166
|
+
return ret
|
|
1167
|
+
}
|
|
1168
|
+
return ret
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
/**
|
|
1172
|
+
* 解析原始消息体为Wechaty支持的格式
|
|
1173
|
+
* @param payload
|
|
1174
|
+
*/
|
|
1175
|
+
override async messageRawPayloadParser (payload: MessagePayload): Promise<PUPPET.payloads.Message> {
|
|
1176
|
+
return engineMessageToWechaty(this, payload)
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
/**
|
|
1180
|
+
* 根据消息id 获取消息
|
|
1181
|
+
* @param id
|
|
1182
|
+
*/
|
|
1183
|
+
override async messageRawPayload (id: string): Promise<MessagePayload> {
|
|
1184
|
+
const ret = await this._cacheMgr!.getMessage(id)
|
|
1185
|
+
if (!ret) {
|
|
1186
|
+
throw new Error(`can not find message in cache for messageId: ${id}`)
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
return ret
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
/**
|
|
1193
|
+
* 群数据格式化为Wechaty 支持类型
|
|
1194
|
+
* @param rawPayload
|
|
1195
|
+
*/
|
|
1196
|
+
override async roomRawPayloadParser (payload: ContactPayload): Promise<PUPPET.payloads.Room> {
|
|
1197
|
+
return engineRoomToWechaty(payload)
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
/**
|
|
1201
|
+
* 查找群基础信息
|
|
1202
|
+
* @param id
|
|
1203
|
+
*/
|
|
1204
|
+
override async roomRawPayload (id: string): Promise<ContactPayload|undefined> {
|
|
1205
|
+
let ret = await this._cacheMgr!.getRoom(id)
|
|
1206
|
+
if (!ret) {
|
|
1207
|
+
const contact = await this._refreshContact(id)
|
|
1208
|
+
ret = contact
|
|
1209
|
+
}
|
|
1210
|
+
return ret
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
/**
|
|
1214
|
+
* 查找群成员信息
|
|
1215
|
+
* @param roomId
|
|
1216
|
+
* @param contactId
|
|
1217
|
+
*/
|
|
1218
|
+
override async roomMemberRawPayload (roomId: string, contactId: string): Promise<ContactPayload> {
|
|
1219
|
+
const roomMemberMap = await this._getRoomMemberList(roomId)
|
|
1220
|
+
if (roomMemberMap[contactId]) {
|
|
1221
|
+
return roomMemberMap[contactId]!
|
|
1222
|
+
}
|
|
1223
|
+
const newMap = await this._getRoomMemberList(roomId, true)
|
|
1224
|
+
|
|
1225
|
+
return newMap[contactId]!
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
/**
|
|
1229
|
+
* 解析群成员信息
|
|
1230
|
+
* @param rawPayload
|
|
1231
|
+
*/
|
|
1232
|
+
override async roomMemberRawPayloadParser (rawPayload: ContactPayload): Promise<PUPPET.payloads.RoomMember> {
|
|
1233
|
+
return engineRoomMemberToWechaty(rawPayload)
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
/**
|
|
1237
|
+
* 接收群邀请信息 暂不支持
|
|
1238
|
+
* @param roomInvitationId
|
|
1239
|
+
*/
|
|
1240
|
+
override async roomInvitationRawPayload (roomInvitationId: string): Promise<any> {
|
|
1241
|
+
return PUPPET.throwUnsupportedError(roomInvitationId)
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
/**
|
|
1245
|
+
* 解析群邀请信息 暂不支持
|
|
1246
|
+
* @param rawPayload
|
|
1247
|
+
*/
|
|
1248
|
+
override async roomInvitationRawPayloadParser (rawPayload: any): Promise<PUPPET.payloads.RoomInvitation> {
|
|
1249
|
+
return PUPPET.throwUnsupportedError(rawPayload)
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
/**
|
|
1253
|
+
* 好友申请信息解析
|
|
1254
|
+
* @param rawPayload
|
|
1255
|
+
*/
|
|
1256
|
+
override async friendshipRawPayloadParser (rawPayload: PUPPET.payloads.Friendship): Promise<PUPPET.payloads.Friendship> {
|
|
1257
|
+
return rawPayload
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
/**
|
|
1261
|
+
* 获取好友申请信息
|
|
1262
|
+
* @param id
|
|
1263
|
+
*/
|
|
1264
|
+
override async friendshipRawPayload (id: string): Promise<PUPPET.payloads.Friendship> {
|
|
1265
|
+
const ret = await this._cacheMgr!.getFriendshipRawPayload(id)
|
|
1266
|
+
|
|
1267
|
+
if (!ret) {
|
|
1268
|
+
throw new Error(`Can not find friendship for id: ${id}`)
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
return ret
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
/****************************************************************************
|
|
1275
|
+
* moment section
|
|
1276
|
+
***************************************************************************/
|
|
1277
|
+
/**
|
|
1278
|
+
* 发布朋友圈
|
|
1279
|
+
* @param payload
|
|
1280
|
+
*/
|
|
1281
|
+
override async postPublish (payload: PUPPET.payloads.Post): Promise<void | string> {
|
|
1282
|
+
log.verbose(PRE, 'postPublish(%s)', payload)
|
|
1283
|
+
if (!PUPPET.payloads.isPostClient(payload)) {
|
|
1284
|
+
throw new Error('can only publish client post now')
|
|
1285
|
+
}
|
|
1286
|
+
const momentInfo:any = {
|
|
1287
|
+
content: '',
|
|
1288
|
+
mentionIdList: [],
|
|
1289
|
+
visibledList: [],
|
|
1290
|
+
imageUrls: [],
|
|
1291
|
+
videoUrl: '',
|
|
1292
|
+
urlLink: null,
|
|
1293
|
+
channel: null,
|
|
1294
|
+
miniInfo: null,
|
|
1295
|
+
location: null,
|
|
1296
|
+
rootId: '',
|
|
1297
|
+
parentId: '',
|
|
1298
|
+
}
|
|
1299
|
+
for (const item of payload.sayableList) {
|
|
1300
|
+
switch (item.type) {
|
|
1301
|
+
case PUPPET.types.Sayable.Text:
|
|
1302
|
+
momentInfo.content = `${momentInfo.content ? momentInfo.content + '\n' : ''}${item.payload.text}`
|
|
1303
|
+
momentInfo.mentionIdList = item.payload.mentions
|
|
1304
|
+
break
|
|
1305
|
+
case PUPPET.types.Sayable.Attachment: {
|
|
1306
|
+
const fileBox = item.payload.filebox as FileBoxInterface
|
|
1307
|
+
if (typeof item.payload.filebox !== 'string' && (fileBox as FileBoxInterface).type === FileBoxType.Url) {
|
|
1308
|
+
const fileType = fileBox.mediaType && fileBox.mediaType !== 'application/octet-stream' ? fileBox.mediaType : path.extname(fileBox.name)
|
|
1309
|
+
// @ts-ignore
|
|
1310
|
+
const fileUrl = fileBox.remoteUrl || ''
|
|
1311
|
+
if (fileBox.mediaType.startsWith('image/')) {
|
|
1312
|
+
momentInfo.imageUrls.push(fileUrl)
|
|
1313
|
+
} else if (fileType.includes('video/mp4') || fileType.includes('.mp4')) {
|
|
1314
|
+
momentInfo.videoUrl = fileUrl
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
break
|
|
1318
|
+
}
|
|
1319
|
+
case PUPPET.types.Sayable.Url: {
|
|
1320
|
+
momentInfo.urlLink = item.payload
|
|
1321
|
+
break
|
|
1322
|
+
}
|
|
1323
|
+
case PUPPET.types.Sayable.Channel: {
|
|
1324
|
+
momentInfo.channel = item.payload
|
|
1325
|
+
break
|
|
1326
|
+
}
|
|
1327
|
+
case PUPPET.types.Sayable.MiniProgram: {
|
|
1328
|
+
momentInfo.miniInfo = item.payload
|
|
1329
|
+
break
|
|
1330
|
+
}
|
|
1331
|
+
default:
|
|
1332
|
+
throw new Error(`postPublish unsupported type ${item.type}`)
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
if (payload.rootId) momentInfo.rootId = payload.rootId
|
|
1336
|
+
if (payload.parentId) momentInfo.parentId = payload.parentId
|
|
1337
|
+
if (payload.location) momentInfo.location = payload.location
|
|
1338
|
+
|
|
1339
|
+
momentInfo.visibleList = payload.visibleList
|
|
1340
|
+
|
|
1341
|
+
const res = await this._client?.sendSnsMoment(this._self?.wxid || '', momentInfo)
|
|
1342
|
+
|
|
1343
|
+
return res
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
override async postUnpublish (id: string): Promise<void> {
|
|
1347
|
+
log.verbose(PRE, 'postUnpublish(%s)', id)
|
|
1348
|
+
|
|
1349
|
+
await this._client?.unSendMoment(id)
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
override async postRawPayload (id: string): Promise<PUPPET.payloads.Post | string> {
|
|
1353
|
+
log.verbose(PRE, 'postRawPayload(%s)', id)
|
|
1354
|
+
return id
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
override async postPayloadSayable (postId: string, sayableId: string): Promise<PUPPET.payloads.Sayable> {
|
|
1358
|
+
log.verbose(PRE, 'postPayloadSayable(%s, %s)', postId, sayableId)
|
|
1359
|
+
return postId as any
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
override async postRawPayloadParser (payload: PUPPET.payloads.Post): Promise<PUPPET.payloads.Post> {
|
|
1363
|
+
// log.silly('PuppetService', 'postRawPayloadParser({id:%s})', payload.id)
|
|
1364
|
+
// passthrough
|
|
1365
|
+
return payload
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
override async tap (postId: string, type: PUPPET.types.Tap, tap = true): Promise<boolean | void> {
|
|
1369
|
+
log.verbose(PRE, 'tap(%s, %s, %s)', postId, type, tap)
|
|
1370
|
+
|
|
1371
|
+
const res = await this._client?.sendMomentLike(postId, type)
|
|
1372
|
+
|
|
1373
|
+
return !!res
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
/****************************************************************************
|
|
1377
|
+
* extra methods section
|
|
1378
|
+
***************************************************************************/
|
|
1379
|
+
|
|
1380
|
+
async syncRoom () {
|
|
1381
|
+
if (this.state.active() !== true) {
|
|
1382
|
+
throw new Error('Can not sync contact before login')
|
|
1383
|
+
}
|
|
1384
|
+
await this._client?.getGroupList(1, 100)
|
|
1385
|
+
}
|
|
1386
|
+
/****************************************************************************
|
|
1387
|
+
* private section
|
|
1388
|
+
***************************************************************************/
|
|
1389
|
+
|
|
1390
|
+
// 获取群成员列表
|
|
1391
|
+
private async _getRoomMemberList (roomId: string, force?: boolean): Promise<RoomMemberMap> {
|
|
1392
|
+
// FIX: https://github.com/wechaty/puppet-padlocal/issues/115
|
|
1393
|
+
if (!this._cacheMgr) {
|
|
1394
|
+
return {}
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
let ret = await this._cacheMgr!.getRoomMember(roomId)
|
|
1398
|
+
|
|
1399
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1400
|
+
if (!ret || (ret && !Object.keys(ret).length) || force) {
|
|
1401
|
+
const resMembers = await this._client?.getGroupMembers(roomId) || []
|
|
1402
|
+
|
|
1403
|
+
const roomMemberMap: RoomMemberMap = {}
|
|
1404
|
+
for (const roomMember of resMembers) {
|
|
1405
|
+
const hasContact = await this._cacheMgr!.hasContact(roomMember.wxid)
|
|
1406
|
+
let MemberInfo: ContactPayload
|
|
1407
|
+
// save chat room member as contact, to forbid massive this._client.api.getContact(id) requests while room.ready()
|
|
1408
|
+
if (!hasContact) {
|
|
1409
|
+
MemberInfo = chatRoomMemberToContact(roomMember)
|
|
1410
|
+
await this._cacheMgr!.setContact(MemberInfo.wxid, MemberInfo)
|
|
1411
|
+
roomMemberMap[roomMember.wxid] = MemberInfo
|
|
1412
|
+
} else {
|
|
1413
|
+
MemberInfo = await this._cacheMgr!.getContact(roomMember.wxid) as ContactPayload
|
|
1414
|
+
roomMemberMap[roomMember.wxid] = MemberInfo
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
ret = roomMemberMap
|
|
1418
|
+
await this._updateRoomMember(roomId, roomMemberMap)
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
return ret
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
// 更新联系人缓存
|
|
1425
|
+
private async _updateContactCache (contact: ContactPayload): Promise<void> {
|
|
1426
|
+
if (!contact.wxid) {
|
|
1427
|
+
log.warn(PRE, `wxid is required for contact: ${JSON.stringify(contact)}`)
|
|
1428
|
+
return
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
if (isRoomId(contact.wxid)) {
|
|
1432
|
+
const oldRoomPayload = await this._cacheMgr!.getRoom(contact.wxid)
|
|
1433
|
+
|
|
1434
|
+
const roomId = contact.wxid
|
|
1435
|
+
let finalPayload: ContactPayload = contact
|
|
1436
|
+
if (oldRoomPayload) {
|
|
1437
|
+
finalPayload = Object.assign(oldRoomPayload, contact)
|
|
1438
|
+
}
|
|
1439
|
+
await this._cacheMgr!.setRoom(roomId, finalPayload)
|
|
1440
|
+
|
|
1441
|
+
if (!finalPayload.chatroommemberList || finalPayload.chatroommemberList.length === 0) {
|
|
1442
|
+
const map = await this._getRoomMemberList(roomId)
|
|
1443
|
+
await this._cacheMgr!.setRoom(roomId, Object.assign(finalPayload, { chatroommemberList: Object.keys(map) }))
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
} else {
|
|
1447
|
+
const oldContactPaylod = await this._cacheMgr!.getContact(contact.wxid)
|
|
1448
|
+
if (oldContactPaylod) {
|
|
1449
|
+
await this._cacheMgr!.setContact(contact.wxid, Object.assign(oldContactPaylod, contact))
|
|
1450
|
+
} else {
|
|
1451
|
+
await this._cacheMgr!.setContact(contact.wxid, contact)
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
// 更新群成员
|
|
1457
|
+
private async _updateRoomMember (roomId: string, roomMemberMap?: RoomMemberMap) {
|
|
1458
|
+
if (roomMemberMap) {
|
|
1459
|
+
await this._cacheMgr!.setRoomMember(roomId, roomMemberMap)
|
|
1460
|
+
} else {
|
|
1461
|
+
await this._cacheMgr!.deleteRoomMember(roomId)
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
/** this.emit('login', { contactId: userId })
|
|
1466
|
+
* 更新群成员信息
|
|
1467
|
+
* @param roomId
|
|
1468
|
+
*/
|
|
1469
|
+
public async _updateRoom (roomId:string) {
|
|
1470
|
+
if (!roomId) {
|
|
1471
|
+
log.warn(PRE, 'roomid is required for updateRoom')
|
|
1472
|
+
return
|
|
1473
|
+
}
|
|
1474
|
+
let contact: ContactPayload | undefined = await this._client?.searchContact([ roomId ]) as ContactPayload | undefined
|
|
1475
|
+
|
|
1476
|
+
if (contact) {
|
|
1477
|
+
const memeberMap: RoomMemberMap = await this._getRoomMemberList(contact.wxid, true)
|
|
1478
|
+
const chatroommemberList = Object.values(memeberMap).map(item => item.wxid)
|
|
1479
|
+
contact = { ...contact, memberNum: Object.keys(memeberMap).length, chatroommemberList }
|
|
1480
|
+
await this._onPushContact(contact)
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
// 添加好友信息到缓存
|
|
1485
|
+
private async _onPushContact (contact: ContactPayload): Promise<void> {
|
|
1486
|
+
log.silly(PRE, `on push contact: ${JSON.stringify(contact)}`)
|
|
1487
|
+
|
|
1488
|
+
await this._updateContactCache(contact)
|
|
1489
|
+
|
|
1490
|
+
if (contact.wxid) {
|
|
1491
|
+
const aliasToSet = await this._cacheMgr!.getContactStrangerAlias(contact.wxid)
|
|
1492
|
+
if (aliasToSet) {
|
|
1493
|
+
await this.contactAlias(contact.wxid, aliasToSet)
|
|
1494
|
+
await this._cacheMgr!.deleteContactStrangerAlias(contact.wxid)
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
private async _onPushMessage (message: MessagePayload): Promise<void> {
|
|
1500
|
+
const messageId = message.id
|
|
1501
|
+
log.silly(PRE, `on push original message: ${JSON.stringify(message)}`)
|
|
1502
|
+
if (await this._cacheMgr!.hasMessage(messageId)) {
|
|
1503
|
+
return
|
|
1504
|
+
}
|
|
1505
|
+
await this._cacheMgr!.setMessage(message.id, message)
|
|
1506
|
+
const event = await parseEvent(this, message)
|
|
1507
|
+
log.info('event-------', JSON.stringify(event))
|
|
1508
|
+
switch (event.type) {
|
|
1509
|
+
case EventType.Message:
|
|
1510
|
+
this.emit('message', {
|
|
1511
|
+
messageId,
|
|
1512
|
+
})
|
|
1513
|
+
break
|
|
1514
|
+
case EventType.Friendship: {
|
|
1515
|
+
if (event.payload.type !== PUPPET.types.Friendship.Confirm && event.payload.type !== PUPPET.types.Friendship.Verify) {
|
|
1516
|
+
await this._cacheMgr?.setFriendshipRawPayload(event.payload.contactId, event.payload)
|
|
1517
|
+
|
|
1518
|
+
setTimeout(() => {
|
|
1519
|
+
this.emit('friendship', {
|
|
1520
|
+
friendshipId: event.payload.contactId,
|
|
1521
|
+
type: event.payload.type,
|
|
1522
|
+
})
|
|
1523
|
+
}, 1000)
|
|
1524
|
+
|
|
1525
|
+
} else {
|
|
1526
|
+
this.emit('message', {
|
|
1527
|
+
messageId,
|
|
1528
|
+
})
|
|
1529
|
+
}
|
|
1530
|
+
break
|
|
1531
|
+
}
|
|
1532
|
+
case EventType.RoomInvite: {
|
|
1533
|
+
const roomInvite: PUPPET.payloads.RoomInvitation = event.payload
|
|
1534
|
+
await this._cacheMgr!.setRoomInvitation(messageId, roomInvite)
|
|
1535
|
+
|
|
1536
|
+
this.emit('room-invite', {
|
|
1537
|
+
roomInvitationId: messageId,
|
|
1538
|
+
})
|
|
1539
|
+
break
|
|
1540
|
+
}
|
|
1541
|
+
case EventType.RoomJoin: {
|
|
1542
|
+
// const roomJoin: PUPPET.payloads.EventRoomJoin = event.payload
|
|
1543
|
+
// await this._updateRoomMember(roomJoin.roomId)
|
|
1544
|
+
// this.emit('room-join', roomJoin)
|
|
1545
|
+
break
|
|
1546
|
+
}
|
|
1547
|
+
case EventType.RoomLeave: {
|
|
1548
|
+
// const roomLeave: PUPPET.payloads.EventRoomLeave = event.payload
|
|
1549
|
+
// this.emit('room-leave', roomLeave)
|
|
1550
|
+
|
|
1551
|
+
// await this._updateRoomMember(roomLeave.roomId)
|
|
1552
|
+
break
|
|
1553
|
+
}
|
|
1554
|
+
case EventType.RoomTopic: {
|
|
1555
|
+
// const roomTopic: PUPPET.payloads.EventRoomTopic = event.payload
|
|
1556
|
+
// this.emit('room-topic', roomTopic)
|
|
1557
|
+
break
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
// 刷新用户信息
|
|
1563
|
+
private async _refreshContact (wxid: string, isFriend?: number): Promise<ContactPayload | undefined> {
|
|
1564
|
+
const contact = await this._client?.searchContactDetail([ wxid ]) as ContactPayload | undefined
|
|
1565
|
+
// may return contact with empty payload, empty username, nickname, etc.
|
|
1566
|
+
if (contact && !contact.wxid) {
|
|
1567
|
+
contact.wxid = wxid
|
|
1568
|
+
await this._updateContactCache({ ...contact, isFriend })
|
|
1569
|
+
return contact
|
|
1570
|
+
} else if (contact) {
|
|
1571
|
+
return contact
|
|
1572
|
+
}
|
|
1573
|
+
return undefined
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
// 开始监听心跳
|
|
1577
|
+
private async _startPuppetHeart (firstTime: boolean = true) {
|
|
1578
|
+
if (firstTime && this._heartBeatTimer) {
|
|
1579
|
+
return
|
|
1580
|
+
}
|
|
1581
|
+
this.emit('heartbeat', { data: 'heartbeat@matrix' })
|
|
1582
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
1583
|
+
this._heartBeatTimer = setTimeout(async (): Promise<void> => {
|
|
1584
|
+
await this._startPuppetHeart(false)
|
|
1585
|
+
return undefined
|
|
1586
|
+
}, 15 * 1000) // 15s
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
// 停止监听心跳
|
|
1590
|
+
private _stopPuppetHeart () {
|
|
1591
|
+
if (!this._heartBeatTimer) {
|
|
1592
|
+
return
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
clearTimeout(this._heartBeatTimer)
|
|
1596
|
+
this._heartBeatTimer = undefined
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
/**
|
|
1600
|
+
* 解析图片
|
|
1601
|
+
* @param messageId
|
|
1602
|
+
* @param messagePayload
|
|
1603
|
+
* @private
|
|
1604
|
+
*/
|
|
1605
|
+
private async _getMessageImageFileBox (messageId: string, messagePayload: MessagePayload):Promise<FileBoxInterface> {
|
|
1606
|
+
const imageInfo: ImageMessagePayload = await parseImageMessagePayload(messagePayload)
|
|
1607
|
+
if (!imageInfo.file_id) {
|
|
1608
|
+
log.error(`Can not get file for message: ${messageId}`)
|
|
1609
|
+
}
|
|
1610
|
+
const url = await this._client?.downloadImage(imageInfo)
|
|
1611
|
+
return FileBox.fromUrl(url)
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
private async _getMessageAudioFileBox (messageId: string, messagePayload: MessagePayload):Promise<FileBoxInterface> {
|
|
1615
|
+
const audioInfo: AudioMessagePayload = await parseAudioMessagePayload(messagePayload)
|
|
1616
|
+
if (!audioInfo.length) {
|
|
1617
|
+
log.error(`Can not get file for message: ${messageId}`)
|
|
1618
|
+
}
|
|
1619
|
+
const url = await this._client?.downloadAudio(messageId, audioInfo)
|
|
1620
|
+
return FileBox.fromUrl(url)
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
private async _getMessageVideoFileBox (messageId: string, messagePayload: MessagePayload):Promise<FileBoxInterface> {
|
|
1624
|
+
const videoInfo: VideoMessagePayload = await parseVideoMessagePayload(messagePayload)
|
|
1625
|
+
if (!videoInfo.file_id) {
|
|
1626
|
+
log.error(`Can not get file for message: ${messageId}`)
|
|
1627
|
+
}
|
|
1628
|
+
const url = await this._client?.downloadVideo(videoInfo)
|
|
1629
|
+
log.info(PRE, '_getMessageVideoFileBox(): %s', url)
|
|
1630
|
+
return FileBox.fromUrl(url)
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
/**
|
|
1634
|
+
* 解析文件
|
|
1635
|
+
* @param messageId
|
|
1636
|
+
* @param messagePayload
|
|
1637
|
+
* @private
|
|
1638
|
+
*/
|
|
1639
|
+
private async _getMessageFileFileBox (messageId: string, messagePayload: MessagePayload):Promise<FileBoxInterface> {
|
|
1640
|
+
const appPayload: AppMessagePayload = await parseAppmsgMessagePayload(messagePayload.msg)
|
|
1641
|
+
if (!appPayload.appattach?.attachid) {
|
|
1642
|
+
log.error(`Can not get file for message: ${messageId}`)
|
|
1643
|
+
}
|
|
1644
|
+
const appattach = appPayload.appattach
|
|
1645
|
+
const params = {
|
|
1646
|
+
aes_key: appattach?.aeskey || '',
|
|
1647
|
+
file_id: appattach?.attachid || '',
|
|
1648
|
+
file_name: appPayload.title,
|
|
1649
|
+
file_size: appattach?.totallen || '',
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
const url = await this._client?.downloadFile(params)
|
|
1653
|
+
|
|
1654
|
+
if (!url) {
|
|
1655
|
+
log.error(`Can not get file url: ${JSON.stringify(params)}`)
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
return FileBox.fromUrl(url, { name: appPayload.title })
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
// @ts-ignore
|
|
1662
|
+
public async syncContact () {
|
|
1663
|
+
if (!this.currentUserId) {
|
|
1664
|
+
throw new Error('Can not sync contact before login')
|
|
1665
|
+
}
|
|
1666
|
+
log.info('syncContact')
|
|
1667
|
+
await this._client!.syncContact()
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
export { PuppetMatrix, VERSION }
|
|
1673
|
+
|
|
1674
|
+
export default PuppetMatrix
|