某钉自动打卡,让工作更简单,再也不怕上下班忘记打卡了
本文最后更新于 88 天前,其中的信息可能已经有所发展或是发生改变。
内容目录

声明:本文内容仅供学习,此脚本也仅限帮助打工仔减少忘记打卡的痛苦,切勿用于非法用途,否则后果自负。


某钉,已经成为上班族必不可少的工作软件。但是,每天的上下班打卡,时间久了,总是会偶尔忘记一两次,又是填单,又是申请,超过一定次数还会扣钱,让人为之头疼。

此前,某钉已经与远控软件(向日葵、AirDroid 等)多次展开交锋。远控软件也一个接一个地倒在某钉的规则之下。

AirDroid,又一款远控APP倒在钉钉规则之下

近日,众所周知的远控软件AirDroid也不幸又一次“倒在”了钉钉的更新规则之下。这让我想起了曾经热衷的Android自动化工具——autojs。

想当年,它可是帮我抢过红包、刷过学分,还曾经助我登上“跳一跳”的榜首,可谓是功能相当全面。

图片

使用autojs自动打卡的步骤

现在的AutoJs6,功能相比以前更加强大(详细信息可参阅官方文档)。让我们看看如何使用autojs来实现自动打卡:

  1. 权限申请:首先,需要申请各种必要权限,如访问屏幕、修改系统设置、启用无障碍服务等。
  2. 脚本启动与省电操作:脚本运行后,自动将屏幕亮度调至最低,并设置定时器每20秒唤醒一次屏幕。这样可以在不Root的手机上避免锁屏后定时器停止的问题。经过多次测试,发现在非 root 的手机上,一旦锁屏,AutoJS 自身的定时任务和代码定义的计时器都会暂停运行。所以暂时只能以保持其唤醒状态。
  3. 时间监测与打卡:定时器会持续检查当前时间是否处于打卡时间范围内。到了指定时间,自动唤醒钉钉,并等待20秒确保应用成功启动并连接网络。
  4. 自动化打卡优化:若钉钉内部已开启“快速打卡”功能,在应用打开时会自动打卡。此外,还可以通过脚本识别并点击“打卡”按钮,进一步优化打卡流程。

代码分享

随便写的,也没有做重构,仅为大家简单提供个思路,如需使用,自己定制化优化修改即可。

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。

学海无涯,回头是岸。 --- hola
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