wechaty-puppet-matrix 0.0.46 → 0.0.48

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.
@@ -230,7 +230,8 @@ export type ClientEvent =
230
230
  | 'contact'
231
231
  | 'message'
232
232
  | 'scan'
233
- | 'verify-code';
233
+ | 'verify-code'
234
+ | 'room-leave';
234
235
  /**
235
236
  * 消息类型:1|文本 3|图片 34|语音 42|名片 43|视频 47|动态表情 48|地理位置 49|分享链接或附件 2001|红包 2002|小程序 2003|群邀请 10000|系统消息
236
237
  */
@@ -442,17 +443,17 @@ async function getImageInfo (imageUrl: string) {
442
443
 
443
444
  await fs.writeFile(finalFilePath, response.data)
444
445
 
445
- // 4. 获取图片宽高
446
- const dimensions = imageSize(finalFilePath)
447
-
448
- // 5. 删除临时文件
449
- await fs.unlink(finalFilePath)
450
-
451
- return {
452
- file_size: fileSize, // 文件大小(字节)
453
- image_width: dimensions.width,
454
- image_height: dimensions.height,
455
- file_type: dimensions.type,
446
+ // 4. 获取图片宽高,确保临时文件始终被删除
447
+ try {
448
+ const dimensions = imageSize(finalFilePath)
449
+ return {
450
+ file_size: fileSize,
451
+ image_width: dimensions.width,
452
+ image_height: dimensions.height,
453
+ file_type: dimensions.type,
454
+ }
455
+ } finally {
456
+ await fs.unlink(finalFilePath).catch(() => undefined)
456
457
  }
457
458
  } catch (error) {
458
459
  console.error('获取图片信息错误:', error)
@@ -696,6 +697,7 @@ class Client extends EventEmitter {
696
697
  override emit(event: 'verify-code', verifyInfo: VerifyInfo): boolean;
697
698
  override emit(event: 'update-contacts', contacts: ContactPayload[]): boolean;
698
699
  override emit(event: 'room-join', info: any): boolean;
700
+ override emit(event: 'room-leave', info: any): boolean;
699
701
  override emit(event: 'expired', info: boolean): boolean;
700
702
 
701
703
  override emit (event: ClientEvent, ...args: any[]): boolean {
@@ -721,11 +723,13 @@ class Client extends EventEmitter {
721
723
  this.lastInitSeqInfo = { contactSeq: 0, roomSeq: 0 }
722
724
  }
723
725
 
724
- destroy () {
726
+ async destroy () {
725
727
  log.verbose(PRE, 'destroy()')
726
728
  this.hasEmitLogout = false
727
- this.socket && this.socket.end()
728
- this.socket = null
729
+ if (this.socket) {
730
+ await new Promise<void>(resolve => this.socket.end(false, undefined, resolve))
731
+ this.socket = null
732
+ }
729
733
  this.checkExpiredInterval && clearInterval(this.checkExpiredInterval)
730
734
  this.checkExpiredInterval = undefined
731
735
  this.heartbeatTimer && clearTimeout(this.heartbeatTimer)
@@ -768,7 +772,11 @@ class Client extends EventEmitter {
768
772
  }
769
773
 
770
774
  async initServer () {
771
- await this.getTokenInfo()
775
+ const tokenValid = await this.getTokenInfo()
776
+ if (!tokenValid) {
777
+ log.error(PRE, 'initServer: token invalid or expired, abort')
778
+ return
779
+ }
772
780
  this.checkHasExpired()
773
781
  if (this.socket) {
774
782
  log.error('socket had already been opened!')
@@ -980,7 +988,6 @@ class Client extends EventEmitter {
980
988
  // this.emit('room-join', params)
981
989
  break
982
990
  case NotifyTypeEnum.RoomMemberDel:
983
- // @ts-ignore
984
991
  this.emit('room-leave', {
985
992
  roomId: data.room_username,
986
993
  leaveIds: data.member_list.map((item: any) => item.username),
@@ -1067,6 +1074,10 @@ class Client extends EventEmitter {
1067
1074
  log.error('Token已到期,无法提供服务')
1068
1075
  return
1069
1076
  }
1077
+ if (!this.tokenInfo) {
1078
+ log.error(PRE, 'postData: tokenInfo is null, skip request')
1079
+ return
1080
+ }
1070
1081
  const config: any = {
1071
1082
  data: {
1072
1083
  guid: this.tokenInfo.guid,
@@ -1085,7 +1096,6 @@ class Client extends EventEmitter {
1085
1096
  if (res.data?.errcode === -102) {
1086
1097
  log.warn('request error: %s', res.data?.errmsg)
1087
1098
  if (res.data?.errmsg === 'bridge guid not exist') {
1088
- await this.setBridgeId('')
1089
1099
  return
1090
1100
  }
1091
1101
  if (!this.hasEmitLogout) {
@@ -1121,22 +1131,28 @@ class Client extends EventEmitter {
1121
1131
  * 获取微信运行状态
1122
1132
  */
1123
1133
  public async getStats (): Promise<StatsResult> {
1124
- const res = await this.postData({
1125
- path: '/client/get_client_status',
1126
- data: {},
1127
- })
1128
- // 0: stop, 1: running, 2: online
1129
- const { errcode, data } = res
1130
- if (!errcode) {
1131
- return {
1132
- status:
1133
- data.status === 1
1134
- ? Status.pending
1135
- : data.status === 2
1136
- ? Status.normal
1137
- : Status.offline,
1134
+ try {
1135
+ const res = await this.postData({
1136
+ path: '/client/get_client_status',
1137
+ data: {},
1138
+ })
1139
+ // 0: stop, 1: running, 2: online
1140
+ if (!res) return { status: Status.fail }
1141
+ const { errcode, data } = res
1142
+ if (!errcode) {
1143
+ return {
1144
+ status:
1145
+ data.status === 1
1146
+ ? Status.pending
1147
+ : data.status === 2
1148
+ ? Status.normal
1149
+ : Status.offline,
1150
+ }
1151
+ } else {
1152
+ return { status: Status.fail }
1138
1153
  }
1139
- } else {
1154
+ } catch (e) {
1155
+ log.error(PRE, 'getStats(): %s', e)
1140
1156
  return { status: Status.fail }
1141
1157
  }
1142
1158
  }
@@ -1185,7 +1201,6 @@ class Client extends EventEmitter {
1185
1201
  }
1186
1202
  if (res.errcode === -11790 && res.errmsg.includes('timeout') && res.errmsg.includes('bridge')) {
1187
1203
  log.info(PRE, 'bridgeId 错误,请启动异地登录器后,重新配置bridgeId参数后重启服务,否则会出现异地登录提醒')
1188
- await this.setBridgeId('')
1189
1204
  return 'stop'
1190
1205
  }
1191
1206
  log.info(PRE, '请提供此报错信息,或者重启容器获取最新二维码: %s', JSON.stringify(res))
@@ -1292,46 +1307,6 @@ class Client extends EventEmitter {
1292
1307
  })
1293
1308
  }
1294
1309
 
1295
- public async setBridgeId (bridgeId?: string) {
1296
- try {
1297
- const res = await this.postData({
1298
- path: '/client/set_bridge',
1299
- data: {
1300
- bridge: bridgeId,
1301
- },
1302
- })
1303
- if (res.errcode === -1 && res.errmsg === 'bridge guid not exist') {
1304
- log.info(PRE, 'setBridgeId(%s): %s', bridgeId, 'bridge guid not exist')
1305
- void this.setBridgeId('')
1306
- return
1307
- }
1308
- log.info(PRE, 'setBridgeId(%s): %s', bridgeId, JSON.stringify(res))
1309
- } catch (e) {
1310
- log.error(PRE, 'setBridgeId(%s): %s', bridgeId, e)
1311
- }
1312
-
1313
- }
1314
-
1315
- public async setProxyEmpty () {
1316
- try {
1317
- const res = await this.postData({
1318
- path: '/client/set_proxy',
1319
- data: {
1320
- proxy: '',
1321
- is_long_proxy: false,
1322
- },
1323
- })
1324
- if (res.errcode === -1) {
1325
- log.info(PRE, 'setProxyEmpty(): %s', res.errmsg)
1326
- return
1327
- }
1328
- log.info(PRE, 'setProxyEmpty(): %s', JSON.stringify(res))
1329
- } catch (e) {
1330
- log.error(PRE, 'setProxyEmpty(): %s', e)
1331
- }
1332
-
1333
- }
1334
-
1335
1310
  /**
1336
1311
  * 获取当前bot信息
1337
1312
  */
@@ -1589,7 +1564,12 @@ class Client extends EventEmitter {
1589
1564
  /**
1590
1565
  * 同步联系人
1591
1566
  */
1592
- public async syncContact (init?: boolean): Promise<void> {
1567
+ public async syncContact (init?: boolean, depth = 0): Promise<void> {
1568
+ const maxDepth = 200
1569
+ if (depth >= maxDepth) {
1570
+ log.warn(PRE, 'syncContact: reached max recursion depth %d, stop', maxDepth)
1571
+ return
1572
+ }
1593
1573
  try {
1594
1574
  if (init) {
1595
1575
  this.lastInitSeqInfo = { contactSeq: 0, roomSeq: 0 }
@@ -1615,7 +1595,7 @@ class Client extends EventEmitter {
1615
1595
  }
1616
1596
 
1617
1597
  if (continueFlag) {
1618
- await this.syncContact()
1598
+ await this.syncContact(undefined, depth + 1)
1619
1599
  }
1620
1600
  } catch (e) {
1621
1601
  log.error(PRE, 'syncContact(): %s', e)
@@ -1638,8 +1618,8 @@ class Client extends EventEmitter {
1638
1618
  },
1639
1619
  })
1640
1620
 
1641
- const totalPage = res.total_page || 1
1642
- const contactList: ContactPayload[] = res.contact_list
1621
+ const totalPage = res?.total_page || 1
1622
+ const contactList: ContactPayload[] = (res?.contact_list || [])
1643
1623
  .filter((item: any) => item.username)
1644
1624
  .map((contact: any) => {
1645
1625
  return {
@@ -1679,8 +1659,8 @@ class Client extends EventEmitter {
1679
1659
  },
1680
1660
  })
1681
1661
 
1682
- const totalPage = res.total_page || 1
1683
- const contactList: ContactPayload[] = res.contact_list
1662
+ const totalPage = res?.total_page || 1
1663
+ const contactList: ContactPayload[] = (res?.contact_list || [])
1684
1664
  .filter((item: any) => item.username)
1685
1665
  .map((contact: any) => {
1686
1666
  return {
@@ -1722,8 +1702,8 @@ class Client extends EventEmitter {
1722
1702
  page_size: pageSize,
1723
1703
  },
1724
1704
  })
1725
- const totalPage = res.total_page || 1
1726
- const groupList: ContactPayload[] = res.room_list
1705
+ const totalPage = res?.total_page || 1
1706
+ const groupList: ContactPayload[] = (res?.room_list || [])
1727
1707
  .filter((item: any) => item.username)
1728
1708
  .map((item: any) => {
1729
1709
  return {
@@ -1741,8 +1721,8 @@ class Client extends EventEmitter {
1741
1721
  this.emit('update-contacts', groupList)
1742
1722
  }
1743
1723
 
1744
- if (parseInt(page.toString()) < parseInt(totalPage.toString())) {
1745
- await this.getGroupList(parseInt(page.toString()) + 1, pageSize)
1724
+ if (page < totalPage) {
1725
+ await this.getGroupList(page + 1, pageSize)
1746
1726
  }
1747
1727
  } catch (e) {
1748
1728
  log.error(PRE, 'getGroupList(): %s', e)
@@ -1804,8 +1784,8 @@ class Client extends EventEmitter {
1804
1784
  page_size: pageSize,
1805
1785
  },
1806
1786
  })
1807
- const totalPage = res.total_page || 1
1808
- const officeList = res.contact_list
1787
+ const totalPage = res?.total_page || 1
1788
+ const officeList = (res?.contact_list || [])
1809
1789
  .filter((item: any) => item.username)
1810
1790
  .map((contact: any) => {
1811
1791
  return {
@@ -1920,6 +1900,26 @@ class Client extends EventEmitter {
1920
1900
  })
1921
1901
  }
1922
1902
 
1903
+ private async retryDownload (fn: () => Promise<any>): Promise<any> {
1904
+ const maxRetries = 5
1905
+ const delayMs = 2000
1906
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
1907
+ try {
1908
+ const res = await fn()
1909
+ if (res?.url) {
1910
+ return res
1911
+ }
1912
+ log.warn(PRE, 'download attempt %d/%d failed (no url in response)', attempt, maxRetries)
1913
+ } catch (e) {
1914
+ log.warn(PRE, 'download attempt %d/%d error: %s', attempt, maxRetries, e)
1915
+ }
1916
+ if (attempt < maxRetries) {
1917
+ await new Promise(resolve => setTimeout(resolve, delayMs))
1918
+ }
1919
+ }
1920
+ return null
1921
+ }
1922
+
1923
1923
  public async downloadImage (imageInfo: ImageMessagePayload): Promise<any> {
1924
1924
  const params = {
1925
1925
  file_type: 2,
@@ -1928,16 +1928,12 @@ class Client extends EventEmitter {
1928
1928
  file_size: imageInfo.file_size,
1929
1929
  file_name: `/微信图片_${format(new Date(), 'yyyyMMddHHmmss')}.png`,
1930
1930
  }
1931
- const res = await this.postData({
1932
- path: '/cloud/cdn_download',
1933
- data: params,
1934
- })
1935
- if (res.url) {
1931
+ const res = await this.retryDownload(() => this.postData({ path: '/cloud/cdn_download', data: params }))
1932
+ if (res?.url) {
1936
1933
  return encodeURI(res.url) || ''
1937
- } else {
1938
- console.log('downloadImage error:', res, params)
1939
- return ''
1940
1934
  }
1935
+ log.error(PRE, 'downloadImage failed after %d retries, params: %s', 5, JSON.stringify(params))
1936
+ return ''
1941
1937
  }
1942
1938
 
1943
1939
  public async downloadWWMedia (imageInfo: ImageMessagePayload, type: number): Promise<any> {
@@ -1953,16 +1949,12 @@ class Client extends EventEmitter {
1953
1949
  file_name: typeNameMap[type],
1954
1950
  fast_download: false,
1955
1951
  }
1956
- const res = await this.postData({
1957
- path: '/cloud/cdn_download_wwfile',
1958
- data: params,
1959
- })
1960
- if (res.url) {
1952
+ const res = await this.retryDownload(() => this.postData({ path: '/cloud/cdn_download_wwfile', data: params }))
1953
+ if (res?.url) {
1961
1954
  return encodeURI(res.url) || ''
1962
- } else {
1963
- console.log('downloadWWMedia error:', res, params)
1964
- return ''
1965
1955
  }
1956
+ log.error(PRE, 'downloadWWMedia failed after %d retries, params: %s', 5, JSON.stringify(params))
1957
+ return ''
1966
1958
  }
1967
1959
 
1968
1960
  /**
@@ -1977,17 +1969,12 @@ class Client extends EventEmitter {
1977
1969
  file_size: imageInfo.file_size,
1978
1970
  file_name: '/' + sanitizeFilename(imageInfo.file_name),
1979
1971
  }
1980
- const res = await this.postData({
1981
- path: '/cloud/cdn_download',
1982
- data: params,
1983
- })
1984
-
1985
- if (res.url) {
1972
+ const res = await this.retryDownload(() => this.postData({ path: '/cloud/cdn_download', data: params }))
1973
+ if (res?.url) {
1986
1974
  return encodeURI(res.url) || ''
1987
- } else {
1988
- console.log('downloadFile error:', res, params)
1989
- return ''
1990
1975
  }
1976
+ log.error(PRE, 'downloadFile failed after %d retries, params: %s', 5, JSON.stringify(params))
1977
+ return ''
1991
1978
  }
1992
1979
 
1993
1980
  /**
@@ -2006,17 +1993,12 @@ class Client extends EventEmitter {
2006
1993
  file_name: '/voice.mp3',
2007
1994
  to_mp3: true,
2008
1995
  }
2009
- const res = await this.postData({
2010
- path: '/cloud/download_voice',
2011
- data: params,
2012
- })
2013
-
2014
- if (res.url) {
1996
+ const res = await this.retryDownload(() => this.postData({ path: '/cloud/download_voice', data: params }))
1997
+ if (res?.url) {
2015
1998
  return encodeURI(res.url) || ''
2016
- } else {
2017
- console.log('downloadAudio error:', res, params)
2018
- return ''
2019
1999
  }
2000
+ log.error(PRE, 'downloadAudio failed after %d retries, params: %s', 5, JSON.stringify(params))
2001
+ return ''
2020
2002
  }
2021
2003
 
2022
2004
  /**
@@ -2031,18 +2013,12 @@ class Client extends EventEmitter {
2031
2013
  file_size: videoInfo.file_size,
2032
2014
  file_name: `/微信视频_${format(new Date(), 'yyyyMMddHHmmss')}.mp4`,
2033
2015
  }
2034
-
2035
- const res = await this.postData({
2036
- path: '/cloud/cdn_download',
2037
- data: params,
2038
- })
2039
-
2040
- if (res.url) {
2016
+ const res = await this.retryDownload(() => this.postData({ path: '/cloud/cdn_download', data: params }))
2017
+ if (res?.url) {
2041
2018
  return encodeURI(res.url) || ''
2042
- } else {
2043
- console.log('downloadVideo error:', res, params)
2044
- return ''
2045
2019
  }
2020
+ log.error(PRE, 'downloadVideo failed after %d retries, params: %s', 5, JSON.stringify(params))
2021
+ return ''
2046
2022
  }
2047
2023
 
2048
2024
  /**
@@ -2118,7 +2094,7 @@ class Client extends EventEmitter {
2118
2094
  return {
2119
2095
  id: res?.newMsgId || '',
2120
2096
  timeStamp: res?.createTime || getUnixTime(new Date()),
2121
- msgType: WechatMessageType.Image,
2097
+ msgType: WechatMessageType.Video,
2122
2098
  talkerId: selfId || '',
2123
2099
  text: url,
2124
2100
  msg: url,
@@ -2158,7 +2134,7 @@ class Client extends EventEmitter {
2158
2134
  return {
2159
2135
  id: res?.newMsgId || '',
2160
2136
  timeStamp: res?.createTime || '',
2161
- msgType: WechatMessageType.Image,
2137
+ msgType: WechatMessageType.File,
2162
2138
  talkerId: selfId || '',
2163
2139
  text: url,
2164
2140
  msg: url,
@@ -2379,7 +2355,7 @@ class Client extends EventEmitter {
2379
2355
  data: {
2380
2356
  username: content,
2381
2357
  from_scene: 0,
2382
- search_scene: 0,
2358
+ search_scene: 1,
2383
2359
  },
2384
2360
  })
2385
2361
 
@@ -2652,9 +2628,8 @@ class Client extends EventEmitter {
2652
2628
  const imageBaseInfo = await getImageInfo(image)
2653
2629
  imageInfo.push({ ...res, ...imageBaseInfo })
2654
2630
  }
2655
- xmlContent = genImageSnsXml(wxid, momentInfo.content, imageInfo, momentInfo.location)
2656
-
2657
2631
  }
2632
+ xmlContent = genImageSnsXml(wxid, momentInfo.content, imageInfo, momentInfo.location)
2658
2633
  } else if (momentInfo.videoUrl) {
2659
2634
  const mediaInfo: any = await this.uploadSnsVideo(momentInfo.videoUrl)
2660
2635
  if (mediaInfo) {
@@ -41,7 +41,6 @@ const SEARCH_CONTACT_PREFIX = '$search$-'
41
41
  const STRANGER_SUFFIX = '@stranger'
42
42
 
43
43
  export type PuppetEngineOptions = PUPPET.PuppetOptions & {
44
- proxyId?: string,
45
44
  token?: string,
46
45
  engine?: any
47
46
  maxGetQrcoderTimes: number
@@ -81,13 +80,6 @@ class PuppetMatrix extends PUPPET.Puppet {
81
80
  this.options.token = token
82
81
  }
83
82
  }
84
-
85
- if (!this.options.proxyId) {
86
- const proxyId = process.env['WECHATY_PUPPET_MATRIX_PROXYID'] || ''
87
- if (proxyId) {
88
- this.options.proxyId = proxyId
89
- }
90
- }
91
83
  }
92
84
 
93
85
  public get client () {
@@ -227,13 +219,9 @@ class PuppetMatrix extends PUPPET.Puppet {
227
219
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
228
220
  if (info) {
229
221
  log.info(PRE, `login success: ${info.name}`)
230
- this.options.proxyId = ''
231
222
  this._getQrcodeTimes = 0
232
- await this._client?.setBridgeId('')
233
- await this._client?.setProxyEmpty()
234
223
  await this.onLogin(info)
235
224
  } else {
236
- await this._client?.setBridgeId(this.options.proxyId)
237
225
  if (this._qrcodeInterval) {
238
226
  clearInterval(this._qrcodeInterval)
239
227
  this._qrcodeInterval = null
@@ -256,8 +244,6 @@ class PuppetMatrix extends PUPPET.Puppet {
256
244
  private async _getQrcode () {
257
245
  const qrcode = await this._client?.getQrcode()
258
246
  if (qrcode === 'stop') {
259
- this.options.proxyId = ''
260
-
261
247
  if (this._qrcodeInterval) {
262
248
  clearInterval(this._qrcodeInterval)
263
249
  this._qrcodeInterval = null