wechaty-puppet-matrix 0.0.9 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/cjs/src/matrix/cache-manager.js +1 -1
  2. package/dist/cjs/src/matrix/service/request.d.ts +63 -0
  3. package/dist/cjs/src/matrix/service/request.d.ts.map +1 -1
  4. package/dist/cjs/src/matrix/service/request.js +407 -4
  5. package/dist/cjs/src/matrix/utils/index.d.ts +3 -0
  6. package/dist/cjs/src/matrix/utils/index.d.ts.map +1 -1
  7. package/dist/cjs/src/matrix/utils/index.js +31 -0
  8. package/dist/cjs/src/puppet-matrix.d.ts +6 -0
  9. package/dist/cjs/src/puppet-matrix.d.ts.map +1 -1
  10. package/dist/cjs/src/puppet-matrix.js +84 -0
  11. package/dist/cjs/src/utils/normalize-filebox.d.ts +6 -0
  12. package/dist/cjs/src/utils/normalize-filebox.d.ts.map +1 -0
  13. package/dist/cjs/src/utils/normalize-filebox.js +46 -0
  14. package/dist/cjs/src/utils/sns-xml-generator.d.ts +19 -0
  15. package/dist/cjs/src/utils/sns-xml-generator.d.ts.map +1 -0
  16. package/dist/cjs/src/utils/sns-xml-generator.js +171 -0
  17. package/dist/esm/src/matrix/cache-manager.js +1 -1
  18. package/dist/esm/src/matrix/service/request.d.ts +63 -0
  19. package/dist/esm/src/matrix/service/request.d.ts.map +1 -1
  20. package/dist/esm/src/matrix/service/request.js +407 -4
  21. package/dist/esm/src/matrix/utils/index.d.ts +3 -0
  22. package/dist/esm/src/matrix/utils/index.d.ts.map +1 -1
  23. package/dist/esm/src/matrix/utils/index.js +28 -0
  24. package/dist/esm/src/puppet-matrix.d.ts +6 -0
  25. package/dist/esm/src/puppet-matrix.d.ts.map +1 -1
  26. package/dist/esm/src/puppet-matrix.js +85 -1
  27. package/dist/esm/src/utils/normalize-filebox.d.ts +6 -0
  28. package/dist/esm/src/utils/normalize-filebox.d.ts.map +1 -0
  29. package/dist/esm/src/utils/normalize-filebox.js +42 -0
  30. package/dist/esm/src/utils/sns-xml-generator.d.ts +19 -0
  31. package/dist/esm/src/utils/sns-xml-generator.d.ts.map +1 -0
  32. package/dist/esm/src/utils/sns-xml-generator.js +165 -0
  33. package/package.json +6 -5
  34. package/src/matrix/cache-manager.ts +1 -1
  35. package/src/matrix/service/request.ts +523 -8
  36. package/src/matrix/utils/index.ts +62 -0
  37. package/src/puppet-matrix.ts +112 -1
  38. package/src/utils/normalize-filebox.ts +90 -0
  39. package/src/utils/sns-xml-generator.ts +184 -0
@@ -7,7 +7,10 @@ import * as PUPPET from '@juzi/wechaty-puppet'
7
7
  import { format, getUnixTime } from 'date-fns'
8
8
  import { xmlToJson } from '../utils/xml-to-json.js'
9
9
  import { isRoomId } from '../utils/is-type.js'
10
-
10
+ import imageSize from 'image-size'
11
+ import fs from 'fs-extra'
12
+ import os from 'os'
13
+ import path from 'path'
11
14
  const PRE = '[PuppetMatrix]'
12
15
 
