This commit is contained in:
xuncha
2026-02-16 17:26:06 +08:00
parent ea0dad132c
commit 1930b91a5b
5 changed files with 84 additions and 15 deletions

View File

@@ -12,6 +12,7 @@ interface ConfigSchema {
// 缓存相关 // 缓存相关
cachePath: string cachePath: string
weixinDllPath: string
lastOpenedDb: string lastOpenedDb: string
lastSession: string lastSession: string
@@ -72,6 +73,7 @@ export class ConfigService {
imageAesKey: '', imageAesKey: '',
wxidConfigs: {}, wxidConfigs: {},
cachePath: '', cachePath: '',
weixinDllPath: '',
lastOpenedDb: '', lastOpenedDb: '',
lastSession: '', lastSession: '',
theme: 'system', theme: 'system',

View File

@@ -75,6 +75,7 @@ const detectImageMime = (buf: Buffer, fallback: string = 'image/jpeg') => {
} }
class SnsService { class SnsService {
private configService: ConfigService
private contactCache: ContactCacheService private contactCache: ContactCacheService
private imageCache = new Map<string, string>() private imageCache = new Map<string, string>()
@@ -87,8 +88,8 @@ class SnsService {
private nativeDecryptFn: any = null private nativeDecryptFn: any = null
constructor() { constructor() {
const config = new ConfigService() this.configService = new ConfigService()
this.contactCache = new ContactCacheService(config.get('cachePath') as string) this.contactCache = new ContactCacheService(this.configService.get('cachePath') as string)
} }
async getTimeline(limit: number = 20, offset: number = 0, usernames?: string[], keyword?: string, startTime?: number, endTime?: number): Promise<{ success: boolean; timeline?: SnsPost[]; error?: string }> { async getTimeline(limit: number = 20, offset: number = 0, usernames?: string[], keyword?: string, startTime?: number, endTime?: number): Promise<{ success: boolean; timeline?: SnsPost[]; error?: string }> {
@@ -182,11 +183,14 @@ class SnsService {
return null return null
} }
} }
private resolveWeixinDllPath(): string | null { private resolveWeixinDllPath(): string | null {
const candidates: string[] = [] const candidates: string[] = []
if (process.env.WEFLOW_WEIXIN_DLL) candidates.push(process.env.WEFLOW_WEIXIN_DLL) if (process.env.WEFLOW_WEIXIN_DLL) candidates.push(process.env.WEFLOW_WEIXIN_DLL)
const configuredPath = String(this.configService.get('weixinDllPath') || '').trim()
if (configuredPath) candidates.push(configuredPath)
const weixinExe = process.env.WEFLOW_WEIXIN_EXE const weixinExe = process.env.WEFLOW_WEIXIN_EXE
if (weixinExe) { if (weixinExe) {
const dir = weixinExe.replace(/\\Weixin\.exe$/i, '') const dir = weixinExe.replace(/\\Weixin\.exe$/i, '')
@@ -195,27 +199,32 @@ class SnsService {
const programFiles = process.env.ProgramFiles || 'C:\\Program Files' const programFiles = process.env.ProgramFiles || 'C:\\Program Files'
const localAppData = process.env.LOCALAPPDATA || '' const localAppData = process.env.LOCALAPPDATA || ''
candidates.push( candidates.push(join(programFiles, 'Tencent', 'Weixin', 'Weixin.dll'))
join(programFiles, 'Tencent', 'Weixin', 'Weixin.dll'),
'D:\\weixindata\\Weixin\\Weixin.dll',
'C:\\Users\\16586\\Desktop\\sns\\Weixin.dll'
)
if (localAppData) candidates.push(join(localAppData, 'Tencent', 'xwechat', 'Weixin.dll')) if (localAppData) candidates.push(join(localAppData, 'Tencent', 'xwechat', 'Weixin.dll'))
const seen = new Set<string>()
for (const p of candidates) { for (const p of candidates) {
if (p && existsSync(p)) return p if (!p) continue
const normalized = p.trim()
if (!normalized || seen.has(normalized)) continue
seen.add(normalized)
if (existsSync(normalized)) return normalized
} }
return null return null
} }
private ensureNativeDecryptor(): boolean { private ensureNativeDecryptor(): boolean {
const configuredPath = String(this.configService.get('weixinDllPath') || '').trim()
if (this.nativeDecryptInit && !this.nativeDecryptReady && configuredPath && configuredPath !== this.nativeDecryptDllPath) {
this.nativeDecryptInit = false
}
if (this.nativeDecryptInit) return this.nativeDecryptReady if (this.nativeDecryptInit) return this.nativeDecryptReady
this.nativeDecryptInit = true this.nativeDecryptInit = true
try { try {
const dllPath = this.resolveWeixinDllPath() const dllPath = this.resolveWeixinDllPath()
if (!dllPath) { if (!dllPath) {
this.nativeDecryptError = 'Weixin.dll not found, set WEFLOW_WEIXIN_DLL if needed' this.nativeDecryptError = 'Weixin.dll not found, please set it in Settings or WEFLOW_WEIXIN_DLL'
return false return false
} }
@@ -349,6 +358,3 @@ class SnsService {
} }
export const snsService = new SnsService() export const snsService = new SnsService()

View File

@@ -74,6 +74,7 @@ function SettingsPage() {
const exportExcelColumnsDropdownRef = useRef<HTMLDivElement>(null) const exportExcelColumnsDropdownRef = useRef<HTMLDivElement>(null)
const exportConcurrencyDropdownRef = useRef<HTMLDivElement>(null) const exportConcurrencyDropdownRef = useRef<HTMLDivElement>(null)
const [cachePath, setCachePath] = useState('') const [cachePath, setCachePath] = useState('')
const [weixinDllPath, setWeixinDllPath] = useState('')
const [logEnabled, setLogEnabled] = useState(false) const [logEnabled, setLogEnabled] = useState(false)
const [whisperModelName, setWhisperModelName] = useState('base') const [whisperModelName, setWhisperModelName] = useState('base')
const [whisperModelDir, setWhisperModelDir] = useState('') const [whisperModelDir, setWhisperModelDir] = useState('')
@@ -249,6 +250,7 @@ function SettingsPage() {
const savedPath = await configService.getDbPath() const savedPath = await configService.getDbPath()
const savedWxid = await configService.getMyWxid() const savedWxid = await configService.getMyWxid()
const savedCachePath = await configService.getCachePath() const savedCachePath = await configService.getCachePath()
const savedWeixinDllPath = await configService.getWeixinDllPath()
const savedExportPath = await configService.getExportPath() const savedExportPath = await configService.getExportPath()
const savedLogEnabled = await configService.getLogEnabled() const savedLogEnabled = await configService.getLogEnabled()
const savedImageXorKey = await configService.getImageXorKey() const savedImageXorKey = await configService.getImageXorKey()
@@ -277,6 +279,7 @@ function SettingsPage() {
if (savedPath) setDbPath(savedPath) if (savedPath) setDbPath(savedPath)
if (savedWxid) setWxid(savedWxid) if (savedWxid) setWxid(savedWxid)
if (savedCachePath) setCachePath(savedCachePath) if (savedCachePath) setCachePath(savedCachePath)
if (savedWeixinDllPath) setWeixinDllPath(savedWeixinDllPath)
const wxidConfig = savedWxid ? await configService.getWxidConfig(savedWxid) : null const wxidConfig = savedWxid ? await configService.getWxidConfig(savedWxid) : null
const decryptKeyToUse = wxidConfig?.decryptKey ?? savedKey ?? '' const decryptKeyToUse = wxidConfig?.decryptKey ?? savedKey ?? ''
@@ -613,6 +616,29 @@ function SettingsPage() {
await applyWxidSelection(selectedWxid) await applyWxidSelection(selectedWxid)
} }
const handleSelectWeixinDllPath = async () => {
try {
const result = await dialog.openFile({
title: '选择 Weixin.dll 文件',
properties: ['openFile'],
filters: [{ name: 'DLL', extensions: ['dll'] }]
})
if (!result.canceled && result.filePaths.length > 0) {
const selectedPath = result.filePaths[0]
setWeixinDllPath(selectedPath)
await configService.setWeixinDllPath(selectedPath)
showMessage('已选择 Weixin.dll 路径', true)
}
} catch {
showMessage('选择 Weixin.dll 失败', false)
}
}
const handleResetWeixinDllPath = async () => {
setWeixinDllPath('')
await configService.setWeixinDllPath('')
showMessage('已清空 Weixin.dll 路径', true)
}
const handleSelectCachePath = async () => { const handleSelectCachePath = async () => {
try { try {
const result = await dialog.openFile({ title: '选择缓存目录', properties: ['openDirectory'] }) const result = await dialog.openFile({ title: '选择缓存目录', properties: ['openDirectory'] })
@@ -1306,6 +1332,29 @@ function SettingsPage() {
</div> </div>
</div> </div>
<div className="form-group">
<label>Weixin.dll <span className="optional">()</span></label>
<span className="form-hint">线使 DLL</span>
<input
type="text"
placeholder="例如: D:\weixindata\Weixin\Weixin.dll"
value={weixinDllPath}
onChange={(e) => {
const value = e.target.value
setWeixinDllPath(value)
scheduleConfigSave('weixinDllPath', () => configService.setWeixinDllPath(value))
}}
/>
<div className="btn-row">
<button className="btn btn-secondary" onClick={handleSelectWeixinDllPath}>
<FolderOpen size={16} />
</button>
<button className="btn btn-secondary" onClick={handleResetWeixinDllPath}>
</button>
</div>
</div>
<div className="form-group"> <div className="form-group">
<label> wxid</label> <label> wxid</label>
<span className="form-hint"></span> <span className="form-hint"></span>

View File

@@ -434,7 +434,7 @@ export default function SnsPage() {
<div className="sns-content-wrapper"> <div className="sns-content-wrapper">
<div className="sns-notice-banner"> <div className="sns-notice-banner">
<AlertTriangle size={16} /> <AlertTriangle size={16} />
<span></span> <span></span>
</div> </div>
<div className="sns-content custom-scrollbar" onScroll={handleScroll} onWheel={handleWheel} ref={postsContainerRef}> <div className="sns-content custom-scrollbar" onScroll={handleScroll} onWheel={handleWheel} ref={postsContainerRef}>
<div className="posts-list"> <div className="posts-list">

View File

@@ -12,6 +12,7 @@ export const CONFIG_KEYS = {
LAST_SESSION: 'lastSession', LAST_SESSION: 'lastSession',
WINDOW_BOUNDS: 'windowBounds', WINDOW_BOUNDS: 'windowBounds',
CACHE_PATH: 'cachePath', CACHE_PATH: 'cachePath',
WEIXIN_DLL_PATH: 'weixinDllPath',
EXPORT_PATH: 'exportPath', EXPORT_PATH: 'exportPath',
AGREEMENT_ACCEPTED: 'agreementAccepted', AGREEMENT_ACCEPTED: 'agreementAccepted',
LOG_ENABLED: 'logEnabled', LOG_ENABLED: 'logEnabled',
@@ -162,6 +163,17 @@ export async function setCachePath(path: string): Promise<void> {
} }
// 获取 Weixin.dll 路径
export async function getWeixinDllPath(): Promise<string | null> {
const value = await config.get(CONFIG_KEYS.WEIXIN_DLL_PATH)
return value as string | null
}
// 设置 Weixin.dll 路径
export async function setWeixinDllPath(path: string): Promise<void> {
await config.set(CONFIG_KEYS.WEIXIN_DLL_PATH, path)
}
// 获取导出路径 // 获取导出路径
export async function getExportPath(): Promise<string | null> { export async function getExportPath(): Promise<string | null> {
const value = await config.get(CONFIG_KEYS.EXPORT_PATH) const value = await config.get(CONFIG_KEYS.EXPORT_PATH)