wechaty-puppet-matrix 0.0.10 → 0.0.13

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 (46) hide show
  1. package/dist/cjs/src/matrix/cache-manager.js +1 -1
  2. package/dist/cjs/src/matrix/messages/message-location.d.ts +2 -0
  3. package/dist/cjs/src/matrix/messages/message-location.d.ts.map +1 -0
  4. package/dist/cjs/src/matrix/messages/message-location.js +18 -0
  5. package/dist/cjs/src/matrix/service/request.d.ts +55 -0
  6. package/dist/cjs/src/matrix/service/request.d.ts.map +1 -1
  7. package/dist/cjs/src/matrix/service/request.js +365 -1
  8. package/dist/cjs/src/matrix/utils/index.d.ts +3 -0
  9. package/dist/cjs/src/matrix/utils/index.d.ts.map +1 -1
  10. package/dist/cjs/src/matrix/utils/index.js +31 -0
  11. package/dist/cjs/src/puppet-matrix.d.ts +7 -0
  12. package/dist/cjs/src/puppet-matrix.d.ts.map +1 -1
  13. package/dist/cjs/src/puppet-matrix.js +94 -0
  14. package/dist/cjs/src/utils/normalize-filebox.d.ts +6 -0
  15. package/dist/cjs/src/utils/normalize-filebox.d.ts.map +1 -0
  16. package/dist/cjs/src/utils/normalize-filebox.js +46 -0
  17. package/dist/cjs/src/utils/sns-xml-generator.d.ts +19 -0
  18. package/dist/cjs/src/utils/sns-xml-generator.d.ts.map +1 -0
  19. package/dist/cjs/src/utils/sns-xml-generator.js +171 -0
  20. package/dist/esm/src/matrix/cache-manager.js +1 -1
  21. package/dist/esm/src/matrix/messages/message-location.d.ts +2 -0
  22. package/dist/esm/src/matrix/messages/message-location.d.ts.map +1 -0
  23. package/dist/esm/src/matrix/messages/message-location.js +15 -0
  24. package/dist/esm/src/matrix/service/request.d.ts +55 -0
  25. package/dist/esm/src/matrix/service/request.d.ts.map +1 -1
  26. package/dist/esm/src/matrix/service/request.js +365 -1
  27. package/dist/esm/src/matrix/utils/index.d.ts +3 -0
  28. package/dist/esm/src/matrix/utils/index.d.ts.map +1 -1
  29. package/dist/esm/src/matrix/utils/index.js +28 -0
  30. package/dist/esm/src/puppet-matrix.d.ts +7 -0
  31. package/dist/esm/src/puppet-matrix.d.ts.map +1 -1
  32. package/dist/esm/src/puppet-matrix.js +95 -1
  33. package/dist/esm/src/utils/normalize-filebox.d.ts +6 -0
  34. package/dist/esm/src/utils/normalize-filebox.d.ts.map +1 -0
  35. package/dist/esm/src/utils/normalize-filebox.js +42 -0
  36. package/dist/esm/src/utils/sns-xml-generator.d.ts +19 -0
  37. package/dist/esm/src/utils/sns-xml-generator.d.ts.map +1 -0
  38. package/dist/esm/src/utils/sns-xml-generator.js +165 -0
  39. package/package.json +6 -5
  40. package/src/matrix/cache-manager.ts +1 -1
  41. package/src/matrix/messages/message-location.ts +46 -0
  42. package/src/matrix/service/request.ts +483 -2
  43. package/src/matrix/utils/index.ts +62 -0
  44. package/src/puppet-matrix.ts +130 -1
  45. package/src/utils/normalize-filebox.ts +90 -0
  46. 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,268 @@ 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
