diff --git a/.github/run.yml b/.github/run.yml new file mode 100644 index 0000000..3992232 --- /dev/null +++ b/.github/run.yml @@ -0,0 +1,49 @@ +name: CheckHomeworks +on: + workflow_dispatch: + push: + pull_request: + watch: + types: [ started ] + schedule: + - cron: 45 22 * * * + +jobs: + HomeworkCheck: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: 'Set python' + uses: actions/setup-python@v1 + with: + python-version: '3.9.7' + - name: 'Install dependencies' + run: python3 -m pip install --upgrade pip + + - name: 'install libs' + run: pip3 install requests + + - name: StratCheckHomework + env: + ZJU_USERNAME: ${{ secrets.ZJU_USERNAME }} + ZJU_PASSWORD: ${{ secrets.ZJU_PASSWORD }} + + DD_BOT_SECRET: ${{ secrets.DD_BOT_SECRET }} + DD_BOT_TOKEN: ${{ secrets.DD_BOT_TOKEN }} + + FSKEY: ${{ secrets.FSKEY }} + + PUSH_PLUS_TOKEN: ${{ secrets.PUSH_PLUS_TOKEN }} + PUSH_PLUS_USER: ${{ secrets.PUSH_PLUS_USER }} + + PUSH_KEY: ${{ secrets.PUSH_KEY }} + + TG_BOT_TOKEN: ${{ secrets.TG_BOT_TOKEN }} + TG_USER_ID: ${{ secrets.TG_USER_ID }} + TG_PROXY_HOST: ${{ secrets.TG_PROXY_HOST }} + TG_PROXY_PORT: ${{ secrets.TG_PROXY_PORT }} + TG_PROXY_AUTH: ${{ secrets.TG_PROXY_AUTH }} + + run: python3 GetDDL.py \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9fe17bc --- /dev/null +++ b/.gitignore @@ -0,0 +1,129 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ \ No newline at end of file diff --git a/GetDDL.py b/GetDDL.py new file mode 100644 index 0000000..0422f21 --- /dev/null +++ b/GetDDL.py @@ -0,0 +1,111 @@ +import datetime +import os +from threading import Thread +import requests +import re +import time +from notify import console, dingding, pushplus, feishu,serverj,telegram +class LoginError(Exception): + """Login Exception""" + pass + +class ZJULogin(object): + """ + Attributes: + username: (str) 浙大统一认证平台用户名(一般为学号) + password: (str) 浙大统一认证平台密码 + sess: (requests.Session) 统一的session管理 + """ + headers = { + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36', + } + BASE_URL = "https://courses.zju.edu.cn/api/todos?no-intercept=true" + LOGIN_URL = "https://zjuam.zju.edu.cn/cas/login?service=http%3A%2F%2Fservice.zju.edu.cn%2F" + + def __init__(self, username, password): + self.username = username + self.password = password + self.sess = requests.Session() + + def login(self): + """Login to ZJU platform""" + res = self.sess.get(self.LOGIN_URL) + execution = re.search( + 'name="execution" value="(.*?)"', res.text).group(1) + res = self.sess.get( + url='https://zjuam.zju.edu.cn/cas/v2/getPubKey').json() + n, e = res['modulus'], res['exponent'] + encrypt_password = self._rsa_encrypt(self.password, e, n) + + data = { + 'username': self.username, + 'password': encrypt_password, + 'execution': execution, + '_eventId': 'submit', + "authcode": "" + } + res = self.sess.post(url=self.LOGIN_URL, data=data) + # check if login successfully + if '用户名或密码错误' in res.content.decode(): + raise LoginError('登录失败,请核实账号密码重新登录') + print("统一认证平台登录成功~") + return self.sess + + def _rsa_encrypt(self, password_str, e_str, M_str): + password_bytes = bytes(password_str, 'ascii') + password_int = int.from_bytes(password_bytes, 'big') + e_int = int(e_str, 16) + M_int = int(M_str, 16) + result_int = pow(password_int, e_int, M_int) + return hex(result_int)[2:].rjust(128, '0') + + +class getdll(ZJULogin): + """ + Attributes: + get请求"https://courses.zju.edu.cn/api/todos?no-intercept=true" 返回列表 + + """ + def __init__(self, username, password): + super().__init__(username, password) + self.login() + def getddl(self): + res = self.sess.get(self.BASE_URL) + try: + res = res.json()['todo_list'] + for event in res: + self.compare(event) + except: + raise LoginError('获取数据失败') + def compare(self,event): + daynow = time.strftime('%Y-%m-%d',time.localtime(time.time())) + timenow = time.strftime('%H:%M',time.localtime(time.time())) + + endtime = re.findall('(.*)T(.*?):00Z', event.get('end_time'))[0] + endday,endhour = datetime.datetime.strptime(endtime[0], '%Y-%m-%d'),datetime.datetime.strptime(endtime[1], '%H:%M') + + dayleft = (endday-datetime.datetime.strptime(daynow, '%Y-%m-%d')).days + hourleft = (endhour-datetime.datetime.strptime(timenow, '%H:%M')).seconds/3600 + + self.reminder(event.get('course_name')+' 的作业 '+ event.get('title')+ ' : 剩余时间'+str(dayleft)+'天') + if dayleft == 0 and hourleft > 0: + self.reminder(event.get('course_name')+'的'+ event.get('title')+ ''+str(round(hourleft))+'小时') + self.reminder('死到临头啦同学') + + def reminder(self,content): + remind_func = [console.console, pushplus.pushplus_bot,dingding.dingding_bot,feishu.feishu_bot,serverj.serverJ,telegram.telegram_bot] + title = '浙大统一认证平台提醒您:' + threads = [] + for func in remind_func: + t = Thread(target=func, args=(title,content)) + threads.append(t) + t.start() + t.join() + + def run(self): + self.getddl() + +if __name__ == '__main__': + username = os.getenv('ZJU_USERNAME') + password = os.getenv('ZJU_PASSWORD') + getdll(username, password).run() \ No newline at end of file diff --git a/notify/console.py b/notify/console.py new file mode 100644 index 0000000..1f71048 --- /dev/null +++ b/notify/console.py @@ -0,0 +1,6 @@ + +def console(title: str, content: str) -> None: + """ + 使用 控制台 推送消息。 + """ + print(f"{title}\n\n{content}") diff --git a/notify/dingding.py b/notify/dingding.py new file mode 100644 index 0000000..d35db8b --- /dev/null +++ b/notify/dingding.py @@ -0,0 +1,38 @@ + +import base64 +import hashlib +import hmac +import json +import os +import time +import requests +import urllib3 + +def dingding_bot(title: str, content: str) -> None: + """ + 使用 钉钉机器人 推送消息。 + """ + if not os.getenv("DD_BOT_SECRET") or not os.getenv("DD_BOT_TOKEN"): + pass + return + print("钉钉机器人 服务启动") + + timestamp = str(round(time.time() * 1000)) + secret_enc = os.getenv("DD_BOT_SECRET").encode("utf-8") + string_to_sign = "{}\n{}".format(timestamp, os.getenv("DD_BOT_SECRET")) + string_to_sign_enc = string_to_sign.encode("utf-8") + hmac_code = hmac.new( + secret_enc, string_to_sign_enc, digestmod=hashlib.sha256 + ).digest() + sign = urllib3.parse.quote_plus(base64.b64encode(hmac_code)) + url = f'https://oapi.dingtalk.com/robot/send?access_token={os.getenv("DD_BOT_TOKEN")}×tamp={timestamp}&sign={sign}' + headers = {"Content-Type": "application/json;charset=utf-8"} + data = {"msgtype": "text", "text": {"content": f"{title}\n\n{content}"}} + response = requests.post( + url=url, data=json.dumps(data), headers=headers, timeout=15 + ).json() + + if not response["errcode"]: + print("钉钉机器人 推送成功!") + else: + print("钉钉机器人 推送失败!") \ No newline at end of file diff --git a/notify/feishu.py b/notify/feishu.py new file mode 100644 index 0000000..5f8015b --- /dev/null +++ b/notify/feishu.py @@ -0,0 +1,22 @@ +import json +import os +import requests + + +def feishu_bot(title: str, content: str) -> None: + """ + 使用 飞书机器人 推送消息。 + """ + if not os.getenv("FSKEY"): + pass + return + print("飞书 服务启动") + + url = f'https://open.feishu.cn/open-apis/bot/v2/hook/{os.getenv("FSKEY")}' + data = {"msg_type": "text", "content": {"text": f"{title}\n\n{content}"}} + response = requests.post(url, data=json.dumps(data)).json() + + if response.get("StatusCode") == 0: + print("飞书 推送成功!") + else: + print("飞书 推送失败!错误信息如下:\n", response) \ No newline at end of file diff --git a/notify/pushplus.py b/notify/pushplus.py new file mode 100644 index 0000000..cc1abda --- /dev/null +++ b/notify/pushplus.py @@ -0,0 +1,38 @@ +import json +import os +import requests + +def pushplus_bot(title: str, content: str) -> None: + """ + 通过 push+ 推送消息。 + """ + if not os.getenv("PUSH_PLUS_TOKEN"): + pass + return + print("PUSHPLUS 服务启动") + + url = "http://www.pushplus.plus/send" + data = { + "token": os.getenv("PUSH_PLUS_TOKEN"), + "title": title, + "content": content, + "topic": os.getenv("PUSH_PLUS_USER"), + } + body = json.dumps(data).encode(encoding="utf-8") + headers = {"Content-Type": "application/json"} + response = requests.post(url=url, data=body, headers=headers).json() + + if response["code"] == 200: + print("PUSHPLUS 推送成功!") + + else: + + url_old = "http://pushplus.hxtrip.com/send" + headers["Accept"] = "application/json" + response = requests.post(url=url_old, data=body, headers=headers).json() + + if response["code"] == 200: + print("PUSHPLUS(hxtrip) 推送成功!") + + else: + print("PUSHPLUS 推送失败!") \ No newline at end of file diff --git a/notify/serverj.py b/notify/serverj.py new file mode 100644 index 0000000..55f69f1 --- /dev/null +++ b/notify/serverj.py @@ -0,0 +1,23 @@ +import os +import requests + +def serverJ(title: str, content: str) -> None: + """ + 通过 serverJ 推送消息。 + """ + if not os.getenv("PUSH_KEY"): + pass + return + print("serverJ 服务启动") + + data = {"text": title, "desp": content.replace("\n", "\n\n")} + if os.getenv("PUSH_KEY").index("SCT") != -1: + url = f'https://sctapi.ftqq.com/{os.getenv("PUSH_KEY")}.send' + else: + url = f'https://sc.ftqq.com/${os.getenv("PUSH_KEY")}.send' + response = requests.post(url, data=data).json() + + if response.get("errno") == 0 or response.get("code") == 0: + print("serverJ 推送成功!") + else: + print(f'serverJ 推送失败!错误码:{response["message"]}') diff --git a/notify/telegram.py b/notify/telegram.py new file mode 100644 index 0000000..7b692fb --- /dev/null +++ b/notify/telegram.py @@ -0,0 +1,46 @@ +import os +import requests + +def telegram_bot(title: str, content: str) -> None: + """ + 使用 telegram 机器人 推送消息。 + """ + if not os.getenv("TG_BOT_TOKEN") or not os.getenv("TG_USER_ID"): + pass + return + print("tg 服务启动") + + if os.getenv("TG_API_HOST"): + url = f"https://{os.getenv('TG_API_HOST')}/bot{os.getenv('TG_BOT_TOKEN')}/sendMessage" + else: + url = ( + f"https://api.telegram.org/bot{os.getenv('TG_BOT_TOKEN')}/sendMessage" + ) + headers = {"Content-Type": "application/x-www-form-urlencoded"} + payload = { + "chat_id": str(os.getenv("TG_USER_ID")), + "text": f"{title}\n\n{content}", + "disable_web_page_preview": "true", + } + proxies = None + if os.getenv("TG_PROXY_HOST") and os.getenv("TG_PROXY_PORT"): + if os.getenv("TG_PROXY_AUTH") is not None and "@" not in os.getenv( + "TG_PROXY_HOST" + ): + os.getenv["TG_PROXY_HOST"] = ( + os.getenv("TG_PROXY_AUTH") + + "@" + + os.getenv("TG_PROXY_HOST") + ) + proxyStr = "http://{}:{}".format( + os.getenv("TG_PROXY_HOST"), os.getenv("TG_PROXY_PORT") + ) + proxies = {"http": proxyStr, "https": proxyStr} + response = requests.post( + url=url, headers=headers, params=payload, proxies=proxies + ).json() + + if response["ok"]: + print("tg 推送成功!") + else: + print("tg 推送失败!") \ No newline at end of file diff --git a/pic/logo.png b/pic/logo.png new file mode 100644 index 0000000..224a02b Binary files /dev/null and b/pic/logo.png differ diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..e69de29