├── requirements.txt ├── README.md ├── LICENSE ├── .gitignore └── main.py /requirements.txt: -------------------------------------------------------------------------------- 1 | requests -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nCoVReport 2 | 3 | 基于 Python3 的适用于北工大的 nCoV 自动填报脚本 4 | 5 | [下载地址](https://github.com/nonPointer/BJUT_nCoV_Report/releases) 6 | 7 | ## 使用方式 8 | 9 | 1. 创建 `account.txt`,格式为(不包含方括号) 10 | ```text 11 | [学号] 12 | [密码] 13 | ``` 14 | 2. 修改 `main.py` 内的经纬度和地址信息(可选) 15 | 3. 安装所需依赖:`pip3 install -r requirements.txt` 16 | 4. 执行 `main.py` 17 | 18 | ## 自动化 19 | 20 | ### Linux:使用 Crontab 21 | 22 | 每天早晨 8:00 上报并在 8:01、9:00、9:01 重试。 23 | ```shell script 24 | 0,1 8,9 * * * python3 main.py 25 | ``` 26 | 27 | ### Windows:使用计划任务(Task Scheduler) 28 | 29 | 务必选中 `Run whether user is logged on or not`。 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Vegetables 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. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #config 2 | account.txt 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # celery beat schedule file 88 | celerybeat-schedule 89 | 90 | # SageMath parsed files 91 | *.sage.py 92 | 93 | # Environments 94 | .env 95 | .venv 96 | env/ 97 | venv/ 98 | ENV/ 99 | env.bak/ 100 | venv.bak/ 101 | .idea/ 102 | 103 | # Spyder project settings 104 | .spyderproject 105 | .spyproject 106 | 107 | # Rope project settings 108 | .ropeproject 109 | 110 | # mkdocs documentation 111 | /site 112 | 113 | # mypy 114 | .mypy_cache/ 115 | .dmypy.json 116 | dmypy.json 117 | 118 | # Pyre type checker 119 | .pyre/ 120 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import json 5 | import time 6 | import datetime 7 | import pickle 8 | import requests 9 | import random 10 | import os 11 | 12 | 13 | def notification(param, key='', action='changed'): 14 | # Define your own notification. 15 | # Get notification of warnings and status 16 | # If return False, then stop the submit process 17 | return True 18 | 19 | 20 | if __name__ == '__main__': 21 | 22 | # load config 23 | 24 | try: 25 | f = open('account.txt') 26 | except: 27 | print('account.txt not found!') 28 | time.sleep(3) 29 | exit() 30 | 31 | username = f.readline().strip() 32 | password = f.readline().strip() 33 | location = f.readline().strip() 34 | f.close() 35 | if location == '': 36 | print('【生成地址】没有指定地址,正在生成随机地址…') 37 | lng = 116.397499 + random.random() / 10.0 - 0.05 38 | lat = 39.908722 + random.random() / 10.0 - 0.05 39 | coordination = str(lng) + ',' + str(lat) 40 | PARAMS = { 41 | 'key': '729923f88542d91590470f613adb27b5', 42 | 's': 'rsv3', 43 | 'location': coordination 44 | } 45 | r = requests.get(url='https://restapi.amap.com/v3/geocode/regeo', params=PARAMS) 46 | f = open('account.txt', "w") 47 | location = r.json() 48 | location['lng'] = lng 49 | location['lat'] = lat 50 | f.write(username + '\n' + password + '\n' + json.dumps(location, ensure_ascii=False)) 51 | f.close() 52 | try: 53 | print(r.json()['regeocode']['formatted_address']) 54 | print('10 秒钟后继续') 55 | time.sleep(10) 56 | except: 57 | print('生成地址时遇到问题') 58 | exit('程序已经中断') 59 | else: 60 | location = json.loads(location) 61 | lng = location['lng'] 62 | lat = location['lat'] 63 | print('【使用地址】' + location['regeocode']['formatted_address']) 64 | 65 | # init 66 | s = requests.session() 67 | headers = {} 68 | log = open('log.txt', 'a') 69 | curr_time = datetime.datetime.now() 70 | 71 | try: 72 | with open('cookie.txt', 'rb') as f: 73 | s.cookies.update(pickle.load(f)) 74 | except: 75 | print('cookie.txt not found!') 76 | # login 77 | data = {'username': username, 'password': password} 78 | r = s.post('https://itsapp.bjut.edu.cn/uc/wap/login/check', 79 | data=data, headers=headers) 80 | tmp = '【登录】' + r.json()['m'] 81 | print(tmp) 82 | log.write('\n' + curr_time.strftime('%Y-%m-%d-%H:%M:%S') + tmp) 83 | if not '成功' in r.text: 84 | time.sleep(3) 85 | exit() 86 | with open('cookie.txt', 'wb') as f: 87 | pickle.dump(s.cookies, f) 88 | 89 | # previous data 90 | r = s.get('https://itsapp.bjut.edu.cn/ncov/wap/default') 91 | if r.history: 92 | print("Cookie expired") 93 | os.remove('cookie.txt') 94 | exit() 95 | else: 96 | print("Cookie not expired") 97 | 98 | default = r.text[r.text.find('var def = ') + 10:] 99 | default = json.loads(default[:default.find(';')]) 100 | oldInfo = r.text[r.text.find('oldInfo: ') + 9:] 101 | oldInfo = json.loads(oldInfo[:oldInfo.find(',\n')]) 102 | 103 | # report 104 | data = { 105 | 'ismoved': '0', 106 | 'dqjzzt': '1', # 当前居住状态,0在校、1在京不在校 107 | 'jhfjrq': '', # 计划返京日期 108 | 'jhfjjtgj': '', # 计划返京交通工具 109 | 'jhfjhbcc': '', # 计划返京航班车次 110 | 'tw': str(random.randint(2, 3)), # 体温范围所对应的页面上的序号(下标从 1 开始) 111 | 'sfcxtz': '0', # 今日是否出现发热、乏力、干咳、呼吸困难等症状? 112 | 'sfjcbh': '0', # 今日是否接触疑似/确诊人群? 113 | 'sfcxzysx': '0', # 是否有任何与疫情相关的注意事项? 114 | 'qksm': '', # 情况说明 115 | 'sfyyjc': '0', # 是否医院检查 116 | 'jcjgqr': '0', # 检查结果确认 117 | 'remark': '', 118 | 'address': location['regeocode']['formatted_address'], 119 | 'geo_api_info': json.dumps({ 120 | 'type': 'complete', 121 | 'info': 'SUCCESS', 122 | 'status': 1, 123 | 'position': { 124 | 'O': lng, 125 | 'P': lat, 126 | 'lng': lng, 127 | 'lat': lat 128 | }, 129 | 'message': 'Get geolocation success.Convert Success.Get address success.', 130 | 'location_type': 'html5', 131 | 'accuracy': random.randint(10, 100), 132 | 'isConverted': True, 133 | 'addressComponent': location['regeocode']['addressComponent'], 134 | 'formatted_address': location['regeocode']['formatted_address'], 135 | 'roads': [], 136 | 'crosses': [], 137 | 'pois': [], 138 | }, ensure_ascii=False), 139 | 'area': '北京市 ' + location['regeocode']['addressComponent']['district'], 140 | 'province': '北京市', 141 | 'city': '北京市', 142 | 'sfzx': '0', # 是否已经返校 143 | 'sfjcwhry': '0', # 是否接触武汉人员 144 | 'sfjchbry': '0', # 是否接触湖北人员 145 | 'sfcyglq': '0', # 是否处于隔离期 146 | 'gllx': '', # 隔离类型 147 | 'glksrq': '', # 隔离开始日期 148 | 'jcbhlx': '', # 接触病患类型 149 | 'jcbhrq': '', # 接触病患日期 150 | 'bztcyy': '', # 当前地点与上次不在同一城市,原因如下:2 探亲, 3 旅游, 4 回家, 1 其他 151 | 'sftjhb': '0', # 是否停经湖北 152 | 'sftjwh': '0', # 是否停经武汉 153 | 'sfsfbh': '0', # 是否所在省份变化 154 | 'xjzd': '', # 现居住地 155 | 'jcwhryfs': '', # 接触武汉人员方式 156 | 'jchbryfs': '', # 接触湖北人员方式 157 | 'szgj': '', # 所在国家 158 | 'jcjg': '', # 检查结果 159 | # --- The following are uncommented field --- # 160 | 'date': datetime.datetime.now().strftime('%Y%m%d'), 161 | # 'uid': '0', 162 | 'created': int(time.time()), 163 | 'jcqzrq': '', 164 | 'sfjcqz': '', 165 | 'szsqsfybl': 0, 166 | 'sfsqhzjkk': 0, 167 | 'sqhzjkkys': '', 168 | 'sfygtjzzfj': 0, 169 | 'gtjzzfjsj': '', 170 | 'ljrq': '', 171 | 'ljjtgj': '', 172 | 'ljhbcc': '', 173 | 'fjrq': '', 174 | 'fjjtgj': '', 175 | 'fjhbcc': '', 176 | 'fjqszgj': '', 177 | 'fjq_province': '', 178 | 'fjq_city': '', 179 | 'fjq_szdz': '', 180 | 'jrfjjtgj': '', 181 | 'jrfjhbcc': '', 182 | 'fjyy': '', 183 | 'szsqsfty': '', 184 | 'sfxxxbb': '', 185 | 'created_uid': 0, 186 | # 'id': 0, 187 | 'gwszdd': '', 188 | 'sfyqjzgc': '', 189 | 'jrsfqzys': '', 190 | 'jrsfqzfy': '' 191 | } 192 | 193 | go_on = True 194 | 195 | for key, value in default.items(): 196 | if not data.__contains__(key): 197 | data[key] = value 198 | if key != 'id' and key != 'uid': 199 | warn = 'Warn: [' + key + '] New key' 200 | go_on = go_on and notification(warn, key, 'new') 201 | print(warn) 202 | 203 | for key, value in oldInfo.items(): 204 | if data.__contains__(key): 205 | if data[key] != oldInfo[key] and key != 'geo_api_info' and key != 'tw' and key != 'date' \ 206 | and key != 'created' and key != 'id': 207 | warn = 'Warn: [' + key + '] ' + str(oldInfo[key]) + ' -> ' + str(data[key]) 208 | go_on = go_on and notification(warn, key, 'changed') 209 | print(warn) 210 | else: 211 | warn = 'Warn: [' + key + '] New key' 212 | go_on = go_on and notification(warn, key, 'new') 213 | print(warn) 214 | 215 | if go_on: 216 | r = s.post('https://itsapp.bjut.edu.cn/ncov/wap/default/save', 217 | data=data, headers=headers) 218 | tmp = '【上报】' + json.loads(r.text)['m'] 219 | print(tmp) 220 | log.write('\n' + curr_time.strftime('%Y-%m-%d-%H:%M:%S') + tmp) 221 | log.close() 222 | r.raise_for_status() 223 | if r.status_code != 200: 224 | err = 'Err: Login failed!' 225 | print(err) 226 | notification(err) 227 | os.remove('cookie.txt') 228 | 229 | if str(r.json()['e']) == '0': 230 | notification(r.json()['m']) 231 | --------------------------------------------------------------------------------