Skip to content

Commit

Permalink
支持重复刷课
Browse files Browse the repository at this point in the history
  • Loading branch information
CXRunfree committed May 23, 2024
1 parent 6498bb8 commit 7fff507
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 105 deletions.
135 changes: 103 additions & 32 deletions Autovisor.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ def optimize_page(page: Page):
if hour >= 18 or hour < 7:
page.wait_for_selector(".Patternbtn-div")
page.evaluate(config.night_js)
# 关闭右侧AI助手
page.wait_for_selector(".show-icon.icon-appear", timeout=1500)
page.evaluate(config.close_assist)
# 关闭上方横幅
page.wait_for_selector(".exploreTip", timeout=1000)
page.query_selector('a:has-text("不再提示")').click()
Expand Down Expand Up @@ -109,40 +112,59 @@ def play_video(page: Page):

def skip_questions(page: Page):
try:
page.wait_for_selector(".topic-item", state="attached",timeout=1000)
page.wait_for_selector(".topic-item", state="attached", timeout=1000)
except TimeoutError:
return
if not page.query_selector(".answer"):
choices = page.locator(".topic-item").all()
# 直接遍历点击所有选项
for each in choices:
each.click()
page.wait_for_timeout(200)
#close = page.locator('//div[@class="btn"]')
#close.click()
page.wait_for_timeout(100)
# close = page.locator('//div[@class="btn"]')
# close.click()
# js模拟ESC键关闭更快
page.evaluate(config.close_ques)


def get_filtered_class(page: Page) -> List[Locator]:
def get_filtered_class(page: Page, enableRepeat=False) -> List[Locator]:
try:
page.wait_for_selector(".time_icofinish", timeout=1000)
except TimeoutError:
pass
all_class = page.locator(".clearfix.video").all()
new_class = []
for each in all_class:
isDone = each.locator(".time_icofinish").count()
if not isDone:
new_class.append(each)
return new_class
if enableRepeat:
return all_class
else:
new_class = []
for each in all_class:
isDone = each.locator(".time_icofinish").count()
if not isDone:
new_class.append(each)
return new_class


def tail_work(page: Page, start_time, all_class, title):
reachTimeLimit = False
page.set_default_timeout(90 * 60 * 1000)
time_period = (time.time() - start_time) / 60
if 0 < config.limitMaxTime <= time_period: # 若达到设定的时限将直接进入下一门课
print(f"\n当前课程已达时限:{config.limitMaxTime}min\n即将进入下门课程!")
reachTimeLimit = True
# 如果当前小节是最后一节代表课程学习完毕
class_name = all_class[-1].get_attribute('class')
if "current_play" in class_name:
print("\n已学完本课程全部内容!")
print("==" * 10)
else: # 否则为完成当前课程的一个小节
print(f"\n\"{title}\" Done !")
# 每完成一节提示一次时间
print(f"本次课程已学习:{time_period:.1f} min")
return reachTimeLimit

def start_course_loop(page: Page, course_url, config: Config):
page.goto(course_url)
page.wait_for_selector(".studytime-div")
# 关闭弹窗,优化页面体验
optimize_page(page)

# 学习模式: 从未完成课程开始
def learning_loop(page: Page, config: Config):
# 获取当前课程名
course_title = page.wait_for_selector(".source-name").text_content()
print(f"当前课程:<<{course_title}>>")
Expand Down Expand Up @@ -186,20 +208,62 @@ def start_course_loop(page: Page, course_url, config: Config):
else:
print(f"\n[Warn]{e.message}")
# 完成该小节后的操作
page.set_default_timeout(90 * 60 * 1000)
time_period = (time.time() - start_time) / 60
if 0 < config.limitMaxTime <= time_period: # 若达到设定的时限将直接进入下一门课
print(f"\n当前课程已达时限:{config.limitMaxTime}min\n即将进入下门课程!")
break
# 如果当前小节是最后一节代表课程学习完毕
class_name = all_class[-1].get_attribute('class')
if "current_play" in class_name:
print("\n已学完本课程全部内容!")
print("==" * 10)
else: # 否则为完成当前课程的一个小节
print(f"\n\"{title}\" Done !")
# 每完成一节提示一次时间
print(f"本次课程已学习:{time_period:.1f} min")
reachTimeLimit = tail_work(page, start_time, all_class, title)
if reachTimeLimit:
return


