wechaty-puppet-matrix 0.0.12 → 0.0.14

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.
@@ -392,20 +392,23 @@ async function getImageInfo (imageUrl: string) {
392
392
  '.wechaty',
393
393
  'puppet-matrix-cache',
394
394
  path.sep,
395
- 'temp_image_' + Date.now() + path.extname(imageUrl),
396
- path.sep,
397
395
  )
398
396
  const baseDirExist = await fs.pathExists(tempFilePath)
399
397
  if (!baseDirExist) {
400
398
  await fs.mkdirp(tempFilePath)
401
399
  }
402
- await fs.writeFile(tempFilePath, response.data)
400
+ const finalFilePath = path.join(
401
+ tempFilePath,
402
+ 'temp_image_' + Date.now() + path.extname(imageUrl),
403
+ )
404
+
405
+ await fs.writeFile(finalFilePath, response.data)
403
406
 
404
407
  // 4. 获取图片宽高
405
- const dimensions = imageSize(tempFilePath)
408
+ const dimensions = imageSize(finalFilePath)
406
409
 
407
410
  // 5. 删除临时文件
408
- await fs.unlink(tempFilePath)
411
+ await fs.unlink(finalFilePath)
409
412
 
410
413
  return {
411
414
  file_size: fileSize, // 文件大小(字节)
@@ -419,59 +422,81 @@ async function getImageInfo (imageUrl: string) {
419
422
  }
420
423
  }
421
424
 
