一、概述

学校教务系统更换成了强智教务系统(吐槽:花了200万的教务系统连个手机客户端都没有,且根本没对手机进行适配),导致没有较好的手机客户端能适配,很多同学们用的第三方客户端都出现了乱码或者缺课的现象。由于我手机开发实在不行,所以我萌生了每天0点自动发送今天课表加本周课表到我自己的邮箱的这么一个想法。

二、抓包分析

1、登录

我们打开官网后,输入账号密码点击登录按钮,发现第一个包是POST /Logon.do?method=logon&flag=sess返回了一串字符,到这里不清楚这是干啥的;然后第二个包是POST /Logon.do?method=logon,传入了userAccount(学号)encoded(密文)返回了一个重定向,第三个包是访问了刚刚的重定向网址结果又重定向到了/jsxsd/framework/xsMain.jsp主页。

分析到这里,我们发现密码是被加密了再传输的。我第一个想到的是右键查看源代码看看密文是怎么来的吧,登录按钮直接绑定了js函数login(),跳转到这里发现如下代码:

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
function login() {
if($("#userAccount").val() == "") {
$("#showMsg").text("请输入账号");
$("#userAccount").focus();
return false;
}
if($("#userPassword").val() == "") {
$("#showMsg").text("请输入密码");
$("#userPassword").focus();
return false;
}


var strUrl = "/Logon.do?method=logon&flag=sess";


$.ajax( {
url:strUrl,
type:"post",
cache:false,
dataType:"text",
success:function(dataStr) {
if(dataStr=="no"){
return false;
}else{
var scode=dataStr.split("#")[0];
var sxh=dataStr.split("#")[1];
var code=document.getElementById("userAccount").value+"%%%"+document.getElementById("userPassword").value;
var encoded="";
for(var i=0;i<code.length;i++){
if(i<20){
encoded=encoded+code.substring(i,i+1)+scode.substring(0,parseInt(sxh.substring(i,i+1)));
scode = scode.substring(parseInt(sxh.substring(i,i+1)),scode.length);
}else{
encoded=encoded+code.substring(i,code.length);
i=code.length;
}
}
document.getElementById("encoded").value=encoded;
if("logon"!="logonLdap"){
document.getElementById("userPassword").value="";
}

document.getElementById("loginForm").submit();
}
},
error:function() {
alert("计算异常!");
}
});
}

发现了猫腻,原来这里访问了第一个包得到的字符串是一串可以说是密钥的东西;通过一系列算法把密码加密成密文,我把这段翻译成了Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def encrypt_password(xuehao, password, dataStr):
sCode, sxh = dataStr.split("#")
code = xuehao + "%%%" + password
encoded = ""
i = 0
while i < len(code):
if i < 20:
encoded = encoded + code[i:i + 1] + sCode[0:int(sxh[i:i + 1])]
sCode = sCode[int(sxh[i:i + 1]):len(sCode)]
i += 1
else:
encoded = encoded + code[i:len(code)]
i = len(code)
return encoded

得到密文之后,就可以正常登录了。

1
2
3
4
5
s = requests.session()
r = s.post(url="http://xk.csust.edu.cn/Logon.do?method=logon&flag=sess")

r = s.post(url="http://xk.csust.edu.cn/Logon.do?method=logon",
data={"userAccount": xh, "encoded": encrypt_password(xh, mm, r.text)})

2、[可选]获得姓名

登录后重定向两次到了/jsxsd/framework/xsMain.jsp,里面含有名字可以取出,查看源码容易知道用正则

<span class="glyphicon-class">(.*?)</span>可以返回的第一个数据就是姓名。

3、[可选]获得当前周

我们发现主页是多个iframe框架拼凑的,第几周的信息在/jsxsd/framework/xsMain_new.jsp路径下,查看源码容易知道用正则>(.*?)</span>/(.*?)$可以返回第几周和总周数。

4、获得课表

分析包容易发现/jsxsd/framework/main_index_loadkb.jsp就是课表的网页,传入的rq参数是当天的日期,传入日期不同课表显示不同。但是课表名字过长的话会显示..导致显示不全,刚开始我还以为是前端CSS造成的,结果是后端就处理好的!汗!幸好我们将鼠标滑动的时候能显示出详细的信息,所以通过查看源码可以得到课程的完整名称和地点。课程的详细信息在td标签里的p标签title属性里。我们把所有的数据打包成一个二维数组,供生成图片使用。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
r = s.post(url="http://xk.csust.edu.cn/jsxsd/framework/main_index_loadkb.jsp", data={"rq": time.strftime("%Y-%m-%d", timeStruct)})

table_data = [] # 全部数据数组
table_header = re.findall(r'<th[^>]*">(.*?)</th>', r.text) # 表头
table_data.append(table_header)
header_length = len(table_header) # 取得几列 也就是表头个数
all_td_content = re.findall(r'<td[^>]*>(.*?)</td>', r.text.replace("\t", "").replace("\n", "")) # 取得所有td内容 删除换行符号和制表符

