├── 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 | ![image-20240903170459936](./picture/classList说明.png) 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 | ![img.png](img.png) 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 | ![msg.jpg](https://i.loli.net/2019/10/22/9pOLRsrvIWe5Tqn.jpg) 32 | 33 | 详细数据: 34 | 35 | ![detail.jpg](https://i.loli.net/2019/10/22/H2w1zFVvcltLjA6.jpg) 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 | ![network.jpg](https://ftp.bmp.ovh/imgs/2019/09/2021ada6023d5368.jpg) 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 | --------------------------------------------------------------------------------