diff --git a/.gitignore b/.gitignore index ae42d85..5cc4ff8 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,4 @@ Thumbs.db wcdb/ *info +*.md diff --git a/electron/services/analyticsService.ts b/electron/services/analyticsService.ts index 1153cfb..e9d965e 100644 --- a/electron/services/analyticsService.ts +++ b/electron/services/analyticsService.ts @@ -67,6 +67,38 @@ class AnalyticsService { return new Set(this.getExcludedUsernamesList()) } + private escapeSqlValue(value: string): string { + return value.replace(/'/g, "''") + } + + private async getAliasMap(usernames: string[]): Promise> { + const map: Record = {} + if (usernames.length === 0) return map + + const chunkSize = 200 + for (let i = 0; i < usernames.length; i += chunkSize) { + const chunk = usernames.slice(i, i + chunkSize) + const inList = chunk.map((u) => `'${this.escapeSqlValue(u)}'`).join(',') + if (!inList) continue + const sql = ` + SELECT username, alias + FROM contact + WHERE username IN (${inList}) + ` + const result = await wcdbService.execQuery('contact', null, sql) + if (!result.success || !result.rows) continue + for (const row of result.rows as Record[]) { + const username = row.username || '' + const alias = row.alias || '' + if (username && alias) { + map[username] = alias + } + } + } + + return map + } + private cleanAccountDirName(name: string): string { const trimmed = name.trim() if (!trimmed) return trimmed @@ -419,7 +451,7 @@ class AnalyticsService { } } - async getExcludeCandidates(): Promise<{ success: boolean; data?: Array<{ username: string; displayName: string; avatarUrl?: string }>; error?: string }> { + async getExcludeCandidates(): Promise<{ success: boolean; data?: Array<{ username: string; displayName: string; avatarUrl?: string; wechatId?: string }>; error?: string }> { try { const conn = await this.ensureConnected() if (!conn.success || !conn.cleanedWxid) return { success: false, error: conn.error } @@ -435,9 +467,10 @@ class AnalyticsService { } const usernameList = Array.from(usernames) - const [displayNames, avatarUrls] = await Promise.all([ + const [displayNames, avatarUrls, aliasMap] = await Promise.all([ wcdbService.getDisplayNames(usernameList), - wcdbService.getAvatarUrls(usernameList) + wcdbService.getAvatarUrls(usernameList), + this.getAliasMap(usernameList) ]) const entries = usernameList.map((username) => { @@ -447,7 +480,9 @@ class AnalyticsService { const avatarUrl = avatarUrls.success && avatarUrls.map ? avatarUrls.map[username] : undefined - return { username, displayName, avatarUrl } + const alias = aliasMap[username] + const wechatId = alias || (!username.startsWith('wxid_') ? username : '') + return { username, displayName, avatarUrl, wechatId } }) return { success: true, data: entries } diff --git a/electron/services/exportService.ts b/electron/services/exportService.ts index 948a2b0..3609f00 100644 --- a/electron/services/exportService.ts +++ b/electron/services/exportService.ts @@ -260,7 +260,7 @@ class ExportService { } // 清理昵称:去除前后空白和特殊字符 - nickname = nickname.trim().replace(/[\x00-\x1F\x7F]/g, '') + nickname = this.normalizeGroupNickname(nickname) // 只保存有效的群昵称(长度 > 0 且 < 50) if (nickname && nickname.length > 0 && nickname.length < 50) { @@ -432,6 +432,15 @@ class ExportService { return /^[0-9a-fA-F]+$/.test(s) } + private 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 + } + /** * 根据用户偏好获取显示名称 */ @@ -2034,7 +2043,7 @@ class ExportService { ? contact.contact.nickName : (senderInfo.displayName || senderWxid) const senderRemark = contact.success && contact.contact?.remark ? contact.contact.remark : '' - const senderGroupNickname = groupNicknamesMap.get(senderWxid?.toLowerCase() || '') || '' + const senderGroupNickname = this.normalizeGroupNickname(groupNicknamesMap.get(senderWxid?.toLowerCase() || '') || '') // 使用用户偏好的显示名称 const senderDisplayName = this.getPreferredDisplayName( @@ -2080,7 +2089,7 @@ class ExportService { ? sessionContact.contact.remark : '' const sessionGroupNickname = isGroup - ? (groupNicknamesMap.get(sessionId.toLowerCase()) || '') + ? this.normalizeGroupNickname(groupNicknamesMap.get(sessionId.toLowerCase()) || '') : '' // 使用用户偏好的显示名称 @@ -2447,7 +2456,7 @@ class ExportService { // 获取群昵称 (仅群聊且完整列模式) if (isGroup && !useCompactColumns && senderWxid) { - senderGroupNickname = groupNicknamesMap.get(senderWxid.toLowerCase()) || '' + senderGroupNickname = this.normalizeGroupNickname(groupNicknamesMap.get(senderWxid.toLowerCase()) || '') } diff --git a/package-lock.json b/package-lock.json index faac1e1..ee1d0f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "weflow", - "version": "1.4.4", + "version": "1.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "weflow", - "version": "1.4.4", + "version": "1.5.0", "hasInstallScript": true, "dependencies": { "better-sqlite3": "^12.5.0", diff --git a/package.json b/package.json index 534b89f..4ba7a88 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "weflow", - "version": "1.4.4", + "version": "1.5.0", "description": "WeFlow", "main": "dist-electron/main.js", "author": "cc", diff --git a/src/pages/AnalyticsPage.tsx b/src/pages/AnalyticsPage.tsx index 41cebba..9e56515 100644 --- a/src/pages/AnalyticsPage.tsx +++ b/src/pages/AnalyticsPage.tsx @@ -11,6 +11,7 @@ interface ExcludeCandidate { username: string displayName: string avatarUrl?: string + wechatId?: string } const normalizeUsername = (value: string) => value.trim().toLowerCase() @@ -167,7 +168,8 @@ function AnalyticsPage() { .filter((candidate) => { const query = excludeQuery.trim().toLowerCase() if (!query) return true - const haystack = `${candidate.displayName} ${candidate.username}`.toLowerCase() + const wechatId = candidate.wechatId || '' + const haystack = `${candidate.displayName} ${candidate.username} ${wechatId}`.toLowerCase() return haystack.includes(query) }) .sort((a, b) => { @@ -464,6 +466,7 @@ function AnalyticsPage() {
{visibleExcludeCandidates.map((candidate) => { const isChecked = draftExcluded.has(normalizeUsername(candidate.username)) + const wechatId = candidate.wechatId?.trim() || candidate.username return ( ) diff --git a/src/types/electron.d.ts b/src/types/electron.d.ts index 1f5f8eb..4e30d2a 100644 --- a/src/types/electron.d.ts +++ b/src/types/electron.d.ts @@ -191,6 +191,7 @@ export interface ElectronAPI { username: string displayName: string avatarUrl?: string + wechatId?: string }> error?: string }>