版权声明:本文为博主原创文章,转载请声明出处 https://blog.csdn.net/qq_16666847/article/details/84108226
越有钱的公司越不会在意这些打卡的字面东西,也不会让你天天写个日报啥的。不写还罚钱 迟到还扣钱。
emmm… 当一个公司开始计较这些东西的时候 ,他就开始走下坡路了。废话不多说。开始走起我们程序员的翻盘之路
你如果需要使用的:
- 安装上边的手机 软件安装包
- 下载 代码 文件
- 修改你需要上传的 ftp(非必要,你需要有一个ftp账号) ,或修改 邮件账号 密码(非必要,你需要有一个邮箱以及安装python,不过你可以用 bat 实现发送邮箱)
- 一个手机 ,一条数据线 ,连接 电脑 adb
- 开启任务管理器 定时开启 continue.bat 和 start.bat
- 嘿 ,其实不是 100行,得有个 500行吧
说一下实现的效果吧
- 长连接
- 打卡操作
最后 实现是 ftp 上传的截图 ,每天去看一下图片URL就可以了
我想过 最后能达到的效果就是 能通过 网站查看 自己有没有打卡,并 发送邮件 通知打卡成功。
你们get 到技能后 如果再做得细一点就是 异步回调 短信通知,等等
首先说一下在网上搜索到的各种实现方式
- 无障碍服务 AccessibilityService + ROOT 手机使用 ADB 命令 (适用于 root手机)
- 无障碍服务 AccessibilityService 模式 + 7.0手机的无障碍手势 dispatchGesture()(适用于7.0以后的手机)这是我反编译一个成型软件看到的方法,屌屌的
- 无障碍服务 AccessibilityService + 电脑 ADB 连接后 发送shell 指令(适用一切手机,缺陷得连电脑,不过电脑开机让他黑屏就好)
- 编译Android 源码 拿到 系统签名 使用 Instrumentation 类实现 (太麻烦)
- IOS手机修改系统地图就好
- 采用向日葵远程连接电脑,电脑Vysor 连接 手机 ,另一台手机 安装向日葵 控制端 手动打卡 (小白也可以做成功,但是 成本两手机 + 电脑,以前我用这个但是 不智能,所以我就写了此脚本 )
- 第一种方式 网上一大堆 这里不做介绍,小白式编程
- 第二种方式 新的 API 去看看就好了 谷歌搜索 无障碍手势 dispatchGesture()就能做
- 第三种方式 ,要做就做支持全款手机的,省的找个root手机 或7.0测试机费劲,所以我用的这个
电脑端需要做的:
- 把这几个文件 放到一个文件夹中
- adbshell.bat 是发送shell命令 并判断是否打卡
- click.bat 是上传打卡结果截图发送到服务器
- continue.bat 是重复发送adb命令 保持长连接,不然OPPO 这类手机 会10分钟自动关掉开发者模式需要加到任务计划中Dcontinue
- start.bat 是任务开始,需要加到任务计划中Dstart
- 任务计划程序 实现 定时开启 程序 (怎么添加任务?任务计划程序库上 右键 创建任务)
- 其实这样完事了
bat脚本代码实现:
- start.bat
:: 非工作日 才打卡,不是工作日退出就好
:: 路径为你放上边那几个bat文件的路径
cd C:\Users\newone\Desktop\shell
set day=%date:~-1%
if %day%==六 (
echo 非工作日
exit
)
if %day%==七 (
echo 非工作日
exit
)
adbshell.bat
exit
- continue.bat
:: 编码格式 GB2312
@echo off
echo 重复发送指令 保持 adb连接,一分钟跑一次 (非工作日执行)
set day=%date:~-1%
:a
if %day%==六 (
echo %day%
choice /t 5 /d y /n >nul
exit
)
if %day%==七 (
echo %day%
choice /t 5 /d y /n >nul
exit
)
adb shell dumpsys activity | findstr "mFocusedActivity"
choice /t 60 /d y /n >nul
goto a
- adbshell.bat 主要逻辑实现Android 开启服务 开启钉钉页面
:: 编码格式 GB2312
@echo off
echo 服务进行中...
:: 查看是否锁屏?
del screen.txt
choice /t 1 /d y /n >nul
adb shell "dumpsys window policy|grep mShowingLockscreen" >>screen.txt
choice /t 1 /d y /n >nul
set /p a=<screen.txt
echo %a%|findstr "mShowingLockscreen=true"
if %errorlevel% equ 0 (
:: 锁屏状态开屏(不可设置密码或指纹)
adb shell input keyevent 26
adb shell input keyevent 3
adb shell input swipe 300 1800 300 800
)
::adb shell settings get secure enabled_accessibility_services获取无障碍列表
::adb 指定 到 无障碍服务 并关闭其他已开启的无障碍
adb shell settings put secure enabled_accessibility_services top.jplayer.quick_test1.debug/top.jplayer.baseprolibrary.alive.service.CustomAccessibilityService
:: 1 开启服务
adb shell settings put secure accessibility_enabled 1
::回到手机主页
adb shell input keyevent 3
::开启服务
adb shell am startservice top.jplayer.quick_test1.debug/top.jplayer.baseprolibrary.alive.service.WhiteService>nul
choice /t 2 /d y /n >nul
adb shell am start -n com.alibaba.android.rimet/com.alibaba.android.rimet.biz.SplashActivity>nul
choice /t 10 /d y /n >nul
:a
set HOUR=%time:~0,2%
set MINUTE=%time:~3,2%
set SECOND=%time:~6,2%
set CURRENT_TIME=%HOUR%:%MINUTE%:%SECOND%
set yy=%date:~0,4%
set mm=%date:~5,2%
set dd=%date:~8,2%
set hh=%time:~0,2%
set mn=%time:~3,2%
set ss=%time:~6,2%
set filename=%yy%%mm%%dd%%hh%%mn%%ss%
set delay=5
set /a am=%random%
set /a am=am%%600+1
set /a pm=%random%
set /a pm=pm%%600+1
:: 打卡操作
if %HOUR% LEQ 16 if %HOUR% GEQ 11 (
echo 非打卡时间
echo 等待时间:%am%
echo 等待时间:%pm%
adb shell dumpsys activity | findstr "mFocusedActivity">nul
echo adb 指令重复发送中
choice /t 60 /d y /n >nul
goto a
)
if %HOUR% LEQ 10 if %HOUR% GEQ 8 (
echo 上班打卡
echo 等待时间:%am%
choice /t %am% /d y /n >nul
adb shell input tap 540 800
click.bat
pause
)
if %HOUR% GEQ 18 (
echo 下班打卡
echo 等待时间:%pm%
choice /t %pm% /d y /n >nul
adb shell input tap 540 1200
click.bat
pause
)
goto a
- click.bat ftp 上传 截图 到服务器
:: 获取图片
set delay=5
choice /t %delay% /d y /n >nul
adb shell screencap -p sdcard/screen.png
adb pull sdcard/screen.png
adb shell rm sdcard/screen.png
move /-y screen.png %~sdp0
::ren "screen.png" "%filename%.png"
:: ftp 上传
choice /t %delay% /d y /n >nul
echo open 60.205.30.49>>ftp.up
::ftp 账号
echo bxu2359240250>>ftp.up
::我怎么会告诉你我的ftp密码
echo ********>>ftp.up
:: 刷成 bin 模式 不然图片上传出问题
echo bin>>ftp.up
echo cd /htdocs/>>ftp.up
echo put "screen.png">>ftp.up
echo close>>ftp.up
echo bye>>ftp.up
FTP -s:ftp.up
choice /t %delay% /d y /n >nul
del screen.png
del ftp.up
del screen.txt
python _email.py
pause
- _email.py发送邮件到指定邮箱
#coding:utf-8 #强制使用utf-8编码格式
import smtplib #加载smtplib模块
from email.mime.text import MIMEText
import datetime
from email.utils import formataddr
my_sender='[email protected]' #发件人邮箱账号,为了后面易于维护,所以写成了变量
my_user='[email protected]' #收件人邮箱账号,为了后面易于维护,所以写成了变量
def mail():
ret=True
try:
strTime=datetime.datetime.now().strftime('%Y-%m-%d')
print(strTime)
body="<a href='https://jplayer.top/screen.png'>前往查看打卡记录</a>"
msg=MIMEText(body,"html","utf-8")
msg['From']=formataddr(["来着自动打卡",my_sender]) #括号里的对应发件人邮箱昵称、发件人邮箱账号
msg['To']=formataddr(["敬爱的 刘大大",my_user]) #括号里的对应收件人邮箱昵称、收件人邮箱账号
msg['Subject']="打卡成功提醒" #邮件的主题,也可以说是标题
server=smtplib.SMTP("smtp.qq.com",25) #发件人邮箱中的SMTP服务器,端口是25
server.login(my_sender,"*******") #括号中对应的是发件人邮箱账号、邮箱密码
server.sendmail(my_sender,[my_user,],msg.as_string()) #括号中对应的是发件人邮箱账号、收件人邮箱账号、发送邮件
server.quit() #这句是关闭连接的意思
except Exception as e: #如果try中的语句没有执行,则会执行下面的ret=False
ret=False
print(e)
return ret
ret=mail()
if ret:
print("ok") #如果发送成功则会返回ok,稍等20秒左右就可以收到邮件
else:
print("filed") #如果发送失败则会返回filed
android 端核心代码实现:
- 自定义 AccessibilityService
public class CustomAccessibilityService extends AccessibilityService {
/**
* 当启动服务的时候就会被调用
*/
@Override
protected void onServiceConnected() {
super.onServiceConnected();
settingAccessibilityInfo();
}
/**
* 监听窗口变化的回调
*/
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
//根据事件回调类型进行处理
int eventType = event.getEventType();
switch (eventType) {
//当通知栏发生改变时
case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:
ToastUtils.init().showQuickToast("通知栏的状态发生改变");
break;
//当窗口的状态发生改变时
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
ToastUtils.init().showQuickToast("窗口的状态发生改变");
break;
/*窗口变化*/
case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
Observable.timer(1, TimeUnit.SECONDS).compose(new IoMainSchedule<>()).subscribe(aLong -> {
String id = "com.alibaba.android.rimet:id/home_bottom_tab_button_work";
clickById(findById(id));
});
Observable.timer(1, TimeUnit.SECONDS).compose(new IoMainSchedule<>()).subscribe(aLong -> {
String id = "com.alibaba.android.rimet:id/oa_entry_title";
clickById(findById(id));
});
break;
}
}
private void clickByWeb(String text) {
AccessibilityNodeInfo mAccessibilityNodeInfo = this.getRootInActiveWindow();
for (int i = 0; i < mAccessibilityNodeInfo.getChildCount(); i++) {
AccessibilityNodeInfo child = mAccessibilityNodeInfo.getChild(i);
CharSequence contentDescription = child.getContentDescription();
LogUtil.str(contentDescription);
}
//todo 7.0 dispatchGesture
}
private AccessibilityNodeInfo findById(String id) {
AccessibilityNodeInfo mAccessibilityNodeInfo = this.getRootInActiveWindow();
AccessibilityNodeInfo ac = null;
if (mAccessibilityNodeInfo == null)
return null;
List<AccessibilityNodeInfo> mNodeInfos = mAccessibilityNodeInfo.findAccessibilityNodeInfosByViewId(id);
if (mNodeInfos == null || mNodeInfos.isEmpty())
return null;
for (AccessibilityNodeInfo info : mNodeInfos) {
CharSequence contentDescription = info.getContentDescription();
if (info.getContentDescription() != null) {
boolean contains = contentDescription.toString().contains("工作");
if (contains) {
ac = info;
}
}
CharSequence text = info.getText();
if (text != null && text.toString().contains("考勤打卡")) {
ac = info;
}
}
return ac;
}
private void clickById(AccessibilityNodeInfo ac0) {
if (ac0 != null) {
CharSequence contentDescription = ac0.getContentDescription();
if (contentDescription != null) {
boolean contains = contentDescription.toString().contains("工作");
if (contains) {
ac0.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
CharSequence text = ac0.getText();
if (text != null && "考勤打卡".equals(text)) {
ac0.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
}
private void clickById(String id) {
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
if (nodeInfo != null) {
List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByViewId(id);
LogUtil.str(list + "----------what");
if (list != null && !list.isEmpty()) {
AccessibilityNodeInfo accessibilityNodeInfo = list.get(0);
LogUtil.str(accessibilityNodeInfo + "-----");
}
}
}
// dispatchGesture()
private void clickByText(String text) {
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText(text);
AccessibilityNodeInfo ac0;
for (AccessibilityNodeInfo info : list) {
ac0 = info;
if (text.equals(ac0.getContentDescription())) {
ac0.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
LogUtil.str(ac0);
}
}
/**
* 中断服务的回调
*/
@Override
public void onInterrupt() {
}
private void settingAccessibilityInfo() {
String[] packageNames = {"com.alibaba.android.rimet"};
AccessibilityServiceInfo mAccessibilityServiceInfo = new AccessibilityServiceInfo();
// 响应事件的类型,这里是全部的响应事件(长按,单击,滑动等)
mAccessibilityServiceInfo.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
// 反馈给用户的类型,这里是语音提示
mAccessibilityServiceInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN;
// 过滤的包名
mAccessibilityServiceInfo.packageNames = packageNames;
setServiceInfo(mAccessibilityServiceInfo);
}
}
- 开启服务代码
public class WhiteService extends Service {
private final static int FOREGROUND_ID = 1000;
@Override
public void onCreate() {
LogUtil.e("WhiteService->onCreate");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
LogUtil.e("WhiteService->onStartCommand");
Intent activityIntent = new Intent(BuildConfig.APPLICATION_ID + ".main.live");
Notification notification = NotificationUtil.init(this).pendingIntent(activityIntent, "白色服务", "服务运行中...");
startForeground(FOREGROUND_ID, notification);
Intent intent1 = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
intent1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent1);
startOutActivity("com.alibaba.android.rimet", "com.alibaba.android.rimet.biz.SplashActivity");
return START_STICKY;
}
public void startOutActivity(String sPkg, String tClass) {
try {
ComponentName cn = new ComponentName(sPkg, tClass);
Intent i = new Intent();
i.setComponent(cn);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(i);
} catch (Exception e) {
e.printStackTrace();
ToastUtils.init().showQuickToast("无法查找Activity");
}
}
@Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onDestroy() {
LogUtil.e("WhiteService->onDestroy");
super.onDestroy();
}
}