├── .github └── workflows │ └── main.yml ├── .gitignore ├── README.md ├── __pycache__ └── captcha_break.cpython-39.pyc ├── captcha_break.py ├── dailyFudan.py └── requirements.txt /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: "Daily Fudan" 2 | 3 | on: 4 | schedule: 5 | - cron: "17 22,5 * * *" # scheduled at 06:17 & 13:17 (UTC+8) everyday 6 | workflow_dispatch: 7 | 8 | env: 9 | # auto merge from y1ndan/genshin-impact-helper, default: false 10 | ALLOW_MERGE: 'true' 11 | RUN_ENV: 'prod' 12 | TZ: 'Asia/Shanghai' 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | # if: github.ref == 'refs/heads/master' 18 | 19 | steps: 20 | - name: Checkout master 21 | uses: actions/checkout@v2 22 | with: 23 | fetch-depth: 0 24 | ref: master 25 | 26 | - name: Auto merge 27 | if: ${{ env.ALLOW_MERGE != 'false' }} 28 | run: | 29 | git config --global user.name github-actions 30 | git config --global user.email github-actions@github.com 31 | git config --global pull.rebase true 32 | git remote add upstream https://github.com/Limourli-liu/daily_fudan.git 33 | git pull upstream master --allow-unrelated-histories 34 | git push origin master --force 35 | - name: Set up python 36 | uses: actions/setup-python@v2 37 | with: 38 | python-version: 3.8 39 | 40 | - name: Random sleep 41 | if: github.event_name == 'schedule' 42 | run: sleep $(shuf -i 10-300 -n 1) 43 | 44 | - name: Run sign 45 | run: | 46 | python -m pip install --upgrade pip 47 | pip install -r requirements.txt 48 | python3 ./dailyFudan.py '${{ secrets.FUDAN }}' 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | .idea 3 | .idea/ 4 | _reshape/.idea/ 5 | account 6 | /get-info.txt 7 | /tst.py 8 | /account.txt 9 | /sess.data 10 | /dailyFudan3.1.py 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 10.26 更新: 2 | 信息办加了图形验证 3 | 4 | [Limourli-liu](https://github.com/Limourli-liu)添加了验证码识别及微信通知功能 5 | 6 | 各位可跟进这位 7 | 8 | 查看:https://github.com/k652/daily_fudan/pull/15 9 | 10 | 11 | 世事繁忙,无力维护,山高水长,后会无期 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /__pycache__/captcha_break.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/potatoshred/daily_fudan/068a1862f5ad8e0bce39b78ca5a2e78981ceddd2/__pycache__/captcha_break.cpython-39.pyc -------------------------------------------------------------------------------- /captcha_break.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import json 3 | import sys 4 | import requests 5 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 6 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 7 | 8 | def base64_api(uname, pwd, img, typeid): 9 | base64_data = base64.b64encode(img) 10 | b64 = base64_data.decode() 11 | data = {"username": uname, "password": pwd, "typeid": typeid, "image": b64} 12 | result = json.loads(requests.post("http://api.ttshitu.com/predict", json=data).text) 13 | return result 14 | 15 | def reportError(id): 16 | data = {"id": id} 17 | result = json.loads(requests.post("http://api.kuaishibie.cn/reporterror.json", json=data).text) 18 | if result['success']: 19 | return "报错成功" 20 | else: 21 | return result["message"] 22 | 23 | from sys import exit as sys_exit 24 | def getCaptchaData(zlapp): 25 | url = 'https://zlapp.fudan.edu.cn/backend/default/code' 26 | headers = {'accept': 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8', 27 | 'accept-encoding': 'gzip', 28 | 'accept-language': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7', 29 | 'dnt': '1', 30 | 'referer': 'https://zlapp.fudan.edu.cn/site/ncov/fudanDaily', 31 | 'sec-ch-ua': '"Chromium";v="92", " Not A;Brand";v="99", "Google Chrome";v="92"', 32 | 'sec-ch-ua-mobile': '?0', 33 | 'sec-fetch-dest': 'image', 34 | 'sec-fetch-mode': 'no-cors', 35 | 'sec-fetch-site': 'same-origin', 36 | "User-Agent": zlapp.UA} 37 | res = zlapp.session.get(url, headers=headers) 38 | return res.content 39 | 40 | class DailyFDCaptcha: 41 | zlapp = None 42 | uname = '' 43 | pwd = '' 44 | typeid = 2 # 纯英文 45 | info = lambda x: x 46 | id = 0 47 | def __init__(self, 48 | uname, pwd, 49 | zlapp, 50 | info_callback): 51 | self.zlapp = zlapp 52 | self.uname = uname 53 | self.pwd = pwd 54 | self.info = info_callback 55 | def __call__(self): 56 | img = getCaptchaData(self.zlapp) 57 | result = base64_api(self.uname,self.pwd,img,self.typeid) 58 | print(result) 59 | if result['success']: 60 | self.id = result["data"]["id"] 61 | return result["data"]["result"] 62 | else: 63 | self.info(result["message"]) 64 | def reportError(self): 65 | if self.id != 0: 66 | self.info(reportError(self.id)) 67 | 68 | if __name__ == "__main__": 69 | def base64_api(uname, pwd, img, typeid): 70 | return { 71 | "success": False, 72 | "code": "-1", 73 | "message": "用户名或密码错误", 74 | "data": "" 75 | } 76 | print(base64_api(0,0,0,0)) 77 | test = DailyFDCaptcha(0,0,0,print) 78 | test(0) 79 | def base64_api(uname, pwd, img, typeid): 80 | return { 81 | "success": True, 82 | "code": "0", 83 | "message": "success", 84 | "data": { 85 | "result": "hhum", 86 | "id": "00504808e68a41ad83ab5c1e6367ae6b" 87 | } 88 | } 89 | print(test(0)) 90 | def reportError(id): 91 | return id 92 | test.reportError() -------------------------------------------------------------------------------- /dailyFudan.py: -------------------------------------------------------------------------------- 1 | import time 2 | from json import loads as json_loads 3 | from json import dumps as json_dumps 4 | from os import path as os_path 5 | from sys import exit as sys_exit 6 | from sys import argv as sys_argv 7 | 8 | from lxml import etree 9 | from requests import session 10 | import logging 11 | logging.basicConfig(level=logging.INFO, 12 | format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s') 13 | 14 | # WeChat notice 15 | #get token via http://iyuu.cn/ 16 | import requests 17 | from captcha_break import DailyFDCaptcha 18 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 19 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 20 | def iyuu(IYUU_TOKEN): 21 | url = f"https://iyuu.cn/{IYUU_TOKEN}.send" 22 | headers = {'Content-type': 'application/x-www-form-urlencoded'} 23 | def send(text, desp=""): 24 | Form = {'text': text, 'desp': desp} 25 | return requests.post(url, data=Form, headers=headers, verify=False) 26 | return send 27 | 28 | # fix random area bug 29 | def set_q(iterO): 30 | res = list() 31 | for item in iterO: 32 | if item not in res: 33 | res.append(item) 34 | return res 35 | 36 | 37 | 38 | class Fudan: 39 | """ 40 | 建立与复旦服务器的会话,执行登录/登出操作 41 | """ 42 | UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:76.0) Gecko/20100101 Firefox/76.0" 43 | 44 | # 初始化会话 45 | def __init__(self, 46 | uid, psw, 47 | url_login='https://uis.fudan.edu.cn/authserver/login'): 48 | """ 49 | 初始化一个session,及登录信息 50 | :param uid: 学号 51 | :param psw: 密码 52 | :param url_login: 登录页,默认服务为空 53 | """ 54 | self.session = session() 55 | self.session.headers['User-Agent'] = self.UA 56 | self.url_login = url_login 57 | 58 | self.uid = uid 59 | self.psw = psw 60 | 61 | def _page_init(self): 62 | """ 63 | 检查是否能打开登录页面 64 | :return: 登录页page source 65 | """ 66 | logging.debug("Initiating——") 67 | page_login = self.session.get(self.url_login) 68 | 69 | logging.debug("return status code " + str(page_login.status_code)) 70 | 71 | if page_login.status_code == 200: 72 | logging.debug("Initiated——") 73 | return page_login.text 74 | else: 75 | logging.debug("Fail to open Login Page, Check your Internet connection\n") 76 | self.close() 77 | 78 | def login(self): 79 | """ 80 | 执行登录 81 | """ 82 | page_login = self._page_init() 83 | 84 | logging.debug("parsing Login page——") 85 | html = etree.HTML(page_login, etree.HTMLParser()) 86 | 87 | logging.debug("getting tokens") 88 | data = { 89 | "username": self.uid, 90 | "password": self.psw, 91 | "service" : "https://zlapp.fudan.edu.cn/site/ncov/fudanDaily" 92 | } 93 | 94 | # 获取登录页上的令牌 95 | data.update( 96 | zip( 97 | html.xpath("/html/body/form/input/@name"), 98 | html.xpath("/html/body/form/input/@value") 99 | ) 100 | ) 101 | 102 | headers = { 103 | "Host" : "uis.fudan.edu.cn", 104 | "Origin" : "https://uis.fudan.edu.cn", 105 | "Referer" : self.url_login, 106 | "User-Agent": self.UA 107 | } 108 | 109 | logging.debug("Login ing——") 110 | post = self.session.post( 111 | self.url_login, 112 | data=data, 113 | headers=headers, 114 | allow_redirects=False) 115 | 116 | logging.debug("return status code %d" % post.status_code) 117 | 118 | if post.status_code == 302: 119 | logging.debug("登录成功") 120 | return True 121 | else: 122 | logging.debug("登录失败,请检查账号信息") 123 | self.close() 124 | return False 125 | 126 | def logout(self): 127 | """ 128 | 执行登出 129 | """ 130 | exit_url = 'https://uis.fudan.edu.cn/authserver/logout?service=/authserver/login' 131 | expire = self.session.get(exit_url).headers.get('Set-Cookie') 132 | 133 | if '01-Jan-1970' in expire: 134 | logging.debug("登出完毕") 135 | else: 136 | logging.debug("登出异常") 137 | 138 | def close(self): 139 | """ 140 | 执行登出并关闭会话 141 | """ 142 | self.logout() 143 | self.session.close() 144 | logging.debug("关闭会话") 145 | # sys_exit() 通知完成再退出 注释掉 146 | 147 | class Zlapp(Fudan): 148 | last_info = '' 149 | 150 | def check(self): 151 | """ 152 | 检查 153 | """ 154 | logging.debug("检测是否已提交") 155 | get_info = self.session.get( 156 | 'https://zlapp.fudan.edu.cn/ncov/wap/fudan/get-info') 157 | last_info = get_info.json() 158 | 159 | logging.info("上一次提交日期为: %s " % last_info["d"]["info"]["date"]) 160 | 161 | position = last_info["d"]["info"]['geo_api_info'] 162 | position = json_loads(position) 163 | 164 | logging.info("上一次提交地址为: %s" % position['formattedAddress']) 165 | # logging.debug("上一次提交GPS为", position["position"]) 166 | 167 | today = time.strftime("%Y%m%d", time.localtime()) 168 | 169 | if last_info["d"]["info"]["date"] == today: 170 | logging.info("今日已提交") 171 | self.close() 172 | global gl_info 173 | gl_info = last_info 174 | geo_api_info = json_loads(last_info["d"]["info"]["geo_api_info"]) 175 | province = geo_api_info["addressComponent"].get("province", "") 176 | city = geo_api_info["addressComponent"].get("city", "") or province 177 | district = geo_api_info["addressComponent"].get("district", "") 178 | gl_info['dailyFudan'] = " ".join(set_q((province, city, district))) 179 | gl_info = json_dumps(gl_info, indent=4, ensure_ascii=False) 180 | return True 181 | else: 182 | logging.info("未提交") 183 | self.last_info = last_info["d"]["info"] 184 | return False 185 | 186 | def checkin(self, captcha): 187 | """ 188 | 提交 189 | """ 190 | headers = { 191 | "Host" : "zlapp.fudan.edu.cn", 192 | "Referer" : "https://zlapp.fudan.edu.cn/site/ncov/fudanDaily?from=history", 193 | "DNT" : "1", 194 | "TE" : "Trailers", 195 | "User-Agent": self.UA 196 | } 197 | 198 | logging.debug("提交中") 199 | 200 | geo_api_info = json_loads(self.last_info["geo_api_info"]) 201 | province = geo_api_info["addressComponent"].get("province", "") 202 | city = geo_api_info["addressComponent"].get("city", "") or province 203 | district = geo_api_info["addressComponent"].get("district", "") 204 | self.last_info.update( 205 | { 206 | "tw" : "13", 207 | "province": province, 208 | "city" : city, 209 | "area" : " ".join(set_q((province, city, district))), 210 | "ismoved" : 0 211 | } 212 | ) 213 | # logging.debug(self.last_info) 214 | for i in range(3): 215 | captcha_text = captcha() 216 | #captcha_text = 'abcd' 217 | self.last_info.update({ 218 | 'sfzx': 1, 219 | 'code': captcha_text 220 | }) 221 | save = self.session.post( 222 | 'https://zlapp.fudan.edu.cn/ncov/wap/fudan/save', 223 | data=self.last_info, 224 | headers=headers, 225 | allow_redirects=False) 226 | 227 | save_msg = json_loads(save.text)["m"] 228 | logging.info(save_msg) 229 | if save_msg != '验证码错误': 230 | break 231 | else: 232 | captcha.reportError() 233 | print('captcha.reportError') 234 | 235 | def get_account(): 236 | """ 237 | 获取账号信息 238 | """ 239 | uid, psw, *IYUU_TOKEN = sys_argv[1].strip().split(' ') 240 | return uid, psw, IYUU_TOKEN 241 | 242 | gl_info = "快去手动填写!" 243 | if __name__ == '__main__': 244 | uid, psw, IYUU_TOKE = get_account() 245 | if IYUU_TOKE: #有token则通知,无token不通知 246 | if len(IYUU_TOKE) != 3: 247 | logging.error("请正确配置微信通知功能和验证码打码功能~\n") 248 | sys_exit(1) 249 | uname = IYUU_TOKE[1] 250 | pwd = IYUU_TOKE[2] 251 | IYUU_TOKE = IYUU_TOKE[0] 252 | if IYUU_TOKE.startswith('IYUU'): 253 | iy_info = iyuu(IYUU_TOKE) 254 | else: 255 | def iy_info(text, desp=""): 256 | pass 257 | else: 258 | def iy_info(text, desp=""): 259 | pass 260 | logging.error("请按readme操作,以正确完成配置~\n") 261 | sys_exit(1) 262 | 263 | # logging.debug("ACCOUNT:" + uid + psw) 264 | zlapp_login = 'https://uis.fudan.edu.cn/authserver/login?' \ 265 | 'service=https://zlapp.fudan.edu.cn/site/ncov/fudanDaily' 266 | daily_fudan = Zlapp(uid, psw, url_login=zlapp_login) 267 | if not daily_fudan.login(): 268 | iy_info("平安复旦:登陆失败", gl_info) 269 | sys_exit() 270 | 271 | if daily_fudan.check(): 272 | iy_info("平安复旦:今日已提交", gl_info) 273 | sys_exit() 274 | 275 | def captcha_info(message): 276 | iy_info(message, gl_info) 277 | captcha = DailyFDCaptcha(uname,pwd,daily_fudan,captcha_info) 278 | daily_fudan.checkin(captcha) 279 | 280 | # 再检查一遍 281 | if daily_fudan.check(): 282 | iy_info("平安复旦:今日已提交", gl_info) 283 | else: 284 | iy_info("平安复旦:本次提交失败", gl_info) 285 | 286 | daily_fudan.close() 287 | sys_exit() 288 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | lxml 2 | requests 3 | --------------------------------------------------------------------------------