Skip to content

Commit

Permalink
fix: 修复身份认证的问题,现在获取课程信息前要登陆
Browse files Browse the repository at this point in the history
  • Loading branch information
AuYang261 committed Aug 12, 2024
1 parent bfa97f6 commit dbbaa37
Show file tree
Hide file tree
Showing 14 changed files with 174 additions and 32 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ whisper_models/
release_*/
*.json
.idea
.DS_Store
.DS_Store
auth.txt
50 changes: 40 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,43 @@

![image-20231018204208066](md/README/image-20231018204208066.png)

### 网页
### 登陆延河课堂

新版的延河课堂要求登陆才能查看课程列表,故需要先自行登陆延河课堂。登陆后,在延河课堂的页面的地址栏输入如下代码(注意,浏览器会自动去掉前缀"javascript:",故直接复制粘贴后需手动补上):

```
javascript:alert(JSON.parse(localStorage.auth).token)
```

![image-20240809182406184](md/README/image-20240809182406184.png)

回车后会弹出提示框,复制该身份认证码。

![image-20240809182413373](md/README/image-20240809182413373.png)

或者可以按`F12`键打开”控制台“,在其中输入上述代码,也能得到身份认证码。

### 网页GUI交互

双击运行`webui_interface.exe`文件打开网页服务器,会自动弹出浏览器网页。

而后在打开的网页中新建任务即可。

![image-20240529174739174](md/README/image-20240529174739174.png)
下载类型可选摄像头(即教室后的摄像头录像)或电脑屏幕(即教室电脑的屏幕信号)。

可以选择是否下载教室蓝牙话筒信号(该课程有蓝牙话筒信号时有效),若老师未使用蓝牙话筒则该信号没有声音。

![image-20240529171709402](md/README/image-20240529171709402.png)

首次使用或之前的登陆失效时,需要输入上述获取的身份认证码。

若之前使用过本工具(包括其他交互方式),登陆未失效,身份认证码会自动保存,无需每次都填写。

![image-20240809182420653](md/README/image-20240809182420653.png)

下载完成的文件在`output/`目录下以`课程名-video/screen`格式命名的文件夹中。若下载了蓝牙音频则保存在和视频同目录同名的`.aac`文件中。

![image-20230926124922726](md/README/image-20230926124922726.png)

### 命令行GUI交互

Expand All @@ -32,29 +62,29 @@

![image-20240413001734218](md/README/image-20240413001734218.png)

同样,首次使用或之前的登陆失效时,需要输入上述获取的身份认证码;登陆未失效则不用。

![image-20240809183350633](md/README/image-20240809183350633.png)

<img src="md/README/image-20240413002004628.png" alt="image-20240413002004628" style="zoom:80%;" />

按键盘上下键移动光标,按空格选择/取消选择,至少需要选择一个视频。选择完成后按回车确认。若想退出按q键即可。

确认后,选择要下载的信号,可选摄像头(即教室后的摄像头录像)或电脑屏幕(即教室电脑的屏幕信号),同样至少需要选择一个信号,选择完成后按回车确认。
确认后,选择要下载的信号,同样至少需要选择一个信号,选择完成后按回车确认。

![image-20240413002242979](md/README/image-20240413002242979.png)



而后选择是否下载教室蓝牙话筒信号,若老师未使用蓝牙话筒则该信号没有声音。选择完成后按回车确认。开始下载。按`ctrl+c`停止。

![image-20240529171709402](md/README/image-20240529171709402.png)
而后选择是否下载教室蓝牙话筒信号,选择完成后按回车确认。开始下载。按`ctrl+c`停止。

![image-20240529171253980](md/README/image-20240529171253980.png)

下载完成的文件在`output/`目录下以`课程名-video/screen`格式命名的文件夹中。若下载了蓝牙音频则保存在和视频同目录同名的`.aac`文件中。

![image-20230926124922726](md/README/image-20230926124922726.png)

### 原始交互方式

若使用上述GUI显示有问题,可直接使用原始交互方式。双击运行`main.exe`文件,并输入你想下载的课程编号(40524)。输出课程视频列表:
若使用上述GUI显示有问题,可直接使用原始交互方式。双击运行`main.exe`文件,并输入你想下载的课程编号(40524)和身份认证码(如果需要)。输出课程视频列表:

![image-20240529171540279](md/README/image-20240529171540279.png)

