Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ authors = [
{name = "Vincent Son", email = "107471539+szw0407@users.noreply.github.com"},
]
dependencies = [
"httpx>=0.25.2",
"beautifulsoup4>=4.12.2",
"sqlmodel>=0.0.14",
"pandas>=2.2.2",
"beautifulsoup4>=4.14.3",
"httpx>=0.28.1",
"pandas>=2.3.3",
"sqlmodel>=0.0.38",
]
requires-python = ">=3.10"
readme = "README.md"
Expand Down
16 changes: 8 additions & 8 deletions src/bkzhjx_login.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,18 +83,18 @@ def main(rq, semester = '', kbjcmsid = ''):
if page.status_code == 200:
print('已获取本周课表:')
page = bs4.BeautifulSoup(page.text, 'html.parser')
lessons = page.findAll('td')
lessons = page.find_all('td')
for lesson in lessons:
c = lesson.findAll('div')
c = lesson.find_all('div')
if len(c) == 0:
continue
name = c[0].find('p').text
credit = c[1].findAll('span')[0].text
time = c[1].findAll('span')[1].text
loc = c[2].findAll('span')[0].text
credit = c[1].find_all('span')[0].text
time = c[1].find_all('span')[1].text
loc = c[2].find_all('span')[0].text
# remove the img before loc
loc = re.sub(r'<img.*?>', '', str(loc))
week = c[2].findAll('span')[1].text
week = c[2].find_all('span')[1].text
week = re.sub(r'<img.*?>', '', str(week))
print(name, credit, time, loc, week)
return True
Expand All @@ -121,14 +121,14 @@ def main(rq, semester = '', kbjcmsid = ''):
print('尝试拉取所有课程:')
page = bs4.BeautifulSoup(page.text, 'html.parser')
try:
kbjcmsid = page.find(id='kbjcmsid_ul').findAll('li')[0].attrs['data-value'] # type: ignore
kbjcmsid = page.find(id='kbjcmsid_ul').find_all('li')[0].attrs['data-value'] # type: ignore
assert main(rq, semester, kbjcmsid)
except Exception as exc:
print(f'第二次尝试拉取课程列表失败——{exc}')
else:
return
print('尝试仅拉取课程信息:')
lessons = page.findAll('td')
lessons = page.find_all('td')
k=0
for lesson in lessons:
if span:=lesson.find('span').text:
Expand Down
228 changes: 138 additions & 90 deletions src/login/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,114 +63,162 @@ def webpage_login(
"""
Using webpage to login, powered by execjs, BeautifulSoup4 to parse the html and httpx to send requests.
"""
if service:
page = httpx.get(f"https://pass.sdu.edu.cn/cas/login?service={service}")
else:
page = httpx.get(r"https://pass.sdu.edu.cn/cas/login")
lt = re.findall(r'name="lt" value="(.*?)"', page.text)[0]
ul = len(username)
pl = len(password)
rsa = strEnc(username + password + lt, "1", "2", "3")
execution = re.findall(r'name="execution" value="(.*?)"', page.text)[0]
event_id = re.findall(r'name="_eventId" value="(.*?)"', page.text)[0]
content = (
f"rsa={rsa}&ul={ul}&pl={pl}&lt={lt}&execution={execution}&_eventId={event_id}"
)
det = platform_fingerprint
murmur = platform_fingerprint
murmur_s = "" # this is not checked by the server, so we can use an empty string
murmur_md5 = hashlib.md5(murmur.encode('utf-8')).hexdigest()
body1 = {
"u": strEnc(username, "1", "2", "3"),
"p": strEnc(password, "1", "2", "3"),
"m": "1", # mode 1 to get if device registered
"d": murmur,
"d_s": murmur_s,
"d_md5": murmur_md5, # md5 of d_s
}
device_status = httpx.post(
r"https://pass.sdu.edu.cn/cas/device", data=body1, cookies=page.cookies
)
device_status_dict = json.loads(device_status.text)

match device_status_dict.get("info"):
case "binded" | "pass":
pass
case "bind":
print("2FA:" + device_status_dict.get("m"))
tmp = httpx.post(
r"https://pass.sdu.edu.cn/cas/device",
data={"m": "2"},
cookies=page.cookies,
def extract_hidden_value(html: str, field: str) -> str:
match = re.search(
rf'(?:name|id)=[\'\"]{re.escape(field)}[\'\"]\s+value=[\'\"](.*?)[\'\"]',
html,
)
Comment on lines +67 to +70
if not match:
raise SystemError(
f"Cannot find {field} in login page. Check service URL or login page changes."
)
if tmp.text == r'{"info":"send"}':
print("已发送验证码")
return match.group(1)

with httpx.Client() as client:
login_url = "https://pass.sdu.edu.cn/cas/login"
params = None
if service:
if "%" in service:
login_url = f"{login_url}?service={service}"
else:
raise SystemError(f"Unknown SMS status: {tmp.text}")
body3 = {
"d": murmur_s,
"i": det,
"m": "3",
"u": username,
"c": input("请输入验证码:"),
"s": "1" if input("下次不再输入验证码?(y/N):") == "y" else "0",
}
k = httpx.post(
r"https://pass.sdu.edu.cn/cas/device",
data=body3,
cookies=page.cookies,
)
while k.text == r'{"info":"codeErr"}':
body3["c"] = input("验证码错误,请重新输入:")
k = httpx.post(
params = {"service": service}
page = client.get(login_url, params=params)

lt = extract_hidden_value(page.text, "lt")
ul = len(username)
pl = len(password)
rsa = strEnc(username + password + lt, "1", "2", "3")
execution = extract_hidden_value(page.text, "execution")
event_id = extract_hidden_value(page.text, "_eventId")

murmur_s = hashlib.sha256(platform_fingerprint.encode("utf-8")).hexdigest()
murmur_md5 = hashlib.md5(platform_fingerprint.encode("utf-8")).hexdigest()
browser_md5 = hashlib.md5(platform_fingerprint.encode("utf-8")).hexdigest()

device_base_payload = {
"u": strEnc(username, "1", "2", "3"),
"p": strEnc(password, "1", "2", "3"),
"d": platform_fingerprint,
"d_s": murmur_s,
"d_md5": strEnc(murmur_md5, "1", "2", "3"),
"d_browser_md5": strEnc(browser_md5, "1", "2", "3"),
"i": strEnc(platform_fingerprint, "1", "2", "3"),
}

payload_m1 = device_base_payload.copy()
payload_m1["m"] = "1"
device_status = client.post(
r"https://pass.sdu.edu.cn/cas/device", data=payload_m1
)
device_status_dict = json.loads(device_status.text)

match device_status_dict.get("info"):
case "binded" | "pass":
pass
case "bind":
print("2FA:" + device_status_dict.get("m"))
payload_m2 = device_base_payload.copy()
payload_m2["m"] = "2"
tmp = client.post(
r"https://pass.sdu.edu.cn/cas/device",
data=payload_m2,
)
try:
json.loads(tmp.text)
print("SMS verification code sent.")
except json.JSONDecodeError:
print(f"Warning: SMS send response: {tmp.text[:200]}")
body3 = {
"d": murmur_s,
"i": platform_fingerprint,
"m": "3",
"u": username,
"c": input("Verification Code: "),
"s": "1" if input("Remember this device? (y/N):") == "y" else "0",
}
k = client.post(
r"https://pass.sdu.edu.cn/cas/device",
data=body3,
cookies=page.cookies,
)
if k.text == r'{"info":"ok"}':
print("验证成功")
if body3["s"] == "1":
print(
f"对于设备指纹:{platform_fingerprint},下次登录将不再需要验证码"
while k.text == r'{"info":"codeErr"}':
body3["c"] = input("Wrong, please retry: ")
k = client.post(
r"https://pass.sdu.edu.cn/cas/device",
data=body3,
)
case _:
print(
"Please check your username. Device information cannot be loaded by SDU pass."
)
raise SystemError(
"Unknown device status: {}".format(str(device_status_dict))
if k.text == r'{"info":"ok"}':
print("Login successful.")
if body3["s"] == "1":
print(
"For device fingerprint: "
f"{platform_fingerprint}, the next login will no longer require a verification code"
)
else:
raise SystemError("Device verification failed: {}".format(k.text))
case _:
print(
"Please check your username. Device information cannot be loaded by SDU pass."
)
raise SystemError(
"Unknown device status: {}".format(str(device_status_dict))
)

if service:
if "%" in service:
post_url = f"https://pass.sdu.edu.cn/cas/login?service={service}"
post_params = None
else:
post_url = "https://pass.sdu.edu.cn/cas/login"
post_params = {"service": service}
else:
post_url = (
"https://pass.sdu.edu.cn/cas/login?service=http%3A%2F%2Fbkzhjx.wh.sdu.edu.cn%2Fsso.jsp"
)
cookies = page.cookies
headers = {
"Content-Type": "application/x-www-form-urlencoded",
}
page2 = httpx.post(
r"https://pass.sdu.edu.cn/cas/login?service=http%3A%2F%2Fbkzhjx.wh.sdu.edu.cn%2Fsso.jsp",
content=content,
cookies=cookies,
headers=headers,
)
if page2.status_code == 302:
return page2
else:
post_params = None
page2 = client.post(
post_url,
params=post_params,
data={
"rsa": rsa,
"ul": ul,
"pl": pl,
"lt": lt,
"execution": execution,
"_eventId": event_id,
},
)
if page2.status_code == 302:
return page2
raise SystemError("Login to pass.sdu.edu.cn failed.")


def get_username_and_password_from_stdin() -> tuple[str, str, str]:
"""
Get username and password from stdin, and get the platform fingerprint from Fingerprint.txt.
"""
if not os.path.exists("Fingerprint.txt"):
# create fingerprint file which should be empty at first
with open("Fingerprint.txt", "w", encoding='utf-8') as f:
with open("Fingerprint.txt", "w", encoding="utf-8") as f:
f.write("")
print("生成默认指纹文件,请修改Fingerprint.txt中的信息后重新运行;或者使用空指纹登录。")
print(
"生成默认指纹文件,请修改Fingerprint.txt中的信息后重新运行;或者使用空指纹登录。"
)
print("指纹文件路径:", os.path.abspath("Fingerprint.txt"))
print("此文件作为设备的标志。其内容如果不改变,就不必重复输入短信验证码。如果更换设备,可以直接拷贝走这个文件。")
with open("Fingerprint.txt", "r", encoding='utf-8') as f:
print(
"此文件作为设备的标志。其内容如果不改变,就不必重复输入短信验证码。如果更换设备,可以直接拷贝走这个文件。"
)
with open("Fingerprint.txt", "r", encoding="utf-8") as f:
fake_platoform_fingerprint = f.read()
if not fake_platoform_fingerprint:
if not input("当前指纹文件为空。指纹文件存放在:"+os.path.abspath("Fingerprint.txt")+"\n是否继续运行程序并使用空指纹作为设备标志?(y/N)") == "y":
if (
not input(
"当前指纹文件为空。指纹文件存放在:"
+ os.path.abspath("Fingerprint.txt")
+ "\n是否继续运行程序并使用空指纹作为设备标志?(y/N)"
)
== "y"
):
sys.exit(0)
username = input('学号:')
password = getpass.getpass('密码:')
username = input("学号:")
password = getpass.getpass("密码:")
return username, password, fake_platoform_fingerprint
Loading
Loading