├── .gitignore ├── .DS_Store ├── dingding ├── coderdingding_auto-masterscreen ├── package.json ├── config.js ├── README.md └── app.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /screen -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxingheng/dingding_auto/HEAD/.DS_Store -------------------------------------------------------------------------------- /dingding: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxingheng/dingding_auto/HEAD/dingding -------------------------------------------------------------------------------- /coderdingding_auto-masterscreen: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxingheng/dingding_auto/HEAD/coderdingding_auto-masterscreen -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dingding_auto", 3 | "version": "1.0.0", 4 | "description": "钉钉自动打卡", 5 | "main": "app.js", 6 | "scripts": {}, 7 | "keywords": [ 8 | "dingding", 9 | "dd", 10 | "node" 11 | ], 12 | "author": "wuxh", 13 | "license": "ISC", 14 | "dependencies": { 15 | "baidu-aip-sdk": "^2.3.3", 16 | "mime-types": "^2.1.21", 17 | "moment": "^2.22.2", 18 | "nodemailer": "^4.6.8", 19 | "shelljs": "^0.8.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // adb 的安装绝对路径 3 | directory: 'D:\adb\Universal Adb Driver', 4 | // 上班时间 5 | go_hour: 9, 6 | // 下班时间 7 | back_hour: 18.5, 8 | // 点击工作的像素位置点 9 | work_position: [540, 1818], 10 | // 点击考勤打卡的像素未位置点 11 | check_position: [678, 1618], 12 | // 上班打卡的像素位置点 13 | work_start: [552, 801], 14 | // 下班打卡的像素位置点 15 | work_off: [538, 1223], 16 | // 每一步的时间 17 | step_time: 7000, 18 | // qq邮箱 打卡成功 将只用qq邮箱给自己发一封邮件 19 | email: '1228678518@qq.com', 20 | // 邮箱密钥 21 | email_token: 'dzurdbumhdlgichd', 22 | // 你的设备代号 adb devices 可以获取到 23 | devices: '91QEBP8563ST', 24 | // 项目 screen 的跟目录绝对路径 25 | screen_path: 'D:/dingdingdaka/dingding_auto-master/screen' 26 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # 钉钉自动打 001 5 | ---- 6 | 钉钉自动上下班打卡辅助 7 | ---- 8 | ---- 9 | 基于windows10 ,node.10,adb,安卓手机实现。需要安装adb 和 node运行环境。原理:通过node逻辑化的调度cmd来执行adb来操作安卓手机。 10 | 11 | # 准备 windows电脑一台;能装钉钉的安卓手机一部; 12 | 13 | ## 1.安装 ADB: 14 | ---- 15 | windows版本adb下载地址: 16 | [https://adb.clockworkmod.com/](https://adb.clockworkmod.com/) 17 | ### 安装完成后,把adb.exe所在文件夹路径加入环境变量Path中。 18 | ![1.添加adb到path](https://github.com/1414044032/imgs/blob/master/adbinstall.png) 19 | ![2.添加adb到path](https://github.com/1414044032/imgs/blob/master/adbpath.png) 20 | ![3.添加adb到path](https://github.com/1414044032/imgs/blob/master/path1.png) 21 | ### 手机需要打开开发者选项,通过USB数据线连接电脑。 22 | ### 打开CMD命令行,输入“adb devices”,能成功显示手机信息即可。 23 | 24 | ## 2.安装node 25 | ---- 26 | windows版本adb下载地址: 27 | [https://nodejs.org/zh-cn/](https://nodejs.org/zh-cn/) 28 | ### 打开CMD命令行,输入“node -v”,能成功显示手机信息即可。 29 | 30 | 31 | ## 3.获取屏幕尺寸,设置模拟点击位置: 32 | 可以自己截图 把像素点的位置填写到 config.js 里面 33 | 34 | ---- 35 | ### 像素点的获取: 36 | ---- 37 | ![screen1](https://github.com/1414044032/imgs/blob/master/screen1.png) 38 | ![screen2](https://github.com/1414044032/imgs/blob/master/screen2.png) 39 | ### 画图工具打开保存到电脑的设备截图: 40 | ---- 41 | ![screen3](https://github.com/1414044032/imgs/blob/master/screen3.png) 42 | ![screen4](https://github.com/1414044032/imgs/blob/master/screen4.png) 43 | ![screen5](https://github.com/1414044032/imgs/blob/master/screen5.png) 44 | 45 | 46 | ## 4.运行: 或者用 pm2 挂起 47 | ---- 48 | 运行: 'node app.js' 49 | ---- 50 | ![screen7](https://github.com/1414044032/imgs/blob/master/screen7.png) 51 | 52 | ## 5.参考资料: 53 | ---- 54 | [https://github.com/1414044032/DingDingAutoPlayCard](https://github.com/1414044032/DingDingAutoPlayCard) 55 | ---- 56 | 57 | 测试命令 58 | 59 | ### 60 | 61 | 截屏 62 | 63 | ``` 64 | adb shell screencap -p sdcard/screen.png 65 | adb pull sdcard/screen.png E:\dingding 66 | ``` 67 | 68 | ### 69 | 70 | 点亮屏幕 71 | 72 | ``` 73 | adb shell input keyevent 26 E:\Universal Adb Driver 74 | ``` 75 | 76 | ### 77 | 78 | 打开钉钉 79 | 80 | ``` 81 | adb shell monkey -p com.alibaba.android.rimet -c android.intent.category.LAUNCHER 1 E:\Universal Adb Driver 82 | ``` 83 | 84 | ### 85 | 86 | 关闭钉钉 87 | 88 | ``` 89 | adb shell am force-stop com.alibaba.android.rimet E:\Universal Adb Driver 90 | ``` 91 | 92 | ### 93 | 94 | 返回桌面 95 | 96 | ``` 97 | adb shell input keyevent 3 E:\Universal Adb Driver 98 | ``` 99 | 100 | ### 101 | 102 | 点击工作 103 | 104 | ``` 105 | adb shell input tap 540 1818 106 | ``` 107 | 108 | ### 109 | 110 | 点击考勤打卡 111 | 112 | ``` 113 | adb shell input tap 678 1626 114 | ``` 115 | 116 | ### 117 | 118 | 点击上班打卡 ------- 119 | 120 | ``` 121 | adb shell input tap 678 1626 122 | ``` 123 | 124 | ### 125 | 126 | 点击上班打卡 ------- 127 | 128 | ``` 129 | adb shell input tap 535 1233 130 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const shell = require('shelljs'); 2 | const moment = require('moment'); 3 | const conf = require('./config'); 4 | const nodemailer = require("nodemailer"); 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | const mineType = require('mime-types'); 8 | 9 | const AipOcrClient = require("baidu-aip-sdk").ocr; 10 | // 设置APPID/AK/SK 11 | const APP_ID = "14617464"; 12 | const API_KEY = "pxUG2m2KBbN9eq9EhZxhaRc2"; 13 | const SECRET_KEY = "kmWAi5qmsIs6aH4GkWHvZVq9bUBKkRAy"; 14 | 15 | // 新建一个对象,建议只保存一个对象调用服务接口 16 | const client = new AipOcrClient(APP_ID, API_KEY, SECRET_KEY); 17 | 18 | const HttpClient = require("baidu-aip-sdk").HttpClient; 19 | 20 | // 设置request库的一些参数,例如代理服务地址,超时时间等 21 | // request参数请参考 https://github.com/request/request#requestoptions-callback 22 | HttpClient.setRequestOptions({ 23 | timeout: 5000 24 | }); 25 | 26 | // 也可以设置拦截每次请求(设置拦截后,调用的setRequestOptions设置的参数将不生效), 27 | // 可以按需修改request参数(无论是否修改,必须返回函数调用参数) 28 | // request参数请参考 https://github.com/request/request#requestoptions-callback 29 | HttpClient.setRequestInterceptor(function (requestOptions) { 30 | // 查看参数 31 | // console.log(requestOptions) 32 | // 修改参数 33 | requestOptions.timeout = 5000; 34 | // 返回参数 35 | return requestOptions; 36 | }); 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | shell.echo('自动打开已启动...'); 48 | 49 | 50 | const random = () => (Math.floor(Math.random() * 15) + 15) 51 | // const random = () => (1) 52 | 53 | const get_start_time = () => moment(moment().format('YYYY-MM-DD')).add(conf.go_hour, 'hour').subtract((random()), 'minute').format('YYYY-MM-DD HH:mm'); 54 | const get_last_time = () => moment(moment().format('YYYY-MM-DD')).add(conf.back_hour, 'hour').add((2), 'minute').format('YYYY-MM-DD HH:mm'); 55 | let default_delay = 1000; 56 | let start_time = get_start_time(); 57 | let last_time = get_last_time(); 58 | let num = 10; 59 | 60 | 61 | const logs = (str) => { 62 | console.log(`log: ${str}`); 63 | } 64 | 65 | const delay = (fun) => { 66 | default_delay += conf.step_time; 67 | setTimeout(() => { 68 | fun(); 69 | }, default_delay); 70 | } 71 | 72 | // 点亮屏幕 73 | const lighten = () => { 74 | delay(() => { 75 | logs('点亮屏幕'); 76 | shell.exec(`adb -s ${conf.devices} shell input keyevent 26 ${conf.directory}`); 77 | }) 78 | } 79 | 80 | // 返回桌面 81 | const back_desktop = () => { 82 | delay(() => { 83 | logs('返回桌面'); 84 | shell.exec(`adb -s ${conf.devices} shell input keyevent 3 ${conf.directory}`); 85 | }) 86 | } 87 | 88 | // 关闭钉钉 89 | const close_dd = () => { 90 | delay(() => { 91 | logs('关闭钉钉'); 92 | shell.exec(`adb -s ${conf.devices} shell am force-stop com.alibaba.android.rimet ${conf.directory}`); 93 | }) 94 | } 95 | 96 | // 打开钉钉 97 | const open_dd = () => { 98 | delay(() => { 99 | logs('打开钉钉'); 100 | shell.exec(`adb -s ${conf.devices} shell monkey -p com.alibaba.android.rimet -c android.intent.category.LAUNCHER 1 ${conf.directory}`); 101 | }) 102 | } 103 | 104 | // click_imitate 模拟点击 105 | const click_imitate = (position) => { 106 | delay(() => { 107 | shell.exec(`adb -s ${conf.devices} shell input tap ${position.map((d) => d)}`.replace(/,/g, ' ')); 108 | }) 109 | } 110 | // 点击钉钉工作 111 | const click_work = () => { 112 | logs('点击钉钉工作'); 113 | click_imitate(conf.work_position); 114 | } 115 | 116 | // 点击钉钉考勤打卡 117 | const click_check = () => { 118 | logs('点击考勤打卡'); 119 | click_imitate(conf.check_position); 120 | } 121 | 122 | // 下班打卡 123 | const work_off = () => { 124 | logs('点击下班打卡'); 125 | click_imitate(conf.work_off); 126 | } 127 | // 上班打卡 128 | const start_work = () => { 129 | logs('点击上班打卡'); 130 | click_imitate(conf.work_start); 131 | } 132 | 133 | // 清除缓存 134 | const clear_cache = () => { 135 | logs('清除缓存'); 136 | default_delay = 1000; 137 | num = num + 1; 138 | } 139 | 140 | // 截图 并保存指定路径 141 | const printscreen = () => { 142 | delay(() => { 143 | logs('截图并保存'); 144 | shell.exec(`adb -s ${conf.devices} shell screencap -p sdcard/screen${num}.png`); 145 | shell.exec(`adb -s ${conf.devices} pull sdcard/screen${num}.png ${conf.screen_path}`); 146 | // shell.exec(`adb pull sdcard/screen${num}.png E:/study/Electron/Angular-electron/new-hoslink-client-core/DingDingAutoPlayCard/dingding_node/screen`); 147 | 148 | }) 149 | } 150 | 151 | // 发送到邮箱 152 | const send_email = () => { 153 | delay(() => { 154 | logs('发送到邮箱'); 155 | let filePath = path.resolve(`screen/screen${num}.png`); 156 | let image = fs.readFileSync(filePath); 157 | let text = ''; 158 | image = new Buffer(image).toString('base64'); 159 | // 调用通用文字识别, 图片参数为本地图片 160 | client.generalBasic(image).then(function (result) { 161 | // console.log(JSON.stringify(result)); 162 | const v = result.words_result.filter(d => d.words.search('打卡时间') !== -1); 163 | text = v.map(d => d.words); 164 | let base64 = 'data:' + mineType.lookup(filePath) + ';base64,' + image; 165 | const transporter = nodemailer.createTransport(`smtps://${conf.email}:${conf.email_token}@smtp.qq.com`); 166 | const mailOptions = { 167 | from: `${conf.email}`, //发信邮箱 168 | to: `${conf.email}`, //接收者邮箱 169 | subject: "打卡截图", //邮件主题 170 | text: "Hello!", 171 | html: `

