├── requirements.txt ├── .gitignore ├── search_class_id.py ├── img └── img.png ├── readme.md ├── dlut_sso.py ├── app.py ├── main.py ├── templates ├── index.html ├── monitor.html └── auto_select.html └── des.py /requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | requests 3 | configparser 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | test.py 2 | config.ini 3 | .idea 4 | __pycache__ 5 | .venv -------------------------------------------------------------------------------- /search_class_id.py: -------------------------------------------------------------------------------- 1 | from main import * 2 | 3 | print(search_class("大学物理",ilist)) -------------------------------------------------------------------------------- /img/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/songhahaha66/dlut_auto_select_courses/HEAD/img/img.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # 大连理工大学自动抢课脚本 2 | ## 介绍 3 | 本脚本仅在正选阶段测试过,不适用于预选阶段 4 | ## 配置 5 | ### config.ini 6 | 需要在项目目录中配置config.ini 7 | ``` 8 | [dlut_sso] 9 | userid = 用户名 10 | password = 密码 11 | [turn] 12 | number = 0 #选课界面会有多个轮次,这里填写你要选的轮次 13 | ``` 14 | 关于turn number解释: 15 |  16 | 如图所示,选课界面有多个轮次,上图第一个代表0,第二个代表1,以此类推 17 | 18 | ## 运行 19 | ### 获取class_id 20 | 将脚本中的`print(search_class("大学物理",ilist))`,中大学物理改为你的课程名字,并运行该脚本。从而获取id 21 | ### 运行抢课脚本 22 | 在main.py中修改 23 | ``` 24 | class_id = 6666 #请将此替换为你要选的class_id 25 | ``` 26 | 然后运行main.py即可 27 | 28 | **注:请在抢课正式开始前就完成获取class_id的操作,并运行main.py,以免耽误抢课时间** 29 | -------------------------------------------------------------------------------- /dlut_sso.py: -------------------------------------------------------------------------------- 1 | import des 2 | import requests 3 | 4 | def initial(initUrl, id): 5 | s = requests.Session() 6 | response = s.get(initUrl) 7 | al = 'LT{}cas'.format(response.text.split('LT')[1].split('cas')[0]) 8 | s.cookies.set('dlut_cas_un', id) 9 | s.cookies.set('cas_hash', "") 10 | return s, al 11 | 12 | def constructPara(id, passwd, lt): 13 | al = { 14 | #'none': 'on', 15 | 'rsa': des.strEnc(id + passwd + lt, '1', '2', '3'), 16 | 'ul': str(len(id)), 17 | 'pl': str(len(passwd)), 18 | 'lt': lt, 19 | 'sl':"0", #不知道干什么的 20 | 'execution': 'e1s1', 21 | '_eventId': 'submit', 22 | } 23 | return '&'.join([i+'='+j for i, j in al.items()]) 24 | 25 | def login(id, passwd): 26 | targetUrl = 'https://sso.dlut.edu.cn/cas/login?service=http%3A%2F%2Fjxgl.dlut.edu.cn%2Fstudent%2Fucas-sso%2Flogin' 27 | s, lt = initial(targetUrl, id) 28 | s.headers = { 29 | 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0', 30 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8' 31 | } 32 | res = s.post(targetUrl, constructPara(id, passwd, lt), headers={'Content-Type': 'application/x-www-form-urlencoded'}).headers 33 | return s 34 | 35 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, request, jsonify 2 | import json 3 | import threading 4 | import time 5 | from main import * 6 | 7 | app = Flask(__name__) 8 | 9 | # 使用main.py中已经初始化的登录信息 10 | # cookies, stu_id, turn_id, ilist 已经在main.py中定义 11 | 12 | @app.route('/') 13 | def index(): 14 | return render_template('index.html') 15 | 16 | @app.route('/auto_select') 17 | def auto_select(): 18 | return render_template('auto_select.html') 19 | 20 | @app.route('/search_course', methods=['POST']) 21 | def search_course(): 22 | try: 23 | data = request.get_json() 24 | course_name = data.get('course_name', '') 25 | campus = data.get('campus', '') 26 | 27 | # 搜索课程 28 | result = [] 29 | lesson_ids = [] 30 | for i in ilist: 31 | # 校区筛选 32 | course_campus = i.get('campus', {}).get('nameZh', '') if 'campus' in i else '' 33 | 34 | # 课程名称匹配 + 校区筛选 35 | if course_name in i['course']['nameZh'] and (not campus or campus == course_campus): 36 | teachers = ', '.join([t['nameZh'] for t in i['teachers']]) 37 | result.append({ 38 | "name": i['course']['nameZh'], 39 | "code": i['code'], 40 | "id": i['id'], 41 | "teachers": teachers, 42 | "credits": i['course']['credits'], 43 | "capacity": i['limitCount'], 44 | "campus": course_campus 45 | }) 46 | lesson_ids.append(i['id']) 47 | 48 | # 获取已选人数 49 | if lesson_ids: 50 | selected_numbers = get_selected_numbers(lesson_ids) 51 | for course in result: 52 | course_id = str(course['id']) 53 | if course_id in selected_numbers: 54 | # 格式为"已选人数-候补人数" 55 | selected_info = selected_numbers[course_id] 56 | course['selected'] = selected_info.split('-')[0] # 只显示已选人数 57 | course['selected_full'] = selected_info # 完整信息"已选-候补" 58 | else: 59 | course['selected'] = '0' 60 | course['selected_full'] = '0-0' 61 | 62 | return jsonify({'success': True, 'courses': result}) 63 | except Exception as e: 64 | return jsonify({'success': False, 'message': f'搜索失败: {str(e)}'}) 65 | 66 | @app.route('/select_course', methods=['POST']) 67 | def select_course(): 68 | try: 69 | data = request.get_json() 70 | class_id = data.get('class_id') 71 | 72 | # 选课 73 | result = select_classes(class_id, turn_id) 74 | if result is True: 75 | return jsonify({'success': True, 'message': '选课成功'}) 76 | else: 77 | error_msg = str(result) if result else "选课失败,未知错误" 78 | return jsonify({'success': False, 'message': error_msg}) 79 | except Exception as e: 80 | return jsonify({'success': False, 'message': f'选课失败: {str(e)}'}) 81 | 82 | @app.route('/drop_course', methods=['POST']) 83 | def drop_course(): 84 | try: 85 | data = request.get_json() 86 | class_id = data.get('class_id') 87 | 88 | # 退课 89 | result = drop_classes(class_id, turn_id) 90 | if result is True: 91 | return jsonify({'success': True, 'message': '退课成功'}) 92 | else: 93 | error_msg = str(result) if result else "退课失败,未知错误" 94 | return jsonify({'success': False, 'message': error_msg}) 95 | except Exception as e: 96 | return jsonify({'success': False, 'message': f'退课失败: {str(e)}'}) 97 | 98 | @app.route('/selected_courses') 99 | def selected_courses(): 100 | try: 101 | # 获取已选课程 102 | selected = get_selected_classes(turn_id) 103 | 104 | courses = [] 105 | lesson_ids = [] 106 | for course in selected: 107 | teachers = ', '.join([t['nameZh'] for t in course['teachers']]) 108 | course_campus = course.get('campus', {}).get('nameZh', '') if 'campus' in course else '' 109 | courses.append({ 110 | "name": course['course']['nameZh'], 111 | "code": course['code'], 112 | "id": course['id'], 113 | "teachers": teachers, 114 | "credits": course['course']['credits'], 115 | "campus": course_campus, 116 | "capacity": course['limitCount'] 117 | }) 118 | lesson_ids.append(course['id']) 119 | 120 | # 获取已选人数 121 | if lesson_ids: 122 | selected_numbers = get_selected_numbers(lesson_ids) 123 | for course in courses: 124 | course_id = str(course['id']) 125 | if course_id in selected_numbers: 126 | # 格式为"已选人数-候补人数" 127 | selected_info = selected_numbers[course_id] 128 | course['selected'] = selected_info.split('-')[0] # 只显示已选人数 129 | course['selected_full'] = selected_info # 完整信息"已选-候补" 130 | else: 131 | course['selected'] = '0' 132 | course['selected_full'] = '0-0' 133 | 134 | return jsonify({'success': True, 'courses': courses}) 135 | except Exception as e: 136 | return jsonify({'success': False, 'message': f'获取已选课程失败: {str(e)}'}) 137 | 138 | 139 | @app.route('/get_campuses') 140 | def get_campuses(): 141 | try: 142 | campuses = set() 143 | for i in ilist: 144 | if 'campus' in i and 'nameZh' in i['campus']: 145 | campuses.add(i['campus']['nameZh']) 146 | return jsonify({'success': True, 'campuses': sorted(list(campuses))}) 147 | except Exception as e: 148 | return jsonify({'success': False, 'message': f'获取校区失败: {str(e)}'}) 149 | 150 | @app.route("/refresh_lesson_cache", methods=['GET']) 151 | def refresh_lesson_cache(): 152 | try: 153 | global ilist 154 | ilist = get_itemList(turn_id) # 重新获取课程列表 155 | ilist = json.loads(ilist) 156 | with open("ilist.json", "w", encoding="utf-8") as f: 157 | json.dump(ilist, f, ensure_ascii=False, indent=4) 158 | return jsonify({'success': True, 'message': '课程缓存已刷新'}) 159 | except Exception as e: 160 | return jsonify({'success': False, 'message': f'刷新课程缓存失败: {str(e)}'}) 161 | 162 | @app.route('/monitor') 163 | def monitor(): 164 | return render_template('monitor.html') 165 | 166 | @app.route('/check_course_availability', methods=['POST']) 167 | def check_course_availability(): 168 | try: 169 | data = request.get_json() 170 | course_ids = data.get('course_ids', []) 171 | 172 | if not course_ids: 173 | return jsonify({'success': False, 'message': '没有提供课程ID'}) 174 | 175 | # 获取课程的当前选课人数 176 | selected_numbers = get_selected_numbers(course_ids) 177 | 178 | available_courses = [] 179 | for course_id in course_ids: 180 | course_id_str = str(course_id) 181 | if course_id_str in selected_numbers: 182 | selected_info = selected_numbers[course_id_str] 183 | selected_count = int(selected_info.split('-')[0]) 184 | 185 | # 从ilist中找到对应课程的容量信息 186 | course_info = None 187 | for course in ilist: 188 | if course['id'] == course_id: 189 | course_info = course 190 | break 191 | 192 | if course_info: 193 | capacity = course_info['limitCount'] 194 | available_spots = capacity - selected_count 195 | 196 | # 如果有余量,记录课程信息 197 | if available_spots > 0: 198 | teachers = ', '.join([t['nameZh'] for t in course_info['teachers']]) 199 | course_campus = course_info.get('campus', {}).get('nameZh', '') if 'campus' in course_info else '' 200 | available_courses.append({ 201 | 'id': course_id, 202 | 'name': course_info['course']['nameZh'], 203 | 'code': course_info['code'], 204 | 'teachers': teachers, 205 | 'campus': course_campus, 206 | 'selected': selected_count, 207 | 'capacity': capacity, 208 | 'available': available_spots 209 | }) 210 | 211 | return jsonify({ 212 | 'success': True, 213 | 'available_courses': available_courses, 214 | 'total_monitored': len(course_ids) 215 | }) 216 | except Exception as e: 217 | return jsonify({'success': False, 'message': f'检查课程余量失败: {str(e)}'}) 218 | 219 | if __name__ == '__main__': 220 | app.run(debug=True, host='0.0.0.0', port=5000) 221 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | import json 3 | import re 4 | import time 5 | 6 | import requests 7 | import dlut_sso 8 | 9 | config = configparser.ConfigParser() 10 | config.read("./config.ini",encoding="utf-8") 11 | userid = config.get("dlut_sso","userid") 12 | password = config.get("dlut_sso","password") 13 | turn_number = config.getint("turn","number") 14 | 15 | def jw_login(): 16 | s = dlut_sso.login(userid, password) 17 | cookies = { 18 | "SESSION": s.cookies['SESSION'], 19 | "INGRESSCOOKIE": s.cookies['INGRESSCOOKIE'], 20 | "SERVERNAME": s.cookies['SERVERNAME'] 21 | } 22 | s.get("http://jxgl.dlut.edu.cn/student/for-std/course-select") 23 | return cookies 24 | 25 | def get_class_turns(i): 26 | url = "http://jxgl.dlut.edu.cn/student/ws/for-std/course-select/open-turns" 27 | data = { 28 | "bizTypeId":"2", 29 | "studentId":stu_id 30 | } 31 | r = requests.post(url, data=data, cookies=cookies) 32 | data1=json.loads(r.text) 33 | data1=data1[i] #0一般是主修选课 34 | return data1['id'] 35 | 36 | def get_itemList(id): 37 | url = f"http://jxgl.dlut.edu.cn/student/cache/course-select/version/{id}/version.json" 38 | r = requests.get(url, cookies=cookies) 39 | data = json.loads(r.text) 40 | a = data['itemList'][0] 41 | url1 = f"http://cdn-dlut.supwisdom.com/student/cache/course-select/addable-lessons/{id}/{a}.json" 42 | return json.loads(requests.get(url1, cookies=cookies).text)['data'] 43 | 44 | def get_student_id(): 45 | url = "http://jxgl.dlut.edu.cn/student/for-std/course-select/single-student/turns" 46 | html = requests.get(url,cookies=cookies).text 47 | match = re.search(r'studentId\s*:\s*(\d+),', html) 48 | if match: 49 | student_id = match.group(1) 50 | return int(student_id) 51 | 52 | def select_classes(class_id, turn_id): 53 | try: 54 | url = "http://jxgl.dlut.edu.cn/student/ws/for-std/course-select/add-request" 55 | data = {"studentAssoc":stu_id,"courseSelectTurnAssoc":turn_id,"requestMiddleDtos":[{"lessonAssoc":class_id,"virtualCost":0,"scheduleGroupAssoc":None}]} 56 | r1= requests.post(url,json=data,cookies=cookies) 57 | uuid1 = r1.text 58 | url1 = "http://jxgl.dlut.edu.cn/student/ws/for-std/course-select/add-drop-response" 59 | data1 ={ 60 | "studentId":stu_id, 61 | "requestId":uuid1 62 | } 63 | r2 = requests.post(url1,data=data1,cookies=cookies) 64 | 65 | if r2.status_code != 200: 66 | return {"error": f"HTTP错误: {r2.status_code}"} 67 | 68 | try: 69 | r2_res = json.loads(r2.text) 70 | except json.JSONDecodeError: 71 | return {"error": "服务器返回无效的JSON格式"} 72 | 73 | if r2_res is None: 74 | return {"error": "服务器返回空响应"} 75 | 76 | if r2_res.get('success'): 77 | print("选课成功") 78 | return True 79 | else: 80 | print("选课失败") 81 | # 返回完整错误信息字典 82 | return r2_res['errorMessage']['textZh'] if r2_res else {"error": "未知错误"} 83 | except Exception as e: 84 | return {"error": f"选课请求异常: {str(e)}"} 85 | 86 | def drop_classes(class_id,turn_id): 87 | try: 88 | url = "http://jxgl.dlut.edu.cn/student/ws/for-std/course-select/drop-request" 89 | data = {"studentAssoc":stu_id,"lessonAssocs":[class_id],"courseSelectTurnAssoc":turn_id,"coursePackAssoc":None} 90 | r1 = requests.post(url,json=data,cookies=cookies) 91 | uuid1 = r1.text 92 | url1 = "http://jxgl.dlut.edu.cn/student/ws/for-std/course-select/add-drop-response" 93 | data1 = { 94 | "studentId": stu_id, 95 | "requestId": uuid1 96 | } 97 | r2 = requests.post(url1, data=data1, cookies=cookies) 98 | 99 | if r2.status_code != 200: 100 | return {"error": f"HTTP错误: {r2.status_code}"} 101 | 102 | try: 103 | r2_res = json.loads(r2.text) 104 | except json.JSONDecodeError: 105 | return {"error": "服务器返回无效的JSON格式"} 106 | 107 | if r2_res is None: 108 | return {"error": "服务器返回空响应"} 109 | 110 | if r2_res.get('success'): 111 | print("退课成功") 112 | return True 113 | else: 114 | print("退课失败") 115 | print(r2_res) 116 | # 返回完整错误信息字典 117 | return r2_res['errorMessage']['textZh'] if r2_res else {"error": "未知错误"} 118 | except Exception as e: 119 | return {"error": f"退课请求异常: {str(e)}"} 120 | 121 | 122 | def search_class(class_name,ilist): 123 | result = [] 124 | for i in ilist: 125 | if class_name in i['course']['nameZh']: 126 | result.append({"name":i['course']['nameZh'],"id":i['id'], "teachers":i['teachers']}) 127 | return result 128 | 129 | def get_selected_classes(turn_Id): 130 | url = "http://jxgl.dlut.edu.cn/student/ws/for-std/course-select/selected-lessons" 131 | data = { 132 | "studentId":stu_id, 133 | "turnId":turn_Id 134 | } 135 | r = requests.post(url,data=data,cookies=cookies) 136 | d = json.loads(r.text) 137 | return d 138 | 139 | def get_selected_numbers(lesson_ids): 140 | url = "http://jxgl.dlut.edu.cn/student/ws/for-std/course-select/std-count" 141 | data = [("lessonIds[]", lid) for lid in lesson_ids] 142 | r = requests.post(url, data=data, cookies=cookies) 143 | return json.loads(r.text) 144 | 145 | def batch_operations(operations, interval=2): 146 | """ 147 | 批量执行选课/退课操作 148 | operations: 操作列表,每个操作包含 {'type': 'select'|'drop', 'class_id': int} 149 | interval: 操作间隔时间(秒) 150 | """ 151 | results = [] 152 | 153 | for i, operation in enumerate(operations): 154 | operation_type = operation.get('type') 155 | class_id = operation.get('class_id') 156 | 157 | if operation_type not in ['select', 'drop']: 158 | results.append({ 159 | 'success': False, 160 | 'message': f'无效的操作类型: {operation_type}', 161 | 'operation': operation 162 | }) 163 | continue 164 | 165 | try: 166 | if operation_type == 'select': 167 | result = select_classes(class_id, turn_id) 168 | else: # drop 169 | result = drop_classes(class_id, turn_id) 170 | 171 | if result is True: 172 | msg = f'{"选课" if operation_type == "select" else "退课"}成功' 173 | results.append({ 174 | 'success': True, 175 | 'message': msg, 176 | 'operation': operation 177 | }) 178 | else: 179 | # result为错误信息字典 180 | error_msg = "未知错误" 181 | if isinstance(result, dict): 182 | # 尝试提取更详细的错误信息 183 | if 'errorMessage' in result: 184 | error_detail = result['errorMessage'] 185 | if isinstance(error_detail, dict): 186 | error_msg = error_detail.get('textZh', error_detail.get('text', error_msg)) 187 | else: 188 | error_msg = str(error_detail) 189 | elif 'message' in result: 190 | error_msg = result['message'] 191 | elif 'error' in result: 192 | error_msg = result['error'] 193 | elif isinstance(result, str): 194 | error_msg = result 195 | msg = f'{"选课" if operation_type == "select" else "退课"}失败: {error_msg}' 196 | results.append({ 197 | 'success': False, 198 | 'message': msg, 199 | 'operation': operation 200 | }) 201 | 202 | except Exception as e: 203 | results.append({ 204 | 'success': False, 205 | 'message': f'操作出错: {str(e)}', 206 | 'operation': operation 207 | }) 208 | 209 | # 如果不是最后一个操作,等待间隔时间 210 | if i < len(operations) - 1: 211 | time.sleep(interval) 212 | 213 | return results 214 | 215 | def continuous_operations(operations, interval=2, max_attempts=None, stop_check=None): 216 | """ 217 | 持续执行操作直到所有操作成功 218 | operations: 操作列表 219 | interval: 操作间隔时间(秒) 220 | max_attempts: 最大尝试次数,None表示无限制 221 | stop_check: 停止检查函数,返回True时停止执行 222 | """ 223 | attempt = 0 224 | remaining_operations = operations.copy() 225 | 226 | while remaining_operations and (max_attempts is None or attempt < max_attempts): 227 | # 检查是否需要停止 228 | if stop_check and stop_check(): 229 | print("收到停止信号,脚本终止") 230 | break 231 | 232 | attempt += 1 233 | print(f"第 {attempt} 次尝试,剩余 {len(remaining_operations)} 个操作") 234 | 235 | results = batch_operations(remaining_operations, interval) 236 | 237 | # 移除成功的操作 238 | successful_operations = [] 239 | for i, result in enumerate(results): 240 | if result['success']: 241 | successful_operations.append(remaining_operations[i]) 242 | 243 | for op in successful_operations: 244 | remaining_operations.remove(op) 245 | 246 | if remaining_operations: 247 | print(f"还有 {len(remaining_operations)} 个操作未完成,等待 {interval} 秒后继续...") 248 | time.sleep(interval) 249 | else: 250 | print("所有操作已完成!") 251 | break 252 | 253 | return len(remaining_operations) == 0, attempt 254 | 255 | def continuous_operations_with_log(operations, interval=2, max_attempts=None, stop_check=None, log_callback=None): 256 | """ 257 | 持续执行操作直到所有操作成功,带日志记录功能 258 | operations: 操作列表,每个操作包含 {'type': 'select'|'drop', 'class_id': int, 'course_info': dict} 259 | interval: 操作间隔时间(秒) 260 | max_attempts: 最大尝试次数,None表示无限制 261 | stop_check: 停止检查函数,返回True时停止执行 262 | log_callback: 日志回调函数 263 | """ 264 | attempt = 0 265 | remaining_operations = operations.copy() 266 | 267 | while remaining_operations and (max_attempts is None or attempt < max_attempts): 268 | # 检查是否需要停止 269 | if stop_check and stop_check(): 270 | if log_callback: 271 | log_callback("收到停止信号,脚本终止", 'warning') 272 | break 273 | 274 | attempt += 1 275 | if log_callback: 276 | log_callback(f"第 {attempt} 次尝试,剩余 {len(remaining_operations)} 个操作", 'info') 277 | 278 | # 执行当前轮次的所有操作 279 | successful_operations = [] 280 | for i, operation in enumerate(remaining_operations): 281 | # 添加空值检查 282 | if operation is None: 283 | if log_callback: 284 | log_callback("❌ 发现空操作,跳过", 'error') 285 | continue 286 | 287 | operation_type = operation.get('type') if isinstance(operation, dict) else None 288 | class_id = operation.get('class_id') if isinstance(operation, dict) else None 289 | course_info = operation.get('course_info', {}) if isinstance(operation, dict) else {} 290 | 291 | # 确保course_info是字典 292 | if not isinstance(course_info, dict): 293 | course_info = {} 294 | 295 | course_name = course_info.get('name', f'课程ID:{class_id}') if course_info else f'课程ID:{class_id}' 296 | 297 | if operation_type not in ['select', 'drop']: 298 | if log_callback: 299 | log_callback(f"❌ {course_name}: 无效的操作类型 {operation_type}", 'error', course_info) 300 | continue 301 | 302 | if class_id is None: 303 | if log_callback: 304 | log_callback(f"❌ {course_name}: 课程ID为空", 'error', course_info) 305 | continue 306 | 307 | try: 308 | if operation_type == 'select': 309 | result = select_classes(class_id, turn_id) 310 | else: # drop 311 | result = drop_classes(class_id, turn_id) 312 | 313 | if result is True: 314 | action = "选课" if operation_type == "select" else "退课" 315 | msg = f"✅ {course_name}: {action}成功" 316 | if log_callback: 317 | log_callback(msg, 'success', course_info) 318 | successful_operations.append(operation) 319 | else: 320 | # result为错误信息字典 321 | error_msg = "未知错误" 322 | if isinstance(result, dict): 323 | # 尝试提取更详细的错误信息 324 | if 'errorMessage' in result: 325 | error_detail = result['errorMessage'] 326 | if isinstance(error_detail, dict): 327 | error_msg = error_detail.get('textZh', error_detail.get('text', error_msg)) 328 | else: 329 | error_msg = str(error_detail) 330 | elif 'message' in result: 331 | error_msg = result['message'] 332 | elif 'error' in result: 333 | error_msg = result['error'] 334 | elif isinstance(result, str): 335 | error_msg = result 336 | 337 | action = "选课" if operation_type == "select" else "退课" 338 | msg = f"❌ {course_name}: {action}失败 - {error_msg}" 339 | if log_callback: 340 | log_callback(msg, 'error', course_info) 341 | 342 | except Exception as e: 343 | action = "选课" if operation_type == "select" else "退课" 344 | msg = f"❌ {course_name}: {action}操作出错 - {str(e)}" 345 | if log_callback: 346 | log_callback(msg, 'error', course_info) 347 | 348 | # 操作间短暂延时 349 | if i < len(remaining_operations) - 1: 350 | time.sleep(min(interval * 0.2, 1)) # 单个操作间的小延时 351 | 352 | # 移除成功的操作 353 | for op in successful_operations: 354 | remaining_operations.remove(op) 355 | 356 | if remaining_operations: 357 | if log_callback: 358 | log_callback(f"还有 {len(remaining_operations)} 个操作未完成,等待 {interval} 秒后继续...", 'info') 359 | time.sleep(interval) 360 | else: 361 | if log_callback: 362 | log_callback("🎉 所有操作已完成!", 'success') 363 | break 364 | 365 | return len(remaining_operations) == 0, attempt 366 | 367 | cookies = jw_login() 368 | stu_id = get_student_id() 369 | turn_id = get_class_turns(turn_number) 370 | try: 371 | with open("ilist.json", "r", encoding="utf-8") as f: 372 | ilist = json.load(f) 373 | except FileNotFoundError: 374 | print("ilist.json not found, fetching from server...") 375 | ilist = get_itemList(turn_id) 376 | ilist = json.loads(ilist) 377 | with open("ilist.json", "w", encoding="utf-8") as f: 378 | json.dump(ilist, f, ensure_ascii=False, indent=4) 379 | 380 | if __name__ == "__main__": 381 | class_id = 6666 #请将此替换为你要选的class_id 382 | while True: 383 | res = select_classes(class_id, turn_id) 384 | if res: 385 | break -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |