为什么要做

疫情打卡要求刚刚推出没多久,已经有同学使用了Python完成了一个定时打卡脚本功能完备,孩子很喜欢。

在后续的学习中,我开始了解JS如何发送请求,意识到事实上JS也能完成这个任务~~(阳不阳间是另一回事)~~。

对了,现在已经写完了,放在GitHub上了😆

登录

折大通行证的登录原理参考了这篇文章

由于Python的request库已经自动实现了对Cookie的处理,而JS的node-fetchaxios库都没有直接帮我们完成这些事情。我们还是需要手动处理一下Cookie。

为了调试方便,我选择了axios,它自带一个拦截器Interceptors方便调试。

加密

文章里已经提到了具体的加密策略,即浏览器首先会去https://zjuam.zju.edu.cn/cas/v2/getPubKey处获取exponentmodule,并通过一个Cookie来进行验证。

具体的加密过程如下:

1
2
3
4
5
6
7
8
9
const encode = async (str: string, m: string, e: number) => {
const hex: string = Buffer.from(str, "utf-8").toString("hex");
const M = bigInt(m, 16);
const H = bigInt(hex, 16);
const E = bigInt(e.toString(), 16);

const result = H.modPow(E, M);
return result.toString(16);
};

execution

在进行登录操作时,注意到在发送请求的时候,我们需要一个execution的值,这个值出现在了折大通行证主页的index.html中。因此,需要请求一次通行证主页并拿到这段字符串。

1
2
const tmpExe = (await response2.data).match(/name="execution" value=".+?"/);
const execution = tmpExe && tmpExe[0].slice(24, -1); //获取execution

这大概是这篇文章存在的最大原因。毕竟python版不需要研究这些东西XD

经过抓包可以发现,在我们登录的时候,需要至少发送5个Cookie,key分别是:

  1. JSESSIONID
  2. _csrf
  3. _pv0
  4. UUkey
  5. eai-sess

其中UUKeyeai-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处获得的。

_pv0https://zjuam.zju.edu.cn/cas/v2/getPubKey处获得。

知道需要哪些Cookie就好办了,现在全部存入cookieArr中即可。

发请求

比起处理Cookie,发请求显然容易一些。需要登录前,将账号、密码、execution值等添加进URLSearchParams对象中发送。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const params = new URLSearchParams();
params.append('username', username);
params.append('password', encoded);
params.append('_eventId', 'submit');
params.append('authcode', '');
params.append('execution', execution);

const response = await axios({
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',
method: 'POST',
headers: {
Cookie: concatCookies(cookiesArr),
'Content-type': 'application/x-www-form-urlencoded',
},
data: params
});

不出意外,我们应该已经登录成功了。如果担心服务器抽风导致登录失败,可以做一个重复发送的操作。

打卡

打卡的原理比较好扒,页面似乎是使用vue写的,数据怎么赋值看起来都比较清晰。

新表单

我们都知道,只要你打过一次卡后,第二天重新打卡,有一些填报信息就不需要重复填写。显然,网页从后端获取了我们前一天打卡的信息,在初始化的时候做了一个赋值,这个变量也很容易找,就叫oldInfo。并且我们也发现,后端很贴心的把前一天的地理位置也发给了我们,我们甚至不需要制造地理位置了

现在,需要我们操作的key就所剩不多了:

  1. 姓名、学号:其实这个可以通过自己填写的方式简化一下代码,不过既然后端发给我们,那就不客气了(
  2. id:某种神秘id,可以直接照搬def中的id
  3. date、created:一个是今天的日期,一个是创建的时间,生成一个时间戳就好。
  4. 一些填报信息:和勾选项相关的值,起名风格也非常的emm。总之按照对应的数据手动做一个赋值。

到这一步,新表单的创建就OK了。

没错,发送请求前还需要额外的Cookie。根据常识也能想到,应该是我们登录折大通行证成功之后发给我们的字段。通过抓包比对,最终锁定了_pf0_pc0以及iPlanetDirectoryPro

最后一步就是发送打卡请求。

1
2
3
4
5
6
7
8
9
10
11
12
const http = axios.create();
const data = new URLSearchParams(info as Record<string, string>);

const response = await http({
url: 'https://healthreport.zju.edu.cn/ncov/wap/default/save',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Cookie: concatCookies(cookiesArr),
},
data: data,
});

现在,你可以根据返回的m字段判断你是否打卡成功了。

定时?

和朋友讨论之后,我暂时没有打算添加类似python版本的定时器。最主要的理由大概是setInterval()这个做法看起来也不太美妙。

不过我们的目的只是每天早上12点前启动一次这个脚本,我也了解到服务器本身其实就能完成这个任务,例如crontab

例如这个Shell脚本,我们就启动了一个每天8点打卡的定时任务,顺便把log丢到我的桌面上。之后可以考虑把消息接入QQbot之类的地方,不过这就是后话啦😜

1
2
3
4
5
#/usr/bin/sh
crontab -l > zju-clocker
echo "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
crontab zju-clocker
rm zju-clocker