${text}

` 172 | }; 173 | transporter.sendMail(mailOptions, function (error, info) { 174 | if (error) { 175 | return console.log(error); 176 | } 177 | logs('Message sent: ' + info.response); 178 | }); 179 | start_time = get_start_time(); 180 | last_time = get_last_time(); 181 | }).catch(function (err) { 182 | // 如果发生网络错误 183 | console.log(err); 184 | }); 185 | }) 186 | } 187 | 188 | 189 | 190 | // 下班打卡流程 191 | const Work_flow = function () { 192 | clear_cache(); 193 | lighten() 194 | back_desktop(); 195 | close_dd(); 196 | open_dd(); 197 | click_work(); 198 | click_check(); 199 | work_off(); 200 | printscreen(); 201 | send_email(); 202 | } 203 | 204 | // 上班打卡流程 205 | const start_work_flow = function () { 206 | clear_cache(); 207 | lighten() 208 | back_desktop(); 209 | close_dd(); 210 | open_dd(); 211 | click_work(); 212 | click_check(); 213 | start_work(); 214 | printscreen(); 215 | send_email(); 216 | } 217 | 218 | // 周末 219 | const isWeekend = () => { 220 | if(new Date().getDay() == 6 || new Date().getDay() == 0){ 221 | return true; 222 | }else{ 223 | return false; 224 | } 225 | } 226 | 227 | // start_work_flow(); 228 | const run = () => { 229 | if ((new Date().getHours()) < conf.go_hour) { 230 | logs(`下次上班打卡时间 ${start_time}`) 231 | } else { 232 | logs(`下次下班打卡时间 ${last_time}`) 233 | } 234 | setInterval(() => { 235 | // 每天重置 打卡时间 236 | if (moment().format('HH:mm') === '00:01' || moment().format('HH:mm') === '00:02' || moment().format('HH:mm') === '00:03') { 237 | start_time = get_start_time(); 238 | last_time = get_last_time(); 239 | } 240 | if ((new Date().getHours()) < conf.go_hour) { 241 | logs(`下次上班打卡时间 ${start_time}`) 242 | } else { 243 | logs(`下次下班打卡时间 ${last_time}`) 244 | } 245 | if(!isWeekend()){ 246 | if (moment().format('YYYY-MM-DD HH:mm') === start_time || moment().add(1, 'min').format('YYYY-MM-DD HH:mm') === start_time|| moment().add(2, 'min').format('YYYY-MM-DD HH:mm') === start_time) { 247 | start_work_flow(); 248 | } else if (moment().format('YYYY-MM-DD HH:mm') === last_time || moment().add(1, 'min').format('YYYY-MM-DD HH:mm') === start_time|| moment().add(2, 'min').format('YYYY-MM-DD HH:mm') === start_time) { 249 | Work_flow(); 250 | } 251 | } 252 | }, 1000 * 60 * 3) 253 | } 254 | run(); --------------------------------------------------------------------------------