Expand Down Expand Up @@ -122,7 +152,7 @@ pip install pyinstaller
# 打包
pyinstaller -F main.py
pyinstaller -F gui.py
pyinstaller -F webui_interface.py --add-data webui:webui
pyinstaller -F webui_interface.py --add-data webui:webui --add-data templates:templates
pyinstaller -F gen_caption.py
```
打包`gen_caption.py`时可能会失败,提示递归过深:
Expand Down
19 changes: 15 additions & 4 deletions gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,22 @@ def config(stdscr):
draw_line(stdscr, f"{url_base}", 1)

# 等待用户输入字符串并显示它
courseID = stdscr.getstr()
courseID = stdscr.getstr().decode("utf-8")
if not courseID:
sys.exit()
videoList, courseName, professor = utils.get_course_info(
courseID=courseID.decode("utf-8")
)

if not utils.read_auth() or not utils.test_auth(courseID=courseID):
stdscr.clear()
for i, line in enumerate(utils.auth_prompt()):
draw_line(stdscr, line, i)
auth = stdscr.getstr().decode("utf-8")
utils.write_auth(auth)
if not utils.test_auth(courseID=courseID):
stdscr.clear()
draw_line(stdscr, "身份验证失败", 0)
stdscr.getch()
sys.exit()
videoList, courseName, professor = utils.get_course_info(courseID=courseID)

selected_videos = []

Expand Down Expand Up @@ -170,6 +180,7 @@ def get_cmd_window_size(stdscr):
return stdscr.getmaxyx()


@utils.print_help
def main():
global align
align = 25
Expand Down
23 changes: 10 additions & 13 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,23 @@
}


@utils.print_help
def main():
if len(sys.argv) == 1:
courseID = input("输 入 课 程 ID: ")
else:
courseID = sys.argv[1]

if not utils.read_auth() or not utils.test_auth(courseID=courseID):
auth = input("。".join(utils.auth_prompt()))
utils.write_auth(auth)
if not utils.test_auth(courseID=courseID):
print("身份验证失败")
sys.exit()
videoList, courseName, professor = utils.get_course_info(courseID=courseID)

print(f"课 程 名: {courseName}")

for i, c in enumerate(videoList):
print(f"[{i}]: ", c["title"])

Expand Down Expand Up @@ -59,16 +68,4 @@ def main():


if __name__ == "__main__":
try:
main()
# cProfile.run('main()', 'output/profile.txt')
except Exception as e:
print(e)
print(
"If the problem is still not solved, you can report an issue in https://github.com/AuYang261/BIT_yanhe_download/issues."
)
print("Or contact with the author [email protected]. Thanks for your report!")
print(
"如果问题仍未解决,您可以在https://github.com/AuYang261/BIT_yanhe_download/issues 中报告问题。"
)
print("或者联系作者[email protected]。感谢您的报告!")
main()
Binary file removed md/README/image-20240529174739174.png
Binary file not shown.
Binary file added md/README/image-20240809182344017.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added md/README/image-20240809182406184.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added md/README/image-20240809182413373.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added md/README/image-20240809182420653.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added md/README/image-20240809183350633.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions webui/index.html → templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
<div class="form-group">
<label for="courseId">课程ID号:</label>
<input type="text" id="courseId" name="courseId" class="input-field" />
<label for="auth">身份认证码: </label>
<p id="auth_prompt">{{ auth_prompt }}</p>
<input type="text" id="auth" name="auth" class="input-field" placeholder="{{ auth }}" />
<button type="button" class="btn" onclick="fetchCourseNumber()">
获取课程信息
</button>
Expand Down
77 changes: 77 additions & 0 deletions utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,33 @@
import m3u8dl
import time
from hashlib import md5
import os


headers = {
"Origin": "https://www.yanhekt.cn",
"Referer": "https://www.yanhekt.cn/",
"xdomain-client": "web_user",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.26",
"Xdomain-Client": "web_user",
"Xclient-Signature": "e6e3bf5851b0e4af888cb4bc1938c568",
"Xclient-Version": "v1",
"Xclient-Timestamp": str(int(time.time())),
"Authorization": "",
}
magic = "1tJrMwNq3h0yLgx86Rued2J1tFc"


def auth_prompt(code=True):
return [
"请先在浏览器登陆延河课堂",
"并在延河课堂的地址栏输入 javascript:alert(JSON.parse(localStorage.auth).token)",
'注意粘贴时浏览器会自动去掉"javascript:",需要手动补上',
"或者按F12打开控制台粘贴这段代码",
"然后将弹出的内容粘贴到" + ("这里:" if code else '"身份认证码"栏'),
]


def encryptURL(url):
url_list = url.split("/")
# "a97f12c055a10ee51d60e441e618bfef"
Expand All @@ -33,6 +49,14 @@ def getToken():
"https://cbiz.yanhekt.cn/v1/auth/video/token?id=0", headers=headers
)
data = req.json()["data"]
if not data:
read_auth()
req = requests.get(
"https://cbiz.yanhekt.cn/v1/auth/video/token?id=0", headers=headers
)
data = req.json()["data"]
if not data:
raise Exception("获取Token失败")
return data["token"]


Expand All @@ -50,6 +74,39 @@ def add_signature_for_url(url, token, timestamp, signature):
return url


def read_auth():
if not os.path.exists("auth.txt"):
return ""
with open("auth.txt", "r") as f:
auth = f.read().strip()
headers["Authorization"] = "Bearer " + auth
return auth


def write_auth(auth):
headers["Authorization"] = "Bearer " + auth
with open("auth.txt", "w") as f:
f.write(auth)


def remove_auth():
headers["Authorization"] = ""
if os.path.exists("auth.txt"):
os.remove("auth.txt")


def test_auth(courseID):
"""
Test if the auth in headers is valid.
Return True if the auth is valid, otherwise False.
"""
res = requests.get(
f"https://cbiz.yanhekt.cn/v2/course/session/list?course_id={courseID}",
headers=headers,
)
return bool(res.json()["data"])


def get_course_info(courseID):
courseID = courseID.strip()

Expand Down Expand Up @@ -104,3 +161,23 @@ def download_audio(url, path, name):
res = requests.get(url, headers=_headers)
with open(f"{path}/{name}.aac", "wb") as f:
f.write(res.content)


def print_help(f: callable):
def wrap():
try:
f()
except Exception as e:
print(e)
print(
"If the problem is still not solved, you can report an issue in https://github.com/AuYang261/BIT_yanhe_download/issues."
)
print(
"Or contact with the author [email protected]. Thanks for your report!"
)
print(
"如果问题仍未解决,您可以在https://github.com/AuYang261/BIT_yanhe_download/issues 中报告问题。"
)
print("或者联系作者[email protected]。感谢您的报告!")

return wrap
8 changes: 6 additions & 2 deletions webui/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@ document.getElementsByClassName("close")[0].onclick = function () {

// Implement the logic to fetch course number and handle form submission
function fetchCourseNumber() {
fetch(`/get_course?course_id=${document.getElementById("courseId").value}`)
fetch(`/get_course?course_id=${document.getElementById("courseId").value}&auth=${document.getElementById("auth").value}`)
.then((response) => response.json())
.then((data) => {
console.log(data);
if (data.code && data.code == 403) {
document.getElementById("auth_prompt").innerHTML = data.msg;
alert(data.msg);
}
document.getElementById("courseList").innerHTML = ``;
document.getElementById("courseName11").innerHTML = `课程名: <b>${data.courseName == "" ? "未知" : data.courseName
}</b>`;
Expand Down Expand Up @@ -186,4 +190,4 @@ function selectAll(select) {
for (let i = 0; i < list.childNodes.length; i++) {
list.childNodes[i].className = select ? "selected" : "";
}
}
}
23 changes: 21 additions & 2 deletions webui_interface.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
from flask import Flask, request, jsonify, send_from_directory
from flask import (
Flask,
request,
jsonify,
send_from_directory,
render_template,
url_for,
redirect,
)
import os
import requests
import m3u8dl
Expand Down Expand Up @@ -134,12 +142,23 @@ def execute_tasks():

@app.route("/")
def index():
return send_from_directory(app.static_folder, "index.html")
auth = utils.read_auth()
return render_template(
"index.html",
auth=auth,
auth_prompt="" if auth else "。".join(utils.auth_prompt()),
)


@app.route("/get_course")
def get_course():
course_id = request.args.get("course_id")
auth = request.args.get("auth")
if auth:
utils.write_auth(auth)
if not utils.test_auth(courseID=course_id):
utils.remove_auth()
return jsonify({"code": 403, "msg": "。".join(utils.auth_prompt(False))})
try:
videoList, courseName, professor = utils.get_course_info(courseID=course_id)
except:
Expand Down

0 comments on commit dbbaa37

Please sign in to comment.