需要说明一下:
- 最近钉钉升级,中间打卡部分Layout中的控件内容AutoJs捕获不到了;
- 可以通过另一种方式实现,就是AutoJs实现区间时段随机启动钉钉,在钉钉中打开自动打卡功能。
我们这里的钉钉自动打卡主要是使用AutoJs脚本实现的,但由于AutoJs受限于android版本,高版本的Android手机,可能无法实现完整功能,尽量使用Android10或一下版本的手机。
操作如下:
- 手机安装AutoJs;
- 开发机安装VSCode + Auto.js-Autox.js-VSCodeExt;
- 在VSCode中启动AutoJs服务监听
- 手机连接服务器
- 开发调试
- 打包应用
具体操作详情可以百度,这里就不展开细讲了,下面是主要脚本代码:
-
main.ts
import {Cron} from "croner"; import Ding from "./ding"; import {ZKER_CHECKIN_PACKAGENAME} from "./utils/constants"; const checkin = () => { const timeout = random(1, 9) console.log(timeout + "秒后执行") setTimeout(() => { console.log("任务开始:", new Date().getTime()) threads.start(() => new Ding().checkin()) }, timeout * 1e3 * 60) } new Cron("0 25 08 ? * 1-5", checkin); new Cron("0 35 17 ? * 1-5", checkin); ui.layout( <> <frame w="*" h="*"> <vertical layout_gravity="top|center" w="auto" h="auto"> <text text="" textSize="8sp"/> <text text="打卡任务已在后台运行" textSize="11sp"/> <text text="保持程序正常运行即可" textSize="11sp" color="red"/> </vertical> <vertical layout_gravity="center" w="auto" h="auto"> <button id="testRun" text="测试打卡"/> <button id="testWS" text="连接WS"/> <button id='exit' text="退出"/> </vertical> <vertical w="auto" h="auto" layout_gravity="bottom|center"> <text text="钉钉自动打卡程序 PowerBy @ZKer" textSize="8sp"/> <text text="" textSize="8sp"/> </vertical> </frame> </> ); ui.testRun.click(() => threads.start(() => new Ding().checkin())) ui.exit.click(() => app.exit()) ui.testWS.click(() => { threads.start(() => { let nickName = "ZKer" try { nickName = new Ding().getName() } catch (e) { nickName = "心境" } app.launch(ZKER_CHECKIN_PACKAGENAME) importPackage(Packages["okhttp3"]); //导入包 const client = new OkHttpClient.Builder().retryOnConnectionFailure(true).build(); const request = new Request.Builder().url(`ws://www.kuaiyuai.top:59000/flamigo/ding/ws?name=${nickName}`).build(); //vscode 插件的ip地址,` client.dispatcher().cancelAll();//清理一次 const myListener = { onOpen: function (webSocket, response) { print("onOpen"); //打开链接后,想服务器端发送一条消息 const json = { type: "device_info", data: { device_model: device.model, device_hardware: device.hardware, // device_imei: device.getIMEI(), device_serial: device.serial, android_version: device.release, baseOS: device.baseOS, app_version: app.versionName, app_version_code: app.versionCode } }; const hello = JSON.stringify(json); webSocket.send(hello); }, onMessage: function (webSocket, msg) { //msg可能是字符串,也可能是byte数组,取决于服务器送的内容 print("onMessage:", msg); threads.start(() => new Ding().checkin()); }, onClosing: function (webSocket, code, response) { print("正在关闭"); }, onClosed: function (webSocket, code, response) { print("已关闭"); }, onFailure: function (webSocket, t, response) { print("错误", t); client.newWebSocket(request, new WebSocketListener(myListener)); //创建链接 } } const webSocket = client.newWebSocket(request, new WebSocketListener(myListener)); //创建链接 }) })
-
ding.ts
import {SERVER_CHECKIN_URL, ZKER_CHECKIN_PACKAGENAME} from "./utils/constants"; export default class { private appName: string = "钉钉" private packageName: string = "com.alibaba.android.rimet" private corporation: string = "" private nickName: string = "" private isRunning: boolean = false constructor() { this.checkStatus() } openAppSetting() { app.openAppSetting(this.packageName); text(this.appName).waitFor(); sleep(500) console.log("判断控件是否已启用(想要关闭的app是否运行)") return textMatches(/(.*强.*|.*停.*|.*结.*|.*行.*)/).findOne(); } checkStatus() { const is_sure = this.openAppSetting() this.isRunning = is_sure.enabled() return this.isRunning } kill() { const is_sure = this.openAppSetting() if (is_sure.enabled()) { console.log("结束应用的控件如果无法点击,需要在布局中找寻它的父控件,如果还无法点击,再上一级控件,本案例就是控件无法点击") is_sure.click(); is_sure.parent().click(); // 确保保小米等收集在sure按钮层无法正确点击 console.log("需找包含“确”,“定”的控件") textMatches(/(.*确.*|.*定.*)/).findOne().click(); this.isRunning = false console.log(this.appName + "应用已被关闭"); sleep(1000); back(); } else { console.log(this.appName + "应用不能被正常关闭或不在后台运行"); back(); } } run() { if (this.isRunning) { this.kill() } console.log("打开应用") app.launch(this.packageName) this.isRunning = true } getName() { this.run() console.log("开始获取名字...") while (!click("我的")) ; this.nickName = id(`${this.packageName}:id/tv_biz_card_nick`).findOne().text(); console.log("获取到名字:", this.nickName) return this.nickName } checkin() { this.getName() while (!click("工作台")) ; this.corporation = id(`${this.packageName}:id/tv_org_name`).findOne().text(); console.log("获取到公司名:", this.corporation) let btn = text("考勤打卡") btn.waitFor(); text("考勤打卡").findOne().click(); btn = textEndsWith("班打卡") btn.waitFor(); btn.findOne().click(); sleep(8000) if (text("继续打卡").exists()) { text("继续打卡").findOne().click() } if (text("拍照备注并打卡").exists()) { console.log("找到【拍照备注并打卡】") text("拍照备注并打卡").findOne().click() id(`${this.packageName}:id/camera_takephoto`).findOne().click(); // const pic_id = id(`${this.packageName}:id/picedit_usephoto`).findOne() text("确认").waitFor() text("确认").findOne().click() } else { console.log("没找到【拍照备注并打卡】") } const now = new Date() console.log("now", now.toLocaleDateString()) textEndsWith("班打卡").waitFor(); sleep(10000) const success = textEndsWith("班打卡成功").exists() back() const params = { success, name: this.nickName, corporation: this.corporation, timestamp: now.getTime().toString(), } console.log("打卡参数:", params) http.postJson(SERVER_CHECKIN_URL, params) console.log(`打卡${success ? "成功" : "失败"}!`) back() app.launch(ZKER_CHECKIN_PACKAGENAME) } }