├── src ├── .gitignore ├── publicfunc.py ├── SearchAns.py ├── startdriver.py ├── login_courses.py ├── queryans.py ├── playmedia.py └── singlecourse.py ├── requirements.txt ├── .gitignore ├── setting.py ├── README.md ├── autocx.py └── LICENSE /src/.gitignore: -------------------------------------------------------------------------------- 1 | *pycache*/ 2 | *bak* 3 | *.exe 4 | *test* 5 | chrome* 6 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | selenium 2 | pillow 3 | requests 4 | beautifulsoup4 5 | colorama -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | docker/* 3 | img/* 4 | 版本包/* 5 | test/* 6 | *codes/* 7 | *vscode*/* 8 | *bak* 9 | html.txt 10 | *pycache* -------------------------------------------------------------------------------- /setting.py: -------------------------------------------------------------------------------- 1 | # 用户账户 2 | ACCOUNTS = [ 3 | { 4 | "phone": "", # 手机号 5 | "passwd": "" # 密码 6 | } 7 | #, 8 | #{ 9 | # "phone": "", # 手机号 10 | # "passwd": "" # 密码 11 | #} 12 | #... 13 | ] 14 | 15 | # 刷课模式 16 | # single 单课程自动模式: 选择课程,自动完成该课程 17 | # fullauto 全自动模式: 自动遍历全部课程,无需输入 18 | # control 单课程控制模式: 选择课程并选择控制章节,自动完成[该课程第一个未完成章节,选定章节)范围内章节 19 | # debug 调试模式 20 | MODE = "single" 21 | 22 | # 视频倍速 23 | # [0.625, 16] 24 | RATE = 1.0 25 | 26 | # 自动答题时,如果未找到答案的题目数量达到num,则暂时保存答案,不提交 27 | # [0, +∞) 28 | NUM = 5 -------------------------------------------------------------------------------- /src/publicfunc.py: -------------------------------------------------------------------------------- 1 | from colorama import Fore 2 | from colorama import init as colorinit 3 | 4 | class Color(object): 5 | END = Fore.RESET 6 | OK = Fore.GREEN 7 | NOTE = Fore.YELLOW 8 | WARN = Fore.MAGENTA 9 | ERR = Fore.RED 10 | DISPLAY = Fore.BLUE 11 | 12 | instance = None 13 | 14 | def __new__(cls, *args, **kwargs): 15 | if cls.instance is None: 16 | cls.instance = super().__new__(cls) 17 | return cls.instance 18 | 19 | def __init__(self): 20 | colorinit() 21 | #print(self) 22 | 23 | 24 | COLOR = Color() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # autochaoxing 2 | 3 | 本项目不再维护 4 | ## Recommonded projects 5 | 6 | - [GreasyFork related scripts](https://greasyfork.org/zh-CN/scripts?q=%E8%B6%85%E6%98%9F) 7 | 8 | - [cxmooc-tools](https://github.com/CodFrm/cxmooc-tools) 9 | 10 | - [chaoxing-xuexitong-autoflush](https://github.com/ZhyMC/chaoxing-xuexitong-autoflush) 11 | 12 | - [chaoxing-api](https://github.com/destoryD/chaoxing-api) 13 | 14 | ## 使用 15 | 16 | - 安装chrome浏览器以及相对应的chromedriver,并**将chromedriver放在项目目录下** 17 | 18 | [chrome浏览器下载地址](https://www.google.cn/chrome/),[chromedriver下载地址](http://npm.taobao.org/mirrors/chromedriver/)或者[这里](http://chromedriver.storage.googleapis.com/index.html),注意版本对应 19 | 20 | - 安装依赖:命令行执行
21 | `pip install -r requirements.txt` 22 | 23 | - 编辑**setting.py**,修改相关配置 24 | 25 | - 查看帮助信息 ,选择合适的参数开始刷课
26 | `python autocx.py -h` -------------------------------------------------------------------------------- /autocx.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from subprocess import Popen 4 | from time import sleep 5 | from sys import path as sys_path 6 | import argparse 7 | 8 | from setting import ACCOUNTS, MODE, RATE, NUM 9 | 10 | sys_path.append('./src/') 11 | from publicfunc import Color 12 | COLOR=Color() 13 | 14 | 15 | def main(): 16 | #处理账号信息 17 | print(COLOR.DISPLAY+'Welcome To Multi-Autocx!'+COLOR.END) 18 | 19 | for account in ACCOUNTS: 20 | Popen('start cmd /k python ./src/login_courses.py '+str(account['phone'])+' '+str(account['passwd'])+' '+str(MODE)+' '+str(RATE)+' '+str(NUM), shell=True) 21 | sleep(2) 22 | 23 | print(COLOR.DISPLAY+'Now will exit this program! Good luck!'+COLOR.END) 24 | sleep(1.5) 25 | 26 | if __name__=='__main__': 27 | parser = argparse.ArgumentParser(description="Multi-Autocx", formatter_class=argparse.RawTextHelpFormatter) 28 | parser.add_argument("-m","--mode", type=str,default="single", choices=['single', 'fullauto', 'control','debug'], help= 29 | '''single 单课程自动模式: 选择课程,自动完成该课程 30 | fullauto 全自动模式: 自动遍历全部课程,无需输入 31 | control 单课程控制模式: 选择课程并选择控制章节,自动完成[该课程第一个未完成章节,选定章节)范围内章节''') 32 | parser.add_argument("-r","--rate",type=float,default=1.0,help="视频倍速") 33 | parser.add_argument("-n", "--num", type=int, default=5,help="自动答题时,如果未找到答案的题目数量达到num,则暂时保存答案,不提交") 34 | 35 | args = parser.parse_args() 36 | 37 | if args.rate>16 or args.rate<0.625: 38 | print(COLOR.ERR+'Invalid rate range, Try -h or --help for more information'+COLOR.END) 39 | 40 | # 当脚本参数与setting.py中的配置不一致时 以执行时指定的参数为准 41 | MODE = args.mode if args.mode != MODE else MODE 42 | RATE = args.rate if args.rate != RATE else RATE 43 | NUM = args.num if args.num != NUM else NUM 44 | 45 | print(COLOR.DISPLAY+'Set mode: %s\trate: %.2f\tnoans_num:%d'%(MODE,RATE,NUM)+COLOR.END) 46 | 47 | main() -------------------------------------------------------------------------------- /src/SearchAns.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import tkinter as tk 4 | from tkinter import ttk 5 | from queryans import QueryAns 6 | 7 | win = tk.Tk() 8 | win.title("SearchAns") 9 | win.geometry('505x230') # 设定窗口大小 10 | 11 | # win.resizable(0, 0) # Disable resizing the GUI 12 | tabControl = ttk.Notebook(win) # Create Tab Control 13 | 14 | tab1 = ttk.Frame(tabControl) # Create a tab 15 | tabControl.add(tab1, text="Normal") # Add the tab 16 | 17 | #tab2 = ttk.Frame(tabControl) # Add a second tab 18 | #tabControl.add(tab2, text="Exact") # Make second tab visible 19 | 20 | tabControl.pack(expand=1, fill="both") # Pack to make visible 21 | 22 | # Tab1控件介绍 23 | monty = ttk.LabelFrame(tab1, text="Main") 24 | monty.grid(column=0, row=0, padx=10, pady=4, ipady=5) 25 | # tab1 input 26 | ttk.Label(monty, text="题目:", font=('Arial', 12), width=5).grid(column=0, row=0, padx=5) # , sticky='E') 27 | queText = tk.Text(monty, font=('Arial', 12), width=40, height=3) 28 | queText.grid(column=1, row=0, rowspan=2) # ,sticky='W') 29 | # tab1 button 30 | 31 | def query_ans_normal(ev=None): 32 | #global res 33 | infodic = { 34 | 'question': str(queText.get('0.0', 'end')), 35 | 'type': '其他', 36 | 'course':'', 37 | 'courseID': '' 38 | } 39 | resText.delete('1.0','end') 40 | QA=QueryAns(**infodic) 41 | #res.set(str(QA.work())) 42 | resText.insert('1.0', str(QA.work())) 43 | queText.delete('1.0', 'end') 44 | 45 | 46 | 47 | 48 | queText.bind("", query_ans_normal) 49 | go = ttk.Button(monty, text='Go', width=5, command=query_ans_normal) 50 | go.grid(column=2, row=0, rowspan=2, sticky="n" + "s", padx=5) 51 | # tab1 output 52 | ttk.Label(monty, text="结果:", font=('Arial', 12), width=5).grid(column=0, row=3, padx=5, pady=5) 53 | #res = tk.StringVar() 54 | resText = tk.Text(monty,font=('Arial', 12), width=46, height=5) 55 | resText.grid(column=1, row=3, rowspan=3, columnspan=2, pady=5, sticky="n"+"s") 56 | #tk.Label(monty, height=5, textvariable=res, font=('Arial', 12), width=45, wraplength=400, justify='left', anchor='w').grid( 57 | # column=1, row=3, rowspan=3, columnspan=2, pady=5, sticky="n"+"s") 58 | #for child in monty.winfo_children(): 59 | # child.grid_configure(padx=5, pady=1) 60 | 61 | 62 | #win.iconbitmap('../img/searchans.ico') 63 | win.mainloop() 64 | -------------------------------------------------------------------------------- /src/startdriver.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | from selenium.webdriver.chrome.options import Options 3 | from publicfunc import COLOR 4 | from os import popen as os_popen 5 | 6 | class StartDriver(object): 7 | 8 | def __enter__(self): 9 | self.driver = StartDriver.startchrome() 10 | driver_pid = self.driver.service.process.pid 11 | self.chrome_pid = driver_pid 12 | return self 13 | 14 | def __exit__(self, exc_type, exc_val, exc_tb): 15 | os_popen('taskkill /F /T /PID '+str(self.chrome_pid)) 16 | ''' 17 | try: 18 | self.driver.quit() 19 | except KeyboardInterrupt: 20 | self.driver.quit() 21 | ''' 22 | 23 | if exc_type in [SystemExit, KeyboardInterrupt]: 24 | print(COLOR.NOTE, "QUIT!", COLOR.END) 25 | elif exc_type != None: 26 | print('程序遇到错误,请分析处理:') 27 | print('TYPE:'+str(exc_type)) 28 | print('VAL: '+str(exc_val)) 29 | print('TB: '+str(exc_tb)) 30 | return True 31 | 32 | # 启动chrome 33 | # debugarg:拓展启动选项 34 | @staticmethod 35 | def startchrome(debugarg=''): 36 | # 初始化 37 | chrome_options = Options() 38 | chrome_options.add_argument("--headless") 39 | chrome_options.add_argument('log-level=3') 40 | chrome_options.add_argument('–incognito') # 启动无痕/隐私模式 41 | chrome_options.add_argument('--ignore-certificate-errors') 42 | chrome_options.add_argument('--ignore-ssl-errors') 43 | chrome_options.add_argument("--mute-audio") 44 | chrome_options.add_argument('--disable-background-networking') 45 | chrome_options.add_experimental_option('excludeSwitches', ['enable-logging']) 46 | chrome_options.add_argument('--no-sandbox') 47 | chrome_options.add_argument('--disable-dev-shm-usage') 48 | chrome_options.add_argument('blink-settings=imagesEnabled=false') # 不加载图片, 提升速度 49 | chrome_options.add_argument('--disable-gpu') 50 | # chrome_options.add_argument('--single-process') 51 | chrome_options.add_argument('--disable-plugins') 52 | if debugarg != '': 53 | chrome_options.add_argument(debugarg) 54 | ## INFO = 0, 55 | ## WARNING = 1, 56 | ## LOG_ERROR = 2, 57 | ## LOG_FATAL = 3 58 | ## default is 0 59 | return webdriver.Chrome(options=chrome_options, executable_path=r"./chromedriver") 60 | # 在哪个目录执行就要在该目录下有chromedriver 61 | -------------------------------------------------------------------------------- /src/login_courses.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | ## 3 | # brief 执行单一账号的刷课前准备工作 4 | # details 通过账号信息登录、获取课程信息并输出、用户选择课程信息 并 实例化SC类执行刷课 5 | 6 | from requests import Session, utils 7 | from json import loads 8 | from re import sub, findall 9 | from selenium.webdriver.support import expected_conditions as EC 10 | from sys import argv,exit 11 | from os import popen as os_popen 12 | from singlecourse import SingleCourse as SC 13 | from publicfunc import Color 14 | from queryans import QueryAns 15 | from playmedia import PlayMedia 16 | from signal import signal, SIGINT, SIGTERM 17 | from startdriver import StartDriver 18 | 19 | COLOR = Color() 20 | 21 | 22 | class Login_courses(object): 23 | # 登录基类,账户信息,刷课选项 24 | def __init__(self, phone, passwd, pattern=0): 25 | self.account = phone 26 | self.password = passwd 27 | self.pattern = pattern 28 | 29 | class Login_courses_by_request(Login_courses): 30 | 31 | #driver=None 32 | 33 | def __init__(self, phone, passwd, pattern=0): 34 | super().__init__(phone, passwd, pattern) 35 | self.mysession = Session() 36 | 37 | # 登录成功后返回该账号对应的课程信息 38 | def _login_by_phone(self): 39 | #headers = { 40 | # 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36', 41 | # 'Referer': 'https://passport2.chaoxing.com/wlogin', 42 | # 'Host': 'passport2.chaoxing.com', 43 | # 'Connection': 'keep-alive', 44 | # 'Accept': 'application/json, text/javascript, */*; q=0.01', 45 | # 'Origin': 'https://passport2.chaoxing.com' 46 | #} 47 | print(COLOR.DISPLAY, 'check your phonenum:', self.account, COLOR.END) 48 | print(COLOR.DISPLAY, 'check your password:', self.password, COLOR.END) 49 | url = "https://passport2.chaoxing.com/mylogin" 50 | data = { 51 | 'msg': self.account, 52 | 'vercode': self.password 53 | } 54 | res = self.mysession.post(url, data=data).text # str 55 | # print(res) 56 | try: 57 | msg = loads(res) 58 | except: 59 | print(' login failed,', COLOR.END, 'please check your login_info') 60 | exit(1) 61 | if(msg['status'] == 'false'): 62 | print(COLOR.ERR, msg['mes'], end="") 63 | print(' login failed,', COLOR.END, 'please check your login_info') 64 | exit(1) 65 | else: 66 | return self._getcourses() 67 | 68 | def _getcourses(self): 69 | # print(msg) 70 | headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0', 71 | # 'X-Forwarded-For': ip, 72 | 'Host': 'mooc1-2.chaoxing.com', 73 | 'Connection': 'keep-alive', 74 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 75 | 'Referer': 'https://i.mooc.chaoxing.com', 76 | } 77 | self.mysession.get(url="https://i.mooc.chaoxing.com/space/index").text 78 | courses = self.mysession.get(url='https://mooc1-2.chaoxing.com/visit/courses', headers=headers).text 79 | # print(courses) 80 | # 正则处理 81 | courses = sub(r'[ \t\n]', '', courses) 82 | # info = findall(r'(.+?)

