| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 |
- /**
- * 语句:adb / keyevent / scroll / swipe / locate / click / press / input / ocr 统一入口
- * type===adb 时按 method 分发到 adb/*.js;其余 type 在本文件内执行,公共逻辑见 utils.js
- */
- const { calculateSwipeCoordinates } = require('./utils.js')
- const { assertStrictKeys } = require('../../../action-schema.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 path = parseContext.actionPath || 'adb'
- const type = action.type || 'adb'
- const { extractVarName, resolveValue } = parseContext
- const variableContext = parseContext.variableContext || {}
- if (type === 'adb') {
- assertStrictKeys(action, ['type', 'method', 'inVars', 'outVars', 'target', 'value', 'variable', 'clear'], path)
- if (action.method == null || String(action.method).trim() === '') {
- throw new Error(`${path}: adb 须包含 method`)
- }
- if (!Array.isArray(action.inVars)) {
- throw new Error(`${path}: adb 须包含 inVars 数组`)
- }
- if (!Array.isArray(action.outVars)) {
- throw new Error(`${path}: adb 须包含 outVars 数组`)
- }
- return {
- type: 'adb',
- method: action.method,
- inVars: action.inVars.map((v) => extractVarName(v)),
- outVars: action.outVars.map((v) => extractVarName(v)),
- target: action.target,
- value: action.value,
- variable: action.variable,
- clear: action.clear === true,
- }
- }
- if (type === 'locate' || type === 'click') {
- assertStrictKeys(action, ['type', 'method', 'target', 'variable'], path)
- return {
- type,
- method: action.method,
- target: action.target,
- variable: action.variable,
- }
- }
- if (type === 'input') {
- assertStrictKeys(action, ['type', 'value', 'target', 'clear'], path)
- if (action.value === undefined || action.value === null || action.value === '') {
- throw new Error(`${path}: input 须包含 value`)
- }
- return {
- type: 'input',
- value: action.value,
- target: action.target,
- clear: action.clear === true,
- }
- }
- if (type === 'press') {
- assertStrictKeys(action, ['type', 'value'], path)
- if (action.value === undefined || action.value === null || action.value === '') {
- throw new Error(`${path}: press 须包含 value`)
- }
- return { type: 'press', value: action.value }
- }
- if (type === 'ocr') {
- assertStrictKeys(action, ['type', 'method', 'area', 'avatar', 'variable'], path)
- return {
- type: 'ocr',
- method: action.method,
- area: action.area,
- avatar: action.avatar != null ? resolveValue(action.avatar, variableContext) : undefined,
- variable: action.variable,
- }
- }
- if (type === 'keyevent') {
- assertStrictKeys(action, ['type', 'inVars'], path)
- if (!Array.isArray(action.inVars) || action.inVars.length < 1) {
- throw new Error(`${path}: keyevent 须使用 inVars 且至少 1 项为键码`)
- }
- return {
- type: 'keyevent',
- inVars: action.inVars.map((v) => extractVarName(v)),
- }
- }
- if (type === 'scroll') {
- assertStrictKeys(action, ['type', 'value'], path)
- if (action.value === undefined || action.value === null || action.value === '') {
- throw new Error(`${path}: scroll 须包含 value(方向)`)
- }
- return { type: 'scroll', value: action.value }
- }
- if (type === 'swipe') {
- assertStrictKeys(action, ['type', 'value'], path)
- if (action.value === undefined || action.value === null || action.value === '') {
- throw new Error(`${path}: swipe 须包含 value`)
- }
- return { type: 'swipe', value: action.value }
- }
- throw new Error(`${path}: 未知的 adb-parser type: ${type}`)
- }
- /* ========== 执行入口 ========== */
- 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') {
- const inVars = action.inVars || []
- const keyVar = extractVarName(inVars[0])
- const keyCode = variableContext[keyVar] != null && variableContext[keyVar] !== ''
- ? variableContext[keyVar]
- : keyVar
- if (!keyCode && keyCode !== 0) 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 }
|