13
16
  /**
@@ -252,6 +255,25 @@ export interface ImageMessagePayload {
252
255
  file_name?: string;
253
256
  }
254
257
 
258
+ export interface SnsImageMessagePayload {
259
+ errcode: number;
260
+ file_key: string;
261
+ file_url: string;
262
+ thumb_url: string;
263
+ }
264
+ export interface SnsVideoMessagePayload {
265
+ errcode: number;
266
+ mp4_identify: string;
267
+ file_key: string;
268
+ file_url: string;
269
+ thumb_url: string;
270
+ thumb_width: string;
271
+ thumb_height: string;
272
+ video_duration: string;
273
+ video_width: string;
274
+ video_height: string;
275
+ }
276
+
255
277
  export interface AudioMessagePayload {
256
278
  aesKey: string;
257
279
  length: string;
@@ -319,6 +341,22 @@ export interface RoomLeavPayload {
319
341
  roomId: string;
320
342
  leaveIds: string[];
321
343
  }
344
+ export interface Media {
345
+ fileId: string
346
+ file_size: number
347
+ fileType: number
348
+ video_duration: number
349
+ url: string
350
+ video_width: number
351
+ video_height: number
352
+ file_url: string
353
+ file_key: string
354
+ thumb_url: string
355
+ thumb_width: number
356
+ thumb_height: number
357
+ image_width: number
358
+ image_height: number
359
+ }
322
360
 
323
361
  async function getAtWxidList (source: string): Promise<string[]> {
324
362
  if (source) {
@@ -330,9 +368,241 @@ async function getAtWxidList (source: string): Promise<string[]> {
330
368
  }
331
369
  return []
332
370
  }
371
+
333
372
  interface ConnectionStatus {
334
373
  status: 'disconnected' | 'connected' | 'connecting'
335
374
  }
375
+
376
+ async function getImageInfo (imageUrl: string) {
377
+ try {
378
+ // 1. 下载图片
379
+ const response = await axios({
380
+ method: 'get',
381
+ url: imageUrl,
382
+ responseType: 'arraybuffer',
383
+ })
384
+
385
+ // 2. 获取文件大小(字节数)
386
+ const fileSize = response.data.length
387
+
388
+ // 3. 创建临时文件保存图片
389
+ const tempFilePath = path.join(
390
+ os.homedir(),
391
+ path.sep,
392
+ '.wechaty',
393
+ 'puppet-matrix-cache',
394
+ path.sep,
395
+ 'temp_image_' + Date.now() + path.extname(imageUrl),
396
+ path.sep,
397
+ )
398
+ const baseDirExist = await fs.pathExists(tempFilePath)
399
+ if (!baseDirExist) {
400
+ await fs.mkdirp(tempFilePath)
401
+ }
402
+ await fs.writeFile(tempFilePath, response.data)
403
+
404
+ // 4. 获取图片宽高
405
+ const dimensions = imageSize(tempFilePath)
406
+
407
+ // 5. 删除临时文件
408
+ await fs.unlink(tempFilePath)
409
+
410
+ return {
411
+ file_size: fileSize, // 文件大小(字节)
412
+ image_width: dimensions.width,
413
+ image_height: dimensions.height,
414
+ file_type: dimensions.type,
415
+ }
416
+ } catch (error) {
417
+ console.error('获取图片信息错误:', error)
418
+ throw error
419
+ }
420
+ }
421
+
422
+ const genTextSnsXml = (wxid: string, content: string): string => {
423
+ const xmlTemplate = `
424
+ <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, '')
471
+ return xmlTemplate
472
+ }
473
+
474
+ const genVideoSnsXml = (wxid: string, content: string, media: Media): string => {
475
+ const xmlTemplate = `
476
+ <TimelineObject>
477
+ <id>0</id>
478
+ <username>${wxid}</username>
479
+ <createTime>${Math.floor(Date.now() / 1000)}</createTime>
480
+ <contentDesc>${content}</contentDesc>
481
+ <contentDescShowType>0</contentDescShowType>
482
+ <contentDescScene>0</contentDescScene>
483
+ <private>0</private>
484
+ <sightFolded>0</sightFolded>
485
+ <showFlag>0</showFlag>
486
+ <appInfo>
487
+ <id></id>
488
+ <version></version>
489
+ <appName></appName>
490
+ <installUrl></installUrl>
491
+ <fromUrl></fromUrl>
492
+ <isForceUpdate>0</isForceUpdate>
493
+ <isHidden>0</isHidden>
494
+ </appInfo>
495
+ <sourceUserName></sourceUserName>
496
+ <sourceNickName></sourceNickName>
497
+ <statisticsData></statisticsData>
498
+ <statExtStr></statExtStr>
499
+ <ContentObject>
500
+ <contentStyle>15</contentStyle>
501
+ <title></title>
502
+ <description>Sight</description>
503
+ <mediaList>
504
+ <media>
505
+ <id>0</id>
506
+ <type>6</type>
507
+ <title></title>
508
+ <description>测试</description>
509
+ <private>0</private>
510
+ <userData></userData>
511
+ <subType>0</subType>
512
+ <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>
515
+ <size width="${media.thumb_width}.000000" height="${media.thumb_height}.000000" totalSize="${media.file_size}"/>
516
+ <videoDuration>${media.video_duration}.000000</videoDuration>
517
+ </media>
518
+ </mediaList>
519
+ </ContentObject>
520
+ </TimelineObject>`
521
+ return xmlTemplate
522
+ }
523
+
524
+ const genImageSnsXml = (wxid: string, contentDesc: string, mediaList: Media[]): string => {
525
+ const mediaTemplate = (media: Media) => `
526
+ <media>
527
+ <id><![CDATA[0]]></id>
528
+ <type><![CDATA[2]]></type>
529
+ <title></title>
530
+ <description></description>
531
+ <private><![CDATA[0]]></private>
532
+ <url type="1" md5="951a7d7864d685a92fd2624155794bf9"><![CDATA[${media.file_url}]]></url>
533
+ <thumb type="1"><![CDATA[${media.thumb_url}]]></thumb>
534
+ <videoDuration><![CDATA[0.0]]></videoDuration>
535
+ <size totalSize="${media.file_size}" width="${media.image_width}" height="${media.image_height}"></size>
536
+ </media>`
537
+
538
+ const mediaString = mediaList.map(media => mediaTemplate(media)).join('')
539
+
540
+ const xmlTemplate = `
541
+ <TimelineObject>
542
+ <id><![CDATA[0]]></id>
543
+ <username><![CDATA[${wxid}]]></username>
544
+ <createTime><![CDATA[${Math.floor(Date.now() / 1000)}]]></createTime>
545
+ <contentDescShowType>0</contentDescShowType>
546
+ <contentDescScene>0</contentDescScene>
547
+ <private><![CDATA[0]]></private>
548
+ <contentDesc><![CDATA[${contentDesc}]]></contentDesc>
549
+ <contentattr><![CDATA[0]]></contentattr>
550
+ <sourceUserName></sourceUserName>
551
+ <sourceNickName></sourceNickName>
552
+ <statisticsData></statisticsData>
553
+ <weappInfo>
554
+ <appUserName></appUserName>
555
+ <pagePath></pagePath>
556
+ <version><![CDATA[0]]></version>
557
+ <isHidden>0</isHidden>
558
+ <debugMode><![CDATA[0]]></debugMode>
559
+ <shareActionId></shareActionId>
560
+ <isGame><![CDATA[0]]></isGame>
561
+ <messageExtraData></messageExtraData>
562
+ <subType><![CDATA[0]]></subType>
563
+ <preloadResources></preloadResources>
564
+ </weappInfo>
565
+ <canvasInfoXml></canvasInfoXml>
566
+ <ContentObject>
567
+ <contentStyle><![CDATA[1]]></contentStyle>
568
+ <contentSubStyle><![CDATA[0]]></contentSubStyle>
569
+ <title></title>
570
+ <description></description>
571
+ <contentUrl></contentUrl>
572
+ <mediaList>${mediaString}</mediaList>
573
+ </ContentObject>
574
+ <actionInfo>
575
+ <appMsg>
576
+ <mediaTagName></mediaTagName>
577
+ <messageExt></messageExt>
578
+ <messageAction></messageAction>
579
+ </appMsg>
580
+ </actionInfo>
581
+ <appInfo><id></id></appInfo>
582
+ <publicUserName></publicUserName>
583
+ <streamvideo>
584
+ <streamvideourl></streamvideourl>
585
+ <streamvideothumburl></streamvideothumburl>
586
+ <streamvideoweburl></streamvideoweburl>
587
+ </streamvideo>
588
+ </TimelineObject>`
589
+ return xmlTemplate
590
+ }
591
+
592
+ export interface MomentInfo {
593
+ content: string,
594
+ mentionIdList: string[],
595
+ visibledList: string[],
596
+ imageUrls: string[],
597
+ videoUrl: string,
598
+ urlLink: null | PUPPET.payloads.UrlLink,
599
+ channel: null | PUPPET.payloads.Channel,
600
+ miniInfo: null | PUPPET.payloads.MiniProgram,
601
+ location: null | PUPPET.payloads.Location,
602
+ rootId: string,
603
+ parentId: string
604
+ }
605
+
336
606
  class Client extends EventEmitter {
337
607
 
338
608
  private readonly options: PuppetMatrixOptions
@@ -340,7 +610,11 @@ class Client extends EventEmitter {
340
610
  private readonly MAX_RECONNECT_ATTEMPTS = 10
341
611
  private readonly INITIAL_RECONNECT_DELAY = 1000 // 1秒
342
612
  private readonly MAX_RECONNECT_DELAY = 30000 // 30秒
613
+ private readonly HEARTBEAT_INTERVAL = 40 * 1000 // 1分钟心跳间隔
614
+ private readonly HEARTBEAT_TIMEOUT = 5000 // 5秒超时
343
615
  private reconnectAttempts = 0
616
+ private heartbeatTimer?: any
617
+ private heartbeatTimeoutTimer?: any
344
618
  socket: any
345
619
  server: any
346
620
  tokenInfo: any
@@ -407,7 +681,6 @@ class Client extends EventEmitter {
407
681
  log.error('Token info not available')
408
682
  return
409
683
  }
410
-
411
684
  const ws = new WebSocket(
412
685
  `${this.tokenInfo.endpoint}?guid=${this.tokenInfo.guid}`,
413
686
  {
@@ -421,7 +694,7 @@ class Client extends EventEmitter {
421
694
  ws.once('open', () => {
422
695
  this.connectionStatus.status = 'connected'
423
696
  this.reconnectAttempts = 0
424
- log.silly('WebSocket connection opened')
697
+ log.info('WebSocket connection opened')
425
698
  resolve()
426
699
  })
427
700
 
@@ -435,17 +708,21 @@ class Client extends EventEmitter {
435
708
  reject(new Error(`WebSocket closed: ${code} - ${reason}`))
436
709
  })
437
710
  })
711
+ this.socket = ws
438
712
 
439
713
  // 设置事件处理器
440
714
  this.setupWebSocketListeners(ws)
441
-
442
- this.socket = ws
443
715
  }
444
716
 
445
717
  private setupWebSocketListeners (ws: WebSocket): void {
446
718
  // 消息处理
447
719
  ws.on('message', (data: string) => {
720
+ this.resetHeartbeatTimeout() // 收到任何消息都重置心跳超时
448
721
  log.silly(PRE, 'initWebSocket() ws.on(message): %s', data)
722
+ if (data.toString() === 'pong') {
723
+ log.info('Received heartbeat')
724
+ return
725
+ }
449
726
  try {
450
727
  const payload = JSON.parse(data)
451
728
  log.info('Received payload', JSON.stringify(payload))
@@ -471,14 +748,69 @@ class Client extends EventEmitter {
471
748
 
472
749
  // 关闭处理
473
750
  ws.on('close', (code:any, reason: any) => {
751
+ this.stopHeartbeat()
474
752
  void this.handleWebSocketClose(code, reason)
475
753
  })
754
+ // 启动心跳
755
+ this.startHeartbeat()
476
756
  }
477
757
 
478
- private async handleWebSocketClose (code?: number, reason?: Buffer) {
758
+ private startHeartbeat (): void {
759
+ this.stopHeartbeat() // 确保清理现有定时器
760
+ this.socket?.send('ping')
761
+ this.heartbeatTimer = setInterval(() => {
762
+ if (this.socket?.readyState === WebSocket.OPEN) {
763
+ try {
764
+ this.socket.send('ping')
765
+ this.setHeartbeatTimeout()
766
+ } catch (error) {
767
+ log.error('Failed to send heartbeat:', error)
768
+ void this.handleWebSocketClose(1006, 'Heartbeat failed')
769
+ }
770
+ }
771
+ }, this.HEARTBEAT_INTERVAL)
772
+ }
773
+
774
+ private stopHeartbeat (): void {
775
+ if (this.heartbeatTimer) {
776
+ // @ts-ignore
777
+ clearInterval(this.heartbeatTimer)
778
+ this.heartbeatTimer = undefined
779
+ }
780
+ if (this.heartbeatTimeoutTimer) {
781
+ // @ts-ignore
782
+ clearTimeout(this.heartbeatTimeoutTimer)
783
+ this.heartbeatTimeoutTimer = undefined
784
+ }
785
+ }
786
+
787
+ private setHeartbeatTimeout (): void {
788
+ if (this.heartbeatTimeoutTimer) {
789
+ // @ts-ignore
790
+ clearTimeout(this.heartbeatTimeoutTimer)
791
+ }
792
+ this.heartbeatTimeoutTimer = setTimeout(() => {
793
+ log.warn('Heartbeat timeout, reconnecting...')
794
+ void this.handleWebSocketClose(1006, 'Heartbeat timeout')
795
+ }, this.HEARTBEAT_TIMEOUT)
796
+ }
797
+
798
+ private resetHeartbeatTimeout (): void {
799
+ if (this.heartbeatTimeoutTimer) {
800
+ // @ts-ignore
801
+ clearTimeout(this.heartbeatTimeoutTimer)
802
+ this.heartbeatTimeoutTimer = undefined
803
+ }
804
+ }
805
+
806
+ private async handleWebSocketClose (code?: number, reason?: Buffer | String) {
479
807
  this.connectionStatus.status = 'disconnected'
480
808
  log.warn(`WebSocket closed: Code ${code}, Reason ${reason}`)
481
- this.socket = null
809
+ if (this.socket) {
810
+ this.socket.close()
811
+ this.socket = null
812
+ }
813
+
482
814
  // 触发重连
483
815
  await this.reconnect()
484
816
 
@@ -1989,7 +2321,7 @@ class Client extends EventEmitter {
1989
2321
  announcement: content,
1990
2322
  },
1991
2323
  })
1992
- if (res.baseResponse.ret) {
2324
+ if (res?.baseResponse?.ret) {
1993
2325
  log.error('sendAnnouncement error: %s', JSON.stringify(res.baseResponse))
1994
2326
  }
1995
2327
  } catch (error) {
@@ -1997,6 +2329,189 @@ class Client extends EventEmitter {
1997
2329
  }
1998
2330
  }
1999
2331
 
2332
+ /**
2333
+ * 上传朋友圈图片
2334
+ * @param url
2335
+ * @returns
2336
+ */
2337
+ public async uploadSnsImage (url: string): Promise<SnsImageMessagePayload | void> {
2338
+ try {
2339
+ const res = await this.postData({
2340
+ path: '/cloud/cdn_upload_sns_image',
2341
+ data: {
2342
+ url,
2343
+ },
2344
+ })
2345
+ if (res?.errcode !== 0) {
2346
+ log.error('uploadSnsImage error: %s', JSON.stringify(res))
2347
+ return
2348
+ }
2349
+ return res as SnsImageMessagePayload
2350
+ } catch (error) {
2351
+ log.error(PRE, 'uploadSnsImage(%s): %s', url, error)
2352
+
2353
+ }
2354
+ }
2355
+
2356
+ /**
2357
+ * 上传朋友视频
2358
+ * @param url
2359
+ * @returns
2360
+ */
2361
+ public async uploadSnsVideo (url: string): Promise<SnsVideoMessagePayload | void> {
2362
+ try {
2363
+ const res = await this.postData({
2364
+ path: '/cloud/cdn_upload_sns_video',
2365
+ data: {
2366
+ url,
2367
+ },
2368
+ })
2369
+ log.info('uploadSnsVideo result:%s', res.data)
2370
+ if (res?.errcode !== 0) {
2371
+ log.error('uploadSnsVideo error: %s', JSON.stringify(res))
2372
+ }
2373
+ return res as SnsVideoMessagePayload
2374
+ } catch (error) {
2375
+ log.error(PRE, 'updateSnsImage(%s): %s', url, error)
2376
+ }
2377
+ }
2378
+
2379
+ /**
2380
+ * 发送朋友圈
2381
+ * @param wxid
2382
+ * @param momentInfo
2383
+ */
2384
+ public async sendSnsMoment (wxid: string, momentInfo: MomentInfo) {
2385
+ try {
2386
+ log.info('momentInfo: %s', JSON.stringify(momentInfo))
2387
+ let xmlContent = ''
2388
+ // 朋友圈评论
2389
+ if (momentInfo.parentId && momentInfo.rootId) {
2390
+ return await this.sendMomentReply(momentInfo.rootId, momentInfo.content, momentInfo.parentId === momentInfo.rootId ? '' : momentInfo.parentId)
2391
+ }
2392
+ if (momentInfo.imageUrls.length) {
2393
+ const imageInfo: any[] = []
2394
+ for (const image of momentInfo.imageUrls) {
2395
+ const res = await this.uploadSnsImage(image)
2396
+ if (res) {
2397
+ const imageBaseInfo = await getImageInfo(image)
2398
+ imageInfo.push({ ...res, ...imageBaseInfo })
2399
+ }
2400
+ xmlContent = genImageSnsXml(wxid, momentInfo.content, imageInfo)
2401
+
2402
+ }
2403
+ } else if (momentInfo.videoUrl) {
2404
+ const mediaInfo: any = await this.uploadSnsVideo(momentInfo.videoUrl)
2405
+ if (mediaInfo) {
2406
+ const thumbInfo = await getImageInfo(mediaInfo.thumb_url)
2407
+ xmlContent = genVideoSnsXml(wxid, momentInfo.content, { ...mediaInfo, ...thumbInfo })
2408
+ }
2409
+ } else {
2410
+ xmlContent = genTextSnsXml(wxid, momentInfo.content)
2411
+ }
2412
+ return await this.sendMoment(xmlContent)
2413
+ } catch (error) {
2414
+ log.error(PRE, 'sendSnsMoment(%s): %s', JSON.stringify(momentInfo), error)
2415
+ }
2416
+ }
2417
+
2418
+ public async sendMoment (content: string):Promise<string | void> {
2419
+ try {
2420
+ const res = await this.postData({
2421
+ path: '/sns/sns_post',
2422
+ data: {
2423
+ object_desc: content,
2424
+ with_user_list: [],
2425
+ block_user_list: [],
2426
+ group_user_list: [],
2427
+ group_contact_tag_id_list: [],
2428
+ black_contact_tag_id_list: [],
2429
+ },
2430
+ })
2431
+ if (res?.baseResponse?.ret !== 0) {
2432
+ log.error('sendMoment error: %s', JSON.stringify(res))
2433
+ return
2434
+ }
2435
+ log.info(PRE, 'sendMomen success: %s', res?.snsObject?.id)
2436
+ return res?.snsObject?.id
2437
+ } catch (error) {
2438
+ log.error(PRE, 'sendMoment(%s): %s', content, error)
2439
+
2440
+ }
2441
+ }
2442
+
2443
+ public async unSendMoment (objectId: string):Promise<string | void> {
2444
+ try {
2445
+ const res = await this.postData({
2446
+ path: '/sns/sns_delete',
2447
+ data: {
2448
+ object_id: objectId,
2449
+ },
2450
+ })
2451
+ if (res?.baseResponse?.ret !== 0) {
2452
+ log.error('unSendMoment error: %s', JSON.stringify(res))
2453
+ return
2454
+ }
2455
+ return objectId
2456
+ } catch (error) {
2457
+ log.error(PRE, 'unSendMoment(%s): %s', objectId, error)
2458
+ }
2459
+ }
2460
+
2461
+ /**
2462
+ * 朋友圈点赞
2463
+ * @param objectId
2464
+ * @param status 1 赞 0 取消点赞
2465
+ * @returns
2466
+ */
2467
+ public async sendMomentLike (objectId: string, status: PUPPET.types.Tap):Promise<string | void> {
2468
+ try {
2469
+ const res = await this.postData({
2470
+ path: '/sns/sns_like',
2471
+ data: {
2472
+ object_id: objectId,
2473
+ status,
2474
+ },
2475
+ })
2476
+ if (res?.baseResponse?.ret !== 0) {
2477
+ log.error('sendMomentLike error: %s', JSON.stringify(res))
2478
+ return
2479
+ }
2480
+ return res?.snsObject?.id
2481
+ } catch (error) {
2482
+ log.error(PRE, 'sendMomentLike(%s): %s', objectId, error)
2483
+
2484
+ }
2485
+ }
2486
+
2487
+ /**
2488
+ * 朋友圈回复
2489
+ * @param objectId 朋友圈id
2490
+ * @param content
2491
+ * @param commentId 评论id
2492
+ * @returns
2493
+ */
2494
+ public async sendMomentReply (objectId: string, content: string, commentId?: string):Promise<string | void> {
2495
+ try {
2496
+ const res = await this.postData({
2497
+ path: '/sns/sns_comment',
2498
+ data: {
2499
+ object_id: objectId,
2500
+ content,
2501
+ reply_comment_id: commentId || '0',
2502
+ },
2503
+ })
2504
+ if (res?.baseResponse?.ret !== 0) {
2505
+ log.error('sendMomentLike error: %s', JSON.stringify(res))
2506
+ return
2507
+ }
2508
+ return res?.snsObject?.id
2509
+ } catch (error) {
2510
+ log.error(PRE, 'sendMomentLike(%s): %s', objectId, error)
2511
+
2512
+ }
2513
+ }
2514
+
2000
2515
  }
