├── requirements.txt ├── logger.py ├── main.py ├── README.md ├── LICENSE ├── notification.py ├── .gitignore ├── config.py └── health_report.py /requirements.txt: -------------------------------------------------------------------------------- 1 | PyYAML==6.0 2 | requests==2.27.1 3 | -------------------------------------------------------------------------------- /logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | # 创建log 4 | log = logging.getLogger("log.txt") 5 | log.setLevel(logging.DEBUG) 6 | # 控制台输出Handler 7 | shandler = logging.StreamHandler() 8 | shandler.setFormatter(logging.Formatter("[%(levelname)s] %(message)s")) 9 | shandler.setLevel(logging.INFO) 10 | # 文件输出Handler 11 | fhandler = logging.FileHandler(filename="log.txt", mode="w", encoding="utf-8") 12 | fhandler.setFormatter(logging.Formatter("<%(asctime)s> [%(levelname)s] %(message)s")) 13 | fhandler.setLevel(logging.DEBUG) 14 | # 添加Handler 15 | log.addHandler(shandler) 16 | log.addHandler(fhandler) 17 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from logger import log 2 | from config import conf_student 3 | from health_report import report_by_dict 4 | from notification import msg 5 | import time 6 | 7 | if __name__ == "__main__": 8 | log.info("======程序启动=======") 9 | message = "【健康填报】v1.2.3\n" 10 | for user in conf_student: 11 | text = "" 12 | log.info(f"==={user['account']}===") 13 | for retry in range(1, 4): # 重试三次 14 | time.sleep(3) 15 | log.info(f"---第{retry}次尝试---") 16 | status, text = report_by_dict(user) 17 | if status: 18 | message += text + "\n" 19 | break 20 | else: # 若三次都失败 21 | message += text + "\n" 22 | message += "@WHUT-AutoHealthReport" 23 | log.info("=======消息推送=======") 24 | log.debug("待推送消息:\n" + message) 25 | msg(message) 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WHUT-AutoHealthReport 2 | 3 | 通过访问学校健康填报接口,模拟本科生或研究生健康填报过程。并且通过 cqhttp 发送结果到 QQ 号,或通过邮件发送结果到指定邮箱。(支持批量) 4 | 5 | 配置方法非常简单,第一次运行程序将会进行交互式的配置文件初始化操作,根据提示填写信息后将自动生成配置文件,今后可直接运行程序填报。 6 | 7 | 程序有 Windows、Linux、macOS 平台的打包版本,无需配置 Python 环境即可运行。 8 | 9 | ### 效果预览 10 | 11 | ![Preview](https://assets.zouht.com/img/md/WHUT-AutoHealthReport-README-01.png) 12 | 13 | ### 使用方法 14 | 15 | **运行打包版本** 16 | 17 | 1. 在右侧 `Releases` 界面下载对应平台版本。 18 | 2. 首次运行程序,根据程序提示填写信息。**请注意地址和在校情况的填写,尤其是假期。** 19 | 3. 定时运行程序即可自动填报(Linux 可用 `Crontab` 定时执行) 20 | 21 | **从源码运行** 22 | 23 | 1. 确保系统有 `Python3` 环境。 24 | 2. 安装模块:`pip install -r requirements.txt` 25 | 3. 首次运行 `main.py`,根据程序提示填写信息。**请注意地址和在校情况的填写,尤其是假期。** 26 | 4. 定时运行 `main.py` 即可自动填报(Linux 可用 `Crontab` 定时执行) 27 | 28 | ### 免责声明 29 | 30 | 该自动填报程序仅为了方便每日填报过程,严禁通过该程序伪造填报地址、健康状态。**若使用该程序导致不良后果,使用者需承担所有责任!** 31 | 32 | ### 问题反馈 33 | 34 | 如果你使用的时候出现了异常情况,欢迎提交 issue 并附上错误详情,程序日志为同目录的 `log.txt` 文件。 35 | 36 | 注意,日志中含有敏感隐私信息,不建议直接公开。详细日志中的 `message` 条目和 `Python Traceback` 信息可公开分享。 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Haotian Zou 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /notification.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import smtplib 3 | from config import conf_notification 4 | from logger import log 5 | from email.mime.text import MIMEText 6 | from email.utils import formataddr 7 | 8 | # ===== 邮件设置 ===== 9 | # 正确配置并启用后,每次填报后程序会发送电子邮件到对应账户。 10 | # 邮件发送使用SMTP协议,可在各大电子邮箱平台找到配置方法。 11 | mail = conf_notification["mail"]["enable"] # 是否启用邮件 12 | ssl = conf_notification["mail"]["ssl"] # 是否启用SSL 13 | host = conf_notification["mail"]["host"] # SMTP服务器地址(如smtp.qq.com) 14 | port = conf_notification["mail"]["port"] # 输入SMTP服务器端口(如465) 15 | account = conf_notification["mail"]["account"] # 发信账号 16 | password = conf_notification["mail"]["password"] # 发信密码 17 | sender = conf_notification["mail"]["sender"] # 发信人邮箱 18 | receiver = conf_notification["mail"]["receiver"] # 收信人邮箱 19 | # ===== go-cqhttp配置 ===== 20 | # 正确配置并启用后,每次填报后程序会发送QQ消息到对应收信QQ号或QQ群。 21 | # 需要正确配置go-cqhttp(https://github.com/Mrs4s/go-cqhttp)并启用HTTP API接口。 22 | # !若你不知道这是什么,请不要启用! 23 | cqhttp = conf_notification["cqhttp"]["enable"] # 是否启用go-cqhttp发信 24 | api = conf_notification["cqhttp"]["api"] # cqhttp http API 地址 25 | uid = conf_notification["cqhttp"]["uid"] # 收信QQ号,不填则不发送 26 | gid = conf_notification["cqhttp"]["gid"] # 收信群号,不填则不发送 27 | 28 | 29 | def msg(text): 30 | if cqhttp: 31 | try: 32 | send_cqhttp(text) 33 | log.info("成功发送cqhttp消息") 34 | except: 35 | log.error("发送cqhttp消息失败") 36 | else: 37 | log.info("未启用cqhttp推送") 38 | if mail: 39 | try: 40 | send_mail(text) 41 | log.info("成功发送邮件") 42 | except: 43 | log.error("发送邮件失败") 44 | else: 45 | log.info("未启用邮件发信") 46 | 47 | 48 | def send_cqhttp(text): 49 | data_user = { 50 | "user_id": uid, 51 | "message": text 52 | } 53 | data_group = { 54 | "group_id": gid, 55 | "message": text 56 | } 57 | if data_user["user_id"] != "": 58 | requests.get(api, params=data_user) 59 | if data_group["group_id"] != "": 60 | requests.get(api, params=data_group) 61 | 62 | 63 | def send_mail(text): 64 | if ssl: 65 | server = smtplib.SMTP_SSL(host, port) 66 | else: 67 | server = smtplib.SMTP(host, port) 68 | server.login(account, password) 69 | mail_msg = MIMEText(text, "plain", "utf-8") 70 | mail_msg["From"] = formataddr(["WHUT-AutoHealthReport", sender]) 71 | mail_msg["Subject"] = "【WHUT-AutoHealthReport】" 72 | server.sendmail(sender, receiver, mail_msg.as_string()) 73 | server.close() 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.txt 3 | config.yaml 4 | 5 | ### Python template 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | wheels/ 28 | share/python-wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .nox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | *.py,cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | cover/ 58 | 59 | # Translations 60 | *.mo 61 | *.pot 62 | 63 | # Django stuff: 64 | *.log 65 | local_settings.py 66 | db.sqlite3 67 | db.sqlite3-journal 68 | 69 | # Flask stuff: 70 | instance/ 71 | .webassets-cache 72 | 73 | # Scrapy stuff: 74 | .scrapy 75 | 76 | # Sphinx documentation 77 | docs/_build/ 78 | 79 | # PyBuilder 80 | .pybuilder/ 81 | target/ 82 | 83 | # Jupyter Notebook 84 | .ipynb_checkpoints 85 | 86 | # IPython 87 | profile_default/ 88 | ipython_config.py 89 | 90 | # pyenv 91 | # For a library or package, you might want to ignore these files since the code is 92 | # intended to run in multiple environments; otherwise, check them in: 93 | # .python-version 94 | 95 | # pipenv 96 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 97 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 98 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 99 | # install all needed dependencies. 100 | #Pipfile.lock 101 | 102 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 103 | __pypackages__/ 104 | 105 | # Celery stuff 106 | celerybeat-schedule 107 | celerybeat.pid 108 | 109 | # SageMath parsed files 110 | *.sage.py 111 | 112 | # Environments 113 | .env 114 | .venv 115 | env/ 116 | venv/ 117 | ENV/ 118 | env.bak/ 119 | venv.bak/ 120 | 121 | # Spyder project settings 122 | .spyderproject 123 | .spyproject 124 | 125 | # Rope project settings 126 | .ropeproject 127 | 128 | # mkdocs documentation 129 | /site 130 | 131 | # mypy 132 | .mypy_cache/ 133 | .dmypy.json 134 | dmypy.json 135 | 136 | # Pyre type checker 137 | .pyre/ 138 | 139 | # pytype static type analyzer 140 | .pytype/ 141 | 142 | # Cython debug symbols 143 | cython_debug/ 144 | 145 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import yaml 3 | 4 | conf_notification = { 5 | "mail": { 6 | "enable": False, 7 | "ssl": False, 8 | "host": "", 9 | "port": 0, 10 | "account": "", 11 | "password": "", 12 | "sender": "", 13 | "receiver": "", 14 | }, 15 | "cqhttp": { 16 | "enable": False, 17 | "api": "", 18 | "uid": "", 19 | "gid": "", 20 | } 21 | } 22 | conf_student = [] 23 | 24 | 25 | def save_config(): 26 | all_config = { 27 | "notification": conf_notification, 28 | "student": conf_student, 29 | } 30 | try: 31 | with open("config.yaml", "w") as config_file: 32 | yaml.safe_dump(all_config, config_file, sort_keys=False) 33 | except: 34 | print("配置文件保存失败") 35 | 36 | 37 | def load_config(): 38 | global conf_notification, conf_student 39 | try: 40 | with open("config.yaml", "r") as config_file: 41 | all_config = yaml.safe_load(config_file) 42 | conf_notification = all_config["notification"] 43 | conf_student = all_config["student"] 44 | except: 45 | print("配置文件读取失败,请检查配置文件是否有格式错误") 46 | 47 | 48 | def student_dict(global_province: str = "湖北省", global_city: str = "武汉市", 49 | global_county: str = "武昌区", global_street: str = "友谊大道") -> dict: 50 | account = input("输入账号: ") 51 | password = input("输入密码: ") 52 | is_graduate = input("是否为研究生(Y/N,留空默认否): ") 53 | if is_graduate == "Y" or is_graduate == "y": 54 | is_graduate = True 55 | else: 56 | is_graduate = False 57 | province = input(f"填报省份(留空默认\"{global_province}\"): ") or global_province 58 | city = input(f"填报城市(留空默认\"{global_city}\"): ") or global_city 59 | county = input(f"填报区县(留空默认\"{global_county}\"): ") or global_county 60 | street = input(f"填报街道(留空默认\"{global_street}\"): ") or global_street 61 | is_inschool = input("是否在校(Y/N,留空默认是): ") 62 | if is_inschool == "N" or is_inschool == "n": 63 | is_inschool = False 64 | else: 65 | is_inschool = True 66 | is_leacecity = input("是否离汉(Y/N,留空默认否): ") 67 | if is_leacecity == "Y" or is_leacecity == "y": 68 | is_leacecity = True 69 | else: 70 | is_leacecity = False 71 | temperature = input("填报温度,若要修改请与微信小程序一致(留空默认\"36.5°C~36.9°C\"): ") or "36.5°C~36.9°C" 72 | return { 73 | "account": account, 74 | "password": password, 75 | "is_graduate": is_graduate, 76 | "province": province, 77 | "city": city, 78 | "county": county, 79 | "street": street, 80 | "is_inschool": is_inschool, 81 | "is_leacecity": is_leacecity, 82 | "temperature": temperature, 83 | } 84 | 85 | 86 | def init_config(): 87 | print("======身份设置======") 88 | batch = input("是否批量填报,即一次填报多人(Y/N): ") 89 | if batch == "Y" or batch == "y": 90 | count = int(input("批量填报人数(留空默认\"1\"): ") or "1") 91 | print("---全局设置---\n" 92 | "填写后会成为默认值,方便后续每位同学的填写") 93 | province = input("填报省份(留空默认\"湖北省\"): ") or "湖北省" 94 | city = input("填报城市(留空默认\"武汉市\"): ") or "武汉市" 95 | county = input("填报区县(留空默认\"武昌区\"): ") or "武昌区" 96 | street = input("填报街道(留空默认\"友谊大道\"): ") or "友谊大道" 97 | for i in range(1, count + 1): 98 | print(f"---学生{i}---") 99 | conf_student.append(student_dict(province, city, county, street)) 100 | else: 101 | conf_student.append(student_dict()) 102 | print("======推送设置======") 103 | print("---邮件推送---\n" 104 | "正确配置并启用后,每次填报后程序会发送电子邮件到对应账户。\n" 105 | "邮件发送使用SMTP协议,可在各大电子邮箱平台找到配置方法。") 106 | enable_email = input("是否启用邮件推送(Y/N): ") 107 | if enable_email == "Y" or enable_email == "y": 108 | conf_notification["mail"]["enable"] = True 109 | ssl = input("是否启用SSL(Y/N): ") 110 | if ssl == "Y" or ssl == "y": 111 | conf_notification["mail"]["ssl"] = True 112 | else: 113 | conf_notification["mail"]["ssl"] = False 114 | conf_notification["mail"]["host"] = input("输入SMTP服务器地址,如smtp.qq.com: ") 115 | conf_notification["mail"]["port"] = int(input("输入SMTP服务器端口,如465: ")) 116 | conf_notification["mail"]["account"] = input("输入发信账号: ") 117 | conf_notification["mail"]["password"] = input("输入发信密码: ") 118 | conf_notification["mail"]["sender"] = input("输入发信邮箱: ") 119 | conf_notification["mail"]["receiver"] = input("输入收信邮箱: ") 120 | print("---QQ推送---\n" 121 | "正确配置并启用后,每次填报后程序会发送QQ消息到对应收信QQ号或QQ群。\n" 122 | "需要正确配置go-cqhttp(https://github.com/Mrs4s/go-cqhttp)并启用HTTP API接口。\n" 123 | "!若你不知道cqhttp是什么,请不要启用!") 124 | enable_cqhttp = input("是否启用QQ推送(Y/N): ") 125 | if enable_cqhttp == "Y" or enable_cqhttp == "y": 126 | conf_notification["cqhttp"]["enable"] = True 127 | conf_notification["cqhttp"]["api"] = input( 128 | "cqhttp http API 地址(留空默认\"http://127.0.0.1:5700/send_msg\"): ") or "http://127.0.0.1:5700/send_msg" 129 | conf_notification["cqhttp"]["uid"] = input("收信QQ号,不填则不发送: ") 130 | conf_notification["cqhttp"]["gid"] = input("收信群号,不填则不发送: ") 131 | print("======设置完成======") 132 | save_config() 133 | print("======保存完成======") 134 | 135 | 136 | if os.path.exists("config.yaml"): 137 | load_config() 138 | else: 139 | print("未发现配置文件,进行配置文件初始化") 140 | init_config() 141 | -------------------------------------------------------------------------------- /health_report.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import base64 4 | import random 5 | from logger import log 6 | 7 | # User-Agent列表 分别是Android微信、iOS微信、PC微信 8 | ua_list = [ 9 | "Mozilla/5.0 (Linux; Android 11; POCO F2 Pro Build/RKQ1.200826.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.136 Mobile Safari/537.36 MMWEBID/1230 MicroMessenger/8.0.17.2040(0x28001133) Process/toolsmp WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64", 10 | "Mozilla/5.0 (iPhone; CPU iPhone OS 15_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.20(0x1800142f) NetType/WIFI Language/zh_CN", 11 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36 MicroMessenger/7.0.9.501 NetType/WIFI MiniProgramEnv/Windows WindowsWechat" 12 | ] 13 | 14 | # http请求头 15 | headers = { 16 | # "Host": "", 17 | "Connection": "keep-alive", 18 | # "Content-Length": "", 19 | # "User-Agent": "", 20 | "X-Tag": "flyio", 21 | "content-type": "application/json", 22 | "encode": "true", 23 | "Referer": "https://servicewechat.com/wxa0738e54aae84423/21/page-frame.html", 24 | "Accept-Encoding": "gzip, deflate, br" 25 | } 26 | 27 | error_log = "" 28 | 29 | 30 | # 获取SessionID 返回:0-失败,1-成功 31 | # https://zhxg.whut.edu.cn/yqtjwx/api/login/checkBind 32 | # https://yjsxx.whut.edu.cn/wx/api/login/checkBind 33 | def check_bind(is_graduate: bool) -> int: 34 | global error_log 35 | log.info("[1] 正在创建会话") 36 | # 设置header 37 | if is_graduate: 38 | url = "https://yjsxx.whut.edu.cn/wx/api/login/checkBind" 39 | headers["Host"] = "yjsxx.whut.edu.cn" 40 | else: 41 | url = "https://zhxg.whut.edu.cn/yqtjwx/api/login/checkBind" 42 | headers["Host"] = "zhxg.whut.edu.cn" 43 | headers["User-Agent"] = random.choice(ua_list) 44 | log.debug(f"设置header完成: {headers}") 45 | data = dict_to_base64_bin({"sn": None, "idCard": None}) 46 | respounce = requests.post(url=url, headers=headers, data=data).json() 47 | log.debug(f"请求发送完成,收到响应: {respounce}") 48 | if respounce["status"]: 49 | resp_data = base64_str_to_dict(respounce["data"]) 50 | log.debug(f"响应data解码: {resp_data}") 51 | headers["Cookie"] = f"JSESSIONID={resp_data['sessionId']}" # 写入Cookie 52 | return 1 53 | else: 54 | error_log += str(respounce) 55 | log.error("[X] 创建会话出现错误,详情见log.txt") 56 | return 0 57 | 58 | 59 | # 绑定身份 返回:0-失败,1-成功 60 | # https://zhxg.whut.edu.cn/yqtjwx/api/login/bindUserInfo 61 | # https://yjsxx.whut.edu.cn/wx/api/login/bindUserInfo 62 | def bind_user_info(account: str, password: str, is_graduate: bool) -> int: 63 | global error_log 64 | log.info("[2] 正在绑定身份") 65 | if is_graduate: 66 | url = "https://yjsxx.whut.edu.cn/wx/api/login/bindUserInfo" 67 | else: 68 | url = "https://zhxg.whut.edu.cn/yqtjwx/api/login/bindUserInfo" 69 | data = dict_to_base64_bin({"sn": account, "idCard": password}) 70 | respounce = requests.post(url=url, headers=headers, data=data).json() 71 | log.debug(f"请求发送完成,收到响应: {respounce}") 72 | if respounce["status"]: 73 | resp_data = base64_str_to_dict(respounce["data"]) 74 | if resp_data["user"]["sn"] == account: 75 | log.debug(f"响应data解码: {resp_data}") 76 | return 1 77 | error_log += str(respounce) 78 | log.error("[X] 绑定身份出现错误,详情见log.txt") 79 | return 0 80 | 81 | 82 | # 健康填报 返回:0-失败,1-成功,2-已填报 83 | # https://zhxg.whut.edu.cn/yqtjwx/./monitorRegister 84 | # https://yjsxx.whut.edu.cn/wx/./monitorRegister 85 | def monitor_register(is_graduate: bool, province: str, city: str, county: str, street: str, 86 | is_inschool: bool, is_leavecity: bool, temperature: str) -> int: 87 | global error_log 88 | log.info("[3] 正在填报操作") 89 | address = province + city + county + street 90 | if is_graduate: 91 | url = "https://yjsxx.whut.edu.cn/wx/./monitorRegister" 92 | else: 93 | url = "https://zhxg.whut.edu.cn/yqtjwx/./monitorRegister" 94 | dict_data = { 95 | "diagnosisName": "", 96 | "relationWithOwn": "", 97 | "currentAddress": address, 98 | "remark": "无", 99 | "healthInfo": "正常", 100 | "isDiagnosis": "0", 101 | "isFever": "0", 102 | "isInSchool": str(int(is_inschool)), 103 | "isLeaveChengdu": str(int(is_leavecity)), 104 | "isSymptom": "0", 105 | "temperature": temperature, 106 | "province": province, 107 | "city": city, 108 | "county": county 109 | } 110 | log.debug(f"填报数据生成完成: {dict_data}") 111 | data = dict_to_base64_bin(dict_data) 112 | respounce = requests.post(url=url, headers=headers, data=data).json() 113 | log.debug(f"请求发送完成,收到响应: {respounce}") 114 | if respounce["status"]: 115 | resp_data = base64_str_to_dict(respounce["data"]) 116 | log.debug(f"响应data解码: {resp_data}") 117 | return 1 118 | elif respounce["message"] == "今日已填报": 119 | log.debug("该账号已填报") 120 | return 2 121 | else: 122 | error_log += str(respounce) 123 | log.error("[X] 填报操作出现错误,详情见log.txt") 124 | return 0 125 | 126 | 127 | # 解绑:若不解绑,下次将无法绑定 返回:0-失败,1-成功 128 | # https://zhxg.whut.edu.cn/yqtjwx/api/login/cancelBind 129 | # "https://zhxg.whut.edu.cn/yqtjwx/api/login/cancelBind" 130 | def cancel_bind(is_graduate: bool) -> int: 131 | global error_log 132 | log.info("[4] 正在解绑身份") 133 | if is_graduate: 134 | url = "https://yjsxx.whut.edu.cn/wx/api/login/cancelBind" 135 | else: 136 | url = "https://zhxg.whut.edu.cn/yqtjwx/api/login/cancelBind" 137 | respounce = requests.post(url=url, headers=headers).json() 138 | log.debug(f"请求发送完成,收到响应: {respounce}") 139 | if respounce["status"]: 140 | resp_data = base64_str_to_dict(respounce["data"]) 141 | log.debug(f"响应data解码: {resp_data}") 142 | return 1 143 | else: 144 | error_log += str(respounce) 145 | log.error("[X] 解绑身份出现错误,详情见log.txt") 146 | return 0 147 | 148 | 149 | def dict_to_base64_bin(data: dict) -> bin: 150 | string = json.dumps(data) 151 | binary = string.encode() 152 | base64_binary = base64.b64encode(binary) 153 | return base64_binary 154 | 155 | 156 | def base64_str_to_dict(data: str) -> dict: 157 | string_binary = base64.b64decode(data) 158 | string = string_binary.decode() 159 | dictionary = json.loads(string) 160 | return dictionary 161 | 162 | 163 | def report(account: str, password: str, is_graduate: bool, 164 | province: str, city: str, county: str, street: str, 165 | is_inschool: bool, is_leacecity: bool, temperature: str) -> tuple: 166 | global error_log 167 | log.debug(f"开始填报: {account}") 168 | status = 1 # 填报状态 169 | headers["Cookie"] = "" # 重置headers 170 | error_log = "" # 重置错误日志 171 | try: 172 | if status: 173 | status = check_bind(is_graduate) # 获取SessionID 174 | if status: 175 | status = bind_user_info(account, password, is_graduate) # 绑定身份 176 | if status: 177 | status = monitor_register(is_graduate, province, city, county, street, 178 | is_inschool, is_leacecity, temperature) # 健康填报 179 | finally: 180 | if not cancel_bind(is_graduate): 181 | status = 0 182 | if status == 1: 183 | log.info(f"{account}填报成功") 184 | return True, f"学生 {account} 填报成功!" 185 | elif status == 2: 186 | log.warn(f"{account}今日已填报") 187 | return True, f"学生 {account} 今日已填报!" 188 | else: 189 | log.error(f"{account}填报失败") 190 | return False, f"学生 {account} 填报失败!\n" + error_log 191 | 192 | 193 | def report_by_dict(user: dict) -> tuple: # 一层套娃而已 194 | return report(user["account"], user["password"], user["is_graduate"], 195 | user["province"], user["city"], user["county"], user["street"], 196 | user["is_inschool"], user["is_leacecity"], user["temperature"]) 197 | --------------------------------------------------------------------------------