# 复习模式: 允许播放已完成课程小节
def reviewing_loop(page: Page, config: Config):
# 获取当前课程名
course_title = page.wait_for_selector(".source-name").text_content()
print(f"当前课程:<<{course_title}>>")
page.wait_for_selector(".clearfix.video", state="attached")
all_class = get_filtered_class(page, enableRepeat=True)
course_start_time = time.time() # 记录开始学习时间
for each in all_class:
page.wait_for_selector(".current_play", state="attached")
skip_questions(page)
each.click()
page.wait_for_timeout(1000)
title = get_lesson_name(page) # 获取课程小节名
print("正在学习:%s" % title)
skip_questions(page)
# 根据进度条判断播放状态
curtime, total_time = get_progress(page)
play_video(page) # 开始播放
video_optimize(page) # 对播放页进行初始化配置
start_time = time.time()
page.set_default_timeout(4000)
timer = 0
while True:
est_time = (time.time() - start_time) * config.limitSpeed
if est_time > total_time:
break
try:
skip_questions(page)
play_video(page)
time_period = (time.time() - course_start_time) / 60
timer += 1
if 0 < config.limitMaxTime <= time_period:
break # 到达限定时间就结束当前课程
elif timer % 5 == 0: # 降低更新频率,减少卡住情况
curtime, total_time = get_progress(page)
show_progress(desc="完成进度:", cur_str=f"{int(est_time * 100 // total_time)}%", enableRepeat=True)
except TimeoutError as e:
if page.query_selector(".yidun_modal__title"):
print("\n检测到安全验证,正在等待手动完成...")
page.wait_for_selector(
".yidun_modal__title", state="hidden", timeout=90 * 60 * 1000
)
elif page.query_selector(".topic-item"):
skip_questions(page)
else:
print(f"\n[Warn]{e.message}")
# 完成该小节后的操作
reachTimeLimit = tail_work(page, course_start_time, all_class, title)
if reachTimeLimit:
return


def main_function(config: Config):
Expand All @@ -214,8 +278,15 @@ def main_function(config: Config):
for course_url in config.course_urls:
print("开始加载播放页...")
page.set_default_timeout(90 * 60 * 1000)
page.goto(course_url)
page.wait_for_selector(".studytime-div")
# 关闭弹窗,优化页面体验
optimize_page(page)
# 启动课程主循环
start_course_loop(page, course_url, config)
if config.enableRepeat:
reviewing_loop(page, config)
else:
learning_loop(page, config)
browser.close()
print("==" * 10)
print("所有课程学习完毕!")
Expand All @@ -240,7 +311,7 @@ def main_function(config: Config):
input("[Error]程序缺失依赖文件,请重新安装程序!")
elif isinstance(e, TargetClosedError):
input("[Error]糟糕,网页关闭了!")
elif isinstance(e,UnicodeDecodeError):
elif isinstance(e, UnicodeDecodeError):
print("configs配置文件编码有误,保存时请选择utf-8或gbk!")
input(f"[Error]{e}")
else:
Expand Down
13 changes: 4 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,15 @@

**新学期必备干货, 建议收藏备用 !!**