+ )
396
+ const baseDirExist = await fs.pathExists(tempFilePath)
397
+ if (!baseDirExist) {
398
+ await fs.mkdirp(tempFilePath)
399
+ }
400
+ const finalFilePath = path.join(
401
+ tempFilePath,
402
+ 'temp_image_' + Date.now() + path.extname(imageUrl),
403
+ )
404
+
405
+ await fs.writeFile(finalFilePath, response.data)
406
+
407
+ // 4. 获取图片宽高
408
+ const dimensions = imageSize(finalFilePath)
409
+
410
+ // 5. 删除临时文件
411
+ await fs.unlink(finalFilePath)
412
+
413
+ return {
414
+ file_size: fileSize, // 文件大小(字节)
415
+ image_width: dimensions.width,
416
+ image_height: dimensions.height,
417
+ file_type: dimensions.type,
418
+ }
419
+ } catch (error) {
420
+ console.error('获取图片信息错误:', error)
421
+ throw error
422
+ }
423
+ }
424
+
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) : {}
463
+ const xmlTemplate = `
464
+ <TimelineObject>
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>`
494
+ return xmlTemplate
495
+ }
496
+
497
+ const genVideoSnsXml = (wxid: string, content: string, media: Media, location: any): string => {
498
+ const addressInfo: any = location ? splitAddress(location.address) : {}
499
+
500
+ const xmlTemplate = `
501
+ <TimelineObject>
502
+ <id>0</id>
503
+ <username>${wxid}</username>
504
+ <createTime>${Math.floor(Date.now() / 1000)}</createTime>
505
+ <contentDesc>${content}</contentDesc>
506
+ <contentDescShowType>0</contentDescShowType>
507
+ <contentDescScene>0</contentDescScene>
508
+ <private>0</private>
509
+ <sightFolded>0</sightFolded>
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=""/>` : ''}
511
+ <appInfo>
512
+ <id></id>
513
+ <version></version>
514
+ <appName></appName>
515
+ <installUrl></installUrl>
516
+ <fromUrl></fromUrl>
517
+ <isForceUpdate>0</isForceUpdate>
518
+ <isHidden>0</isHidden>
519
+ </appInfo>
520
+ <sourceUserName></sourceUserName>
521
+ <sourceNickName></sourceNickName>
522
+ <statisticsData></statisticsData>
523
+ <statExtStr></statExtStr>
524
+ <ContentObject>
525
+ <contentStyle>15</contentStyle>
526
+ <title>微信小视频</title>
527
+ <description>Sight</description>
528
+ <mediaList>
529
+ <media>
530
+ <id>0</id>
531
+ <type>6</type>
532
+ <title></title>
533
+ <description>${content}</description>
534
+ <private>0</private>
535
+ <userData></userData>
536
+ <subType>0</subType>
537
+ <videoSize width="${media.video_width}" height="${media.video_height}"/>
538
+ <url type="1" md5="951a7d7864d685a92fd2624155794bf9" videomd5="577f55635faf44f595a69ded26d87bcc">${media.file_url}</url>
539
+ <thumb type="1">${media.thumb_url}</thumb>
540
+ <size width="${media.thumb_width}.000000" height="${media.thumb_height}.000000" totalSize="${media.file_size}"/>
541
+ <videoDuration>${media.video_duration}.000000</videoDuration>
542
+ </media>
543
+ </mediaList>
544
+ <contentUrl>https://support.weixin.qq.com/cgi-bin/mmsupport-bin/readtemplate?t=page/common_page__upgrade&amp;v=1</contentUrl>
545
+ </ContentObject>
546
+ </TimelineObject>`
547
+ return xmlTemplate
548
+ }
549
+
550
+ const genImageSnsXml = (wxid: string, contentDesc: string, mediaList: Media[], location: any): string => {
551
+ const mediaTemplate = (media: Media) => `
552
+ <media>
553
+ <id><![CDATA[0]]></id>
554
+ <type><![CDATA[2]]></type>
555
+ <title></title>
556
+ <description></description>
557
+ <private><![CDATA[0]]></private>
558
+ <url type="1" md5="951a7d7864d685a92fd2624155794bf9"><![CDATA[${media.file_url}]]></url>
559
+ <thumb type="1"><![CDATA[${media.thumb_url}]]></thumb>
560
+ <videoDuration><![CDATA[0.0]]></videoDuration>
561
+ <size totalSize="${media.file_size}" width="${media.image_width}" height="${media.image_height}"></size>
562
+ </media>`
563
+
564
+ const mediaString = mediaList.map(media => mediaTemplate(media)).join('')
565
+ const addressInfo: any = location ? splitAddress(location.address) : {}
566
+ const xmlTemplate = `
567
+ <TimelineObject>
568
+ <id><![CDATA[0]]></id>
569
+ <username><![CDATA[${wxid}]]></username>
570
+ <createTime><![CDATA[${Math.floor(Date.now() / 1000)}]]></createTime>
571
+ <contentDescShowType>0</contentDescShowType>
572
+ <contentDescScene>0</contentDescScene>
573
+ <private><![CDATA[0]]></private>
574
+ <contentDesc><![CDATA[${contentDesc}]]></contentDesc>
575
+ <contentattr><![CDATA[0]]></contentattr>
576
+ <sourceUserName></sourceUserName>
577
+ <sourceNickName></sourceNickName>
578
+ <statisticsData></statisticsData>
579
+ <weappInfo>
580
+ <appUserName></appUserName>
581
+ <pagePath></pagePath>
582
+ <version><![CDATA[0]]></version>
583
+ <isHidden>0</isHidden>
584
+ <debugMode><![CDATA[0]]></debugMode>
585
+ <shareActionId></shareActionId>
586
+ <isGame><![CDATA[0]]></isGame>
587
+ <messageExtraData></messageExtraData>
588
+ <subType><![CDATA[0]]></subType>
589
+ <preloadResources></preloadResources>
590
+ </weappInfo>
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=""/>` : ''}
593
+ <ContentObject>
594
+ <contentStyle><![CDATA[1]]></contentStyle>
595
+ <contentSubStyle><![CDATA[0]]></contentSubStyle>
596
+ <title></title>
597
+ <description></description>
598
+ <contentUrl></contentUrl>
599
+ <mediaList>${mediaString}</mediaList>
600
+ </ContentObject>
601
+ <actionInfo>
602
+ <appMsg>
603
+ <mediaTagName></mediaTagName>
604
+ <messageExt></messageExt>
605
+ <messageAction></messageAction>
606
+ </appMsg>
607
+ </actionInfo>
608
+ <appInfo><id></id></appInfo>
609
+ <publicUserName></publicUserName>
610
+ <streamvideo>
611
+ <streamvideourl></streamvideourl>
612
+ <streamvideothumburl></streamvideothumburl>
613
+ <streamvideoweburl></streamvideoweburl>
614
+ </streamvideo>
615
+ </TimelineObject>`
616
+ return xmlTemplate
617
+ }
618
+
619
+ export interface MomentInfo {
620
+ content: string,
621
+ mentionIdList: string[],
622
+ visibledList: string[],
623
+ imageUrls: string[],
624
+ videoUrl: string,
625
+ urlLink: null | PUPPET.payloads.UrlLink,
626
+ channel: null | PUPPET.payloads.Channel,
627
+ miniInfo: null | PUPPET.payloads.MiniProgram,
628
+ location: null | PUPPET.payloads.Location,
629
+ rootId: string,
630
+ parentId: string
631
+ }
632
+
336
633
  class Client extends EventEmitter {
337
634
 
338
635
  private readonly options: PuppetMatrixOptions
@@ -2051,7 +2348,7 @@ class Client extends EventEmitter {
2051
2348
  announcement: content,
2052
2349
  },
2053
2350
  })
