-
Notifications
You must be signed in to change notification settings - Fork 266
/
hitcarder.py
244 lines (205 loc) · 8.2 KB
/
hitcarder.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# -*- coding: utf-8 -*-
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
import json
import re
import time
import datetime
import os
import sys
import message
import ddddocr
class HitCarder(object):
"""Hit carder class
Attributes:
username: (str) 浙大统一认证平台用户名(一般为学号)
password: (str) 浙大统一认证平台密码
login_url: (str) 登录url
base_url: (str) 打卡首页url
save_url: (str) 提交打卡url
sess: (requests.Session) 统一的session
"""
def __init__(self, username, password):
self.username = username
self.password = password
self.login_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"
self.base_url = "https://healthreport.zju.edu.cn/ncov/wap/default/index"
self.save_url = "https://healthreport.zju.edu.cn/ncov/wap/default/save"
self.captcha_url = 'https://healthreport.zju.edu.cn/ncov/wap/default/code'
self.sess = requests.Session()
self.sess.keep_alive = False
retry = Retry(connect=3, backoff_factor=0.5)
adapter = HTTPAdapter(max_retries=retry)
self.sess.mount('http://', adapter)
self.sess.mount('https://', adapter)
# ua = UserAgent()
# self.sess.headers['User-Agent'] = ua.chrome
self.sess.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36'}
def login(self):
"""Login to ZJU platform"""
time.sleep(1)
res = self.sess.get(self.login_url)
execution = re.search(
'name="execution" value="(.*?)"', res.text).group(1)
time.sleep(1)
res = self.sess.get(
url='https://zjuam.zju.edu.cn/cas/v2/getPubKey').json()
n, e = res['modulus'], res['exponent']
encrypt_password = self._rsa_encrypt(self.password, e, n)
data = {
'username': self.username,
'password': encrypt_password,
'execution': execution,
'_eventId': 'submit'
}
time.sleep(1)
res = self.sess.post(url=self.login_url, data=data)
# check if login successfully
if '统一身份认证' in res.content.decode():
raise LoginError('登录失败,请核实账号密码重新登录')
return self.sess
def post(self):
"""Post the hit card info."""
time.sleep(1)
res = self.sess.post(self.save_url, data=self.info)
return json.loads(res.text)
def get_date(self):
"""Get current date."""
today = datetime.datetime.utcnow() + datetime.timedelta(hours=+8)
return "%4d%02d%02d" % (today.year, today.month, today.day)
def check_form(self):
"""Get hitcard form, compare with old form """
res = self.sess.get(self.base_url)
html = res.content.decode()
try:
new_form = re.findall(r'<ul>[\s\S]*?</ul>', html)[0]
except IndexError as _:
raise RegexMatchError('Relative info not found in html with regex')
with open("form.txt", "r", encoding="utf-8") as f:
if new_form == f.read():
return True
#with open("form.txt", "w", encoding="utf-8") as f:
# f.write(new_form)
return False
def get_info(self, html=None):
"""Get hit card info, which is the old info with updated new time."""
if not html:
time.sleep(1)
res = self.sess.get(self.base_url)
html = res.content.decode()
try:
old_infos = re.findall(r'oldInfo: ({[^\n]+})', html)
if len(old_infos) != 0:
old_info = json.loads(old_infos[0])
else:
raise RegexMatchError("未发现缓存信息,请先至少手动成功打卡一次再运行脚本")
def_info = json.loads(re.findall(r'def = ({[^\n]+})', html)[0])
magic_code = re.findall(
r'"([0-9a-z]{32})": "([0-9]{10})","([0-9a-z]{32})":"([0-9a-z]{32})"', html)[0]
magic_code_group = {
magic_code[0]: magic_code[1],
magic_code[2]: magic_code[3]
}
except IndexError as err:
raise RegexMatchError(
'Relative info not found in html with regex: ' + str(err))
except json.decoder.JSONDecodeError as err:
raise DecodeError('JSON decode error: ' + str(err))
new_info = def_info.copy()
new_info.update(magic_code_group)
ocr = ddddocr.DdddOcr()
resp = self.sess.get(self.captcha_url)
# form change
new_info['szgjcs'] = ""
new_info['zgfx14rfhsj'] = ""
new_info['geo_api_info'] = old_info['geo_api_info'] # 定位
new_info['address'] = old_info['address']
new_info['area'] = old_info['area']
new_info['city'] = old_info['city']
new_info['ismoved'] = 0
new_info['sfzx'] = old_info['sfzx'] # 在校
new_info['sfymqjczrj'] = old_info['sfymqjczrj'] # 入境
new_info['sfqrxxss'] = 1 # 属实
new_info['campus'] = '紫金港校区' #校区
new_info['internship'] = old_info['internship'] # 实习
#new_info['verifyCode'] = ocr.classification(resp.content)#验证码
self.info = new_info
# print(json.dumps(self.info))
return new_info
def _rsa_encrypt(self, password_str, e_str, M_str):
password_bytes = bytes(password_str, 'ascii')
password_int = int.from_bytes(password_bytes, 'big')
e_int = int(e_str, 16)
M_int = int(M_str, 16)
result_int = pow(password_int, e_int, M_int)
return hex(result_int)[2:].rjust(128, '0')
# Exceptions
class LoginError(Exception):
"""Login Exception"""
pass
class RegexMatchError(Exception):
"""Regex Matching Exception"""
pass
class DecodeError(Exception):
"""JSON Decode Exception"""
pass
def main(username, password):
"""Hit card process
Arguments:
username: (str) 浙大统一认证平台用户名(一般为学号)
password: (str) 浙大统一认证平台密码
"""
hit_carder = HitCarder(username, password)
print("[Time] %s" % datetime.datetime.now().strftime(
'%Y-%m-%d %H:%M:%S'))
print(datetime.datetime.utcnow() + datetime.timedelta(hours=+8))
print("打卡任务启动")
try:
hit_carder.login()
print('已登录到浙大统一身份认证平台')
except Exception as err:
return 1, '打卡登录失败:' + str(err)
try:
ret = hit_carder.check_form()
if not ret:
return 2, '打卡信息已改变,请手动打卡'
except Exception as err:
return 1, '获取信息失败,请手动打卡: ' + str(err)
try:
hit_carder.get_info()
except Exception as err:
return 1, '获取信息失败,请手动打卡: ' + str(err)
try:
res = hit_carder.post()
print(res)
if str(res['e']) == '0':
return 0, '打卡成功'
elif str(res['m']) == '今天已经填报了':
return 0, '今天已经打卡'
else:
return 1, '打卡失败'
except:
return 1, '打卡数据提交失败'
if __name__ == "__main__":
username = os.environ['USERNAME']
password = os.environ['PASSWORD']
ret, msg = main(username, password)
print(ret, msg)
if ret == 1:
time.sleep(5)
ret, msg = main(username, password)
print(ret, msg)
dingtalk_token = os.environ.get('DINGTALK_TOKEN')
if dingtalk_token:
ret = message.dingtalk(msg, dingtalk_token)
print('send_dingtalk_message', ret)
serverchan_key = os.environ.get('SERVERCHAN_KEY')
if serverchan_key:
ret = message.serverchan(msg, '', serverchan_key)
print('send_serverChan_message', ret)
pushplus_token = os.environ.get('PUSHPLUS_TOKEN')
if pushplus_token:
print('pushplus服务已下线,建议使用钉钉')
exit(-1)