/** * 语句:adb / keyevent / scroll / swipe / locate / click / press / input / ocr 统一入口 * type===adb 时按 method 分发到 adb/*.js;其余 type 在本文件内执行,公共逻辑见 utils.js */ const { calculateSwipeCoordinates } = require('./utils.js') const types = ['adb', 'keyevent', 'scroll', 'swipe', 'locate', 'click', 'press', 'input', 'ocr'] const METHOD_HANDLERS = { input: require('./input.js'), click: require('./click.js'), locate: require('./locate.js'), swipe: require('./swipe.js'), scroll: require('./scroll.js'), keyevent: require('./keyevent.js'), press: require('./press.js'), 'string-press': require('./string-press.js'), 'send-img-to-device': require('./send-img-to-device.js'), } /* ========== 解析入口 ========== */ function parse(action, parseContext) { const type = action.type || 'adb' const { extractVarName, resolveValue } = parseContext const variableContext = parseContext.variableContext || {} const parsed = Object.assign({}, action, { type }) if (type === 'adb') { parsed.method = action.method parsed.inVars = action.inVars && Array.isArray(action.inVars) ? action.inVars.map(v => extractVarName(v)) : [] parsed.outVars = action.outVars && Array.isArray(action.outVars) ? action.outVars.map(v => extractVarName(v)) : [] parsed.target = action.target parsed.value = action.value parsed.variable = action.variable parsed.clear = action.clear || false return parsed } if (type === 'locate' || type === 'click') return parsed if (type === 'input') { parsed.clear = action.clear || false return parsed } if (type === 'ocr') { parsed.area = action.area parsed.avatar = resolveValue(action.avatar, variableContext) return parsed } return parsed } /* ========== 执行入口 ========== */ async function execute(action, ctx) { const { device, folderPath, resolution, variableContext, api, extractVarName, resolveValue, logOutVars, DEFAULT_SCROLL_DISTANCE = 100, } = ctx /* --- type: locate --- */ if (action.type === 'locate') { const method = action.method || 'image' let position = null if (method === 'image') { const imagePath = action.target.startsWith('/') || action.target.includes(':') ? action.target : `${folderPath}/${action.target}` if (!api?.matchImageAndGetCoordinate) return { success: false, error: '图像匹配 API 不可用' } const matchResult = await api.matchImageAndGetCoordinate(device, imagePath, folderPath) if (!matchResult.success) return { success: false, error: `图像匹配失败: ${matchResult.error != null ? matchResult.error : 'unknown'}` } position = matchResult.clickPosition } else if (method === 'text') { if (!api?.findTextAndGetCoordinate) return { success: false, error: '文字识别 API 不可用' } const matchResult = await api.findTextAndGetCoordinate(device, action.target) if (!matchResult.success) return { success: false, error: `文字识别失败: ${matchResult.error != null ? matchResult.error : 'unknown'}` } position = matchResult.clickPosition } else if (method === 'coordinate') { position = Array.isArray(action.target) ? { x: action.target[0], y: action.target[1] } : action.target } if (action.variable && position) variableContext[action.variable] = position return { success: true, result: position } } /* --- type: click --- */ if (action.type === 'click') { const method = action.method || 'position' let position = null if (method === 'position') position = resolveValue(action.target, variableContext) else if (method === 'image') { const imagePath = action.target.startsWith('/') || action.target.includes(':') ? action.target : `${folderPath}/${action.target}` if (!api?.matchImageAndGetCoordinate) return { success: false, error: '图像匹配 API 不可用' } const matchResult = await api.matchImageAndGetCoordinate(device, imagePath, folderPath) if (!matchResult.success) return { success: false, error: `图像匹配失败: ${matchResult.error != null ? matchResult.error : 'unknown'}` } position = matchResult.clickPosition } else if (method === 'text') { if (!api?.findTextAndGetCoordinate) return { success: false, error: '文字识别 API 不可用' } const matchResult = await api.findTextAndGetCoordinate(device, action.target) if (!matchResult.success) return { success: false, error: `文字识别失败: ${matchResult.error != null ? matchResult.error : 'unknown'}` } position = matchResult.clickPosition } if (!position?.x || !position?.y) return { success: false, error: '无法获取点击位置' } if (!api?.sendTap) return { success: false, error: '点击 API 不可用' } const tapResult = await api.sendTap(device, position.x, position.y) if (!tapResult.success) return { success: false, error: `点击失败: ${tapResult.error != null ? tapResult.error : 'unknown'}` } return { success: true } } /* --- type: press --- */ if (action.type === 'press') { const imagePath = `${folderPath}/resources/${action.value}` if (!api?.matchImageAndGetCoordinate) return { success: false, error: '图像匹配 API 不可用' } const matchResult = await api.matchImageAndGetCoordinate(device, imagePath, folderPath) if (!matchResult.success) return { success: false, error: `图像匹配失败: ${matchResult.error != null ? matchResult.error : 'unknown'}` } const { x, y } = matchResult.clickPosition if (!api?.sendTap) return { success: false, error: '点击 API 不可用' } const tapResult = await api.sendTap(device, x, y) if (!tapResult.success) return { success: false, error: `点击失败: ${tapResult.error != null ? tapResult.error : 'unknown'}` } return { success: true } } /* --- type: input --- */ if (action.type === 'input') { let inputValue = resolveValue(action.value, variableContext) if (!inputValue && action.target) { const resolvedTarget = resolveValue(action.target, variableContext) if (resolvedTarget !== action.target || !action.target.includes(' ')) inputValue = resolvedTarget } if (!inputValue) return { success: false, error: '输入内容为空' } if (!api?.sendText) return { success: false, error: '输入 API 不可用' } if (action.clear) { for (let i = 0; i < 200; i++) { const clearResult = await api.sendKeyEvent(device, '67') if (!clearResult.success) break await new Promise((r) => setTimeout(r, 10)) } await new Promise((r) => setTimeout(r, 200)) } const textResult = await api.sendText(device, inputValue) if (!textResult.success) return { success: false, error: `输入失败: ${textResult.error != null ? textResult.error : 'unknown'}` } return { success: true } } /* --- type: ocr --- */ if (action.type === 'ocr') { if (!api?.ocrLastMessage) return { success: false, error: 'OCR API 不可用' } const method = action.method || 'full-screen' let avatarPath = null if (method === 'by-avatar' && action.avatar) { const avatarName = resolveValue(action.avatar, variableContext) if (avatarName) { const folderName = folderPath.split(/[/\\]/).pop() avatarPath = `${folderName}/${avatarName}` } } const ocrResult = await api.ocrLastMessage(device, method, avatarPath, action.area, folderPath) if (!ocrResult.success) return { success: false, error: `OCR识别失败: ${ocrResult.error != null ? ocrResult.error : 'unknown'}` } if (action.variable) variableContext[action.variable] = ocrResult.text || '' return { success: true, text: ocrResult.text, position: ocrResult.position } } /* --- type: keyevent --- */ if (action.type === 'keyevent') { let keyCode = null const inVars = action.inVars || [] if (inVars.length > 0) { const keyVar = extractVarName(inVars[0]) keyCode = variableContext[keyVar] || keyVar } else if (action.value) { keyCode = resolveValue(action.value, variableContext) } if (!keyCode) return { success: false, error: 'keyevent 操作缺少按键代码参数' } if (keyCode === 'KEYCODE_BACK') keyCode = '4' const keyResult = api.sendSystemKey(device, String(keyCode)) if (!keyResult.success) return { success: false, error: `按键失败: ${keyResult.error != null ? keyResult.error : 'unknown'}` } return { success: true } } /* --- type: scroll --- */ if (action.type === 'scroll') { if (!api.sendScroll) return { success: false, error: '滚动 API 不可用' } const direction = action.value const r = await api.sendScroll(device, direction, resolution.width, resolution.height, DEFAULT_SCROLL_DISTANCE, 500) if (!r.success) return { success: false, error: `滚动失败: ${r.error != null ? r.error : 'unknown'}` } return { success: true } } /* --- type: swipe --- */ if (action.type === 'swipe') { if (!api.sendSwipe) return { success: false, error: '滑动 API 不可用' } const { x1, y1, x2, y2 } = calculateSwipeCoordinates(action.value, resolution.width, resolution.height) const r = await api.sendSwipe(device, x1, y1, x2, y2, 300) if (!r.success) return { success: false, error: `滑动失败: ${r.error != null ? r.error : 'unknown'}` } return { success: true } } /* --- type: adb 按 method 分发到 adb/*.js --- */ const method = action.method if (!method) return { success: false, error: 'adb 操作缺少 method 参数' } const handler = METHOD_HANDLERS[method] if (!handler || !handler.run) return { success: false, error: `未知的 adb method: ${method}` } return handler.run(action, ctx) } module.exports = { types, parse, execute }