* 预处理 QQ JSON 文件
(inputPath: string, onProgress?: (progress: ParseProgress) => void)
| 209 | * 预处理 QQ JSON 文件 |
| 210 | */ |
| 211 | async function preprocessQQJson(inputPath: string, onProgress?: (progress: ParseProgress) => void): Promise<string> { |
| 212 | const totalBytes = getFileSize(inputPath) |
| 213 | let bytesRead = 0 |
| 214 | let messagesProcessed = 0 |
| 215 | |
| 216 | const tempDir = getTempDir() |
| 217 | ensureDir(tempDir) |
| 218 | const outputFilename = `slim_${Date.now()}_${path.basename(inputPath)}` |
| 219 | const outputPath = path.join(tempDir, outputFilename) |
| 220 | |
| 221 | onProgress?.(createProgress('parsing', 0, totalBytes, 0, '')) |
| 222 | |
| 223 | // 先从原文件读取 avatars(因为它在文件末尾,消息处理时可能无法访问) |
| 224 | const avatarsStr = readAvatarsFromFile(inputPath) |
| 225 | |
| 226 | return new Promise((resolve, reject) => { |
| 227 | const headChunks: string[] = [] |
| 228 | let headSize = 0 |
| 229 | const maxHeadSize = 100000 |
| 230 | |
| 231 | const headStream = fs.createReadStream(inputPath, { encoding: 'utf-8' }) |
| 232 | let chatInfo: Record<string, unknown> = { name: '未知群聊', type: 'group' } |
| 233 | let metadata: Record<string, unknown> | undefined |
| 234 | let statistics: Record<string, unknown> | undefined |
| 235 | |
| 236 | headStream.on('data', (chunk: string | Buffer) => { |
| 237 | const str = typeof chunk === 'string' ? chunk : chunk.toString('utf-8') |
| 238 | if (headSize < maxHeadSize) { |
| 239 | headChunks.push(str) |
| 240 | headSize += str.length |
| 241 | } else { |
| 242 | headStream.destroy() |
| 243 | } |
| 244 | }) |
| 245 | |
| 246 | headStream.on('close', () => { |
| 247 | const headContent = headChunks.join('') |
| 248 | |
| 249 | try { |
| 250 | const chatInfoMatch = headContent.match(/"chatInfo"\s*:\s*(\{[^}]+\})/) |
| 251 | if (chatInfoMatch) { |
| 252 | chatInfo = JSON.parse(chatInfoMatch[1]) |
| 253 | } |
| 254 | } catch { |
| 255 | // 使用默认值 |
| 256 | } |
| 257 | |
| 258 | try { |
| 259 | const metadataMatch = headContent.match(/"metadata"\s*:\s*(\{[^}]+\})/) |
| 260 | if (metadataMatch) { |
| 261 | metadata = JSON.parse(metadataMatch[1]) |
| 262 | } |
| 263 | } catch { |
| 264 | // 忽略 |
| 265 | } |
| 266 | |
| 267 | try { |
| 268 | const statisticsMatch = headContent.match(/"statistics"\s*:\s*(\{[\s\S]*?\})\s*,\s*"messages"/) |
nothing calls this directly
no test coverage detected