mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-05-01 07:26:48 +00:00
feat: 选择会话的前端界面
This commit is contained in:
@@ -3956,8 +3956,8 @@ function registerIpcHandlers() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 自动下载原图
|
// 自动下载原图
|
||||||
ipcMain.handle('image:startAutoDownload', async () => {
|
ipcMain.handle('image:startAutoDownload', async (_, whitelist?: string[]) => {
|
||||||
return await imageDownloadService.startAutoDownload()
|
return await imageDownloadService.startAutoDownload(whitelist || [])
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('image:stopAutoDownload', async () => {
|
ipcMain.handle('image:stopAutoDownload', async () => {
|
||||||
@@ -4096,7 +4096,11 @@ app.whenReady().then(async () => {
|
|||||||
updateSplashProgress(28, '正在初始化...')
|
updateSplashProgress(28, '正在初始化...')
|
||||||
registerIpcHandlers()
|
registerIpcHandlers()
|
||||||
if (configService.get('autoDownloadHighRes')) {
|
if (configService.get('autoDownloadHighRes')) {
|
||||||
imageDownloadService.startAutoDownload()
|
const whitelistArr = configService.get('autoDownloadWhitelist') || []
|
||||||
|
const whitelistStr = (Array.isArray(whitelistArr) && whitelistArr.length > 0)
|
||||||
|
? (whitelistArr.join('\0') + '\0\0')
|
||||||
|
: ''
|
||||||
|
imageDownloadService.startAutoDownload(whitelistStr)
|
||||||
}
|
}
|
||||||
chatService.addDbMonitorListener((type, json) => {
|
chatService.addDbMonitorListener((type, json) => {
|
||||||
messagePushService.handleDbMonitorChange(type, json)
|
messagePushService.handleDbMonitorChange(type, json)
|
||||||
|
|||||||
@@ -366,7 +366,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
ipcRenderer.on('image:decryptProgress', listener)
|
ipcRenderer.on('image:decryptProgress', listener)
|
||||||
return () => ipcRenderer.removeListener('image:decryptProgress', listener)
|
return () => ipcRenderer.removeListener('image:decryptProgress', listener)
|
||||||
},
|
},
|
||||||
startAutoDownload: () => ipcRenderer.invoke('image:startAutoDownload'),
|
startAutoDownload: (whitelist: string[] | string) => ipcRenderer.invoke('image:startAutoDownload', whitelist),
|
||||||
stopAutoDownload: () => ipcRenderer.invoke('image:stopAutoDownload'),
|
stopAutoDownload: () => ipcRenderer.invoke('image:stopAutoDownload'),
|
||||||
getAutoDownloadStatus: () => ipcRenderer.invoke('image:getAutoDownloadStatus')
|
getAutoDownloadStatus: () => ipcRenderer.invoke('image:getAutoDownloadStatus')
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ interface ConfigSchema {
|
|||||||
/** 是否将 AI 见解调试日志输出到桌面 */
|
/** 是否将 AI 见解调试日志输出到桌面 */
|
||||||
aiInsightDebugLogEnabled: boolean
|
aiInsightDebugLogEnabled: boolean
|
||||||
autoDownloadHighRes: boolean
|
autoDownloadHighRes: boolean
|
||||||
|
autoDownloadWhitelist: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ConfigStoreLike<T extends Record<string, any>> {
|
interface ConfigStoreLike<T extends Record<string, any>> {
|
||||||
@@ -296,7 +297,8 @@ export class ConfigService {
|
|||||||
aiFootprintEnabled: false,
|
aiFootprintEnabled: false,
|
||||||
aiFootprintSystemPrompt: '',
|
aiFootprintSystemPrompt: '',
|
||||||
aiInsightDebugLogEnabled: false,
|
aiInsightDebugLogEnabled: false,
|
||||||
autoDownloadHighRes: false
|
autoDownloadHighRes: false,
|
||||||
|
autoDownloadWhitelist: []
|
||||||
}
|
}
|
||||||
|
|
||||||
const cwd = String(process.env.WEFLOW_CONFIG_CWD || process.env.WEFLOW_USER_DATA_PATH || '').trim()
|
const cwd = String(process.env.WEFLOW_CONFIG_CWD || process.env.WEFLOW_USER_DATA_PATH || '').trim()
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ export class ImageDownloadService {
|
|||||||
private pollTimer: NodeJS.Timeout | null = null
|
private pollTimer: NodeJS.Timeout | null = null
|
||||||
private isHooked = false
|
private isHooked = false
|
||||||
|
|
||||||
|
private lastWhitelist: string[] = []
|
||||||
|
|
||||||
static getInstance(): ImageDownloadService {
|
static getInstance(): ImageDownloadService {
|
||||||
if (!ImageDownloadService.instance) {
|
if (!ImageDownloadService.instance) {
|
||||||
ImageDownloadService.instance = new ImageDownloadService()
|
ImageDownloadService.instance = new ImageDownloadService()
|
||||||
@@ -38,13 +40,11 @@ export class ImageDownloadService {
|
|||||||
try {
|
try {
|
||||||
this.koffi = require('koffi')
|
this.koffi = require('koffi')
|
||||||
const dllPath = this.getDllPath()
|
const dllPath = this.getDllPath()
|
||||||
if (!existsSync(dllPath)) {
|
if (!existsSync(dllPath)) return false
|
||||||
console.error(`[ImageDownloadService] dll not found: ${dllPath}`)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
this.lib = this.koffi.load(dllPath)
|
this.lib = this.koffi.load(dllPath)
|
||||||
this.initImgHelper = this.lib.func('bool InitImgHelper(uint32)')
|
|
||||||
|
this.initImgHelper = this.lib.func('bool InitImgHelper(uint32, const char*)')
|
||||||
this.uninstallImgHelper = this.lib.func('void UninstallImgHelper()')
|
this.uninstallImgHelper = this.lib.func('void UninstallImgHelper()')
|
||||||
this.getImgHelperError = this.lib.func('const char* GetImgHelperError()')
|
this.getImgHelperError = this.lib.func('const char* GetImgHelperError()')
|
||||||
|
|
||||||
@@ -96,16 +96,22 @@ export class ImageDownloadService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async startAutoDownload(): Promise<{ success: boolean; error?: string }> {
|
async startAutoDownload(whitelist: string[] | string = []): Promise<{ success: boolean; error?: string }> {
|
||||||
if (!await this.ensureInitialized()) {
|
if (!await this.ensureInitialized()) {
|
||||||
return { success: false, error: '核心组件初始化失败,请检查环境' }
|
return { success: false, error: '核心组件初始化失败' }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.pollTimer) return { success: true }
|
if (this.isHooked) {
|
||||||
|
await this.unhook()
|
||||||
|
}
|
||||||
|
|
||||||
this.pollTimer = setInterval(() => this.checkAndHook(), 30000)
|
this.lastWhitelist = whitelist
|
||||||
// 首次尝试 Hook,并返回结果
|
|
||||||
return await this.checkAndHook(true)
|
if (!this.pollTimer) {
|
||||||
|
this.pollTimer = setInterval(() => this.checkAndHook(this.lastWhitelist, false), 30000)
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.checkAndHook(whitelist, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
async stopAutoDownload() {
|
async stopAutoDownload() {
|
||||||
@@ -116,7 +122,7 @@ export class ImageDownloadService {
|
|||||||
await this.unhook()
|
await this.unhook()
|
||||||
}
|
}
|
||||||
|
|
||||||
private async checkAndHook(isManualStart = false): Promise<{ success: boolean; error?: string }> {
|
private async checkAndHook(whitelist: string[] | string = [], isManualStart = false): Promise<{ success: boolean; error?: string }> {
|
||||||
const pid = await this.findMainWeChatPid()
|
const pid = await this.findMainWeChatPid()
|
||||||
|
|
||||||
if (!pid) {
|
if (!pid) {
|
||||||
@@ -124,7 +130,6 @@ export class ImageDownloadService {
|
|||||||
console.log('[ImageDownloadService] WeChat exited, unhooking')
|
console.log('[ImageDownloadService] WeChat exited, unhooking')
|
||||||
await this.unhook()
|
await this.unhook()
|
||||||
}
|
}
|
||||||
// 如果是手动开启时没找到进程,不认为是严重错误,只是挂起等待
|
|
||||||
return { success: true, error: '等待微信启动' }
|
return { success: true, error: '等待微信启动' }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +144,17 @@ export class ImageDownloadService {
|
|||||||
|
|
||||||
console.log(`[ImageDownloadService] attempting to hook PID: ${pid}`)
|
console.log(`[ImageDownloadService] attempting to hook PID: ${pid}`)
|
||||||
try {
|
try {
|
||||||
const success = this.initImgHelper(pid)
|
let whitelistBuffer: Buffer | null = null;
|
||||||
|
if (typeof whitelist === 'string') {
|
||||||
|
if (whitelist.length > 0) {
|
||||||
|
whitelistBuffer = Buffer.from(whitelist, 'utf8');
|
||||||
|
}
|
||||||
|
} else if (Array.isArray(whitelist) && whitelist.length > 0) {
|
||||||
|
whitelistBuffer = Buffer.from(whitelist.join('\0') + '\0\0', 'utf8');
|
||||||
|
}
|
||||||
|
|
||||||
|
const success = this.initImgHelper(pid, whitelistBuffer)
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
this.isHooked = true
|
this.isHooked = true
|
||||||
this.currentPid = pid
|
this.currentPid = pid
|
||||||
@@ -148,7 +163,6 @@ export class ImageDownloadService {
|
|||||||
} else {
|
} else {
|
||||||
const err = this.getImgHelperError()
|
const err = this.getImgHelperError()
|
||||||
console.error(`[ImageDownloadService] hook failed: ${err}`)
|
console.error(`[ImageDownloadService] hook failed: ${err}`)
|
||||||
// 如果是手动点击开启时失败,停止轮询并向上报错
|
|
||||||
if (isManualStart && this.pollTimer) {
|
if (isManualStart && this.pollTimer) {
|
||||||
clearInterval(this.pollTimer)
|
clearInterval(this.pollTimer)
|
||||||
this.pollTimer = null
|
this.pollTimer = null
|
||||||
|
|||||||
@@ -327,7 +327,11 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
const [aiFootprintEnabled, setAiFootprintEnabled] = useState(false)
|
const [aiFootprintEnabled, setAiFootprintEnabled] = useState(false)
|
||||||
const [aiFootprintSystemPrompt, setAiFootprintSystemPrompt] = useState('')
|
const [aiFootprintSystemPrompt, setAiFootprintSystemPrompt] = useState('')
|
||||||
const [aiInsightDebugLogEnabled, setAiInsightDebugLogEnabled] = useState(false)
|
const [aiInsightDebugLogEnabled, setAiInsightDebugLogEnabled] = useState(false)
|
||||||
|
|
||||||
|
// 自动下载图片
|
||||||
const [autoDownloadStatus, setAutoDownloadStatus] = useState<{ isHooked: boolean; pid: number | null; supported: boolean } | null>(null)
|
const [autoDownloadStatus, setAutoDownloadStatus] = useState<{ isHooked: boolean; pid: number | null; supported: boolean } | null>(null)
|
||||||
|
const [autoDownloadSelectedIds, setAutoDownloadSelectedIds] = useState<Set<string>>(new Set())
|
||||||
|
const [autoDownloadSearchKeyword, setAutoDownloadSearchKeyword] = useState('')
|
||||||
|
|
||||||
// 检查 Hello 可用性
|
// 检查 Hello 可用性
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -541,10 +545,11 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
setExcludeWordsInput(savedExcludeWords.join('\n'))
|
setExcludeWordsInput(savedExcludeWords.join('\n'))
|
||||||
|
|
||||||
const savedAutoDownloadHighRes = await configService.getAutoDownloadHighRes()
|
const savedAutoDownloadHighRes = await configService.getAutoDownloadHighRes()
|
||||||
|
const savedAutoDownloadWhitelist = await configService.getAutoDownloadWhitelist()
|
||||||
const savedAnalyticsConsent = await configService.getAnalyticsConsent()
|
const savedAnalyticsConsent = await configService.getAnalyticsConsent()
|
||||||
setAnalyticsConsent(savedAnalyticsConsent ?? false)
|
setAnalyticsConsent(savedAnalyticsConsent ?? false)
|
||||||
setAutoDownloadHighRes(savedAutoDownloadHighRes)
|
setAutoDownloadHighRes(savedAutoDownloadHighRes)
|
||||||
|
setAutoDownloadSelectedIds(new Set(savedAutoDownloadWhitelist))
|
||||||
|
|
||||||
|
|
||||||
// 如果语言列表为空,保存默认值
|
// 如果语言列表为空,保存默认值
|
||||||
@@ -4695,96 +4700,173 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
const renderAutoDownloadTab = () => (
|
const renderAutoDownloadTab = () => {
|
||||||
<div className="tab-content">
|
const sortedSessions = [...chatSessions].sort((a, b) => (b.sortTimestamp || 0) - (a.sortTimestamp || 0))
|
||||||
<div className="updates-hero" style={{ background: 'linear-gradient(110deg, var(--bg-primary) 0%, rgba(245, 158, 11, 0.1) 100%)', borderColor: 'rgba(245, 158, 11, 0.3)' }}>
|
const keyword = autoDownloadSearchKeyword.trim().toLowerCase()
|
||||||
<div className="updates-hero-main">
|
const filteredSessions = sortedSessions.filter((session) => {
|
||||||
<span className="updates-chip" style={{ color: '#f59e0b', background: 'rgba(245, 158, 11, 0.15)' }}>实验性功能</span>
|
if (!keyword) return true
|
||||||
<h2>自动下载原图</h2>
|
return (session.displayName || '').toLowerCase().includes(keyword) ||
|
||||||
<p>强制微信在接收图片时下载高清原图,而非默认的模糊缩略图。</p>
|
session.username.toLowerCase().includes(keyword)
|
||||||
</div>
|
})
|
||||||
</div>
|
const filteredSessionIds = filteredSessions.map((session) => session.username)
|
||||||
|
const selectedCount = autoDownloadSelectedIds.size
|
||||||
|
const selectedInFilteredCount = filteredSessionIds.filter((id) => autoDownloadSelectedIds.has(id)).length
|
||||||
|
const allFilteredSelected = filteredSessionIds.length > 0 && selectedInFilteredCount === filteredSessionIds.length
|
||||||
|
const isHooked = autoDownloadStatus?.isHooked
|
||||||
|
|
||||||
<div className="form-group" style={{ marginTop: '24px' }}>
|
const persistWhitelist = (ids: Set<string>) => {
|
||||||
<div className="setting-control vertical has-border">
|
const whitelistArr = Array.from(ids)
|
||||||
<div className="log-toggle-line" style={{ marginTop: 0, border: 'none', background: 'transparent', padding: 0 }}>
|
configService.setAutoDownloadWhitelist(whitelistArr)
|
||||||
<div>
|
if (autoDownloadHighRes) {
|
||||||
<span className="log-status" style={{ fontSize: '15px', fontWeight: 600 }}>启用自动下载</span>
|
// 转换为 wxid\0wxid\0wxid\0\0 格式
|
||||||
<div style={{ marginTop: '4px', fontSize: '13px', color: 'var(--text-tertiary)' }}>
|
const whitelistStr = whitelistArr.length > 0 ? (whitelistArr.join('\0') + '\0\0') : '';
|
||||||
开启后,WeFlow 将通过远程 Hook 技术干预微信进程。
|
(window as any).electronAPI.image.startAutoDownload(whitelistStr)
|
||||||
</div>
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleSelection = (id: string) => {
|
||||||
|
const next = new Set(autoDownloadSelectedIds)
|
||||||
|
if (next.has(id)) next.delete(id)
|
||||||
|
else next.add(id)
|
||||||
|
setAutoDownloadSelectedIds(next)
|
||||||
|
persistWhitelist(next)
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectAllFiltered = () => {
|
||||||
|
const next = new Set(autoDownloadSelectedIds)
|
||||||
|
filteredSessionIds.forEach(id => next.add(id))
|
||||||
|
setAutoDownloadSelectedIds(next)
|
||||||
|
persistWhitelist(next)
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearSelection = () => {
|
||||||
|
const next = new Set<string>()
|
||||||
|
setAutoDownloadSelectedIds(next)
|
||||||
|
persistWhitelist(next)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="tab-content anti-revoke-tab">
|
||||||
|
{/* 顶部 Hero 区域 */}
|
||||||
|
<div className="anti-revoke-hero" style={{ background: 'linear-gradient(110deg, var(--bg-primary) 0%, rgba(245, 158, 11, 0.1) 100%)', borderColor: 'rgba(245, 158, 11, 0.3)' }}>
|
||||||
|
<div className="anti-revoke-hero-main">
|
||||||
|
<span className="updates-chip" style={{ color: '#f59e0b', background: 'rgba(245, 158, 11, 0.15)', width: 'fit-content' }}>测试功能 (Beta)</span>
|
||||||
|
<h2 style={{ marginTop: '8px' }}>自动下载原图</h2>
|
||||||
|
<p>强制微信在接收图片时下载高清原图。建议仅在必要会话中开启以节省流量和空间。</p>
|
||||||
|
</div>
|
||||||
|
<div className="anti-revoke-metrics">
|
||||||
|
<div className={`anti-revoke-metric ${isHooked ? 'is-installed' : 'is-pending'}`}>
|
||||||
|
<span className="label">服务状态</span>
|
||||||
|
<span className="value" style={{ fontSize: '14px' }}>
|
||||||
|
{isHooked ? '正在监控' : autoDownloadHighRes ? '等待连接' : '未启用'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="anti-revoke-metric">
|
||||||
|
<span className="label">已选会话</span>
|
||||||
|
<span className="value">{selectedCount}</span>
|
||||||
</div>
|
</div>
|
||||||
<label className="switch switch-lg" htmlFor="auto-download-high-res-toggle">
|
|
||||||
<input
|
|
||||||
id="auto-download-high-res-toggle"
|
|
||||||
className="switch-input"
|
|
||||||
type="checkbox"
|
|
||||||
checked={autoDownloadHighRes}
|
|
||||||
onChange={handleToggleAutoDownload}
|
|
||||||
/>
|
|
||||||
<span className="switch-slider" />
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="api-warning-modal" style={{ width: '100%', animation: 'none', boxShadow: 'none', border: '1px solid var(--border-color)', marginTop: '20px' }}>
|
<div className="anti-revoke-control-card">
|
||||||
<div className="modal-header" style={{ padding: '16px 20px', background: 'var(--bg-tertiary)' }}>
|
<div className="anti-revoke-toolbar">
|
||||||
<ShieldCheck size={18} />
|
<div className="filter-search-box anti-revoke-search">
|
||||||
<h3 style={{ fontSize: '14px' }}>运行状态</h3>
|
<Search size={14} />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="搜索联系人或群聊..."
|
||||||
|
value={autoDownloadSearchKeyword}
|
||||||
|
onChange={(e) => setAutoDownloadSearchKeyword(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="anti-revoke-toolbar-actions">
|
||||||
|
<div className="anti-revoke-btn-group">
|
||||||
|
<button className="btn btn-secondary btn-sm" onClick={selectAllFiltered} disabled={filteredSessionIds.length === 0 || allFilteredSelected}>
|
||||||
|
全选过滤
|
||||||
|
</button>
|
||||||
|
<button className="btn btn-secondary btn-sm" onClick={clearSelection} disabled={selectedCount === 0}>
|
||||||
|
清空选择
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="anti-revoke-btn-group" style={{ marginLeft: '12px', paddingLeft: '12px', borderLeft: '1px solid var(--border-color)' }}>
|
||||||
|
<label className="switch switch-md" title={autoDownloadHighRes ? '关闭自动下载' : '开启自动下载'}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={autoDownloadHighRes}
|
||||||
|
onChange={() => handleToggleAutoDownload(Array.from(autoDownloadSelectedIds))}
|
||||||
|
/>
|
||||||
|
<span className="switch-slider" />
|
||||||
|
</label>
|
||||||
|
<span style={{ fontSize: '12px', color: 'var(--text-secondary)', marginLeft: '8px' }}>
|
||||||
|
{autoDownloadHighRes ? '已开启' : '已关闭'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="anti-revoke-batch-actions">
|
||||||
|
<div className="anti-revoke-selected-count">
|
||||||
|
<span>已选 <strong>{selectedCount}</strong> 个目标会话</span>
|
||||||
|
<span style={{ opacity: 0.6 }}>(若不选则默认对所有聊天生效)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="modal-body" style={{ padding: '16px 20px' }}>
|
|
||||||
{!autoDownloadHighRes ? (
|
<div className="anti-revoke-list">
|
||||||
<div className="warning-item"><span style={{ color: 'var(--text-tertiary)' }}>服务未启动</span></div>
|
<div className="anti-revoke-list-header">
|
||||||
) : !autoDownloadStatus ? (
|
<span>会话({filteredSessions.length})</span>
|
||||||
<div className="warning-item"><span>正在检测状态...</span></div>
|
<span>选择</span>
|
||||||
) : !autoDownloadStatus.supported ? (
|
</div>
|
||||||
<div className="warning-item"><span style={{ color: 'var(--danger)' }}>⚠️ 当前系统架构不支持此功能(仅支持 Win32 x64)</span></div>
|
{filteredSessions.length === 0 ? (
|
||||||
) : autoDownloadStatus.isHooked ? (
|
<div className="anti-revoke-empty">{autoDownloadSearchKeyword ? '没有匹配的会话' : '暂无会话'}</div>
|
||||||
<div className="warning-item">
|
|
||||||
<span style={{ color: '#10b981', fontWeight: 'bold' }}>✓ 运行中</span>
|
|
||||||
<span style={{ marginLeft: '12px', color: 'var(--text-secondary)' }}>已成功挂载到微信进程 (PID: {autoDownloadStatus.pid})</span>
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<div className="warning-item">
|
filteredSessions.map((session) => (
|
||||||
<span style={{ color: '#f59e0b', fontWeight: 'bold' }}>⏳ 等待中</span>
|
<div
|
||||||
<span style={{ marginLeft: '12px', color: 'var(--text-secondary)' }}>未检测到微信主进程 (Weixin.exe) 运行,请启动微信</span>
|
key={session.username}
|
||||||
</div>
|
className={`anti-revoke-row ${autoDownloadSelectedIds.has(session.username) ? 'selected' : ''}`}
|
||||||
|
onClick={() => toggleSelection(session.username)}
|
||||||
|
>
|
||||||
|
<div className="anti-revoke-row-main">
|
||||||
|
<Avatar src={session.avatarUrl} name={session.displayName} size={30} />
|
||||||
|
<div className="anti-revoke-row-text">
|
||||||
|
<span className="name">{session.displayName || session.username}</span>
|
||||||
|
<span className="username" style={{ fontSize: '11px', opacity: 0.5 }}>{session.username}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="anti-revoke-row-status">
|
||||||
|
<span className={`anti-revoke-check ${autoDownloadSelectedIds.has(session.username) ? 'checked' : ''}`}>
|
||||||
|
<Check size={14} />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="api-warning-modal" style={{ width: '100%', animation: 'none', boxShadow: 'none', border: '1px solid rgba(239, 68, 68, 0.3)', marginTop: '20px' }}>
|
{/* 风险提示 */}
|
||||||
<div className="modal-header" style={{ padding: '16px 20px', background: 'rgba(239, 68, 68, 0.05)', borderBottomColor: 'rgba(239, 68, 68, 0.2)' }}>
|
<div className="api-warning-modal" style={{ width: '100%', border: '1px solid rgba(239, 68, 68, 0.2)', marginTop: '16px', background: 'rgba(239, 68, 68, 0.02)', animation: 'none', boxShadow: 'none', position: 'static' }}>
|
||||||
<ShieldCheck size={18} color="#ef4444" />
|
<div className="modal-header" style={{ border: 'none', padding: '12px 20px 0' }}>
|
||||||
<h3 style={{ fontSize: '14px', color: '#ef4444' }}>风险提示</h3>
|
<Lock size={16} color="#ef4444" />
|
||||||
</div>
|
<h3 style={{ fontSize: '13px', color: '#ef4444' }}>风险警告</h3>
|
||||||
<div className="modal-body" style={{ padding: '16px 20px' }}>
|
</div>
|
||||||
<div className="warning-list">
|
<div className="modal-body" style={{ fontSize: '12px', color: 'var(--text-secondary)', padding: '8px 20px 12px' }}>
|
||||||
<div className="warning-item">
|
此功能通过内存 Hook 修改微信行为,具有一定的风险。请尽量仅在白名单模式下针对必要会话开启。
|
||||||
<span className="bullet" style={{ color: '#ef4444' }}>•</span>
|
|
||||||
<span>此功能涉及hook修改微信进程内存</span>
|
|
||||||
</div>
|
|
||||||
<div className="warning-item">
|
|
||||||
<span className="bullet" style={{ color: '#ef4444' }}>•</span>
|
|
||||||
<span>虽然当前方案不直接注入 DLL,但<strong>仍存在被微信安全机制检测的风险</strong>。</span>
|
|
||||||
</div>
|
|
||||||
<div className="warning-item">
|
|
||||||
<span className="bullet" style={{ color: '#ef4444' }}>•</span>
|
|
||||||
<span>建议先少量测试使用,确认有无被检测的风险</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)
|
||||||
)
|
}
|
||||||
const handleToggleAutoDownload = async () => {
|
const handleToggleAutoDownload = async (whitelist?: string[] | string) => {
|
||||||
const newVal = !autoDownloadHighRes
|
const newVal = !autoDownloadHighRes
|
||||||
setAutoDownloadHighRes(newVal)
|
setAutoDownloadHighRes(newVal)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (newVal) {
|
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) {
|
if (result && !result.success) {
|
||||||
// 如果底层明确返回了失败
|
// 如果底层明确返回了失败
|
||||||
throw new Error(result.error || '启动自动下载服务失败')
|
throw new Error(result.error || '启动自动下载服务失败')
|
||||||
|
|||||||
@@ -120,7 +120,8 @@ export const CONFIG_KEYS = {
|
|||||||
AI_FOOTPRINT_ENABLED: 'aiFootprintEnabled',
|
AI_FOOTPRINT_ENABLED: 'aiFootprintEnabled',
|
||||||
AI_FOOTPRINT_SYSTEM_PROMPT: 'aiFootprintSystemPrompt',
|
AI_FOOTPRINT_SYSTEM_PROMPT: 'aiFootprintSystemPrompt',
|
||||||
AI_INSIGHT_DEBUG_LOG_ENABLED: 'aiInsightDebugLogEnabled',
|
AI_INSIGHT_DEBUG_LOG_ENABLED: 'aiInsightDebugLogEnabled',
|
||||||
AUTO_DOWNLOAD_HIGH_RES: 'autoDownloadHighRes'
|
AUTO_DOWNLOAD_HIGH_RES: 'autoDownloadHighRes',
|
||||||
|
AUTO_DOWNLOAD_WHITELIST: 'autoDownloadWhitelist'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export interface WxidConfig {
|
export interface WxidConfig {
|
||||||
@@ -2157,3 +2158,13 @@ export async function setAutoDownloadHighRes(enabled: boolean): Promise<void> {
|
|||||||
await config.set(CONFIG_KEYS.AUTO_DOWNLOAD_HIGH_RES, enabled)
|
await config.set(CONFIG_KEYS.AUTO_DOWNLOAD_HIGH_RES, enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getAutoDownloadWhitelist(): Promise<string[]> {
|
||||||
|
const value = await config.get(CONFIG_KEYS.AUTO_DOWNLOAD_WHITELIST)
|
||||||
|
return Array.isArray(value) ? value : []
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setAutoDownloadWhitelist(list: string[]): Promise<void> {
|
||||||
|
const normalized = Array.from(new Set((list || []).map(item => String(item || '').trim()).filter(Boolean)))
|
||||||
|
await config.set(CONFIG_KEYS.AUTO_DOWNLOAD_WHITELIST, normalized)
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user