refactor(core): centralize contact identity resolution

This commit is contained in:
digua committed 2026-07-12 15:29:09 +08:00
1 parent 84f033efe4
commit 740c410d7b
6 files changed
+54 -32

No files matched your search

+2
View File
@@ -61,6 +61,8 @@ export {
getPrivateContactFacts,
isValidContactPlatformId,
resolveOwnerMember,
buildContactKey,
shouldScopeContactToSession,
MIN_GROUP_SESSIONS_FOR_CONTACTS,
MIN_PRIVATE_SESSIONS_FOR_CONTACTS,
shouldEnableContactsEntry,
@@ -0,0 +1,27 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { buildContactKey, shouldScopeContactToSession } from '../contact-identity'
const contact = {
id: 2,
platformId: 'alice',
name: 'Alice',
aliases: [],
avatar: null,
}
test('builds platform-level contact keys for stable identities', () => {
assert.equal(shouldScopeContactToSession('weixin', contact), false)
assert.equal(buildContactKey('weixin', contact.platformId), 'weixin:alice')
})
test('scopes name-matched and QQ nickname identities to their session', () => {
assert.equal(shouldScopeContactToSession('whatsapp', contact), true)
assert.equal(shouldScopeContactToSession('qq', { ...contact, platformId: 'Alice' }), true)
assert.equal(buildContactKey('whatsapp', contact.platformId, 'chat-1'), 'whatsapp:chat-1:alice')
})
test('rejects empty contact key parts', () => {
assert.throws(() => buildContactKey('', 'alice'), /platform is required/)
assert.throws(() => buildContactKey('weixin', ' '), /platformId is required/)
})
@@ -0,0 +1,18 @@
import type { ChatPlatform } from '@openchatlab/shared-types'
import { isNameMatchPlatform } from '../owner'
import type { ContactMemberRef } from './contact-queries'
export function shouldScopeContactToSession(platform: ChatPlatform, contact: ContactMemberRef): boolean {
if (isNameMatchPlatform(platform)) return true
return platform.trim().toLowerCase() === 'qq' && contact.platformId.trim() === contact.name.trim()
}
export function buildContactKey(platform: ChatPlatform, platformId: string, sessionId?: string): string {
const normalizedPlatform = platform.trim()
const normalizedPlatformId = platformId.trim()
if (!normalizedPlatform) throw new Error('platform is required')
if (!normalizedPlatformId) throw new Error('platformId is required')
return sessionId?.trim()
? `${normalizedPlatform}:${sessionId.trim()}:${normalizedPlatformId}`
: `${normalizedPlatform}:${normalizedPlatformId}`
}
+3
View File
@@ -84,6 +84,9 @@ export {
isValidContactPlatformId,
resolveOwnerMember,
} from './contact-queries'
export { buildContactKey, shouldScopeContactToSession } from './contact-identity'
export type {
ContactFactsOptions,
ContactMemberRef,
@@ -8,6 +8,7 @@ import type {
ContactSourceSession,
} from '@openchatlab/shared-types'
import {
buildContactKey,
computeFriendScores,
computeNonFriendScores,
getGroupContactFacts,
@@ -15,8 +16,8 @@ import {
getPrivateContactFacts,
getSessionMeta,
isChatSessionDb,
isNameMatchPlatform,
resolveOwnerMember,
shouldScopeContactToSession,
shouldEnableContactsEntry,
} from '@openchatlab/core'
import type { ContactMemberRef, SessionMeta } from '@openchatlab/core'
@@ -578,21 +579,6 @@ function getOrCreateAccumulator(
return created
}
function shouldScopeContactToSession(platform: ChatPlatform, contact: ContactMemberRef): boolean {
if (isNameMatchPlatform(platform)) return true
return platform.trim().toLowerCase() === 'qq' && contact.platformId.trim() === contact.name.trim()
}
function buildContactKey(platform: ChatPlatform, platformId: string, sessionId?: string): string {
const normalizedPlatform = platform.trim()
const normalizedPlatformId = platformId.trim()
if (!normalizedPlatform) throw new Error('platform is required')
if (!normalizedPlatformId) throw new Error('platformId is required')
return sessionId?.trim()
? `${normalizedPlatform}:${sessionId.trim()}:${normalizedPlatformId}`
: `${normalizedPlatform}:${normalizedPlatformId}`
}
function mergeContactIdentity(acc: ContactAccumulator, contact: ContactMemberRef): void {
if (contact.name) acc.aliases.add(contact.name)
acc.aliases.add(contact.platformId)
@@ -10,6 +10,7 @@ import {
type PeopleRelationshipsDiagnostics,
} from '@openchatlab/shared-types'
import {
buildContactKey,
computeFriendScores,
computeNonFriendScores,
getGroupContactFacts,
@@ -18,8 +19,8 @@ import {
getPrivateContactFacts,
getSessionMeta,
isChatSessionDb,
isNameMatchPlatform,
resolveOwnerMember,
shouldScopeContactToSession,
} from '@openchatlab/core'
import type { ContactMemberRef, SessionMeta } from '@openchatlab/core'
import { getDbFileVersion } from '../../../cache/analytics-cache'
@@ -1446,21 +1447,6 @@ function toPeopleRelationshipsSessionMetaFacts(
}
}
function shouldScopeContactToSession(platform: ChatPlatform, contact: ContactMemberRef): boolean {
if (isNameMatchPlatform(platform)) return true
return platform.trim().toLowerCase() === 'qq' && contact.platformId.trim() === contact.name.trim()
}
function buildContactKey(platform: ChatPlatform, platformId: string, sessionId?: string): string {
const normalizedPlatform = platform.trim()
const normalizedPlatformId = platformId.trim()
if (!normalizedPlatform) throw new Error('platform is required')
if (!normalizedPlatformId) throw new Error('platformId is required')
return sessionId?.trim()
? `${normalizedPlatform}:${sessionId.trim()}:${normalizedPlatformId}`
: `${normalizedPlatform}:${normalizedPlatformId}`
}
function buildOwnerKey(): string {
return OWNER_KEY_PREFIX
}