diff --git a/.gitignore b/.gitignore index b6e4761..34d75ff 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,11 @@ dmypy.json # Pyre type checker .pyre/ + +# ide +.idea/ + +# data +cookie.txt +captcha.jpg +qrcode.png diff --git a/cqwu/__init__.py b/cqwu/__init__.py new file mode 100644 index 0000000..1a9dabd --- /dev/null +++ b/cqwu/__init__.py @@ -0,0 +1,2 @@ +from . import types, enums +from .client import Client diff --git a/cqwu/client.py b/cqwu/client.py new file mode 100644 index 0000000..5ee81a7 --- /dev/null +++ b/cqwu/client.py @@ -0,0 +1,73 @@ +import asyncio +from typing import Coroutine, Optional +from httpx import AsyncClient, Cookies +from urllib.parse import urlparse + +from cqwu.methods import Methods +from cqwu.types import User + + +class Client(Methods): + """CQWU main client.""" + + def __init__( + self, + username: int = None, + password: str = None, + cookie: str = None, + cookie_file_path: str = "cookie.txt", + client_vpn: bool = False, + ): + self.username = username + self.password = password + self.cookie = cookie + self.cookie_file_path = cookie_file_path + if client_vpn: + self.host = "https://clientvpn.cqwu.edu.cn:10443/http/webvpn507e990968de07079b0f10d16c49bdb1cb8d3ca3a4d14f557999e92cbdf19fcd" + self.auth_host = self.host + else: + self.host = "http://ehall.cqwu.edu.cn" + self.auth_host = "http://authserver.cqwu.edu.cn" + + self.headers = { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", + "Accept-Encoding": "gzip, deflate", + "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", + "Host": urlparse(self.host).netloc, + "Origin": self.host, + "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36" + } + self.cookies = Cookies() + self.sub_cookies = Cookies() + self.init_sub_web = [] + self.request = AsyncClient + self.loop = asyncio.get_event_loop() + self.me: Optional[User] = None + + @staticmethod + def get_input(word: str = "", is_int: bool = False): + while True: + value = input(f"请输入{word}:") + if not value: + continue + if is_int: + try: + value = int(value) + except ValueError: + continue + confirm = (input(f'确认是 "{value}" 吗?(y/N): ')).lower() + if confirm == "y": + break + return value + + def sync(self, coroutine: Coroutine): + """ + 同步执行异步函数 + + Args: + coroutine (Coroutine): 异步函数 + + Returns: + 该异步函数的返回值 + """ + return self.loop.run_until_complete(coroutine) diff --git a/cqwu/enums/__init__.py b/cqwu/enums/__init__.py new file mode 100644 index 0000000..f58681b --- /dev/null +++ b/cqwu/enums/__init__.py @@ -0,0 +1 @@ +from .order import OrderStatus diff --git a/cqwu/enums/order.py b/cqwu/enums/order.py new file mode 100644 index 0000000..fa666a7 --- /dev/null +++ b/cqwu/enums/order.py @@ -0,0 +1,9 @@ +from enum import Enum + + +class OrderStatus(Enum): + NO_PAY = 0 + NO_PAY_RE = 1 + SUCCESS = 2 + FAILURE = 3 + EXPIRED = 4 diff --git a/cqwu/errors/auth.py b/cqwu/errors/auth.py new file mode 100644 index 0000000..c6f7818 --- /dev/null +++ b/cqwu/errors/auth.py @@ -0,0 +1,6 @@ +class UsernameOrPasswordError(Exception): + pass + + +class CookieError(Exception): + pass diff --git a/cqwu/methods/__init__.py b/cqwu/methods/__init__.py new file mode 100644 index 0000000..6293b13 --- /dev/null +++ b/cqwu/methods/__init__.py @@ -0,0 +1,11 @@ +from .auth import Auth +from .epay import EPay +from .users import Users + + +class Methods( + Auth, + EPay, + Users +): + pass diff --git a/cqwu/methods/auth/__init__.py b/cqwu/methods/auth/__init__.py new file mode 100644 index 0000000..d298896 --- /dev/null +++ b/cqwu/methods/auth/__init__.py @@ -0,0 +1,15 @@ +from .login_with_password import LoginWithPassword +from .login_with_cookie import LoginWithCookie +from .login_with_cookie_file import LoginWithCookieFile +from .export_cookie_to_file import ExportCookieToFile +from .oauth import Oauth + + +class Auth( + LoginWithPassword, + LoginWithCookie, + LoginWithCookieFile, + ExportCookieToFile, + Oauth +): + pass diff --git a/cqwu/methods/auth/export_cookie_to_file.py b/cqwu/methods/auth/export_cookie_to_file.py new file mode 100644 index 0000000..39e87b5 --- /dev/null +++ b/cqwu/methods/auth/export_cookie_to_file.py @@ -0,0 +1,17 @@ +import cqwu +from cqwu.errors.auth import CookieError + + +class ExportCookieToFile: + async def export_cookie_to_file( + self: "cqwu.Client", + ): + """ + 导出 cookie 到文件 + """ + if not self.cookies: + raise CookieError() + + data = "".join(f"{key}={value};" for key, value in self.cookies.items()) + with open(self.cookie_file_path, "w") as f: + f.write(data) diff --git a/cqwu/methods/auth/login_with_cookie.py b/cqwu/methods/auth/login_with_cookie.py new file mode 100644 index 0000000..af392a6 --- /dev/null +++ b/cqwu/methods/auth/login_with_cookie.py @@ -0,0 +1,25 @@ +import cqwu +from cqwu.errors.auth import CookieError + + +class LoginWithCookie: + async def login_with_cookie( + self: "cqwu.Client", + ): + """ + 使用 cookie 登录 + """ + if not self.cookie: + raise CookieError() + + try: + data = self.cookie.split(";") + for cookie in data: + if not cookie: + continue + key, value = cookie.split("=") + self.cookies.set(key, value) + self.sub_cookies.set(key, value) + self.me = await self.get_me() # noqa + except: + raise CookieError() diff --git a/cqwu/methods/auth/login_with_cookie_file.py b/cqwu/methods/auth/login_with_cookie_file.py new file mode 100644 index 0000000..dde52c6 --- /dev/null +++ b/cqwu/methods/auth/login_with_cookie_file.py @@ -0,0 +1,22 @@ +from os.path import exists + +import cqwu +from cqwu.errors.auth import CookieError + + +class LoginWithCookieFile: + async def login_with_cookie_file( + self: "cqwu.Client", + ): + """ + 使用 cookie 文本文件登录 + """ + if not exists(self.cookie_file_path): + raise CookieError() + + try: + with open(self.cookie_file_path, "r") as f: + self.cookie = f.read() # noqa + await self.login_with_cookie() + except: + raise CookieError() diff --git a/cqwu/methods/auth/login_with_password.py b/cqwu/methods/auth/login_with_password.py new file mode 100644 index 0000000..bec3c05 --- /dev/null +++ b/cqwu/methods/auth/login_with_password.py @@ -0,0 +1,74 @@ +import time + +from datetime import datetime +from urllib.parse import urlencode, urlparse +from lxml import etree + +import cqwu +from cqwu.errors.auth import UsernameOrPasswordError +from cqwu.utils.auth import encode_password + + +class LoginWithPassword: + async def login_with_password( + self: "cqwu.Client", + ): + """ + 使用学号加密码登录 + """ + headers = { + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', + 'Accept-Language': 'zh-CN,zh;q=0.9', + 'Cache-Control': 'max-age=0', + 'Connection': 'keep-alive', + 'DNT': '1', + 'Upgrade-Insecure-Requests': '1', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.167 Safari/537.36', + "Referer": f"{self.auth_host}/authserver/login", + "Origin": self.auth_host, + "Host": urlparse(self.auth_host).netloc + } + session = self.request(headers=self.headers, follow_redirects=True) + html = await session.get(f"{self.host}/authserver/login") + self.cookies.update(html.cookies) + tree = etree.HTML(html.text) + pwd_default_encrypt_salt = tree.xpath('//*[@id="pwdDefaultEncryptSalt"]/@value')[0] + form_data = { + 'username': str(self.username), + 'password': encode_password(self.password, pwd_default_encrypt_salt), + 'lt': tree.xpath('//*[@id="casLoginForm"]/input[1]/@value')[0], + 'dllt': tree.xpath('//*[@id="casLoginForm"]/input[2]/@value')[0], + 'execution': tree.xpath('//*[@id="casLoginForm"]/input[3]/@value')[0], + '_eventId': tree.xpath('//*[@id="casLoginForm"]/input[4]/@value')[0], + 'rmShown': tree.xpath('//*[@id="casLoginForm"]/input[5]/@value')[0] + } + + # 是否需要验证码 + params = { + "username": self.username, + "pwdEncrypt2": "pwdEncryptSalt", + "_": str(round(time.time() * 1000)) + } + + need_captcha_url = f"{self.auth_host}/authserver/needCaptcha.html?{urlencode(params)}" + async with self.request() as client: + html = await client.get(need_captcha_url, follow_redirects=False) + if html.text == 'true': + ts = round(datetime.now().microsecond / 1000) # get milliseconds + captcha_url = f"{self.auth_host}/authserver/captcha.html?" + urlencode({"ts": ts}) + async with self.request() as client: + res = await client.get(captcha_url, follow_redirects=False) + with open("captcha.jpg", mode="wb") as f: + f.write(res.content) + print("验证码已保存在当前目录下的 captcha.jpg 文件中。") + code = self.get_input("验证码") + form_data['captchaResponse'] = code + + # 登录 + async with self.request(headers=headers, cookies=self.cookies) as client: + html = await client.post(f"{self.auth_host}/authserver/login", data=form_data, follow_redirects=False) + if 'CASTGC' not in html.cookies.keys(): + raise UsernameOrPasswordError + self.cookies.update(html.cookies) + self.sub_cookies.update(html.cookies) + self.me = await self.get_me() # noqa diff --git a/cqwu/methods/auth/oauth.py b/cqwu/methods/auth/oauth.py new file mode 100644 index 0000000..2ce0acf --- /dev/null +++ b/cqwu/methods/auth/oauth.py @@ -0,0 +1,22 @@ +from urllib.parse import urlparse + +import cqwu + + +class Oauth: + async def oauth( + self: "cqwu.Client", + url: str, + host: str = None, + ): + """ + 使用 统一身份认证平台 登录子系统,并且保存 cookie + """ + host = host or urlparse(url).hostname + async with self.request(cookies=self.sub_cookies, follow_redirects=True) as client: + html = await client.get(url) + for history in html.history: + self.sub_cookies.update(history.cookies) + if host not in self.init_sub_web: + self.init_sub_web.append(host) + return None if html.url.host != host else html diff --git a/cqwu/methods/epay/__init__.py b/cqwu/methods/epay/__init__.py new file mode 100644 index 0000000..4ae6504 --- /dev/null +++ b/cqwu/methods/epay/__init__.py @@ -0,0 +1,11 @@ +from .gen_pay_qrcode import GenPayQrcode +from .get_balance import GetBalance +from .get_orders import GetOrders + + +class EPay( + GenPayQrcode, + GetBalance, + GetOrders +): + pass diff --git a/cqwu/methods/epay/gen_pay_qrcode.py b/cqwu/methods/epay/gen_pay_qrcode.py new file mode 100644 index 0000000..66f0cef --- /dev/null +++ b/cqwu/methods/epay/gen_pay_qrcode.py @@ -0,0 +1,27 @@ +import qrcode +from bs4 import BeautifulSoup + +import cqwu + + +class GenPayQrcode: + async def gen_pay_qrcode( + self: "cqwu.Client", + ) -> None: + """ + 生成支付二维码 + """ + html = await self.oauth("http://218.194.176.214:8382/epay/thirdconsume/qrcode") + if not html: + return + soup = BeautifulSoup(html.text, "lxml") + try: + data = soup.find("input", attrs={"id": "myText"})["value"] + except (ValueError, TypeError, KeyError, IndexError): + return + qr = qrcode.QRCode() + qr.add_data(data) + qr.print_ascii(invert=True) + img = qrcode.make(data) + img.save("qrcode.png") + print("生成支付码到 qrcode.png 成功,请打开该文件查看") diff --git a/cqwu/methods/epay/get_balance.py b/cqwu/methods/epay/get_balance.py new file mode 100644 index 0000000..d9d8079 --- /dev/null +++ b/cqwu/methods/epay/get_balance.py @@ -0,0 +1,24 @@ +from typing import Optional +from bs4 import BeautifulSoup + +import cqwu + + +class GetBalance: + async def get_balance( + self: "cqwu.Client", + ) -> Optional[str]: + """ + 获取校园卡余额 + + Returns: + str: 余额 + """ + html = await self.oauth("http://218.194.176.214:8382/epay/thirdapp/balance") + if not html: + return "" + soup = BeautifulSoup(html.text, "lxml") + try: + return soup.find_all("div", "weui-cell__ft")[2].next + except (ValueError, TypeError, KeyError, IndexError): + return "" diff --git a/cqwu/methods/epay/get_orders.py b/cqwu/methods/epay/get_orders.py new file mode 100644 index 0000000..9a859dd --- /dev/null +++ b/cqwu/methods/epay/get_orders.py @@ -0,0 +1,39 @@ +from typing import List + +import cqwu +from cqwu.types.order import Order + + +class GetOrders: + async def get_orders( + self: "cqwu.Client", + page: int = 1, + page_size: int = 10, + search: str = "" + ) -> List[Order]: + """ + 获取历史订单 + + :param page: 页码 + :param page_size: 每页数量 + :param search: 搜索关键字 + + :return: 订单列表 + """ + url = "http://pay.cqwu.edu.cn/queryOrderList" + params = { + "orderno": search, + "page": page, + "pagesize": page_size, + } + if "pay.cqwu.edu.cn" not in self.init_sub_web: + await self.oauth( + "http://authserver.cqwu.edu.cn/authserver/login?service=" + "http%3A%2F%2Fpay.cqwu.edu.cn%2FsignAuthentication%3Furl%3DopenPortal") + async with self.request(cookies=self.sub_cookies, follow_redirects=True) as client: + html = await client.get(url, params=params) + try: + data = html.json()["payOrderList"] + except KeyError: + return [] + return [Order(**order) for order in data] diff --git a/cqwu/methods/users/__init__.py b/cqwu/methods/users/__init__.py new file mode 100644 index 0000000..2795c61 --- /dev/null +++ b/cqwu/methods/users/__init__.py @@ -0,0 +1,7 @@ +from .get_me import GetMe + + +class Users( + GetMe +): + pass diff --git a/cqwu/methods/users/get_me.py b/cqwu/methods/users/get_me.py new file mode 100644 index 0000000..3a80926 --- /dev/null +++ b/cqwu/methods/users/get_me.py @@ -0,0 +1,54 @@ +from typing import Optional, Union +from bs4 import BeautifulSoup + +import cqwu +from cqwu import types + + +def get_value_from_soup(soup: BeautifulSoup, attr_id: str) -> Union[type(None), str, int]: + try: + data = soup.find("input", attrs={"id": attr_id})["value"] + try: + return int(data) + except ValueError: + return data + except (ValueError, TypeError, KeyError): + return None + + +class GetMe: + async def get_me( + self: "cqwu.Client", + ) -> Optional["types.User"]: + """ + 获取个人信息 + + Returns: + types.User: 个人信息 + """ + html = await self.oauth( + "http://218.194.176.8/prizepunishnv/studentInfoManageStudentNV!forwardStudentInfo.action") + if not html: + return None + soup = BeautifulSoup(html.text, "lxml") + data = { + "username": "detail_xh", + "name": "detail_xm", + "sex": "detail_xb", + "age": "detail_nl", + "grade": "detail_nj", + "institute": "detail_xy", + "now_class": "detail_bj", + "join_year": "detail_rxnj", + "birthday": "detail_csrq", + "sfz": "detail_sfzh", + "level": "detail_pycc", + "home": "detail_hkszd", + } + temp = {key: get_value_from_soup(soup, value) for key, value in data.items()} + temp["password"] = self.password + try: + temp["specialty"] = soup.find_all("input", attrs={"id": "detail_xy"})[1]["value"] + except (ValueError, TypeError, KeyError, IndexError): + temp["specialty"] = None + return types.User(**temp) diff --git a/cqwu/types/__init__.py b/cqwu/types/__init__.py new file mode 100644 index 0000000..ee4c00b --- /dev/null +++ b/cqwu/types/__init__.py @@ -0,0 +1 @@ +from .user import User diff --git a/cqwu/types/order.py b/cqwu/types/order.py new file mode 100644 index 0000000..8c88f86 --- /dev/null +++ b/cqwu/types/order.py @@ -0,0 +1,27 @@ +import cqwu + + +class Order: + def __init__( + self, + orderno: str = None, + payproname: str = None, + orderamt: float = None, + createtime: str = None, + payflag: str = "", + **_ + ): + """ + 订单 + + :param orderno: 订单编号 + :param payproname: 缴费项目 + :param orderamt: 缴费金额 + :param createtime: 订单生成时间 + :param payflag: 缴费状态 + """ + self.order_id = orderno + self.project = payproname + self.amount = orderamt / 100 + self.create_time = createtime + self.status = cqwu.enums.OrderStatus(int(payflag)) diff --git a/cqwu/types/user.py b/cqwu/types/user.py new file mode 100644 index 0000000..6509585 --- /dev/null +++ b/cqwu/types/user.py @@ -0,0 +1,50 @@ +class User: + def __init__( + self, + username: int, + password: str = None, + name: str = None, + sex: str = None, + age: int = None, + grade: int = None, + institute: str = None, + specialty: str = None, + now_class: str = None, + join_year: int = None, + birthday: str = None, + sfz: str = None, + level: str = None, + home: str = None, + ): + """ + 一个用户 + + :param username: 学号 + :param password: 密码 + :param name: 姓名 + :param sex: 性别 + :param age: 年龄 + :param grade: 年级 + :param institute: 学院 + :param specialty: 专业 + :param now_class: 班级 + :param join_year: 入学年份 + :param birthday: 出生日期 + :param sfz: 身份证号 + :param level: 培养层次 + :param home: 户籍地址 + """ + self.username = username + self.password = password + self.name = name + self.sex = sex + self.age = age + self.grade = grade + self.institute = institute + self.specialty = specialty + self.now_class = now_class + self.join_year = join_year + self.birthday = birthday + self.sfz = sfz + self.level = level + self.home = home diff --git a/cqwu/utils/auth.py b/cqwu/utils/auth.py new file mode 100644 index 0000000..708d575 --- /dev/null +++ b/cqwu/utils/auth.py @@ -0,0 +1,39 @@ +import execjs + + +encrypt_js = """var CryptoJS=CryptoJS||function(u,p){var d={},l=d.lib={},s=function(){},t=l.Base={extend:function(a){s.prototype=this;var c=new s;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}}, +r=l.WordArray=t.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=p?c:4*a.length},toString:function(a){return(a||v).stringify(this)},concat:function(a){var c=this.words,e=a.words,j=this.sigBytes;a=a.sigBytes;this.clamp();if(j%4)for(var k=0;k>>2]|=(e[k>>>2]>>>24-8*(k%4)&255)<<24-8*((j+k)%4);else if(65535>>2]=e[k>>>2];else c.push.apply(c,e);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<< +32-8*(c%4);a.length=u.ceil(c/4)},clone:function(){var a=t.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],e=0;e>>2]>>>24-8*(j%4)&255;e.push((k>>>4).toString(16));e.push((k&15).toString(16))}return e.join("")},parse:function(a){for(var c=a.length,e=[],j=0;j>>3]|=parseInt(a.substr(j, +2),16)<<24-4*(j%8);return new r.init(e,c/2)}},b=w.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var e=[],j=0;j>>2]>>>24-8*(j%4)&255));return e.join("")},parse:function(a){for(var c=a.length,e=[],j=0;j>>2]|=(a.charCodeAt(j)&255)<<24-8*(j%4);return new r.init(e,c)}},x=w.Utf8={stringify:function(a){try{return decodeURIComponent(escape(b.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return b.parse(unescape(encodeURIComponent(a)))}}, +q=l.BufferedBlockAlgorithm=t.extend({reset:function(){this._data=new r.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=x.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,e=c.words,j=c.sigBytes,k=this.blockSize,b=j/(4*k),b=a?u.ceil(b):u.max((b|0)-this._minBufferSize,0);a=b*k;j=u.min(4*a,j);if(a){for(var q=0;q>>2]>>>24-8*(r%4)&255)<<16|(l[r+1>>>2]>>>24-8*((r+1)%4)&255)<<8|l[r+2>>>2]>>>24-8*((r+2)%4)&255,v=0;4>v&&r+0.75*v>>6*(3-v)&63));if(l=t.charAt(64))for(;d.length%4;)d.push(l);return d.join("")},parse:function(d){var l=d.length,s=this._map,t=s.charAt(64);t&&(t=d.indexOf(t),-1!=t&&(l=t));for(var t=[],r=0,w=0;w< +l;w++)if(w%4){var v=s.indexOf(d.charAt(w-1))<<2*(w%4),b=s.indexOf(d.charAt(w))>>>6-2*(w%4);t[r>>>2]|=(v|b)<<24-8*(r%4);r++}return p.create(t,r)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}})(); +(function(u){function p(b,n,a,c,e,j,k){b=b+(n&a|~n&c)+e+k;return(b<>>32-j)+n}function d(b,n,a,c,e,j,k){b=b+(n&c|a&~c)+e+k;return(b<>>32-j)+n}function l(b,n,a,c,e,j,k){b=b+(n^a^c)+e+k;return(b<>>32-j)+n}function s(b,n,a,c,e,j,k){b=b+(a^(n|~c))+e+k;return(b<>>32-j)+n}for(var t=CryptoJS,r=t.lib,w=r.WordArray,v=r.Hasher,r=t.algo,b=[],x=0;64>x;x++)b[x]=4294967296*u.abs(u.sin(x+1))|0;r=r.MD5=v.extend({_doReset:function(){this._hash=new w.init([1732584193,4023233417,2562383102,271733878])}, +_doProcessBlock:function(q,n){for(var a=0;16>a;a++){var c=n+a,e=q[c];q[c]=(e<<8|e>>>24)&16711935|(e<<24|e>>>8)&4278255360}var a=this._hash.words,c=q[n+0],e=q[n+1],j=q[n+2],k=q[n+3],z=q[n+4],r=q[n+5],t=q[n+6],w=q[n+7],v=q[n+8],A=q[n+9],B=q[n+10],C=q[n+11],u=q[n+12],D=q[n+13],E=q[n+14],x=q[n+15],f=a[0],m=a[1],g=a[2],h=a[3],f=p(f,m,g,h,c,7,b[0]),h=p(h,f,m,g,e,12,b[1]),g=p(g,h,f,m,j,17,b[2]),m=p(m,g,h,f,k,22,b[3]),f=p(f,m,g,h,z,7,b[4]),h=p(h,f,m,g,r,12,b[5]),g=p(g,h,f,m,t,17,b[6]),m=p(m,g,h,f,w,22,b[7]), +f=p(f,m,g,h,v,7,b[8]),h=p(h,f,m,g,A,12,b[9]),g=p(g,h,f,m,B,17,b[10]),m=p(m,g,h,f,C,22,b[11]),f=p(f,m,g,h,u,7,b[12]),h=p(h,f,m,g,D,12,b[13]),g=p(g,h,f,m,E,17,b[14]),m=p(m,g,h,f,x,22,b[15]),f=d(f,m,g,h,e,5,b[16]),h=d(h,f,m,g,t,9,b[17]),g=d(g,h,f,m,C,14,b[18]),m=d(m,g,h,f,c,20,b[19]),f=d(f,m,g,h,r,5,b[20]),h=d(h,f,m,g,B,9,b[21]),g=d(g,h,f,m,x,14,b[22]),m=d(m,g,h,f,z,20,b[23]),f=d(f,m,g,h,A,5,b[24]),h=d(h,f,m,g,E,9,b[25]),g=d(g,h,f,m,k,14,b[26]),m=d(m,g,h,f,v,20,b[27]),f=d(f,m,g,h,D,5,b[28]),h=d(h,f, +m,g,j,9,b[29]),g=d(g,h,f,m,w,14,b[30]),m=d(m,g,h,f,u,20,b[31]),f=l(f,m,g,h,r,4,b[32]),h=l(h,f,m,g,v,11,b[33]),g=l(g,h,f,m,C,16,b[34]),m=l(m,g,h,f,E,23,b[35]),f=l(f,m,g,h,e,4,b[36]),h=l(h,f,m,g,z,11,b[37]),g=l(g,h,f,m,w,16,b[38]),m=l(m,g,h,f,B,23,b[39]),f=l(f,m,g,h,D,4,b[40]),h=l(h,f,m,g,c,11,b[41]),g=l(g,h,f,m,k,16,b[42]),m=l(m,g,h,f,t,23,b[43]),f=l(f,m,g,h,A,4,b[44]),h=l(h,f,m,g,u,11,b[45]),g=l(g,h,f,m,x,16,b[46]),m=l(m,g,h,f,j,23,b[47]),f=s(f,m,g,h,c,6,b[48]),h=s(h,f,m,g,w,10,b[49]),g=s(g,h,f,m, +E,15,b[50]),m=s(m,g,h,f,r,21,b[51]),f=s(f,m,g,h,u,6,b[52]),h=s(h,f,m,g,k,10,b[53]),g=s(g,h,f,m,B,15,b[54]),m=s(m,g,h,f,e,21,b[55]),f=s(f,m,g,h,v,6,b[56]),h=s(h,f,m,g,x,10,b[57]),g=s(g,h,f,m,t,15,b[58]),m=s(m,g,h,f,D,21,b[59]),f=s(f,m,g,h,z,6,b[60]),h=s(h,f,m,g,C,10,b[61]),g=s(g,h,f,m,j,15,b[62]),m=s(m,g,h,f,A,21,b[63]);a[0]=a[0]+f|0;a[1]=a[1]+m|0;a[2]=a[2]+g|0;a[3]=a[3]+h|0},_doFinalize:function(){var b=this._data,n=b.words,a=8*this._nDataBytes,c=8*b.sigBytes;n[c>>>5]|=128<<24-c%32;var e=u.floor(a/ +4294967296);n[(c+64>>>9<<4)+15]=(e<<8|e>>>24)&16711935|(e<<24|e>>>8)&4278255360;n[(c+64>>>9<<4)+14]=(a<<8|a>>>24)&16711935|(a<<24|a>>>8)&4278255360;b.sigBytes=4*(n.length+1);this._process();b=this._hash;n=b.words;for(a=0;4>a;a++)c=n[a],n[a]=(c<<8|c>>>24)&16711935|(c<<24|c>>>8)&4278255360;return b},clone:function(){var b=v.clone.call(this);b._hash=this._hash.clone();return b}});t.MD5=v._createHelper(r);t.HmacMD5=v._createHmacHelper(r)})(Math); +(function(){var u=CryptoJS,p=u.lib,d=p.Base,l=p.WordArray,p=u.algo,s=p.EvpKDF=d.extend({cfg:d.extend({keySize:4,hasher:p.MD5,iterations:1}),init:function(d){this.cfg=this.cfg.extend(d)},compute:function(d,r){for(var p=this.cfg,s=p.hasher.create(),b=l.create(),u=b.words,q=p.keySize,p=p.iterations;u.length>>2]&255}};d.BlockCipher=v.extend({cfg:v.cfg.extend({mode:b,padding:q}),reset:function(){v.reset.call(this);var a=this.cfg,b=a.iv,a=a.mode;if(this._xformMode==this._ENC_XFORM_MODE)var c=a.createEncryptor;else c=a.createDecryptor,this._minBufferSize=1;this._mode=c.call(a, +this,b&&b.words)},_doProcessBlock:function(a,b){this._mode.processBlock(a,b)},_doFinalize:function(){var a=this.cfg.padding;if(this._xformMode==this._ENC_XFORM_MODE){a.pad(this._data,this.blockSize);var b=this._process(!0)}else b=this._process(!0),a.unpad(b);return b},blockSize:4});var n=d.CipherParams=l.extend({init:function(a){this.mixIn(a)},toString:function(a){return(a||this.formatter).stringify(this)}}),b=(p.format={}).OpenSSL={stringify:function(a){var b=a.ciphertext;a=a.salt;return(a?s.create([1398893684, +1701076831]).concat(a).concat(b):b).toString(r)},parse:function(a){a=r.parse(a);var b=a.words;if(1398893684==b[0]&&1701076831==b[1]){var c=s.create(b.slice(2,4));b.splice(0,4);a.sigBytes-=16}return n.create({ciphertext:a,salt:c})}},a=d.SerializableCipher=l.extend({cfg:l.extend({format:b}),encrypt:function(a,b,c,d){d=this.cfg.extend(d);var l=a.createEncryptor(c,d);b=l.finalize(b);l=l.cfg;return n.create({ciphertext:b,key:c,iv:l.iv,algorithm:a,mode:l.mode,padding:l.padding,blockSize:a.blockSize,formatter:d.format})}, +decrypt:function(a,b,c,d){d=this.cfg.extend(d);b=this._parse(b,d.format);return a.createDecryptor(c,d).finalize(b.ciphertext)},_parse:function(a,b){return"string"==typeof a?b.parse(a,this):a}}),p=(p.kdf={}).OpenSSL={execute:function(a,b,c,d){d||(d=s.random(8));a=w.create({keySize:b+c}).compute(a,d);c=s.create(a.words.slice(b),4*c);a.sigBytes=4*b;return n.create({key:a,iv:c,salt:d})}},c=d.PasswordBasedCipher=a.extend({cfg:a.cfg.extend({kdf:p}),encrypt:function(b,c,d,l){l=this.cfg.extend(l);d=l.kdf.execute(d, +b.keySize,b.ivSize);l.iv=d.iv;b=a.encrypt.call(this,b,c,d.key,l);b.mixIn(d);return b},decrypt:function(b,c,d,l){l=this.cfg.extend(l);c=this._parse(c,l.format);d=l.kdf.execute(d,b.keySize,b.ivSize,c.salt);l.iv=d.iv;return a.decrypt.call(this,b,c,d.key,l)}})}(); +(function(){for(var u=CryptoJS,p=u.lib.BlockCipher,d=u.algo,l=[],s=[],t=[],r=[],w=[],v=[],b=[],x=[],q=[],n=[],a=[],c=0;256>c;c++)a[c]=128>c?c<<1:c<<1^283;for(var e=0,j=0,c=0;256>c;c++){var k=j^j<<1^j<<2^j<<3^j<<4,k=k>>>8^k&255^99;l[e]=k;s[k]=e;var z=a[e],F=a[z],G=a[F],y=257*a[k]^16843008*k;t[e]=y<<24|y>>>8;r[e]=y<<16|y>>>16;w[e]=y<<8|y>>>24;v[e]=y;y=16843009*G^65537*F^257*z^16843008*e;b[k]=y<<24|y>>>8;x[k]=y<<16|y>>>16;q[k]=y<<8|y>>>24;n[k]=y;e?(e=z^a[a[a[G^z]]],j^=a[a[j]]):e=j=1}var H=[0,1,2,4,8, +16,32,64,128,27,54],d=d.AES=p.extend({_doReset:function(){for(var a=this._key,c=a.words,d=a.sigBytes/4,a=4*((this._nRounds=d+6)+1),e=this._keySchedule=[],j=0;j>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255]):(k=k<<8|k>>>24,k=l[k>>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255],k^=H[j/d|0]<<24);e[j]=e[j-d]^k}c=this._invKeySchedule=[];for(d=0;dd||4>=j?k:b[l[k>>>24]]^x[l[k>>>16&255]]^q[l[k>>> +8&255]]^n[l[k&255]]},encryptBlock:function(a,b){this._doCryptBlock(a,b,this._keySchedule,t,r,w,v,l)},decryptBlock:function(a,c){var d=a[c+1];a[c+1]=a[c+3];a[c+3]=d;this._doCryptBlock(a,c,this._invKeySchedule,b,x,q,n,s);d=a[c+1];a[c+1]=a[c+3];a[c+3]=d},_doCryptBlock:function(a,b,c,d,e,j,l,f){for(var m=this._nRounds,g=a[b]^c[0],h=a[b+1]^c[1],k=a[b+2]^c[2],n=a[b+3]^c[3],p=4,r=1;r>>24]^e[h>>>16&255]^j[k>>>8&255]^l[n&255]^c[p++],s=d[h>>>24]^e[k>>>16&255]^j[n>>>8&255]^l[g&255]^c[p++],t= +d[k>>>24]^e[n>>>16&255]^j[g>>>8&255]^l[h&255]^c[p++],n=d[n>>>24]^e[g>>>16&255]^j[h>>>8&255]^l[k&255]^c[p++],g=q,h=s,k=t;q=(f[g>>>24]<<24|f[h>>>16&255]<<16|f[k>>>8&255]<<8|f[n&255])^c[p++];s=(f[h>>>24]<<24|f[k>>>16&255]<<16|f[n>>>8&255]<<8|f[g&255])^c[p++];t=(f[k>>>24]<<24|f[n>>>16&255]<<16|f[g>>>8&255]<<8|f[h&255])^c[p++];n=(f[n>>>24]<<24|f[g>>>16&255]<<16|f[h>>>8&255]<<8|f[k&255])^c[p++];a[b]=q;a[b+1]=s;a[b+2]=t;a[b+3]=n},keySize:8});u.AES=p._createHelper(d)})();function _gas(data,key0, +iv0){key0=key0.replace(/(^\s+)|(\s+$)/g, "");var key = CryptoJS.enc.Utf8.parse(key0);var iv = CryptoJS.enc.Utf8.parse(iv0);var encrypted =CryptoJS.AES.encrypt(data,key,{iv:iv,mode:CryptoJS.mode.CBC,padding:CryptoJS.pad.Pkcs7});return encrypted.toString();}function encryptAES(data,_p1){if(!_p1){return data;}var encrypted =_gas(_rds(64)+data,_p1,_rds(16));return encrypted;}function _ep(p0,p1) {try{return encryptAES(p0,p1);}catch(e){}return p0; +}var $_chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';var _chars_len = $_chars.length;function _rds(len) {var retStr = '';for (i = 0; i < len; i++) {retStr += $_chars.charAt(Math.floor(Math.random() * _chars_len));}return retStr;};""" + + +def encode_password(password: str, pwd_default_encrypt_salt: str) -> str: + ctx = execjs.compile(encrypt_js) + return ctx.call("encryptAES", password, pwd_default_encrypt_salt) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3a518f5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +httpx +lxml==4.9.1 +PyExecJS==1.5.1 +beautifulsoup4==4.11.1 +qrcode==7.3.1 +pillow