为什么要做
疫情打卡要求刚刚推出没多久,已经有同学使用了Python完成了一个定时打卡脚本。功能完备,孩子很喜欢。
在后续的学习中,我开始了解JS如何发送请求,意识到事实上JS也能完成这个任务(阳不阳间是另一回事)。
对了,现在已经写完了,放在GitHub上了😆
登录
折大通行证的登录原理参考了这篇文章。
由于Python的request
库已经自动实现了对Cookie的处理,而JS的node-fetch
和axios
库都没有直接帮我们完成这些事情。我们还是需要手动处理一下Cookie。
为了调试方便,我选择了axios
,它自带一个拦截器Interceptors
方便调试。
加密
文章里已经提到了具体的加密策略,即浏览器首先会去https://zjuam.zju.edu.cn/cas/v2/getPubKey
处获取exponent
和module
,并通过一个Cookie来进行验证。
具体的加密过程如下:
typescript复制代码1const encode = async (str: string, m: string, e: number) => {
2 const hex: string = Buffer.from(str, "utf-8").toString("hex");
3 const M = bigInt(m, 16);
4 const H = bigInt(hex, 16);
5 const E = bigInt(e.toString(), 16);
6
7 const result = H.modPow(E, M);
8 return result.toString(16);
9};
execution
在进行登录操作时,注意到在发送请求的时候,我们需要一个execution的值,这个值出现在了折大通行证主页的index.html
中。因此,需要请求一次通行证主页并拿到这段字符串。
typescript复制代码
1const tmpExe = (await response2.data).match(/name="execution" value=".+?"/); 2const execution = tmpExe && tmpExe[0].slice(24, -1); //获取execution
Cookie
这大概是这篇文章存在的最大原因。毕竟python版不需要研究这些东西XD
经过抓包可以发现,在我们登录的时候,需要至少发送5个Cookie,key分别是:
- JSESSIONID
- _csrf
- _pv0
- UUkey
- eai-sess
其中UUKey
和eai-sess
是在https://healthreport.zju.edu.cn/uc/wap/login?redirect=https%3A%2F%2Fhealthreport.zju.edu.cn%2Fncov%2Fwap%2Fdefault%2Findex
处获得的,由于会自动重定向,因此需要手动阻止。
JSESSIONID
和_csrf
是从登录首页https://zjuam.zju.edu.cn/cas/login?service=https%3A%2F%2Fhealthreport.zju.edu.cn%2Fa_zju%2Fapi%2Fsso%2Findex%3Fredirect%3Dhttps%253A%252F%252Fhealthreport.zju.edu.cn%252Fncov%252Fwap%252Fdefault%252Findex%26from%3Dwap
处获得的。
_pv0
从https://zjuam.zju.edu.cn/cas/v2/getPubKey
处获得。
知道需要哪些Cookie就好办了,现在全部存入cookieArr
中即可。
发请求
比起处理Cookie,发请求显然容易一些。需要登录前,将账号、密码、execution值等添加进URLSearchParams
对象中发送。
javascript复制代码1const params = new URLSearchParams();
2params.append('username', username);
3params.append('password', encoded);
4params.append('_eventId', 'submit');
5params.append('authcode', '');
6params.append('execution', execution);
7
8const response = await axios({
9 url: 'https://zjuam.zju.edu.cn/cas/login?service=https%3A%2F%2Fhealthreport.zju.edu.cn%2Fa_zju%2Fapi%2Fsso%2Findex%3Fredirect%3Dhttps%253A%252F%252Fhealthreport.zju.edu.cn%252Fncov%252Fwap%252Fdefault%252Findex%26from%3Dwap',
10 method: 'POST',
11 headers: {
12 Cookie: concatCookies(cookiesArr),
13 'Content-type': 'application/x-www-form-urlencoded',
14 },
15 data: params
16});
不出意外,我们应该已经登录成功了。如果担心服务器抽风导致登录失败,可以做一个重复发送的操作。
打卡
打卡的原理比较好扒,页面似乎是使用vue写的,数据怎么赋值看起来都比较清晰。
新表单
我们都知道,只要你打过一次卡后,第二天重新打卡,有一些填报信息就不需要重复填写。显然,网页从后端获取了我们前一天打卡的信息,在初始化的时候做了一个赋值,这个变量也很容易找,就叫oldInfo
。并且我们也发现,后端很贴心的把前一天的地理位置也发给了我们,我们甚至不需要制造地理位置了。
现在,需要我们操作的key就所剩不多了:
- 姓名、学号:其实这个可以通过自己填写的方式简化一下代码,不过既然后端发给我们,那就不客气了(
- id:某种神秘id,可以直接照搬
def
中的id - date、created:一个是今天的日期,一个是创建的时间,生成一个时间戳就好。
- 一些填报信息:和勾选项相关的值,起名风格也非常的emm。总之按照对应的数据手动做一个赋值。
到这一步,新表单的创建就OK了。
Cookie
没错,发送请求前还需要额外的Cookie。根据常识也能想到,应该是我们登录折大通行证成功之后发给我们的字段。通过抓包比对,最终锁定了_pf0
、_pc0
以及iPlanetDirectoryPro
。
最后一步就是发送打卡请求。
typescript复制代码1const http = axios.create();
2const data = new URLSearchParams(info as Record<string, string>);
3
4const response = await http({
5 url: 'https://healthreport.zju.edu.cn/ncov/wap/default/save',
6 method: 'POST',
7 headers: {
8 'Content-Type': 'application/x-www-form-urlencoded',
9 Cookie: concatCookies(cookiesArr),
10 },
11 data: data,
12});
现在,你可以根据返回的m
字段判断你是否打卡成功了。
定时?
和朋友讨论之后,我暂时没有打算添加类似python版本的定时器。最主要的理由大概是setInterval()
这个做法看起来也不太美妙。
不过我们的目的只是每天早上12点前启动一次这个脚本,我也了解到服务器本身其实就能完成这个任务,例如crontab
。
例如这个Shell脚本,我们就启动了一个每天8点打卡的定时任务,顺便把log丢到我的桌面上。之后可以考虑把消息接入QQbot之类的地方,不过这就是后话啦😜
sh复制代码
1#/usr/bin/sh 2crontab -l > zju-clocker 3echo "00 08 * * * /Users/palemoons/.nvm/versions/node/v16.5.0/bin/node --experimental-json-modules /Users/palemoons/Documents/ZJUClocker/clocker.js >> /Users/palemoons/Desktop/time.txt 2>&1" >> zju-clocker 4crontab zju-clocker 5rm zju-clocker