wechaty-puppet-matrix 0.0.54 → 0.0.56

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 (76) hide show
  1. package/dist/cjs/src/engine-schema.d.ts +1 -0
  2. package/dist/cjs/src/engine-schema.d.ts.map +1 -1
  3. package/dist/cjs/src/matrix/messages/message-contact-card.d.ts +16 -0
  4. package/dist/cjs/src/matrix/messages/message-contact-card.d.ts.map +1 -0
  5. package/dist/cjs/src/matrix/messages/message-contact-card.js +25 -0
  6. package/dist/cjs/src/matrix/messages/message-emotion.d.ts +7 -0
  7. package/dist/cjs/src/matrix/messages/message-emotion.d.ts.map +1 -1
  8. package/dist/cjs/src/matrix/messages/message-emotion.js +20 -0
  9. package/dist/cjs/src/matrix/messages/message-image.d.ts +8 -0
  10. package/dist/cjs/src/matrix/messages/message-image.d.ts.map +1 -1
  11. package/dist/cjs/src/matrix/messages/message-image.js +38 -2
  12. package/dist/cjs/src/matrix/messages/message-image.spec.d.ts +3 -0
  13. package/dist/cjs/src/matrix/messages/message-image.spec.d.ts.map +1 -0
  14. package/dist/cjs/src/matrix/messages/message-image.spec.js +66 -0
  15. package/dist/cjs/src/matrix/messages/message-video.d.ts +6 -0
  16. package/dist/cjs/src/matrix/messages/message-video.d.ts.map +1 -1
  17. package/dist/cjs/src/matrix/messages/message-video.js +20 -0
  18. package/dist/cjs/src/matrix/messages/message-video.spec.d.ts +3 -0
  19. package/dist/cjs/src/matrix/messages/message-video.spec.d.ts.map +1 -0
  20. package/dist/cjs/src/matrix/messages/message-video.spec.js +27 -0
  21. package/dist/cjs/src/matrix/service/request.d.ts +21 -0
  22. package/dist/cjs/src/matrix/service/request.d.ts.map +1 -1
  23. package/dist/cjs/src/matrix/service/request.js +194 -32
  24. package/dist/cjs/src/matrix/utils/file-type.d.ts +4 -0
  25. package/dist/cjs/src/matrix/utils/file-type.d.ts.map +1 -0
  26. package/dist/cjs/src/matrix/utils/file-type.js +55 -0
  27. package/dist/cjs/src/matrix/utils/file-type.spec.d.ts +3 -0
  28. package/dist/cjs/src/matrix/utils/file-type.spec.d.ts.map +1 -0
  29. package/dist/cjs/src/matrix/utils/file-type.spec.js +25 -0
  30. package/dist/cjs/src/puppet-matrix.d.ts +3 -0
  31. package/dist/cjs/src/puppet-matrix.d.ts.map +1 -1
  32. package/dist/cjs/src/puppet-matrix.js +121 -33
  33. package/dist/esm/src/engine-schema.d.ts +1 -0
  34. package/dist/esm/src/engine-schema.d.ts.map +1 -1
  35. package/dist/esm/src/matrix/messages/message-contact-card.d.ts +16 -0
  36. package/dist/esm/src/matrix/messages/message-contact-card.d.ts.map +1 -0
  37. package/dist/esm/src/matrix/messages/message-contact-card.js +22 -0
  38. package/dist/esm/src/matrix/messages/message-emotion.d.ts +7 -0
  39. package/dist/esm/src/matrix/messages/message-emotion.d.ts.map +1 -1
  40. package/dist/esm/src/matrix/messages/message-emotion.js +19 -0
  41. package/dist/esm/src/matrix/messages/message-image.d.ts +8 -0
  42. package/dist/esm/src/matrix/messages/message-image.d.ts.map +1 -1
  43. package/dist/esm/src/matrix/messages/message-image.js +37 -2
  44. package/dist/esm/src/matrix/messages/message-image.spec.d.ts +3 -0
  45. package/dist/esm/src/matrix/messages/message-image.spec.d.ts.map +1 -0
  46. package/dist/esm/src/matrix/messages/message-image.spec.js +64 -0
  47. package/dist/esm/src/matrix/messages/message-video.d.ts +6 -0
  48. package/dist/esm/src/matrix/messages/message-video.d.ts.map +1 -1
  49. package/dist/esm/src/matrix/messages/message-video.js +19 -0
  50. package/dist/esm/src/matrix/messages/message-video.spec.d.ts +3 -0
  51. package/dist/esm/src/matrix/messages/message-video.spec.d.ts.map +1 -0
  52. package/dist/esm/src/matrix/messages/message-video.spec.js +25 -0
  53. package/dist/esm/src/matrix/service/request.d.ts +21 -0
  54. package/dist/esm/src/matrix/service/request.d.ts.map +1 -1
  55. package/dist/esm/src/matrix/service/request.js +193 -32
  56. package/dist/esm/src/matrix/utils/file-type.d.ts +4 -0
  57. package/dist/esm/src/matrix/utils/file-type.d.ts.map +1 -0
  58. package/dist/esm/src/matrix/utils/file-type.js +47 -0
  59. package/dist/esm/src/matrix/utils/file-type.spec.d.ts +3 -0
  60. package/dist/esm/src/matrix/utils/file-type.spec.d.ts.map +1 -0
  61. package/dist/esm/src/matrix/utils/file-type.spec.js +23 -0
  62. package/dist/esm/src/puppet-matrix.d.ts +3 -0
  63. package/dist/esm/src/puppet-matrix.d.ts.map +1 -1
  64. package/dist/esm/src/puppet-matrix.js +121 -33
  65. package/package.json +1 -1
  66. package/src/engine-schema.ts +1 -0
  67. package/src/matrix/messages/message-contact-card.ts +48 -0
  68. package/src/matrix/messages/message-emotion.ts +36 -0
  69. package/src/matrix/messages/message-image.spec.ts +107 -0
  70. package/src/matrix/messages/message-image.ts +63 -2
  71. package/src/matrix/messages/message-video.spec.ts +37 -0
  72. package/src/matrix/messages/message-video.ts +28 -1
  73. package/src/matrix/service/request.ts +274 -32
  74. package/src/matrix/utils/file-type.spec.ts +29 -0
  75. package/src/matrix/utils/file-type.ts +59 -0
  76. package/src/puppet-matrix.ts +150 -35
