import { extractXmlValue, normalizeAppMessageContent } from './xmlExtractor' export function buildGroupNicknameIdCandidates(values: Array): string[] { const set = new Set() for (const rawValue of values) { const raw = String(rawValue || '').trim() if (!raw) continue set.add(raw) } return Array.from(set) } function normalizeGroupNicknameIdentity(value: string): string { return String(value || '').trim().toLowerCase() } function normalizeGroupNickname(value: string): string { const trimmed = (value || '').trim() if (!trimmed) return '' const cleaned = trimmed.replace(/[\x00-\x1F\x7F]/g, '') if (!cleaned) return '' if (/^[,"'“”‘’,、]+$/.test(cleaned)) return '' return cleaned } export function resolveGroupNicknameByCandidates(groupNicknamesMap: Map, candidates: Array): string { const idCandidates = buildGroupNicknameIdCandidates(candidates) if (idCandidates.length === 0) return '' let resolved = '' for (const id of idCandidates) { const normalizedId = normalizeGroupNicknameIdentity(id) if (!normalizedId) continue const candidateNickname = normalizeGroupNickname(groupNicknamesMap.get(normalizedId) || '') if (!candidateNickname) continue if (!resolved) { resolved = candidateNickname continue } if (resolved !== candidateNickname) return '' } return resolved } export async function resolveTransferDesc( content: string, myWxid: string, groupNicknamesMap: Map, getContactName: (username: string) => Promise ): Promise { const normalizedContent = normalizeAppMessageContent(content || '') if (!normalizedContent) return null const xmlType = extractXmlValue(normalizedContent, 'type') if (xmlType && xmlType !== '2000') return null const payerUsername = extractXmlValue(normalizedContent, 'payer_username') const receiverUsername = extractXmlValue(normalizedContent, 'receiver_username') if (!payerUsername || !receiverUsername) return null const cleanedMyWxid = myWxid const resolveName = async (username: string): Promise => { if (myWxid && (username === myWxid || username === cleanedMyWxid)) { const groupNick = resolveGroupNicknameByCandidates(groupNicknamesMap, [username, myWxid, cleanedMyWxid]) if (groupNick) return groupNick return '我' } const groupNick = resolveGroupNicknameByCandidates(groupNicknamesMap, [username]) if (groupNick) return groupNick return getContactName(username) } const [payerName, receiverName] = await Promise.all([ resolveName(payerUsername), resolveName(receiverUsername) ]) return `${payerName} 转账给 ${receiverName}` } export function isSameWxid(lhs?: string, rhs?: string): boolean { const left = new Set(buildGroupNicknameIdCandidates([lhs]).map((id) => id.toLowerCase())) if (left.size === 0) return false const right = buildGroupNicknameIdCandidates([rhs]).map((id) => id.toLowerCase()) return right.some((id) => left.has(id)) } export function getTransferPrefix(content: string, myWxid?: string, senderWxid?: string, isSend?: boolean): '[转账]' | '[转账收款]' { const normalizedContent = normalizeAppMessageContent(content || '') if (!normalizedContent) return '[转账]' const paySubtype = extractXmlValue(normalizedContent, 'paysubtype') if (paySubtype === '3') return '[转账收款]' if (paySubtype === '1') return '[转账]' const payerUsername = extractXmlValue(normalizedContent, 'payer_username') const receiverUsername = extractXmlValue(normalizedContent, 'receiver_username') const senderIsPayer = senderWxid ? isSameWxid(senderWxid, payerUsername) : false const senderIsReceiver = senderWxid ? isSameWxid(senderWxid, receiverUsername) : false if (senderWxid) { if (senderIsReceiver && !senderIsPayer) return '[转账]' if (senderIsPayer && !senderIsReceiver) return '[转账收款]' } if (myWxid) { if (isSameWxid(myWxid, receiverUsername)) return '[转账]' if (isSameWxid(myWxid, payerUsername)) return '[转账收款]' } return '[转账]' } export function isTransferExportContent(content: string): boolean { return content.startsWith('[转账]') || content.startsWith('[转账收款]') } export function appendTransferDesc(content: string, transferDesc: string): string { const prefix = content.startsWith('[转账收款]') ? '[转账收款]' : '[转账]' return content.replace(prefix, `${prefix} (${transferDesc})`) } export function extractAmountFromText(text: string): string | null { if (!text) return null const match = /([¥¥]\s*\d+(?:\.\d+)?|\d+(?:\.\d+)?)/.exec(text) return match ? match[1].replace(/\s+/g, '') : null }