fix(merge): isolate ambiguous private exports

Keep one-member private exports source-local until exact message overlap proves they belong to the same chat. This prevents unrelated outgoing-only chats from losing messages when platform IDs collide.
This commit is contained in:
digua committed 2026-08-05 17:02:09 +08:00
1 parent 691293b46f
commit c883e6ab47
2 files changed
+47 -4

No files matched your search

@@ -178,6 +178,46 @@ describe('buildMergedOutput', () => {
assert.equal(new Set(result.chatLabData.messages.map((message) => message.platformMessageId)).size, 2)
})
it('keeps ambiguous one-member private chats source-local without verified overlap', () => {
const first = createMockSource(
{ name: 'Alice', platform: 'telegram', type: 'private' },
[{ platformId: 'owner' }],
[
{
platformMessageId: '1',
senderPlatformId: 'owner',
timestamp: 100,
type: 0,
content: 'first chat',
},
]
)
const second = createMockSource(
{ name: 'Bob', platform: 'telegram', type: 'private' },
[{ platformId: 'owner' }],
[
{
platformMessageId: '1',
senderPlatformId: 'owner',
timestamp: 200,
type: 0,
content: 'second chat',
},
]
)
const sources = [
{ source: first, filename: 'alice.json' },
{ source: second, filename: 'bob.json' },
]
const conflicts = checkConflictsFromSources(sources)
const result = buildMergedOutput(sources, 'Merged')
assert.equal(conflicts.totalMessages, 2)
assert.equal(result.chatLabData.messages.length, 2)
assert.equal(new Set(result.chatLabData.messages.map((message) => message.platformMessageId)).size, 2)
})
it('deduplicates overlapping private exports when their observed members differ', () => {
const meta: MergerSourceMeta = { name: 'Alice', platform: 'qq', type: 'private' }
const sharedMessage: MergerInputMessage = {
+7 -4
View File
@@ -56,12 +56,15 @@ function createMessageScope(meta: MergerSourceMeta | null, members: MergerMember
const memberIds = [...new Set(members.map((member) => member.platformId))].sort()
if (memberIds.length > 0) {
if (type === 'private') return JSON.stringify([platform, type, 'members', memberIds])
return JSON.stringify([platform, type, 'name-members', meta?.name || '', memberIds])
if (type === 'private') {
if (memberIds.length >= 2) return JSON.stringify([platform, type, 'members', memberIds])
} else {
return JSON.stringify([platform, type, 'name-members', meta?.name || '', memberIds])
}
}
// Without a stable group ID or participant set, sharing message IDs across
// sources could silently merge unrelated chats. Keep those source-local.
// A one-sided private export does not identify the other participant. Without
// a stable group ID or complete participant set, keep message IDs source-local.
return JSON.stringify([platform, type, 'source', sourceIndex])
}