企业微信打卡
本文旨在业余爱好,本人所在公司也没有微信打卡系统。建议小伙伴们还是要按时上班,好好工作,天道酬勤哦。
下面开始正题,企业微信打卡是基于定位的,所以如果要实现自动打卡,至少有如下两种方法:
- 使用模拟定位,这种方式很简单,只要手机获取到root并安装模拟定位软件即可。不过,对于已经成熟的打卡平台,模拟定位数据应该是可以技术上做甄别的,有一定被发现的风险。而且,技术含量太低,所以本文没做尝试。
- 通过类似按键精灵等工具模拟操作,完成打卡操作。该方式接近于物理外挂,安全性可以保证。缺点就是需要把手机一直放在有效定位范围内,可以找一个旧的不用的手机。
需要安装的工具
- Magisk:用来获取root权限。我用的是红米Note2+MIUI9官方ROM,在调试过程中发现V18.0版本的Magisk存在兼容性问题,安装后无法正常弹出root权限提示框,降级到V16.0后正常。
- RE文件管理器:需要root权限,用来操作系统目录。
- Tasker:定时运行工具。
- Secure Settings:用于配合Tasker完成手机唤醒等操作。
- 终端模拟器:可以直接在手机上完成adb的相关操作,对于没有电脑调试环境的小朋友,可以借助该工具直接调试,需要安装两个Magisk模块,详细使用见下文。
- 按键精灵手机版:完成打卡主流程操作。
另外,建议安装按键精灵电脑助手,在电脑上完成调试比较方便快捷。其实,不使用按键精灵,只通过shell脚本模拟点击也是可以实现的,不过按键精灵做的比较完善,用它来实现会更稳定,也更方便。
安装Magisk,获取ROOT权限
首先,需要获取root权限,无论是按键精灵还是其他软件,如果要实现全自动化操作,一般都绕不过这一步。
Magisk安装有两种方式,这里推荐直接通过第三方Recovery安装ZIP包。
在Magisk官网下载对应的安装包,本文使用的是V16.0版本。Magisk貌似只提供最新版本的下载链接,其他的
第三方Recovery可以用TWRP,可在https://twrp.me/下载最新版本。下载后,线刷recovery分区即可。线刷方式网上有对应教程,此处不做详述。
然后,将SuperSu的zip包放到手机存储中,重启手机进入recovery模式,找到SuperSu的zip包,按照提示完成安装即可。
类似操作网上有很多教程,此处不做赘述。
安装完成后,在桌面上会有个超级授权的图标,即是SuperSu应用了。此时,再运行其他需要root权限的应用时,就会弹框提示获取root权限,选择允许即可。
定时唤醒手机,调用按键精灵应用
通过Tasker的定时任务,调用Secure Settings定时的唤醒手机,点亮屏幕,然后调用内置的shell脚本。Tasker的配置文件如下:
<TaskerData sr="" dvi="1" tv="5.2.bf1"> <Task sr="task4"> <cdate>1546242617374</cdate> <edate>1547296906325</edate> <id>4</id> <nme>start</nme> <pri>100</pri> <Action sr="act0" ve="7"> <code>1563799945</code> <Bundle sr="arg0"> <Vals sr="val"> <com.intangibleobject.securesettings.plugin.extra.BLURB>Screen Bright 1 Second</com.intangibleobject.securesettings.plugin.extra.BLURB> <com.intangibleobject.securesettings.plugin.extra.BLURB-type>java.lang.String</com.intangibleobject.securesettings.plugin.extra.BLURB-type> <com.intangibleobject.securesettings.plugin.extra.SETTING>wake_device</com.intangibleobject.securesettings.plugin.extra.SETTING> <com.intangibleobject.securesettings.plugin.extra.SETTING-type>java.lang.String</com.intangibleobject.securesettings.plugin.extra.SETTING-type> <com.intangibleobject.securesettings.plugin.extra.WAKE_LOCK_DURATION>1000</com.intangibleobject.securesettings.plugin.extra.WAKE_LOCK_DURATION> <com.intangibleobject.securesettings.plugin.extra.WAKE_LOCK_DURATION-type>java.lang.Long</com.intangibleobject.securesettings.plugin.extra.WAKE_LOCK_DURATION-type> <com.intangibleobject.securesettings.plugin.extra.WAKE_LOCK_TYPE>bright</com.intangibleobject.securesettings.plugin.extra.WAKE_LOCK_TYPE> <com.intangibleobject.securesettings.plugin.extra.WAKE_LOCK_TYPE-type>java.lang.String</com.intangibleobject.securesettings.plugin.extra.WAKE_LOCK_TYPE-type> <com.twofortyfouram.locale.intent.extra.BLURB>Screen Bright 1 Second</com.twofortyfouram.locale.intent.extra.BLURB> <com.twofortyfouram.locale.intent.extra.BLURB-type>java.lang.String</com.twofortyfouram.locale.intent.extra.BLURB-type> <net.dinglisch.android.tasker.subbundled>true</net.dinglisch.android.tasker.subbundled> <net.dinglisch.android.tasker.subbundled-type>java.lang.Boolean</net.dinglisch.android.tasker.subbundled-type> </Vals> </Bundle> <Str sr="arg1" ve="3">com.intangibleobject.securesettings.plugin</Str> <Str sr="arg2" ve="3">com.intangibleobject.securesettings.plugin.Activities.TabsActivity</Str> <Int sr="arg3" val="0"/> </Action> <Action sr="act1" ve="7"> <code>1563799945</code> <Bundle sr="arg0"> <Vals sr="val"> <com.intangibleobject.securesettings.plugin.extra.BLURB>Keep Display On: AC, USB, Wireless</com.intangibleobject.securesettings.plugin.extra.BLURB> <com.intangibleobject.securesettings.plugin.extra.BLURB-type>java.lang.String</com.intangibleobject.securesettings.plugin.extra.BLURB-type> <com.intangibleobject.securesettings.plugin.extra.EXTRA_SPINNER_OPTION>7</com.intangibleobject.securesettings.plugin.extra.EXTRA_SPINNER_OPTION> <com.intangibleobject.securesettings.plugin.extra.EXTRA_SPINNER_OPTION-type>java.lang.String</com.intangibleobject.securesettings.plugin.extra.EXTRA_SPINNER_OPTION-type> <com.intangibleobject.securesettings.plugin.extra.SETTING>display_charge</com.intangibleobject.securesettings.plugin.extra.SETTING> <com.intangibleobject.securesettings.plugin.extra.SETTING-type>java.lang.String</com.intangibleobject.securesettings.plugin.extra.SETTING-type> <com.twofortyfouram.locale.intent.extra.BLURB>Keep Display On: AC, USB, Wireless</com.twofortyfouram.locale.intent.extra.BLURB> <com.twofortyfouram.locale.intent.extra.BLURB-type>java.lang.String</com.twofortyfouram.locale.intent.extra.BLURB-type> <net.dinglisch.android.tasker.subbundled>true</net.dinglisch.android.tasker.subbundled> <net.dinglisch.android.tasker.subbundled-type>java.lang.Boolean</net.dinglisch.android.tasker.subbundled-type> </Vals> </Bundle> <Str sr="arg1" ve="3">com.intangibleobject.securesettings.plugin</Str> <Str sr="arg2" ve="3">com.intangibleobject.securesettings.plugin.Activities.TabsActivity</Str> <Int sr="arg3" val="0"/> </Action> <Action sr="act2" ve="7"> <code>30</code> <Int sr="arg0" val="0"/> <Int sr="arg1" val="5"/> <Int sr="arg2" val="0"/> <Int sr="arg3" val="0"/> <Int sr="arg4" val="0"/> </Action> <Action sr="act3" ve="7"> <code>123</code> <Str sr="arg0" ve="3">./system/xbin/auto.sh</Str> <Int sr="arg1" val="0"/> <Int sr="arg2" val="1"/> <Str sr="arg3" ve="3"/> <Str sr="arg4" ve="3"/> <Str sr="arg5" ve="3"/> </Action> <Action sr="act4" ve="7"> <code>30</code> <Int sr="arg0" val="0"/> <Int sr="arg1" val="0"/> <Int sr="arg2" val="30"/> <Int sr="arg3" val="0"/> <Int sr="arg4" val="0"/> </Action> <Action sr="act5" ve="7"> <code>18</code> <App sr="arg0"> <appClass>com.cyjh.mobileanjian.activity.GuiActivity</appClass> <appPkg>com.cyjh.mobileanjian</appPkg> <label>按键精灵</label> </App> <Int sr="arg1" val="1"/> </Action> <Action sr="act6" ve="7"> <code>18</code> <App sr="arg0"> <appClass>com.tencent.wework.launch.LaunchSplashActivity</appClass> <appPkg>com.tencent.wework</appPkg> <label>企业微信</label> </App> <Int sr="arg1" val="1"/> </Action> <Action sr="act7" ve="7"> <code>1563799945</code> <Bundle sr="arg0"> <Vals sr="val"> <com.intangibleobject.securesettings.plugin.extra.BLURB>Keep Display On: Never</com.intangibleobject.securesettings.plugin.extra.BLURB> <com.intangibleobject.securesettings.plugin.extra.BLURB-type>java.lang.String</com.intangibleobject.securesettings.plugin.extra.BLURB-type> <com.intangibleobject.securesettings.plugin.extra.EXTRA_SPINNER_OPTION>0</com.intangibleobject.securesettings.plugin.extra.EXTRA_SPINNER_OPTION> <com.intangibleobject.securesettings.plugin.extra.EXTRA_SPINNER_OPTION-type>java.lang.String</com.intangibleobject.securesettings.plugin.extra.EXTRA_SPINNER_OPTION-type> <com.intangibleobject.securesettings.plugin.extra.SETTING>display_charge</com.intangibleobject.securesettings.plugin.extra.SETTING> <com.intangibleobject.securesettings.plugin.extra.SETTING-type>java.lang.String</com.intangibleobject.securesettings.plugin.extra.SETTING-type> <com.twofortyfouram.locale.intent.extra.BLURB>Keep Display On: Never</com.twofortyfouram.locale.intent.extra.BLURB> <com.twofortyfouram.locale.intent.extra.BLURB-type>java.lang.String</com.twofortyfouram.locale.intent.extra.BLURB-type> <net.dinglisch.android.tasker.subbundled>true</net.dinglisch.android.tasker.subbundled> <net.dinglisch.android.tasker.subbundled-type>java.lang.Boolean</net.dinglisch.android.tasker.subbundled-type> </Vals> </Bundle> <Str sr="arg1" ve="3">com.intangibleobject.securesettings.plugin</Str> <Str sr="arg2" ve="3">com.intangibleobject.securesettings.plugin.Activities.TabsActivity</Str> <Int sr="arg3" val="0"/> </Action> <Img sr="icn" ve="2"> <nme>hl_aaa_nixx_button</nme> </Img> </Task> </TaskerData>
在定时任务中,调用了一个内置的shell脚本./system/xbin/auto.sh,在该脚本中完成了几个简单的模拟点击操作,最终调起了按键精灵应用中的打卡脚本。脚本内如如下:
#!/system/bin/sh
#每次运行时,创建一个ClockInScreencap的文件夹,用于存放运行过程中的截图等信息,方便调试。
log_path=/sdcard/Liuxd_tool_logs/auto_run
flag_file=/sdcard/Liuxd_tool_logs/mobile_anjian_done.flag
rm -rf $flag_file
rm -rf $log_path
mkdir -p $log_path
date>$log_path/time.txt
#模拟滑动解锁
input swipe 200 1500 800 500 200
sleep 3
screencap -p $log_path/01.png
#启动按键精灵
am force-stop com.cyjh.mobileanjian
sleep 2
screencap -p $log_path/02.png
sleep 5
am start -n com.cyjh.mobileanjian/com.cyjh.mobileanjian.activity.GuiActivity
sleep 15
screencap -p $log_path/03.png
sleep 5
#启动按键精灵后,通过几步点击,启动预先写好的打卡脚本,剩下的就是按键精灵的事情了
input tap 973 1838
sleep 2
screencap -p $log_path/04.png
sleep 5
input tap 145 1300
sleep 2
screencap -p $log_path/05.png
sleep 5
input tap 100 444
sleep 2
screencap -p $log_path/06.png
sleep 5
input tap 534 1450
screencap -p $log_path/07.png
sleep 5
可以直接通过RE文件管理器,在system/xbin目录下完成上述脚本的编辑,或者在电脑端完成编辑后,再通过RE文件管理器放到system/xbin目录下。
然后通过RE文件器修改文件执行权限为777,修改方式:长按文件–>右上角菜单->权限设定->将所有权限都勾选即可。
该脚本的坐标都是写死的,所以需要根据手机的实际情况自行调试,目的就是最终调起按键精灵的脚本开始执行。
Taps:如果采用电脑端编辑后拖入的方式,会因为文件格式的问题导致每行最后多了个空格,执行时报错。解决办法,在电脑端编辑完成后,将文件转换为UNIX格式(即清除每行的\r换行符),或者在拖入后,通过RE文件管理器编辑删除每一行最后的空格。
按键精灵的打卡脚本
手动操作几遍打卡步骤,然后提取所有的操作点,通过脚本模拟完成。
为了是整个过程更健壮,要适当的加入时延和状态判断,重试机制等。这也是使用按键精灵,而不是直接使用shell脚本完成所有打卡步骤的原因,按键精灵很多功能可以帮助我们将脚本写的更完善,更健壮。
所以此处建议手动写脚本,而非通过录制功能完成,录制的脚本兼容性差,很容易出错。
Taps:由于是定时运行,为了避免每天的打卡时间相同或相近,可以在打卡开始时随机等待一段时间。
Function find_pic_with_retry(start_x, start_y, end_x, end_y, pic1, pic2) Dim find_x = -1, find_y = -1 Rem rem_retry_find Device.UnLock() Delay 1000 Dim pic = "Attachment:"&pic1 If pic2 <> Null Then pic = "Attachment:"&pic1&"|"&"Attachment:"&pic2 End If FindPic start_x, start_y, end_x, end_y, pic, "000000", 0, 0.8, find_x, find_y If find_x > -1 And find_y > -1 Then TracePrint "找到"&pic Else TracePrint "未找到"&pic If retry_times > 0 Then retry_times = retry_times - 1 Delay 10000 Goto rem_retry_find End If End If find_pic_with_retry = Array(find_x, find_y) End Function Function find_workbench Device.UnLock() Delay 1000 KillApp "com.tencent.wework" KeyPress "Home" Delay 1000 RunApp "com.tencent.wework" Delay 10000 FindPic 627,1870,723,1904, "Attachment:工作台.png|Attachment:工作台_选中.png", "000000", 0, 0.9, workbench_X, workbench_Y If workbench_X > -1 And workbench_Y > -1 Then TracePrint "找到工作台" Else TracePrint "未找到工作台" If retry_times > 0 Then KillApp "com.tencent.wework" retry_times = retry_times - 1 Call find_workbench() End If End If End Function Function echo_message(msg_str) TracePrint msg_str ShowMessage msg_str End Function Function end_script Delay 5000 KeepScreen False KeyPress "Power" EndScript End Function Function keep_screen_on(keep_time) While keep_time > 0 keep_time = keep_time - 1 For 12 KeyPress "Home" Delay 5000 Next echo_message keep_time&"分钟后启动..." Wend End Function Dim all_retry_times = 5 echo_message "一切从这里开始..." Dim wait_time Randomize wait_time = Int(((15-2+1) * Rnd()) + 1) echo_message "等待:"&wait_time&"分钟" //Traceprint wait_time keep_screen_on(wait_time) Rem Start Rem 查找工作台按钮 Dim workbench_X = -1, workbench_Y = -1 Dim retry_times=3 Call find_workbench() If workbench_X = -1 Or workbench_Y = -1 Then echo_message "未找到“工作台”" end_script End If echo_message "工作台按钮: "&workbench_X&", "&workbench_Y workbench_X = workbench_X + 40 Tap workbench_X, workbench_Y Rem 查找打卡菜单 Dim x_y = find_pic_with_retry(165,474,265,526, "打卡.png", Null) Dim clock_x = x_y(0) Dim clock_y = x_y(1) If clock_x = -1 Or clock_y = -1 Then echo_message "未找到“打卡”菜单" end_script End If echo_message "打卡菜单:"&clock_X&", "&clock_Y Tap clock_X, clock_Y Rem 查找上下班Table页 Delay 1000 x_y = find_pic_with_retry(207, 247, 337, 292, "上下班_选中.png", "上下班.png") Dim on_off_duty_x = x_y(0) Dim on_off_duty_y = x_y(1) If on_off_duty_x = -1 Or on_off_duty_y = -1 Then echo_message "未找到上下班Tabel页" end_script Else echo_message "上下班Table页:"&on_off_duty_x&", "&on_off_duty_y Tap on_off_duty_x, on_off_duty_y End If Rem 是否还在定位中 Dim time_out = 6 x_y = find_pic_with_retry(461,698,624,757, "定位中.png", Null) Dim location_x = x_y(0) Dim location_y = x_y(1) If location_x > -1 Or location_y > -1 Then echo_message "还在定位中,等待定位完成。" If time_out > 0 Then time_out = time_out - 1 Delay 10000 Goto 是否还在定位中 End If End If If location_x > -1 Or location_y > -1 Then echo_message "定位失败!" End If Rem 查找是否存在我要加班按钮 Delay 1000 x_y = find_pic_with_retry(371, 1790, 544, 1838, "我要加班.png", Null) Dim overtime_work_x = x_y(0) Dim overtime_work_y = x_y(1) overtime_work_x = overtime_work_x + 100 overtime_work_y = overtime_work_y + 20 If overtime_work_x = -1 Or overtime_work_y = -1 Then echo_message "没有找到我要加班的信息" Else //Tap overtime_work_x, overtime_work_y //Delay 1000 echo_message "非工作日,不需要打卡。" end_script End If Rem 查找是否存在不在打卡范围内的信息 x_y = find_pic_with_retry(173,696,912,760, "不在打卡范围内.png", Null) Dim not_in_x = x_y(0) Dim not_in_y = x_y(1) If not_in_x > -1 or not_in_y > -1 Then echo_message "不在打卡范围内,不做打卡操作。" end_script End If Rem 查找上班打卡按钮 Delay 10000 x_y = find_pic_with_retry(173, 696, 912, 760, "已在打卡范围.png", Null) Dim in_x = x_y(0) Dim in_y = x_y(1) If in_x = -1 or in_y = -1 Then echo_message "没有找到已在打卡范围内的信息。" End If Rem 查找上班打卡或下班打卡按钮 x_y = find_pic_with_retry(462, 1292, 620, 1337, "上班_下班打卡.png", Null) Dim clock_in_out_x = x_y(0) Dim clock_in_out_y = x_y(1) If clock_in_out_x = -1 Or clock_in_out_y = -1 Then echo_message "没有找到上班打卡或下班打卡的选项。" If all_retry_times > 0 Then all_retry_times = all_retry_times - 1 echo_message "打卡失败,再来一次......" KillApp "com.tencent.wework" Delay 1000 Goto Start End If Else echo_message "滴滴..." Tap clock_in_out_x, clock_in_out_y end_script End If
小结
在整个调试过程中,按键精灵完成打卡步骤其实是很简单的,稍微有点编程基础的都可以很轻松的完成。
比较困难的是如何完成定时运行,由于之前没有这方便经验,包括Tasker、Magisk、Secure Settings等都折腾了很久,其实上述几个工具,真的很强大,可以完成很多的自动化事务,有兴趣的可以自行研究。
再次声明,此文仅是出于爱好,尝试自动化技术用于日常事务的可行性。强烈建议大家按时上下班,好好工作,不要将上班流于形式,害人害己。