', courses) 83 | info = findall( 84 | r'(.+?)

', courses) 85 | del_lt = [] 86 | for i in range(len(info)): 87 | info[i] = list(info[i]) 88 | info[i][1] = sub(r' ', '', info[i][1]) 89 | if "结课模式" in info[i][2]: # 去掉有结课模式的课程 90 | del_lt.append(i) 91 | info[i][0] = info[i][0][0:info[i][0].find('\'')] # 获取课程主页url 92 | del info[i][2] 93 | for i in range(len(del_lt)): # 去掉有结课模式的课程 94 | del info[del_lt[i]-i] 95 | # print("info:"+str(info)) 96 | return info 97 | 98 | @staticmethod # 传入课程列表 输出课程 进行选课(1门) 选择0直接exit退出 选择范围外课程会陷入循环 99 | def _choose_course(courses_lt): 100 | # 选择课程,返回课程相对应的url和课程名称 101 | num = len(courses_lt) 102 | for i in range(num): 103 | print(COLOR.DISPLAY + '\t', i+1, '、', courses_lt[i][1] + COLOR.END) 104 | while 1: 105 | try: 106 | course_id = int(input('please select which course by order(0 is exit):')) 107 | if course_id > 0 and course_id <= len(courses_lt): 108 | return courses_lt[course_id-1] 109 | elif course_id == 0: 110 | return 0 111 | else: 112 | print(COLOR.ERR, ' invalid course order, please reinput!', COLOR.END) 113 | except: 114 | print(COLOR.ERR, ' invalid course order, please reinput!', COLOR.END) 115 | 116 | def work(self): 117 | courses_lt = self._login_by_phone() 118 | cookies = utils.dict_from_cookiejar(self.mysession.cookies) 119 | # utils.dict_from_cookiejar(cookies)把cookiejar对象转换为字典对象 120 | 121 | global chrome_pid 122 | with StartDriver() as chrome_driver: 123 | driver = chrome_driver.driver 124 | chrome_pid = chrome_driver.chrome_pid 125 | base_url = 'https://mooc1-1.chaoxing.com' 126 | driver.get(base_url) 127 | driver.delete_all_cookies() 128 | for k, v in cookies.items(): 129 | driver.add_cookie({'name': k, 'value': v}) 130 | # print(str(k),str(v)) 131 | # print(driver.get_cookies()) 132 | # driver.get(base_url+goal_url) 133 | if self.pattern == 1: 134 | print(COLOR.OK+' LOGIN_FINISHED'+COLOR.END) 135 | for url, name in courses_lt: 136 | print(COLOR.NOTE+' Course:'+name+COLOR.END) 137 | singlecourse = SC(driver, base_url+url, name, 0) 138 | singlecourse.work() 139 | else: 140 | while(1): 141 | goal = self._choose_course(courses_lt) 142 | if goal==0: 143 | exit(0) 144 | print(COLOR.OK+' LOGIN_FINISHED'+COLOR.END) 145 | singlecourse = SC(driver, base_url+goal[0], goal[1], self.pattern) 146 | singlecourse.work() 147 | 148 | chrome_pid=0 149 | 150 | def check_exit(signalnum,frame): 151 | global chrome_pid 152 | #if SYSTEM==0 and chrome_pid!=0: 153 | # if signalnum==CTRL_CLOSE_EVENT: 154 | # os_popen('taskkill /F /T /PID '+str(chrome_pid)) 155 | chrome_pid=0 156 | raise KeyboardInterrupt 157 | 158 | if __name__ == "__main__": 159 | 160 | signal(SIGINT,check_exit) 161 | signal(SIGTERM,check_exit) 162 | # print(argv) 163 | phone = argv[1] 164 | passwd = argv[2] 165 | pattern = { 166 | 'single': 0, 167 | 'fullauto': 1, 168 | 'control': 2, 169 | 'debug': 3 170 | } 171 | pattern = pattern[argv[3]] 172 | rate = eval(argv[4]) 173 | noans_num = eval(argv[5]) 174 | 175 | QueryAns.noans_num=noans_num 176 | PlayMedia.rate=rate 177 | 178 | try: 179 | process = Login_courses_by_request(phone, passwd, pattern) 180 | process.work() 181 | finally: 182 | if chrome_pid!=0: 183 | os_popen('taskkill /F /T /PID '+str(chrome_pid)) -------------------------------------------------------------------------------- /src/queryans.py: -------------------------------------------------------------------------------- 1 | import re 2 | from time import sleep 3 | from urllib import parse 4 | from requests import post, get as requestget 5 | from traceback import format_exc 6 | from ast import literal_eval 7 | 8 | 9 | class QueryAns(object): 10 | que_type = ['单选题', '多选题', '填空题', '判断题', '简答题', '名词解释题', '论述题', '计算题', '其他', 11 | '分录题', '资料题', '连线题', '', '排序题', '完形填空', '阅读理解', '', '', '口语题', '听力题'] 12 | pd_opt = ['正确', '错误', '√', '×', '对', '错', '是', '否', 'T', 'F', 'ri', 'wr', 'true', 'false'] 13 | instance = None 14 | 15 | api_priority = { 16 | # 接口对应取值越低 优先级越高 会优先查询该接口 (取值范围不限) 17 | 'greasyfork': 0, # 项目地址 可以star支持:https://github.com/CodFrm/cxmooc-tools 18 | # 'blog.vcing.top': -1, # 需要自行配置token, 详见https://github.com/destoryD/chaoxing-api 19 | # 已失效: 20 | # 'wangketiku.com': 3.5 21 | # 'api.xmlm8.com': 2.5, 22 | } 23 | noans_num = 5 24 | noans_flag = ['暂未搜', '暂无答案', '奋力撰写', '收录中', '日再来', '李恒', '未搜索到', '未搜到', '数据库异常', '请输入题目'] 25 | 26 | def __new__(cls, *args, **kwargs): 27 | if cls.instance is None: 28 | cls.instance = super().__new__(cls) 29 | return cls.instance 30 | 31 | def __init__(self, h5page='', *, question='', type='', course='', courseID=''): 32 | # 实例化方式:QueryAns(course,h5page)页面源码 操作:将处理源码内全部题目 33 | # QueryAns(course,**info)课程-题目-类型 将处理单个题目 34 | self.course = course 35 | self.courseID = courseID 36 | self.no_ans_num = 0 37 | self.que_lt = [] 38 | self.ans_ul = [] 39 | self.que = "" 40 | self.que_ori = "" 41 | self.que_type = "" 42 | if h5page == "": 43 | self.que_ori = question 44 | self.que_lt.append((type, question)) # 有可能为空 45 | self._re_que_lt() 46 | else: 47 | self._re_h5page(h5page) # get que_lt,ans_ul 48 | 49 | def work(self): 50 | if len(self.ans_ul) == 0: 51 | return self.work4single() 52 | else: 53 | return self.work4page() 54 | 55 | def work4single(self): 56 | if QueryAns.que_type.count(self.que_lt[0][0]) == 0: 57 | self.que_type = 8 58 | else: 59 | self.que_type = QueryAns.que_type.index(self.que_lt[0][0]) 60 | 61 | # 访问查题接口获取答案 62 | self.que = self.que_lt[0][1] 63 | return self._query_ans() 64 | 65 | def work4page(self): 66 | # 答案序号列表 67 | ans_order = [] 68 | for i in range(1, len(self.ans_ul) + 1): 69 | 70 | # 问题类型 71 | if QueryAns.que_type.count(self.que_lt[i-1][0]) == 0: 72 | self.que_type = 8 73 | else: 74 | self.que_type = QueryAns.que_type.index(self.que_lt[i-1][0]) 75 | if self.que_type not in [0, 1, 3]: # 非单选、多选、判断题的话只保存不作答 76 | self.no_ans_num = QueryAns.noans_num 77 | 78 | # 访问查题接口获取答案 79 | self.que = self.que_lt[i - 1][1] 80 | ans = self._query_ans() 81 | sleep(3) 82 | # ans为0,未获取到答案 83 | if ans == 0: 84 | ans_order.append([0]) # 服务器异常,默认选1 85 | continue 86 | 87 | ansopt = re.findall(r'?([\w\W]+?)?', self.ans_ul[i - 1]) # 当前题目 选项列表 88 | for ansopt_index in range(0, len(ansopt)): 89 | ansopt[ansopt_index] = re.sub(r'<.+?>', '', ansopt[ansopt_index]) 90 | 91 | if ans in QueryAns.pd_opt: # 判断题 92 | ans_order.append([(QueryAns.pd_opt.index(ans)) % 2 + 1]) 93 | elif ans in ['A', 'B', 'C', 'D', 'E', 'F']: # 直接返回选项的单选题 94 | if (ord(ans) - ord('A') + 1) > len(ansopt): 95 | ans_order.append([1]) 96 | else: 97 | ans_order.append([ord(ans) - ord('A') + 1]) 98 | else: 99 | now_que_order = [] 100 | for opt in ansopt: 101 | if opt in ans: 102 | now_que_order.append(ansopt.index(opt) + 1) 103 | if len(now_que_order) == 0: 104 | now_que_order.append(0) # 无匹配答案默认选A 105 | self.no_ans_num += 1 106 | ans_order.append(now_que_order) 107 | # if ansopt.count(ans) == 1: 108 | # ans_order.append(ansopt.index(ans)+1) 109 | # else: 110 | # ans_order.append(1) 111 | 112 | #print("no_ans_num:"+str(self.no_ans_num)+' '+str(QueryAns.noans_num)) 113 | if self.no_ans_num < QueryAns.noans_num: 114 | for index in range(0, len(ans_order)): 115 | if ans_order[index] == [0]: 116 | ans_order[index] = [1] 117 | return 2, ans_order 118 | else: 119 | return 1, ans_order # 返回一个列表,列表内的每一项是每个题目的答案列表 120 | 121 | def _query_ans(self): 122 | # 根据api优先级排序,然后访问获取答案 123 | api_dic = { 124 | 'api.xmlm8.com': self.SearchAns_GUI_API, 125 | 'blog.vcing.top': self.BlogVCing_API, 126 | 'greasyfork': self.GreasyFork_Group_API, 127 | 'wangketiku.com': self.WangKeTiKu_API 128 | } 129 | url_order = sorted(QueryAns.api_priority.items(), key=lambda x: x[1], reverse=False) 130 | # print(url_order) [('',),('',)...] 131 | 132 | res = "" 133 | for url in url_order: 134 | res = api_dic[url[0]]() 135 | # print(url) 136 | # print(res) 137 | if res == 0 or res == '': 138 | res = 0 139 | continue 140 | flag = 1 141 | for item in QueryAns.noans_flag: 142 | if item in str(res): 143 | flag = 0 144 | res = 0 145 | break 146 | if flag == 0: 147 | continue 148 | else: 149 | break 150 | 151 | self.no_ans_num += 1 152 | return res 153 | 154 | def SearchAns_GUI_API(self): 155 | # 以原问题访问准确率更高___h5源码实例化的时候尽量不使用该方式 156 | #url = 'http://api.xmlm8.com/tk.php?t='+parse.quote(re.sub(r'[ \t\n]','',self.que_ori)) 157 | url = 'http://api.xmlm8.com/tk.php?t='+parse.quote(self.que_ori) 158 | try: 159 | ret_da = literal_eval(requestget(url).text) 160 | # print(ret_da) 161 | # print("que:"+ret_da['tm']+'\n'+"ans:"+ret_da['da']) 162 | return ret_da['da'] 163 | except KeyboardInterrupt: 164 | raise KeyboardInterrupt 165 | except: 166 | return 0 167 | 168 | def GreasyFork_Group_API(self): 169 | # NOTE: 项目地址 可以star支持:https://github.com/CodFrm/cxmooc-tools 170 | # url = 'http://mooc.forestpolice.org/cx/0/' #WYN 171 | url = 'http://cx.icodef.com/wyn-nb?v=3' 172 | 173 | # def _prepare_query(index): 174 | # data = { 175 | # 'courseId': '', 176 | # 'classId': '', 177 | # 'oldWorkId': '', 178 | # 'workRelationId': '' 179 | # } 180 | # for key in data.keys(): 181 | # data[key] = self.driver.execute_script('return document.getElementById(arguments[0]).value', key) 182 | # sleep(0.1) 183 | # #print(data) 184 | # url = 'http://mooc.forestpolice.org/report/cx/' 185 | # requestget(url, data=data) 186 | # sleep(1) 187 | # _prepare_query() 188 | 189 | data = { 190 | 'question': self.que, 191 | 'type': str(type), 192 | 'id': ""# self.driver.execute_script('return document.getElementById(arguments[0]).value', key) 193 | } 194 | 195 | def post_url(url, data): 196 | headers = { 197 | 'Content-type': 'application/x-www-form-urlencoded', 198 | 'Authorization': '' 199 | } 200 | timeout = 30 201 | r = post(url, data, headers=headers, timeout=timeout) 202 | # print(r.text) 203 | status = r.status_code # int 204 | # 200 且 code=1 响应成功 205 | # 200 且 code!=1 服务器繁忙 206 | # 403 请求过于频繁 207 | # 其他 服务器异常 208 | if status == 200: 209 | try: 210 | res = literal_eval(r.text.strip(' \n')) 211 | # if res['code'] == 1: 212 | # print(' 响应成功\n') 213 | # print(res['data']) 214 | try: 215 | return res['data'] 216 | except KeyboardInterrupt: 217 | raise KeyboardInterrupt 218 | except: 219 | return res['answer'] 220 | except KeyboardInterrupt: 221 | raise KeyboardInterrupt 222 | except: 223 | return 0 224 | # except: 225 | # print('=======') 226 | # print(format_exc()) 227 | # return 0 228 | # else: 229 | # print(' 服务器繁忙\n') 230 | # elif status == 403: 231 | # print(' 操作过于频繁\n') 232 | # else: 233 | # print(' 服务器异常\n') 234 | return 0 235 | 236 | res = post_url(url, data) 237 | for item in QueryAns.noans_flag: 238 | if item in str(res): 239 | res = 0 240 | break 241 | if res != 0: 242 | return res 243 | return res 244 | 245 | def WangKeTiKu_API(self): 246 | # print(self.que) 247 | url = 'http://www.wangketiku.com/getquestion.php?question='+re.sub(r'[ \t\n]', '', self.que_ori) 248 | headers = { 249 | 'Host': 'www.wangketiku.com', 250 | 'Referer': 'http://www.wangketiku.com/?', 251 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36' 252 | } 253 | try: 254 | ret = literal_eval(requestget(url, headers=headers).text)['answer'] 255 | # print(ret) 256 | ret = re.sub('[ \t\n:]', '', ret) 257 | ret = re.sub(r'题目(.+?)答案', '', ret) 258 | index = ret.rfind('选项') 259 | if index != -1: 260 | ret = ret[:index] 261 | ret_lt = ret.split('备选') 262 | del ret_lt[0] 263 | ans = "" 264 | for item in ret_lt: 265 | if '李恒道不会做这道题' in item or '暂无答案' in item: 266 | continue 267 | else: 268 | ans += item+'#' 269 | return ans 270 | except KeyboardInterrupt: 271 | raise KeyboardInterrupt 272 | except: 273 | return 0 274 | 275 | def BlogVCing_API(self): 276 | # github:https://github.com/destoryD/chaoxing-api 277 | # NOTE: 需要自行加群申请token然后更改此处token值 278 | token = "test123" 279 | url = 'http://api.gochati.cn/htapi.php?q='+re.sub(r'[ \t\n]', '', self.que_ori)+'&token='+str(token) 280 | try: 281 | ret = requestget(url, timeout=2).text 282 | index = ret.find('答案') 283 | if index != -1: 284 | ret = ret[index+3:ret.find('剩余次数')] 285 | ret = re.sub(r'
', '', ret) 286 | # print(ret) 287 | return ret 288 | else: 289 | return 0 290 | except KeyboardInterrupt: 291 | raise KeyboardInterrupt 292 | except: 293 | return 0 294 | 295 | def _re_h5page(self, text): 296 | # 去空格、tab、换行 297 | regex = re.compile(r'[ \t\n]') 298 | text = regex.sub('', text) 299 | 300 | # 提取列表self.que_lt 并处理 301 | regex = re.compile(r'\u3010([\u4e00-\u9fa5]+?)\u3011([\w\W]+?)[ \t\n]*') 302 | self.que_lt = regex.findall(text) 303 | self._re_que_lt() 304 | 305 | # 提取得到self.ans_ul 306 | self.ans_ul = re.findall(r'', text) # 答案列表 307 | 308 | def _re_que_lt(self): 309 | # self.que_lt[i][0]是题型,self.que_lt[i][1]是问题 310 | for i in range(0, len(self.que_lt)): 311 | self.que_lt[i] = list(self.que_lt[i]) 312 | self.que_lt[i][1] = re.sub(r'<.+?>', '', self.que_lt[i][1]) 313 | self.que_lt[i][1] = re.sub(r'[(](.*?)[)]', '()', self.que_lt[i][1]) 314 | self.que_lt[i][1] = re.sub(r' ', '', self.que_lt[i][1]) 315 | self.que_lt[i][1] = re.sub(r'\uff08(.*?)\uff09', '', self.que_lt[i][1]) 316 | self.que_lt[i][0] = re.sub(r'[ \t\n]+', '', self.que_lt[i][0]) 317 | self.que_lt[i][1] = re.sub(r'[ \t\n]+', '', self.que_lt[i][1]) 318 | self.que_lt[i][1] = re.sub(r'[\。]', '', self.que_lt[i][1]) 319 | # print(self.que_lt) 320 | 321 | 322 | def test(): 323 | infodic = { 324 | 'question': 'question', 325 | 'type': 'type' 326 | } 327 | QA = QueryAns(**infodic) 328 | print(QA.work()) 329 | 330 | 331 | if __name__ == "__main__": 332 | test() 333 | -------------------------------------------------------------------------------- /src/playmedia.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.support.wait import WebDriverWait 2 | from selenium.webdriver.common.action_chains import ActionChains 3 | from selenium.webdriver.support import expected_conditions as EC 4 | from selenium.webdriver.common.by import By 5 | from time import sleep 6 | from bs4 import BeautifulSoup 7 | from sys import stdout 8 | from publicfunc import Color 9 | from re import search as re_search 10 | 11 | COLOR=Color() 12 | 13 | 14 | class PlayMedia(object): 15 | 16 | rate=1 17 | instance=None 18 | 19 | def __new__(cls,*args,**kwargs): 20 | if cls.instance is None: 21 | cls.instance=super().__new__(cls) 22 | return cls.instance 23 | 24 | def __init__(self,driver,out_fp=stdout): 25 | self.driver=driver 26 | self._out_fp=out_fp 27 | 28 | def play_media(self,url): 29 | wait = WebDriverWait(self.driver, 30) 30 | action_chains = ActionChains(self.driver) 31 | 32 | self.driver.get(url) 33 | # 点击视频确保进入视频界面(若出现问题增多可以改换bs4或者re分析) 34 | media_tag = ['@title="视频"', '@title="视频 "', '@title="微课"', 'last()-1'] 35 | try: 36 | wait.until(EC.presence_of_element_located( 37 | (By.XPATH, '//div[@class="left"]/div/div[@class="main"]/div[@class="tabtags"]'))) 38 | for tag in media_tag: 39 | try: 40 | bt = self.driver.find_element_by_xpath( 41 | '//div[@class="left"]/div/div[@class="main"]/div[@class="tabtags"]/span['+tag+']') 42 | break 43 | except KeyboardInterrupt: 44 | raise KeyboardInterrupt 45 | except: 46 | pass 47 | sleep(5) 48 | self.driver.execute_script("arguments[0].scrollIntoView();arguments[0].click();", bt) 49 | except KeyboardInterrupt: 50 | raise KeyboardInterrupt 51 | except: 52 | pass 53 | 54 | try: 55 | wait.until(EC.presence_of_element_located((By.XPATH,'//div[@class="switchbtn"]'))) 56 | switch_btn=self.driver.find_element_by_xpath('//div[@class="switchbtn"]') 57 | action_chains.move_to_element(switch_btn) 58 | switch_btn.click() 59 | except KeyboardInterrupt: 60 | raise KeyboardInterrupt 61 | except: 62 | pass 63 | 64 | try: 65 | wait.until(EC.presence_of_element_located((By.XPATH, '//iframe'))) 66 | iframe = self.driver.find_element_by_xpath('//iframe') 67 | self.driver.switch_to.frame(iframe) 68 | sleep(1) 69 | except KeyboardInterrupt: 70 | raise KeyboardInterrupt 71 | except: 72 | print(COLOR.NOTE, ' no videos,continue~', COLOR.END, file=self._out_fp) 73 | #log_fp.write(' no videos,continue~\n') 74 | return 75 | 76 | # 多视频处理 77 | try: 78 | video_num = self.driver.execute_script( 79 | "window.scrollTo(0,document.body.scrollHeight);return document.getElementsByClassName('ans-job-icon').length") 80 | except KeyboardInterrupt: 81 | raise KeyboardInterrupt 82 | except: 83 | video_num = self.driver.execute_script( 84 | "return document.getElementsByClassName('ans-job-icon').length") 85 | try: 86 | self.driver.execute_script("window.scrollTo(0,0)") 87 | except KeyboardInterrupt: 88 | raise KeyboardInterrupt 89 | except: 90 | pass 91 | 92 | wait.until(EC.presence_of_all_elements_located((By.XPATH, '//div[@class="ans-cc"]'))) 93 | ans_cc = self.driver.find_element_by_xpath('//div[@class="ans-cc"]') 94 | for i in range(3): 95 | try: 96 | h5_text = ans_cc.get_attribute('innerHTML') 97 | except KeyboardInterrupt: 98 | raise KeyboardInterrupt 99 | except: 100 | sleep(1) 101 | video_road = PlayMedia.get_road(h5_text, video_num) # bs4处理得到各个视频路径 102 | 103 | print(COLOR.DISPLAY, ' there are ' + str(video_num) + ' media in this section:', COLOR.END, file=self._out_fp) 104 | #log_fp.write(' there are ' + str(video_num) + ' videos in this section:\n') 105 | 106 | # 开始播放所有视频 107 | first_road = '//div[@class="ans-cc"]' 108 | 109 | for v_num in range(1, video_num + 1): 110 | # log 111 | #self.driver.refresh() 112 | self.driver.switch_to.default_content() 113 | for i in range(5): 114 | try: 115 | wait.until(EC.presence_of_element_located((By.XPATH, '//iframe'))) 116 | iframe = self.driver.find_element_by_xpath('//iframe') 117 | self.driver.switch_to.frame(iframe) 118 | break 119 | except KeyboardInterrupt: 120 | raise KeyboardInterrupt 121 | except: 122 | sleep(i+0.5) 123 | 124 | print(COLOR.DISPLAY, ' go ' + str(v_num) + ':', COLOR.END, file=self._out_fp) 125 | #log_fp.write(' go ' + str(v_num) + ':\n') 126 | # 拖动滚动条 127 | #self.driver.execute_script("window.scrollTo(0,arguments[0])", 400 + 700 * (v_num - 1)) 128 | sleep(2) 129 | 130 | goal_road = first_road + video_road[v_num - 1] 131 | # 查看是否有任务点标识并查看是或否已经完成该任务点 132 | try: 133 | flag = self.driver.find_element_by_xpath(goal_road) 134 | self.driver.execute_script( 135 | '''var goal=document.evaluate(arguments[0],document).iterateNext();goal.scrollIntoView();''', goal_road) 136 | sleep(5) 137 | icon_flag = 1 138 | nowflag = flag.get_attribute('class') 139 | if 'finished' in nowflag: 140 | print(COLOR.OK + ' Well! the video is already finished! continue~' + COLOR.END, file=self._out_fp) 141 | #log_fp.write(' Well! the video is already finished! continue~' + '\n') 142 | self._end = 0 143 | # 如果视频任务已完成,访问下一个视频 144 | continue 145 | except KeyboardInterrupt: 146 | raise KeyboardInterrupt 147 | except: 148 | #print(traceback.format_exc()) 149 | icon_flag = 0 150 | 151 | #print(1) 152 | # try: 153 | iframe_flag = 0 154 | for i in range(10): 155 | try: 156 | wait.until(EC.presence_of_element_located((By.XPATH, goal_road + '/iframe'))) 157 | iframe = self.driver.find_element_by_xpath(goal_road + '/iframe') 158 | self.driver.switch_to.frame(iframe) 159 | iframe_flag = 1 160 | sleep(2) 161 | break 162 | except KeyboardInterrupt: 163 | raise KeyboardInterrupt 164 | except: 165 | #print(traceback.format_exc()) 166 | # log 167 | sleep(i+1) 168 | if iframe_flag == 0: 169 | print(COLOR.ERR+" can't into the video,continue"+COLOR.END, file=self._out_fp) 170 | continue 171 | 172 | #print(2) 173 | 174 | try: 175 | ppt_num = eval(self.driver.execute_script("return document.getElementsByClassName('all')[0].innerText")) 176 | for i in range(0, ppt_num): 177 | self.driver.execute_script("document.getElementsByClassName('mkeRbtn')[0].click()") 178 | sleep(1) 179 | continue 180 | except KeyboardInterrupt: 181 | raise KeyboardInterrupt 182 | except: 183 | pass 184 | 185 | #print(2.5) 186 | 187 | # 通过js代码开始视频播放 188 | play_ok = 0 189 | for i in range(3): 190 | try: 191 | self.driver.execute_script( 192 | """ 193 | var video=document.querySelector('video');video.scrollIntoView();video.play(); 194 | video.onmouseout=function(){return false;} 195 | """) 196 | play_ok = 1 197 | sleep(2) 198 | self.driver.execute_script("document.querySelector('video').autoplay=true;") 199 | self.driver.execute_script("document.querySelector('video').play();") 200 | self.driver.execute_script( 201 | "document.querySelector('video').playbackRate=arguments[0];document.querySelector('video').defaultPlaybackRate=arguments[0]", PlayMedia.rate) 202 | sleep(1) 203 | #self.driver.execute_script("document.querySelector('video').load();") 204 | break 205 | except KeyboardInterrupt: 206 | raise KeyboardInterrupt 207 | except: 208 | #print(format_exc()) 209 | sleep(i+1) 210 | #print(3) 211 | 212 | audio = 0 213 | if play_ok == 0: 214 | for i in range(3): 215 | try: 216 | self.driver.execute_script( 217 | "var audio=document.querySelector('audio');audio.scrollIntoView();audio.play();audio.onmouseout=function(){return false;}") 218 | play_ok = 1 219 | audio = 1 220 | self.driver.execute_script("document.querySelector('audio').autoplay=true;") 221 | self.driver.execute_script( 222 | "document.querySelector('audio').playbackRate=arguments[0];document.querySelector('audio').defaultPlaybackRate=arguments[0]", PlayMedia.rate) 223 | #self.driver.execute_script("document.querySelector('audio').load();") 224 | break 225 | except KeyboardInterrupt: 226 | raise KeyboardInterrupt 227 | except: 228 | sleep(i+1) 229 | if audio == 1: 230 | media_type = 'audio' 231 | else: 232 | media_type = 'video' 233 | 234 | #print(media_type) 235 | 236 | if play_ok == 0: 237 | # 未播放成功 238 | self.driver.switch_to.parent_frame() 239 | print(COLOR.DISPLAY+' this is not a media, go ahead!'+COLOR.END, file=self._out_fp) 240 | #log_fp.write(" this is not a video, go ahead!\n") 241 | continue 242 | else: 243 | # 开倍速 & 获取时间信息 244 | sleep(2) 245 | for i in range(5): 246 | total_tm = self.driver.execute_script( 247 | "return document.querySelector(arguments[0]).duration", media_type) 248 | #print(total_tm) 249 | now_tm = self.driver.execute_script( 250 | "return document.querySelector(arguments[0]).currentTime", media_type) 251 | #print(now_tm) 252 | self.driver.execute_script("document.querySelector(arguments[0]).play();", media_type) 253 | if total_tm != None and now_tm != None: 254 | break 255 | else: 256 | sleep(i+1) 257 | total_tm = int(total_tm) 258 | now_tm = int(now_tm) 259 | need_tm = total_tm-now_tm 260 | print(" now_tm:", now_tm, '\t', "total_tm:", total_tm, '\t', "need_tm:", need_tm, file=self._out_fp) 261 | 262 | #print(4) 263 | 264 | real_time = 0 265 | while 1: 266 | real_time += 10 267 | try: 268 | now_tm = self.driver.execute_script( 269 | "return document.querySelector(arguments[0]).currentTime", media_type) 270 | need_tm = total_tm-int(now_tm) 271 | self.driver.execute_script("document.querySelector(arguments[0]).play();", media_type) 272 | except KeyboardInterrupt: 273 | raise KeyboardInterrupt 274 | except: 275 | pass 276 | # 交互 277 | progress = (total_tm-need_tm)*100/total_tm 278 | print(COLOR.OK+" progress:{0:.2f}%\trest:{1} ".format(progress, 279 | need_tm)+COLOR.END, file=self._out_fp, end="\r") 280 | self._out_fp.flush() 281 | 282 | # 剩余时间<5min则间隔性检验任务是否已完成 283 | if (icon_flag == 1 and need_tm <= 300): 284 | self.driver.switch_to.parent_frame() 285 | flag = self.driver.find_element_by_xpath(goal_road) 286 | nowflag = flag.get_attribute('class') 287 | self.driver.switch_to.frame(self.driver.find_element_by_xpath(goal_road + '/iframe')) 288 | if 'finished' in nowflag: 289 | print(COLOR.OK, ' Well!the video is finished ahead of time! continue~', COLOR.END, file=self._out_fp) 290 | #log_fp.write(' Well!the video is finished ahead of time! continue~' + '\n') 291 | sleep(10) 292 | break 293 | 294 | if need_tm <= 2 or real_time > (total_tm+100): 295 | print(COLOR.OK, ' Well!the video is finished! continue~', COLOR.END, file=self._out_fp) 296 | #log_fp.write(' Well!the video is finished! continue~' + '\n') 297 | sleep(10) 298 | break 299 | 300 | # 自动检测答题 301 | pre = 1 # 选项默认值 302 | try: 303 | uls = self.driver.find_element_by_xpath( 304 | '//div[@class="x-container ans-timelineobjects x-container-default"]/span/div/div/ul') 305 | que_type = self.driver.find_element_by_xpath( 306 | '//div[@class="ans-videoquiz-title"]').get_attribute('textContent') 307 | #log_fp.write('que_type:' + que_type + '\n') 308 | que_type = re_search(r'[[]([\w\W]+?)[]]', que_type).group(1) 309 | #log_fp.write(' monitor question,' + que_type + '\n') 310 | if "多选题" in que_type: 311 | # print(uls.find_elements_by_xpath('//li[@class="ans-videoquiz-opt"]')) 312 | opt_num = len(uls.find_elements_by_xpath('//li[@class="ans-videoquiz-opt"]')) # 选项个数 313 | #print(opt_num,file=self._out_fp) 314 | for opt_i in range(2, opt_num + 1): # 选择个数2,3,4,…… 315 | fin_que = 1 316 | for opt_j in range(1, opt_num - opt_i + 2): # 起始位置 317 | #print(' select:',file=self._out_fp) 318 | for opt_k in range(0, opt_i): # 个数 319 | option = uls.find_element_by_xpath('//li[' + str(opt_j + opt_k) + ']/label/input') 320 | self.driver.execute_script("arguments[0].click();", option) 321 | sleep(5) 322 | bn = self.driver.find_element_by_xpath( 323 | '//div[@class="x-container ans-timelineobjects x-container-default"]/span/div/div/div[2]') 324 | self.driver.execute_script("arguments[0].click();", bn) 325 | try: 326 | self.driver.switch_to_alert().accept() 327 | except: 328 | fin_que = 0 329 | break 330 | try: 331 | while 1: # 多选题答错会弹出不止一个alert 332 | self.driver.switch_to_alert().accept() 333 | except: 334 | sleep(0.5) 335 | 336 | for opt_k in range(0, opt_i): # 个数 337 | option = uls.find_element_by_xpath('//li[' + str(opt_j + opt_k) + ']/label/input') 338 | self.driver.execute_script("arguments[0].click();", option) 339 | sleep(5) 340 | bn = self.driver.find_element_by_xpath( 341 | '//div[@class="x-container ans-timelineobjects x-container-default"]/span/div/div/div[2]') 342 | self.driver.execute_script("arguments[0].click();", bn) 343 | try: 344 | while 1: # 多选题答错会弹出不止一个alert 345 | self.driver.switch_to_alert().accept() 346 | except: 347 | sleep(0.5) 348 | sleep(0.5) 349 | 350 | if fin_que == 0: 351 | break 352 | #log_fp.write(' solve the question\n') 353 | sleep(10) 354 | else: 355 | while 1: 356 | try: 357 | option = uls.find_element_by_xpath('//li[' + str(pre) + ']/label/input') 358 | self.driver.execute_script("arguments[0].click();", option) 359 | # action_chains.move_to_element(option) 360 | # option.click() 361 | #log_fp.write(' select ' + chr(pre + 64) + '\n') 362 | bn = self.driver.find_element_by_xpath( 363 | '//div[@class="x-container ans-timelineobjects x-container-default"]/span/div/div/div[2]') 364 | self.driver.execute_script("arguments[0].click();", bn) 365 | # action_chains.move_to_element(bn) 366 | # bn.click() 367 | # action_chains.click(bn) 368 | try: 369 | while 1: 370 | self.driver.switch_to_alert().accept() 371 | except KeyboardInterrupt: 372 | raise KeyboardInterrupt 373 | except: 374 | sleep(0.3) 375 | pre += 1 376 | except KeyboardInterrupt: 377 | raise KeyboardInterrupt 378 | except: 379 | #log_fp.write(' solve the question\n') 380 | sleep(10) 381 | break 382 | except KeyboardInterrupt: 383 | raise KeyboardInterrupt 384 | except: # 10s延时 385 | sleep(10) 386 | 387 | print(COLOR.OK+' finish the video '+COLOR.END, file=self._out_fp) 388 | #return 0 # 只要成功执行到这里就置end为0 389 | 390 | ## 391 | # brief 获取任务点xpath路径 392 | # details 通过bs4处理h5文本获取xpath 393 | @staticmethod 394 | def get_road(text, num): 395 | # print(text) 396 | soup = BeautifulSoup('' + text + '', 'html.parser') 397 | # 'lxml'和'html5lib'会解析错误,把

