mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-08-17 19:47:08 +08:00
feat: 完成 TraceMemo v2.2.0 品牌升级并保留旧数据兼容
- 将用户可见品牌升级为 TraceMemo(迹忆) - 增加最早期 userData/sessionData 兼容路径选择 - 保留 WechatExplorer runtime identity 以兼容 safeStorage - 继续使用旧 Knowledge、Settings、API Token 和 Provider 配置 - 新日志写入 TraceMemo 目录并保留历史日志 - 保留旧 API、Skill、环境变量和导出目录兼容标识 - 更新相关文档、界面文案与自动化测试
This commit is contained in:
@@ -23,7 +23,7 @@ function renderPage(
|
||||
wechatVersion: '4.1.9.57',
|
||||
dataStructureVersion: '微信 4.x(WCDB)',
|
||||
dataDirectoryDetected: true,
|
||||
diagnosticSummary: 'WechatExplorer: v2.1.6',
|
||||
diagnosticSummary: 'TraceMemo: v2.1.6',
|
||||
autoDetectSupported: true,
|
||||
wechatRunning: true,
|
||||
accountIdentified: false,
|
||||
|
||||
@@ -40,7 +40,7 @@ describe('ImageTestSection', () => {
|
||||
fileFound: true,
|
||||
decrypted: false,
|
||||
readable: false,
|
||||
diagnosticLog: 'WechatExplorer 图片解析测试日志(已脱敏)'
|
||||
diagnosticLog: 'TraceMemo 图片解析测试日志(已脱敏)'
|
||||
}
|
||||
}}
|
||||
batchTest={emptyBatchTest}
|
||||
|
||||
@@ -9,7 +9,7 @@ test('APP-01 first launch renders a usable connection screen without uncaught er
|
||||
const pageErrors: Error[] = []
|
||||
fixture.page.on('pageerror', (error) => pageErrors.push(error))
|
||||
try {
|
||||
await expect(fixture.page.getByRole('heading', { name: 'WechatExplorer' })).toBeVisible()
|
||||
await expect(fixture.page.getByRole('heading', { name: 'TraceMemo(迹忆)' })).toBeVisible()
|
||||
await expect(fixture.page.getByRole('main')).not.toBeEmpty()
|
||||
expect(pageErrors).toEqual([])
|
||||
} finally {
|
||||
|
||||
@@ -117,7 +117,7 @@ handle('key:getEnvironment', () => ({
|
||||
wechatVersion: '4.1.9.57',
|
||||
dataStructureVersion: settings.dbRoot === 'fixture-account' ? '微信 4.x(WCDB)' : '未检测到',
|
||||
dataDirectoryDetected: settings.dbRoot === 'fixture-account',
|
||||
diagnosticSummary: 'WechatExplorer: v2.1.6\n数据目录: 已检测到',
|
||||
diagnosticSummary: 'TraceMemo: v2.1.6\n数据目录: 已检测到',
|
||||
autoDetectSupported: true,
|
||||
wechatRunning: true,
|
||||
accountIdentified: connected,
|
||||
@@ -371,7 +371,7 @@ handle('image:testConfig', () => ({
|
||||
fileFound: true,
|
||||
decrypted: true,
|
||||
readable: true,
|
||||
diagnosticLog: 'WechatExplorer 图片解析测试日志(已脱敏)\n测试结果:成功(SUCCESS)'
|
||||
diagnosticLog: 'TraceMemo 图片解析测试日志(已脱敏)\n测试结果:成功(SUCCESS)'
|
||||
}))
|
||||
handle('image:clearConfig', () => ({ success: true }))
|
||||
handle('image:getDecoderStatus', () => ({
|
||||
|
||||
@@ -14,7 +14,7 @@ test('NAV-01 login page visual @visual', async () => {
|
||||
const fixture = await launchTestApp({ mode: 'disconnected' })
|
||||
try {
|
||||
await fixture.page.setViewportSize(visualViewport)
|
||||
await expect(fixture.page.getByRole('heading', { name: 'WechatExplorer' })).toBeVisible()
|
||||
await expect(fixture.page.getByRole('heading', { name: 'TraceMemo(迹忆)' })).toBeVisible()
|
||||
await expect(fixture.page).toHaveScreenshot('login-page.png', {
|
||||
animations: 'disabled',
|
||||
caret: 'hide'
|
||||
|
||||
@@ -1138,7 +1138,7 @@ describe('media export flow', () => {
|
||||
|
||||
expect(result).toEqual({ success: false, error: '已取消' })
|
||||
expect(state.exportReads).toEqual(['cancel-1'])
|
||||
const outputDir = join(state.documents, 'WechatExplorer', '导出', 'cancelled-all-conversations')
|
||||
const outputDir = join(state.documents, 'TraceMemo', '导出', 'cancelled-all-conversations')
|
||||
expect(existsSync(join(outputDir, '联系人', '联系人一', 'index.html'))).toBe(true)
|
||||
expect(existsSync(join(outputDir, '联系人', '联系人二', 'index.html'))).toBe(false)
|
||||
const partialManifest = JSON.parse(readFileSync(join(outputDir, '导出清单.json'), 'utf8')) as {
|
||||
@@ -1195,7 +1195,7 @@ describe('media export flow', () => {
|
||||
expect(firstSize).toBeGreaterThan(0)
|
||||
expect(readFileSync(second.outputPath!).subarray(0, 2).toString()).toBe('PK')
|
||||
const entries = execFileSync('unzip', ['-Z1', second.outputPath!], { encoding: 'utf8' })
|
||||
const htmlPath = join(state.documents, 'WechatExplorer', '导出', 'zip-fixture', 'index.html')
|
||||
const htmlPath = join(state.documents, 'TraceMemo', '导出', 'zip-fixture', 'index.html')
|
||||
const archive = readArchive(htmlPath)
|
||||
expect(entries).toContain('zip-fixture/index.html')
|
||||
expect(entries).toContain('zip-fixture/data/messages.js')
|
||||
@@ -1209,7 +1209,7 @@ describe('media export flow', () => {
|
||||
true
|
||||
)
|
||||
expect(
|
||||
readdirSync(join(state.documents, 'WechatExplorer', '导出')).some((name) =>
|
||||
readdirSync(join(state.documents, 'TraceMemo', '导出')).some((name) =>
|
||||
name.startsWith('zip-fixture.zip.tmp-')
|
||||
)
|
||||
).toBe(false)
|
||||
@@ -1250,7 +1250,7 @@ describe('media export flow', () => {
|
||||
expect(cancelled).toEqual({ success: false, error: '已取消' })
|
||||
expect(readFileSync(first.outputPath!)).toEqual(completeZip)
|
||||
expect(
|
||||
readdirSync(join(state.documents, 'WechatExplorer', '导出')).some((name) =>
|
||||
readdirSync(join(state.documents, 'TraceMemo', '导出')).some((name) =>
|
||||
name.startsWith('zip-cancel-fixture.zip.tmp-')
|
||||
)
|
||||
).toBe(false)
|
||||
|
||||
@@ -95,7 +95,7 @@ describe('Local API authentication', () => {
|
||||
const health = await fetch(`${baseUrl(handle)}/api/v1/health`)
|
||||
expect(health.status).toBe(200)
|
||||
const healthBody = await health.json()
|
||||
expect(healthBody).toMatchObject({ ok: true, service: 'WechatExplorer Reader' })
|
||||
expect(healthBody).toMatchObject({ ok: true, service: 'TraceMemo Reader' })
|
||||
expect(JSON.stringify(healthBody)).not.toMatch(
|
||||
/token|authorization|wxid|databasePath|provider/i
|
||||
)
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'fs'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import { chooseUserDataRoot, getUserDataRoots, hasValidUserAssets } from '../../src/main/app-data-paths'
|
||||
|
||||
let root = ''
|
||||
|
||||
beforeEach(() => {
|
||||
root = mkdtempSync(path.join(os.tmpdir(), 'tracememo-app-data-paths-'))
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
rmSync(root, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
function fixtureRoots(): ReturnType<typeof getUserDataRoots> {
|
||||
return getUserDataRoots(path.join(root, 'Application Support'))
|
||||
}
|
||||
|
||||
function writeMarker(filePath: string): void {
|
||||
mkdirSync(path.dirname(filePath), { recursive: true })
|
||||
writeFileSync(filePath, 'fixture')
|
||||
}
|
||||
|
||||
describe('app data compatibility root selection', () => {
|
||||
it('chooses the new root for a clean install', () => {
|
||||
const roots = fixtureRoots()
|
||||
expect(hasValidUserAssets(roots.legacy)).toBe(false)
|
||||
expect(hasValidUserAssets(roots.current)).toBe(false)
|
||||
expect(chooseUserDataRoot(roots)).toBe(roots.current)
|
||||
})
|
||||
|
||||
it('ignores runtime-only Chromium files', () => {
|
||||
const roots = fixtureRoots()
|
||||
mkdirSync(roots.current, { recursive: true })
|
||||
writeMarker(path.join(roots.current, 'Local State'))
|
||||
mkdirSync(path.join(roots.current, 'Cache'), { recursive: true })
|
||||
expect(hasValidUserAssets(roots.current)).toBe(false)
|
||||
expect(chooseUserDataRoot(roots)).toBe(roots.current)
|
||||
})
|
||||
|
||||
it('prefers a legacy root containing user settings', () => {
|
||||
const roots = fixtureRoots()
|
||||
writeMarker(path.join(roots.legacy, 'settings.json'))
|
||||
expect(chooseUserDataRoot(roots)).toBe(roots.legacy)
|
||||
})
|
||||
|
||||
it('uses a current root when only it contains valid user assets', () => {
|
||||
const roots = fixtureRoots()
|
||||
writeMarker(path.join(roots.current, 'ai-providers.json'))
|
||||
expect(chooseUserDataRoot(roots)).toBe(roots.current)
|
||||
})
|
||||
|
||||
it('prefers legacy without modifying a valid current root', () => {
|
||||
const roots = fixtureRoots()
|
||||
writeMarker(path.join(roots.legacy, 'reports', '2026', '08', 'report.json'))
|
||||
writeMarker(path.join(roots.current, 'local-api-token.bin'))
|
||||
const currentToken = path.join(roots.current, 'local-api-token.bin')
|
||||
expect(chooseUserDataRoot(roots)).toBe(roots.legacy)
|
||||
expect(hasValidUserAssets(roots.current)).toBe(true)
|
||||
expect(readFileSync(currentToken, 'utf8')).toBe('fixture')
|
||||
})
|
||||
|
||||
it('recognizes Knowledge only when a non-empty knowledge.sqlite exists', () => {
|
||||
const roots = fixtureRoots()
|
||||
mkdirSync(path.join(roots.legacy, 'knowledge', 'account-hash'), { recursive: true })
|
||||
expect(hasValidUserAssets(roots.legacy)).toBe(false)
|
||||
writeMarker(path.join(roots.legacy, 'knowledge', 'account-hash', 'knowledge.sqlite'))
|
||||
expect(hasValidUserAssets(roots.legacy)).toBe(true)
|
||||
})
|
||||
|
||||
it('honors WXE_USER_DATA isolation before normal root selection', () => {
|
||||
const roots = fixtureRoots()
|
||||
writeMarker(path.join(roots.legacy, 'settings.json'))
|
||||
const isolated = path.join(root, 'isolated-user-data')
|
||||
expect(chooseUserDataRoot({ ...roots, isolated })).toBe(isolated)
|
||||
})
|
||||
|
||||
it.each([
|
||||
'ai-provider-keys.bin',
|
||||
'local-api-token.bin',
|
||||
'wechat-image-keys.bin',
|
||||
path.join('database-keys', 'account.bin'),
|
||||
path.join('recall-archive', 'entry.json'),
|
||||
path.join('Local Storage', 'leveldb', '000001.ldb')
|
||||
])('recognizes %s as a persistent user asset', (relativePath) => {
|
||||
const roots = fixtureRoots()
|
||||
writeMarker(path.join(roots.legacy, relativePath))
|
||||
expect(hasValidUserAssets(roots.legacy)).toBe(true)
|
||||
expect(chooseUserDataRoot(roots)).toBe(roots.legacy)
|
||||
})
|
||||
})
|
||||
@@ -17,7 +17,7 @@ describe('connection diagnostics', () => {
|
||||
encryptionAvailable: true
|
||||
})
|
||||
|
||||
expect(summary).toContain('WechatExplorer: v2.1.6')
|
||||
expect(summary).toContain('TraceMemo: v2.1.6')
|
||||
expect(summary).toContain('微信客户端: 4.1.9.57')
|
||||
expect(summary).not.toContain('0123456789abcdef')
|
||||
expect(summary).not.toContain('C:\\Users\\fixture\\xwechat_files\\wxid_secret')
|
||||
|
||||
@@ -45,11 +45,11 @@ describe('production runtime packaging', () => {
|
||||
expect(config).toContain('docs/skill/wechatexplorer-reader')
|
||||
expect(config).toContain('to: skill/wechatexplorer-reader')
|
||||
expect(() => validateReaderSkillRuntime(resources)).toThrow(
|
||||
/Missing bundled WechatExplorer Reader Skill/
|
||||
/Missing bundled TraceMemo Reader Skill/
|
||||
)
|
||||
|
||||
mkdirSync(dirname(skillPath), { recursive: true })
|
||||
writeFileSync(skillPath, '# WechatExplorer Reader\n')
|
||||
writeFileSync(skillPath, '# TraceMemo Reader\n')
|
||||
expect(validateReaderSkillRuntime(resources)).toBe(skillPath)
|
||||
})
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ function environment(root: string, packaged: boolean) {
|
||||
|
||||
function writeSkill(filePath: string): void {
|
||||
mkdirSync(dirname(filePath), { recursive: true })
|
||||
writeFileSync(filePath, '# WechatExplorer Reader\n', 'utf8')
|
||||
writeFileSync(filePath, '# TraceMemo Reader\n', 'utf8')
|
||||
}
|
||||
|
||||
describe('Reader Skill resource resolution', () => {
|
||||
|
||||
Reference in New Issue
Block a user