2001
2516
 
2002
2517
  export default Client
@@ -32,3 +32,65 @@ export function getFileName (path:string): string {
32
32
  const pos = Math.max(pos1, pos2)
33
33
  if (pos < 0) { return path } else { return path.substring(pos + 1) }
34
34
  }
35
+
36
+ /**
37
+ * 生成指定位数的唯一数字标识
38
+ * @param {number} digits - 期望生成的总位数,默认为20
39
+ * @returns {number} 唯一数字标识
40
+ */
41
+ export function generateUniqueNumeric (digits = 20) {
42
+ digits = digits - 13
43
+ if (digits <= 0) {
44
+ throw new Error('位数长度必须大于0')
45
+ }
46
+
47
+ // 获取当前时间戳
48
+ const currentTimestamp = Date.now()
49
+
50
+ // 生成随机数,并确保在指定位数内
51
+ const maxRandom = Math.pow(10, digits) - 1
52
+ const randomPart = Math.floor(Math.random() * maxRandom)
53
+
54
+ // 将随机数补足指定位数
55
+ const paddedRandomPart = randomPart.toString().padStart(digits, '0')
56
+
57
+ // 将时间戳和随机数组合成唯一数字
58
+ const uniqueNumeric = parseInt(`${currentTimestamp}${paddedRandomPart}`)
59
+
60
+ return uniqueNumeric
61
+ }
62
+
63
+ /**
64
+ * 生成指定长度的随机字符串
65
+ * @param {number} length - 字符串长度
66
+ * @returns {string} 随机字符串
67
+ */
68
+ export function generateRandomString (length: number) {
69
+ const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
70
+ let result = ''
71
+
72
+ for (let i = 0; i < length; i++) {
73
+ const randomIndex = Math.floor(Math.random() * characters.length)
74
+ result += characters.charAt(randomIndex)
75
+ }
76
+
77
+ return result
78
+ }
79
+
80
+ /**
81
+ * 生成带有指定前缀的随机字符串
82
+ * @param {string} prefix - 字符串前缀
83
+ * @param {number} length - 总字符串长度
84
+ * @returns {string} 带前缀的随机字符串
85
+ */
86
+ export function generateCustomRandomString (prefix: string, length: number) {
87
+ // 确保前缀部分不超过指定长度
88
+ if (prefix.length >= length) {
89
+ throw new Error('前缀长度不能大于或等于总长度')
90
+ }
91
+
92
+ // 生成剩余部分的随机字符串
93
+ const remainingPart = generateRandomString(length - prefix.length)
94
+
95
+ return prefix + remainingPart
96
+ }