From 9c7ed1729a0b190a33551559ff114b6dfd5b56dc Mon Sep 17 00:00:00 2001 From: H3CoF6 <1707889225@qq.com> Date: Wed, 29 Apr 2026 02:44:01 +0800 Subject: [PATCH 1/7] chore: add win32 dll + readme --- electron/services/imageDownloadService.ts | 0 resources/image/README.md | 1 + resources/image/win32/x64/img_helper.dll | Bin 0 -> 22528 bytes 3 files changed, 1 insertion(+) create mode 100644 electron/services/imageDownloadService.ts create mode 100644 resources/image/README.md create mode 100644 resources/image/win32/x64/img_helper.dll diff --git a/electron/services/imageDownloadService.ts b/electron/services/imageDownloadService.ts new file mode 100644 index 0000000..e69de29 diff --git a/resources/image/README.md b/resources/image/README.md new file mode 100644 index 0000000..a964638 --- /dev/null +++ b/resources/image/README.md @@ -0,0 +1 @@ +> 目前只适配了x64 win32平台,其它平台同样原理,但是代码还没写( \ No newline at end of file diff --git a/resources/image/win32/x64/img_helper.dll b/resources/image/win32/x64/img_helper.dll new file mode 100644 index 0000000000000000000000000000000000000000..ad5112577c6ad7b12cee6658ad43462120034356 GIT binary patch literal 22528 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 Y1;z!**lm0)_pyq{fd2jZ|HK0S8yH`L6#xJL literal 0 HcmV?d00001 From 1f0b2613bfa7cfcccb6fd40ddefd9d7847680f8e Mon Sep 17 00:00:00 2001 From: H3CoF6 <1707889225@qq.com> Date: Wed, 29 Apr 2026 04:05:48 +0800 Subject: [PATCH 2/7] =?UTF-8?q?feat(image):=20=E6=96=B0=E5=A2=9E=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E4=B8=8B=E8=BD=BD=E5=A4=A7=E5=9B=BE=E9=80=89=E9=A1=B9?= =?UTF-8?q?=EF=BC=88win32=20x64=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: NineBird --- electron/main.ts | 20 +++ electron/preload.ts | 10 +- electron/services/config.ts | 4 +- electron/services/imageDownloadService.ts | 174 ++++++++++++++++++++++ src/pages/SettingsPage.tsx | 53 ++++++- src/services/config.ts | 12 +- 6 files changed, 269 insertions(+), 4 deletions(-) diff --git a/electron/main.ts b/electron/main.ts index b57b76b..5f54f94 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -34,6 +34,7 @@ import { insightService } from './services/insightService' import { normalizeWeiboCookieInput, weiboService } from './services/social/weiboService' import { bizService } from './services/bizService' import { backupService } from './services/backupService' +import { imageDownloadService } from './services/imageDownloadService' // 配置自动更新 autoUpdater.autoDownload = false @@ -3954,6 +3955,20 @@ function registerIpcHandlers() { } }) + // 自动下载原图 + ipcMain.handle('image:startAutoDownload', async () => { + await imageDownloadService.startAutoDownload() + return { success: true } + }) + + ipcMain.handle('image:stopAutoDownload', async () => { + await imageDownloadService.stopAutoDownload() + return { success: true } + }) + + ipcMain.handle('image:getAutoDownloadStatus', async () => { + return await imageDownloadService.getStatus() + }) } // 主窗口引用 @@ -4081,6 +4096,9 @@ app.whenReady().then(async () => { // 注册 IPC 处理器 updateSplashProgress(28, '正在初始化...') registerIpcHandlers() + if (configService.get('autoDownloadHighRes')) { + imageDownloadService.startAutoDownload() + } chatService.addDbMonitorListener((type, json) => { messagePushService.handleDbMonitorChange(type, json) insightService.handleDbMonitorChange(type, json) @@ -4252,6 +4270,8 @@ const shutdownAppServices = async (): Promise => { }, 5000) forceExitTimer.unref() try { await cloudControlService.stop() } catch {} + // 停止自动下载服务 + try { await imageDownloadService.stopAutoDownload() } catch {} // 停止 chatService(内部会关闭 cursor 与 DB),避免退出阶段仍触发监控回调 try { chatService.close() } catch {} // 停止 HTTP 服务器,释放 TCP 端口占用,避免进程无法退出 diff --git a/electron/preload.ts b/electron/preload.ts index c7ba7c2..84ed45f 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -365,7 +365,10 @@ contextBridge.exposeInMainWorld('electronAPI', { }) => callback(payload) ipcRenderer.on('image:decryptProgress', listener) return () => ipcRenderer.removeListener('image:decryptProgress', listener) - } + }, + startAutoDownload: () => ipcRenderer.invoke('image:startAutoDownload'), + stopAutoDownload: () => ipcRenderer.invoke('image:stopAutoDownload'), + getAutoDownloadStatus: () => ipcRenderer.invoke('image:getAutoDownloadStatus') }, // 视频 @@ -374,6 +377,11 @@ contextBridge.exposeInMainWorld('electronAPI', { parseVideoMd5: (content: string) => ipcRenderer.invoke('video:parseVideoMd5', content) }, + process: { + platform: process.platform, + arch: process.arch + }, + // 数据分析 analytics: { getOverallStatistics: (force?: boolean) => ipcRenderer.invoke('analytics:getOverallStatistics', force), diff --git a/electron/services/config.ts b/electron/services/config.ts index 9e38931..27c216c 100644 --- a/electron/services/config.ts +++ b/electron/services/config.ts @@ -117,6 +117,7 @@ interface ConfigSchema { aiFootprintSystemPrompt: string /** 是否将 AI 见解调试日志输出到桌面 */ aiInsightDebugLogEnabled: boolean + autoDownloadHighRes: boolean } interface ConfigStoreLike> { @@ -294,7 +295,8 @@ export class ConfigService { aiInsightWeiboBindings: {}, aiFootprintEnabled: false, aiFootprintSystemPrompt: '', - aiInsightDebugLogEnabled: false + aiInsightDebugLogEnabled: false, + autoDownloadHighRes: false } const cwd = String(process.env.WEFLOW_CONFIG_CWD || process.env.WEFLOW_USER_DATA_PATH || '').trim() diff --git a/electron/services/imageDownloadService.ts b/electron/services/imageDownloadService.ts index e69de29..3d978f6 100644 --- a/electron/services/imageDownloadService.ts +++ b/electron/services/imageDownloadService.ts @@ -0,0 +1,174 @@ +import { app } from 'electron' +import { join } from 'path' +import { existsSync } from 'fs' +import { execFile } from 'child_process' +import { promisify } from 'util' +// import { ConfigService } from './config' + +const execFileAsync = promisify(execFile) + +export class ImageDownloadService { + private static instance: ImageDownloadService + private koffi: any = null + private lib: any = null + private initialized = false + + private initImgHelper: any = null + private uninstallImgHelper: any = null + private getImgHelperError: any = null + + private currentPid: number | null = null + private pollTimer: NodeJS.Timeout | null = null + private isHooked = false + + static getInstance(): ImageDownloadService { + if (!ImageDownloadService.instance) { + ImageDownloadService.instance = new ImageDownloadService() + } + return ImageDownloadService.instance + } + + private constructor() { + } + + private async ensureInitialized(): Promise { + if (this.initialized) return true + if (process.platform !== 'win32' || process.arch !== 'x64') return false + + try { + this.koffi = require('koffi') + const dllPath = this.getDllPath() + if (!existsSync(dllPath)) { + console.error(`[ImageDownloadService] dll not found: ${dllPath}`) + return false + } + + this.lib = this.koffi.load(dllPath) + this.initImgHelper = this.lib.func('bool InitImgHelper(uint32)') + this.uninstallImgHelper = this.lib.func('void UninstallImgHelper()') + this.getImgHelperError = this.lib.func('const char* GetImgHelperError()') + + this.initialized = true + return true + } catch (error) { + console.error('[ImageDownloadService] failed to initialize:', error) + return false + } + } + + private getDllPath(): string { + const isPackaged = app.isPackaged + const candidates: string[] = [] + + if (isPackaged) { + candidates.push(join(process.resourcesPath, 'resources', 'image', 'win32', 'x64', 'img_helper.dll')) + } else { + candidates.push(join(process.cwd(), 'resources', 'image', 'win32', 'x64', 'img_helper.dll')) + } + + for (const path of candidates) { + if (existsSync(path)) return path + } + return candidates[0] + } + + private async findMainWeChatPid(): Promise { + try { + const script = ` + Get-CimInstance Win32_Process -Filter "Name = 'Weixin.exe'" | + Select-Object ProcessId, CommandLine | + ConvertTo-Json -Compress + `; + + const { stdout } = await execFileAsync('powershell', ['-NoProfile', '-Command', script]) + if (!stdout || !stdout.trim()) return null + + let processes = JSON.parse(stdout.trim()) + if (!Array.isArray(processes)) processes = [processes] + + const target = processes + .filter((p: any) => p.CommandLine && p.CommandLine.toLowerCase().includes('weixin.exe')) + .sort((a: any, b: any) => a.CommandLine.length - b.CommandLine.length)[0] + + return target ? target.ProcessId : null; + } catch (e) { + return null + } + } + + async startAutoDownload() { + if (!await this.ensureInitialized()) return + + if (this.pollTimer) return + + this.pollTimer = setInterval(() => this.checkAndHook(), 30000) + // Initial check + await this.checkAndHook() + } + + async stopAutoDownload() { + if (this.pollTimer) { + clearInterval(this.pollTimer) + this.pollTimer = null + } + await this.unhook() + } + + private async checkAndHook() { + const pid = await this.findMainWeChatPid() + + if (!pid) { + if (this.isHooked) { + console.log('[ImageDownloadService] WeChat exited, unhooking') + await this.unhook() + } + return + } + + if (this.isHooked && this.currentPid === pid) { + return + } + + if (this.isHooked && this.currentPid !== pid) { + console.log('[ImageDownloadService] WeChat PID changed, re-hooking') + await this.unhook() + } + + console.log(`[ImageDownloadService] attempting to hook PID: ${pid}`) + try { + const success = this.initImgHelper(pid) + if (success) { + this.isHooked = true + this.currentPid = pid + console.log('[ImageDownloadService] hook successful') + } else { + const err = this.getImgHelperError() + console.error(`[ImageDownloadService] hook failed: ${err}`) + } + } catch (e) { + console.error('[ImageDownloadService] InitImgHelper call crashed:', e) + } + } + + private async unhook() { + if (this.isHooked && this.uninstallImgHelper) { + try { + this.uninstallImgHelper() + } catch (e) { + console.error('[ImageDownloadService] uninstall failed:', e) + } + } + this.isHooked = false + this.currentPid = null + } + + async getStatus() { + return { + isHooked: this.isHooked, + pid: this.currentPid, + supported: process.platform === 'win32' && process.arch === 'x64' + } + } +} + +export const imageDownloadService = ImageDownloadService.getInstance() diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx index 0d508ed..a5bf75b 100644 --- a/src/pages/SettingsPage.tsx +++ b/src/pages/SettingsPage.tsx @@ -32,6 +32,7 @@ type SettingsTab = | 'aiCommon' | 'insight' | 'aiFootprint' + | 'autoDownload' const tabs: { id: Exclude; label: string; icon: React.ElementType }[] = [ { id: 'appearance', label: '外观', icon: Palette }, @@ -39,6 +40,7 @@ const tabs: { id: Exclude; label: string { id: 'antiRevoke', label: '防撤回', icon: RotateCcw }, { id: 'database', label: '数据库连接', icon: Database }, { id: 'models', label: '模型管理', icon: Mic }, + { id: 'autoDownload', label: '自动下载', icon: Download }, { id: 'cache', label: '缓存', icon: HardDrive }, { id: 'api', label: 'API 服务', icon: Globe }, { id: 'analytics', label: '分析', icon: BarChart2 }, @@ -47,6 +49,13 @@ const tabs: { id: Exclude; label: string { id: 'about', label: '关于', icon: Info } ] +const filteredTabs = tabs.filter(tab => { + if (tab.id === 'autoDownload') { + return (window as any).electronAPI.process.platform === 'win32' && (window as any).electronAPI.process.arch === 'x64' + } + return true +}) + const aiTabs: Array<{ id: Extract; label: string }> = [ { id: 'aiCommon', label: '基础配置' }, { id: 'insight', label: 'AI 见解' }, @@ -149,6 +158,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { const [imageKeyPercent, setImageKeyPercent] = useState(null) const [logEnabled, setLogEnabled] = useState(false) + const [autoDownloadHighRes, setAutoDownloadHighRes] = useState(false) const [whisperModelName, setWhisperModelName] = useState('base') const [whisperModelDir, setWhisperModelDir] = useState('') const [isWhisperDownloading, setIsWhisperDownloading] = useState(false) @@ -529,8 +539,10 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { setWordCloudExcludeWords(savedExcludeWords) setExcludeWordsInput(savedExcludeWords.join('\n')) + const savedAutoDownloadHighRes = await configService.getAutoDownloadHighRes() const savedAnalyticsConsent = await configService.getAnalyticsConsent() setAnalyticsConsent(savedAnalyticsConsent ?? false) + setAutoDownloadHighRes(savedAutoDownloadHighRes) @@ -4658,6 +4670,44 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { ) + const renderAutoDownloadTab = () => ( +
+
+ + + 开启后,WeFlow 会通过远程 Hook 技术强制微信在接收图片时下载高清原图(而非默认的缩略图)。 +
+ 风险提示:Hook 涉及修改微信进程内存,虽不注入 DLL 但仍有被检测风险,请谨慎开启。 +
+
+ {autoDownloadHighRes ? '已开启' : '已关闭'} + +
+
+
+ ) + + const handleToggleAutoDownload = async () => { + const newVal = !autoDownloadHighRes + setAutoDownloadHighRes(newVal) + await configService.setAutoDownloadHighRes(newVal) + if (newVal) { + await (window as any).electronAPI.image.startAutoDownload() + } else { + await (window as any).electronAPI.image.stopAutoDownload() + } + showMessage(newVal ? '自动下载已开启' : '自动下载已关闭', true) + } + const renderUpdatesTab = () => { const downloadPercent = Math.max(0, Math.min(100, Number(downloadProgress?.percent || 0))) const channelCards: { id: configService.UpdateChannel; title: string; desc: string }[] = [ @@ -4792,7 +4842,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
- {tabs.flatMap((tab) => { + {filteredTabs.flatMap((tab) => { const row: React.ReactNode[] = [ + +
+
+ + + {autoDownloadHighRes ? '已开启' : '已关闭'} + +
+
+ + +
+
+ 已选 {selectedCount} 个目标会话 + (若不选则默认对所有聊天生效) +
+
-
- {!autoDownloadHighRes ? ( -
服务未启动
- ) : !autoDownloadStatus ? ( -
正在检测状态...
- ) : !autoDownloadStatus.supported ? ( -
⚠️ 当前系统架构不支持此功能(仅支持 Win32 x64)
- ) : autoDownloadStatus.isHooked ? ( -
- ✓ 运行中 - 已成功挂载到微信进程 (PID: {autoDownloadStatus.pid}) -
+ +
+
+ 会话({filteredSessions.length}) + 选择 +
+ {filteredSessions.length === 0 ? ( +
{autoDownloadSearchKeyword ? '没有匹配的会话' : '暂无会话'}
) : ( -
- ⏳ 等待中 - 未检测到微信主进程 (Weixin.exe) 运行,请启动微信 -
+ filteredSessions.map((session) => ( +
toggleSelection(session.username)} + > +
+ +
+ {session.displayName || session.username} + {session.username} +
+
+
+ + + +
+
+ )) )}
-
-
-
- -

风险提示

-
-
-
-
- - 此功能涉及hook修改微信进程内存 -
-
- - 虽然当前方案不直接注入 DLL,但仍存在被微信安全机制检测的风险 -
-
- - 建议先少量测试使用,确认有无被检测的风险 -
+ {/* 风险提示 */} +
+
+ +

风险警告

+
+
+ 此功能通过内存 Hook 修改微信行为,具有一定的风险。请尽量仅在白名单模式下针对必要会话开启。
-
- ) - const handleToggleAutoDownload = async () => { + ) + } + const handleToggleAutoDownload = async (whitelist?: string[] | string) => { const newVal = !autoDownloadHighRes setAutoDownloadHighRes(newVal) try { if (newVal) { - const result = await (window as any).electronAPI.image.startAutoDownload() + let currentWhitelist: string[] | string = whitelist || Array.from(autoDownloadSelectedIds) + if (Array.isArray(currentWhitelist)) { + currentWhitelist = currentWhitelist.length > 0 ? (currentWhitelist.join('\0') + '\0\0') : '' + } + const result = await (window as any).electronAPI.image.startAutoDownload(currentWhitelist) if (result && !result.success) { // 如果底层明确返回了失败 throw new Error(result.error || '启动自动下载服务失败') diff --git a/src/services/config.ts b/src/services/config.ts index c06c6f4..0d6588e 100644 --- a/src/services/config.ts +++ b/src/services/config.ts @@ -120,7 +120,8 @@ export const CONFIG_KEYS = { AI_FOOTPRINT_ENABLED: 'aiFootprintEnabled', AI_FOOTPRINT_SYSTEM_PROMPT: 'aiFootprintSystemPrompt', AI_INSIGHT_DEBUG_LOG_ENABLED: 'aiInsightDebugLogEnabled', - AUTO_DOWNLOAD_HIGH_RES: 'autoDownloadHighRes' + AUTO_DOWNLOAD_HIGH_RES: 'autoDownloadHighRes', + AUTO_DOWNLOAD_WHITELIST: 'autoDownloadWhitelist' } as const export interface WxidConfig { @@ -2157,3 +2158,13 @@ export async function setAutoDownloadHighRes(enabled: boolean): Promise { await config.set(CONFIG_KEYS.AUTO_DOWNLOAD_HIGH_RES, enabled) } +export async function getAutoDownloadWhitelist(): Promise { + const value = await config.get(CONFIG_KEYS.AUTO_DOWNLOAD_WHITELIST) + return Array.isArray(value) ? value : [] +} + +export async function setAutoDownloadWhitelist(list: string[]): Promise { + const normalized = Array.from(new Set((list || []).map(item => String(item || '').trim()).filter(Boolean))) + await config.set(CONFIG_KEYS.AUTO_DOWNLOAD_WHITELIST, normalized) +} + From bdf285062fb5ae5798ac75517a3e4296a78bb363 Mon Sep 17 00:00:00 2001 From: H3CoF6 <1707889225@qq.com> Date: Wed, 29 Apr 2026 08:25:45 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E4=BC=9A=E8=AF=9D=E9=80=89=E6=8B=A9=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/SettingsPage.tsx | 79 ++++++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx index 09bca0a..fb53979 100644 --- a/src/pages/SettingsPage.tsx +++ b/src/pages/SettingsPage.tsx @@ -1055,11 +1055,11 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { } useEffect(() => { - if (activeTab !== 'antiRevoke' && activeTab !== 'insight') return + if (activeTab !== 'antiRevoke' && activeTab !== 'insight' && activeTab !== 'autoDownload') return let canceled = false ;(async () => { try { - if (activeTab === 'antiRevoke') { + if (activeTab === 'antiRevoke' || activeTab === 'autoDownload') { await ensureAntiRevokeSessionsLoaded() } else { await ensureChatSessionsLoaded() @@ -4701,12 +4701,13 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { ) const renderAutoDownloadTab = () => { - const sortedSessions = [...chatSessions].sort((a, b) => (b.sortTimestamp || 0) - (a.sortTimestamp || 0)) + const sortedSessions = [...antiRevokeSessions].sort((a, b) => (b.sortTimestamp || 0) - (a.sortTimestamp || 0)) const keyword = autoDownloadSearchKeyword.trim().toLowerCase() const filteredSessions = sortedSessions.filter((session) => { if (!keyword) return true - return (session.displayName || '').toLowerCase().includes(keyword) || - session.username.toLowerCase().includes(keyword) + const displayName = String(session.displayName || '').toLowerCase() + const username = String(session.username || '').toLowerCase() + return displayName.includes(keyword) || username.includes(keyword) }) const filteredSessionIds = filteredSessions.map((session) => session.username) const selectedCount = autoDownloadSelectedIds.size @@ -4718,7 +4719,6 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { const whitelistArr = Array.from(ids) configService.setAutoDownloadWhitelist(whitelistArr) if (autoDownloadHighRes) { - // 转换为 wxid\0wxid\0wxid\0\0 格式 const whitelistStr = whitelistArr.length > 0 ? (whitelistArr.join('\0') + '\0\0') : ''; (window as any).electronAPI.image.startAutoDownload(whitelistStr) } @@ -4747,10 +4747,10 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { return (
- {/* 顶部 Hero 区域 */} + {/* 顶部 Hero 区域保持不变 */}
- 测试功能 (Beta) + 测试功能 (Test)

自动下载原图

强制微信在接收图片时下载高清原图。建议仅在必要会话中开启以节省流量和空间。

@@ -4758,8 +4758,8 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
服务状态 - {isHooked ? '正在监控' : autoDownloadHighRes ? '等待连接' : '未启用'} - + {isHooked ? '正在监控' : autoDownloadHighRes ? '等待连接' : '未启用'} +
已选会话 @@ -4782,14 +4782,14 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
-
@@ -4815,35 +4815,44 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
会话({filteredSessions.length}) - 选择 + 状态
{filteredSessions.length === 0 ? (
{autoDownloadSearchKeyword ? '没有匹配的会话' : '暂无会话'}
) : ( - filteredSessions.map((session) => ( -
toggleSelection(session.username)} - > -
- -
- {session.displayName || session.username} - {session.username} + filteredSessions.map((session) => { + const isSelected = autoDownloadSelectedIds.has(session.username) + return ( +
+ +
+ +