From 723bec499bf23d8b6802336740566be0d57e8723 Mon Sep 17 00:00:00 2001 From: n0sense <94608280+RX-105@users.noreply.github.com> Date: Sat, 26 Mar 2022 07:14:56 -0500 Subject: [PATCH] =?UTF-8?q?add(module):=20=E5=88=9B=E5=BB=BA=E4=B8=BB?= =?UTF-8?q?=E7=95=8C=E9=9D=A2=E7=B1=BB=E5=92=8C=E5=85=A5=E5=8F=A3=E6=96=87?= =?UTF-8?q?=E4=BB=B6=20(#5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add(module): added dashboard graphic class and implemented login interface * add(module): add course list, lesson list and corresponding functions * fix(module): add process interval to avoid server blocking * add(module): add entrance file * fix(module): remove redundant codes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix(module): remove unused imported constants * fix(module): remove unused imported constants * fix(module): remove unused imported constants * add(deps): add ttkbootstrap * fix(module): remove redundant codes Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Fatpandac --- Pipfile | 1 + Pipfile.lock | 81 +++++++++--- fuck_cqooc.pyw | 4 + ui/__init__.py | 0 ui/dashboardRoot.py | 294 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 364 insertions(+), 16 deletions(-) create mode 100644 fuck_cqooc.pyw create mode 100644 ui/__init__.py create mode 100644 ui/dashboardRoot.py diff --git a/Pipfile b/Pipfile index d297cfa..6a58ce9 100644 --- a/Pipfile +++ b/Pipfile @@ -6,6 +6,7 @@ name = "pypi" [packages] requests = "*" js2py = "*" +ttkbootstrap = "*" [dev-packages] pre-commit = "*" diff --git a/Pipfile.lock b/Pipfile.lock index cf87676..ceaeda9 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "390ed3b52c8870702036e6685760a44e5fd36d0c9af0c6ce2510c6eef941f77e" + "sha256": "61ef1ae7301e0d4350a336a7f5a01d7159c047a42696dfbc2f58fe4a886fd0ca" }, "pipfile-spec": 6, "requires": { @@ -47,6 +47,47 @@ "index": "pypi", "version": "==0.71" }, + "pillow": { + "hashes": [ + "sha256:011233e0c42a4a7836498e98c1acf5e744c96a67dd5032a6f666cc1fb97eab97", + "sha256:0f29d831e2151e0b7b39981756d201f7108d3d215896212ffe2e992d06bfe049", + "sha256:12875d118f21cf35604176872447cdb57b07126750a33748bac15e77f90f1f9c", + "sha256:14d4b1341ac07ae07eb2cc682f459bec932a380c3b122f5540432d8977e64eae", + "sha256:1c3c33ac69cf059bbb9d1a71eeaba76781b450bc307e2291f8a4764d779a6b28", + "sha256:1d19397351f73a88904ad1aee421e800fe4bbcd1aeee6435fb62d0a05ccd1030", + "sha256:253e8a302a96df6927310a9d44e6103055e8fb96a6822f8b7f514bb7ef77de56", + "sha256:2632d0f846b7c7600edf53c48f8f9f1e13e62f66a6dbc15191029d950bfed976", + "sha256:335ace1a22325395c4ea88e00ba3dc89ca029bd66bd5a3c382d53e44f0ccd77e", + "sha256:413ce0bbf9fc6278b2d63309dfeefe452835e1c78398efb431bab0672fe9274e", + "sha256:5100b45a4638e3c00e4d2320d3193bdabb2d75e79793af7c3eb139e4f569f16f", + "sha256:514ceac913076feefbeaf89771fd6febde78b0c4c1b23aaeab082c41c694e81b", + "sha256:528a2a692c65dd5cafc130de286030af251d2ee0483a5bf50c9348aefe834e8a", + "sha256:6295f6763749b89c994fcb6d8a7f7ce03c3992e695f89f00b741b4580b199b7e", + "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa", + "sha256:718856856ba31f14f13ba885ff13874be7fefc53984d2832458f12c38205f7f7", + "sha256:7f7609a718b177bf171ac93cea9fd2ddc0e03e84d8fa4e887bdfc39671d46b00", + "sha256:80ca33961ced9c63358056bd08403ff866512038883e74f3a4bf88ad3eb66838", + "sha256:80fe64a6deb6fcfdf7b8386f2cf216d329be6f2781f7d90304351811fb591360", + "sha256:81c4b81611e3a3cb30e59b0cf05b888c675f97e3adb2c8672c3154047980726b", + "sha256:855c583f268edde09474b081e3ddcd5cf3b20c12f26e0d434e1386cc5d318e7a", + "sha256:9bfdb82cdfeccec50aad441afc332faf8606dfa5e8efd18a6692b5d6e79f00fd", + "sha256:a5d24e1d674dd9d72c66ad3ea9131322819ff86250b30dc5821cbafcfa0b96b4", + "sha256:a9f44cd7e162ac6191491d7249cceb02b8116b0f7e847ee33f739d7cb1ea1f70", + "sha256:b5b3f092fe345c03bca1e0b687dfbb39364b21ebb8ba90e3fa707374b7915204", + "sha256:b9618823bd237c0d2575283f2939655f54d51b4527ec3972907a927acbcc5bfc", + "sha256:cef9c85ccbe9bee00909758936ea841ef12035296c748aaceee535969e27d31b", + "sha256:d21237d0cd37acded35154e29aec853e945950321dd2ffd1a7d86fe686814669", + "sha256:d3c5c79ab7dfce6d88f1ba639b77e77a17ea33a01b07b99840d6ed08031cb2a7", + "sha256:d9d7942b624b04b895cb95af03a23407f17646815495ce4547f0e60e0b06f58e", + "sha256:db6d9fac65bd08cea7f3540b899977c6dee9edad959fa4eaf305940d9cbd861c", + "sha256:ede5af4a2702444a832a800b8eb7f0a7a1c0eed55b644642e049c98d589e5092", + "sha256:effb7749713d5317478bb3acb3f81d9d7c7f86726d41c1facca068a04cf5bb4c", + "sha256:f154d173286a5d1863637a7dcd8c3437bb557520b01bddb0be0258dcb72696b5", + "sha256:f25ed6e28ddf50de7e7ea99d7a976d6a9c415f03adcaac9c41ff6ff41b6d86ac" + ], + "markers": "python_version >= '3.7'", + "version": "==9.0.1" + }, "pyjsparser": { "hashes": [ "sha256:2b12842df98d83f65934e0772fa4a5d8b123b3bc79f1af1789172ac70265dd21", @@ -78,13 +119,21 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.16.0" }, + "ttkbootstrap": { + "hashes": [ + "sha256:5a16def8b45e4227b13535b71fda5780680771160a5873ff05fda8cbd44c9368", + "sha256:f9a8a801e08634df6c5fad50bc7aabe2e0c8fdd0b4d04a2e3d6f2a95419dab09" + ], + "index": "pypi", + "version": "==1.7.4" + }, "tzdata": { "hashes": [ - "sha256:3eee491e22ebfe1e5cfcc97a4137cd70f092ce59144d81f8924a844de05ba8f5", - "sha256:68dbe41afd01b867894bbdfd54fa03f468cfa4f0086bfb4adcd8de8f24f3ee21" + "sha256:238e70234214138ed7b4e8a0fab0e5e13872edab3be586ab8198c407620e2ab9", + "sha256:8b536a8ec63dc0751342b3984193a3118f8fca2afe25752bb9b7fffd398552d3" ], "markers": "python_version >= '3.6'", - "version": "==2021.5" + "version": "==2022.1" }, "tzlocal": { "hashes": [ @@ -96,11 +145,11 @@ }, "urllib3": { "hashes": [ - "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed", - "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c" + "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14", + "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", - "version": "==1.26.8" + "version": "==1.26.9" } }, "develop": { @@ -187,11 +236,11 @@ }, "identify": { "hashes": [ - "sha256:2986942d3974c8f2e5019a190523b0b0e2a07cb8e89bf236727fb4b26f27f8fd", - "sha256:fd906823ed1db23c7a48f9b176a1d71cb8abede1e21ebe614bac7bdd688d9213" + "sha256:3f3244a559290e7d3deb9e9adc7b33594c1bc85a9dd82e0f1be519bf12a1ec17", + "sha256:5f06b14366bd1facb88b00540a1de05b69b310cbc2654db3c7e07fa3a4339323" ], "markers": "python_version >= '3.7'", - "version": "==2.4.11" + "version": "==2.4.12" }, "iniconfig": { "hashes": [ @@ -257,11 +306,11 @@ }, "pytest": { "hashes": [ - "sha256:9ce3ff477af913ecf6321fe337b93a2c0dcf2a0a1439c43f5452112c1e4280db", - "sha256:e30905a0c131d3d94b89624a1cc5afec3e0ba2fbdb151867d8e0ebd49850f171" + "sha256:841132caef6b1ad17a9afde46dc4f6cfa59a05f9555aae5151f73bdf2820ca63", + "sha256:92f723789a8fdd7180b6b06483874feca4c48a5c76968e03bb3e7f806a1869ea" ], "index": "pypi", - "version": "==7.0.1" + "version": "==7.1.1" }, "pytest-cov": { "hashes": [ @@ -349,11 +398,11 @@ }, "virtualenv": { "hashes": [ - "sha256:dd448d1ded9f14d1a4bfa6bfc0c5b96ae3be3f2d6c6c159b23ddcfd701baa021", - "sha256:e9dd1a1359d70137559034c0f5433b34caf504af2dc756367be86a5a32967134" + "sha256:1e8588f35e8b42c6ec6841a13c5e88239de1e6e4e4cedfd3916b306dc826ec66", + "sha256:8e5b402037287126e81ccde9432b95a8be5b19d36584f64957060a3488c11ca8" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==20.13.3" + "version": "==20.14.0" } } } diff --git a/fuck_cqooc.pyw b/fuck_cqooc.pyw new file mode 100644 index 0000000..e6b1fe7 --- /dev/null +++ b/fuck_cqooc.pyw @@ -0,0 +1,4 @@ +from ui.dashboardRoot import dashboardRoot + +if __name__ == '__main__': + dashboardRoot().start() diff --git a/ui/__init__.py b/ui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ui/dashboardRoot.py b/ui/dashboardRoot.py new file mode 100644 index 0000000..8c6e8db --- /dev/null +++ b/ui/dashboardRoot.py @@ -0,0 +1,294 @@ +# -*- coding: utf-8 -*- +from src.core import Core + +import sys +import tkinter +import ttkbootstrap as ttk +from ttkbootstrap.constants import X +from ttkbootstrap.constants import YES +from ttkbootstrap.constants import LEFT +from ttkbootstrap.constants import NE +from ttkbootstrap.constants import BOTH +from ttkbootstrap.constants import HEADINGS +from ttkbootstrap.constants import END +from ttkbootstrap.constants import SUCCESS +from ttkbootstrap.constants import OUTLINE +from ttkbootstrap.constants import INFO +from ttkbootstrap.dialogs import Messagebox +from ttkbootstrap.dialogs import MessageDialog +from ttkbootstrap.icons import Icon +from time import sleep + + +class dashboardRoot(ttk.Window): + def __init__(self) -> None: + super().__init__() + + self.core = None + self.courseID = None + + # 设置基础视图 + self.title("fuck_cqooc") + self.geometry("800x500") + self.resizable(False, False) + + # 定义控件 + self.root = ttk.Frame(self, padding=10) + self.setupLoginFrame() + # 根 + self.root.pack(fill=X, expand=YES) + + def start(self) -> None: + """启动登录界面""" + self.mainloop() + + def setupLoginFrame(self) -> None: + """设置登陆界面框架,需要首先保证根里面没有组件""" + self.loginFrame = ttk.Frame(self.root) + self.labelWelcome = ttk.Label(self.loginFrame, text="fuck_cqooc") + + self.usernameFrame = ttk.Frame(self.loginFrame) + self.labelUsername = ttk.Label( + self.usernameFrame, text="用户名:", width=6 + ) + self.varUsername = ttk.StringVar(self.usernameFrame) + self.entryUsername = ttk.Entry( + self.usernameFrame, width=200, textvariable=self.varUsername + ) + + self.passwordFrame = ttk.Frame(self.loginFrame) + self.labelPassword = ttk.Label(self.passwordFrame, text="密码:", width=6) + self.varPassword = ttk.StringVar(self.passwordFrame) + self.entryPassword = ttk.Entry( + self.passwordFrame, + width=200, + textvariable=self.varPassword, + show="*", + ) + + self.buttonLogin = ttk.Button( + self.loginFrame, text="登录", command=self.login, width=30 + ) + + # 提示语部分 + self.labelWelcome.pack(pady=10) + self.labelWelcome.config(font=("Microsoft Yahei", 15)) + + # 用户名模块 + self.labelUsername.pack(side=LEFT, anchor=NE, pady=(10, 0), fill=X) + self.entryUsername.pack( + side=LEFT, padx=(10, 0), pady=(10, 0), expand=YES, fill=BOTH + ) + self.usernameFrame.pack() + + # 密码模块 + self.labelPassword.pack(side=LEFT, anchor=NE, pady=(10, 0), fill=X) + self.entryPassword.pack( + side=LEFT, padx=(10, 0), pady=(10, 0), expand=YES, fill=BOTH + ) + self.passwordFrame.pack() + + # 登录按钮 + self.buttonLogin.pack(pady=(10, 0)) + # 整个登录模块 + self.loginFrame.pack() + + def disposeLoginFrame(self) -> None: + """清除登录框架""" + self.loginFrame.destroy() + + def setupDashboardFrame(self) -> None: + # 定义所有框架 + self.welcomeFrame = ttk.Frame(self.root) + self.contentWrapperFrame = ttk.Frame(self.root) + self.courseFrame = ttk.Frame(self.contentWrapperFrame) + self.lessonFrame = ttk.Frame(self.contentWrapperFrame) + self.actionFrame = ttk.Frame(self.root) + + # 放置所有框架 + self.welcomeFrame.pack() + self.contentWrapperFrame.pack() + self.courseFrame.pack(side=LEFT) + self.lessonFrame.pack(side=LEFT) + self.actionFrame.pack() + + # 设置顶部状态提示语 + self.labelWelcome = ttk.Label( + self.welcomeFrame, text=f"当前登录用户为{self.varUsername.get()}" + ) + self.labelWelcome.config(font=("Microsoft Yahei", 15)) + self.labelWelcome.pack(side=LEFT, padx=(20, 0), pady=(0, 20)) + + # 设置课程列表 + self.treeCourse = ttk.Treeview( + master=self.courseFrame, columns=[0], show=HEADINGS, height=10 + ) + self.courseList = self.getCourseDict()["course"] + for i in self.courseList: + t = (i[0],) + self.treeCourse.insert("", END, values=t) + self.treeCourse.heading(0, text="课程名称") + self.treeCourse.column(0, width=400) + self.treeCourse.bind("<>", self.displayLessonsByEvent) + self.treeCourse.pack(side=LEFT) + + # 设置任务列表 + self.treeLesson = ttk.Treeview( + master=self.lessonFrame, + columns=[0, 1, 2, 3], + show=HEADINGS, + height=10, + ) + self.treeLesson.insert( + "", END, values=("☐", "点击左侧列表课程来选择", "未完成", "1000") + ) + self.treeLesson.heading(0, text="勾选") + self.treeLesson.heading(1, text="任务名称") + self.treeLesson.heading(2, text="完成情况") + self.treeLesson.heading(3, text="sectionID") + self.treeLesson.column(0, width=50) + self.treeLesson.column(1, width=350) + self.treeLesson.column(2, width=100) + self.treeLesson.column(3, width=0) + self.treeLesson["displaycolumns"] = [0, 1, 2] + self.treeLesson.bind("<>", self.processCheckbox) + self.treeLesson.pack(side=LEFT) + + # 设置功能按钮 + self.buttonExit = ttk.Button( + master=self.actionFrame, + text="退出", + bootstyle=(INFO, OUTLINE), + command=sys.exit, + width=10, + ) + self.buttonSelectAll = ttk.Button( + master=self.actionFrame, + text="全选", + bootstyle=(INFO, OUTLINE), + width=10, + ) + self.buttonProceed = ttk.Button( + master=self.actionFrame, text="fuck", bootstyle=SUCCESS, width=10 + ) + self.buttonSelectAll.bind("", self.selectAll) + self.buttonProceed.bind("", self.proceedTask) + self.buttonExit.pack(side=LEFT, pady=(20, 0)) + self.buttonSelectAll.pack(side=LEFT, padx=(400, 0), pady=(20, 0)) + self.buttonProceed.pack(side=LEFT, padx=(50, 0), pady=(20, 0)) + + def login(self) -> None: + """登录""" + username = self.varUsername.get() + password = self.varPassword.get() + if username == "" or password == "": + Messagebox.show_error("用户名或密码不能为空。", "错误") + return + + self.core = Core(username, password) + loginResult = self.core.login() + + self.buttonLogin["text"] = "登录" + if loginResult["status"] == "ok": + self.geometry("1000x500") + self.disposeLoginFrame() + self.setupDashboardFrame() + infoMessage = "因为重庆高校在线课程平台服务器的管控策略,短时间跳过太多\000\n" + infoMessage += "可能会遭到网站临时屏蔽。因此,跳过每个任务之间都会间隔一\000\n段时间。\n" + infoMessage += "跳过某一门课程之前,请先注意这门课程是否已经结束。" + MessageDialog( + message=infoMessage, + title="提示", + parent=None, + buttons=["我知道了:primary"], + icon=Icon.info, + localize=True, + ).show(None) + else: + Messagebox.show_error( + "登录失败。\n1. 你的用户名或密码可能有误\n2. 你可能需要前往网站手动登录,然后再试一次", "提示" + ) + + def getCourseDict(self) -> dict: + courseDict = dict() + courseList = list() + courseData = self.core.get_course() + courseDict["count"] = courseData["meta"]["total"] + for i in courseData["data"]: + courseList.append((i["title"], i["courseId"])) + courseDict["course"] = courseList + return courseDict + + def displayLessonsByEvent(self, e: tkinter.Event) -> None: + selection = e.widget.item(e.widget.selection()[0])["values"] + for i in self.courseList: + if i[0] == selection[0]: + courseID = i[1] + self.courseID = courseID + taskData = self.core.get_course_lessons(course_id=courseID) + taskList = list() + for i in taskData["data"]: + if i["category"] not in ["2", "3"]: # 判断是不是测验 + name = i["title"] + status = "完成" if i["status"] == 1 else "未完成" + sectionID = i["sectionId"] + taskList.append(("☐", name, status, sectionID)) + # 清空列表 + for i in self.treeLesson.get_children(): + self.treeLesson.delete(i) + for i in taskList: + self.treeLesson.insert("", END, values=i) + + def displayLessonsById(self) -> None: + taskData = self.core.get_course_lessons(course_id=self.courseID) + taskList = list() + for i in taskData["data"]: + if i["category"] not in ["2", "3"]: # 判断是不是测验 + name = i["title"] + status = "完成" if i["status"] == 1 else "未完成" + sectionID = i["sectionId"] + taskList.append(("☐", name, status, sectionID)) + # 清空列表 + for i in self.treeLesson.get_children(): + self.treeLesson.delete(i) + for i in taskList: + self.treeLesson.insert("", END, values=i) + + def processCheckbox(self, e: tkinter.Event) -> None: + selection = e.widget.item(e.widget.selection()[0])["values"] + if selection[0] == "☐": + selection[0] = "☑" + elif selection[0] == "☑": + selection[0] = "☐" + e.widget.item(e.widget.selection()[0], values=tuple(selection)) + + def selectAll(self, e: tkinter.Event) -> None: + for i in self.treeLesson.get_children(): + item = self.treeLesson.item(i)["values"] + if item[0] == "☐": + item[0] = "☑" + self.treeLesson.item(i, values=item) + + def proceedTask(self, e: tkinter.Event) -> None: + successCount = 0 + failCount = 0 + sectionList = list() + 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()