最近有个需求,为了提高工作效率,借助autox.js 来进行一个私信截图上传脚本小工具的开发。
首先先大概了解一下autox.js ,AutoX.js 是在 Auto.js 基础上进行了扩展和改进的自动化测试框架。它采用了 V8 引擎,提供了更高的性能和更多的功能。AutoX.js 支持使用 JavaScript 或 Kotlin 编写脚本,并提供了更多的 API 和工具,用于进行更复杂的自动化测试和任务自动化。它还提供了图像识别和模拟点击等功能,使得在 Android 平台上进行 UI 自动化更加方便和灵活。autox.js 免root 且可以用javaScript 来写,那真的是十分契合作为前端的我。
中文文档是:http://doc.autoxjs.com/#/
需求是:截图->上传云cos服务器->拿到返回地址->获取用户抖音号,昵称,检测提取谈话内容手机号->一起把数据发送到后台。
大概就是这么个流程。
autox.js 需要打开无障碍,悬浮框权限。
1. 首先需要渲染一个“截图”侧边按钮,同时需要获取截图权限
/** * 点击初始菜单页私信截图 */ function showFloatControl3() { //获取截图权限 if (!requestScreenCapture()) { toast("请求截图授权失败"); return false; } closeFloatyWindow(); threads.start(function () { window = floaty.window( <vertical> <button id="screenshot" margin="0" w="60" style="Widget.AppCompat.Button.Colored" > 截图 </button> </vertical> ); let wx = window.getX(); let wy = window.getY() + 200; window.setPosition(wx, wy); let x = 0; let y = 0; let X = 0; let Y = 0; let startX, startY; // 设置按钮的触摸事件 window.screenshot.setOnTouchListener(function (view, event) { switch (event.getAction()) { case event.ACTION_DOWN: X = event.getRawX(); Y = event.getRawY(); startX = event.getX(); startY = event.getY(); return true; case event.ACTION_MOVE: x = event.getRawX() - X; y = event.getRawY() - Y; window.setPosition(wx + x, wy + y); return true; case event.ACTION_UP: let endX = event.getX(); let endY = event.getY(); let distance = Math.abs(endX - startX) + Math.abs(endY - startY); wx += x; wy += y; // 如果移动的距离小于 10, 则认为点击事件 if (distance < 10) { renderScreenshotClick(); } return false; } return true; }); //当脚本结束时关闭悬浮按钮 events.on("exit", function () { if (window) { window.close(); } }); }); }
2. 给侧边“截图”按钮绑定 点击时间,进行截图。
/** * 进行私信截图操作 */ function renderScreenshotClick() { if (!isChatPage()) { alert("请转到私信页面截图"); return; } if (!id("an=").exists()) { alert("客户没有说话"); return; } var screenshot = captureScreen(); // 截取当前屏幕截图 var saveDir = files.join(files.getSdcardPath(), "Pictures", "Screenshots"); // 构建保存目录 files.createWithDirs(saveDir); // 创建保存目录(如果不存在) var timestamp = new Date().getTime(); // 获取当前时间戳 var savePath = files.join(saveDir, "screenshot_" + timestamp + ".png"); if (screenshot) { images.save(screenshot, savePath); // 保存截图到指定路径 console.log("截图保存路径:", savePath); toast("截图保存路径:", savePath); uploadScreenshot(savePath); } else { alert("出现错误", "截屏失败,请检查是否开启权限", function () { global.robotStop(); return; }); } }
3. 判断是否处于私信页
/** * 判断是否在私信页 * @returns {Boolean} 是否在私信页 */ function isChatPage() { var idList = ["xxx"]; //私信页标志性节点id集合 var existsInIdList = false; for (var i = 0; i < idList.length; i++) { if (id(idList[i]).exists()) { existsInIdList = true; break; } } return existsInIdList; }
4. 将截图上传到云cos服务器
/** * 上传截图到云 COS 服务器 * @param path {String} 图片路径 */ function uploadScreenshot(path) { threads.start(function () { try { var res = postFile("/upload/imagesToCos", { file: open(path), }); var rbody = res.body.string(); if (!isJSON(rbody)) { throw new Error("上传截图失败"); } var uploadResJsonData = JSON.parse(rbody); var clientInfo = getProfileInfo(1); //获取客户的抖音号和昵称信息 sleep(1000); var mineInfo = getProfileInfo(2); //获取自己的抖音号和昵称信息 sleep(1000); var phone = phoneExtract(); //提取聊天内容手机号 var params = { nickname: clientInfo.nickname, douyinId: clientInfo.douyinId, url: uploadResJsonData.url, orginName: mineInfo.nickname, mobile: phone, }; if ( !clientInfo.douyinId || !mineInfo.nickname || !uploadResJsonData.url ) { throw new Error("数据缺失无法上传"); } setTimeout(function () { saveGuest(params); }, 0); } catch (error) { alert("出现错误", error.message, function () { global.robotStop(); return; }); } }); }
5. 根据节点标识,在个人主页,获取昵称+抖音号
/** * 获取个人主页昵称+抖音号 * @param role {Number} 角色1 是客户; 角色2 是我 * @return {Object} 返回对象 {昵称,抖音号} */ function getProfileInfo(role) { // 根据角色选择不同的控件ID var targetId = role === 1 ? "an=" : "an-"; var targetElement = id(targetId).findOne(); if (targetElement) { var isClickable = targetElement.clickable(); console.log("控件的 clickable 属性为:" + isClickable); if (isClickable) { targetElement.click(); } else { var bounds = targetElement.bounds(); // 获取目标控件的边界信息 var centerX = bounds.centerX(); var centerY = bounds.centerY(); press(centerX, centerY, 10); // 模拟长按操作,持续时间为 1000ms 抖音只能用press,估计有点击时间控制现在,click()函数模拟连续点击时可能有点击速度过慢的问题 } } else { alert("出现错误", "未找到目标控件", function () { global.robotStop(); return; }); } sleep(3000); // 等待个人主页加载完成 // 查找私信用户的昵称和抖音号所在的元素text(""); var nicknameElement = id("og0").findOnce(); var douyinIdElement = id("yzh").findOnce(); var nickname = nicknameElement ? nicknameElement.text() : ""; var douyinId = douyinIdElement ? douyinIdElement.text().replace("抖音号:", "") : ""; if (douyinId && nickname) { id("back_btn").click(); //返回私信 } else { alert("出现错误", "获取抖音号,昵称数据异常,请手动上传数据", function () { global.robotStop(); return; }); } return { nickname, douyinId }; }
6. 提取他人谈话内容中的手机号,这里主要是考虑如何在内容节点集合中,过滤掉自己说话的内容,再从内容中提取手机号。采用了中线分割内容框,根据左右侧长度长短来判断,左侧长度比右侧长,则说明是对方的内容。
/** * 提取客户谈话内容手机号 */ function phoneExtract() { var nodeList = id("content").find(); // content列表 var phoneRegex = /(+d{1,2}s?)?(d{3,4}s?){2}d{4}/g; // 定义匹配手机号码的正则表达式 for (var i = 0; i < nodeList.length; i++) { var node = nodeList[i]; var content = node.desc(); // 获取节点的内容 var bounds = node.bounds(); // 聊天节点的边界信息 var deviceInfo = getDeviceInfo(); var centerWidth = deviceInfo.width / 2; //屏幕中线 if (centerWidth - bounds.left > bounds.right - centerWidth) { //屏幕中线,如果左侧长度 大于右侧长度,则说明是他人说话的内容 var matches = content.match(phoneRegex); // 使用正则表达式匹配手机号码 if (matches) { for (var j = 0; j < matches.length; j++) { var phoneNumber = matches[j].replace(/s/g, ""); // 去除空格 console.log("提取到的手机号码:", phoneNumber); return phoneNumber; // 找到手机号码后立即返回 } } } } return ""; // 找不到手机号码时返回空字符串 }
7. 最后进行私信信息入库
/** * 私信资料入库 * @param params {Object} 需要提交入库的数据 */ function saveGuest(params) { threads.start(function () { try { var param = { imgUrls: JSON.stringify([ { url: params.url, }, ]), // 截图 appNickname: params.nickname, //抖音昵称 appAccount: params.douyinId, //抖音号 orginName: params.orginName, //本号昵称 mobile: params.mobile, }; console.log("入库参数", param); var res = post("/save", param); var rbody = res.body.string(); console.log(rbody); if (!isJSON(rbody)) { throw new Error("入库失败"); } var resJsonData = JSON.parse(rbody); toast(resJsonData.message); } catch (error) { alert("出现错误:" + error.message, function () { global.robotStop(); return; }); } }); }
以上就是主要代码及思想。