@@ -44,6 +44,7 @@ const types_js_1 = require("./matrix/types.js");
44
44
  const request_js_1 = __importDefault(require("./matrix/service/request.js"));
45
45
  const date_fns_1 = require("date-fns");
46
46
  const index_js_1 = require("./matrix/utils/index.js");
47
+ const file_type_js_1 = require("./matrix/utils/file-type.js");
47
48
  const path_1 = __importDefault(require("path"));
48
49
  const cache_manager_js_1 = require("./matrix/cache-manager.js");
49
50
  const is_type_js_1 = require("./matrix/utils/is-type.js");
@@ -55,6 +56,7 @@ const message_image_js_1 = require("./matrix/messages/message-image.js");
55
56
  const message_audio_js_1 = require("./matrix/messages/message-audio.js");
56
57
  const message_video_js_1 = require("./matrix/messages/message-video.js");
57
58
  const message_location_js_1 = require("./matrix/messages/message-location.js");
59
+ const message_contact_card_js_1 = require("./matrix/messages/message-contact-card.js");
58
60
  const cached_promise_js_1 = require("./matrix/utils/cached-promise.js");
59
61
  const message_js_1 = require("./matrix/schema-mapper/message.js");
60
62
  const contact_js_1 = require("./matrix/schema-mapper/contact.js");
@@ -62,7 +64,6 @@ const room_js_1 = require("./matrix/schema-mapper/room.js");
62
64
  const mod_js_1 = require("./matrix/events/mod.js");
63
65
  const cleanup_js_1 = require("./cleanup.js");
64
66
  const package_json_js_1 = require("./package-json.js");
65
- const xml_to_json_js_1 = require("./matrix/utils/xml-to-json.js");
66
67
  const VERSION = package_json_js_1.packageJson.version || '0.0.0';
67
68
  exports.VERSION = VERSION;
68
69
  const PRE = '[PuppetMatrix]';
@@ -560,7 +561,7 @@ class PuppetMatrix extends PUPPET.Puppet {
560
561
  hello = option.hello;
561
562
  }
562
563
  }
563
- await this._client?.addFriendByWxid({ content: hello, ticket, scene: addContactScene, wxid: contactId });
564
+ await this._client?.addFriendByWxid({ content: hello, ticket, scene: addContactScene, wxid: cachedContactSearch?.wxid || contactId });
564
565
  }
