├── requirements.txt ├── inside ├── __init__.py ├── Baidu_AI │ ├── __init__.py │ ├── Baidu_AI_Manage.py │ ├── Baidu_AI_Verify.py │ └── Baidu_AI_Tools.py ├── Config │ ├── __init__.py │ ├── Path.py │ ├── User_Agent.py │ ├── System.py │ ├── SystemArgs.py │ └── Api.py ├── DB │ ├── __init__.py │ ├── Table_Class │ │ ├── __init__.py │ │ ├── Project.py │ │ ├── Baidu_AI.py │ │ ├── User.py │ │ ├── Task.py │ │ ├── Video.py │ │ └── Article.py │ ├── DB_Project.py │ ├── DB_Manage.py │ ├── DB_Baidu_AI.py │ ├── DB_Task.py │ ├── DB_User.py │ ├── DB_Video.py │ ├── DB_Config.py │ ├── DB_Create.py │ ├── DB_Article.py │ └── DB_Check.py ├── Driver │ ├── __init__.py │ ├── Driver_Check.py │ ├── Driver_Download.py │ ├── Driver_Manage.py │ ├── Driver_Init.py │ └── Driver_Analysis.py ├── Info │ ├── __init__.py │ ├── Task_Info.py │ ├── Info_Manage.py │ ├── Get_Info.py │ └── User_Info.py ├── Login │ ├── __init__.py │ ├── Check_Token.py │ ├── QR_Vessel.py │ └── Login.py ├── Task │ ├── __init__.py │ ├── Mitmdump │ │ ├── __init__.py │ │ ├── Intercept │ │ │ ├── __init__.py │ │ │ ├── ABC_Answer.py │ │ │ ├── Script.py │ │ │ ├── Daily.py │ │ │ ├── Weekly.py │ │ │ └── Project.py │ │ └── Mitmdump.py │ ├── Task_Article_Video.py │ ├── Task_Init.py │ ├── Task_Answer.py │ └── Task_Manage.py ├── Tools │ ├── __init__.py │ ├── Quit.py │ ├── Output.py │ ├── Url_Test.py │ ├── Random.py │ ├── Requests.py │ └── Network.py ├── Options │ ├── __init__.py │ ├── Options_Manage.py │ └── Options.py └── Template │ ├── __init__.py │ ├── Task_Exception.py │ ├── Meta_Singleton.py │ ├── ABC_Driver_Analysis.py │ └── ABC_System_Args.py ├── LICENSE ├── main.py └── README.md /requirements.txt: -------------------------------------------------------------------------------- 1 | mitmproxy~=6.0.2 2 | tqdm~=4.56.0 3 | selenium~=3.141.0 4 | requests~=2.25.1 5 | beautifulsoup4~=4.9.3 6 | mitmproxy~=6.0.2 7 | lxml~=4.6.2 8 | psutil~=5.8.0 9 | opencv-python~=4.5.1.48 10 | Pillow~=8.1.0 -------------------------------------------------------------------------------- /inside/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/14 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : __init__.py.py 8 | # @Function : 内部 9 | -------------------------------------------------------------------------------- /inside/Baidu_AI/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/2/6 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : __init__.py.py 8 | # @Function : 9 | -------------------------------------------------------------------------------- /inside/Config/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/14 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : __init__.py.py 8 | # @Function : 配置 9 | -------------------------------------------------------------------------------- /inside/DB/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/15 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : __init__.py.py 8 | # @Function : 数据库 9 | -------------------------------------------------------------------------------- /inside/Driver/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/16 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : __init__.py.py 8 | # @Function : 驱动 9 | -------------------------------------------------------------------------------- /inside/Info/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/22 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : __init__.py.py 8 | # @Function : 信息 9 | -------------------------------------------------------------------------------- /inside/Login/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/15 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : __init__.py.py 8 | # @Function : 登录 9 | -------------------------------------------------------------------------------- /inside/Task/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/24 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : __init__.py.py 8 | # @Function : 任务 9 | -------------------------------------------------------------------------------- /inside/Tools/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/15 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : __init__.py.py 8 | # @Function : 工具 9 | -------------------------------------------------------------------------------- /inside/Options/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/23 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : __init__.py.py 8 | # @Function : 选项 9 | -------------------------------------------------------------------------------- /inside/Template/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/14 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : __init__.py.py 8 | # @Function : 模板 9 | -------------------------------------------------------------------------------- /inside/DB/Table_Class/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/15 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : __init__.py.py 8 | # @Function : 数据对象 9 | -------------------------------------------------------------------------------- /inside/Task/Mitmdump/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/26 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : __init__.py.py 8 | # @Function : 拦截注入 9 | -------------------------------------------------------------------------------- /inside/Task/Mitmdump/Intercept/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/2/6 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : __init__.py.py 8 | # @Function : 9 | -------------------------------------------------------------------------------- /inside/Template/Task_Exception.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/2/4 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Task_Exception.py 8 | # @Function : 任务异常 9 | 10 | class TASK_EXCEPTION(Exception): 11 | pass 12 | -------------------------------------------------------------------------------- /inside/DB/Table_Class/Project.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/2/6 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Project.py 8 | # @Function : 专项答题类 9 | __all__ = ['PROJECT'] 10 | 11 | 12 | class PROJECT(object): 13 | 14 | def __init__(self, pid: int): 15 | """ 16 | PROJECT(pid: int) 17 | 初始化 18 | 19 | @param pid: 专项答题ID 20 | """ 21 | self.__pid = pid 22 | 23 | @property 24 | def Id(self) -> int: 25 | """ 26 | Id -> int 27 | 28 | @return: 专项答题ID 29 | """ 30 | return self.__pid 31 | -------------------------------------------------------------------------------- /inside/Tools/Quit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/25 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Quit.py 8 | # @Function : 退出 9 | from inside.DB.DB_Manage import DB_MANAGE 10 | from inside.Driver.Driver_Manage import DRIVER_MANAGE 11 | from inside.Task.Mitmdump.Mitmdump import MITMDUMP 12 | from inside.Tools.Network import NETWORK 13 | 14 | __all__ = ['Quit'] 15 | 16 | 17 | def Quit() -> None: 18 | """ 19 | Quit() -> None 20 | 有序退出程序 21 | 22 | :return: None 23 | """ 24 | NETWORK().Quit() 25 | temp = DRIVER_MANAGE().Task_Quit 26 | DB_MANAGE().Quit() 27 | MITMDUMP().Close() 28 | del temp 29 | 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 lisztomania 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/20 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : main.py 8 | # @Function : 入口 9 | import os 10 | 11 | from inside.Config.System import SYSTEM 12 | from inside.Driver.Driver_Manage import DRIVER_MANAGE 13 | from inside.Login.Login import LOGIN 14 | from inside.Options.Options_Manage import OPTIONS_MANAGE 15 | from inside.Task.Mitmdump.Mitmdump import MITMDUMP 16 | from inside.Task.Task_Manage import TASK_MANAGE 17 | from inside.Tools.Network import NETWORK 18 | from inside.Tools.Output import OUTPUT 19 | 20 | if __name__ == '__main__': 21 | SYSTEM().Check_Chrome() 22 | os.system(command=SYSTEM().Clear) 23 | OPTIONS_MANAGE.Init_Options() 24 | MITMDUMP().Open() 25 | driver = DRIVER_MANAGE() 26 | network = NETWORK() 27 | network.Init(driver=driver.Task) 28 | if LOGIN(task_driver=driver.Task).Login(): 29 | while True: 30 | OUTPUT.Info() 31 | OPTIONS_MANAGE.Task_Options() 32 | task = TASK_MANAGE(driver=driver.Task) 33 | task.Task() 34 | 35 | -------------------------------------------------------------------------------- /inside/Login/Check_Token.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/20 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Check_Token.py 8 | # @Function : 检测令牌 9 | from inside.Config.Api import API 10 | from inside.Template.Meta_Singleton import SINGLETON 11 | 12 | from inside.Tools.Random import RANDOM 13 | from inside.Tools.Requests import REQUESTS 14 | 15 | __all__ = ['CHECK_TOKEN'] 16 | 17 | 18 | class CHECK_TOKEN(metaclass=SINGLETON): 19 | """检查令牌类""" 20 | 21 | @classmethod 22 | def Check_Token(cls, token: str) -> bool: 23 | """ 24 | Check_Token(token: str) -> bool 25 | 检查令牌是否有效 26 | 27 | :param token: 令牌 28 | :return: bool 29 | """ 30 | cookie = {'token': token} 31 | link = API().Token_Check.geturl().format( 32 | timestamp=RANDOM().Float_Precision(a=0, b=1, precision=16) 33 | ) 34 | html = REQUESTS.Get( 35 | url=link, 36 | cookies=cookie 37 | ) 38 | if html.cookies.get(name='token'): 39 | return True 40 | return False 41 | -------------------------------------------------------------------------------- /inside/Task/Mitmdump/Mitmdump.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/26 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Mitmdump.py 8 | # @Function : 拦截注入器 9 | import subprocess 10 | 11 | from inside.Config.Path import PATH 12 | from inside.Template.Meta_Singleton import SINGLETON 13 | __all__ = ['MITMDUMP'] 14 | 15 | 16 | class MITMDUMP(metaclass=SINGLETON): 17 | """拦截注入器""" 18 | __mitmdump: subprocess.Popen 19 | 20 | def __init__(self): 21 | """ 22 | MITMDUMP() 23 | 初始化 24 | 25 | """ 26 | self.__self = '_'+type(self).__name__ 27 | 28 | def Open(self) -> None: 29 | """ 30 | Open() -> None 31 | 打开 32 | 33 | :return: None 34 | """ 35 | if not hasattr(self, self.__self+'__mitmdump') or self.__mitmdump.poll(): 36 | self.__mitmdump = subprocess.Popen( 37 | ['mitmdump', '-q', '-p', '8080', '-s', PATH().Script] 38 | ) 39 | 40 | def Close(self) -> None: 41 | """ 42 | Close() -> None 43 | 关闭 44 | 45 | :return: None 46 | """ 47 | if hasattr(self, self.__self+'__mitmdump'): 48 | self.__mitmdump.kill() 49 | -------------------------------------------------------------------------------- /inside/Tools/Output.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/15 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Output.py 8 | # @Function : 信息输出 9 | import os 10 | 11 | from inside.Config.System import SYSTEM 12 | from inside.Info.Info_Manage import INFO_MANAGE 13 | from inside.Template.Meta_Singleton import SINGLETON 14 | 15 | __all__ = ['OUTPUT', 'Info'] 16 | 17 | 18 | class OUTPUT(metaclass=SINGLETON): 19 | """ 20 | 输出信息 21 | """ 22 | 23 | @classmethod 24 | def Info(cls): 25 | os.system(command=SYSTEM().Clear) 26 | temp = INFO_MANAGE() 27 | level = temp.Level_Info 28 | print(f"项目地址:https://github.com/lisztomania-Zero/learning-power") 29 | print(f"用户ID:{temp.Id}\n" 30 | f"等级:{level[0]}\t段位:{level[1]}\t全国排名:{level[2]}\n" 31 | f"总积分:{temp.Aggregate_Score}\t今日积分:{temp.Daily_Score}\n" 32 | f"每日积分细则:") 33 | print() 34 | for index, value in enumerate(list(temp.Task_Bar.values())): 35 | if (index+1) % 5 == 0: 36 | print(f"{value.Name}:{value.Current_Score}分") 37 | continue 38 | print(f"{value.Name}:{value.Current_Score}分\t", end='') 39 | print('\n') 40 | 41 | 42 | _inst = OUTPUT() 43 | Info = _inst.Info 44 | -------------------------------------------------------------------------------- /inside/Tools/Url_Test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/28 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Url_Test.py 8 | # @Function : 链接延迟测试 9 | import time 10 | import ssl 11 | from socket import timeout 12 | from urllib import request 13 | from urllib.error import URLError 14 | 15 | from inside.Template.Meta_Singleton import SINGLETON 16 | 17 | __all__ = ['URL_TEST', 'Url_Test'] 18 | 19 | 20 | class URL_TEST(metaclass=SINGLETON): 21 | """url延迟测试类""" 22 | ssl._create_default_https_context = ssl._create_unverified_context 23 | 24 | @classmethod 25 | def Url_Test(cls, url: str) -> float: 26 | """ 27 | Url_Test(url: str) -> float 28 | 测试url访问延迟 29 | 30 | :param url: url 31 | :return: float 32 | """ 33 | try: 34 | temp = 0 35 | for _ in range(3): 36 | s = time.time() 37 | try: 38 | request.urlopen(url=url, timeout=3) 39 | except (timeout, URLError): 40 | pass 41 | temp += time.time() - s 42 | return temp / 3 43 | except (ValueError, AttributeError): 44 | raise Exception(f"{url}:不是正确的url链接") 45 | 46 | 47 | _inst = URL_TEST 48 | Url_Test = _inst.Url_Test 49 | -------------------------------------------------------------------------------- /inside/DB/Table_Class/Baidu_AI.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/2/6 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Baidu_AI.py 8 | # @Function : 百度AI类 9 | __all__ = ['BAIDU_AI'] 10 | 11 | 12 | class BAIDU_AI(object): 13 | """百度AI类""" 14 | 15 | def __init__(self, ak: str, sk: str): 16 | """ 17 | BAIDU_AI(ak: str, sk: str) 18 | 初始化 19 | 20 | @param ak: API Key 21 | @param sk: Secret Key 22 | """ 23 | self.__ak = ak 24 | self.__sk = sk 25 | 26 | @property 27 | def Ak(self) -> str: 28 | """ 29 | Ak -> str 30 | 31 | @return: API Key 32 | """ 33 | return self.__ak 34 | 35 | @Ak.setter 36 | def Ak(self, ak: str) -> None: 37 | """ 38 | Ak = ak: str 39 | 40 | @param ak: API Key 41 | @return: None 42 | """ 43 | self.__ak = ak 44 | 45 | @property 46 | def Sk(self) -> str: 47 | """ 48 | Sk -> str 49 | 50 | @return: Secret Key 51 | """ 52 | return self.__sk 53 | 54 | @Sk.setter 55 | def Sk(self, sk: str) -> None: 56 | """ 57 | Sk = sk: str 58 | 59 | @param sk: Secret Key 60 | @return: None 61 | """ 62 | self.__sk = sk 63 | -------------------------------------------------------------------------------- /inside/Template/Meta_Singleton.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/14 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Meta_Singleton.py 8 | # @Function : 单例基类 9 | from abc import ABCMeta 10 | 11 | __all__ = ['SINGLETON', 'SINGLETON_ABC'] 12 | 13 | 14 | class SINGLETON(type): 15 | """单例元类""" 16 | __instances = {} 17 | 18 | def __call__(cls, *args, **kwargs): 19 | """ 20 | 重载(),确保返回的为唯一对象 21 | 22 | :param args: 23 | :param kwargs: 24 | :return: 25 | """ 26 | if not cls.__instances.get(cls): 27 | cls.__instances[cls] = super(SINGLETON, cls).__call__(*args, 28 | **kwargs) 29 | return cls.__instances[cls] 30 | 31 | 32 | class SINGLETON_ABC(ABCMeta): 33 | """抽象类的单例元类""" 34 | __instances = {} 35 | 36 | def __call__(cls, *args, **kwargs): 37 | """ 38 | 重载(),确保返回的为唯一对象 39 | 40 | :param args: 41 | :param kwargs: 42 | :return: 43 | """ 44 | if not cls.__instances.get(cls): 45 | cls.__instances[cls] = super(SINGLETON_ABC, cls).__call__(*args, 46 | **kwargs) 47 | return cls.__instances[cls] 48 | -------------------------------------------------------------------------------- /inside/Login/QR_Vessel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/15 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : QR_Vessel.py 8 | # @Function : 二维码容器 9 | from inside.Driver.Driver_Manage import DRIVER_MANAGE 10 | from inside.Template.Meta_Singleton import SINGLETON 11 | 12 | __all__ = ['QR_VESSEL'] 13 | 14 | 15 | class QR_VESSEL(metaclass=SINGLETON): 16 | """二维码容器类""" 17 | 18 | def __init__(self): 19 | """ 20 | QR_VESSEL() 21 | 初始化 22 | 23 | """ 24 | self.__driver = DRIVER_MANAGE().QR 25 | 26 | def Show_QR(self, qr: str): 27 | """ 28 | Show_QR(qr: str) -> None 29 | 显示二维码 30 | 31 | :param qr: 图片二维码,格式为:data:image/png;base64,xxx== 32 | :return: None 33 | """ 34 | js = "document.body.innerHTML='';" 35 | js += f''' 36 | var qr = document.createElement("img"); 37 | qr.src="{qr}"; 38 | document.body.appendChild(qr);''' 39 | self.__driver.execute_script(script=js) 40 | 41 | def QR_QUIT(self) -> str: 42 | """ 43 | QR_QUIT() -> str 44 | 二维码容器退出 45 | 46 | :return: str 47 | Success: 退出成功 48 | Nonexistence: 不存在任务浏览器 49 | """ 50 | return DRIVER_MANAGE().QR_Quit 51 | 52 | def __del__(self) -> str: 53 | return DRIVER_MANAGE().QR_Quit 54 | 55 | 56 | -------------------------------------------------------------------------------- /inside/Baidu_AI/Baidu_AI_Manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/2/6 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Baidu_AI_Manage.py 8 | # @Function : 百度AI管理类 9 | from inside.Baidu_AI.Baidu_AI_Verify import BAIDU_AI_Verify 10 | from inside.Baidu_AI.Baidu_AI_Tools import BAIDU_AI_TOOLS 11 | from inside.Template.Meta_Singleton import SINGLETON 12 | from inside.DB.DB_Manage import DB_MANAGE 13 | __all__ = ['BAIDU_AI_MANAGE'] 14 | 15 | 16 | class BAIDU_AI_MANAGE(metaclass=SINGLETON): 17 | """百度AI管理类""" 18 | 19 | @classmethod 20 | def Verify(cls) -> None: 21 | """ 22 | Verify() -> None 23 | 验证百度AI 24 | 25 | Returns: None 26 | 27 | """ 28 | baidu_ai = None 29 | if not DB_MANAGE().Baidu_AI.Empty(): 30 | baidu_ai = DB_MANAGE().Baidu_AI.Query() 31 | if BAIDU_AI_Verify.Check_AS(baidu_ai=baidu_ai) != 1: 32 | baidu_ai = None 33 | if not baidu_ai: 34 | baidu_ai = BAIDU_AI_Verify().Verify() 35 | DB_MANAGE().Baidu_AI.Insert(baidu_ai=baidu_ai) 36 | 37 | @classmethod 38 | def Tools(cls) -> BAIDU_AI_TOOLS: 39 | """ 40 | Tools() -> BAIDU_AI_TOOLS 41 | 返回百度AI工具 42 | 43 | Returns: BAIDU_AI_TOOLS 44 | 45 | """ 46 | baidu_ai = DB_MANAGE().Baidu_AI.Query() 47 | return BAIDU_AI_TOOLS(baidu_ai=baidu_ai) 48 | -------------------------------------------------------------------------------- /inside/Task/Mitmdump/Intercept/ABC_Answer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/2/6 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : ABC_Answer.py 8 | # @Function : 答题抽象类 9 | from abc import abstractmethod 10 | from typing import Dict 11 | 12 | from inside.Template.Meta_Singleton import SINGLETON_ABC 13 | __all__ = ['ABC_ANSWER'] 14 | 15 | 16 | class ABC_ANSWER(metaclass=SINGLETON_ABC): 17 | """答题抽象类""" 18 | 19 | @abstractmethod 20 | def Url_Put(self, url: str) -> bool: 21 | """ 22 | Url_Put(url: str) -> bool 23 | 提交判断 24 | 25 | :param url: url 26 | :return: bool 27 | """ 28 | pass 29 | 30 | @abstractmethod 31 | def Url_Res(self, url: str) -> bool: 32 | """ 33 | Url_Res(url: str) -> bool 34 | 答案判断 35 | 36 | :param url: url 37 | :return: bool 38 | """ 39 | pass 40 | 41 | @abstractmethod 42 | def Extract(self, data: bytes) -> Dict: 43 | """ 44 | Extract(data: bytes) -> Dict 45 | 提取答案 46 | 47 | :param data: 包含答案的字节数据 48 | :return: Dict 49 | """ 50 | pass 51 | 52 | @abstractmethod 53 | def Inject(self, data: bytes, res: Dict) -> bytes: 54 | """ 55 | Inject(data: bytes, res: Dict) -> bytes 56 | 注入答案 57 | 58 | :param data: 原始提交数据 59 | :param res: 包含答案数据 60 | :return: 修改后的提交数据 61 | """ 62 | pass 63 | -------------------------------------------------------------------------------- /inside/Tools/Random.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/15 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Random.py 8 | # @Function : 随机数 9 | import random 10 | import time 11 | from typing import Union 12 | 13 | from inside.Template.Meta_Singleton import SINGLETON 14 | 15 | __all__ = ['RANDOM', 'Int', 'Float'] 16 | 17 | 18 | class RANDOM(metaclass=SINGLETON): 19 | """ 20 | 提供随机数 21 | """ 22 | random.seed(time.time()) 23 | 24 | @classmethod 25 | def Int(cls, a: int, b: int) -> int: 26 | """ 27 | Int(a: int, b: int) -> int 28 | 类方法 29 | 输出随机整数,a<=结果<=b 30 | 31 | :param a: 起始数 32 | :param b: 结束数 33 | :return: int 34 | """ 35 | return random.randint(a=a, b=b) 36 | 37 | # 浮点数 38 | @classmethod 39 | def Float(cls, a: Union[float, int], b: Union[float, int]) -> float: 40 | """ 41 | Float(a: float, b: float) -> float 42 | 类方法 43 | 输出随机浮点数,a<=结果<=b 44 | 45 | :param a: 起始数 46 | :param b: 结束数 47 | :return: float 48 | """ 49 | return random.uniform(a=a, b=b) 50 | 51 | @classmethod 52 | def Float_Precision(cls, a: Union[float, int], b: Union[float, int], 53 | precision: int): 54 | return round(number=cls.Float(a=a, b=b), ndigits=precision) 55 | 56 | 57 | _inst = RANDOM() 58 | Int = _inst.Int 59 | Float = _inst.Float 60 | Float_Precision = _inst.Float_Precision 61 | -------------------------------------------------------------------------------- /inside/DB/Table_Class/User.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/15 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : User.py 8 | # @Function : 用户类 9 | from datetime import datetime 10 | from typing import Dict 11 | 12 | __all__ = ['USER'] 13 | 14 | 15 | class USER(object): 16 | """用户类""" 17 | 18 | def __init__(self, user_id: int, token: str): 19 | """ 20 | USER(user_id: int, token: str) 21 | 初始化 22 | 23 | :param user_id: 用户ID 24 | :param token: 令牌 25 | """ 26 | self.__user_id = user_id 27 | self.__token = token 28 | self.__time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') 29 | 30 | @property 31 | def Id(self) -> int: 32 | """ 33 | Id -> int 34 | 名称 35 | 36 | :return: int 37 | """ 38 | return self.__user_id 39 | 40 | @property 41 | def Token(self) -> str: 42 | """ 43 | Token -> str 44 | 令牌 45 | 46 | :return: str 47 | """ 48 | return self.__token 49 | 50 | @property 51 | def Token_Driver(self) -> Dict: 52 | """ 53 | Token_Driver -> Dict 54 | 驱动令牌,格式为:{'domain': '.xuexi.cn', 'name': 'token', 'value': 'token'} 55 | 56 | :return: Dict 57 | """ 58 | return {'domain': '.xuexi.cn', 'name': 'token', 'value': self.Token} 59 | 60 | @property 61 | def Time(self) -> str: 62 | """ 63 | Time -> str 64 | 对象创建时间戳 65 | 66 | :return: str 67 | """ 68 | return self.__time 69 | -------------------------------------------------------------------------------- /inside/Template/ABC_Driver_Analysis.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/17 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : ABC_Driver_Analysis.py 8 | # @Function : 驱动解析基类 9 | from abc import abstractmethod 10 | from urllib.parse import ParseResult 11 | 12 | from inside.Config.System import SYSTEM 13 | from inside.Template.Meta_Singleton import SINGLETON_ABC 14 | 15 | __all__ = ['DRIVER_ANALYSIS'] 16 | 17 | 18 | class ABC_DRIVER_ANALYSIS(metaclass=SINGLETON_ABC): 19 | """驱动解析抽象类""" 20 | @abstractmethod 21 | def Master(self) -> ParseResult: 22 | """ 23 | Url() -> ParseResult 24 | 驱动集合链接 25 | 26 | :return: ParseResult 27 | """ 28 | pass 29 | 30 | @abstractmethod 31 | def Download(self, system: SYSTEM) -> ParseResult: 32 | """ 33 | Download() -> ParseResult 34 | 驱动下载链接 35 | 36 | :param system: 系统 37 | :param version: 版本号 38 | :return: ParseResult 39 | """ 40 | pass 41 | 42 | 43 | class DRIVER_ANALYSIS(ABC_DRIVER_ANALYSIS): 44 | """驱动解析类实际继承类""" 45 | _map = { 46 | 'linux': 'linux', 47 | 'win32': 'win', 48 | 'darwin': 'mac' 49 | } 50 | 51 | @property 52 | def Master(self) -> ParseResult: 53 | """ 54 | Url -> ParseResult 55 | 驱动集合链接 56 | 57 | :return: ParseResult 58 | """ 59 | pass 60 | 61 | def Download(self, system: SYSTEM) -> ParseResult: 62 | """ 63 | Download -> ParseResult 64 | 驱动下载链接 65 | 66 | :param system: 系统 67 | :param version: 版本号 68 | :return: ParseResult 69 | """ 70 | pass 71 | 72 | -------------------------------------------------------------------------------- /inside/Task/Mitmdump/Intercept/Script.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/26 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Intercept11.py 8 | # @Function : 答题拦截注入脚本 9 | import os 10 | import sys 11 | from json import JSONDecodeError 12 | from typing import Dict 13 | 14 | """此处是因为脚本加载Intercept失败,所做的增加搜索路径""" 15 | sys.path.append( 16 | os.path.dirname( 17 | os.path.dirname( 18 | os.path.dirname( 19 | os.path.dirname( 20 | os.path.dirname(os.path.abspath(__file__)) 21 | ) 22 | ) 23 | ) 24 | ) 25 | ) 26 | from inside.Task.Mitmdump.Intercept.ABC_Answer import ABC_ANSWER 27 | from inside.Task.Mitmdump.Intercept.Daily import DAILY 28 | from inside.Task.Mitmdump.Intercept.Project import PROJECT 29 | from inside.Task.Mitmdump.Intercept.Weekly import WEEKLY 30 | 31 | from mitmproxy.http import HTTPFlow 32 | 33 | gears = (DAILY(), WEEKLY(), PROJECT()) 34 | gear: ABC_ANSWER = None 35 | put_data: Dict 36 | 37 | 38 | def request(up: HTTPFlow) -> None: 39 | if gear: 40 | if gear.Url_Put(url=up.request.url): 41 | print("检测到提交,注入中") 42 | try: 43 | up.request.content = gear.Inject( 44 | data=up.request.content, 45 | res=put_data 46 | ) 47 | print("注入成功") 48 | except JSONDecodeError: 49 | print("检测到本次提交为干扰") 50 | 51 | 52 | def response(down: HTTPFlow): 53 | for g in gears: 54 | if g.Url_Res(url=down.request.url): 55 | try: 56 | global put_data, gear 57 | put_data = g.Extract(data=down.response.content) 58 | gear = g 59 | print("抓取答案完成") 60 | except JSONDecodeError: 61 | pass 62 | -------------------------------------------------------------------------------- /inside/DB/DB_Project.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/2/4 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : DB_Project.py 8 | # @Function : 专项答题表操作 9 | from sqlite3 import Connection, Cursor 10 | 11 | from inside.DB.DB_Config import DB_CONFIG 12 | from inside.DB.Table_Class.Project import PROJECT 13 | from inside.Template.Meta_Singleton import SINGLETON 14 | 15 | __all__ = ['DB_PROJECT'] 16 | 17 | 18 | class DB_PROJECT(metaclass=SINGLETON): 19 | """专项答题表操作类""" 20 | 21 | def __init__(self, connect: Connection, cursor: Cursor): 22 | """ 23 | 初始化 24 | 25 | :param connect: 数据库连接对象 26 | :param cursor: 数据库光标 27 | """ 28 | self.__connect = connect 29 | self.__cursor = cursor 30 | self.__name = DB_CONFIG().Project 31 | self.__fields = DB_CONFIG().Project_Fields 32 | 33 | def Insert(self, project: PROJECT) -> None: 34 | """ 35 | Insert(project: PROJECT) -> None 36 | 插入专项答题id,如已存在则不进行操作 37 | 38 | :param project: 专项答题 39 | :return: None 40 | """ 41 | if not self.Exist(project=project): 42 | sql = f'''INSERT INTO 43 | {self.__name} ( 44 | {self.__fields[1]}) 45 | VALUES (?);''' 46 | data = (project.Id, ) 47 | self.__cursor.execute(sql, data) 48 | self.__connect.commit() 49 | 50 | def Exist(self, project: PROJECT) -> bool: 51 | """ 52 | Exist(project: PROJECT) -> bool 53 | 是否存在指定id 54 | 55 | :param project: 专项答题 56 | :return: bool 57 | """ 58 | sql = f'''SELECT 1 FROM {self.__name} 59 | WHERE {self.__fields[1]}=?;''' 60 | data = (project.Id,) 61 | result = self.__cursor.execute(sql, data) 62 | try: 63 | next(result) 64 | return True 65 | except StopIteration: 66 | return False 67 | -------------------------------------------------------------------------------- /inside/DB/Table_Class/Task.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/15 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Task.py 8 | # @Function : 任务类 9 | from typing import Union 10 | 11 | __all__ = ['TASK'] 12 | 13 | 14 | class TASK(object): 15 | """任务类""" 16 | 17 | def __init__(self, link: str, isread: Union[int, bool]): 18 | """ 19 | TASK(link: str, isread: Union[int, bool]) 20 | 初始化 21 | 22 | :param link: 链接 23 | :param isread: 是否已读 24 | """ 25 | 26 | self.__link = link 27 | self.__isread = self.__Is_Read(isread=isread) 28 | 29 | @property 30 | def Link(self) -> str: 31 | """ 32 | Link -> str 33 | 链接 34 | 35 | :return: str 36 | """ 37 | return self.__link 38 | 39 | @property 40 | def Is_Read(self) -> bool: 41 | """ 42 | Is_Read -> bool 43 | 是否已读 44 | 45 | :return: bool 46 | """ 47 | return self.__isread 48 | 49 | @Is_Read.setter 50 | def Is_Read(self, isread: Union[int, bool]) -> None: 51 | """ 52 | Is_Read -> None 53 | 设置是否已读 54 | 55 | :param isread: Union[int, bool] 56 | :return: None 57 | """ 58 | self.__isread = self.__Is_Read(isread=isread) 59 | 60 | @classmethod 61 | def __Is_Read(cls, isread: Union[int, bool]) -> bool: 62 | """ 63 | __Is_Read(isread: Union[int, bool]) -> bool 64 | 设定是否已读,由于数据库内不能存储布尔值,所以由0/1代替; 65 | 因此数字只限定0/1 66 | 67 | :param isread: 1/0 or bool 68 | :return: bool 69 | """ 70 | if isinstance(isread, bool): 71 | return isread 72 | elif isinstance(isread, int): 73 | if isread in (0, 1): 74 | return True if isread else False 75 | else: 76 | raise Exception('isread must be 0 or 1') 77 | 78 | @property 79 | def Is_Read_DB(self) -> int: 80 | """ 81 | Is_Read_DB -> int 82 | 是否已读(数据库读取接口,返回1/0) 83 | 84 | :return: int 85 | """ 86 | return 1 if self.__isread else 0 87 | -------------------------------------------------------------------------------- /inside/Baidu_AI/Baidu_AI_Verify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/2/6 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Baidu_AI_Verify.py 8 | # @Function : 验证百度AI 9 | import requests 10 | 11 | from inside.DB.Table_Class.Baidu_AI import BAIDU_AI 12 | from inside.Template.Meta_Singleton import SINGLETON 13 | __all__ = ['BAIDU_AI_Verify'] 14 | 15 | 16 | class BAIDU_AI_Verify(metaclass=SINGLETON): 17 | """百度AI验证类""" 18 | 19 | def Verify(self) -> BAIDU_AI: 20 | """ 21 | Verify() -> BAIDU_AI 22 | 验证 23 | 24 | Returns: BAIDU_AI 25 | 26 | """ 27 | ak = input("请输入API Key:").strip() 28 | sk = input("请输入Secret Key:").strip() 29 | baidu_ai = BAIDU_AI(ak=ak, sk=sk) 30 | while True: 31 | temp = self.Check_AS(baidu_ai=baidu_ai) 32 | if temp == 1: 33 | break 34 | elif temp == -1: 35 | print('API Key错误') 36 | baidu_ai.Ak = input("请重新输入API Key:").strip() 37 | continue 38 | elif temp == -2: 39 | print('Secret Key错误') 40 | baidu_ai.Sk = input("请重新输入Secret Key:").strip() 41 | continue 42 | return baidu_ai 43 | 44 | @classmethod 45 | def Check_AS(cls, baidu_ai: BAIDU_AI) -> int: 46 | """ 47 | Check_AS(baidu_ai: BAIDU_AI) -> int 48 | 检查API Key、Secret Key是否有效 49 | 50 | Args: 51 | baidu_ai: 百度AI 52 | 53 | Returns: 54 | 1: 有效 55 | -1: API Key 无效 56 | -2: Secret Key 无效 57 | 58 | """ 59 | host = f'https://aip.baidubce.com/oauth/2.0/token?grant_type' \ 60 | f'=client_credentials&client_id={baidu_ai.Ak}&client_' \ 61 | f'secret={baidu_ai.Sk}' 62 | html = requests.get(host) 63 | if html.json().get('access_token'): 64 | return 1 65 | elif html.json().get('error_description') == 'unknown client id': 66 | return -1 67 | elif html.json().get('error_description') == 'Client authentication ' \ 68 | 'failed': 69 | return -2 70 | -------------------------------------------------------------------------------- /inside/Task/Mitmdump/Intercept/Daily.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/2/6 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Daily.py 8 | # @Function : 9 | import base64 10 | import json 11 | from random import randint 12 | from typing import Dict 13 | 14 | from inside.Task.Mitmdump.Intercept.ABC_Answer import ABC_ANSWER 15 | __all__ = ['DAILY'] 16 | 17 | 18 | class DAILY(ABC_ANSWER): 19 | """每日答题""" 20 | def Url_Put(self, url: str) -> bool: 21 | """ 22 | Url_Put(url: str) -> bool 23 | 提交判断 24 | 25 | :param url: url 26 | :return: bool 27 | """ 28 | return "https://pc-proxy-api.xuexi.cn/api/exam/service/practice" \ 29 | "/quizSubmitV3" == url 30 | 31 | def Url_Res(self, url: str) -> bool: 32 | """ 33 | Url_Res(url: str) -> bool 34 | 答案判断 35 | 36 | :param url: url 37 | :return: bool 38 | """ 39 | return "https://pc-proxy-api.xuexi.cn/api/exam/service/common" \ 40 | "/deduplicateRandomSearchV3?limit=5&activityCode=QUIZ_ALL" \ 41 | "&forced=true" == url 42 | 43 | def Extract(self, data: bytes) -> Dict: 44 | """ 45 | Extract(data: bytes) -> Dict 46 | 提取答案 47 | 48 | :param data: 包含答案的字节数据 49 | :return: Dict 50 | """ 51 | data = data.decode('utf-8') 52 | data = json.loads(data)['data_str'] 53 | return json.loads(base64.b64decode(data).decode('utf-8')) 54 | 55 | def Inject(self, data: bytes, res: Dict) -> bytes: 56 | """ 57 | Inject(data: bytes, res: Dict) -> bytes 58 | 注入答案 59 | 60 | :param data: 原始提交数据 61 | :param res: 包含答案数据 62 | :return: 修改后的提交数据 63 | """ 64 | data = json.loads(data.decode('utf-8')) 65 | data['uniqueId'] = res['uniqueId'] 66 | data['questions'].clear() 67 | data['usedTime'] = randint(20, 50) 68 | for question in res['questions']: 69 | data['questions'].append( 70 | { 71 | 'questionId': question['questionId'], 72 | 'answers': question['correct'], 73 | 'correct': True 74 | } 75 | ) 76 | return json.dumps(data).encode('utf-8') 77 | -------------------------------------------------------------------------------- /inside/Task/Mitmdump/Intercept/Weekly.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/2/6 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Weekly.py 8 | # @Function : 9 | import base64 10 | import json 11 | import re 12 | from random import randint 13 | from typing import Dict 14 | 15 | from inside.Task.Mitmdump.Intercept.ABC_Answer import ABC_ANSWER 16 | __all__ = ['WEEKLY'] 17 | 18 | 19 | class WEEKLY(ABC_ANSWER): 20 | """每周答题拦截体""" 21 | 22 | def Url_Put(self, url: str) -> bool: 23 | """ 24 | Url_Put(url: str) -> bool 25 | 提交判断 26 | 27 | :param url: url 28 | :return: bool 29 | """ 30 | return "https://pc-proxy-api.xuexi.cn/api/exam/service/practice" \ 31 | "/weekPracticeSubmitV3" == url 32 | 33 | def Url_Res(self, url: str) -> bool: 34 | """ 35 | Url_Res(url: str) -> bool 36 | 答案判断 37 | 38 | :param url: url 39 | :return: bool 40 | """ 41 | res = "https://pc-proxy-api.xuexi.cn/api/exam/service/detail/queryV3" \ 42 | "\\?type=2&id=.*&forced=true" 43 | if re.match(res, url): 44 | return True 45 | return False 46 | 47 | def Extract(self, data: bytes) -> Dict: 48 | """ 49 | Extract(data: bytes) -> Dict 50 | 提取答案 51 | 52 | :param data: 包含答案的字节数据 53 | :return: Dict 54 | """ 55 | data = data.decode('utf-8') 56 | data = json.loads(data)['data_str'] 57 | return json.loads(base64.b64decode(data).decode('utf-8')) 58 | 59 | def Inject(self, data: bytes, res: Dict) -> bytes: 60 | """ 61 | Inject(data: bytes, res: Dict) -> bytes 62 | 注入答案 63 | 64 | :param data: 原始提交数据 65 | :param res: 包含答案数据 66 | :return: 修改后的提交数据 67 | """ 68 | data = json.loads(data.decode('utf-8')) 69 | data['uniqueId'] = res['uniqueId'] 70 | data['questions'].clear() 71 | data['usedTime'] = randint(20, 50) 72 | for question in res['questions']: 73 | data['questions'].append( 74 | { 75 | 'questionId': question['questionId'], 76 | 'answers': question['correct'], 77 | 'correct': True 78 | } 79 | ) 80 | return json.dumps(data).encode('utf-8') 81 | -------------------------------------------------------------------------------- /inside/DB/Table_Class/Video.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/15 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Video.py 8 | # @Function : 视频类 9 | from typing import Union 10 | 11 | __all__ = ['VIDEO'] 12 | 13 | 14 | class VIDEO(object): 15 | """视频类""" 16 | 17 | def __init__(self, item: str, link: str, isread: Union[int, bool]): 18 | """ 19 | VIDEO(item: str, link: str, isread: Union[int, bool]) 20 | 初始化 21 | 22 | :param item: 标识 23 | :param link: 链接 24 | :param isread: 是否已读 25 | """ 26 | self.__item = item 27 | self.__link = link 28 | self.__isread = self.__Is_Read(isread=isread) 29 | 30 | @property 31 | def Item(self) -> str: 32 | """ 33 | Item -> str 34 | 标识 35 | 36 | :return: str 37 | """ 38 | return self.__item 39 | 40 | @property 41 | def Link(self) -> str: 42 | """ 43 | Link -> str 44 | 链接 45 | 46 | :return: str 47 | """ 48 | return self.__link 49 | 50 | def __Is_Read(self, isread: Union[int, bool]) -> bool: 51 | """ 52 | __Is_Read(isread: Union[int, bool]) -> bool 53 | 设定是否已读,由于数据库内不能存储布尔值,所以由0/1代替; 54 | 因此数字只限定0/1 55 | 56 | :param isread: 1/0 or bool 57 | :return: bool 58 | """ 59 | if isinstance(isread, bool): 60 | return isread 61 | elif isinstance(isread, int): 62 | if isread in (0, 1): 63 | return True if isread else False 64 | else: 65 | raise Exception('isread must be 0 or 1') 66 | 67 | @property 68 | def Is_Read(self) -> bool: 69 | """ 70 | Is_Read -> bool 71 | 是否已读 72 | 73 | :return: bool 74 | """ 75 | return self.__isread 76 | 77 | @Is_Read.setter 78 | def Is_Read(self, isread: Union[int, bool]) -> None: 79 | """ 80 | Is_Read -> None 81 | 设置是否已读 82 | 83 | :param isread: Union[int, bool] 84 | :return: None 85 | """ 86 | self.__isread = self.__Is_Read(isread=isread) 87 | 88 | @property 89 | def Is_Read_DB(self) -> int: 90 | """ 91 | Is_Read_DB -> int 92 | 是否已读(数据库读取接口,返回1/0) 93 | 94 | :return: int 95 | """ 96 | return 1 if self.__isread else 0 97 | -------------------------------------------------------------------------------- /inside/DB/Table_Class/Article.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/15 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Task_Article_Video.py 8 | # @Function : 文章类 9 | from typing import Union 10 | 11 | __all__ = ['ARTICLE'] 12 | 13 | 14 | class ARTICLE(object): 15 | """文章类""" 16 | 17 | def __init__(self, item: str, link: str, isread: Union[int, bool]): 18 | """ 19 | ARTICLE(item: str, link: str, isread: Union[int, bool]) 20 | 初始化 21 | 22 | :param item: 标识 23 | :param link: 链接 24 | :param isread: 是否已读 25 | """ 26 | self.__item = item 27 | self.__link = link 28 | self.__isread = self.__Is_Read(isread=isread) 29 | 30 | @property 31 | def Item(self) -> str: 32 | """ 33 | Item -> str 34 | 标识 35 | 36 | :return: str 37 | """ 38 | return self.__item 39 | 40 | @property 41 | def Link(self) -> str: 42 | """ 43 | Link -> str 44 | 链接 45 | 46 | :return: str 47 | """ 48 | return self.__link 49 | 50 | @classmethod 51 | def __Is_Read(cls, isread: Union[int, bool]) -> bool: 52 | """ 53 | __Is_Read(isread: Union[int, bool]) -> bool 54 | 设定是否已读,由于数据库内不能存储布尔值,所以由0/1代替; 55 | 因此数字只限定0/1 56 | 57 | :param isread: 1/0 or bool 58 | :return: bool 59 | """ 60 | if isinstance(isread, bool): 61 | return isread 62 | elif isinstance(isread, int): 63 | if isread in (0, 1): 64 | return True if isread else False 65 | else: 66 | raise Exception('isread must be 0 or 1') 67 | 68 | @property 69 | def Is_Read(self) -> bool: 70 | """ 71 | Is_Read -> bool 72 | 是否已读 73 | 74 | :return: bool 75 | """ 76 | return self.__isread 77 | 78 | @Is_Read.setter 79 | def Is_Read(self, isread: Union[int, bool]) -> None: 80 | """ 81 | Is_Read -> None 82 | 设置是否已读 83 | 84 | :param isread: Union[int, bool] 85 | :return: None 86 | """ 87 | self.__isread = self.__Is_Read(isread=isread) 88 | 89 | @property 90 | def Is_Read_DB(self) -> int: 91 | """ 92 | Is_Read_DB -> int 93 | 是否已读(数据库读取接口,返回1/0) 94 | 95 | :return: int 96 | """ 97 | return 1 if self.__isread else 0 98 | -------------------------------------------------------------------------------- /inside/Driver/Driver_Check.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/16 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Driver_Check.py 8 | # @Function : 驱动检测 9 | import os 10 | 11 | from inside.Config.Path import PATH 12 | from inside.Config.System import SYSTEM 13 | from inside.Template.Meta_Singleton import SINGLETON 14 | 15 | __all__ = ['DRIVER_CHECK'] 16 | 17 | 18 | class DRIVER_CHECK(metaclass=SINGLETON): 19 | """驱动检测类""" 20 | @property 21 | def Dir(self) -> bool: 22 | """ 23 | Dir -> bool 24 | 驱动目录是否存在 25 | 26 | :return: bool 27 | """ 28 | return os.path.exists(PATH().Driver) 29 | 30 | @property 31 | def File(self) -> bool: 32 | """ 33 | File -> bool 34 | 驱动文件是否存在 35 | 36 | :return: bool 37 | """ 38 | return os.path.exists(PATH().Driver_File) 39 | 40 | @property 41 | def Version(self) -> str: 42 | """ 43 | Version -> str 44 | 驱动支持谷歌浏览器版本号 45 | 46 | :return: str 47 | """ 48 | cmd = PATH().Driver_File + ' --version' 49 | version = os.popen(cmd=cmd).readline() 50 | version = version.strip().split() 51 | return version[1] 52 | 53 | @property 54 | def Execute_Permission(self) -> bool: 55 | """ 56 | Execute_Permission -> bool 57 | 驱动程序是否有执行权限 58 | 59 | :return: bool 60 | """ 61 | try: 62 | isinstance(self.Version, str) 63 | return True 64 | except IndexError: 65 | return False 66 | 67 | def Driver_Chrome_Version(self, system: SYSTEM) -> bool: 68 | """ 69 | Driver_Chrome_Version(system: SYSTEM) -> bool 70 | 驱动程序是否支持谷歌浏览器版本 71 | 72 | :param system: 系统类 73 | :return: bool 74 | """ 75 | driver_version = self.Version.split('.')[:3] 76 | chrome_version = system.Chrome_Version.split('.')[:3] 77 | return driver_version == chrome_version 78 | 79 | def Add_Execute_Permission(self, system: SYSTEM) -> bool: 80 | """ 81 | Add_Execute_Permission -> bool 82 | 为驱动程序添加执行权限 83 | 84 | :param system: 系统类 85 | :return: bool 86 | """ 87 | command = system.Driver_Chmod+PATH().Driver_File 88 | if command != PATH().Driver_File: 89 | return os.system(command=command) == 0 90 | return True 91 | 92 | -------------------------------------------------------------------------------- /inside/Task/Task_Article_Video.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/25 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Task_Article_Video.py 8 | # @Function : 文章视频任务 9 | import time 10 | from typing import Union 11 | 12 | from tqdm import tqdm 13 | from selenium.webdriver.chrome.webdriver import WebDriver 14 | 15 | from inside.Config.Api import API 16 | from inside.DB.Table_Class.Article import ARTICLE 17 | from inside.DB.Table_Class.Video import VIDEO 18 | from inside.Template.Meta_Singleton import SINGLETON 19 | from inside.Tools.Network import NETWORK 20 | from inside.Tools.Random import RANDOM 21 | 22 | __all__ = ['TASK_ARTICLE_VIDEO'] 23 | 24 | 25 | class TASK_ARTICLE_VIDEO(metaclass=SINGLETON): 26 | 27 | def __init__(self, task_driver: WebDriver): 28 | """ 29 | TASK_ARTICLE_VIDEO(task_driver: WebDriver) 30 | 初始化 31 | 32 | :param task_driver: 驱动 33 | """ 34 | self.__driver = task_driver 35 | self.__network = NETWORK() 36 | 37 | def __Check(self, item: str, tq: int) -> None: 38 | """ 39 | __Check(item: str, tq: int) -> None 40 | 监测任务的完成 41 | 42 | :param item: 任务编号 43 | :param tq: 等待程度 44 | :return: None 45 | """ 46 | bar = tqdm( 47 | desc=item, 48 | total=tq, 49 | unit='it', 50 | leave=False, 51 | ncols=70 52 | ) 53 | count = 0 54 | start = time.time() 55 | while True: 56 | js = "document.documentElement.scrollTop={speed}" 57 | self.__driver.execute_script( 58 | script=js.format(speed=RANDOM.Int(a=1, b=10)) 59 | ) 60 | temp = self.__network.Get() 61 | if API().Task_Check.geturl() in temp.keys() or \ 62 | time.time() - start >= 35: 63 | count += 1 64 | bar.update(n=1) 65 | start = time.time() 66 | if count >= tq: 67 | break 68 | time.sleep(0.1) 69 | bar.close() 70 | 71 | def Do(self, task: Union[ARTICLE, VIDEO], tq: int) -> None: 72 | """ 73 | Do(task: Union[ARTICLE, VIDEO]) -> None 74 | 进行一次文章或视频任务 75 | 76 | :param task: 任务 77 | :param tq: 等待程度 78 | :return: None 79 | """ 80 | self.__network.Clear() 81 | self.__driver.get(task.Link) 82 | self.__Check(item=task.Item, tq=tq) 83 | -------------------------------------------------------------------------------- /inside/Driver/Driver_Download.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/18 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Driver_Download.py 8 | # @Function : 驱动下载 9 | import os 10 | import zipfile 11 | from urllib.parse import ParseResult 12 | from urllib.request import urlopen 13 | 14 | import requests 15 | import shutil 16 | from tqdm import tqdm 17 | 18 | from inside.Config.Path import PATH 19 | from inside.Template.Meta_Singleton import SINGLETON 20 | 21 | __all__ = ['DRIVER_DOWNLOAD'] 22 | 23 | 24 | class DRIVER_DOWNLOAD(metaclass=SINGLETON): 25 | """驱动下载类""" 26 | 27 | def __Download(self, link: ParseResult, dst: str) -> int: 28 | """ 29 | __Download(link: ParseResult, dst: str) -> str 30 | 驱动下载本体 31 | 32 | :param link: 下载链接 33 | :param dst: 保存路径及文件名 34 | :return: str 35 | """ 36 | size = int(urlopen(link.geturl()).info().get('Content-Length', -1)) 37 | bar = tqdm( 38 | total=size, 39 | initial=0, 40 | unit='B', 41 | unit_scale=True, 42 | desc=os.path.basename(dst), 43 | ncols=70 44 | ) 45 | req = requests.get(url=link.geturl(), stream=True) 46 | with open(dst, 'ab') as f: 47 | for chunk in req.iter_content(chunk_size=1024): 48 | if chunk: 49 | f.write(chunk) 50 | bar.update(len(chunk)) 51 | else: 52 | f.close() 53 | bar.close() 54 | return size 55 | 56 | def __Unzip(self, dst: str) -> bool: 57 | """ 58 | __Unzip(dst: str) -> bool 59 | 解压zip文件 60 | 61 | :param dst: 解压路径 62 | :return: 63 | """ 64 | if zipfile.is_zipfile(dst): 65 | files = zipfile.ZipFile(dst) 66 | if not files.testzip(): 67 | files.extractall(path=PATH().Driver) 68 | return True 69 | return False 70 | 71 | def Download(self, link: ParseResult) -> int: 72 | """ 73 | Download(link: ParseResult) -> int 74 | 驱动下载 75 | 76 | :param link: 驱动下载链接 77 | :return: int 文件大小, 默认单位B 78 | """ 79 | suffix = os.path.basename(link.path) 80 | zip_file = os.path.join(PATH().Driver, suffix) 81 | shutil.rmtree(path=PATH().Driver) 82 | os.mkdir(PATH().Driver) 83 | temp = self.__Download(link=link, dst=zip_file) 84 | self.__Unzip(dst=zip_file) 85 | return temp 86 | -------------------------------------------------------------------------------- /inside/Info/Task_Info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/23 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Task_Info.py 8 | # @Function : 任务信息类 9 | __all__ = ['TASK_INFO'] 10 | 11 | 12 | class TASK_INFO(object): 13 | """任务信息类""" 14 | 15 | def __init__(self, ruleId: int, name: str, desc: str, currentScore: int, 16 | dayMaxScore: int): 17 | """ 18 | TASK_INFO(ruleId: int, name: str, desc: str, currentScore: int, 19 | dayMaxScore: int) 20 | 初始化 21 | 22 | :param ruleId: ID 23 | :param name: 名称 24 | :param desc: 说明 25 | :param currentScore: 已获积分 26 | :param dayMaxScore: 最大积分 27 | """ 28 | self.__rule_id = ruleId 29 | self.__name = name 30 | self.__desc = desc 31 | self.__current_score = currentScore 32 | self.__day_max_score = dayMaxScore 33 | 34 | @property 35 | def Rule_Id(self) -> int: 36 | """ 37 | Rule_Id -> int 38 | Id 39 | 40 | :return: int 41 | """ 42 | return self.__rule_id 43 | 44 | @property 45 | def Name(self) -> str: 46 | """ 47 | Name -> str 48 | 名称 49 | 50 | :return: str 51 | """ 52 | return self.__name 53 | 54 | @property 55 | def Desc(self) -> str: 56 | """ 57 | Desc -> str 58 | 说明 59 | 60 | :return: str 61 | """ 62 | return self.__desc 63 | 64 | @property 65 | def Current_Score(self) -> int: 66 | """ 67 | Current_Score -> int 68 | 已获积分 69 | 70 | :return: int 71 | """ 72 | return self.__current_score 73 | 74 | @Current_Score.setter 75 | def Current_Score(self, currentScore: int) -> None: 76 | """ 77 | Current_Score -> None 78 | 设置已获积分 79 | 80 | :param currentScore: 更新积分 81 | :return: None 82 | """ 83 | self.__current_score = currentScore 84 | 85 | @property 86 | def Day_Max_Score(self) -> int: 87 | """ 88 | Day_Max_Score -> int 89 | 最大积分 90 | 91 | :return: int 92 | """ 93 | return self.__day_max_score 94 | 95 | @property 96 | def Difference_Score(self) -> int: 97 | """ 98 | Difference_Score -> int 99 | 剩余可获积分 100 | 101 | :return: int 102 | """ 103 | return self.Day_Max_Score - self.Current_Score 104 | -------------------------------------------------------------------------------- /inside/Tools/Requests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/21 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Requests.py 8 | # @Function : requests初始化 9 | from typing import Dict 10 | 11 | import requests 12 | import urllib3 13 | from requests import Response 14 | 15 | from inside.Config.User_Agent import USER_AGENT 16 | from inside.Template.Meta_Singleton import SINGLETON 17 | 18 | __all__ = ['REQUESTS'] 19 | 20 | 21 | class REQUESTS(metaclass=SINGLETON): 22 | """requests重写类,拥有统一user-agent""" 23 | 24 | __header = { 25 | 'User-Agent': USER_AGENT().User_Agent 26 | } 27 | __proxies = { 28 | 'http': 'http://127.0.0.1:8080', 29 | 'https': 'http://127.0.0.1:8080' 30 | } 31 | urllib3.disable_warnings() 32 | 33 | @classmethod 34 | def __Check_Headers(cls, **kwargs) -> Dict: 35 | """ 36 | __Check_Headers(**kwargs) -> Dict 37 | 检测headers内容进行更新 38 | 39 | :param kwargs: 关键字参数 40 | :return: Dict 41 | """ 42 | if kwargs.get('headers'): 43 | headers = kwargs.pop('headers') 44 | cls.__header.update(headers) 45 | return kwargs 46 | 47 | @classmethod 48 | def Get(cls, url: str, params=None, **kwargs) -> Response: 49 | """ 50 | Get(url: str, params=None, **kwargs) -> Response 51 | Get请求 52 | 53 | :param url: 链接 54 | :param params: 参数 55 | :param kwargs: 关键字参数 56 | :return: Response 57 | """ 58 | kwargs = cls.__Check_Headers(**kwargs) 59 | html = requests.get( 60 | url=url, 61 | headers=cls.__header, 62 | proxies=cls.__proxies, 63 | verify=False, 64 | params=params, 65 | **kwargs 66 | ) 67 | html.encoding = html.apparent_encoding 68 | return html 69 | 70 | @classmethod 71 | def Post(cls, url: str, data=None, json=None, **kwargs) -> Response: 72 | """ 73 | Post(url: str, data=None, json=None, **kwargs) -> Response 74 | Post请求 75 | 76 | :param url:链接 77 | :param data: 数据 78 | :param json: 数据 79 | :param kwargs: 参数 80 | :return: Response 81 | """ 82 | kwargs = cls.__Check_Headers(**kwargs) 83 | html = requests.post( 84 | url=url, 85 | headers=cls.__header, 86 | proxies=cls.__proxies, 87 | verify=False, 88 | data=data, 89 | json=json, **kwargs 90 | ) 91 | html.encoding = html.apparent_encoding 92 | return html 93 | -------------------------------------------------------------------------------- /inside/Config/Path.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/14 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Path.py 8 | # @Function : 路径配置文件 9 | import os 10 | 11 | from inside.Config.System import SYSTEM 12 | from inside.Template.Meta_Singleton import SINGLETON 13 | 14 | __all__ = ['PATH'] 15 | 16 | 17 | class PATH(metaclass=SINGLETON): 18 | """路径类""" 19 | 20 | def __init__(self): 21 | """ 22 | PATH() 23 | 初始化,配置文件绝对路径 24 | 25 | """ 26 | self.__Config_Path: str = os.path.dirname(os.path.abspath(__file__)) 27 | 28 | @property 29 | def Base(self) -> str: 30 | """ 31 | Base -> str 32 | 项目绝对路径 33 | 34 | :return: str 35 | """ 36 | return os.path.dirname(os.path.dirname(self.__Config_Path)) 37 | 38 | @property 39 | def Driver(self) -> str: 40 | """ 41 | Driver -> str 42 | 驱动目录绝对路径 43 | 44 | :return: str 45 | """ 46 | return os.path.join(self.Base, 'Driver') 47 | 48 | @property 49 | def Driver_File(self) -> str: 50 | """ 51 | Driver_File -> str 52 | 驱动文件绝对路径 53 | 54 | :return: str 55 | """ 56 | return os.path.join(self.Driver, SYSTEM().Driver) 57 | 58 | @property 59 | def DB(self) -> str: 60 | """ 61 | DB -> str 62 | 数据库目录绝对路径 63 | 64 | :return: str 65 | """ 66 | return os.path.join(self.Base, 'DB') 67 | 68 | @property 69 | def DB_File(self) -> str: 70 | """ 71 | DB_File -> str 72 | 数据库文件绝对路径 73 | 74 | :return: str 75 | """ 76 | return os.path.join(self.DB, 'db.db') 77 | 78 | @property 79 | def Temp(self) -> str: 80 | """ 81 | Temp -> str 82 | 临时文件绝对路径 83 | 84 | :return: str 85 | """ 86 | return os.path.join(self.Base, 'Temp') 87 | 88 | @property 89 | def Script(self) -> str: 90 | """ 91 | Script -> str 92 | 拦截注入脚本绝对路径 93 | 94 | :return: str 95 | """ 96 | temp = os.path.join(os.path.dirname(self.__Config_Path), 'Task') 97 | temp = os.path.join(temp, 'Mitmdump') 98 | temp = os.path.join(temp, 'Intercept') 99 | temp = os.path.join(temp, 'Script.py') 100 | return temp 101 | 102 | @property 103 | def Baidu_AI_On(self) -> str: 104 | """ 105 | 百度AI开关共享文件 106 | 107 | Returns: str 108 | 109 | """ 110 | return os.path.join(os.path.dirname(self.Script), 'on') 111 | -------------------------------------------------------------------------------- /inside/Info/Info_Manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/23 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Info_Manage.py 8 | # @Function : 信息管理 9 | from typing import Dict, Tuple 10 | 11 | from inside.Info.Get_Info import GET_INFO 12 | from inside.Info.Task_Info import TASK_INFO 13 | from inside.Info.User_Info import USER_INFO 14 | from inside.Template.Meta_Singleton import SINGLETON 15 | 16 | __all__ = ['INFO_MANAGE'] 17 | 18 | 19 | class INFO_MANAGE(metaclass=SINGLETON): 20 | """信息管理类""" 21 | __Get: GET_INFO 22 | 23 | def __init__(self): 24 | """ 25 | INFO_MANAGE() 26 | 初始化 27 | 28 | """ 29 | self.__self = '_'+type(self).__name__ 30 | 31 | def Init(self, token: str) -> None: 32 | """ 33 | Init(token: str) -> None 34 | 初始化信息获取 35 | 36 | :param token: 令牌 37 | :return: None 38 | """ 39 | self.__Get = GET_INFO(token=token) 40 | 41 | @property 42 | def Get(self) -> GET_INFO: 43 | """ 44 | Get -> GET_INFO 45 | 信息获取类 46 | 47 | :return: GET_INFO 48 | """ 49 | if hasattr(self, self.__self+'__Get'): 50 | return self.__Get 51 | raise Exception(f"检测到更新信息类未初始化\n" 52 | f"请先调用{type(self).__name__}().Init(token=xxx)") 53 | 54 | @property 55 | def Id(self) -> int: 56 | """ 57 | Id - > int 58 | 用户ID 59 | 60 | :return: int 61 | """ 62 | return USER_INFO().User_Id 63 | 64 | @property 65 | def Aggregate_Score(self) -> float: 66 | """ 67 | Aggregate_Score -> float 68 | 总积分 69 | 70 | :return: float 71 | """ 72 | self.Get.Get_Aggregate_Score() 73 | return USER_INFO().Aggregate_Score 74 | 75 | @property 76 | def Daily_Score(self) -> float: 77 | """ 78 | Daily_Score -> float 79 | 每日积分 80 | 81 | :return: float 82 | """ 83 | self.Get.Get_Daily_Score() 84 | return USER_INFO().Daily_Score 85 | 86 | @property 87 | def Level_Info(self) -> Tuple[int, str, int]: 88 | """ 89 | Level -> int 90 | 等级、段位、全国排名 91 | 92 | :return: Tuple[int, str, int] 93 | """ 94 | self.Get.Get_Level() 95 | return (USER_INFO().Level, 96 | USER_INFO().Level_Name, 97 | USER_INFO().Rank_Accumulate_In_Country) 98 | 99 | @property 100 | def Task_Bar(self) -> Dict[int, TASK_INFO]: 101 | """ 102 | Task_Bar -> Dict[int, TASK_INFO] 103 | 任务进度 104 | 105 | :return: Dict[int, TASK_INFO] 106 | """ 107 | self.Get.Get_Task_Bar() 108 | return USER_INFO().Task_Bar 109 | -------------------------------------------------------------------------------- /inside/Config/User_Agent.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/15 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : User_Agent.py 8 | # @Function : User_Agent大全 9 | from inside.Template.Meta_Singleton import SINGLETON 10 | from inside.Tools.Random import RANDOM 11 | 12 | __all__ = ['USER_AGENT'] 13 | 14 | 15 | class USER_AGENT(metaclass=SINGLETON): 16 | """模拟浏览器头类""" 17 | 18 | __User_Agents = [ 19 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0' 20 | 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36 OPR/26.0.1656.60', 21 | 'Opera/8.0 (Windows NT 5.1; U; en)', 22 | 'Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.50', 23 | 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0', 24 | 'Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10', 25 | 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.57.2 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2', 26 | 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36', 27 | 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11', 28 | 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16', 29 | 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36', 30 | 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko', 31 | 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.11 TaoBrowser/2.0 Safari/536.11', 32 | 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.71 Safari/537.1 LBBROWSER', 33 | 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.84 Safari/535.11 SE 2.X MetaSr 1.0', 34 | 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Maxthon/4.4.3.4000 Chrome/30.0.1599.101 Safari/537.36' 35 | 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.122 UBrowser/4.0.3214.0 Safari/537.36' 36 | ] 37 | 38 | def __init__(self): 39 | """ 40 | USER_AGENT() 41 | 初始化,随机挑选一个头 42 | 43 | """ 44 | ran = RANDOM() 45 | self.__user_agent = self.__User_Agents[ 46 | ran.Int(a=0, b=len(self.__User_Agents)-1) 47 | ] 48 | 49 | @property 50 | def User_Agent(self) -> str: 51 | """ 52 | User_Agent -> str 53 | 获取浏览器伪装,由于初始化时,已随机设定过,此后一直是同一条。 54 | 55 | :return: str 56 | """ 57 | return self.__user_agent 58 | -------------------------------------------------------------------------------- /inside/Template/ABC_System_Args.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/15 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : ABC_System_Args.py 8 | # @Function : 系统基类 9 | from abc import abstractmethod 10 | 11 | from inside.Template.Meta_Singleton import SINGLETON_ABC 12 | 13 | __all__ = ["SYSTEM_ARGS"] 14 | 15 | 16 | class ABC_SYSTEM_ARGS(metaclass=SINGLETON_ABC): 17 | """抽象系统类""" 18 | 19 | @abstractmethod 20 | def System(self) -> str: 21 | """ 22 | System() -> str 23 | 操作系统 24 | 25 | :return: str 26 | """ 27 | 28 | @abstractmethod 29 | def Clear(self) -> str: 30 | """ 31 | Clear() -> str 32 | 清空控制台命令 33 | 34 | :return: str 35 | """ 36 | pass 37 | 38 | @abstractmethod 39 | def Driver(self) -> str: 40 | """ 41 | Driver() -> str 42 | 驱动文件名称 43 | 44 | :return: str 45 | """ 46 | pass 47 | 48 | @abstractmethod 49 | def Driver_Chmod(self) -> str: 50 | """ 51 | Driver_Chmod() -> str 52 | 添加驱动文件执行权限命令 53 | 54 | :return: str 55 | """ 56 | pass 57 | 58 | @abstractmethod 59 | def Chrome(self) -> bool: 60 | """ 61 | Chrome() -> bool 62 | 谷歌浏览器是否安装 63 | 64 | :return: bool 65 | """ 66 | pass 67 | 68 | @abstractmethod 69 | def Chrome_Version(self) -> str: 70 | """ 71 | Chrome_Version() -> str 72 | 谷歌浏览器版本号 73 | 74 | :return: str 75 | """ 76 | pass 77 | 78 | 79 | class SYSTEM_ARGS(ABC_SYSTEM_ARGS): 80 | """系统实际继承类,使每个方法都变为属性""" 81 | 82 | @property 83 | def System(self) -> str: 84 | """ 85 | System() -> str 86 | 操作系统 87 | 88 | :return: str 89 | """ 90 | pass 91 | 92 | @property 93 | def Clear(self) -> str: 94 | """ 95 | Clear -> str 96 | 清空控制台命令 97 | 98 | :return: str 99 | """ 100 | pass 101 | 102 | @property 103 | def Driver(self) -> str: 104 | """ 105 | Driver -> str 106 | 驱动文件名称 107 | 108 | :return: str 109 | """ 110 | pass 111 | 112 | @property 113 | def Driver_Chmod(self) -> str: 114 | """ 115 | Driver_Chmod -> str 116 | 添加驱动文件执行权限命令 117 | 118 | :return: str 119 | """ 120 | pass 121 | 122 | @property 123 | def Chrome(self) -> bool: 124 | """ 125 | Chrome -> bool 126 | 谷歌浏览器是否安装 127 | 128 | :return: bool 129 | """ 130 | pass 131 | 132 | @property 133 | def Chrome_Version(self) -> str: 134 | """ 135 | Chrome_Version -> str 136 | 谷歌浏览器版本号 137 | 138 | :return: str 139 | """ 140 | pass 141 | 142 | -------------------------------------------------------------------------------- /inside/DB/DB_Manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/15 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : DB_Manage.py 8 | # @Function : 数据库管理 9 | import sqlite3 10 | from inside.Config.Path import PATH 11 | from inside.DB.DB_Article import DB_ARTICLE 12 | from inside.DB.DB_Baidu_AI import DB_BAIDU_AI 13 | from inside.DB.DB_Check import DB_CHECK 14 | from inside.DB.DB_Project import DB_PROJECT 15 | from inside.DB.DB_Task import DB_TASK 16 | from inside.DB.DB_User import DB_USER 17 | from inside.DB.DB_Video import DB_VIDEO 18 | from inside.Template.Meta_Singleton import SINGLETON 19 | 20 | __all__ = ['DB_MANAGE'] 21 | 22 | 23 | class DB_MANAGE(metaclass=SINGLETON): 24 | """数据库管理类""" 25 | 26 | def __init__(self): 27 | """ 28 | DB_MANAGE() 29 | 初始化时自动检查数据库信息 30 | 31 | """ 32 | DB_CHECK().Check_Dir() 33 | self.__connect = sqlite3.connect(PATH().DB_File) 34 | self.__cursor = self.__connect.cursor() 35 | self.__Check_Table() 36 | 37 | def __Check_Table(self) -> None: 38 | """ 39 | __Check_Table() -> None 40 | 检查表 41 | 42 | :return: None 43 | """ 44 | DB_CHECK().Check_Table( 45 | connect=self.__connect, 46 | cursor=self.__cursor 47 | ) 48 | 49 | def Quit(self): 50 | self.__connect.close() 51 | 52 | @property 53 | def User(self) -> DB_USER: 54 | """ 55 | User -> DB_USER 56 | 用户表操作对象 57 | 58 | :return: DB_USER 59 | """ 60 | return DB_USER(connect=self.__connect, cursor=self.__cursor) 61 | 62 | @property 63 | def Task(self) -> DB_TASK: 64 | """ 65 | Task -> DB_TASK 66 | 任务表操作对象 67 | 68 | :return: DB_TASK 69 | """ 70 | return DB_TASK(connect=self.__connect, cursor=self.__cursor) 71 | 72 | @property 73 | def Article(self) -> DB_ARTICLE: 74 | """ 75 | Article -> DB_ARTICLE 76 | 文章表操作对象 77 | 78 | :return: DB_ARTICLE 79 | """ 80 | return DB_ARTICLE(connect=self.__connect, cursor=self.__cursor) 81 | 82 | @property 83 | def Video(self) -> DB_VIDEO: 84 | """ 85 | Video -> DB_VIDEO 86 | 视频表操作对象 87 | 88 | :return: DB_VIDEO 89 | """ 90 | return DB_VIDEO(connect=self.__connect, cursor=self.__cursor) 91 | 92 | @property 93 | def Project(self) -> DB_PROJECT: 94 | """ 95 | Project -> DB_PROJECT 96 | 专项答题表操作对象 97 | 98 | :return: DB_PROJECT 99 | """ 100 | return DB_PROJECT(connect=self.__connect, cursor=self.__cursor) 101 | 102 | @property 103 | def Baidu_AI(self) -> DB_BAIDU_AI: 104 | """ 105 | Baidu_AI -> DB_BAIDU_AI 106 | 百度AI表操作对象 107 | 108 | @return: DB_BAIDU_AI 109 | """ 110 | return DB_BAIDU_AI(connect=self.__connect, cursor=self.__cursor) 111 | -------------------------------------------------------------------------------- /inside/Driver/Driver_Manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/16 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Driver_Manage.py 8 | # @Function : 驱动管理 9 | import os 10 | 11 | from selenium.webdriver.chrome.webdriver import WebDriver 12 | 13 | from inside.Config.Api import API 14 | from inside.Config.Path import PATH 15 | from inside.Config.System import SYSTEM 16 | from inside.Driver.Driver_Check import DRIVER_CHECK 17 | from inside.Driver.Driver_Download import DRIVER_DOWNLOAD 18 | from inside.Driver.Driver_Init import DRIVER_INIT 19 | from inside.Template.Meta_Singleton import SINGLETON 20 | 21 | __all__ = ['DRIVER_MANAGE'] 22 | 23 | 24 | class DRIVER_MANAGE(metaclass=SINGLETON): 25 | """驱动管理类""" 26 | 27 | def __init__(self): 28 | """ 29 | 初始化时会要求设置选项 30 | """ 31 | self.__Driver_Check() 32 | 33 | @classmethod 34 | def __Check_Dir(cls) -> bool: 35 | """ 36 | Check_Dir() -> None 37 | 检测驱动目录是否存在,如不存在则自动创建 38 | 39 | :return: bool 40 | """ 41 | if not DRIVER_CHECK().Dir: 42 | print(f"检测到驱动目录未创建\n" 43 | f"自动创建中") 44 | os.mkdir(PATH().Driver) 45 | print(f"驱动目录为{PATH().Driver}") 46 | return False 47 | return True 48 | 49 | def __Driver_Check(self) -> None: 50 | """ 51 | __Driver_Check() -> None 52 | 驱动检查,确保驱动能够正常使用 53 | 54 | :return: None 55 | """ 56 | temp = False 57 | if not temp and not self.__Check_Dir(): 58 | temp = True 59 | if not temp and not DRIVER_CHECK().File: 60 | print(f"检测到驱动未下载") 61 | temp = True 62 | if not temp and not DRIVER_CHECK().Driver_Chrome_Version( 63 | system=SYSTEM()): 64 | print(f"检测到驱动不支持本机Chrome") 65 | temp = True 66 | if temp: 67 | print(f"驱动自动下载中") 68 | size = DRIVER_DOWNLOAD().Download( 69 | link=API().Driver.Download(system=SYSTEM())) 70 | print(f"驱动自动下载完毕\n" 71 | f"文件大小为{size / 1024 / 1024}MB") 72 | if not DRIVER_CHECK().Execute_Permission: 73 | print(f"检测到驱动没有执行权限\n" 74 | f"自动添加执行权限") 75 | DRIVER_CHECK().Add_Execute_Permission(system=SYSTEM()) 76 | print(f"添加执行权限完毕") 77 | 78 | @property 79 | def Task(self) -> WebDriver: 80 | """ 81 | Task -> WebDriver 82 | 任务浏览器驱动器,关闭时,请务必使用Task_Quit 83 | 84 | :return: WebDriver 85 | """ 86 | return DRIVER_INIT().Task_Driver 87 | 88 | @property 89 | def Task_Quit(self) -> str: 90 | """ 91 | Task_Quit -> str 92 | 任务浏览器驱动器关闭 93 | 94 | :return: str 95 | """ 96 | return DRIVER_INIT().Task_Quit 97 | 98 | @property 99 | def QR(self) -> WebDriver: 100 | """ 101 | QR -> WebDriver 102 | 二维码浏览器驱动器,关闭时,请务必使用QR_Quit 103 | 104 | :return: WebDriver 105 | """ 106 | return DRIVER_INIT().QR_Driver 107 | 108 | @property 109 | def QR_Quit(self) -> str: 110 | """ 111 | QR_Quit -> str 112 | 二维码浏览器驱动器关闭 113 | 114 | :return: str 115 | Success: 退出成功 116 | Nonexistence: 不存在任务浏览器 117 | """ 118 | return DRIVER_INIT().QR_Quit 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 项目简介 2 | 3 | 4 | 5 | > 学习强国自动化脚本,解放你的时间! 6 | > 7 | > 使用Selenium、requests、mitmpoxy、百度智能云文字识别开发而成 8 | 9 | 10 | 11 | # 使用说明 12 | 13 | > **注**:Chrome版本 14 | > 15 | > ![image-20201207091125954](https://gitee.com/lisztomania/Figure-bed/raw/master/img/image-20201207091125954.png) 16 | > 17 | > 驱动会自动下载 18 | > 19 | > 首次使用会生成数据库文件db.db,用于提高文章、视频任务效率。 20 | 21 | 22 | 23 | ## 依赖安装 24 | 25 | > pip install -r requirements.txt 26 | > 27 | > 没有梯子的同学可使用国内阿里源: 28 | > 29 | > pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple 30 | 31 | 32 | 33 | ## 使用方法 34 | 35 | > **一定要找个网络好的地方,不然可能会出现错误** 36 | > 37 | > 1、控制台运行:python main.py 38 | > 39 | > 2、选择选项(如非必要,尽量选择不显示自动化过程,以免误操) 40 | > 41 | > 等待片刻,连接学习强国服务器需要时间,等待时间与网速关系很大。 42 | > 43 | > 3、扫描二维码登录 44 | > 45 | > 4、选择任务(暂时只支持文章、视频、每日答题、每周答题、专项答题,后续功能正在开发,不过暂时也差不多够用了,45分呢),可多选,不过每个选项要用空格隔开,选择文章或视频时,等待时间稍久一点。 46 | > 47 | > 5、任务完成后需手动结束程序 48 | 49 | 50 | 51 | ## 使用示例 52 | 53 | > ![image-20210127134017543](https://i.loli.net/2021/01/27/TNXRhOM12KC5lBf.png) 54 | > 55 | > ![image-20210207000645085](https://i.loli.net/2021/02/07/oN3wudgqn8zKiDX.png) 56 | > 57 | > ![image-20210207000823436](https://i.loli.net/2021/02/07/5DbiwCL6ZQK3fMu.png) 58 | > 59 | > ![image-20210207001005808](https://i.loli.net/2021/02/07/Rt6udzivIks7Cl2.png) 60 | > 61 | > ![image-20210207001047255](https://i.loli.net/2021/02/07/FKgL1yf6cqpa4Bi.png) 62 | > 63 | > ![image-20210207001122282](https://i.loli.net/2021/02/07/dXRsVLnjzvKtQ9l.png) 64 | > 65 | > ![image-20210207001149837](https://i.loli.net/2021/02/07/aOqSbpMywn6Xel3.png) 66 | > 67 | > ![image-20210207001303406](https://i.loli.net/2021/02/07/W5X4RQcGljiUZJw.png) 68 | > 69 | > ![image-20210207001404717](https://i.loli.net/2021/02/07/6MXVxlvuBk5OFi3.png) 70 | 71 | 72 | 73 | ## 百度智能云操作流程 74 | 75 | > 1、登录控制台 76 | > 77 | > 点击→[百度智能云](https://login.bce.baidu.com/?account=&redirect=http://console.bce.baidu.com/ai/#/ai/ocr/overview/index, "百度智能云") 78 | > 79 | > ![image-20210206231654368](https://i.loli.net/2021/02/06/fS9zDyHXx4jILgB.png) 80 | > 81 | > 2、创建应用 82 | > 83 | > ![image-20210206235118045](https://i.loli.net/2021/02/06/Z5Cx2RhylLBITni.png) 84 | > 85 | > 3、选择选项 86 | > 87 | > ![image-20210206235421370](https://i.loli.net/2021/02/06/tnqzByNGEIDSvJY.png) 88 | > 89 | > ![image-20210206235616741](https://i.loli.net/2021/02/06/vpT9MYQ8dtFVGAD.png) 90 | > 91 | > 4、获取API Key、Secret Key 92 | > 93 | > ![image-20210207000519698](https://i.loli.net/2021/02/07/JkDVs4hwn7EN8xd.png) 94 | 95 | 96 | 97 | 98 | 99 | ## 版本说明 100 | 101 | > - [ ] ~~v0.1:文章、视频,分数:25~~ 102 | > - [ ] ~~v0.2:优化文章、优化视频、每日答题(百分百正确),分数30~~ 103 | > - [ ] ~~v0.3:新增每周答题、专项答题(也是百分百正确),分数45~~ 104 | > - [ ] ~~v0.31:优化记录存储、优化目录结构、优化配置文件结构,增加进度条、增加自动下载驱动、增加系统兼容(Linux、Windows、MacOS)~~ 105 | > - [ ] ~~v1.0: 重构整个项目,增加持久化、驱动自动检测与谷歌浏览器匹配、驱动自主下载、更快的登录、文章和视频自适应、更快更精准的答题、加强的防检测、每个文件都有说明注释(便于各位大佬修改)~~ 106 | > - [x] v1.1: 由于专项答题视频答案匹配问题,现加入百度智能云的文字识别功能,可将视频中的答案提取出来,不过答案还需手动填写,因为提取的答案暂时没有好的办法过滤。至少不用看视频了是不2333. 107 | 108 | 109 | 110 | ## 附语 111 | 112 | > 在持久化登录方面思考了很久,要不要做批量持久化?,后来想了想,本项目的目的是为了帮助没有时间做学习强国任务的个人节省时间,如果做了批量的话,恐怕会沦为某些人的牟利工具。所以最后决定只做单用户持久模式,如果有想做批量的话,建议细读学习强国App积分页的提醒警示。 113 | > 114 | > 希望大佬点点start,给点动力,希望能给大家带来更多的功能! 115 | 116 | -------------------------------------------------------------------------------- /inside/Baidu_AI/Baidu_AI_Tools.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/2/5 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Baidu_AI_Tools.py 8 | # @Function : 百度AI图像文字识别工具 9 | import requests 10 | import base64 11 | import cv2 12 | from PIL import Image 13 | 14 | from inside.DB.Table_Class.Baidu_AI import BAIDU_AI 15 | from inside.Template.Meta_Singleton import SINGLETON 16 | __all__ = ['BAIDU_AI_TOOLS'] 17 | 18 | 19 | class BAIDU_AI_TOOLS(metaclass=SINGLETON): 20 | """百度AI工具""" 21 | __token: str 22 | 23 | def __init__(self, baidu_ai: BAIDU_AI): 24 | """ 25 | BAIDU_AI_TOOLS(baidu_ai: BAIDU_AI) 26 | 初始化 27 | 28 | Args: 29 | baidu_ai: 百度AI 30 | """ 31 | self.__self = '_'+type(self).__name__ 32 | self.__ai = baidu_ai 33 | self.__Set_Token() 34 | 35 | def __Set_Token(self) -> None: 36 | """ 37 | __Set_Token() -> None 38 | 获取设置token 39 | 40 | Returns: None 41 | 42 | """ 43 | host = f'https://aip.baidubce.com/oauth/2.0/token?grant_type' \ 44 | f'=client_credentials&client_id={self.__ai.Ak}&client_' \ 45 | f'secret={self.__ai.Sk}' 46 | html = requests.get(host) 47 | self.__token = html.json().get('access_token') 48 | 49 | @classmethod 50 | def Cut(cls, video_link: str) -> bytes: 51 | """ 52 | Cut(video_link: str) -> bytes 53 | 取帧迭代器 54 | 55 | Args: 56 | video_link: 视频链接 57 | 58 | Returns: 图片 59 | 60 | """ 61 | video = cv2.VideoCapture(video_link) 62 | if video.isOpened(): 63 | frame_rate = video.get(5) 64 | frame_number = video.get(7) 65 | interval = frame_number // frame_rate 66 | video.set(cv2.CAP_PROP_POS_FRAMES, frame_rate * (interval - 2)) 67 | num = 0 68 | rval, frame = video.read() 69 | while rval: 70 | if num % frame_rate == 0: 71 | ret, buf = cv2.imencode('.jpg', frame) 72 | pic = Image.fromarray(buf).tobytes() 73 | yield pic 74 | rval, frame = video.read() 75 | num += 1 76 | 77 | def Answer(self, video_link: str) -> str: 78 | """ 79 | Answer(video_link: str) -> str 80 | 获取答案 81 | 通用文字识别(高精度版) 82 | 83 | Args: 84 | video_link: 视频链接 85 | 86 | Returns: str 87 | 88 | """ 89 | video = self.Cut(video_link=video_link) 90 | while True: 91 | try: 92 | img = base64.b64encode(next(video)) 93 | except StopIteration: 94 | break 95 | request_url = "https://aip.baidubce.com/rest/2.0/ocr/v1" \ 96 | "/accurate_basic" 97 | params = {"image": img} 98 | request_url = request_url + "?access_token=" + self.__token 99 | headers = {'content-type': 'application/x-www-form-urlencoded'} 100 | response = requests.post(request_url, data=params, headers=headers) 101 | if response: 102 | for words in response.json()['words_result']: 103 | for topic in ('考题答案:', '考题答案', '参考答案:', '参考答案'): 104 | if topic in words['words']: 105 | return words['words'].replace(topic, '').strip() 106 | -------------------------------------------------------------------------------- /inside/DB/DB_Baidu_AI.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/2/6 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : DB_Baidu_AI.py 8 | # @Function : 百度AI操作类 9 | from sqlite3 import Connection, Cursor 10 | 11 | from inside.DB.DB_Config import DB_CONFIG 12 | from inside.DB.Table_Class.Baidu_AI import BAIDU_AI 13 | from inside.Template.Meta_Singleton import SINGLETON 14 | 15 | __all__ = ['DB_BAIDU_AI'] 16 | 17 | 18 | class DB_BAIDU_AI(metaclass=SINGLETON): 19 | """百度AI表操作类""" 20 | 21 | def __init__(self, connect: Connection, cursor: Cursor): 22 | """ 23 | 初始化 24 | 25 | :param connect: 数据库连接对象 26 | :param cursor: 数据库光标 27 | """ 28 | self.__connect = connect 29 | self.__cursor = cursor 30 | self.__name = DB_CONFIG().Baidu_AI 31 | self.__fields = DB_CONFIG().Baidu_AI_Fields 32 | 33 | def Insert(self, baidu_ai: BAIDU_AI) -> None: 34 | """ 35 | Insert(article: ARTICLE) -> None 36 | 插入百度AI,如表不为空则清空表 37 | 38 | @param baidu_ai: 百度AI 39 | @return: None 40 | """ 41 | if not self.Empty(): 42 | self.Delete() 43 | sql = f'''INSERT INTO 44 | {self.__name} ( 45 | {self.__fields[1]}, 46 | {self.__fields[2]}) 47 | VALUES (?, ?);''' 48 | data = ( 49 | baidu_ai.Ak, 50 | baidu_ai.Sk 51 | ) 52 | self.__cursor.execute(sql, data) 53 | self.__connect.commit() 54 | 55 | def Seq_Init(self) -> None: 56 | """ 57 | Seq_Init() -> None 58 | 将百度AI表的数据序号重置为0 59 | 60 | @return: None 61 | """ 62 | sql = f'''UPDATE sqlite_sequence SET seq=0 WHERE name='{self.__name}';''' 63 | self.__cursor.execute(sql) 64 | self.__connect.commit() 65 | 66 | def Delete(self) -> None: 67 | """ 68 | Delete() -> None 69 | 清空百度AI表,并重置序号 70 | 71 | @return: None 72 | """ 73 | sql = f'''DELETE FROM {self.__name};''' 74 | self.__cursor.execute(sql) 75 | self.__connect.commit() 76 | self.Seq_Init() 77 | 78 | def Query(self) -> BAIDU_AI: 79 | """ 80 | Query() -> BAIDU_AI 81 | 获取百度AI 82 | 83 | @return: BAIDU_AI 84 | """ 85 | sql = f'''SELECT * FROM {self.__name} LIMIT 1;''' 86 | result = self.__cursor.execute(sql) 87 | temp = next(result) 88 | return BAIDU_AI(ak=temp[1], sk=temp[2]) 89 | 90 | def Empty(self) -> bool: 91 | """ 92 | Empty() -> bool 93 | 是否为空 94 | 95 | @return: bool 96 | """ 97 | sql = f'''SELECT 1 FROM {self.__name} LIMIT 1;''' 98 | result = self.__cursor.execute(sql) 99 | try: 100 | next(result) 101 | return False 102 | except StopIteration: 103 | return True 104 | 105 | def Exist(self, baidu_ai: BAIDU_AI) -> bool: 106 | """ 107 | Exist(baidu_ai: BAIDU_AI = None) -> bool 108 | 是否存在指定百度AI 109 | 110 | @param baidu_ai: 百度AI 111 | @return: 112 | """ 113 | sql = f'''SELECT 1 FROM {self.__name} 114 | WHERE {self.__fields[1]}=? 115 | AND {self.__fields[2]}=?;''' 116 | data = ( 117 | baidu_ai.Ak, 118 | baidu_ai.Sk 119 | ) 120 | result = self.__cursor.execute(sql, data) 121 | try: 122 | next(result) 123 | return True 124 | except StopIteration: 125 | return False 126 | -------------------------------------------------------------------------------- /inside/Config/System.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/15 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : System.py 8 | # @Function : 系统适配 9 | import math 10 | import sys 11 | from typing import FrozenSet 12 | 13 | from inside.Config.SystemArgs import WINDOWS, LINUX, MACOS 14 | from inside.Template.ABC_System_Args import SYSTEM_ARGS 15 | 16 | __all__ = ['SYSTEM'] 17 | 18 | 19 | class SYSTEM(SYSTEM_ARGS): 20 | """系统类""" 21 | __supports = frozenset({'linux', 'win32', 'darwin'}) 22 | __supports_map = { 23 | 'linux': 'Linux', 24 | 'win32': 'Windows', 25 | 'darwin': 'MacOs' 26 | } 27 | 28 | # 支持系统 29 | @property 30 | def Supports(self) -> FrozenSet: 31 | """ 32 | Supports -> FrozenSet 33 | 支持系统集合 34 | 35 | :return: FrozenSet 36 | """ 37 | return self.__supports 38 | 39 | # 系统 40 | @property 41 | def Name(self) -> str: 42 | """ 43 | Name -> str 44 | 系统名称 45 | 46 | :return: str 47 | """ 48 | return sys.platform 49 | 50 | # 位数 51 | @property 52 | def Bit(self) -> int: 53 | """ 54 | Bit -> int 55 | 系统位数 56 | 57 | :return: int 58 | """ 59 | return int(math.log2(sys.maxsize + 1) + 1) 60 | 61 | # 系统参数 62 | @property 63 | def __System_Args(self) -> SYSTEM_ARGS: 64 | """ 65 | __System_Args -> SYSTEM_ARGS 66 | 获取当前操作系统类 67 | 68 | :return: SYSTEM_ARGS 69 | """ 70 | if self.Name not in self.Supports: 71 | print(f"检测到运行环境为{self.Name}操作系统\n" 72 | f"本程序暂不支持此系统\n" 73 | f"仅支持{tuple(self.__supports_map.values())}") 74 | exit(code=0) 75 | else: 76 | if self.Name == 'win32': 77 | return WINDOWS() 78 | elif self.Name == 'linux': 79 | return LINUX() 80 | elif self.Name == 'darwin': 81 | return MACOS() 82 | 83 | @property 84 | def System(self) -> str: 85 | """ 86 | System -> str 87 | 操作系统 88 | 89 | :return: str 90 | """ 91 | return self.__System_Args.System 92 | 93 | @property 94 | def Clear(self) -> str: 95 | """ 96 | Clear -> str 97 | 清空控制台命令 98 | 99 | :return: str 100 | """ 101 | return self.__System_Args.Clear 102 | 103 | @property 104 | def Driver(self) -> str: 105 | """ 106 | Driver -> str 107 | 驱动文件名称 108 | 109 | :return: str 110 | """ 111 | return self.__System_Args.Driver 112 | 113 | @property 114 | def Driver_Chmod(self) -> str: 115 | """ 116 | Driver_Chmod -> str 117 | 添加驱动文件执行权限命令 118 | 119 | :return: 120 | """ 121 | return self.__System_Args.Driver_Chmod 122 | 123 | @property 124 | def Chrome(self) -> bool: 125 | """ 126 | Chrome -> bool 127 | 谷歌浏览器是否安装 128 | 129 | :return: bool 130 | """ 131 | return self.__System_Args.Chrome 132 | 133 | @property 134 | def Chrome_Version(self) -> str: 135 | """ 136 | Chrome_Version -> str 137 | 谷歌浏览器版本号 138 | 139 | :return: str 140 | """ 141 | return self.__System_Args.Chrome_Version 142 | 143 | def Check_Chrome(self) -> None: 144 | """ 145 | Check_Chrome() -> None 146 | 检测谷歌浏览器是否安装,未安装则退出程序 147 | 148 | :return: None 149 | """ 150 | if not self.Chrome: 151 | print("检测到谷歌浏览器未安装,无法进行本程序") 152 | exit(code=0) 153 | -------------------------------------------------------------------------------- /inside/Options/Options_Manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/23 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Options_Manage.py 8 | # @Function : 选项管理 9 | from inside.Config.Path import PATH 10 | from inside.Baidu_AI.Baidu_AI_Manage import BAIDU_AI_MANAGE 11 | from inside.Options.Options import OPTIONS 12 | 13 | from inside.Template.Meta_Singleton import SINGLETON 14 | from inside.Tools import Quit 15 | 16 | __all__ = ['OPTIONS_MANAGE'] 17 | 18 | 19 | class OPTIONS_MANAGE(metaclass=SINGLETON): 20 | """选项管理类""" 21 | 22 | @classmethod 23 | def __Auto(cls) -> None: 24 | """ 25 | __Auto() -> None 26 | 禁音选项(默认: True) 27 | 28 | :return: None 29 | """ 30 | auto = input('是否静音(Y/n):').strip() 31 | if auto in ['y', 'Y', '']: 32 | OPTIONS().Mute_Audio = True 33 | else: 34 | OPTIONS().Mute_Audio = False 35 | 36 | @classmethod 37 | def __Headless(cls) -> None: 38 | """ 39 | __Headless() -> None 40 | 显示过程选项(默认: False) 41 | 42 | :return: None 43 | """ 44 | headless = input('是否显示自动化过程(y/N):').strip() 45 | if headless in ['y', 'Y']: 46 | OPTIONS().Headless = False 47 | else: 48 | OPTIONS().Headless = True 49 | 50 | @classmethod 51 | def __Token(cls) -> None: 52 | """ 53 | __Token() -> None 54 | 持久化选项(默认:True) 55 | 56 | :return: None 57 | """ 58 | token = input('是否持久化登录(Y/n):').strip() 59 | if token in ['y', 'Y', '']: 60 | OPTIONS().Token = True 61 | else: 62 | OPTIONS().Token = False 63 | 64 | @classmethod 65 | def __Baidu_AI(cls) -> None: 66 | """ 67 | __Baidu_AI() -> None 68 | 百度AI选项(默认:False) 69 | 70 | Returns: None 71 | 72 | """ 73 | baidu_ai = input('是否使用百度AI(y/N):').strip() 74 | if baidu_ai in ['y', 'Y']: 75 | OPTIONS().Baidu_AI = True 76 | BAIDU_AI_MANAGE.Verify() 77 | with open(PATH().Baidu_AI_On, 'w', encoding='utf-8') as f: 78 | f.write("1") 79 | f.close() 80 | else: 81 | OPTIONS().Baidu_AI = False 82 | with open(PATH().Baidu_AI_On, 'w', encoding='utf-8') as f: 83 | f.write("0") 84 | f.close() 85 | 86 | @classmethod 87 | def Init_Options(cls) -> None: 88 | """ 89 | Options() -> None 90 | 选项初始化 91 | 92 | :return: None 93 | """ 94 | cls.__Auto() 95 | cls.__Headless() 96 | cls.__Token() 97 | cls.__Baidu_AI() 98 | 99 | @classmethod 100 | def Task_Options(cls, hint: str = None) -> None: 101 | """ 102 | Task_Options() -> None 103 | 任务选项初始化 104 | 105 | :return: None 106 | """ 107 | OPTIONS().Task_Option_Set_Off_All() 108 | print("可选任务:") 109 | print("0、全选\t", end='') 110 | for key, value in OPTIONS().Task_Options.items(): 111 | print(f"{key}、{value}\t", end='') 112 | print(hint if hint else '') 113 | options = input("选择任务(空为退出):").strip() 114 | if not options: 115 | Quit.Quit() 116 | exit(code=0) 117 | try: 118 | options = set([int(x) for x in options.split()]) 119 | if 0 in options: 120 | OPTIONS().Task_Option_Set_On_All() 121 | return None 122 | if options - set(OPTIONS().Task_Options.keys()): 123 | cls.Task_Options(hint="\n请输入规定的选项序号") 124 | for seq in options: 125 | OPTIONS().Task_Option_Set_On(seq=seq) 126 | except ValueError: 127 | cls.Task_Options(hint="\n请输入数字") 128 | 129 | 130 | _inst = OPTIONS_MANAGE 131 | Init_Options = _inst.Init_Options 132 | -------------------------------------------------------------------------------- /inside/DB/DB_Task.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/15 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : DB_Task.py 8 | # @Function : 任务表操作 9 | from sqlite3 import Connection, Cursor 10 | 11 | from inside.DB.DB_Config import DB_CONFIG 12 | from inside.DB.Table_Class.Task import TASK 13 | from inside.Template.Meta_Singleton import SINGLETON 14 | 15 | __all__ = ['DB_TASK'] 16 | 17 | 18 | class DB_TASK(metaclass=SINGLETON): 19 | """任务表操作类""" 20 | 21 | def __init__(self, connect: Connection, cursor: Cursor): 22 | """ 23 | 初始化 24 | 25 | :param connect: 数据库连接对象 26 | :param cursor: 光标 27 | """ 28 | self.__connect = connect 29 | self.__cursor = cursor 30 | self.__name = DB_CONFIG().Task 31 | self.__fields = DB_CONFIG().Task_Fields 32 | 33 | def Insert(self, task: TASK) -> None: 34 | """ 35 | Insert(task: TASK) -> None 36 | 插入任务, 如任务已存在则不进行操作 37 | 38 | :param task: 任务 39 | :return: None 40 | """ 41 | if not self.Exist(task=task): 42 | sql = f'''INSERT INTO 43 | {self.__name} ( 44 | {self.__fields[1]}, 45 | {self.__fields[2]}) 46 | VALUES (?, ?);''' 47 | data = ( 48 | task.Link, 49 | task.Is_Read_DB 50 | ) 51 | self.__cursor.execute(sql, data) 52 | self.__connect.commit() 53 | 54 | def Update(self, task: TASK) -> None: 55 | """ 56 | Update(task: TASK) -> None 57 | 更新任务为已读, 如任务不存在则不进行操作 58 | 59 | :param task: 任务 60 | :return: None 61 | """ 62 | if self.Exist(task=task): 63 | sql = f'''UPDATE {self.__name} 64 | SET {self.__fields[2]}=1 65 | WHERE {self.__fields[1]}=?''' 66 | data = (task.Link, ) 67 | self.__cursor.execute(sql, data) 68 | self.__connect.commit() 69 | 70 | def Query(self) -> TASK: 71 | """ 72 | Query() -> TASK 73 | 随机获取一条未读任务 74 | 75 | :return: TASK 76 | """ 77 | sql = f'''SELECT * FROM {self.__name} 78 | WHERE {self.__fields[2]}=0 ORDER BY RANDOM() LIMIT 1;''' 79 | result = self.__cursor.execute(sql) 80 | temp = next(result) 81 | task = TASK( 82 | link=temp[1], 83 | isread=temp[2] 84 | ) 85 | return task 86 | 87 | def Exist_Enough(self) -> bool: 88 | """ 89 | Exist_Enough() -> bool 90 | 是否存在未读任务 91 | 92 | :return: bool 93 | """ 94 | sql = f'''SELECT 1 FROM {self.__name} 95 | WHERE {self.__fields[2]}=0 LIMIT 1;''' 96 | result = self.__cursor.execute(sql) 97 | try: 98 | next(result) 99 | return True 100 | except StopIteration: 101 | return False 102 | 103 | def Empty(self) -> bool: 104 | """ 105 | Empty() -> bool 106 | 任务表是否为空 107 | 108 | :return: bool 109 | """ 110 | sql = f'''SELECT 1 FROM {self.__name} LIMIT 1;''' 111 | result = self.__cursor.execute(sql) 112 | try: 113 | next(result) 114 | return False 115 | except StopIteration: 116 | return True 117 | 118 | def Exist(self, task: TASK) -> bool: 119 | """ 120 | Exist(task: TASK) -> bool 121 | 是否存在指定的任务 122 | 123 | :param task: 任务 124 | :return: bool 125 | """ 126 | sql = f'''SELECT 1 FROM {self.__name} 127 | WHERE {self.__fields[1]} =?;''' 128 | data = (task.Link, ) 129 | result = self.__cursor.execute(sql, data) 130 | try: 131 | next(result) 132 | return True 133 | except StopIteration: 134 | return False 135 | -------------------------------------------------------------------------------- /inside/DB/DB_User.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/15 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : DB_User.py 8 | # @Function : 用户表操作 9 | from sqlite3 import Connection, Cursor 10 | 11 | from inside.DB.DB_Config import DB_CONFIG 12 | from inside.DB.Table_Class.User import USER 13 | from inside.Template.Meta_Singleton import SINGLETON 14 | 15 | __all__ = ['DB_USER'] 16 | 17 | 18 | class DB_USER(metaclass=SINGLETON): 19 | """用户表操作类""" 20 | 21 | def __init__(self, connect: Connection, cursor: Cursor): 22 | """ 23 | 初始化 24 | 25 | :param connect: 数据库连接对象 26 | :param cursor: 数据库光标 27 | """ 28 | self.__connect = connect 29 | self.__cursor = cursor 30 | self.__name = DB_CONFIG().User 31 | self.__fields = DB_CONFIG().User_Fields 32 | 33 | def Insert(self, user: USER) -> None: 34 | """ 35 | Insert(user: USER) -> None 36 | 插入用户,插入之前如果存在用户则清空用户表,以保证只有一个用户使用 37 | 38 | :param user: 用户 39 | :return: None 40 | """ 41 | if self.Exist(): 42 | self.Delete() 43 | sql = f'''INSERT INTO 44 | {self.__name} 45 | ({self.__fields[1]}, 46 | {self.__fields[2]}, 47 | {self.__fields[3]}) 48 | VALUES(?, ?, ?);''' 49 | data = ( 50 | user.Id, 51 | user.Token, 52 | user.Time 53 | ) 54 | self.__cursor.execute(sql, data) 55 | self.__connect.commit() 56 | 57 | def Seq_Init(self) -> None: 58 | """ 59 | Seq_Init() -> None 60 | 将用户表的数据序号重置为0 61 | 62 | :return: None 63 | """ 64 | sql = f'''UPDATE sqlite_sequence SET seq=0 WHERE name='{self.__name}';''' 65 | self.__cursor.execute(sql) 66 | self.__connect.commit() 67 | 68 | def Delete(self) -> None: 69 | """ 70 | Delete() -> None 71 | 清空用户表,并重置序号 72 | 73 | :return: None 74 | """ 75 | sql = f'''DELETE FROM {self.__name};''' 76 | self.__cursor.execute(sql) 77 | self.__connect.commit() 78 | self.Seq_Init() 79 | 80 | def Update(self, user: USER) -> None: 81 | """ 82 | Update(user: USER) -> None 83 | 更新指定用户token 84 | 85 | :param user: 用户 86 | :return: None 87 | """ 88 | sql = f'''UPDATE {self.__name} 89 | SET {self.__fields[2]}=? 90 | WHERE {self.__fields[1]} =?;''' 91 | data = ( 92 | user.Token, 93 | user.Id 94 | ) 95 | self.__cursor.execute(sql, data) 96 | self.__connect.commit() 97 | 98 | def Query(self) -> USER: 99 | """ 100 | Query() -> USER 101 | 获取一条用户 102 | 103 | :return: USER 104 | """ 105 | sql = f'''SELECT * FROM {self.__name} LIMIT 1;''' 106 | result = self.__cursor.execute(sql) 107 | temp = next(result) 108 | user = USER( 109 | user_id=temp[1], 110 | token=temp[2] 111 | ) 112 | return user 113 | 114 | def Exist(self) -> bool: 115 | """ 116 | Exist() -> bool 117 | 是否存在用户 118 | 119 | :return: bool 120 | """ 121 | sql = f'''SELECT 1 FROM {self.__name} LIMIT 1;''' 122 | result = self.__cursor.execute(sql) 123 | try: 124 | next(result) 125 | return True 126 | except StopIteration: 127 | return False 128 | 129 | def Exist_User(self, user: USER) -> bool: 130 | """ 131 | Exist(user: USER) -> bool 132 | 是否存在指定用户 133 | 134 | :return: bool 135 | """ 136 | sql = f'''SELECT 1 FROM {self.__name} 137 | WHERE {self.__fields[1]}=? LIMIT 1;''' 138 | data = (user.Id, ) 139 | result = self.__cursor.execute(sql, data) 140 | try: 141 | next(result) 142 | return True 143 | except StopIteration: 144 | return False 145 | -------------------------------------------------------------------------------- /inside/DB/DB_Video.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/15 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : DB_Video.py 8 | # @Function : 视频表操作 9 | from sqlite3 import Connection, Cursor 10 | from typing import List 11 | 12 | from inside.DB.DB_Config import DB_CONFIG 13 | from inside.DB.Table_Class.Video import VIDEO 14 | from inside.Template.Meta_Singleton import SINGLETON 15 | 16 | __all__ = ['DB_VIDEO'] 17 | 18 | 19 | class DB_VIDEO(metaclass=SINGLETON): 20 | """视频表操作类""" 21 | 22 | def __init__(self, connect: Connection, cursor: Cursor): 23 | """ 24 | 初始化 25 | 26 | :param connect: 数据库连接对象 27 | :param cursor: 数据库光标 28 | """ 29 | self.__connect = connect 30 | self.__cursor = cursor 31 | self.__name = DB_CONFIG().Video 32 | self.__fields = DB_CONFIG().Video_Fields 33 | 34 | def Insert(self, video: VIDEO) -> None: 35 | """ 36 | Insert(video: VIDEO) -> None 37 | 插入视频,如视频已存在则不进行操作 38 | 39 | :param video: 视频 40 | :return: None 41 | """ 42 | if not self.Exist(video=video): 43 | sql = f'''INSERT INTO 44 | {self.__name} 45 | ({self.__fields[1]}, 46 | {self.__fields[2]}, 47 | {self.__fields[3]}) 48 | VALUES (?, ?, ?);''' 49 | data = ( 50 | video.Item, 51 | video.Link, 52 | video.Is_Read_DB 53 | ) 54 | self.__cursor.execute(sql, data) 55 | self.__connect.commit() 56 | 57 | def Update(self, video: VIDEO) -> None: 58 | """ 59 | Update(video: VIDEO) -> None 60 | 更新视频为已读,如视频不存在则不进行操作 61 | 62 | :param video: 视频 63 | :return: None 64 | """ 65 | if self.Exist(video=video): 66 | sql = f'''UPDATE {self.__name} 67 | SET {self.__fields[3]}=1 68 | WHERE {self.__fields[1]}=? 69 | AND {self.__fields[2]}=?;''' 70 | data = ( 71 | video.Item, 72 | video.Link 73 | ) 74 | self.__cursor.execute(sql, data) 75 | self.__connect.commit() 76 | 77 | def Update_All(self) -> None: 78 | """ 79 | Update_All() -> None 80 | 更新所有视频为未读 81 | 82 | :return: None 83 | """ 84 | sql = f'''UPDATE {self.__name} 85 | SET {self.__fields[3]}=0;''' 86 | self.__cursor.execute(sql) 87 | self.__connect.commit() 88 | 89 | def Query(self, limit: int) -> List[VIDEO]: 90 | """ 91 | Query(limit: int) -> List[VIDEO] 92 | 随机获取指定数量的未读视频 93 | 94 | :param limit: 数量 95 | :return: List[VIDEO] 96 | """ 97 | sql = f'''SELECT * FROM {self.__name} 98 | WHERE {self.__fields[3]}=0 ORDER BY RANDOM() LIMIT ?;''' 99 | data = (limit, ) 100 | result = self.__cursor.execute(sql, data) 101 | temp = [] 102 | for value in result: 103 | video = VIDEO( 104 | item=value[1], 105 | link=value[2], 106 | isread=value[3] 107 | ) 108 | temp.append(video) 109 | return temp 110 | 111 | def Exist_Enough(self, limit: int) -> bool: 112 | """ 113 | Exist_Enough(limit: int) -> bool 114 | 是否存在指定数量的未读视频 115 | 116 | :param limit: 数量 117 | :return: bool 118 | """ 119 | sql = f'''SELECT count() FROM {self.__name} 120 | WHERE {self.__fields[3]}=0 LIMIT ?;''' 121 | data = (limit, ) 122 | result = self.__cursor.execute(sql, data) 123 | temp = next(result) 124 | return temp[0] >= limit 125 | 126 | def Exist(self, video: VIDEO) -> bool: 127 | """ 128 | Exist(video: VIDEO) -> bool 129 | 是否存在指定的视频 130 | 131 | :param video: 视频 132 | :return: bool 133 | """ 134 | sql = f'''SELECT 1 FROM {self.__name} 135 | WHERE {self.__fields[1]}=? 136 | AND {self.__fields[2]}=?;''' 137 | data = ( 138 | video.Item, 139 | video.Link 140 | ) 141 | result = self.__cursor.execute(sql, data) 142 | try: 143 | next(result) 144 | return True 145 | except StopIteration: 146 | return False 147 | -------------------------------------------------------------------------------- /inside/Tools/Network.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/20 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Network.py 8 | # @Function : 网络日志 9 | import json 10 | import psutil 11 | import gc 12 | from _queue import Empty 13 | from queue import Queue 14 | from threading import Thread 15 | from typing import Dict 16 | 17 | from selenium.webdriver.chrome.webdriver import WebDriver 18 | 19 | __all__ = ['NETWORK'] 20 | 21 | from inside.Template.Meta_Singleton import SINGLETON 22 | 23 | 24 | class NETWORK(metaclass=SINGLETON): 25 | """network日志类,实现原理为队列+线程""" 26 | 27 | __task: Thread 28 | __driver: WebDriver 29 | 30 | def __init__(self): 31 | """ 32 | NETWORK() 33 | 初始化,队列 34 | 35 | """ 36 | self.__self = '_'+type(self).__name__ 37 | self.__queue = Queue(maxsize=0) 38 | self.__on = True 39 | 40 | def Init(self, driver: WebDriver): 41 | if not hasattr(self, self.__self+'__driver'): 42 | self.__driver = driver 43 | self.__task = Thread(target=self.__Get_Log) 44 | self.__task.start() 45 | else: 46 | print('已初始化') 47 | 48 | def __Get_Log(self) -> None: 49 | """ 50 | __Get_Log() -> None 51 | 获取有效日志,并加入队列,结束条件为开关 52 | 53 | :return: None 54 | """ 55 | while True: 56 | if not self.__on: 57 | break 58 | logs = self.__driver.get_log(log_type='performance') 59 | for log in logs: 60 | log['message'] = json.loads(log['message']) 61 | try: 62 | url = log['message']['message']['params']['request']['url'] 63 | requestId = log['message']['message']['params']['requestId'] 64 | while psutil.virtual_memory().used / psutil.virtual_memory().total >= 0.95: 65 | gc.collect() 66 | continue 67 | self.__queue.put({url: requestId}) 68 | except KeyError: 69 | continue 70 | 71 | def __On_Logs(self) -> None: 72 | """ 73 | __On_Logs() -> None 74 | 开启日志 75 | :return: None 76 | """ 77 | if not self.__task.is_alive(): 78 | self.__task = Thread(target=self.__Get_Log) 79 | self.__task.start() 80 | 81 | def Get(self) -> Dict: 82 | """ 83 | Get() -> Dict 84 | 获取日志 85 | 86 | :return:Dict 87 | 格式为{url: requestId} 88 | """ 89 | try: 90 | return self.__queue.get_nowait() 91 | except Empty: 92 | return {} 93 | 94 | def GetResponseBody(self, requestId: str) -> Dict: 95 | """ 96 | GetResponseBody(requestId: str) -> Dict 97 | 获取指定请求Id的响应信息,考虑到网络延迟,可能会触发WebDriverException错误, 98 | 建议使用try/except对同一requestId进行循环获取 99 | 100 | :param requestId: str 101 | :return: Dict 102 | 格式为:{'base64Encoded': bool, 'body': str} 103 | """ 104 | body = self.__driver.execute_cdp_cmd( 105 | cmd='Network.getResponseBody', 106 | cmd_args={'requestId': requestId} 107 | ) 108 | return body 109 | 110 | def Clear(self) -> None: 111 | """ 112 | Clear() -> None 113 | 清空缓存日志 114 | 115 | :return: None 116 | """ 117 | self.__queue.queue.clear() 118 | 119 | @property 120 | def On(self) -> bool: 121 | """ 122 | On -> bool 123 | 查看开关 124 | 125 | :return: bool 126 | """ 127 | return self.__on 128 | 129 | @On.setter 130 | def On(self, on: bool) -> None: 131 | """ 132 | On -> bool 133 | 设置开关 134 | 135 | :param on: bool 136 | :return: None 137 | """ 138 | self.__on = on 139 | if self.__on: 140 | self.__On_Logs() 141 | 142 | @property 143 | def Is_Alive(self) -> bool: 144 | """ 145 | Is_Alive -> bool 146 | 线程是否存活 147 | 148 | :return: bool 149 | """ 150 | if hasattr(self, self.__self+'__task'): 151 | return self.__task.is_alive() 152 | return False 153 | 154 | def Quit(self) -> None: 155 | """ 156 | Quit() -> None 157 | 退出 158 | 159 | :return: None 160 | """ 161 | self.__on = False 162 | -------------------------------------------------------------------------------- /inside/DB/DB_Config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/15 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : DB_Config.py 8 | # @Function : 数据库配置文件(此配置文件不可更改) 9 | from inside.Template.Meta_Singleton import SINGLETON 10 | 11 | __all__ = ['DB_CONFIG'] 12 | 13 | 14 | class DB_CONFIG(metaclass=SINGLETON): 15 | """ 16 | 数据库配置类 17 | """ 18 | 19 | @property 20 | def User(self) -> str: 21 | """ 22 | User -> str 23 | 用户表名 24 | 25 | :return: str 26 | """ 27 | return 'user' 28 | 29 | @property 30 | def User_Fields(self) -> tuple: 31 | """ 32 | User_Fields -> tuple 33 | 用户表字段 34 | 35 | :return: tuple 36 | """ 37 | return 'ID', 'USERID', 'TOKEN', 'TIME' 38 | 39 | @property 40 | def User_Fields_Types(self) -> tuple: 41 | """ 42 | User_Fields_Types -> tuple 43 | 用户表字段类型 44 | 45 | :return: tuple 46 | """ 47 | return 'INTEGER', 'INTEGER', 'TEXT', 'TEXT' 48 | 49 | @property 50 | def Task(self) -> str: 51 | """ 52 | Task -> str 53 | 任务表名 54 | 55 | :return: str 56 | """ 57 | return 'task' 58 | 59 | @property 60 | def Task_Fields(self) -> tuple: 61 | """ 62 | Task_Fields -> tuple 63 | 任务表字段 64 | 65 | :return: tuple 66 | """ 67 | return 'ID', 'LINK', 'ISREAD' 68 | 69 | @property 70 | def Task_Fields_Types(self) -> tuple: 71 | """ 72 | Task_Fields_Types -> tuple 73 | 任务表字段类型 74 | 75 | :return: tuple 76 | """ 77 | return 'INTEGER', 'TEXT', 'INTEGER' 78 | 79 | @property 80 | def Article(self) -> str: 81 | """ 82 | Article -> str 83 | 文章表名 84 | 85 | :return: str 86 | """ 87 | return 'article' 88 | 89 | @property 90 | def Article_Fields(self) -> tuple: 91 | """ 92 | Article_Fields -> tuple 93 | 文章表字段 94 | 95 | :return: tuple 96 | """ 97 | return 'ID', 'ITEM', 'LINK', 'ISREAD' 98 | 99 | @property 100 | def Article_Fields_Types(self) -> tuple: 101 | """ 102 | Article_Fields_Types -> tuple 103 | 文章表字段类型 104 | 105 | :return: tuple 106 | """ 107 | return 'INTEGER', 'TEXT', 'TEXT', 'INTEGER' 108 | 109 | @property 110 | def Video(self) -> str: 111 | """ 112 | Video -> str 113 | 视频表名 114 | 115 | :return: str 116 | """ 117 | return 'video' 118 | 119 | @property 120 | def Video_Fields(self) -> tuple: 121 | """ 122 | Video_Fields -> tuple 123 | 视频表字段 124 | 125 | :return: tuple 126 | """ 127 | return 'ID', 'ITEM', 'LINK', 'ISREAD' 128 | 129 | @property 130 | def Video_Fields_Types(self) -> tuple: 131 | """ 132 | Video_Fields_Types -> tuple 133 | 视频表字段类型 134 | 135 | :return: tuple 136 | """ 137 | return 'INTEGER', 'TEXT', 'TEXT', 'INTEGER' 138 | 139 | @property 140 | def Project(self) -> str: 141 | """ 142 | Project -> str 143 | 专项答题表名 144 | 145 | :return: str 146 | """ 147 | return 'project' 148 | 149 | @property 150 | def Project_Fields(self) -> tuple: 151 | """ 152 | Project -> tuple 153 | 专项答题表字段 154 | 155 | :return:tuple 156 | """ 157 | return 'ID', 'PID' 158 | 159 | @property 160 | def Project_Fields_Types(self) -> tuple: 161 | """ 162 | Project_Fields_Types -> tuple 163 | 专项答题表字段类型 164 | 165 | :return: tuple 166 | """ 167 | return 'INTEGER', 'INTEGER' 168 | 169 | @property 170 | def Baidu_AI(self) -> str: 171 | """ 172 | Baidu_AI -> str 173 | 百度AI表名 174 | 175 | @return: str 176 | """ 177 | return 'baidu_ai' 178 | 179 | @property 180 | def Baidu_AI_Fields(self) -> tuple: 181 | """ 182 | Baidu_AI_Fields -> tuple 183 | 百度AI表字段名 184 | 185 | :return: tuple 186 | """ 187 | return 'ID', 'AK', 'SK' 188 | 189 | @property 190 | def Baidu_AI_Fields_Types(self) -> tuple: 191 | """ 192 | Baidu_AI_Fields_Types -> tuple 193 | 百度AI表字段类型 194 | 195 | :return: tuple 196 | """ 197 | return 'INTEGER', 'TEXT', 'TEXT' 198 | -------------------------------------------------------------------------------- /inside/DB/DB_Create.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/15 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : DB_Create.py 8 | # @Function : 数据库表创建 9 | from sqlite3 import Connection, Cursor 10 | 11 | from inside.DB.DB_Config import DB_CONFIG 12 | from inside.Template.Meta_Singleton import SINGLETON 13 | 14 | __all__ = ['DB_CREATE'] 15 | 16 | 17 | class DB_CREATE(metaclass=SINGLETON): 18 | """数据库表创建操作类""" 19 | 20 | def __init__(self, connect: Connection, cursor: Cursor): 21 | """ 22 | DB_CREATE(connect: Connection, cursor: Cursor) 23 | 初始化 24 | 25 | :param connect: 数据库连接对象 26 | :param cursor: 光标 27 | """ 28 | self.__connect = connect 29 | self.__cursor = cursor 30 | 31 | def User(self) -> None: 32 | """ 33 | User() -> None 34 | 用户表创建 35 | 36 | :return: None 37 | """ 38 | name = DB_CONFIG().User 39 | fields = DB_CONFIG().User_Fields 40 | fields_types = DB_CONFIG().User_Fields_Types 41 | sql = f'''CREATE TABLE {name} 42 | ({fields[0]} {fields_types[0]} PRIMARY KEY AUTOINCREMENT, 43 | {fields[1]} {fields_types[1]} NOT NULL, 44 | {fields[2]} {fields_types[2]} NOT NULL, 45 | {fields[3]} {fields_types[3]} NOT NULL 46 | );''' 47 | self.__cursor.execute(sql) 48 | self.__connect.commit() 49 | 50 | def Task(self) -> None: 51 | """ 52 | Task() -> None 53 | 任务表创建 54 | 55 | :return: None 56 | """ 57 | name = DB_CONFIG().Task 58 | fields = DB_CONFIG().Task_Fields 59 | fields_types = DB_CONFIG().Task_Fields_Types 60 | sql = f'''CREATE TABLE {name} 61 | ({fields[0]} {fields_types[0]} PRIMARY KEY AUTOINCREMENT, 62 | {fields[1]} {fields_types[1]} NOT NULL, 63 | {fields[2]} {fields_types[2]} NOT NULL 64 | );''' 65 | self.__cursor.execute(sql) 66 | self.__connect.commit() 67 | 68 | def Article(self) -> None: 69 | """ 70 | Article() -> None 71 | 文章表创建 72 | 73 | :return: None 74 | """ 75 | name = DB_CONFIG().Article 76 | fields = DB_CONFIG().Article_Fields 77 | fields_types = DB_CONFIG().Article_Fields_Types 78 | sql = f'''CREATE TABLE {name} 79 | ({fields[0]} {fields_types[0]} PRIMARY KEY AUTOINCREMENT, 80 | {fields[1]} {fields_types[1]} NOT NULL, 81 | {fields[2]} {fields_types[2]} NOT NULL, 82 | {fields[3]} {fields_types[3]} NOT NULL 83 | );''' 84 | self.__cursor.execute(sql) 85 | self.__connect.commit() 86 | 87 | def Video(self) -> None: 88 | """ 89 | Video() -> None 90 | 视频表创建 91 | 92 | :return: None 93 | """ 94 | name = DB_CONFIG().Video 95 | fields = DB_CONFIG().Video_Fields 96 | fields_types = DB_CONFIG().Video_Fields_Types 97 | sql = f'''CREATE TABLE {name} 98 | ({fields[0]} {fields_types[0]} PRIMARY KEY AUTOINCREMENT, 99 | {fields[1]} {fields_types[1]} NOT NULL, 100 | {fields[2]} {fields_types[2]} NOT NULL, 101 | {fields[3]} {fields_types[3]} NOT NULL 102 | );''' 103 | self.__cursor.execute(sql) 104 | self.__connect.commit() 105 | 106 | def Project(self) -> None: 107 | """ 108 | Project() -> None 109 | 专项答题表创建 110 | 111 | :return: None 112 | """ 113 | name = DB_CONFIG().Project 114 | fields = DB_CONFIG().Project_Fields 115 | fields_types = DB_CONFIG().Project_Fields_Types 116 | sql = f'''CREATE TABLE {name} 117 | ({fields[0]} {fields_types[0]} PRIMARY KEY AUTOINCREMENT, 118 | {fields[1]} {fields_types[1]} NOT NULL 119 | );''' 120 | self.__cursor.execute(sql) 121 | self.__connect.commit() 122 | 123 | def Baidu_AI(self) -> None: 124 | """ 125 | Baidu_AI() -> None 126 | 百度AI表创建 127 | 128 | :return: None 129 | """ 130 | name = DB_CONFIG().Baidu_AI 131 | fields = DB_CONFIG().Baidu_AI_Fields 132 | fields_types = DB_CONFIG().Baidu_AI_Fields_Types 133 | sql = f'''CREATE TABLE {name} 134 | ({fields[0]} {fields_types[0]} PRIMARY KEY AUTOINCREMENT, 135 | {fields[1]} {fields_types[1]} NOT NULL, 136 | {fields[2]} {fields_types[2]} NOT NULL 137 | );''' 138 | self.__cursor.execute(sql) 139 | self.__connect.commit() -------------------------------------------------------------------------------- /inside/DB/DB_Article.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/15 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : DB_Article.py 8 | # @Function : 文章表操作 9 | from sqlite3 import Connection, Cursor 10 | from typing import List 11 | 12 | from inside.DB.DB_Config import DB_CONFIG 13 | from inside.DB.Table_Class.Article import ARTICLE 14 | from inside.Template.Meta_Singleton import SINGLETON 15 | 16 | __all__ = ['DB_ARTICLE'] 17 | 18 | 19 | class DB_ARTICLE(metaclass=SINGLETON): 20 | """文章表操作类""" 21 | 22 | def __init__(self, connect: Connection, cursor: Cursor): 23 | """ 24 | 初始化 25 | 26 | :param connect: 数据库连接对象 27 | :param cursor: 数据库光标 28 | """ 29 | self.__connect = connect 30 | self.__cursor = cursor 31 | self.__name = DB_CONFIG().Article 32 | self.__fields = DB_CONFIG().Article_Fields 33 | 34 | def Insert(self, article: ARTICLE) -> None: 35 | """ 36 | Insert(article: ARTICLE) -> None 37 | 插入文章,如文章已存在则不进行操作 38 | 39 | :param article: 文章 40 | :return: None 41 | """ 42 | if not self.Exist(article=article): 43 | sql = f'''INSERT INTO 44 | {self.__name} ( 45 | {self.__fields[1]}, 46 | {self.__fields[2]}, 47 | {self.__fields[3]}) 48 | VALUES (?, ?, ?);''' 49 | data = ( 50 | article.Item, 51 | article.Link, 52 | article.Is_Read_DB 53 | ) 54 | self.__cursor.execute(sql, data) 55 | self.__connect.commit() 56 | 57 | def Update(self, article: ARTICLE) -> None: 58 | """ 59 | Update(article: ARTICLE) -> None 60 | 更新文章为已读,如文章不存在则不进行操作 61 | 62 | :param article: 文章 63 | :return: None 64 | """ 65 | if self.Exist(article=article): 66 | sql = f'''UPDATE {self.__name} 67 | SET {self.__fields[3]}=1 68 | WHERE {self.__fields[1]}=? 69 | AND {self.__fields[2]}=?;''' 70 | data = ( 71 | article.Item, 72 | article.Link 73 | ) 74 | self.__cursor.execute(sql, data) 75 | self.__connect.commit() 76 | 77 | def Update_All(self) -> None: 78 | """ 79 | Update_All() -> None 80 | 更新所有文章为未读 81 | 82 | :return: None 83 | """ 84 | sql = f'''UPDATE {self.__name} 85 | SET {self.__fields[3]}=0;''' 86 | self.__cursor.execute(sql) 87 | self.__connect.commit() 88 | 89 | def Query(self, limit: int) -> List[ARTICLE]: 90 | """ 91 | Query(limit: int) -> List[ARTICLE] 92 | 随机获取指定数量的未读文章 93 | 94 | :param limit: 数量 95 | :return: List[ARTICLE] 96 | """ 97 | sql = f'''SELECT * FROM {self.__name} 98 | WHERE {self.__fields[3]}=0 ORDER BY random() LIMIT ?;''' 99 | data = (limit, ) 100 | result = self.__cursor.execute(sql, data) 101 | temp = [] 102 | for value in result: 103 | article = ARTICLE( 104 | item=value[1], 105 | link=value[2], 106 | isread=value[3] 107 | ) 108 | temp.append(article) 109 | return temp 110 | 111 | def Exist_Enough(self, limit: int) -> bool: 112 | """ 113 | Exist_Enough(limit: int) -> bool 114 | 是否存在指定数量的未读文章 115 | 116 | :param limit: 数量 117 | :return: bool 118 | """ 119 | sql = f'''SELECT count() FROM {self.__name} 120 | WHERE {self.__fields[3]}=0 LIMIT ?;''' 121 | data = (limit, ) 122 | result = self.__cursor.execute(sql, data) 123 | temp = next(result) 124 | return temp[0] >= limit 125 | 126 | def Exist(self, article: ARTICLE) -> bool: 127 | """ 128 | Exist(article: ARTICLE) -> bool 129 | 是否存在指定的文章 130 | 131 | :param article: 文章 132 | :return: bool 133 | """ 134 | sql = f'''SELECT 1 FROM {self.__name} 135 | WHERE {self.__fields[1]}=? 136 | AND {self.__fields[2]}=?;''' 137 | data = ( 138 | article.Item, 139 | article.Link 140 | ) 141 | result = self.__cursor.execute(sql, data) 142 | try: 143 | next(result) 144 | return True 145 | except StopIteration: 146 | return False 147 | -------------------------------------------------------------------------------- /inside/Info/Get_Info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/23 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Get_Info.py 8 | # @Function : 获取用户信息 9 | from inside.Config.Api import API 10 | from inside.Info.Task_Info import TASK_INFO 11 | from inside.Info.User_Info import USER_INFO 12 | from inside.Template.Meta_Singleton import SINGLETON 13 | from inside.Tools.Requests import REQUESTS 14 | __all__ = ['GET_INFO'] 15 | 16 | 17 | class GET_INFO(metaclass=SINGLETON): 18 | """获取信息类""" 19 | 20 | def __init__(self, token: str): 21 | """ 22 | GET_INFO(token: str) 23 | 初始化 24 | 25 | :param token: 令牌 26 | """ 27 | self.__cookie = {'token': token} 28 | self.__info = USER_INFO() 29 | self._Init_Info() 30 | 31 | def _Init_Info(self) -> None: 32 | """ 33 | _Init_Info() -> None 34 | 初始化用户id 35 | 36 | :return: None 37 | """ 38 | while True: 39 | try: 40 | html = REQUESTS().Get( 41 | url=API().Aggregate_Score.geturl(), 42 | cookies=self.__cookie 43 | ) 44 | data = html.json() 45 | self.__info.User_Id = data['data']['userId'] 46 | break 47 | except TypeError: 48 | continue 49 | 50 | def Get_Aggregate_Score(self) -> None: 51 | """ 52 | Get_Aggregate_Score() -> None 53 | 获取总积分 54 | 55 | :return: None 56 | """ 57 | while True: 58 | try: 59 | html = REQUESTS().Get( 60 | url=API().Aggregate_Score.geturl(), 61 | cookies=self.__cookie 62 | ) 63 | data = html.json() 64 | self.__info.Aggregate_Score = data['data']['score'] 65 | break 66 | except TypeError: 67 | continue 68 | 69 | def Get_Level(self) -> None: 70 | """ 71 | Get_Level() -> None 72 | 获取等级 73 | 74 | :return: None 75 | """ 76 | while True: 77 | try: 78 | html = REQUESTS().Get( 79 | url=API().Level.geturl(), 80 | cookies=self.__cookie 81 | ) 82 | data = html.json() 83 | self.__info.Level = data['data']['level'] 84 | self.__info.Level_Name = data['data']['levelName'] 85 | self.__info.Rank_Accumulate_In_Country = \ 86 | data['data']['rankAccumulateInCountry'] 87 | break 88 | except TypeError: 89 | continue 90 | 91 | def Get_Daily_Score(self) -> None: 92 | """ 93 | Get_Daily_Score() -> None 94 | 获取每日积分 95 | 96 | :return: None 97 | """ 98 | while True: 99 | try: 100 | html = REQUESTS().Get( 101 | url=API().Daily_Score.geturl(), 102 | cookies=self.__cookie 103 | ) 104 | data = html.json() 105 | self.__info.Daily_Score = data['data']['score'] 106 | break 107 | except TypeError: 108 | continue 109 | 110 | def Get_Task_Bar(self) -> None: 111 | """ 112 | Get_Task_Bar() -> None 113 | 获取任务进度 114 | 115 | :return: None 116 | """ 117 | while True: 118 | try: 119 | html = REQUESTS().Get( 120 | url=API().Task_Bar.geturl(), 121 | cookies=self.__cookie 122 | ) 123 | data = html.json() 124 | for rule in data['data']['dayScoreDtos']: 125 | task = self.__info.Task_Bar.get(rule['ruleId']) 126 | if not task: 127 | ruleId = rule['ruleId'] 128 | name = rule['name'] 129 | desc = rule['desc'] 130 | currentScore = rule['currentScore'] 131 | dayMaxScore = rule['dayMaxScore'] 132 | task = TASK_INFO( 133 | ruleId=ruleId, name=name, desc=desc, 134 | currentScore=currentScore, dayMaxScore=dayMaxScore 135 | ) 136 | else: 137 | task.Current_Score = rule['currentScore'] 138 | self.__info.Update_Task_Bar_Info(task_info=task) 139 | break 140 | except TypeError: 141 | continue 142 | -------------------------------------------------------------------------------- /inside/Driver/Driver_Init.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/16 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Driver_Init.py 8 | # @Function : 驱动初始化 9 | from selenium.webdriver import Chrome 10 | from selenium.webdriver.chrome.webdriver import WebDriver 11 | 12 | from inside.Options.Options import OPTIONS 13 | from inside.Config.Path import PATH 14 | from inside.Config.User_Agent import USER_AGENT 15 | from inside.Template.Meta_Singleton import SINGLETON 16 | 17 | __all__ = ['DRIVER_INIT'] 18 | 19 | 20 | class DRIVER_INIT(metaclass=SINGLETON): 21 | """驱动初始化类""" 22 | __instances = {} 23 | 24 | def __init__(self): 25 | """初始化驱动配置文件""" 26 | self.__config = { 27 | 'browserName': 'chrome', 28 | # network日志相关 29 | 'loggingPrefs': 30 | { 31 | 'browser': 'ALL', 32 | 'driver': 'ALL', 33 | 'performance': 'ALL' 34 | }, 35 | 'goog:chromeOptions': 36 | { 37 | 'prefs': 38 | { 39 | # 默认下载目录 40 | "download.default_directory": PATH().Temp, 41 | # 自动下载 42 | "download.prompt_for_download": False, 43 | # 无图模式 44 | "profile.managed_default_content_settings.images": 2 45 | }, 46 | 'excludeSwitches': [ 47 | # 不显示日志 48 | 'enable-logging', 49 | # 规避检测 50 | 'enable-automation' 51 | ], 52 | 'args': 53 | [ 54 | # 浏览器标识 55 | '--user-agent=' + USER_AGENT().User_Agent, 56 | # 本地代理 57 | '--proxy-server=127.0.0.1:8080', 58 | # 忽略证书问题 59 | '--ignore-certificate-errors' 60 | ], 61 | 'perfLoggingPrefs': 62 | { 63 | # 开启network日志 64 | 'enableNetwork': True 65 | }, 66 | 'w3c': False 67 | } 68 | } 69 | self.__Check_Options() 70 | 71 | def __Check_Options(self) -> None: 72 | """ 73 | __Check_Options() -> None 74 | 检查选项,根据选项初始化配置文件 75 | 76 | :return: None 77 | """ 78 | temp = [] 79 | if OPTIONS().Mute_Audio: 80 | self.__config['goog:chromeOptions']['args'].append("--mute-audio") 81 | if OPTIONS().Headless: 82 | self.__config['goog:chromeOptions']['args'].append("--headless") 83 | 84 | @property 85 | def Task_Driver(self) -> WebDriver: 86 | """ 87 | Task_Driver -> WebDriver 88 | 任务浏览器,根据配置文件生成 89 | 90 | :return: WebDriver 91 | """ 92 | if not self.__instances.get('Task'): 93 | self.__instances['Task'] = Chrome( 94 | desired_capabilities=self.__config, 95 | executable_path=PATH().Driver_File 96 | ) 97 | return self.__instances['Task'] 98 | 99 | @property 100 | def Task_Quit(self) -> str: 101 | """ 102 | Task_Quit -> str 103 | 任务浏览器退出 104 | 105 | :return: str 106 | Success: 退出成功 107 | Nonexistence: 不存在任务浏览器 108 | """ 109 | if self.__instances.get('Task'): 110 | self.__instances['Task'].quit() 111 | self.__instances.pop('Task') 112 | return 'Success' 113 | return 'Nonexistence' 114 | 115 | @property 116 | def QR_Driver(self) -> WebDriver: 117 | """ 118 | Task_Driver -> WebDriver 119 | 二维码浏览器,根据默认配置文件生成 120 | 121 | :return: WebDriver 122 | """ 123 | if not self.__instances.get('QR'): 124 | self.__instances['QR'] = Chrome( 125 | executable_path=PATH().Driver_File 126 | ) 127 | self.__instances['QR'].set_window_size(width=50, height=350) 128 | return self.__instances['QR'] 129 | 130 | @property 131 | def QR_Quit(self) -> str: 132 | """ 133 | Task_Quit -> str 134 | 二维码浏览器退出 135 | 136 | :return: str 137 | Success: 退出成功 138 | Nonexistence: 不存在任务浏览器 139 | """ 140 | if self.__instances.get('QR'): 141 | self.__instances['QR'].quit() 142 | self.__instances.pop('QR') 143 | return 'Success' 144 | return 'Nonexistence' 145 | -------------------------------------------------------------------------------- /inside/Options/Options.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/16 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Options_Manage.py 8 | # @Function : 选项 9 | from typing import Dict 10 | 11 | from inside.Template.Meta_Singleton import SINGLETON 12 | 13 | __all__ = ['OPTIONS'] 14 | 15 | 16 | class OPTIONS(metaclass=SINGLETON): 17 | """选项类""" 18 | 19 | def __init__(self): 20 | """ 21 | OPTIONS() 22 | 初始化,默认选项 23 | 24 | """ 25 | self.__mute_audio = True 26 | self.__headless = True 27 | self.__token = True 28 | self.__baidu_ai = False 29 | self.__task_options = { 30 | 1: ['文章', False], 31 | 2: ['视频', False], 32 | 3: ['每日答题', False], 33 | 4: ['每周答题', False], 34 | 5: ['专项答题', False] 35 | } 36 | 37 | @property 38 | def Mute_Audio(self) -> bool: 39 | """ 40 | Mute_Audio -> bool 41 | 获取禁音选项 42 | 43 | :return: bool 44 | """ 45 | return self.__mute_audio 46 | 47 | @Mute_Audio.setter 48 | def Mute_Audio(self, on: bool) -> None: 49 | """ 50 | Mute_Audio -> None 51 | 设置禁音选项 52 | 使用方法:Mute_Audio = True or False 53 | 54 | :param on: 开关 55 | :return: None 56 | """ 57 | self.__mute_audio = on 58 | 59 | @property 60 | def Headless(self) -> bool: 61 | """ 62 | Headless -> bool 63 | 获取无窗口化选项 64 | 65 | :return: bool 66 | """ 67 | return self.__headless 68 | 69 | @Headless.setter 70 | def Headless(self, on: bool) -> None: 71 | """ 72 | Headless -> None 73 | 设置无窗口化选项 74 | 使用方法:Headless = True or False 75 | 76 | :param on: 开关 77 | :return: None 78 | """ 79 | self.__headless = on 80 | 81 | @property 82 | def Token(self) -> bool: 83 | """ 84 | Token -> bool 85 | 获取持久化访问选项 86 | 87 | :return: bool 88 | """ 89 | return self.__token 90 | 91 | @Token.setter 92 | def Token(self, on: bool) -> None: 93 | """ 94 | Token -> None 95 | 设置持久化访问选项 96 | 使用方法:Token = True or False 97 | 98 | :param on: 开关 99 | :return: None 100 | """ 101 | self.__token = on 102 | 103 | @property 104 | def Task_Options(self) -> Dict[int, str]: 105 | """ 106 | Task_Options -> Dict[int, str] 107 | 任务选项,不暴露是否选择情况 108 | 109 | :return: Dict[int, str] 110 | """ 111 | temp = {} 112 | for key, value in self.__task_options.items(): 113 | temp[key] = value[0] 114 | return temp 115 | 116 | def Task_Option_Set_On(self, seq: int) -> None: 117 | """ 118 | Task_Option_Set_On(seq: int) -> None 119 | 打开指定任务选项 120 | 121 | :param seq: 任务序号 122 | :return: None 123 | """ 124 | self.__task_options[seq][-1] = True 125 | 126 | def Task_Option_Set_On_All(self) -> None: 127 | """ 128 | Task_Option_Set_On_All() -> None 129 | 任务选项全选 130 | 131 | :return: None 132 | """ 133 | for key in self.__task_options.keys(): 134 | self.__task_options[key][-1] = True 135 | 136 | def Task_Option_Set_Off_All(self) -> None: 137 | """ 138 | Task_Option_Set_Off_All() -> None 139 | 任务选项初始化 140 | 141 | :return: None 142 | """ 143 | for key in self.__task_options.keys(): 144 | self.__task_options[key][-1] = False 145 | 146 | @property 147 | def Article(self) -> bool: 148 | """ 149 | Article -> bool 150 | 文章选项 151 | 152 | :return: bool 153 | """ 154 | return self.__task_options[1][-1] 155 | 156 | @property 157 | def Video(self) -> bool: 158 | """ 159 | Video -> bool 160 | 视频选项 161 | 162 | :return: bool 163 | """ 164 | return self.__task_options[2][-1] 165 | 166 | @property 167 | def Daily_Answer(self) -> bool: 168 | """ 169 | Daily_Answer -> bool 170 | 每日答题选项 171 | 172 | :return: bool 173 | """ 174 | return self.__task_options[3][-1] 175 | 176 | @property 177 | def Weekly_Answer(self) -> bool: 178 | """ 179 | Weekly_Answer -> bool 180 | 每周答题选项 181 | 182 | :return: bool 183 | """ 184 | return self.__task_options[4][-1] 185 | 186 | @property 187 | def Project_Answer(self) -> bool: 188 | """ 189 | Project_Answer -> bool 190 | 专项答题选项 191 | 192 | :return: bool 193 | """ 194 | return self.__task_options[5][-1] 195 | 196 | @property 197 | def Baidu_AI(self) -> bool: 198 | """ 199 | Baidu_AI -> bool 200 | 百度AI选项 201 | 202 | Returns: bool 203 | 204 | """ 205 | return self.__baidu_ai 206 | 207 | @Baidu_AI.setter 208 | def Baidu_AI(self, on: bool) -> None: 209 | """ 210 | Baidu_AI = on: bool 211 | 设置百度AI选项 212 | 213 | Args: 214 | on: bool 215 | 216 | Returns: None 217 | 218 | """ 219 | self.__baidu_ai = on 220 | -------------------------------------------------------------------------------- /inside/Info/User_Info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/22 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : User_Info.py 8 | # @Function : 用户信息 9 | from typing import Dict 10 | 11 | from inside.Info.Task_Info import TASK_INFO 12 | from inside.Template.Meta_Singleton import SINGLETON 13 | 14 | __all__ = ['USER_INFO'] 15 | 16 | 17 | class USER_INFO(metaclass=SINGLETON): 18 | """用户信息类""" 19 | __token: str 20 | __user_id: int 21 | __aggregate_score: float 22 | __daily_score: float 23 | __level: int 24 | __level_name: str 25 | __rank_accumulate_in_country: int 26 | 27 | __task_bar: Dict[int, TASK_INFO] 28 | 29 | def __init__(self): 30 | """ 31 | USER_INFO() 32 | 初始化 33 | 34 | """ 35 | self.__self = '_'+type(self).__name__ 36 | 37 | @property 38 | def User_Id(self) -> int: 39 | """ 40 | User_Id -> int 41 | 用户ID 42 | 43 | :return: int 44 | """ 45 | if hasattr(self, self.__self+'__user_id'): 46 | return self.__user_id 47 | return -1 48 | 49 | @User_Id.setter 50 | def User_Id(self, user_id: int) -> None: 51 | """ 52 | User_Id -> None 53 | 设置用户ID 54 | 55 | :param user_id: ID 56 | :return: None 57 | """ 58 | if not hasattr(self, self.__self+'__user_id'): 59 | self.__user_id = user_id 60 | 61 | @property 62 | def Aggregate_Score(self) -> float: 63 | """ 64 | Aggregate_Score -> float 65 | 总积分 66 | 67 | :return: float 68 | """ 69 | if hasattr(self, self.__self+'__aggregate_score'): 70 | return self.__aggregate_score 71 | return -1 72 | 73 | @Aggregate_Score.setter 74 | def Aggregate_Score(self, aggregate_score: float) -> None: 75 | """ 76 | Aggregate_Score -> None 77 | 设置总积分 78 | 79 | :param aggregate_score: 总积分 80 | :return: None 81 | """ 82 | self.__aggregate_score = aggregate_score 83 | 84 | @property 85 | def Daily_Score(self) -> float: 86 | """ 87 | Daily_Score -> float 88 | 每日积分 89 | 90 | :return: float 91 | """ 92 | if hasattr(self, self.__self+'__daily_score'): 93 | return self.__daily_score 94 | return -1 95 | 96 | @Daily_Score.setter 97 | def Daily_Score(self, daily_score: float) -> None: 98 | """ 99 | Daily_Score -> None 100 | 设置每日积分 101 | 102 | :param daily_score: 每日积分 103 | :return: None 104 | """ 105 | self.__daily_score = daily_score 106 | 107 | @property 108 | def Level(self) -> int: 109 | """ 110 | Level -> int 111 | 等级 112 | 113 | :return: int 114 | """ 115 | if hasattr(self, self.__self+'__level'): 116 | return self.__level 117 | return -1 118 | 119 | @Level.setter 120 | def Level(self, level: int) -> None: 121 | """ 122 | Level -> None 123 | 设置等级 124 | 125 | :param level: 等级 126 | :return: None 127 | """ 128 | self.__level = level 129 | 130 | @property 131 | def Level_Name(self) -> str: 132 | """ 133 | Level_Name -> str 134 | 段位 135 | 136 | :return: str 137 | """ 138 | if hasattr(self, self.__self+'__level_name'): 139 | return self.__level_name 140 | return '' 141 | 142 | @Level_Name.setter 143 | def Level_Name(self, level_name: str) -> None: 144 | """ 145 | Level_Name -> None 146 | 设置段位 147 | 148 | :param level_name: 段位 149 | :return: None 150 | """ 151 | self.__level_name = level_name 152 | 153 | @property 154 | def Rank_Accumulate_In_Country(self) -> int: 155 | """ 156 | Rank_Accumulate_In_Country -> int 157 | 全国排名 158 | 159 | :return: int 160 | """ 161 | if hasattr(self, self.__self+'__rank_accumulate_in_country'): 162 | return self.__rank_accumulate_in_country 163 | return -1 164 | 165 | @Rank_Accumulate_In_Country.setter 166 | def Rank_Accumulate_In_Country(self, rank_accumulate_in_country: int) -> None: 167 | """ 168 | Rank_Accumulate_In_Country -> None 169 | 设置全国排名 170 | 171 | :param rank_accumulate_in_country: 全国排名 172 | :return: None 173 | """ 174 | self.__rank_accumulate_in_country = rank_accumulate_in_country 175 | 176 | @property 177 | def Task_Bar(self) -> Dict[int, TASK_INFO]: 178 | """ 179 | Task_Bar -> Dict[int, TASK_INFO] 180 | 任务进度 181 | 182 | :return: Dict[int, TASK_INFO] 183 | """ 184 | if hasattr(self, self.__self+'__task_bar'): 185 | return self.__task_bar 186 | else: 187 | return {} 188 | 189 | def Update_Task_Bar_Info(self, task_info: TASK_INFO) -> None: 190 | """ 191 | Update_Task_Bar_Info(task_info: TASK_INFO) -> None 192 | 更新任务进度 193 | 194 | :param task_info: 任务类 195 | :return: None 196 | """ 197 | if not hasattr(self, self.__self+'__task_bar'): 198 | self.__task_bar = {task_info.Rule_Id: task_info} 199 | else: 200 | self.__task_bar[task_info.Rule_Id] = task_info 201 | -------------------------------------------------------------------------------- /inside/Config/SystemArgs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/15 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : SystemArgs.py 8 | # @Function : 系统参数 9 | from inside.Template.ABC_System_Args import SYSTEM_ARGS 10 | 11 | __all__ = ['WINDOWS', 'LINUX', 'MACOS'] 12 | 13 | 14 | class WINDOWS(SYSTEM_ARGS): 15 | """Windows操作系统类""" 16 | 17 | def __init__(self): 18 | """ 19 | WINDOWS() 20 | 初始化 21 | 22 | """ 23 | import winreg 24 | self.__winreg = winreg 25 | self.__winreg_key = self.__winreg.OpenKey( 26 | key=self.__winreg.HKEY_CURRENT_USER, 27 | sub_key=r'Software\Google\Chrome\BLBeacon') 28 | 29 | @property 30 | def System(self) -> str: 31 | """ 32 | System -> str 33 | 操作系统 34 | 35 | :return: str 36 | """ 37 | return 'Windowns' 38 | 39 | @property 40 | def Clear(self) -> str: 41 | """ 42 | Clear -> str 43 | 清空控制台命令 44 | 45 | :return: str 46 | """ 47 | return 'cls' 48 | 49 | @property 50 | def Driver(self) -> str: 51 | """ 52 | Driver -> str 53 | 驱动文件名称 54 | 55 | :return: str 56 | """ 57 | return 'chromedriver.exe' 58 | 59 | @property 60 | def Driver_Chmod(self) -> str: 61 | """ 62 | Driver_Chmod -> str 63 | 添加驱动文件执行权限命令 64 | 65 | :return: str 66 | """ 67 | return '' 68 | 69 | @property 70 | def Chrome(self) -> bool: 71 | """ 72 | Chrome -> bool 73 | 谷歌浏览器是否安装 74 | 75 | :return: bool 76 | """ 77 | try: 78 | return isinstance(self.Chrome_Version, str) 79 | except FileNotFoundError: 80 | return False 81 | 82 | @property 83 | def Chrome_Version(self) -> str: 84 | """ 85 | Chrome_Version -> str 86 | 谷歌浏览器版本号 87 | 88 | :return: str 89 | """ 90 | return self.__winreg.QueryValueEx( 91 | self.__winreg_key, 92 | 'version' 93 | )[0] 94 | 95 | 96 | class LINUX(SYSTEM_ARGS): 97 | """Linux操作系统类""" 98 | 99 | def __init__(self): 100 | """ 101 | LINUX() 102 | 初始化 103 | 104 | """ 105 | import os 106 | self.__os = os 107 | 108 | @property 109 | def System(self) -> str: 110 | """ 111 | System -> str 112 | 操作系统 113 | 114 | :return: str 115 | """ 116 | return 'Linux' 117 | 118 | @property 119 | def Clear(self) -> str: 120 | """ 121 | Clear -> str 122 | 清空控制台命令 123 | 124 | :return: str 125 | """ 126 | return 'clear' 127 | 128 | @property 129 | def Driver(self) -> str: 130 | """ 131 | Driver -> str 132 | 驱动文件名称 133 | 134 | :return: str 135 | """ 136 | return 'chromedriver' 137 | 138 | @property 139 | def Driver_Chmod(self) -> str: 140 | """ 141 | Driver_Chmod -> str 142 | 添加驱动文件执行权限命令 143 | 144 | :return: str 145 | """ 146 | return 'chmod +x ' 147 | 148 | @property 149 | def Chrome(self) -> bool: 150 | """ 151 | Chrome -> bool 152 | 谷歌浏览器是否安装 153 | 154 | :return: bool 155 | """ 156 | try: 157 | return isinstance(self.Chrome_Version, str) 158 | except IndexError: 159 | return False 160 | 161 | @property 162 | def Chrome_Version(self) -> str: 163 | """ 164 | Chrome_Version -> str 165 | 谷歌浏览器版本号 166 | 167 | :return: str 168 | """ 169 | version = self.__os.popen(cmd='google-chrome --version').readline() 170 | return version.strip().split()[-1] 171 | 172 | 173 | class MACOS(SYSTEM_ARGS): 174 | """MacOs操作系统类""" 175 | 176 | def __init__(self): 177 | """ 178 | MACOS() 179 | 初始化 180 | 181 | """ 182 | import os 183 | self.__os = os 184 | 185 | @property 186 | def System(self) -> str: 187 | """ 188 | System -> str 189 | 操作系统 190 | 191 | :return: str 192 | """ 193 | return 'macOs' 194 | 195 | @property 196 | def Clear(self) -> str: 197 | """ 198 | Clear -> str 199 | 清空控制台命令 200 | 201 | :return: str 202 | """ 203 | return 'clear' 204 | 205 | @property 206 | def Driver(self) -> str: 207 | """ 208 | Driver -> str 209 | 驱动文件名称 210 | 211 | :return: str 212 | """ 213 | return 'chromedriver' 214 | 215 | @property 216 | def Driver_Chmod(self) -> str: 217 | """ 218 | Driver_Chmod -> str 219 | 添加驱动文件执行权限命令 220 | 221 | :return: str 222 | """ 223 | return 'chmod +x ' 224 | 225 | @property 226 | def Chrome(self) -> bool: 227 | """ 228 | Chrome -> bool 229 | 谷歌浏览器是否安装 230 | 231 | :return: bool 232 | """ 233 | try: 234 | return isinstance(self.Chrome_Version, str) 235 | except IndexError: 236 | return False 237 | 238 | @property 239 | def Chrome_Version(self) -> str: 240 | """ 241 | Chrome_Version -> str 242 | 谷歌浏览器版本号 243 | 244 | :return: str 245 | """ 246 | version = self.__os.popen(cmd=r'/Applications/Google\ ' 247 | r'Chrome.app/Contents/MacOS/Google\ ' 248 | r'Chrome --version').readline() 249 | return version.strip().split()[-1] 250 | -------------------------------------------------------------------------------- /inside/Task/Task_Init.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/24 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Task_Init.py 8 | # @Function : 任务初始化 9 | import base64 10 | import json 11 | from json.decoder import JSONDecodeError 12 | from typing import List 13 | 14 | from inside.Config.Api import API 15 | from inside.DB.DB_Manage import DB_MANAGE 16 | from inside.DB.Table_Class.Article import ARTICLE 17 | from inside.DB.Table_Class.Project import PROJECT 18 | from inside.DB.Table_Class.Task import TASK 19 | from inside.DB.Table_Class.Video import VIDEO 20 | from inside.Template.Meta_Singleton import SINGLETON 21 | from inside.Tools.Requests import REQUESTS 22 | 23 | __all__ = ['TASK_INIT'] 24 | 25 | 26 | class TASK_INIT(metaclass=SINGLETON): 27 | """任务初始化类""" 28 | 29 | def __init__(self): 30 | """ 31 | TASK_INIT() 32 | 初始化 33 | 34 | """ 35 | self.__Init_Task() 36 | 37 | @classmethod 38 | def __Init_Task(cls) -> None: 39 | """ 40 | 任务表初始化(不为空的时候) 41 | 42 | :return: None 43 | """ 44 | if DB_MANAGE().Task.Empty(): 45 | html = REQUESTS.Get(url=API().Task_Parent.geturl()) 46 | parent = html.json() 47 | for key in parent.keys(): 48 | son = API().Task_Son.geturl().format(channel_id=key) 49 | task = TASK(link=son, isread=False) 50 | DB_MANAGE().Task.Insert(task=task) 51 | 52 | @classmethod 53 | def Init_Article_Video(cls) -> None: 54 | """ 55 | Init_Article_Video() -> None 56 | 从任务表中取出任务,进行解析并按类别填入文章表或视频表 57 | 58 | :return: None 59 | """ 60 | if DB_MANAGE().Task.Exist_Enough(): 61 | print("解析任务集中") 62 | task = DB_MANAGE().Task.Query() 63 | html = REQUESTS.Get(url=task.Link) 64 | DB_MANAGE().Task.Update(task=task) 65 | try: 66 | for temp in html.json(): 67 | if temp['type'] == 'tuwen': 68 | print("解析为文章任务集") 69 | article = ARTICLE( 70 | item=temp['itemId'], 71 | link=temp['url'], 72 | isread=False 73 | ) 74 | DB_MANAGE().Article.Insert(article=article) 75 | elif temp['type'] == 'shipin': 76 | print("解析为视频任务集") 77 | video = VIDEO( 78 | item=temp['itemId'], 79 | link=temp['url'], 80 | isread=False 81 | ) 82 | DB_MANAGE().Video.Insert(video=video) 83 | else: 84 | print("解析为其他任务集") 85 | except JSONDecodeError: 86 | print("解析为干扰项") 87 | cls.Init_Article_Video() 88 | 89 | def Assigning_Article(self, num: int) -> List[ARTICLE]: 90 | """ 91 | Assigning_Article(num: int) -> List[ARTICLE] 92 | 获取指定数量的文章任务 93 | 94 | :param num: 任务数 95 | :return: List[ARTICLE] 96 | """ 97 | print("正在分发文章任务") 98 | if DB_MANAGE().Article.Exist_Enough(limit=num): 99 | return DB_MANAGE().Article.Query(limit=num) 100 | else: 101 | print("检测到文章任务数不足,自动获取任务集") 102 | self.Init_Article_Video() 103 | return self.Assigning_Article(num=num) 104 | 105 | def Assigning_Video(self, num: int) -> List[VIDEO]: 106 | """ 107 | Assigning_Video(num: int) -> List[VIDEO] 108 | 获取指定数量的视频任务 109 | 110 | :param num: 任务数 111 | :return: List[VIDEO] 112 | """ 113 | print("正在分发视频任务") 114 | if DB_MANAGE().Video.Exist_Enough(limit=num): 115 | return DB_MANAGE().Video.Query(limit=num) 116 | else: 117 | print("检测到视频任务数不足,自动获取任务集") 118 | self.Init_Article_Video() 119 | return self.Assigning_Video(num=num) 120 | 121 | def Assigning_Weekly_Answer(self, token: str) -> int: 122 | """ 123 | Assigning_Weekly_Answer(token: str) -> int 124 | 获取本周最早且未做的每周答题任务ID 125 | 126 | :param token: 令牌 127 | :return: int 128 | """ 129 | cookie = {'token': token} 130 | for num in range(3, 0, -1): 131 | html = REQUESTS.Get( 132 | url=API().Weekly_Answer_Topics.geturl().format(num=num), 133 | cookies=cookie 134 | ) 135 | data = html.json() 136 | data = json.loads(base64.b64decode(data['data_str']).decode('utf-8')) 137 | for group in data['list'][::-1]: 138 | for practice in group['practices'][::-1]: 139 | if not practice['seeSolution']: 140 | return practice['id'] 141 | 142 | def Assigning_Project_Answer(self, token: str) -> int: 143 | """ 144 | Assigning_Project_Answer(token: str) -> int 145 | 获取最早且未过期的专项答题任务ID 146 | 147 | :param token: 令牌 148 | :return: int 149 | """ 150 | cookie = {'token': token} 151 | for num in range(5, 0, -1): 152 | html = REQUESTS.Get( 153 | url=API().Project_Answer_Topics.geturl().format(num=num), 154 | cookies=cookie 155 | ) 156 | data = html.json() 157 | data = json.loads(base64.b64decode(data['data_str']).decode('utf-8')) 158 | for topic in data['list'][::-1]: 159 | if (not topic['overdue']) and (not topic['seeSolution']): 160 | if not DB_MANAGE().Project.Exist(project=PROJECT(pid=topic['id'])): 161 | return topic['id'] 162 | -------------------------------------------------------------------------------- /inside/Task/Mitmdump/Intercept/Project.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/2/6 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Project.py 8 | # @Function : 9 | import base64 10 | import json 11 | import re 12 | from random import randint 13 | from typing import Dict, List 14 | 15 | from inside.Task.Mitmdump.Intercept.ABC_Answer import ABC_ANSWER 16 | 17 | from inside.Baidu_AI.Baidu_AI_Manage import BAIDU_AI_MANAGE 18 | from inside.Config.Path import PATH 19 | __all__ = ['PROJECT'] 20 | 21 | 22 | class PROJECT(ABC_ANSWER): 23 | """专项答题拦截体""" 24 | 25 | def Url_Put(self, url: str) -> bool: 26 | """ 27 | Url_Put(url: str) -> bool 28 | 提交判断 29 | 30 | :param url: url 31 | :return: bool 32 | """ 33 | return "https://pc-proxy-api.xuexi.cn/api/exam/service/detail/submitV3" \ 34 | == url 35 | 36 | def Url_Res(self, url: str) -> bool: 37 | """ 38 | Url_Res(url: str) -> bool 39 | 答案判断 40 | 41 | :param url: url 42 | :return: bool 43 | """ 44 | res = "https://pc-proxy-api.xuexi.cn/api/exam/service/detail" \ 45 | "/queryV3\\?type=1&id=.*&forced=true" 46 | if re.match(res, url): 47 | return True 48 | return False 49 | 50 | def Extract(self, data: bytes) -> Dict: 51 | """ 52 | Extract(data: bytes) -> Dict 53 | 提取答案 54 | 55 | :param data: 包含答案的字节数据 56 | :return: Dict 57 | """ 58 | data = data.decode('utf-8') 59 | data = json.loads(data)['data_str'] 60 | return json.loads(base64.b64decode(data).decode('utf-8')) 61 | 62 | def __Answer(self, res: Dict) -> List[Dict]: 63 | """ 64 | __Answer(res: Dict) -> List[Dict] 65 | 由于专项答题没有答案,判题是在云端,所以需要分析答案 66 | 67 | :param res: 包含提示的题目 68 | :return: List[Dict] 69 | """ 70 | res_type = res['questionDisplay'] 71 | temp = [] 72 | options = re.findall(r".*?", 73 | res['questionDesc']) 74 | for index, value in enumerate(options): 75 | options[index] = value.split('>')[1].split('<')[0] 76 | if res_type in (1, 2): 77 | for option in options: 78 | for answer in res['answers']: 79 | if option in answer['content'] or answer['content'] in option: 80 | tp = { 81 | 'answerId': answer['answerId'], 82 | 'value': answer['label'] 83 | } 84 | if tp not in temp: 85 | temp.append(tp) 86 | if not temp: 87 | for answer in res['answers']: 88 | if answer['content'] in res['questionDesc']: 89 | tp = { 90 | 'answerId': answer['answerId'], 91 | 'value': answer['label'] 92 | } 93 | if tp not in temp: 94 | temp.append(tp) 95 | elif res_type == 4: 96 | with open(PATH().Baidu_AI_On, 'r', encoding='utf-8') as f: 97 | on = f.read() 98 | f.close() 99 | if on == '1': 100 | if res['questionDesc'] == '请观看视频': 101 | body = res['body'] 102 | value = BAIDU_AI_MANAGE.Tools().Answer(video_link=res['videoUrl']) 103 | print('检测到视频题目,由于专项答题答案匹配目前还未完善,所以需要手动填入答案') 104 | print(f"题目部分:{body[body.index('()')-len(value):]}") 105 | print(f"答案为:{value}") 106 | print("请从答案中提取个数与()个数一致的答案,以空格分隔,注意顺序") 107 | while True: 108 | daan = input(":").strip().split() 109 | check = [ck for ck in daan if ck in value] 110 | if len(check) != len(daan) or not daan: 111 | print("输入非答案,重新输入!") 112 | continue 113 | break 114 | for index, answer in enumerate(res['answers']): 115 | temp.append({ 116 | 'answerId': answer['answerId'], 117 | 'value': daan[index] 118 | }) 119 | else: 120 | num = res['body'].count('()') 121 | if num == 1: 122 | tp = '' 123 | for option in options: 124 | tp += option 125 | temp.append( 126 | { 127 | 'answerId': res['answers'][0]['answerId'], 128 | 'value': tp 129 | } 130 | ) 131 | else: 132 | for index, option in enumerate(options): 133 | temp.append( 134 | { 135 | 'answerId': res['answers'][index]['answerId'], 136 | 'value': option 137 | } 138 | ) 139 | return temp 140 | 141 | def Inject(self, data: bytes, res: Dict) -> bytes: 142 | """ 143 | Inject(data: bytes, res: Dict) -> bytes 144 | 注入答案 145 | 146 | :param data: 原始提交数据 147 | :param res: 包含答案数据 148 | :return: 修改后的提交数据 149 | """ 150 | data = json.loads(data.decode('utf-8')) 151 | data['uniqueId'] = res['uniqueId'] 152 | data['questions'].clear() 153 | data['usedTime'] = randint(20, 50) 154 | for question in res['questions']: 155 | data['questions'].append( 156 | { 157 | 'questionId': question['questionId'], 158 | 'answers': self.__Answer(res=question) 159 | } 160 | ) 161 | return json.dumps(data).encode('utf-8') -------------------------------------------------------------------------------- /inside/Task/Task_Answer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/25 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Task_Answer.py 8 | # @Function : 答题任务 9 | import re 10 | import time 11 | 12 | from tqdm import tqdm 13 | from typing import List 14 | 15 | from selenium.common.exceptions import StaleElementReferenceException, \ 16 | ElementClickInterceptedException, TimeoutException, \ 17 | NoAlertPresentException, NoSuchElementException 18 | from selenium.webdriver.chrome.webdriver import WebDriver 19 | 20 | from inside.Config.Api import API 21 | from inside.Template.Meta_Singleton import SINGLETON 22 | from inside.Template.Task_Exception import TASK_EXCEPTION 23 | from inside.Tools.Network import NETWORK 24 | 25 | from selenium.webdriver.remote.webelement import WebElement 26 | from selenium.webdriver.support import expected_conditions as EC 27 | from selenium.webdriver.support.wait import WebDriverWait 28 | from selenium.webdriver.common.by import By 29 | 30 | __all__ = ['TASK_ANSWER'] 31 | 32 | 33 | class TASK_ANSWER(metaclass=SINGLETON): 34 | """答题任务类""" 35 | 36 | def __init__(self, driver: WebDriver): 37 | """ 38 | TASK_ANSWER(driver: WebDriver) 39 | 初始化 40 | 41 | :param driver: 驱动 42 | """ 43 | self.__driver = driver 44 | self.__network = NETWORK() 45 | self.__wait = WebDriverWait(self.__driver, 10) 46 | 47 | def Topic_Type(self) -> str: 48 | """ 49 | Topic_Type() -> str 50 | 题目类型 51 | 52 | :return: str 53 | """ 54 | topic_type_ec = EC.presence_of_element_located( 55 | ( 56 | By.CLASS_NAME, 'q-header' 57 | ) 58 | ) 59 | topic_type: WebElement = self.__wait.until(topic_type_ec) 60 | return topic_type.text.strip() 61 | 62 | def Topic_Seq(self) -> str: 63 | """ 64 | Topic_Seq() -> str 65 | 题目序号,返回'x/x' 66 | 67 | :return: str 68 | """ 69 | topic_seq_ec = EC.presence_of_element_located( 70 | ( 71 | By.CLASS_NAME, 'pager' 72 | ) 73 | ) 74 | topic_seq: WebElement = self.__wait.until(topic_seq_ec) 75 | return topic_seq.text.strip() 76 | 77 | def Topic_Input(self) -> List[WebElement]: 78 | """ 79 | Topic_Input() -> WebElement 80 | 填空题输入框 81 | 82 | :return: WebElement 83 | """ 84 | topic_input_ec = EC.presence_of_all_elements_located( 85 | ( 86 | By.TAG_NAME, 'input' 87 | ) 88 | ) 89 | return self.__wait.until(topic_input_ec) 90 | 91 | def Topic_Options(self) -> List[WebElement]: 92 | """ 93 | Topic_Options() -> List[WebElement] 94 | 选择题选项 95 | 96 | :return: List[WebElement] 97 | """ 98 | topic_options_ec = EC.presence_of_element_located( 99 | ( 100 | By.CLASS_NAME, 'q-answers' 101 | ) 102 | ) 103 | topic_options: WebElement = self.__wait.until(topic_options_ec) 104 | return topic_options.find_elements_by_tag_name(name='div') 105 | 106 | def Topic_Submit(self) -> WebElement: 107 | """ 108 | Topic_Submit() -> WebElement 109 | 题目提交按钮 110 | 111 | :return: WebElement 112 | """ 113 | topic_submits_ec = EC.presence_of_element_located( 114 | ( 115 | By.CLASS_NAME, 'action-row' 116 | ) 117 | ) 118 | topic_submits: WebElement = self.__wait.until(topic_submits_ec) 119 | submits = topic_submits.find_elements_by_tag_name(name='button') 120 | for submit in submits: 121 | if submit.is_enabled(): 122 | return submit 123 | 124 | def __Accomplish(self) -> bool: 125 | """ 126 | __Accomplish() -> bool 127 | 检测答题是否完成 128 | 129 | :return: bool 130 | """ 131 | while True: 132 | for key, value in self.__network.Get().items(): 133 | if re.match( 134 | pattern=API().Answer_Accomplish.geturl(), 135 | string=key 136 | ): 137 | return True 138 | 139 | def __Error(self) -> bool: 140 | """ 141 | __Error() -> bool 142 | 检测是否有弹窗 143 | 144 | :return: bool 145 | """ 146 | try: 147 | time.sleep(1) 148 | self.__driver.find_element_by_class_name( 149 | name='ant-modal-content') 150 | return True 151 | except NoSuchElementException: 152 | return False 153 | 154 | def __Alert(self) -> None: 155 | """ 156 | __Alert() -> None 157 | 检测弹窗,并点击确认 158 | 159 | :return: None 160 | """ 161 | try: 162 | self.__driver.switch_to.alert.accept() 163 | except NoAlertPresentException: 164 | pass 165 | 166 | def __Do(self) -> None: 167 | """ 168 | __Do() -> None 169 | 做一个题目 170 | 171 | :return: None 172 | """ 173 | topic_type = self.Topic_Type() 174 | if '填空题' in topic_type: 175 | for answer in self.Topic_Input(): 176 | answer.send_keys('1') 177 | elif [x for x in ('单选题', '多选题') if x in topic_type]: 178 | for answer in self.Topic_Options(): 179 | while answer.get_attribute(name='class') == 'q-answer choosable': 180 | answer.click() 181 | time.sleep(0.1) 182 | topic_seq = self.Topic_Seq() 183 | while self.Topic_Seq() == topic_seq: 184 | topic_submit = self.Topic_Submit() 185 | topic_submit.click() 186 | if self.__Error(): 187 | raise TASK_EXCEPTION('失败') 188 | 189 | def Do(self, link: str) -> None: 190 | """ 191 | Do(link: str) -> None 192 | 答题 193 | 194 | :param link: 题目链接 195 | :return: None 196 | """ 197 | self.__network.Clear() 198 | self.__driver.get(url=link) 199 | self.__Alert() 200 | seq = int(self.Topic_Seq().split('/')[-1]) 201 | bar = tqdm( 202 | desc='答题', 203 | total=seq, 204 | unit='题', 205 | leave=False, 206 | ncols=70 207 | ) 208 | while True: 209 | try: 210 | self.__Do() 211 | bar.update(n=1) 212 | except (StaleElementReferenceException, 213 | ElementClickInterceptedException, 214 | TimeoutException): 215 | self.__Accomplish() 216 | bar.update(n=1) 217 | time.sleep(0.1) 218 | break 219 | bar.close() 220 | -------------------------------------------------------------------------------- /inside/Task/Task_Manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/25 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Task_Manage.py 8 | # @Function : 任务管理器 9 | import time 10 | 11 | from tqdm import tqdm 12 | from selenium.webdriver.chrome.webdriver import WebDriver 13 | 14 | from inside.Config.Api import API 15 | from inside.DB.DB_Manage import DB_MANAGE 16 | from inside.DB.Table_Class.Project import PROJECT 17 | from inside.Info.Info_Manage import INFO_MANAGE 18 | from inside.Options.Options import OPTIONS 19 | from inside.Task.Task_Answer import TASK_ANSWER 20 | from inside.Task.Task_Article_Video import TASK_ARTICLE_VIDEO 21 | from inside.Task.Task_Init import TASK_INIT 22 | from inside.Template.Meta_Singleton import SINGLETON 23 | from inside.Template.Task_Exception import TASK_EXCEPTION 24 | from inside.Tools.Output import OUTPUT 25 | 26 | __all__ = ['TASK_MANAGE'] 27 | 28 | 29 | class TASK_MANAGE(metaclass=SINGLETON): 30 | """任务管理类""" 31 | 32 | def __init__(self, driver: WebDriver): 33 | """ 34 | TASK_MANAGE(driver: WebDriver) 35 | 初始化 36 | 37 | :param driver: 驱动 38 | """ 39 | self.__driver = driver 40 | self.__answer_time = (time.time(), False) 41 | 42 | def __Article(self, num: int, tq: int) -> None: 43 | """ 44 | __Article(num: int) -> None 45 | 进行文章任务 46 | 47 | :param num: 任务数量 48 | :return: None 49 | """ 50 | temp = TASK_ARTICLE_VIDEO(task_driver=self.__driver) 51 | tasks = TASK_INIT().Assigning_Article(num=num) 52 | bar = tqdm( 53 | desc='文章', 54 | total=num, 55 | unit='it', 56 | leave=False, 57 | ncols=70 58 | ) 59 | for task in tasks: 60 | temp.Do(task=task, tq=tq) 61 | DB_MANAGE().Article.Update(article=task) 62 | bar.update(n=1) 63 | OUTPUT.Info() 64 | bar.close() 65 | 66 | def __Video(self, num: int, tq: int) -> None: 67 | """ 68 | __Video(num: int) -> None 69 | 进行视频任务 70 | 71 | :param num: 任务数量 72 | :return: None 73 | """ 74 | temp = TASK_ARTICLE_VIDEO(task_driver=self.__driver) 75 | tasks = TASK_INIT().Assigning_Video(num=num) 76 | bar = tqdm( 77 | desc='视频', 78 | total=num, 79 | unit='it', 80 | leave=False, 81 | ncols=70 82 | ) 83 | for task in tasks: 84 | temp.Do(task=task, tq=tq) 85 | DB_MANAGE().Video.Update(video=task) 86 | bar.update(n=1) 87 | OUTPUT.Info() 88 | bar.close() 89 | 90 | def __Check_Article(self) -> None: 91 | """ 92 | __Check_Article() -> None 93 | 监测文章任务的完成 94 | 95 | :return: None 96 | """ 97 | while True: 98 | bar = INFO_MANAGE().Task_Bar 99 | p = bar[1].Day_Max_Score - bar[1].Current_Score 100 | c = bar[1002].Day_Max_Score - bar[1002].Current_Score 101 | if p and not c: 102 | self.__Article(num=p, tq=1) 103 | elif p and c: 104 | self.__Article(num=p, tq=(c*4)//p) 105 | elif not p and c: 106 | self.__Article(num=1, tq=c*4) 107 | else: 108 | break 109 | 110 | def __Check_Video(self) -> None: 111 | """ 112 | __Check_Video() -> None 113 | 监测视频任务的完成 114 | 115 | :return: None 116 | """ 117 | while True: 118 | bar = INFO_MANAGE().Task_Bar 119 | p = bar[2].Day_Max_Score - bar[2].Current_Score 120 | c = bar[1003].Day_Max_Score - bar[1003].Current_Score 121 | if p and not c: 122 | self.__Video(num=p, tq=1) 123 | elif p and c: 124 | self.__Video(num=p, tq=(c*4)//p) 125 | elif not p and c: 126 | self.__Video(num=1, tq=c*4) 127 | else: 128 | break 129 | 130 | def __Check_Daily_Answer(self) -> None: 131 | """ 132 | __Check_Daily_Answer() -> None 133 | 监测每日答题任务的完成 134 | 135 | :return: None 136 | """ 137 | while True: 138 | bar = INFO_MANAGE().Task_Bar 139 | if bar[6].Current_Score != bar[6].Day_Max_Score: 140 | if self.__answer_time[-1]: 141 | if time.time() - self.__answer_time[0] <= 10: 142 | continue 143 | temp = TASK_ANSWER(driver=self.__driver) 144 | temp.Do(link=API().Daily_Answer.geturl()) 145 | OUTPUT.Info() 146 | self.__answer_time = (time.time(), True) 147 | else: 148 | break 149 | 150 | def __Check_Weekly_Answer(self) -> None: 151 | """ 152 | __Check_Weekly_Answer() -> None 153 | 监测每周答题任务的完成 154 | 155 | :return: None 156 | """ 157 | while True: 158 | bar = INFO_MANAGE().Task_Bar 159 | if bar[5].Current_Score != bar[5].Day_Max_Score: 160 | if self.__answer_time[-1]: 161 | if time.time() - self.__answer_time[0] <= 10: 162 | continue 163 | token = self.__driver.get_cookie(name='token')['value'] 164 | iid = TASK_INIT().Assigning_Weekly_Answer(token=token) 165 | if not iid: 166 | print("没有每周答题任务了") 167 | break 168 | temp = TASK_ANSWER(driver=self.__driver) 169 | temp.Do(link=API().Weekly_Answer_Topic.geturl().format(num=iid)) 170 | OUTPUT.Info() 171 | self.__answer_time = (time.time(), True) 172 | else: 173 | break 174 | 175 | def __Check_Project_Answer(self) -> None: 176 | """ 177 | __Check_Project_Answer() -> None 178 | 监测专项答题任务的完成 179 | 180 | :return: None 181 | """ 182 | while True: 183 | bar = INFO_MANAGE().Task_Bar 184 | if bar[4].Current_Score != bar[4].Day_Max_Score: 185 | if self.__answer_time[-1]: 186 | if time.time() - self.__answer_time[0] <= 10: 187 | continue 188 | token = self.__driver.get_cookie(name='token')['value'] 189 | iid = TASK_INIT().Assigning_Project_Answer(token=token) 190 | if not iid: 191 | print("没有专项答题任务了") 192 | break 193 | temp = TASK_ANSWER(driver=self.__driver) 194 | try: 195 | temp.Do(link=API().Project_Answer_Topic.geturl().format(num=iid)) 196 | except TASK_EXCEPTION: 197 | DB_MANAGE().Project.Insert(PROJECT(pid=iid)) 198 | OUTPUT.Info() 199 | self.__answer_time = (time.time(), True) 200 | else: 201 | break 202 | 203 | def Task(self) -> None: 204 | """ 205 | Task() -> None 206 | 根据选项执行相应的任务 207 | 208 | :return: None 209 | """ 210 | if OPTIONS().Article: 211 | self.__Check_Article() 212 | if OPTIONS().Video: 213 | self.__Check_Video() 214 | if OPTIONS().Daily_Answer: 215 | self.__Check_Daily_Answer() 216 | if OPTIONS().Weekly_Answer: 217 | self.__Check_Weekly_Answer() 218 | if OPTIONS().Project_Answer: 219 | self.__Check_Project_Answer() 220 | -------------------------------------------------------------------------------- /inside/Driver/Driver_Analysis.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/17 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Driver_Analysis.py 8 | # @Function : 驱动解析 9 | import requests 10 | from urllib.parse import urlparse, ParseResult 11 | from bs4 import BeautifulSoup, ResultSet 12 | 13 | from inside.Config.System import SYSTEM 14 | from inside.Template.ABC_Driver_Analysis import DRIVER_ANALYSIS 15 | __all__ = ['GOOGLEAPIS_DRIVER_ANALYSIS', 'TAOBAO_DRIVER_ANALYSIS'] 16 | 17 | 18 | class GOOGLEAPIS_DRIVER_ANALYSIS(DRIVER_ANALYSIS): 19 | """驱动官网下载类""" 20 | 21 | @property 22 | def Master(self) -> ParseResult: 23 | """ 24 | Master -> ParseResult 25 | 官网 26 | 27 | :return: ParseResult 28 | """ 29 | return urlparse( 30 | url="http://chromedriver.storage.googleapis.com/index.html" 31 | ) 32 | 33 | def __Master(self) -> ParseResult: 34 | """ 35 | __Master() -> ParseResult 36 | 官网xml列表 37 | 38 | :return: ParseResult 39 | """ 40 | return urlparse( 41 | url="http://chromedriver.storage.googleapis.com/?delimiter" 42 | "=/&prefix=" 43 | ) 44 | 45 | def __Definite(self) -> ParseResult: 46 | """ 47 | __Definite() -> ParseResult 48 | 具体版本xml列表 49 | 50 | :return: ParseResult 51 | """ 52 | return urlparse( 53 | url="http://chromedriver.storage.googleapis.com/?delimiter=/&prefix={version}/" 54 | ) 55 | 56 | def __Download_Link(self) -> ParseResult: 57 | """ 58 | __Download_Link() -> ParseResult 59 | 驱动下载链接 60 | 61 | :return: ParseResult 62 | """ 63 | return urlparse( 64 | url="http://chromedriver.storage.googleapis.com/{path}" 65 | ) 66 | 67 | def __All_Driver(self) -> ResultSet: 68 | """ 69 | __All_Driver() -> ResultSet 70 | 所有版本xml列表 71 | 72 | :return: ResultSet 73 | """ 74 | html = requests.get(url=self.__Master().geturl()) 75 | for retry in range(3): 76 | if html.status_code == 200: 77 | break 78 | html = requests.get(url=self.__Master().geturl()) 79 | html.encoding = html.apparent_encoding 80 | soup = BeautifulSoup(html.text, 'lxml') 81 | return soup.select(selector='CommonPrefixes') 82 | 83 | def __Filtrate_Version(self, chrome_version: str) -> str: 84 | """ 85 | __Filtrate_Version(chrome_version: str) -> str 86 | 匹配驱动版本 87 | 88 | :param chrome_version: 谷歌浏览器版本号 89 | :return: str 90 | """ 91 | res = [] 92 | chrome_version = chrome_version.split('.')[:-1] 93 | for commonprefixes in self.__All_Driver(): 94 | temp = commonprefixes.text 95 | if temp.split('.')[:-1] == chrome_version: 96 | res.append(temp.strip('/')) 97 | return max(res, key=lambda x: int(x.split('.')[-1])) 98 | 99 | def __Select_Driver(self, driver_version: str) -> ResultSet: 100 | """ 101 | __Select_Driver(driver_version: str) -> ResultSet 102 | 获取具体版本驱动xml列表 103 | 104 | :param driver_version: 驱动版本号 105 | :return: ResultSet 106 | """ 107 | html = requests.get( 108 | url=self.__Definite().geturl().format(version=driver_version) 109 | ) 110 | for retry in range(3): 111 | if html.status_code == 200: 112 | break 113 | html = requests.get( 114 | url=self.__Definite().geturl().format(version=driver_version) 115 | ) 116 | html.encoding = html.apparent_encoding 117 | soup = BeautifulSoup(html.text, 'lxml') 118 | return soup.select(selector='Contents') 119 | 120 | def __Filtrate_Driver(self, system: SYSTEM, soups: ResultSet) -> str: 121 | """ 122 | __Filtrate_Driver(system: SYSTEM, soups: ResultSet) -> str 123 | 获取与系统匹配的驱动 124 | 125 | :param system: 系统类 126 | :param soups: 驱动xml列表 127 | :return: str 128 | """ 129 | res = [] 130 | Os = self._map.get(system.Name) 131 | for contents in soups: 132 | temp = contents.key.text 133 | if Os in temp: 134 | res.append(temp) 135 | if len(res) == 1: 136 | return res[0] 137 | else: 138 | t = [x for x in res if str(system.Bit) in x] 139 | if len(t) == 1: 140 | return t[0] 141 | return min(res, key=len) 142 | 143 | def Download(self, system: SYSTEM) -> ParseResult: 144 | """ 145 | Download(system: SYSTEM) -> ParseResult 146 | 获取驱动下载链接 147 | 148 | :param system: 系统类 149 | :return: ParseResult 150 | """ 151 | driver_version = self.__Filtrate_Version( 152 | chrome_version=system.Chrome_Version) 153 | soups = self.__Select_Driver(driver_version=driver_version) 154 | path = self.__Filtrate_Driver(system=system, soups=soups) 155 | return urlparse( 156 | url=self.__Download_Link().geturl().format(path=path) 157 | ) 158 | 159 | 160 | class TAOBAO_DRIVER_ANALYSIS(DRIVER_ANALYSIS): 161 | """淘宝镜像类""" 162 | 163 | @property 164 | def Master(self) -> ParseResult: 165 | """ 166 | Master -> ParseResult 167 | 官网 168 | 169 | :return: ParseResult 170 | """ 171 | return urlparse( 172 | url="http://npm.taobao.org/mirrors/chromedriver/" 173 | ) 174 | 175 | def __Defininite(self) -> ParseResult: 176 | """ 177 | __Definite() -> ParseResult 178 | 具体版本列表 179 | 180 | :return: ParseResult 181 | """ 182 | return urlparse( 183 | url="http://npm.taobao.org{path}" 184 | ) 185 | 186 | def __Download_link(self) -> ParseResult: 187 | """ 188 | __Download_link() -> ParseResult 189 | 驱动下载链接 190 | 191 | :return: ParseResult 192 | """ 193 | return urlparse( 194 | url="https://cdn.npm.taobao.org/dist{path}" 195 | ) 196 | 197 | def __All_Driver(self) -> ResultSet: 198 | """ 199 | __All_Driver() -> ResultSet 200 | 所有驱动列表 201 | 202 | :return: ResultSet 203 | """ 204 | html = requests.get(url=self.Master.geturl()) 205 | for retry in range(3): 206 | if html.status_code == 200: 207 | break 208 | html = requests.get(url=self.Master.geturl()) 209 | html.encoding = html.apparent_encoding 210 | soup = BeautifulSoup(html.text, 'html.parser') 211 | return soup.pre.select(selector='a') 212 | 213 | def __Filtrate_Version(self, chrome_version: str) -> str: 214 | """ 215 | __Filtrate_Version(chrome_version: str) -> str 216 | 获取与谷歌浏览器版本匹配的驱动版本 217 | 218 | :param chrome_version: 谷歌浏览器版本号 219 | :return: str 220 | """ 221 | res = [] 222 | chrome_version = chrome_version.split('.')[:-1] 223 | for a in self.__All_Driver(): 224 | temp = a.text.split('.')[:-1] 225 | if temp == chrome_version: 226 | res.append(a) 227 | return max(res, key=lambda x: int(x.text.split('.')[-1].strip('/'))).attrs.get('href') 228 | 229 | def __Select_Driver(self, drivers_link: ParseResult) -> ResultSet: 230 | """ 231 | __Select_Driver(drivers_link: ParseResult) -> ResultSet 232 | 具体版本号驱动列表 233 | 234 | :param drivers_link: 具体版本号驱动链接 235 | :return: ResultSet 236 | """ 237 | html = requests.get(url=drivers_link.geturl()) 238 | for retry in range(3): 239 | if html.status_code == 200: 240 | break 241 | html = requests.get(url=drivers_link.geturl()) 242 | html.encoding = html.apparent_encoding 243 | soup = BeautifulSoup(html.text, 'html.parser') 244 | return soup.pre.select(selector='a') 245 | 246 | def __Filtrate_Driver(self, system: SYSTEM, soups: ResultSet) -> str: 247 | """ 248 | __Filtrate_Driver(system: SYSTEM, soups: ResultSet) -> str 249 | 获取与系统匹配的驱动 250 | 251 | :param system: 系统类 252 | :param soups: 具体版本号驱动列表 253 | :return: str 254 | """ 255 | res = [] 256 | Os = self._map.get(system.Name) 257 | for a in soups: 258 | if Os in a.text: 259 | res.append(a) 260 | if len(res) == 1: 261 | return res[0].attrs.get('href') 262 | else: 263 | t = [x for x in res if str(system.Bit) in x.text] 264 | if len(t) == 1: 265 | return t[0].attrs.get('href') 266 | return min(res, key=lambda x: len(x.text)).attrs.get('href') 267 | 268 | def Download(self, system: SYSTEM) -> ParseResult: 269 | """ 270 | Download(system: SYSTEM) -> ParseResult 271 | 获取驱动下载链接 272 | 273 | :param system: 系统类 274 | :return: ParseResult 275 | """ 276 | driver_version = self.__Filtrate_Version( 277 | chrome_version=system.Chrome_Version 278 | ) 279 | driver_version = self.__Defininite().geturl().format(path=driver_version) 280 | driver_version = urlparse(url=driver_version) 281 | soups = self.__Select_Driver(drivers_link=driver_version) 282 | path = self.__Filtrate_Driver(system=system, soups=soups) 283 | index = path[1:].find('/')+1 284 | return urlparse( 285 | url=self.__Download_link().geturl().format(path=path[index:]) 286 | ) 287 | -------------------------------------------------------------------------------- /inside/DB/DB_Check.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/15 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : DB_Check.py 8 | # @Function : 数据库检测 9 | import os 10 | import time 11 | from sqlite3 import Cursor, Connection 12 | 13 | from inside.Config.Path import PATH 14 | from inside.DB.DB_Create import DB_CREATE 15 | from inside.DB.DB_Config import DB_CONFIG 16 | from inside.Template.Meta_Singleton import SINGLETON 17 | 18 | __all__ = ['DB_CHECK'] 19 | 20 | 21 | class DB_CHECK(metaclass=SINGLETON): 22 | """数据库检测类""" 23 | 24 | @property 25 | def Dir(self) -> bool: 26 | """ 27 | Dir -> bool 28 | 是否存在数据库目录 29 | 30 | :return: bool 31 | """ 32 | return os.path.exists(PATH().DB) 33 | 34 | def Check_Dir(self) -> None: 35 | """ 36 | Check_Dir() -> None 37 | 检查数据库目录是否存在,如不存在则自动创建 38 | 39 | :return: None 40 | """ 41 | if not self.Dir: 42 | print(f"检测到数据库目录未创建\n" 43 | f"自动创建中") 44 | os.mkdir(PATH().DB) 45 | print(f"数据库目录为{PATH().DB}") 46 | 47 | def _Exist_Table(self, cursor: Cursor, table_name: str) -> bool: 48 | """ 49 | _Exist_Table(cursor: Cursor, table_name: str) -> None 50 | 检查表是否存在 51 | 52 | :param cursor: 光标 53 | :param table_name: 表名 54 | :return: bool 55 | """ 56 | sql = '''SELECT name FROM sqlite_master 57 | WHERE type='table' 58 | AND name=? COLLATE NOCASE;''' 59 | data = (table_name,) 60 | result = cursor.execute(sql, data) 61 | try: 62 | next(result) 63 | return True 64 | except StopIteration: 65 | return False 66 | 67 | def _Change_Table(self, connect: Connection, cursor: Cursor, 68 | table_name: str) -> None: 69 | """ 70 | _Change_Table(connect: Connection, cursor: Cursor, 71 | table_name: str) -> None 72 | 更改表名,在表名后面加上当前时间戳 73 | 74 | :param connect: 数据库连接对象 75 | :param cursor: 光标 76 | :param table_name: 表名 77 | :return: None 78 | """ 79 | new_name = table_name + str(int(time.time())) 80 | sql = f'''ALTER TABLE {table_name} RENAME TO {new_name};''' 81 | print(f"检测到同名不同结构表<{table_name}>\n" 82 | f"自动为其重命名中") 83 | cursor.execute(sql) 84 | connect.commit() 85 | print(f"重命名为<{new_name}>") 86 | 87 | def _Check_Table_Fields(self, cursor: Cursor, table_name: str, 88 | table_info: tuple) -> bool: 89 | """ 90 | _Check_Table_Fields(cursor: Cursor, table_name: str, 91 | table_info: tuple) -> bool 92 | 检查表结构是否一致 93 | 94 | :param cursor: 光标 95 | :param table_name: 表名 96 | :param table_info: 表结构(从左至右顺序) 97 | :return: bool 98 | """ 99 | sql = f'''PRAGMA TABLE_INFO({table_name})''' 100 | result = cursor.execute(sql) 101 | temp_name, temp_type = [], [] 102 | for temp in result: 103 | temp_name.append(temp[1]) 104 | temp_type.append(temp[2]) 105 | return (tuple(temp_name), tuple(temp_type)) == table_info 106 | 107 | def _Check_Table(self, connect: Connection, cursor: Cursor, 108 | table_name: str, table_info: tuple) -> bool: 109 | """ 110 | _Check_Table(connect: Connection, cursor: Cursor, 111 | table_name: str, table_info: tuple) -> bool: 112 | 检查表是否存在,且表结构是否一致;如存在且表结构不一致,则将已存在同名表重命名,返回False 113 | 114 | :param connect: 数据库连接对象 115 | :param cursor: 光标 116 | :param table_name: 表名 117 | :param table_info: 表结构(从左至右顺序) 118 | :return: bool 119 | """ 120 | if self._Exist_Table(cursor=cursor, table_name=table_name): 121 | temp = self._Check_Table_Fields(cursor=cursor, 122 | table_name=table_name, 123 | table_info=table_info) 124 | if temp: 125 | return temp 126 | else: 127 | self._Change_Table(connect=connect, cursor=cursor, 128 | table_name=table_name) 129 | return False 130 | 131 | def Check_User(self, connect: Connection, db_create: DB_CREATE, 132 | cursor: Cursor) -> None: 133 | """ 134 | Check_User(connect: Connection, db_create: DB_CREATE, 135 | cursor: Cursor) -> None 136 | 检查用户表是否存在,不存在则创建 137 | 138 | :param connect: 数据库连接对象 139 | :param db_create: 数据库创建操作对象 140 | :param cursor: 光标 141 | :return: None 142 | """ 143 | if not self._Check_Table( 144 | connect=connect, 145 | cursor=cursor, 146 | table_name=DB_CONFIG().User, 147 | table_info=( 148 | DB_CONFIG().User_Fields, 149 | DB_CONFIG().User_Fields_Types 150 | ) 151 | ): 152 | print(f"检测到用户表<{DB_CONFIG().User}>未创建\n" 153 | f"自动创建中") 154 | db_create.User() 155 | print(f"用户表为<{DB_CONFIG().User}>") 156 | 157 | def Check_Task(self, connect: Connection, db_create: DB_CREATE, 158 | cursor: Cursor) -> None: 159 | """ 160 | Check_Task(connect: Connection, db_create: DB_CREATE, 161 | cursor: Cursor) -> None 162 | 检查任务表是否存在,不存在则创建 163 | 164 | :param connect: 数据库连接对象 165 | :param db_create: 数据库创建操作对象 166 | :param cursor: 光标 167 | :return: None 168 | """ 169 | if not self._Check_Table( 170 | connect=connect, 171 | cursor=cursor, 172 | table_name=DB_CONFIG().Task, 173 | table_info=( 174 | DB_CONFIG().Task_Fields, 175 | DB_CONFIG().Task_Fields_Types 176 | ) 177 | ): 178 | print(f"检测到任务表<{DB_CONFIG().Task}>未创建\n" 179 | f"自动创建中") 180 | db_create.Task() 181 | print(f"任务表为<{DB_CONFIG().Task}>") 182 | 183 | def Check_Article(self, connect: Connection, db_create: DB_CREATE, 184 | cursor: Cursor) -> None: 185 | """ 186 | Check_Article(connect: Connection, db_create: DB_CREATE, 187 | cursor: Cursor) -> None 188 | 检查文章表是否存在,不存在则创建 189 | 190 | :param connect: 数据库连接对象 191 | :param db_create: 数据库创建操作对象 192 | :param cursor: 光标 193 | :return: None 194 | """ 195 | if not self._Check_Table( 196 | connect=connect, 197 | cursor=cursor, 198 | table_name=DB_CONFIG().Article, 199 | table_info=( 200 | DB_CONFIG().Article_Fields, 201 | DB_CONFIG().Article_Fields_Types 202 | ) 203 | ): 204 | print(f"检测到文章表<{DB_CONFIG().Article}>未创建\n" 205 | f"自动创建中") 206 | db_create.Article() 207 | print(f"文章表为<{DB_CONFIG().Article}>") 208 | 209 | def Check_Video(self, connect: Connection, db_create: DB_CREATE, 210 | cursor: Cursor) -> None: 211 | """ 212 | Check_Video(connect: Connection, db_create: DB_CREATE, 213 | cursor: Cursor) -> None 214 | 检查视频表是否存在,不存在则创建 215 | 216 | :param connect: 数据库连接对象 217 | :param db_create: 数据库创建操作对象 218 | :param cursor: 光标 219 | :return: None 220 | """ 221 | if not self._Check_Table( 222 | connect=connect, 223 | cursor=cursor, 224 | table_name=DB_CONFIG().Video, 225 | table_info=( 226 | DB_CONFIG().Video_Fields, 227 | DB_CONFIG().Video_Fields_Types 228 | ) 229 | ): 230 | print(f"检测到视频表<{DB_CONFIG().Video}>未创建\n" 231 | f"自动创建中") 232 | db_create.Video() 233 | print(f"视频表为<{DB_CONFIG().Video}>") 234 | 235 | def Check_Project(self, connect: Connection, db_create: DB_CREATE, 236 | cursor: Cursor) -> None: 237 | """ 238 | Check_Project(connect: Connection, db_create: DB_CREATE, 239 | cursor: Cursor) -> None 240 | 检查专项答题表是否存在,不存在则创建 241 | 242 | :param connect: 数据库连接对象 243 | :param db_create: 数据库创建操作对象 244 | :param cursor: 光标 245 | :return: None 246 | """ 247 | if not self._Check_Table( 248 | connect=connect, 249 | cursor=cursor, 250 | table_name=DB_CONFIG().Project, 251 | table_info=( 252 | DB_CONFIG().Project_Fields, 253 | DB_CONFIG().Project_Fields_Types 254 | ) 255 | ): 256 | print(f"检测到专项答题表<{DB_CONFIG().Project}>未创建\n" 257 | f"自动创建中") 258 | db_create.Project() 259 | print(f"视频表为<{DB_CONFIG().Project}>") 260 | 261 | def Check_Baidu_AI(self, connect: Connection, db_create: DB_CREATE, 262 | cursor: Cursor) -> None: 263 | """ 264 | Check_Project(connect: Connection, db_create: DB_CREATE, 265 | cursor: Cursor) -> None 266 | 检查专项答题表是否存在,不存在则创建 267 | 268 | :param connect: 数据库连接对象 269 | :param db_create: 数据库创建操作对象 270 | :param cursor: 光标 271 | :return: None 272 | """ 273 | if not self._Check_Table( 274 | connect=connect, 275 | cursor=cursor, 276 | table_name=DB_CONFIG().Baidu_AI, 277 | table_info=( 278 | DB_CONFIG().Baidu_AI_Fields, 279 | DB_CONFIG().Baidu_AI_Fields_Types 280 | ) 281 | ): 282 | print(f"检测到百度AI表<{DB_CONFIG().Baidu_AI}>未创建\n" 283 | f"自动创建中") 284 | db_create.Baidu_AI() 285 | print(f"百度AI表为<{DB_CONFIG().Baidu_AI}>") 286 | 287 | def Check_Table(self, connect: Connection, cursor: Cursor) -> None: 288 | """ 289 | Check_Table(connect: Connection, cursor: Cursor) -> None 290 | 检查表信息 291 | 292 | :param connect: 数据库连接对象 293 | :param cursor: 光标 294 | :return: None 295 | """ 296 | db_create = DB_CREATE(connect=connect, cursor=cursor) 297 | self.Check_User(connect=connect, db_create=db_create, cursor=cursor) 298 | self.Check_Task(connect=connect, db_create=db_create, cursor=cursor) 299 | self.Check_Article(connect=connect, db_create=db_create, cursor=cursor) 300 | self.Check_Video(connect=connect, db_create=db_create, cursor=cursor) 301 | self.Check_Project(connect=connect, db_create=db_create, cursor=cursor) 302 | self.Check_Baidu_AI(connect=connect, db_create=db_create, cursor=cursor) 303 | 304 | -------------------------------------------------------------------------------- /inside/Config/Api.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2020/12/13 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.2 7 | # @File : Api.py 8 | # @Function : api配置 9 | from typing import List 10 | from urllib.parse import ParseResult 11 | from urllib.parse import urlparse 12 | 13 | from inside.Driver.Driver_Analysis import GOOGLEAPIS_DRIVER_ANALYSIS, \ 14 | TAOBAO_DRIVER_ANALYSIS 15 | from inside.Template.ABC_Driver_Analysis import DRIVER_ANALYSIS 16 | from inside.Template.Meta_Singleton import SINGLETON 17 | 18 | __all__ = ['API'] 19 | 20 | from inside.Tools.Url_Test import URL_TEST 21 | 22 | 23 | class API(metaclass=SINGLETON): 24 | """Api集合类""" 25 | 26 | @property 27 | def Master(self) -> ParseResult: 28 | """ 29 | Master -> ParseResult 30 | 官网 31 | 32 | :return: ParseResult 33 | """ 34 | return urlparse( 35 | url="https://www.xuexi.cn/" 36 | ) 37 | 38 | @property 39 | def Login(self) -> ParseResult: 40 | """ 41 | Login -> ParseResult 42 | 登录api 43 | 44 | :return: ParseResult 45 | """ 46 | return urlparse( 47 | url="https://pc.xuexi.cn/points/login.html?ref=https://www.xuexi" 48 | ".cn/" 49 | ) 50 | 51 | @property 52 | def Login_Generate(self) -> ParseResult: 53 | """ 54 | Login_Generate -> ParseResult 55 | 登录二维码号码api 56 | 57 | :return: ParseResult 58 | """ 59 | return urlparse( 60 | url="https://login.xuexi.cn/user/qrcode/generate" 61 | ) 62 | 63 | @property 64 | def Login_QR_Status(self) -> ParseResult: 65 | """ 66 | Login_State -> ParseResult 67 | 登录二维码状态api 68 | 69 | :return: ParseResult 70 | """ 71 | return urlparse( 72 | url="https://login.xuexi.cn/login/login_with_qr" 73 | ) 74 | 75 | @property 76 | def Login_QR_Iframe(self) -> ParseResult: 77 | """ 78 | Login_QR_Iframe -> ParseResult 79 | 登录二维码state的正则规则api 80 | 81 | :return: ParseResult 82 | """ 83 | return urlparse( 84 | url="https://login.xuexi.cn/login/xuexiWeb\\?appid" 85 | "=dingoankubyrfkttorhpou&goto=https://oa.xuexi.cn&type=1" 86 | "&state=.*?&check_login=https://pc-api.xuexi.cn" 87 | ) 88 | 89 | @property 90 | def Login_Token(self) -> ParseResult: 91 | """ 92 | Login_Token -> ParseResult 93 | 登录cookie获取api 94 | 95 | :return: ParseResult 96 | """ 97 | return urlparse( 98 | url="https://pc-api.xuexi.cn/login/secure_check?code={" 99 | "code}&state={state}" 100 | ) 101 | 102 | @property 103 | def Token_Check(self) -> ParseResult: 104 | """ 105 | Token_Check(self) -> ParseResult 106 | Token检测api 107 | 108 | :return: ParseResult 109 | """ 110 | return urlparse( 111 | url="https://pc-api.xuexi.cn/open/api/auth/check?t={timestamp}" 112 | ) 113 | 114 | @property 115 | def Aggregate_Score(self) -> ParseResult: 116 | """ 117 | Aggregate_Score -> ParseResult 118 | 总积分api 119 | 120 | :return: ParseResult 121 | """ 122 | return urlparse( 123 | url="https://pc-api.xuexi.cn/open/api/score/get?_t=1606620395029" 124 | ) 125 | 126 | @property 127 | def Daily_Score(self) -> ParseResult: 128 | """ 129 | Daily_Score -> ParseResult 130 | 每日已获积分api 131 | 132 | :return: ParseResult 133 | """ 134 | return urlparse( 135 | url="https://pc-api.xuexi.cn/open/api/score/today/query" 136 | ) 137 | 138 | @property 139 | def Task_Bar(self) -> ParseResult: 140 | """ 141 | Task_Bar -> ParseResult 142 | 每日任务进度api 143 | 144 | :return: ParseResult 145 | """ 146 | return urlparse( 147 | url="https://pc-api.xuexi.cn/open/api/score/today/queryrate" 148 | ) 149 | 150 | @property 151 | def Level(self) -> ParseResult: 152 | """ 153 | Level -> ParseResult 154 | 等级api 155 | 156 | :return: ParseResult 157 | """ 158 | return urlparse( 159 | url="https://pc-api.xuexi.cn/open/api/score/self/get" 160 | ) 161 | 162 | @property 163 | def Task_Parent(self) -> ParseResult: 164 | """ 165 | Task_Parent -> ParseResult 166 | 所有任务类别api 167 | 168 | :return: ParseResult 169 | """ 170 | return urlparse( 171 | url="https://www.xuexi.cn/lgdata/channel-list.json?_st=26777175" 172 | ) 173 | 174 | @property 175 | def Task_Son(self) -> ParseResult: 176 | """ 177 | Task_Son -> ParseResult 178 | 具体任务集合api,此处返回Url中需格式化后使用,格式变量为channel_id,此变量值从 179 | Task_Parent中解析而来; 180 | 使用方法:Task_Son.geturl().format(channel_id=xxx) 181 | 182 | :return: ParseResult 183 | """ 184 | return urlparse( 185 | url="https://www.xuexi.cn/lgdata/{channel_id}.json?_st=26777175" 186 | ) 187 | 188 | @property 189 | def Task_Check(self) -> ParseResult: 190 | return urlparse( 191 | url="https://iflow-api.xuexi.cn/logflow/api/v1/pclog" 192 | ) 193 | 194 | @property 195 | def Not_Found(self) -> ParseResult: 196 | """ 197 | Not_Found -> ParseResult 198 | 404api 199 | 200 | :return: ParseResult 201 | """ 202 | return urlparse( 203 | url="https://www.xuexi.cn/notFound.html" 204 | ) 205 | 206 | @property 207 | def Daily_Answer(self) -> ParseResult: 208 | """ 209 | Daily_Answer -> ParseResult 210 | 每日答题api 211 | 212 | :return: ParseResult 213 | """ 214 | return urlparse( 215 | url="https://pc.xuexi.cn/points/exam-practice.html" 216 | ) 217 | 218 | @property 219 | def Daily_Answer_Res(self) -> ParseResult: 220 | """ 221 | Daily_Answer_Res -> ParseResult 222 | 每日答题答案api 223 | 224 | :return: ParseResult 225 | """ 226 | return urlparse( 227 | url="https://pc-proxy-api.xuexi.cn/api/exam/service/common" 228 | "/deduplicateRandomSearchV3?limit=5&activityCode=QUIZ_ALL" 229 | "&forced=true" 230 | ) 231 | 232 | @property 233 | def Daily_Answer_Put(self) -> ParseResult: 234 | """ 235 | Daily_Answer_Put -> ParseResult 236 | 每日答题答案提交api 237 | 238 | :return: ParseResult 239 | """ 240 | return urlparse( 241 | url="https://pc-proxy-api.xuexi.cn/api/exam/service/practice" 242 | "/quizSubmitV3" 243 | ) 244 | 245 | @property 246 | def Answer_Accomplish(self) -> ParseResult: 247 | """ 248 | Answer_Accomplish -> ParseResult 249 | 答题完成api 250 | 251 | :return: ParseResult 252 | """ 253 | return urlparse( 254 | url="https://pc-proxy-api.xuexi.cn/api/exam/service/detail/score" 255 | "\\?queryTipScoreId=.*" 256 | ) 257 | 258 | @property 259 | def Weekly_Answer_Topics(self) -> ParseResult: 260 | """ 261 | Weekly_Answer_Topics -> ParseResult 262 | 每周答题总览api,由于每周答题2021/01/10改版,所有答题不过期,所以将获取往期未答; 263 | 而目前总共为3页,所以num从3开始,其实可以调整pageSize的大小,一次性将题目列表 264 | 全部获取,但不是正常的操作。 265 | 266 | :return: ParseResult 267 | """ 268 | return urlparse( 269 | url="https://pc-proxy-api.xuexi.cn/api/exam/service/practice/pc" 270 | "/weekly/more?pageSize=50&pageNo={num}" 271 | ) 272 | 273 | @property 274 | def Weekly_Answer_Topic(self) -> ParseResult: 275 | """ 276 | Weekly_Answer_Topic -> ParseResult 277 | 每周答题api,此处返回Url中需格式化后使用,格式变量为num,此变量值从 278 | Weekly_Answer_Topics中解析而来。 279 | 使用方法:Weekly_Answer_Topic.geturl().format(num=x) 280 | 281 | :return: ParseResult 282 | """ 283 | return urlparse( 284 | url="https://pc.xuexi.cn/points/exam-weekly-detail.html?id={num}" 285 | ) 286 | 287 | @property 288 | def Weekly_Answer_Res(self) -> ParseResult: 289 | """ 290 | Weekly_Answer_Res -> ParseResult 291 | 每周答题答案api,此处返回Url中需格式化后使用,格式变量为num,此变量值从 292 | Weekly_Answer_Topics中解析而来并与Weekly_Answer_Topic统一; 293 | 使用方法:Weekly_Answer_Res.geturl().format(num=x) 294 | 295 | :return: ParseResult 296 | """ 297 | return urlparse( 298 | url="https://pc-proxy-api.xuexi.cn/api/exam/service/detail" 299 | "/queryV3?type=2&id={num}&forced=true" 300 | ) 301 | 302 | @property 303 | def Project_Answer_Topics(self) -> ParseResult: 304 | """ 305 | Project_Answer_Topics -> ParseResult 306 | 专项答题总览api,由于每周答题2021/01/10改版,所有答题不过期,所以将获取往期未答; 307 | 而目前总共为5页,所以num从5开始,其实可以调整pageSize的大小,一次性将题目列表 308 | 全部获取,但不是正常的操作。 309 | 310 | :return: ParseResult 311 | """ 312 | return urlparse( 313 | url="https://pc-proxy-api.xuexi.cn/api/exam/service/paper/pc" 314 | "/list?pageSize=50&pageNo={num}" 315 | ) 316 | 317 | @property 318 | def Project_Answer_Topic(self) -> ParseResult: 319 | """ 320 | Project_Answer_Topic -> ParseResult 321 | 专项答题api,此处返回Url中需格式化后使用,格式变量为page,此变量值从 322 | Project_Answer_Topics解析而来。 323 | 使用方法:Project_Answer_Topic.geturl().format(num=x) 324 | 325 | :return: ParseResult 326 | """ 327 | return urlparse( 328 | url="https://pc.xuexi.cn/points/exam-paper-detail.html?id={num}" 329 | ) 330 | 331 | @property 332 | def Project_Answer_Res(self) -> ParseResult: 333 | """ 334 | Project_Answer_Res -> ParseResult 335 | 专项答题答案api,此处返回Url中需格式化后使用,格式变量为num,此变量值从 336 | Project_Answer_Topics中解析而来并与Project_Answer_Topic统一; 337 | 使用方法:Project_Answer_Res.geturl().format(num=x) 338 | 339 | :return: ParseResult 340 | """ 341 | return urlparse( 342 | url="https://pc-proxy-api.xuexi.cn/api/exam/service/detail" 343 | "/queryV3?type=1&id={num}&forced=true" 344 | ) 345 | 346 | @property 347 | def __Drivers(self) -> List[DRIVER_ANALYSIS]: 348 | """ 349 | __Drivers -> List[ParseResult] 350 | 驱动下载站点集合 351 | 352 | :return: List[ParseResult] 353 | """ 354 | google = GOOGLEAPIS_DRIVER_ANALYSIS() 355 | taobao = TAOBAO_DRIVER_ANALYSIS() 356 | return [google, taobao] 357 | 358 | @property 359 | def Driver(self) -> DRIVER_ANALYSIS: 360 | """ 361 | Driver -> DRIVER_ANALYSIS 362 | 测试所有驱动下载站点,返回最佳站点 363 | 364 | :return: DRIVER_ANALYSIS 365 | """ 366 | temp = float('inf') 367 | driver = None 368 | for value in self.__Drivers: 369 | test = URL_TEST.Url_Test(url=value.Master.geturl()) 370 | if test and test < temp: 371 | temp = test 372 | driver = value 373 | return driver 374 | -------------------------------------------------------------------------------- /inside/Login/Login.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | # @Author : lisztomania 4 | # @Date : 2021/1/15 5 | # @Software : Pycharm 6 | # @Version : Python 3.8.5 7 | # @File : Login.py 8 | # @Function : 登录 9 | import json 10 | import re 11 | from typing import Dict 12 | from urllib import parse 13 | 14 | from selenium.common.exceptions import WebDriverException 15 | from selenium.webdriver.chrome.webdriver import WebDriver 16 | from selenium.webdriver.remote.webelement import WebElement 17 | from selenium.webdriver.support.wait import WebDriverWait 18 | from selenium.webdriver.support import expected_conditions as EC 19 | from selenium.webdriver.common.by import By 20 | from inside.Config.Api import API 21 | from inside.Options.Options import OPTIONS 22 | from inside.DB.DB_Manage import DB_MANAGE 23 | from inside.DB.Table_Class.User import USER 24 | from inside.Info.Info_Manage import INFO_MANAGE 25 | from inside.Login.Check_Token import CHECK_TOKEN 26 | from inside.Login.QR_Vessel import QR_VESSEL 27 | 28 | from inside.Tools.Network import NETWORK 29 | from inside.Tools.Requests import REQUESTS 30 | 31 | __all__ = ['LOGIN'] 32 | 33 | 34 | class LOGIN(object): 35 | """登录类""" 36 | 37 | def __init__(self, task_driver: WebDriver): 38 | """ 39 | LOGIN() 40 | 初始化, 41 | 42 | """ 43 | self.__driver = task_driver 44 | self.__network = NETWORK() 45 | self.__wait = WebDriverWait(self.__driver, 10) 46 | 47 | def __QR_Iframe(self) -> WebElement: 48 | """ 49 | __QR_Iframe() -> WebElement 50 | 获取二维码iframe 51 | 52 | :return: WebElement 53 | """ 54 | iframe_Ec = EC.presence_of_element_located( 55 | ( 56 | By.ID, "ddlogin-iframe" 57 | ) 58 | ) 59 | return self.__wait.until(iframe_Ec) 60 | 61 | def __QR_Base64(self) -> str: 62 | """ 63 | __QR_Base64() -> str 64 | 二维码base64,格式为:data:image/png;base64,xxx== 65 | 66 | :return: str 67 | """ 68 | qr_code_Ec = EC.presence_of_element_located( 69 | (By.CLASS_NAME, "login_qrcode_content") 70 | ) 71 | qr_code: WebElement = self.__wait.until(qr_code_Ec) 72 | qr = qr_code.find_element_by_tag_name(name='img') 73 | return qr.get_attribute(name='src') 74 | 75 | def __Get_QR(self) -> str: 76 | """ 77 | __Get_QR() -> str 78 | 获取二维码,格式为:data:image/png;base64,xxx== 79 | 80 | :return: str 81 | """ 82 | qr_iframe = self.__QR_Iframe() 83 | self.__driver.switch_to.frame(frame_reference=qr_iframe) 84 | temp = self.__QR_Base64() 85 | self.__driver.switch_to.default_content() 86 | return temp 87 | 88 | def __Get_QR_ID(self, key: str, value: str) -> str: 89 | """ 90 | __Get_QR_ID(key: str, value: str) -> str 91 | 获取二维码ID,格式为:qr:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 92 | 93 | :param key: url 94 | :param value: requestId 95 | :return: str 96 | """ 97 | if API().Login_Generate.geturl() == key: 98 | response = self.__network.GetResponseBody(requestId=value)[ 99 | 'body'] 100 | response = json.loads(response) 101 | return response['result'] 102 | return '' 103 | 104 | def __Get_QR_Status_Network(self, key: str, value: str) -> int: 105 | """ 106 | __Get_QR_Status_Network(key: str, value: str) -> int 107 | 获取二维码状态码(Network中获取) 108 | 109 | :param key: url 110 | :param value: requestId 111 | :return: int 112 | -1: 还未请求 113 | 1: 成功 114 | 11021: 未登录 115 | 11019: 二维码失效 116 | """ 117 | if API().Login_QR_Status.geturl() == key: 118 | while True: 119 | try: 120 | response = self.__network.GetResponseBody(requestId=value) 121 | response = json.loads(response['body']) 122 | if response['success']: 123 | return 1 124 | else: 125 | return int(response['code']) 126 | except WebDriverException: 127 | continue 128 | return -1 129 | 130 | def __Get_QR_Status_Requests(self, qr_data: Dict, qr_state: str): 131 | """ 132 | __Get_QR_Status_Requests(qr_data: Dict) -> int 133 | 获取二维码状态码(requests请求) 134 | 135 | :param qr_data: 带有二维码编号的data 136 | :param qr_state: 二维码的state 137 | :return: int 138 | 1: 成功 139 | 11021: 未登录 140 | 11019: 二维码失效 141 | """ 142 | html = REQUESTS.Post( 143 | url=API().Login_QR_Status.geturl(), 144 | data=qr_data 145 | ) 146 | status = html.json() 147 | if status['success']: 148 | self.__Change_Driver_Cookie(key=status['data'], state=qr_state) 149 | return 1 150 | else: 151 | return int(status['code']) 152 | 153 | def __Change_Driver_Cookie(self, key: str, state: str) -> None: 154 | """ 155 | __Change_Driver_Cookie(key: str, state: str) -> None 156 | 此处是应对network中还未查询二维码状态时,所做的措施,用处是加快登录过程,而不需要浏览器 157 | 自主添加cookie 158 | 159 | :param key: 最后一次查询二维码状态的api会返回code(只会允许查询一次,随后就是过期) 160 | :param state: iframe链接中提取 161 | :return: None 162 | """ 163 | url = parse.urlparse(url=key) 164 | code = parse.parse_qs(qs=url.query)['loginTmpCode'][0] 165 | html = REQUESTS.Get( 166 | url=API().Login_Token.geturl().format(code=code, state=state) 167 | ) 168 | token = html.cookies.get(name='token') 169 | cookie = {'domain': '.xuexi.cn', 170 | 'name': 'token', 171 | 'value': token} 172 | self.__driver.add_cookie(cookie_dict=cookie) 173 | self.__driver.refresh() 174 | 175 | def __Get_QR_State(self, key: str) -> str: 176 | """ 177 | __Get_QR_State(key: str) -> str 178 | 获取二维码的state,在iframe链接中提取 179 | 180 | :param key: iframe链接 181 | :return: str 182 | """ 183 | temp = re.match( 184 | pattern=API().Login_QR_Iframe.geturl(), 185 | string=key 186 | ) 187 | if temp: 188 | url = parse.urlparse(url=key) 189 | return parse.parse_qs(qs=url.query)['state'][0] 190 | return '' 191 | 192 | def __QR_Refresh(self) -> None: 193 | """ 194 | __QR_Refresh() -> None 195 | 刷新二维码 196 | 197 | :return: None 198 | """ 199 | qr_refresh_Ec = EC.presence_of_element_located( 200 | ( 201 | By.CLASS_NAME, "refresh" 202 | ) 203 | ) 204 | qr_refresh: WebElement = self.__wait.until(qr_refresh_Ec) 205 | qr_refresh.click() 206 | 207 | def __Check_Status(self) -> bool: 208 | """ 209 | __Check_Status() -> bool 210 | 检测二维码状态, 此处略微复杂了一点 211 | 具体流程: 212 | 1、初始化state、iid、status、req为空 213 | state:二维码的state值,用于处理浏览器未触发状态检测的空窗期登录问题 214 | iid:二维码的id值,作用同上 215 | status:二维码的状态值 216 | req:检测浏览器是否处于空窗期 217 | 2、开启循环 218 | 3、从日志中取值 219 | 4、为state、iid、status、req赋值 220 | 5、如果日志中没有了、且初值都赋值完毕,且浏览器处于空窗期,就进行自主查询 221 | 6、判断查询的状态 222 | 223 | :return: bool 224 | True:成功 225 | False:过期 226 | """ 227 | state = iid = status = None 228 | req = False 229 | while True: 230 | network = self.__network.Get() 231 | for key, value in network.items(): 232 | key = parse.unquote(string=key) 233 | temp = self.__Get_QR_State(key=key) 234 | if temp: 235 | state = temp 236 | break 237 | temp = self.__Get_QR_ID(key=key, value=value) 238 | if temp: 239 | iid = temp 240 | break 241 | status = self.__Get_QR_Status_Network(key=key, value=value) 242 | if status == 11019: 243 | return False 244 | req = True if status == -1 else False 245 | if not network and state and iid and status and req: 246 | qr_data = { 247 | 'qrCode': iid, 248 | 'goto': 'https://oa.xuexi.cn', 249 | 'pdmToken': '' 250 | } 251 | status = self.__Get_QR_Status_Requests( 252 | qr_data=qr_data, 253 | qr_state=state 254 | ) 255 | if status == 1: 256 | return True 257 | elif status == 11021: 258 | continue 259 | 260 | def _Login(self) -> bool: 261 | """ 262 | _Login() -> Bool 263 | 进行登录; 264 | 具体流程: 265 | 1、开启循环检测二维码状态 266 | 1、获取二维码图片 267 | 2、显示二维码 268 | 3、二维码状态检测 269 | 4、根据3的返回值决定: 270 | 1、刷新二维码,中断本次循环,再来一次 271 | 2、提取Token值,根据选项(持久化)决定是否保持token,关闭二维码容器 272 | :return: Bool,返回值只有True,如未登录则会一直循环 273 | """ 274 | self.__network.Clear() 275 | self.__driver.get(url=API().Login.geturl()) 276 | while True: 277 | qr = self.__Get_QR() 278 | QR_VESSEL().Show_QR(qr=qr) 279 | status = self.__Check_Status() 280 | if not status: 281 | self.__QR_Refresh() 282 | continue 283 | else: 284 | while self.__driver.current_url != API().Master.geturl(): 285 | continue 286 | cookies = self.__driver.get_cookies() 287 | token = [{cookie['name']: cookie['value']} for cookie in 288 | cookies if cookie['name'] == 'token'] 289 | if token: 290 | INFO_MANAGE().Init(token=token[0]['token']) 291 | if OPTIONS().Token: 292 | cookie = token[0] 293 | html = REQUESTS().Get( 294 | url=API().Aggregate_Score.geturl(), 295 | cookies=cookie 296 | ) 297 | data = html.json() 298 | user_id = data['data']['userId'] 299 | user = USER(user_id=user_id, token=token[0]['token']) 300 | if DB_MANAGE().User.Exist_User(user=user): 301 | DB_MANAGE().User.Update(user=user) 302 | else: 303 | DB_MANAGE().User.Insert(user=user) 304 | DB_MANAGE().Article.Update_All() 305 | DB_MANAGE().Video.Update_All() 306 | QR_VESSEL().QR_QUIT() 307 | return status 308 | 309 | def Login(self) -> bool: 310 | """ 311 | Login() -> bool 312 | 登录,如果user表不为空会检测令牌是否有效,有效则直接登录 313 | 314 | :return: bool 315 | """ 316 | if DB_MANAGE().User.Exist(): 317 | user = DB_MANAGE().User.Query() 318 | if CHECK_TOKEN.Check_Token(token=user.Token): 319 | INFO_MANAGE().Init(token=user.Token) 320 | self.__driver.get(url=API().Master.geturl()) 321 | self.__driver.add_cookie(cookie_dict=user.Token_Driver) 322 | self.__driver.refresh() 323 | return True 324 | return self._Login() 325 | 326 | --------------------------------------------------------------------------------