本文最后更新于 88 天前,其中的信息可能已经有所发展或是发生改变。
内容目录
声明:本文内容仅供学习,此脚本也仅限帮助打工仔减少忘记打卡的痛苦,切勿用于非法用途,否则后果自负。
某钉,已经成为上班族必不可少的工作软件。但是,每天的上下班打卡,时间久了,总是会偶尔忘记一两次,又是填单,又是申请,超过一定次数还会扣钱,让人为之头疼。
此前,某钉已经与远控软件(向日葵、AirDroid 等)多次展开交锋。远控软件也一个接一个地倒在某钉的规则之下。
AirDroid,又一款远控APP倒在钉钉规则之下
近日,众所周知的远控软件AirDroid也不幸又一次“倒在”了钉钉的更新规则之下。这让我想起了曾经热衷的Android自动化工具——autojs。
想当年,它可是帮我抢过红包、刷过学分,还曾经助我登上“跳一跳”的榜首,可谓是功能相当全面。
使用autojs自动打卡的步骤
现在的AutoJs6,功能相比以前更加强大(详细信息可参阅官方文档)。让我们看看如何使用autojs来实现自动打卡:
- 权限申请:首先,需要申请各种必要权限,如访问屏幕、修改系统设置、启用无障碍服务等。
- 脚本启动与省电操作:脚本运行后,自动将屏幕亮度调至最低,并设置定时器每20秒唤醒一次屏幕。这样可以在不Root的手机上避免锁屏后定时器停止的问题。经过多次测试,发现在非 root 的手机上,一旦锁屏,AutoJS 自身的定时任务和代码定义的计时器都会暂停运行。所以暂时只能以保持其唤醒状态。
- 时间监测与打卡:定时器会持续检查当前时间是否处于打卡时间范围内。到了指定时间,自动唤醒钉钉,并等待20秒确保应用成功启动并连接网络。
- 自动化打卡优化:若钉钉内部已开启“快速打卡”功能,在应用打开时会自动打卡。此外,还可以通过脚本识别并点击“打卡”按钮,进一步优化打卡流程。
代码分享
随便写的,也没有做重构,仅为大家简单提供个思路,如需使用,自己定制化优化修改即可。
auto();
/* 申请截屏权限 */
if (!requestScreenCapture()) {
/* 请求截屏失败 */
toast("请求截屏失败");
exit();
}
/* 资源文件路径 */
const wxHeadImgPath = "./wx_head_img.jpg"; // 要分享的微信头像
const wxBtnSharePath = "./wx_share_msg.jpg"; // 分享按钮
const wxReturn3rdToolsPath = "./wx_return_3rd_tools.jpg"; // 返回第三方工具的按钮
/* 打卡时间 */
var morning = [8, 40];
var evening = [18, 10];
/* 时间检查函数 */
function isTimeInRange(hour, minutes, targetHour, targetMinute, offset) {
let targetTime = new Date();
targetTime.setHours(targetHour, targetMinute, 0, 0);
let startTime = new Date(targetTime.getTime() - offset * 60000);
let endTime = new Date(targetTime.getTime() + offset * 60000);
let currentTime = new Date();
currentTime.setHours(hour, minutes, 0, 0);
return currentTime >= startTime && currentTime <= endTime;
}
setInterval(function() {
device.wakeUp();
let interval = Math.floor(Math.random() * 15 + 1);
sleep(interval * 1000);
let now = new Date();
let hour = now.getHours();
let minutes = now.getMinutes();
/* 4分钟内的随机偏差 */
let offset = Math.floor(Math.random() * 4 + 1);
/* 时间范围内执行操作 */
if (isTimeInRange(hour, minutes, morning[0], morning[1], offset) || isTimeInRange(hour, minutes, evening[0], evening[1], offset)) {
callDingTalk();
} else {
/* 每半小时输出一次 listening,接近定义时间则更频繁输出 */
let timeToNextHalfHour = 30 - (minutes % 30);
if (timeToNextHalfHour <= 20) {
// 接近定义时间,每5分钟提示一次(这里的逻辑少了小时的判断,但不影响效果,所以没有修改)
if (timeToNextHalfHour % 5 === 0) {
console.log("ready...");
}
} else if (minutes % 30 === 0) {
console.log("listening...");
}
}
}, 2000);
function callDingTalk() {
let result = app.launch("com.alibaba.android.rimet");
if (result) {
console.info("open success");
sleep(10 * 1000);
home();
console.info("return home");
sleep(2000);
/* 开始执行:分享消息 */
var message = "打卡成功 at: " + new Date().toLocaleTimeString();
sendMsgToWeChat(message);
} else {
console.error("open failed and try again later 5 seconds");
sleep(5 * 1000);
result = app.launch("com.alibaba.android.rimet");
if (result) {
console.warn("open success when try again");
sleep(8 * 1000);
home();
console.info("return home");
sleep(2000);
/* 开始执行:分享消息 */
var message = "打卡成功 at: " + new Date().toLocaleTimeString();
sendMsgToWeChat(message);
} else {
console.error("open failed when trying, please check your code");
}
}
}
function sendMsg2Wechat(msg) {
App.WECHAT.ensureInstalled();
let content = rawInput(msg);
content && app.startActivity({
action: 'android.intent.action.SEND',
type: 'text/*',
extras: {
'android.intent.extra.TEXT': content
},
packageName: App.WECHAT.getPackageName(),
className: '@{packageName}.ui.tools.ShareImgUI',
});
}
/* 开启手动屏幕亮度 */
device.setBrightnessMode(0);
/* 设置屏幕亮度为 */
device.setBrightness(0.1);
/**
* 启动控制台,调试阶段方便查看运行状态
*/
console.setSize(0.8, 0.6)
.setPosition(0.1, 0.15)
.setTitle('By: Hola Security')
.setTitleTextSize(18)
.setContentTextSize(16)
.setBackgroundColor('deep-orange-900')
.setTitleBackgroundAlpha(0.8)
.setContentBackgroundAlpha(0.5)
.setExitOnClose(6e3)
.show();
openConsole();
/**
* 唤起微信分享功能,发送指定消息内容
* @param {string} msg 要发送的消息内容
*/
function sendMsgToWeChat(msg) {
/* 调用微信分享页面 */
callWeChatMsgShare(msg).then(() => {
/* 寻找指定微信用户头像,持续15秒 */
return clickImageWithTimeoutAsync(wxHeadImgPath, 15000);
}).then(() => {
/* 寻找【分享】按钮,持续5秒 */
return clickImageWithTimeoutAsync(wxBtnSharePath, 5000);
}).then(() => {
/* 寻找【返回】按钮,持续5秒 */
return clickImageWithTimeoutAsync(wxReturn3rdToolsPath, 5000);
}).then(() => {
/* 分享结束,返回主界面 */
home();
}).catch(error => {
console.error("Error during process: " + error.message);
});
}
/**
* 调用微信分享页面
* @param {string} msg 消息文本
*/
function callWeChatMsgShare(msg) {
/* 检查微信是否安装 */
App.WECHAT.ensureInstalled();
/* 启动微信分享 */
app.startActivity({
action: "android.intent.action.SEND",
type: "text/*",
extras: {
"android.intent.extra.TEXT": msg
},
packageName: "com.tencent.mm",
className: "com.tencent.mm.ui.tools.ShareImgUI"
});
return Promise.resolve();
}
/**
* 在当前屏幕中寻找图片,返回坐标
* @param {string} path 图片路径
* @returns {Promise}
*/
function clickImageWithTimeoutAsync(imagePath, timeout) {
return new Promise((resolve, reject) => {
/* 开始时间 */
const startTime = new Date().getTime();
function checkImage() {
/* 检查图片位置 */
const point = screenAndFindImg(imagePath);
if (point) {
/* 成功找到图片 */
sleep(4000) ;
click(point.x + 15, point.y + 15);
toast("成功点击: " + imagePath + " at " + point);
resolve();
} else {
const currentTime = new Date().getTime();
if (currentTime - startTime < timeout) {
/* 未超时,继续检查 */
setTimeout(checkImage, 500);
} else {
/* 超时后拒绝 */
reject(new Error(`超时失败: 在 ${timeout} 毫秒后未找到图片: ${imagePath}`));
}
}
}
/* 初始化检查 */
checkImage();
});
}
/**
* 在屏幕上寻找指定图片
* @param {string} path 图片路径
* @returns {Point|null}
*/
function screenAndFindImg(path) {
const img = images.read(path);
const point = findImage(captureScreen(), img, {
threshold: 0.9
});
img.recycle();
return point;
}
目前的效果图
因为是测试,截图的时间与代码中可能会有不同,请忽略。
脚本运行状态:
打卡成功:
微信通知:
后续优化与分享
为了让打卡更加稳定,脚本还会定期返回主屏幕,以防应用偶尔卡死。目前考虑到的优化可能包括:
- 唤醒微信,识别指定头像,并发送“已成功打卡”消息;(已完成)
- 自动识别“已打卡”标识,确认打卡完成;(未完成)
- 自动寻找“打卡”按钮,必要时(如加班)进行打卡更新;(未完成)
虽然我因工作繁忙可能无法继续深入优化,但以此来抛砖引玉,我希望通过这篇分享,能够激发更多大佬进行个性化定制和优化,也以此来纪念一下当年助我良多的自动化神器 —— AutoJS。