temp = [] # 临时存储单行的数据
for i, v in enumerate(all_td_content):
if i % header_length == 0: # 说明这是这一行的第一个数据 也就是表格中的第几节
dijijie = v.split("<br/>")
temp.append("\n%s\n%s\n%s" % (dijijie[0], dijijie[1], dijijie[2]))
else: # 否则就是具体课程啦
name = findMiddle(v, "课程名称:", "<br/>")
room = findMiddle(v, "上课地点:", "'")
temp.append("\n" + name + "\n\n" + room + "\n")
if (i + 1) % header_length == 0: # 检测目前td内容是不是一行的最后一个数据 如果是就把这一整行给加入总的二维表
table_data.append(temp)
temp = []

print(table_data)

生成的数据长这个样子

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
[
[
"周/节次",
"星期一",
"星期二",
"星期三",
"星期四",
"星期五",
"星期六",
"星期日"
],
[
"\n第一大节\n(01,02小节)\n08:00-09:40",
"\n人工智能概论\n\n云综教[假装打码]\n",
"\n英语听说写译综合\n\n云综教[假装打码]\n",
"\n软件项目管理及案例\n\n云文科[假装打码]\n",
"\n软件工程经济学\n\n云文科[假装打码]\n",
"\n\n\n\n",
"\n\n\n\n",
"\n\n\n\n"
],
[
"\n第二大节\n(03,04小节)\n10:00-11:40",
"\n\n\n\n",
"\n\n\n\n",
"\n\n\n\n",
"\n\n\n\n",
"\n\n\n\n",
"\n\n\n\n",
"\n\n\n\n"
],
[
"\n第三大节\n(05,06小节)\n14:00-15:40",
"\n\n\n\n",
"\n\n\n\n",
"\n\n\n\n",
"\n人工智能概论\n\n云综教[假装打码]\n",
"\n\n\n\n",
"\n\n\n\n",
"\n\n\n\n"
],
[
"\n第四大节\n(07,08小节)\n16:00-17:40",
"\n\n\n\n",
"\n\n\n\n",
"\n\n\n\n",
"\n\n\n\n",
"\n\n\n\n",
"\n\n\n\n",
"\n\n\n\n"
],
[
"\n第五大节\n(09,10小节)\n19:30-21:10",
"\n\n\n\n",
"\n\n\n\n",
"\n\n\n\n",
"\n\n\n\n",
"\n\n\n\n",
"\n\n\n\n",
"\n\n\n\n"
]
]

然后参考https://www.cnblogs.com/xiamibk/p/10299609.html的文章,执行

1
2
3
create_table_img(table_data, xh + '(' + rq + ')课表.png', font="font.ttf",
describe='以上数据仅供参考,作者:亦之(looyeagee)博客地址:https://looyeagee.cn/',
table_title=xm + '(' + rq + ')[%s]的课程表' % now_zhou)

将二维数组表转换成图片就是这样:

图1:课表截图

三、自动发送邮件

首先我们在163邮箱里创建一个授权码当做第三方客户端的密码,然后就可以将当天的课表和本周的课表图发出去了。

1、获得当天课表

1
2
3
4
5
6
7
8
t = table_data.copy()  # 复制一份刚刚生成的二维表
del (t[0]) # 删除第一行 也就是星期几的那一行
table_data_90 = [[row[i] for row in t] for i in range(len(t[0]))] # 矩阵转置
today = "你的今日课表如下:<br>"
for i, v in enumerate(table_data_90[0]):
detail_kecheng = table_data_90[datetime.datetime.now().weekday() + 1][i].split()
if len(detail_kecheng) != 0:
today = today + "%s:%s(%s)<br>" % (v.split()[2], detail_kecheng[0], detail_kecheng[1])

2、PNG图片转Base64

不想以附件的形式发邮件了,就直接用base64图片返回的是HTML的IMG标签:

1
2
3
4
5
def img_to_base64_img(fileName):
img_file = open(fileName, 'rb')
base64_data = base64.b64encode(img_file.read())
img_file.close()
return '<img src="data:image/png;base64,%s">' % str(base64_data, "utf8")

3、发送邮件

没啥好说的,贴代码吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def sent_email(sender, pwd, receiver, text, img_path):
host = 'smtp.163.com'
port = 465
body = img_to_base64_img(img_path)
msg = MIMEText(text + "<br>" + body, 'html')
msg['subject'] = '%s的课表' % rq
msg['from'] = sender
msg['to'] = receiver
try:
s = smtplib.SMTP_SSL(host, port)
s.login(sender, pwd)
s.sendmail(sender, receiver, msg.as_string())
s.close()
print('Done.sent email success')
except smtplib.SMTPException:
print('Error.sent email fail')

然后在Linux设个定时任务就行了啦!

图2:邮箱截图