/** * Node.js 环境下的 API 桥接,替代 window.electronAPI */ const path = require('path') const fs = require('fs') const os = require('os') const { spawnSync } = require('child_process') const projectRoot = path.resolve(__dirname, '..', '..') const adbInteractPath = path.join(projectRoot, 'nodejs', 'adb', 'adb-interact.js') const imageMatchScriptPath = path.join(projectRoot, 'python', 'scripts', 'image-match.py') const config = require(path.join(projectRoot, 'configs', 'config.js')) function runAdb(action, args = [], deviceId = '') { const result = spawnSync('node', [adbInteractPath, action, ...args, deviceId], { encoding: 'utf-8', timeout: 10000 }) return { success: result.status === 0, error: result.stderr } } async function sendTap(device, x, y) { const r = runAdb('tap', [String(x), String(y)], device) return r } async function sendSwipe(device, x1, y1, x2, y2, duration) { const r = runAdb('swipe-coords', [String(x1), String(y1), String(x2), String(y2), String(duration || 300)], device) return r } async function sendKeyEvent(device, keyCode) { const r = runAdb('keyevent', [String(keyCode)], device) return r } async function sendText(device, text) { const r = runAdb('text', [String(text)], device) return r } async function matchImageAndGetCoordinate(device, imagePath) { const templatePath = path.isAbsolute(imagePath) ? imagePath : path.resolve(projectRoot, imagePath) if (!fs.existsSync(templatePath)) { return { success: false, error: `模板图片不存在: ${templatePath}` } } const ts = Date.now() const screenshotPath = path.join(os.tmpdir(), `ef-screenshot-${ts}.png`) const templateCopyPath = path.join(os.tmpdir(), `ef-template-${ts}.png`) try { fs.copyFileSync(templatePath, templateCopyPath) } catch (e) { return { success: false, error: `复制模板失败: ${e.message}` } } const venvPython = path.join(projectRoot, 'python', 'env', 'Scripts', 'python.exe') const hasVenv = fs.existsSync(venvPython) const pythonPath = hasVenv ? venvPython : (config.pythonPath?.path ? path.join(config.pythonPath.path, 'python.exe') : 'python') const adbPathRel = config.adbPath?.path || 'lib/scrcpy-adb/adb.exe' const adbPath = path.isAbsolute(adbPathRel) ? adbPathRel : path.join(projectRoot, adbPathRel) const screenshotPathNorm = screenshotPath.replace(/\\/g, '/') const templateCopyPathNorm = templateCopyPath.replace(/\\/g, '/') const matchResult = spawnSync(pythonPath, [imageMatchScriptPath, '--adb', adbPath, '--device', device, '--screenshot', screenshotPathNorm, '--template', templateCopyPathNorm], { encoding: 'utf-8', timeout: 20000, env: { ...process.env, PYTHONIOENCODING: 'utf-8' }, cwd: projectRoot }) try { fs.unlinkSync(screenshotPath) } catch (_) {} try { fs.unlinkSync(templateCopyPath) } catch (_) {} if (matchResult.status !== 0) { const errMsg = (matchResult.stderr || matchResult.stdout || '').trim() return { success: false, error: errMsg || '图像匹配失败' } } let out try { out = JSON.parse(matchResult.stdout.trim()) } catch (e) { return { success: false, error: `解析匹配结果失败: ${matchResult.stdout}` } } if (!out.success) { return { success: false, error: out.error || '未找到匹配' } } return { success: true, coordinate: { x: out.x, y: out.y, width: out.width, height: out.height }, clickPosition: { x: out.center_x, y: out.center_y } } } async function findTextAndGetCoordinate(device, targetText) { return { success: false, error: 'findTextAndGetCoordinate 需在主进程实现' } } async function appendLog(folderPath, message) { const logPath = path.join(folderPath, 'log.txt') fs.appendFileSync(logPath, message + '\n') return Promise.resolve() } function readTextFile(filePath) { const fullPath = path.isAbsolute(filePath) ? filePath : path.resolve(projectRoot, filePath) const content = fs.readFileSync(fullPath, 'utf8') return { success: true, content } } function writeTextFile(filePath, content) { const fullPath = path.isAbsolute(filePath) ? filePath : path.resolve(projectRoot, filePath) const dir = path.dirname(fullPath) if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }) fs.writeFileSync(fullPath, content) return { success: true } } async function stub(name) { return { success: false, error: `${name} 需在主进程实现` } } const nodeApi = { sendTap, sendSwipe, sendKeyEvent, sendText, matchImageAndGetCoordinate, findTextAndGetCoordinate, appendLog, readTextFile, writeTextFile, saveChatHistory: () => stub('saveChatHistory'), readChatHistory: () => stub('readChatHistory'), readAllChatHistory: () => stub('readAllChatHistory'), saveChatHistorySummary: () => stub('saveChatHistorySummary'), getChatHistorySummary: () => stub('getChatHistorySummary'), saveChatHistoryTxt: () => stub('saveChatHistoryTxt'), extractChatHistory: () => stub('extractChatHistory'), readLastMessage: () => stub('readLastMessage'), ocrLastMessage: () => stub('ocrLastMessage'), getCachedScreenshot: () => stub('getCachedScreenshot'), captureScreenshot: () => stub('captureScreenshot'), async readImageFileAsBase64(filePath) { try { const fullPath = path.isAbsolute(filePath) ? filePath : path.resolve(projectRoot, filePath) const buf = fs.readFileSync(fullPath) const data = buf.toString('base64') return { success: true, data } } catch (e) { return { success: false, error: e.message } } }, matchImageRegionLocation: () => stub('matchImageRegionLocation'), cropAndSaveImage: () => stub('cropAndSaveImage'), async saveBase64Image(base64, filePath) { try { const fullPath = path.isAbsolute(filePath) ? filePath : path.resolve(projectRoot, filePath) const dir = path.dirname(fullPath) if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }) const buf = Buffer.from(base64, 'base64') fs.writeFileSync(fullPath, buf) return { success: true } } catch (e) { return { success: false, error: e.message } } } } module.exports = nodeApi