NJUlogin 3.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
NJUlogin/QRlogin.py ADDED
@@ -0,0 +1,133 @@
1
+ import requests
2
+ import numpy as np
3
+ import time
4
+ from PIL import Image
5
+ from io import BytesIO
6
+ import os
7
+ from lxml import etree
8
+ from user_agents import parse
9
+
10
+ from .utils import config, urls, get_post
11
+ from .utils.clear import clear
12
+ from .base import baseLogin
13
+
14
+
15
+ class QR(object):
16
+ def __init__(self, session: requests.Session, getTimeout: int):
17
+ self.session = session
18
+ self.getTimeout = getTimeout
19
+
20
+ def getQR(self) -> np.ndarray:
21
+ """获取二维码图片,返回numpy数组"""
22
+ self.ts = int(time.time() * 1000)
23
+ url = urls.QRid % self.ts
24
+ QRid = get_post.get(self.session, url, timeout=self.getTimeout).text
25
+ self.QRid = QRid
26
+ url = urls.QRimg % QRid
27
+ QRdata = get_post.get(self.session, url, timeout=self.getTimeout).content
28
+ qr = Image.open(BytesIO(QRdata)).convert("L")
29
+ return np.array(qr)[6:-6, 6:-6]
30
+
31
+ def printQR(self):
32
+ """打印二维码至终端,同时也会保存一份QR.png到当前目录"""
33
+ QRimg = self.getQR()
34
+ Image.fromarray(QRimg).save("QR.png")
35
+ char_full = "\u2588"
36
+ char_up = "\u2580"
37
+ char_down = "\u2584"
38
+ for i in range(0, QRimg.shape[0] - 3, 6):
39
+ for j in range(0, QRimg.shape[1], 3):
40
+ if QRimg[i, j] < 128 and QRimg[i + 3, j] < 128:
41
+ print(char_full, end="")
42
+ elif QRimg[i, j] < 128 and QRimg[i + 3, j] >= 128:
43
+ print(char_up, end="")
44
+ elif QRimg[i, j] >= 128 and QRimg[i + 3, j] < 128:
45
+ print(char_down, end="")
46
+ else:
47
+ print(" ", end="")
48
+ print("")
49
+ for j in range(0, QRimg.shape[1], 3):
50
+ if QRimg[-1, j] < 128:
51
+ print(char_up, end="")
52
+ else:
53
+ print(" ", end="")
54
+ print("")
55
+
56
+
57
+ class QRlogin(baseLogin):
58
+ def __init__(self, loginTimeout: int = config.loginTimeout, *args, **kwargs):
59
+ """
60
+ QRlogin(loginTimeout: int = config.loginTimeout, headers: dict = config.headers, getTimeout: int = config.getTimeout)
61
+ @description:
62
+ 二维码登录
63
+ -------
64
+ @param:
65
+ loginTimeout: int, 登录超时时间,即二维码有效时间
66
+ headers: dict, 请求头
67
+ getTimeout: int, 请求超时时间,即在getTimeout秒内未获取到响应则抛出TimeoutError
68
+ -------
69
+ """
70
+ super().__init__(*args, **kwargs)
71
+ assert parse(self.session.headers["User-Agent"]).is_pc, "扫码登录只支持PC端"
72
+ self.loginTimeout = loginTimeout
73
+
74
+ def getStatus(self, qr: QR) -> str:
75
+ """等候扫码,返回扫码状态"""
76
+ url = urls.status % (qr.ts, qr.QRid)
77
+ status = self.get(url, timeout=self.getTimeout).text
78
+ return status
79
+
80
+ def waitingLogin(self, qr: QR) -> bool:
81
+ """等候登录,返回登录状态"""
82
+ # 0: 未扫码, 1: 登录成功, 2: 已扫码未确认登录
83
+ first0, first2 = False, False
84
+ for _ in range(self.loginTimeout):
85
+ status = self.getStatus(qr)
86
+ try:
87
+ status = int(status)
88
+ if status not in [0, 1, 2]:
89
+ raise ValueError
90
+ except ValueError:
91
+ raise ValueError("未知状态,代码可能需要维护")
92
+ if status == 0 and not first0:
93
+ print("微信或南京大学APP扫码登录")
94
+ first0 = True
95
+ elif status == 2 and not first2:
96
+ print("扫描成功,请在手机上『确认登录』")
97
+ first2 = True
98
+ elif status == 1:
99
+ clear()
100
+ return True
101
+ time.sleep(1)
102
+ clear()
103
+ print("登录超时")
104
+ return False
105
+
106
+ def login(self, dest: str = None) -> requests.Session:
107
+ if dest is not None:
108
+ url = urls.login % dest
109
+ else:
110
+ url = urls.login.split("?")[0]
111
+ html = self.get(url, timeout=self.getTimeout).text
112
+ qr = QR(self.session, self.getTimeout)
113
+ clear()
114
+ qr.printQR()
115
+ if not self.waitingLogin(qr):
116
+ return None
117
+
118
+ selector = etree.HTML(html)
119
+ data = {
120
+ "lt": selector.xpath('//input[@name="lt"]/@value')[1],
121
+ "uuid": qr.QRid,
122
+ "dllt": selector.xpath('//input[@name="dllt"]/@value')[1],
123
+ "execution": selector.xpath('//input[@name="execution"]/@value')[1],
124
+ "_eventId": selector.xpath('//input[@name="_eventId"]/@value')[1],
125
+ "rmShown": selector.xpath('//input[@name="rmShown"]/@value')[1],
126
+ }
127
+ res = self.post(url, data=data, timeout=self.getTimeout)
128
+ if self.judge_not_login(res, url):
129
+ print("登录失败")
130
+ return None
131
+ os.remove("QR.png")
132
+ self.response = res
133
+ return self.session
NJUlogin/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from .base import baseLogin
2
+ from .QRlogin import QRlogin
3
+ from .pwdLogin import pwdLogin
NJUlogin/base.py ADDED
@@ -0,0 +1,101 @@
1
+ import requests
2
+ import json
3
+ from lxml import etree
4
+ from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
5
+ from cryptography.hazmat.primitives import hashes
6
+ from cryptography.hazmat.backends import default_backend
7
+ from cryptography.fernet import Fernet
8
+ import base64
9
+ import os
10
+
11
+ from .utils import config, get_post, urls
12
+
13
+
14
+ class baseLogin(object):
15
+ def __init__(
16
+ self, headers: dict = config.headers, getTimeout: int = config.getTimeout
17
+ ):
18
+ self.session = requests.Session()
19
+ self.session.headers.update(headers)
20
+ self.getTimeout = getTimeout
21
+
22
+ def get(self, url: str, **kwargs) -> requests.Response:
23
+ return get_post.get(self.session, url, **kwargs)
24
+
25
+ def post(self, url: str, data: dict, **kwargs) -> requests.Response:
26
+ return get_post.post(self.session, url, data, **kwargs)
27
+
28
+ def judge_not_login(self, html: requests.Response, loginurl: str) -> bool:
29
+ """判断是否登录成功"""
30
+ return (
31
+ html is None or html.url == loginurl or html.url.endswith("security_check")
32
+ )
33
+
34
+ def logout(self) -> None:
35
+ """退出登录"""
36
+ self.get(urls.logout, timeout=self.getTimeout)
37
+
38
+ def logout_all(self) -> None:
39
+ """退出所有设备的登录"""
40
+ html = self.get(urls.onlineList, timeout=self.getTimeout)
41
+ selector = etree.HTML(html.text)
42
+ OnlineList = selector.xpath('//input[@value="踢出"]/@onclick')
43
+ for item in OnlineList:
44
+ tokenId = item.split("'")[1]
45
+ self.post(urls.logoutOthers, data={"tokenId": tokenId})
46
+
47
+ @property
48
+ def available(self) -> bool:
49
+ """判断是否登录成功"""
50
+ html = self.get(urls.index, timeout=self.getTimeout)
51
+ return html.url == urls.index
52
+
53
+ def export(self, filename: str, password: str = None) -> None:
54
+ """导出登录信息"""
55
+ if not self.available:
56
+ raise ValueError("未登录,无法导出登录信息")
57
+ if password is None:
58
+ with open(filename, "w") as f:
59
+ json.dump(self.session.cookies.get_dict(), f, indent=2)
60
+ else:
61
+ cookies = json.dumps(self.session.cookies.get_dict(), indent=2).encode()
62
+ salt = os.urandom(16)
63
+ kdf = PBKDF2HMAC(
64
+ algorithm=hashes.SHA256(),
65
+ length=32,
66
+ salt=salt,
67
+ iterations=100000,
68
+ backend=default_backend(),
69
+ )
70
+ key = base64.urlsafe_b64encode(kdf.derive(password.encode()))
71
+ fernet = Fernet(key)
72
+ enc_cookie = fernet.encrypt(cookies)
73
+ with open(filename, "wb") as f:
74
+ f.write(enc_cookie + salt)
75
+
76
+ def load(self, filename: str, password: str = None) -> None:
77
+ """加载登录信息"""
78
+ if password is None:
79
+ try:
80
+ with open(filename, "r") as f:
81
+ cookies = json.load(f)
82
+ except UnicodeDecodeError:
83
+ raise RuntimeError("请提供密码")
84
+ else:
85
+ with open(filename, "rb") as f:
86
+ enc_cookie = f.read()
87
+ salt = enc_cookie[-16:]
88
+ enc_cookie = enc_cookie[:-16]
89
+ kdf = PBKDF2HMAC(
90
+ algorithm=hashes.SHA256(),
91
+ length=32,
92
+ salt=salt,
93
+ iterations=100000,
94
+ backend=default_backend(),
95
+ )
96
+ key = base64.urlsafe_b64encode(kdf.derive(password.encode()))
97
+ fernet = Fernet(key)
98
+ cookies = json.loads(fernet.decrypt(enc_cookie))
99
+ self.session.cookies.update(cookies)
100
+ if not self.available:
101
+ raise ValueError("登录信息已失效,请重新登录")
@@ -0,0 +1 @@
1
+ from .ocr import CaptchaOCR