# 必要的依赖库 import re import hashlib import os import shutil from scripts.user_login import login from scripts.get_user_info import get_user_info from scripts.get_grade import get_grade from scripts.get_selected_courses import get_selected_courses from scripts.push import send_message from datetime import datetime # MD5加密 def md5_encrypt(string): return hashlib.md5(string.encode()).hexdigest() # 从环境变量中提取教务系统的URL、用户名、密码和TOKEN等信息 force_push_message = os.environ.get("FORCE_PUSH_MESSAGE") github_actions = os.environ.get("GITHUB_ACTIONS") url = os.environ.get("URL") username = os.environ.get("USERNAME") password = os.environ.get("PASSWORD") token = os.environ.get("TOKEN") github_ref_name = os.environ.get("GITHUB_REF_NAME") github_event_name = os.environ.get("GITHUB_EVENT_NAME") github_actor = os.environ.get("GITHUB_ACTOR") github_actor_id = os.environ.get("GITHUB_ACTOR_ID") github_triggering_actor = os.environ.get("GITHUB_TRIGGERING_ACTOR") repository_name = os.environ.get("REPOSITORY_NAME") github_sha = os.environ.get("GITHUB_SHA") github_workflow = os.environ.get("GITHUB_WORKFLOW") github_run_number = os.environ.get("GITHUB_RUN_NUMBER") github_run_id = os.environ.get("GITHUB_RUN_ID") beijing_time = os.environ.get("BEIJING_TIME") github_step_summary = os.environ.get("GITHUB_STEP_SUMMARY") # 将字符串转换为布尔值 # 是否强制推送信息 # 若是非GitHub Actions环境,则默认强制推送信息 force_push_message = force_push_message == "True" if github_actions else True # 定义文件路径 folder_path = "data" info_file_path = os.path.join(folder_path, "info.txt") grade_file_path = os.path.join(folder_path, "grade.txt") old_grade_file_path = os.path.join(folder_path, "old_grade.txt") # 初始化运行次数 run_count = 2 # 初始化运行日志 run_log = "" # 初始化错误内容 error_content = [] # 当前时间 current_time = "------\n" + datetime.now().strftime("%Y-%m-%d %H:%M:%S:%f")[:-3] # 当前文件名 current_file_name = os.path.realpath(__file__) # 登录 student_client = login(url, username, password) # 获取个人信息 info = get_user_info(student_client, output_type="info") # 获取完整个人信息 integrated_info = get_user_info(student_client, output_type="integrated_info") if not info or not integrated_info: error_content.append("个人信息为空") run_count = 1 elif "获取个人信息时出错" in info or "获取个人信息时出错" in integrated_info: error_content.append("获取个人信息时出错") run_count = 1 else: # 加密个人信息 encrypted_info = md5_encrypt(info) # 检查文件夹是否存在,如果不存在则创建 if not os.path.exists(folder_path): os.makedirs(folder_path) # 判断info.txt文件是否存在 if not os.path.exists(info_file_path): # 如果文件不存在,创建并写入加密后的个人信息 with open(info_file_path, "w") as info_file: info_file.write(encrypted_info) else: # 如果文件存在,读取文件内容并比较 with open(info_file_path, "r") as info_file: info_file_content = info_file.read() # 若info.txt文件中保存的个人信息与获取到的个人信息一致,则代表非第一次运行程序 if info_file_content == encrypted_info: # 非第一次运行程序 run_count = 1 # 第一次运行程序则运行两遍,否则运行一遍 for _ in range(run_count): # 如果grade.txt文件不存在,则创建文件 if not os.path.exists(grade_file_path): open(grade_file_path, "w").close() # 清空old_grade.txt文件内容 with open(old_grade_file_path, "w") as old_grade_file: old_grade_file.truncate() # 将grade.txt文件中的内容写入old_grade.txt文件内 with open(grade_file_path, "r") as grade_file, open(old_grade_file_path, "w") as old_grade_file: old_grade_file.write(grade_file.read()) # 获取成绩信息 grade = get_grade(student_client, output_type="grade") if not grade: # 成绩为空时将成绩信息定义为"成绩为空" integrated_grade_info = "------\n成绩信息:\n成绩为空\n------" error_content.append("成绩为空") elif "获取成绩时出错" in grade: # 获取成绩时出错时将成绩信息定义为"获取成绩时出错" integrated_grade_info = "------\n成绩信息:\n获取成绩时出错\n------" error_content.append("获取成绩时出错") else: # 清空grade.txt文件内容 with open(grade_file_path, "w") as grade_file: grade_file.truncate() # 获取整合后的成绩信息 integrated_grade_info = get_grade(student_client, output_type="integrated_grade_info") # 加密保存成绩 encrypted_integrated_grade_info = md5_encrypt(integrated_grade_info) # 将加密后的成绩信息写入grade.txt文件 with open(grade_file_path, "w") as grade_file: grade_file.write(encrypted_integrated_grade_info) # 加密保存成绩 encrypted_integrated_grade_info = md5_encrypt(integrated_grade_info) # 读取grade.txt和old_grade.txt文件的内容 with open(grade_file_path, "r") as grade_file, open(old_grade_file_path, "r") as old_grade_file: grade_content = grade_file.read() old_grade_content = old_grade_file.read() # 整合MD5值 integrated_grade_info += f"\n" f"MD5:{encrypted_integrated_grade_info}" # 获取未公布成绩的课程和异常的课程 selected_courses_filtering = get_selected_courses(student_client) # 工作流信息 workflow_info = ( f"------\n" f"工作流信息:\n" f"Force Push Message:{force_push_message}\n" f"Branch Name:{github_ref_name}\n" f"Triggered By:{github_event_name}\n" f"Initial Run By:{github_actor}\n" f"Initial Run By ID:{github_actor_id}\n" f"Initiated Run By:{github_triggering_actor}\n" f"Repository Name:{repository_name}\n" f"Commit SHA:{github_sha}\n" f"Workflow Name:{github_workflow}\n" f"Workflow Number:{github_run_number}\n" f"Workflow ID:{github_run_id}\n" f"Beijing Time:{beijing_time}" ) copyright_text = "Copyright © 2024 NianBroken. All rights reserved." # 第一次运行时的提示文本 first_run_text = ( "你的程序运行成功\n" "从现在开始,程序将会每隔 30 分钟自动检测一次成绩是否有更新\n" "若有更新,将通过微信推送及时通知你\n" "------" ) # 整合所有信息 # 注意此处integrated_send_info保存的是未加密的信息,仅用于信息推送 # 若是在 Github Actions 等平台运行,请不要使用print(integrated_send_info) integrated_send_info = ( f"{integrated_info}\n" f"{integrated_grade_info}\n" f"{selected_courses_filtering}\n" f"{workflow_info if github_actions else current_time}\n" f"{copyright_text}" ) # 整合首次运行时需要使用到的所有信息 first_time_run_integrated_send_info = f"{first_run_text}\n" f"{integrated_send_info}" # 整合成绩已更新时需要使用到的所有信息 grades_updated_push_integrated_send_info = ( f"{'强制推送信息成功' if force_push_message else '教务管理系统成绩已更新'}\n" f"------\n" f"{integrated_send_info}" ) if error_content: error_content = "、".join(map(str, error_content)) run_log += f"你因{error_content}原因而运行失败。\n" run_log += "------\n" else: # 如果是第一次运行,则提示程序运行成功 if run_count == 2: run_log += f"{first_run_text}\n" # 推送信息 first_run_text_response_text = send_message( token, "正方教务管理系统成绩推送", first_time_run_integrated_send_info, ) # 输出响应内容 run_log += f"{first_run_text_response_text}\n" else: # 对grade.txt和old_grade.txt两个文件的内容进行比对,输出成绩是否更新 if grade_content != old_grade_content or force_push_message: # 如果非第一次运行,则输出成绩信息 if grade: run_log += f"新成绩:{encrypted_integrated_grade_info}\n" run_log += f"旧成绩:{old_grade_content}\n" run_log += "------\n" # 判断是否选中了强制推送信息 run_log += f"{'强制推送信息' if force_push_message else '成绩已更新'}\n" # 推送信息 response_text = send_message( token, "正方教务管理系统成绩推送", grades_updated_push_integrated_send_info, ) # 输出响应内容 run_log += f"{response_text}" else: run_log += "成绩未更新" # 更新info.txt if run_count == 2: with open(info_file_path, "r") as info_file: info_file_content = info_file.read() if info_file_content != encrypted_info: with open(info_file_path, "w") as info_file: info_file.write(encrypted_info) # 输出运行日志 if run_log: print(run_log) # 如果是Github Actions运行,则将运行日志写入到GitHub Actions的日志文件中 if github_actions: # 整合JobSummary信息 github_step_summary_run_log = ( f"# 正方教务管理系统成绩推送\n{run_log}\n{workflow_info}\n{copyright_text}" ) # 定义正则表达式模式 error_content_pattern = r"你因(.*?)原因而运行失败。" error_content_replacement = ( r"你因 **\1** 原因而运行失败。\n" r"若你不明白或不理解为什么登录失败,请到上游仓库的 " r"[Issue](https://github.com/NianBroken/ZFCheckScores/issues/new 'Issue') 中寻求帮助。\n" ) # 将任意个数的换行替换为两个换行 github_step_summary_run_log = re.sub("\n+", "\n\n", github_step_summary_run_log) # 将你因xx原因而运行失败。替换为你因**xx**原因而运行失败。 github_step_summary_run_log = re.sub( error_content_pattern, error_content_replacement, github_step_summary_run_log ) # 将 github_step_summary_run_log 写入到 GitHub Actions 的环境文件中 with open(github_step_summary, "w", encoding="utf-8") as file: file.write(github_step_summary_run_log) # 删除 __pycache__ 缓存目录及其内容 current_directory = os.getcwd() scripts_folder = os.path.join(current_directory, "scripts") cache_folder = os.path.join(scripts_folder, "__pycache__") # 检查目录是否存在 if os.path.exists(cache_folder): # 删除目录及其内容 shutil.rmtree(cache_folder)