422
- const genTextSnsXml = (wxid: string, content: string): string => {
425
+ function splitAddress (address = '') {
426
+ if (!address) {
427
+ return { province: '', city: '', area: '' }
428
+ }
429
+
430
+ let province: any = ''
431
+ let city: any = ''
432
+ let area: any = ''
433
+
434
+ // 省级行政区划
435
+ const provinceRegex = /^(.*?(省|自治区|市))|^(.*?)(北京|天津|上海|重庆)市?$/
436
+ const provinceMatch = address.match(provinceRegex)
437
+ if (provinceMatch) {
438
+ province = provinceMatch[1] || provinceMatch[3]
439
+ address = address.replace(province, '')
440
+ }
441
+
442
+ // 市级行政区划
443
+ const cityRegex = /^(.*?(市|自治州|地区|盟))|^([^\u4e00-\u9fa5]*)(北京|天津|上海|重庆)$/
444
+ const cityMatch = address.match(cityRegex)
445
+ if (cityMatch) {
446
+ city = cityMatch[1] || cityMatch[3]
447
+ address = address.replace(city, '')
448
+ }
449
+
450
+ // 区/县级行政区划
451
+ const areaRegex = /^(.*?(区|县|市|旗|自治县|林区|特区))|^(.*?(街道|镇|乡))|^([^\u4e00-\u9fa5]*)/
452
+ const areaMatch = address.match(areaRegex)
453
+ if (areaMatch) {
454
+ area = areaMatch[1] || areaMatch[3] || areaMatch[4]
455
+ address = address.replace(area, '')
456
+ }
457
+
458
+ return { province, city, area }
459
+ }
460
+
461
+ const genTextPosSnsXml = (wxid: string, content: string, location: any): string => {
462
+ const addressInfo: any = location ? splitAddress(location.address) : {}
423
463
  const xmlTemplate = `
424
464
  <TimelineObject>
425
- <id><![CDATA[0]]></id>
426
- <username><![CDATA[${wxid}]]></username>
427
- <createTime><![CDATA[${Math.floor(Date.now() / 1000)}]]></createTime>
428
- <contentDescShowType>0</contentDescShowType>
429
- <contentDescScene>0</contentDescScene>
430
- <private><![CDATA[0]]></private>
431
- <contentDesc><![CDATA[${content}]]></contentDesc>
432
- <contentattr><![CDATA[0]]></contentattr>
433
- <sourceUserName></sourceUserName>
434
- <sourceNickName></sourceNickName>
435
- <statisticsData></statisticsData>
436
- <weappInfo>
437
- <appUserName></appUserName>
438
- <pagePath></pagePath>
439
- <version><![CDATA[0]]></version>
440
- <isHidden>0</isHidden>
441
- <debugMode><![CDATA[0]]></debugMode>
442
- <shareActionId></shareActionId>
443
- <isGame><![CDATA[0]]></isGame>
444
- <messageExtraData></messageExtraData>
445
- <subType><![CDATA[0]]></subType>
446
- <preloadResources></preloadResources>
447
- </weappInfo>
448
- <canvasInfoXml></canvasInfoXml>
449
- <ContentObject>
450
- <contentStyle><![CDATA[2]]></contentStyle>
451
- <contentSubStyle><![CDATA[0]]></contentSubStyle>
452
- <title></title>
453
- <description></description>
454
- <contentUrl></contentUrl>
455
- </ContentObject>
456
- <actionInfo>
457
- <appMsg>
458
- <mediaTagName></mediaTagName>
459
- <messageExt></messageExt>
460
- <messageAction></messageAction>
461
- </appMsg>
462
- </actionInfo>
463
- <appInfo><id></id></appInfo>
464
- <publicUserName></publicUserName>
465
- <streamvideo>
466
- <streamvideourl></streamvideourl>
467
- <streamvideothumburl></streamvideothumburl>
468
- <streamvideoweburl></streamvideoweburl>
469
- </streamvideo>
470
- </TimelineObject>`.replace(/\s+/g, '')
465
+ <id>0</id>
466
+ <username><![CDATA[${wxid}]]></username>
467
+ <createTime><![CDATA[${Math.floor(Date.now() / 1000)}]]></createTime>
468
+ <contentDesc><![CDATA[${content}]]></contentDesc>
469
+ <contentDescShowType>0</contentDescShowType>
470
+ <contentDescScene>3</contentDescScene>
471
+ <private>0</private>
472
+ <sightFolded>0</sightFolded>
473
+ <showFlag>0</showFlag>${location ? `<location city="${addressInfo.city}" longitude="${location.longitude}" latitude="${location.latitude}" poiName="${location.name}" poiAddress="${location.address}" poiScale="11.000000" poiInfoUrl="" poiClassifyId="${location.poiId}" poiClassifyType="1" poiClickableStatus="0" buildingId="0" floorName=""/>` : ''}
474
+ <appInfo>
475
+ <id/>
476
+ <version/>
477
+ <appName/>
478
+ <installUrl/>
479
+ <fromUrl/>
480
+ <isForceUpdate>0</isForceUpdate>
481
+ <isHidden>0</isHidden>
482
+ </appInfo>
483
+ <sourceUserName/>
484
+ <sourceNickName/>
485
+ <statisticsData/>
486
+ <statExtStr/>
487
+ <ContentObject>
488
+ <contentStyle>2</contentStyle>
489
+ <title/>
490
+ <description/>
491
+ <mediaList/>
492
+ </ContentObject>
493
+ </TimelineObject>`
471
494
  return xmlTemplate
472
495
  }
473
496
 
474
- const genVideoSnsXml = (wxid: string, content: string, media: Media): string => {
497
+ const genVideoSnsXml = (wxid: string, content: string, media: Media, location: any): string => {
498
+ const addressInfo: any = location ? splitAddress(location.address) : {}
499
+
475
500
  const xmlTemplate = `
476
501
  <TimelineObject>
477
502
  <id>0</id>
@@ -482,7 +507,7 @@ const genVideoSnsXml = (wxid: string, content: string, media: Media): string =>
482
507
  <contentDescScene>0</contentDescScene>
483
508
  <private>0</private>
484
509
  <sightFolded>0</sightFolded>
485
- <showFlag>0</showFlag>
510
+ <showFlag>0</showFlag>${location ? `<location city="${addressInfo.city}" longitude="${location.longitude}" latitude="${location.latitude}" poiName="${location.name}" poiAddress="${location.address}" poiScale="11.000000" poiInfoUrl="" poiClassifyId="${location.poiId}" poiClassifyType="1" poiClickableStatus="0" buildingId="0" floorName=""/>` : ''}
486
511
  <appInfo>
487
512
  <id></id>
488
513
  <version></version>
@@ -505,23 +530,24 @@ const genVideoSnsXml = (wxid: string, content: string, media: Media): string =>
505
530
  <id>0</id>
506
531
  <type>6</type>
507
532
  <title></title>
508
- <description>测试</description>
533
+ <description>${content}</description>
509
534
  <private>0</private>
510
535
  <userData></userData>
511
536
  <subType>0</subType>
512
537
  <videoSize width="${media.video_width}" height="${media.video_height}"/>
513
- <url type="1" md5="951a7d7864d685a92fd2624155794bf9" videomd5="577f55635faf44f595a69ded26d87bcc">${media.file_url}</url>
514
- <thumb type="1">${media.thumb_url}</thumb>
538
+ <url type="1" md5="951a7d7864d685a92fd2624155794bf9" videomd5="577f55635faf44f595a69ded26d87bcc">${media.file_url.replaceAll('&', '&amp;')}</url>
539
+ <thumb type="1">${media.thumb_url.replaceAll('&', '&amp;')}</thumb>
515
540
  <size width="${media.thumb_width}.000000" height="${media.thumb_height}.000000" totalSize="${media.file_size}"/>
516
541
  <videoDuration>${media.video_duration}.000000</videoDuration>
517
542
  </media>
518
543
  </mediaList>
544
+ <contentUrl>https://support.weixin.qq.com/cgi-bin/mmsupport-bin/readtemplate?t=page/common_page__upgrade&amp;v=1</contentUrl>
519
545
  </ContentObject>
520
546
  </TimelineObject>`
521
547
  return xmlTemplate
522
548
  }
523
549
 
524
- const genImageSnsXml = (wxid: string, contentDesc: string, mediaList: Media[]): string => {
550
+ const genImageSnsXml = (wxid: string, contentDesc: string, mediaList: Media[], location: any): string => {
525
551
  const mediaTemplate = (media: Media) => `
526
552
  <media>
527
553
  <id><![CDATA[0]]></id>
@@ -536,7 +562,7 @@ const genImageSnsXml = (wxid: string, contentDesc: string, mediaList: Media[]):
536
562
  </media>`
537
563
 
538
564
  const mediaString = mediaList.map(media => mediaTemplate(media)).join('')
539
-
565
+ const addressInfo: any = location ? splitAddress(location.address) : {}
540
566
  const xmlTemplate = `
541
567
  <TimelineObject>
542
568
  <id><![CDATA[0]]></id>
@@ -563,6 +589,7 @@ const genImageSnsXml = (wxid: string, contentDesc: string, mediaList: Media[]):
563
589
  <preloadResources></preloadResources>
564
590
  </weappInfo>
565
591
  <canvasInfoXml></canvasInfoXml>
592
+ ${location ? `<location city="${addressInfo.city}" longitude="${location.longitude}" latitude="${location.latitude}" poiName="${location.name}" poiAddress="${location.address}" poiScale="11.000000" poiInfoUrl="" poiClassifyId="${location.poiId}" poiClassifyType="1" poiClickableStatus="0" buildingId="0" floorName=""/>` : ''}
566
593
  <ContentObject>
567
594
  <contentStyle><![CDATA[1]]></contentStyle>
568
595
  <contentSubStyle><![CDATA[0]]></contentSubStyle>
@@ -2240,6 +2267,33 @@ class Client extends EventEmitter {
2240
2267
  })
2241
2268
  }
