(options: ParseOptions)
| 85 | // ==================== 解析器实现 ==================== |
| 86 | |
| 87 | async function* parseChatLab(options: ParseOptions): AsyncGenerator<ParseEvent, void, unknown> { |
| 88 | const { filePath, batchSize = 5000, onProgress, onLog } = options |
| 89 | |
| 90 | const totalBytes = getFileSize(filePath) |
| 91 | let bytesRead = 0 |
| 92 | let messagesProcessed = 0 |
| 93 | |
| 94 | // 发送初始进度 |
| 95 | const initialProgress = createProgress('parsing', 0, totalBytes, 0, '') |
| 96 | yield { type: 'progress', data: initialProgress } |
| 97 | onProgress?.(initialProgress) |
| 98 | |
| 99 | // 记录解析开始 |
| 100 | onLog?.('info', `开始解析 ChatLab 格式文件,大小: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`) |
| 101 | |
| 102 | // 读取文件头获取 meta 和 members 信息 |
| 103 | const headContent = readFileHeadBytes(filePath, 200000) |
| 104 | |
| 105 | // 解析 meta |
| 106 | let meta: ParsedMeta = { |
| 107 | name: '未知群聊', |
| 108 | platform: KNOWN_PLATFORMS.UNKNOWN, |
| 109 | type: ChatType.GROUP, |
| 110 | } |
| 111 | try { |
| 112 | // 使用更健壮的方式解析嵌套 JSON 对象 |
| 113 | // 因为 meta 可能包含 sources 数组(嵌套对象),简单的正则无法正确匹配 |
| 114 | const metaStartMatch = headContent.match(/"meta"\s*:\s*\{/) |
| 115 | if (metaStartMatch && metaStartMatch.index !== undefined) { |
| 116 | const startIndex = metaStartMatch.index + metaStartMatch[0].length - 1 // 指向 { |
| 117 | let depth = 0 |
| 118 | let endIndex = startIndex |
| 119 | |
| 120 | // 遍历字符找到匹配的闭合 } |
| 121 | for (let i = startIndex; i < headContent.length; i++) { |
| 122 | const char = headContent[i] |
| 123 | if (char === '{') { |
| 124 | depth++ |
| 125 | } else if (char === '}') { |
| 126 | depth-- |
| 127 | if (depth === 0) { |
| 128 | endIndex = i |
| 129 | break |
| 130 | } |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | if (endIndex > startIndex) { |
| 135 | const metaJson = headContent.slice(startIndex, endIndex + 1) |
| 136 | const metaObj = JSON.parse(metaJson) |
| 137 | meta = { |
| 138 | name: metaObj.name || '未知群聊', |
| 139 | platform: metaObj.platform || KNOWN_PLATFORMS.UNKNOWN, |
| 140 | type: (metaObj.type as ChatType) || ChatType.GROUP, |
| 141 | groupId: metaObj.groupId, |
| 142 | groupAvatar: metaObj.groupAvatar, |
| 143 | } |
| 144 | } |
nothing calls this directly
no test coverage detected