mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-08-17 19:47:08 +08:00
init: 初始化
This commit is contained in:
@@ -0,0 +1,244 @@
|
||||
import { app, shell, BrowserWindow, ipcMain, nativeImage, clipboard } from 'electron'
|
||||
import { join } from 'path'
|
||||
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
|
||||
import icon from '../../resources/icon.png?asset'
|
||||
import { WechatDb, Contact, WechatMessage } from './wechat-db'
|
||||
|
||||
let wechatDb: WechatDb | null = null
|
||||
|
||||
function createWindow(): void {
|
||||
// 创建浏览器窗口
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 670,
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
...(process.platform === 'linux' ? { icon } : {}),
|
||||
webPreferences: {
|
||||
preload: join(__dirname, '../preload/index.js'),
|
||||
sandbox: false
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.on('ready-to-show', () => {
|
||||
mainWindow.show()
|
||||
})
|
||||
|
||||
mainWindow.webContents.setWindowOpenHandler((details) => {
|
||||
shell.openExternal(details.url)
|
||||
return { action: 'deny' }
|
||||
})
|
||||
|
||||
// 基于 electron-vite cli 的渲染器 HMR
|
||||
// 加载开发环境的远程 URL 或生产环境的本地 html 文件
|
||||
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
|
||||
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
|
||||
} else {
|
||||
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
|
||||
}
|
||||
}
|
||||
|
||||
// 当 Electron 完成初始化并准备好创建浏览器窗口时,将调用此方法
|
||||
// 某些 API 只能在此事件发生后使用
|
||||
app.whenReady().then(() => {
|
||||
// 为窗口设置应用程序用户模型 ID
|
||||
electronApp.setAppUserModelId('com.electron')
|
||||
|
||||
// 在开发环境中默认按 F12 打开或关闭 DevTools
|
||||
// 在生产环境中忽略 CommandOrControl + R
|
||||
// 参见 https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
|
||||
app.on('browser-window-created', (_, window) => {
|
||||
optimizer.watchWindowShortcuts(window)
|
||||
})
|
||||
|
||||
// IPC test
|
||||
ipcMain.on('ping', () => console.log('pong'))
|
||||
|
||||
ipcMain.handle('db:init', (_, key: string) => {
|
||||
try {
|
||||
wechatDb = new WechatDb(key)
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('Failed to init DB:', error)
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('db:getContacts', (_, filter?: string) => {
|
||||
if (!wechatDb) return []
|
||||
|
||||
const contacts: Contact[] = []
|
||||
const groupContacts = wechatDb.getAllGroupContacts()
|
||||
const userList = wechatDb.getUserList(filter)
|
||||
const existingMd5s = new Set<string>()
|
||||
|
||||
// 1. 处理普通联系人
|
||||
for (const user of userList) {
|
||||
const md5 = wechatDb.md5(user.m_nsUsrName)
|
||||
existingMd5s.add(md5)
|
||||
contacts.push({
|
||||
m_nsUsrName: user.m_nsUsrName,
|
||||
m_nsNickName: user.nickname || '未知用户',
|
||||
md5: md5,
|
||||
type: 'user'
|
||||
})
|
||||
}
|
||||
|
||||
// 2. 处理聊天表
|
||||
const chatTables = wechatDb.getAllChatTables()
|
||||
for (const table of chatTables) {
|
||||
if (!table.name.startsWith('Chat_')) continue
|
||||
const md5 = table.name.substring(5)
|
||||
|
||||
if (!existingMd5s.has(md5)) {
|
||||
if (groupContacts[md5]) {
|
||||
contacts.push({
|
||||
m_nsUsrName: `Group_${md5}`,
|
||||
m_nsNickName: groupContacts[md5],
|
||||
md5: md5,
|
||||
type: 'group'
|
||||
})
|
||||
} else {
|
||||
contacts.push({
|
||||
m_nsUsrName: `Unknown_${md5}`,
|
||||
m_nsNickName: `Chat_${md5}`,
|
||||
md5: md5,
|
||||
type: 'user'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return contacts
|
||||
})
|
||||
|
||||
ipcMain.handle('db:getMessages', (_, userMd5: string, startTime?: number, endTime?: number) => {
|
||||
if (!wechatDb) return []
|
||||
const rawMessages = wechatDb.getUserMessages(userMd5, startTime, endTime)
|
||||
const groupMembers = wechatDb.getAllGroupMembers()
|
||||
|
||||
return rawMessages.map((msg: WechatMessage) => {
|
||||
const typeDict: Record<number, string> = {
|
||||
1: '普通文本',
|
||||
3: '图片',
|
||||
34: '语音',
|
||||
43: '视频',
|
||||
47: '表情包',
|
||||
48: '位置',
|
||||
49: '分享消息',
|
||||
10000: '系统消息'
|
||||
}
|
||||
|
||||
const msgType = parseInt(msg.messageType)
|
||||
const createTime = parseInt(msg.msgCreateTime)
|
||||
const date = new Date(createTime * 1000)
|
||||
|
||||
let content = msg.msgContent
|
||||
let img = ''
|
||||
let name = ''
|
||||
// 检查内容是否以 wxid 开头并包含冒号
|
||||
// 示例: wxid_xxxx:\nContent 或 wxid_xxxx:Content
|
||||
if (content && typeof content === 'string') {
|
||||
const colonIndex = content.indexOf(':')
|
||||
if (colonIndex > 0) {
|
||||
const potentialWxid = content.substring(0, colonIndex)
|
||||
if (potentialWxid.startsWith('wxid_')) {
|
||||
// 尝试获取头像
|
||||
if (wechatDb) {
|
||||
const member = wechatDb.getGroupMember(potentialWxid)
|
||||
if (member) {
|
||||
img = member.m_nsHeadImgUrl
|
||||
}
|
||||
}
|
||||
|
||||
if (groupMembers[potentialWxid]) {
|
||||
const nickname = groupMembers[potentialWxid]
|
||||
name = nickname
|
||||
content = content.substring(colonIndex + 1) // +1 to skip the colon
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: msg.mesLocalID || Math.random().toString(),
|
||||
from: msg.mesDes === '1' ? 'user' : 'assistant', // 1 通常是接收到的,0 是发送的?需要验证。Swift 说:[1: "user", 0: "assistant"]
|
||||
type: typeDict[msgType] || msg.messageType,
|
||||
datetime: date.toLocaleString('zh-CN', { hour12: false }),
|
||||
content: content,
|
||||
isSender: msg.mesDes === '0',
|
||||
img: img,
|
||||
name: name
|
||||
// 实际上让我们坚持我们观察到的:1=用户(其他人), 0=助手(我)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
ipcMain.handle('db:search', (_, keyword: string) => {
|
||||
if (!wechatDb) return null
|
||||
return wechatDb.searchAllMessages(keyword)
|
||||
})
|
||||
|
||||
ipcMain.handle(
|
||||
'ai:chat',
|
||||
async (
|
||||
_,
|
||||
messages: { role: string; content: string }[],
|
||||
options?: { apiKey?: string; model?: string }
|
||||
) => {
|
||||
// @ts-ignore: vite env
|
||||
const apiKey = options?.apiKey || import.meta.env.VITE_DEEPSEEK_API_KEY
|
||||
const model = options?.model || 'deepseek-chat'
|
||||
|
||||
if (!apiKey) {
|
||||
return { success: false, error: '未配置 DeepSeek API Key' }
|
||||
}
|
||||
|
||||
// 动态导入以避免如果未安装或初始类型缺失的问题
|
||||
const { OpenAI } = await import('openai')
|
||||
|
||||
const openai = new OpenAI({
|
||||
baseURL: 'https://api.deepseek.com',
|
||||
apiKey: apiKey
|
||||
})
|
||||
try {
|
||||
const completion = await openai.chat.completions.create({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
messages: messages as any,
|
||||
model: model
|
||||
})
|
||||
return { success: true, data: completion.choices[0].message.content }
|
||||
} catch (error: unknown) {
|
||||
console.error('DeepSeek API Error:', error)
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
||||
return { success: false, error: errorMessage }
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
ipcMain.handle('copy-image', async (_, base64String) => {
|
||||
try {
|
||||
const image = nativeImage.createFromDataURL(base64String)
|
||||
clipboard.writeImage(image)
|
||||
return { success: true }
|
||||
} catch (error: unknown) {
|
||||
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' }
|
||||
}
|
||||
})
|
||||
|
||||
createWindow()
|
||||
|
||||
app.on('activate', function () {
|
||||
// 在 macOS 上,当点击 dock 图标且没有其他窗口打开时,
|
||||
// 通常会在应用程序中重新创建一个窗口。
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||
})
|
||||
})
|
||||
|
||||
// 当所有窗口关闭时退出,除了 macOS。在那里,
|
||||
// 应用程序及其菜单栏通常会保持活动状态,直到用户
|
||||
// 显式使用 Cmd + Q 退出。
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,362 @@
|
||||
import DatabaseConstructor from 'better-sqlite3-multiple-ciphers'
|
||||
import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
import crypto from 'crypto'
|
||||
import os from 'os'
|
||||
|
||||
type Database = import('better-sqlite3-multiple-ciphers').Database
|
||||
|
||||
export interface UserContact {
|
||||
m_nsUsrName: string
|
||||
nickname: string
|
||||
}
|
||||
|
||||
export interface WechatMessage {
|
||||
mesLocalID: string
|
||||
mesDes: string
|
||||
messageType: string
|
||||
msgCreateTime: string
|
||||
msgContent: string
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface Contact {
|
||||
m_nsUsrName: string
|
||||
m_nsNickName: string
|
||||
md5: string
|
||||
type: 'user' | 'group'
|
||||
}
|
||||
|
||||
export interface GroupMemberInfo {
|
||||
m_nsUsrName: string
|
||||
nickname: string
|
||||
m_nsHeadImgUrl: string
|
||||
}
|
||||
|
||||
export class WechatDb {
|
||||
private static WECHAT_DIR = path.join(
|
||||
os.homedir(),
|
||||
'Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat/2.0b4.0.9'
|
||||
)
|
||||
|
||||
private rawKey: string
|
||||
private correctUserId: string | null = null
|
||||
private chatDb: { name: string; db_number: string }[] | null = null
|
||||
private groupMemberCache = new Map<string, GroupMemberInfo | null>()
|
||||
|
||||
constructor(rawKey: string) {
|
||||
this.rawKey = rawKey
|
||||
console.log(`Initializing WechatDb with key: ${rawKey}`)
|
||||
|
||||
if (!fs.existsSync(WechatDb.WECHAT_DIR)) {
|
||||
throw new Error(`WeChat directory not found at ${WechatDb.WECHAT_DIR}`)
|
||||
}
|
||||
|
||||
if (this.getUser()) {
|
||||
console.log('User found, getting chat DB number')
|
||||
this.chatDb = this.getChatDbNumber()
|
||||
} else {
|
||||
throw new Error('No valid user found or invalid key')
|
||||
}
|
||||
}
|
||||
|
||||
private connectDb(dbPath: string): Database | null {
|
||||
// 1. 路径预检查
|
||||
const targetPath = path.resolve(dbPath)
|
||||
if (!fs.existsSync(targetPath)) {
|
||||
console.error(`❌ 错误:数据库文件不存在于路径:${targetPath}`)
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
// console.log(`🔌 正在尝试连接至:${path.basename(targetPath)}`)
|
||||
// console.log('路径是', targetPath)
|
||||
// 2. 初始化数据库句柄
|
||||
// 关键:使用 better-sqlite3-multiple-ciphers 的构造函数
|
||||
// verbose 选项有助于调试 Pragma 的执行情况
|
||||
const db = new DatabaseConstructor(targetPath, {
|
||||
// verbose: console.log
|
||||
})
|
||||
|
||||
// 此时 db 实例已创建,但因为未提供密钥,任何数据读取都会失败。
|
||||
// 必须立即配置加密参数。
|
||||
|
||||
// 3. 配置加密方案 (Cipher Scheme)
|
||||
// 必须显式切换到 'sqlcipher',因为该库默认可能使用 'chacha20'
|
||||
db.pragma("cipher='sqlcipher'")
|
||||
|
||||
// 4. 配置页大小 (Page Size)
|
||||
// macOS 版 WeChat 使用 1024 字节。这必须在设置密钥前(或紧随其后)生效。
|
||||
db.pragma('cipher_page_size = 1024')
|
||||
|
||||
// 5. 配置兼容性与算法细节
|
||||
// 显式指定 SQLCipher 3 的行为
|
||||
db.pragma('legacy=3')
|
||||
db.pragma('kdf_iter = 64000')
|
||||
db.pragma('cipher_hmac_algorithm = HMAC_SHA1')
|
||||
db.pragma('cipher_kdf_algorithm = PBKDF2_HMAC_SHA1')
|
||||
|
||||
// 6. 应用密钥
|
||||
// 使用正确的 hex 语法: key = "x'HEXSTRING'"
|
||||
const processedKey = this.processRawKey(this.rawKey)
|
||||
// console.log(`🔑 应用密钥 (前6位): ${processedKey.substring(0, 6)}...`)
|
||||
|
||||
db.pragma(`key = "x'${processedKey}'"`)
|
||||
|
||||
// 7. 验证连接
|
||||
// 如果参数错误,这里将抛出 "file is not a database" 或 "HMAC check failed"
|
||||
db.prepare('SELECT count(*) as count FROM sqlite_master').get()
|
||||
|
||||
// console.log(`✅ 连接成功!当前数据库 ${path.basename(targetPath)} 包含 ${row.count} 张表。`)
|
||||
return db
|
||||
} catch (error) {
|
||||
console.error(`❌ 连接失败:`, error)
|
||||
|
||||
const errString = String(error)
|
||||
if (errString.includes('file is not a database')) {
|
||||
console.error(
|
||||
"💡 诊断建议:\n1. 请确认是否已安装 'better-sqlite3-multiple-ciphers'。\n2. 检查密钥是否正确(WeChat密钥与设备强绑定)。\n3. 尝试将页大小改为 4096 (db.pragma('cipher_page_size = 4096'))。"
|
||||
)
|
||||
} else if (errString.includes('HMAC')) {
|
||||
console.error(
|
||||
'💡 诊断建议:密钥可能正确,但HMAC算法或页大小不匹配。尝试调整 cipher_hmac_algorithm。'
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private processRawKey(key: string): string {
|
||||
// 移除可能的 '0x' 前缀并修剪空格
|
||||
return key.replace(/^0x/i, '').trim()
|
||||
}
|
||||
|
||||
private getUser(): boolean {
|
||||
const keyValuePath = path.join(WechatDb.WECHAT_DIR, 'KeyValue')
|
||||
if (!fs.existsSync(keyValuePath)) return false
|
||||
|
||||
const contents = fs.readdirSync(keyValuePath)
|
||||
const potentialUsers = contents.filter((name) => !name.startsWith('.'))
|
||||
|
||||
for (const user of potentialUsers) {
|
||||
const dbPath = path.join(keyValuePath, user, 'KeyValue.db')
|
||||
if (fs.existsSync(dbPath)) {
|
||||
const db = this.connectDb(dbPath)
|
||||
if (db) {
|
||||
this.correctUserId = user
|
||||
console.log(`Valid user found: ${user}`)
|
||||
db.close()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private getChatDbNumber(): { name: string; db_number: string }[] {
|
||||
const chatDb: { name: string; db_number: string }[] = []
|
||||
if (!this.correctUserId) return []
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const dbPath = path.join(WechatDb.WECHAT_DIR, this.correctUserId, 'Message', `msg_${i}.db`)
|
||||
if (!fs.existsSync(dbPath)) continue
|
||||
|
||||
const db = this.connectDb(dbPath)
|
||||
if (db) {
|
||||
const tables = db
|
||||
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'Chat_%'")
|
||||
.all() as { name: string }[]
|
||||
for (const table of tables) {
|
||||
chatDb.push({ name: table.name, db_number: `msg_${i}.db` })
|
||||
}
|
||||
db.close()
|
||||
}
|
||||
}
|
||||
return chatDb
|
||||
}
|
||||
|
||||
public getUserList(nicknameFilter?: string): UserContact[] {
|
||||
if (!this.correctUserId) return []
|
||||
const dbPath = path.join(WechatDb.WECHAT_DIR, this.correctUserId, 'Contact/wccontact_new2.db')
|
||||
const db = this.connectDb(dbPath)
|
||||
if (!db) return []
|
||||
|
||||
let query = "SELECT m_nsUsrName, nickname FROM WCContact WHERE m_nsUsrName NOT LIKE 'gh_%'"
|
||||
if (nicknameFilter) {
|
||||
query += ` AND nickname LIKE '%${nicknameFilter}%'`
|
||||
}
|
||||
|
||||
const results = db.prepare(query).all() as unknown as UserContact[]
|
||||
db.close()
|
||||
return results
|
||||
}
|
||||
|
||||
public getAllGroupContacts(): Record<string, string> {
|
||||
if (!this.correctUserId) return {}
|
||||
const dbPath = path.join(WechatDb.WECHAT_DIR, this.correctUserId, 'Group/group_new.db')
|
||||
const db = this.connectDb(dbPath)
|
||||
if (!db) return {}
|
||||
|
||||
const results = db.prepare('SELECT m_nsUsrName, nickname FROM GroupContact').all() as {
|
||||
m_nsUsrName: string
|
||||
nickname: string
|
||||
}[]
|
||||
db.close()
|
||||
|
||||
const groupContacts: Record<string, string> = {}
|
||||
for (const row of results) {
|
||||
if (row.m_nsUsrName && row.nickname) {
|
||||
const md5 = this.md5(row.m_nsUsrName)
|
||||
groupContacts[md5] = row.nickname
|
||||
}
|
||||
}
|
||||
return groupContacts
|
||||
}
|
||||
|
||||
public getAllGroupMembers(): Record<string, string> {
|
||||
if (!this.correctUserId) return {}
|
||||
const dbPath = path.join(WechatDb.WECHAT_DIR, this.correctUserId, 'Group/group_new.db')
|
||||
const db = this.connectDb(dbPath)
|
||||
if (!db) return {}
|
||||
|
||||
// 查询 GroupMember 表
|
||||
const results = db.prepare('SELECT m_nsUsrName, nickname FROM GroupMember').all() as {
|
||||
m_nsUsrName: string
|
||||
nickname: string
|
||||
}[]
|
||||
db.close()
|
||||
|
||||
const groupMembers: Record<string, string> = {}
|
||||
for (const row of results) {
|
||||
if (row.m_nsUsrName && row.nickname) {
|
||||
// 将 wxid (m_nsUsrName) 映射到昵称
|
||||
groupMembers[row.m_nsUsrName] = row.nickname
|
||||
}
|
||||
}
|
||||
return groupMembers
|
||||
}
|
||||
|
||||
public getGroupMember(wxid: string): GroupMemberInfo | null {
|
||||
if (!this.correctUserId) return null
|
||||
|
||||
// 1. 检查缓存
|
||||
if (this.groupMemberCache.has(wxid)) {
|
||||
return this.groupMemberCache.get(wxid) || null
|
||||
}
|
||||
|
||||
const dbPath = path.join(WechatDb.WECHAT_DIR, this.correctUserId, 'Group/group_new.db')
|
||||
const db = this.connectDb(dbPath)
|
||||
if (!db) {
|
||||
this.groupMemberCache.set(wxid, null)
|
||||
return null
|
||||
}
|
||||
|
||||
const result = db
|
||||
.prepare(
|
||||
'SELECT m_nsUsrName, nickname, m_nsHeadImgUrl FROM GroupMember WHERE m_nsUsrName = ?'
|
||||
)
|
||||
.get(wxid) as GroupMemberInfo
|
||||
|
||||
db.close()
|
||||
|
||||
// 2. 写入缓存
|
||||
this.groupMemberCache.set(wxid, result || null)
|
||||
|
||||
return result || null
|
||||
}
|
||||
|
||||
public getAllChatTables(): { name: string; db_number: string }[] {
|
||||
return this.chatDb || []
|
||||
}
|
||||
|
||||
public getUserMessages(userMd5: string, startTime?: number, endTime?: number): WechatMessage[] {
|
||||
if (!this.chatDb || !this.correctUserId) return []
|
||||
|
||||
const tableName = `Chat_${userMd5}`
|
||||
const tableInfo = this.chatDb.find((t) => t.name === tableName)
|
||||
|
||||
if (!tableInfo) {
|
||||
console.error(`No table found for ${tableName}`)
|
||||
return []
|
||||
}
|
||||
|
||||
const dbPath = path.join(
|
||||
WechatDb.WECHAT_DIR,
|
||||
this.correctUserId,
|
||||
'Message',
|
||||
tableInfo.db_number
|
||||
)
|
||||
const db = this.connectDb(dbPath)
|
||||
if (!db) return []
|
||||
|
||||
try {
|
||||
let query = `SELECT * FROM ${tableName}`
|
||||
const conditions: string[] = []
|
||||
|
||||
// console.log(startTime, endTime, 'time range')
|
||||
|
||||
if (startTime) {
|
||||
conditions.push(`msgCreateTime >= ${startTime}`)
|
||||
}
|
||||
if (endTime) {
|
||||
conditions.push(`msgCreateTime <= ${endTime}`)
|
||||
}
|
||||
|
||||
if (conditions.length > 0) {
|
||||
query += ` WHERE ${conditions.join(' AND ')}`
|
||||
}
|
||||
|
||||
// 添加排序以确保存储正确的顺序
|
||||
query += ` ORDER BY msgCreateTime ASC`
|
||||
|
||||
const results = db.prepare(query).all() as unknown as WechatMessage[]
|
||||
db.close()
|
||||
return results
|
||||
} catch (e) {
|
||||
console.error(`Error querying messages for ${tableName}:`, e)
|
||||
db.close()
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
public searchAllMessages(keyword: string): string | null {
|
||||
if (!this.correctUserId) return null
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const dbName = `msg_${i}.db`
|
||||
const dbPath = path.join(WechatDb.WECHAT_DIR, this.correctUserId, 'Message', dbName)
|
||||
if (!fs.existsSync(dbPath)) continue
|
||||
|
||||
const db = this.connectDb(dbPath)
|
||||
if (!db) continue
|
||||
|
||||
const tables = db
|
||||
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'Chat_%'")
|
||||
.all() as { name: string }[]
|
||||
for (const table of tables) {
|
||||
try {
|
||||
// 首先检查 msgContent 列是否存在以避免错误?
|
||||
// 或者像 Swift 代码中那样使用 try/catch "抑制错误"
|
||||
const results = db
|
||||
.prepare(`SELECT 1 FROM ${table.name} WHERE msgContent LIKE '%${keyword}%' LIMIT 1`)
|
||||
.get()
|
||||
if (results) {
|
||||
console.log(`Found keyword in ${table.name} (${dbName})`)
|
||||
db.close()
|
||||
return table.name
|
||||
}
|
||||
} catch {
|
||||
// 忽略没有 msgContent 的表
|
||||
}
|
||||
}
|
||||
db.close()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
public md5(str: string): string {
|
||||
return crypto.createHash('md5').update(str).digest('hex')
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user