提前 398 | # print(soup.prettify()) 399 | # video_lt = soup.find_all('iframe')#视频不一定全部有效 400 | video_lt = soup.find_all(class_='ans-job-icon') 401 | road_lt = [] 402 | if len(video_lt) < num: 403 | num = len(video_lt) 404 | for i in range(0, num): 405 | road = '' 406 | for parent in video_lt[i].parents: 407 | if parent.name == 'body': 408 | break 409 | index = '[' + str(len([x for x in parent.previous_siblings if x.name == parent.name]) + 1) + ']' 410 | road = '/' + parent.name + index + road 411 | road_lt.append(road) 412 | #log_fp.write(' video road:' + str(road_lt) + '\n') 413 | return road_lt 414 | -------------------------------------------------------------------------------- /src/singlecourse.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | ## 3 | # brief 单账号下单课程任务类 4 | 5 | from selenium.webdriver.support.wait import WebDriverWait 6 | from selenium.webdriver.support import expected_conditions as EC 7 | from selenium.webdriver.common.by import By 8 | from selenium.webdriver.common.action_chains import ActionChains 9 | from bs4 import BeautifulSoup 10 | from time import sleep 11 | import time 12 | from publicfunc import Color 13 | from playmedia import PlayMedia 14 | from queryans import QueryAns 15 | from sys import stdout 16 | from PIL import Image 17 | 18 | COLOR = Color() 19 | 20 | 21 | # 单账号单课程自动化 22 | 23 | 24 | class SingleCourse(object): 25 | 26 | # 实例化该类需传入3-5个参数:已经登录的driver,章节名称及对应的url,运行模式以及视频速率 27 | def __init__(self, driver, menu_url, course_name, pattern=0, out_fp=stdout): 28 | self.driver = driver 29 | self.menu_url = menu_url 30 | self.course_name = course_name 31 | self.courseID = '' 32 | self.pattern = pattern 33 | # self.rate=rate 34 | self._setflag() 35 | self._out_fp = out_fp if out_fp != None else stdout 36 | self.levelclass = ['leveltwo', 'levelthree', 'levelfour', 'levelfive'] 37 | 38 | 39 | def _setflag(self): 40 | self.ch_se_lt = [] 41 | self.retry_dic = {} 42 | self._err_lt = [] # 错误章节列表 43 | #self._chapter = 0 44 | #self._section = 0 45 | #self._subsection = 0 46 | #self._end = 0 47 | self._que_server_flag = 0 # 1正常 0异常 48 | 49 | def work(self): 50 | if self.pattern == 0: 51 | self._perform_model0() # auto模式 52 | elif self.pattern == 2: 53 | self._perform_model2() # control模式 54 | elif self.pattern == 3: 55 | self._perform_model3() 56 | self._out_fp.flush() 57 | 58 | def _get_chapter_section(self): 59 | action_chains = ActionChains(self.driver) 60 | try: 61 | self.driver.get(self.menu_url) 62 | self._bs4_menu_page() 63 | except KeyboardInterrupt: 64 | raise KeyboardInterrupt 65 | except: 66 | # 未读消息通知框 67 | self.driver.get(self.menu_url) 68 | WebDriverWait(self.driver, 10).until(EC.presence_of_element_located( 69 | (By.XPATH, '//div[@class="weedialog bradius"]'))) 70 | sleep(2) 71 | dialog = self.driver.find_element_by_xpath('//div[@class="weedialog bradius"]/div/a') 72 | sleep(2) 73 | action_chains.move_to_element(dialog) 74 | dialog.click() 75 | self._bs4_menu_page() 76 | sleep(1) 77 | 78 | print(COLOR.WARN, '未完成任务章节:', COLOR.END, file=self._out_fp) 79 | # , str(ch_se_lt), file=self._out_fp) 80 | for item in self.ch_se_lt: 81 | print('\t'+str(item[0]), file=self._out_fp) 82 | if str(item[0]) not in self.retry_dic.keys(): 83 | self.retry_dic[str(item[0])] = 0 84 | 85 | def _midprocess(self, unit, level): 86 | # 课程目录分析的中间过程 87 | try: 88 | # item = unit.find(class_='clearfix') 89 | items=unit.find_all(class_='clearfix') 90 | for item in items: 91 | title = item.find(class_='chapterNumber').string+'|-|' # +str(level) 92 | title += item.find(class_='articlename').attrs['title'] 93 | icon = item.find(class_='icon').em.attrs['class'] 94 | #print(title+' '+str(icon)) # debuging 95 | # "display:inline-block;" 96 | if 'orange' in icon or 'blank' in icon: # 未完成 97 | tup = (title, item.a.attrs['href']) 98 | if tup not in self.ch_se_lt: 99 | self.ch_se_lt.append(tup) 100 | except KeyboardInterrupt: 101 | raise KeyboardInterrupt 102 | except: 103 | pass 104 | 105 | sub_lt = unit.find_all(class_=self.levelclass[level]) 106 | 107 | for item in sub_lt: 108 | self._midprocess(item, level+1) 109 | 110 | def _bs4_menu_page(self, page_source=''): 111 | # 对课程目录页分析,得到课程目录信息 以及 未完成任务章节 112 | self.ch_se_lt = [] # (order,url) 113 | if page_source == "": 114 | page_source = self.driver.page_source 115 | soup = BeautifulSoup(page_source, 'html.parser') 116 | 117 | timeline = soup.find(class_='timeline') 118 | units_lt = timeline.find_all(class_='units') 119 | for unit in units_lt: 120 | # unit.h2.a.attrs['title'] 121 | self._midprocess(unit, 0) 122 | 123 | # print(str(self.ch_se_lt)) 124 | 125 | def _ans_question(self): 126 | # 点击章节测验 127 | #action_chains = ActionChains(self.driver) 128 | self.driver.switch_to.default_content() 129 | sleep(2) 130 | try: 131 | bt = self.driver.find_element_by_xpath( 132 | '//div[@class="left"]/div/div[@class="main"]/div[@class="tabtags"]/span[@title="章节测验"]') 133 | except KeyboardInterrupt: 134 | raise KeyboardInterrupt 135 | except: 136 | try: 137 | bt = self.driver.find_element_by_xpath( 138 | '//div[@class="left"]/div/div[@class="main"]/div[@class="tabtags"]/span[last()]') 139 | except KeyboardInterrupt: 140 | raise KeyboardInterrupt 141 | except: # 还可能没有标签页 142 | pass 143 | 144 | try: 145 | sleep(3) 146 | self.driver.execute_script("arguments[0].click();", bt) 147 | # action_chains.move_to_element(bt) 148 | # bt.click() 149 | except KeyboardInterrupt: 150 | raise KeyboardInterrupt 151 | except: 152 | pass 153 | 154 | #print(6, end=" ") 155 | wait = WebDriverWait(self.driver, 30) 156 | try: 157 | # 进入答题界面 158 | wait.until(EC.presence_of_element_located((By.XPATH, '//iframe[1]'))) 159 | iframe = self.driver.find_element_by_xpath('//iframe[1]') 160 | self.driver.switch_to.frame(iframe) 161 | print(COLOR.NOTE+' now go to question '+COLOR.END, file=self._out_fp) 162 | #log_fp.write(' now go to question \n') 163 | #print(7, end=" ") 164 | except KeyboardInterrupt: 165 | raise KeyboardInterrupt 166 | except: 167 | print(COLOR.NOTE, ' no questions,continue~', COLOR.END, file=self._out_fp) # 未找到章节测验 168 | #log_fp.write(' no questions,continue~\n') 169 | return 0 170 | 171 | # 多任务点处理 172 | for i in range(3): 173 | try: 174 | task_num = self.driver.execute_script( 175 | "window.scrollTo(0,document.body.scrollHeight);return document.getElementsByClassName('ans-job-icon').length") 176 | except KeyboardInterrupt: 177 | raise KeyboardInterrupt 178 | except: 179 | sleep(1) 180 | # task_num = self.driver.execute_script( 181 | # "return document.getElementsByClassName('ans-job-icon').length") 182 | try: 183 | self.driver.execute_script("window.scrollTo(0,0)") 184 | except KeyboardInterrupt: 185 | raise KeyboardInterrupt 186 | except: 187 | pass 188 | wait.until(EC.presence_of_all_elements_located((By.XPATH, '//div[@class="ans-cc"]'))) 189 | for i in range(3): 190 | try: 191 | ans_cc = self.driver.find_element_by_xpath('//div[@class="ans-cc"]') 192 | h5_text = ans_cc.get_attribute('innerHTML') 193 | except KeyboardInterrupt: 194 | raise KeyboardInterrupt 195 | except: 196 | sleep(1) 197 | task_road = PlayMedia.get_road(h5_text, task_num) # bs4处理得到各个任务点路径 198 | 199 | print(COLOR.DISPLAY, ' there are ' + str(task_num) + ' task in this section:', COLOR.END, file=self._out_fp) 200 | #log_fp.write(' there are ' + str(task_num) + ' task in this section:\n') 201 | 202 | first_road = '//div[@class="ans-cc"]' 203 | for v_num in range(1, task_num + 1): 204 | print(COLOR.DISPLAY, ' go ' + str(v_num) + ':', COLOR.END, file=self._out_fp) 205 | #log_fp.write(' go ' + str(v_num) + ':\n') 206 | sleep(2) 207 | try: # 查看是否有任务点标识并查看是或否已经完成该任务点 208 | #flag = self.driver.find_element_by_xpath('//div[@class="ans-cc"]/p['+str(p_index[v_num-1])+']/div') 209 | # print(first_road+video_road[v_num-1]) 210 | flag = self.driver.find_element_by_xpath(first_road + task_road[v_num - 1]) 211 | self.driver.execute_script("arguments[0].scrollIntoView();", flag) 212 | 213 | #icon_flag = 1 214 | nowflag = flag.get_attribute('class') 215 | #print(nowflag, end=" ") 216 | if 'finished' in nowflag: 217 | print(COLOR.OK + ' Well! the task is already finished! continue~' + COLOR.END, file=self._out_fp) 218 | #log_fp.write(' Well! the task is already finished! continue~' + '\n') 219 | continue 220 | except KeyboardInterrupt: 221 | raise KeyboardInterrupt 222 | except: 223 | pass 224 | # icon_flag = 0 # 有的无任务点标识 225 | 226 | try: 227 | wait.until(EC.presence_of_element_located((By.XPATH, first_road+task_road[v_num-1]+'/iframe[1]'))) 228 | iframe = self.driver.find_element_by_xpath(first_road+task_road[v_num-1]+'/iframe[1]') 229 | self.driver.switch_to.frame(iframe) 230 | wait.until(EC.presence_of_element_located((By.XPATH, '//iframe[1]'))) 231 | iframe = self.driver.find_element_by_xpath('//iframe[1]') 232 | self.driver.switch_to.frame(iframe) 233 | except KeyboardInterrupt: 234 | raise KeyboardInterrupt 235 | except: 236 | print(COLOR.NOTE, ' no questions,continue~', COLOR.END, file=self._out_fp) # 未找到章节测验 237 | #log_fp.write(' no questions,continue~\n') 238 | self.driver.switch_to.default_content() 239 | wait.until(EC.presence_of_element_located((By.XPATH, '//iframe[1]'))) 240 | iframe = self.driver.find_element_by_xpath('//iframe[1]') 241 | self.driver.switch_to.frame(iframe) 242 | continue 243 | # print(self.driver.page_source) 244 | sleep(3) 245 | 246 | # 查询并获取答案 247 | data = { 248 | 'courseId': '', 249 | 'classId': '', 250 | # 'oldWorkId': '', 251 | # 'workRelationId': '' 252 | } 253 | try: 254 | for key in data.keys(): 255 | data[key] = self.driver.execute_script('return document.getElementById(arguments[0]).value', key) 256 | sleep(0.1) 257 | self.courseID = data['courseId']+' '+data['classId'] 258 | except KeyboardInterrupt: 259 | raise KeyboardInterrupt 260 | except: 261 | self.courseID = "" 262 | QA = QueryAns(self.driver.page_source, course=self.course_name, courseID=self.courseID) 263 | ans_flag, ans_lt = QA.work() 264 | # print(ans_flag) 265 | # print(ans_lt) 266 | # sleep(20) 267 | 268 | # print(ans_lt) 269 | # 开始答题 270 | # //*[@id="ZyBottom"]/div/div[1]/div 271 | # //*[@id="ZyBottom"]/div/div[2]/ul/li[1]/label/input 272 | # //*[@id="ZyBottom"]/div/div[2]/ul/li[2]/label/input 273 | # //*[@id="ZyBottom"]/div/div[4]/div[2]/ul/li[1]/label/input 274 | # //*[@id="ZyBottom"]/div/div[4]/div[4]/div[2]/ul/li[1]/label/input 275 | # //*[@id="ZyBottom"]/div/div[4]/div[4]/div[4]/div[2]/div/ul/li[1]/label/input 276 | try: 277 | for i in range(0, len(ans_lt)): 278 | for j in range(0, len(ans_lt[i])): 279 | if ans_lt[i][j] == 0: 280 | continue 281 | try: 282 | # print(i,j,ans_lt[i][j]) 283 | radio = self.driver.find_element_by_xpath( 284 | '//*[@id="ZyBottom"]/div[' + str(i+1) + ']/div[2]/ul/li[' + str(ans_lt[i][j]) + ']/label/input') 285 | except KeyboardInterrupt: 286 | raise KeyboardInterrupt 287 | except: 288 | radio = self.driver.find_element_by_xpath( 289 | '//*[@id="ZyBottom"]/div[' + str(i+1) + ']/div[2]/div/ul/li[' + str(ans_lt[i][j]) + ']/label/input') 290 | self.driver.execute_script("arguments[0].scrollIntoView();arguments[0].click();", radio) 291 | # action_chains.move_to_element(radio) 292 | # radio.click() 293 | sleep(1) 294 | except KeyboardInterrupt: 295 | raise KeyboardInterrupt 296 | except: 297 | #print('==========', file=self._out_fp) 298 | #print(traceback.format_exc(), file=self._out_fp) 299 | print(COLOR.ERR, " 答题失败!", COLOR.END, file=self._out_fp) 300 | #log_fp.write(" 答题失败!" + '\n') 301 | sleep(5) 302 | return 1 303 | #str(self._chapter) + '-' + str(self._section) 304 | 305 | # 点击提交并确定,检测验证码 306 | # //*[@id="tempsave"] 307 | # //*[@id="ZyBottom"]/div/div[4]/div[4]/div[4]/div[5]/a[1] 308 | # //*[@id="ZyBottom"]/div/div[4]/div[4]/div[4]/div[5]/a[2] 309 | # //*[@id="ZyBottom"]/div/div[4]/div[4]/div[4]/div[4]/div[5]/a[2] 310 | # //*[@id="ZyBottom"]/div[2]/a[2]/span 311 | try: 312 | bn = self.driver.find_element_by_xpath('//*[@id="ZyBottom"]/div' + '/div[4]' * 313 | (len(ans_lt) - 2) + '/div[5]/a['+str(ans_flag)+']') # 多个题目 314 | except KeyboardInterrupt: 315 | raise KeyboardInterrupt 316 | except: 317 | bn = self.driver.find_element_by_xpath('//*[@id="ZyBottom"]/div[2]/a['+str(ans_flag)+']') # 只有一个题 318 | # action_chains.move_to_element(bn) 319 | # bn.click() 320 | self.driver.execute_script("arguments[0].scrollIntoView();arguments[0].click();", bn) 321 | sleep(1) 322 | 323 | try: # 提交验证码 324 | self.driver.switch_to.default_content() 325 | while 1: 326 | img = self.driver.find_element_by_id('imgVerCode') 327 | img.screenshot('ans_vercode.png') 328 | img = Image.open('ans_vercode.png') 329 | img.show() 330 | numVerCode = input(COLOR.NOTE + " please input the ans_vercode:" + COLOR.END) 331 | #log_fp.write(' input the ans_vercode\n') 332 | # self.driver.find_element_by_id('code').send_keys(numVerCode) 333 | self.driver.find_element_by_xpath('//input[@id="code"]').send_keys(numVerCode) 334 | self.driver.find_element_by_xpath('//a[@id="sub"]').click() 335 | sleep(1) 336 | except KeyboardInterrupt: 337 | raise KeyboardInterrupt 338 | except: 339 | wait.until(EC.presence_of_element_located((By.XPATH, '//iframe[1]'))) 340 | iframe = self.driver.find_element_by_xpath('//iframe[1]') 341 | self.driver.switch_to.frame(iframe) 342 | wait.until(EC.presence_of_element_located((By.XPATH, first_road+task_road[v_num-1]+'/iframe[1]'))) 343 | iframe = self.driver.find_element_by_xpath(first_road+task_road[v_num-1]+'/iframe[1]') 344 | self.driver.switch_to.frame(iframe) 345 | wait.until(EC.presence_of_element_located((By.XPATH, '//iframe[1]'))) 346 | iframe = self.driver.find_element_by_xpath('//iframe[1]') 347 | self.driver.switch_to.frame(iframe) 348 | 349 | # //*[@id="confirmSubWin"]/div/div/a[1] 350 | wait.until(EC.presence_of_element_located((By.XPATH, '//*[@id="confirmSubWin"]/div/div/a[1]'))) 351 | bn = self.driver.find_element_by_xpath('//*[@id="confirmSubWin"]/div/div/a[1]') 352 | # action_chains.move_to_element(bn) 353 | try: 354 | # bn.click() 355 | self.driver.execute_script("arguments[0].click();", bn) 356 | print(COLOR.OK, 'questions of the section is finished! continue~', COLOR.END, file=self._out_fp) 357 | #log_fp.write(' finish the questions ' + '\n') 358 | except KeyboardInterrupt: 359 | raise KeyboardInterrupt 360 | except: 361 | #print('=======', file=self._out_fp) 362 | #print(traceback.format_exc(), file=self._out_fp) 363 | print(COLOR.ERR, " 提交失败!", COLOR.END, file=self._out_fp) 364 | return 1 365 | self.driver.switch_to.parent_frame() 366 | self.driver.switch_to.parent_frame() 367 | sleep(5) 368 | # self.driver.switch_to_alert().accept() 369 | # if(len(err_lt)==0): 370 | return 0 371 | 372 | def _go_que_task(self): 373 | try: 374 | err_flag = self._ans_question() 375 | 376 | if err_flag != 0: 377 | print(COLOR.ERR, 'unfinished!', COLOR.END, file=self._out_fp) 378 | # self._err_lt.append() # 记录答题提交失败的章节 379 | else: 380 | print(COLOR.OK, 'finished!', COLOR.END, file=self._out_fp) 381 | except KeyboardInterrupt: 382 | raise KeyboardInterrupt 383 | except: 384 | pass 385 | 386 | ## 387 | # brief 单课程自动模式 388 | # details 自动完成单课程下的任务(过程不需要输入),递归调用自身,未完成任务点为空时退出 389 | # (单章节设定重试次数,超过次数则退出,避免死循环) 390 | 391 | def _perform_model0(self): 392 | # 获取未完成章节列表并输出 393 | self._get_chapter_section() 394 | 395 | if len(self.ch_se_lt) == 0: 396 | print(COLOR.OK, 'finish the lesson! quit! ', COLOR.END, file=self._out_fp) 397 | return 398 | self._out_fp.flush() 399 | 400 | last_time = time.time()-120 # 答题间隔控制,减少答题验证码的弹出 401 | 402 | # 遍历每个未完成章节 403 | end_flag = 1 404 | for ch_se in self.ch_se_lt: 405 | if self.retry_dic[str(ch_se[0])] > 2: 406 | continue 407 | end_flag = 0 408 | self.retry_dic[str(ch_se[0])] += 1 409 | 410 | print(COLOR.DISPLAY + 'now turns to '+str(ch_se[0]) + COLOR.END, file=self._out_fp) 411 | try: 412 | PM = PlayMedia(self.driver, self._out_fp) 413 | PM.play_media('https://mooc1-1.chaoxing.com'+ch_se[1]) 414 | except KeyboardInterrupt: 415 | raise KeyboardInterrupt 416 | except: 417 | pass 418 | 419 | if self._que_server_flag == 1: 420 | # 答题间隔控制 421 | now_time = time.time() 422 | if now_time-last_time < 120: 423 | sleep(120-(now_time-last_time)) 424 | last_time = time.time() 425 | self._go_que_task() 426 | 427 | if end_flag == 1: 428 | print(COLOR.OK, 'finish the lesson! quit! ', COLOR.END, file=self._out_fp) 429 | return 430 | #log_fp.write("err_lt:" + str(error_lt) + '\n') 431 | # 递归调用 432 | return self._perform_model0() 433 | 434 | def _perform_model3(self): 435 | self._get_chapter_section() 436 | last_time = time.time()-120 # 答题间隔控制,减少答题验证码的弹出 437 | ch_se_st = input('please input ch_se:') 438 | 439 | ch_se_flag = 0 440 | # 遍历每个未完成章节 441 | for ch_se in self.ch_se_lt: 442 | if ch_se_st in ch_se[0]: 443 | ch_se_flag = 1 444 | if ch_se_flag == 0: 445 | continue 446 | print(COLOR.DISPLAY + 'now turns to '+str(ch_se[0]) + COLOR.END, file=self._out_fp) 447 | try: 448 | PM = PlayMedia(self.driver, self._out_fp) 449 | PM.play_media('https://mooc1-1.chaoxing.com'+ch_se[1]) 450 | except KeyboardInterrupt: 451 | raise KeyboardInterrupt 452 | except: 453 | pass 454 | 455 | if self._que_server_flag == 1: 456 | # 答题间隔控制 457 | now_time = time.time() 458 | if now_time-last_time < 120: 459 | sleep(120-(now_time-last_time)) 460 | last_time = time.time() 461 | self._go_que_task() 462 | 463 | ## 464 | # brief 单课程控制模式 465 | # details 需要输入 终止章节信息 466 | def _perform_model2(self): 467 | # 获取未完成章节列表并输出 468 | self._get_chapter_section() 469 | 470 | #chapter = eval(input("please select the end chapter(from unfinished list):")) 471 | #section = eval(input("please select which section:")) 472 | #subsection = eval(input("please select which subsection(if not input 0):")) 473 | end_ch_se = input("please input the end chapter(from unfinished list):") 474 | 475 | self._out_fp.flush() 476 | last_time = time.time()-150 # 答题间隔控制,减少答题验证码的弹出 477 | 478 | # 遍历每个未完成章节 479 | for ch_se in self.ch_se_lt: 480 | 481 | if end_ch_se in ch_se[0]: 482 | print(COLOR.OK, "OK! finish your task!", COLOR.END, file=self._out_fp) 483 | print(COLOR.DISPLAY, "now check your unfinished tasks:", COLOR.END, file=self._out_fp) 484 | self._get_chapter_section() 485 | break 486 | 487 | print(COLOR.DISPLAY + 'now turns to '+str(ch_se[0]) + COLOR.END, file=self._out_fp) 488 | try: 489 | PM = PlayMedia(self.driver, self._out_fp) 490 | PM.play_media('https://mooc1-1.chaoxing.com'+ch_se[1]) 491 | except KeyboardInterrupt: 492 | raise KeyboardInterrupt 493 | except: 494 | pass 495 | 496 | if self._que_server_flag == 1: 497 | # 答题间隔控制 498 | now_time = time.time() 499 | if now_time-last_time < 150: 500 | sleep(150-(now_time-last_time)) 501 | last_time = time.time() 502 | self._go_que_task() 503 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------