1. 程式人生 > >100行程式碼 實現 釘釘實現自動打卡

100行程式碼 實現 釘釘實現自動打卡

越有錢的公司越不會在意這些打卡的字面東西,也不會讓你天天寫個日報啥的。不寫還罰錢 遲到還扣錢。
emmm… 當一個公司開始計較這些東西的時候 ,他就開始走下坡路了。廢話不多說。開始走起我們程式設計師的翻盤之路


你如果需要使用的:

  1. 安裝上邊的手機 軟體安裝包
  2. 下載 程式碼 檔案
  3. 修改你需要上傳的 ftp(非必要,你需要有一個ftp賬號) ,或修改 郵件賬號 密碼(非必要,你需要有一個郵箱以及安裝python,不過你可以用 bat 實現傳送郵箱)
  4. 一個手機 ,一條資料線 ,連線 電腦 adb
  5. 開啟工作管理員 定時開啟 continue.bat 和 start.bat

  • 嘿 ,其實不是 100行,得有個 500行吧

說一下實現的效果吧

在這裡插入圖片描述
在這裡插入圖片描述

  • 長連線
    1
  • 打卡操作
  • 2
    最後 實現是 ftp 上傳的截圖 ,每天去看一下圖片URL就可以了
    我想過 最後能達到的效果就是 能通過 網站檢視 自己有沒有打卡,並 傳送郵件 通知打卡成功。
    你們get 到技能後 如果再做得細一點就是 非同步回撥 簡訊通知,等等

首先說一下在網上搜索到的各種實現方式

  1. 無障礙服務 AccessibilityService + ROOT 手機使用 ADB 命令 (適用於 root手機)
  2. 無障礙服務 AccessibilityService 模式 + 7.0手機的無障礙手勢 dispatchGesture()(適用於7.0以後的手機)這是我反編譯一個成型軟體看到的方法,屌屌的
  3. 無障礙服務 AccessibilityService + 電腦 ADB 連線後 傳送shell 指令(適用一切手機,缺陷得連電腦,不過電腦開機讓他黑屏就好)
  4. 編譯Android 原始碼 拿到 系統簽名 使用 Instrumentation 類實現 (太麻煩)
  5. IOS手機修改系統地圖就好
  6. 採用向日葵遠端連線電腦,電腦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();
    }
}

主要是實現手段 ,不是拿過來用