diff --git a/Autovisor.py b/Autovisor.py index e843a98..42a0706 100644 --- a/Autovisor.py +++ b/Autovisor.py @@ -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() @@ -109,7 +112,7 @@ 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"): @@ -117,32 +120,51 @@ def skip_questions(page: Page): # 直接遍历点击所有选项 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}>>") @@ -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): @@ -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("所有课程学习完毕!") @@ -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: diff --git a/README.md b/README.md index f45d4dc..947eddd 100644 --- a/README.md +++ b/README.md @@ -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**) 详细配置方式请见下方的 **"使用须知".** diff --git a/configs.ini b/configs.ini index 84e6e28..2cd207c 100644 --- a/configs.ini +++ b/configs.ini @@ -11,6 +11,9 @@ driver = Edge ;例如 C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe EXE_PATH = +;允许重复刷已完成的课程(默认:False) +enableRepeat = False + ;限制每门课程刷课时长/min (不限制:0) limitMaxTime = 30 diff --git a/res/configs.py b/res/configs.py index df0ff52..1f5a04d 100644 --- a/res/configs.py +++ b/res/configs.py @@ -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() # 全局常量 @@ -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()''' @@ -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") @@ -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() diff --git a/res/process.py b/res/process.py index 573c1e2..59ff00c 100644 --- a/res/process.py +++ b/res/process.py @@ -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) diff --git "a/\351\270\243\350\260\242\345\220\215\345\215\225.md" "b/\351\270\243\350\260\242\345\220\215\345\215\225.md" index 2421c27..f1cc35d 100644 --- "a/\351\270\243\350\260\242\345\220\215\345\215\225.md" +++ "b/\351\270\243\350\260\242\345\220\215\345\215\225.md" @@ -1,6 +1,6 @@ ### 感谢来自五湖四海的伙伴们对本项目的支持! -**一、特别贡献者 (GIthub Contributors):** +**一、特别贡献者 (Github Contributors):** **bwnotfound (蓝白bw)** @@ -10,58 +10,70 @@ 1. 以下名单不分排名, 仅以时间为索引排序 2. 昵称以微信赞赏显示为依据, 不记录隐私信息 -3. 本名单从2024年3月开始统计 +3. 本名单从2024年2024.3开始统计 -最后更新时间: 2024.5.11 +最后更新时间: 2024.5.23 -| 昵称 | 留言 | 日期 | -| :------------------: | :-----------------------------------------: | :--: | -| 含光 | c3141 | 3月 | -| 佚名 | 真心感谢你 | 3月 | -| 佚名 | good! | 3月 | -| 佚名 | thanks | 3月 | -| 佚名 | 请问uu为什么程序显示"所有课程...... | 3月 | -| [Joker] | 默认点赞~ | 3月 | -| 芋泥奶酒 | 而你, 我的朋友, 你才是真正的英雄 | 3月 | -| Hkhnj | 谢谢你, 好心人 | 3月 | -| 佚名 | 神中神 !! | 3月 | -| 亦山 | 继续加油 | 3月 | -| Nuya | 感谢 | 3月 | -| 古希腊能转进信...... | 大学生苦智慧树久矣, 感谢 | 3月 | -| 微栗 | 微栗 | 3月 | -| 佚名 | 感谢感谢 ୧⍢⃝୨ | 3月 | -| 瓷器 | 很好的软件, 爱来自瓷器 | 4月 | -| 旷野听风 | 谢谢大佬 | 4月 | -| 东荥燧 | 默认点赞~ | 4月 | -| ## | 默认点赞~ | 4月 | -| 泽 | 非常好程序, 爱来自每月限流量但要挂智慧树... | 4月 | -| 佚名 | 帮我刷完了10门课, 表达微薄谢意 | 4月 | -| 故故 | 好用感谢 | 4月 | -| 淮 | 谢谢大佬 | 4月 | -| Pushpush~pa! | 默认点赞~ | 4月 | -| 半只桃 | 默认点赞~ | 4月 | -| 佚名 | 太棒啦 | 4月 | -| 彷徨 | 默认点赞~ | 4月 | -| yyyy | 默认点赞~ | 4月 | -| Aaron | 默认点赞~ | 4月 | -| yanouo | 默认点赞~ | 4月 | -| 啾啾 | 希望能持续更新,拯救卑微大学牲 | 4月 | -| (^_^) | 默认点赞~ | 4月 | -| 我家 | 默认点赞~ | 4月 | -| 年年有余 | 默认点赞~ | 4月 | -| 罗安~ | 罗安~ | 4月 | -| 展翅飞翔 | 感谢作者! 希望可以继续努力! | 4月 | -| Bansgct | 默认点赞~ | 4月 | -| I。I | 默认点赞~ | 4月 | -| 佚名 | 请作者喝可乐 | 4月 | -| 纸鸢 | 很好用 | 4月 | -| Sep. | 默认点赞~ | 5月 | -| Fan. | 默认点赞~ | 5月 | -| 佚名 | Autovisor非常好用[赞] | 5月 | -| 遨游太空 | 默认点赞~ | 5月 | -| 佚名 | 真的非常棒 请喝奶茶 | 5月 | -| 沧桑的土拨鼠 | 默认点赞~ | 5月 | -| 佚名 | good job | 5月 | -| foggy | 谢谢你, 嘿嘿 | 5月 | -| | | | +| 昵称 | 留言 | 日期 | +| :------------------: | :-----------------------------------------: | :----: | +| 含光 | c3141 | 2024.3 | +| 佚名 | 真心感谢你 | 2024.3 | +| 佚名 | good! | 2024.3 | +| 佚名 | thanks | 2024.3 | +| 佚名 | 请问uu为什么程序显示"所有课程...... | 2024.3 | +| [Joker] | 默认点赞~ | 2024.3 | +| 芋泥奶酒 | 而你, 我的朋友, 你才是真正的英雄 | 2024.3 | +| Hkhnj | 谢谢你, 好心人 | 2024.3 | +| 佚名 | 神中神 !! | 2024.3 | +| 亦山 | 继续加油 | 2024.3 | +| Nuya | 感谢 | 2024.3 | +| 古希腊能转进信...... | 大学生苦智慧树久矣, 感谢 | 2024.3 | +| 微栗 | 微栗 | 2024.3 | +| 佚名 | 感谢感谢 ୧⍢⃝୨ | 2024.3 | +| 瓷器 | 很好的软件, 爱来自瓷器 | 2024.4 | +| 旷野听风 | 谢谢大佬 | 2024.4 | +| 东荥燧 | 默认点赞~ | 2024.4 | +| ## | 默认点赞~ | 2024.4 | +| 泽 | 非常好程序, 爱来自每月限流量但要挂智慧树... | 2024.4 | +| 佚名 | 帮我刷完了10门课, 表达微薄谢意 | 2024.4 | +| 故故 | 好用感谢 | 2024.4 | +| 淮 | 谢谢大佬 | 2024.4 | +| Pushpush~pa! | 默认点赞~ | 2024.4 | +| 半只桃 | 默认点赞~ | 2024.4 | +| 佚名 | 太棒啦 | 2024.4 | +| 彷徨 | 默认点赞~ | 2024.4 | +| yyyy | 默认点赞~ | 2024.4 | +| Aaron | 默认点赞~ | 2024.4 | +| yanouo | 默认点赞~ | 2024.4 | +| 啾啾 | 希望能持续更新,拯救卑微大学牲 | 2024.4 | +| (^_^) | 默认点赞~ | 2024.4 | +| 我家 | 默认点赞~ | 2024.4 | +| 年年有余 | 默认点赞~ | 2024.4 | +| 罗安~ | 罗安~ | 2024.4 | +| 展翅飞翔 | 感谢作者! 希望可以继续努力! | 2024.4 | +| Bansgct | 默认点赞~ | 2024.4 | +| I。I | 默认点赞~ | 2024.4 | +| 佚名 | 请作者喝可乐 | 2024.4 | +| 纸鸢 | 很好用 | 2024.4 | +| Sep. | 默认点赞~ | 2024.5 | +| Fan. | 默认点赞~ | 2024.5 | +| 佚名 | Autovisor非常好用[赞] | 2024.5 | +| 遨游太空 | 默认点赞~ | 2024.5 | +| 佚名 | 真的非常棒 请喝奶茶 | 2024.5 | +| 沧桑的土拨鼠 | 默认点赞~ | 2024.5 | +| 佚名 | good job | 2024.5 | +| foggy | 谢谢你, 嘿嘿 | 2024.5 | +| 佚名 | 配享太庙 | 2024.5 | +| Rhys0m | 牛的哥 | 2024.5 | +| 佚名 | 我的英雄, 兄弟 | 2024.5 | +| 8 | 默认点赞~ | 2024.5 | +| 淡泊 | 致敬 | 2024.5 | +| ! | 很好用, 室友已经用糕潮了 (?) | 2024.5 | +| mo | 默认点赞~ | 2024.5 | +| 双手插口袋 | 默认点赞~ | 2024.5 | +| pen^2 | 默认点赞~ | 2024.5 | +| 同是江湖人士也 | 默认点赞~ | 2024.5 | +| 佚名 | 谢谢博主, 大学生手头紧, 感谢 | 2024.5 | +| [鱼] | 默认点赞~ | 2024.5 | +| | | |