wechaty-puppet-matrix 0.0.10 → 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 +55 -0
  3. package/dist/cjs/src/matrix/service/request.d.ts.map +1 -1
  4. package/dist/cjs/src/matrix/service/request.js +349 -1
  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 +55 -0
  19. package/dist/esm/src/matrix/service/request.d.ts.map +1 -1
  20. package/dist/esm/src/matrix/service/request.js +349 -1
  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 +455 -2
  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
@@ -2051,7 +2321,7 @@ class Client extends EventEmitter {
2051
2321
  announcement: content,
2052
2322
  },
2053
2323
  })
2054
- if (res.baseResponse.ret) {
2324
+ if (res?.baseResponse?.ret) {
2055
2325
  log.error('sendAnnouncement error: %s', JSON.stringify(res.baseResponse))
2056
2326
  }
2057
2327
  } catch (error) {
@@ -2059,6 +2329,189 @@ class Client extends EventEmitter {
2059
2329
  }
2060
2330
  }
2061
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
+
2062
2515
  }
2063
2516
 
2064
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
+ }
@@ -1,7 +1,10 @@
1
1
  import { log } from '@juzi/wechaty-puppet'
2
2
  import * as PUPPET from '@juzi/wechaty-puppet'
3
3
  import type { FileBoxInterface } from 'file-box'
4
- import { FileBox } from 'file-box'
4
+ import {
5
+ FileBox,
6
+ FileBoxType,
7
+ } from 'file-box'
5
8
  import type { ContactPayload, MessagePayload } from './engine-schema.js'
6
9
  import type { FileBoxMetadataMessage } from './matrix/types.js'
7
10
  import Client from './matrix/service/request.js'
@@ -54,6 +57,11 @@ class PuppetMatrix extends PUPPET.Puppet {
54
57
  private _verifyInterval?: ReturnType<typeof setInterval> | null
55
58
  private _qrcodeStatuasInterval?: ReturnType<typeof setInterval> | null
56
59
  public static override readonly VERSION = VERSION
60
+ /**
61
+ * UUIDify:
62
+ * We need to clone a FileBox
63
+ * to set uuid loader/saver with this grpc client
64
+ */
57
65
 
58
66
  constructor (public override options: PuppetEngineOptions = {} as PuppetEngineOptions) {
59
67
  super(options)
@@ -1160,6 +1168,109 @@ class PuppetMatrix extends PUPPET.Puppet {
1160
1168
 
1161
1169
  return ret
1162
1170
  }
1171
+
1172
+ /****************************************************************************
1173
+ * moment section
1174
+ ***************************************************************************/
1175
+ /**
1176
+ * 发布朋友圈
1177
+ * @param payload
1178
+ */
1179
+ override async postPublish (payload: PUPPET.payloads.Post): Promise<void | string> {
1180
+ log.verbose(PRE, 'postPublish(%s)', payload)
1181
+ if (!PUPPET.payloads.isPostClient(payload)) {
1182
+ throw new Error('can only publish client post now')
1183
+ }
1184
+ const momentInfo:any = {
1185
+ content: '',
1186
+ mentionIdList: [],
1187
+ visibledList: [],
1188
+ imageUrls: [],
1189
+ videoUrl: '',
1190
+ urlLink: null,
1191
+ channel: null,
1192
+ miniInfo: null,
1193
+ location: null,
1194
+ rootId: '',
1195
+ parentId: '',
1196
+ }
1197
+ for (const item of payload.sayableList) {
1198
+ switch (item.type) {
1199
+ case PUPPET.types.Sayable.Text:
1200
+ momentInfo.content = `${momentInfo.content ? momentInfo.content + '\n' : ''}${item.payload.text}`
1201
+ momentInfo.mentionIdList = item.payload.mentions
1202
+ break
1203
+ case PUPPET.types.Sayable.Attachment: {
1204
+ const fileBox = item.payload.filebox as FileBoxInterface
1205
+ if (typeof item.payload.filebox !== 'string' && (fileBox as FileBoxInterface).type === FileBoxType.Url) {
1206
+ const fileType = fileBox.mediaType && fileBox.mediaType !== 'application/octet-stream' ? fileBox.mediaType : path.extname(fileBox.name)
1207
+ // @ts-ignore
1208
+ const fileUrl = fileBox.remoteUrl || ''
1209
+ if (fileBox.mediaType.startsWith('image/')) {
1210
+ momentInfo.imageUrls.push(fileUrl)
1211
+ } else if (fileType.includes('video/mp4') || fileType.includes('.mp4')) {
1212
+ momentInfo.videoInfo = fileUrl
1213
+ }
1214
+ }
1215
+ break
1216
+ }
1217
+ case PUPPET.types.Sayable.Url: {
1218
+ momentInfo.urlLink = item.payload
1219
+ break
1220
+ }
1221
+ case PUPPET.types.Sayable.Channel: {
1222
+ momentInfo.channel = item.payload
1223
+ break
1224
+ }
1225
+ case PUPPET.types.Sayable.MiniProgram: {
1226
+ momentInfo.miniInfo = item.payload
1227
+ break
1228
+ }
1229
+ default:
1230
+ throw new Error(`postPublish unsupported type ${item.type}`)
1231
+ }
1232
+ }
1233
+ if (payload.rootId) momentInfo.rootId = payload.rootId
1234
+ if (payload.parentId) momentInfo.parentId = payload.parentId
1235
+ if (payload.location) momentInfo.location = payload.location
1236
+
1237
+ momentInfo.visibleList = payload.visibleList
1238
+
1239
+ const res = await this._client?.sendSnsMoment(this._self?.wxid || '', momentInfo)
1240
+
1241
+ return res
1242
+ }
1243
+
1244
+ override async postUnpublish (id: string): Promise<void> {
1245
+ log.verbose(PRE, 'postUnpublish(%s)', id)
1246
+
1247
+ await this._client?.unSendMoment(id)
1248
+ }
1249
+
1250
+ override async postRawPayload (id: string): Promise<PUPPET.payloads.Post | string> {
1251
+ log.verbose(PRE, 'postRawPayload(%s)', id)
1252
+ return id
1253
+ }
1254
+
1255
+ override async postPayloadSayable (postId: string, sayableId: string): Promise<PUPPET.payloads.Sayable> {
1256
+ log.verbose(PRE, 'postPayloadSayable(%s, %s)', postId, sayableId)
1257
+ return postId as any
1258
+ }
1259
+
1260
+ override async postRawPayloadParser (payload: PUPPET.payloads.Post): Promise<PUPPET.payloads.Post> {
1261
+ // log.silly('PuppetService', 'postRawPayloadParser({id:%s})', payload.id)
1262
+ // passthrough
1263
+ return payload
1264
+ }
1265
+
1266
+ override async tap (postId: string, type: PUPPET.types.Tap, tap = true): Promise<boolean | void> {
1267
+ log.verbose(PRE, 'tap(%s, %s, %s)', postId, type, tap)
1268
+
1269
+ const res = await this._client?.sendMomentLike(postId, type)
1270
+
1271
+ return !!res
1272
+ }
1273
+
1163
1274
  /****************************************************************************
1164
1275
  * extra methods section
1165
1276
  ***************************************************************************/