From e69c3626d6610225387080990704182c97b3c3fc Mon Sep 17 00:00:00 2001 From: wuyouMaster Date: Thu, 24 Sep 2026 20:06:16 +0800 Subject: [PATCH] feat: parse streamvideo/gift cards and heuristic local_type 2/8 --- src/main/message-parser.ts | 117 ++++++++++++++++++++++++++++++ tests/unit/message-parser.test.ts | 54 ++++++++++++++ 2 files changed, 171 insertions(+) diff --git a/src/main/message-parser.ts b/src/main/message-parser.ts index 51a34af..26fe73e 100644 --- a/src/main/message-parser.ts +++ b/src/main/message-parser.ts @@ -195,8 +195,12 @@ export function parseMessageContent(content: string, messageType: number): Parse switch (localType) { case 1: return { type: 'text', content: normalized } + case 2: + return parseLegacyType2(normalized) case 3: return parseImageMessage(normalized) + case 8: + return parseLegacyType8(normalized) case 17: return parseForwardBundle(normalized) case 37: @@ -559,6 +563,12 @@ function parseShareMessage(content: string): ParsedContent { if (looksLikeKefuCard(content)) { return parseKefuCard(content) } + if (looksLikeStreamVideo(content)) { + return parseStreamVideoCard(content) + } + if (looksLikeGiftCard(content)) { + return parseGiftCard(content) + } if (appMsgType === '19') { return parseForwardBundle(content) } @@ -643,6 +653,8 @@ function parseShareMessage(content: string): ParsedContent { if (looksLikeMusicShare(content)) return parseMusicShareCard(content) if (looksLikeSubscribeCard(content)) return parseSubscribeCard(content) if (looksLikeKefuCard(content)) return parseKefuCard(content) + if (looksLikeStreamVideo(content)) return parseStreamVideoCard(content) + if (looksLikeGiftCard(content)) return parseGiftCard(content) return { type: 'unknown', raw: content } } @@ -804,6 +816,111 @@ function looksLikeKefuCard(content: string): boolean { ) } +/** 流视频 / 长视频卡(`streamvideotitle` / `finderMegaVideo`)。 */ +function looksLikeStreamVideo(content: string): boolean { + return /streamvideotitle|streamvideoword|streamvideoweburl|streamvideothumb|finderMegaVideo|finderLiveInvite/i.test( + content + ) +} + +/** + * 礼物 / 礼品卡(`csgift` / `giftcarditem`)。 + * 只读展示;**不**调用 `acceptgiftcard` / `preacceptgiftcard` / `getcardgiftinfo`。 + */ +function looksLikeGiftCard(content: string): boolean { + return /]+>/g, ' ')) || '礼物卡', + raw: content + } + } + return { + type: 'share', + title: title || '礼物卡', + des: des || undefined, + url: url || '', + appname: extractXmlValue(decoded, 'brandname') || '礼物', + typeVal: extractAppMsgType(content) || 'giftcard' + } +} + +/** + * local_type=2:真机 histogram 未采到;按内容形态尽量落到 system/text, + * 否则保留 unknown(见 wechat-message-type-coverage.md)。 + */ +function parseLegacyType2(content: string): ParsedContent { + if (/ { }) }) + it('parses stream video cards', () => { + const xml = [ + '', + '51', + '', + '', + '', + '', + '' + ].join('') + expect(parseMessageContent(xml, 49)).toMatchObject({ + type: 'share', + title: '户外长视频', + des: '点击播放', + url: 'https://finder.example/v/1', + appname: '视频号', + typeVal: '51' + }) + }) + + it('parses gift cards without enabling accept actions', () => { + const xml = [ + '', + '<![CDATA[生日礼物卡]]>', + '', + '', + '' + ].join('') + expect(parseMessageContent(xml, 49)).toMatchObject({ + type: 'share', + title: '生日礼物卡', + des: '送你一张礼物卡', + appname: '示例品牌', + typeVal: 'giftcard' + }) + }) + + it('maps legacy local_type 2 and 8 without dropping content', () => { + expect(parseMessageContent('plain status line', 2)).toEqual({ + type: 'text', + content: 'plain status line' + }) + const gifLike = + '' + expect(parseMessageContent(gifLike, 8)).toMatchObject({ + type: 'sticker', + md5: 'abcdefabcdefabcdefabcdefabcdefab' + }) + expect(parseMessageContent('opaque type8', 8)).toMatchObject({ + type: 'unknown', + messageType: 8 + }) + }) + it('parses transfer wcpayinfo fields (type 2000)', () => { // 2026-09 真机转账采样字段名(含微信原文 transcationid 拼写) const xml = [