mirror of
https://mirror.skon.top/github.com/ILoveBingLu/CipherTalk
synced 2026-04-30 22:01:03 +08:00
fix: 修复 issue #92 中的 4 个 bug
- fix(MomentsWindow): 修复朋友圈刷新崩溃问题 - 新增 postsRef 追踪最新 posts 状态,避免触发重渲染 - loadPosts 中改用 postsRef.current 读取数据 - 从 useCallback 依赖数组移除 posts,断开依赖循环 - fix(exportService): 修复群聊导出数据串问题 - 改进 findMessageTable 哈希匹配为大小写无关的精确匹配 - 匹配失败返回 null,不再回退到第一个表 - fix(exportService): 修复导出 HTML 实体转义乱码问题 - 增强 decodeHtmlEntities,支持   
 等常见实体 - 对 content、senderDisplayName、formattedTime 字段解码 - fix(exportService): 修复红包/群收款显示原始 XML 问题 - parseChatHistory 中过滤 datatype=2001 和 2002 的消息 - fix(htmlExportGenerator): 新增浏览器端二次解码兜底 关闭 #92
This commit is contained in:
@@ -301,13 +301,24 @@ class ExportService {
|
||||
const tables = db.prepare(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'Msg_%'"
|
||||
).all() as any[]
|
||||
const hash = this.getTableNameHash(sessionId)
|
||||
const hash = this.getTableNameHash(sessionId).toLowerCase()
|
||||
// 1. 精确哈希提取匹配(大小写无关):从表名中提取 32 位 hex 片段后比对
|
||||
for (const table of tables) {
|
||||
if ((table.name as string).includes(hash)) {
|
||||
return table.name
|
||||
const name = table.name as string
|
||||
const hexMatch = name.match(/[0-9a-fA-F]{32}/)
|
||||
if (hexMatch && hexMatch[0].toLowerCase() === hash) {
|
||||
return name
|
||||
}
|
||||
}
|
||||
// 2. 包含匹配(大小写无关)
|
||||
for (const table of tables) {
|
||||
const name = table.name as string
|
||||
if (name.toLowerCase().includes(hash)) {
|
||||
return name
|
||||
}
|
||||
}
|
||||
} catch { }
|
||||
// 匹配失败时返回 null,不回退到第一个表(避免数据串)
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1137,6 +1148,9 @@ class ExportService {
|
||||
const fileext = this.extractXmlValue(body, 'fileext')
|
||||
const datasize = parseInt(this.extractXmlValue(body, 'datasize') || '0')
|
||||
|
||||
// 过滤红包(2001)和群收款(2002)消息,不在导出中显示
|
||||
if (datatype === 2001 || datatype === 2002) continue
|
||||
|
||||
items.push({
|
||||
datatype,
|
||||
sourcename,
|
||||
@@ -1168,6 +1182,15 @@ class ExportService {
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/'/g, "'")
|
||||
// 常见十六进制数字实体
|
||||
.replace(/ /gi, ' ')
|
||||
.replace(/
/gi, '\n')
|
||||
.replace(/	/gi, '\t')
|
||||
.replace(/
/gi, '\r')
|
||||
// 通用十六进制实体 &#xHH;
|
||||
.replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => String.fromCodePoint(parseInt(hex, 16)))
|
||||
// 通用十进制实体 &#NN;
|
||||
.replace(/&#(\d+);/g, (_, dec) => String.fromCodePoint(parseInt(dec, 10)))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1230,11 +1253,18 @@ class ExportService {
|
||||
content = record.datadesc || record.datatitle || '[消息]'
|
||||
}
|
||||
|
||||
// 对关键字段进行 HTML 实体解码,防止导出时出现   等转义字符
|
||||
content = this.decodeHtmlEntities(content)
|
||||
const senderDisplayName = this.decodeHtmlEntities(record.sourcename || 'unknown')
|
||||
const formattedTime = this.decodeHtmlEntities(
|
||||
timestamp > 0 ? this.formatTimestamp(timestamp) : (record.sourcetime || '')
|
||||
)
|
||||
|
||||
const chatRecord: any = {
|
||||
sender: record.sourcename || 'unknown',
|
||||
senderDisplayName: record.sourcename || 'unknown',
|
||||
senderDisplayName,
|
||||
timestamp,
|
||||
formattedTime: timestamp > 0 ? this.formatTimestamp(timestamp) : record.sourcetime,
|
||||
formattedTime,
|
||||
type: typeName,
|
||||
datatype: record.datatype,
|
||||
content
|
||||
|
||||
@@ -878,10 +878,19 @@ body {
|
||||
return 'c' + (Math.abs(hash) % 8);
|
||||
}
|
||||
|
||||
// HTML 实体解码(防止导出数据中残留   等转义字符)
|
||||
function decodeEntities(text) {
|
||||
if (!text) return '';
|
||||
const d = document.createElement('textarea');
|
||||
d.innerHTML = text;
|
||||
return d.value;
|
||||
}
|
||||
|
||||
// HTML 转义
|
||||
function esc(text) {
|
||||
const decoded = decodeEntities(String(text || ''));
|
||||
const d = document.createElement('div');
|
||||
d.textContent = text;
|
||||
d.textContent = decoded;
|
||||
return d.innerHTML;
|
||||
}
|
||||
|
||||
|
||||
@@ -747,6 +747,12 @@ function MomentsWindow() {
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [loadingNewer, setLoadingNewer] = useState(false)
|
||||
const [posts, setPosts] = useState<SnsPost[]>([])
|
||||
const postsRef = useRef<SnsPost[]>([])
|
||||
|
||||
// 同步 postsRef 与 posts state
|
||||
useEffect(() => {
|
||||
postsRef.current = posts
|
||||
}, [posts])
|
||||
const [deletedPostIds, setDeletedPostIds] = useState<Set<string>>(new Set())
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
@@ -877,7 +883,7 @@ function MomentsWindow() {
|
||||
}
|
||||
currentOffset = 0
|
||||
} else if (direction === 'newer') {
|
||||
const topPost = posts[0]
|
||||
const topPost = postsRef.current[0]
|
||||
if (topPost) {
|
||||
startTs = topPost.createTime + 1
|
||||
}
|
||||
@@ -885,7 +891,7 @@ function MomentsWindow() {
|
||||
endTs = undefined // Ensure endTs is cleared for newer posts check
|
||||
} else {
|
||||
// Load older
|
||||
currentOffset = posts.length
|
||||
currentOffset = postsRef.current.length
|
||||
|
||||
// Maintain jumpTargetDate filter if active
|
||||
if (jumpTargetDate) {
|
||||
@@ -946,7 +952,7 @@ function MomentsWindow() {
|
||||
setLoadingNewer(false)
|
||||
loadingRef.current = false
|
||||
}
|
||||
}, [posts, selectedUsernames, searchKeyword, jumpTargetDate])
|
||||
}, [selectedUsernames, searchKeyword, jumpTargetDate])
|
||||
|
||||
// 监听筛选条件变化,自动重置加载
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user