send-img-to-device.js 3.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. /**
  2. * adb method: send-img-to-device — 将本地图片推送到手机并加入相册(1+ 等 Android 通用)
  3. * inVars: [本地图片路径] 路径可为相对 folderPath 或绝对
  4. */
  5. const { spawnSync } = require('child_process')
  6. const path = require('path')
  7. const fs = require('fs')
  8. const defaultRoot = path.resolve(__dirname, '..', '..', '..', '..')
  9. const configPath = process.env.STATIC_ROOT
  10. ? path.join(path.dirname(process.env.STATIC_ROOT), 'configs', 'config.js')
  11. : path.join(defaultRoot, 'configs', 'config.js')
  12. const config = fs.existsSync(configPath) ? require(configPath) : {}
  13. const projectRoot = (config.projectRoot && fs.existsSync(config.projectRoot)) ? config.projectRoot : defaultRoot
  14. const adbPath = config.adbPath?.path
  15. ? (path.isAbsolute(config.adbPath.path) ? config.adbPath.path : path.resolve(projectRoot, config.adbPath.path))
  16. : path.join(projectRoot, 'lib', 'scrcpy-adb', process.platform === 'win32' ? 'adb.exe' : 'adb')
  17. // Android 10+ 相册对 Pictures 目录更友好,1+ 等机型也常从此处读
  18. const DEVICE_PICTURES = '/sdcard/Pictures/'
  19. const DEVICE_DCIM = '/sdcard/DCIM/'
  20. /**
  21. * 将本地图片推送到设备相册(供 run 与 CLI 复用)
  22. * @param {string} localPath - 本地图片绝对路径
  23. * @param {string} deviceId - 设备 ID
  24. * @param {string} [adbExe] - 可选,默认用 config
  25. */
  26. function doSendImageToDevice(localPath, deviceId, adbExe = adbPath) {
  27. const resolved = path.resolve(localPath)
  28. if (!fs.existsSync(resolved)) {
  29. return { success: false, error: `本地文件不存在: ${resolved}` }
  30. }
  31. const basename = path.basename(resolved)
  32. // 优先推送到 Pictures,Android 10+ 相册更容易识别
  33. const deviceFile = DEVICE_PICTURES + basename
  34. const localForAdb = resolved.replace(/\\/g, '/')
  35. const args = deviceId ? ['-s', deviceId, 'push', localForAdb, deviceFile] : ['push', localForAdb, deviceFile]
  36. const push = spawnSync(adbExe, args, { encoding: 'utf-8', timeout: 60000 })
  37. const ok = push.status === 0
  38. if (!ok) {
  39. const msg = (push.stderr || push.stdout || '').trim()
  40. const code = push.status
  41. const sig = push.signal
  42. let err = msg || (code != null ? `adb push 退出码 ${code}` : sig ? `adb push 被终止 (${sig})` : 'adb push 未正常结束(可能超时或 adb 未找到)')
  43. if (code == null && !msg) err += '。请检查设备连接与 adb 路径'
  44. return { success: false, error: err }
  45. }
  46. // Android 10/11+ 需带 --receiver-include-background 扫描才易在相册中显示
  47. const scan = (d) => {
  48. const base = ['shell', 'am', 'broadcast', '-a', 'android.intent.action.MEDIA_SCANNER_SCAN_FILE', '-d', d, '--receiver-include-background']
  49. const a = deviceId ? ['-s', deviceId, ...base] : base
  50. spawnSync(adbExe, a, { encoding: 'utf-8', timeout: 8000 })
  51. }
  52. scan(`file://${deviceFile}`)
  53. scan('file:///sdcard/Pictures')
  54. scan('file:///sdcard/DCIM')
  55. return { success: true, devicePath: deviceFile }
  56. }
  57. async function run(action, ctx) {
  58. const { device, folderPath, variableContext, extractVarName, logMessage } = ctx
  59. const inVars = action.inVars || []
  60. let localPath = inVars.length > 0 ? (variableContext[extractVarName(inVars[0])] || inVars[0]) : action.value
  61. if (!localPath) return { success: false, error: 'send-img-to-device 操作缺少本地图片路径' }
  62. if (!device || String(device).trim() === '') return { success: false, error: 'send-img-to-device 需要设备 ID,请确保流程已连接设备' }
  63. const fullPath = localPath.startsWith('/') || (localPath.length >= 2 && localPath[1] === ':') ? localPath : path.resolve(folderPath, localPath)
  64. if (!fs.existsSync(fullPath)) return { success: false, error: `本地文件不存在: ${fullPath}(流程目录: ${folderPath})` }
  65. const result = doSendImageToDevice(fullPath, device, adbPath)
  66. if (!result.success) return result
  67. return { success: true, devicePath: result.devicePath }
  68. }
  69. module.exports = { run, doSendImageToDevice }