2242
2269
 
2270
+ /**
2271
+ *
2272
+ * @returns
2273
+ */
2274
+ public async creatRoom (contactIdList: string[], topic: string): Promise<string> {
2275
+ try {
2276
+ const res = await this.postData({
2277
+ path: '/room/create_chatroom',
2278
+ data: {
2279
+ username_list: contactIdList,
2280
+ },
2281
+ })
2282
+ if (res?.baseResponse?.ret) {
2283
+ log.error('creatRoom error: %s', JSON.stringify(res.baseResponse))
2284
+ }
2285
+ const roomId = res.chatRoomName.string
2286
+ if (topic) {
2287
+ void this.setGroupName(roomId, topic)
2288
+ }
2289
+ return roomId
2290
+
2291
+ } catch (error) {
2292
+ log.error(PRE, 'sendContactCard(%s, %s): %s', contactIdList, topic, error)
2293
+ return ''
2294
+ }
2295
+ }
2296
+
2243
2297
  /**
2244
2298
  * 修改群名
2245
2299
  * @param groupId 群id
@@ -2366,7 +2420,7 @@ class Client extends EventEmitter {
2366
2420
  url,
2367
2421
  },
2368
2422
  })
2369
- log.info('uploadSnsVideo result:%s', res.data)
2423
+ log.verbose('uploadSnsVideo result: %s', JSON.stringify(res))
2370
2424
  if (res?.errcode !== 0) {
2371
2425
  log.error('uploadSnsVideo error: %s', JSON.stringify(res))
2372
2426
  }
@@ -2397,17 +2451,18 @@ class Client extends EventEmitter {
2397
2451
  const imageBaseInfo = await getImageInfo(image)
2398
2452
  imageInfo.push({ ...res, ...imageBaseInfo })
2399
2453
  }
2400
- xmlContent = genImageSnsXml(wxid, momentInfo.content, imageInfo)
2454
+ xmlContent = genImageSnsXml(wxid, momentInfo.content, imageInfo, momentInfo.location)
2401
2455
 
2402
2456
  }
2403
2457
  } else if (momentInfo.videoUrl) {
2404
2458
  const mediaInfo: any = await this.uploadSnsVideo(momentInfo.videoUrl)
2405
2459
  if (mediaInfo) {
2406
2460
  const thumbInfo = await getImageInfo(mediaInfo.thumb_url)
2407
- xmlContent = genVideoSnsXml(wxid, momentInfo.content, { ...mediaInfo, ...thumbInfo })
2461
+ xmlContent = genVideoSnsXml(wxid, momentInfo.content, { ...mediaInfo, ...thumbInfo }, momentInfo.location)
2408
2462
  }
2409
2463
  } else {
2410
- xmlContent = genTextSnsXml(wxid, momentInfo.content)
2464
+ xmlContent = genTextPosSnsXml(wxid, momentInfo.content, momentInfo.location)
2465
+
2411
2466
  }
2412
2467
  return await this.sendMoment(xmlContent)
2413
2468
  } catch (error) {
@@ -21,6 +21,7 @@ import { parseEmotionMessagePayload } from './matrix/messages/message-emotion.js
21
21
  import { ImageMessagePayload, parseImageMessagePayload } from './matrix/messages/message-image.js'
22
22
  import { parseAudioMessagePayload, AudioMessagePayload } from './matrix/messages/message-audio.js'
23
23
  import { parseVideoMessagePayload, VideoMessagePayload } from './matrix/messages/message-video.js'
24
+ import { parseLocationMessagePayload } from './matrix/messages/message-location.js'
24
25
  import { CachedPromiseFunc } from './matrix/utils/cached-promise.js'
25
26
  import { engineMessageToWechaty } from './matrix/schema-mapper/message.js'
26
27
  import { engineContactToWechaty } from './matrix/schema-mapper/contact.js'
@@ -758,6 +759,23 @@ class PuppetMatrix extends PUPPET.Puppet {
758
759
  }
759
760
  }
760
761
 
762
+ /**
763
+ * 解析h5链接
764
+ * @param messageId
765
+ */
766
+ override async messageLocation (messageId: string) : Promise<PUPPET.payloads.Location> {
767
+ const rawPayload = await this.messageRawPayload(messageId)
768
+ const payload = await this.messageRawPayloadParser(rawPayload)
769
+
770
+ if (payload.type !== PUPPET.types.Message.Location) {
771
+ throw new Error('Can not get location from non location payload')
772
+ }
773
+
774
+ // FIXME: thumb may not in appPayload.thumburl, but in appPayload.appAttachPayload
775
+ const locationPayload = await parseLocationMessagePayload(rawPayload.msg) as PUPPET.payloads.Location
776
+ return locationPayload
777
+ }
778
+
761
779
  /****************************************************************************
762
780
  * send message
763
781
  ***************************************************************************/
