wechaty-puppet-matrix 0.0.42 → 0.0.47

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!')
@@ -899,6 +907,10 @@ class Client extends EventEmitter {
899
907
  })
900
908
  break
901
909
  case NotifyTypeEnum.NewMsg:
910
+ if (data.create_time < Math.floor(Date.now() / 1000) - 60) {
911
+ log.warn('消息时间超过60秒,可能是重复消息,已忽略', data)
912
+ return
913
+ }
902
914
  if (data.is_chatroom_msg) {
903
915
  atWxidList = await getAtWxidList(data.source)
904
916
  }
@@ -976,7 +988,6 @@ class Client extends EventEmitter {
976
988
  // this.emit('room-join', params)
977
989
  break
978
990
  case NotifyTypeEnum.RoomMemberDel:
979
- // @ts-ignore
980
991
  this.emit('room-leave', {
981
992
  roomId: data.room_username,
982
993
  leaveIds: data.member_list.map((item: any) => item.username),
@@ -1063,6 +1074,10 @@ class Client extends EventEmitter {
1063
1074
  log.error('Token已到期,无法提供服务')
1064
1075
  return
1065
1076
  }
1077
+ if (!this.tokenInfo) {
1078
+ log.error(PRE, 'postData: tokenInfo is null, skip request')
1079
+ return
1080
+ }
1066
1081
  const config: any = {
1067
1082
  data: {
1068
1083
  guid: this.tokenInfo.guid,
@@ -1117,22 +1132,28 @@ class Client extends EventEmitter {
1117
1132
  * 获取微信运行状态
1118
1133
  */
1119
1134
  public async getStats (): Promise<StatsResult> {
1120
- const res = await this.postData({
1121
- path: '/client/get_client_status',
1122
- data: {},
1123
- })
1124
- // 0: stop, 1: running, 2: online
1125
- const { errcode, data } = res
1126
- if (!errcode) {
1127
- return {
1128
- status:
1129
- data.status === 1
1130
- ? Status.pending
1131
- : data.status === 2
1132
- ? Status.normal
1133
- : Status.offline,
1135
+ try {
1136
+ const res = await this.postData({
1137
+ path: '/client/get_client_status',
1138
+ data: {},
1139
+ })
1140
+ // 0: stop, 1: running, 2: online
1141
+ if (!res) return { status: Status.fail }
1142
+ const { errcode, data } = res
1143
+ if (!errcode) {
1144
+ return {
1145
+ status:
1146
+ data.status === 1
1147
+ ? Status.pending
1148
+ : data.status === 2
1149
+ ? Status.normal
1150
+ : Status.offline,
1151
+ }
1152
+ } else {
1153
+ return { status: Status.fail }
1134
1154
  }
1135
- } else {
1155
+ } catch (e) {
1156
+ log.error(PRE, 'getStats(): %s', e)
1136
1157
  return { status: Status.fail }
1137
1158
  }
1138
1159
  }
@@ -1297,7 +1318,11 @@ class Client extends EventEmitter {
1297
1318
  },
1298
1319
  })
1299
1320
  if (res.errcode === -1 && res.errmsg === 'bridge guid not exist') {
1300
- log.info(PRE, 'setBridgeId(%s): %s', bridgeId, 'bridge guid not exist')
1321
+ if (bridgeId === '') {
1322
+ log.warn(PRE, 'setBridgeId: empty bridgeId still invalid, skip retry to avoid infinite recursion')
1323
+ return
1324
+ }
1325
+ log.info(PRE, 'setBridgeId(%s): %s', bridgeId, 'bridge guid not exist, retrying with empty bridgeId')
1301
1326
  void this.setBridgeId('')
1302
1327
  return
1303
1328
  }
@@ -1585,7 +1610,12 @@ class Client extends EventEmitter {
1585
1610
  /**
1586
1611
  * 同步联系人
1587
1612
  */
1588
- public async syncContact (init?: boolean): Promise<void> {
1613
+ public async syncContact (init?: boolean, depth = 0): Promise<void> {
1614
+ const maxDepth = 200
1615
+ if (depth >= maxDepth) {
1616
+ log.warn(PRE, 'syncContact: reached max recursion depth %d, stop', maxDepth)
1617
+ return
1618
+ }
1589
1619
  try {
1590
1620
  if (init) {
1591
1621
  this.lastInitSeqInfo = { contactSeq: 0, roomSeq: 0 }
@@ -1611,7 +1641,7 @@ class Client extends EventEmitter {
1611
1641
  }
1612
1642
 
1613
1643
  if (continueFlag) {
1614
- await this.syncContact()
1644
+ await this.syncContact(undefined, depth + 1)
1615
1645
  }
1616
1646
  } catch (e) {
1617
1647
  log.error(PRE, 'syncContact(): %s', e)
@@ -1634,8 +1664,8 @@ class Client extends EventEmitter {
1634
1664
  },
1635
1665
  })
1636
1666
 
1637
- const totalPage = res.total_page || 1
1638
- const contactList: ContactPayload[] = res.contact_list
1667
+ const totalPage = res?.total_page || 1
1668
+ const contactList: ContactPayload[] = (res?.contact_list || [])
1639
1669
  .filter((item: any) => item.username)
1640
1670
  .map((contact: any) => {
1641
1671
  return {
@@ -1675,8 +1705,8 @@ class Client extends EventEmitter {
1675
1705
  },
1676
1706
  })
1677
1707
 
1678
- const totalPage = res.total_page || 1
1679
- const contactList: ContactPayload[] = res.contact_list
1708
+ const totalPage = res?.total_page || 1
1709
+ const contactList: ContactPayload[] = (res?.contact_list || [])
1680
1710
  .filter((item: any) => item.username)
1681
1711
  .map((contact: any) => {
1682
1712
  return {
@@ -1718,8 +1748,8 @@ class Client extends EventEmitter {
1718
1748
  page_size: pageSize,
1719
1749
  },
1720
1750
  })
1721
- const totalPage = res.total_page || 1
1722
- const groupList: ContactPayload[] = res.room_list
1751
+ const totalPage = res?.total_page || 1
1752
+ const groupList: ContactPayload[] = (res?.room_list || [])
1723
1753
  .filter((item: any) => item.username)
1724
1754
  .map((item: any) => {
1725
1755
  return {
@@ -1737,8 +1767,8 @@ class Client extends EventEmitter {
1737
1767
  this.emit('update-contacts', groupList)
1738
1768
  }
1739
1769
 
1740
- if (parseInt(page.toString()) < parseInt(totalPage.toString())) {
1741
- await this.getGroupList(parseInt(page.toString()) + 1, pageSize)
1770
+ if (page < totalPage) {
1771
+ await this.getGroupList(page + 1, pageSize)
1742
1772
  }
1743
1773
  } catch (e) {
1744
1774
  log.error(PRE, 'getGroupList(): %s', e)
@@ -1800,8 +1830,8 @@ class Client extends EventEmitter {
1800
1830
  page_size: pageSize,
1801
1831
  },
1802
1832
  })
1803
- const totalPage = res.total_page || 1
1804
- const officeList = res.contact_list
1833
+ const totalPage = res?.total_page || 1
1834
+ const officeList = (res?.contact_list || [])
1805
1835
  .filter((item: any) => item.username)
1806
1836
  .map((contact: any) => {
1807
1837
  return {
@@ -1916,6 +1946,26 @@ class Client extends EventEmitter {
1916
1946
  })
1917
1947
  }
1918
1948
 
1949
+ private async retryDownload (fn: () => Promise<any>): Promise<any> {
1950
+ const maxRetries = 5
1951
+ const delayMs = 2000
1952
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
1953
+ try {
1954
+ const res = await fn()
1955
+ if (res?.url) {
1956
+ return res
1957
+ }
1958
+ log.warn(PRE, 'download attempt %d/%d failed (no url in response)', attempt, maxRetries)
1959
+ } catch (e) {
1960
+ log.warn(PRE, 'download attempt %d/%d error: %s', attempt, maxRetries, e)
1961
+ }
1962
+ if (attempt < maxRetries) {
1963
+ await new Promise(resolve => setTimeout(resolve, delayMs))
1964
+ }
1965
+ }
1966
+ return null
1967
+ }
1968
+
1919
1969
  public async downloadImage (imageInfo: ImageMessagePayload): Promise<any> {
1920
1970
  const params = {
1921
1971
  file_type: 2,
@@ -1924,16 +1974,12 @@ class Client extends EventEmitter {
1924
1974
  file_size: imageInfo.file_size,
1925
1975
  file_name: `/微信图片_${format(new Date(), 'yyyyMMddHHmmss')}.png`,
1926
1976
  }
1927
- const res = await this.postData({
1928
- path: '/cloud/cdn_download',
1929
- data: params,
1930
- })
1931
- if (res.url) {
1977
+ const res = await this.retryDownload(() => this.postData({ path: '/cloud/cdn_download', data: params }))
1978
+ if (res?.url) {
1932
1979
  return encodeURI(res.url) || ''
1933
- } else {
1934
- console.log('downloadImage error:', res, params)
1935
- return ''
1936
1980
  }
1981
+ log.error(PRE, 'downloadImage failed after %d retries, params: %s', 5, JSON.stringify(params))
1982
+ return ''
1937
1983
  }
1938
1984
 
1939
1985
  public async downloadWWMedia (imageInfo: ImageMessagePayload, type: number): Promise<any> {
@@ -1949,16 +1995,12 @@ class Client extends EventEmitter {
1949
1995
  file_name: typeNameMap[type],
1950
1996
  fast_download: false,
1951
1997
  }
1952
- const res = await this.postData({
1953
- path: '/cloud/cdn_download_wwfile',
1954
- data: params,
1955
- })
1956
- if (res.url) {
1998
+ const res = await this.retryDownload(() => this.postData({ path: '/cloud/cdn_download_wwfile', data: params }))
1999
+ if (res?.url) {
1957
2000
  return encodeURI(res.url) || ''
1958
- } else {
1959
- console.log('downloadWWMedia error:', res, params)
1960
- return ''
1961
2001
  }
2002
+ log.error(PRE, 'downloadWWMedia failed after %d retries, params: %s', 5, JSON.stringify(params))
2003
+ return ''
1962
2004
  }
1963
2005
 
1964
2006
  /**
@@ -1973,17 +2015,12 @@ class Client extends EventEmitter {
1973
2015
  file_size: imageInfo.file_size,
1974
2016
  file_name: '/' + sanitizeFilename(imageInfo.file_name),
1975
2017
  }
1976
- const res = await this.postData({
1977
- path: '/cloud/cdn_download',
1978
- data: params,
1979
- })
1980
-
1981
- if (res.url) {
2018
+ const res = await this.retryDownload(() => this.postData({ path: '/cloud/cdn_download', data: params }))
2019
+ if (res?.url) {
1982
2020
  return encodeURI(res.url) || ''
1983
- } else {
1984
- console.log('downloadFile error:', res, params)
1985
- return ''
1986
2021
  }
2022
+ log.error(PRE, 'downloadFile failed after %d retries, params: %s', 5, JSON.stringify(params))
2023
+ return ''
1987
2024
  }
1988
2025
 
1989
2026
  /**
@@ -2002,17 +2039,12 @@ class Client extends EventEmitter {
2002
2039
  file_name: '/voice.mp3',
2003
2040
  to_mp3: true,
2004
2041
  }
2005
- const res = await this.postData({
2006
- path: '/cloud/download_voice',
2007
- data: params,
2008
- })
2009
-
2010
- if (res.url) {
2042
+ const res = await this.retryDownload(() => this.postData({ path: '/cloud/download_voice', data: params }))
2043
+ if (res?.url) {
2011
2044
  return encodeURI(res.url) || ''
2012
- } else {
2013
- console.log('downloadAudio error:', res, params)
2014
- return ''
2015
2045
  }
2046
+ log.error(PRE, 'downloadAudio failed after %d retries, params: %s', 5, JSON.stringify(params))
2047
+ return ''
2016
2048
  }
2017
2049
 
2018
2050
  /**
@@ -2027,18 +2059,12 @@ class Client extends EventEmitter {
2027
2059
  file_size: videoInfo.file_size,
2028
2060
  file_name: `/微信视频_${format(new Date(), 'yyyyMMddHHmmss')}.mp4`,
2029
2061
  }
2030
-
2031
- const res = await this.postData({
2032
- path: '/cloud/cdn_download',
2033
- data: params,
2034
- })
2035
-
2036
- if (res.url) {
2062
+ const res = await this.retryDownload(() => this.postData({ path: '/cloud/cdn_download', data: params }))
2063
+ if (res?.url) {
2037
2064
  return encodeURI(res.url) || ''
2038
- } else {
2039
- console.log('downloadVideo error:', res, params)
2040
- return ''
2041
2065
  }
2066
+ log.error(PRE, 'downloadVideo failed after %d retries, params: %s', 5, JSON.stringify(params))
2067
+ return ''
2042
2068
  }
2043
2069
 
2044
2070
  /**
@@ -2114,7 +2140,7 @@ class Client extends EventEmitter {
2114
2140
  return {
2115
2141
  id: res?.newMsgId || '',
2116
2142
  timeStamp: res?.createTime || getUnixTime(new Date()),
2117
- msgType: WechatMessageType.Image,
2143
+ msgType: WechatMessageType.Video,
2118
2144
  talkerId: selfId || '',
2119
2145
  text: url,
2120
2146
  msg: url,
@@ -2154,7 +2180,7 @@ class Client extends EventEmitter {
2154
2180
  return {
2155
2181
  id: res?.newMsgId || '',
2156
2182
  timeStamp: res?.createTime || '',
2157
- msgType: WechatMessageType.Image,
2183
+ msgType: WechatMessageType.File,
2158
2184
  talkerId: selfId || '',
2159
2185
  text: url,
2160
2186
  msg: url,
@@ -2648,9 +2674,8 @@ class Client extends EventEmitter {
2648
2674
  const imageBaseInfo = await getImageInfo(image)
2649
2675
  imageInfo.push({ ...res, ...imageBaseInfo })
2650
2676
  }
2651
- xmlContent = genImageSnsXml(wxid, momentInfo.content, imageInfo, momentInfo.location)
2652
-
2653
2677
  }
2678
+ xmlContent = genImageSnsXml(wxid, momentInfo.content, imageInfo, momentInfo.location)
2654
2679
  } else if (momentInfo.videoUrl) {
2655
2680
  const mediaInfo: any = await this.uploadSnsVideo(momentInfo.videoUrl)
2656
2681
  if (mediaInfo) {