├── user ├── cookies.json └── user_data.json ├── package ├── exception.log ├── requirements.txt ├── bin │ └── unzip.exe ├── learn │ ├── task │ │ ├── __init__.py │ │ ├── quiz │ │ │ ├── exception.py │ │ │ ├── trueOrFalseOfTask.py │ │ │ ├── question.py │ │ │ ├── multipleChoiceOfTask.py │ │ │ ├── no_secret.py │ │ │ ├── quiz.py │ │ │ └── getanswer.py │ │ ├── README.md │ │ ├── interface.py │ │ ├── audio.py │ │ ├── ppt.py │ │ └── video.py │ ├── tools │ │ ├── __init__.py │ │ └── internetTime.py │ ├── printer │ │ ├── __init__.py │ │ ├── color.py │ │ ├── factory.py │ │ ├── setter.py │ │ ├── README.md │ │ └── printer.py │ ├── driver │ │ ├── __init__.py │ │ ├── driverException.py │ │ ├── mydriver.py │ │ └── useragent.py │ ├── data_management │ │ ├── __init__.py │ │ ├── file.py │ │ └── datamanger.py │ ├── school │ │ ├── getter.py │ │ ├── template.py │ │ ├── README.md │ │ └── concreteSchool.py │ ├── exception.py │ ├── boot.py │ ├── globalvar.py │ ├── userinterface.py │ └── learn_helper.py ├── config.ini ├── version.json └── README.md ├── 程序更新.cmd ├── 运行程序.cmd ├── 使用说明.md ├── README.md └── faithlearning.py /user/cookies.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /user/user_data.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /package/exception.log: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /package/requirements.txt: -------------------------------------------------------------------------------- 1 | selenium==3.141.0 2 | requests 3 | colorama 4 | fontTools -------------------------------------------------------------------------------- /程序更新.cmd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Levitans/XueXiTongAutoFlush/HEAD/程序更新.cmd -------------------------------------------------------------------------------- /运行程序.cmd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Levitans/XueXiTongAutoFlush/HEAD/运行程序.cmd -------------------------------------------------------------------------------- /package/bin/unzip.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Levitans/XueXiTongAutoFlush/HEAD/package/bin/unzip.exe -------------------------------------------------------------------------------- /package/learn/task/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-08-07 11:05 3 | # @Author : Levitan 4 | # @File : __init__.py 5 | # @Software : PyCharm 6 | -------------------------------------------------------------------------------- /package/learn/tools/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-08-01 20:49 3 | # @Author : Levitan 4 | # @File : __init__.py.py 5 | # @Software : PyCharm 6 | -------------------------------------------------------------------------------- /package/learn/printer/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-07-29 22:52 3 | # @Author : Levitan 4 | # @File : __init__.py.py 5 | # @Software : PyCharm 6 | -------------------------------------------------------------------------------- /package/learn/driver/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-08-01 15:53 3 | # @Author : Levitan 4 | # @File : __init__.py.py 5 | # @Software : PyCharm 6 | 7 | from .mydriver import MyDriver 8 | -------------------------------------------------------------------------------- /package/learn/data_management/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-04-14 18:51 3 | # @Author : Levitan 4 | # @File : __init__.py.py 5 | # @Software : PyCharm 6 | 7 | from .datamanger import * 8 | from .file import * 9 | -------------------------------------------------------------------------------- /package/learn/driver/driverException.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-08-01 16:38 3 | # @Author : Levitan 4 | # @File : driverException.py 5 | # @Software : PyCharm 6 | 7 | class TimeoutException(Exception): 8 | pass 9 | -------------------------------------------------------------------------------- /package/learn/task/quiz/exception.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-08-01 19:39 3 | # @Author : Levitan 4 | # @File : exception.py 5 | # @Software : PyCharm 6 | 7 | class NoFoundAnswerException(Exception): 8 | """ 9 | 在接口中没有找到答案抛出此异常。 10 | """ 11 | pass 12 | -------------------------------------------------------------------------------- /package/learn/task/README.md: -------------------------------------------------------------------------------- 1 | # task 目录说明 2 | 3 | --- 4 | 5 | ### 一 6 | 该目录存放当前程序能完成的**任务类型**,分别是:测验(quiz目录)、音频(audio.py)、视频(video.py)、PPT(ppt.py) 7 | 其中实现 **quiz** 功能的代码较多所以单独放在一个目录中 8 | 9 | ### 二 10 | 每个任务类型都抽象为一个 class ,并且每个任务类型类都继承 **interface.py** 中的的 Task 接口。 11 | Task 接口提供 **isCurrentTask()** 和 **finish()** 两个方法。 12 | 13 | **isCurrentTask():** 判断任务点是否为该任务类型。 14 | **finish():** 完成当前任务点。 -------------------------------------------------------------------------------- /package/learn/school/getter.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-07-27 23:15 3 | # @Author : Levitan 4 | # @File : getter.py 5 | # @Software : PyCharm 6 | 7 | from .template import School 8 | from . import concreteSchool as cs 9 | 10 | # 学校实例获取器 11 | # 通过传入学校类的类名来获取学校实例 12 | def schoolGetter(schoolType: str) -> School: 13 | try: 14 | school_obj_class = getattr(cs, schoolType) 15 | except AttributeError: 16 | raise Exception 17 | return school_obj_class() 18 | -------------------------------------------------------------------------------- /package/config.ini: -------------------------------------------------------------------------------- 1 | [school] 2 | school_type = Default 3 | 4 | [user_config] 5 | user_path = .\user\user_data.json 6 | cookie_path = .\user\cookies.json 7 | 8 | [browser_config] 9 | no_head = False 10 | no_img = False 11 | mute_audion = True 12 | browser_path = .\driver\chrome\chrome.exe 13 | driver_path = .\driver\chrome\chromedriver.exe 14 | 15 | [task_config] 16 | automatic_judgment_task_point_state = True 17 | decode_secret_status = 2 18 | quiz_get_answer_speed_max = 7 19 | quiz_get_answer_speed_min = 2 20 | quiz_click_speed_max = 5 21 | quiz_click_speed_min = 1 22 | 23 | [other] 24 | exception_log_file_path = .\package\exception.log 25 | version_file_path = .\package\version.json -------------------------------------------------------------------------------- /package/learn/printer/color.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-07-29 22:52 3 | # @Author : Levitan 4 | # @File : color.py 5 | # @Software : PyCharm 6 | 7 | from colorama import Fore, Back, init 8 | init(autoreset=True) 9 | 10 | def read(some_str): 11 | return Fore.LIGHTRED_EX + some_str + Fore.RESET 12 | 13 | def yellow(some_str): 14 | return Fore.YELLOW + some_str + Fore.RESET 15 | 16 | def blue(some_str): 17 | return Fore.BLUE + some_str + Fore.RESET 18 | 19 | def green(some_str): 20 | return Fore.GREEN + some_str + Fore.RESET 21 | 22 | def magenta(some_str): 23 | return Fore.MAGENTA + some_str + Fore.RESET 24 | 25 | def white(some_str): 26 | return some_str 27 | -------------------------------------------------------------------------------- /package/learn/task/interface.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-04-15 22:53 3 | # @Author : Levitan 4 | # @File : interface.py 5 | # @Software : PyCharm 6 | import abc 7 | 8 | # 第三方库 9 | from selenium.webdriver.chrome.webdriver import WebDriver 10 | from selenium.webdriver.remote.webelement import WebElement 11 | 12 | class Task(abc.ABC): 13 | def __init__(self, driver: WebDriver): 14 | self.__driver = driver 15 | 16 | @abc.abstractmethod 17 | def isCurrentTask(self, iframeIndex) -> bool: 18 | pass 19 | 20 | @abc.abstractmethod 21 | def finish(self): 22 | pass 23 | 24 | 25 | class Answerable(abc.ABC): 26 | @abc.abstractmethod 27 | def getAnswerWebElement(self) -> list[WebElement]: 28 | pass 29 | -------------------------------------------------------------------------------- /使用说明.md: -------------------------------------------------------------------------------- 1 | ## 使用说明 2 | ### 1、准备好浏览器和与浏览器版本匹配的浏览器驱动(只能使用 Chrome ) 3 | Chrome驱动下载:[http://chromedriver.storage.googleapis.com/index.html](http://chromedriver.storage.googleapis.com/index.html) 4 | ### 2、将浏览器的位置和浏览器驱动的位置写在package\config.ini文件中的browser_path和driver_path 5 | 6 | 例如: 7 | ``` 8 | [browser_config] 9 | browser_path = F:\python项目\XueXiTongAutoFlush4.0.0\driver\chrome\chrome.exe 10 | driver_path = F:\python项目\XueXiTongAutoFlush4.0.0\driver\chrome\chromedriver.exe 11 | ``` 12 | 13 | ### 3、安装依赖库 14 | ``` 15 | pip install selenium==3.141.0 16 | pip install requests 17 | pip install colorama 18 | ``` 19 | 20 | ### 4、运行程序 21 | #### 方法1:运行下面命令 22 | ~~~ 23 | python faithlearning.py 24 | ~~~ 25 | 26 | #### 方法2:双击 “运行程序.cmd” 27 | > .cmd文件上传github后中文会出现乱码,无法直接双击运行 28 | > 目前这个问题还没去解决 -------------------------------------------------------------------------------- /package/learn/printer/factory.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-07-29 22:57 3 | # @Author : Levitan 4 | # @File : factory.py 5 | # @Software : PyCharm 6 | 7 | from .printer import * 8 | from .setter import AbstractSetter 9 | 10 | nameSpace = locals() 11 | 12 | 13 | def getPrinterBySetter(setter: AbstractSetter) -> AbstractPrinter: 14 | try: 15 | obj_class = nameSpace[setter.getProductClassname()] 16 | obj = obj_class() 17 | obj.setSetter(setter) 18 | except KeyError: 19 | raise KeyError("Printer.py 中没有与传入 Setter 实例匹配的类") 20 | return obj 21 | 22 | 23 | def getPrinterByName(printerName: str) -> AbstractPrinter: 24 | try: 25 | obj_class = nameSpace[printerName] 26 | except KeyError: 27 | raise KeyError("printer.py模块中没有 {} 类".format(printerName)) 28 | return obj_class() 29 | 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 通过Python的selenium开发的学习通刷课程序,可自动完成视频、PPT和答题。 2 | # 项目已支持学习通自动答题 3 | 4 | **更新日期: 2022-05-17** 5 | 6 | --- 7 | 8 | ## 项目背景 9 | 在学习Python的时候了解到了selenium库,正好学校要求学生要用学习通,于是萌发了一个自己写刷课程序的想法最终产生了这个项目。 10 | 目前任有些问题无法修复,作者能力有限,如果对项目感兴趣非常欢迎进行交流和提交贡献。 11 | ## 项目介绍 12 | ### 该项目能够自动完成学习通中视频、PPT和答题等任务,并且保存每个人每个课程的刷课进度。 13 | 1、视频部分,程序能自动播放视频,回答视频中出现的问题。 14 | 2、PPT部分,程序自动识别PPT页数以1秒一页的速度点击PPT。 15 | 3、答题部分,程序自动识别出现的题目,通过查题的API进行答案搜索,自动填写答案,完成后进行保存。 16 | **已经添加应对超新字体反爬的功能** 参考链接:[https://blog.csdn.net/BigBoy_Coder/article/details/103239672](https://blog.csdn.net/BigBoy_Coder/article/details/103239672) 17 | 4、程序支持账号密码登陆和二维码登陆两种方式,且会保存用户登陆 18 | 19 | ## 使用 20 | ### 该项目依赖: 21 | >1、selenium(*version3.141.0*) 22 | 2、requests 23 | 3、colorama 24 | 25 | ## 主要项目负责人 26 | **Levitans** 27 | 28 | ## 声明 29 | 30 | 本项目只供对编程感兴趣、喜欢研究的同学来学习和锻炼自己的能力。 31 | ***本项目禁止进行任何商业化*** 32 | ***在使用中出现任何意外(如:被官方检测、答题零分等)均由使用者自己承担。*** 33 | **本软件只为学习研究使用,作者不承担任何非法使用本软件造成的法律后果** 34 | 35 | ### 查考连接 36 | 37 | -------------------------------------------------------------------------------- /package/learn/tools/internetTime.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2021-11-11 22:50 3 | # @Author : Levitan 4 | # @File : internetTime.py 5 | # @Software : PyCharm 6 | 7 | from time import mktime, strftime, strptime 8 | import json 9 | import requests 10 | 11 | class InternetTime: 12 | # 过期时间 13 | __expirationDate = '2022-07-01 00:00:00' 14 | 15 | @staticmethod 16 | def isExpiration(): 17 | isExpiration = False 18 | try: 19 | url = "http://api.m.taobao.com/rest/api3.do?api=mtop.common.getTimestamp" 20 | r = requests.get(url) 21 | nowTime = json.loads(r.text) 22 | nowTime = int(nowTime["data"]["t"])/1000 23 | except Exception: 24 | print("网络时间抓取失败") 25 | return isExpiration 26 | 27 | expirationTimestamp = mktime(strptime(InternetTime.__expirationDate, '%Y-%m-%d %H:%M:%S')) 28 | 29 | isExpiration = True if nowTime < expirationTimestamp else False 30 | return isExpiration 31 | -------------------------------------------------------------------------------- /package/learn/exception.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-02-18 13:22 3 | # @Author : Levitan 4 | # @File : exception.py 5 | # @Software : PyCharm 6 | 7 | class AtOrPdException(Exception): 8 | def __str__(self): 9 | return "账号或密码错误" 10 | 11 | class BrowseOrDriverPathException(Exception): 12 | def __init__(self, errorBrowserPath, errorDriverPath): 13 | self.__errorBrowserPath = errorBrowserPath 14 | self.__errorDriverPath = errorDriverPath 15 | 16 | def __str__(self): 17 | errorInfo = r"""浏览器地址或浏览器驱动地址配置错误 18 | 当前,浏览器地址为:{} 19 | 驱动地址为:{} 20 | 21 | 地址格式应该为以下几种 22 | 第一种: 23 | 浏览器地址:C:\\Users\\admin-dell\\Desktop\\chrome\\chrome.exe 24 | 浏览器驱动地址:C:\\Users\\admin-dell\\Desktop\\driver\\chrome\\chromedriver.exe 25 | 26 | 第二种: 27 | 浏览器地址:C:/Users/admin-dell/Desktop/chrome/chrome.exe 28 | 浏览器驱动地址:C:/Users/admin-dell/Desktop/driver/chrome/chromedriver.exe 29 | """.format(self.__errorBrowserPath, self.__errorDriverPath) 30 | return errorInfo 31 | 32 | class InitializationException(Exception): 33 | pass 34 | 35 | class TimeoutException(Exception): 36 | pass 37 | -------------------------------------------------------------------------------- /package/learn/task/quiz/trueOrFalseOfTask.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-02-06 22:28 3 | # @Author : Levitan 4 | # @File : trueOrFalseOfTask.py 5 | # @Software : PyCharm 6 | 7 | # 自定义包 8 | from ..interface import Answerable 9 | from .question import TrueOrFalse 10 | 11 | # 第三方包 12 | from selenium.webdriver.remote.webelement import WebElement 13 | 14 | 15 | class TrueOrFalseOfTask(TrueOrFalse, Answerable): 16 | def __init__(self, questionWebObj, qType, question, answer, answerWebElementList): 17 | """ 18 | :param qType: 题目类型(单选题,多选题) 19 | :param question: 题目问题 20 | :param answer: 查找到的题目答案 21 | :param answerWebElementList: 题目选项的WebElement对象, 列表类型 22 | """ 23 | super(TrueOrFalseOfTask, self).__init__(qType, question, answer) 24 | self.__answerWebElementList: list[WebElement] = answerWebElementList 25 | self.qWebObj = questionWebObj # 整个题目的web对象,用于定位到题目 26 | 27 | def getAnswerWebElement(self): 28 | """ 29 | :return: 返回正确答案的WebElement对象 30 | """ 31 | answer = self.getAnswer()[0] 32 | if not isinstance(answer, bool): 33 | return [] 34 | index = 0 if answer else 1 35 | return [self.__answerWebElementList[index]] 36 | -------------------------------------------------------------------------------- /package/learn/data_management/file.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-04-14 19:53 3 | # @Author : Levitan 4 | # @File : file.py 5 | # @Software : PyCharm 6 | import os 7 | import configparser 8 | import json 9 | 10 | # 读取配置文件 11 | def get_config_file(filename) -> configparser.ConfigParser: 12 | conf_obj = configparser.ConfigParser() 13 | try: 14 | conf_obj.read(filename, encoding="utf-8") 15 | except Exception as e: 16 | raise Exception(filename+"解析错误:"+str(e)+"\n"+"请检查"+filename+"中的信息") 17 | return conf_obj 18 | 19 | # 读取json文件 20 | def get_json_data(filename) -> dict: 21 | try: 22 | with open(filename, 'r', encoding="utf-8") as f: 23 | try: 24 | json_data = json.load(f) 25 | except Exception as e: 26 | raise Exception("文件 "+filename + " 解析错误:" + str(e) + "\n" 27 | + "请检查 " + filename + " 中的信息") 28 | return json_data 29 | except FileNotFoundError: 30 | raise Exception("文件 "+filename+" 未找到") 31 | 32 | 33 | # 保存json数据 34 | def save_json_data(filename, dictData): 35 | with open(filename, "w", encoding="utf-8") as f: 36 | data = json.dumps(dictData, indent=4, ensure_ascii=False) 37 | f.write(data) 38 | 39 | 40 | # 保存文本文件 41 | def save_text_file(filename, text): 42 | with open(filename, "w", encoding="utf-8") as f: 43 | f.write(text) 44 | 45 | # 向文本文件中添加数据 46 | def append_text_file(filename, text): 47 | with open(filename, "a", encoding="utf-8") as f: 48 | f.write(text) 49 | 50 | # 判断文件是否存在 51 | def is_file_exists(filename): 52 | return filename if os.path.exists(filename) else "" 53 | -------------------------------------------------------------------------------- /package/learn/task/audio.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-03-26 21:47 3 | # @Author : Levitan 4 | # @File : audio.py 5 | # @Software : PyCharm 6 | import time 7 | 8 | # 第三方包 9 | from selenium.webdriver.common.by import By 10 | from package.learn.task.interface import Task 11 | 12 | class Audio(Task): 13 | def __init__(self, driver): 14 | super().__init__(driver) 15 | self.__name__ = "音频" 16 | 17 | def isCurrentTask(self, iframeIndex) -> bool: 18 | iframeList = self._Task__driver.find_elements(By.TAG_NAME, 'iframe') 19 | return iframeList[iframeIndex].get_attribute("class") == "ans-attach-online ans-insertaudio" 20 | 21 | def finish(self): 22 | button = self._Task__driver.find_element(By.CSS_SELECTOR, '[class="vjs-play-control vjs-control vjs-button"]') 23 | self._Task__driver.execute_script("arguments[0].scrollIntoView(false);", button) 24 | 25 | nowTime = self._Task__driver.find_element(By.CSS_SELECTOR, '[class="vjs-current-time vjs-time-control vjs-control"]')\ 26 | .find_element(By.CSS_SELECTOR, '[class="vjs-current-time-display"]').text 27 | 28 | endTime = self._Task__driver.find_element(By.CSS_SELECTOR, '[class="vjs-duration vjs-time-control vjs-control"]')\ 29 | .find_element(By.CSS_SELECTOR, '[class="vjs-duration-display"]').text 30 | 31 | button.click() 32 | 33 | while nowTime != endTime: 34 | time.sleep(1) 35 | nowTime = self._Task__driver.find_element(By.CSS_SELECTOR, '[class="vjs-current-time vjs-time-control vjs-control"]') \ 36 | .find_element(By.CSS_SELECTOR, '[class="vjs-current-time-display"]').text 37 | print("\r"+"当前音频进度{}/{}".format(nowTime, endTime), end="") 38 | -------------------------------------------------------------------------------- /package/version.json: -------------------------------------------------------------------------------- 1 | { 2 | "current_version": "v20220809", 3 | "historical_version_info": [ 4 | { 5 | "version": "v20220809", 6 | "info": "1、整理项目代码减少模块间的耦合。\n2、增加printer模块,重构原本的display模块并整合到printer模块中\n3、修改 boot.py 依赖检测方式,现在只需将用到的第三方库名写到 requirements.txt 中即可\n4、将获取页面课程到获取用户选择课程这段单独抽为一个函数,为之后自动完成作业功能做准备\n5、新增从指定章节开始刷课的功能" 7 | }, 8 | { 9 | "version": "v20220630", 10 | "info": "1、修复删除多个过期cookie时出现的异常。2、添加对新页面中视频任务点出现题目的答题支持。3、将判断“Python版本”、“检测依赖”和“检测配置文件”等功能写入boot.py中。4、配置文件中添加“default_wait_time”和“default_wait_times”,将这个两个变量应用在mydriver类中。5、修复某些页面判断任务点是否完成时出现越界的异常。" 11 | }, 12 | { 13 | "version": "v20220605", 14 | "info": "1、更改判断任务点完成的逻辑。2、配置文件中添加是否跳过已完成任务点的选项" 15 | }, 16 | { 17 | "version": "v20220521", 18 | "info": "1、增加查题功能单独使用、且增加debug模式。2、优化答单选题时程序的行为" 19 | }, 20 | { 21 | "version": "v20220520", 22 | "info": "1、修改页面加载失败时的响应逻辑。2、配置文件中增加是否开启文字解密选项。3、完善文档" 23 | }, 24 | { 25 | "version": "v20220519", 26 | "info": "1、优化程序答题逻辑。2、增加自动删除过期cookies。3、增加判断任务点是否完成的功能" 27 | }, 28 | { 29 | "version": "v20220517", 30 | "info": "1、添加对抗学习通字体反爬的功能。2、修改 quiz.py 通过题目的 clss 值搜索题目的代码" 31 | }, 32 | { 33 | "version": "v20220512", 34 | "info": "将下载第三方库的功能从 运行程序.cmd 移到了faithlearning.py 里面。将需要第三方库写在了 requirements.txt 文件中。" 35 | }, 36 | { 37 | "version": "v20220425", 38 | "info": "添加一个程序启动脚本和程序更新脚本" 39 | }, 40 | { 41 | "version": "v20220424", 42 | "info": "优化程序异常处理。修复完成作业时判断选项是否被选中时会出现异常的问题。增加保存异常信息功能。添加保存程序更信息的文件version.json" 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /package/learn/school/template.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-04-15 15:01 3 | # @Author : Levitan 4 | # @File : template.py 5 | # @Software : PyCharm 6 | 7 | import abc 8 | 9 | # 第三方包 10 | from selenium.webdriver.chrome.webdriver import WebDriver 11 | 12 | class Course: 13 | def __init__(self, name, courseid, clazzid, personid, url): 14 | """ 15 | :name: 课程名称 16 | :courseid: 课程的id,这个值由学习通提供,可在页面中找到 17 | :clazzid: 这个值由学习通提供,可在页面中找到 18 | :personid: 这个值由学习通提供,可在页面中找到 19 | :url: 课程的url 20 | """ 21 | self.name = name 22 | self.courseid = courseid 23 | self.clazzid = clazzid 24 | self.personid = personid 25 | self.url = url 26 | 27 | # 获取课程的章节地址 28 | def get_ZJ_path(self): 29 | father_path = "https://mooc2-ans.chaoxing.com/mycourse/studentcourse?" 30 | return "{}courseid={}&clazzid={}&cpi={}&ut=s".format(father_path, self.courseid, self.clazzid, self.personid) 31 | 32 | 33 | class Chapter: 34 | """ 35 | :catalog_sbar: 章节号 36 | :name: 章节名字 37 | :weElement: 章节的WebElement对象 38 | :isFinish: 当前章节是否完成的标志 39 | """ 40 | def __init__(self, catalog_sbar, name, webElement, isFinish): 41 | self.catalog_sbar = catalog_sbar 42 | self.name = name 43 | self.webElement = webElement 44 | self.isFinish = isFinish 45 | 46 | # 返回当前章节的描述 47 | def toString(self) -> str: 48 | return "{} {}".format(self.catalog_sbar, self.name) 49 | 50 | 51 | class School(abc.ABC): 52 | @staticmethod 53 | @abc.abstractmethod 54 | def get_courses(driver: WebDriver) -> list[Course]: 55 | ... 56 | 57 | @staticmethod 58 | @abc.abstractmethod 59 | def get_chapters(driver: WebDriver) -> list[Chapter]: 60 | ... 61 | -------------------------------------------------------------------------------- /package/learn/task/quiz/question.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2021-12-16 16:21 3 | # @Author : Levitan 4 | # @File : question.py 5 | # @Software : PyCharm 6 | 7 | class Question: 8 | def __init__(self, question: str, answer: list): 9 | self.__question = question 10 | self.__answer = answer 11 | 12 | def __str__(self): 13 | return "[问题:"+self.getQuestion()+",答案:"+str(self.getAnswer())+"]" 14 | 15 | def getQuestion(self): return self.__question 16 | 17 | def getAnswer(self): return self.__answer 18 | 19 | 20 | # 选择题类 21 | class MultipleChoice(Question): 22 | def __init__(self, qType: str, question="", answer: list = None, options: list = None): 23 | super(MultipleChoice, self).__init__(question, answer) 24 | self.__type = qType 25 | self.__options = options 26 | 27 | def __str__(self): 28 | return "[问题:" + self.getQuestion() + ",答案:" + str(self.getAnswer()) + ",类型:" + self.getType() + ",选项:" + str( 29 | self.getOptions()) + "]" 30 | 31 | def toString(self): 32 | info = self.getQuestion()+"\n" 33 | if self.getOptions() is not None: 34 | for i in self.getOptions(): 35 | info += i + "\n" 36 | if self.getAnswer() is not None: 37 | for i in self.getAnswer(): 38 | info += i + "\n" 39 | return info 40 | 41 | def getType(self): 42 | return self.__type 43 | 44 | def getOptions(self): 45 | return self.__options 46 | 47 | 48 | # 判断题类 49 | class TrueOrFalse(Question): 50 | def __init__(self, qType: str, question="", answer: list = None): 51 | super(TrueOrFalse, self).__init__(question, answer) 52 | self.__type = qType 53 | 54 | def __str__(self): 55 | return "[问题:" + self.getQuestion() + ",答案:" + str(self.getAnswer()) + ",类型:" + self.getType() + "]" 56 | 57 | def toString(self): 58 | return self.getType() + "\n" + self.getQuestion() + "\n" + self.getAnswer()[0] 59 | 60 | def getType(self): 61 | return self.__type 62 | -------------------------------------------------------------------------------- /package/learn/boot.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-06-29 23:17 3 | # @Author : Levitan 4 | # @File : boot.py 5 | # @Software : PyCharm 6 | import re 7 | import os 8 | import sys 9 | import traceback 10 | 11 | # 判断系统中的 Python 版本是否满足要求 12 | def python_version_detect(): 13 | pattern = re.compile(r"\d+\d*") 14 | versionStr = sys.version.split(" ")[0].replace(".", "") 15 | versionNumber = re.search(pattern, versionStr) 16 | if versionNumber is None: 17 | print("获取系统 Python 版本时出现异常") 18 | eixt(233) 19 | local_version = int(versionNumber.group()) 20 | if local_version < 390: 21 | print("当前系统中 Python 版本过低") 22 | print("程序运行依赖 3.9 及以上版本的Python") 23 | print("可以访问 https://cdn.npmmirror.com/binaries/python/3.9.0/python-3.9.0.exe 下载Python3.9.0安装包") 24 | print("安装新 Python 后请手动删除,当前文件夹下的 venv 文件夹") 25 | exit(233) 26 | 27 | # 检测依赖是否存在 28 | def isDependencyReady(): 29 | file = open("package/requirements.txt") 30 | for depend in file.readlines(): 31 | depend = depend.replace("\n", "").split("==")[0] 32 | __import__(depend) 33 | try: 34 | __import__(depend) 35 | except ModuleNotFoundError: 36 | print("未安装 {} 模块".format(depend)) 37 | try_install_library() 38 | 39 | # 检测配置文件是否正确 40 | def initGlobalVar(): 41 | from package.learn import exception 42 | from package.learn import globalvar 43 | from package.learn.printer import color 44 | try: 45 | globalvar.init_global() 46 | except exception.InitializationException as e: 47 | print(color.read("程序初始化异常")) 48 | print("异常信息:"+str(e)+"\n") 49 | input("输入回车退出程序...") 50 | exit(233) 51 | 52 | # 自动安装依赖 53 | def try_install_library(): 54 | print("检测到未安装所需要的第三方库") 55 | haveVenv = os.path.exists("./venv") 56 | if haveVenv: 57 | print(r"你可以手动执行命令 “.\venv\Scripts\pip install -r .\package\requirements.txt” 安装第三方库") 58 | else: 59 | print(r"你可以手动执行命令 “pip install -r .\package\requirements.txt” 安装第三方库") 60 | print("下面尝试自动安装第三方库") 61 | input("如需自动安装按回车键继续(如不需要可现在关闭程序)...\n") 62 | from pip._internal import main 63 | main(["install", "-r", "./package/requirements.txt", "-i", "https://mirrors.aliyun.com/pypi/simple/"]) 64 | print("\n第三方库安装成功,请重新运行程序") 65 | input("按回车键退出程序......") 66 | exit(0) 67 | -------------------------------------------------------------------------------- /package/README.md: -------------------------------------------------------------------------------- 1 | # 当前目录下各文件和文件夹作用 2 | 3 | ## bin 文件夹 4 | 5 | 放置工具程序 6 | 7 | ### unzip.exe 程序 8 | 9 | 用于解压.zip文件,在”程序更新.cmd“中会调用该程序 10 | 11 | **"程序更新.cmd"的逻辑** 12 | 13 | 1. 下载该项目在GitHub上的压缩包 14 | 2. 解压该压缩包并替换当前系统中项目的源码 15 | 3. 删除解压后的文件夹和下载的压缩包 16 | 17 | 18 | 19 | ## learn 文件夹 20 | 21 | 存放该项目的源码 22 | 23 | 24 | 25 | ## config.ini 文件 26 | 程序配置文件,保存程序运行时的一些配置信息 27 | 28 | ### 学校(school) 29 | | 选项名 | 作用 | 其他 | 30 | |:----------:|:----:|:---------------------------------------:| 31 | | schol_type | 学校类型 | 一般使用默认,如果有特殊情况需要更改,请查看 school 中的 README | 32 | 33 | ### 用户(user_config) 34 | 35 | | 选项名 | 作用 | 其他 | 36 | |:------------:|:-------------:|:-----:| 37 | | user_path | 账号密码文件路径 || 38 | | cookie_path | cookie数据保存路径 || 39 | 40 | 41 | 42 | ### 浏览器及驱动(browser_config) 43 | 44 | | 选项名 | 作用 | 其他 | 45 | |:-------------:|:----------:|:------------:| 46 | | no_head | 是否开启浏览器显示 | False 或 True | 47 | | no_img | 是否开启无图模式 | False 或 True | 48 | | mute_audion | 是否静音 | False 或 True | 49 | | browser_path | 浏览器路径 || 50 | | driver_path | 浏览器驱动路径 || 51 | 52 | 53 | 54 | ### 答题模块(task_config) 55 | 56 | | 选项名 | 作用 | 其他 | 57 | | :---------------------------------: | :--------------------------: | :------------------------------: | 58 | | automatic_judgment_task_point_state | 自动判断章节、任务点是否完成 | True:开启、False:关闭 | 59 | | decode_secret_status | 字体解密器状态 | 0:关闭、1:开启、2:自动 | 60 | | quiz_get_answer_speed_max | 答案获取最长等待时间 | 不宜过快,不然会被接口判定为爬虫 | 61 | | quiz_get_answer_speed_min | 答案获取最短等待时间 | 同上 | 62 | | quiz_click_speed_max | 点击答案最长等待时间 | 不宜过快,不然会被学习通警告 | 63 | | quiz_click_speed_min | 点击答案最短等待时间 | 同上 | 64 | 65 | 66 | 67 | ### 其他(other) 68 | 69 | | 选项名 | 作用 | 其他 | 70 | |:------------------------:|:-----------:|:---:| 71 | | exception_log_file_path | 异常信息保存文件路径 || 72 | | version_file_path | 版本信息文件路径 || 73 | 74 | ## exception.log 文件 75 | 保存程序异常,当程序运行时出现异常都会详细记录在该文件中,便于debug 76 | 77 | ## requirements.txt 文件 78 | 保存程序运行时需要的第三方依赖。当程序检测到缺少依赖时会读取本文件并自动安装所需依赖 79 | 80 | ## font_dict.txt 文件 81 | 保存思源黑体字体的字形信息的md5值和字符编码的映射,用于解决学习通的字体反爬 82 | 83 | ## version.json 文件 84 | 保存程序更新信息和版本信息 -------------------------------------------------------------------------------- /package/learn/school/README.md: -------------------------------------------------------------------------------- 1 | # school 模块说明 2 | 3 | --- 4 | 5 | > 该模块的主要作用是实现程序获取页面上出现的课程以及章节。 6 | 7 | ### 一、文件说明: 8 | #### template.py 文件 9 | 该文件中定义一下了3个类 10 | 11 | - **Course(课程类)** 12 | 13 | | 属性名 | 属性作用 | 14 | | :------- | ------------------------------------------------------------ | 15 | | name | 课程的名称 | 16 | | courseid | 课程的id,由学习通提供,可在页面中找到,用于生成章节链接 | 17 | | clazzid | 不知道表示什么,由学习通提供,可在页面中找到,用于生成章节链接 | 18 | | personid | 学生的id,由学习通提供,可在页面中找到,用于生成章节链接 | 19 | | url | 课程的URL | 20 | 21 | | 方法名 | 方法作用 | 22 | | ------------- | ----------------------------- | 23 | | get_ZJ_path() | 返回当前课程的章节的URL字符串 | 24 | 25 | 26 | 27 | - **Chapter(章节类)** 28 | 29 | | 属性名 | 属性作用 | 30 | | ------------ | ---------------------------------- | 31 | | catalog_sbar | 章节号 | 32 | | name | 章节的名称 | 33 | | webElement | 章节的WebElement对象,用于进入章节 | 34 | | isFinish | 标志当前章节是否完成 | 35 | 36 | | 方法名 | 方法作用 | 37 | | ---------- | ------------------------ | 38 | | toString() | 返回当前章节描述的字符串 | 39 | 40 | 41 | 42 | - **School(学校类)** 43 | 44 | 该类是一个抽象类,在School类中定义了两个抽象方法分别是**get_courses()**和**get_chapters()** 45 | 46 | **get_courses()** 方法的主要作用是获取当前页面中的课程,并将每个课程封装为Course的对象并保存在数组中。 47 | *该方法会返回一个元素为Course的数组* 48 | 49 | **get_chapters()** 方法主要作用是获取当前页面中的课程,并将每个课程封装为Chapter的对象并保持在数组中。 50 | *该方法会返回一个元素为Chapter的数组* 51 | 52 | #### concreteSchool.py 文件 53 | 54 | 该文件保存具体的学校类 55 | 56 | 当该文件中的学校类无法满足需求时,就需要在该文件中添加新的学校类 57 | 58 | #### getter.py 文件 59 | 60 | 该文件中保存了学校类的获取方法 61 | 62 | **schoolGetter()** 方法通过反射的方式根据传入的学校名来创建学校对象 63 | 64 | 65 | 66 | ### 二、无法获取课程或章节的原因 67 | 68 | > 由于学校不同,学习通的页面可能也会不同。导致一样的程序有些人能用,有些人不能用 69 | 70 | **例如:** 有的学校在课程标签的属性中有 class="color1" 所以程序可以通过 class="color1" 来定位课程标签。而别的学校课程标签中 class 的值不一定为 "color1" 程序运行就会出现异常。 71 | 72 | 73 | 74 | ### 三、无法获取课程或章节的解决方式: 75 | 76 | > 目前这个问题还没有通用的解决方式 77 | 78 | 程序中提供了默认获取课程和章节的类 *(concreteSchol.py中的 Default 类)* ,该类能够满足大部分人的需求。但仍有部分人的学习通页面和默认的不一样。 79 | 80 | 如果遇到这个问题,就需要自己根据页面编写获取课程和章节的学校类。 81 | 82 | **注意:** 83 | 84 | 1. 自定义的学校类必须写在 *concreteSchool.py* 文件中。 85 | 2. 自定义学校类必须继承 *template.py* 中的 *School* 抽象类,并重写其中的抽象方法。 86 | 3. 将 *config.ini* 文件中 *school* 部分的 *school_type* 选项的值改为自定义学校类的*类名* 87 | -------------------------------------------------------------------------------- /package/learn/task/ppt.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2021-10-30 21:47 3 | # @Author : Levitan 4 | # @File : PPT.py 5 | # @Software : PyCharm 6 | import time 7 | import random 8 | 9 | # 自定义包 10 | from .interface import Task 11 | 12 | # 第三方包 13 | from selenium.webdriver.common.by import By 14 | 15 | class PPT(Task): 16 | __maxSpeed = 3 17 | __minSpeed = 1 18 | 19 | def __init__(self, driver): 20 | super().__init__(driver) 21 | self.__name__ = "PPT" 22 | 23 | def isCurrentTask(self, iframeIndex) -> bool: 24 | 25 | iframeList = self._Task__driver.find_elements(By.TAG_NAME, 'iframe') 26 | return iframeList[iframeIndex].get_attribute("class") in ("ans-attach-online insertdoc-online-pdf", "ans-attach-online insertdoc-online-ppt") 27 | 28 | def finish(self): 29 | try: 30 | self.__ppt1() 31 | except Exception: 32 | self.__ppt2() 33 | 34 | def __ppt1(self): 35 | pageData = self._Task__driver.find_element(By.CSS_SELECTOR, '[class="mkeNum mkeNum_bom"]') \ 36 | .find_element(By.CSS_SELECTOR, '[class="fl pageInfo"]') \ 37 | .text \ 38 | .replace(" ", "") \ 39 | .partition("/") 40 | 41 | nextBtn = self._Task__driver.find_element(By.CSS_SELECTOR, '[class="mkeNum mkeNum_bom"]') \ 42 | .find_element(By.CSS_SELECTOR, '[class="turnpage_Btn"]') \ 43 | .find_element(By.CSS_SELECTOR, '[class="nextBtn"]') 44 | 45 | newPage = int(pageData[0]) 46 | endPage = int(pageData[-1]) 47 | 48 | while newPage < endPage: 49 | nextBtn.click() 50 | newPage += 1 51 | print('\r' + '已观看{}张'.format(newPage), end='') 52 | time.sleep(random.randint(self.__minSpeed, self.__maxSpeed)) 53 | 54 | def __ppt2(self): 55 | self._Task__driver.switch_to.frame(self._Task__driver.find_element(By.CSS_SELECTOR, '[id="panView"]')) # id="iframe" 56 | imgList = self._Task__driver.find_elements(By.TAG_NAME, 'img') 57 | print("共有{}张PPT".format(len(imgList))) 58 | for i in range(len(imgList)): 59 | print('\r'+"观看第{}张PPT".format(i+1), end="") 60 | self._Task__driver.execute_script("window.scrollBy(0,2000)") 61 | time.sleep(random.randint(self.__minSpeed, self.__maxSpeed)) 62 | print() 63 | -------------------------------------------------------------------------------- /package/learn/school/concreteSchool.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-04-15 21:00 3 | # @Author : Levitan 4 | # @File : concreteSchool.py 5 | # @Software : PyCharm 6 | 7 | # 自定义包 8 | from .template import Course, Chapter, School 9 | 10 | # 第三方包 11 | from selenium.common import exceptions 12 | from selenium.webdriver.common.by import By 13 | 14 | """ 15 | 这是程序中默认获取课程列表和章节列表的类 16 | 该类对大部分学校的学习通都适用 17 | 18 | 如果默认的类无法获取的你学习通上的课程或章节 19 | """ 20 | class Default(School): 21 | @staticmethod 22 | def get_courses(driver) -> list[Course]: 23 | courseList = [] 24 | item_list = driver.find_elements(By.CSS_SELECTOR, '[class="course clearfix"]') 25 | for i in item_list: 26 | courseid = i.get_attribute("courseid") 27 | clazzid = i.get_attribute("clazzid") 28 | personid = i.get_attribute("personid") 29 | name = i.find_element(By.CSS_SELECTOR, '[class="color1"]').text 30 | url = i.find_element(By.TAG_NAME, "a").get_attribute("href") 31 | courseList.append(Course(name, courseid, clazzid, personid, url)) 32 | return courseList 33 | 34 | @staticmethod 35 | def get_chapters(driver) -> list[Chapter]: 36 | chapterUnitList = driver.find_elements(By.CSS_SELECTOR, '[class="chapter_unit"]') 37 | itemList = [] 38 | for i in chapterUnitList: 39 | # 因为找到的WebElement是在一个列表中,所用这里使用 “+” 来连接 40 | itemList += i.find_elements(By.TAG_NAME, "li") 41 | chaptersList = [] 42 | for i in itemList: 43 | catalog_sbar = i.find_element(By.CSS_SELECTOR, '[class="catalog_sbar"]').text 44 | name = i.find_element(By.CSS_SELECTOR, '[class="chapter_item"]').get_attribute("title") 45 | catalogTaskText = i.find_element(By.CSS_SELECTOR, '[class="catalog_task"]').text 46 | finishKey = True if catalogTaskText == "" else False 47 | chaptersList.append(Chapter(catalog_sbar, name, i, finishKey)) 48 | return chaptersList 49 | 50 | 51 | class Default2(Default): 52 | """ 53 | 这个是在默认类中获取课程适用,但是获取章节不适用的一种情况 54 | 55 | """ 56 | @staticmethod 57 | def get_chapters(driver) -> list[Chapter]: 58 | chapterUnitList = driver.find_elements(By.CSS_SELECTOR, '[class="chapter_unit"]') 59 | itemList = [] 60 | for i in chapterUnitList: 61 | itemList += i.find_elements(By.CSS_SELECTOR, '[class="chapter_item"]') 62 | chaptersList = [] 63 | for i in itemList: 64 | catalog_sbar = i.find_element(By.CSS_SELECTOR, '[class="catalog_sbar"]').text 65 | name = i.get_attribute("title") 66 | catalogTaskText = i.find_element(By.CSS_SELECTOR, '[class="catalog_task"]').text 67 | finishKey = True if catalogTaskText == "" else False 68 | chaptersList.append(Chapter(catalog_sbar, name, i, finishKey)) 69 | return chaptersList 70 | -------------------------------------------------------------------------------- /package/learn/task/quiz/multipleChoiceOfTask.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-02-06 16:08 3 | # @Author : Levitan 4 | # @File : multipleChoiceOfTask.py 5 | # @Software : PyCharm 6 | 7 | import difflib 8 | import traceback 9 | 10 | from ..interface import Answerable 11 | from .question import MultipleChoice 12 | from package.learn.globalvar import exception_log_manger, version, color 13 | 14 | # 第三方库 15 | from selenium.webdriver.common.by import By 16 | from selenium.webdriver.remote.webelement import WebElement 17 | from selenium.common.exceptions import NoSuchElementException 18 | 19 | class MultipleChoiceOfTask(MultipleChoice, Answerable): 20 | def __init__(self, questionWebObj, qType, question, answersList, options, optionsWebElements): 21 | """ 22 | :questionWebObj: 题目整体的WebElement对象,用于定位题目位置 23 | :param qType: 题目类型(单选题,多选题) 24 | :param question: 题目问题 25 | :param answersList: 查找到的题目答案,list类型 26 | :param options: 题目选项文字,list类型 27 | :param optionsWebElements: 题目选项的WebElement对象, 列表类型 28 | """ 29 | super(MultipleChoiceOfTask, self).__init__(qType, question, answersList, options) 30 | self.__optionsWebElements: list[WebElement] = optionsWebElements 31 | self.qWebObj = questionWebObj 32 | 33 | def getAnswerWebElement(self): 34 | """ 35 | :return: 将查找到的答案与题目选项相比较,返回一个包含正确选项WebElement对象的列表 36 | """ 37 | answerWebElementList = [] 38 | answerList = [self.getAnswer()[0]] if self.getType() == "单选题" else self.getAnswer() 39 | options = self.getOptions() 40 | for answerIndex in range(len(answerList)): 41 | for i in range(len(options)): 42 | for j in range(len(answerList[answerIndex])): 43 | if self.__optionsWebElements[i] in answerWebElementList: 44 | continue 45 | similarDiffRatio = difflib.SequenceMatcher(None, options[i], answerList[answerIndex][j]).quick_ratio() 46 | if similarDiffRatio > 0.88: 47 | # 选项是否被选中,用于多选题,多选题重复选择会取消选项 48 | # isElementBeClick 为Ture表示选项被选中,False表示选项没被选中 49 | try: 50 | isElementBeClick = False if self.__optionsWebElements[i].find_element(By.TAG_NAME, "input").get_attribute("checked") is None else True 51 | except NoSuchElementException: 52 | try: 53 | elementClass = self.__optionsWebElements[i].get_attribute("class") 54 | isElementBeClick = True if elementClass.split(" ")[-1] == "check_answer" else False 55 | except NoSuchElementException: 56 | exception_log_manger.writeLog(version, traceback.format_exc()) 57 | print(color.read("无法判断选项是否被选中\n默认被选中")) 58 | isElementBeClick = True 59 | if isElementBeClick: 60 | print("选项已经被选中") 61 | else: 62 | answerWebElementList.append(self.__optionsWebElements[i]) 63 | return answerWebElementList 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /package/learn/task/video.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2021-10-30 22:02 3 | # @Author : Levitan 4 | # @File : video.py 5 | # @Software : PyCharm 6 | import time 7 | from selenium.webdriver.common.by import By 8 | from package.learn.task.interface import Task 9 | 10 | class Video(Task): 11 | def __init__(self, driver): 12 | super().__init__(driver) 13 | self.__name__ = "视频" 14 | 15 | def isCurrentTask(self, iframeIndex) -> bool: 16 | iframeList = self._Task__driver.find_elements(By.TAG_NAME, 'iframe') 17 | return iframeList[iframeIndex].get_attribute("class") == "ans-attach-online ans-insertvideo-online" 18 | 19 | def finish(self): 20 | # 将按钮移动到屏幕中在点击 21 | button = self._Task__driver.find_element(By.CSS_SELECTOR, '[class="vjs-big-play-button"]') 22 | self._Task__driver.execute_script("arguments[0].scrollIntoView(false);", button) 23 | time.sleep(1) 24 | button.click() 25 | 26 | time.sleep(2) 27 | nowTime = self._Task__driver.find_element(By.CLASS_NAME, 'vjs-current-time-display').text 28 | endTime = self._Task__driver.find_element(By.CLASS_NAME, 'vjs-duration-display').text 29 | endTimeData = endTime.partition(":") 30 | print("视频总时长{}分{}秒".format(endTimeData[0], endTimeData[-1])) 31 | counterTime = 2 32 | 33 | while nowTime != endTime: 34 | time.sleep(0.96) 35 | nowTime = self._Task__driver.find_element(By.CLASS_NAME, 'vjs-current-time-display').text 36 | print('\r'+'已观看{}分{}秒'.format(counterTime//60, counterTime % 60), end='') 37 | counterTime += 1 38 | try: 39 | self.__answer() 40 | except Exception: 41 | continue 42 | 43 | def __answer(self): 44 | try: 45 | # 一版视频答题 46 | title = self._Task__driver.find_element(By.CSS_SELECTOR, '.ans-videoquiz-title').text[1:4] 47 | if title == '判断题' or title == '单选题.txt': 48 | self._Task__driver \ 49 | .find_element(By.CSS_SELECTOR, '[type="radio"][name="ans-videoquiz-opt"][value="true"]') \ 50 | .click() 51 | time.sleep(1) 52 | else: 53 | answerList = self._Task__driver.find_elements(By.CSS_SELECTOR, '[type="checkbox"][value="true"]') 54 | for i in answerList: 55 | i.click() 56 | time.sleep(1) 57 | self._Task__driver.find_element(By.CSS_SELECTOR, '.ans-videoquiz-submit').click() 58 | except Exception: 59 | # 二版视屏答题 60 | tkTopic = self._Task__driver.find_element(By.CLASS_NAME, "tkTopic") 61 | title = tkTopic.find_element(By.CLASS_NAME, "tkTopic_title").text 62 | tkItem = tkTopic.find_element(By.CLASS_NAME, "tkItem") 63 | if title == "[单选题]" or title == "[判断题]": 64 | ansList = tkItem.find_elements(By.CLASS_NAME, "ans-videoquiz-opt") 65 | for ansItem in ansList: 66 | value = ansItem.find_element(By.TAG_NAME, "input").get_attribute("value") 67 | if value == "false": 68 | continue 69 | elif value == "true": 70 | ansItem.click() 71 | break 72 | elif title == "[多选题]": 73 | ansList = tkItem.find_elements(By.CLASS_NAME, "ans-videoquiz-opt") 74 | for ansItem in ansList: 75 | value = ansItem.find_element(By.TAG_NAME, "input").get_attribute("value") 76 | if value == "false": 77 | continue 78 | elif value == "true": 79 | ansItem.click() 80 | else: 81 | print("视屏答题出错,没有匹配的题目") 82 | tkTopic.find_element(By.CSS_SELECTOR, '[class="ans-videoquiz-submit bntLinear fr"]').click() 83 | -------------------------------------------------------------------------------- /package/learn/printer/setter.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-08-04 21:54 3 | # @Author : Levitan 4 | # @File : setter.py 5 | # @Software : PyCharm 6 | 7 | import abc 8 | 9 | # 抽象配置器类 10 | class AbstractSetter(abc.ABC): 11 | __productClassname: str 12 | 13 | def __init__(self, name: str, pClassname: str) -> None: 14 | self._name = name 15 | self.__setProductClassname(pClassname) 16 | 17 | # 设置配置类的类型 18 | @classmethod 19 | def __setProductClassname(cls, pClassname: str): 20 | cls.__productClassname = pClassname 21 | 22 | # 获取配置类的类型 23 | @classmethod 24 | def getProductClassname(cls): 25 | return cls.__productClassname 26 | 27 | 28 | class TableSetter(AbstractSetter): 29 | def __init__(self, name="") -> None: 30 | super().__init__(name, "TablePrinter") # 传入配置类的类型 31 | """ 32 | 序号相关参数 33 | :autoOrdNumber: 是否开启自动序号 34 | True表示开启,False表示关闭 35 | :ordNumberName: 序号字段的名字 36 | :ordNumberStart: 序号的开始值 37 | :ordNumberStep: 序号值间的步长 38 | """ 39 | self.hasHead = False # 数据中是否有表头 40 | self.autoOrdNumber = False # 是否开启自动序号 41 | self.ordNumberName = "序号" # 序号字段的名字 42 | self.ordNumberStart = 1 # 序号的开始值 43 | self.ordNumberStep = 1 # 序号的步长 44 | """ 45 | 表格形状相关参数 46 | :_autoColumnWidth: 自动计算表格中的列宽 47 | 默认开启自动计算 48 | 当调用 setTableColWidth() 设置手动设置表格列宽时该功能会关闭 49 | 50 | :alignment: 数据的对齐方式 51 | “l” 或 “L” 表示左对齐 52 | “r” 或 “R” 表示右对齐 53 | “c” 或 “C” 标识居中 54 | 55 | :_tableColumnWidth: 表格中列元素的宽度 56 | 该参数的值通过两种方式设置 57 | 方式1、程序通过传入的数据自动计算出每列所需的最大宽度,并赋给该参数 58 | 方式2、通过外部通过调用 setTableColwidth() 方法传入自定义的宽度 59 | 当通过 setTableColwidth() 程序会将 _autoColumnWidth 的值设置为 False 60 | 注意:通过方式2设置该参数值是,每列的宽度值大于等于3! 61 | 62 | :margin_left: 数据与单元格左边的距离 63 | :margin_right: 数据与单元格右边的距离 64 | 65 | """ 66 | self.autoColumnWidth = True # 是否自动计算表格列宽 67 | self.alignment = "l" # 表格中的对齐方式 68 | self.margin_left = 1 # 左边距 69 | self.margin_right = 1 # 右边距 70 | """ 71 | 表格外观设置 72 | :borderChar: 绘制表格边框所用的符号 73 | :splitChar: 分割单元格所用的符号 74 | :turnChar: 绘制转折点所用的符号(单元格的顶点) 75 | """ 76 | self.borderChar = "-" # 边框符号 77 | self.splitChar = "|" # 分割符号 78 | self.turnChar = "+" # 转折点符号 79 | self.abreastTableNumber = 1 # 并排显示的表格数目 80 | self.tableSplitWidth = 5 # 并排显示的表格间距离 81 | self.headColor = "white" # 表头颜色 82 | self.bodyColor = "white" # 表主体颜色 83 | 84 | # 获取表格所有列宽数据 85 | def getTableColWidth(self): 86 | return self._tableColumnWidth 87 | 88 | # 是否自动计算表格列宽 89 | def isAutoColumnWidth(self): 90 | return self.autoColumnWidth 91 | 92 | 93 | # 消息控制器 94 | class MsgSetter(AbstractSetter): 95 | def __init__(self, name: str = ""): 96 | super().__init__(name, "MsgPrinter") 97 | self.horizontalSymbol = "=" # 上下边框的符号 98 | self.verticalSymbol = "|" # 两侧边框的符号 99 | self.color = "white" # 显示的颜色 100 | self.margin_left = 1 # 左边距 101 | self.margin_right = 1 # 右边距 102 | 103 | 104 | # 分隔线控制器 105 | class SplitSetter(AbstractSetter): 106 | def __init__(self, name: str = ""): 107 | super().__init__(name, "SplitPrinter") 108 | self.symbol = "-" 109 | self.length: int = 50 110 | self.color = "white" 111 | self.frontNewlineNumber: int = 1 112 | self.behindNewlineNumber: int = 1 113 | self.message: str = "" 114 | self.leftmostSymbol = self.symbol 115 | self.rightmostSymbol = self.symbol 116 | 117 | -------------------------------------------------------------------------------- /faithlearning.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-04-14 23:26 3 | # @Author : Levitan 4 | # @File : faithlearning.py 5 | # @Software : PyCharm 6 | import shutil 7 | import sys 8 | import traceback 9 | 10 | """ 11 | 运行前自检 12 | 检测依赖是否存在和全局变量是否正确读入 13 | """ 14 | from package.learn import boot 15 | try: 16 | boot.python_version_detect() 17 | boot.isDependencyReady() 18 | boot.initGlobalVar() 19 | except Exception as e: 20 | print("自检时出现异常:") 21 | err_info = traceback.format_exc() 22 | print("异常详细信息:\n"+err_info) 23 | exit(233) 24 | 25 | 26 | from package.learn import globalvar as gl 27 | from package.learn.data_management.datamanger import ExceptionLevel as errLevel 28 | from package.learn import learn_helper 29 | from package.learn.printer import color 30 | from package.learn.printer.factory import getPrinterBySetter 31 | from package.learn.printer.setter import * 32 | from package.learn.userinterface import select_Login_UI, login_of_QRCoed, login_of_history, add_new_user, \ 33 | change_user_data, delete_historical, system_settings, find_answers 34 | 35 | def start_learn(): 36 | spliter = gl.spliter 37 | mySetter = TableSetter() 38 | mySetter.hasHead = True 39 | mySetter.autoOrdNumber = True 40 | mySetter.headColor = "yellow" 41 | mySetter.abreastTableNumber = 2 42 | tablePrinter = getPrinterBySetter(mySetter) 43 | 44 | if gl.no_head: 45 | print(color.blue("选择模式") + "(" + "当前浏览器为 " + color.read("关闭显示") + "):") 46 | else: 47 | print(color.blue("选择模式") + "(" + "当前浏览器为 " + color.read("开启显示") + "):") 48 | 49 | tablePrinter.print([["选项"], ["开始学习"], ["查找答案"], ["用户设置"], ["系统设置"]]) 50 | key = input("\n输入序号:") 51 | spliter.print() 52 | if key == "1": 53 | print(color.blue("选择登陆方式:")) 54 | tablePrinter.print([["选项"], ["账号密码登陆"], ["二维码登陆"], ["历史登陆"]]) 55 | key = input("\n输入序号:") 56 | spliter.print() 57 | if key == "1": 58 | driver = select_Login_UI() 59 | elif key == "2": 60 | driver = login_of_QRCoed() 61 | elif key == "3": 62 | driver = login_of_history() 63 | else: 64 | raise Exception("序号输入错误") 65 | spliter.print() 66 | tablePrinter.print([["选项"], ["自动刷课"], ["从指定章节刷课"], ["作业(还未完善,暂时不要使用)"]]) 67 | key = input("\n输入序号:") 68 | spliter.print() 69 | try: 70 | if key == "1": 71 | learn_helper.automatic_learning(driver) 72 | elif key == "2": 73 | learn_helper.automatic_learning(driver, True) 74 | elif key == "3": 75 | learn_helper.do_homework(driver) 76 | else: 77 | raise Exception("序号输入错误") 78 | except Exception as e: 79 | # input("出现异常程序已经暂停"+traceback.format_exc()) 80 | driver.quit() 81 | gl.errorPrinter.print("程序运行出现异常\n异常原因:"+str(e)+"\n程序已退出,异常详细信息已写入日志中") 82 | err_info = traceback.format_exc() 83 | gl.exception_log_manger.writeLog(gl.version, err_info, errLevel.severe) 84 | elif key == "2": 85 | find_answers() 86 | elif key == "3": 87 | print(color.blue("选择设置:")) 88 | tablePrinter.print([["选项"], ["创建新用户"], ["修改用户信息"], ["删除所有历史登陆信息"]]) 89 | key = input("\n输入序号:") 90 | spliter.print() 91 | if key == "1": 92 | add_new_user() 93 | elif key == "2": 94 | change_user_data() 95 | elif key == "3": 96 | delete_historical() 97 | else: 98 | raise Exception("序号输入错误") 99 | elif key == "4": 100 | system_settings() 101 | 102 | 103 | if __name__ == '__main__': 104 | information = """ 105 | ============================================================ 106 | ·作者:Levitan 107 | ·本项目已在GitHub上开源 108 | ·GitHub地址:https://github.com/Levitans/XueXiTongAutoFlush 109 | ============================================================ 110 | """ 111 | print(color.magenta(information)) 112 | print(color.yellow(" LEARNING IS A BELIEF")) 113 | print() 114 | start_learn() 115 | -------------------------------------------------------------------------------- /package/learn/task/quiz/no_secret.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-05-16 23:31 3 | # @Author : Levitan 4 | # @File : no_secret.py 5 | # @Software : PyCharm 6 | import base64 7 | import hashlib 8 | import re 9 | import os 10 | from xml.dom.minidom import parse 11 | 12 | # 自定义包 13 | from package.learn.data_management.file import get_json_data 14 | 15 | # 第三方包 16 | from selenium.webdriver.common.by import By 17 | from fontTools.ttLib import TTFont 18 | 19 | class DecodeSecret: 20 | # 参数 statusCode 表示是否开启加密字符解密 21 | # statusCode 可取三个值分别是 0、1、2 22 | # 0 表示不启用解密 23 | # 1 表示启用解密 24 | # 2 表示程序自动判断是否解密 25 | def __init__(self, statusCode): 26 | if statusCode not in (0, 1, 2): 27 | raise Exception("实例化 DecodeSecret 对象时传入错误参数 "+str(statusCode)+",可传入数据有:0, 1, 2") 28 | self._statusCode = statusCode 29 | self.font_dict_name = "./package/font_dict.txt" 30 | self._secret_dict = {} 31 | self._font_dict = {} 32 | self._setFontDict() 33 | 34 | # 获取页面 font_face 的值 35 | def getFontFace(self, driver): 36 | if self._statusCode == 0: 37 | return 38 | fontFaceItem = driver.find_element(By.TAG_NAME, "head").find_elements(By.CSS_SELECTOR, '[type="text/css"]') 39 | fontFaceStr = "" 40 | for i in fontFaceItem: 41 | strData = i.get_attribute('innerHTML') 42 | if strData == "": 43 | continue 44 | else: 45 | try: 46 | fontFaceStr = re.findall(";base64,(.*)'[)] format", strData)[0] 47 | break 48 | except Exception as e: 49 | print("当前 fontFace 无法解析:"+str(e)) 50 | continue 51 | if self._statusCode == 1: 52 | if fontFaceStr == "": 53 | raise Exception("当前任务点无法获取 font_face 值") 54 | elif self._statusCode == 2: 55 | if fontFaceStr == "": 56 | self._statusCode = 0 57 | return 58 | else: 59 | self._statusCode = 1 60 | self._setSecretDict(fontFaceStr) 61 | 62 | """ 63 | 函数功能:解析加密后的字体数据,将字形信息和字体编码映射到 self._secret_dict 中 64 | 65 | 参数: 66 | :fontFace: 页面的 @font_face 中 base64 编码的值 67 | """ 68 | def _setSecretDict(self, fontFace): 69 | ttf_temp_path = "./package/temp.ttf" # 临时文件 temp.ttf 存放路径 70 | xml_temp_path = "./package/temp.xml" # 临时文件 temp.xml 存放路径 71 | 72 | # 将 fontFace 解析为 temp.ttf 文件,在吧temp.ttf 文件解析为 temp.xml 文件 73 | b = base64.b64decode(fontFace) 74 | with open(ttf_temp_path, "wb") as f: 75 | f.write(b) 76 | font = TTFont(ttf_temp_path) 77 | font.saveXML(xml_temp_path) 78 | 79 | # 将字的十进制code和字形信息映射在 self._secret_dict 中 80 | domTree = parse(xml_temp_path) 81 | rootNode = domTree.documentElement 82 | ttglyph_list = rootNode.getElementsByTagName("TTGlyph") 83 | for ttglyph in ttglyph_list: 84 | name = ttglyph.getAttribute('name') 85 | if name == ".notdef": 86 | continue 87 | code = int(re.findall("uni(.*)", name)[0], 16) # 10进制的值 88 | ttglyphStr = "" 89 | contour_list = ttglyph.getElementsByTagName("contour") 90 | for contour in contour_list: 91 | ttglyphStr += contour.toxml() 92 | value = hashlib.md5(ttglyphStr.encode(encoding="utf-8")).hexdigest() 93 | self._secret_dict[code] = value 94 | 95 | # 删除临时文件 96 | os.remove(ttf_temp_path) 97 | os.remove(xml_temp_path) 98 | 99 | """ 100 | 函数功能:读取 font_dict.txt 中的数据 101 | font_dict.txt 中存放的是学习通字体加密前,字形信息的md5值和字体编码的映射 102 | """ 103 | def _setFontDict(self): 104 | if self._statusCode == 0: 105 | return 106 | self._font_dict = get_json_data(self.font_dict_name) 107 | 108 | """ 109 | 函数功能:将加密字符串解密 110 | :string: 被加密的字符串 111 | :return: 返回解密后的字符串 112 | """ 113 | def decode(self, string: str): 114 | # 如果不开启解密则直接返回原字符串 115 | if self._statusCode == 0: 116 | return string 117 | trueStr = "" 118 | for word in string: 119 | wordMD5 = self._secret_dict.get(ord(word), None) 120 | if wordMD5 is None: 121 | trueStr += word 122 | continue 123 | trueWordCode = self._font_dict.get(wordMD5, None) 124 | trueWor = chr(trueWordCode) 125 | trueStr += trueWor 126 | return trueStr 127 | -------------------------------------------------------------------------------- /package/learn/globalvar.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-04-14 18:52 3 | # @Author : Levitan 4 | # @File : globalvar.py.py 5 | # @Software : PyCharm 6 | 7 | from package.learn.printer import color 8 | from package.learn.printer.factory import getPrinterBySetter 9 | from package.learn.printer.setter import * 10 | 11 | from package.learn.exception import InitializationException 12 | from .data_management.datamanger import * 13 | from .data_management import file 14 | 15 | 16 | # 全局变量是否初始化 17 | is_init = False 18 | 19 | # 学校配置 20 | school_type: str 21 | 22 | # 文件管理器 23 | exception_log_manger: ExceptionLogManger # 日志文件管理器 24 | 25 | # 文件地址 26 | user_file_path: str 27 | cookie_file_path: str 28 | 29 | # 浏览器配置 30 | no_head = False 31 | mute = False 32 | no_img = False 33 | browser_path = "" 34 | driver_path = "" 35 | 36 | # 任务点配置 37 | judgment_TP_tate = True 38 | quiz_get_answer_speed_max: int 39 | quiz_get_answer_speed_min: int 40 | quiz_click_speed_max: int 41 | quiz_click_speed_min: int 42 | decode_secret_status: int 43 | 44 | # 其他配置 45 | version = "" # 程序当前版本 46 | 47 | """ 48 | 初始化异常信息打印器和分隔线打印器 49 | """ 50 | # 初始化异常打印器 51 | errorPrinterSetter = MsgSetter("errorCfg") 52 | errorPrinterSetter.color = "read" 53 | errorPrinterSetter.horizontalSymbol = "*" 54 | errorPrinter = getPrinterBySetter(errorPrinterSetter) 55 | 56 | # 初始化分隔线打印器 57 | splitSetter = SplitSetter("myDividerCfg") 58 | splitSetter.symbol = "=" 59 | splitSetter.color = "green" 60 | splitSetter.length = 40 61 | splitSetter.message = ":I am the dividing line:" 62 | splitSetter.leftmostSymbol = "<" 63 | splitSetter.rightmostSymbol = ">" 64 | spliter = getPrinterBySetter(splitSetter) 65 | 66 | 67 | def init_global(): 68 | """ 69 | 在配置文件中加载配置 70 | """ 71 | global is_init, school_type, no_head, mute, no_img, browser_path, driver_path, user_file_path, cookie_file_path, \ 72 | quiz_get_answer_speed_max, quiz_get_answer_speed_min, quiz_click_speed_max, quiz_click_speed_min, \ 73 | exception_log_manger, version, decode_secret_status, judgment_TP_tate, version 74 | 75 | myCig = ConfigManger() 76 | try: 77 | # <--------------------加载浏览器配置-------------------------> 78 | 79 | if myCig.getCfg("browser_config", "no_head") == "True": 80 | no_head = True 81 | if myCig.getCfg("browser_config", "mute_audion") == "True": 82 | mute = True 83 | if myCig.getCfg("browser_config", "no_img") == "True": 84 | no_img = True 85 | browser_path = file.is_file_exists(myCig.getCfg("browser_config", "browser_path")) 86 | if browser_path == "": 87 | raise Exception("浏览器路径错误,请检查路径 "+color.read(myCig.getCfg("browser_config", "browser_path"))+" 的正确性") 88 | driver_path = file.is_file_exists(myCig.getCfg("browser_config", "driver_path")) 89 | if driver_path == "": 90 | raise Exception("驱动路径错误,请检查路径 "+color.read(myCig.getCfg("browser_config", "driver_path"))+" 的正确性") 91 | 92 | # <------------------------加载数据管理器-----------------------> 93 | user_file_path = myCig.getCfg("user_config", "user_path") 94 | cookie_file_path = myCig.getCfg("user_config", "cookie_path") 95 | exception_log_manger = ExceptionLogManger(myCig.getCfg("other", "exception_log_file_path")) 96 | 97 | # <------------------------加载任务点配置-----------------------> 98 | # ppt_speed_max = int(cfg_get("task_config", "ppt_speed_max")) 99 | # ppt_speed_min = int(cfg_get("task_config", "ppt_speed_min")) 100 | school_type = myCig.getCfg("school", "school_type") 101 | quiz_get_answer_speed_max = int(myCig.getCfg("task_config", "quiz_get_answer_speed_max")) 102 | quiz_get_answer_speed_min = int(myCig.getCfg("task_config", "quiz_get_answer_speed_min")) 103 | quiz_click_speed_max = int(myCig.getCfg("task_config", "quiz_click_speed_max")) 104 | quiz_click_speed_min = int(myCig.getCfg("task_config", "quiz_click_speed_min")) 105 | decode_secret_status = int(myCig.getCfg("task_config", "decode_secret_status")) 106 | judgment_TP_tate = eval(myCig.getCfg("task_config", "automatic_judgment_task_point_state")) 107 | 108 | # <------------------------加载其他点配置-----------------------> 109 | version = file.get_json_data(myCig.getCfg("other", "version_file_path")).get("current_version") 110 | # default_wait_time = int(myCig.getCfg("other", "default_wait_time")) 111 | # default_wait_times = int(myCig.getCfg("other", "default_wait_times")) 112 | 113 | # 所有配置加载成功 114 | is_init = True 115 | except Exception as e: 116 | raise InitializationException(str(e)) 117 | -------------------------------------------------------------------------------- /package/learn/printer/README.md: -------------------------------------------------------------------------------- 1 | # printer 包介绍 2 | 3 | --- 4 | 5 | > printer 包的作用是提供格式化的打印数据的功能,如打印表格、消息和分隔线等。 6 | > 该包使用了模板方法模式和工厂方法模式 7 | 8 | ## 1、模块介绍 9 | 10 | printer 主要由3个模块, 分别是 11 | 12 | - **打印器模块(printer.py)** 13 | 14 | > 打印模块中主存放打印器,**每个打印器都对应一个设置器** 15 | 16 | printer.py 中定义了打印器的模板(抽象父类)AbstractPrinter。AbstractPrinter规定每个打印器都必须*设置器(Setter类实例)*属性。 17 | 18 | 目前printer.py中实现了3个打印器,分别是: 19 | 20 | - **表格打印器(TablePrinter)** 21 | 22 | *TablePrinter* 主要实现的是将结构化的数据在控制台中以表格的形式打印,可以自动计算表格列宽,也可以手动传入表格列宽并将超过宽度的数据进行缩略显示。 23 | 24 | 还可以通过修改 *TableSetter* 的属性来自定义多种多样的表格外观。 25 | 26 | 27 | 28 | - **消息打印器(MsgPrinter)** 29 | 30 | *MsgPrinter* 主要功能是格式化的打印提示信息。 31 | 32 | 可通过修改 *MsgSetter* 的属性来自定义打印消息的外观 33 | 34 | 35 | 36 | - **分隔线打印器(SplitPrinter)** 37 | 38 | *SplitPrinter* 主要功能是打印分隔线。 39 | 40 | 可通过修改 *SplitSetter* 的属性来更改分隔线的外观和在分隔线中加入提示信息 41 | 42 | 43 | 44 | - **设置器模块(setter.py)** 45 | 46 | > 每一个设置器都对应一个打印器,设置器用户调整打印器打印的效果 47 | 48 | setter.py 模块中定义了所有打印器的设置器,其中的 AbstractSetter 是所有设置器的抽象父类 49 | 50 | - **表格设置器(TableSetter)** 51 | 52 | 用于设置 *TablePrinter* 打印出的表格效果,如数据对齐方式、表头和表身颜色、是否自动计算宽度等。 53 | 54 | 55 | 56 | - **消息设置器(MsgSetter)** 57 | 58 | 用于设置 *MsgPrinter* 打印出的消息效果 59 | 60 | 61 | 62 | - **分隔线设置器(SplitSetter)** 63 | 64 | 用于设置 *SplitPrinter* 打印出的分隔线的效果 65 | 66 | 67 | 68 | - **工厂模块(factory.py)** 69 | 70 | > 该模块中定义了获取各种 *打印器* 的方法 71 | 72 | - **getPrinterBySetter(setter: AbstractSetter) -> AbstractPrinter** 73 | 74 | 该方法通过传入的设置器类型来实例化出对应的打印器实例,并将该打印器的 *_setter* 属性设置为传入的设置器 75 | 76 | 77 | 78 | - **getPrinterByName(printerName: str) -> AbstractPrinter** 79 | 80 | 该方法通过传入的 *打印器类名* 来实例化对应的打印器。改方法实例化的打印器会使用默认的设置器。 81 | 82 | 83 | 84 | 85 | 86 | ## 2、使用演示 87 | 88 | 89 | 90 | ### TablePrinter 91 | 92 | **示例代码** 93 | 94 | ~~~python 95 | data = [["学号", "姓名", "年级", "专业", "性别"], 96 | ["1001", "Levitan", "大三", "数据科学与大数据技术", "男"], 97 | ["1002", "Bob", "大一", "数据科学与大数据技术"], 98 | ["1003", "Jack", "大二", "工程造价"], 99 | ["1004", "小红", "大四", "环境设计", "女"], 100 | ["1005", "小明", "大一", "", "男"]] 101 | 102 | # 实例化配置类,修改配置信息 103 | myCfg = TableSetter("demo") 104 | myCfg.autoOrdNumber = True 105 | myCfg.hasHead = True 106 | myCfg.headColor = "blue" 107 | 108 | a = getPrinterBySetter(myCfg) # 获取显示器 109 | a.print(data) # 传入数据进行显示 110 | ~~~ 111 | 112 | **效果** 113 | 114 | ~~~ 115 | +------+-----------+---------+-----------+----------------------+--------+ 116 | | 序号 | StudentID | Name | Grade | Major | Sex | 117 | +------+-----------+---------+-----------+----------------------+--------+ 118 | | 1 | 1001 | Levitan | junior | Data Science | male | 119 | +------+-----------+---------+-----------+----------------------+--------+ 120 | | 2 | 1002 | Bob | freshmen | Accounting | | 121 | +------+-----------+---------+-----------+----------------------+--------+ 122 | | 3 | 1003 | Jack | sophomore | Project Costs | | 123 | +------+-----------+---------+-----------+----------------------+--------+ 124 | | 4 | 1004 | Alice | senior | Environmental Design | female | 125 | +------+-----------+---------+-----------+----------------------+--------+ 126 | | 5 | 1005 | Tom | freshmen | | male | 127 | +------+-----------+---------+-----------+----------------------+--------+ 128 | ~~~ 129 | 130 | 131 | 132 | ### MsgPrinter 133 | 134 | **示例代码** 135 | 136 | ~~~python 137 | data = "Error in current program\n" \ 138 | "Error reason: subscript out of bounds\n" \ 139 | "You can view \"\\errorDemo\\README.md\" to solve this problem" 140 | mySetter = MsgSetter() 141 | mySetter.color = "read" 142 | printer = getPrinterBySetter(mySetter) 143 | printer.setSetter(mySetter) 144 | printer.print(data) 145 | ~~~ 146 | 147 | **效果** 148 | 149 | ~~~ 150 | ============================================================= 151 | | Error in current program | 152 | | Error reason: subscript out of bounds | 153 | | You can view "\errorDemo\README.md" to solve this problem | 154 | ============================================================= 155 | ~~~ 156 | 157 | 158 | 159 | ### SplitPrinter 160 | 161 | **示例代码** 162 | 163 | ~~~python 164 | mySetter = SplitSetter() 165 | mySetter.symbol = "=" 166 | mySetter.color = "green" 167 | mySetter.length = 40 168 | mySetter.message = ":I am the dividing line:" 169 | mySetter.leftmostSymbol = "<" 170 | mySetter.rightmostSymbol = ">" 171 | printer = getPrinterBySetter(mySetter) 172 | printer.print() 173 | ~~~ 174 | 175 | **效果** 176 | 177 | ~~~ 178 | <====================:I am the dividing line:====================> 179 | ~~~ 180 | 181 | -------------------------------------------------------------------------------- /package/learn/data_management/datamanger.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-04-14 21:10 3 | # @Author : Levitan 4 | # @File : datamanger.py 5 | # @Software : PyCharm 6 | 7 | # 系统包 8 | import time 9 | import pickle 10 | import base64 11 | from . import file 12 | 13 | # 用户数据文件管理器 14 | class UserManger: 15 | def __init__(self, filename): 16 | self.__filename = filename 17 | self.__users = file.get_json_data(filename) 18 | 19 | def getUserData(self, username) -> dict: 20 | return self.__users[username] 21 | 22 | def getUsersName(self) -> list: 23 | return list(self.__users.keys()) 24 | 25 | def modifyUserData(self, name, newData, mode="pwd"): 26 | """ 27 | :param name: 需要修改信息的用户名 28 | :param newData: 需要修改的新信息 29 | :param mode: 30 | "pwd": 修改密码 31 | "acc": 修改账号 32 | "del": 删除用户 33 | :return: void 34 | """ 35 | if mode == "pwd": 36 | self.__users[name]["password"] = newData 37 | elif mode == "acc": 38 | self.__users[name]["account"] = newData 39 | elif mode == "del": 40 | self.__users.pop(name) 41 | file.save_json_data(self.__filename, self.__users) 42 | 43 | def addNewUser(self, name, account, password): 44 | self.__users[name] = {"account": account, "password": password} 45 | file.save_json_data(self.__filename, self.__users) 46 | 47 | 48 | # cookies 文件管理器 49 | class CookiesManger: 50 | def __init__(self, filename): 51 | self.__filename = filename 52 | self.__cookies = self.__readData(filename) 53 | 54 | def __readData(self, filename): 55 | cookiesDict = file.get_json_data(filename) 56 | usernameList = cookiesDict.keys() 57 | expiredUsernameList = [] 58 | for username in usernameList: 59 | cookies_b64 = cookiesDict[username] 60 | cookies_bytes = base64.b64decode(cookies_b64) 61 | cookies_list = pickle.loads(cookies_bytes) 62 | # 检查cookies是否过期 63 | # 如果过期则移出 cookies 字典 64 | for i in cookies_list: 65 | if "expiry" not in list(i.keys()): 66 | continue 67 | expiry_timestamp = int(i['expiry']) 68 | if expiry_timestamp < int(time.time()): 69 | expiredUsernameList.append(username) 70 | break 71 | for expiredUsername in expiredUsernameList: 72 | cookiesDict.pop(expiredUsername) 73 | file.save_json_data(self.__filename, cookiesDict) 74 | return cookiesDict 75 | 76 | def getNameList(self): 77 | return list(self.__cookies.keys()) 78 | 79 | def getCookies(self, username): 80 | # 用户不存在返回空 81 | if username not in self.getNameList(): 82 | return [] 83 | cookies_b64 = self.__cookies[username] 84 | cookies_bytes = base64.b64decode(cookies_b64) 85 | cookies_list = pickle.loads(cookies_bytes) 86 | return cookies_list 87 | 88 | def setCookies(self, username, cookies): 89 | cookies_bytes = pickle.dumps(cookies) 90 | cookies_b64 = base64.b64encode(cookies_bytes) 91 | self.__cookies[username] = str(cookies_b64, encoding="utf-8") 92 | file.save_json_data(self.__filename, self.__cookies) 93 | 94 | def isUserExist(self, username: str) -> bool: 95 | return username in self.getNameList() 96 | 97 | def removeCookie(self, username): 98 | self.__cookies.pop(username) 99 | file.save_json_data(self.__filename, self.__cookies) 100 | 101 | def removeAll(self): 102 | self.__cookies.clear() 103 | file.save_json_data(self.__filename, self.__cookies) 104 | 105 | 106 | # 异常等级类 107 | # 用于指定异常发生的等级 108 | class ExceptionLevel: 109 | low = 1 110 | middle = 2 111 | high = 3 112 | severe = 0 113 | 114 | 115 | # 日志文件管理器 116 | class ExceptionLogManger: 117 | EXC_LEVEL = {0: "严重", 1: "低级", 2: "中级", 3: "高级"} 118 | 119 | def __init__(self, filePath): 120 | self.__filename = filePath 121 | 122 | def writeLog(self, version, info, excLevel=ExceptionLevel.low): 123 | # 异常等级 124 | level = self.EXC_LEVEL[excLevel] 125 | # 时间 126 | nowTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) 127 | # 异常信息 128 | data = "异常等级:"+level + \ 129 | "\n时间:"+nowTime + \ 130 | "\n版本:"+version + \ 131 | "\n异常信息:\n"+info + \ 132 | "\n\n" 133 | file.append_text_file(self.__filename, data) 134 | 135 | 136 | # 配置文件管理器 137 | class ConfigManger: 138 | _configFilePath = r".\package\config.ini" 139 | _config = file.get_config_file(_configFilePath) 140 | 141 | @classmethod 142 | def getCfg(cls, section, option, default_value=None): 143 | try: 144 | return cls._config.get(section, option) 145 | except Exception as e: 146 | if default_value is not None: 147 | return default_value 148 | err_info = "配置文件路径:" + config_file_path + "\n" + \ 149 | '读取配置失败:section="' + section + '", option="' + option + '"\n错误信息:' + str(e) 150 | raise Exception(err_info) 151 | 152 | @classmethod 153 | def change_cfg(cls, section, option, value): 154 | cls._config.set(section, option, value) 155 | cls._config.write(open(cls._configFilePath, "w", encoding="utf-8")) 156 | -------------------------------------------------------------------------------- /package/learn/driver/mydriver.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-04-14 18:52 3 | # @Author : Levitan 4 | # @File : mydriver.py 5 | # @Software : PyCharm 6 | 7 | # 自定义包 8 | from . import useragent, driverException as myException 9 | 10 | # 第三方包 11 | from selenium import webdriver 12 | from selenium.common import exceptions 13 | from selenium.webdriver.common.by import By 14 | from selenium.webdriver.support.ui import WebDriverWait 15 | from selenium.webdriver.remote.webelement import WebElement 16 | 17 | 18 | class MyDriver: 19 | def __init__(self, browserPath, driverPath, noImg=False, noHead=False, mute=False): 20 | try: 21 | # 配置驱动选项 22 | options = webdriver.ChromeOptions() 23 | options.add_experimental_option('excludeSwitches', ['enable-logging']) 24 | if noImg: 25 | options.add_argument('blink-settings=imagesEnabled=true') 26 | if noHead: 27 | options.add_argument('headless') 28 | options.add_argument('--disable-gpu') 29 | if mute: 30 | options.add_argument('--mute-audion') 31 | options.add_argument('--user-agent={}'.format(useragent.getheaders())) 32 | options.binary_location = browserPath 33 | 34 | # 创建驱动实例 35 | self.__driver = webdriver.Chrome(executable_path=driverPath, options=options) 36 | except Exception as e: 37 | print(e.__str__()) 38 | 39 | # 访问url 40 | def get_url(self, url): 41 | self.__driver.get(url) 42 | 43 | # 返回驱动 44 | def get_driver(self): 45 | return self.__driver 46 | 47 | # 通过账号密码登陆 48 | # 登录成功后将会返回cookies 49 | def login_with_acc_and_pwd(self, act, pwd): 50 | self.get_url("http://i.chaoxing.com") 51 | # 1、设置账号 52 | account = self.__driver.find_element(By.ID, "phone") 53 | account.send_keys(act) 54 | # 2、设置密码 55 | password = self.__driver.find_element(By.ID, "pwd") 56 | password.send_keys(pwd) 57 | # 3、点击登录 58 | self.__driver.find_element(By.ID, "loginBtn").click() 59 | # 4、等待页面加载 60 | try: 61 | self.driver_wait(By.CLASS_NAME, "header") 62 | except myException.TimeoutException as e: 63 | raise Exception("网络延迟,页面未正常加载") 64 | # 5、返回cookies 65 | return self.__driver.get_cookies() 66 | 67 | # 使用二维码登陆 68 | # 登陆成功后返回登陆者的姓名和cookies 69 | def login_with_QRCode(self): 70 | self.get_url("http://i.chaoxing.com") 71 | 72 | # 等待用户扫描二维码 73 | try: 74 | self.driver_wait(By.CLASS_NAME, "user-name") 75 | except myException.TimeoutException: 76 | raise Exception("网络延迟,页面未正常加载") 77 | 78 | # 获取用户名和cookies值 79 | username = self.__driver.find_element(By.CLASS_NAME, "user-name").text 80 | cookies = self.__driver.get_cookies() 81 | return username, cookies 82 | 83 | # 通过 cookie 登陆 84 | def login_with_cookies(self, cookies): 85 | self.get_url("http://i.chaoxing.com") 86 | self.set_cookies(cookies) 87 | self.get_url("http://i.chaoxing.com") 88 | 89 | # 获取二维码的URL 90 | def getQRCoed(self): 91 | try: 92 | img = WebDriverWait(self.__driver, 30, 0.2).until( 93 | lambda driver: driver.find_element(By.ID, "quickCode") 94 | ) 95 | path = img.get_attribute("src") 96 | except exceptions.TimeoutException: 97 | print("当前网络缓慢...") 98 | else: 99 | return path 100 | 101 | def get_cookies(self): 102 | cookies = self.__driver.get_cookies() 103 | return cookies 104 | 105 | def set_cookies(self, cookies): 106 | try: 107 | for i in cookies: 108 | self.__driver.add_cookie(i) 109 | except exceptions.InvalidCookieDomainException as e: 110 | print(e.__str__) 111 | 112 | # 跳转到课程的页面 113 | def go_courses_page(self): 114 | self.__driver.get("http://mooc1-1.chaoxing.com/visit/interaction") 115 | self.driver_wait(By.CLASS_NAME, "course-list") 116 | 117 | # 判断元素在页面中是否存在 118 | # 如果存在,返回该元素 119 | # 如果不存在,返回None 120 | def is_element_presence(self, by, value): 121 | try: 122 | item = self.__driver.find_element(by, value) 123 | except: 124 | return None 125 | return item 126 | 127 | def getElement(self, by, value) -> WebElement: 128 | self.driver_wait(by, value) 129 | return self.__driver.find_element(by, value) 130 | 131 | def getElements(self, by, value) -> list[WebElement]: 132 | self.driver_wait(by, value) 133 | return self.__driver.find_elements(by, value) 134 | 135 | # 等待元素出现 136 | def driver_wait(self, by, value, wait_time=30, wait_times=5): 137 | waitCount = 1 138 | while True: 139 | try: 140 | WebDriverWait(self.__driver, wait_time, 0.2).until( 141 | lambda driver: driver.find_element(by, value) 142 | ) 143 | break 144 | except exceptions.TimeoutException: 145 | waitCount += 1 146 | if waitCount > wait_times: 147 | raise myException.TimeoutException("当前网络延迟严重\n通过 " + by + " 在页面中没有找到 " + value) 148 | print("当前网络缓慢...") 149 | print("程序会等待 " + str(wait_times) + " 轮,当前等待第 " + str(waitCount) + " 轮") 150 | 151 | # 使用js 152 | def go_js(self, js): 153 | self.__driver.execute_script(js) 154 | 155 | # 关闭驱动 156 | def quit(self): 157 | self.__driver.quit() 158 | -------------------------------------------------------------------------------- /package/learn/driver/useragent.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-04-15 14:54 3 | # @Author : Levitan 4 | # @File : useragent.py 5 | # @Software : PyCharm 6 | 7 | import random 8 | 9 | # 获取一个随机的 userAgent 值 10 | def getheaders(): 11 | user_agent_library = [ 12 | "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36", 13 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36", 14 | "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36", 15 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36", 16 | "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2226.0 Safari/537.36", 17 | "Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36", 18 | "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36", 19 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2224.3 Safari/537.36", 20 | "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.93 Safari/537.36", 21 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36", 22 | "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36", 23 | "Mozilla/5.0 (Windows NT 4.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36", 24 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36", 25 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36", 26 | "Mozilla/5.0 (X11; OpenBSD i386) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36", 27 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1944.0 Safari/537.36", 28 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.3319.102 Safari/537.36", 29 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.2309.372 Safari/537.36", 30 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.2117.157 Safari/537.36", 31 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36", 32 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1866.237 Safari/537.36", 33 | "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.137 Safari/4E423F", 34 | "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.517 Safari/537.36", 35 | "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.36", 36 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1664.3 Safari/537.36", 37 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1664.3 Safari/537.36", 38 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36", 39 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1623.0 Safari/537.36", 40 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.17 Safari/537.36", 41 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.62 Safari/537.36", 42 | "Mozilla/5.0 (X11; CrOS i686 4319.74.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.57 Safari/537.36", 43 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.2 Safari/537.36", 44 | "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1468.0 Safari/537.36", 45 | "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1467.0 Safari/537.36", 46 | "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1464.0 Safari/537.36", 47 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1500.55 Safari/537.36", 48 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36", 49 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36", 50 | "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36", 51 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36", 52 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36", 53 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36", 54 | "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.90 Safari/537.36", 55 | "Mozilla/5.0 (X11; NetBSD) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36", 56 | "Mozilla/5.0 (X11; CrOS i686 3912.101.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36", 57 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.60 Safari/537.17", 58 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1309.0 Safari/537.17", 59 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.15 (KHTML, like Gecko) Chrome/24.0.1295.0 Safari/537.15", 60 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.14 (KHTML, like Gecko) Chrome/24.0.1292.0 Safari/537.14" 61 | ] 62 | 63 | user_agent = random.choice(user_agent_library) 64 | return user_agent 65 | -------------------------------------------------------------------------------- /package/learn/task/quiz/quiz.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2021-10-30 22:20 3 | # @Author : Levitan 4 | # @File : homework.py 5 | # @Software : PyCharm 6 | import time 7 | import random 8 | 9 | from ..interface import Task, Answerable 10 | from .getanswer import GetAnswer 11 | from .multipleChoiceOfTask import MultipleChoiceOfTask 12 | from .trueOrFalseOfTask import TrueOrFalseOfTask 13 | from .no_secret import DecodeSecret 14 | 15 | # 导入全局变量 16 | from package.learn.globalvar import decode_secret_status, quiz_get_answer_speed_min, quiz_get_answer_speed_max, \ 17 | quiz_click_speed_min, quiz_click_speed_max, color, spliter 18 | 19 | # 第三方库 20 | from selenium.webdriver.common.by import By 21 | from selenium.common.exceptions import NoSuchElementException 22 | 23 | 24 | class QuizOfTask(Task): 25 | def __init__(self, driver): 26 | super().__init__(driver) 27 | self.__name__ = "答题" 28 | self.__questionList: list[Answerable] = [] 29 | 30 | def isCurrentTask(self, iframeIndex) -> bool: 31 | # 还没找到判断任务点是否为答题的方法,所以直接返回True 32 | return True 33 | 34 | def __getData(self): 35 | # 进入iframe 36 | iframe = self._Task__driver.find_element(By.CSS_SELECTOR, '[id="frame_content"]') 37 | self._Task__driver.switch_to.frame(iframe) 38 | 39 | # 实例化 DecodeSecret 类 40 | decodeSecret = DecodeSecret(decode_secret_status) 41 | if decode_secret_status == 0: 42 | print(color.yellow("不启用字体解密")) 43 | elif decode_secret_status == 1: 44 | print(color.yellow("启用字体解密")) 45 | elif decode_secret_status == 2: 46 | print(color.yellow("程序自动判断是是否需要字体解密")) 47 | decodeSecret.getFontFace(self._Task__driver) 48 | 49 | # 获取页面中的所有题目 50 | questionList = self._Task__driver.find_elements(By.CSS_SELECTOR, '[class="TiMu"]') 51 | print("当前页面共有{}题".format(len(questionList))) 52 | 53 | myGetAnswer = GetAnswer() 54 | for i in range(len(questionList)): 55 | spliter.print() 56 | item = questionList[i] 57 | title = decodeSecret.decode(item.find_element(By.CSS_SELECTOR, '[class="Zy_TItle clearfix"]').text.replace("\n", "")) 58 | # 获取问题 59 | question = title[title.find("】")+1:] 60 | 61 | # 题目类型 62 | questionType = title[title.find("【")+1: title.find("】")] 63 | 64 | # 判断题目能否解决 65 | if questionType not in ("单选题", "多选题", "判断题"): 66 | print(color.blue("程序能回答的题有:单选题、多选题、判断题")) 67 | print(color.blue("本题类型为:" + questionType)) 68 | print(color.blue("跳过该题")) 69 | continue 70 | # 获取问题答案 71 | answerList = myGetAnswer.getAnswer(question, questionType) 72 | # 判断是否找到答案 73 | if answerList is None or len(answerList) == 0: 74 | continue 75 | if questionType in ("单选题", "多选题"): 76 | # 获取题目选项的WebElement对象 77 | # optionWebElementList = item.find_elements(By.CSS_SELECTOR, '[class="fl after"]') 78 | optionWebElementList = item.find_elements(By.TAG_NAME, 'li') 79 | # 获取题目选项 80 | optionTextList = [] 81 | for option in optionWebElementList: 82 | """ 83 | 说明变更时间:===========2022-05-14=========== 84 | 目前发现,在课程章节里的答题任务点中,单选和多选题目类型的选项标签中有以下三个class值 85 | 86 | class="fl after" 87 | class="font-cxsecret fl after" 88 | class="fl before" 89 | 90 | 学习通的作业页面中的答题任务和章节中的答题任务差异非常大,所以目前 quiz 模块无直接用于作业功能 91 | """ 92 | try: 93 | rawData = option.find_element(By.CSS_SELECTOR, '[class="fl after"]').text.replace("\n", "") 94 | optionTextList.append(decodeSecret.decode(rawData)) 95 | except NoSuchElementException: 96 | try: 97 | rawData = option.find_element(By.CSS_SELECTOR, '[class="font-cxsecret fl after"]').text.replace("\n", "") 98 | optionTextList.append(decodeSecret.decode(rawData)) 99 | except NoSuchElementException: 100 | try: 101 | rawData = option.find_element(By.CSS_SELECTOR, '[class="fl before"]').text.replace("\n", "") 102 | optionTextList.append(decodeSecret.decode(rawData)) 103 | except NoSuchElementException: 104 | raise NoSuchElementException('题目的选项的class不在 ["fl after", "font-cxsecret fl after", "fl before"] 中,系统无法获取题目选项') 105 | self.__questionList.append( 106 | MultipleChoiceOfTask(item, questionType, question, answerList, optionTextList, optionWebElementList)) 107 | elif questionType == "判断题": 108 | answerWebElementList = item.find_elements(By.TAG_NAME, "label") 109 | self.__questionList.append(TrueOrFalseOfTask(item, questionType, question, answerList[0], answerWebElementList)) 110 | else: 111 | print(color.read("当前题目类型程序无法判断")) 112 | print(color.read("题目类型:"+questionType)) 113 | print(color.read("题目内容:"+question)) 114 | print(color.read("跳过该题目")) 115 | time.sleep(random.randint(quiz_get_answer_speed_min, quiz_get_answer_speed_max)) # 防止访问接口频率过高被判断为爬虫 116 | myGetAnswer.close() 117 | 118 | def finish(self): 119 | self.__getData() 120 | for i in range(len(self.__questionList)): 121 | print("正在完成第{}题".format(i+1)) 122 | webElementList = self.__questionList[i].getAnswerWebElement() 123 | if len(webElementList) == 0: 124 | print("查找答案和选项答案不匹配") 125 | continue 126 | for answerWebElement in webElementList: 127 | self._Task__driver.execute_script("arguments[0].scrollIntoView();", self.__questionList[i].qWebObj) 128 | time.sleep(1) 129 | answerWebElement.click() 130 | time.sleep(random.randint(quiz_click_speed_min, quiz_get_answer_speed_max)) 131 | self.__submitOrSave() 132 | 133 | def __submitOrSave(self): 134 | # 暂时不启用判断是否提交 135 | # 现在都是点击保存,程序不会主动提交 136 | # if self.__checkpoint: 137 | # self._Task__driver.find_element(By.CSS_SELECTOR, '[href="javascript:void(0);"][onclick="btnBlueSubmit();"]').click() 138 | # time.sleep(1) 139 | # try: 140 | # self._Task__driver.find_element(By.CSS_SELECTOR, '[class="bluebtn "][onclick="form1submit();"]').click() 141 | # except Exception: 142 | # self._Task__driver.find_element(By.CSS_SELECTOR, '[class="bluebtn "][onclick="submitCheckTimes();"]').click() 143 | # time.sleep(1) 144 | # else: 145 | # self._Task__driver.find_element(By.CSS_SELECTOR, '[onclick="noSubmit();"]').click() 146 | # time.sleep(1) 147 | # self._Task__driver.switch_to.alert.accept() 148 | self._Task__driver.find_element(By.CSS_SELECTOR, '[onclick="noSubmit();"]').click() 149 | time.sleep(1) 150 | self._Task__driver.switch_to.alert.accept() 151 | 152 | -------------------------------------------------------------------------------- /package/learn/userinterface.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-04-16 15:49 3 | # @Author : Levitan 4 | # @File : userinterface.py 5 | # @Software : PyCharm 6 | 7 | from package.learn import globalvar as gl 8 | from package.learn.driver.mydriver import MyDriver 9 | from package.learn.task.quiz.getanswer import GetAnswer 10 | 11 | from package.learn.data_management.datamanger import ConfigManger, UserManger, CookiesManger 12 | 13 | from package.learn.printer import color 14 | from package.learn.printer.factory import getPrinterBySetter 15 | from package.learn.printer.setter import TableSetter, MsgSetter 16 | 17 | mySetter = TableSetter() 18 | mySetter.hasHead = True 19 | mySetter.autoOrdNumber = True 20 | mySetter.headColor = "green" 21 | mySetter.abreastTableNumber = 2 22 | tablePrinter = getPrinterBySetter(mySetter) 23 | 24 | user_manager = UserManger(gl.user_file_path) 25 | cookie_manager = CookiesManger(gl.cookie_file_path) 26 | 27 | # --------------------------各种登陆交互---------------------------- 28 | def select_Login_UI(): 29 | username_list = user_manager.getUsersName() 30 | print(color.yellow(" 当前系统中的用户如下表\n")) 31 | print(color.blue("选择登陆用户:")) 32 | printList = [["用户名"]] 33 | printList += [[_] for _ in username_list] 34 | tablePrinter.print(printList) 35 | print() 36 | index = int(input("选择用户:")) - 1 37 | username = username_list[index] 38 | gl.spliter.print() 39 | 40 | print("正在登陆...") 41 | driver = MyDriver( 42 | gl.browser_path, 43 | gl.driver_path, 44 | gl.no_img, 45 | gl.no_head 46 | ) 47 | # 查看本地是否储存所选用户的 cookies 48 | # 如果存在则用 cookies 登陆,如果不存在则用账号密码登陆,登陆成功后保存 cookies 49 | if cookie_manager.isUserExist(username): 50 | driver.login_with_cookies(cookie_manager.getCookies(username)) 51 | else: 52 | userData = user_manager.getUserData(username) 53 | cookies = driver.login_with_acc_and_pwd(userData["account"], userData["password"]) 54 | cookie_manager.setCookies(username, cookies) 55 | return driver 56 | 57 | 58 | def login_of_QRCoed(): 59 | driver = MyDriver( 60 | gl.browser_path, 61 | gl.driver_path, 62 | gl.no_img, 63 | noHead=False 64 | ) 65 | print("正在打开二维码登陆界面,请稍后...") 66 | username, cookies = driver.login_with_QRCode() 67 | print("请扫描浏览器上的二维码") 68 | cookie_manager.setCookies(username, cookies) 69 | print("登陆成功") 70 | driver.quit() 71 | if gl.no_head: 72 | print("请稍后") 73 | driver = MyDriver( 74 | gl.browser_path, 75 | gl.driver_path, 76 | gl.no_img, 77 | gl.no_head 78 | ) 79 | driver.login_with_cookies(cookie_manager.getCookies(username)) 80 | return driver 81 | 82 | 83 | def login_of_history(): 84 | print(color.yellow("==================================================")) 85 | print(color.yellow("| 历史登陆中的信息有存活期限 |")) 86 | print(color.yellow("| 程序会自动删除过期的登陆数据 |")) 87 | print(color.yellow("| 若没有你的登陆信息请重新扫码登陆或账号密码登陆 |")) 88 | print(color.yellow("==================================================")) 89 | 90 | print(color.blue("选择登陆用户:")) 91 | name_list = cookie_manager.getNameList() 92 | printList = [["名称"]] 93 | printList += [[_] for _ in name_list] 94 | tablePrinter.print(printList) 95 | print() 96 | index = int(input("选择用户:")) - 1 97 | name = name_list[index] 98 | gl.spliter.print() 99 | print("正在登陆...") 100 | driver = MyDriver( 101 | gl.browser_path, 102 | gl.driver_path, 103 | gl.no_img, 104 | gl.no_head 105 | ) 106 | driver.login_with_cookies(cookie_manager.getCookies(name)) 107 | print("登陆成功") 108 | return driver 109 | 110 | 111 | # -----------------------答案查找页面--------------------------------- 112 | def find_answers(): 113 | msgSetter = MsgSetter() 114 | msgSetter.color = "yellow" 115 | msgPrinter = getPrinterBySetter(msgSetter) 116 | msgPrinter.print("\n 注意!查找答案的频率不要过高\n频率过高会被接口判定为爬虫,有被封ip风险\n") 117 | print() 118 | getAnswer = GetAnswer() 119 | while True: 120 | q = input(color.blue("输入题目(q退出):")) 121 | if q == "q" or q == "Q": 122 | break 123 | answerList = getAnswer.getAnswer(q) 124 | if len(answerList) == 0: 125 | print(color.yellow("没有找到这题的答案")) 126 | gl.spliter.print() 127 | 128 | 129 | # ---------------------各种用户设置交互-------------------------------- 130 | def add_new_user(): 131 | userName = input("输入用户名:") 132 | if userName in user_manager.getUsersName(): 133 | print("用户名已存在!!") 134 | return 135 | else: 136 | account = input("输入手机号:") 137 | password = input("输入密码:") 138 | user_manager.addNewUser(userName, account, password) 139 | print("用户添加成功") 140 | 141 | 142 | def change_user_data(): 143 | username_list = user_manager.getUsersName() 144 | print(color.blue("当前系统中的用户有:")) 145 | printDataList = [["用户名"]] 146 | printDataList += [[_] for _ in username_list] 147 | tablePrinter.print(printDataList) 148 | index = int(input("\n选择需要修改的用户:")) - 1 149 | gl.spliter.print() 150 | username = username_list[index] 151 | userdata = user_manager.getUserData(username) 152 | print("{}的数据:".format(username)) 153 | print("\t账号:" + userdata["account"]) 154 | print("\t密码:" + userdata["password"] + "\n") 155 | tablePrinter.print([["选项"], ["修改账号"], ["修改密码"], ["删除账号"]]) 156 | key = input("\n选择修改信息:") 157 | gl.spliter.print() 158 | if key == "1": 159 | newData = input("输入新账号:") 160 | user_manager.modifyUserData(username, newData, "pwd") 161 | elif key == "2": 162 | newData = input("输入新密码:") 163 | user_manager.modifyUserData(username, newData) 164 | elif key == "3": 165 | user_manager.modifyUserData(username, "", "del") 166 | else: 167 | raise Exception("输入序号错误!!!") 168 | print("信息修改成功") 169 | 170 | 171 | def delete_historical(): 172 | cookie_manager.removeAll() 173 | print("历史记录删除成功") 174 | 175 | 176 | # ----------------------------系统设置交互-------------------------------- 177 | def system_settings(): 178 | cfgManger = ConfigManger() 179 | print(color.blue("选择设置:")) 180 | tablePrinter.print([["选项"], ["浏览器显示"], ["浏览器声音"]]) 181 | key = input("\n输入序号:") 182 | gl.spliter.print() 183 | if key == "1": 184 | info = color.blue("关") if gl.no_head else color.blue("开") 185 | print("\t当前浏览器显示为:" + info) 186 | print(color.blue("选择设置选项:")) 187 | tablePrinter.print([["选项"], ["开启显示"], ["关闭显示"]]) 188 | key = input("\n输入序号:") 189 | gl.spliter.print() 190 | if key == "1": 191 | cfgManger.change_cfg("browser_config", "no_head", "False") 192 | elif key == "2": 193 | cfgManger.change_cfg("browser_config", "no_head", "True") 194 | else: 195 | raise Exception("输入序号错误!!!") 196 | 197 | elif key == "2": 198 | info = color.blue("关") if gl.mute else color.blue("开") 199 | print("\t当前浏览器声音为:" + info) 200 | print(color.blue("选择设置选项:")) 201 | tablePrinter.print([["选项"], ["开启声音"], ["关闭声音"]]) 202 | key = input("\n输入序号:") 203 | gl.spliter.print() 204 | if key == "1": 205 | cfgManger.change_cfg("browser_config", "mute_audion", "False") 206 | elif key == "2": 207 | cfgManger.change_cfg("browser_config", "mute_audion", "True") 208 | else: 209 | raise Exception("输入序号错误!!!") 210 | else: 211 | raise Exception("输入序号错误!!!") 212 | print("设置修改成功") 213 | -------------------------------------------------------------------------------- /package/learn/learn_helper.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-04-16 0:08 3 | # @Author : Levitan 4 | # @File : learn_helper.py 5 | # @Software : PyCharm 6 | import random 7 | import time 8 | import traceback 9 | 10 | from package.learn import globalvar as gl 11 | from package.learn import exception as myException 12 | from package.learn.driver.mydriver import MyDriver 13 | from package.learn.school.getter import schoolGetter 14 | from package.learn.task.quiz.quiz import QuizOfTask 15 | from package.learn.task.audio import Audio 16 | from package.learn.task.video import Video 17 | from package.learn.task.ppt import PPT 18 | 19 | from package.learn.data_management.datamanger import ExceptionLevel as errLevel 20 | 21 | from package.learn.printer import color 22 | from package.learn.printer.factory import getPrinterBySetter 23 | from package.learn.printer.setter import TableSetter 24 | 25 | import package.learn.driver 26 | 27 | from selenium.webdriver.common.by import By 28 | from selenium.common import exceptions 29 | 30 | # 该方法获页面中的课程,并返回用户选择的课程 31 | def getCourse(driver: MyDriver, learn): 32 | # 获取所有课程 33 | driver.go_courses_page() 34 | try: 35 | courses_list = learn.get_courses(driver.get_driver()) 36 | except exceptions.NoSuchElementException as e: 37 | errInfo = traceback.format_exc() 38 | gl.exception_log_manger.writeLog(gl.version, errInfo) 39 | raise Exception("获取课程时出现异常:" + str(e) + "\n可能原因见:package\\learn\\school\\README.md") 40 | 41 | # 以表格的形式打印课程 42 | 43 | # 设置表格的外观 44 | mySetter = TableSetter() 45 | mySetter.hasHead = True 46 | mySetter.autoOrdNumber = True 47 | mySetter.headColor = "green" 48 | mySetter.abreastTableNumber = 2 49 | tablePrinter = getPrinterBySetter(mySetter) 50 | tablePrinter.setTableColWidth([30]) 51 | 52 | # 设置需要打印的数据 53 | courseNameList = [["课程名"]] 54 | courseNameList += [[i.name] for i in courses_list] 55 | 56 | # 打印 57 | print(color.blue("当前课程有:")) 58 | tablePrinter.print(courseNameList) 59 | 60 | # 读取用户指定的课程 61 | index = int(input("\n输入课程序号:")) - 1 62 | course = courses_list[index] 63 | gl.spliter.print() 64 | return course 65 | 66 | 67 | def automatic_learning(driver: MyDriver, specificChapter=False): 68 | """ 69 | learn = fuist 70 | 这一句是指定学校 71 | 不同的学校对应 package/learn/school 下的一个 .py 文件 72 | 如果程序运行时没有找到课程,或者进入课程后没有找到章节,则大概率是因为学校不同导致页面元素不同。 73 | 详细解决方案看 package/learn/school/README.md 74 | """ 75 | learn = schoolGetter(gl.school_type) 76 | course = getCourse(driver, learn) 77 | # 获取所有章节 78 | try: 79 | driver.get_url(course.get_ZJ_path()) 80 | driver.driver_wait(By.CLASS_NAME, "chapter_td") 81 | except myException.TimeoutException as e: 82 | raise Exception("章节页面未成功加载:"+str(e)) 83 | try: 84 | chapter_list = learn.get_chapters(driver.get_driver()) 85 | except exceptions.NoSuchElementException as e: 86 | errInfo = traceback.format_exc() 87 | gl.exception_log_manger.writeLog(errInfo, errLevel.high) 88 | raise Exception("获取章节时出现异常:"+str(e)+"\n可能原因见:package\\learn\\school\\README.md") 89 | 90 | # 跳过已完成的章节 91 | for i in chapter_list: 92 | if not gl.judgment_TP_tate: 93 | print(color.yellow("当前程序不自动跳过已完成章节")) 94 | print(color.yellow("若需要开启自动跳过已完成章节功能\n请前往 config.ini 文件修改")) 95 | i.webElement.click() 96 | break 97 | if i.isFinish: 98 | print("章节:" + color.green(i.toString()) + " 已经完成") 99 | else: 100 | print("章节:" + color.blue(i.toString()) + " 未完成") 101 | if not specificChapter: 102 | i.webElement.click() 103 | break 104 | 105 | if specificChapter: 106 | while True: 107 | starCatalogSbar = input(color.blue("输入章节号(例如:1.1):")) 108 | for i in chapter_list: 109 | if i.catalog_sbar == starCatalogSbar: 110 | i.webElement.click() 111 | break 112 | else: 113 | print(color.yellow("章节号不存在,请重新输入!!")) 114 | continue 115 | break 116 | gl.spliter.print() 117 | 118 | driver.driver_wait(By.CSS_SELECTOR, '[id="iframe"]') 119 | # 收起目录栏 120 | driver.getElement(By.CLASS_NAME, "switchbtn").click() 121 | time.sleep(1) 122 | driver.go_js("var q=document.documentElement.scrollTop=10000") 123 | time.sleep(0.5) 124 | 125 | # 循环所有章节 126 | while True: 127 | # 寻找是否有任务卡 128 | prev_table_list = [] 129 | try: 130 | prev_table_list = driver.getElement(By.CSS_SELECTOR, '[class="prev_tab"]') \ 131 | .find_element(By.CSS_SELECTOR, '[class="prev_ul"]') \ 132 | .find_elements(By.TAG_NAME, 'li') 133 | except exceptions.NoSuchElementException: 134 | pass 135 | prev_table_number = len(prev_table_list) if len(prev_table_list) != 0 else 1 # 选项卡个数 136 | print("当前章节选项卡有 " + str(prev_table_number) + " 个") 137 | 138 | # 遍历每个任务卡 139 | for tableIndex in range(prev_table_number): 140 | # 第一个任务卡不用点击 141 | if tableIndex != 0: 142 | item = prev_table_list[tableIndex].find_element(By.TAG_NAME, 'div') 143 | driver.get_driver().execute_script("arguments[0].scrollIntoView(false);", item) 144 | item.click() 145 | time.sleep(1) 146 | 147 | # 进入第一层iframe 148 | driver.get_driver().switch_to.frame("iframe") 149 | 150 | # 获取任务点 151 | """ 152 | iframeList 是一个列表,其中存的是任务点的 WebElement 对象 153 | taskPointFinishStateList 是一个列表,其中存的是每个任务点的状态 154 | 155 | iframeList中的元素与taskPointFinishStateList中的元素一一对应 156 | """ 157 | iframeList = driver.getElements(By.TAG_NAME, 'iframe') 158 | print("当前章节有 " + color.yellow(str(len(iframeList))) + " 个任务点") 159 | 160 | # 获取当前页面中任务点的状态 161 | """ 162 | taskPointFinishStateList 中储存任务点状态 163 | 任务点状态为以下三种之一 164 | 1、任务点未完成 165 | 2、任务点完成 166 | 3、任务点状态无法判断 167 | """ 168 | taskPointFinishStateList = [] 169 | for i in iframeList: 170 | taskPoint = i.find_element(By.XPATH, "../.") # 获取任务点的爷爷元素 171 | taskPointClass = taskPoint.get_attribute("class") 172 | if taskPointClass is None: # 任务点状态无法判断 173 | taskPointFinishStateList.append(3) 174 | elif taskPointClass == "ans-attach-ct": 175 | taskPointFinishStateList.append(1) # 任务点未完成 176 | elif taskPointClass == "ans-attach-ct ans-job-finished": 177 | taskPointFinishStateList.append(2) # 任务点已完成 178 | else: 179 | taskPointFinishStateList.append(3) # 任务点状态无法判断 180 | 181 | # 遍历每个任务点 182 | for i in range(len(iframeList)): 183 | print("当前为第 " + color.blue(str(i + 1)) + " 任务点") 184 | 185 | # 判断任务点状态 186 | if gl.judgment_TP_tate and taskPointFinishStateList[i] == 3: 187 | print(color.yellow("当前任务点无法判断其状态")) 188 | print(color.yellow("跳过当前任务点")+"\n") 189 | time.sleep(random.randint(3, 5)) 190 | continue 191 | elif gl.judgment_TP_tate and taskPointFinishStateList[i] == 2: 192 | print(color.green("当前任务点已完成")) 193 | print(color.green("跳过当前任务点")+"\n") 194 | time.sleep(random.randint(3, 5)) 195 | continue 196 | elif gl.judgment_TP_tate and taskPointFinishStateList[i] == 1: 197 | print(color.green("当前任务点未完成")) 198 | print(color.green("开始学习当前任务点")) 199 | # 循环判断任务点类型 200 | # 因为当前 QuizOfTask 类型任务点还没有欧判读方法,所以 QuizOfTask 任务放在元组最后面 201 | for item in (PPT, Video, Audio, QuizOfTask): 202 | task = item(driver.get_driver()) 203 | if task.isCurrentTask(i): 204 | print("当前任务点是 " + color.green(task.__name__)) 205 | driver.get_driver().switch_to.frame(iframeList[i]) 206 | try: 207 | task.finish() 208 | break 209 | except Exception as e: 210 | errInfo = traceback.format_exc() 211 | errInfo += "以 {} 执行时出错".format(task.__name__) 212 | gl.exception_log_manger.writeLog(gl.version, errInfo, errLevel.middle) 213 | driver.get_driver().switch_to.default_content() 214 | gl.errorPrinter.print("当前任务点 {} 运行时出错\n详细错误信息已写入 exception.log 中".format(task.__name__)) 215 | print("跳过当前任务点") 216 | break 217 | else: 218 | print("当前任务点不是 " + color.yellow(task.__name__)) 219 | gl.spliter.print() 220 | driver.get_driver().switch_to.default_content() 221 | driver.get_driver().switch_to.frame("iframe") 222 | iframeList = driver.getElements(By.TAG_NAME, 'iframe') 223 | # 完成当前章节后退出到最外层 224 | # 不退出到最外层就无法找到”下一章节“的按钮 225 | driver.get_driver().switch_to.default_content() 226 | 227 | print("当前章节已完成") 228 | print(color.green("完成时间:"+getTimeStr())) 229 | gl.spliter.print() 230 | 231 | # 判断有没有出现章节提示 232 | chapterTips = driver.is_element_presence(By.CSS_SELECTOR, '[class="popDiv wid440 popMove"]') 233 | if chapterTips is not None: 234 | chapterTips.find_element(By.CLASS_NAME, '[class="jb_btn jb_btn_92 fr fs14 nextChapter"]').click() 235 | 236 | next_button = driver.is_element_presence(By.CSS_SELECTOR, '[class="jb_btn jb_btn_92 fs14 prev_next next"]') 237 | if next_button is not None: 238 | next_button.click() 239 | driver.driver_wait(By.CLASS_NAME, "course_main") 240 | else: 241 | print("课程学习完毕") 242 | exit() 243 | 244 | 245 | def do_homework(driver: MyDriver): 246 | # 做作业的功能等待实现 247 | pass 248 | 249 | 250 | def getTimeStr(): 251 | timeFormat = "%Y年%m月%d日 %H时%M分%S秒" 252 | return time.strftime(timeFormat, time.localtime()) 253 | 254 | 255 | 256 | -------------------------------------------------------------------------------- /package/learn/task/quiz/getanswer.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2021-09-14 0:02 3 | # @Author : Levitan 4 | # @File : getanswer.py 5 | # @Software : PyCharm 6 | import json 7 | from concurrent.futures import ThreadPoolExecutor 8 | 9 | # 自定义文件 10 | from .exception import NoFoundAnswerException 11 | 12 | # 第三方包 13 | from concurrent.futures._base import TimeoutError 14 | import requests 15 | 16 | class GetAnswer: 17 | __debug = False 18 | 19 | def __init__(self, debug=False): 20 | GetAnswer.__debug = debug 21 | self.__pool = ThreadPoolExecutor(max_workers=5) 22 | 23 | @staticmethod 24 | def __API1(question): 25 | api = r"https://cx.icodef.com/wyn-nb" 26 | data = {'question': question} 27 | try: 28 | r = requests.post(url=api, data=data, timeout=10) 29 | except requests.exceptions.Timeout: 30 | return "" 31 | dataText = r.text 32 | 33 | # ==============显示返回内容,测试时使用================= 34 | if GetAnswer.__debug: 35 | print("API1返回内容:"+dataText) 36 | 37 | dataJson = json.loads(dataText) 38 | if dataJson['code'] == -1: 39 | raise NoFoundAnswerException(dataJson["data"]) 40 | answer = dataJson['data'] 41 | return answer 42 | 43 | @staticmethod 44 | def __API2(question): 45 | api = r"https://api.julym.com/class/damn.php?question=" 46 | url = "{}{}".format(api, question) 47 | try: 48 | r = requests.get(url, timeout=10) 49 | except requests.exceptions.Timeout: 50 | return "" 51 | dataText = r.text 52 | 53 | # ==============显示返回内容,测试时使用================= 54 | if GetAnswer.__debug: 55 | print("API2返回内容:"+dataText) 56 | 57 | dataJson = json.loads(dataText) 58 | if dataJson['code'] == "0" or dataJson['code'] == 0: 59 | raise NoFoundAnswerException(dataJson['answer'].encode().decode()) 60 | if isinstance(dataJson['answer'], list): 61 | return "" 62 | answer = dataJson['answer'].encode().decode() 63 | return answer 64 | 65 | @staticmethod 66 | def __API3(question): 67 | api = r"https://api.gochati.cn/jsapi.php?token=cxmooc&q=" 68 | url = "{}{}".format(api, question) 69 | try: 70 | r = requests.get(url, timeout=10) 71 | except requests.exceptions.Timeout: 72 | raise NoFoundAnswerException 73 | dataText = r.text 74 | 75 | # ==============显示返回内容,测试时使用================= 76 | if GetAnswer.__debug: 77 | print("API3返回内容:"+dataText) 78 | 79 | try: 80 | dataJson = json.loads(dataText) 81 | except json.decoder.JSONDecodeError: 82 | raise NoFoundAnswerException 83 | if dataJson["code"] == 0: 84 | return "" 85 | answer = dataJson["da"] 86 | return answer 87 | 88 | @staticmethod 89 | def __API4(question): 90 | api = "http://api.902000.xyz:88/wkapi.php" 91 | # 公众号:如月的梦想 92 | data = {'q': question} 93 | try: 94 | r = requests.post(url=api, data=data, timeout=10) 95 | except requests.exceptions.Timeout: 96 | return "" 97 | dataText = r.text 98 | 99 | # ==============显示返回内容,测试时使用================= 100 | if GetAnswer.__debug: 101 | print("API4返回内容:"+dataText) 102 | 103 | dataJson = json.loads(dataText) 104 | if dataJson['code'] == 0: 105 | raise NoFoundAnswerException(dataJson['answer']) 106 | answer = dataJson['answer'] 107 | return answer 108 | 109 | @staticmethod 110 | def __API5(question): 111 | api = "http://118.25.10.121/moocapi?q=" 112 | url = "{}{}".format(api, question) 113 | try: 114 | r = requests.get(url, timeout=10) 115 | except requests.exceptions.Timeout: 116 | raise NoFoundAnswerException 117 | dataText = r.text 118 | 119 | # ==============显示返回内容,测试时使用================= 120 | if GetAnswer.__debug: 121 | print("API5返回内容:"+dataText) 122 | 123 | dataJson = json.loads(dataText) 124 | if dataJson["code"] == -1: 125 | raise NoFoundAnswerException(dataJson["msg"]) 126 | answer = dataJson['answer'] 127 | return answer 128 | 129 | def __requestAnswer(self, question, questionType): 130 | """ 131 | :param question: 需要查询的问题 132 | :return: 若找到答案则返答案的string,若没找到答案则返回空string 133 | """ 134 | answerList = [] 135 | 136 | future1 = self.__pool.submit(GetAnswer.__API1, question) 137 | future2 = self.__pool.submit(GetAnswer.__API2, question) 138 | future3 = self.__pool.submit(GetAnswer.__API3, question) 139 | future4 = self.__pool.submit(GetAnswer.__API4, question) 140 | future5 = self.__pool.submit(GetAnswer.__API5, question) 141 | 142 | try: 143 | answer1 = future1.result(timeout=20) 144 | answerList.append(GetAnswer.__parseAnswer(answer1, questionType)) 145 | except TimeoutError: 146 | print("线程1响应超时") 147 | except NoFoundAnswerException as e: 148 | print("线程1未找到答案,返回信息:"+str(e)) 149 | except ConnectionError: 150 | print("接口1连接失败") 151 | 152 | try: 153 | answer2 = future2.result(timeout=20) 154 | answerList.append(GetAnswer.__parseAnswer(answer2, questionType)) 155 | except TimeoutError: 156 | print("线程2响应超时") 157 | except NoFoundAnswerException as e: 158 | print("线程2未找到答案,返回信息:"+str(e)) 159 | except requests.exceptions.ConnectionError: 160 | print("接口2连接失败") 161 | 162 | try: 163 | answer3 = future3.result(timeout=20) 164 | answerList.append(GetAnswer.__parseAnswer(answer3, questionType)) 165 | except TimeoutError: 166 | print("线程3响应超时") 167 | except NoFoundAnswerException: 168 | print("线程3未找到答案,返回信息:None") 169 | except requests.exceptions.ConnectionError: 170 | print("接口3连接失败") 171 | 172 | try: 173 | answer4 = future4.result(timeout=20) 174 | answerList.append(GetAnswer.__parseAnswer(answer4, questionType)) 175 | except TimeoutError: 176 | print("线程4响应超时") 177 | except NoFoundAnswerException as e: 178 | print("线程4未找到答案,返回信息:"+str(e)) 179 | except requests.exceptions.ConnectionError: 180 | print("接口4连接失败") 181 | 182 | try: 183 | answer5 = future5.result(timeout=20) 184 | answerList.append(GetAnswer.__parseAnswer(answer5, questionType)) 185 | except TimeoutError: 186 | print("线程5响应超时") 187 | except NoFoundAnswerException as e: 188 | print("线程5未找到答案,返回信息:"+str(e)) 189 | except requests.exceptions.ConnectionError: 190 | print("接口5连接失败") 191 | print() 192 | return answerList 193 | 194 | @staticmethod 195 | def __parseAnswer(answer, questionType): 196 | if answer == "": 197 | return None 198 | separator = ("#", "\u0001", "\x01", " ") 199 | for i in separator: 200 | if i in answer: 201 | answer = answer.split(i) 202 | break 203 | 204 | # 验证获取的答案和题目类型是否相同 205 | if questionType == "": 206 | return answer 207 | 208 | elif questionType == "单选题": 209 | if isinstance(answer, list): 210 | return None 211 | return [answer] 212 | 213 | elif questionType == "多选题": 214 | if not isinstance(answer, list): 215 | return None 216 | return answer 217 | elif questionType == "判断题": 218 | if isinstance(answer, list): 219 | return None 220 | data = {'√': True, '正确': True, 'T': True, 'ri': True, '是': True, '对': True, 221 | '×': False, '错误': False, '错': False, 'F': False, 'wr': False, '否': False} 222 | for i in data.keys(): 223 | if answer == i: 224 | return [data[i]] 225 | return None 226 | 227 | @staticmethod 228 | def callback(question, answer, qType=""): 229 | url = "http://118.25.10.121/moocapi2?t={}&q={}&a={}".format(qType, question, answer) 230 | r = requests.get(url) 231 | 232 | def getAnswer(self, question, questionType=""): 233 | """ 234 | :param question: 待搜索的题目 235 | :param questionType: 题目的类型,若不提供题目类型则不检测查找答案正确性 236 | :return: 形如[[答案1], [答案2], [答案3]]的二维列表,其中列表的元素个数在[0, 5]范围内 237 | """ 238 | print("正在搜索题目:{}\n".format(question)) 239 | answerList = self.__requestAnswer(question, questionType) 240 | while None in answerList: 241 | answerList.remove(None) 242 | if len(answerList) != 0: 243 | GetAnswer.callback(question, str(answerList[0]), questionType) 244 | print("找到 " + str(len(answerList)) + " 个答案") 245 | for i in range(len(answerList)): 246 | print("答案{}:{}".format(i + 1, answerList[i])) 247 | return answerList 248 | 249 | def getSingleAnswer(self, question, questionType=""): 250 | answerList = self.getAnswer(question, questionType) 251 | similarityMatrix = [[0 for j in range(len(answerList))] for i in range(len(answerList))] 252 | maxSimilarDiffRatio = 0 253 | for item1 in range(len(answerList)): 254 | for item2 in range(item1, len(answerList)): 255 | if item1 == item2: 256 | continue 257 | similarQuestionNumber = 0 258 | for q1 in answerList[item1]: 259 | for q2 in answerList[item2]: 260 | similarDiffRatio = difflib.SequenceMatcher(None, q1, q2).quick_ratio() 261 | if similarDiffRatio > 0.9: 262 | similarQuestionNumber += 1 263 | similarityMatrix[item1][item2] = similarQuestionNumber 264 | print(similarityMatrix) 265 | 266 | def close(self): 267 | self.__pool.shutdown() 268 | 269 | 270 | if __name__ == "__main__": 271 | getAnswer = GetAnswer(True) 272 | while True: 273 | q = input("输入题目(q退出):") 274 | if q == "q": 275 | break 276 | answerList = getAnswer.getAnswer(q) 277 | print(answerList) 278 | print() 279 | 280 | """ 281 | 资本-帝国主义列强不能灭亡和瓜分近代中国的最根本原因是( )。 282 | ['物质是第一性的,意识是第二性的', '主观能动性的发挥,必须尊重客观规律'] 283 | ['物质是第一性的,意识是第二性的', '主观能动性的发挥,必须尊重客观规律'] 284 | ['物质是第一性的,意识是第二性的', '主观能动性的发挥,必须尊重客观规律'] 285 | """ 286 | -------------------------------------------------------------------------------- /package/learn/printer/printer.py: -------------------------------------------------------------------------------- 1 | # -*- encoding = utf-8 -*- 2 | # @Time : 2022-08-04 21:55 3 | # @Author : Levitan 4 | # @File : printer.py 5 | # @Software : PyCharm 6 | import abc 7 | from math import ceil 8 | from .setter import * 9 | from . import color 10 | 11 | 12 | # 抽象打印器类 13 | class AbstractPrinter(abc.ABC): 14 | def __init__(self): 15 | self._setter: AbstractSetter = None # 保存打印器的配置器 16 | 17 | # 设置打印器的配置器 18 | @abc.abstractmethod 19 | def setSetter(self, setter: AbstractSetter): 20 | pass 21 | 22 | # 判断打印器的设置器是否为空 23 | # 如果设置器不为空则什么都不做,如果为空则创建一个默认的设置器 24 | # 具体打印器需要什么设置器又子类实现 25 | @abc.abstractmethod 26 | def _isSetterNone(self): 27 | pass 28 | 29 | # 将传入的数据进行预处理 30 | @abc.abstractmethod 31 | def _preprocessing(self, data): 32 | pass 33 | 34 | # 将数据输出 35 | @abc.abstractmethod 36 | def _printOut(self, data): 37 | pass 38 | 39 | # 重置属性 40 | @abc.abstractmethod 41 | def _resetProperties(self): 42 | pass 43 | 44 | # 每个打印器的入口 45 | # 该方法规定了打印器中方法调用的顺序 46 | def print(self, data=None): 47 | self._isSetterNone() 48 | data = self._preprocessing(data) 49 | self._printOut(data) 50 | self._resetProperties() 51 | 52 | # 获取字符的显示宽度 53 | @staticmethod 54 | def _getCharWidth(char): 55 | o = ord(char) 56 | widths = [ 57 | (126, 1), (159, 0), (687, 1), (710, 0), (711, 1), 58 | (727, 0), (733, 1), (879, 0), (1154, 1), (1161, 0), 59 | (4347, 1), (4447, 2), (7467, 1), (7521, 0), (8369, 1), 60 | (8426, 0), (9000, 1), (9002, 2), (11021, 1), (12350, 2), 61 | (12351, 1), (12438, 2), (12442, 0), (19893, 2), (19967, 1), 62 | (55203, 2), (63743, 1), (64106, 2), (65039, 1), (65059, 0), 63 | (65131, 2), (65279, 1), (65376, 2), (65500, 1), (65510, 2), 64 | (120831, 1), (262141, 2), (1114109, 1), 65 | ] 66 | if o == 0xe or o == 0xf: 67 | return 0 68 | for num, wid in widths: 69 | if o <= num: 70 | return wid 71 | return 1 72 | 73 | # 获取字符串的显示宽度 74 | def _getStringWidth(self, string): 75 | width = 0 76 | for char in string: 77 | width += self._getCharWidth(char) 78 | return width 79 | 80 | 81 | # 表格打印器 82 | class TablePrinter(AbstractPrinter): 83 | # 默认单元格宽度 84 | _defaultGridWidth = 5 85 | 86 | def __init__(self) -> None: 87 | super().__init__() 88 | self._tableColumnWidth = [] # 表格中每列的宽度 89 | self._dataWidths: list[list] 90 | self._line: str 91 | self._xMax: int 92 | self._yMax: int 93 | self._head: list 94 | self._headWidths: list 95 | 96 | def setSetter(self, setter: TableSetter): 97 | self._setter = setter 98 | 99 | def _isSetterNone(self): 100 | if self._setter is not None: 101 | return 102 | self._setter = TableSetter() 103 | 104 | def _preprocessing(self, dataList): 105 | """ 106 | 将数据标准化 107 | """ 108 | # 获取数据中的最大行的元素个数 109 | standardKey = 0 # 用于标记数据是否是标准的,大于一表示不标准 110 | maxRowLength = 0 # 保存数据中元素最多的行的元素个数 111 | for row in dataList: 112 | rowLength = len(row) 113 | if rowLength > maxRowLength: 114 | maxRowLength = rowLength 115 | if rowLength != maxRowLength: 116 | standardKey += 1 117 | 118 | if standardKey > 1: 119 | # 补全数据 120 | for index in range(len(dataList)): 121 | differ = maxRowLength - len(dataList[index]) 122 | dataList[index] += ["" for _ in range(differ)] 123 | 124 | """ 125 | 向数据中添加序号,这一步之后表格的长宽就不会变了 126 | """ 127 | if self._setter.autoOrdNumber: 128 | starNumber = self._setter.ordNumberStart 129 | step = self._setter.ordNumberStep 130 | title = self._setter.ordNumberName 131 | hasHead = self._setter.hasHead 132 | 133 | # 添加字段名 134 | start: int # 开始添加位置 135 | if hasHead: 136 | start = 1 137 | dataList[0].insert(0, title) 138 | else: 139 | start = 0 140 | for i in range(start, len(dataList)): 141 | dataList[i].insert(0, starNumber) 142 | starNumber += step 143 | if not self._setter.isAutoColumnWidth(): 144 | shapeList = self._tableColumnWidth 145 | numberWidth = self._getStringWidth(str(starNumber)) 146 | if numberWidth <= 5: 147 | shapeList.insert(0, 5) 148 | else: 149 | shapeList.insert(0, numberWidth) 150 | self._tableColumnWidth = shapeList 151 | self._xMax = len(dataList[0]) 152 | self._yMax = len(dataList) 153 | 154 | """ 155 | 讲超过将超过用户指定长度的数据改为省略显示 156 | """ 157 | widths = [[0 for _ in range(self._xMax)] for _ in range(self._yMax)] 158 | tableWidths = [0 for _ in range(self._xMax)] 159 | # 按列进行扫描 160 | for x in range(self._xMax): 161 | for y in range(self._yMax): 162 | dataList[y][x] = str(dataList[y][x]) # 将所有数据都转为字符串形 163 | if self._setter.isAutoColumnWidth(): 164 | # 自动生成表格形状 165 | widths[y][x] = self._getStringWidth(dataList[y][x]) 166 | if widths[y][x] > tableWidths[x]: 167 | tableWidths[x] = widths[y][x] 168 | else: 169 | # 进行省略显示操作 170 | if self._getStringWidth(dataList[y][x]) > self._tableColumnWidth[x]: 171 | strItem = dataList[y][x] 172 | while self._getStringWidth(strItem) + 3 > self._tableColumnWidth[x]: 173 | strItem = strItem[:len(strItem) // 2] 174 | dataList[y][x] = strItem + "..." 175 | widths[y][x] = self._getStringWidth(dataList[y][x]) 176 | self._dataWidths = widths 177 | if self._setter.isAutoColumnWidth(): 178 | self._tableColumnWidth = tableWidths 179 | 180 | """ 181 | 如果有表头就将表头提取出来 182 | """ 183 | if self._setter.hasHead: 184 | self._head = dataList[0] 185 | self._headWidths = self._dataWidths[0] 186 | dataList = dataList[1:] 187 | self._dataWidths = self._dataWidths[1:] 188 | self._yMax -= 1 189 | 190 | self._createLine() 191 | return dataList 192 | 193 | # 打印表格 194 | def _printOut(self, dataList): 195 | # 获取配置信息 196 | splitChar = self._setter.splitChar 197 | tableShap = self._tableColumnWidth 198 | margin_left = self._setter.margin_left 199 | margin_right = self._setter.margin_right 200 | abreastTableNumber = self._setter.abreastTableNumber 201 | tableSplitChar = " " * self._setter.tableSplitWidth 202 | hasHead = self._setter.hasHead 203 | 204 | step = ceil(self._yMax / abreastTableNumber) # 计算不步长 205 | tables = [] 206 | # 需要显示的表格数将数据切片保存到 tables 中 207 | for i in range(step): 208 | item = dataList[i::step] 209 | # 判断数据是否够 210 | # 如果不够则补充空数据,并且在_dataWidths中添加上对应数据位置的宽度 211 | if len(item) < abreastTableNumber: 212 | for n in range(abreastTableNumber - len(item)): 213 | item.append(["" for _ in range(self._xMax)]) 214 | self._dataWidths.append([0 for _ in range(self._xMax)]) 215 | tables.append(item) 216 | 217 | headColor = self._setter.headColor 218 | bodyColor = self._setter.bodyColor 219 | try: 220 | headColorize = getattr(color, headColor) 221 | bodyColorize = getattr(color, bodyColor) 222 | except AttributeError: 223 | raise Exception("传入的颜色参数错误,color.py中没有名为 {} 或 {} 的函数".format(headColor, bodyColor)) 224 | 225 | # 打印表头 226 | if hasHead: 227 | heads = [] 228 | for t in range(abreastTableNumber): 229 | head = splitChar 230 | for cIndex in range(len(self._head)): 231 | if self._setter.alignment in "lL": 232 | spaceBefore = " " * margin_left 233 | spaceAfter = " " * (tableShap[cIndex] - self._headWidths[cIndex] + margin_right) 234 | elif self._setter.alignment in "rR": 235 | spaceBefore = " " * (tableShap[cIndex] - self._headWidths[cIndex] + margin_left) 236 | spaceAfter = " " * margin_right 237 | elif self._setter.alignment in "cC": 238 | spaceCount = tableShap[cIndex] - self._headWidths[cIndex] 239 | start = spaceCount // 2 240 | end = spaceCount - start 241 | spaceBefore = " " * (start + margin_left) 242 | spaceAfter = " " * (end + margin_right) 243 | else: 244 | raise Exception("配置的表格对齐方式'{}'是错误的".format(self._setter.alignment)) 245 | head += spaceBefore + self._head[cIndex] + spaceAfter + splitChar 246 | heads.append(head) 247 | print(headColorize(self._line)) 248 | print(headColorize(tableSplitChar.join(heads))) 249 | 250 | # 打印表格主体 251 | for rIndex in range(len(tables)): 252 | rowList = [] 253 | for tIndex in range(len(tables[rIndex])): 254 | row = splitChar 255 | for cIndex in range(len(tables[rIndex][tIndex])): 256 | if self._setter.alignment in "lL": 257 | spaceBefore = " " * margin_left 258 | spaceAfter = " " * (tableShap[cIndex] - self._dataWidths[tIndex * step + rIndex][cIndex] + margin_right) 259 | elif self._setter.alignment in "rR": 260 | spaceBefore = " " * (tableShap[cIndex] - self._dataWidths[tIndex * step + rIndex][cIndex] + margin_left) 261 | spaceAfter = " " * margin_right 262 | elif self._setter.alignment in "cC": 263 | spaceCount = tableShap[cIndex] - self._dataWidths[tIndex * step + rIndex][cIndex] 264 | start = spaceCount // 2 265 | end = spaceCount - start 266 | spaceBefore = " " * (start + margin_left) 267 | spaceAfter = " " * (end + margin_right) 268 | else: 269 | raise Exception("配置的表格对齐方式'{}'是错误的".format(self._setter.alignment)) 270 | row += spaceBefore + tables[rIndex][tIndex][cIndex] + spaceAfter + splitChar 271 | # 加上表格之间的分割符 272 | rowList.append(row) 273 | print(bodyColorize(self._line)) 274 | print(bodyColorize(tableSplitChar.join(rowList))) 275 | print(bodyColorize(self._line)) 276 | 277 | def _resetProperties(self): 278 | self._tableColumnWidth.clear() 279 | self._dataWidths.clear() 280 | self._line = None 281 | self._xMax = None 282 | self._yMax = None 283 | self._head = None 284 | self._headWidths = None 285 | 286 | # 手动设置表格单元格的宽度 287 | def setTableColWidth(self, tableColWidth): 288 | # 检测出入参数是否符合标准 289 | for i in range(len(tableColWidth)): 290 | if tableColWidth[i] < 3: 291 | raise Exception("表格列宽中的的元素的值不能小于3,当前{}个元素的值为{}".format(i + 1, tableColWidth[i])) 292 | self._tableColumnWidth = tableColWidth 293 | self._setter.autoColumnWidth = False 294 | 295 | # 生成表格的边框线 296 | def _createLine(self): 297 | # 获取配置信息 298 | turnChar = self._setter.turnChar 299 | borderChar = self._setter.borderChar 300 | margin_left = self._setter.margin_left 301 | margin_right = self._setter.margin_right 302 | tableSplitChar = self._setter.tableSplitWidth * " " 303 | abreastTableNumber = self._setter.abreastTableNumber 304 | 305 | row = [] 306 | for tIndex in range(abreastTableNumber): 307 | line = turnChar 308 | for i in self._tableColumnWidth: 309 | line += borderChar * (i + margin_left + margin_right) + turnChar 310 | row.append(line) 311 | self._line = tableSplitChar.join(row) 312 | 313 | 314 | # 消息打印器 315 | class MsgPrinter(AbstractPrinter): 316 | def __init__(self): 317 | super().__init__() 318 | self._maxSize = 0 319 | self._msgWidths = [] 320 | 321 | def setSetter(self, setter: MsgSetter): 322 | self._setter = setter 323 | 324 | def _isSetterNone(self): 325 | if self._setter is not None: 326 | return 327 | self._setter = MsgSetter() 328 | 329 | def _preprocessing(self, msg: str) -> list: 330 | msgList = msg.split("\n") 331 | for i in msgList: 332 | size = super()._getStringWidth(i) 333 | self._msgWidths.append(size) 334 | if size > self._maxSize: 335 | self._maxSize = size 336 | return msgList 337 | 338 | def _printOut(self, msgList: list): 339 | horizontalSymbol = self._setter.horizontalSymbol 340 | verticalSymbol = self._setter.verticalSymbol 341 | marginLeft = self._setter.margin_left 342 | marginRight = self._setter.margin_right 343 | colorType = self._setter.color 344 | 345 | try: 346 | colorize = getattr(color, colorType) 347 | except AttributeError: 348 | raise Exception("传入的颜色参数错误,color.py中没有名为 {} 的函数".format(colorType)) 349 | 350 | line = horizontalSymbol * (marginLeft + self._maxSize + marginRight + 2) 351 | print(colorize(line)) 352 | for index in range(len(msgList)): 353 | front = verticalSymbol + " " * marginLeft 354 | behind = " " * (self._maxSize - self._msgWidths[index] + marginRight) + verticalSymbol 355 | print(colorize(front + msgList[index] + behind)) 356 | print(colorize(line)) 357 | 358 | def _resetProperties(self): 359 | self._maxSize = 0 360 | self._msgWidths.clear() 361 | 362 | 363 | class SplitPrinter(AbstractPrinter): 364 | def __init__(self): 365 | super().__init__() 366 | 367 | def setSetter(self, setter: SplitSetter): 368 | self._setter = setter 369 | 370 | def _isSetterNone(self): 371 | if self._setter is not None: 372 | return 373 | self._setter = SplitSetter() 374 | 375 | def _preprocessing(self, data): 376 | return data 377 | 378 | def _printOut(self, length): 379 | length = self._setter.length 380 | symbol = self._setter.symbol 381 | colorType = self._setter.color 382 | frontNewlineNumber = self._setter.frontNewlineNumber 383 | behindNewlineNumber = self._setter.behindNewlineNumber 384 | message = self._setter.message 385 | lSymbol = self._setter.leftmostSymbol 386 | rSymbol = self._setter.rightmostSymbol 387 | 388 | try: 389 | colorize = getattr(color, colorType) 390 | except AttributeError: 391 | raise Exception("传入的颜色参数错误,color.py中没有名为 {} 的函数".format(colorType)) 392 | bNumber = length // 2 393 | aNumber = length - bNumber 394 | line = "\n"*frontNewlineNumber + lSymbol + symbol*bNumber + message + symbol*aNumber + rSymbol + "\n"*behindNewlineNumber 395 | print(colorize(line)) 396 | 397 | def _resetProperties(self): 398 | pass 399 | 400 | --------------------------------------------------------------------------------