├── .gitignore ├── README.assets ├── cookies_browser.jpg ├── cookies_pc1.jpg ├── cookies_pc2.jpg ├── cookies_phone.jpg ├── example.jpg └── example_manual.jpg ├── README.md ├── report(manual).py ├── report(manual)_cookies.py ├── report.py ├── report_cookies.py └── utils ├── httpRequestUtil.py ├── navigator.py └── user.py /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | *.ini 3 | /build 4 | /dist 5 | *.spec 6 | pack.txt 7 | __pycache__ 8 | *.bat -------------------------------------------------------------------------------- /README.assets/cookies_browser.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barryZZJ/ucas_cronavirus_report/433b6fc54427ddccac6ba4ad9b11d24a77cb5d19/README.assets/cookies_browser.jpg -------------------------------------------------------------------------------- /README.assets/cookies_pc1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barryZZJ/ucas_cronavirus_report/433b6fc54427ddccac6ba4ad9b11d24a77cb5d19/README.assets/cookies_pc1.jpg -------------------------------------------------------------------------------- /README.assets/cookies_pc2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barryZZJ/ucas_cronavirus_report/433b6fc54427ddccac6ba4ad9b11d24a77cb5d19/README.assets/cookies_pc2.jpg -------------------------------------------------------------------------------- /README.assets/cookies_phone.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barryZZJ/ucas_cronavirus_report/433b6fc54427ddccac6ba4ad9b11d24a77cb5d19/README.assets/cookies_phone.jpg -------------------------------------------------------------------------------- /README.assets/example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barryZZJ/ucas_cronavirus_report/433b6fc54427ddccac6ba4ad9b11d24a77cb5d19/README.assets/example.jpg -------------------------------------------------------------------------------- /README.assets/example_manual.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barryZZJ/ucas_cronavirus_report/433b6fc54427ddccac6ba4ad9b11d24a77cb5d19/README.assets/example_manual.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ucas_cronavirus_report 2 | 3 | 适用于国科大企业微信平台的疫情每日打卡脚本。 4 | 5 | **原理:** 6 | 7 | 基于上次打卡记录构造本次打卡记录,因此仅适用于无异常情况时的打卡,如打卡信息有变动请手动打卡。(注:考虑到最近“昨日是否接受核酸检测”一项会经常变更,故新增了在昨日记录的基础上,支持手动修改该项的脚本,详见[脚本入口说明](#脚本入口说明)) 8 | 9 | **运行演示:** 10 | 11 | | 自动打卡(账号密码登录) | 可手动修改参数(cookies登录) | 12 | | -------------------------------- | --------------------------------------- | 13 | | ![](./README.assets/example.jpg) | ![](./README.assets/example_manual.jpg) | 14 | 15 | **注意:** 16 | 17 | 由于脚本需要登录sep系统,故(只会在本地)用明文存储用户名和密码。 18 | 19 | 如果担心安全问题或无法正常登录,也提供了使用cookies登陆的方法,详见[脚本入口说明](#脚本入口说明)。 20 | 21 | 获取cookies的方法见[获取cookies](#获取cookies)。 22 | 23 | ## 目录 24 | 25 | 1. [脚本入口说明](#脚本入口说明) 26 | 2. [运行脚本](#运行脚本) 27 | 3. [用户信息配置](#用户信息配置) 28 | 4. [实现自动化](#实现自动化) 29 | 30 | ## 脚本入口说明 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |
数据构造方式登陆方式脚本名备注
自动复制昨日报告使用账号密码登录report.py
使用cookies登录report_cookies.py
可手动修改参数使用账号密码登录report(manual).py目前只支持修改“昨日是否接受核酸检测”,其他参数修改请手动填报。
使用cookies登录report(manual)_cookies.py
65 | 66 | 对于可手动修改参数的脚本,可以在调用时传入参数`y`或`n`,则不需要在执行过程中手动输入该参数,方便实现自动化。 67 | 68 | ## 运行脚本 69 | 70 | ### 用可执行文件运行 71 | 72 | 本脚本已打包成exe可执行程序,请在[releases](https://github.com/barryZZJ/ucas_cronavirus_report/releases)页面下载。 73 | 74 | ### 用python3运行 75 | 1. 配置环境 76 | 77 | ``` 78 | pip install requests 79 | pip install easydict 80 | ``` 81 | 82 | 2. - 运行入口脚本(用户名密码登录) 83 | 84 | ```sh 85 | python3 <入口脚本名>.py 86 | ``` 87 | 88 | - 对于含`manual`的脚本,可提前传入参数值`y`或`n`表示昨日是否接受核酸检测 89 | 90 | ```sh 91 | python3 report(manual).py y 92 | ``` 93 | 94 | ## 用户信息配置 95 | 96 | 首次运行时会要求输入sep系统用户名和密码(或cookies),并生成ini配置文件。 97 | 98 | 如果不小心输入了信息,可以修改`user.ini`(或`user_cookies.ini`)中的信息,或直接删除ini文件,重新运行脚本。 99 | 100 | ### 获取cookies 101 | 102 | 打卡系统需要`eai-sess`和`UUKey`两个cookie。 103 | 104 | 可以使用抓包软件获取,或手动从浏览器中获取。 105 | 106 |
107 | 抓包软件获取cookies 108 |
    109 |
  1. 安装抓包软件:我使用的是fiddler classic,安装与使用教程请自行上网搜索,注意需要安装证书才能抓取https报文。
  2. 110 |
  3. 111 |
      112 |
    • 抓PC端的包:用浏览器打开https://app.ucas.ac.cn/uc/wap/login,抓取登录时的POST报文,既可获得两个cookie。如图: 113 |
      114 | eai-sess 115 |
      116 | UUKey 117 |
    • 118 |
    • 抓手机端的包:参考这篇博客分别配置好PC端和手机端后,在手机上点开国科大企业微信——A疫情防控,然后随便找一个域名是app.ucas.ac.cn的报文,就能看到所需的cookie了。如图: 119 |
      120 | cookies_phone 121 |
    • 122 |
    123 |
  4. 124 |
125 |
126 | 127 |
128 | 从浏览器中获取cookies 129 |

使用浏览器打开https://app.ucas.ac.cn/uc/wap/login,登录后在开发者工具里找到cookie。

130 |

以Chrome为例,按下F12后,Application——左侧找到Cookies下拉菜单——选择ucas的域名。如图: 131 |
132 | cookies_browser 133 |

134 |
135 | 136 | ## 实现自动化 137 | 如果要挂服务器的话,可以自行魔改,记得把脚本里最后的`os.system('pause')`删掉。 138 | 139 | ### Windows 140 | 141 | 任务计划程序。可参考[Windows创建定时任务执行Python脚本](https://blog.csdn.net/u012849872/article/details/82719372)。 142 | 143 | ### Linux 144 | 使用`crontab`。可参考[Linux crontab 命令](https://www.runoob.com/linux/linux-comm-crontab.html)。 145 | 146 | 示例:每天早上8点运行脚本: 147 | 148 | `0 8 * * * python3 report.py` 149 | -------------------------------------------------------------------------------- /report(manual).py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from utils.navigator import Navigator 5 | from utils.user import UserManager 6 | 7 | def assert_input(msg, choices: list): 8 | while True: 9 | res = input(msg) 10 | if res in choices: 11 | return res 12 | print('输入内容错误!') 13 | 14 | print('***********************************************') 15 | print('*** ***') 16 | print('*** UCAS Cronavirus Report Script ***') 17 | print('*** Manual ***') 18 | print('*** Password Login - by ZZJ ***') 19 | print('*** ***') 20 | print('***********************************************') 21 | print() 22 | 23 | try: 24 | usermg = UserManager(False) 25 | user = usermg.get_user() 26 | nav = Navigator(user) 27 | print('登录中') 28 | nav.login() 29 | print('获取历史打卡记录中') 30 | old_data = nav.get_history_data() 31 | print('构造本次打卡记录中') 32 | new_data = nav.gen_report_data(old_data) 33 | print('请设置需修改的参数:') 34 | if len(sys.argv) > 1 and sys.argv[1] in ['y', 'Y', 'n', 'N']: 35 | res = sys.argv[1] 36 | else: 37 | res = assert_input('昨日是否接受核酸检测?[y/n]', ['y', 'Y', 'n', 'N']) 38 | sfjshsjc = 1 if res in ['y', 'Y'] else 0 39 | assert 'sfjshsjc' in new_data, '待提交数据内不含"sfjshsjc"参数!打卡系统可能有变动,则本脚本不再支持!' 40 | print('用户输入为 昨日' + ('已' if sfjshsjc else '未') + '接受核酸检测') 41 | new_data['sfjshsjc'] = sfjshsjc 42 | print('提交打卡记录中') 43 | resp = nav.do_report(new_data) 44 | if resp.json()['e']==0: 45 | print('打卡成功!') 46 | 47 | except Exception as err: 48 | print(err) 49 | 50 | os.system('pause') 51 | -------------------------------------------------------------------------------- /report(manual)_cookies.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from utils.navigator import Navigator 5 | from utils.user import UserManager 6 | 7 | def assert_input(msg, choices: list): 8 | while True: 9 | res = input(msg) 10 | if res in choices: 11 | return res 12 | print('输入内容错误!') 13 | 14 | print('***********************************************') 15 | print('*** ***') 16 | print('*** UCAS Cronavirus Report Script ***') 17 | print('*** Manual ***') 18 | print('*** Cookies Login - by ZZJ ***') 19 | print('*** ***') 20 | print('***********************************************') 21 | 22 | try: 23 | usermg = UserManager(True) 24 | user = usermg.get_user() 25 | nav = Navigator(user) 26 | nav.login() 27 | print('获取历史打卡记录中') 28 | old_data = nav.get_history_data() 29 | print('构造本次打卡记录中') 30 | new_data = nav.gen_report_data(old_data) 31 | print('请设置需修改的参数:') 32 | if len(sys.argv) > 1 and sys.argv[1] in ['y', 'Y', 'n', 'N']: 33 | res = sys.argv[1] 34 | else: 35 | res = assert_input('昨日是否接受核酸检测?[y/n]', ['y', 'Y', 'n', 'N']) 36 | sfjshsjc = 1 if res in ['y', 'Y'] else 0 37 | assert 'sfjshsjc' in new_data, '待提交数据内不含"sfjshsjc"参数!打卡系统可能有变动,则本脚本不再支持!' 38 | print('用户输入为 昨日' + ('已' if sfjshsjc else '未') + '接受核酸检测') 39 | new_data['sfjshsjc'] = sfjshsjc 40 | print('提交打卡记录中') 41 | resp = nav.do_report(new_data) 42 | if resp.json()['e']==0: 43 | print('打卡成功!') 44 | 45 | except Exception as err: 46 | print(err) 47 | 48 | os.system('pause') 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /report.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from utils.navigator import Navigator 4 | from utils.user import UserManager 5 | 6 | 7 | print('***********************************************') 8 | print('*** ***') 9 | print('*** UCAS Cronavirus Report Script ***') 10 | print('*** Password Login - by ZZJ ***') 11 | print('*** ***') 12 | print('***********************************************') 13 | print() 14 | 15 | try: 16 | usermg = UserManager(False) 17 | user = usermg.get_user() 18 | nav = Navigator(user) 19 | print('登录中') 20 | nav.login() 21 | print('获取历史打卡记录中') 22 | old_data = nav.get_history_data() 23 | print('构造本次打卡记录中') 24 | new_data = nav.gen_report_data(old_data) 25 | print('提交打卡记录中') 26 | resp = nav.do_report(new_data) 27 | if resp.json()['e']==0: 28 | print('打卡成功!') 29 | 30 | except Exception as err: 31 | print(err) 32 | 33 | os.system('pause') 34 | -------------------------------------------------------------------------------- /report_cookies.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from utils.navigator import Navigator 4 | from utils.user import UserManager 5 | 6 | 7 | print('***********************************************') 8 | print('*** ***') 9 | print('*** UCAS Cronavirus Report Script ***') 10 | print('*** Cookies Login - by ZZJ ***') 11 | print('*** ***') 12 | print('***********************************************') 13 | 14 | try: 15 | usermg = UserManager(True) 16 | user = usermg.get_user() 17 | nav = Navigator(user) 18 | nav.login() 19 | print('获取历史打卡记录中') 20 | old_data = nav.get_history_data() 21 | print('构造本次打卡记录中') 22 | new_data = nav.gen_report_data(old_data) 23 | print('提交打卡记录中') 24 | resp = nav.do_report(new_data) 25 | if resp.json()['e']==0: 26 | print('打卡成功!') 27 | 28 | except Exception as err: 29 | print(err) 30 | 31 | os.system('pause') 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /utils/httpRequestUtil.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Union 2 | import requests 3 | from contextlib import contextmanager 4 | 5 | @contextmanager 6 | def httpRequest(session, url, method, payload: dict = None, **params): 7 | """ 8 | Generate a context to automatically send http request, allowing user to focus on response handling. 9 | 10 | Parameters 11 | ---------- 12 | session : requests.Session 13 | Provide a session to be used. 14 | url : str 15 | Full url address with scheme 'http://' or 'https://'. 16 | method : str 17 | Only 'get' or 'post' is supported. 18 | payload : dict, optional 19 | This parameter is a lazy replacement for "params" and "data" (correspond to GET and POST respectively). 20 | **params : 21 | Additional arguments passed to package ``request``, such as headers and cookies. 22 | 23 | Returns 24 | ------- 25 | requests.Response 26 | 27 | Examples 28 | -------- 29 | >>> s = requests.Session() 30 | >>> with httpRequest(s, 'https://www.bilibili.com', 'get', 31 | ... payload={'key': 'data'}, 32 | ... headers={}, 33 | ... cookies={}) as resp: 34 | ... # Handle the response object 35 | ... print(resp.ok) 36 | True 37 | """ 38 | if params is None: 39 | params = {} 40 | 41 | # DEBUG = True 42 | # if DEBUG: 43 | # params = { 44 | # 'proxies': { 45 | # 'http': 'http://127.0.0.1:9999', 46 | # 'https': 'http://127.0.0.1:9999', 47 | # } 48 | # } 49 | 50 | # Set default headers 51 | headers = params.setdefault('headers', {}) 52 | headers.setdefault('User-Agent', 53 | 'Mozilla/5.0 (Linux; Android 10; NOH-AN00 Build/HUAWEINOH-AN00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/86.0.4240.99 XWEB/3263 MMWEBSDK/20211202 Mobile Safari/537.36 MMWEBID/9462 MicroMessenger/8.0.18.2060(0x28001257) Process/toolsmp WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64') 54 | 55 | if method not in ['get', 'post']: 56 | raise NotImplementedError('http method', method, 'is not supported!') 57 | 58 | if payload: 59 | # replace key 'payload' with the correct key 60 | payload_key = 'params' if method == 'get' else 'data' 61 | params[payload_key] = payload 62 | 63 | # send the request 64 | yield session.request(method, url, **params) 65 | 66 | -------------------------------------------------------------------------------- /utils/navigator.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import requests 4 | from easydict import EasyDict as edict 5 | from utils.user import User 6 | from utils.httpRequestUtil import httpRequest 7 | 8 | conf = edict(dict( 9 | auth_welcome=dict(url='https://app.ucas.ac.cn/uc/wap/login', method='get'), 10 | auth_check=dict(url='https://app.ucas.ac.cn/uc/wap/login/check', method='post'), 11 | history_data=dict(url='https://app.ucas.ac.cn/ucasncov/api/default/daily?xgh=0&app_id=ucas', method='get'), 12 | report=dict(url='https://app.ucas.ac.cn/ucasncov/api/default/save', method='post'), 13 | )) 14 | 15 | 16 | class Navigator: 17 | REPORT_KEYS = ['date', 'realname', 'number', 'jzdz', 'zrzsdd', 'sfzx', 'dqszdd', 'geo_api_infot', 'szgj', 'szgj_select_info[id]', 'szgj_select_info[name]', 'geo_api_info', 'dqsfzzgfxdq', 'zgfxljs', 'tw', 'sffrzz', 'dqqk1', 'dqqk1qt', 'dqqk2', 'dqqk2qt', 'sfjshsjc', 'dyzymjzqk', 'dyzwjzyy', 'dyzjzsj', 'dezymjzqk', 'dezwjzyy', 'dezjzsj', 'dszymjzqk', 'dszwjzyy', 'dszjzsj', 'gtshryjkzk', 'extinfo', 'app_id'] 18 | def __init__(self, user: User): 19 | self.s = requests.Session() 20 | self.user = user 21 | 22 | def login(self): 23 | # load user config from file 24 | if self.user.use_cookies: 25 | cookies = self.user.get_info() 26 | self.s.cookies.update(cookies) 27 | else: 28 | # authenticater user to get cookies (stored in session) 29 | self._authenticate(self.user) 30 | 31 | def _authenticate(self, user: User): 32 | # get first cookie eai-sess 33 | with httpRequest(self.s, **conf.auth_welcome) as resp: 34 | assert 'eai-sess' in self.s.cookies, self._err_msg('获取cookie eai-sess', 'cookie中不含eai-sess') 35 | 36 | # get second cookie UUkey 37 | payload = user.get_info() 38 | with httpRequest(self.s, **conf.auth_check, payload=payload) as resp: 39 | self._assert_json(resp, '获取cookie UUkey') 40 | assert 'UUkey' in self.s.cookies, self._err_msg('获取cookie UUkey', 'cookie中不含UUkey') 41 | 42 | def get_history_data(self): 43 | with httpRequest(self.s, **conf.history_data) as resp: 44 | self._assert_json(resp, '获取上次记录') 45 | res = resp.json() 46 | assert 'd' in res, self._err_msg('获取上次记录', '结果不含数据:{}'.format(res)) 47 | return res['d'] 48 | 49 | def gen_report_data(self, old_data): 50 | new_data = {key: old_data[key] for key in old_data if key in self.REPORT_KEYS} 51 | # 注意,经对比需要对数据做一下小调整, 52 | new_data.update({ 53 | 'date': time.strftime("%Y-%m-%d", time.localtime()), 54 | 'geo_api_infot': '{"area":{"label":"","value":""},"city":{"label":"","value":""},"address":"","details":"","province":{"label":"","value":""}}', 55 | 'szgj': '', 56 | 'szgj_select_info[id]': '0', 57 | 'szgj_select_info[name]': '', 58 | 'app_id': 'ucas', 59 | }) 60 | assert len(new_data) == len(self.REPORT_KEYS), self._err_msg('构造打卡数据', '构造的数据不完整!已有数据为:{}'.format(list(new_data.keys()))) 61 | return new_data 62 | 63 | def do_report(self, data): 64 | with httpRequest(self.s, **conf.report, payload=data) as resp: 65 | self._assert_json(resp, '提交打卡数据') 66 | return resp 67 | 68 | @staticmethod 69 | def _assert_json(resp: requests.Response, msg=''): 70 | try: 71 | js = resp.json() # type: dict 72 | except requests.exceptions.JSONDecodeError as err: 73 | raise requests.exceptions.ContentDecodingError(f'{msg}失败!json解析错误,返回结果为:{resp.text}') 74 | assert 'e' in js, f'{msg}失败!json不含键"e",返回结果为:{resp.text}' 75 | assert js['e'] == 0, f'{msg}失败!服务器报错信息为:{js["m"]}' 76 | 77 | @staticmethod 78 | def _err_msg(msg1, msg2=''): 79 | return f'{msg1}失败!{msg2}' -------------------------------------------------------------------------------- /utils/user.py: -------------------------------------------------------------------------------- 1 | import os 2 | import configparser 3 | import sys 4 | 5 | 6 | class User: 7 | def __init__(self, use_cookies): 8 | self.use_cookies = use_cookies 9 | self.info = None 10 | 11 | def get_info(self): 12 | return self.info 13 | 14 | class User_pass(User): 15 | def __init__(self, username, password): 16 | super().__init__(False) 17 | self.info = dict( 18 | username=username, 19 | password=password 20 | ) 21 | 22 | class User_cookie(User): 23 | def __init__(self, eai_sess, UUKey): 24 | super().__init__(True) 25 | self.info = { 26 | 'eai-sess': eai_sess, 27 | 'UUKey': UUKey 28 | } 29 | 30 | 31 | class UserManager: 32 | BASEPATH = os.path.dirname(os.path.dirname(sys.executable if hasattr(sys, 'frozen') else __file__)) # 打包成exe文件后__file__失效 33 | CONFPATH = [os.path.join(BASEPATH, 'user.ini'), os.path.join(BASEPATH, 'user_cookies.ini')] 34 | def __init__(self, use_cookies): 35 | self.use_cookies = use_cookies 36 | self.config = {} 37 | self.check_new_user() 38 | self.load_config() 39 | 40 | def check_new_user(self): 41 | if not os.path.exists(self.CONFPATH[self.use_cookies]) or os.path.getsize(self.CONFPATH[self.use_cookies]) == 0: 42 | print('未检测到配置文件'+self.CONFPATH[self.use_cookies]+',请输入', end='') 43 | if self.use_cookies: 44 | print('cookies\n(默认记住此配置,如需更新请删除或修改.ini文件)\n') 45 | eai_sess = input('eai-sess: ') 46 | UUKey = input('UUKey: ') 47 | self.config = { 48 | 'eai-sess': eai_sess, 49 | 'UUKey': UUKey 50 | } 51 | else: 52 | print('sep系统登录账号和密码\n(默认记住此配置,如需更新请删除或修改.ini文件)\n') 53 | print('说明:sep用户名一般为学校邮箱**@ucas.ac.cn。\n') 54 | username = input('sep账号:') 55 | password = input('sep密码:') 56 | self.config = dict( 57 | username=username, 58 | password=password 59 | ) 60 | self.save_config() 61 | 62 | def save_config(self): 63 | parser = configparser.ConfigParser() 64 | if self.use_cookies: 65 | parser['user_cookies'] = self.config 66 | else: 67 | parser['user'] = self.config 68 | with open(self.CONFPATH[self.use_cookies], 'w') as conffile: 69 | parser.write(conffile) 70 | 71 | def load_config(self): 72 | parser = configparser.ConfigParser() 73 | parser.read(self.CONFPATH[self.use_cookies]) 74 | if parser.has_section('user_cookies'): 75 | self.use_cookies = True 76 | config = parser['user_cookies'] 77 | print('已读取cookies') 78 | elif parser.has_section('user'): 79 | self.use_cookies = False 80 | config = parser['user'] 81 | print('已读取账号', config['username']) 82 | else: 83 | os.remove(self.CONFPATH[self.use_cookies]) 84 | raise NotImplementedError('.ini配置文件已损坏,请重新配置') 85 | self.config = config 86 | 87 | def get_user(self): 88 | if self.use_cookies: 89 | return User_cookie(self.config['eai-sess'], self.config['UUKey']) 90 | else: 91 | return User_pass(self.config['username'], self.config['password']) 92 | --------------------------------------------------------------------------------