├── 1. 蓝码生成器 ├── blue_horse_generator.py ├── horse.png └── real_blue_horse_generator.py ├── 2. 年度信息化账单 └── it2020.py ├── 3. 健康打卡 └── daka.py ├── 4. CC98账号管理 └── cc98_account.py ├── 5. 就业网隐藏信息查询 ├── README.md ├── career_data_query.py └── images │ ├── bynf.png │ ├── copy_curl.png │ ├── example.png │ └── 不想回家.md ├── 6. 校园卡流水查询 ├── README.md ├── ecard_flow.py └── images │ ├── found.png │ ├── result.png │ └── search.png ├── 7. 正版软件下载 └── software_download.py ├── 8. CC98拼车信息批量汇总 ├── README.md └── pinche.py ├── 9. NexusHD 批量赠送魔力值 └── batch_transfer.py ├── LICENSE ├── README.md ├── config.json ├── qrcode.png ├── requirements.txt └── zjuam ├── __init__.py └── zjuam.py /1. 蓝码生成器/blue_horse_generator.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import json 4 | import requests 5 | import qrcode 6 | from PIL import Image 7 | import getpass 8 | import sys 9 | sys.path.append("..") 10 | from zjuam import ZJUAccount 11 | 12 | 13 | def generate_blue_horse(sess): 14 | """ 15 | 蓝码生成函数 16 | :param sess: 登录浙大通行证后的 session 17 | :return: 一个二维码 Image 对象 18 | """ 19 | resp = sess.get('http://one.zju.edu.cn/pass_code/zx') 20 | horse_code = re.search(r"text: \'(.*?)\'", resp.text).group(1) 21 | 22 | # 初始化二维码对象 23 | qr = qrcode.QRCode( 24 | version=5, 25 | error_correction=qrcode.constants.ERROR_CORRECT_L, 26 | box_size=5, 27 | border=0, 28 | ) 29 | qr.add_data(horse_code) 30 | qr.make(fit=True) 31 | 32 | # 生成二维码 33 | image = qr.make_image(fill_color="#1E90FF", back_color="white") 34 | return image 35 | 36 | if __name__ == '__main__': 37 | configs = json.loads(open('../config.json', 'r').read()) 38 | username = configs["username"] 39 | password = configs["password"] 40 | if not (username and password): 41 | print('未能获取用户名和密码,请手动输入!') 42 | username = input("👤 浙大统一认证用户名: ") 43 | password = getpass.getpass('🔑 浙大统一认证密码: ') 44 | 45 | zju = ZJUAccount(username, password) 46 | sess = zju.login() 47 | blue_horse = generate_blue_horse(sess) 48 | blue_horse.show() -------------------------------------------------------------------------------- /1. 蓝码生成器/horse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrazierLei/ZJU-toolkit/a98d2a2df168b93531c32c5bd57917c194421a00/1. 蓝码生成器/horse.png -------------------------------------------------------------------------------- /1. 蓝码生成器/real_blue_horse_generator.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import json 4 | import requests 5 | from MyQR import myqr 6 | from PIL import Image 7 | import getpass 8 | import sys 9 | sys.path.append("..") 10 | from zjuam import ZJUAccount 11 | 12 | 13 | def generate_blue_horse(sess): 14 | """ 15 | 蓝码生成函数 16 | :param sess: 登录浙大通行证后的 session 17 | :return: 一个二维码 Image 对象 18 | """ 19 | resp = sess.get('http://one.zju.edu.cn/pass_code/zx') 20 | horse_code = re.search(r"text: \'(.*?)\'", resp.text).group(1) 21 | 22 | myqr.run( 23 | words=horse_code, 24 | version=1, 25 | level='L', 26 | picture='./horse.png', 27 | colorized=True, 28 | contrast=1.0, 29 | brightness=1.0, 30 | save_name=None, 31 | save_dir=os.getcwd() 32 | ) 33 | 34 | image = Image.open('./horse_qrcode.png') 35 | return image 36 | 37 | if __name__ == '__main__': 38 | configs = json.loads(open('../config.json', 'r').read()) 39 | username = configs["username"] 40 | password = configs["password"] 41 | if not (username and password): 42 | print('未能获取用户名和密码,请手动输入!') 43 | username = input("👤 浙大统一认证用户名: ") 44 | password = getpass.getpass('🔑 浙大统一认证密码: ') 45 | 46 | zju = ZJUAccount(username, password) 47 | sess = zju.login() 48 | blue_horse = generate_blue_horse(sess) 49 | blue_horse.show() -------------------------------------------------------------------------------- /2. 年度信息化账单/it2020.py: -------------------------------------------------------------------------------- 1 | import os 2 | import getpass 3 | import json 4 | import sys 5 | sys.path.append("..") 6 | from zjuam import ZJUAccount 7 | from pprint import pprint 8 | 9 | 10 | def get_ticket(sess): 11 | """ 12 | 获取 ticket 参数 13 | :param sess: 登录浙大通行证后的 session 14 | :return: ticket 的值 15 | """ 16 | resp = sess.get('https://zjuam.zju.edu.cn/cas/oauth2.0/authorize?redirect_uri=https%3A%2F%2Fit2020.zju.edu.cn&response_type=code&client_id=PY8yOkVI3QgFerx2cK') 17 | # for r in resp.history: 18 | # print(r.status_code, r.url) 19 | 20 | ticket = resp.history[2].url.split('ticket=')[-1] 21 | return ticket 22 | 23 | 24 | if __name__ == '__main__': 25 | configs = json.loads(open('../config.json', 'r').read()) 26 | username = configs["username"] 27 | password = configs["password"] 28 | if not (username and password): 29 | print('未能获取用户名和密码,请手动输入!') 30 | username = input("👤 浙大统一认证用户名: ") 31 | password = getpass.getpass('🔑 浙大统一认证密码: ') 32 | 33 | zju = ZJUAccount(username, password) 34 | sess = zju.login() 35 | 36 | params = { 37 | 'code': get_ticket(sess), 38 | 'redirect_uri': 'https://it2020.zju.edu.cn', 39 | } 40 | resp = sess.post('https://it2020.zju.edu.cn/api/data', 41 | headers={'X': 'X'}, 42 | params=params) 43 | 44 | pprint(resp.json()) 45 | -------------------------------------------------------------------------------- /3. 健康打卡/daka.py: -------------------------------------------------------------------------------- 1 | # daka 函数改写自 https://github.com/Tishacy/ZJU-nCov-Hitcarder/blob/master/daka.py,该脚本可以实现定点打卡 2 | 3 | import os 4 | import requests 5 | import re 6 | import json 7 | import time 8 | import datetime 9 | import getpass 10 | import sys 11 | sys.path.append("..") 12 | from zjuam import ZJUAccount 13 | 14 | 15 | def daka(sess): 16 | """ 17 | 获取个人信息,并完成打卡 18 | :param sess: 登录浙大通行证后的 session 19 | :return: 提交打卡表单后的响应 20 | """ 21 | base_url = "https://healthreport.zju.edu.cn/ncov/wap/default/index" 22 | save_url = "https://healthreport.zju.edu.cn/ncov/wap/default/save" 23 | today = datetime.date.today() 24 | 25 | resp = sess.get(base_url) 26 | old_infos = re.findall(r'oldInfo: ({[^\n]+})', resp.text) 27 | if len(old_infos) != 0: 28 | old_info = json.loads(old_infos[0]) 29 | else: 30 | return "未发现缓存信息,请先至少手动成功打卡一次再运行脚本。" 31 | 32 | new_info_tmp = json.loads(re.findall(r'def = ({[^\n]+})', resp.text)[0]) 33 | new_id = new_info_tmp['id'] 34 | name = re.findall(r'realname: "([^\"]+)",', resp.text)[0] 35 | number = re.findall(r"number: '([^\']+)',", resp.text)[0] 36 | 37 | info = old_info.copy() 38 | info['id'] = new_id 39 | info['name'] = name 40 | info['number'] = number 41 | info["date"] = "%4d%02d%02d" % (today.year, today.month, today.day) 42 | info["created"] = round(time.time()) 43 | info['jrdqtlqk[]'] = 0 44 | info['jrdqjcqk[]'] = 0 45 | info['sfsqhzjkk'] = 1 # 是否申领杭州健康码 46 | info['sqhzjkkys'] = 1 # 杭州健康吗颜色,1:绿色 2:红色 3:黄色 47 | info['sfqrxxss'] = 1 # 是否确认信息属实 48 | info['jcqzrq'] = "" 49 | info['gwszdd'] = "" 50 | info['szgjcs'] = "" 51 | 52 | print(info['name'], end=' ') 53 | resp = sess.post(save_url, data=info) 54 | return resp.json()['m'] 55 | 56 | 57 | 58 | if __name__ == "__main__": 59 | configs = json.loads(open('../config.json', 'r').read()) 60 | username = configs["username"] 61 | password = configs["password"] 62 | if not (username and password): 63 | print('未能获取用户名和密码,请手动输入!') 64 | username = input("👤 浙大统一认证用户名: ") 65 | password = getpass.getpass('🔑 浙大统一认证密码: ') 66 | 67 | zju = ZJUAccount(username, password) 68 | sess = zju.login() 69 | message = daka(sess) 70 | print(message) -------------------------------------------------------------------------------- /4. CC98账号管理/cc98_account.py: -------------------------------------------------------------------------------- 1 | import os 2 | import requests 3 | from bs4 import BeautifulSoup 4 | import getpass 5 | import json 6 | from prettytable import PrettyTable 7 | import sys 8 | sys.path.append("..") 9 | from zjuam import ZJUAccount 10 | import colorama # 防止 windows 命令行打印带颜色字符串失败 11 | colorama.init(autoreset=True) 12 | 13 | def get_cc98_info(sess): 14 | """ 15 | 获取该通行证下所有CC98账号的基本信息 16 | :param sess: 登录浙大通行证后的 session 17 | :return: 包含所需数据的 table 对象 18 | """ 19 | sess.get('https://account.cc98.org/LogOn?returnUrl=%2F') 20 | resp = sess.get('https://account.cc98.org/My') 21 | bs = BeautifulSoup(resp.text, 'html.parser') 22 | accounts = bs.find_all('h4') 23 | print(f'您共有 \033[1;31m{len(accounts)}\033[0m 个 CC98 账号,主要数据如下:') 24 | 25 | username_list = [] 26 | post_count_list = [] 27 | wealth_list = [] 28 | register_time_list = [] 29 | fan_count_list = [] 30 | popularity_list = [] 31 | like_count_list = [] 32 | 33 | for i in accounts: 34 | username = i.span.text 35 | resp = requests.get(f'https://api.cc98.org/user/name/{username}') 36 | resp_json = resp.json() 37 | post_count = resp_json['postCount'] 38 | wealth = resp_json['wealth'] 39 | register_time = resp_json['registerTime'].replace('T', ' ') 40 | fan_count = resp_json['fanCount'] 41 | popularity = resp_json['popularity'] 42 | like_count = resp_json['receivedLikeCount'] 43 | 44 | username_list.append(username) 45 | post_count_list.append(post_count) 46 | wealth_list.append(wealth) 47 | register_time_list.append(register_time) 48 | fan_count_list.append(fan_count) 49 | popularity_list.append(popularity) 50 | like_count_list.append(like_count) 51 | 52 | table = PrettyTable() 53 | table.add_column('\033[33m用户名\033[0m', username_list) 54 | table.add_column('\033[33m注册时间\033[0m', register_time_list) 55 | table.add_column('\033[33m发帖数\033[0m', post_count_list) 56 | table.add_column('\033[33m财富值\033[0m', wealth_list) 57 | table.add_column('\033[33m粉丝数\033[0m', fan_count_list) 58 | table.add_column('\033[33m收到的赞\033[0m', like_count_list) 59 | table.add_column('\033[33m风评\033[0m', popularity_list) 60 | 61 | return table 62 | 63 | 64 | if __name__ == '__main__': 65 | configs = json.loads(open('../config.json', 'r').read()) 66 | username = configs["username"] 67 | password = configs["password"] 68 | if not (username and password): 69 | print('未能获取用户名和密码,请手动输入!') 70 | username = input("👤 浙大统一认证用户名: ") 71 | password = getpass.getpass('🔑 浙大统一认证密码: ') 72 | 73 | zju = ZJUAccount(username, password) 74 | sess = zju.login() 75 | table = get_cc98_info(sess) 76 | print(table) -------------------------------------------------------------------------------- /5. 就业网隐藏信息查询/README.md: -------------------------------------------------------------------------------- 1 | ## 浙大的就业信息网是浙大众多垃圾网站中最的最垃圾的一个。 2 | 3 | 4 | 5 | 2021年了,才把2020年的数据放出来,2021年的数据更是只有一点点,而且前端还对年份做了限制。下面写一下获得2021年少得可怜的数据的方法,用Python是因为我对JS知之甚少。 6 | 7 | 1. 先随便设置一个年份,然后将其他筛选条件选好,打开**开发者工具**,复制cURL 8 | 9 | ![](./images/copy_curl.png) 10 | 11 | 12 | 13 | 2. 然后将复制的内容粘贴到 [curlconverter](https://curl.trillworks.com/) 中转化为Python代码,把`data`复制下来。(`headers`和`cookies`都不是必须的,这更说明就业网做的有多垃圾) 14 | 15 | 为了防止内网信息泄漏,请从转化出的Python代码中获取目标 url 16 | 17 | 3. 将`data`中的bynf改为2021 18 | 19 | ![](./images/bynf.png) 20 | 21 | 4. 运行程序,下图是以2021年电气硕士为例的运行结果。 22 | 23 | ![](./images/example.png) 24 | 25 | 26 | 27 | PS:如果希望联系到具体在某个公司就业的同学,可以在开发者工具中查询他们的邮箱。为了防止个人信息泄漏,这里也不展开描述了。 -------------------------------------------------------------------------------- /5. 就业网隐藏信息查询/career_data_query.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from prettytable import PrettyTable 3 | 4 | 5 | # 为了防止内网信息泄漏,请从 curl 的转化结果中获取 url 6 | url = '' 7 | 8 | data = { 9 | 'send_by_bootstrap_table': 'true', 10 | 'searchModel.inputType': '{"dwmc":[""]}', 11 | 'searchModel.selectType': '{}', 12 | 'searchModel.dateType': '{}', 13 | 'searchModel.numberType': '{}', 14 | 'searchModel.inputSqlType': '0', 15 | 'bynf': '2021', 16 | 'xydm': '1000', 17 | 'xlccdm': '11', 18 | 'queryModel.showCount': '15', 19 | 'queryModel.currentPage': '1' 20 | } 21 | 22 | resp = requests.post(url, data=data) 23 | 24 | company_name = [] 25 | student_num = [] 26 | page_num = resp.json()['totalPage'] 27 | 28 | # 保存数据 29 | for page in range(1, page_num+1): 30 | data.update({'queryModel.currentPage': str(page)}) 31 | resp = requests.post(url, data=data) 32 | for item in resp.json()['items']: 33 | company_name.append(item['dwmc']) 34 | student_num.append(item['num']) 35 | 36 | 37 | table = PrettyTable() 38 | table.add_column('\033[33m单位名称\033[0m', company_name) 39 | table.add_column('\033[33m就业人数\033[0m', student_num) 40 | 41 | print(table) -------------------------------------------------------------------------------- /5. 就业网隐藏信息查询/images/bynf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrazierLei/ZJU-toolkit/a98d2a2df168b93531c32c5bd57917c194421a00/5. 就业网隐藏信息查询/images/bynf.png -------------------------------------------------------------------------------- /5. 就业网隐藏信息查询/images/copy_curl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrazierLei/ZJU-toolkit/a98d2a2df168b93531c32c5bd57917c194421a00/5. 就业网隐藏信息查询/images/copy_curl.png -------------------------------------------------------------------------------- /5. 就业网隐藏信息查询/images/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrazierLei/ZJU-toolkit/a98d2a2df168b93531c32c5bd57917c194421a00/5. 就业网隐藏信息查询/images/example.png -------------------------------------------------------------------------------- /5. 就业网隐藏信息查询/images/不想回家.md: -------------------------------------------------------------------------------- 1 | 不想回家 2 | 3 | 很羡慕那些盼望回家但是回不了的同学们,在我看来他们这种烦恼都是幸福的,因为我真的不想回家。 4 | 5 | 原因也简单,不想回去见到父母。俩人离婚离的早,也都没有再找,回去了我还得两头跑。 6 | 7 | 我母亲最大的乐趣就是push我,说咱们家里穷,你要努力,以后你只能靠自己。我早已默认这个事实,从来没怨过谁,投胎是个技术活,我自己菜那也没办法。但是把这种话挂在嘴边真的很烦,就像天天在一个还能活三个月的癌症病人耳朵边给他倒计时一样——你今天还能活2个月29天,还剩2个月28天了呦,还有2个月27天你就死了。艹。 8 | 9 | 我父亲早些年得过精神分裂,这种病根本就不可能痊愈,时至今日他有时候也会神神叨叨、疯疯癫癫,结结巴巴。自小学起他就开始臆想出我因为父母离异而感到极度痛苦和自卑,然后觉得我心理有问题。尽管我确实觉得父母离异对小孩子的成长不是好事,但我早就接受这个事实了。不管我重复多少次我心理没任何问题,他都会不厌其烦的骚扰我的老师和周围同学,问他们我是不是心理有问题。很多老师带了这么多年学生,什么家长没见过,了解情况以后也就不以为意了,但是弄到我同学那里我就会觉得很没有面子。 10 | 11 | 艹。 12 | 13 | 我竭尽全力像一个普通人、正常人一样活着。但是他们不断在我耳边提醒我,你不正常、你心理比较全,你生来就比人家低人一等。 14 | 15 | 艹。 16 | 17 | 大部分时候我是一个比较孤僻的人,我不喜欢参加人数较多的社交活动,也不喜欢嘈杂的环境。安静能让我感受到放松,很多时候,遇到再心烦的事,安静一会就能好很多。但是我比较喜欢小孩子,偶尔也带带家教,我感觉和小孩子交流比和成年人交流要轻松愉快的多,尤其是建立起信任以后。相比老师的身份,我更喜欢作为一个朋友。熟悉以后我都会让他们叫我名字或者叫”x哥“。 18 | 19 | 我幻想之后我也有一个孩子,ta愿意经常和我一起玩,和我分享ta在学校遇到的开心事、烦心事,我去接ta的时候ta可以骄傲得和周围小伙伴们介绍——这是我爸爸!这是我不曾体会过的。 20 | 21 | 但是我终究还是会回去的,因为我想见我奶奶。我爷爷去世以后,她便一个人住了。她膝盖做过手术,所以虽然有电梯,她下一趟楼也是很费劲的。之前我堂哥还在国内的时候,每年过年她还会做顿饭,虽然姑姑一家子在国外,但是也还算有点年味。现在我父亲和我叔叔身体都不好,堂哥也出国上学了,奶奶也就不做饭了。这样也好,轻松一点,少点操劳。 22 | 23 | 我已经能想象到回去以后到景象。也罢,谁叫咱菜呢? 24 | 25 | 别人当团圆,我只当吃饭。 -------------------------------------------------------------------------------- /6. 校园卡流水查询/README.md: -------------------------------------------------------------------------------- 1 | # 校园卡流水查询 2 | 3 | 根据提示输入开始时间和结束时间。 4 | 5 | ![](./images/result.png) 6 | 7 | `ssoticketid`这个参数找了我半天,一开始以为是在一个请求的响应的set-cookie里,结果比了一下发现两个的值不一样。最后发现原来藏在一个302的响应里,探索过程还挺曲折的。 8 | 9 | 10 | 11 | ![](./images/search.png) 12 | 13 | ![](./images/found.png) -------------------------------------------------------------------------------- /6. 校园卡流水查询/ecard_flow.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import re 4 | import getpass 5 | from prettytable import PrettyTable 6 | import sys 7 | sys.path.append("..") 8 | from zjuam import ZJUAccount 9 | import colorama # 防止 windows 命令行打印带颜色字符串失败 10 | colorama.init(autoreset=True) 11 | 12 | def get_flow(sess, start_date, end_date): 13 | """ 14 | 查阅指定时间段的消费流水 15 | :param sess: 登录浙大通行证后的 session 16 | :param start_date: 开始时间,格式为2020-01-01 17 | :param end_date: 结束时间,格式为2020-01-01 18 | :return: 包含所需数据的 table 对象 19 | """ 20 | # 获取 ssoticketid 21 | resp = sess.get('http://ecardsso.zju.edu.cn/ias/prelogin?sysid=FWDT') 22 | ssoticketid = re.search('id="ssoticketid" value="(.*?)"', resp.text).group(1) 23 | 24 | # resp 中包含了需要的cookie: hallticket 25 | data = { 26 | 'errorcode': '1', 27 | 'continueurl': 'http://ecardhall.zju.edu.cn:808/cassyno/index', 28 | 'ssoticketid': ssoticketid 29 | } 30 | sess.post('http://ecardhall.zju.edu.cn:808/cassyno/index', data=data) 31 | 32 | # 查询对应的卡号 33 | resp = sess.post('http://ecardhall.zju.edu.cn:808/User/GetCardInfoByAccountNoParm', data={'json': 'true'}) 34 | account = json.loads(resp.json()['Msg'])['query_card']['card'][0]['account'] 35 | 36 | # 获取流水信息,这里只获取了第一页,可以按照需要修改 37 | data = { 38 | 'sdate': start_date, 39 | 'edate': end_date, 40 | 'account': account, 41 | 'page': '1', 42 | 'rows': '100' 43 | } 44 | resp = sess.post('http://ecardhall.zju.edu.cn:808/Report/GetPersonTrjn', data=data) 45 | 46 | # 将重点数据储存在列表中 47 | time = [] 48 | location = [] 49 | amount = [] 50 | balace = [] 51 | 52 | for i in resp.json()['rows']: 53 | time.append(i['OCCTIME']) 54 | location.append(i['MERCNAME']) 55 | amount.append(i['TRANAMT']) 56 | balace.append(i['ZMONEY']) 57 | 58 | # 初始化 table,再按列填入数据 59 | table = PrettyTable() 60 | table.add_column('\033[33m交易时间\033[0m', time) 61 | table.add_column('\033[33m交易地点\033[0m', location) 62 | table.add_column('\033[33m交易金额\033[0m', amount) 63 | table.add_column('\033[33m余额\033[0m', balace) 64 | 65 | return table 66 | 67 | if __name__ == '__main__': 68 | configs = json.loads(open('../config.json', 'r').read()) 69 | username = configs["username"] 70 | password = configs["password"] 71 | if not (username and password): 72 | print('未能获取用户名和密码,请手动输入!') 73 | username = input("👤 浙大统一认证用户名: ") 74 | password = getpass.getpass('🔑 浙大统一认证密码: ') 75 | 76 | zju = ZJUAccount(username, password) 77 | sess = zju.login() 78 | 79 | start_date = input('请输入开始时间,格式为\033[33m2020-01-01\033[0m:\n') 80 | end_date = input('请输入结束时间,格式为\033[33m2020-01-01\033[0m:\n') 81 | table = get_flow(sess, start_date=start_date, end_date=end_date) 82 | print(table) -------------------------------------------------------------------------------- /6. 校园卡流水查询/images/found.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrazierLei/ZJU-toolkit/a98d2a2df168b93531c32c5bd57917c194421a00/6. 校园卡流水查询/images/found.png -------------------------------------------------------------------------------- /6. 校园卡流水查询/images/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrazierLei/ZJU-toolkit/a98d2a2df168b93531c32c5bd57917c194421a00/6. 校园卡流水查询/images/result.png -------------------------------------------------------------------------------- /6. 校园卡流水查询/images/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrazierLei/ZJU-toolkit/a98d2a2df168b93531c32c5bd57917c194421a00/6. 校园卡流水查询/images/search.png -------------------------------------------------------------------------------- /7. 正版软件下载/software_download.py: -------------------------------------------------------------------------------- 1 | import json 2 | import re 3 | import getpass 4 | import requests 5 | from bs4 import BeautifulSoup 6 | from prettytable import PrettyTable 7 | import sys 8 | sys.path.append("..") 9 | from zjuam import ZJUAccount 10 | import colorama # 防止 windows 命令行打印带颜色字符串失败 11 | colorama.init(autoreset=True) 12 | 13 | 14 | if __name__ == '__main__': 15 | configs = json.loads(open('../config.json', 'r').read()) 16 | username = configs["username"] 17 | password = configs["password"] 18 | if not (username and password): 19 | print('未能获取用户名和密码,请手动输入!') 20 | username = input("👤 浙大统一认证用户名: ") 21 | password = getpass.getpass('🔑 浙大统一认证密码: ') 22 | 23 | zju = ZJUAccount(username, password) 24 | sess = zju.login() 25 | 26 | # 授权给软件中心 27 | sess.get('https://zjuam.zju.edu.cn/cas/oauth2.0/authorize?response_type=code&client_id=yRndoY4MQsFLAq0Md6&redirect_uri=http://user.ms.zju.edu.cn/login') 28 | 29 | software_type = input('请输入想要下载的软件类型序号\n\033[33m1. Microsoft 软件下载\ 30 | \033[0m\n\033[33m2. Adobe 软件下载\033[0m \n\033[33m3. MATLAB 软件下载\033[0m\n') 31 | 32 | assert int(software_type) in [1, 2, 3], '请输入正确的软件类型' 33 | 34 | if software_type == '1': 35 | software = 'microsoft' 36 | elif software_type == '2': 37 | software = 'adobe' 38 | elif software_type == '3': 39 | software = 'matlab' 40 | 41 | print(f'正在获取{software}下载链接,请耐心等待。') 42 | # 初始化 list 43 | name_list = [] 44 | desc_list = [] 45 | url_list = [] 46 | download_url = 'http://ms.zju.edu.cn/download/file' 47 | 48 | resp = sess.get(f'http://ms.zju.edu.cn/{software}/download.html') 49 | bs = BeautifulSoup(resp.text, 'html.parser') 50 | divs = bs.find_all('div', class_=re.compile('product.*?') if software_type == '1' else 'adobeinfo') 51 | 52 | # 提取数据 53 | if software_type == '1': 54 | for div in divs[2:] : 55 | name_list.append(div.h2.text) 56 | desc_list.append(div.li.text.replace('\n', '')) 57 | 58 | # 直接下载 59 | url = div.find('a')['href'] 60 | if url == 'javascript:;': 61 | links = div.ul.find_all('a') 62 | multi_url = '' 63 | for link in links: 64 | version = link.text.replace('下载', '') 65 | multi_url += f'[\033[1;31m{version}\033[0m]' + link['href'] + '\n' 66 | url_list.append(multi_url.rstrip('\n')) 67 | 68 | # 需要再次获取下载链接 69 | else: 70 | url = 'http://ms.zju.edu.cn' + url 71 | resp = sess.get(url) 72 | bs = BeautifulSoup(resp.text, 'html.parser') 73 | name = bs.find('input')['value'] 74 | options = bs.find_all('option') 75 | 76 | # 有的软件有多个版本 77 | multi_url = '' 78 | for option in options: 79 | bit = option['value'] 80 | version = option.text.replace('本站下载', '') 81 | if not version: 82 | version = '通用版本' 83 | data = { 84 | 'name': name, 85 | 'bit': str(bit) 86 | } 87 | redirect_url = sess.post(download_url, data=data, allow_redirects=False).headers['Location'] 88 | multi_url += f'[\033[1;31m{version}\033[0m]' + redirect_url + '\n' 89 | 90 | url_list.append(multi_url.rstrip('\n')) 91 | else: 92 | for div in divs : 93 | name_list.append(div.h2.text + ' ' + div.a.text.replace('下载', '')) 94 | desc_list.append(div.p.text) 95 | url_list.append(sess.get(div.a['href'], allow_redirects=False).headers['Location']) 96 | 97 | table = PrettyTable() 98 | table.add_column('\033[33m软件名称\033[0m', name_list) 99 | table.add_column('\033[33m软件描述\033[0m', desc_list) 100 | table.add_column('\033[33m下载地址\033[0m', url_list) 101 | print(table) -------------------------------------------------------------------------------- /8. CC98拼车信息批量汇总/README.md: -------------------------------------------------------------------------------- 1 | # CC98拼车专楼自动汇总程序 2 | 3 | ## 目的 4 | 5 | 减少版主手动汇总信息的工作量。 6 | 7 | 8 | 9 | ## 格式 10 | 11 | 要求每层按照固定的格式回复: 12 | 13 | ``` 14 | 日期:(如1月27日) 15 | 出发时间:(如下午3点15分) 16 | 出发地点:(如紫金港校区东2门/玉泉校区新桥门) 17 | 目的地:(如杭州东站) 18 | 联系方式:(电话、qq、微信号三选一) 19 | 备注:(选填) 20 | ``` 21 | 22 | 23 | 24 | ## 环境需求 25 | 26 | - Python 3.6 或更新版本 27 | 28 | 因为字符串部分用到了 `f-string` 语法 29 | 30 | - Reuqests 模块 31 | 32 | 通过 `pip install requests` 安装 33 | 34 | 35 | 36 | ## 使用方法 37 | 38 | ### 手动调整几个参数 39 | 40 | - 首先创建一个 CC98 对象: 41 | 42 | `CC98(username=, password=, special_topic_id=, start_floor=)` 43 | 44 | 其中: 45 | 46 | - `username`、`password`:98用户名和密码。如果帖子是在内部板块,则需要版主以上权限的账号。如果是一般版面,则可以是普通账号。 47 | - `special_topic_id`:拼车专楼的 `topic_id` ,例如5026129 48 | - `start_floor`:拼车专楼中开始统计的楼层(如果从3楼开始,则输入2,以此类推) 49 | 50 | - 然后生成 ubb 语法的表格代码: 51 | 52 | `table = cc98.make_table(check_last_table=False)` 中 `check_last_table` 表示是否检查上次保存的信息,如果为 `False` 则从头开始获取每层楼的内容,如果为 `True` 则从上一次获取的最后一层楼的下一层开始。 53 | 54 | 55 | 56 | ### 运行程序 57 | 58 | ```shell 59 | $ python pinche.py 60 | ``` 61 | 62 | 控制台中会打印出生成的表格,复制编辑到汇总楼中。 63 | 64 | 当前路径下会生成一个 `last_table.txt` 文件,包含本次程序运行时的最新楼层和表格代码。 65 | 66 | -------------------------------------------------------------------------------- /8. CC98拼车信息批量汇总/pinche.py: -------------------------------------------------------------------------------- 1 | import os 2 | import math 3 | import re 4 | import requests 5 | import time 6 | 7 | 8 | class CC98: 9 | def __init__(self, username, password, special_topic_id, start_floor): 10 | """ 11 | 初始化。 12 | :param username: 用户名 13 | :param password: 密码 14 | """ 15 | self.sess = requests.session() 16 | self.username = username 17 | self.password = password 18 | self.token = "" 19 | self.expiretime = -1 20 | self.special_topic_id = special_topic_id 21 | self.start_floor = start_floor 22 | 23 | def login(self): 24 | """ 25 | 发起登录请求 以获取access_token 存入self.token 26 | 并将过期时间写入self.expiretime 27 | """ 28 | login_data = { 29 | 'client_id': '9a1fd200-8687-44b1-4c20-08d50a96e5cd', 30 | 'client_secret': '8b53f727-08e2-4509-8857-e34bf92b27f2', 31 | 'grant_type': 'password', 32 | 'username': self.username, 33 | 'password': self.password, 34 | 'scope': 'cc98-api openid offline_access' 35 | } 36 | resp = self.sess.post('https://openid.cc98.org/connect/token', 37 | data=login_data, 38 | headers={'content-type': 'application/x-www-form-urlencoded'}) 39 | 40 | data = resp.json() 41 | token = data['access_token'] 42 | expirein = data['expires_in'] 43 | self.token = token 44 | self.expiretime = int(time.time()) + expirein 45 | self.sess.headers.update({'authorization': 'Bearer ' + self.token}) 46 | print(f'{self.username} 登录成功。') 47 | 48 | def get_topic_post(self, topic_id, from_=0): 49 | """ 50 | 获取指定 id 帖子中从 from_ 开始的全部回复 51 | :param topic_id: 帖子的 id 52 | :param from_: 起始楼层 53 | :return: 键值对为 {floor: content} 的字典 54 | """ 55 | content_dict = {} 56 | while True: 57 | resp = self.sess.get(f'https://api.cc98.org/Topic/{topic_id}/post?from={from_}&size=20') 58 | resp_json = resp.json() 59 | if not resp_json: 60 | break 61 | for post in resp_json: 62 | content_dict.update({post['floor']: post['content']}) 63 | from_ += 20 64 | 65 | # 休息一下,防止触发反爬虫系统 66 | time.sleep(1) 67 | 68 | print(f'共获取到{len(content_dict)}条回复') 69 | return content_dict 70 | 71 | @staticmethod 72 | def parse_content(content): 73 | """ 74 | 解析获取得到的文本 75 | :param content: 获取到的文本 76 | :return: 解析后的结果,如果解析成功则返回包含关键信息的 ubb 代码,如果解析失败返回 None 77 | """ 78 | try: 79 | date = re.search("日期[::](.*)", content).group(1) 80 | time_ = re.search("出发时间[::](.*)", content).group(1) 81 | start = re.search("出发地点[::](.*)", content).group(1) 82 | end = re.search("目的地[::](.*)", content).group(1) 83 | contact = re.search("联系方式[::](.*)", content).group(1) 84 | return f'[tr][th]{date}[/th][th]{time_}[/th][th]{start}[/th][th]{end}[/th][th]{contact}[/th]' 85 | except: 86 | return None 87 | 88 | def make_table(self, check_last_table=True): 89 | """ 90 | 生成ubb语法的表格 91 | :param check_last_table: 是否检查上次保存的结果 92 | :return: ubb语法的表格 93 | """ 94 | # 检查上次的获取记录 95 | file_path = './last_table.txt' 96 | if check_last_table and os.path.exists(file_path): 97 | with open(file_path, 'r') as f: 98 | text = f.read().split('\n', 1) 99 | start = int(re.search("当前更新到第(.*)层", text[0]).group(1)) 100 | table = text[1] 101 | print(f'检测到有上次的获取记录,从第{start}层开始获取...') 102 | else: 103 | print('从头开始获取...') 104 | start = self.start_floor 105 | table = '[table]\n[tr][th]日期[/th][th]时间[/th][th]出发地[/th][th]目的地[/th][th]联系方式[/th][th]楼层[/th][/tr]\n' 106 | 107 | content_dict = self.get_topic_post(topic_id=self.special_topic_id, from_=start) 108 | 109 | # content_dict 非空,表示有新的楼层 110 | if content_dict: 111 | for floor, content in content_dict.items(): 112 | result = self.parse_content(content) 113 | if result: 114 | table += result + f'[th][url=/topic/{self.special_topic_id}/{math.ceil(floor/10)}#{floor%10}]>>戳我去楼层<<[/url][/th][/tr]\n' 115 | else: 116 | print(f'第{floor}层解析失败,请手动添加。') 117 | 118 | table += '[/table]' 119 | 120 | # 在本地文件中记录本次结果 121 | with open(file_path, 'w') as f: 122 | f.write(f'当前更新到第{floor}层。\n' + table) 123 | 124 | return table 125 | else: 126 | print('未发现新楼层。') 127 | 128 | 129 | if __name__ == '__main__': 130 | cc98 = CC98(username='', password='', special_topic_id=5026129, start_floor=2) 131 | cc98.login() 132 | table = cc98.make_table(check_last_table=False) 133 | print(table) -------------------------------------------------------------------------------- /9. NexusHD 批量赠送魔力值/batch_transfer.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | username = '' # 你的用户名 5 | password = '' # 你的密码 6 | login_url = 'http://www.nexushd.org/takelogin.php' 7 | exchange_url = 'http://www.nexushd.org/mybonus.php?action=exchange' 8 | gift = 1000 # 每个人的转账金额,大于25,小于10,000,000 9 | users = [] # 包含待转账的用户名的list,例如['feifeizaici', 'imbitch', 'hahahaha'] 10 | 11 | sess = requests.Session() 12 | sess.post(login_url, 13 | headers={'content-type': 'application/x-www-form-urlencoded'}, 14 | data=f'username={username}&password={password}') 15 | 16 | for user in users: 17 | data = { 18 | 'option': '7', 19 | 'username': user, 20 | 'bonusgift': str(gift), 21 | 'message': '有钱任性', 22 | 'submit': '赠送' 23 | } 24 | resp = sess.post(exchange_url, data=data) 25 | if '不存在该用户' in resp.text: 26 | print(f'不存在{user},请检查。') 27 | elif '你没有足够的魔力值' in resp.text: 28 | print('魔力值不足') 29 | else: 30 | print(f'给 {user} 转账 {gift} 成功') 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ZJU-toolkit 2 | 3 | 一些登录浙大通行证以后可以查看到的信息,just for fun. 4 | 5 | ## 目前实现的一些小功能 6 | 7 | 0. 登录浙大通行证 8 | 9 | 分别实现了web端的登录和钉钉扫码登录,详见文章[【JS逆向】模拟登录浙大通行证](https://mp.weixin.qq.com/s/SOHmtLgxgpXvpbeHXhBVEQ) 10 | 11 | 1. 蓝码生成器 12 | 13 | 文章:[生成「蓝马」背景的蓝码](https://mp.weixin.qq.com/s/O3CBi2M7o-X5a_idtHk2Hg) 14 | 15 | 2. 2020年度信息化账单 16 | 17 | 文章:[2020浙大信息化年度数据账单中的code参数分析](https://mp.weixin.qq.com/s/8G88f8ip8PpJs3Lx-JmrvA) 18 | 19 | 3. 每日健康打卡 20 | 21 | 参考:[ZJU-nCov-Hitcarder](https://github.com/Tishacy/ZJU-nCov-Hitcarder) 22 | 23 | 4. CC98账号管理 24 | 25 | 查看一下同一通行证下CC98账户的基本参数。 26 | 27 | 或许有兴趣看一下 [CC98抽卡机](https://github.com/FrazierLei/cc98-drawcard)? 28 | 29 | - 文章:[CC98抽卡机](https://mp.weixin.qq.com/s/WCTEPiMs-So_GRdYiheVAw) 30 | - 代码:[cc98-drawcard](https://github.com/FrazierLei/cc98-drawcard) 31 | 32 | 5. 就业网2021年隐藏信息查询 33 | 34 | 详见[README](./5.%20就业网隐藏信息查询/README.md) 35 | 36 | 6. 校园卡流水查询 37 | 38 | 详见[README](./6.%20校园卡流水查询/README.md) 39 | 40 | 7. 正版软件下载 41 | 42 | 获取微软、Adobe、Matlab所有软件的实际下载链接 43 | 44 | 8. CC98 拼车信息批量汇总 45 | 46 | 详见[README](./8.%20CC98拼车信息批量汇总/README.md) 47 | 48 | ## 使用方法 49 | 50 | 1. 在`config.json`中添加浙大通行证的用户名(通常为学号)和密码。 51 | 52 | 2. 在每一个文件夹中运行 53 | 54 | ```shell 55 | $ python xxx.py 56 | ``` 57 | 58 | 59 | 60 | ## 欢迎关注 61 | 62 | ![](./qrcode.png) 63 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "username": "", 3 | "password": "" 4 | } -------------------------------------------------------------------------------- /qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrazierLei/ZJU-toolkit/a98d2a2df168b93531c32c5bd57917c194421a00/qrcode.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.25.0 2 | prettytable==2.0.0 3 | qrcode==6.1 4 | colorama==0.4.3 5 | beautifulsoup4==4.9.3 6 | MyQR==2.3.1 7 | Pillow==8.1.0 8 | -------------------------------------------------------------------------------- /zjuam/__init__.py: -------------------------------------------------------------------------------- 1 | from .zjuam import ZJUAccount -------------------------------------------------------------------------------- /zjuam/zjuam.py: -------------------------------------------------------------------------------- 1 | import re 2 | import time 3 | import qrcode 4 | import requests 5 | 6 | 7 | class ZJUAccount: 8 | """ 9 | PC端登录浙大通行证 10 | """ 11 | def __init__(self, username, password): 12 | """ 13 | 初始化 14 | :param username: 用户名(学号) 15 | :param password: 密码 16 | """ 17 | self.username = username 18 | self.password = password 19 | self.session = requests.Session() 20 | self.session.headers = { 21 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:84.0) Gecko/20100101 Firefox/84.0', 22 | } 23 | self.login_url = 'https://zjuam.zju.edu.cn/cas/login' 24 | self.pubkey_url = 'https://zjuam.zju.edu.cn/cas/v2/getPubKey' 25 | 26 | def login(self): 27 | """ 28 | 登录函数 29 | :return: session 30 | """ 31 | # 获取公钥 32 | pubkey = self.session.get(self.pubkey_url).json() 33 | exponent, modulus = pubkey['exponent'], pubkey['modulus'] 34 | 35 | data = { 36 | 'username': self.username, 37 | 'password': self._rsa_encrypt(self.password, exponent, modulus), 38 | 'execution': self._get_execution(), 39 | 'authcode': '', 40 | '_eventId': 'submit' 41 | } 42 | resp = self.session.post(self.login_url, data=data) 43 | 44 | # 登录成功,获取姓名 45 | if self.check_login(): 46 | print(re.search('nick: \'(.*?)\'', resp.text).group(1), '登录成功!') 47 | return self.session 48 | else: 49 | print('登录失败。') 50 | return 51 | 52 | def _rsa_encrypt(self, password, exponent, modulus): 53 | """ 54 | RSA加密函数 55 | :param password: 原始密码 56 | :param exponent: 十六进制 exponent 57 | :param modulus: 十六进制 modulus 58 | :return: RSA 加密后的密码 59 | """ 60 | password_bytes = bytes(password, 'ascii') 61 | password_int = int.from_bytes(password_bytes, 'big') 62 | e_int = int(exponent, 16) 63 | m_int = int(modulus, 16) 64 | result_int = pow(password_int, e_int, m_int) 65 | return hex(result_int)[2:].rjust(128, '0') 66 | 67 | def _get_execution(self): 68 | """ 69 | 从页面HTML中获取 execution 的值 70 | :return: execution 的值 71 | """ 72 | resp = self.session.get(self.login_url) 73 | return re.search('name="execution" value="(.*?)"', resp.text).group(1) 74 | 75 | def check_login(self): 76 | """ 77 | 检查登录状态,访问登录页面出现跳转则是已登录, 78 | :return: bool 79 | """ 80 | resp = self.session.get(self.login_url, allow_redirects=False) 81 | if resp.status_code == 302: 82 | return True 83 | return False 84 | 85 | 86 | class ZJUAccountScanqr: 87 | """ 88 | 钉钉扫描二维码登录浙大通行证 89 | """ 90 | def __init__(self): 91 | """ 92 | 初始化,主要是各种 url 93 | """ 94 | self.session = requests.Session() 95 | self.session.headers = { 96 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:84.0) Gecko/20100101 Firefox/84.0', 97 | } 98 | self.qrcode_url = 'https://login.dingtalk.com/user/qrcode/generate?bizScene=http_third_party&sceneId=dingoayiy2etqcpmyc0lpk' 99 | self.qrcode_image_url = 'https://oapi.dingtalk.com/connect/qrcommit?showmenu=false&code={}&appid=dingoayiy2etqcpmyc0lpk&redirect_uri=https%3A%2F%2Fzjuam.zju.edu.cn%2Fcas%2Flogin%3Fclient_name%3DDingDingClient' 100 | self.um_url = 'https://ynuf.alipay.com/service/um.json' 101 | self.login_url = 'https://login.dingtalk.com/login/login_with_qr' 102 | self.main_page_url = 'https://zjuam.zju.edu.cn/cas/login' 103 | 104 | def login(self): 105 | """ 106 | 登录函数 107 | :return: session 108 | """ 109 | # 获取二维码 110 | resp = self.session.get(self.qrcode_url) 111 | qr_code = resp.json()['result'] 112 | qr_img = qrcode.make(self.qrcode_image_url.format(qr_code)).resize((300, 300)) 113 | qr_img.show() 114 | 115 | # 循环检测二维码扫描情况 116 | while True: 117 | data = { 118 | 'qrCode': qr_code, 119 | 'goto': 'https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=dingoayiy2etqcpmyc0lpk&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=https%3A%2F%2Fzjuam.zju.edu.cn%2Fcas%2Flogin%3Fclient_name%3DDingDingClient', 120 | 'pdmToken': self.session.post(self.um_url, data={'data': ''}).json()['id'], 121 | 'bizScene': 'http_third_party', 122 | 'sceneId': 'dingoayiy2etqcpmyc0lpk' 123 | } 124 | resp = self.session.post(self.login_url, data=data) 125 | resp_json = resp.json() 126 | 127 | # 扫码成功 128 | if resp_json['success']: 129 | self.session.get(resp_json['data']) 130 | break 131 | # 扫码中 132 | elif resp_json['code'] in ['11021', '11041']: 133 | pass 134 | # 二维码过期或其他情况 135 | else: 136 | raise RuntimeError(resp_json['message']) 137 | time.sleep(1) 138 | 139 | # 登录成功,获取姓名 140 | resp = self.session.get(self.main_page_url) 141 | print(re.search('nick: \'(.*?)\'', resp.text).group(1), '登录成功!') 142 | return self.session 143 | 144 | 145 | if __name__ == '__main__': 146 | zju = ZJUAccount('', '') 147 | # x = ZJUAccountScanqr() 148 | sess = zju.login() 149 | --------------------------------------------------------------------------------