mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-10-04 03:01:42 +08:00
fix: scope HTTP image media handles to database and conversation
This commit is contained in:
@@ -187,6 +187,32 @@ describe('Local API authentication', () => {
|
||||
expect(body.messages[0].contentData).not.toHaveProperty('aeskey')
|
||||
})
|
||||
|
||||
it('serves opaque media handles while preserving the chatlog message id', async () => {
|
||||
const mediaId = `image:${'a'.repeat(64)}`
|
||||
const previousUrl = fixture.chatlogMessages[0].media.url
|
||||
fixture.chatlogMessages[0].media.url = `/api/v1/media/${encodeURIComponent(mediaId)}`
|
||||
try {
|
||||
const provider = vi.fn(async (id: string) => {
|
||||
expect(id).toBe(mediaId)
|
||||
return { buffer: Buffer.from([0xff, 0xd8, 0xff, 0xd9]), mimeType: 'image/jpeg' }
|
||||
})
|
||||
const handle = await startFixtureServer(() => VALID_TOKEN, provider)
|
||||
const headers = { Authorization: `Bearer ${VALID_TOKEN}` }
|
||||
const chatlog = await fetch(`${baseUrl(handle)}/api/v1/chatlog?talker=fixture`, { headers })
|
||||
const body = await chatlog.json()
|
||||
expect(body.messages[0].id).toBe('message:1')
|
||||
expect(body.messages[0].media.url).not.toContain('message')
|
||||
const response = await fetch(`${baseUrl(handle)}${body.messages[0].media.url}`, { headers })
|
||||
expect(response.status).toBe(200)
|
||||
expect(Buffer.from(await response.arrayBuffer())).toEqual(
|
||||
Buffer.from([0xff, 0xd8, 0xff, 0xd9])
|
||||
)
|
||||
expect(provider).toHaveBeenCalledOnce()
|
||||
} finally {
|
||||
fixture.chatlogMessages[0].media.url = previousUrl
|
||||
}
|
||||
})
|
||||
|
||||
it('maps media lookup failures to stable API statuses', async () => {
|
||||
const handle = await startFixtureServer(
|
||||
() => VALID_TOKEN,
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { WechatDb, WechatMessage } from '../../src/main/wechat-db'
|
||||
|
||||
vi.mock('../../src/main/services/recall-archive-service', () => ({
|
||||
recordRecallArchiveMessages: vi.fn(),
|
||||
mergeRecallArchiveMessages: (_md5: string, messages: unknown[]) => messages
|
||||
}))
|
||||
|
||||
import {
|
||||
getImageMessageReference,
|
||||
listMessages,
|
||||
listMessagesAsync,
|
||||
setChatDb,
|
||||
type FormattedMessage
|
||||
} from '../../src/main/services/chat-service'
|
||||
|
||||
const IMAGE_A = 'a'.repeat(32)
|
||||
const IMAGE_B = 'b'.repeat(32)
|
||||
|
||||
function image(md5: string, overrides: Partial<WechatMessage> = {}): WechatMessage {
|
||||
return {
|
||||
mesLocalID: '56',
|
||||
mesDes: 1,
|
||||
messageType: '3',
|
||||
msgCreateTime: '1756000000',
|
||||
msgContent: `<msg><img md5="${md5}" /></msg>`,
|
||||
serverId: '9007199254740993123',
|
||||
...overrides
|
||||
}
|
||||
}
|
||||
|
||||
function connect(messages: Record<string, WechatMessage[]>): void {
|
||||
const client = { getUsernameByMd5: (md5: string) => `wxid_${md5}` }
|
||||
setChatDb({
|
||||
close: vi.fn(),
|
||||
getWcdb4Client: () => client,
|
||||
getUserMessages: (md5: string) => messages[md5] || [],
|
||||
getUserMessagesAsync: async (md5: string) => messages[md5] || []
|
||||
} as unknown as WechatDb)
|
||||
}
|
||||
|
||||
function mediaId(message: FormattedMessage): string {
|
||||
expect(message.media).toMatchObject({ type: 'image', available: true })
|
||||
return decodeURIComponent(message.media!.url.slice('/api/v1/media/'.length))
|
||||
}
|
||||
|
||||
describe('chat service image media handles', () => {
|
||||
afterEach(() => setChatDb(null))
|
||||
|
||||
it('keeps colliding local ids readable across conversations and repeated reads', async () => {
|
||||
connect({ first: [image(IMAGE_A)], second: [image(IMAGE_B)] })
|
||||
const first = listMessages('first')[0]
|
||||
const firstId = mediaId(first)
|
||||
expect(first.id).toBe('56')
|
||||
expect(firstId).toMatch(/^image:[a-f0-9]{64}$/)
|
||||
expect(firstId).not.toContain('wxid_first')
|
||||
expect(getImageMessageReference('56')?.imageMd5).toBe(IMAGE_A)
|
||||
|
||||
const second = (await listMessagesAsync('second'))[0]
|
||||
const secondId = mediaId(second)
|
||||
expect(second.id).toBe(first.id)
|
||||
expect(secondId).not.toBe(firstId)
|
||||
expect(getImageMessageReference(firstId)).toMatchObject({
|
||||
sessionId: 'wxid_first',
|
||||
imageMd5: IMAGE_A
|
||||
})
|
||||
expect(getImageMessageReference(secondId)).toMatchObject({
|
||||
sessionId: 'wxid_second',
|
||||
imageMd5: IMAGE_B
|
||||
})
|
||||
expect(getImageMessageReference('56')).toBeNull()
|
||||
|
||||
expect(mediaId(listMessages('first')[0])).toBe(firstId)
|
||||
expect(mediaId((await listMessagesAsync('second'))[0])).toBe(secondId)
|
||||
expect(getImageMessageReference(firstId)?.imageMd5).toBe(IMAGE_A)
|
||||
expect(getImageMessageReference(secondId)?.imageMd5).toBe(IMAGE_B)
|
||||
expect(getImageMessageReference('56')).toBeNull()
|
||||
})
|
||||
|
||||
it('distinguishes images with the same local id within one conversation', () => {
|
||||
connect({ first: [image(IMAGE_A), image(IMAGE_B, { serverId: '9007199254740993124' })] })
|
||||
const [first, second] = listMessages('first').map(mediaId)
|
||||
expect(first).not.toBe(second)
|
||||
expect(getImageMessageReference(first)?.imageMd5).toBe(IMAGE_A)
|
||||
expect(getImageMessageReference(second)?.imageMd5).toBe(IMAGE_B)
|
||||
expect(getImageMessageReference('56')).toBeNull()
|
||||
})
|
||||
|
||||
it('does not reuse media handles after reconnecting or switching accounts', () => {
|
||||
const messages = { first: [image(IMAGE_A)] }
|
||||
connect(messages)
|
||||
const previousId = mediaId(listMessages('first')[0])
|
||||
setChatDb(null)
|
||||
expect(getImageMessageReference(previousId)).toBeNull()
|
||||
connect(messages)
|
||||
const currentId = mediaId(listMessages('first')[0])
|
||||
expect(currentId).not.toBe(previousId)
|
||||
expect(getImageMessageReference(previousId)).toBeNull()
|
||||
expect(getImageMessageReference(currentId)?.imageMd5).toBe(IMAGE_A)
|
||||
})
|
||||
|
||||
it('normalizes native server ids without losing integer precision', () => {
|
||||
const message = image(IMAGE_A, { serverId: 9007199254740993123n })
|
||||
connect({ first: [message] })
|
||||
const firstId = mediaId(listMessages('first')[0])
|
||||
message.serverId = '9007199254740993123'
|
||||
expect(mediaId(listMessages('first')[0])).toBe(firstId)
|
||||
})
|
||||
|
||||
it('scopes recovered images and supports images identified only by dat name', () => {
|
||||
connect({
|
||||
first: [image(IMAGE_A, { _wxe_recovered: true })],
|
||||
second: [image(IMAGE_B, { _wxe_recovered: true })],
|
||||
third: [image('', { msgContent: JSON.stringify({ imageDatName: IMAGE_A }) })]
|
||||
})
|
||||
const first = listMessages('first')[0]
|
||||
const second = listMessages('second')[0]
|
||||
expect(first.id).toBe('recovered:56')
|
||||
expect(mediaId(second)).not.toBe(mediaId(first))
|
||||
expect(getImageMessageReference(mediaId(first))?.imageMd5).toBe(IMAGE_A)
|
||||
expect(getImageMessageReference(mediaId(second))?.imageMd5).toBe(IMAGE_B)
|
||||
expect(getImageMessageReference('recovered:56')).toBeNull()
|
||||
const third = listMessages('third')[0]
|
||||
expect(getImageMessageReference(mediaId(third))?.imageDatName).toBe(IMAGE_A)
|
||||
})
|
||||
|
||||
it('does not add media handles to text messages', () => {
|
||||
connect({ first: [image(IMAGE_A, { messageType: '1', msgContent: 'text' })] })
|
||||
const message = listMessages('first')[0]
|
||||
expect(message.id).toBe('56')
|
||||
expect(message.media).toBeUndefined()
|
||||
expect(getImageMessageReference('56')).toBeNull()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user