diff --git a/electron/main/parser/formats/qq-native-txt.ts b/electron/main/parser/formats/qq-native-txt.ts index 9c15fdd..0c3c186 100644 --- a/electron/main/parser/formats/qq-native-txt.ts +++ b/electron/main/parser/formats/qq-native-txt.ts @@ -67,12 +67,41 @@ const GROUP_NAME_REGEX = /^消息对象:(.+)$/ function detectMessageType(content: string): MessageType { const trimmed = content.trim() + + // 基础消息类型 if (trimmed === '[图片]') return MessageType.IMAGE if (trimmed === '[表情]') return MessageType.EMOJI if (trimmed === '[语音]') return MessageType.VOICE if (trimmed === '[视频]') return MessageType.VIDEO if (trimmed === '[文件]') return MessageType.FILE + if (trimmed === '[位置]' || trimmed === '[地理位置]') return MessageType.LOCATION + if (trimmed === '[链接]' || trimmed === '[卡片消息]') return MessageType.LINK + + // 交互消息类型 + if (trimmed === '[红包]' || trimmed.includes('发出了红包')) return MessageType.RED_PACKET + if (trimmed === '[转账]' || trimmed.includes('向你转账')) return MessageType.TRANSFER + if (trimmed.includes('拍了拍') || trimmed === '[拍一拍]') return MessageType.POKE + if (trimmed === '[语音通话]' || trimmed === '[视频通话]' || trimmed.includes('通话时长')) return MessageType.CALL + if (trimmed === '[分享]' || trimmed === '[音乐]' || trimmed === '[小程序]') return MessageType.SHARE + if (trimmed.startsWith('[回复]')) return MessageType.REPLY + if (trimmed === '[转发]' || trimmed === '[聊天记录]') return MessageType.FORWARD + + // 系统消息类型 + if (trimmed.includes('撤回了一条消息') || trimmed === '[撤回]') return MessageType.RECALL + if ( + trimmed.includes('加入了群聊') || + trimmed.includes('退出了群聊') || + trimmed.includes('被移出群聊') || + trimmed.includes('修改了群名称') || + trimmed.includes('成为新群主') || + trimmed.includes('群公告') + ) { + return MessageType.SYSTEM + } + + // 其他方括号包裹的特殊消息 if (trimmed.startsWith('[') && trimmed.endsWith(']')) return MessageType.OTHER + return MessageType.TEXT } @@ -144,7 +173,8 @@ async function* parseTxt(options: ParseOptions): AsyncGenerator ({ platformId: m.platformId, - name: m.platformId, // name 使用 ID - nickname: m.nickname, // nickname 使用群昵称 + accountName: m.nickname, // QQ TXT 格式只有昵称,作为账号名称 + // 不设置 groupNickname,避免同一昵称被重复追踪 })) yield { type: 'members', data: members } diff --git a/electron/main/parser/formats/shuakami-qq-exporter-v4.ts b/electron/main/parser/formats/shuakami-qq-exporter-v4.ts index 8e9bc10..f07ef7a 100644 --- a/electron/main/parser/formats/shuakami-qq-exporter-v4.ts +++ b/electron/main/parser/formats/shuakami-qq-exporter-v4.ts @@ -103,7 +103,16 @@ interface MemberInfo { // ==================== 消息类型转换 ==================== -function convertMessageType(messageType: number | undefined, content: V4Message['content']): MessageType { +function convertMessageType( + messageType: number | undefined, + content: V4Message['content'], + isRecalled?: boolean +): MessageType { + // 撤回消息 + if (isRecalled) { + return MessageType.RECALL + } + // 检查资源类型 if (content.resources && content.resources.length > 0) { const resourceType = content.resources[0].type @@ -117,6 +126,8 @@ function convertMessageType(messageType: number | undefined, content: V4Message[ return MessageType.VOICE case 'file': return MessageType.FILE + case 'location': + return MessageType.LOCATION } } @@ -125,6 +136,49 @@ function convertMessageType(messageType: number | undefined, content: V4Message[ return MessageType.EMOJI } + // 根据文本内容判断特殊消息类型 + const text = content.text?.trim() || '' + + // 红包消息 + if (text.includes('QQ红包') || text.includes('发出了红包') || text === '[红包]') { + return MessageType.RED_PACKET + } + + // 转账消息 + if (text.includes('转账') || text === '[转账]') { + return MessageType.TRANSFER + } + + // 拍一拍/戳一戳 + if (text.includes('拍了拍') || text.includes('戳了戳') || text === '[拍一拍]') { + return MessageType.POKE + } + + // 通话消息 + if (text.includes('语音通话') || text.includes('视频通话') || text.includes('通话时长')) { + return MessageType.CALL + } + + // 分享消息 + if (text === '[分享]' || text === '[音乐]' || text === '[小程序]') { + return MessageType.SHARE + } + + // 链接/卡片消息 + if (text === '[链接]' || text === '[卡片消息]') { + return MessageType.LINK + } + + // 位置消息 + if (text === '[位置]' || text === '[地理位置]') { + return MessageType.LOCATION + } + + // 转发消息 + if (text === '[转发]' || text === '[聊天记录]') { + return MessageType.FORWARD + } + // 根据 messageType 判断 switch (messageType) { case 1: @@ -136,7 +190,7 @@ function convertMessageType(messageType: number | undefined, content: V4Message[ case 7: return MessageType.VIDEO case 9: - return MessageType.TEXT // 回复消息 + return MessageType.REPLY // 回复消息 default: return MessageType.TEXT } @@ -250,7 +304,9 @@ async function* parseV4(options: ParseOptions): AsyncGenerator = { + // 基础消息类型 [MessageType.TEXT]: '文字', [MessageType.IMAGE]: '图片', [MessageType.VOICE]: '语音', [MessageType.VIDEO]: '视频', [MessageType.FILE]: '文件', [MessageType.EMOJI]: '表情', + [MessageType.LINK]: '链接', + [MessageType.LOCATION]: '位置', + // 交互消息类型 + [MessageType.RED_PACKET]: '红包', + [MessageType.TRANSFER]: '转账', + [MessageType.POKE]: '拍一拍', + [MessageType.CALL]: '通话', + [MessageType.SHARE]: '分享', + [MessageType.REPLY]: '回复', + [MessageType.FORWARD]: '转发', + // 系统消息类型 [MessageType.SYSTEM]: '系统', + [MessageType.RECALL]: '撤回', + // 其他 [MessageType.OTHER]: '其他', }