**项目主页**[CXRunfree/Autovisor](https://github.com/CXRunfree/Autovisor)
**Github项目主页**[CXRunfree/Autovisor](https://github.com/CXRunfree/Autovisor)

------

#### 2024/5/14 rebuild-3.12.4 更新
#### 2024/5/23 rebuild-3.13.1 更新

修复到达限定时间无法跳转下一课程的Bug (3.12.3版本出现)
- **为适应习惯分机制, 此次更新后可重复刷已完成的课程小节**

- 继续优化题目弹窗的检测功能, 重写了答题逻辑;
- 优化了配置文件读取方式, 避免编码不一致的报错问题;
- 新增build.py可对代码进行pyinstaller打包;
- 代码结构更加规范化;

**感谢 bwnotfound (蓝白bw)** **提供的代码优化方案 !**
(需要在config配置文件中将 **"enableRepeat"** 项 设为 **True**)

详细配置方式请见下方的 **"使用须知".**

Expand Down
3 changes: 3 additions & 0 deletions configs.ini
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ driver = Edge
;例如 C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe
EXE_PATH =

;允许重复刷已完成的课程(默认:False)
enableRepeat = False

;限制每门课程刷课时长/min (不限制:0)
limitMaxTime = 30

Expand Down
26 changes: 19 additions & 7 deletions res/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,17 @@

class Config:
def __init__(self, config_path=None):
self._config = configparser.ConfigParser()
if not config_path:
config_path = 'configs.ini'
self.config_path = config_path
self._config = configparser.ConfigParser()
# 读取用户常量
self._read_config()
self.username = self._config.get('user-account', 'username', raw=True)
self.password = self._config.get('user-account', 'password', raw=True)
self.driver = self._config.get('custom-option', 'driver', raw=True)
if not self.driver:
self.driver = "Edge"
self.driver = self.driver.lower()
self.driver = self.get_driver()
self.exe_path = self._config.get('custom-option', 'EXE_PATH', raw=True)
self.enableRepeat = self.get_enableRepeat()
self.course_match_rule = re.compile("recruitAndCourseId=[a-zA-Z0-9]+")
self.course_urls = self.get_course_urls()
# 全局常量
Expand All @@ -31,6 +29,7 @@ def __init__(self, config_path=None):
self.gzh_pop = '''document.getElementsByClassName("course-warn")[0].click();'''
self.close_gjh = '''document.getElementsByClassName("rlready-bound-btn")[0].click();'''
self.close_ques = '''document.dispatchEvent(new KeyboardEvent('keydown', {bubbles: true, keyCode: 27 }));'''
self.close_assist = '''document.getElementsByClassName("show-icon icon-appear")[0].remove();'''
# 其他
self.night_js = '''document.getElementsByClassName("Patternbtn-div")[0].click()'''

Expand All @@ -40,6 +39,19 @@ def _read_config(self):
except UnicodeDecodeError:
self._config.read(self.config_path, encoding='gbk')

def get_driver(self):
driver = self._config.get('custom-option', 'driver', raw=True)
if not driver:
driver = "Edge"
return driver.lower()

def get_enableRepeat(self):
enableRepeat = self._config.get('custom-option', 'enableRepeat', raw=True).lower()
if enableRepeat == "true":
return True
else:
return False

def get_course_urls(self):
course_urls = []
_options = self._config.options("course-url")
Expand All @@ -52,8 +64,8 @@ def get_course_urls(self):
course_urls.append(course_url)
return course_urls

#@property修饰器可设置属性
#这样写可实时响应配置变化
# @property修饰器可设置属性
# 这样写可实时响应配置变化
@property
def limitMaxTime(self):
self._read_config()
Expand Down
10 changes: 6 additions & 4 deletions res/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@ def parse_time(h, m, s):
curtime = "100%"
else:
curtime = progress.text_content()

return curtime, total_time


def show_progress(desc, cur_str: str):
percent: int = int(cur_str.split("%")[0])
if percent >= 80: # 80%进度即为完成
def show_progress(desc, cur_str=None, enableRepeat=False):
percent = int(cur_str.split("%")[0])
if percent >= 80 and not enableRepeat: # 学习模式下,80%进度即为完成
percent = 100
length = int(percent * 30 // 100)
progress = ("█" * length).ljust(30, " ")
print(f"\r{desc} |{progress}| {percent}%\t", end="", flush=True)
percent = str(percent) + "%"
print(f"\r{desc} |{progress}| {percent}\t", end="", flush=True)
Loading

0 comments on commit 7fff507

Please sign in to comment.