feat: 添加微信数据库实时监控,聊天记录自动刷新

This commit is contained in:
电摇小子
2026-06-30 14:36:45 +08:00
parent 10dc74e2a1
commit 5a87a10d12
10 changed files with 294 additions and 35 deletions
+16 -3
View File
@@ -109,14 +109,22 @@ app.whenReady().then(() => {
try {
const trimmedKey = String(key || '').trim()
console.log(`db:init build=${BUILD_MARK} keyLength=${trimmedKey.length}`)
wechatDb = new WechatDb(key)
const wcdb4Client = wechatDb.getWcdb4Client()
const nextWechatDb = new WechatDb(key)
wechatDb?.close()
wechatDb = nextWechatDb
const wcdb4Client = nextWechatDb.getWcdb4Client()
let monitoring = false
if (wcdb4Client) {
voiceService = new VoiceService(wcdb4Client)
stickerService = new StickerService(wcdb4Client)
monitoring = wcdb4Client.startMonitor((type, json) => {
for (const window of BrowserWindow.getAllWindows()) {
if (!window.isDestroyed()) window.webContents.send('wcdb-change', { type, json })
}
})
}
imageDecryptService = null
return { success: true }
return { success: true, monitoring }
} catch (error) {
console.error('Failed to init DB:', error)
return { success: false, error: error instanceof Error ? error.message : String(error) }
@@ -453,3 +461,8 @@ app.on('window-all-closed', () => {
app.quit()
}
})
app.on('before-quit', () => {
wechatDb?.close()
wechatDb = null
})
+161 -1
View File
@@ -3,6 +3,7 @@ import path from 'path'
import os from 'os'
import crypto from 'crypto'
import { createRequire } from 'module'
import { createConnection, Socket } from 'net'
export interface Wcdb4Session {
username: string
@@ -132,6 +133,15 @@ export class Wcdb4Client {
private wcdbGetEmoticonCdnUrl:
| ((handle: number, dbPath: string, md5: string, outUrl: WcdbVoidOut) => number)
| null = null
private wcdbStartMonitorPipe: (() => number) | null = null
private wcdbStopMonitorPipe: (() => void) | null = null
private wcdbGetMonitorPipeName: ((outName: WcdbVoidOut) => number) | null = null
private monitorPipeClient: Socket | null = null
private monitorCallback: ((type: string, json: string) => void) | null = null
private monitorConnectTimer: ReturnType<typeof setTimeout> | null = null
private monitorReconnectTimer: ReturnType<typeof setTimeout> | null = null
private monitorPipePath = ''
private monitorStarted = false
constructor(key: string, accountRoot?: string) {
this.key = key.replace(/^0x/i, '').trim()
@@ -225,6 +235,7 @@ export class Wcdb4Client {
}
close(): void {
this.stopMonitor()
if (!this.initialized || !this.wcdbShutdown) return
try {
@@ -241,6 +252,142 @@ export class Wcdb4Client {
this.groupNicknameCache.clear()
}
startMonitor(callback: (type: string, json: string) => void): boolean {
if (!this.wcdbStartMonitorPipe || !this.wcdbGetMonitorPipeName || !this.koffi) return false
this.stopMonitor()
this.monitorCallback = callback
try {
const startResult = this.wcdbStartMonitorPipe()
if (startResult !== 0) {
this.monitorCallback = null
console.warn(`[WCDB4] wcdb_start_monitor_pipe 失败,错误码: ${startResult}`)
return false
}
this.monitorStarted = true
const outName: WcdbVoidOut = [null]
const nameResult = this.wcdbGetMonitorPipeName(outName)
if (nameResult !== 0 || !outName[0]) {
console.warn(`[WCDB4] wcdb_get_monitor_pipe_name 失败,错误码: ${nameResult}`)
this.stopMonitor()
return false
}
try {
this.monitorPipePath = this.koffi.decode(outName[0], 'char', -1).trim()
} finally {
this.wcdbFreeString?.(outName[0])
}
if (!this.monitorPipePath) {
this.stopMonitor()
return false
}
this.connectMonitorPipe()
return true
} catch (error) {
console.warn('[WCDB4] 启动数据库监听失败:', error)
this.stopMonitor()
return false
}
}
stopMonitor(): void {
this.monitorCallback = null
if (this.monitorConnectTimer) {
clearTimeout(this.monitorConnectTimer)
this.monitorConnectTimer = null
}
if (this.monitorReconnectTimer) {
clearTimeout(this.monitorReconnectTimer)
this.monitorReconnectTimer = null
}
if (this.monitorPipeClient) {
this.monitorPipeClient.destroy()
this.monitorPipeClient = null
}
if (this.monitorStarted && this.wcdbStopMonitorPipe) {
try {
this.wcdbStopMonitorPipe()
} catch {
// Native monitor cleanup is best-effort during reconnect or shutdown.
}
}
this.monitorStarted = false
this.monitorPipePath = ''
}
private connectMonitorPipe(): void {
if (!this.monitorCallback || !this.monitorPipePath || this.monitorConnectTimer) return
this.monitorConnectTimer = setTimeout(() => {
this.monitorConnectTimer = null
if (!this.monitorCallback || !this.monitorPipePath || this.monitorPipeClient) return
const socket = createConnection(this.monitorPipePath)
this.monitorPipeClient = socket
let buffer = ''
socket.on('data', (data) => {
const normalizedChunk = data
.toString('utf8')
.split('\0')
.join('\n')
.replace(/}\s*{/g, '}\n{')
buffer += normalizedChunk
const lines = buffer.split(/\r?\n/)
buffer = lines.pop() || ''
for (const line of lines) this.emitMonitorPayload(line)
const tail = buffer.trim()
if (tail.startsWith('{') && tail.endsWith('}')) {
try {
JSON.parse(tail)
this.emitMonitorPayload(tail)
buffer = ''
} catch {
// Keep the partial payload until the next socket chunk arrives.
}
}
})
socket.on('error', (error) => {
console.warn('[WCDB4] 数据库监听管道异常:', error.message)
})
socket.on('close', () => {
if (this.monitorPipeClient === socket) this.monitorPipeClient = null
this.scheduleMonitorReconnect()
})
}, 100)
}
private emitMonitorPayload(rawPayload: string): void {
const payload = rawPayload.trim()
if (!payload || !this.monitorCallback) return
try {
const parsed = JSON.parse(payload) as { action?: string }
this.monitorCallback(parsed.action || 'update', payload)
} catch {
this.monitorCallback('update', payload)
}
}
private scheduleMonitorReconnect(): void {
if (this.monitorReconnectTimer || !this.monitorCallback || !this.monitorPipePath) return
this.monitorReconnectTimer = setTimeout(() => {
this.monitorReconnectTimer = null
this.connectMonitorPipe()
}, 3000)
}
getSessions(): Wcdb4Session[] {
if (this.cachedSessions) return this.cachedSessions
if (!this.wcdbGetSessions) return []
@@ -768,6 +915,19 @@ export class Wcdb4Client {
console.warn('[WCDB4] wcdb_get_emoticon_cdn_url symbol unavailable')
this.wcdbGetEmoticonCdnUrl = null
}
try {
this.wcdbStartMonitorPipe = lib.func('int32 wcdb_start_monitor_pipe()') as () => number
this.wcdbStopMonitorPipe = lib.func('void wcdb_stop_monitor_pipe()') as () => void
this.wcdbGetMonitorPipeName = lib.func(
'int32 wcdb_get_monitor_pipe_name(_Out_ void** outName)'
) as (outName: WcdbVoidOut) => number
} catch {
console.warn('[WCDB4] monitor pipe symbols unavailable')
this.wcdbStartMonitorPipe = null
this.wcdbStopMonitorPipe = null
this.wcdbGetMonitorPipeName = null
}
}
private initProtection(lib: KoffiLibrary, libDir: string): void {
@@ -974,7 +1134,7 @@ export class Wcdb4Client {
])
return {
mesLocalID: localId || `${createTime}-${crypto.randomUUID()}`,
mesLocalID: localId || `${createTime}-${this.md5(JSON.stringify(row))}`,
mesDes: isSend ? 0 : 1,
messageType: messageType || '1',
msgCreateTime: String(createTime),
+5
View File
@@ -364,6 +364,11 @@ export class WechatDb {
return this.wcdb4Client
}
public close(): void {
this.wcdb4Client?.close()
this.wcdb4Client = null
}
public getUserMessages(userMd5: string, startTime?: number, endTime?: number): WechatMessage[] {
if (this.wcdb4Client) {
const username = this.chatMd5ToUsername.get(userMd5)