├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── leetcoding_cron.yml │ └── publish_health_checkin_helper.yml ├── .gitignore ├── Health_Checkin ├── clock-in.py ├── health_checkin_helper.py ├── health_checkin_helper_cst.py ├── readme.md ├── requirements.txt └── src │ └── bitbug_favicon.ico ├── LICENSE ├── LeetCoding ├── Readme.md ├── leetcoding.py ├── pics │ └── demo.jpg └── pusher.py ├── Net_Assistant ├── Andriod │ ├── ZjuCstNet.apk │ └── ZjuCstNet.js ├── PC │ ├── ZJU_CST_Net.exe │ └── ZJU_CST_Net.py ├── README.md └── src │ ├── ZJU_logo.jpeg │ └── bitbug_favicon.ico ├── README.md ├── Yellow_Page └── readme.md ├── guide_classes_and_tests ├── for_classes │ ├── README.md │ ├── guide_class_login.py │ ├── loginUtils.py │ └── zju_guide_class.py └── for_tests │ ├── README.md │ ├── __init__.py │ ├── assets │ └── favicon.ico │ ├── loginUtils.py │ ├── main_script.exe │ └── main_script.py └── soft_web_scrapy ├── README.md ├── ZJU_education.py └── ZJU_tuimian.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ['1063052964@qq.com'] -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/leetcoding_cron.yml: -------------------------------------------------------------------------------- 1 | name: Leetcoding every day 2 | 3 | on: 4 | schedule: 5 | # 由于8小时时差的原因, 15+8 == 23.58左右, 加上延迟和误差, 大约是在00:05左右执行完成 6 | - cron: "58 15 * * *" 7 | workflow_dispatch: 8 | 9 | 10 | jobs: 11 | build-linux: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | max-parallel: 5 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Set up Python 3.8 19 | uses: actions/setup-python@v2 20 | with: 21 | python-version: 3.8 22 | 23 | - name: install requirements 24 | run: pip install requests 25 | 26 | - name: Run Scripts 27 | env: 28 | PUSHPLUS_TOKEN: ${{ secrets.PUSHPLUS_TOKEN }} 29 | SERVERCHAN_TOKEN: ${{ secrets.SERVERCHAN_TOKEN }} 30 | DING_ACCESS_TOKEN: ${{ secrets.DING_ACCESS_TOKEN }} 31 | DING_SECRET: ${{ secrets.DING_SECRET }} 32 | run: python ./LeetCoding/leetcoding.py 33 | -------------------------------------------------------------------------------- /.github/workflows/publish_health_checkin_helper.yml: -------------------------------------------------------------------------------- 1 | name: Build and Publish health_checkin_helper 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*.*.*' 7 | 8 | jobs: 9 | release: 10 | name: health_checkin_helper_all 11 | runs-on: windows-latest # 包主要打给windows使用 12 | # runs-on: ${{ matrix.os }} 13 | 14 | # strategy: 15 | # fail-fast: false 16 | # matrix: 17 | # os: [ ubuntu-latest, windows-latest ] 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | 22 | - name: Setup Python 23 | uses: actions/setup-python@v2.2.2 24 | with: 25 | python-version: 3.9.1 26 | 27 | - name: install requirements 28 | run: pip install requests pyinstaller 29 | 30 | - name: Freeze2exe 31 | run: | 32 | cd ./Health_Checkin 33 | mkdir artifacts 34 | pyinstaller -i ./src/bitbug_favicon.ico -F ./health_checkin_helper_cst.py 35 | mv ./dist/health_checkin_helper_cst.exe ./artifacts 36 | pyinstaller -i ./src/bitbug_favicon.ico -F ./health_checkin_helper_all.py 37 | mv ./dist/health_checkin_helper_all.exe ./artifacts 38 | 39 | - name: Create zip 40 | uses: ihiroky/archive-action@v1 41 | with: 42 | root_dir: ./Health_Checkin/artifacts 43 | file_path: ./Health_Checkin/artifacts/health_checkin_helper.zip 44 | 45 | - name: Upload a Build Artifact 46 | uses: actions/upload-artifact@v2.2.4 47 | with: 48 | name: artifacts 49 | path: ./Health_Checkin/artifacts 50 | 51 | - name: Get version 52 | id: get_version 53 | run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} 54 | 55 | - name: Upload gzip file to release 56 | uses: softprops/action-gh-release@v1 57 | if: startsWith(github.ref, 'refs/tags/') 58 | with: 59 | name: 浙大健康打卡助手 60 | files: './Health_Checkin/artifacts/**' 61 | # env: 62 | # GITHUB_TOKEN: ${{ secrets.TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | .idea/ 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | workspace.xml 81 | # SageMath parsed files 82 | *.sage.py 83 | .idea/workspace.xml 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /Health_Checkin/clock-in.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # 打卡脚修改自ZJU-nCov-Hitcarder的开源代码,感谢这位同学开源的代码 4 | 5 | import requests 6 | import json 7 | import re 8 | import datetime 9 | import time 10 | import sys 11 | 12 | 13 | class DaKa(object): 14 | """Hit card class 15 | 16 | Attributes: 17 | username: (str) 浙大统一认证平台用户名(一般为学号) 18 | password: (str) 浙大统一认证平台密码 19 | login_url: (str) 登录url 20 | base_url: (str) 打卡首页url 21 | save_url: (str) 提交打卡url 22 | sess: (requests.Session) 统一的session 23 | """ 24 | 25 | headers = { 26 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36', 27 | } 28 | 29 | LOGIN_URL = "https://zjuam.zju.edu.cn/cas/login?service=https%3A%2F%2Fhealthreport.zju.edu.cn%2Fa_zju%2Fapi%2Fsso%2Findex%3Fredirect%3Dhttps%253A%252F%252Fhealthreport.zju.edu.cn%252Fncov%252Fwap%252Fdefault%252Findex" 30 | BASE_URL = "https://healthreport.zju.edu.cn/ncov/wap/default/index" 31 | SAVE_URL = "https://healthreport.zju.edu.cn/ncov/wap/default/save" 32 | 33 | def __init__(self, username, password): 34 | self.username = username 35 | self.password = password 36 | self.sess = requests.Session() 37 | 38 | def login(self): 39 | """Login to ZJU platform""" 40 | res = self.sess.get(self.LOGIN_URL) 41 | execution = re.search( 42 | 'name="execution" value="(.*?)"', res.text).group(1) 43 | res = self.sess.get( 44 | url='https://zjuam.zju.edu.cn/cas/v2/getPubKey').json() 45 | n, e = res['modulus'], res['exponent'] 46 | encrypt_password = self._rsa_encrypt(self.password, e, n) 47 | 48 | data = { 49 | 'username': self.username, 50 | 'password': encrypt_password, 51 | 'execution': execution, 52 | '_eventId': 'submit' 53 | } 54 | res = self.sess.post(url=self.LOGIN_URL, data=data) 55 | 56 | # check if login successfully 57 | if '统一身份认证' in res.content.decode(): 58 | raise LoginError('登录失败,请核实账号密码重新登录') 59 | return self.sess 60 | 61 | def post(self): 62 | """Post the hitcard info""" 63 | res = self.sess.post(self.SAVE_URL, data=self.info, headers=self.headers) 64 | return json.loads(res.text) 65 | 66 | def get_info(self, html=None): 67 | """Get hitcard info, which is the old info with updated new time.""" 68 | if not html: 69 | res = self.sess.get(self.BASE_URL, headers=self.headers) 70 | html = res.content.decode() 71 | try: 72 | old_infos = re.findall(r'oldInfo: ({[^\n]+})', html) 73 | if len(old_infos) != 0: 74 | old_info = json.loads(old_infos[0]) 75 | else: 76 | raise RegexMatchError("未发现缓存信息,请先至少手动成功打卡一次再运行脚本") 77 | 78 | new_info_tmp = json.loads(re.findall(r'def = ({[^\n]+})', html)[0]) 79 | new_id = new_info_tmp['id'] 80 | name = re.findall(r'realname: "([^\"]+)",', html)[0] 81 | number = re.findall(r"number: '([^\']+)',", html)[0] 82 | except IndexError: 83 | raise RegexMatchError('Relative info not found in html with regex') 84 | except json.decoder.JSONDecodeError: 85 | raise DecodeError('JSON decode error') 86 | 87 | new_info = old_info.copy() 88 | new_info['id'] = new_id 89 | new_info['name'] = name 90 | new_info['number'] = number 91 | new_info["date"] = self.get_date() 92 | new_info["created"] = round(time.time()) 93 | new_info["address"] = "浙江省杭州市西湖区" 94 | new_info["area"] = "浙江省 杭州市 西湖区" 95 | new_info["province"] = new_info["area"].split(' ')[0] 96 | new_info["city"] = new_info["area"].split(' ')[1] 97 | # form change 98 | new_info['jrdqtlqk[]'] = 0 99 | new_info['jrdqjcqk[]'] = 0 100 | new_info['sfsqhzjkk'] = 1 # 是否申领杭州健康码 101 | new_info['sqhzjkkys'] = 1 # 杭州健康吗颜色,1:绿色 2:红色 3:黄色 102 | new_info['sfqrxxss'] = 1 # 是否确认信息属实 103 | new_info['jcqzrq'] = "" 104 | new_info['gwszdd'] = "" 105 | new_info['szgjcs'] = "" 106 | self.info = new_info 107 | return new_info 108 | 109 | def _rsa_encrypt(self, password_str, e_str, M_str): 110 | password_bytes = bytes(password_str, 'ascii') 111 | password_int = int.from_bytes(password_bytes, 'big') 112 | e_int = int(e_str, 16) 113 | M_int = int(M_str, 16) 114 | result_int = pow(password_int, e_int, M_int) 115 | return hex(result_int)[2:].rjust(128, '0') 116 | 117 | @staticmethod 118 | def get_date(): 119 | """Get current date""" 120 | today = datetime.date.today() 121 | return "%4d%02d%02d" % (today.year, today.month, today.day) 122 | 123 | # Exceptions 124 | class LoginError(Exception): 125 | """Login Exception""" 126 | pass 127 | 128 | 129 | class RegexMatchError(Exception): 130 | """Regex Matching Exception""" 131 | pass 132 | 133 | 134 | class DecodeError(Exception): 135 | """JSON Decode Exception""" 136 | pass 137 | 138 | 139 | def main(username, password): 140 | """Hit card process 141 | 142 | Arguments: 143 | username: (str) 浙大统一认证平台用户名(一般为学号) 144 | password: (str) 浙大统一认证平台密码 145 | """ 146 | print("\n[Time] %s" % 147 | datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')) 148 | print("🚌 打卡任务启动") 149 | 150 | dk = DaKa(username, password) 151 | 152 | print("登录到浙大统一身份认证平台...") 153 | try: 154 | dk.login() 155 | print("已登录到浙大统一身份认证平台") 156 | except Exception as err: 157 | print(str(err)) 158 | raise Exception 159 | 160 | print('正在获取个人信息...') 161 | try: 162 | dk.get_info() 163 | print('%s %s同学, 你好~' % (dk.info['number'], dk.info['name'])) 164 | except Exception as err: 165 | print('获取信息失败,请手动打卡,更多信息: ' + str(err)) 166 | raise Exception 167 | 168 | print('正在为您打卡打卡打卡') 169 | try: 170 | res = dk.post() 171 | if str(res['e']) == '0': 172 | print('已为您打卡成功!') 173 | else: 174 | print(res['m']) 175 | except Exception: 176 | print('数据提交失败') 177 | raise Exception 178 | 179 | 180 | if __name__ == "__main__": 181 | print(sys.argv) 182 | username = sys.argv[1] 183 | password = sys.argv[2] 184 | try: 185 | main(username, password) 186 | except Exception: 187 | exit(1) 188 | -------------------------------------------------------------------------------- /Health_Checkin/health_checkin_helper.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import json 3 | import random 4 | import re 5 | import time 6 | 7 | import ddddocr 8 | import requests 9 | 10 | ocr = ddddocr.DdddOcr(det=False, ocr=True) 11 | 12 | 13 | def cope_with_captcha(sess): 14 | """识别验证码""" 15 | response = sess.get('https://healthreport.zju.edu.cn/ncov/wap/default/code') 16 | img_bytes = response.content 17 | # with open("captcha.png", "wb") as f: 18 | # f.write(img_bytes) 19 | res = ocr.classification(img_bytes) 20 | return res.upper() 21 | 22 | 23 | # ▲.在这里设置推送pushplus! 24 | def push2pushplus(content, token=""): 25 | """ 26 | 推送消息到pushplus 27 | """ 28 | if token == '': 29 | print("[注意] 未提供token,不进行pushplus推送!") 30 | else: 31 | server_url = f"http://www.pushplus.plus/send" 32 | params = { 33 | "token": token, 34 | "title": 'ZJU钉钉健康打卡', 35 | "content": content 36 | } 37 | 38 | response = requests.get(server_url, params=params) 39 | json_data = response.json() 40 | print(json_data) 41 | 42 | 43 | class LoginError(Exception): 44 | """Login Exception""" 45 | pass 46 | 47 | 48 | def get_day(delta=0): 49 | """ 50 | 获得指定格式的日期 51 | """ 52 | today = datetime.date.today() 53 | oneday = datetime.timedelta(days=delta) 54 | yesterday = today - oneday 55 | return yesterday.strftime("%Y%m%d") 56 | 57 | 58 | def take_out_json(content): 59 | """ 60 | 从字符串jsonp中提取json数据 61 | """ 62 | s = re.search("^jsonp_\d+_\((.*?)\);?$", content, re.S) 63 | return json.loads(s.group(1) if s else "{}") 64 | 65 | 66 | def get_date(): 67 | """Get current date""" 68 | today = datetime.date.today() 69 | return "%4d%02d%02d" % (today.year, today.month, today.day) 70 | 71 | 72 | class ZJULogin(object): 73 | """ 74 | Attributes: 75 | username: (str) 浙大统一认证平台用户名(一般为学号) 76 | password: (str) 浙大统一认证平台密码 77 | sess: (requests.Session) 统一的session管理 78 | """ 79 | headers = { 80 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36', 81 | } 82 | BASE_URL = "https://healthreport.zju.edu.cn/ncov/wap/default/index" 83 | LOGIN_URL = "https://zjuam.zju.edu.cn/cas/login?service=http%3A%2F%2Fservice.zju.edu.cn%2F" 84 | 85 | def __init__(self, username, password, delay_run=False): 86 | self.username = username 87 | self.password = password 88 | self.delay_run = delay_run 89 | self.sess = requests.Session() 90 | 91 | def login(self): 92 | """Login to ZJU platform""" 93 | res = self.sess.get(self.LOGIN_URL) 94 | execution = re.search( 95 | 'name="execution" value="(.*?)"', res.text).group(1) 96 | res = self.sess.get( 97 | url='https://zjuam.zju.edu.cn/cas/v2/getPubKey').json() 98 | n, e = res['modulus'], res['exponent'] 99 | encrypt_password = self._rsa_encrypt(self.password, e, n) 100 | 101 | data = { 102 | 'username': self.username, 103 | 'password': encrypt_password, 104 | 'execution': execution, 105 | '_eventId': 'submit', 106 | "authcode": "" 107 | } 108 | res = self.sess.post(url=self.LOGIN_URL, data=data) 109 | # check if login successfully 110 | if '用户名或密码错误' in res.content.decode(): 111 | raise LoginError('登录失败,请核实账号密码重新登录') 112 | print("统一认证平台登录成功~") 113 | return self.sess 114 | 115 | def _rsa_encrypt(self, password_str, e_str, M_str): 116 | password_bytes = bytes(password_str, 'ascii') 117 | password_int = int.from_bytes(password_bytes, 'big') 118 | e_int = int(e_str, 16) 119 | M_int = int(M_str, 16) 120 | result_int = pow(password_int, e_int, M_int) 121 | return hex(result_int)[2:].rjust(128, '0') 122 | 123 | 124 | class HealthCheckInHelper(ZJULogin): 125 | headers = { 126 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36', 127 | } 128 | REDIRECT_URL = "https://zjuam.zju.edu.cn/cas/login?service=https%3A%2F%2Fhealthreport.zju.edu.cn%2Fa_zju%2Fapi%2Fsso%2Findex%3Fredirect%3Dhttps%253A%252F%252Fhealthreport.zju.edu.cn%252Fncov%252Fwap%252Fdefault%252Findex%26from%3Dwap" 129 | 130 | def get_geo_info(self, location: dict): 131 | params = ( 132 | ('key', '729923f88542d91590470f613adb27b5'), 133 | ('s', 'rsv3'), 134 | ('language', 'zh_cn'), 135 | ('location', '{lng},{lat}'.format(lng=location.get("lng"), lat=location.get("lat"))), 136 | ('extensions', 'base'), 137 | ('callback', 'jsonp_376062_'), 138 | ('platform', 'JS'), 139 | ('logversion', '2.0'), 140 | ('appname', 'https://healthreport.zju.edu.cn/ncov/wap/default/index'), 141 | ('csid', '63157A4E-D820-44E1-B032-A77418183A4C'), 142 | ('sdkversion', '1.4.19'), 143 | ) 144 | 145 | response = self.sess.get('https://restapi.amap.com/v3/geocode/regeo', headers=self.headers, params=params, ) 146 | return take_out_json(response.text) 147 | 148 | def take_in(self, geo_info: dict, campus: str): 149 | formatted_address = geo_info.get("regeocode").get("formatted_address") 150 | address_component = geo_info.get("regeocode").get("addressComponent") 151 | if not formatted_address or not address_component: return 152 | 153 | # 获得id和uid参数-->新版接口里不需要这两个参数了 154 | res = self.sess.get(self.BASE_URL, headers=self.headers) 155 | html = res.content.decode() 156 | new_info_tmp = json.loads(re.findall(r'def = ({[^\n]+})', html)[0]) 157 | # new_info_tmp = json5.loads(re.findall(r'def = (\{.*?\});', html, re.S)[0]) 158 | # print(new_info_tmp) 159 | new_id = new_info_tmp['id'] 160 | new_uid = new_info_tmp['uid'] 161 | # 拼凑geo信息 162 | lng, lat = address_component.get("streetNumber").get("location").split(",") 163 | geo_api_info_dict = {"type": "complete", "info": "SUCCESS", "status": 1, 164 | "position": {"Q": lat, "R": lng, "lng": lng, "lat": lat}, 165 | "message": "Get ipLocation success.Get address success.", "location_type": "ip", 166 | "accuracy": 40, "isConverted": "true", "addressComponent": address_component, 167 | "formattedAddress": formatted_address, "roads": [], "crosses": [], "pois": []} 168 | 169 | data = { 170 | "sfymqjczrj": "0", 171 | "sfyrjjh": "0", 172 | "nrjrq": "0", 173 | "sfqrxxss": "1", 174 | "sfqtyyqjwdg": "0", 175 | "sffrqjwdg": "0", 176 | "zgfx14rfh": "0", 177 | "sfyxjzxgym": "1", 178 | "sfbyjzrq": "5", 179 | "jzxgymqk": "2", 180 | "campus": campus, 181 | "ismoved": "0", 182 | "tw": "0", 183 | "sfcxtz": "0", 184 | "sfjcbh": "0", 185 | "sfcxzysx": "0", 186 | "sfyyjc": "0", 187 | "jcjgqr": "0", 188 | 'address': formatted_address, 189 | 'geo_api_info': geo_api_info_dict, 190 | 'area': "{} {} {}".format(address_component.get("province"), address_component.get("city"), 191 | address_component.get("district")), 192 | 'province': address_component.get("province"), 193 | 'city': address_component.get("city"), 194 | "sfzx": "1", 195 | "sfjcwhry": "0", 196 | "sfjchbry": "0", 197 | "sfcyglq": "0", 198 | "sftjhb": "0", 199 | "sftjwh": "0", 200 | "sfyqjzgc": "0", 201 | "sfsqhzjkk": "1", 202 | "sqhzjkkys": "1", 203 | # 日期 204 | 'date': get_date(), 205 | 'created': round(time.time()), 206 | "szsqsfybl": "0", 207 | "sfygtjzzfj": "0", 208 | "uid": new_uid, 209 | "id": new_id, 210 | "verifyCode": cope_with_captcha(self.sess), 211 | "dbfb7190a31b5f8cd4a85f5a4975b89b": "1651977968", 212 | "1a7c5b2e52854a2480947880eabe1fe1": "a3fefb4a32d22d9a3ff5827ac60bb1b0" 213 | } 214 | response = self.sess.post('https://healthreport.zju.edu.cn/ncov/wap/default/save', data=data, 215 | headers=self.headers) 216 | return response.json() 217 | 218 | def run(self, lng, lat, campus, delay_run=False): 219 | """ 220 | Args: 221 | 'lng': '121.63529', 'lat': '29.89154' 222 | delay_run: 是否延迟运行 223 | lng: 经度 224 | lat: 维度 225 | campus: 校区, 如玉泉校区 226 | Returns: 227 | """ 228 | print("正在为{}健康打卡".format(self.username)) 229 | if delay_run: 230 | # 确保定时脚本执行时间不太一致 231 | time.sleep(random.randint(0, 360)) 232 | # 拿到Cookies和headers 233 | self.login() 234 | # 拿取eai-sess的cookies信息 235 | self.sess.get(self.REDIRECT_URL) 236 | location = {'info': 'LOCATE_SUCCESS', 'status': 1, 'lng': lng, 'lat': lat} 237 | geo_info = self.get_geo_info(location) 238 | # print(geo_info) 239 | try: 240 | res = self.take_in(geo_info, campus=campus) 241 | print(res) 242 | except Exception as e: # 失败消息推送 243 | push2pushplus("打卡失败, {}".format(e)) 244 | 245 | 246 | def _init_parser(): 247 | """获得CLI参数""" 248 | from argparse import ArgumentParser 249 | 250 | parser = ArgumentParser("自动打卡脚本v2") 251 | parser.add_argument("-a", "--account", type=str, required=True, help="统一认证平台账号") 252 | parser.add_argument("-p", "--password", type=str, required=True, help="统一认证平台密码") 253 | parser.add_argument("-lng", "--longitude", type=str, required=True, help="定位经度") 254 | parser.add_argument("-lat", "--latitude", type=str, required=True, help="定位纬度") 255 | parser.add_argument("-c", "--campus", type=str, required=True, help="校区") 256 | return parser.parse_args() 257 | 258 | 259 | if __name__ == '__main__': 260 | args = _init_parser() 261 | s = HealthCheckInHelper(args.account, args.password) 262 | s.run(lng=args.longitude, lat=args.latitude, campus=args.campus, delay_run=False) 263 | -------------------------------------------------------------------------------- /Health_Checkin/health_checkin_helper_cst.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import json 3 | import os 4 | import random 5 | import re 6 | import time 7 | 8 | import ddddocr 9 | import requests 10 | 11 | ocr = ddddocr.DdddOcr(det=False, ocr=True) 12 | 13 | 14 | def cope_with_captcha(sess): 15 | """识别验证码""" 16 | response = sess.get('https://healthreport.zju.edu.cn/ncov/wap/default/code') 17 | # img_bytes = response.content 18 | # with open("captcha.png", "wb") as f: 19 | # f.write(img_bytes) 20 | res = ocr.classification(response.content) 21 | return res.upper() 22 | 23 | 24 | # ▲.在这里设置推送pushplus! 25 | def push2pushplus(content, token=""): 26 | """ 27 | 推送消息到pushplus 28 | """ 29 | if token == '': 30 | print("[注意] 未提供token,不进行pushplus推送!") 31 | else: 32 | server_url = f"http://www.pushplus.plus/send" 33 | params = { 34 | "token": token, 35 | "title": 'ZJU钉钉健康打卡', 36 | "content": content 37 | } 38 | 39 | response = requests.get(server_url, params=params) 40 | json_data = response.json() 41 | print(json_data) 42 | 43 | 44 | class LoginError(Exception): 45 | """Login Exception""" 46 | pass 47 | 48 | 49 | def get_day(delta=0): 50 | """ 51 | 获得指定格式的日期 52 | """ 53 | today = datetime.date.today() 54 | oneday = datetime.timedelta(days=delta) 55 | yesterday = today - oneday 56 | return yesterday.strftime("%Y%m%d") 57 | 58 | 59 | def take_out_json(content): 60 | """ 61 | 从字符串jsonp中提取json数据 62 | """ 63 | s = re.search("^jsonp_\d+_\((.*?)\);?$", content) 64 | return json.loads(s.group(1) if s else "{}") 65 | 66 | 67 | def get_date(): 68 | """Get current date""" 69 | today = datetime.date.today() 70 | return "%4d%02d%02d" % (today.year, today.month, today.day) 71 | 72 | 73 | class ZJULogin(object): 74 | """ 75 | Attributes: 76 | username: (str) 浙大统一认证平台用户名(一般为学号) 77 | password: (str) 浙大统一认证平台密码 78 | sess: (requests.Session) 统一的session管理 79 | """ 80 | headers = { 81 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36', 82 | } 83 | BASE_URL = "https://healthreport.zju.edu.cn/ncov/wap/default/index" 84 | LOGIN_URL = "https://zjuam.zju.edu.cn/cas/login?service=http%3A%2F%2Fservice.zju.edu.cn%2F" 85 | 86 | def __init__(self, username, password, delay_run=False): 87 | self.username = username 88 | self.password = password 89 | self.delay_run = delay_run 90 | self.sess = requests.Session() 91 | 92 | def login(self): 93 | """Login to ZJU platform""" 94 | res = self.sess.get(self.LOGIN_URL) 95 | execution = re.search( 96 | 'name="execution" value="(.*?)"', res.text).group(1) 97 | res = self.sess.get( 98 | url='https://zjuam.zju.edu.cn/cas/v2/getPubKey').json() 99 | n, e = res['modulus'], res['exponent'] 100 | encrypt_password = self._rsa_encrypt(self.password, e, n) 101 | 102 | data = { 103 | 'username': self.username, 104 | 'password': encrypt_password, 105 | 'execution': execution, 106 | '_eventId': 'submit', 107 | "authcode": "" 108 | } 109 | res = self.sess.post(url=self.LOGIN_URL, data=data) 110 | # check if login successfully 111 | if '用户名或密码错误' in res.content.decode(): 112 | raise LoginError('登录失败,请核实账号密码重新登录') 113 | print("统一认证平台登录成功~") 114 | return self.sess 115 | 116 | def _rsa_encrypt(self, password_str, e_str, M_str): 117 | password_bytes = bytes(password_str, 'ascii') 118 | password_int = int.from_bytes(password_bytes, 'big') 119 | e_int = int(e_str, 16) 120 | M_int = int(M_str, 16) 121 | result_int = pow(password_int, e_int, M_int) 122 | return hex(result_int)[2:].rjust(128, '0') 123 | 124 | 125 | class HealthCheckInHelper(ZJULogin): 126 | headers = { 127 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36', 128 | } 129 | 130 | REDIRECT_URL = "https://zjuam.zju.edu.cn/cas/login?service=https%3A%2F%2Fhealthreport.zju.edu.cn%2Fa_zju%2Fapi%2Fsso%2Findex%3Fredirect%3Dhttps%253A%252F%252Fhealthreport.zju.edu.cn%252Fncov%252Fwap%252Fdefault%252Findex%26from%3Dwap" 131 | 132 | @DeprecationWarning 133 | def get_ip_location(self): 134 | headers = { 135 | 'authority': 'webapi.amap.com', 136 | 'pragma': 'no-cache', 137 | 'cache-control': 'no-cache', 138 | 'sec-ch-ua': '"Chromium";v="92", " Not A;Brand";v="99", "Google Chrome";v="92"', 139 | 'sec-ch-ua-mobile': '?0', 140 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36', 141 | 'accept': '*/*', 142 | 'sec-fetch-site': 'cross-site', 143 | 'sec-fetch-mode': 'no-cors', 144 | 'sec-fetch-dest': 'script', 145 | 'referer': 'https://healthreport.zju.edu.cn/', 146 | 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8', 147 | 'cookie': 'isg=BIaGbUMSG7BxFM4x941hm4D913wI58qhRFwZi3CvdKmEcyaN2nUJsfYKT6-_W8K5', 148 | } 149 | 150 | params = ( 151 | ('key', '729923f88542d91590470f613adb27b5'), 152 | ('callback', 'jsonp_859544_'), 153 | ('platform', 'JS'), 154 | ('logversion', '2.0'), 155 | ('appname', 'https://healthreport.zju.edu.cn/ncov/wap/default/index'), 156 | ('csid', '17F714D6-756D-49E4-96F2-B31F04B14A5A'), 157 | ('sdkversion', '1.4.16'), 158 | ) 159 | response = self.sess.get( 160 | 'https://webapi.amap.com/maps/ipLocation?key=729923f88542d91590470f613adb27b5&callback=jsonp_859544_&platform=JS&logversion=2.0&appname=https%3A%2F%2Fhealthreport.zju.edu.cn%2Fncov%2Fwap%2Fdefault%2Findex&csid=17F714D6-756D-49E4-96F2-B31F04B14A5A&sdkversion=1.4.16', 161 | headers=headers, params=params) 162 | return take_out_json(response.text) 163 | 164 | def get_geo_info(self, location: dict): 165 | params = ( 166 | ('key', '729923f88542d91590470f613adb27b5'), 167 | ('s', 'rsv3'), 168 | ('language', 'zh_cn'), 169 | ('location', '{lng},{lat}'.format(lng=location.get("lng"), lat=location.get("lat"))), 170 | ('extensions', 'base'), 171 | ('callback', 'jsonp_376062_'), 172 | ('platform', 'JS'), 173 | ('logversion', '2.0'), 174 | ('appname', 'https://healthreport.zju.edu.cn/ncov/wap/default/index'), 175 | ('csid', '63157A4E-D820-44E1-B032-A77418183A4C'), 176 | ('sdkversion', '1.4.19'), 177 | ) 178 | 179 | response = self.sess.get('https://restapi.amap.com/v3/geocode/regeo', headers=self.headers, params=params, ) 180 | return take_out_json(response.text) 181 | 182 | def take_in(self, geo_info: dict, campus: str): 183 | formatted_address = geo_info.get("regeocode").get("formatted_address") 184 | address_component = geo_info.get("regeocode").get("addressComponent") 185 | if not formatted_address or not address_component: return 186 | 187 | # 获得id和uid参数-->新版接口里不需要这两个参数了 188 | res = self.sess.get(self.BASE_URL, headers=self.headers) 189 | html = res.content.decode() 190 | new_info_tmp = json.loads(re.findall(r'def = ({[^\n]+})', html)[0]) 191 | # new_info_tmp = json5.loads(re.findall(r'def = (\{.*?\});', html, re.S)[0]) 192 | # print(new_info_tmp) 193 | new_id = new_info_tmp['id'] 194 | new_uid = new_info_tmp['uid'] 195 | # 拼凑geo信息 196 | lng, lat = address_component.get("streetNumber").get("location").split(",") 197 | geo_api_info_dict = {"type": "complete", "info": "SUCCESS", "status": 1, 198 | "position": {"Q": lat, "R": lng, "lng": lng, "lat": lat}, 199 | "message": "Get ipLocation success.Get address success.", "location_type": "ip", 200 | "accuracy": 40, "isConverted": "true", "addressComponent": address_component, 201 | "formattedAddress": formatted_address, "roads": [], "crosses": [], "pois": []} 202 | 203 | data = { 204 | "sfymqjczrj": "0", 205 | "sfyrjjh": "0", 206 | "nrjrq": "0", 207 | "sfqrxxss": "1", 208 | "sfqtyyqjwdg": "0", 209 | "sffrqjwdg": "0", 210 | "zgfx14rfh": "0", 211 | "sfyxjzxgym": "1", 212 | "sfbyjzrq": "5", 213 | "jzxgymqk": "2", 214 | "campus": campus, 215 | "ismoved": "0", 216 | "tw": "0", 217 | "sfcxtz": "0", 218 | "sfjcbh": "0", 219 | "sfcxzysx": "0", 220 | "sfyyjc": "0", 221 | "jcjgqr": "0", 222 | 'address': formatted_address, 223 | 'geo_api_info': geo_api_info_dict, 224 | 'area': "{} {} {}".format(address_component.get("province"), address_component.get("city"), 225 | address_component.get("district")), 226 | 'province': address_component.get("province"), 227 | 'city': address_component.get("city"), 228 | "sfzx": "1", 229 | "sfjcwhry": "0", 230 | "sfjchbry": "0", 231 | "sfcyglq": "0", 232 | "sftjhb": "0", 233 | "sftjwh": "0", 234 | "sfyqjzgc": "0", 235 | "sfsqhzjkk": "1", 236 | "sqhzjkkys": "1", 237 | # 日期 238 | 'date': get_date(), 239 | 'created': round(time.time()), 240 | "szsqsfybl": "0", 241 | "sfygtjzzfj": "0", 242 | "uid": new_uid, 243 | "id": new_id, 244 | "verifyCode": cope_with_captcha(self.sess), 245 | "dbfb7190a31b5f8cd4a85f5a4975b89b": "1651977968", 246 | "1a7c5b2e52854a2480947880eabe1fe1": "a3fefb4a32d22d9a3ff5827ac60bb1b0" 247 | } 248 | response = self.sess.post('https://healthreport.zju.edu.cn/ncov/wap/default/save', data=data, 249 | headers=self.headers) 250 | return response.json() 251 | 252 | def run(self): 253 | print("正在为{}健康打卡".format(self.username)) 254 | if self.delay_run: 255 | # 确保定时脚本执行时间不太一致 256 | time.sleep(random.randint(0, 360)) 257 | # 拿到Cookies和headers 258 | self.login() 259 | # 拿取eai-sess的cookies信息 260 | self.sess.get(self.REDIRECT_URL) 261 | # 由于IP定位放到服务器上运行后会是服务器的IP定位, 因此这边手动执行后获得IP location后直接写死 262 | # 如果是其他校区可以在本地运行下get_ip_location()后拿到location数据写死 263 | # location = self.get_ip_location() 264 | # print(location) 265 | 266 | # 软院位置得到的IP定位, 如果不是软院的, 则执行上面代码后代替这边的location位置 267 | location = {'info': 'LOCATE_SUCCESS', 'status': 1, 'lng': '121.63529', 'lat': '29.89154'} 268 | geo_info = self.get_geo_info(location) 269 | # print(geo_info) 270 | try: 271 | res = self.take_in(geo_info, campus="宁波校区") 272 | print(res) 273 | except Exception as e: # 失败消息推送 274 | push2pushplus("打卡失败, {}".format(e)) 275 | 276 | 277 | if __name__ == '__main__': 278 | f_name = "account.json" 279 | # 填写要自动打卡的:账号 密码, 然后自己实现循环即可帮多人打卡 280 | # aps = [("", "")] 281 | account = "" 282 | pwd = "" 283 | if account == "" or pwd == "": 284 | if not os.path.exists(f_name): 285 | with open(f_name, "w") as f: 286 | account, pwd = input("请输入账号, 密码:").split() 287 | json.dump({"account": account, "password": pwd}, f) 288 | else: 289 | f = open(f_name, "r") 290 | try: 291 | d = json.load(f) 292 | except json.decoder.JSONDecodeError: 293 | f.close() 294 | import os 295 | 296 | os.remove(f_name) 297 | print("删除错误Json文件") 298 | with open(f_name, "w") as f: 299 | account, pwd = input("请输入账号, 密码:").split() 300 | json.dump({"account": account, "password": pwd}, f) 301 | else: 302 | account, pwd = d.get("account"), d.get("password") 303 | 304 | s = HealthCheckInHelper(account, pwd, delay_run=False) 305 | s.run() 306 | -------------------------------------------------------------------------------- /Health_Checkin/readme.md: -------------------------------------------------------------------------------- 1 | # Health_Checkin_Helper 2 | 3 | > 健康打卡助手: 将脚本部署于服务器上后,使用cron启动定时任务,每日打卡。 4 | > Python < 3.9 5 | 6 | ## 前置依赖安装 7 | 8 | `pip install -r requirements.txt` 9 | 10 | 注: 由于ddddocr用了不少机器学习的库, 还挺大的, 如果Python比较熟,可以创建虚拟环境使用`virtualenv daka`, `pipenv shell` 11 | ## 脚本说明: 12 | 13 | 本工程提供两个脚本二选一使用: 14 | 15 | - ZJU_Clock_In 16 | - ZJU_Health_Checkin_Helper 17 | - cst: 是IP定位写死宁波浙软的版本 18 | - all: 是会根据第一次手动运行获得的IP信息之后默认以这个地理信息打卡的版本 19 | 20 | 区别于:[ZJU_Clock_In](https://github.com/lgaheilongzi/ZJU-Clock-In) 的地方是,ZJU_Clock_In采用的是利用缓存数据提交,如果没有缓存数据则需要手动先打一次卡;而Health_Checkin_Helper**没有这个限制**,直接可以打卡,并且可以**设置打卡位置**。 21 | 22 | ## 设置打卡位置说明: 23 | 24 | 1. 运行 health_checkin_helper.py 脚本: `python health_checkin_helper.py -a * -p * -lng 121.63529 -lat 29.89154 -c 宁波校区` 25 | - 如果不清楚参数设置可以运行`python health_checkin_helper.py --help`, 查看参数帮助 26 | 2. 将脚本放在服务器上cron定时执行: `05 12 * * * python /home/mrli/dscripts/app/zju/zju_health_checkin.py -a * -p * -lng 121.63529 -lat 29.89154 -c 宁波校区` 27 | 28 | 注: 如果使用了`pipenv`, 定时任务为: `30 10 * * * bash /home/mrli/dscripts/app/zju/start.sh` 29 | ```bash 30 | # !/usr/bin/bash 31 | # start.sh 32 | set -e 33 | cd `dirname $0` 34 | pipenv run python health_checkin_helper.py -a * -p * -lng 121.63529 -lat 29.89154 -c 宁波校区 35 | ``` 36 | 37 | 38 | ## 更新日志: 39 | - 2022年5月8日: 增加验证码识别, 使用ddddocr库完成, 由于onnruntime需要 bool: 27 | pass 28 | 29 | @classmethod 30 | def valid(cls): 31 | pass 32 | 33 | 34 | class PushplusPush(IPushUtil): 35 | PUSHPLUS_TOKEN = os.getenv("PUSHPLUS_TOKEN", None) 36 | 37 | def push(self, title: str = "", content: str = "", *args, **kwargs) -> bool: 38 | d = { 39 | "token": self.PUSHPLUS_TOKEN, 40 | "template": "markdown", 41 | "title": "{date}-{title}".format(date=date.today(), title=title), 42 | "content": content 43 | } 44 | res = requests.post("http://www.pushplus.plus/send", data=d) 45 | if not (200 <= res.json().get("code") < 300): 46 | print(res.json()) 47 | return 200 <= res.json().get("code") < 300 48 | 49 | @classmethod 50 | def valid(cls): 51 | return cls.PUSHPLUS_TOKEN is not None 52 | 53 | 54 | class ServerChanPush(IPushUtil): 55 | SERVERCHAN_TOKEN = os.getenv("SERVERCHAN_TOKEN", None) 56 | 57 | def push(self, title: str = "", content: str = "", *args, **kwargs) -> bool: 58 | data = { 59 | 'text': "{date}-{title}".format(date=date.today(), title=title), 60 | 'desp': content 61 | } 62 | res = requests.post(url='https://sc.ftqq.com/{}.send'.format(self.SERVERCHAN_TOKEN), data=data) 63 | if not (res.json().get("errmsg") == "success"): 64 | print(res.json()) 65 | return res.json().get("errmsg") == "success" 66 | 67 | @classmethod 68 | def valid(cls): 69 | return cls.SERVERCHAN_TOKEN is not None 70 | 71 | 72 | class DingDingPush(IPushUtil): 73 | URL = "https://oapi.dingtalk.com/robot/send" 74 | # 钉钉机器人的设置 75 | DING_ACCESS_TOKEN = os.getenv("DING_ACCESS_TOKEN", None) 76 | DING_SECRET = os.getenv("DING_SECRET", None) 77 | 78 | def __init__(self): 79 | self.target_url = self.get_url() 80 | 81 | def get_url(self): 82 | timestamp = round(time.time() * 1000) 83 | secret_enc = bytes(self.DING_SECRET, encoding="utf-8") 84 | string_to_sign = "{}\n{}".format(timestamp, self.DING_SECRET) 85 | string_to_sign_enc = bytes(string_to_sign, encoding="utf-8") 86 | hmac_code = hmac.new( 87 | secret_enc, string_to_sign_enc, digestmod=hashlib.sha256 88 | ).digest() 89 | sign = urllib.parse.quote_plus(base64.b64encode(hmac_code)) 90 | return self.URL + "?access_token={access_token}×tamp={timestamp}&sign={sign}".format( 91 | access_token=self.DING_ACCESS_TOKEN, timestamp=timestamp, sign=sign) 92 | 93 | def push(self, title: str = "", content: str = "", *args, **kwargs) -> bool: 94 | # msg = self.gen_markdown_msg(title, content) 95 | msg = self.gen_card_msg(title, **kwargs) 96 | return self.send(msg) 97 | 98 | @classmethod 99 | def valid(cls): 100 | return cls.DING_ACCESS_TOKEN is not None and cls.DING_SECRET is not None 101 | 102 | def send(self, message): 103 | resp = requests.post(self.target_url, json=message) 104 | return resp.json() 105 | 106 | @staticmethod 107 | def gen_card_msg(title, *args, **kwargs): 108 | return { 109 | "msgtype": "actionCard", 110 | "actionCard": { 111 | "title": "Leetcode", 112 | "text": title, 113 | "btnOrientation": "0", 114 | "btns": [ 115 | { 116 | "title": "查看原文", 117 | "actionURL": kwargs.get("url") 118 | }, 119 | ] 120 | } 121 | } 122 | 123 | @staticmethod 124 | def gen_text_msg(content, at=None, at_all=False): 125 | if at is None: 126 | at = [] 127 | return { 128 | "msgtype": "text", 129 | "text": {"content": content}, 130 | "at": {"atMobiles": at, "isAtAll": at_all}, 131 | } 132 | 133 | @staticmethod 134 | def gen_markdown_msg(title, text, at=None, at_all=False): 135 | if at is None: 136 | at = ["15061873738"] 137 | 138 | def generateText(): 139 | res = "" 140 | # 最顶行显示标题 141 | res += "# " + title + "\n" 142 | # 内容 143 | res += text 144 | # at对象 145 | res += reduce(lambda x, y: x + "@" + y, at, "") 146 | return res 147 | 148 | return { 149 | "msgtype": "markdown", 150 | "markdown": { 151 | "title": title, 152 | "text": generateText() 153 | }, 154 | "at": {"atMobiles": at, "isAtAll": at_all}, 155 | } 156 | 157 | 158 | class PushUtilContext: 159 | def __init__(self, push_type: PushTypeEnum = None): 160 | if not push_type: 161 | self.pusher = None 162 | self.init() 163 | else: 164 | if push_type == PushTypeEnum.PushplusPush: 165 | self.pusher = PushplusPush() 166 | elif push_type == PushTypeEnum.DingDingPush: 167 | self.pusher = DingDingPush() 168 | elif push_type == PushTypeEnum.ServerChanPush: 169 | self.pusher = ServerChanPush() 170 | else: 171 | raise Exception("初始化的PusherTpye不合规") 172 | 173 | def init(self): 174 | if DingDingPush.valid(): 175 | self.pusher = DingDingPush() 176 | elif PushplusPush.valid(): 177 | self.pusher = PushplusPush() 178 | elif ServerChanPush.valid(): 179 | self.pusher = ServerChanPush() 180 | else: 181 | raise Exception("没有可用的Pusher, 请进行正确的配置") 182 | 183 | def push(self, title: str = "", content: str = "", *args, **kwargs) -> bool: 184 | if not self.pusher: 185 | print("pusher未配置或是配置错误") 186 | return False 187 | return self.pusher.push(title, content, *args, **kwargs) 188 | 189 | 190 | # 如果要使用pusher, 则自定义 191 | p = PushUtilContext() 192 | -------------------------------------------------------------------------------- /Net_Assistant/Andriod/ZjuCstNet.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Freedomisgood/When_Coding_in_ZJU/d2db401cf0e9e5d788a832e71aae57bae6db7bf6/Net_Assistant/Andriod/ZjuCstNet.apk -------------------------------------------------------------------------------- /Net_Assistant/Andriod/ZjuCstNet.js: -------------------------------------------------------------------------------- 1 | 2 | const storageName = "ZjuNet_nymrli.top"; 3 | //如需更改信息,临时取消下一行的注释 4 | //storages.remove(storageName); 5 | 6 | 7 | var headers = { 8 | 'Connection': 'keep-alive', 9 | 'Pragma': 'no-cache', 10 | 'Cache-Control': 'no-cache', 11 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 12 | 'Content-Type': 'application/x-www-form-urlencoded', 13 | 'Accept': '*/*', 14 | 'Origin': 'http://192.0.0.6', 15 | 'Referer': 'http://192.0.0.6/index.html?url=http://www.msftconnecttest.com/redirect', 16 | 'Accept-Language': 'zh-CN,zh;q=0.9', 17 | } 18 | 19 | 20 | function encrypt_password(pwd){ 21 | let p = hex_md5(pwd) 22 | return p.substr(8, 16) 23 | } 24 | 25 | function getUserConfig() { 26 | var ctx = storages.create(storageName); 27 | var userConfig = {} 28 | if (!ctx.contains("account")) { 29 | var user = rawInput("请输入校园网账号"); 30 | var pwd = rawInput("请输入校园网密码"); 31 | if (user !== null && pwd !== null && user !== "") { 32 | ctx.put("account", user); 33 | ctx.put("password", pwd); 34 | } else { 35 | toast("账号密码无效!"); 36 | exit(); 37 | } 38 | } 39 | userConfig["account"] = ctx.get("account"); 40 | userConfig["password"] = ctx.get("password"); 41 | return userConfig; 42 | } 43 | 44 | 45 | function login(userConfig){ 46 | var data = { 47 | 'username': userConfig["account"], 48 | 'password': encrypt_password(userConfig["password"]), 49 | 'drop': '0', 50 | 'type': '1', 51 | 'n': '100' 52 | } 53 | /** 异步改成同步 54 | http.post('http://192.0.0.6/cgi-bin/do_login', data, { 55 | headers: headers 56 | }, function(res, err){ 57 | if(err){ 58 | toast("登录失败!~检查是否连接校园网"); 59 | return 400; 60 | } 61 | text_result = res.body.string() 62 | if ( !isNaN(text_result)){ 63 | toast("登录成功!~"); 64 | return 200; 65 | }else if (text_result === "online_num_error"){ 66 | toast("登录失败, 在线设备数量超限!~"); 67 | return 400; 68 | }else if (text_result === "username_error"){ 69 | toast("登录失败, 账号密码错误!~"); 70 | storages.remove(storageName) 71 | return 400; 72 | }else{ 73 | toast("Login Unknown error!~"); 74 | return 400; 75 | } 76 | } 77 | ); 78 | */ 79 | 80 | res = http.post('http://192.0.0.6/cgi-bin/do_login', data, { 81 | headers: headers 82 | }); 83 | if(res.statusCode != 200){ 84 | toast("登录失败!~检查是否连接校园网"); 85 | return 500; 86 | } 87 | text_result = res.body.string() 88 | if ( !isNaN(text_result)){ 89 | toast("登录成功!~"); 90 | return 200; 91 | }else if (text_result === "online_num_error"){ 92 | toast("登录失败, 在线设备数量超限!~"); 93 | return 400; 94 | }else if (text_result === "username_error"){ 95 | toast("登录失败, 账号密码错误!~"); 96 | storages.remove(storageName) 97 | return 400; 98 | }else{ 99 | toast("Login Unknown error!~"); 100 | return 400; 101 | } 102 | }; 103 | 104 | 105 | function logout(userConfig){ 106 | data = { 107 | 'username': userConfig["account"], 108 | 'password': userConfig["password"], 109 | 'drop': '0', 110 | 'type': '1', 111 | 'n': '1' 112 | } 113 | 114 | http.post('http://192.0.0.6/cgi-bin/force_logout', data, { 115 | headers: headers 116 | }, function(res, err){ 117 | if(err){ 118 | toast("登录失败!~检查是否连接校园网"); 119 | return ; 120 | } 121 | text = res.body.string() 122 | if (text === "logout_ok"){ 123 | toast("注销成功!已下线其他设备~"); 124 | }else if (text === "logout_error"){ 125 | // toast("注销失败, 没有在线设备!~"); 126 | log("当前无其他设备上线") 127 | }else{ 128 | toast("Logout Unknown error!~"); 129 | } 130 | } 131 | ); 132 | } 133 | 134 | //参数n为休眠时间,单位为毫秒: 135 | function sleep(n) { 136 | var start = new Date().getTime(); 137 | while (true) { 138 | if (new Date().getTime() - start > n) { 139 | break; 140 | } 141 | } 142 | } 143 | 144 | 145 | // storages.remove(storageName) 146 | log("\n/**\n * @author: Mrli\n * @Description: 浙软上网助手\n * @Version: 1.0 2021年9月13日\n**/"); 147 | var userConfig = getUserConfig(); 148 | // 登录前都把其他设备给下线 149 | logout(userConfig); 150 | // 然后再将该设备上线 151 | var code = login(userConfig); 152 | // 如果下线的是其他设备, 需要等待1分钟才能重新上线, 因此需要重新尝试登录 153 | while (code != 200 && code != 500){ 154 | code = login(userConfig) 155 | if (code != 200){ 156 | log("该账号处于上网设备更换冷却期, 上网重试中...") 157 | sleep(5000) 158 | } 159 | } 160 | 161 | 162 | 163 | /** md5加密库 */ 164 | /* 165 | * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message 166 | * Digest Algorithm, as defined in RFC 1321. 167 | * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009 168 | * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet 169 | * Distributed under the BSD License 170 | * See http://pajhome.org.uk/crypt/md5 for more info. 171 | */ 172 | 173 | /* 174 | * Configurable variables. You may need to tweak these to be compatible with 175 | * the server-side, but the defaults work in most cases. 176 | */ 177 | var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */ 178 | var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */ 179 | 180 | /* 181 | * These are the functions you'll usually want to call 182 | * They take string arguments and return either hex or base-64 encoded strings 183 | */ 184 | function hex_md5(s) { return rstr2hex(rstr_md5(str2rstr_utf8(s))); } 185 | function b64_md5(s) { return rstr2b64(rstr_md5(str2rstr_utf8(s))); } 186 | function any_md5(s, e) { return rstr2any(rstr_md5(str2rstr_utf8(s)), e); } 187 | function hex_hmac_md5(k, d) 188 | { return rstr2hex(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); } 189 | function b64_hmac_md5(k, d) 190 | { return rstr2b64(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); } 191 | function any_hmac_md5(k, d, e) 192 | { return rstr2any(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)), e); } 193 | 194 | /* 195 | * Perform a simple self-test to see if the VM is working 196 | */ 197 | function md5_vm_test() 198 | { 199 | return hex_md5("abc").toLowerCase() == "900150983cd24fb0d6963f7d28e17f72"; 200 | } 201 | 202 | /* 203 | * Calculate the MD5 of a raw string 204 | */ 205 | function rstr_md5(s) 206 | { 207 | return binl2rstr(binl_md5(rstr2binl(s), s.length * 8)); 208 | } 209 | 210 | /* 211 | * Calculate the HMAC-MD5, of a key and some data (raw strings) 212 | */ 213 | function rstr_hmac_md5(key, data) 214 | { 215 | var bkey = rstr2binl(key); 216 | if(bkey.length > 16) bkey = binl_md5(bkey, key.length * 8); 217 | 218 | var ipad = Array(16), opad = Array(16); 219 | for(var i = 0; i < 16; i++) 220 | { 221 | ipad[i] = bkey[i] ^ 0x36363636; 222 | opad[i] = bkey[i] ^ 0x5C5C5C5C; 223 | } 224 | 225 | var hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8); 226 | return binl2rstr(binl_md5(opad.concat(hash), 512 + 128)); 227 | } 228 | 229 | /* 230 | * Convert a raw string to a hex string 231 | */ 232 | function rstr2hex(input) 233 | { 234 | try { hexcase } catch(e) { hexcase=0; } 235 | var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; 236 | var output = ""; 237 | var x; 238 | for(var i = 0; i < input.length; i++) 239 | { 240 | x = input.charCodeAt(i); 241 | output += hex_tab.charAt((x >>> 4) & 0x0F) 242 | + hex_tab.charAt( x & 0x0F); 243 | } 244 | return output; 245 | } 246 | 247 | /* 248 | * Convert a raw string to a base-64 string 249 | */ 250 | function rstr2b64(input) 251 | { 252 | try { b64pad } catch(e) { b64pad=''; } 253 | var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 254 | var output = ""; 255 | var len = input.length; 256 | for(var i = 0; i < len; i += 3) 257 | { 258 | var triplet = (input.charCodeAt(i) << 16) 259 | | (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0) 260 | | (i + 2 < len ? input.charCodeAt(i+2) : 0); 261 | for(var j = 0; j < 4; j++) 262 | { 263 | if(i * 8 + j * 6 > input.length * 8) output += b64pad; 264 | else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F); 265 | } 266 | } 267 | return output; 268 | } 269 | 270 | /* 271 | * Convert a raw string to an arbitrary string encoding 272 | */ 273 | function rstr2any(input, encoding) 274 | { 275 | var divisor = encoding.length; 276 | var i, j, q, x, quotient; 277 | 278 | /* Convert to an array of 16-bit big-endian values, forming the dividend */ 279 | var dividend = Array(Math.ceil(input.length / 2)); 280 | for(i = 0; i < dividend.length; i++) 281 | { 282 | dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1); 283 | } 284 | 285 | /* 286 | * Repeatedly perform a long division. The binary array forms the dividend, 287 | * the length of the encoding is the divisor. Once computed, the quotient 288 | * forms the dividend for the next step. All remainders are stored for later 289 | * use. 290 | */ 291 | var full_length = Math.ceil(input.length * 8 / 292 | (Math.log(encoding.length) / Math.log(2))); 293 | var remainders = Array(full_length); 294 | for(j = 0; j < full_length; j++) 295 | { 296 | quotient = Array(); 297 | x = 0; 298 | for(i = 0; i < dividend.length; i++) 299 | { 300 | x = (x << 16) + dividend[i]; 301 | q = Math.floor(x / divisor); 302 | x -= q * divisor; 303 | if(quotient.length > 0 || q > 0) 304 | quotient[quotient.length] = q; 305 | } 306 | remainders[j] = x; 307 | dividend = quotient; 308 | } 309 | 310 | /* Convert the remainders to the output string */ 311 | var output = ""; 312 | for(i = remainders.length - 1; i >= 0; i--) 313 | output += encoding.charAt(remainders[i]); 314 | 315 | return output; 316 | } 317 | 318 | /* 319 | * Encode a string as utf-8. 320 | * For efficiency, this assumes the input is valid utf-16. 321 | */ 322 | function str2rstr_utf8(input) 323 | { 324 | var output = ""; 325 | var i = -1; 326 | var x, y; 327 | 328 | while(++i < input.length) 329 | { 330 | /* Decode utf-16 surrogate pairs */ 331 | x = input.charCodeAt(i); 332 | y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0; 333 | if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF) 334 | { 335 | x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF); 336 | i++; 337 | } 338 | 339 | /* Encode output as utf-8 */ 340 | if(x <= 0x7F) 341 | output += String.fromCharCode(x); 342 | else if(x <= 0x7FF) 343 | output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F), 344 | 0x80 | ( x & 0x3F)); 345 | else if(x <= 0xFFFF) 346 | output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F), 347 | 0x80 | ((x >>> 6 ) & 0x3F), 348 | 0x80 | ( x & 0x3F)); 349 | else if(x <= 0x1FFFFF) 350 | output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07), 351 | 0x80 | ((x >>> 12) & 0x3F), 352 | 0x80 | ((x >>> 6 ) & 0x3F), 353 | 0x80 | ( x & 0x3F)); 354 | } 355 | return output; 356 | } 357 | 358 | /* 359 | * Encode a string as utf-16 360 | */ 361 | function str2rstr_utf16le(input) 362 | { 363 | var output = ""; 364 | for(var i = 0; i < input.length; i++) 365 | output += String.fromCharCode( input.charCodeAt(i) & 0xFF, 366 | (input.charCodeAt(i) >>> 8) & 0xFF); 367 | return output; 368 | } 369 | 370 | function str2rstr_utf16be(input) 371 | { 372 | var output = ""; 373 | for(var i = 0; i < input.length; i++) 374 | output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF, 375 | input.charCodeAt(i) & 0xFF); 376 | return output; 377 | } 378 | 379 | /* 380 | * Convert a raw string to an array of little-endian words 381 | * Characters >255 have their high-byte silently ignored. 382 | */ 383 | function rstr2binl(input) 384 | { 385 | var output = Array(input.length >> 2); 386 | for(var i = 0; i < output.length; i++) 387 | output[i] = 0; 388 | for(var i = 0; i < input.length * 8; i += 8) 389 | output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (i%32); 390 | return output; 391 | } 392 | 393 | /* 394 | * Convert an array of little-endian words to a string 395 | */ 396 | function binl2rstr(input) 397 | { 398 | var output = ""; 399 | for(var i = 0; i < input.length * 32; i += 8) 400 | output += String.fromCharCode((input[i>>5] >>> (i % 32)) & 0xFF); 401 | return output; 402 | } 403 | 404 | /* 405 | * Calculate the MD5 of an array of little-endian words, and a bit length. 406 | */ 407 | function binl_md5(x, len) 408 | { 409 | /* append padding */ 410 | x[len >> 5] |= 0x80 << ((len) % 32); 411 | x[(((len + 64) >>> 9) << 4) + 14] = len; 412 | 413 | var a = 1732584193; 414 | var b = -271733879; 415 | var c = -1732584194; 416 | var d = 271733878; 417 | 418 | for(var i = 0; i < x.length; i += 16) 419 | { 420 | var olda = a; 421 | var oldb = b; 422 | var oldc = c; 423 | var oldd = d; 424 | 425 | a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936); 426 | d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586); 427 | c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819); 428 | b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330); 429 | a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897); 430 | d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426); 431 | c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341); 432 | b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983); 433 | a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416); 434 | d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417); 435 | c = md5_ff(c, d, a, b, x[i+10], 17, -42063); 436 | b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162); 437 | a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682); 438 | d = md5_ff(d, a, b, c, x[i+13], 12, -40341101); 439 | c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290); 440 | b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329); 441 | 442 | a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510); 443 | d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632); 444 | c = md5_gg(c, d, a, b, x[i+11], 14, 643717713); 445 | b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302); 446 | a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691); 447 | d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083); 448 | c = md5_gg(c, d, a, b, x[i+15], 14, -660478335); 449 | b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848); 450 | a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438); 451 | d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690); 452 | c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961); 453 | b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501); 454 | a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467); 455 | d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784); 456 | c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473); 457 | b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734); 458 | 459 | a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558); 460 | d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463); 461 | c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562); 462 | b = md5_hh(b, c, d, a, x[i+14], 23, -35309556); 463 | a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060); 464 | d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353); 465 | c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632); 466 | b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640); 467 | a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174); 468 | d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222); 469 | c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979); 470 | b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189); 471 | a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487); 472 | d = md5_hh(d, a, b, c, x[i+12], 11, -421815835); 473 | c = md5_hh(c, d, a, b, x[i+15], 16, 530742520); 474 | b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651); 475 | 476 | a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844); 477 | d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415); 478 | c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905); 479 | b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055); 480 | a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571); 481 | d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606); 482 | c = md5_ii(c, d, a, b, x[i+10], 15, -1051523); 483 | b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799); 484 | a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359); 485 | d = md5_ii(d, a, b, c, x[i+15], 10, -30611744); 486 | c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380); 487 | b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649); 488 | a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070); 489 | d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379); 490 | c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259); 491 | b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551); 492 | 493 | a = safe_add(a, olda); 494 | b = safe_add(b, oldb); 495 | c = safe_add(c, oldc); 496 | d = safe_add(d, oldd); 497 | } 498 | return Array(a, b, c, d); 499 | } 500 | 501 | /* 502 | * These functions implement the four basic operations the algorithm uses. 503 | */ 504 | function md5_cmn(q, a, b, x, s, t) 505 | { 506 | return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b); 507 | } 508 | function md5_ff(a, b, c, d, x, s, t) 509 | { 510 | return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); 511 | } 512 | function md5_gg(a, b, c, d, x, s, t) 513 | { 514 | return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); 515 | } 516 | function md5_hh(a, b, c, d, x, s, t) 517 | { 518 | return md5_cmn(b ^ c ^ d, a, b, x, s, t); 519 | } 520 | function md5_ii(a, b, c, d, x, s, t) 521 | { 522 | return md5_cmn(c ^ (b | (~d)), a, b, x, s, t); 523 | } 524 | 525 | /* 526 | * Add integers, wrapping at 2^32. This uses 16-bit operations internally 527 | * to work around bugs in some JS interpreters. 528 | */ 529 | function safe_add(x, y) 530 | { 531 | var lsw = (x & 0xFFFF) + (y & 0xFFFF); 532 | var msw = (x >> 16) + (y >> 16) + (lsw >> 16); 533 | return (msw << 16) | (lsw & 0xFFFF); 534 | } 535 | 536 | /* 537 | * Bitwise rotate a 32-bit number to the left. 538 | */ 539 | function bit_rol(num, cnt) 540 | { 541 | return (num << cnt) | (num >>> (32 - cnt)); 542 | } 543 | -------------------------------------------------------------------------------- /Net_Assistant/PC/ZJU_CST_Net.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Freedomisgood/When_Coding_in_ZJU/d2db401cf0e9e5d788a832e71aae57bae6db7bf6/Net_Assistant/PC/ZJU_CST_Net.exe -------------------------------------------------------------------------------- /Net_Assistant/PC/ZJU_CST_Net.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import hashlib 3 | import json 4 | import os 5 | import time 6 | 7 | headers = { 8 | 'Connection': 'keep-alive', 9 | 'Pragma': 'no-cache', 10 | 'Cache-Control': 'no-cache', 11 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 12 | 'Content-Type': 'application/x-www-form-urlencoded', 13 | 'Accept': '*/*', 14 | 'Origin': 'http://192.0.0.6', 15 | 'Referer': 'http://192.0.0.6/index.html?url=http://www.msftconnecttest.com/redirect', 16 | 'Accept-Language': 'zh-CN,zh;q=0.9', 17 | } 18 | 19 | 20 | def get_password(in_s): 21 | s = in_s.encode("utf8") 22 | res = hashlib.md5(s).hexdigest() 23 | start_index = 8 24 | length = 16 25 | return res[start_index: length + start_index] 26 | 27 | 28 | class ResultData: 29 | """ 30 | 解析文本=>返回结果类 31 | """ 32 | 33 | def __init__(self, msg, code): 34 | self.msg = msg 35 | self.code = code 36 | 37 | @staticmethod 38 | def match(msg): 39 | if msg.isalnum(): 40 | return ResultData(msg="登录成功", code=200) 41 | elif msg == "logout_ok": 42 | return ResultData(msg="注销成功", code=200) 43 | elif msg == "logout_error": 44 | return ResultData(msg="注销失败, 没有在线设备", code=400) 45 | elif msg == "online_num_error": 46 | return ResultData(msg="登录失败, 在线设备超时", code=400) 47 | elif msg == "username_error": 48 | return ResultData(msg="登录失败, 账号错误", code=500) 49 | elif msg == "password_error": 50 | return ResultData(msg="登录失败, 密码错误", code=500) 51 | else: 52 | return ResultData(msg="未知消息类型, {}".format(msg), code=400) 53 | 54 | def to_json(self): 55 | return {"msg": self.msg, "code": self.code} 56 | 57 | def __str__(self): 58 | return str(self.to_json()) 59 | 60 | 61 | class ZJUOnline: 62 | @staticmethod 63 | def login(stu_id: str, pwd: str): 64 | """ 65 | 登录 66 | # online_num_error ==> 超时Error 67 | # 10784662880456 ==> 登录成功显示 68 | """ 69 | data = { 70 | 'username': stu_id, 71 | 'password': get_password(pwd), 72 | 'drop': '0', 73 | 'type': '1', 74 | 'n': '100' 75 | } 76 | response = requests.post('http://192.0.0.6/cgi-bin/do_login', headers=headers, data=data, verify=False) 77 | return ResultData.match(response.text) 78 | 79 | @staticmethod 80 | def logout(stu_id: str, pwd: str): 81 | """ 82 | 注销:只要密码正确,就能使其他设备下线 83 | # logout_ok ==> 注销成功 84 | # logout_error ==> 没有上线设备 85 | """ 86 | data = { 87 | 'username': stu_id, 88 | 'password': pwd, 89 | 'drop': '0', 90 | 'type': '1', 91 | 'n': '1' 92 | } 93 | response = requests.post('http://192.0.0.6/cgi-bin/force_logout', headers=headers, data=data, verify=False) 94 | return ResultData.match(response.text) 95 | 96 | 97 | # ====================================== 配置上网信息 ======================================== 98 | account = "" 99 | pwd = "zjucst" 100 | # -------------------------------------- 配置上网信息 -------------------------------------- 101 | 102 | if __name__ == "__main__": 103 | # 账号池有两种方式维护: 104 | # 1.账号信息放在配套的本地配置文件中==>随机碰撞找到空闲账号 105 | # 2.账号信息放在服务器上,通过http请求空闲账号==>通过服务器管控,登出时需要反馈给服务器 106 | # 如果没有空闲账号,则强迫登录自己账号的设备下线,使自己上线 107 | f_name = "account.json" 108 | if account == "" or pwd == "": 109 | if not os.path.exists(f_name): 110 | with open(f_name, "w") as f: 111 | account, pwd = input("请输入账号, 密码:").split() 112 | json.dump({"account": account, "password": pwd}, f) 113 | else: 114 | with open(f_name, "r") as f: 115 | d = json.load(f) 116 | account, pwd = d.get("account"), d.get("password") 117 | res = ZJUOnline.logout(account, pwd) 118 | if res.code != 203: 119 | print(res) 120 | # 如果下线了其他设备, 需要等待1分钟才能重新上线, 所以得等待重试 121 | res.code = 400 122 | while res.code != 200 and res.code != 500 : 123 | res = ZJUOnline.login(account, pwd) 124 | print(res) 125 | if res.code != 200: 126 | print("该账号处于上网设备更换冷却期, 上网重试中...") 127 | time.sleep(5) 128 | -------------------------------------------------------------------------------- /Net_Assistant/README.md: -------------------------------------------------------------------------------- 1 | # Net_Assistant 2 | 3 | - [基于AutoJS的CST上网APP](./Andriod) 4 | - 功能点:手机设备一键上网;可实现长时间在线 5 | - [CST-PC上网二进制](./PC) 6 | - 功能点:免除手动登陆192.0.0.6;并且无在线弹窗显示,可实现长时间在线 7 | - 使用推荐:如果是直接使用二进制,由于没有配置上网信息所以会生成本地外置配置文件`account.json`,这样显得比较凌乱。推荐:可以将其exe放于文件夹中,然后创建exe的快捷方式到桌面使用。 8 | 9 | > :smile: 运行上网助手: 连接校园网后,运行APP或者exe即可登录上网,但由于CST校园网免费账号只能一个设备登录,因此目前实现机制为**设备上线前会下线已在线的设备,从而保证当前设备上线成功** 10 | > 11 | > TODO思路:为了能够更好地利用空闲账号,可以搭建账号池,即每个宿舍留出固定路由器的账号后 共享出自己的账号作为公共账号, 从而高效利用所有人的上网账号并实现单人可以上线多设备的可能。 12 | ``` 13 | 账号池有两种方式维护: 14 | 1.账号信息放在配套的本地配置文件中==>随机碰撞找到空闲账号 15 | 2.账号信息放在服务器上,通过http请求空闲账号==>通过服务器管控,登出时需要反馈给服务器 16 | 如果没有空闲账号,则强迫登录自己账号的设备下线,使自己上线 17 | ``` 18 | 19 | **使用方法**:WIFI或者有线连接校园网后,运行程序 20 | 21 | **下载链接** 22 | 23 | - PC:[ZJU_CST_Net.exe](https://wwe.lanzoui.com/i783au5lqfc) 24 | - Android:[ZjuCstNet.apk](https://wwe.lanzoui.com/ij7IEu0kmah)——密码:b5k5 25 | 26 | --- 27 | 28 | 更新日记: 29 | 30 | - 2021年9月17日: 修复库没导入,以及补充了password_error消息类型 31 | - 2021年9月14日: 第一次上传 -------------------------------------------------------------------------------- /Net_Assistant/src/ZJU_logo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Freedomisgood/When_Coding_in_ZJU/d2db401cf0e9e5d788a832e71aae57bae6db7bf6/Net_Assistant/src/ZJU_logo.jpeg -------------------------------------------------------------------------------- /Net_Assistant/src/bitbug_favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Freedomisgood/When_Coding_in_ZJU/d2db401cf0e9e5d788a832e71aae57bae6db7bf6/Net_Assistant/src/bitbug_favicon.ico -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # When_Coding_in_ZJU 2 | > :smile: 上传些在校期间用到的脚本 3 | 4 | - [ZJU-新生报到服务-适应性课程速过脚本and测试题速过脚本](./guide_classes_and_tests) 5 | - [ZJU浙软官网定时爬虫脚本(发送到微信ServerChan)](./soft_web_scrapy)——尚未维护,目前采用[Rss-Monitor](https://github.com/Freedomisgood/Zju_health_checkin_helper)实现消息推送。 6 | - [浙软课程资料](./Course_Materials)——已移除 7 | - [ZJU健康打卡脚本](./Health_Checkin)——已迁移[Zju_health_checkin_helper](https://github.com/Freedomisgood/Zju_health_checkin_helper) 8 | - [LeetCode每日一题推送](./LeetCoding) 9 | 10 | 附:Github没有提供文件夹的下载方式,所以可以通过[DownGit](https://zhoudaxiaa.gitee.io/downgit/#/home)下载某个文件或者文件夹 11 | 12 | 提示: 13 | - 该项目持续有人fork, 但不少都已经分离出去了, 请移步对应仓库。 14 | - 浙大软院消息推送可进钉钉群(可拓展为浙大官网) 15 | ![lADPJwY7TmPyrJbNBD_NA24_878_1087](https://user-images.githubusercontent.com/31088082/178141317-3a0f7a8b-658f-42a7-8d02-2f73d8a360b8.jpg) 16 | 17 | --- 18 | 19 | ![Stargazers over time](https://starchart.cc/Freedomisgood/When_Coding_in_ZJU.svg) 20 | -------------------------------------------------------------------------------- /Yellow_Page/readme.md: -------------------------------------------------------------------------------- 1 | # 浙大黄页 2 | 3 | - [★浙大服务平台](https://service.zju.edu.cn/_s2/students_yyzx/main.psp) 4 | - [研究生院官网](http://www.grs.zju.edu.cn/) 5 | - [综合服务网](http://zhfw.zju.edu.cn/) 6 | - [校务服务网](http://xwfw.zju.edu.cn/) 7 | - [软件学院官网](http://www.cst.zju.edu.cn/) 8 | - [学在浙大](http://course.zju.edu.cn/) 9 | 10 | -------------------------------------------------------------------------------- /guide_classes_and_tests/for_classes/README.md: -------------------------------------------------------------------------------- 1 | # ZJU-新生报到服务-适应性课程速过 2 | 3 | > ZJU迎新系统中需要完成的课程任务: http://regi.zju.edu.cn/classes (首页->报到服务->适应性课程) 4 | 5 | 需要填写的参数有两个:(需要从Cookies中获取) 6 | 7 | - JSESSIONID 8 | - stu_token 9 | 10 | ## 方一(旧版): 11 | 12 | ### 1.获取所需参数: 13 | 14 | > 选择下列一种即可,推荐第一种 15 | 16 | 1. 点击浏览器地址栏前的安全性图标,选择Cookies,然后按如下操作 17 | 18 | ![](https://i.loli.net/2021/08/23/BGwCz3AcpaSlDkE.png) 19 | 20 | 2. 先访问 http://regi.zju.edu.cn/classes 登录账号后后打开开发者工具(Chrome为F12快捷键) 然后将Application菜单栏下Storage->cookies中的stu_token和 JSESSIONID 对应输入为本程序的参数 21 | 22 | ![](https://i.loli.net/2021/08/23/LiudKFgpBrNkqn3.png) 23 | 24 | ### 2.执行程序 25 | 26 | 在脚本所在目录打开命令行,输入:` python zju_guide_class.py -s 获取的JSESSIONID -t 获取的STU_TOKEN` 27 | 28 | ![](https://i.loli.net/2021/08/23/VtS7DH2PyGM8hsc.png) 29 | 30 | ## 方二(2021年9月7日更新): 31 | 32 | 运行通过统一认证平台登录获得信息从而运行的程序: `python guide_class_login.py` -------------------------------------------------------------------------------- /guide_classes_and_tests/for_classes/guide_class_login.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2021/9/7 13:32 4 | # @Author : Mrli 5 | # @File : guide_class_login.py 6 | import time 7 | from loginUtils import SSOLogin 8 | 9 | 10 | class GuideClass(SSOLogin): 11 | def getAllCourses(self): 12 | params = ( 13 | ('t', str(int(round(time.time() * 1000)))), 14 | ('title', ''), 15 | ('isRequired', ''), 16 | ) 17 | response = self.sess.get('http://regi.zju.edu.cn/grs-pro/config/vclassesdetail/queryList', 18 | params=params, verify=False) 19 | return response.json().get("list") 20 | 21 | def passClass(self, class_id: str) -> None: 22 | self.sess.headers["Referer"] = 'http://regi.zju.edu.cn/classDetail?id={}'.format(class_id) 23 | params = ( 24 | ('t', str(int(round(time.time() * 1000)))), 25 | ('studyTime', str(1000)), 26 | ('classId', class_id), 27 | ) 28 | response = self.sess.post('http://regi.zju.edu.cn/grs-pro/stu/classinfo/addStudyLog', 29 | params=params, verify=False) 30 | print(response.text) 31 | 32 | def run(self): 33 | self.sso_login() 34 | all_courses = self.getAllCourses() 35 | for c in all_courses: 36 | self.passClass(c.get("id")) 37 | 38 | 39 | if __name__ == '__main__': 40 | # if len(sys.argv) < 2: 41 | # print("请输入学号、密码") 42 | # username, password = sys.argv[1], sys.argv[2] 43 | in_str = input("请输入学号、密码(空格分割):").split() 44 | if len(in_str) == 2: 45 | username, password = in_str 46 | else: 47 | raise Exception("请分别输入学号与密码(空格分割)") 48 | 49 | gc = GuideClass(username, password) 50 | gc.run() 51 | -------------------------------------------------------------------------------- /guide_classes_and_tests/for_classes/loginUtils.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import re 3 | import time 4 | import urllib.parse as urlparse 5 | 6 | 7 | # Exceptions 8 | class LoginError(Exception): 9 | """Login Exception""" 10 | pass 11 | 12 | 13 | class ZJULogin(object): 14 | """ 15 | 统一认证平台的登录使用的ZJU-nCov-Hitcarder的开源代码,感谢这位同学开源对RSA加密的贡献 16 | Attributes: 17 | username: (str) 浙大统一认证平台用户名(一般为学号) 18 | password: (str) 浙大统一认证平台密码 19 | login_url: (str) 登录url 20 | sess: (requests.Session) 统一的session管理 21 | """ 22 | 23 | def __init__(self, username, password): 24 | self.username = username 25 | self.password = password 26 | self.login_url = "https://zjuam.zju.edu.cn/cas/login?service=http%3A%2F%2Fservice.zju.edu.cn%2F" 27 | self.sess = requests.Session() 28 | 29 | def login(self): 30 | """Login to ZJU platform""" 31 | res = self.sess.get(self.login_url) 32 | execution = re.search( 33 | 'name="execution" value="(.*?)"', res.text).group(1) 34 | res = self.sess.get( 35 | url='https://zjuam.zju.edu.cn/cas/v2/getPubKey').json() 36 | n, e = res['modulus'], res['exponent'] 37 | encrypt_password = self._rsa_encrypt(self.password, e, n) 38 | 39 | data = { 40 | 'username': self.username, 41 | 'password': encrypt_password, 42 | 'execution': execution, 43 | '_eventId': 'submit', 44 | "authcode": "" 45 | } 46 | res = self.sess.post(url=self.login_url, data=data) 47 | # check if login successfully 48 | if '统一身份认证平台' in res.content.decode(): 49 | raise LoginError('登录失败,请核实账号密码重新登录') 50 | print("统一认证平台登录成功") 51 | return self.sess 52 | 53 | def _rsa_encrypt(self, password_str, e_str, M_str): 54 | password_bytes = bytes(password_str, 'ascii') 55 | password_int = int.from_bytes(password_bytes, 'big') 56 | e_int = int(e_str, 16) 57 | M_int = int(M_str, 16) 58 | result_int = pow(password_int, e_int, M_int) 59 | return hex(result_int)[2:].rjust(128, '0') 60 | 61 | 62 | class SSOLogin(ZJULogin): 63 | """ 64 | 完成测试需要获得token 65 | """ 66 | def sso_login(self): 67 | """ 68 | 获得token,并在请求头上加上token 69 | Returns: token 70 | """ 71 | 72 | def substract_query_params(url): 73 | parsed = urlparse.urlparse(url) 74 | querys = urlparse.parse_qs(parsed.query) 75 | return {k: v[0] for k, v in querys.items()} 76 | 77 | def get_code(): 78 | params = ( 79 | ('response_type', 'code'), 80 | ('client_id', 'kU3pGMMUuXK3zFmnMY'), 81 | ('redirect_uri', 'http://regi.zju.edu.cn/ssologin'), 82 | ) 83 | 84 | response = self.sess.get('https://zjuam.zju.edu.cn/cas/oauth2.0/authorize', params=params) 85 | location = "" 86 | for r in response.history: 87 | if "location" in r.headers: 88 | location = r.headers.get("location") 89 | return substract_query_params(location).get("code") 90 | 91 | # 获得cookies 92 | self.login() 93 | # 获得sso_login所需的code 94 | code = get_code() 95 | params = ( 96 | ('t', int(round(time.time() * 1000))), 97 | ('code', code), 98 | ('role', 'student'), 99 | ) 100 | response = self.sess.get('http://regi.zju.edu.cn/grs-pro/zs/auth/ssoLogin', params=params, verify=False) 101 | token = response.json().get("token") 102 | self.sess.headers["token"] = token 103 | return token 104 | 105 | 106 | if __name__ == "__main__": 107 | login = ZJULogin(username="*", password="*") 108 | print("登录到浙大统一身份认证平台...") 109 | res = login.login() 110 | print(res.headers) 111 | print(res.cookies) 112 | -------------------------------------------------------------------------------- /guide_classes_and_tests/for_classes/zju_guide_class.py: -------------------------------------------------------------------------------- 1 | from copy import copy 2 | import argparse 3 | 4 | import requests 5 | import time 6 | 7 | JSESSIONID = "*" 8 | stu_token = "*" 9 | # token = "*" // 不用填 10 | 11 | cookies = { 12 | 'JSESSIONID': JSESSIONID, 13 | 'Qs_lvt_292926': '1619495274', 14 | 'Qs_pv_292926': '182399785585604800', 15 | '_csrf': 'S8mwplVi9KWoF2WQ0TlCeEh8aBlgVLBgLOUa6g8JMPw%3D', 16 | '_pv0': 'ywLMJMeXPh%2FykjNV24jkfxh6mFAUfILtoDzkLX%2F96x7Jpv%2BHFgCSGimzOFr2hrCm2AIG2pQQucyMR8wxROGowuPj0RGFM3v9%2BlqshxvqV3l2B89KLL4D27cJ68npMhwJAl8D8u0dS0u9cp4WYP7B7L474Tl%2BOKwJ47RJv3NrPvkrfme7t80z2hdpAegSDjSzVNZkk5h75lJwrgfKsrZcZb9ItULZB65G8d6fgbJr6ONTUXZTxECppk%2BCq7jX3bhnqF6GCwtCCb94YliNhx0Y0jKUnFRMKSgCr5ClNuVQ67uoT00wqMqVYwvF6%2BagcPBECJpBGplicjoUh62c7BaZuqvQdBxYxyD4wP7nHm2NgC2vGfGBLRl2rrh018Y7YRYmP9XnpLWw5UnYeSVGeFiWc6HPAMfSVYqUKd949HvYDnQ%3D', 17 | '_pf0': '6GgR2KbEHBQWjpDcUa6X7cyxbW9y7bFDd3RvfkoeIUI%3D', 18 | '_pm0': 'jRckSHvo34%2FTAWDSQWWm3fct5DKIt2jVk0QuOMs23J4%3D', 19 | '_pc0': '9wE8G%2Bo82PYhmhy5BhNMq6v4gKfPw6OG2cC5CkK4dLc%3D', 20 | 'iPlanetDirectoryPro': 'UKkZdfFnupZh%2BFiz8Oqo67cZJGGo%2FPl6Ku%2BrT0hrFezltEfo9TfZa8T%2FA%2F2KQn9npzcTwBagl6DGpRr7Efqr1Qvdrj8fBsrTl%2BcULGim%2FlB%2FcUEzgxrOKJ2ygcCiMt6oIgRxQQbqc4NDJhkn5Kq9Delpp3gsYQMN60wsgRK7S%2FUhG9rOZG11S6KmFqBGWTh%2B03JVS7DTId1O5MYWrq%2B3I1291xbEgLYhGs5tASy3VJgYtqmzFgozWeJrn6yB1cAknSAfBK2iryN7ksTW3Acv5Oys3slK5ucfgiSCMCDEhLdX883lvMV%2BYomLLxsSIRlGOcuS1SONzF0DYGriuzTaT353Np%2BXO10xRghb0ShXwEI%3D', 21 | 'pageType': '', 22 | 'stu-token': stu_token, 23 | } 24 | 25 | headers = { 26 | 'Connection': 'keep-alive', 27 | 'Pragma': 'no-cache', 28 | 'Cache-Control': 'no-cache', 29 | 'Accept': 'application/json, text/plain, */*', 30 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36', 31 | # 'token':token , 32 | 'Referer': 'http://regi.zju.edu.cn', 33 | 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 34 | } 35 | 36 | 37 | def getAllCourses(): 38 | params = ( 39 | ('t', str(int(round(time.time() * 1000)))), 40 | ('title', ''), 41 | ('isRequired', ''), 42 | ) 43 | response = requests.get('http://regi.zju.edu.cn/grs-pro/config/vclassesdetail/queryList', headers=headers, 44 | params=params, cookies=cookies, verify=False) 45 | return response.json().get("list") 46 | 47 | 48 | def passClass(class_id: str) -> None: 49 | pass_header = copy(headers) 50 | pass_header["Referer"] = 'http://regi.zju.edu.cn/classDetail?id={}'.format(class_id) 51 | params = ( 52 | ('t', str(int(round(time.time() * 1000)))), 53 | ('studyTime', str(1000)), 54 | ('classId', class_id), 55 | ) 56 | response = requests.post('http://regi.zju.edu.cn/grs-pro/stu/classinfo/addStudyLog', headers=pass_header, 57 | params=params, cookies=cookies, verify=False) 58 | print(response.text) 59 | 60 | 61 | if __name__ == '__main__': 62 | parser = argparse.ArgumentParser(description="先访问 http://regi.zju.edu.cn/classes \ 63 | 登录账号后后打开开发者工具(Chrome为F12快捷键) \ 64 | 然后将Application菜单栏下Storage->cookies中的stu_token和\ 65 | JSESSIONID对应输入为本程序的参数") 66 | parser.add_argument("-s", "--JSESSIONID", help="Cookies中的JSESSIONID字段", required=True) 67 | parser.add_argument("-t", "--stu_token", help="Cookies中的stu_token字段", required=True) 68 | args = parser.parse_args() 69 | 70 | JSESSIONID = args.JSESSIONID 71 | stu_token = args.stu_token 72 | all_courses = getAllCourses() 73 | for c in all_courses: 74 | passClass(c.get("id")) 75 | -------------------------------------------------------------------------------- /guide_classes_and_tests/for_tests/README.md: -------------------------------------------------------------------------------- 1 | # ZJU-新生报到服务-测试题速过 2 | 3 | > 提供[ZJU迎新系统中需要完成的测试](http://regi.zju.edu.cn/process):权益保护测试、安全测试,两个测试的速过脚本,心理测试比较重要(MMPI测试),做起来也挺方便的20分钟大致能搞定,因此就不弄脚本了 4 | 5 | ## 参数说明: 6 | 7 | 需要填写的参数有两个: 8 | 9 | - 学号 10 | - 密码 11 | 12 | ## 执行方法: 13 | 14 | 1. Python脚本执行如下: 15 | `python main_script.py ` 16 | 2. 双击main_script.exe,按提示输入学号、密码 -------------------------------------------------------------------------------- /guide_classes_and_tests/for_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Freedomisgood/When_Coding_in_ZJU/d2db401cf0e9e5d788a832e71aae57bae6db7bf6/guide_classes_and_tests/for_tests/__init__.py -------------------------------------------------------------------------------- /guide_classes_and_tests/for_tests/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Freedomisgood/When_Coding_in_ZJU/d2db401cf0e9e5d788a832e71aae57bae6db7bf6/guide_classes_and_tests/for_tests/assets/favicon.ico -------------------------------------------------------------------------------- /guide_classes_and_tests/for_tests/loginUtils.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import re 3 | import time 4 | import urllib.parse as urlparse 5 | 6 | 7 | # Exceptions 8 | class LoginError(Exception): 9 | """Login Exception""" 10 | pass 11 | 12 | 13 | class ZJULogin(object): 14 | """ 15 | 统一认证平台的登录使用的ZJU-nCov-Hitcarder的开源代码,感谢这位同学开源对RSA加密的贡献 16 | Attributes: 17 | username: (str) 浙大统一认证平台用户名(一般为学号) 18 | password: (str) 浙大统一认证平台密码 19 | login_url: (str) 登录url 20 | sess: (requests.Session) 统一的session管理 21 | """ 22 | 23 | def __init__(self, username, password): 24 | self.username = username 25 | self.password = password 26 | self.login_url = "https://zjuam.zju.edu.cn/cas/login?service=http%3A%2F%2Fservice.zju.edu.cn%2F" 27 | self.sess = requests.Session() 28 | 29 | def login(self): 30 | """Login to ZJU platform""" 31 | res = self.sess.get(self.login_url) 32 | execution = re.search( 33 | 'name="execution" value="(.*?)"', res.text).group(1) 34 | res = self.sess.get( 35 | url='https://zjuam.zju.edu.cn/cas/v2/getPubKey').json() 36 | n, e = res['modulus'], res['exponent'] 37 | encrypt_password = self._rsa_encrypt(self.password, e, n) 38 | 39 | data = { 40 | 'username': self.username, 41 | 'password': encrypt_password, 42 | 'execution': execution, 43 | '_eventId': 'submit', 44 | "authcode": "" 45 | } 46 | res = self.sess.post(url=self.login_url, data=data) 47 | # check if login successfully 48 | if '统一身份认证平台' in res.content.decode(): 49 | raise LoginError('登录失败,请核实账号密码重新登录') 50 | print("统一认证平台登录成功") 51 | return self.sess 52 | 53 | def _rsa_encrypt(self, password_str, e_str, M_str): 54 | password_bytes = bytes(password_str, 'ascii') 55 | password_int = int.from_bytes(password_bytes, 'big') 56 | e_int = int(e_str, 16) 57 | M_int = int(M_str, 16) 58 | result_int = pow(password_int, e_int, M_int) 59 | return hex(result_int)[2:].rjust(128, '0') 60 | 61 | 62 | class SSOLogin(ZJULogin): 63 | """ 64 | 完成测试需要获得token 65 | """ 66 | def sso_login(self): 67 | """ 68 | 获得token,并在请求头上加上token 69 | Returns: token 70 | """ 71 | 72 | def substract_query_params(url): 73 | parsed = urlparse.urlparse(url) 74 | querys = urlparse.parse_qs(parsed.query) 75 | return {k: v[0] for k, v in querys.items()} 76 | 77 | def get_code(): 78 | params = ( 79 | ('response_type', 'code'), 80 | ('client_id', 'kU3pGMMUuXK3zFmnMY'), 81 | ('redirect_uri', 'http://regi.zju.edu.cn/ssologin'), 82 | ) 83 | 84 | response = self.sess.get('https://zjuam.zju.edu.cn/cas/oauth2.0/authorize', params=params) 85 | location = "" 86 | for r in response.history: 87 | if "location" in r.headers: 88 | location = r.headers.get("location") 89 | return substract_query_params(location).get("code") 90 | 91 | # 获得cookies 92 | self.login() 93 | # 获得sso_login所需的code 94 | code = get_code() 95 | params = ( 96 | ('t', int(round(time.time() * 1000))), 97 | ('code', code), 98 | ('role', 'student'), 99 | ) 100 | response = self.sess.get('http://regi.zju.edu.cn/grs-pro/zs/auth/ssoLogin', params=params, verify=False) 101 | token = response.json().get("token") 102 | self.sess.headers["token"] = token 103 | return token 104 | 105 | 106 | if __name__ == "__main__": 107 | login = ZJULogin(username="*", password="*") 108 | print("登录到浙大统一身份认证平台...") 109 | res = login.login() 110 | print(res.headers) 111 | print(res.cookies) 112 | -------------------------------------------------------------------------------- /guide_classes_and_tests/for_tests/main_script.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Freedomisgood/When_Coding_in_ZJU/d2db401cf0e9e5d788a832e71aae57bae6db7bf6/guide_classes_and_tests/for_tests/main_script.exe -------------------------------------------------------------------------------- /guide_classes_and_tests/for_tests/main_script.py: -------------------------------------------------------------------------------- 1 | import json 2 | import time 3 | from loginUtils import SSOLogin 4 | 5 | 6 | class RightTest(SSOLogin): 7 | def judge_has_exams(self): 8 | response = self.sess.get('http://regi.zju.edu.cn/grs-pro/stu/examstate/checkHasExam', 9 | verify=False) 10 | json_data = response.json() 11 | if json_data.get("code") == 401: raise Exception(json_data.get("msg")) 12 | return json_data.get("flag") 13 | 14 | def get_last_final_exam_id(self): 15 | response = self.sess.get('http://regi.zju.edu.cn/grs-pro/stu/examinfo/getFinalExam', 16 | verify=False) 17 | return response.json().get("index") 18 | 19 | def get_all_exams(self): 20 | params = ( 21 | ('t', int(round(time.time() * 1000))), 22 | ('flag', 'true'), 23 | ) 24 | 25 | response = self.sess.get('http://regi.zju.edu.cn/grs-pro/config/questions/getMutExamsIds', 26 | params=params, verify=False) 27 | return response.json().get("ids") 28 | 29 | def get_all_exams_info(self, exmas: list): 30 | # new_headers = headers.update({'Content-Type': 'application/json; charset=UTF-8',}) 31 | self.sess.headers.update({'Content-Type': 'application/json; charset=UTF-8', }) 32 | response = self.sess.post('http://regi.zju.edu.cn/grs-pro/config/questions/getMutExamsByIds', 33 | data=json.dumps(exmas), verify=False) 34 | return response.json().get("exams") 35 | 36 | def submit(self, exam_info): 37 | selected = exam_info.get("answer").split(",") 38 | data = { 39 | "analysis": exam_info.get("analysis"), 40 | "answer": exam_info.get("answer"), 41 | "answers": exam_info.get("answers"), 42 | "flag": exam_info.get("flag"), 43 | "id": exam_info.get("id"), 44 | "indexs": exam_info.get("indexs"), 45 | "question": exam_info.get("question"), 46 | "selected": exam_info.get("selected"), # None 47 | # 选中的答案 48 | "selecteds": selected, 49 | "t": int(round(time.time() * 1000)), 50 | } 51 | response = self.sess.post('http://regi.zju.edu.cn/grs-pro/stu/examstate/commitExam', 52 | data=json.dumps(data), verify=False) 53 | # 每做完题更新状态, 前端用到, 这里不需要 54 | # response = self.sess.post('http://regi.zju.edu.cn/grs-pro/stu/examstate/updateNextExam', 55 | # data=json.dumps(data), verify=False) 56 | # print(response.text) 57 | print("题目{}:{}, {}".format(exam_info.get("id"), exam_info.get("question"), response.json().get("msg"))) 58 | 59 | def finish_exam(self): 60 | params = { 61 | 't': int(round(time.time() * 1000)), 62 | 'type': 'qybh', 63 | 'value': '1', 64 | 'active': '9', 65 | } 66 | 67 | response = self.sess.post('http://regi.zju.edu.cn/grs-pro/stu/processinfo/updateInfo', 68 | params=params, verify=False) 69 | print("推进下一步", response.text) 70 | return response.status_code == 200 71 | 72 | def run(self): 73 | print("~~~~~当前完成权益测试~~~~~") 74 | self.sso_login() 75 | # 是否还有题目未做完 76 | reminded = self.judge_has_exams() 77 | if reminded: 78 | last_id = self.get_last_final_exam_id() 79 | print("当前最新做到:", last_id) 80 | exam_ids = self.get_all_exams() 81 | exams = self.get_all_exams_info(exam_ids) 82 | for e_data in exams: 83 | # print(e_data) 84 | self.submit(e_data) 85 | self.finish_exam() 86 | 87 | 88 | class SecurityTest(SSOLogin): 89 | def get_all_exams(self): 90 | params = ( 91 | ('t', int(round(time.time() * 1000))), 92 | ('flag', 'true'), 93 | ) 94 | 95 | response = self.sess.get("http://regi.zju.edu.cn/grs-pro/config/questions/getAQExamIds", 96 | params=params, verify=False) 97 | return response.json().get("ids") 98 | 99 | def get_all_exams_info(self, exmas: list): 100 | self.sess.headers.update({'Content-Type': 'application/json; charset=UTF-8', }) 101 | response = self.sess.post('http://regi.zju.edu.cn/grs-pro/config/questions/getExamsByIds', 102 | data=json.dumps(exmas), verify=False) 103 | return response.json().get("exams") 104 | 105 | def finish_exam(self): 106 | params = ( 107 | ('t', int(round(time.time() * 1000))), 108 | ('type', 'aqcs'), 109 | # 自定义成绩 110 | ('value', '98'), 111 | ('active', '10'), 112 | ) 113 | 114 | response = self.sess.post('http://regi.zju.edu.cn/grs-pro/stu/processinfo/updateInfo', 115 | params=params, verify=False) 116 | print("推进下一步", response.text) 117 | return response.status_code == 200 118 | 119 | def run(self): 120 | print("~~~~~当前完成安全测试~~~~~") 121 | self.sso_login() 122 | exam_ids = self.get_all_exams() 123 | exams = self.get_all_exams_info(exam_ids) 124 | print("总共{}题".format(len(exams))) 125 | self.finish_exam() 126 | 127 | 128 | if __name__ == '__main__': 129 | # if len(sys.argv) < 2: 130 | # print("请输入学号、密码") 131 | # username, password = sys.argv[1], sys.argv[2] 132 | in_str = input("请输入学号、密码(空格分割):").split() 133 | if len(in_str) == 2: 134 | username, password = in_str 135 | else: 136 | raise Exception("请分别输入学号与密码(空格分割)") 137 | 138 | rt = RightTest(username, password) 139 | rt.run() 140 | st = SecurityTest(username, password) 141 | st.run() 142 | -------------------------------------------------------------------------------- /soft_web_scrapy/README.md: -------------------------------------------------------------------------------- 1 | # 软件学院官网爬虫 2 | 3 | > 添加cron定时功能,即可完成每日通知捕捉, 由于浙软官网承压较弱,建议定时间隔不要太多,一天一次即可。 4 | 5 | - ZJU_education: 教务信息 6 | - ZJU_tuimian: 招生信息 7 | 8 | 爬取的分别是以下两块内容信息: 9 | 10 | ![](https://i.loli.net/2021/08/23/cFIHbE4qvxPygQN.png) 11 | 12 | -------------------------------------------------------------------------------- /soft_web_scrapy/ZJU_education.py: -------------------------------------------------------------------------------- 1 | import re 2 | import time 3 | 4 | import requests 5 | from lxml import etree 6 | 7 | headers={ 8 | 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36', 9 | } 10 | 11 | _time = time.strftime("%Y-%m-%d", time.localtime()) 12 | # _time = "2020-09-22" 13 | 14 | 15 | def submit_info(secret_key,info): 16 | requests.post(url='https://sc.ftqq.com/{}.send'.format(secret_key),data=info) 17 | 18 | cookies = { 19 | 'td_cookie': '36258965', 20 | 'Hm_lvt_fe30bbc1ee45421ec1679d1b8d8f8453': '1599619275', 21 | 'JSESSIONID': '5A6BFFC2B238F873EA2E7360308592D9', 22 | } 23 | 24 | headers = { 25 | 'Connection': 'keep-alive', 26 | 'Pragma': 'no-cache', 27 | 'Cache-Control': 'no-cache', 28 | 'Upgrade-Insecure-Requests': '1', 29 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36', 30 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 31 | 'Referer': 'http://www.cst.zju.edu.cn/32178/list2.htm', 32 | 'Accept-Encoding': 'gzip, deflate', 33 | 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 34 | } 35 | 36 | response = requests.get('http://www.cst.zju.edu.cn/36216/list1.htm', headers=headers, cookies=cookies) 37 | response.encoding = 'utf8' 38 | html = etree.HTML(response.text) 39 | 40 | PREFIX_URL = "http://www.cst.zju.edu.cn/" 41 | 42 | 43 | def getNews(): 44 | NotesList = html.xpath('//*[@id="wp_news_w15"]/div/ul/li') 45 | # print("getNews", len(NotesList)) 46 | 47 | noteTitleList = [] 48 | wholeUrlList = [] 49 | announceDateList = [] 50 | 51 | for i, note in enumerate(NotesList): 52 | announceDate = note.xpath('span[2]/text()')[0] 53 | # print(announceDate) 54 | if announceDate == _time: 55 | print(announceDate) 56 | announceDateList.append(announceDate) 57 | 58 | noteTitle = note.xpath('span[1]/a/text()')[0] 59 | # print(noteTitle) 60 | noteTitleList.append(noteTitle) 61 | suffixUrl = note.xpath('span[1]/a/@href')[0] 62 | wholeUrl = PREFIX_URL + suffixUrl 63 | # print(wholeUrl) 64 | wholeUrlList.append(wholeUrl) 65 | return zip(noteTitleList, wholeUrlList, announceDateList) 66 | 67 | 68 | 69 | def analyse(): 70 | content = getNews() 71 | for noteTitle, wholeUrl, announceDate in content: 72 | print(noteTitle, wholeUrl, announceDate) 73 | if wholeUrl.endswith('.htm'): 74 | detail = getDetailpage(wholeUrl) 75 | # print("detail", detail) 76 | yield noteTitle, detail 77 | 78 | 79 | def getDetailpage(detailUrl): 80 | html = requests.get(url=detailUrl,headers=headers) 81 | html.encoding = 'utf-8' 82 | content = etree.HTML(html.text) 83 | notes = content.xpath('/html/body/div[2]/div/div[2]/div[4]/div')[0] 84 | stringResult = '' 85 | for content in notes: 86 | if content.tag == 'div': # 表 87 | tabletitle = content.xpath('table/tbody/tr[1]/td') # 表格头 88 | # 注意map被使用过(list)一次后就无效了, print(结果为空) 89 | tabletitleList = map(lambda x: x.xpath('string(.)'), tabletitle) 90 | tablehead = '|' + '|'.join(tabletitleList) + '|' # 效果为|序号|学号|姓名|性别|学院|专业| 91 | tableover = '|' + ':---:|' * len(tabletitle) # 居中显示 92 | tablecontent = '' 93 | trs = content.xpath('table/tbody/tr') 94 | for tr in trs[1:]: #多行row 95 | tds = map(lambda x: x.xpath('string(.)'), tr) # 一行内容 96 | tablecontent += '|' + '|'.join(tds) + '|' + '\n' 97 | tableinfo = tablehead + '\n' + tableover + '\n' + tablecontent 98 | stringResult += '\n' + tableinfo + '\n'# 将表格分离开一点 99 | elif content.tag == 'p': 100 | Pcontent = content.xpath('string(.)') 101 | if re.match('["一二三四五六七八九十"]+、', Pcontent): 102 | stringResult += "####" + Pcontent + '\n\n' 103 | elif re.match('\d+.', Pcontent): 104 | stringResult += "#####" + Pcontent + '\n\n' 105 | else: 106 | stringResult += content.xpath('string(.)') + '\n\n' 107 | else: 108 | print("未知格式") 109 | 110 | return stringResult 111 | 112 | 113 | if __name__ == "__main__": 114 | keyList = { 115 | 'cl':'SCU35113Te369cebc21f6e483c03fffc400c4c5c05bdad63995c32', 116 | } 117 | 118 | for info in analyse(): 119 | title, detail = info 120 | print(info) 121 | data_info = { 122 | 'text': '【教务信息】' + title, 123 | 'desp': detail 124 | } 125 | # print("发送的是:",title) 126 | for secret_key in keyList.values(): 127 | submit_info(secret_key, data_info) 128 | print("发送成功") 129 | -------------------------------------------------------------------------------- /soft_web_scrapy/ZJU_tuimian.py: -------------------------------------------------------------------------------- 1 | import re 2 | import time 3 | 4 | import requests 5 | from lxml import etree 6 | 7 | headers={ 8 | 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36', 9 | } 10 | 11 | _time = time.strftime("%Y-%m-%d", time.localtime()) 12 | # _time = "2020-09-22" 13 | 14 | 15 | def submit_info(secret_key,info): 16 | requests.post(url='https://sc.ftqq.com/{}.send'.format(secret_key),data=info) 17 | 18 | cookies = { 19 | 'td_cookie': '36258965', 20 | 'Hm_lvt_fe30bbc1ee45421ec1679d1b8d8f8453': '1599619275', 21 | 'JSESSIONID': '5A6BFFC2B238F873EA2E7360308592D9', 22 | } 23 | 24 | headers = { 25 | 'Connection': 'keep-alive', 26 | 'Pragma': 'no-cache', 27 | 'Cache-Control': 'no-cache', 28 | 'Upgrade-Insecure-Requests': '1', 29 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36', 30 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 31 | 'Referer': 'http://www.cst.zju.edu.cn/32178/list2.htm', 32 | 'Accept-Encoding': 'gzip, deflate', 33 | 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 34 | } 35 | 36 | response = requests.get('http://www.cst.zju.edu.cn/32178/list1.htm', headers=headers, cookies=cookies) 37 | response.encoding = 'utf8' 38 | html = etree.HTML(response.text) 39 | 40 | PREFIX_URL = "http://www.cst.zju.edu.cn/" 41 | 42 | 43 | def getNews(): 44 | NotesList = html.xpath('//*[@id="wp_news_w15"]/div/ul/li') 45 | # print("getNews", len(NotesList)) 46 | 47 | noteTitleList = [] 48 | wholeUrlList = [] 49 | announceDateList = [] 50 | 51 | for i, note in enumerate(NotesList): 52 | announceDate = note.xpath('span[2]/text()')[0] 53 | # print(announceDate) 54 | if announceDate == _time: 55 | # print(announceDate) 56 | announceDateList.append(announceDate) 57 | 58 | noteTitle = note.xpath('span[1]/a/text()')[0] 59 | # print(noteTitle) 60 | noteTitleList.append(noteTitle) 61 | suffixUrl = note.xpath('span[1]/a/@href')[0] 62 | wholeUrl = PREFIX_URL + suffixUrl 63 | # print(wholeUrl) 64 | wholeUrlList.append(wholeUrl) 65 | return zip(noteTitleList, wholeUrlList, announceDateList) 66 | 67 | 68 | 69 | def analyse(): 70 | content = getNews() 71 | for noteTitle, wholeUrl, announceDate in content: 72 | print(noteTitle, wholeUrl, announceDate) 73 | if wholeUrl.endswith('.htm'): 74 | detail = getDetailpage(wholeUrl) 75 | print("detail", detail) 76 | yield noteTitle, detail 77 | 78 | 79 | def getDetailpage(detailUrl): 80 | html = requests.get(url=detailUrl,headers=headers) 81 | html.encoding = 'utf-8' 82 | content = etree.HTML(html.text) 83 | notes = content.xpath('/html/body/div[2]/div/div[2]/div[4]/div')[0] 84 | stringResult = '' 85 | for content in notes: 86 | if content.tag == 'div': # 表 87 | tabletitle = content.xpath('table/tbody/tr[1]/td') # 表格头 88 | # print(tabletitle) 89 | # 注意map被使用过(list)一次后就无效了, print(结果为空) 90 | tabletitleList = map(lambda x: x.xpath('string(.)'), tabletitle) 91 | tablehead = '|' + '|'.join(tabletitleList) + '|' # 效果为|序号|学号|姓名|性别|学院|专业| 92 | tableover = '|' + ':---:|' * len(tabletitle) # 居中显示 93 | tablecontent = '' 94 | trs = content.xpath('table/tbody/tr') 95 | for tr in trs[1:]: #多行row 96 | tds = map(lambda x: x.xpath('string(.)'), tr) # 一行内容 97 | tablecontent += '|' + '|'.join(tds) + '|' + '\n' 98 | tableinfo = tablehead + '\n' + tableover + '\n' + tablecontent 99 | stringResult += '\n' + tableinfo + '\n'# 将表格分离开一点 100 | elif content.tag == 'p': 101 | Pcontent = content.xpath('string(.)') 102 | if re.match('["一二三四五六七八九十"]+、', Pcontent): 103 | stringResult += "####" + Pcontent + '\n\n' 104 | elif re.match('\d+.', Pcontent): 105 | stringResult += "#####" + Pcontent + '\n\n' 106 | else: 107 | stringResult += content.xpath('string(.)') + '\n\n' 108 | else: 109 | print("未知格式") 110 | return stringResult 111 | 112 | 113 | if __name__ == "__main__": 114 | keyList = { 115 | 'cl':'SCU35113Te369cebc21f6e483c03fffc400c4c5c05bdad63995c32', 116 | } 117 | 118 | for info in analyse(): 119 | title, detail = info 120 | print(info) 121 | data_info = { 122 | 'text': '【招生信息】' + title, 123 | 'desp': detail 124 | } 125 | # print("发送的是:", title) 126 | for secret_key in keyList.values(): 127 | submit_info(secret_key, data_info) 128 | print("发送成功") 129 | --------------------------------------------------------------------------------