mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-25 17:54:34 +08:00
fix(parser): preserve ChatLab members in TS fallback
Stream members and messages in one pass so nested member metadata is parsed structurally. Cover exports whose complete member list exceeds the filtered messages.
This commit is contained in:
1 parent
96235915b8
commit
37c446c751
3 files changed
+33
-44
No files matched your search
@@ -154,27 +154,8 @@ async function* parseChatLab(options: ParseOptions): AsyncGenerator<ParseEvent,
|
||||
|
||||
yield { type: 'meta', data: meta }
|
||||
|
||||
// 解析 members(如果在文件开头能找到)
|
||||
const members: ParsedMember[] = []
|
||||
try {
|
||||
const membersMatch = headContent.match(/"members"\s*:\s*\[([\s\S]*?)\]/)
|
||||
if (membersMatch) {
|
||||
const membersJson = JSON.parse(`[${membersMatch[1]}]`) as ChatLabMember[]
|
||||
for (const m of membersJson) {
|
||||
members.push({
|
||||
platformId: m.platformId,
|
||||
accountName: m.accountName,
|
||||
groupNickname: m.groupNickname,
|
||||
avatar: m.avatar,
|
||||
roles: m.roles,
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// members 可能太大,稍后从消息中收集
|
||||
}
|
||||
|
||||
// 收集成员和消息
|
||||
const members: ParsedMember[] = []
|
||||
const memberMapFromMessages = new Map<string, ParsedMember>()
|
||||
const messageBatch: ParsedMessage[] = []
|
||||
|
||||
@@ -186,12 +167,24 @@ async function* parseChatLab(options: ParseOptions): AsyncGenerator<ParseEvent,
|
||||
bytesRead += typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.length
|
||||
})
|
||||
|
||||
const pipeline = chain([readStream, parser(), pick({ filter: /^messages\.\d+$/ }), streamValues()])
|
||||
// 单次流式遍历同时读取成员和消息,避免用正则截断含嵌套数组的 members。
|
||||
const pipeline = chain([readStream, parser(), pick({ filter: /^(?:members|messages)\.\d+$/ }), streamValues()])
|
||||
|
||||
// 用于收集批次的临时数组
|
||||
const batchCollector: ParsedMessage[] = []
|
||||
|
||||
pipeline.on('data', ({ value }: { value: ChatLabMessage }) => {
|
||||
pipeline.on('data', ({ value }: { value: ChatLabMember | ChatLabMessage }) => {
|
||||
if (!('sender' in value)) {
|
||||
members.push({
|
||||
platformId: value.platformId,
|
||||
accountName: value.accountName,
|
||||
groupNickname: value.groupNickname,
|
||||
avatar: value.avatar,
|
||||
roles: value.roles,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const msg = value
|
||||
|
||||
// 如果前面没解析到 members,从消息中收集
|
||||
|
||||
@@ -163,11 +163,7 @@ describe('chatlab native parser parity', { skip: !nativeAvailable() && 'native m
|
||||
assert.deepEqual(nativeResult.messages, [])
|
||||
})
|
||||
|
||||
it('keeps member roles and avatars where the TS head-regex heuristic loses them', async () => {
|
||||
// The TS parser extracts head members with a non-greedy regex that breaks
|
||||
// on nested arrays (roles), silently degrading to message-collected
|
||||
// members. The Rust kernel parses the members array structurally, so this
|
||||
// is a deliberate improvement over TS behavior, not a parity case.
|
||||
it('keeps member roles and avatars in both parsers', async () => {
|
||||
const withRoles = {
|
||||
chatlab: { version: '0.0.2' },
|
||||
meta: { name: '带角色群', platform: 'qq', type: 'group' },
|
||||
@@ -184,18 +180,10 @@ describe('chatlab native parser parity', { skip: !nativeAvailable() && 'native m
|
||||
}
|
||||
const { nativeResult, tsResult } = await parseBothWays('带角色群.json', JSON.stringify(withRoles))
|
||||
|
||||
// TS loses the head members entirely (only u1 appears, from messages).
|
||||
assert.equal(tsResult.members.length, 1)
|
||||
assert.equal(tsResult.members[0].avatar, undefined)
|
||||
|
||||
// Native keeps the full member list including roles and avatars.
|
||||
assert.equal(nativeResult.members.length, 2)
|
||||
assert.equal(nativeResult.members[0].avatar, 'https://example.com/a.jpg')
|
||||
assert.deepEqual(nativeResult.members[0].roles, [{ id: 'owner' }, { id: 'admin', name: '管理员' }])
|
||||
|
||||
// Meta and messages still match exactly.
|
||||
assert.deepEqual(nativeResult.meta, tsResult.meta)
|
||||
assert.deepEqual(nativeResult.messages, tsResult.messages)
|
||||
assertParity(nativeResult, tsResult)
|
||||
assert.equal(tsResult.members.length, 2)
|
||||
assert.equal(tsResult.members[0].avatar, 'https://example.com/a.jpg')
|
||||
assert.deepEqual(tsResult.members[0].roles, [{ id: 'owner' }, { id: 'admin', name: '管理员' }])
|
||||
})
|
||||
|
||||
it('falls back to the TS parser for off-spec files', async () => {
|
||||
|
||||
@@ -44,7 +44,7 @@ test('exports JSON as ChatLab format that can be parsed for re-import', async ()
|
||||
)
|
||||
insertMessage.run(aliceId, 'Alice Account', '爱丽丝', 100, 0, '筛选范围外', 'msg-1', null)
|
||||
insertMessage.run(bobId, 'Bob Account', '鲍勃', 200, 0, '你好', 'msg-2', null)
|
||||
insertMessage.run(aliceId, 'Alice Account', '爱丽丝', 250, 1, null, 'msg-3', 'msg-2')
|
||||
insertMessage.run(bobId, 'Bob Account', '鲍勃', 250, 1, null, 'msg-3', 'msg-2')
|
||||
|
||||
const result = exportWithFormat(
|
||||
{
|
||||
@@ -69,7 +69,15 @@ test('exports JSON as ChatLab format that can be parsed for re-import', async ()
|
||||
writeFileSync(filePath, result.content, 'utf8')
|
||||
assert.equal(detectFormat(filePath)?.id, 'chatlab')
|
||||
|
||||
const parsed = await parseFileSync(filePath)
|
||||
const nativePerfDisabled = process.env.CHATLAB_DISABLE_NATIVE_PERF
|
||||
process.env.CHATLAB_DISABLE_NATIVE_PERF = '1'
|
||||
const parsed = await parseFileSync(filePath).finally(() => {
|
||||
if (nativePerfDisabled === undefined) {
|
||||
delete process.env.CHATLAB_DISABLE_NATIVE_PERF
|
||||
} else {
|
||||
process.env.CHATLAB_DISABLE_NATIVE_PERF = nativePerfDisabled
|
||||
}
|
||||
})
|
||||
assert.deepEqual(parsed.meta, {
|
||||
name: '测试群',
|
||||
platform: 'qq',
|
||||
@@ -111,9 +119,9 @@ test('exports JSON as ChatLab format that can be parsed for re-import', async ()
|
||||
replyToMessageId: undefined,
|
||||
},
|
||||
{
|
||||
senderPlatformId: 'alice',
|
||||
senderAccountName: 'Alice Account',
|
||||
senderGroupNickname: '爱丽丝',
|
||||
senderPlatformId: 'bob',
|
||||
senderAccountName: 'Bob Account',
|
||||
senderGroupNickname: '鲍勃',
|
||||
timestamp: 250,
|
||||
type: 1,
|
||||
content: null,
|
||||
|
||||
Reference in new issue
Block a user