├── venue-helper
├── data
│ ├── .gitkeep
│ ├── venues.xlsx
│ ├── activities.xlsx
│ ├── RoomList2025-04-29_1.0_001.xlsx
│ └── TimeList2025-04-29_1.0_001_1.xlsx
├── README.md
├── settings.py
├── main.py
├── download.py
└── apis.py
├── img.png
├── xxt_autosign
├── user.csv
├── README.md
└── xxt.py
├── gra-autocourse
├── picture
│ └── classList说明.png
├── README.md
└── autocourse.py
├── gra-prescore
├── README.md
├── prescore.py
└── divide-prescore.py
├── keepdrcom
├── README.md
└── keep_dom.py
├── detailed-score
├── README.md
└── query.py
├── szu-dorm-helper
├── config.json
├── crawler.py
├── sc_sender.py
├── .gitignore
├── README.md
└── main.py
└── README.md
/venue-helper/data/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/img.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Matt-Dong123/tools4szu/HEAD/img.png
--------------------------------------------------------------------------------
/xxt_autosign/user.csv:
--------------------------------------------------------------------------------
1 | name,stuid
2 | testuser0,10000001
3 | testuser1,10000002
4 |
--------------------------------------------------------------------------------
/venue-helper/data/venues.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Matt-Dong123/tools4szu/HEAD/venue-helper/data/venues.xlsx
--------------------------------------------------------------------------------
/venue-helper/data/activities.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Matt-Dong123/tools4szu/HEAD/venue-helper/data/activities.xlsx
--------------------------------------------------------------------------------
/gra-autocourse/picture/classList说明.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Matt-Dong123/tools4szu/HEAD/gra-autocourse/picture/classList说明.png
--------------------------------------------------------------------------------
/venue-helper/data/RoomList2025-04-29_1.0_001.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Matt-Dong123/tools4szu/HEAD/venue-helper/data/RoomList2025-04-29_1.0_001.xlsx
--------------------------------------------------------------------------------
/venue-helper/data/TimeList2025-04-29_1.0_001_1.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Matt-Dong123/tools4szu/HEAD/venue-helper/data/TimeList2025-04-29_1.0_001_1.xlsx
--------------------------------------------------------------------------------
/gra-prescore/README.md:
--------------------------------------------------------------------------------
1 | # gra-prescore
2 | a tool for to query score
3 |
4 | 一个查研究生成绩的小工具~
5 |
6 |
7 | 1. 填入自己的cookie,运行等待输出即可
8 | 2. 本项目仅供学习,使用本工具造成的一切后果均由脚本使用者负责,开发者不承担任何责任。
9 |
--------------------------------------------------------------------------------
/gra-autocourse/README.md:
--------------------------------------------------------------------------------
1 | # Gra-autocourse
2 |
3 | 硕士自动选课小工具
4 |
5 |
6 |
7 | 1. 登录系统,获取自己的cookie和填入要选的课的编号
8 | 3. 运行脚本进行选课
9 | 5. 本项目仅供学习,使用本工具造成的一切后果均由脚本使用者负责,开发者不承担任何责任。
10 |
11 | tips:cookie在gotoChooseCourse.do文件中找到,课程编号在loadJhnCourseInfo.do文件中找到对应课程的BJDM并填入classList中。
12 |
13 | 
14 |
--------------------------------------------------------------------------------
/keepdrcom/README.md:
--------------------------------------------------------------------------------
1 | # keepdrcom
2 |
3 | Dr.com自动登录小脚本
4 |
5 | - [keep_dom.py](keep_dom.py)
6 | 宿舍区自动登录小工具
7 | - [SZU-srun-login](https://github.com/Matt-Dong123/SZU-srun-login)
8 | 教学区自动登录小工具
9 |
10 |
11 | 1. 运行脚本
12 | 2. 按照提示输入用户名和密码
13 | 3. 本项目仅供学习,使用本工具造成的一切后果均由脚本使用者负责,开发者不承担任何责任。
14 |
15 |
16 | ## Updates
17 | - 2025.01.16: 教学区自动登录换了新的登录方式,故更新了教学区自动登录脚本,[深大深澜自动登录小工具](https://github.com/Matt-Dong123/SZU-srun-login)
--------------------------------------------------------------------------------
/xxt_autosign/README.md:
--------------------------------------------------------------------------------
1 | # xxt_autosign
2 | a tool for xxt app to signup without qrcode time limitation
3 |
4 |
5 | 学习通无视二维码过期时间,无视不显示邀请码批量签到脚本。
6 | 只需要一个二维码(过期的也可以),即可实现批量给多人签到。
7 |
8 | 1. 用https://cli.im/deqr/other 扫描二维码,获取activeId
9 | 2. 将需要签到的人的姓名和学号写入user.csv,注意第一行为表头,不要修改。接下来每一行为一个人的姓名和学号,用英文逗号隔开。
10 | 3. 运行脚本进行签到
11 | 4. 可以进入https://mobilelearn.chaoxing.com/widget/sign/end_sign?activeId={} (大括号换成activeId) 查看签到结果
12 | 5. 本项目仅供学习,使用本工具造成的一切后果均由脚本使用者负责,开发者不承担任何责任。
13 |
--------------------------------------------------------------------------------
/detailed-score/README.md:
--------------------------------------------------------------------------------
1 | # detailed-score
2 | a tool for to query detailed-score for bachelors
3 |
4 | 一个查本科生详细成绩(平时成绩, 期末成绩)的小工具~
5 |
6 | # 本科详细成绩查询Detail-Score使用教程😎
7 | ---
8 | 1. 登录进入ehall办事大厅-成绩查询
9 | 2. F12进入开发者-网络并刷新
10 | 3. 搜索`xscjcx.do`
11 | 4. 复制该项的`cookies`并copy到源代码最前端的cookies值中
12 | 5. 运行即可
13 | ---
14 | p.s. 如果不熟悉Python运行环境的, 可以使用油猴脚本 [Greasyfork-szu获取详细成绩](https://github.com/Liunian2000/GradeInquiry4SZU)
15 |
16 | **本项目仅供学习,使用本工具造成的一切后果均由脚本使用者负责,开发者不承担任何责任。**
17 |
--------------------------------------------------------------------------------
/szu-dorm-helper/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "room_name": "1010",
3 | "room_id": "1010",
4 | "client": "192.168.84.110",
5 | "interval_day": 14,
6 | "remind_daily": true,
7 | "server_chan_key": "https://sc.ftqq.com/testuser.send",
8 | "remind_time": 9,
9 | "email_config": {
10 | "send_email": true,
11 | "mail_host": "smtp.qq.com",
12 | "mail_user": "test@qq.com",
13 | "mail_pass": "testpassword",
14 | "receivers": [
15 | "testemail@qq.com",
16 | "testemail2@qq.com"
17 | ]
18 | }
19 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # tools4szu
2 | Convenient tools for szu
3 |
4 |
5 | szu常用小工具~
6 | ---
7 | 请点进二级目录查看详情~
8 |
9 | - [venue-helper](venue-helper)
10 | 深大体育场馆自动预约小助手
11 | - [szu-dorm-helper](szu-dorm-helper)
12 | 深大宿舍电费提醒小助手
13 | - [xxt_autosign](xxt_autosign)
14 | 学习通自动签到小工具
15 | - [gra-autocourse](gra-autocourse)
16 | 研究生自动选课小工具
17 | - [gra-prescore](gra-prescore)
18 | 研究生查成绩小工具
19 | - [detailed-score](detailed-score)
20 | 本科查询详细成绩小工具 (可分别查平时成绩和期末成绩)
21 | - [keepdrcom](keepdrcom)
22 | 宿舍区&教学区自动联网小工具
23 |
24 | 如有任何疑问,请提PR或Issue,thx,或邮件联系`szudyh@qq.com`
25 | 如果使用有帮助,麻烦点个Star~
26 |
27 | **本项目仅供学习交流使用,使用本工具造成的一切后果均由脚本使用者负责,开发者不承担任何责任。**
28 |
29 |
30 | ---
31 | **Q&A:**
32 | - Q: 怎么获得Cookie?
33 | - A: 登录到对应的网站,打开开发者工具,找到Network,找到对应的请求,复制Cookie即可。 以detailed-score为例:
34 | 
35 | 然后把`Cookie`的值复制到脚本中对应位置即可。
36 |
--------------------------------------------------------------------------------
/szu-dorm-helper/crawler.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import datetime
3 | import re
4 |
5 |
6 | # 返回一个n*4的二维数组,分别是日期、剩余电量、总用电量、总购电量
7 | def crawlData(client: str, room_name: str, room_id: str, interval: int = 7) -> list:
8 | # 爬取网页,应该一般不会变动
9 | url = 'http://192.168.84.3:9090/cgcSims/selectList.do'
10 |
11 | # 计算今天与六天前
12 | today = datetime.date.today()
13 | days_before = str(today - datetime.timedelta(days=interval - 1))
14 | today = str(today)
15 |
16 | # 设置 post 请求参数
17 | params = {
18 | 'hiddenType': '',
19 | 'isHost': '0',
20 | 'beginTime': days_before,
21 | 'endTime': today,
22 | 'type': '2',
23 | 'client': client,
24 | 'roomId': room_id,
25 | 'roomName': room_name,
26 | 'building': ''
27 | }
28 |
29 | # 发送 post 请求,获得返回 html 文本
30 | response = requests.post(url, data=params)
31 | html = response.text
32 | # print('\n--- HTML ---\n', html, '\n--- HTML ---\n') # 调试用
33 |
34 | # 匹配需要的表格块
35 | raw_e_data = re.findall(
36 | r'
(.*?) | ', html, re.S)
37 | raw_date_data = re.findall(
38 | r'(.*?) | ', html, re.S)
39 |
40 | # 清洗数据
41 | e_data = []
42 | row, p = -1, 0 # 行数row | 第p位data
43 | for datum in raw_e_data:
44 | # 第一列为序号
45 | if p % 5 == 0:
46 | # 新建一行,插入第一个数据为日期
47 | row += 1
48 | e_data.append([])
49 | e_data[row].append(raw_date_data[row].strip()[:10])
50 | # 除了第二列(房名)以外的数据
51 | elif p % 5 != 1:
52 | e_data[row].append(float(datum.strip()))
53 | p += 1 # 读取下一个数据
54 |
55 | # print(e_data) # 调试用
56 |
57 | return e_data
58 |
--------------------------------------------------------------------------------
/venue-helper/README.md:
--------------------------------------------------------------------------------
1 | # 深大体育场馆自动预约小助手
2 |
3 | **自动预约流程比较复杂, 请务必仔细看完README后再进行操作**
4 |
5 | ## 运行流程
6 |
7 | 1. 运行前请在`settings.py`脚本中, 配置相关参数
8 | - cookies: 可以从浏览器F12中获取, 直接复制粘贴字符串格式到此处即可, 会自动进行解析成字典
9 | - stuid: 你的学号
10 | - stuname: 你的姓名
11 | - courses: 需要自动化预约的场地的列表, 详细格式请见文末
12 | - delay: 每次自动化预约的间隔, 单位是毫秒, 不建议设置过小
13 | - counts: 每次预约的次数, 默认是5次
14 | 2. 执行流程
15 | - 在`settings.py`中配置好`cookies`, 并运行`download.py`下载相关参数
16 | - 在`settings.py`中配置好所有其他信息, 包括需要自动化预约的记录
17 | - 运行`main.py`脚本, 进行自动化预约
18 | - 登录ehall中“体育场馆预约”中“我的预约”进行查看并缴费
19 | 3. 去体育场快乐的玩耍~
20 | ---
21 |
22 | ## Course的结构 (所有字段均为字符串格式!!)
23 |
24 | | 字段名 | 说明 |
25 | |--------|-----------------------------------------------------|
26 | | CGDM | 场馆代码, 通过运行`download.py`下载的`venues.xlsx`获得 |
27 | | CDWID | 场地ID, 通过运行`download.py`获得的`RoomList.xlsx`的`CDWID`字段 (可选, 如果不填则会自动根据时间和场地进行获取) |
28 | | XMDM | 项目代码, 通过运行`download.py`下载的`activities.xlsx`获得 |
29 | | XQWID | 校区ID (1:粤海, 2:丽湖) |
30 | | KYYSJD | 预约的开始时间, 形如`20:00-21:00`, 必须是合法的 |
31 | | YYRQ | 预约日期 例如 `2025-01-01` |
32 | | YYLX | 预约类型 即订场方式(`1.0`: 包场, `2.0`: 散场 ) |
33 |
34 | 下面为一个合法的course例子, settings中的courses以数组格式存储多个预约记录, 会按照设置的顺序依次进行预约.
35 | ```json
36 | {
37 | "CGDM": "001",
38 | "CDWID": "6fbd613382ef48db9d2a2d214e47bae3",
39 | "XMDM": "001",
40 | "XQWID": "1",
41 | "KYYSJD": "20:00-21:00",
42 | "YYRQ": "2025-04-29",
43 | "YYLX": "1.0"
44 | }
45 | ```
46 |
47 | ## 其他注意事项:
48 |
49 | - 可以通过Web页面获取一些参数, 也可以通过运行`download.py`脚本获取,
50 | - 场地和场馆不同, 场馆下包含若干个场地. 可以把场馆理解成羽毛球馆, 场地理解成1号羽毛球场, 2号羽毛球场...
51 | - 场地ID是一串长十六进制字符串
52 | - 运行过程中会在当前目录下生成`venue.log`文件, 记录每次预约的结果, 以及一些其他信息
53 | - 默认不对相同时间的场地重复预约, 如果需要重复预约相同时间的场次, 请修改`main.py`中的代码
54 |
55 |
--------------------------------------------------------------------------------
/venue-helper/settings.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | # -*- coding: utf-8 -*-
3 | # @File : settings.py
4 | '''
5 | CGDM: 场馆代码
6 | XMDM: 项目代码
7 | XQWID: 校区唯一标识 (1粤海, 2丽湖)
8 | KYYSJD: 可预约时间段 (形如20:00-21:00)
9 | YYRQ: 预约日期 例如 2025-01-01
10 | YYLX: 预约类型 即订场方式(1.0: 包场, 2.0: 散场 )
11 | '''
12 | # courses=[{"CGDM":"001","CDWID":"6fbd613382ef48db9d2a2d214e47bae3","XMDM":"001","XQWID":"1","KYYSJD":"20:00-21:00","YYRQ":"2025-04-29","YYLX":"1.0"}] # 预约场次列表
13 | counts = 10 # 预约总轮次
14 | courses=[{"CGDM":"008","XMDM":"002","XQWID":"1","KYYSJD":"20:00-21:00","YYRQ":"2025-04-30","YYLX":"2.0"}] # 预约场次列表
15 | delay = 200 # 延迟时间, 单位毫秒
16 | stuid = 2025101011
17 | stuname = "张xx"
18 | cookies = 'EMAP_LANG=zh; _WEU=172ad976'
19 |
20 |
21 |
22 | if isinstance(cookies, str):
23 | cookie_list = cookies.split(';')
24 | cookies = {}
25 | for item in cookie_list:
26 | item = item.strip()
27 | items = item.split('=')
28 | cookies[items[0]] = items[1]
29 | if not isinstance(cookies, dict):
30 | raise ValueError("invalid cookie!")
31 |
32 | headers = {
33 | 'Accept': '*/*',
34 | 'Accept-Language': 'zh-CN,zh;q=0.9',
35 | 'Cache-Control': 'no-cache',
36 | 'Connection': 'keep-alive',
37 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
38 | 'Origin': 'https://ehall.szu.edu.cn',
39 | 'Pragma': 'no-cache',
40 | 'Referer': 'https://ehall.szu.edu.cn/qljfwapp/sys/lwSzuCgyy/index.do?t_s=1745831680729',
41 | 'Sec-Fetch-Dest': 'empty',
42 | 'Sec-Fetch-Mode': 'cors',
43 | 'Sec-Fetch-Site': 'same-origin',
44 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36',
45 | 'X-Requested-With': 'XMLHttpRequest',
46 | 'sec-ch-ua': '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
47 | 'sec-ch-ua-mobile': '?0',
48 | 'sec-ch-ua-platform': '"Windows"',
49 | }
50 |
--------------------------------------------------------------------------------
/venue-helper/main.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import time
3 | import copy # 添加深拷贝支持
4 | from apis import postBook, getRoom
5 | from settings import courses, delay, counts
6 |
7 | logger = logging.getLogger(__name__)
8 | CDWIDs = []
9 | datas = []
10 |
11 | if __name__ == '__main__':
12 | while len(courses):
13 | course = courses[0]
14 | if course.get("CDWID", None) is None:
15 | rooms = getRoom(**{
16 | "XMDM": course["XMDM"],
17 | "YYRQ": course["YYRQ"],
18 | "YYLX": course["YYLX"],
19 | "KSSJ": course["KYYSJD"].split("-")[0],
20 | "JSSJ": course["KYYSJD"].split("-")[1],
21 | "XQDM": course["XQWID"],
22 | })
23 | time.sleep(0.2)
24 | if rooms is None:
25 | continue
26 | flag = False
27 | for room in rooms:
28 | if not room["disabled"]:
29 | flag = True
30 | course_copy = copy.deepcopy(course)
31 | course_copy["CDWID"] = room["WID"]
32 | datas.append(course_copy)
33 | if not flag:
34 | logger.error(f"没有可预约的场地: {course}")
35 | print(f"没有可预约的场地: {course}")
36 | else:
37 | datas.append(copy.deepcopy(course))
38 | courses.pop(0)
39 |
40 |
41 | for _ in range(counts):
42 | i = 0
43 | while i < len(datas):
44 | course = datas[i]
45 | try:
46 | logger.info(f"开始预约: {course}")
47 | ret = postBook(**course)
48 | if "成功" in ret:
49 | logger.info(f"预约成功: {course}")
50 | print(f"预约成功: {course}")
51 | datas = [
52 | item for item in datas
53 | if item["KYYSJD"] != course["KYYSJD"] or item["YYRQ"] != course["YYRQ"]
54 | ]
55 | i = 0
56 | continue
57 | time.sleep(delay / 1000)
58 | except Exception as e:
59 | logger.error(f"预约失败: {e}")
60 | i += 1
61 |
--------------------------------------------------------------------------------
/xxt_autosign/xxt.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import csv
3 | import random
4 | import time
5 |
6 |
7 | def xxt_signin(activeId, puid, name, stuid):
8 | url = "http://mobilelearn.chaoxing.com:80/widget/sign/signIn"
9 | request_data = {
10 | 'activeId': str(activeId),
11 | 'puid': str(puid),
12 | '_from': '',
13 | 'ignoreEwnCode': '1',
14 | 'forminfo': str([{"name": "姓名", "must": 1, "value": name}, {"name": "学号", "must": 1, "value": stuid}])
15 | }
16 | request_headers = {"Upgrade-Insecure-Requests": "1",
17 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.63 Safari/537.36",
18 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
19 | "Referer": "https://mobilelearn.chaoxing.com/widget/sign/signIn",
20 | "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "zh-CN,zh;q=0.9",
21 | "Connection": "close"
22 | }
23 | data = requests.get(url, params=request_data, headers=request_headers)
24 | return data.text
25 |
26 |
27 | if __name__ == '__main__':
28 | '''
29 | 学习通无视二维码过期时间,无视不显示邀请码签到脚本。
30 | 1. 用https://cli.im/deqr/other扫描二维码,获取activeId
31 | 2. 将需要签到的人的姓名和学号写入user.csv,注意第一行为表头,不要修改。接下来每一行为一个人的姓名和学号,用英文逗号隔开。
32 | 3. 运行脚本进行签到
33 | 4. 可以进入https://mobilelearn.chaoxing.com/widget/sign/end_sign?activeId={} (大括号换成activeId)查看签到结果
34 | '''
35 | activeId = 13953107
36 | user = []
37 | with open('user.csv', 'r') as f:
38 | for row in csv.DictReader(f, skipinitialspace=True, delimiter=','):
39 | user.append(row)
40 | print(f'共有{len(user)}个用户')
41 | for u in user:
42 | print(u['name'], u['stuid'])
43 | print('\n开始签到')
44 | for usr in user:
45 | response = xxt_signin(activeId, 100769212 + random.randint(0, 50000000), usr['name'], usr['stuid'])
46 | if '签到成功' in response:
47 | print('{} {} 签到成功'.format(usr['name'], usr['stuid']))
48 | else:
49 | print('{} {} 签到失败'.format(usr['name'], usr['stuid']))
50 | time.sleep(random.randint(0, 5))
51 | print('签到结束,请访问下面的链接查看签到结果')
52 | print(f'https://mobilelearn.chaoxing.com/widget/sign/end_sign?activeId={activeId}')
53 |
--------------------------------------------------------------------------------
/keepdrcom/keep_dom.py:
--------------------------------------------------------------------------------
1 | import os
2 | import time
3 | import requests
4 | from datetime import datetime
5 |
6 |
7 | def login(username, password, ip):
8 | burp0_url = (
9 | f"http://172.30.255.42:801/eportal/portal/login?callback=dr1003&login_method=1&user_account=%2C0%2C{username}&user_password={password}&"
10 | f"wlan_user_ip={ip}&wlan_user_ipv6=&wlan_user_mac=000000000000&wlan_ac_ip=&wlan_ac_name=&jsVersion=4.1.3&"
11 | f"terminal_type=1&lang=zh-cn&v=6275&lang=zh")
12 | burp0_headers = {
13 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.141 Safari/537.36",
14 | "Accept": "*/*", "Referer": "http://172.30.255.42/", "Accept-Encoding": "gzip, deflate",
15 | "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"}
16 | ret = requests.get(burp0_url, headers=burp0_headers)
17 | return ret.text
18 |
19 |
20 | def getIP():
21 | os.environ['NO_PROXY'] = 'http://www1.szu.edu.cn/'
22 | burp0_headers = {
23 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.141 Safari/537.36",
24 | "Accept": "*/*", "Referer": "http://172.30.255.42/", "Accept-Encoding": "gzip, deflate",
25 | "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"}
26 | ret = requests.get('http://www1.szu.edu.cn/nc/speedtest/', headers=burp0_headers).text
27 | if '1' not in ret:
28 | return None
29 | sl = ret.find('1') + len('1') - 1
30 | el = ret.find('<', sl)
31 | ret = ret[sl:el]
32 | return ret
33 |
34 |
35 | if __name__ == '__main__':
36 | username = input('username:').strip()
37 | password = input('password:')
38 | during = 60
39 | print('正在自动获取ip')
40 | ip = getIP()
41 | if ip is None:
42 | ip = input('无法自动获取ip,请手动输入')
43 | else:
44 | print('自动获取ip成功,ip:', ip)
45 | os.system('cls')
46 | print('username:', username)
47 | print('password:', '*' * len(password))
48 | print('ip:', ip)
49 | while True:
50 | try:
51 | ip = getIP()
52 | ret = login(username, password, ip)
53 | print(str(datetime.now().strftime("%Y-%m-%d %H:%M:%S")) + '\t' + ret)
54 | except InterruptedError as e:
55 | exit(0)
56 | except Exception as e:
57 | print(e)
58 | finally:
59 | time.sleep(during)
60 |
--------------------------------------------------------------------------------
/szu-dorm-helper/sc_sender.py:
--------------------------------------------------------------------------------
1 | import os
2 | import smtplib
3 | from email.mime.text import MIMEText
4 |
5 | import requests
6 | from tabulate import tabulate
7 |
8 | # 不用代理
9 | os.environ['NO_PROXY'] = 'https://sc.ftqq.com/'
10 |
11 |
12 | # 使用 Server酱 发送电量数据至微信
13 | def send(key_url: str, data: list):
14 | # post请求
15 | requests.post(key_url, data=data)
16 | return
17 |
18 |
19 | def handle(data: list, describe: str):
20 | text = '昨日用电{:.2f}度,剩余可用{:.2f}度'.format(data[-2]['cost'], data[-1]['rest'])
21 | # 表头
22 | desp = describe + '\n\n'
23 | # 出于Sever酱的markdown表格样式问题,首行表格空格为全角空格
24 | desp += ('| 日期 | 当日用电 | 可用电量 | 当日充电 |\n'
25 | '| :---: | :------: | :------: | :------: |\n')
26 |
27 | # 表格数据
28 | for line in data:
29 | for datum in line:
30 | # float数据控制小数点为两位
31 | if isinstance(line[datum], float):
32 | desp += '| {:.2f} '.format(line[datum])
33 | else:
34 | desp += '| {} '.format(line[datum])
35 | desp += '|\n'
36 |
37 | data = {
38 | 'text': text,
39 | 'desp': desp
40 | }
41 |
42 | return data
43 |
44 |
45 | def email_handle(email_config: dict, data: list):
46 | # 第三方 SMTP 服务
47 | mail_host = email_config["mail_host"] # 设置服务器
48 | mail_user = email_config["mail_user"]
49 | mail_pass = email_config["mail_pass"]
50 | receivers = email_config["receivers"]
51 |
52 | table_data = [['日期', '当日用电', '可用电量', '当日充电']]
53 | for da in data:
54 | tmp = []
55 | for datum in da:
56 | if isinstance(da[datum], float):
57 | tmp.append('{:.2f}'.format(da[datum]))
58 | else:
59 | tmp.append(da[datum])
60 | table_data.append(tmp)
61 | table_html = tabulate(table_data, headers="firstrow", tablefmt='html')
62 |
63 | # 邮件内容设置
64 | message = MIMEText(table_html, 'html', 'utf-8')
65 | # 邮件主题
66 | message['Subject'] = '昨日用电{:.2f}度,剩余可用{:.2f}度'.format(data[0]['cost'], data[0]['rest'])
67 | print(message['Subject'])
68 | # 发送方信息
69 | message['From'] = mail_user
70 |
71 | # 登录并发送邮件
72 | try:
73 | smtpObj = smtplib.SMTP()
74 | # 连接到服务器
75 | smtpObj.connect(mail_host, 25)
76 | # 登录到服务器
77 | smtpObj.login(mail_user, mail_pass)
78 | # 发送
79 | for receiver in receivers:
80 | message['To'] = receiver
81 | smtpObj.sendmail(
82 | mail_user, receiver, message.as_string()
83 | )
84 | print(f"邮件发送成功,收件人:{receiver}")
85 | # 退出
86 | smtpObj.quit()
87 | except smtplib.SMTPException as e:
88 | print('error', e) # 打印错误
89 | return
90 |
--------------------------------------------------------------------------------
/szu-dorm-helper/.gitignore:
--------------------------------------------------------------------------------
1 | # my ignore
2 | # config.json
3 | config.json.tmp
4 | .vscode/
5 | .idea/
6 |
7 | # Byte-compiled / optimized / DLL files
8 | __pycache__/
9 | *.py[cod]
10 | *$py.class
11 |
12 | # C extensions
13 | *.so
14 |
15 | # Distribution / packaging
16 | .Python
17 | build/
18 | develop-eggs/
19 | dist/
20 | downloads/
21 | eggs/
22 | .eggs/
23 | lib/
24 | lib64/
25 | parts/
26 | sdist/
27 | var/
28 | wheels/
29 | share/python-wheels/
30 | *.egg-info/
31 | .installed.cfg
32 | *.egg
33 | MANIFEST
34 |
35 | # PyInstaller
36 | # Usually these files are written by a python script from a template
37 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
38 | *.manifest
39 | *.spec
40 |
41 | # Installer logs
42 | pip-log.txt
43 | pip-delete-this-directory.txt
44 |
45 | # Unit test / coverage reports
46 | htmlcov/
47 | .tox/
48 | .nox/
49 | .coverage
50 | .coverage.*
51 | .cache
52 | nosetests.xml
53 | coverage.xml
54 | *.cover
55 | *.py,cover
56 | .hypothesis/
57 | .pytest_cache/
58 | cover/
59 |
60 | # Translations
61 | *.mo
62 | *.pot
63 |
64 | # Django stuff:
65 | *.log
66 | local_settings.py
67 | db.sqlite3
68 | db.sqlite3-journal
69 |
70 | # Flask stuff:
71 | instance/
72 | .webassets-cache
73 |
74 | # Scrapy stuff:
75 | .scrapy
76 |
77 | # Sphinx documentation
78 | docs/_build/
79 |
80 | # PyBuilder
81 | .pybuilder/
82 | target/
83 |
84 | # Jupyter Notebook
85 | .ipynb_checkpoints
86 |
87 | # IPython
88 | profile_default/
89 | ipython_config.py
90 |
91 | # pyenv
92 | # For a library or package, you might want to ignore these files since the code is
93 | # intended to run in multiple environments; otherwise, check them in:
94 | # .python-version
95 |
96 | # pipenv
97 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
98 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
99 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
100 | # install all needed dependencies.
101 | #Pipfile.lock
102 |
103 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
104 | __pypackages__/
105 |
106 | # Celery stuff
107 | celerybeat-schedule
108 | celerybeat.pid
109 |
110 | # SageMath parsed files
111 | *.sage.py
112 |
113 | # Environments
114 | .env
115 | .venv
116 | env/
117 | venv/
118 | ENV/
119 | env.bak/
120 | venv.bak/
121 |
122 | # Spyder project settings
123 | .spyderproject
124 | .spyproject
125 |
126 | # Rope project settings
127 | .ropeproject
128 |
129 | # mkdocs documentation
130 | /site
131 |
132 | # mypy
133 | .mypy_cache/
134 | .dmypy.json
135 | dmypy.json
136 |
137 | # Pyre type checker
138 | .pyre/
139 |
140 | # pytype static type analyzer
141 | .pytype/
142 |
143 | # Cython debug symbols
144 | cython_debug/
145 |
--------------------------------------------------------------------------------
/szu-dorm-helper/README.md:
--------------------------------------------------------------------------------
1 | # SZU 宿舍电量微信提醒
2 |
3 | ## 简介
4 |
5 | 1. 使用 python 简单获取深圳大学特定的宿舍电量情况。
6 | 2. 借助 [Server 酱](http://sc.ftqq.com) 将电量情况发送至微信上。或者使用邮件提醒。
7 | 3. 可配合开机自启动完成每日电量提醒功能。
8 | 4. 本项目由 [szu-dormitory-electricity-remind](https://github.com/ceynri/szu-electricity-reporter) 修改而来,感谢原作者。
9 |
10 |
11 |
12 | ## 快速部署
13 | 在安装完必要的包,并设置好config后,可以在服务器上运行以下代码在后台静默执行
14 | ``` bash
15 | nohup python -u main.py > log 2>&1 &
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 | - 电量查询表单的更新存在延迟,如果 remind_time 设置了过早的时间,可能无法获取到前一天的电量情况;
43 | - 本项目仅供学习使用,如因采用本项目产生的一切后果,均由使用人负责
44 |
45 |
46 |
47 | ## 项目结构
48 |
49 | - [main.py](main.py)
50 | 主程序,获得数据并计算电量的报表数值
51 |
52 | - [crawler.py](crawler.py)
53 | 发送电量查询请求,并进行简单数据整理的代码
54 |
55 | - [scsender.py](scsender.py)
56 | 通过 Server 酱 进行微信提醒,使用邮件进行提醒
57 |
58 |
59 |
60 | ## 使用方法
61 |
62 | 注意:本脚本需要配置 config.json 文件的内容
63 |
64 | 必填参数含义:
65 |
66 | | 参数名 | 含义 | 例子 |
67 | |--------------|------------------|-----------------|
68 | | room_name | 宿舍房间号(下面有获取教程) | "1101" |
69 | | room_id | 宿舍楼栋 id(下面有获取教程) | "7596" |
70 | | client | (含义不清楚,下面有获取教程) | "192.168.84.87" |
71 | | interval_day | 报表所要拉取的最近天数范围 | 14 |
72 |
73 | 选填参数含义:
74 |
75 | | 参数名 | 含义 | 例子 |
76 | |-----------------|--------------------------|----------------------------------|
77 | | remind_daily | 是否需要每日提醒(需要和 server 酱配合) | true |
78 | | server_chan_key | server 酱的密钥,用于微信提醒 | "https://sc.ftqq.com/xxxxx.send" |
79 | | remind_time | 每日提醒的时间(单位:时,整数,范围 0~23) | 9 |
80 | | email_config | 邮件发送设置 | 一个具体的例子如下 |
81 |
82 | ```json
83 | "email_config": {
84 | "send_email": true,
85 | "mail_host": "smtp.qq.com",
86 | "mail_user": "test@qq.com",
87 | "mail_pass": "testpassword",
88 | "receivers": [
89 | "testemail@qq.com",
90 | "testemail2@qq.com"
91 | ]
92 | }
93 | ```
94 |
95 |
96 | 环境:深大校内网
97 |
98 | 1. 获取指定的宿舍信息。参数值获取途径(以 Chrome 浏览器 为例):
99 |
100 | 校内网环境,点击F12键或空白处`右键-检查`打开开发者工具,选择 Network
101 | 选项卡,登录深大 [SIMS 电控网上查询系统](http://192.168.84.3:9090/cgcSims/)
102 | ,填写宿舍信息后,随便选择开始时间、结束时间、查询类型,点击查询,在开发者工具中选择 `selectList.do`
103 | 文件(如果没有该条记录,则尝试刷新页面),查看它的 POST 请求参数。
104 |
105 | 
106 |
107 | 将 config.json 文件内的配置对应地替换为图中红框所包含的 `client`、`roomId`、`roomName` 参数即可。
108 |
109 | 2. 如果需要启用邮件提醒功能,需要在 config.json 文件内配置 `email_config` 参数,具体配置方法见上文。
110 |
--------------------------------------------------------------------------------
/venue-helper/download.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | # -*- coding: utf-8 -*-
3 | # @File : getData.py
4 | import os
5 |
6 | import pandas as pd
7 |
8 | from apis import getSysConfig, getTimeList, getRoom
9 |
10 |
11 | def downloadSysConfig():
12 | config = getSysConfig()
13 | if config is None:
14 | print("获取系统配置失败")
15 | exit(1)
16 | venues = []
17 | for v in config["packageVenueList"]:
18 | venues.append({
19 | "WID": v["WID"],
20 | "SSXQ": v["SSXQ"],
21 | "XM": v["XM"],
22 | "CGBM": v["CGBM"],
23 | "CGMC": v["CGMC"],
24 | "DCFS": "1.0",
25 | })
26 | for v in config["dismissalVenueList"]:
27 | venues.append({
28 | "WID": v["WID"],
29 | "SSXQ": v["SSXQ"],
30 | "XM": v["XM"],
31 | "CGBM": v["CGBM"],
32 | "CGMC": v["CGMC"],
33 | "DCFS": "2.0",
34 | })
35 | activities = []
36 | for xm in config["xmList"]:
37 | activities.append({
38 | "XMDM": xm["XMDM"],
39 | "XMMC": xm["XMMC"],
40 | "DCFS": xm["DCFS"],
41 | "XQDM": xm["XQDM"],
42 | })
43 | os.makedirs(os.path.join(os.path.dirname(__file__), "data"), exist_ok=True)
44 | pd.DataFrame(venues).to_excel(os.path.join(os.path.dirname(__file__), "data", "venues.xlsx"), index=False)
45 | pd.DataFrame(activities).to_excel(os.path.join(os.path.dirname(__file__), "data", "activities.xlsx"), index=False)
46 | print("获取数据成功")
47 |
48 |
49 | def downloadTimeList(XQ, YYRQ, YYLX, XMDM):
50 | """
51 | 获取时间列表
52 | :param XQ: 校区(代码)
53 | :param YYRQ: 预约日期 例如 2025-01-01
54 | :param YYLX: 预约类型 即订场方式(1.0: 包场, 2.0: 散场 )
55 | :param XMDM: 项目代码 例如 001
56 | :return:
57 | """
58 | ret = getTimeList(XQ, YYRQ, YYLX, XMDM)
59 | if ret is None:
60 | print("获取时间列表失败")
61 | exit(1)
62 | os.makedirs(os.path.join(os.path.dirname(__file__), "data"), exist_ok=True)
63 | filename = "TimeList" + "_".join([YYRQ, YYLX, XMDM, XQ]) + ".xlsx"
64 | pd.DataFrame(ret).to_excel(os.path.join(os.path.dirname(__file__), "data", filename), index=False)
65 |
66 |
67 | def downloadRoom(XMDM, YYRQ, YYLX, KSSJ, JSSJ, XQDM):
68 | """
69 | 获取场地列表
70 | :param XMDM: 项目代码 例如 001
71 | :param YYRQ: 预约日期 例如 2025-01-01
72 | :param YYLX: 预约类型 即订场方式(1.0: 包场, 2.0: 散场 )
73 | :param KSSJ: 开始时间 例如 08:00
74 | :param JSSJ: 结束时间 例如 09:00
75 | :param XQDM: 校区代码(1:粤海, 2:丽湖)
76 | :param CGBM: 场馆编码
77 | :return:
78 | """
79 | ret = getRoom(XMDM, YYRQ, YYLX, KSSJ, JSSJ, XQDM)
80 | if ret is None:
81 | print("获取场地列表失败")
82 | exit(1)
83 | for room in ret:
84 | room["CDWID"] = room["WID"]
85 | del room["WID"]
86 | os.makedirs(os.path.join(os.path.dirname(__file__), "data"), exist_ok=True)
87 | filename = "RoomList" + "_".join([YYRQ, YYLX, XMDM]) + ".xlsx"
88 | pd.DataFrame(ret).to_excel(os.path.join(os.path.dirname(__file__), "data", filename), index=False)
89 |
90 |
91 | if __name__ == '__main__':
92 | downloadSysConfig()
93 | # 请修改下面的参数, 以查询需要的场地
94 | # downloadTimeList("1", "2025-05-01", "1.0", "001")
95 | downloadRoom("001", "2025-05-01", "1.0", "20:00", "21:00", "1")
96 |
--------------------------------------------------------------------------------
/szu-dorm-helper/main.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import json
3 | import time
4 |
5 | import crawler
6 | import sc_sender
7 |
8 |
9 | def getConfig():
10 | with open('config.json', encoding='utf-8') as f:
11 | config = json.load(f)
12 | return config
13 |
14 |
15 | # main函数
16 | def main():
17 | # 获取配置
18 | config = getConfig()
19 | room_name = config['room_name']
20 | room_id = config['room_id']
21 | client = config['client']
22 | interval_day = config['interval_day']
23 | sc_key = config['server_chan_key']
24 | remind_daily = config['remind_daily']
25 | remind_time = config['remind_time']
26 | email_config = config['email_config']
27 |
28 | if room_name == '' or room_id == '':
29 | print('[error] 未配置config.json!')
30 | exit()
31 | # 获得数据
32 | table_data = crawler.crawlData(client, room_name, room_id, interval_day)
33 | if len(table_data) == 0:
34 | print('[爬取数据失败,请检查是否能访问电费查询网站"http://192.168.84.3:9090/cgcSims/"]')
35 | exit()
36 | print('[爬取数据结束]')
37 |
38 | # 处理数据
39 | data = processingData(table_data)[::-1]
40 | print('[数据处理结束]')
41 | # 在控制台格式化输出爬虫获得的数据
42 | printData(data)
43 |
44 | if email_config['send_email']:
45 | sc_sender.email_handle(email_config, data)
46 | print('[已发送至邮箱]')
47 |
48 | # 若 sc_key 存在,则发送微信提醒
49 | if sc_key != '':
50 | # describe参数内容会添加到内容详情最前端
51 | describe = f'ᶘ ᵒᴥᵒᶅ {room_name}电量查询'
52 | # 处理数据为要发送的表格格式信息
53 | send_msg = sc_sender.handle(data, describe)
54 | # 发送信息
55 | sc_sender.send(
56 | key_url=sc_key,
57 | data=send_msg,
58 | )
59 | print('[已发送至微信]')
60 |
61 | if remind_daily is False or sc_key == '':
62 | exit()
63 | today_date = datetime.date.today()
64 | next_day_date = today_date + datetime.timedelta(days=1)
65 | next_exec_time = datetime.datetime.combine(
66 | next_day_date, datetime.time(hour=remind_time)
67 | )
68 | delta_time = (next_exec_time - datetime.datetime.now()).total_seconds()
69 | print(f'下次查询电量的时间:{next_exec_time}')
70 | time.sleep(delta_time)
71 |
72 |
73 | # 加工数据获得想要的数据格式
74 | def processingData(table_data: list):
75 | data = []
76 | day_num = len(table_data)
77 |
78 | # 日期 | 当日用电量
79 | for i in range(1, day_num):
80 | charge = table_data[i][3] - table_data[i - 1][3]
81 | data.append({
82 | 'date': table_data[i][0],
83 | 'cost': table_data[i - 1][1] - table_data[i][1],
84 | 'rest': table_data[i][1],
85 | 'charge': charge
86 | })
87 | if charge != 0:
88 | data[-1]['cost'] += charge # 充了电,则需要修正耗电计算公式问题
89 | else:
90 | data[-1]['charge'] = '-' # 没充电费
91 |
92 | # # 最后一天需要单独赋值
93 | # data.append({
94 | # 'date': table_data[day_num - 1][0],
95 | # 'cost': '-',
96 | # 'rest': table_data[day_num - 1][1],
97 | # 'charge': '-'
98 | # })
99 |
100 | return data
101 |
102 |
103 | # 格式化输出爬虫获得的数据
104 | def printData(data: list):
105 | print('日期'.ljust(8, ' '), '当日用电'.ljust(8, ' '), '可用电量'.ljust(8, ' '), '当日充电'.ljust(8, ' '))
106 | for row in data:
107 | for datum in row:
108 | value = row[datum]
109 | if isinstance(value, float):
110 | value = '{:.2f}'.format(value)
111 | print(value.ljust(12, ' '), end='')
112 | print()
113 | return
114 |
115 |
116 | if __name__ == '__main__':
117 | while True:
118 | main()
119 |
--------------------------------------------------------------------------------
/gra-prescore/prescore.py:
--------------------------------------------------------------------------------
1 | # ehall提前查研究生百分制成绩
2 | import json
3 | import os
4 |
5 | import requests
6 |
7 | os.environ['NO_PROXY'] = 'ehall.szu.edu.cn'
8 |
9 | # 复制自己字符串格式的的Cookie过来,对应填进去,会自动解析成字典
10 |
11 | cookie = ''
12 |
13 | headers = {
14 | 'Accept': 'application/json, text/javascript, */*; q=0.01',
15 | 'Accept-Language': 'zh-CN,zh;q=0.9',
16 | 'Connection': 'keep-alive',
17 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
18 | 'Origin': 'https://ehall.szu.edu.cn',
19 | 'Sec-Fetch-Dest': 'empty',
20 | 'Sec-Fetch-Mode': 'cors',
21 | 'Sec-Fetch-Site': 'same-origin',
22 | 'User-Agent': 'Mozilla/5.0',
23 | 'X-Requested-With': 'XMLHttpRequest',
24 | 'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
25 | 'sec-ch-ua-mobile': '?0',
26 | 'sec-ch-ua-platform': '"Windows"',
27 | }
28 | cookies = {}
29 |
30 |
31 | def query_gte(score: int):
32 | data = {
33 | 'querySetting': '[{"name":"DYBFZCJ","linkOpt":"AND","builderList":"cbl_Other","builder":"moreEqual","value":' + str(
34 | score) + '},{"name":"CJFZDM","linkOpt":"AND","builderList":"cbl_m_List","builder":"m_value_equal","value":"1","value_display":"百分制"}]',
35 | 'pageSize': 99,
36 | 'pageNumber': 1,
37 | }
38 | ret = requests.post(
39 | 'https://ehall.szu.edu.cn/gsapp/sys/xscjglapp/modules/xscjcx/xscjcx_dqx.do',
40 | cookies=cookies,
41 | headers=headers,
42 | data=data
43 | )
44 | return json.loads(ret.text)['datas']['xscjcx_dqx']['rows']
45 |
46 |
47 | def query(courseName: str):
48 | lScore = 0
49 | rScore = 100
50 | while lScore <= rScore:
51 | mid = round((lScore + rScore) / 2, 1)
52 | res = query_gte(mid)
53 | if courseName in str(res):
54 | lScore = mid
55 | else:
56 | rScore = mid
57 | if rScore - lScore <= 0.11:
58 | if courseName in str(query_gte(rScore)):
59 | return rScore
60 | else:
61 | return lScore
62 | return lScore
63 |
64 |
65 | def getGrade(score: float) -> float:
66 | roundScore = round(score)
67 | if roundScore >= 90:
68 | return 4.0
69 | elif roundScore >= 85:
70 | return 3.5
71 | elif roundScore >= 80:
72 | return 3.0
73 | elif roundScore >= 75:
74 | return 2.5
75 | elif roundScore >= 70:
76 | return 2.0
77 | elif roundScore >= 65:
78 | return 1.5
79 | elif roundScore >= 60:
80 | return 1.0
81 | else:
82 | return 0.0
83 |
84 |
85 | if __name__ == '__main__':
86 | cookie_list = cookie.split(';')
87 | for item in cookie_list:
88 | item = item.strip()
89 | items = item.split('=')
90 | cookies[items[0]] = items[1]
91 | coursesList = query_gte(0)
92 | courseNames = [{'courseName': c['KCMC'], 'score': 0, 'credit': c['XF']} for c in coursesList]
93 | print(f'共有 {len(courseNames)} 门百分制课程')
94 | for course in courseNames:
95 | course['score'] = query(course['courseName'])
96 | print(str(course['courseName']) + str(' ') + str(course["score"]))
97 | print('----------查询完毕----------')
98 | totalCredit = 0
99 | totalGrade = 0
100 | totalScore = 0
101 | for course in courseNames:
102 | totalCredit += course['credit']
103 | totalGrade += course['credit'] * getGrade(course['score'])
104 | totalScore += course['credit'] * course['score']
105 | print(f'总学分: {totalCredit}')
106 | print(f'总GPA: {round(totalGrade / totalCredit, 4)}')
107 | print(f'总百分制分数: {round(totalScore / totalCredit, 4)}')
108 |
--------------------------------------------------------------------------------
/detailed-score/query.py:
--------------------------------------------------------------------------------
1 | # 本科查询平时分和期末分
2 |
3 | import json
4 | import os
5 | from time import sleep
6 |
7 | import requests
8 |
9 | # 请将抓包到的cookie填入此处
10 | cookie = ""
11 |
12 |
13 | # do not modify the code below
14 | os.environ['NO_PROXY'] = 'ehall.szu.edu.cn'
15 | burp0_url = "https://ehall.szu.edu.cn:443/jwapp/sys/cjcx/modules/cjcx/xscjcx.do"
16 | cookies = {}
17 | burp0_headers = {"Sec-Ch-Ua": "\"Chromium\";v=\"117\", \"Not;A=Brand\";v=\"8\"",
18 | "Accept": "application/json, text/javascript, */*; q=0.01",
19 | "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
20 | "X-Requested-With": "XMLHttpRequest", "Sec-Ch-Ua-Mobile": "?0",
21 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.22.2 Safari/530.37",
22 | "Sec-Ch-Ua-Platform": "\"Windows\"", "Origin": "https://ehall.szu.edu.cn",
23 | "Sec-Fetch-Site": "same-origin", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Dest": "empty",
24 | "Referer": "https://ehall.szu.edu.cn/jwapp/sys/cjcx/*default/index.do",
25 | "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"}
26 |
27 | courseData = []
28 |
29 |
30 | def queryps(score):
31 | psQuery = {
32 | "querySetting": "[{\"name\":\"PSCJ\",\"value\":\"" + str(
33 | score) + "\",\"linkOpt\":\"and\",\"builder\":\"equal\"}]",
34 | "pageSize": "100", "pageNumber": "1"}
35 | ret = requests.post(burp0_url, headers=burp0_headers, cookies=cookies, data=psQuery)
36 | res = json.loads(ret.text)['datas']['xscjcx']['rows']
37 | for it in res:
38 | f = True
39 | for tmp in courseData:
40 | if it['KCM'] == tmp['KCM']:
41 | f = False
42 | tmp['PSCJ'] = score
43 | tmp['PSCJXS'] = it['PSCJXS']
44 | tmp['QMCJ'] = tmp.get('QMCJ', 0)
45 | tmp['QMCJXS'] = tmp.get('QMCJXS', 0)
46 | break
47 | if f:
48 | courseData.append({'KCM': it['KCM'], 'PSCJ': score, 'PSCJXS': it['PSCJXS'], 'QMCJ': 0, 'QMCJXS': 0})
49 |
50 |
51 | def queryQM(score):
52 | psQuery = {
53 | "querySetting": "[{\"name\":\"QMCJ\",\"value\":\"" + str(
54 | score) + "\",\"linkOpt\":\"and\",\"builder\":\"equal\"}]",
55 | "pageSize": "100", "pageNumber": "1"}
56 | ret = requests.post(burp0_url, headers=burp0_headers, cookies=cookies, data=psQuery)
57 | res = json.loads(ret.text)['datas']['xscjcx']['rows']
58 | for it in res:
59 | f = True
60 | for tmp in courseData:
61 | if it['KCM'] == tmp['KCM']:
62 | f = False
63 | tmp['QMCJ'] = score
64 | tmp['QMCJXS'] = it['QMCJXS']
65 | tmp['PSCJ'] = tmp.get('PSCJ', 0)
66 | tmp['PSCJXS'] = tmp.get('PSCJXS', 0)
67 | break
68 | if f:
69 | courseData.append({'KCM': it['KCM'], 'QMCJ': score, 'QMCJXS': it['QMCJXS'], 'PSCJ': 0, 'PSCJXS': 0})
70 |
71 |
72 | def makeCookie():
73 | cookie_list = cookie.split(';')
74 | for item in cookie_list:
75 | item = item.strip()
76 | items = item.split('=')
77 | cookies[items[0]] = items[1]
78 |
79 |
80 | if __name__ == '__main__':
81 | try:
82 | makeCookie()
83 | except Exception as e:
84 | print("Cookie解析失败")
85 | exit(0)
86 |
87 | for score in range(0, 101):
88 | queryps(score)
89 | sleep(0.2)
90 | queryQM(score)
91 | sleep(0.2)
92 | print(f"当前进度{score}%")
93 |
94 | print("=====================================")
95 | for course in courseData:
96 | print(
97 | f"{course['KCM']}: 平时成绩系数{course['PSCJXS']}, 平时成绩{course['PSCJ']}, 期末成绩系数{course['QMCJXS']}, 期末成绩{course['QMCJ']}"
98 | )
99 |
--------------------------------------------------------------------------------
/gra-autocourse/autocourse.py:
--------------------------------------------------------------------------------
1 | # 研究生选课
2 | import json
3 | import os
4 | import time
5 |
6 | import requests
7 |
8 | os.environ["no_proxy"] = "http://ehall.szu.edu.cn/" # 不使用代理
9 |
10 | # 只修改以下两个变量即可
11 | cookie = '' # 请填写你的cookie,字符串格式即可,会自动解析,例如:'JSESSIONID=xxxxxx; others=xxxxxx'
12 | classList = ['20232-02027000-2703096-1704782958121', '20232-02027000-2703119-1704785821738'] # 请填写你要选的课程班级代码
13 |
14 |
15 | '''
16 | 下方代码不要修改
17 | 下方代码不要修改
18 | 下方代码不要修改
19 | '''
20 | cookies = {}
21 | headers = {
22 | 'Accept': 'application/json, text/javascript, */*; q=0.01',
23 | 'Accept-Language': 'zh-CN,zh;q=0.9',
24 | 'Connection': 'keep-alive',
25 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
26 | 'Origin': 'https://ehall.szu.edu.cn',
27 | 'Referer': 'https://ehall.szu.edu.cn/yjsxkapp/sys/xsxkapp/xsxkHome/gotoChooseCourse.do',
28 | 'Sec-Fetch-Dest': 'empty',
29 | 'Sec-Fetch-Mode': 'cors',
30 | 'Sec-Fetch-Site': 'same-origin',
31 | 'X-Requested-With': 'XMLHttpRequest',
32 | 'sec-ch-ua': '"Not A(Brand";v="99", "Google Chrome";v="121", "Chromium";v="121"',
33 | 'sec-ch-ua-mobile': '?0',
34 | 'sec-ch-ua-platform': '"Windows"',
35 | }
36 |
37 |
38 | def chooseCourse(classID):
39 | ret = requests.post(
40 | 'https://ehall.szu.edu.cn/yjsxkapp/sys/xsxkapp/xsxkCourse/choiceCourse.do',
41 | cookies=cookies,
42 | headers=headers,
43 | data={"bjdm": classID, "lx": "0"},
44 | )
45 | return ret
46 |
47 |
48 | def loadStdCourseInfo():
49 | ret = requests.post(
50 | 'https://ehall.szu.edu.cn/yjsxkapp/sys/xsxkapp/xsxkCourse/loadStdCourseInfo.do',
51 | cookies=cookies,
52 | headers=headers,
53 | )
54 | courses = json.loads(ret.text)['results']
55 | courses.reverse()
56 | print('====================选课结果====================')
57 | for course in courses:
58 | print(f"班级代码:{course['BJDM']}, 课程名称:{course['KCMC']}, 任课教师:{course['RKJS']} ")
59 | return ret
60 |
61 |
62 | def serverCurrentTime():
63 | ret = requests.get(
64 | 'https://ehall.szu.edu.cn/yjsxkapp/sys/xsxkapp/xsxkHome/loadPublicInfo.do',
65 | cookies=cookies,
66 | headers=headers,
67 | )
68 | return json.loads(ret.text)['dqsj']
69 |
70 |
71 | def loadStuInfo():
72 | ret = requests.get(
73 | 'https://ehall.szu.edu.cn/yjsxkapp/sys/xsxkapp/xsxkHome/loadStdInfo.do',
74 | cookies=cookies,
75 | headers=headers,
76 | )
77 | info = json.loads(ret.text)
78 | print('====================学生信息====================')
79 | print(f"学号:{info['XH']}, 姓名:{info['XM']}")
80 | print(f"{info['YXMC']} {info['ZYMC']}")
81 | return ret
82 |
83 |
84 | successList = []
85 |
86 | if __name__ == '__main__':
87 | try:
88 | cookie_list = cookie.split(';')
89 | for item in cookie_list:
90 | item = item.strip()
91 | items = item.split('=')
92 | cookies[items[0]] = items[1]
93 | except Exception as e:
94 | print(f'Parsing cookie failed {e}')
95 | exit(-1)
96 |
97 | print(f"当前服务器时间:{serverCurrentTime()}")
98 | loadStuInfo()
99 | print('====================开始选课====================')
100 | while True:
101 | if len(successList) == len(classList):
102 | print('全部选课成功')
103 | break
104 | for course in classList:
105 | if course in successList:
106 | print(f'{course} 已选中')
107 | continue
108 | try:
109 | print(course, end=' ')
110 | data = chooseCourse(course)
111 | if data.status_code != 200:
112 | print(data.text)
113 | print('status_code:', data.status_code)
114 | exit(0)
115 | if json.loads(data.text)['code'] == 1:
116 | successList.append(course)
117 | print(data.text + ' 选课成功')
118 | else:
119 | print(data.text)
120 | except KeyboardInterrupt as e:
121 | print('KeyboardInterrupt')
122 | exit(0)
123 | except Exception as e:
124 | print(f'Exception occurred! {e}')
125 | time.sleep(0.5)
126 | loadStdCourseInfo()
127 |
--------------------------------------------------------------------------------
/gra-prescore/divide-prescore.py:
--------------------------------------------------------------------------------
1 | # ehall提前查研究生百分制成绩
2 | import json
3 | import os
4 |
5 | import requests
6 |
7 | os.environ['NO_PROXY'] = 'ehall.szu.edu.cn'
8 |
9 | # 复制自己字符串格式的的Cookie过来,对应填进去,会自动解析成字典
10 | cookie = 'EMAP_LANG=zh; THEME=magenta;'
11 |
12 | headers = {
13 | 'Accept': 'application/json, text/javascript, */*; q=0.01',
14 | 'Accept-Language': 'zh-CN,zh;q=0.9',
15 | 'Connection': 'keep-alive',
16 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
17 | 'Origin': 'https://ehall.szu.edu.cn',
18 | 'Sec-Fetch-Dest': 'empty',
19 | 'Sec-Fetch-Mode': 'cors',
20 | 'Sec-Fetch-Site': 'same-origin',
21 | 'User-Agent': 'Mozilla/5.0',
22 | 'X-Requested-With': 'XMLHttpRequest',
23 | 'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
24 | 'sec-ch-ua-mobile': '?0',
25 | 'sec-ch-ua-platform': '"Windows"',
26 | }
27 | cookies = {}
28 |
29 | def query_gte(score: int):
30 | data = {
31 | 'querySetting': '[{"name":"DYBFZCJ","linkOpt":"AND","builderList":"cbl_Other","builder":"moreEqual","value":' + str(
32 | score) + '},{"name":"CJFZDM","linkOpt":"AND","builderList":"cbl_m_List","builder":"m_value_equal","value":"1","value_display":"百分制"}]',
33 | 'pageSize': 99,
34 | 'pageNumber': 1,
35 | }
36 | ret = requests.post(
37 | 'https://ehall.szu.edu.cn/gsapp/sys/xscjglapp/modules/xscjcx/xscjcx_dqx.do',
38 | cookies=cookies,
39 | headers=headers,
40 | data=data
41 | )
42 | return json.loads(ret.text)['datas']['xscjcx_dqx']['rows']
43 |
44 | def query(courseName: str, lScore: float, rScore: float):
45 | while rScore - lScore > 0.11:
46 | mid = round((lScore + rScore) / 2, 1)
47 | res = query_gte(mid)
48 | if courseName in str(res):
49 | lScore = mid
50 | else:
51 | rScore = mid
52 | if courseName in str(query_gte(rScore)):
53 | return rScore
54 | else:
55 | return lScore
56 |
57 | def divideBoundary(boundy: list):
58 | if len(boundy) <= 1 or boundy[0][2] - boundy[0][1] <= 0.11:
59 | return
60 | lScore = boundy[0][1]
61 | rScore = boundy[0][2]
62 | totallLen = len(boundy)
63 | cnt = 0
64 | while rScore - lScore > 0.11:
65 | cnt = 0
66 | mid = round((lScore + rScore) / 2, 1)
67 | res = query_gte(mid)
68 | for course in boundy:
69 | if course[0] in str(res):
70 | course[1] = mid
71 | else:
72 | course[2] = mid
73 | cnt = cnt + 1
74 | if cnt != 0 and cnt != totallLen:
75 | break
76 | if cnt != 0:
77 | rScore = mid
78 | else:
79 | lScore = mid
80 | if cnt != 0 and cnt != totallLen:
81 | boundy.sort(key=lambda x:x[1])
82 | divideBoundary(boundy[:cnt])
83 | divideBoundary(boundy[cnt:])
84 |
85 | def getGrade(score: float) -> float:
86 | roundScore = round(score)
87 | if roundScore >= 90:
88 | return 4.0
89 | elif roundScore >= 85:
90 | return 3.5
91 | elif roundScore >= 80:
92 | return 3.0
93 | elif roundScore >= 75:
94 | return 2.5
95 | elif roundScore >= 70:
96 | return 2.0
97 | elif roundScore >= 65:
98 | return 1.5
99 | elif roundScore >= 60:
100 | return 1.0
101 | else:
102 | return 0.0
103 |
104 | if __name__ == '__main__':
105 | cookie_list = cookie.split(';')
106 | for item in cookie_list:
107 | item = item.strip()
108 | items = item.split('=')
109 | cookies[items[0]] = items[1]
110 | coursesList = query_gte(0)
111 | courseNames = [{'courseName': c['KCMC'], 'score': 0, 'credit': c['XF']} for c in coursesList]
112 | print(f'共有 {len(courseNames)} 门百分制课程')
113 | boundary = []
114 | for course in courseNames:
115 | boundary.append([course['courseName'], 0.0, 100.0])
116 | divideBoundary(boundary)
117 | for course in courseNames:
118 | for boundy in boundary:
119 | if course['courseName'] == boundy[0]:
120 | course['score'] = query(course['courseName'], boundy[1], boundy[2])
121 | print(str(course['courseName']) + str(' ') + str(course["score"]) + str(' ') + str(getGrade(course["score"])))
122 | break
123 | print('----------查询完毕----------')
124 | totalCredit = 0
125 | totalGrade = 0
126 | totalScore = 0
127 | for course in courseNames:
128 | totalCredit += course['credit']
129 | totalGrade += course['credit'] * getGrade(course['score'])
130 | totalScore += course['credit'] * course['score']
131 | print(f'总学分: {totalCredit}')
132 | print(f'总GPA: {round(totalGrade / totalCredit, 4)}')
133 | print(f'总百分制分数: {round(totalScore / totalCredit, 4)}')
134 |
--------------------------------------------------------------------------------
/venue-helper/apis.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | # -*- coding: utf-8 -*-
3 | # @File : apis.py
4 | import json
5 | import logging
6 | import os
7 |
8 | import requests
9 |
10 | from settings import cookies, headers, stuid, stuname
11 |
12 | os.environ['NO_PROXY'] = 'ehall.szu.edu.cn'
13 |
14 | logging.basicConfig(
15 | level=logging.INFO,
16 | format='%(asctime)s - %(levelname)s - %(message)s',
17 | handlers=[
18 | logging.FileHandler("venue.log"),
19 | logging.StreamHandler()
20 | ]
21 | )
22 | logger = logging.getLogger(__name__)
23 |
24 |
25 | def getSysConfig():
26 | """
27 | 获取系统配置
28 | {
29 | "XMDM": "001", # 项目代码
30 | "XMMC": "羽毛球", # 项目名称
31 | "STORE_NAME": "317a6df934914473b49996840b305987.png", # 插图(b用没有)
32 | "DCFS": "1.0", # 订场方式(1: 包场,2: 散场 )
33 | "XQDM": "1" # 校区代码(1:粤海, 2:丽湖)
34 | }
35 | """
36 | try:
37 | ret = requests.post(
38 | "https://ehall.szu.edu.cn/qljfwapp/sys/lwSzuCgyy/sportVenue/getSportVenueData.do",
39 | cookies=cookies,
40 | headers=headers,
41 | )
42 | logger.info(f"获取系统配置成功: {ret.text}")
43 | ret = json.loads(ret.text)
44 | return ret
45 | except requests.exceptions.RequestException as e:
46 | logger.error(f"获取系统配置失败: {e}")
47 | return None
48 |
49 |
50 | def getTimeList(XQ, YYRQ, YYLX, XMDM):
51 | """
52 | 获取时间列表
53 | :param XQ: 校区(代码)
54 | :param YYRQ: 预约日期 例如 2025-01-01
55 | :param YYLX: 预约类型 即订场方式(1.0: 包场, 2.0: 散场 )
56 | :param XMDM: 项目代码
57 | :return:
58 | """
59 | try:
60 | data = {
61 | 'XQ': XQ,
62 | 'YYRQ': YYRQ,
63 | 'YYLX': YYLX,
64 | 'XMDM': XMDM
65 | }
66 | ret = requests.post(
67 | "https://ehall.szu.edu.cn/qljfwapp/sys/lwSzuCgyy/sportVenue/getTimeList.do",
68 | cookies=cookies,
69 | headers=headers,
70 | data=data
71 | )
72 | logger.info(f"获取时间列表成功: {ret.text}")
73 | ret = json.loads(ret.text)
74 | return ret
75 | except requests.exceptions.RequestException as e:
76 | logger.error(f"获取时间列表失败: {e}")
77 | except KeyError as e:
78 | logger.error(f"获取时间列表失败: {e}")
79 | return None
80 |
81 |
82 | def getRoom(XMDM, YYRQ, YYLX, KSSJ, JSSJ, XQDM):
83 | """
84 | 获取场地列表
85 | :param XMDM: 项目代码
86 | :param YYRQ: 预约日期 例如 2025-01-01
87 | :param YYLX: 预约类型 即订场方式(1.0: 包场, 2.0: 散场 )
88 | :param KSSJ: 开始时间 例如 08:00
89 | :param JSSJ: 结束时间 例如 09:00
90 | :param XQDM: 校区代码 (1:粤海, 2:丽湖)
91 | :param CGBM: 场馆编码
92 | :return:
93 | """
94 | try:
95 | data = {
96 | 'XMDM': XMDM,
97 | 'YYRQ': YYRQ,
98 | 'YYLX': YYLX,
99 | 'KSSJ': KSSJ,
100 | 'JSSJ': JSSJ,
101 | 'XQDM': XQDM,
102 | }
103 | ret = requests.post(
104 | "https://ehall.szu.edu.cn/qljfwapp/sys/lwSzuCgyy/modules/sportVenue/getOpeningRoom.do",
105 | cookies=cookies,
106 | headers=headers,
107 | data=data
108 | )
109 | ret = json.loads(ret.text)
110 | ret = ret["datas"]["getOpeningRoom"]["rows"]
111 | logger.info(f"获取场地列表成功: {ret}")
112 | return ret
113 | except requests.exceptions.RequestException as e:
114 | logger.error(f"获取场地列表失败: {e}")
115 | except KeyError as e:
116 | logger.error(f"获取场地列表失败: {ret}")
117 | return None
118 |
119 |
120 | def postBook(CGDM, CDWID, XMDM, XQWID, KYYSJD, YYRQ, YYLX):
121 | """
122 | 预约场地
123 | :param CGDM: 场馆代码
124 | :param CDWID: 场地唯一标识
125 | :param XMDM: 项目代码
126 | :param XQWID: 校区唯一标识
127 | :param KYYSJD: 可预约时间段
128 | :param YYRQ: 预约日期 例如 2025-01-01
129 | :param YYLX: 预约类型 即订场方式(1.0: 包场, 2.0: 散场 )
130 | :return:
131 | """
132 | try:
133 | data = {
134 | 'DHID': '',
135 | 'CYRS': '', # 参与人数
136 | 'YYRGH': stuid,
137 | 'YYRXM': stuname,
138 | 'CGDM': CGDM,
139 | 'CDWID': CDWID,
140 | 'XMDM': XMDM,
141 | 'XQWID': XQWID,
142 | 'KYYSJD': KYYSJD,
143 | 'YYRQ': YYRQ,
144 | 'YYLX': YYLX,
145 | 'PC_OR_PHONE': 'pc',
146 | }
147 | times = KYYSJD.split('-')
148 | data["YYKS"] = YYRQ + " " + times[0]
149 | data["YYJS"] = YYRQ + " " + times[1]
150 | '''
151 | data = {
152 | 'DHID': '',
153 | 'YYRGH': '2100271001',
154 | 'CYRS': '',
155 | 'YYRXM': '张三',
156 | 'CGDM': '001',
157 | 'CDWID': '0ea473755da04a588ebea78af2e8ef9b',
158 | 'XMDM': '001',
159 | 'XQWID': '1',
160 | 'KYYSJD': '19:00-20:00',
161 | 'YYRQ': '2025-04-28',
162 | 'YYLX': '1.0',
163 | 'YYKS': '2025-04-28 19:00',
164 | 'YYJS': '2025-04-28 20:00',
165 | 'PC_OR_PHONE': 'pc',
166 | }
167 | '''
168 | ret = requests.post(
169 | "https://ehall.szu.edu.cn/qljfwapp/sys/lwSzuCgyy/sportVenue/insertVenueBookingInfo.do",
170 | cookies=cookies,
171 | headers=headers,
172 | data=data
173 | )
174 | logger.info(f"预约结果: {ret.text}")
175 | ret = json.loads(ret.text)
176 | return ret
177 | except requests.exceptions.RequestException as e:
178 | logger.error(f"预约失败: {e}")
179 | return None
180 |
--------------------------------------------------------------------------------