diff --git a/README.md b/README.md index 9e99077..4ed09b9 100644 --- a/README.md +++ b/README.md @@ -42,11 +42,6 @@ 这是因为重庆高校在线课程平台服务器的管控策略。短时间跳过太多任务,你可能会遭到网站临时屏蔽。屏蔽不会持续太久,不到一分钟就会解除。 但为了保证所有任务都能成功跳过,程序设置跳过每个任务之间都会间隔一段时间,具体是30秒。 -- 跳过的过程中按钮没有反应? - -为了防止反复多次跳过任务,执行任务期间,程序会阻止按钮的点击,直到任务完成。 -如果程序进入无响应状态,跳过任务仍然在进行,请不要尝试关闭程序。 - ## Special Thanks JetBrains Logo (Main) logo. diff --git a/ui/dashboardRoot.py b/ui/dashboardRoot.py index 8c6e8db..ca033cb 100644 --- a/ui/dashboardRoot.py +++ b/ui/dashboardRoot.py @@ -14,10 +14,14 @@ from ttkbootstrap.constants import SUCCESS from ttkbootstrap.constants import OUTLINE from ttkbootstrap.constants import INFO +from ttkbootstrap.constants import STRIPED +from ttkbootstrap.constants import DISABLED +from ttkbootstrap.constants import NORMAL from ttkbootstrap.dialogs import Messagebox from ttkbootstrap.dialogs import MessageDialog from ttkbootstrap.icons import Icon -from time import sleep + +from ui.skipper import skipper class dashboardRoot(ttk.Window): @@ -104,6 +108,7 @@ def setupDashboardFrame(self) -> None: self.courseFrame = ttk.Frame(self.contentWrapperFrame) self.lessonFrame = ttk.Frame(self.contentWrapperFrame) self.actionFrame = ttk.Frame(self.root) + self.progressFrame = ttk.Frame(self.root) # 放置所有框架 self.welcomeFrame.pack() @@ -111,6 +116,7 @@ def setupDashboardFrame(self) -> None: self.courseFrame.pack(side=LEFT) self.lessonFrame.pack(side=LEFT) self.actionFrame.pack() + self.progressFrame.pack(fill=X, expand=YES, pady=(20, 0)) # 设置顶部状态提示语 self.labelWelcome = ttk.Label( @@ -177,6 +183,15 @@ def setupDashboardFrame(self) -> None: self.buttonSelectAll.pack(side=LEFT, padx=(400, 0), pady=(20, 0)) self.buttonProceed.pack(side=LEFT, padx=(50, 0), pady=(20, 0)) + # 设置进度条 + self.progressBar = ttk.Progressbar( + master=self.progressFrame, + orient=tkinter.HORIZONTAL, + value=0, + bootstyle=(SUCCESS, STRIPED), + ) + self.progressBar.pack(fill=X, padx=(30, 30), expand=YES) + def login(self) -> None: """登录""" username = self.varUsername.get() @@ -269,26 +284,62 @@ def selectAll(self, e: tkinter.Event) -> None: item[0] = "☑" self.treeLesson.item(i, values=item) + def proceedTaskAfter(self, skipper: skipper) -> None: + # 利用标志位检查完成情况 + if skipper.getState(): + # 执行完成 + self.progressBar["value"] = 100 + Messagebox.show_info( + f"跳过完成。\n成功跳过{skipper.success}个任务,失败{skipper.fail}个。", "提示" + ) + # 恢复按钮到可点击状态 + self.buttonSelectAll["state"] = NORMAL + self.buttonProceed["state"] = NORMAL + # 清空列表 + for i in self.treeLesson.get_children(): + self.treeLesson.delete(i) + self.displayLessonsById() + else: + # 未完成则需要继续回调 + self.after(self.skipInterval, self.proceedTaskAfter, skipper) + + def incrementProgressBar(self, args: list) -> None: + """传递参数列表中,间隔时间为列表元素1,执行次数为元素2""" + countDown = args[1] + if not countDown == 0: + self.progressBar["value"] += 0.1 + args[1] -= 1 + self.progressBar.after( + int(args[0]), self.incrementProgressBar, args + ) + def proceedTask(self, e: tkinter.Event) -> None: - successCount = 0 - failCount = 0 sectionList = list() + argList = list() + skipThread = None + # 检查完成的间隔时间 + self.skipInterval = 1000 + 500 + # 预计任务全部完成需要的时间 + self.skipDuration = 500 + # 获取勾选的任务sectionID for i in self.treeLesson.get_children(): item = self.treeLesson.item(i)["values"] if item[0] == "☑": sectionList.append(str(item[3])) - for i in sectionList: - result = self.core.skip_section(i) - if result["code"] == 200: - successCount += 1 - else: - failCount += 1 - if len(sectionList) != 1: - sleep(31) - Messagebox.show_info( - f"跳过完成。\n成功跳过{successCount}个任务,失败{failCount}个。", "提示" - ) - # 清空列表 - for i in self.treeLesson.get_children(): - self.treeLesson.delete(i) - self.displayLessonsById() + # 默认检查间隔是1秒。如果任务数量大于1,就改为31秒 + # 额外增加了500ms,防止出现检查的时候当前的单个任务差一点就完成的情况 + if len(sectionList) != 1: + self.skipInterval = 31000 + 500 + self.skipDuration = (31000 + 1000) * len(sectionList) + skipThread = skipper(core=self.core, sectionList=sectionList) + skipThread.start() + # 关闭按钮,防止任务执行过程中再次点击 + self.buttonSelectAll["state"] = DISABLED + self.buttonProceed["state"] = DISABLED + # 开启回调,检查任务执行情况 + self.after(self.skipInterval, self.proceedTaskAfter, skipThread) + print(self.skipDuration) + argList = [int(self.skipDuration / 1000), 1000] + # 开始进度条之前需要先清空 + self.progressBar["value"] = 0 + self.progressBar.after(0, self.incrementProgressBar, argList) diff --git a/ui/skipper.py b/ui/skipper.py new file mode 100644 index 0000000..294a950 --- /dev/null +++ b/ui/skipper.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +from src.core import Core + +import threading +from time import sleep + + +class skipper(threading.Thread): + """用于执行跳过课程任务的线程类""" + + def __init__(self, core: Core, sectionList: list) -> None: + """参数说明: + + *core* 功能内核对象,来自src.core + + *sectionList* 包含课程ID的字符串列表 + + """ + threading.Thread.__init__(self) + self.core = core + self.sectionList = sectionList + self.success = 0 + self.fail = 0 + self.state = False + + def run(self) -> None: + print("skip thread started") + self.skip(self.sectionList) + + def skip(self, sectionList: list) -> None: + print("skip task started") + for i in sectionList: + result = self.core.skip_section(i) + if result["code"] == 200: + self.success += 1 + else: + self.fail += 1 + # 对于任务列表长度为1的情况就没有必要sleep这么久了,只有长度超过1的才要分别sleep 31秒 + if len(sectionList) != 1: + sleep(31) + # 跳出循环说明任务执行完成,修改状态标志位为True + self.state = True + + def getState(self) -> bool: + """返回True说明任务执行完成,False为未完成""" + return self.state