修复群昵称读取错误的问题

This commit is contained in:
xuncha
2026-02-01 00:07:38 +08:00
parent cffeeb26ec
commit 65365107f5
7 changed files with 62 additions and 13 deletions

1
.gitignore vendored
View File

@@ -57,3 +57,4 @@ Thumbs.db
wcdb/ wcdb/
*info *info
*.md

View File

@@ -67,6 +67,38 @@ class AnalyticsService {
return new Set(this.getExcludedUsernamesList()) return new Set(this.getExcludedUsernamesList())
} }
private escapeSqlValue(value: string): string {
return value.replace(/'/g, "''")
}
private async getAliasMap(usernames: string[]): Promise<Record<string, string>> {
const map: Record<string, string> = {}
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<string, any>[]) {
const username = row.username || ''
const alias = row.alias || ''
if (username && alias) {
map[username] = alias
}
}
}
return map
}
private cleanAccountDirName(name: string): string { private cleanAccountDirName(name: string): string {
const trimmed = name.trim() const trimmed = name.trim()
if (!trimmed) return trimmed 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 { try {
const conn = await this.ensureConnected() const conn = await this.ensureConnected()
if (!conn.success || !conn.cleanedWxid) return { success: false, error: conn.error } if (!conn.success || !conn.cleanedWxid) return { success: false, error: conn.error }
@@ -435,9 +467,10 @@ class AnalyticsService {
} }
const usernameList = Array.from(usernames) const usernameList = Array.from(usernames)
const [displayNames, avatarUrls] = await Promise.all([ const [displayNames, avatarUrls, aliasMap] = await Promise.all([
wcdbService.getDisplayNames(usernameList), wcdbService.getDisplayNames(usernameList),
wcdbService.getAvatarUrls(usernameList) wcdbService.getAvatarUrls(usernameList),
this.getAliasMap(usernameList)
]) ])
const entries = usernameList.map((username) => { const entries = usernameList.map((username) => {
@@ -447,7 +480,9 @@ class AnalyticsService {
const avatarUrl = avatarUrls.success && avatarUrls.map const avatarUrl = avatarUrls.success && avatarUrls.map
? avatarUrls.map[username] ? avatarUrls.map[username]
: undefined : 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 } return { success: true, data: entries }

View File

@@ -260,7 +260,7 @@ class ExportService {
} }
// 清理昵称:去除前后空白和特殊字符 // 清理昵称:去除前后空白和特殊字符
nickname = nickname.trim().replace(/[\x00-\x1F\x7F]/g, '') nickname = this.normalizeGroupNickname(nickname)
// 只保存有效的群昵称(长度 > 0 且 < 50 // 只保存有效的群昵称(长度 > 0 且 < 50
if (nickname && nickname.length > 0 && nickname.length < 50) { if (nickname && nickname.length > 0 && nickname.length < 50) {
@@ -432,6 +432,15 @@ class ExportService {
return /^[0-9a-fA-F]+$/.test(s) 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 ? contact.contact.nickName
: (senderInfo.displayName || senderWxid) : (senderInfo.displayName || senderWxid)
const senderRemark = contact.success && contact.contact?.remark ? contact.contact.remark : '' 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( const senderDisplayName = this.getPreferredDisplayName(
@@ -2080,7 +2089,7 @@ class ExportService {
? sessionContact.contact.remark ? sessionContact.contact.remark
: '' : ''
const sessionGroupNickname = isGroup const sessionGroupNickname = isGroup
? (groupNicknamesMap.get(sessionId.toLowerCase()) || '') ? this.normalizeGroupNickname(groupNicknamesMap.get(sessionId.toLowerCase()) || '')
: '' : ''
// 使用用户偏好的显示名称 // 使用用户偏好的显示名称
@@ -2447,7 +2456,7 @@ class ExportService {
// 获取群昵称 (仅群聊且完整列模式) // 获取群昵称 (仅群聊且完整列模式)
if (isGroup && !useCompactColumns && senderWxid) { if (isGroup && !useCompactColumns && senderWxid) {
senderGroupNickname = groupNicknamesMap.get(senderWxid.toLowerCase()) || '' senderGroupNickname = this.normalizeGroupNickname(groupNicknamesMap.get(senderWxid.toLowerCase()) || '')
} }

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "weflow", "name": "weflow",
"version": "1.4.4", "version": "1.5.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "weflow", "name": "weflow",
"version": "1.4.4", "version": "1.5.0",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"better-sqlite3": "^12.5.0", "better-sqlite3": "^12.5.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "weflow", "name": "weflow",
"version": "1.4.4", "version": "1.5.0",
"description": "WeFlow", "description": "WeFlow",
"main": "dist-electron/main.js", "main": "dist-electron/main.js",
"author": "cc", "author": "cc",

View File

@@ -11,6 +11,7 @@ interface ExcludeCandidate {
username: string username: string
displayName: string displayName: string
avatarUrl?: string avatarUrl?: string
wechatId?: string
} }
const normalizeUsername = (value: string) => value.trim().toLowerCase() const normalizeUsername = (value: string) => value.trim().toLowerCase()
@@ -167,7 +168,8 @@ function AnalyticsPage() {
.filter((candidate) => { .filter((candidate) => {
const query = excludeQuery.trim().toLowerCase() const query = excludeQuery.trim().toLowerCase()
if (!query) return true 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) return haystack.includes(query)
}) })
.sort((a, b) => { .sort((a, b) => {
@@ -464,6 +466,7 @@ function AnalyticsPage() {
<div className="exclude-list"> <div className="exclude-list">
{visibleExcludeCandidates.map((candidate) => { {visibleExcludeCandidates.map((candidate) => {
const isChecked = draftExcluded.has(normalizeUsername(candidate.username)) const isChecked = draftExcluded.has(normalizeUsername(candidate.username))
const wechatId = candidate.wechatId?.trim() || candidate.username
return ( return (
<label key={candidate.username} className={`exclude-item ${isChecked ? 'active' : ''}`}> <label key={candidate.username} className={`exclude-item ${isChecked ? 'active' : ''}`}>
<input <input
@@ -476,7 +479,7 @@ function AnalyticsPage() {
</div> </div>
<div className="exclude-info"> <div className="exclude-info">
<span className="exclude-name">{candidate.displayName}</span> <span className="exclude-name">{candidate.displayName}</span>
<span className="exclude-username">{candidate.username}</span> <span className="exclude-username">{wechatId}</span>
</div> </div>
</label> </label>
) )

View File

@@ -191,6 +191,7 @@ export interface ElectronAPI {
username: string username: string
displayName: string displayName: string
avatarUrl?: string avatarUrl?: string
wechatId?: string
}> }>
error?: string error?: string
}> }>