adb-parser.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. /**
  2. * 语句:adb / keyevent / scroll / swipe / locate / click / press / input / ocr 统一入口
  3. * type===adb 时按 method 分发到 adb/*.js;其余 type 在本文件内执行,公共逻辑见 utils.js
  4. */
  5. const { calculateSwipeCoordinates } = require('./utils.js')
  6. const types = ['adb', 'keyevent', 'scroll', 'swipe', 'locate', 'click', 'press', 'input', 'ocr']
  7. const METHOD_HANDLERS = {
  8. input: require('./input.js'),
  9. click: require('./click.js'),
  10. locate: require('./locate.js'),
  11. swipe: require('./swipe.js'),
  12. scroll: require('./scroll.js'),
  13. keyevent: require('./keyevent.js'),
  14. press: require('./press.js'),
  15. 'string-press': require('./string-press.js'),
  16. 'send-img-to-device': require('./send-img-to-device.js'),
  17. }
  18. /* ========== 解析入口 ========== */
  19. function parse(action, parseContext) {
  20. const type = action.type || 'adb'
  21. const { extractVarName, resolveValue } = parseContext
  22. const variableContext = parseContext.variableContext || {}
  23. const parsed = Object.assign({}, action, { type })
  24. if (type === 'adb') {
  25. parsed.method = action.method
  26. parsed.inVars = action.inVars && Array.isArray(action.inVars) ? action.inVars.map(v => extractVarName(v)) : []
  27. parsed.outVars = action.outVars && Array.isArray(action.outVars) ? action.outVars.map(v => extractVarName(v)) : []
  28. parsed.target = action.target
  29. parsed.value = action.value
  30. parsed.variable = action.variable
  31. parsed.clear = action.clear || false
  32. return parsed
  33. }
  34. if (type === 'locate' || type === 'click') return parsed
  35. if (type === 'input') {
  36. parsed.clear = action.clear || false
  37. return parsed
  38. }
  39. if (type === 'ocr') {
  40. parsed.area = action.area
  41. parsed.avatar = resolveValue(action.avatar, variableContext)
  42. return parsed
  43. }
  44. return parsed
  45. }
  46. /* ========== 执行入口 ========== */
  47. async function execute(action, ctx) {
  48. const {
  49. device,
  50. folderPath,
  51. resolution,
  52. variableContext,
  53. api,
  54. extractVarName,
  55. resolveValue,
  56. logOutVars,
  57. DEFAULT_SCROLL_DISTANCE = 100,
  58. } = ctx
  59. /* --- type: locate --- */
  60. if (action.type === 'locate') {
  61. const method = action.method || 'image'
  62. let position = null
  63. if (method === 'image') {
  64. const imagePath = action.target.startsWith('/') || action.target.includes(':') ? action.target : `${folderPath}/${action.target}`
  65. if (!api?.matchImageAndGetCoordinate) return { success: false, error: '图像匹配 API 不可用' }
  66. const matchResult = await api.matchImageAndGetCoordinate(device, imagePath, folderPath)
  67. if (!matchResult.success) return { success: false, error: `图像匹配失败: ${matchResult.error != null ? matchResult.error : 'unknown'}` }
  68. position = matchResult.clickPosition
  69. } else if (method === 'text') {
  70. if (!api?.findTextAndGetCoordinate) return { success: false, error: '文字识别 API 不可用' }
  71. const matchResult = await api.findTextAndGetCoordinate(device, action.target)
  72. if (!matchResult.success) return { success: false, error: `文字识别失败: ${matchResult.error != null ? matchResult.error : 'unknown'}` }
  73. position = matchResult.clickPosition
  74. } else if (method === 'coordinate') {
  75. position = Array.isArray(action.target) ? { x: action.target[0], y: action.target[1] } : action.target
  76. }
  77. if (action.variable && position) variableContext[action.variable] = position
  78. return { success: true, result: position }
  79. }
  80. /* --- type: click --- */
  81. if (action.type === 'click') {
  82. const method = action.method || 'position'
  83. let position = null
  84. if (method === 'position') position = resolveValue(action.target, variableContext)
  85. else if (method === 'image') {
  86. const imagePath = action.target.startsWith('/') || action.target.includes(':') ? action.target : `${folderPath}/${action.target}`
  87. if (!api?.matchImageAndGetCoordinate) return { success: false, error: '图像匹配 API 不可用' }
  88. const matchResult = await api.matchImageAndGetCoordinate(device, imagePath, folderPath)
  89. if (!matchResult.success) return { success: false, error: `图像匹配失败: ${matchResult.error != null ? matchResult.error : 'unknown'}` }
  90. position = matchResult.clickPosition
  91. } else if (method === 'text') {
  92. if (!api?.findTextAndGetCoordinate) return { success: false, error: '文字识别 API 不可用' }
  93. const matchResult = await api.findTextAndGetCoordinate(device, action.target)
  94. if (!matchResult.success) return { success: false, error: `文字识别失败: ${matchResult.error != null ? matchResult.error : 'unknown'}` }
  95. position = matchResult.clickPosition
  96. }
  97. if (!position?.x || !position?.y) return { success: false, error: '无法获取点击位置' }
  98. if (!api?.sendTap) return { success: false, error: '点击 API 不可用' }
  99. const tapResult = await api.sendTap(device, position.x, position.y)
  100. if (!tapResult.success) return { success: false, error: `点击失败: ${tapResult.error != null ? tapResult.error : 'unknown'}` }
  101. return { success: true }
  102. }
  103. /* --- type: press --- */
  104. if (action.type === 'press') {
  105. const imagePath = `${folderPath}/resources/${action.value}`
  106. if (!api?.matchImageAndGetCoordinate) return { success: false, error: '图像匹配 API 不可用' }
  107. const matchResult = await api.matchImageAndGetCoordinate(device, imagePath, folderPath)
  108. if (!matchResult.success) return { success: false, error: `图像匹配失败: ${matchResult.error != null ? matchResult.error : 'unknown'}` }
  109. const { x, y } = matchResult.clickPosition
  110. if (!api?.sendTap) return { success: false, error: '点击 API 不可用' }
  111. const tapResult = await api.sendTap(device, x, y)
  112. if (!tapResult.success) return { success: false, error: `点击失败: ${tapResult.error != null ? tapResult.error : 'unknown'}` }
  113. return { success: true }
  114. }
  115. /* --- type: input --- */
  116. if (action.type === 'input') {
  117. let inputValue = resolveValue(action.value, variableContext)
  118. if (!inputValue && action.target) {
  119. const resolvedTarget = resolveValue(action.target, variableContext)
  120. if (resolvedTarget !== action.target || !action.target.includes(' ')) inputValue = resolvedTarget
  121. }
  122. if (!inputValue) return { success: false, error: '输入内容为空' }
  123. if (!api?.sendText) return { success: false, error: '输入 API 不可用' }
  124. if (action.clear) {
  125. for (let i = 0; i < 200; i++) {
  126. const clearResult = await api.sendKeyEvent(device, '67')
  127. if (!clearResult.success) break
  128. await new Promise((r) => setTimeout(r, 10))
  129. }
  130. await new Promise((r) => setTimeout(r, 200))
  131. }
  132. const textResult = await api.sendText(device, inputValue)
  133. if (!textResult.success) return { success: false, error: `输入失败: ${textResult.error != null ? textResult.error : 'unknown'}` }
  134. return { success: true }
  135. }
  136. /* --- type: ocr --- */
  137. if (action.type === 'ocr') {
  138. if (!api?.ocrLastMessage) return { success: false, error: 'OCR API 不可用' }
  139. const method = action.method || 'full-screen'
  140. let avatarPath = null
  141. if (method === 'by-avatar' && action.avatar) {
  142. const avatarName = resolveValue(action.avatar, variableContext)
  143. if (avatarName) {
  144. const folderName = folderPath.split(/[/\\]/).pop()
  145. avatarPath = `${folderName}/${avatarName}`
  146. }
  147. }
  148. const ocrResult = await api.ocrLastMessage(device, method, avatarPath, action.area, folderPath)
  149. if (!ocrResult.success) return { success: false, error: `OCR识别失败: ${ocrResult.error != null ? ocrResult.error : 'unknown'}` }
  150. if (action.variable) variableContext[action.variable] = ocrResult.text || ''
  151. return { success: true, text: ocrResult.text, position: ocrResult.position }
  152. }
  153. /* --- type: keyevent --- */
  154. if (action.type === 'keyevent') {
  155. let keyCode = null
  156. const inVars = action.inVars || []
  157. if (inVars.length > 0) {
  158. const keyVar = extractVarName(inVars[0])
  159. keyCode = variableContext[keyVar] || keyVar
  160. } else if (action.value) {
  161. keyCode = resolveValue(action.value, variableContext)
  162. }
  163. if (!keyCode) return { success: false, error: 'keyevent 操作缺少按键代码参数' }
  164. if (keyCode === 'KEYCODE_BACK') keyCode = '4'
  165. const keyResult = api.sendSystemKey(device, String(keyCode))
  166. if (!keyResult.success) return { success: false, error: `按键失败: ${keyResult.error != null ? keyResult.error : 'unknown'}` }
  167. return { success: true }
  168. }
  169. /* --- type: scroll --- */
  170. if (action.type === 'scroll') {
  171. if (!api.sendScroll) return { success: false, error: '滚动 API 不可用' }
  172. const direction = action.value
  173. const r = await api.sendScroll(device, direction, resolution.width, resolution.height, DEFAULT_SCROLL_DISTANCE, 500)
  174. if (!r.success) return { success: false, error: `滚动失败: ${r.error != null ? r.error : 'unknown'}` }
  175. return { success: true }
  176. }
  177. /* --- type: swipe --- */
  178. if (action.type === 'swipe') {
  179. if (!api.sendSwipe) return { success: false, error: '滑动 API 不可用' }
  180. const { x1, y1, x2, y2 } = calculateSwipeCoordinates(action.value, resolution.width, resolution.height)
  181. const r = await api.sendSwipe(device, x1, y1, x2, y2, 300)
  182. if (!r.success) return { success: false, error: `滑动失败: ${r.error != null ? r.error : 'unknown'}` }
  183. return { success: true }
  184. }
  185. /* --- type: adb 按 method 分发到 adb/*.js --- */
  186. const method = action.method
  187. if (!method) return { success: false, error: 'adb 操作缺少 method 参数' }
  188. const handler = METHOD_HANDLERS[method]
  189. if (!handler || !handler.run) return { success: false, error: `未知的 adb method: ${method}` }
  190. return handler.run(action, ctx)
  191. }
  192. module.exports = { types, parse, execute }