2054
- if (res.baseResponse.ret) {
2351
+ if (res?.baseResponse?.ret) {
2055
2352
  log.error('sendAnnouncement error: %s', JSON.stringify(res.baseResponse))
2056
2353
  }
2057
2354
  } catch (error) {
@@ -2059,6 +2356,190 @@ class Client extends EventEmitter {
2059
2356
  }
2060
2357
  }
2061
2358
 
2359
+ /**
2360
+ * 上传朋友圈图片
2361
+ * @param url
2362
+ * @returns
2363
+ */
2364
+ public async uploadSnsImage (url: string): Promise<SnsImageMessagePayload | void> {
2365
+ try {
2366
+ const res = await this.postData({
2367
+ path: '/cloud/cdn_upload_sns_image',
2368
+ data: {
2369
+ url,
2370
+ },
2371
+ })
2372
+ if (res?.errcode !== 0) {
2373
+ log.error('uploadSnsImage error: %s', JSON.stringify(res))
2374
+ return
2375
+ }
2376
+ return res as SnsImageMessagePayload
2377
+ } catch (error) {
2378
+ log.error(PRE, 'uploadSnsImage(%s): %s', url, error)
2379
+
2380
+ }
2381
+ }
2382
+
2383
+ /**
2384
+ * 上传朋友视频
2385
+ * @param url
2386
+ * @returns
2387
+ */
2388
+ public async uploadSnsVideo (url: string): Promise<SnsVideoMessagePayload | void> {
2389
+ try {
2390
+ const res = await this.postData({
2391
+ path: '/cloud/cdn_upload_sns_video',
2392
+ data: {
2393
+ url,
2394
+ },
2395
+ })
2396
+ log.info('uploadSnsVideo result: %s', res)
2397
+ if (res?.errcode !== 0) {
2398
+ log.error('uploadSnsVideo error: %s', JSON.stringify(res))
2399
+ }
2400
+ return res as SnsVideoMessagePayload
2401
+ } catch (error) {
2402
+ log.error(PRE, 'updateSnsImage(%s): %s', url, error)
2403
+ }
2404
+ }
2405
+
2406
+ /**
2407
+ * 发送朋友圈
2408
+ * @param wxid
2409
+ * @param momentInfo
2410
+ */
2411
+ public async sendSnsMoment (wxid: string, momentInfo: MomentInfo) {
2412
+ try {
2413
+ log.info('momentInfo: %s', JSON.stringify(momentInfo))
2414
+ let xmlContent = ''
2415
+ // 朋友圈评论
2416
+ if (momentInfo.parentId && momentInfo.rootId) {
2417
+ return await this.sendMomentReply(momentInfo.rootId, momentInfo.content, momentInfo.parentId === momentInfo.rootId ? '' : momentInfo.parentId)
2418
+ }
2419
+ if (momentInfo.imageUrls.length) {
2420
+ const imageInfo: any[] = []
2421
+ for (const image of momentInfo.imageUrls) {
2422
+ const res = await this.uploadSnsImage(image)
2423
+ if (res) {
2424
+ const imageBaseInfo = await getImageInfo(image)
2425
+ imageInfo.push({ ...res, ...imageBaseInfo })
2426
+ }
2427
+ xmlContent = genImageSnsXml(wxid, momentInfo.content, imageInfo, momentInfo.location)
2428
+
2429
+ }
2430
+ } else if (momentInfo.videoUrl) {
2431
+ const mediaInfo: any = await this.uploadSnsVideo(momentInfo.videoUrl)
2432
+ if (mediaInfo) {
2433
+ const thumbInfo = await getImageInfo(mediaInfo.thumb_url)
2434
+ xmlContent = genVideoSnsXml(wxid, momentInfo.content, { ...mediaInfo, ...thumbInfo }, momentInfo.location)
2435
+ }
2436
+ } else {
2437
+ xmlContent = genTextPosSnsXml(wxid, momentInfo.content, momentInfo.location)
2438
+
2439
+ }
2440
+ return await this.sendMoment(xmlContent)
2441
+ } catch (error) {
2442
+ log.error(PRE, 'sendSnsMoment(%s): %s', JSON.stringify(momentInfo), error)
2443
+ }
2444
+ }
2445
+
2446
+ public async sendMoment (content: string):Promise<string | void> {
2447
+ try {
2448
+ const res = await this.postData({
2449
+ path: '/sns/sns_post',
2450
+ data: {
2451
+ object_desc: content,
2452
+ with_user_list: [],
2453
+ block_user_list: [],
2454
+ group_user_list: [],
2455
+ group_contact_tag_id_list: [],
2456
+ black_contact_tag_id_list: [],
2457
+ },
2458
+ })
2459
+ if (res?.baseResponse?.ret !== 0) {
2460
+ log.error('sendMoment error: %s', JSON.stringify(res))
2461
+ return
2462
+ }
2463
+ log.info(PRE, 'sendMomen success: %s', res?.snsObject?.id)
2464
+ return res?.snsObject?.id
2465
+ } catch (error) {
2466
+ log.error(PRE, 'sendMoment(%s): %s', content, error)
2467
+
2468
+ }
2469
+ }
2470
+
2471
+ public async unSendMoment (objectId: string):Promise<string | void> {
2472
+ try {
2473
+ const res = await this.postData({
2474
+ path: '/sns/sns_delete',
2475
+ data: {
2476
+ object_id: objectId,
2477
+ },
2478
+ })
2479
+ if (res?.baseResponse?.ret !== 0) {
2480
+ log.error('unSendMoment error: %s', JSON.stringify(res))
2481
+ return
2482
+ }
2483
+ return objectId
2484
+ } catch (error) {
2485
+ log.error(PRE, 'unSendMoment(%s): %s', objectId, error)
2486
+ }
2487
+ }
2488
+
2489
+ /**
2490
+ * 朋友圈点赞
2491
+ * @param objectId
2492
+ * @param status 1 赞 0 取消点赞
2493
+ * @returns
2494
+ */
2495
+ public async sendMomentLike (objectId: string, status: PUPPET.types.Tap):Promise<string | void> {
2496
+ try {
2497
+ const res = await this.postData({
2498
+ path: '/sns/sns_like',
2499
+ data: {
2500
+ object_id: objectId,
2501
+ status,
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
+
2515
+ /**
2516
+ * 朋友圈回复
2517
+ * @param objectId 朋友圈id
2518
+ * @param content
2519
+ * @param commentId 评论id
2520
+ * @returns
2521
+ */
2522
+ public async sendMomentReply (objectId: string, content: string, commentId?: string):Promise<string | void> {
2523
+ try {
2524
+ const res = await this.postData({
2525
+ path: '/sns/sns_comment',
2526
+ data: {
2527
+ object_id: objectId,
2528
+ content,
2529
+ reply_comment_id: commentId || '0',
2530
+ },
2531
+ })
2532
+ if (res?.baseResponse?.ret !== 0) {
2533
+ log.error('sendMomentLike error: %s', JSON.stringify(res))
2534
+ return
2535
+ }
2536
+ return res?.snsObject?.id
2537
+ } catch (error) {
2538
+ log.error(PRE, 'sendMomentLike(%s): %s', objectId, error)
2539
+
2540
+ }
2541
+ }
2542
+
2062
2543
  }
2063
2544
 
2064
2545
  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
+ }