-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathmain.py
More file actions
261 lines (221 loc) · 9.68 KB
/
main.py
File metadata and controls
261 lines (221 loc) · 9.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
'''
Author: LetMeFly, Guo-Chenxu, Crsuh2er0, 420xincheng, tkzzzzzz6
Date: 2023-09-12 20:49:21
LastEditors: LetMeFly.xyz
LastEditTime: 2025-11-13 22:44:00
Description: 开源于https://github.com/LetMeFly666/YuketangAutoPlayer 欢迎issue、PR
'''
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.remote.webelement import WebElement
from typing import List
from time import sleep
import random
import os
import sys
import configparser
def create_config_template(config_path):
"""创建config.ini模板文件"""
config = configparser.ConfigParser()
config['Settings'] = {
'headless': 'false',
'course_url': 'https://在此填写你的课程URL',
'cookie': '在此填写你的sessionid',
'implicitly_wait': '10'
}
try:
with open(config_path, 'w', encoding='utf-8') as f:
f.write('; YuketangAutoPlayer 配置文件\n')
f.write('; headless: 是否无窗口运行 (true/false),首次使用建议false\n')
f.write('; course_url: 你的课程地址,必须包含 https:// 协议头\n')
f.write('; 例如: https://www.yuketang.cn/v2/web/studentLog/12345678\n')
f.write('; cookie: 你的sessionid值,获取方式见README.md\n')
f.write('; implicitly_wait: 元素查找超时时间(秒),一般不需要修改\n\n')
config.write(f)
except Exception as e:
print(f'创建配置文件失败: {e}')
def load_config():
"""从config.ini加载配置"""
config = configparser.ConfigParser()
# 优先从可执行文件所在目录查找config.ini(支持打包后的exe)
config_path = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), 'config.ini')
if not os.path.exists(config_path):
# 如果exe目录没有,尝试从当前工作目录查找
config_path = os.path.join(os.getcwd(), 'config.ini')
if not os.path.exists(config_path):
# 如果config.ini不存在,创建模板
print(f'未找到配置文件,正在创建模板: {config_path}')
create_config_template(config_path)
print('\n请编辑 config.ini 文件填写你的配置信息后重新运行程序')
input('按回车键退出...')
sys.exit(0)
try:
config.read(config_path, encoding='utf-8')
print(f'成功加载配置文件: {config_path}')
return config
except Exception as e:
print(f'读取配置文件失败: {e}')
input('按回车键退出...')
sys.exit(1)
# 加载配置
print('='*50)
print('YuketangAutoPlayer - 雨课堂视频自动播放器')
print('='*50)
config = load_config()
# 读取配置项
try:
IF_HEADLESS = config.getboolean('Settings', 'headless', fallback=False)
COURSE_URL = config.get('Settings', 'course_url', fallback='')
COOKIE = config.get('Settings', 'cookie', fallback='')
IMPLICITLY_WAIT = config.getint('Settings', 'implicitly_wait', fallback=10)
except Exception as e:
print(f'\n配置文件格式错误: {e}')
input('按回车键退出...')
sys.exit(1)
# 验证配置
if not COURSE_URL or not COOKIE or '在此填写' in COURSE_URL or '在此填写' in COOKIE:
print('\n错误: 检测到配置文件未正确填写!')
print('请编辑 config.ini 文件,填写正确的 course_url 和 cookie')
input('按回车键退出...')
sys.exit(1)
print(f'\n配置信息:')
print(f' 无窗口模式: {IF_HEADLESS}')
print(f' 课程URL: {COURSE_URL[:50]}...' if len(COURSE_URL) > 50 else f' 课程URL: {COURSE_URL}')
print(f' Cookie已配置: ✓')
print('='*50 + '\n')
option = webdriver.ChromeOptions()
if IF_HEADLESS:
option.add_argument('--headless')
driver = webdriver.Chrome(options=option)
driver.maximize_window()
driver.implicitly_wait(IMPLICITLY_WAIT)
IS_COMMONUI = False
def str2dic(s):
d = dict()
for i in s.split('; '):
temp = i.split('=')
d[temp[0]] = temp[1]
return d
def setCookie(cookies):
driver.delete_all_cookies()
for name, value in cookies.items():
driver.add_cookie({'name': name, 'value': value, 'path': '/'})
def ifVideo(div: WebElement):
for i in div.find_elements(By.TAG_NAME, 'i'):
i_class = i.get_attribute('class')
if 'icon--suo' in i_class: # 锁的图标,表明视频未开放
return False
if IS_COMMONUI: # www.yuketang.cn,非grsbupt.yuketang.cn,属新版ui
try:
span = div.find_element(By.CSS_SELECTOR, 'span.leaf-flag')
except:
return False
return '视频' in span.text.strip()
try:
i = div.find_element(By.TAG_NAME, 'i')
except:
return False # 每个小结后面都存在空行<li>
i_class = i.get_attribute('class')
return 'icon--shipin' in i_class
def getAllvideos_notFinished(allClasses: List[WebElement]):
driver.implicitly_wait(0.1) # 找不到元素时会找满implicitly_wait秒
allVideos = []
for thisClass in allClasses:
if ifVideo(thisClass) and '已完成' not in thisClass.text and '截止' not in thisClass.text:
print(f'找到未完成的视频: {thisClass.text.strip()}')
allVideos.append(thisClass)
driver.implicitly_wait(IMPLICITLY_WAIT)
return allVideos
def get1video_notFinished(allClasses: List[WebElement]):
for thisClass in allClasses:
if ifVideo(thisClass) and '已完成' not in thisClass.text and '截止' not in thisClass.text:
return thisClass
return None
homePageURL = 'https://' + COURSE_URL.split('https://')[1].split('/')[0] + '/'
if 'www.yuketang.cn' in homePageURL:
IS_COMMONUI = True
# driver.get('https://grsbupt.yuketang.cn/')
driver.get(homePageURL)
setCookie({'sessionid': COOKIE})
driver.get(COURSE_URL)
sleep(3)
if 'pro/portal/home' in driver.current_url:
print('cookie失效或设置有误,请重设cookie或选择每次扫码登录')
driver.get(homePageURL)
driver.find_element(By.CLASS_NAME, 'login-btn').click()
print("请扫码登录")
while 'courselist' not in driver.current_url: # 判断是否已经登录成功
sleep(0.5)
print('登录成功')
driver.get(COURSE_URL)
def change2speed2():
speedbutton = driver.find_element(By.TAG_NAME, 'xt-speedbutton')
ActionChains(driver).move_to_element(speedbutton).perform()
ul = speedbutton.find_element(By.TAG_NAME, 'ul')
lis = ul.find_elements(By.TAG_NAME, 'li')
li_speed2 = lis[0]
diffY = speedbutton.location['y'] - li_speed2.location['y']
# ActionChains(driver).move_to_element_with_offset(speedbutton, 3, 5).perform()
# ActionChains(driver).click().perform()
# 我也不知道为啥要一点一点移动上去,反正直接移动上去的话,点击是无效的
for i in range(diffY // 10): # 可能不是一个好算法
ActionChains(driver).move_by_offset(0, -10).perform()
sleep(0.5)
sleep(0.8)
ActionChains(driver).click().perform()
def mute1video():
if driver.execute_script('return video.muted;'):
return
voice = driver.find_element(By.TAG_NAME, 'xt-volumebutton')
ActionChains(driver).move_to_element(voice).perform()
ActionChains(driver).click().perform()
def finish1video():
if IS_COMMONUI:
# TODO: WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "xxx")))
# then remove the sleep before calling function finish1video
# todo中改动较大需测试面较广,先用sleep代替
scoreList = driver.find_element(By.ID, 'tab-student_school_report')
scoreList.click()
allClasses = driver.find_elements(By.CLASS_NAME, 'study-unit')
else:
allClasses = driver.find_elements(By.CLASS_NAME, 'leaf-detail')
print('正在寻找未完成的视频,请耐心等待')
allVideos = getAllvideos_notFinished(allClasses)
if not allVideos:
return False
video = allVideos[0]
driver.execute_script('arguments[0].scrollIntoView(false);', video)
if IS_COMMONUI:
span = video.find_element(By.TAG_NAME, 'span')
span.click()
else:
video.click()
print('正在播放')
driver.switch_to.window(driver.window_handles[-1])
WebDriverWait(driver, 10).until(lambda x: driver.execute_script('video = document.querySelector("video"); console.log(video); return video;')) # 这里即使2次sleep3s选中的video还是null
driver.execute_script('videoPlay = setInterval(function() {if (video.paused) {video.play();}}, 200);')
driver.execute_script('setTimeout(() => clearInterval(videoPlay), 5000)')
driver.execute_script('addFinishMark = function() {finished = document.createElement("span"); finished.setAttribute("id", "LetMeFly_Finished"); document.body.appendChild(finished); console.log("Finished");}')
driver.execute_script('lastDuration = 0; setInterval(() => {nowDuration = video.currentTime; if (nowDuration < lastDuration) {addFinishMark()}; lastDuration = nowDuration}, 200)')
driver.execute_script('video.addEventListener("pause", () => {video.play()})')
mute1video()
change2speed2()
while True:
if driver.execute_script('return document.querySelector("#LetMeFly_Finished");'):
print('finished, wait 5s')
sleep(5) # 再让它播5秒
driver.close()
driver.switch_to.window(driver.window_handles[-1])
return True
else:
print(f'正在播放视频 | not finished yet | 随机数: {random.random()}')
sleep(3)
return False
while finish1video():
driver.refresh()
sleep(5) # thanks for @420xincheng's #8
driver.quit()
print('恭喜你!全部播放完毕')
sleep(5)