├── README.md └── src └── index.js /README.md: -------------------------------------------------------------------------------- 1 | # YiFuDaoChecker 2 | > 奕辅导自动打卡脚本 3 | 4 | ## 使用方法 5 | 6 | ### 0. 克隆项目到本地 7 | 8 | ```bash 9 | git clone git@github.com:Chorer/YiFuDaoChecker-cloudFunction.git 10 | ``` 11 | 12 | ### 1. 获取 token 13 | 14 | 1)**获取 pushPlus 的 notifyToken**:微信搜索公众号“pushplus 推送加”,关注后即可生成属于自己的 notifyToken,后面需要用到。 15 | 16 | 2)**获取奕辅导的 accessToken**:利用 Fiddler 抓包获取奕辅导的 accessToken,后面需要用到。 17 | 18 | ### 2. 创建 leanCloud 云存储应用 19 | 20 | 1)[注册 leanCloud 账号](https://console.leancloud.app/) (注意是国际版的 leanCloud,因为国内版使用云存储需要绑定域名) 21 | 22 | 2)到控制台新建应用,应用名字随意,应用版本选择 **开发版** 23 | 24 | 3)进入应用,点击左侧的“数据存储 ➡ 结构化数据”,新建 Class:名称为“Answers”,Class 访问权限为“所有用户”,下面的 ACL 权限选择“无限制” 25 | 26 | 4)进入刚才创建的 Class,添加新列,列名称是“answers”,列类型是“Array”;之后添加新行,值空着不填。注意这时候 objectId 会有一个值 27 | 28 | 5)点击左侧的“设置 ➡ 应用凭证”,记住 appId 和 appKey 的值(**请务必自己保管好,不要泄露**),后面需要用到 29 | 30 | ### 3. 创建云函数 31 | 32 | 1)注册腾讯云账号并登录,进行实名认证 33 | 34 | 2)进入[这个链接](https://console.cloud.tencent.com/scf/list?rid=1&ns=default),选择 “新建云函数” ➔ “自定义创建”,函数名随便起一个,运行环境选择 `Nodejs12.16`,提交方法选择“本地上传文件夹”,选中 `YiFuDaoChecker-cloudFunction/src` 文件夹上传,点击“完成”即可创建云函数。 35 | 36 | ### 4. 修改配置文件 37 | 38 | 到刚才新创建的云函数中,打开 `src/checker.js` 文件进行修改: 39 | 40 | 1)修改第 2 行的 `accessToken` 为之前获得的奕辅导的 accessToken 41 | 42 | 2)修改第 3 行的 `notifyToken` 为之前获得的 pushplus 的 notifyToken 43 | 44 | 3)修改第 4 行的 `appId` 为之前复制的 leanCloud 的 appId 45 | 46 | 4)修改第 5 行的 `appKey` 为之前复制的 leanCloud 的 appKey 47 | 48 | ### 5. 安装依赖 49 | 50 | 到刚才新创建的云函数中,`ctrl + shift + ~` 新建终端。 51 | 52 | 通过如下命令安装 axios 和 leancloud-storage: 53 | 54 | ```bash 55 | cd src 56 | npm init -y 57 | npm i axios --save 58 | npm i leancloud-storage --save 59 | ``` 60 | 61 | ### 6. 部署和测试 62 | 63 | 1)首次使用此脚本,**请务必先手动打卡一次,或者确保今天已经打卡**(这是为了能够在数据库中自动存入打卡数据) 64 | 65 | 2)在确保今天已经打卡后,点击云函数下面的“部署”和“测试”,如果微信有收到 pushPlus 公众号发来的打卡结果通知,说明设置没有问题。 66 | 67 | ### 7. 实现自动打卡 68 | 69 | 点击控制台左侧的“触发管理”,新建一个云函数触发器。设置如下: 70 | 71 | ![](https://myblog-1258623898.cos.ap-chengdu.myqcloud.com/2021%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/0.jpg) 72 | 73 | 触发时间使用的是 Cron 表达式,这里的意思是每天早上 8 点触发(打卡)一次,可以自己按需修改。 74 | 75 | 最后点击提交就可以了,以后到点了就会自动打卡并把打卡结果发到你的微信上。 76 | 77 | ## Q & A 78 | 79 | **1)accessToken 的有效期多久?** 80 | 81 | 有效期六天,失效了重新抓包更新即可 82 | 83 | **2)如何修改打卡数据?** 84 | 85 | 每次打卡时,默认会提交使用者初次打卡时提交的数据。如因情况有变而需要修改打卡数据,请手动将 leanCloud 中 answers 列的值重置为空数组 `[]`,之后再手动打卡,脚本会自动将新数据写入数据库中 86 | 87 | ## ⚠️ 免责声明 88 | 89 | - 此脚本仅做正常情况下免去手动打卡之用,**请勿瞒报、误报、漏报** 90 | - 如使用者存在以下异常情况:位于风险地区、接触确诊病例、接触疑似病例、本人疑似患病、本人确诊患病等,请**立即停止使用此脚本**,并在小程序中**如实填报** 91 | - 使用者请务必不要泄露自己的 accessToken、notifyToken、appKey 92 | - 使用此脚本产生的任何问题**由使用者负责**,与作者无关 93 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // 配置项 2 | const accessToken = '在这里填入奕辅导的 accessToken' 3 | const notifyToken = '在这里填入 pushPlus 的 notifyToken' 4 | const appId = '在这里填入 leanCloud 的 appId' 5 | const appKey = '在这里填入 leanCloud 的 appKey' 6 | 7 | // 依赖 8 | const AV = require('leancloud-storage') 9 | AV.init({ appId, appKey }) 10 | const axios = require('axios') 11 | 12 | // 公共请求头 13 | const commonHeaders = { 14 | "accessToken": accessToken, 15 | "Host": "yfd.ly-sky.com", 16 | "Connection": "keep-alive", 17 | "Accept-Encoding": "gzip, deflate, br", 18 | "Content-Type": "application/json;charset=UTF-8", 19 | "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36 MicroMessenger/7.0.9.501 NetType/WIFI MiniProgramEnv/Windows WindowsWechat", 20 | "userAuthType": "MS", 21 | "Referer": "https://servicewechat.com/wx217628c7eb8ec43c/20/page-frame.html" 22 | } 23 | 24 | // 获取自己问卷的 id 25 | const getQuestionnaireIdUrl = 'https://yfd.ly-sky.com/ly-pd-mb/form/api/healthCheckIn/client/stu/index' 26 | const getQuestionnaireId = () => { 27 | return axios({ 28 | method: 'get', 29 | url: getQuestionnaireIdUrl, 30 | headers: commonHeaders 31 | }) 32 | } 33 | 34 | // 获取今日问卷 35 | const getQuestionnaire = id => { 36 | const getQuestionnaireUrl = `https://yfd.ly-sky.com/ly-pd-mb/form/api/questionnairePublish/${id}/getDetailWithAnswer` 37 | return axios({ 38 | method: 'get', 39 | url: getQuestionnaireUrl, 40 | headers: { 41 | ...commonHeaders, 42 | "Content-Type": "application/x-www-form-urlencoded" 43 | } 44 | }) 45 | } 46 | 47 | // 获取数据库中的问卷答案 48 | const getAnswers = async (questionnaireId) => { 49 | const obj = await new AV.Query('Answers').first(); 50 | // 如果数据库没有缓存答案,则请求并缓存 51 | if (!obj.get('answers')) { 52 | const questionnaireRes = await getQuestionnaire(questionnaireId) 53 | let answers = questionnaireRes.data.data.answerInfoList 54 | if (!answers || !answers.length) { 55 | return false 56 | } else { 57 | answers = answers.map(item => ({ 58 | subjectId: item.subjectId, 59 | subjectType: item.subjectType, 60 | [item.subjectType]: item[item.subjectType] 61 | })) 62 | obj.set('answers', answers) 63 | obj.save() 64 | } 65 | } 66 | return { 67 | "answerInfoList": obj.get('answers') 68 | } 69 | } 70 | 71 | // 提交表单 72 | const submitAnswersUrl = 'https://yfd.ly-sky.com/ly-pd-mb/form/api/answerSheet/saveNormal' 73 | const submitAnswers = async (questionnaireId,answers) => { 74 | return axios({ 75 | method: 'post', 76 | url: submitAnswersUrl, 77 | headers: commonHeaders, 78 | data: { 79 | "questionnairePublishEntityId": questionnaireId, 80 | ...answers 81 | } 82 | }) 83 | } 84 | 85 | // 推送通知 86 | const notifyUrl = 'http://www.pushplus.plus/send' 87 | const notifyData = { 88 | "token": notifyToken, 89 | "title": "⏰ 奕辅导打卡结果通知", 90 | "template": "json" 91 | } 92 | const notify = (result) => { 93 | return axios({ 94 | method: 'post', 95 | url: notifyUrl, 96 | data: { 97 | "content": result, 98 | ...notifyData 99 | } 100 | }) 101 | } 102 | 103 | exports.main_handler = async () => { 104 | const questionnaireIdRes = await getQuestionnaireId() 105 | const data = questionnaireIdRes.data 106 | const questionnaireId = data.data.questionnairePublishEntityId 107 | const answers = await getAnswers(questionnaireId) 108 | if (!answers) { 109 | notify('❌ 首次使用本脚本,请先手动打卡一次或者确保今天已经打卡,以便在数据库中存入打卡数据') 110 | return 111 | } 112 | if (data.code === 200) { 113 | // 打过卡了,无需再次打卡 114 | if (data.data.hadFill) { 115 | notify('✅ 你已经打卡了') 116 | } 117 | // 否则打卡 118 | else { 119 | // 问卷校验 120 | const questionnaireRes = await getQuestionnaire(questionnaireId) 121 | const subjectIdLists = questionnaireRes.data.data.questionnaireWithSubjectVo.subjectList.map(item => item.id) 122 | const _subjectIdLists = answers.answerInfoList.map(item => item.subjectId) 123 | // 如果问卷的子问题 id 不变,则正常打卡 124 | if (subjectIdLists.every((item,index) => item === _subjectIdLists[index])) { 125 | const submitRes = await submitAnswers(questionnaireId,answers) 126 | if (submitRes.data.code === 200) { 127 | notify('✅ 打卡成功') 128 | } else { 129 | notify(`❌ 打卡失败,原因是: ${submitRes.data.message}`) 130 | } 131 | } else { 132 | notify(`❌ 打卡失败,原因是: 问卷内容发生更改`) 133 | } 134 | } 135 | } else { 136 | notify(`❌ 打卡失败,原因是${data.message}`) 137 | } 138 | } 139 | --------------------------------------------------------------------------------