一、前言
因为每天晚上十点钟要求在易班中的一个轻应用进行校区定位打卡,但是只有一个小时,这个时间点作为和舍友快乐五排的时间,总是忘记打卡。因此就想着做一个自动打卡的,挂载在云函数中,也不需要单独的服务器,每天定时运行,岂不是美滋滋。
二、发现问题
首先,这个易班是一个手机APP,但是我在电脑网页端查看了易班官网,发现就算是登录后依然不能进行打卡相关操作,所以只能把目标再次转回到手机APP中,利用手机的HttpCanary对其进行了抓包。但是呢,因为高版本安卓手机的整数问题,打开抓包APP都不能登陆。轻应用在易班中呈现一种站内链接跳转的方式,因此,决定先关闭抓包,进入APP,在直接点击进入打卡的链接跳转,毕竟只用获取到用于post打卡信息的url即可,最后发现先进入所有打卡列表的页面,再进行抓包是可行的。
通过抓包之后,获取到了post打卡信息的url,发现请求头中有一个Authorization的验证,是一个加密信息。
于是对authorization加密信息进行base64的解密,解密完发现这个东西由三部分组成,前两部分只进行了base64加密,第三部分应该是还有别的加密。base64解码后显示是乱码。
看来这条路是行不通的。但是,经过试验,发现了打卡的轻应用在“我的”页面中是可以点击退出登录的,就去点了一下,结果跳出了登录界面,通过复制链接,发现电脑是可以打开这个登陆页面的,因为平时这个都是app内应用,所以是没有登陆的,看不到登录页面。
那么有了登陆页面就很简单了啊,通过浏览器登陆并抓包发现,登陆成功后,有一个Ajax请求的返回信息正好是携带了token,而且跟前面的请求头中的authorration格式是一样的。
发现这个就很简单了,只要先对这个Ajax发起post拿到token,在用这个token取进行打卡就行了。
然后点到post表单,发现登陆的密码是加密的。查看后应该是一个MD5的加密,进行一个小小的逆向就可以拿到加密方法了。
做完这一步骤以后,又发现打卡的时候post的url,后面携带了一个叫做ID的东西。
最后返回去之后,抓包发现获取打卡列表的时候,响应值中打卡列表中每一个打卡都有一个对应的ID,就是打卡的时候post携带的ID。
那么,做完以上的所有操作,就知道了代码实现的流程了,首先利用逆向出的加密方法进行模拟登陆,获取Ajax响应结果中的token,使用token请求获取打卡列表中对应今天的打卡ID,利用获取到的打卡ID进行打卡操作。
三、代码操作流程
用到的库文件
1 import execjs
2 import requests
3 import time
4 from multiprocessing.dummy import Pool
逆向过程就不贴出来了,一个简单的JS调试就可以拿到逆向方法啦。
首先就是进行模拟登陆
1 # 利用逆向得到的js文件对明文密码进行加密,传入明文密码,返回加密密码
2 def encrypted_password(cleartext_passwords):
3 node = execjs.get()
4 ctx = node.compile(open(file='./zhxgPasswordEncrypt.js', mode='r', encoding='utf-8').read())
5 function_name = 'getpwd'
6 encrypted_password = ctx.call(function_name, cleartext_passwords)
7 return encrypted_password
8
9
10 # 模拟登陆获取token,传入用户名和加密密码,返回token
11 def get_token(UserName, encrypted_password):
12 url = ''
13 headers = {
14 'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Mobile Safari/537.36 Edg/96.0.1054.62',
15 }
16 data = {
17 'Password': encrypted_password,
18 'UserName': UserName,
19 }
20 response = requests.post(url=url, headers=headers, data=data).json()
21 data = response['data']
22 token = data['Token']
23 return token
第二步就是进行打卡ID的获取
1 # 获取当天需要打卡的签到ID,传入authorization需要的token,返回signID
2 def get_signID(token):
3 url = ''
4 headers = {
5 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; YAL-AL00 Build/HUAWEIYAL-AL00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36 yiban_android/5.0.7',
6 'Authorization': token,
7 'Platform': 'mobile',
8 }
9 response = requests.get(url=url, headers=headers).json()
10 data = response['data']
11 signID = data[0]['ID']
12 return signID
第三部当然就是进行打卡啦
1 # 进行签到操作,传入参数signID,token,返回签到信息
2 def sign(signID, token, UserName):
3 url = ''
4 headers = {
5 'Content-Type': 'application/x-www-form-urlencoded',
6 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; YAL-AL00 Build/HUAWEIYAL-AL00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36 yiban_android/5.0.7',
7 'Authorization': token,
8 'Platform': 'mobile',
9 }
10 timestamp = time.time()
11 format_time1 = time.strftime('%Y-%m-%d', time.localtime(timestamp))
12 format_time2 = time.strftime('%H:%M:%S', time.localtime(timestamp))
13 requests_body = {
14 'jwd': '',
15 'position': '',
16 'signtime': f'{format_time1}\n{format_time2}',
17 'id': signID,
18 }
19 response = requests.post(url=url, headers=headers, data=requests_body).json()
20 if '成功' in response['message']:
21 message = f'{UserName}晚间签到完成-->{format_time1}'
22 else:
23 message = f'{UserName}晚间签到失败-->{format_time1}'
24 return message
最后可以利用server酱进行一个wx推送的通知。
1 # 微信推送,传入参数:要推送的信息,推送信息的key
2 def send_message(message, key):
3 url = 'https://sctapi.ftqq.com/' + key + '.send'
4 data = {
5 'title': message,
6 'desp': message,
7 }
8 requests.post(url=url, params=data)
那一个宿舍的,可以自动打卡总不能不带上舍友吧。所以就带上舍友们咯。再加一个线程池吧!
1 # 完成所有操作。传入用户名和明文密码。
2 def finish(information_dic):
3 UserName = information_dic['UserName']
4 cleartext_passwords = information_dic['Password']
5 key = information_dic['key']
6 # 加密密码
7 enPassword = encrypted_password(cleartext_passwords)
8 # 获取token
9 token = get_token(UserName, enPassword)
10 # 获取signID
11 signID = get_signID(token)
12 # 签到
13 message = sign(signID, token, UserName)
14 # 微信推送
15 send_message(message, key)
16
17
18 def main(*args):
19 pool = Pool(5)
20 pool.map(finish, information_list)
到此,所有的代码编写还有分析过程就结束了,最后测试没问题,直接挂到服务器或者云函数每天就不用自己动手签到了。
最后把整体的代码也贴出来咯。感谢浏览,因为不是正统程序员出身,只是出于对编程的爱好学习的,因此如果代码中不规范的地方还请包涵,也可以提给我,我会虚心学习的。
1 # -*- coding: utf-8 -*-
2 # @Time : 2022/1/6 21:44
3 # @Author : 遠方
4 # @
5 # @File : main.py
6 import execjs
7 import requests
8 import time
9 from multiprocessing.dummy import Pool
10
11 # 信息配置
12 information_list = [
13 # 自己
14 {'UserName': '', 'Password': '', 'key': '', },
15 # 张三
16 {'UserName': '', 'Password': '', 'key': '', },
17 # 李四
18 {'UserName': '', 'Password': '', 'key': '', },
19 # 王麻子
20 {'UserName': '', 'Password': '', 'key': '', },
21 ]
22
23
24 # 利用逆向得到的js文件对明文密码进行加密,传入明文密码,返回加密密码
25 def encrypted_password(cleartext_passwords):
26 node = execjs.get()
27 ctx = node.compile(open(file='./zhxgPasswordEncrypt.js', mode='r', encoding='utf-8').read())
28 function_name = 'getpwd'
29 encrypted_password = ctx.call(function_name, cleartext_passwords)
30 return encrypted_password
31
32
33 # 模拟登陆获取token,传入用户名和加密密码,返回token
34 def get_token(UserName, encrypted_password):
35 url = ''
36 headers = {
37 'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Mobile Safari/537.36 Edg/96.0.1054.62',
38 }
39 data = {
40 'Password': encrypted_password,
41 'UserName': UserName,
42 }
43 response = requests.post(url=url, headers=headers, data=data).json()
44 data = response['data']
45 token = data['Token']
46 return token
47
48
49 # 获取当天需要打卡的签到ID,传入authorization需要的token,返回signID
50 def get_signID(token):
51 url = ''
52 headers = {
53 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; YAL-AL00 Build/HUAWEIYAL-AL00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36 yiban_android/5.0.7',
54 'Authorization': token,
55 'Platform': 'mobile',
56 }
57 response = requests.get(url=url, headers=headers).json()
58 data = response['data']
59 signID = data[0]['ID']
60 return signID
61
62
63 # 进行签到操作,传入参数signID,token,返回签到信息
64 def sign(signID, token, UserName):
65 url = ''
66 headers = {
67 'Content-Type': 'application/x-www-form-urlencoded',
68 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; YAL-AL00 Build/HUAWEIYAL-AL00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36 yiban_android/5.0.7',
69 'Authorization': token,
70 'Platform': 'mobile',
71 }
72 timestamp = time.time()
73 format_time1 = time.strftime('%Y-%m-%d', time.localtime(timestamp))
74 format_time2 = time.strftime('%H:%M:%S', time.localtime(timestamp))
75 requests_body = {
76 'jwd': '',
77 'position': '',
78 'signtime': f'{format_time1}\n{format_time2}',
79 'id': signID,
80 }
81 response = requests.post(url=url, headers=headers, data=requests_body).json()
82 if '成功' in response['message']:
83 message = f'{UserName}晚间签到完成-->{format_time1}'
84 else:
85 message = f'{UserName}晚间签到失败-->{format_time1}'
86 return message
87
88
89 # 微信推送,传入参数:要推送的信息,推送信息的key
90 def send_message(message, key):
91 url = 'https://sctapi.ftqq.com/' + key + '.send'
92 data = {
93 'title': message,
94 'desp': message,
95 }
96 requests.post(url=url, params=data)
97
98
99 # 完成所有操作。传入用户名和明文密码。
100 def finish(information_dic):
101 UserName = information_dic['UserName']
102 cleartext_passwords = information_dic['Password']
103 key = information_dic['key']
104 # 加密密码
105 enPassword = encrypted_password(cleartext_passwords)
106 # 获取token
107 token = get_token(UserName, enPassword)
108 # 获取signID
109 signID = get_signID(token)
110 # 签到
111 message = sign(signID, token, UserName)
112 # 微信推送
113 send_message(message, key)
114
115
116 def main(*args):
117 pool = Pool(5)
118 pool.map(finish, information_list)
119
120
121 if __name__ == '__main__':
122 main()