565
566
  async friendshipSearchPhone(phone) {
566
567
  return this._friendshipSearch(phone, '15');
@@ -597,7 +598,58 @@ class PuppetMatrix extends PUPPET.Puppet {
597
598
  }
598
599
  async messageContact(messageId) {
599
600
  wechaty_puppet_1.log.verbose('PuppetWeChat', 'messageContact(%s)', messageId);
600
- return PUPPET.throwUnsupportedError(messageId);
601
+ const rawPayload = await this.messageRawPayload(messageId);
602
+ if (rawPayload.msgType !== types_js_1.WechatMessageType.ShareCard) {
603
+ throw new Error(`message ${messageId} is not a contact card`);
604
+ }
605
+ const card = await (0, message_contact_card_js_1.parseContactCardMessagePayload)(rawPayload.msg);
606
+ if (!card) {
607
+ throw new Error(`can not parse contact card from message ${messageId}`);
608
+ }
609
+ if (!card.username.includes(STRANGER_SUFFIX)) {
610
+ return card.username;
611
+ }
612
+ const friendId = await this._findFriendByCard(card);
613
+ if (friendId) {
614
+ wechaty_puppet_1.log.verbose(PRE, 'messageContact(%s) matched friend %s', messageId, friendId);
615
+ return friendId;
616
+ }
617
+ const searchId = `${SEARCH_CONTACT_PREFIX}${card.username}`;
618
+ await this._cacheMgr.setContactSearch(searchId, {
619
+ alias: '',
620
+ avatar: card.avatar,
621
+ city: card.city,
622
+ country: '',
623
+ isFriend: 0,
624
+ name: card.nickname,
625
+ province: card.province,
626
+ scene: card.scene,
627
+ sex: card.sex,
628
+ sign: card.sign,
629
+ ticket: card.antispamticket,
630
+ wxNum: card.alias,
631
+ wxid: card.username,
632
+ });
633
+ return searchId;
634
+ }
635
+ async _findFriendByCard(card) {
636
+ const avatarKey = (url) => (url || '').replace(/^https?:\/\//, '').replace(/\/\d+$/, '');
637
+ const cardAvatar = avatarKey(card.avatar);
638
+ const contacts = (await this._cacheMgr.getAllContacts())
639
+ .filter(c => c.wxid && !(0, is_type_js_1.isRoomId)(c.wxid) && !c.wxid.includes(STRANGER_SUFFIX) && c.isFriend !== 0);
640
+ if (cardAvatar) {
641
+ const byAvatar = contacts.find(c => avatarKey(c.avatar) === cardAvatar);
642
+ if (byAvatar) {
643
+ return byAvatar.wxid;
644
+ }
645
+ }
646
+ if (card.nickname) {
647
+ const byName = contacts.filter(c => c.name === card.nickname);
648
+ if (byName.length === 1) {
649
+ return byName[0].wxid;
650
+ }
651
+ }
652
+ return undefined;
601
653
  }
602
654
  async messageChannel(messageId) {
603
655
  wechaty_puppet_1.log.verbose('PuppetService', 'messageChannel(%s)', messageId);
@@ -636,12 +688,20 @@ class PuppetMatrix extends PUPPET.Puppet {
636
688
  return this._getMessageFileFileBox(messageId, messagePayload);
637
689
  case PUPPET.types.Message.Emoticon: {
638
690
  const emotionPayload = await (0, message_emotion_js_1.parseEmotionMessagePayload)(messagePayload);
639
- const emoticonBox = emotionPayload.is_ww ? await this._client?.downloadWWMedia({
640
- aes_key: emotionPayload.aeskey || '',
641
- auth_key: emotionPayload.authkey || '',
642
- file_id: emotionPayload.cdnurl || '',
643
- file_name: `message-${messageId}-emoticon.jpg`,
644
- }, 3) : file_box_1.FileBox.fromUrl(emotionPayload.cdnurl, { name: `message-${messageId}-emoticon.jpg` });
691
+ const url = emotionPayload.is_ww
692
+ ? (await this._client?.downloadWWMedia({
693
+ aes_key: emotionPayload.aeskey || '',
694
+ auth_key: emotionPayload.authkey || '',
695
+ file_id: emotionPayload.cdnurl || '',
696
+ file_name: `message-${messageId}-emoticon.gif`,
697
+ }, 3)) || ''
698
+ : emotionPayload.cdnurl;
699
+ if (!url) {
700
+ throw new Error(`Can not get emoticon url for message: ${messageId}`);
701
+ }
702
+ const head = await (0, file_type_js_1.fetchRemoteHead)(url);
703
+ const ext = (head && (0, file_type_js_1.detectImageExt)(head)) || (emotionPayload.type === 2 && !emotionPayload.game ? 'gif' : 'png');
704
+ const emoticonBox = file_box_1.FileBox.fromUrl(url, { name: `message-${messageId}-emoticon.${ext}` });
645
705
  emoticonBox.metadata = {
646
706
  payload: emotionPayload,
647
707
  type: 'emoticon',
@@ -776,28 +836,14 @@ class PuppetMatrix extends PUPPET.Puppet {
776
836
  }
777
837
  }
778
838
  async messageSendContact(toUserId, contactId) {
779
- wechaty_puppet_1.log.verbose('PuppetWeChat', 'messageSend("%s", %s)', toUserId, contactId);
839
+ wechaty_puppet_1.log.verbose('PuppetWeChat', 'messageSendContact("%s", %s)', toUserId, contactId);
780
840
  const contactPayload = await this.contactRawPayload(contactId);
781
- const xmlObj = {
782
- msg: {
783
- antispamticket: '',
784
- bigheadimgurl: contactPayload?.avatar,
785
- brandFlags: '0',
786
- certflag: '0',
787
- city: contactPayload?.city,
788
- fullpy: contactPayload?.name,
789
- imagestatus: '3',
790
- nickname: contactPayload?.name,
791
- province: contactPayload?.province,
792
- regionCode: 'CN_Shanghai',
793
- scene: '17',
794
- sex: contactPayload?.sex,
795
- smallheadimgurl: contactPayload?.avatar,
796
- username: contactPayload?.ticket,
797
- },
798
- };
799
- const xml = (0, xml_to_json_js_1.JsonToXml)(xmlObj);
800
- const res = await this._client?.sendContactCard(toUserId, xml, this._self?.wxid);
841
+ const res = await this._client?.sendContact({
842
+ shareHeadImg: contactPayload?.avatar,
843
+ shareId: contactPayload?.wxid || contactId,
844
+ shareNickname: contactPayload?.name,
845
+ toId: toUserId,
846
+ }, this._self?.wxid);
801
847
  if (res) {
802
848
  await this._cacheMgr?.setMessage(res.id, res);
803
849
  return res.id;
@@ -810,7 +856,16 @@ class PuppetMatrix extends PUPPET.Puppet {
810
856
  : path_1.default.extname(fileBox.name);
811
857
  const fileUrl = fileBox.remoteUrl || '';
812
858
  if (metadata.type === 'emoticon') {
813
- PUPPET.throwUnsupportedError(conversationId, fileBox);
859
+ const emoji = metadata.payload;
860
+ const byMd5 = emoji?.md5 && emoji.len
861
+ ? await this._client?.sendEmojiMd5(conversationId, emoji.md5, emoji.len, this._self?.wxid)
862
+ : undefined;
863
+ const msgInfo = byMd5 || (fileUrl ? await this._client?.sendEmojiUrl(conversationId, fileUrl, this._self?.wxid) : undefined);
864
+ if (!msgInfo) {
865
+ throw new Error(`send emoticon failed: ${fileBox.name}`);
866
+ }
867
+ await this._cacheMgr?.setMessage(msgInfo.id, msgInfo);
868
+ return msgInfo.id;
814
869
  }
815
870
  else if (fileBox.mediaType.startsWith('image/')) {
816
871
  if (fileUrl) {
@@ -915,8 +970,27 @@ class PuppetMatrix extends PUPPET.Puppet {
915
970
  throw new Error(`Wrong Post!!! please check your Post payload to make sure it right ${conversationId}`);
916
971
  }
917
972
  }
973
+ async sendPat(conversationId, contactId) {
974
+ wechaty_puppet_1.log.verbose(PRE, 'sendPat(%s, %s)', conversationId, contactId);
975
+ const patted = contactId || conversationId;
976
+ if ((0, is_type_js_1.isRoomId)(patted)) {
977
+ throw new Error('sendPat: contactId is required in room');
978
+ }
979
+ return (await this._client?.sendPat(conversationId, patted)) || false;
980
+ }
918
981
  async messageRecall(messageId) {
919
- return PUPPET.throwUnsupportedError(messageId);
982
+ wechaty_puppet_1.log.verbose(PRE, 'messageRecall(%s)', messageId);
983
+ const payload = await this._cacheMgr?.getMessage(messageId);
984
+ if (!payload) {
985
+ wechaty_puppet_1.log.warn(PRE, 'messageRecall(%s): message not found in cache', messageId);
986
+ return false;
987
+ }
988
+ const toId = payload.listenerId || payload.fromWxid || '';
989
+ if (!toId) {
990
+ wechaty_puppet_1.log.warn(PRE, 'messageRecall(%s): can not find conversation id', messageId);
991
+ return false;
992
+ }
993
+ return (await this._client?.revokeMsg(toId, messageId, payload.clientMsgId)) || false;
920
994
  }
921
995
  async messageForward(toUserName, messageId) {
922
996
  const messagePayload = await this.messageRawPayload(messageId);
@@ -1496,7 +1570,21 @@ class PuppetMatrix extends PUPPET.Puppet {
1496
1570
  wechaty_puppet_1.log.error(`Can not get file for message: ${messageId}`);
1497
1571
  }
1498
1572
  const url = imageInfo.is_ww ? await this._client?.downloadWWMedia(imageInfo, 1) : await this._client?.downloadImage(imageInfo);
1499
- return this._fileBoxFromUrl(url, messageId);
1573
+ return this._fileBoxFromUrl(url, messageId, url ? await this._imageNameByHead(url) : undefined);
1574
+ }
1575
+ async _imageNameByHead(url) {
1576
+ const head = await (0, file_type_js_1.fetchRemoteHead)(url);
1577
+ const ext = head && (0, file_type_js_1.detectImageExt)(head);
1578
+ if (!ext) {
1579
+ return undefined;
1580
+ }
1581
+ let rawName = 'image';
1582
+ try {
1583
+ rawName = decodeURIComponent(new URL(url).pathname.split('/').pop() || rawName);
1584
+ }
1585
+ catch {
1586
+ }
1587
+ return (0, file_type_js_1.replaceExt)(rawName, ext);
1500
1588
  }
1501
1589
  async _getMessageAudioFileBox(messageId, messagePayload) {
1502
1590
  const audioInfo = await (0, message_audio_js_1.parseAudioMessagePayload)(messagePayload);
@@ -10,6 +10,7 @@ export interface MessagePayload {
10
10
  msg: string;
11
11
  listenerId: string;
12
12
  voiceText?: string;
13
+ clientMsgId?: number;
13
14
  }
14
15
  export interface ChatRoomMember {
15
16
  wxid: string;
@@ -1 +1 @@
1
- {"version":3,"file":"engine-schema.d.ts","sourceRoot":"","sources":["../../../src/engine-schema.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAE1D,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AACD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAGD,oBAAY,OAAO;IACjB,GAAG,IAAI;IACP,MAAM,IAAI;CACX;AAKD,aAAK,aAAa;IAChB,OAAO,IAAI;IACX,IAAI,IAAI;IACR,MAAM,IAAI;CACX;AAED,MAAM,WAAW,cAAc;IAI7B,MAAM,EAAE,MAAM,CAAC;IAIf,IAAI,EAAE,MAAM,CAAC;IAIb,OAAO,EAAE,MAAM,CAAC;IAIhB,SAAS,CAAC,EAAE,MAAM,CAAC;IAInB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAIhC,IAAI,EAAE,MAAM,CAAC;IAIb,KAAK,EAAE,MAAM,CAAC;IAId,QAAQ,EAAE,MAAM,CAAC;IAIjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAIhB,GAAG,EAAE,aAAa,CAAC;IAInB,IAAI,CAAC,EAAE,MAAM,CAAC;IAKd,MAAM,CAAC,EAAE,MAAM,CAAC;IAIhB,IAAI,EAAE,MAAM,CAAC;IAIb,KAAK,EAAE,MAAM,CAAC;IAId,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAI9B,OAAO,CAAC,EAAE,MAAM,CAAC;IAIjB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;IAItB,QAAQ,CAAC,EAAE,MAAM,CAAA;IAIjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IAIrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAKlB,WAAW,CAAC,EAAE,MAAM,CAAC;IAIrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAC,MAAM,CAAC;IACX,OAAO,EAAC,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAA;CACjB"}
1
+ {"version":3,"file":"engine-schema.d.ts","sourceRoot":"","sources":["../../../src/engine-schema.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAE1D,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AACD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAGD,oBAAY,OAAO;IACjB,GAAG,IAAI;IACP,MAAM,IAAI;CACX;AAKD,aAAK,aAAa;IAChB,OAAO,IAAI;IACX,IAAI,IAAI;IACR,MAAM,IAAI;CACX;AAED,MAAM,WAAW,cAAc;IAI7B,MAAM,EAAE,MAAM,CAAC;IAIf,IAAI,EAAE,MAAM,CAAC;IAIb,OAAO,EAAE,MAAM,CAAC;IAIhB,SAAS,CAAC,EAAE,MAAM,CAAC;IAInB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAIhC,IAAI,EAAE,MAAM,CAAC;IAIb,KAAK,EAAE,MAAM,CAAC;IAId,QAAQ,EAAE,MAAM,CAAC;IAIjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAIhB,GAAG,EAAE,aAAa,CAAC;IAInB,IAAI,CAAC,EAAE,MAAM,CAAC;IAKd,MAAM,CAAC,EAAE,MAAM,CAAC;IAIhB,IAAI,EAAE,MAAM,CAAC;IAIb,KAAK,EAAE,MAAM,CAAC;IAId,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAI9B,OAAO,CAAC,EAAE,MAAM,CAAC;IAIjB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;IAItB,QAAQ,CAAC,EAAE,MAAM,CAAA;IAIjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IAIrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAKlB,WAAW,CAAC,EAAE,MAAM,CAAC;IAIrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAC,MAAM,CAAC;IACX,OAAO,EAAC,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAA;CACjB"}
@@ -0,0 +1,16 @@
1
+ export interface ContactCardPayload {
2
+ username: string;
3
+ nickname: string;
4
+ alias: string;
5
+ avatar: string;
6
+ province: string;
7
+ city: string;
8
+ sex: number;
9
+ sign: string;
10
+ scene: string;
11
+ antispamticket: string;
12
+ brandType: number;
13
+ certflag: number;
14
+ }
15
+ export declare function parseContactCardMessagePayload(content: string): Promise<ContactCardPayload | undefined>;
16
+ //# sourceMappingURL=message-contact-card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-contact-card.d.ts","sourceRoot":"","sources":["../../../../../src/matrix/messages/message-contact-card.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,kBAAkB;IAEjC,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAEhB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IAEb,cAAc,EAAE,MAAM,CAAA;IAEtB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,wBAAsB,8BAA8B,CAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAoB9G"}
@@ -0,0 +1,22 @@
1
+ import { xmlToJson } from '../utils/xml-to-json.js';
2
+ export async function parseContactCardMessagePayload(content) {
3
+ const json = await xmlToJson(content);
4
+ const attrs = json?.msg?.$;
5
+ if (!attrs?.username) {
6
+ return undefined;
7
+ }
8
+ return {
9
+ alias: attrs.alias || '',
10
+ antispamticket: attrs.antispamticket || '',
11
+ avatar: attrs.bigheadimgurl || attrs.smallheadimgurl || '',
12
+ brandType: parseInt(attrs.brandType, 10) || 0,
13
+ certflag: parseInt(attrs.certflag, 10) || 0,
14
+ city: attrs.city || '',
15
+ nickname: attrs.nickname || '',
16
+ province: attrs.province || '',
17
+ scene: attrs.scene || '17',
18
+ sex: parseInt(attrs.sex, 10) || 0,
19
+ sign: attrs.sign || '',
20
+ username: attrs.username,
21
+ };
22
+ }
@@ -1,4 +1,10 @@
1
1
  import type { MessagePayload } from '../../engine-schema.js';
2
+ export interface EmojiGamePayload {
3
+ kind: 'rps' | 'dice';
4
+ value: string | number;
5
+ text: string;
6
+ }
7
+ export declare function parseEmojiGame(type: number, content: number): EmojiGamePayload | undefined;
2
8
  export interface EmojiMessagePayload {
3
9
  type: number;
4
10
  len: number;
@@ -7,6 +13,7 @@ export interface EmojiMessagePayload {
7
13
  width: number;
8
14
  height: number;
9
15
  gameext?: string;
16
+ game?: EmojiGamePayload;
10
17
  aeskey?: string;
11
18
  authkey?: string;
12
19
  is_ww: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"message-emotion.d.ts","sourceRoot":"","sources":["../../../../../src/matrix/messages/message-emotion.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AA2B5D,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,0BAA0B,CAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA6BvG;AAED,wBAAgB,sBAAsB,CAAE,mBAAmB,EAAE,mBAAmB,GAAG,MAAM,CAIxF"}
1
+ {"version":3,"file":"message-emotion.d.ts","sourceRoot":"","sources":["../../../../../src/matrix/messages/message-emotion.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAgC5D,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,KAAK,GAAG,MAAM,CAAC;IAErB,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAEvB,IAAI,EAAE,MAAM,CAAC;CACd;AAQD,wBAAgB,cAAc,CAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAU3F;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,0BAA0B,CAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAgCvG;AAED,wBAAgB,sBAAsB,CAAE,mBAAmB,EAAE,mBAAmB,GAAG,MAAM,CAIxF"}
@@ -1,4 +1,20 @@
1
1
  import { xmlToJson } from '../utils/xml-to-json.js';
2
+ const RPS = {
3
+ 1: ['scissors', '剪刀'],
4
+ 2: ['rock', '石头'],
5
+ 3: ['paper', '布'],
6
+ };
7
+ export function parseEmojiGame(type, content) {
8
+ if (type === 1 && RPS[content]) {
9
+ const [value, text] = RPS[content];
10
+ return { kind: 'rps', text, value };
11
+ }
12
+ if (type === 2 && content >= 4 && content <= 9) {
13
+ const point = content - 3;
14
+ return { kind: 'dice', text: `${point}点`, value: point };
15
+ }
16
+ return undefined;
17
+ }
2
18
  export async function parseEmotionMessagePayload(message) {
3
19
  const jsonPayload = await xmlToJson(message.msg);
4
20
  const len = parseInt(jsonPayload.msg.emoji.$.len, 10) || 0;
@@ -8,16 +24,19 @@ export async function parseEmotionMessagePayload(message) {
8
24
  const type = parseInt(jsonPayload.msg.emoji.$.type, 10) || 0;
9
25
  const md5 = jsonPayload.msg.emoji.$.md5;
10
26
  let gameext;
27
+ let game;
11
28
  if (jsonPayload.msg.gameext) {
12
29
  const gameextType = parseInt(jsonPayload.msg.gameext.$.type, 10) || 0;
13
30
  const gameextContent = parseInt(jsonPayload.msg.gameext.$.content, 10) || 0;
14
31
  gameext = `<gameext type="${gameextType}" content="${gameextContent}" ></gameext>`;
32
+ game = parseEmojiGame(gameextType, gameextContent);
15
33
  }
16
34
  return {
17
35
  authkey: jsonPayload.msg.emoji.$?.tpauthkey,
18
36
  aeskey: jsonPayload.msg.emoji.$?.aeskey,
19
37
  is_ww: !!jsonPayload.msg.emoji.$?.tpurl,
20
38
  cdnurl,
39
+ game,
21
40
  gameext,
22
41
  height,
23
42
  len,
@@ -18,6 +18,8 @@ export interface ImageXmlSchema {
18
18
  length: string;
19
19
  cdnbigimgurl: string;
20
20
  hdlength: string;
21
+ hevc_mid_size?: string;
22
+ cdnmidimgurl_size?: string;
21
23
  md5: string;
22
24
  tpurl?: string;
23
25
  tpauthkey?: string;
@@ -25,6 +27,10 @@ export interface ImageXmlSchema {
25
27
  };
26
28
  };
27
29
  }
30
+ export interface ImageFileCandidate {
31
+ file_id: string;
32
+ file_size: string;
33
+ }
28
34
  export interface ImageMessagePayload {
29
35
  aes_key: string;
30
36
  auth_key: string;
@@ -32,6 +38,8 @@ export interface ImageMessagePayload {
32
38
  file_size: string;
33
39
  file_name: string;
34
40
  is_ww: boolean;
41
+ candidates?: ImageFileCandidate[];
35
42
  }
36
43
  export declare function parseImageMessagePayload(rawPayload: MessagePayload): Promise<ImageMessagePayload>;
44
+ export declare function buildImageCandidates(schema: ImageXmlSchema['msg']['img']['$']): ImageFileCandidate[];
37
45
  //# sourceMappingURL=message-image.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"message-image.d.ts","sourceRoot":"","sources":["../../../../../src/matrix/messages/message-image.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAGvD,MAAM,WAAW,cAAc;IAC3B,GAAG,EAAE;QACD,GAAG,EAAE;YACD,CAAC,EAAE;gBACC,MAAM,EAAE,MAAM,CAAA;gBACd,QAAQ,EAAE,MAAM,CAAA;gBAChB,cAAc,EAAE,MAAM,CAAA;gBACtB,WAAW,EAAE,MAAM,CAAA;gBACnB,cAAc,EAAE,MAAM,CAAA;gBACtB,cAAc,EAAE,MAAM,CAAA;gBACtB,aAAa,EAAE,MAAM,CAAA;gBACrB,YAAY,EAAE,MAAM,CAAA;gBACpB,WAAW,EAAE,MAAM,CAAA;gBACnB,WAAW,EAAE,MAAM,CAAA;gBACnB,UAAU,EAAE,MAAM,CAAA;gBAClB,YAAY,EAAE,MAAM,CAAA;gBACpB,MAAM,EAAE,MAAM,CAAA;gBACd,YAAY,EAAE,MAAM,CAAA;gBACpB,QAAQ,EAAE,MAAM,CAAA;gBAChB,GAAG,EAAE,MAAM,CAAA;gBACX,KAAK,CAAC,EAAE,MAAM,CAAA;gBACd,SAAS,CAAC,EAAE,MAAM,CAAA;aACrB,CAAA;SACJ,CAAA;KACJ,CAAA;CACJ;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,wBAAwB,CAAE,UAAU,EAAE,cAAc,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAYxG"}
1
+ {"version":3,"file":"message-image.d.ts","sourceRoot":"","sources":["../../../../../src/matrix/messages/message-image.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAGvD,MAAM,WAAW,cAAc;IAC3B,GAAG,EAAE;QACD,GAAG,EAAE;YACD,CAAC,EAAE;gBACC,MAAM,EAAE,MAAM,CAAA;gBACd,QAAQ,EAAE,MAAM,CAAA;gBAChB,cAAc,EAAE,MAAM,CAAA;gBACtB,WAAW,EAAE,MAAM,CAAA;gBACnB,cAAc,EAAE,MAAM,CAAA;gBACtB,cAAc,EAAE,MAAM,CAAA;gBACtB,aAAa,EAAE,MAAM,CAAA;gBACrB,YAAY,EAAE,MAAM,CAAA;gBACpB,WAAW,EAAE,MAAM,CAAA;gBACnB,WAAW,EAAE,MAAM,CAAA;gBACnB,UAAU,EAAE,MAAM,CAAA;gBAClB,YAAY,EAAE,MAAM,CAAA;gBACpB,MAAM,EAAE,MAAM,CAAA;gBACd,YAAY,EAAE,MAAM,CAAA;gBACpB,QAAQ,EAAE,MAAM,CAAA;gBAEhB,aAAa,CAAC,EAAE,MAAM,CAAA;gBACtB,iBAAiB,CAAC,EAAE,MAAM,CAAA;gBAC1B,GAAG,EAAE,MAAM,CAAA;gBACX,KAAK,CAAC,EAAE,MAAM,CAAA;gBACd,SAAS,CAAC,EAAE,MAAM,CAAA;aACrB,CAAA;SACJ,CAAA;KACJ,CAAA;CACJ;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IAKf,UAAU,CAAC,EAAE,kBAAkB,EAAE,CAAC;CACnC;AAED,wBAAsB,wBAAwB,CAAE,UAAU,EAAE,cAAc,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAgBxG;AAYD,wBAAgB,oBAAoB,CAAE,MAAM,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,kBAAkB,EAAE,CAgCrG"}
@@ -2,12 +2,47 @@ import { xmlToJson } from '../utils/xml-to-json.js';
2
2
  export async function parseImageMessagePayload(rawPayload) {
3
3
  const imageXml = await xmlToJson(rawPayload.msg);
4
4
  const schema = imageXml.msg.img.$;
5
+ const candidates = buildImageCandidates(schema);
6
+ const primary = candidates[0] || { file_id: schema.cdnmidimgurl || schema.tpurl || '', file_size: schema.length };
5
7
  return {
6
8
  is_ww: !!schema.tpurl,
7
9
  aes_key: schema.aeskey,
8
10
  auth_key: schema.tpauthkey || '',
9
- file_id: schema.cdnmidimgurl || schema.tpurl || '',
10
- file_size: schema.length,
11
+ file_id: primary.file_id,
12
+ file_size: primary.file_size,
11
13
  file_name: '',
14
+ candidates: candidates.length ? candidates : [primary],
12
15
  };
13
16
  }
17
+ const isPositiveSize = (size) => !!size && Number(size) > 0;
18
+ export function buildImageCandidates(schema) {
19
+ if (schema.tpurl) {
20
+ return [{ file_id: schema.cdnmidimgurl || schema.tpurl, file_size: schema.length }];
21
+ }
22
+ const midUrl = schema.cdnmidimgurl;
23
+ const bigUrl = schema.cdnbigimgurl;
24
+ const list = [
25
+ [midUrl, schema.cdnmidimgurl_size],
26
+ [midUrl, schema.hevc_mid_size],
27
+ ];
28
+ if (isPositiveSize(schema.hdlength)) {
29
+ list.push([midUrl, schema.length], [bigUrl, schema.hdlength]);
30
+ }
31
+ else {
32
+ list.push([bigUrl, schema.length], [midUrl, schema.length]);
33
+ }
34
+ const seen = new Set();
35
+ const candidates = [];
36
+ for (const [fileId, fileSize] of list) {
37
+ if (!fileId || !isPositiveSize(fileSize)) {
38
+ continue;
39
+ }
40
+ const key = `${fileId}|${fileSize}`;
41
+ if (seen.has(key)) {
42
+ continue;
43
+ }
44
+ seen.add(key);
45
+ candidates.push({ file_id: fileId, file_size: fileSize });
46
+ }
47
+ return candidates;
48
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env -S node -r ts-node/register
2
+ export {};
3
+ //# sourceMappingURL=message-image.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-image.spec.d.ts","sourceRoot":"","sources":["../../../../../src/matrix/messages/message-image.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env -S node -r ts-node/register
2
+ import { test } from 'tstest';
3
+ import { parseImageMessagePayload } from './message-image.js';
4
+ const buildPayload = (imgAttrs) => ({
5
+ msg: `<?xml version="1.0"?>\n<msg>\n\t<img ${imgAttrs}>\n\t</img>\n</msg>\n`,
6
+ });
7
+ test('parseImageMessagePayload uses hevc_mid_size when downloading mid image', async (t) => {
8
+ const payload = await parseImageMessagePayload(buildPayload('aeskey="key" cdnmidimgurl="mid-url" length="157626" md5="md5" hevc_mid_size="53274"'));
9
+ t.equal(payload.file_id, 'mid-url', 'should use mid image url');
10
+ t.equal(payload.file_size, '53274', 'size should match the mid image, not the origin length');
11
+ t.equal(payload.is_ww, false, 'should not be ww image');
12
+ });
13
+ test('parseImageMessagePayload prefers cdnmidimgurl_size', async (t) => {
14
+ const payload = await parseImageMessagePayload(buildPayload('aeskey="key" cdnmidimgurl="mid-url" length="157626" hevc_mid_size="53274" cdnmidimgurl_size="60000"'));
15
+ t.equal(payload.file_size, '60000', 'cdnmidimgurl_size should win');
16
+ });
17
+ test('parseImageMessagePayload falls back to length for legacy xml', async (t) => {
18
+ const payload = await parseImageMessagePayload(buildPayload('aeskey="key" cdnmidimgurl="mid-url" length="12345" hevc_mid_size="0"'));
19
+ t.equal(payload.file_size, '12345', 'length should be used when no mid size is available');
20
+ });
21
+ test('parseImageMessagePayload falls back to length when mid size attrs are absent', async (t) => {
22
+ const payload = await parseImageMessagePayload(buildPayload('aeskey="key" cdnmidimgurl="mid-url" length="12345" md5="md5"'));
23
+ t.equal(payload.file_size, '12345', 'length should be used when hevc_mid_size is missing');
24
+ });
25
+ test('parseImageMessagePayload uses big image url when mid size is missing', async (t) => {
26
+ const payload = await parseImageMessagePayload(buildPayload('aeskey="key" cdnmidimgurl="mid-url" cdnbigimgurl="big-url" length="157626"'));
27
+ t.equal(payload.file_id, 'big-url', 'should switch to origin image url');
28
+ t.equal(payload.file_size, '157626', 'size should be origin length');
29
+ });
30
+ test('parseImageMessagePayload keeps mid url for legacy xml with hdlength', async (t) => {
31
+ const payload = await parseImageMessagePayload(buildPayload('aeskey="key" cdnmidimgurl="mid-url" cdnbigimgurl="big-url" length="12345" hdlength="99999"'));
32
+ t.equal(payload.file_id, 'mid-url', 'legacy xml should still use mid url');
33
+ t.equal(payload.file_size, '12345', 'legacy length is mid image size');
34
+ });
35
+ test('parseImageMessagePayload keeps length for ww image', async (t) => {
36
+ const payload = await parseImageMessagePayload(buildPayload('aeskey="key" tpurl="tp-url" tpauthkey="auth" length="888" hevc_mid_size="111"'));
37
+ t.equal(payload.is_ww, true, 'should be ww image');
38
+ t.equal(payload.file_id, 'tp-url', 'should use tpurl');
39
+ t.equal(payload.file_size, '888', 'ww image should keep length');
40
+ });
41
+ test('parseImageMessagePayload builds fallback candidates for new xml', async (t) => {
42
+ const payload = await parseImageMessagePayload(buildPayload('aeskey="key" cdnmidimgurl="mid-url" cdnbigimgurl="big-url" length="157626" hevc_mid_size="53274"'));
43
+ t.same(payload.candidates, [
44
+ { file_id: 'mid-url', file_size: '53274' },
45
+ { file_id: 'big-url', file_size: '157626' },
46
+ { file_id: 'mid-url', file_size: '157626' },
47
+ ], 'should try mid size first, then origin, then legacy combination');
48
+ t.equal(payload.file_id, payload.candidates?.[0]?.file_id, 'file_id should be first candidate');
49
+ t.equal(payload.file_size, payload.candidates?.[0]?.file_size, 'file_size should be first candidate');
50
+ });
51
+ test('parseImageMessagePayload builds fallback candidates for legacy xml', async (t) => {
52
+ const payload = await parseImageMessagePayload(buildPayload('aeskey="key" cdnmidimgurl="mid-url" cdnbigimgurl="big-url" length="12345" hdlength="99999"'));
53
+ t.same(payload.candidates, [
54
+ { file_id: 'mid-url', file_size: '12345' },
55
+ { file_id: 'big-url', file_size: '99999' },
56
+ ], 'legacy xml should try mid + length, then big + hdlength');
57
+ });
58
+ test('parseImageMessagePayload dedupes candidates', async (t) => {
59
+ const payload = await parseImageMessagePayload(buildPayload('aeskey="key" cdnmidimgurl="mid-url" length="157626" hevc_mid_size="53274" cdnmidimgurl_size="53274"'));
60
+ t.same(payload.candidates, [
61
+ { file_id: 'mid-url', file_size: '53274' },
62
+ { file_id: 'mid-url', file_size: '157626' },
63
+ ], 'same url + size should only appear once');
64
+ });
@@ -48,6 +48,12 @@ export interface VideoMessagePayload {
48
48
  thumb_width: string;
49
49
  thumb_height: string;
50
50
  file_name: '';
51
+ candidates?: Array<{
52
+ file_id: string;
53
+ file_size: string;
54
+ aes_key?: string;
55
+ }>;
51
56
  }
52
57
  export declare function parseVideoMessagePayload(rawPayload: MessagePayload): Promise<VideoMessagePayload>;
58
+ export declare function buildVideoCandidates(schema: VideoXmlSchema['msg']['videomsg']['$']): VideoMessagePayload['candidates'];
53
59
  //# sourceMappingURL=message-video.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"message-video.d.ts","sourceRoot":"","sources":["../../../../../src/matrix/messages/message-video.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAGvD,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE;QACD,QAAQ,EAAE;YACN,CAAC,EAAE;gBACC,MAAM,EAAE,MAAM,CAAA;gBACd,WAAW,EAAE,MAAM,CAAA;gBACnB,cAAc,EAAE,MAAM,CAAA;gBACtB,WAAW,EAAE,MAAM,CAAA;gBACnB,MAAM,EAAE,MAAM,CAAA;gBACd,UAAU,EAAE,MAAM,CAAA;gBAClB,cAAc,EAAE,MAAM,CAAA;gBACtB,aAAa,EAAE,MAAM,CAAA;gBACrB,cAAc,EAAE,MAAM,CAAA;gBACtB,YAAY,EAAE,MAAM,CAAA;gBACpB,GAAG,EAAE,MAAM,CAAA;gBACX,MAAM,EAAE,MAAM,CAAA;gBACd,aAAa,EAAE,MAAM,CAAA;gBACrB,MAAM,EAAE,MAAM,CAAA;gBACd,SAAS,EAAE,MAAM,CAAA;gBACjB,cAAc,EAAE,MAAM,CAAA;gBACtB,iBAAiB,EAAE,MAAM,CAAA;gBACzB,iBAAiB,EAAE,MAAM,CAAA;gBACzB,eAAe,EAAE,MAAM,CAAA;gBACvB,IAAI,EAAE,MAAM,CAAA;gBACZ,UAAU,CAAC,EAAE,MAAM,CAAA;gBACnB,SAAS,CAAC,EAAE,MAAM,CAAA;gBAClB,UAAU,CAAC,EAAE,MAAM,CAAA;gBACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;gBACjB,aAAa,CAAC,EAAE,MAAM,CAAA;gBACtB,YAAY,CAAC,EAAE,MAAM,CAAA;gBACrB,aAAa,CAAC,EAAE,MAAM,CAAA;gBACtB,aAAa,CAAC,EAAE,MAAM,CAAA;aACzB,CAAA;SACJ,CAAA;KACJ,CAAA;CACF;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,EAAE,CAAA;CACd;AAED,wBAAsB,wBAAwB,CAAE,UAAU,EAAE,cAAc,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAkBxG"}
1
+ {"version":3,"file":"message-video.d.ts","sourceRoot":"","sources":["../../../../../src/matrix/messages/message-video.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAGvD,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE;QACD,QAAQ,EAAE;YACN,CAAC,EAAE;gBACC,MAAM,EAAE,MAAM,CAAA;gBACd,WAAW,EAAE,MAAM,CAAA;gBACnB,cAAc,EAAE,MAAM,CAAA;gBACtB,WAAW,EAAE,MAAM,CAAA;gBACnB,MAAM,EAAE,MAAM,CAAA;gBACd,UAAU,EAAE,MAAM,CAAA;gBAClB,cAAc,EAAE,MAAM,CAAA;gBACtB,aAAa,EAAE,MAAM,CAAA;gBACrB,cAAc,EAAE,MAAM,CAAA;gBACtB,YAAY,EAAE,MAAM,CAAA;gBACpB,GAAG,EAAE,MAAM,CAAA;gBACX,MAAM,EAAE,MAAM,CAAA;gBACd,aAAa,EAAE,MAAM,CAAA;gBACrB,MAAM,EAAE,MAAM,CAAA;gBACd,SAAS,EAAE,MAAM,CAAA;gBACjB,cAAc,EAAE,MAAM,CAAA;gBACtB,iBAAiB,EAAE,MAAM,CAAA;gBACzB,iBAAiB,EAAE,MAAM,CAAA;gBACzB,eAAe,EAAE,MAAM,CAAA;gBACvB,IAAI,EAAE,MAAM,CAAA;gBACZ,UAAU,CAAC,EAAE,MAAM,CAAA;gBACnB,SAAS,CAAC,EAAE,MAAM,CAAA;gBAClB,UAAU,CAAC,EAAE,MAAM,CAAA;gBACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;gBACjB,aAAa,CAAC,EAAE,MAAM,CAAA;gBACtB,YAAY,CAAC,EAAE,MAAM,CAAA;gBACrB,aAAa,CAAC,EAAE,MAAM,CAAA;gBACtB,aAAa,CAAC,EAAE,MAAM,CAAA;aACzB,CAAA;SACJ,CAAA;KACJ,CAAA;CACF;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,EAAE,CAAC;IAMd,UAAU,CAAC,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9E;AAID,wBAAsB,wBAAwB,CAAE,UAAU,EAAE,cAAc,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAmBxG;AAED,wBAAgB,oBAAoB,CAAE,MAAM,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,GAAG,mBAAmB,CAAC,YAAY,CAAC,CAgBvH"}
@@ -1,4 +1,5 @@
1
1
  import { xmlToJson } from '../utils/xml-to-json.js';
2
+ const isPositiveSize = (size) => !!size && Number(size) > 0;
2
3
  export async function parseVideoMessagePayload(rawPayload) {
3
4
  const videoXml = await xmlToJson(rawPayload.msg);
4
5
  const videoSchema = videoXml.msg.videomsg.$;
@@ -15,5 +16,23 @@ export async function parseVideoMessagePayload(rawPayload) {
15
16
  thumb_width: videoSchema.cdnthumbwidth || videoSchema.tpthumbwidth || '',
16
17
  thumb_height: videoSchema.cdnthumbheight || videoSchema.tpthumbheight || '',
17
18
  file_name: '',
19
+ candidates: buildVideoCandidates(videoSchema),
18
20
  };
19
21
  }
22
+ export function buildVideoCandidates(schema) {
23
+ if (schema.tpvideourl) {
24
+ return undefined;
25
+ }
26
+ const candidates = [];
27
+ if (schema.cdnvideourl && isPositiveSize(schema.length)) {
28
+ candidates.push({ file_id: schema.cdnvideourl, file_size: schema.length });
29
+ }
30
+ if (schema.cdnrawvideourl && isPositiveSize(schema.rawlength)) {
31
+ candidates.push({
32
+ file_id: schema.cdnrawvideourl,
33
+ file_size: schema.rawlength,
34
+ aes_key: schema.cdnrawvideoaeskey || schema.aeskey,
35
+ });
36
+ }
37
+ return candidates.length ? candidates : undefined;
38
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env -S node -r ts-node/register
2
+ export {};
3
+ //# sourceMappingURL=message-video.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-video.spec.d.ts","sourceRoot":"","sources":["../../../../../src/matrix/messages/message-video.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env -S node -r ts-node/register
2
+ import { test } from 'tstest';
3
+ import { parseVideoMessagePayload } from './message-video.js';
4
+ import { parseSizeMismatch } from '../service/request.js';
5
+ const buildPayload = (attrs) => ({
6
+ msg: `<?xml version="1.0"?>\n<msg>\n\t<videomsg ${attrs} />\n</msg>\n`,
7
+ });
8
+ test('parseVideoMessagePayload adds raw video as fallback candidate', async (t) => {
9
+ const payload = await parseVideoMessagePayload(buildPayload('aeskey="key" cdnvideourl="video-url" length="766838" playlength="5" rawlength="769445" cdnrawvideourl="raw-url" cdnrawvideoaeskey="raw-key"'));
10
+ t.equal(payload.file_id, 'video-url', 'primary file_id unchanged');
11
+ t.equal(payload.file_size, '766838', 'primary file_size unchanged');
12
+ t.same(payload.candidates, [
13
+ { file_id: 'video-url', file_size: '766838' },
14
+ { file_id: 'raw-url', file_size: '769445', aes_key: 'raw-key' },
15
+ ], 'raw video should be second candidate with its own aes key');
16
+ });
17
+ test('parseVideoMessagePayload skips empty raw video', async (t) => {
18
+ const payload = await parseVideoMessagePayload(buildPayload('aeskey="key" cdnvideourl="video-url" length="766838" playlength="5" rawlength="0" cdnrawvideourl="" cdnrawvideoaeskey=""'));
19
+ t.same(payload.candidates, [{ file_id: 'video-url', file_size: '766838' }], 'only cdn video candidate');
20
+ });
21
+ test('parseSizeMismatch extracts actual size from backend error', async (t) => {
22
+ t.equal(parseSizeMismatch('download plaintext size differs: expected 766838, got 1228708'), '1228708', 'should return actual size');
23
+ t.equal(parseSizeMismatch('timeout'), undefined, 'other errors return undefined');
24
+ t.equal(parseSizeMismatch(undefined), undefined, 'undefined returns undefined');
25
+ });