@@ -974,12 +992,12 @@ class PuppetMatrix extends PUPPET.Puppet {
974
992
  }
975
993
  }
976
994
 
977
- // 创建群聊 暂不支持
995
+ // 创建群聊
978
996
  override async roomCreate (
979
997
  contactIdList : string[],
980
998
  topic : string,
981
999
  ): Promise<string> {
982
- return PUPPET.throwUnsupportedError(contactIdList, topic)
1000
+ return this._client?.creatRoom(contactIdList, topic) || ''
983
1001
  }
984
1002
 
985
1003
  // 删除群聊 暂不支持
@@ -1000,7 +1018,7 @@ class PuppetMatrix extends PUPPET.Puppet {
1000
1018
  return PUPPET.throwUnsupportedError(roomId)
1001
1019
  }
1002
1020
 
1003
- // 机器人退出群聊 暂不支持
1021
+ // 机器人退出群聊
1004
1022
  override async roomQuit (roomId: string): Promise<void> {
1005
1023
  return this._client?.roomQuit(roomId)
1006
1024
  }
@@ -1209,7 +1227,7 @@ class PuppetMatrix extends PUPPET.Puppet {
1209
1227
  if (fileBox.mediaType.startsWith('image/')) {
1210
1228
  momentInfo.imageUrls.push(fileUrl)
1211
1229
  } else if (fileType.includes('video/mp4') || fileType.includes('.mp4')) {
1212
- momentInfo.videoInfo = fileUrl
1230
+ momentInfo.videoUrl = fileUrl
1213
1231
  }
1214
1232
  }
1215
1233
  break