(options: ParseOptions)
| 154 | // ==================== 解析器实现 ==================== |
| 155 | |
| 156 | async function* parseWeFlow(options: ParseOptions): AsyncGenerator<ParseEvent, void, unknown> { |
| 157 | const { filePath, batchSize = 5000, onProgress, onLog } = options |
| 158 | |
| 159 | const totalBytes = getFileSize(filePath) |
| 160 | let bytesRead = 0 |
| 161 | let messagesProcessed = 0 |
| 162 | |
| 163 | // 发送初始进度 |
| 164 | const initialProgress = createProgress('parsing', 0, totalBytes, 0, '') |
| 165 | yield { type: 'progress', data: initialProgress } |
| 166 | onProgress?.(initialProgress) |
| 167 | |
| 168 | // 记录解析开始 |
| 169 | onLog?.('info', `开始解析 WeFlow 导出文件,大小: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`) |
| 170 | |
| 171 | // 读取文件头获取基本信息 |
| 172 | const headContent = readFileHeadBytes(filePath, 5000) |
| 173 | |
| 174 | // 使用流式读取获取完整的 session 对象(因为 session.avatar 可能很大) |
| 175 | let session: WeFlowSession | null = null |
| 176 | try { |
| 177 | await new Promise<void>((resolve) => { |
| 178 | const sessionStream = fs.createReadStream(filePath, { encoding: 'utf-8' }) |
| 179 | |
| 180 | let sessionContent = '' |
| 181 | let inSession = false |
| 182 | let braceDepth = 0 |
| 183 | let inString = false |
| 184 | let escape = false |
| 185 | |
| 186 | sessionStream.on('data', (chunk: string | Buffer) => { |
| 187 | const str = typeof chunk === 'string' ? chunk : chunk.toString() |
| 188 | |
| 189 | for (let i = 0; i < str.length; i++) { |
| 190 | const char = str[i] |
| 191 | |
| 192 | if (!inSession) { |
| 193 | // 查找 "session": 的位置 |
| 194 | const searchStr = '"session":' |
| 195 | if (str.slice(i, i + searchStr.length) === searchStr) { |
| 196 | inSession = true |
| 197 | i += searchStr.length - 1 |
| 198 | continue |
| 199 | } |
| 200 | } else { |
| 201 | sessionContent += char |
| 202 | |
| 203 | if (escape) { |
| 204 | escape = false |
| 205 | continue |
| 206 | } |
| 207 | |
| 208 | if (char === '\\' && inString) { |
| 209 | escape = true |
| 210 | continue |
| 211 | } |
| 212 | |
| 213 | if (char === '"') { |
nothing calls this directly
no test coverage detected