[
  {
    "path": ".gitignore",
    "content": ".vscode\nMooc/__pycache__\nMooc/Icourse163/__pycache__\nMooc/Icourses/__pycache__\nbuild\ndist"
  },
  {
    "path": "HELP.md",
    "content": "### 学无止网课下载器帮助文档\n\n\n\n#### 一.  软件下载：\n\n------\n\n1. [Github Releases](https://github.com/PyJun/Mooc_Downloader/releases)\n2. [百度云链接](https://pan.baidu.com/s/1G43ZZCTc5XtYCeZWUs4uTA)\n3. [蓝奏云](https://lanzouw.com/b00n4ln4b)\n\n\n\n#### 二. 使用说明\n\n------\n\n##### 1.从课程官网下选择任意一个课程复制其网址，如下图：\n\n![](http://118.31.48.9/images/downloader/copy1.png)\n\n![](http://118.31.48.9/images/downloader/copy2.png)\n\n\n\n##### 2.然后粘贴到下载器中，并按要求输入指令，会自动下载相应课程的视频和课件，如下图：\n\n![](http://118.31.48.9/images/downloader/demo1.png)\n\n![](http://118.31.48.9/images/downloader/demo2.png)\n\n\n\n#### 三.常见问题\n\n------\n\n##### 1.学无止下载器支持哪些网站的视频下载？\n\n答：目前已支持以下网课网站视频课程下载，官方网址如下：\n\n1. [腾讯课堂](https://ke.qq.com/)\n2. [网易云课堂](https://study.163.com/)\n3. [有道精品课](https://ke.youdao.com/)\n4. [有道领世](https://c.youdao.com/ydls/pc-download.html)\n5. [高途课堂](https://www.gaotu.cn/)\n6. [途途课堂](https://gaotu100.com/)\n7. [高途高中规划](https://www.gtgz.cn/)\n8. [中国大学](https://www.icourse163.org/)\n9. [哔哩哔哩](https://www.bilibili.com/)\n10. [抖音课堂](https://www.xuelangapp.com/)\n11. [中公网校](https://www.eoffcn.com/)\n12. [新东方在线](https://www.koolearn.com/)\n13. [新东方云教室](https://roombox.xdf.cn/)\n14. [伯索云学堂](https://www.plaso.cn/app/)\n15. [橙啦](https://www.orangevip.com/)\n16. [千聊](https://www.qlchat.com/)\n17. [兴趣岛](https://m.qianliao.net/)\n18. [超星学习通(学银在线)](http://www.xueyinonline.com/)\n19. [知到智慧树](https://www.zhihuishu.com/)\n20. [智慧职教(职教云)](https://www.icve.com.cn/)\n21. [爱课程](http://www.icourses.cn/)\n22. [学堂在线](https://next.xuetangx.com/)\n\n\n有关更多慕课网站的课程下载，敬请期待\n\n##### 2.关于课程还未开课无法下载， 或者正在开课无法下载全部课程问题？\n\n答：若课程有前几次开课，选择图片的版本一般选择最近一次的开课，然后复制链接进行下载。\nPS：关于每次开课内容一般大致相同，新课程可能会有少量更新等。一般不会影响学习。\n\n![图片](http://118.31.48.9/images/downloader/help1.png)\n\n------\n\n##### 3.是否可以下载已经结束的课程？\n\n答：丝毫没影响，依然可以下载，直接复制链接到下载器中下载即可\n\n##### 4.收费视频可以下载吗？\n\n答：因为版权问题，未购买的收费视频该软件不提供下载\n\n##### 5. “播放列表.dpl” 文件有什么用？\n\n答：电脑下载安装 **Potplayer** 播放器后，然后右键用potplayer打开“播放列表.dpl”文件，\n\n即可顺序播放器所有视频，可以更方便的观看\n\n##### 6.“修复播放列表.bat” 文件有什么用？\n\n答：当你手动把整个课程目录拷贝到其它地方后，你会发现 “播放列表.dpl”文件会失效，这时可以通过双击“修复播放列表.bat”文件来修复它。\n\n##### 7.软件出现请求异常和下载异常的问题？\n\n答：一般情况是你的本地网络出现的问题，请检查网络是否正常连接。如果确认网络良好还是出现了这样的问题，那么欢迎反馈给我们\n\n"
  },
  {
    "path": "Mooc/Icourse163/Icourse163_Base.py",
    "content": "'''\n    Icourse163 抽象基类\n'''\n\nimport os\nif __package__ is None:\n    import sys\n    sys.path.append('../')\nfrom Mooc.Mooc_Base import *\nfrom Mooc.Mooc_Download import *\nfrom Mooc.Mooc_Request import *\nfrom Mooc.Mooc_Potplayer import *\n\n__all__ = [\n    \"Icourse163_Base\"\n]\n\nclass Icourse163_Base(Mooc_Base):\n    potplayer = Mooc_Potplayer()\n\n    def __init__(self):\n        super().__init__()\n        self.infos = {}  # 课程视频和文件的链接请求信息，包含id等\n        self.__term_id = None # 下载课程的标题 ID\n    \n    @property\n    def term_id(self):\n        return self.__term_id\n\n    @term_id.setter\n    def term_id(self, term_id):\n        self.__term_id = term_id\n\n    def set_mode(self):\n        while True:\n            try:\n                instr = input(\"请输入一个0-4的数选择性下载内容(1:超高清, 2:高清, 3:标清, 4:仅下载课件) [0退出]: \")\n                if not instr:\n                    continue\n                try:\n                    innum = int(instr)\n                    if innum == 0:\n                        return False\n                    elif  1 <= innum <= 4:\n                        self.mode = innum\n                        return True\n                    else:\n                        print(\"请输入一个0-4之间的整数!\")\n                        continue\n                except ValueError:\n                    print(\"请输入一个0-4之间的整数!\")\n            except KeyboardInterrupt:\n                pass\n\n    @classmethod\n    @potplayer\n    def download_video(cls, video_url, video_name, video_dir):\n        return super().download_video(video_url, video_name, video_dir)\n"
  },
  {
    "path": "Mooc/Icourse163/Icourse163_Config.py",
    "content": "'''\n    Icourse163 模块包的配置文件\n'''\n\nCOURSENAME = '{1}--课程'\nIS_SHD, IS_HD, IS_SD, ONLY_PDF = 1, 2, 3, 4\nLEN_S = 96"
  },
  {
    "path": "Mooc/Icourse163/Icourse163_Mooc.py",
    "content": "'''\n    www.icourse163.org 下所有免费课程的下载和解析\n'''\n\nimport os\nimport re\nif __package__ is None:\n    import sys\n    sys.path.append('..\\\\')\n    sys.path.append(\"..\\\\..\\\\\")\nfrom Mooc.Mooc_Config import *\nfrom Mooc.Mooc_Base import *\nfrom Mooc.Mooc_Download import *\nfrom Mooc.Mooc_Request import *\nfrom Mooc.Mooc_Potplayer import *\nfrom Mooc.Icourse163.Icourse163_Config import * \nfrom Mooc.Icourse163.Icourse163_Base import *\n\n__all__ = [\n    \"Icourse163_Mooc\"\n]\n\nclass Icourse163_Mooc(Icourse163_Base):\n    course_url = \"https://www.icourse163.org/course/\"\n    infos_url = 'https://www.icourse163.org/dwr/call/plaincall/CourseBean.getMocTermDto.dwr'\n    parse_url = 'https://www.icourse163.org/dwr/call/plaincall/CourseBean.getLessonUnitLearnVo.dwr'\n    infos_data = {\n        'callCount':'1', \n        'scriptSessionId':'${scriptSessionId}190', \n        'c0-scriptName':'CourseBean',\n        'c0-methodName':'getMocTermDto', \n        'c0-id':'0', \n        'c0-param0':None,  # 'number:'+self.term_id,\n        'c0-param1':'number:0', \n        'c0-param2':'boolean:true', \n        'batchId':'1543633161622'\n    }\n    parse_data = {\n        'callCount': '1', \n        'scriptSessionId': '${scriptSessionId}190',\n        'c0-scriptName':'CourseBean',\n        'c0-methodName':'getLessonUnitLearnVo', \n        'httpSessionId':'5531d06316b34b9486a6891710115ebc',\n        'c0-id': '0', \n        'c0-param0':None, #'number:'+meta[0],\n        'c0-param1':None, #'number:'+meta[1], \n        'c0-param2':'number:0',\n        'c0-param3':None, #'number:'+meta[2], \n        'batchId': '1543633161622'\n    }\n\n    def __init__(self, mode=IS_SHD):\n        super().__init__()\n        self.mode = mode\n\n    def _get_cid(self, url):\n        self.cid = None\n        match = courses_re['icourse163_mooc'].match(url)\n        if match and match.group(4):\n            self.cid = match.group(4)\n\n    def _get_title(self):\n        if self.cid is None:\n            return\n        self.title = self.term_id = None\n        url = self.course_url + self.cid\n        text = request_get(url)\n        match = re.search(r'termId : \"(\\d+)\"', text)\n        if match:\n            self.term_id = match.group(1)\n        names = re.findall(r'name:\"(.+)\"', text)\n        if names:\n            title = '__'.join(names)\n            self.title = winre.sub('', title)[:WIN_LENGTH] # 用于除去win文件非法字符\n\n    def _get_infos(self):\n        if self.term_id is None:\n            return\n        self.infos = {}\n        self.infos_data['c0-param0'] = 'number:'+self.term_id\n        text = request_post(self.infos_url, self.infos_data, decoding='unicode_escape')\n        chapters = re.findall(r'homeworks=\\w+;.+?id=(\\d+).+?name=\"((.|\\n)+?)\";',text)\n        for i,chapter in enumerate(chapters,1):\n            chapter_title = winre.sub('', '{'+str(i)+'}--'+chapter[1])[:WIN_LENGTH]\n            self.infos[chapter_title] = {}\n            lessons = re.findall(r'chapterId='+chapter[0]+r'.+?contentType=1.+?id=(\\d+).+?isTestChecked=false.+?name=\"((.|\\n)+?)\".+?test', text)\n            for j,lesson in enumerate(lessons,1):\n                lesson_title = winre.sub('', '{'+str(j)+'}--'+lesson[1])[:WIN_LENGTH]\n                self.infos[chapter_title][lesson_title] = {}\n                videos = re.findall(r'contentId=(\\d+).+contentType=(1).+id=(\\d+).+lessonId=' +\n                                lesson[0] + r'.+name=\"(.+)\"', text)\n                pdfs = re.findall(r'contentId=(\\d+).+contentType=(3).+id=(\\d+).+lessonId=' +\n                                lesson[0] + r'.+name=\"(.+)\"', text)\n                video_source = [{'params':video[:3], 'name':winre.sub('','[{}.{}.{}]--{}'.format(i,j,k,video[3])).rstrip('.mp4')[:WIN_LENGTH]} for k,video in enumerate(videos,1)]\n                pdf_source = [{'params':pdf[:3], 'name':winre.sub('','({}.{}.{})--{}'.format(i,j,k,pdf[3])).rstrip('.pdf')[:WIN_LENGTH]} for k,pdf in enumerate(pdfs,1)]\n                self.infos[chapter_title][lesson_title]['videos'] = video_source\n                self.infos[chapter_title][lesson_title]['pdfs'] = pdf_source\n\n    def _get_source_text(self, params):\n        self.parse_data['c0-param0'] = params[0]\n        self.parse_data['c0-param1'] = params[1]\n        self.parse_data['c0-param3'] = params[2]\n        text = request_post(self.parse_url, self.parse_data, decoding='unicode_escape')\n        return text\n\n    def _get_pdf_url(self, params):\n        text = self._get_source_text(params)\n        pdf_match = re.search(r'textOrigUrl:\"(.*?)\"', text)\n        pdf_url = None\n        if pdf_match:\n            pdf_url = pdf_match.group(1)\n        return pdf_url\n\n    def _get_video_url(self, params):\n        text = self._get_source_text(params)\n        sub_match = re.search(r'name=\".+\";.*url=\"(.*?)\"', text)\n        video_url = sub_url = None\n        if sub_match:\n            sub_url = sub_match.group(1)\n        resolutions = ['Shd', 'Hd', 'Sd']\n        for index, sp in enumerate(resolutions,1):\n            video_match = re.search(r'(?P<ext>mp4)%sUrl=\"(?P<url>.*?\\.(?P=ext).*?)\"' % sp, text)\n            if video_match:\n                video_url, _ = video_match.group('url', 'ext')\n                if index >= self.mode: break\n        return video_url, sub_url\n\n    def _download(self):  # 根据课程视频链接来下载高清MP4慕课视频, 成功下载完毕返回 True\n        print('\\n{:^{}s}'.format(self.title, LEN_S))\n        self.rootDir = rootDir = os.path.join(PATH, self.title)\n        courseDir = os.path.join(rootDir, COURSENAME)\n        if not os.path.exists(courseDir):\n            os.makedirs(courseDir)\n        Icourse163_Base.potplayer.init(rootDir)\n        Icourse163_Base.potplayer.enable()\n        for i,chapter in enumerate(self.infos,1):  # 去除 win 文价夹中的非法字符\n            print(chapter)\n            chapterDir = os.path.join(courseDir, chapter)\n            if not os.path.exists(chapterDir):\n                os.mkdir(chapterDir)\n            for j,lesson in enumerate(self.infos[chapter],1):\n                lessonDir = os.path.join(chapterDir, lesson)\n                if not os.path.exists(lessonDir):\n                    os.mkdir(lessonDir)\n                print(\"  \"+lesson)\n                sources = self.infos[chapter][lesson]\n                for k,pdf_source in enumerate(sources['pdfs'],1):\n                    params, pdf_name = pdf_source['params'], pdf_source['name']\n                    pdf_url= self._get_pdf_url(params)\n                    if pdf_url:\n                        self.download_pdf(pdf_url, pdf_name, lessonDir)\n                if self.mode == ONLY_PDF:\n                    continue           \n                for k,video_source in enumerate(sources['videos'],1):\n                    params, name = video_source['params'], video_source['name']\n                    video_name = sub_name = name\n                    video_url, sub_url = self._get_video_url(params)\n                    if video_url:\n                        self.download_video(video_url=video_url, video_name=video_name, video_dir=lessonDir)\n                    if sub_url:\n                        self.download_sub(sub_url, sub_name, lessonDir)\n\n    def prepare(self, url):\n        self._get_cid(url)\n        self._get_title()\n        self._get_infos()\n\n    def download(self):\n        if self.cid and self.title and self.term_id and self.infos:\n            self._download()\n            return True\n        return False\n\n\ndef main():\n    # url = 'http://www.icourse163.org/course/GDUFS-1002493010'\n    # url = 'https://www.icourse163.org/course/WHU-1001539003'\n    url = 'https://www.icourse163.org/course/XHDX-1205600803'\n    icourse163_mooc = Icourse163_Mooc()\n    if (icourse163_mooc.set_mode()):\n        icourse163_mooc.prepare(url)\n        icourse163_mooc.download()\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "Mooc/Icourse163/__init__.py",
    "content": ""
  },
  {
    "path": "Mooc/Icourses/Icourse_Base.py",
    "content": "'''\n    定义一个爱课程 Icourse 的虚基类\n    用于派生 Icourse_Cuoc 和 Icourse_Mooc\n'''\n\nimport os\nfrom abc import abstractmethod\nif __package__ is None:\n    import sys\n    sys.path.append('../')\nfrom Mooc.Mooc_Config import *\nfrom Mooc.Mooc_Base import *\nfrom Mooc.Mooc_Download import *\nfrom Mooc.Mooc_Request import *\nfrom Mooc.Mooc_Potplayer import *\n\n__all__ = [\n    \"Icourse_Base\"\n]\n\nclass Icourse_Base(Mooc_Base):\n    potplayer = Mooc_Potplayer()\n\n    def __init__(self):\n        super().__init__()\n        self.__infos = []\n        self.__cid = None\n\n    def prepare(self, url):\n        getattr(self, \"_get_cid\")(url)\n        getattr(self, \"_get_title\")()\n        getattr(self, \"_get_infos\")()\n\n    def download(self):\n        if self.cid and self.title and self.infos:\n            getattr(self, \"_download\")()\n            return True\n        return False\n\n    @property\n    def cid(self):\n        return self.__cid\n\n    @cid.setter\n    def cid(self, cid):\n        self.__cid = cid\n\n    @abstractmethod\n    def _get_cid(self, url):\n        pass\n\n    def set_mode(self):\n        return True\n\n    @classmethod\n    @potplayer\n    def download_video(cls, video_url, video_name, video_dir):\n        return super().download_video(video_url, video_name, video_dir)\n\n    @classmethod\n    def download_video_list(cls, dirpath, mp4list, prefix=''):\n        for cnt, videos in enumerate(mp4list,1):\n            mp4_url, mp4_name = videos\n            mp4_name = winre.sub('', '['+prefix+str(cnt)+']--'+mp4_name).rstrip('.mp4')[:WIN_LENGTH]\n            cls.download_video(video_url=mp4_url, video_name=mp4_name, video_dir=dirpath)\n\n    @classmethod\n    def download_pdf_list(cls, dirpath, pdflist, prefix=''):\n        for cnt, pdfs in enumerate(pdflist,1):\n            pdf_url, pdf_name = pdfs\n            pdf_name = winre.sub('', '('+prefix+str(cnt)+')--'+pdf_name).rstrip('.pdf')[:WIN_LENGTH]\n            cls.download_pdf(pdf_url, pdf_name, dirpath)\n"
  },
  {
    "path": "Mooc/Icourses/Icourse_Config.py",
    "content": "'''\n    Icourse 模块包的配置文件\n'''\n\n\nCOURSENAME = '{1}--课程'\nPAPERNAME = '{2}--试卷'\nSOURCENAME = '{3}--资源'\nIS_MP4, IS_PDF, IS_PAPER, IS_SOURCE = 1, 2, 4, 8\nLEN_S = 96\nLEN_ = 48"
  },
  {
    "path": "Mooc/Icourses/Icourse_Cuoc.py",
    "content": "'''\n    www.icourses.cn/cuoc/ 下的视频公开课下载解析\n'''\n\nimport os\nimport re\nimport json\nif __package__ is None:\n    import sys\n    sys.path.append('..\\\\')\n    sys.path.append('..\\\\..\\\\')\nfrom Mooc.Mooc_Config import *\nfrom Mooc.Mooc_Request import *\nfrom Mooc.Icourses.Icourse_Config import *\nfrom Mooc.Icourses.Icourse_Base import *\n\n__all__ = [\n    \"Icourse_Cuoc\"\n]\n\nclass Icourse_Cuoc(Icourse_Base):\n    url_course = \"http://www.icourses.cn/web/sword/portal/videoDetail?courseId=\"\n    def __init__(self):\n        super().__init__()\n\n    def _get_cid(self, url):\n        self.cid = None\n        match = courses_re.get('icourse_cuoc').match(url)\n        if match:\n            self.cid = match.group(1)\n\n    def _get_title(self):\n        if self.cid is None:\n            return\n        self.title = None\n        url = self.url_course + self.cid\n        text = request_get(url)\n        match_title = re.search(r\"_courseTitle.*?=.*?'(.*?)';\", text)\n        match_school = re.search(r'<a +?class *?= *?\"teacher-infor-from\">(.*?)</a>', text)\n        if match_title and match_school:\n            title_name = match_title.group(1)+'__'+match_school.group(1)\n            self.title = winre.sub('', title_name)[:WIN_LENGTH]\n\n    def _get_infos(self):\n        if self.cid is None:\n            return\n        self.infos = []\n        url = self.url_course + self.cid\n        text = request_get(url)\n        match_courses = re.search(r'_sourceArrStr *?= *?(\\[.*?\\]);\\s*?var +?_shareUrl', text)\n        if match_courses:\n            #!!! except json.decoder.JSONDecodeError\n            courses = json.loads(match_courses.group(1))\n            self.infos = [{'url':course['fullLinkUrl'], 'name':winre.sub('',course['title'])[:WIN_LENGTH]} for course in courses]\n\n    def _download(self):\n        print('\\n{:^{}s}'.format(self.title, LEN_S))\n        self.rootDir = rootDir = os.path.join(PATH, self.title)\n        courseDir = os.path.join(rootDir, COURSENAME)\n        if not os.path.exists(courseDir):\n            os.makedirs(courseDir)\n        print(COURSENAME)\n        Icourse_Base.potplayer.init(rootDir)\n        mp4_list = [(info['url'], info['name']) for info in self.infos]\n        Icourse_Base.potplayer.enable()\n        self.download_video_list(courseDir, mp4_list)\n\n\ndef main():\n    # url = 'http://www.icourses.cn/web/sword/portal/videoDetail?courseId=9fe9d456-1327-1000-9193-4876d02411f6'\n    url = 'http://www.icourses.cn/web/sword/portal/videoDetail?courseId=9fe99900-1327-1000-9191-4876d02411f6#/?resId=d0fff67d-1334-1000-8f6b-1d109e90c3cf'\n    # url = 'http://www.icourses.cn/web/sword/portal/videoDetail?courseId=9feeeee3-1327-1000-91e3-4876d02411f6#/?resId=d119afd8-1334-1000-9042-1d109e90c3cf'\n    icourse_cuoc = Icourse_Cuoc()\n    icourse_cuoc.prepare(url)\n    icourse_cuoc.download()\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "Mooc/Icourses/Icourse_Mooc.py",
    "content": "'''\n    www.icourses.cn/mooc/ 下的资源共享课下载解析\n'''\n\nimport os\nimport re\nimport json\nif __package__ is None:\n    import sys\n    sys.path.append('..\\\\')\n    sys.path.append('..\\\\..\\\\')\nfrom Mooc.Mooc_Config import *\nfrom Mooc.Mooc_Download import *\nfrom Mooc.Mooc_Request import *\nfrom Mooc.Icourses.Icourse_Config import *\nfrom Mooc.Icourses.Icourse_Base import *\n\n__all__ = [\n    \"Icourse_Mooc\"\n]\n\nclass Icourse_Mooc(Icourse_Base):\n    url_title = 'http://www.icourses.cn/sCourse/course_{}.html'\n    url_id = 'http://www.icourses.cn/web/sword/portal/shareChapter?cid='\n    url_course = 'http://www.icourses.cn/web//sword/portal/getRess' \n    url_assign = 'http://www.icourses.cn/web/sword/portal/assignments?cid='\n    url_paper = 'http://www.icourses.cn/web/sword/portal/testPaper?cid='\n    url_source = 'http://www.icourses.cn/web/sword/portal/sharerSource?cid='\n\n    def __init__(self, mode=IS_MP4|IS_PDF|IS_PAPER|IS_SOURCE):\n        super().__init__()\n        self.mode = mode\n\n    def _get_cid(self, url):\n        self.cid = None\n        match = courses_re.get('icourse_mooc').match(url)\n        if match:\n            cid = match.group(3) or match.group(5)\n            self.cid = cid \n\n    def _get_title(self):\n        if not self.cid:\n            return\n        self.title = None\n        url = self.url_title.format(self.cid)\n        text = request_get(url)\n        match_name = re.search(r'<div +class=\"course-title clearfix\">\\s*<p +class=\"pull-left\">(.*?)</p>', text)\n        match_school = re.search(r'<span +class=\"pull-left\">学校:</span>\\s*<p +class=\"course-information-hour pull-left\">(.*?)</p>', text)\n        if match_name and match_school:\n            title_name = match_name.group(1) + '__' + match_school.group(1)\n            self.title = winre.sub('', title_name)[:WIN_LENGTH]\n\n    def _get_infos(self):\n        if not self.cid:\n            return\n        self.infos = []\n        url1 = self.url_id + self.cid\n        url2 = self.url_assign + self.cid\n        text1 = request_get(url1)\n        text2 = request_get(url2)\n        chapter_ids = re.findall(r'<li +data-id=\"(\\d+)\" +class=\"chapter-bind-click panel[\\s\\S]*?\">', text1)\n        chapter_names = re.findall(r'<a +class=\"chapter-title-text\"[\\s\\S]*?>([\\s\\S]*?)</a>', text1)\n        chapter_ptext = re.findall(r'<div[\\s\\S]*?id=\"collapse(\\d+)\"[\\s\\S]*?<div([\\s\\S]*?)</div>', text2)\n        match_str = r'<div[\\s\\S]*?id=\"collapse{}-{}\"([\\s\\S]*?)</div>'\n        re_pdf = re.compile(r'data-class=\"media\"[\\s\\S]*?data-title=\"([\\s\\S]*?)\"[\\s\\S]*?data-url=\"(.*?)\"')\n        for _id, name in zip(chapter_ids, chapter_names):\n            self.infos.append({'id': _id, 'name': winre.sub('',name)[:WIN_LENGTH], 'units':[], 'pdfs':[]})\n        for index, ptext in chapter_ptext:\n            inx = int(index)-1\n            pdfs = re_pdf.findall(ptext)\n            pdf_list = [{'name':winre.sub('', pdf[0])[:WIN_LENGTH], 'url':pdf[1]} for pdf in pdfs]\n            self.infos[inx]['pdfs'] = pdf_list\n        unit_list = re.findall(r'<a +class=\"chapter-body-content-text section-event-t no-load\"[\\s\\S]*?data-secId=\"(\\d+)\"[\\s\\S]*?<span +class=\"chapter-s\">(\\d+)</span><span>.</span>\\s*?<span +class=\"chapter-t\">(\\d+)</span>(.*?)</a>', text1)\n        for unit_id,unit_inx1, unit_inx2,unit_name in unit_list:\n            inx1 = int(unit_inx1)-1\n            inx2 = int(unit_inx2)-1\n            self.infos[inx1]['units'].append({'id': unit_id, 'name': winre.sub('',unit_name)[:WIN_LENGTH], 'pdfs':[]})\n            m_str = match_str.format(unit_inx1, unit_inx2)\n            match_ptext = re.search(m_str, text2)\n            if match_ptext:\n                ptext = match_ptext.group(1)\n                pdfs = re_pdf.findall(ptext)\n                pdf_list = [{'name':winre.sub('', pdf[0])[:WIN_LENGTH], 'url':pdf[1]} for pdf in pdfs]\n                self.infos[inx1]['units'][inx2]['pdfs'] = pdf_list\n\n    def _get_course_links(self, sid):\n        mp4_list = []\n        pdf_list = []\n        data = {'sectionId': sid}\n        text = request_post(self.url_course, data)\n        #!!! except json.decoder.JSONDecodeError        \n        infos = json.loads(text)\n        if infos['model']['listRes'] :\n            reslist = infos['model']['listRes']\n            for res in reslist:\n                if res['mediaType'] == 'mp4':\n                    if 'fullResUrl' in res:\n                        mp4_list.append((res['fullResUrl'], res['title']))\n                elif res['mediaType'] in ('ppt', 'pdf'):\n                    if 'fullResUrl' in res:\n                        pdf_list.append((res['fullResUrl'], res['title']))\n        return mp4_list, pdf_list\n\n    def _get_paper_links(self):\n        url = self.url_paper + self.cid\n        paper_list = []\n        text = request_get(url)\n        match_text = re.findall(r'<a +data-class=\"media\"((.|\\n)+?)>', text)\n        re_url = re.compile(r'data-url=\"(.*?)\"')\n        re_title = re.compile(r'data-title=\"(.*?)\"')\n        for m_text in match_text:\n            link_list = re_url.findall(m_text[0])\n            title_list = re_title.findall(m_text[0])\n            paper_list += list(zip(link_list, title_list))\n        return paper_list\n\n    def _get_source_links(self):\n        url = self.url_source + self.cid\n        source_list = []\n        text = request_get(url)\n        match_text = re.findall(r'<a +class=\"courseshareresources-content clearfix\"((.|\\n)+?)>', text)\n        re_url = re.compile(r'data-url=\"(.*?)\"')\n        re_title = re.compile(r'data-title=\"(.*?)\"')\n        for m_text in match_text:\n            link_list = re_url.findall(m_text[0])\n            title_list = re_title.findall(m_text[0])\n            source_list += list(zip(link_list, title_list))\n        return source_list\n\n    def _download(self):\n        print('\\n{:^{}s}'.format(self.title, LEN_S))\n        self.rootDir = rootDir = os.path.join(PATH, self.title)\n        if not os.path.exists(rootDir):\n            os.mkdir(rootDir)\n        Icourse_Base.potplayer.init(rootDir)\n        if (self.mode & IS_MP4) or (self.mode & IS_PDF):\n            courseDir = os.path.join(rootDir, COURSENAME)\n            if not os.path.exists(courseDir):\n                os.mkdir(courseDir)\n            print('-'*LEN_+'下载课程'+'-'*LEN_)\n            Icourse_Base.potplayer.enable()\n            for cnt1, info in enumerate(self.infos, 1):\n                chapter = '{'+str(cnt1)+'}--'+info['name']\n                print(chapter)\n                chapterDir = os.path.join(courseDir, chapter)\n                if not os.path.exists(chapterDir):\n                    os.mkdir(chapterDir)\n                mp4_list, pdf_list = self._get_course_links(info['id'])\n                pdf_list += [(pdf['url'], pdf['name']) for pdf in info['pdfs']]\n                if self.mode & IS_PDF:\n                    self.download_pdf_list(chapterDir, pdf_list, '{}.'.format(cnt1))\n                if self.mode & IS_MP4:\n                    self.download_video_list(chapterDir, mp4_list, '{}.'.format(cnt1))\n                for cnt2, unit in enumerate(info['units'],1):\n                    lesson = '{'+str(cnt2)+'}--'+unit['name']\n                    print(\"  \"+lesson)\n                    lessonDir = os.path.join(chapterDir, lesson)\n                    if not os.path.exists(lessonDir):\n                        os.mkdir(lessonDir)\n                    mp4_list, pdf_list = self._get_course_links(unit['id'])\n                    pdf_list += [(pdf['url'], pdf['name']) for pdf in unit['pdfs']]\n                    if self.mode & IS_PDF:\n                        self.download_pdf_list(lessonDir, pdf_list, '{}.{}.'.format(cnt1,cnt2))\n                    if self.mode & IS_MP4:\n                        self.download_video_list(lessonDir, mp4_list, '{}.{}.'.format(cnt1,cnt2))\n        if self.mode & IS_PAPER:\n            paperDir = os.path.join(rootDir, PAPERNAME)\n            if not os.path.exists(paperDir):\n                os.mkdir(paperDir)\n            print(\"-\"*LEN_+\"下载试卷\"+\"-\"*LEN_)\n            paper_list = self._get_paper_links()\n            self.download_pdf_list(paperDir, paper_list)\n        if self.mode & IS_SOURCE:\n            sourceDir = os.path.join(rootDir, SOURCENAME)\n            if not os.path.exists(sourceDir):\n                os.mkdir(sourceDir)\n            print(\"-\"*LEN_+\"下载资源\"+\"-\"*LEN_)\n            Icourse_Base.potplayer.disable()\n            source_list = self._get_source_links()\n            pdf_list = list(filter(lambda x:x[0].endswith('.pdf'), source_list))\n            mp4_list = list(filter(lambda x:x[0].endswith('.mp4'), source_list))\n            self.download_pdf_list(sourceDir, pdf_list)\n            self.download_video_list(sourceDir, mp4_list)\n\n    def set_mode(self):\n        while True:\n            try:\n                instr = input(\n                    \"    视频:[1]    +    课件:[2]    +    试卷:[4]    +    资源:[8]\\n\"\n                    \"请输入一个0-15的数选择性下载内容(如15表示全部下载,15=1+2+4+8) [0退出]: \"\n                    )\n                if not instr:\n                    continue\n                try:\n                    innum = int(instr)\n                    if innum == 0:\n                        return False\n                    elif  1 <= innum <= 15:\n                        self.mode = innum\n                        return True\n                    else:\n                        print(\"请输入一个0-15之间的整数!\")\n                        continue\n                except ValueError:\n                    print(\"请输入一个0-15之间的整数!\")\n            except KeyboardInterrupt:\n                print()\n\n\ndef main():\n    # url = 'http://www.icourses.cn/sCourse/course_4860.html'\n    url = 'http://www.icourses.cn/web/sword/portal/shareDetails?cId=4860#/course/chapter'\n    # url = 'https://www.icourses.cn/sCourse/course_6661.html'\n    # url = 'http://www.icourses.cn/sCourse/course_3459.html'\n    icourse_mooc = Icourse_Mooc()\n    if (icourse_mooc.set_mode()):\n        icourse_mooc.prepare(url)\n        icourse_mooc.download()\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "Mooc/Icourses/__init__.py",
    "content": ""
  },
  {
    "path": "Mooc/Mooc_Base.py",
    "content": "'''\n    Mooc 的虚基类：用于派生所有Mooc子类\n'''\n\nimport os\nfrom abc import ABC, abstractmethod\nfrom Mooc.Mooc_Config import *\nfrom Mooc.Mooc_Download import *\nfrom Mooc.Mooc_Request import *\n\n__all__ = [\n    \"Mooc_Base\"\n]\n\nclass Mooc_Base(ABC):\n    def __init__(self):\n        self.__mode = None\n        self.__cid = None\n        self.__title = None\n        self.__infos = None\n        self.__rootDir = None\n\n    @property\n    def mode(self):\n        '''下载模式: 用于选择性下载'''\n        return self.__mode\n\n    @mode.setter\n    def mode(self, mode):\n        self.__mode = mode\n\n    @property\n    def cid(self):\n        '''课程的 ID'''\n        return self.__cid\n    \n    @cid.setter\n    def cid(self, cid):\n        self.__cid = cid\n\n    @property\n    def title(self):\n        '''课程的标题'''\n        return self.__title\n    \n    @title.setter\n    def title(self, title):\n        self.__title = title\n\n    @property\n    def infos(self):\n        '''解析后的课程信息'''\n        return self.__infos\n\n    @property\n    def rootDir(self):\n        return self.__rootDir\n\n    @rootDir.setter\n    def rootDir(self, rootDir):\n        self.__rootDir = rootDir\n    \n    @infos.setter\n    def infos(self, infos):\n        self.__infos = infos\n\n    @abstractmethod\n    def _get_cid(self):\n        pass\n    \n    @abstractmethod\n    def _get_title(self):\n        pass\n\n    @abstractmethod\n    def _get_infos(self):\n        pass\n\n    @abstractmethod\n    def _download(self):\n        pass\n\n    @abstractmethod\n    def set_mode(self):\n        pass\n\n    @abstractmethod\n    def prepare(self, url):\n        pass\n\n    @abstractmethod\n    def download(self):\n        pass\n\n    @classmethod\n    def download_video(cls, video_url, video_name, video_dir):\n        '''下载 MP4 视频文件'''\n        succeed = True\n        if not cls.judge_file_existed(video_dir, video_name, '.mp4'):\n            try:\n                header =  request_head(video_url)\n                size = float(header['Content-Length']) / (1024*1024)\n                print(\"  |-{}  [mp4] 大小: {:.2f}M\".format(cls.align(video_name,LENGTH), size))\n                aria2_download_file(video_url, video_name+'.mp4', video_dir)\n            except DownloadFailed:\n                print(\"  |-{}  [mp4] 资源无法下载！\".format(cls.align(video_name,LENGTH)))\n                succeed = False\n        else:\n            print(\"  |-{}  [mp4] 已经成功下载！\".format(cls.align(video_name,LENGTH)))\n        return succeed\n\n    @classmethod\n    def download_pdf(cls, pdf_url, pdf_name, pdf_dir):\n        '''下载 PDF '''\n        succeed = True\n        if not cls.judge_file_existed(pdf_dir, pdf_name, '.pdf'):\n            try:\n                aria2_download_file(pdf_url, pdf_name+'.pdf', pdf_dir)\n                print(\"  |-{}  (pdf) 已经成功下载！\".format(cls.align(pdf_name,LENGTH)))\n            except DownloadFailed:\n                print(\"  |-{}  (pdf) 资源无法下载！\".format(cls.align(pdf_name,LENGTH)))\n                succeed = False\n        else:\n            print(\"  |-{}  (pdf) 已经成功下载！\".format(cls.align(pdf_name,LENGTH)))\n        return succeed\n\n    @classmethod\n    def download_sub(cls, sub_url, sub_name, sub_dir):\n        '''下载字幕'''\n        succeed = True\n        if not cls.judge_file_existed(sub_dir, sub_name, '.srt'):\n            try:\n                aria2_download_file(sub_url, sub_name+'.srt', sub_dir)\n            except DownloadFailed:\n                succeed = False\n        return succeed\n\n    @staticmethod\n    def judge_file_existed(dirname, filename, fmt):\n        '''\n        judge_file_existed(dirname, filename, fmt) \n        判断在 dirname 目录下是否存在已下载成功的格式为 fmt 且文件名为 filename 的文件\n        '''\n        filepath = os.path.join(dirname, filename)\n        exist1 = os.path.exists(filepath+fmt)\n        exist2 = os.path.exists(filepath+fmt+'.aria2')\n        return exist1 and not exist2\n\n    @staticmethod\n    def align(string, width):  #  对齐汉字字符窜，同时截断多余输出\n        '''\n        align(string, width) 根据width宽度居中对齐字符窜 string，主要用于汉字居中\n        '''\n        res = \"\"\n        size = 0\n        for ch in string:\n            if (size+3 > width):\n                break\n            size += 1 if ord(ch) <= 127 else 2\n            res += ch\n        res += (width-size)*' '\n        return res\n"
  },
  {
    "path": "Mooc/Mooc_Config.py",
    "content": "'''\n    Mooc 总项目的配置文件\n'''\n\nimport sys\nimport os\nimport re\n\n\n# 常量，固定参数\n__QQgroup__ = \"196020837\"\n__email__  = \"py.jun@qq.com\"\nif hasattr(sys, 'frozen'):\n    PATH = os.path.dirname(sys.executable)\nelse:\n    PATH = os.path.dirname(os.path.abspath(__file__))  # 程序当前路径\nwinre = re.compile(r'[?*|<>:\"/\\\\\\s]')  # windoes 文件非法字符匹配\nWIN_LENGTH = 64\nTIMEOUT = 60   # 请求超时时间\nPLAYLIST = '播放列表.dpl'\nPALYBACK = 'DPL_PYJUN'\nBATNAME = '修复播放列表.bat'\nBATSTRING = '''\\\n@echo off\ncopy {0} {1}\necho 成功修复“{1}”\necho 请用Potplayer播放器打开“{1}”观看视频（未安装Potplayer自行百度下载安装）\npause\n'''.format(PALYBACK, PLAYLIST)\nLENGTH = 80\n\n# 变量，可修改的参数\ndownload_speed = \"0\"\nif getattr(sys, 'frozen', False): #是否打包\n    aria2_path = os.path.join(sys._MEIPASS, \"aria2c.exe\")\n    alipay_path = os.path.join(sys._MEIPASS, \"Alipay.jpg\")\nelse:\n    aria2_path = os.path.join(PATH, \"aria2c.exe\")\n    alipay_path = os.path.join(PATH, \"Alipay.jpg\")\naira2_cmd = '%s --header \"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 -- fUcIvJ01pZVQhNq23lXm9gjazkeonsCx\" --check-certificate=false -x 16 -s 64 -j 64 -k 2M --disk-cache 128M --max-overall-download-limit %s \"{url:}\" -d \"{dirname:}\" -o \"{filename:}\"'%(aria2_path, download_speed)\n\n# 课程链接的正则匹配\ncourses_re = {\n    \"icourse163_mooc\": re.compile(r'\\s*https?://www.icourse163.org/((learn)|(course))/(.*?)(#/.*)?$'),\n    \"icourse_cuoc\": re.compile(r'\\s*https?://www.icourses.cn/web/sword/portal/videoDetail\\?courseId=([\\w-]*)'), \n    \"icourse_mooc\": re.compile(r'\\s*((https?://www.icourses.cn/sCourse/course_(\\d+).html)|'\n                        r'(https?://www.icourses.cn/web/sword/portal/shareDetails\\?cId=(\\d+)))')\n}\n\n__all__ = [\n    \"__QQgroup__\", \"__email__\", \"PATH\", \"winre\", \"TIMEOUT\", \"PLAYLIST\", \"PALYBACK\", \n    \"BATNAME\", \"BATSTRING\", \"LENGTH\", \"WIN_LENGTH\", \n\n    \"download_speed\", \"aria2_path\", \"aira2_cmd\", \"courses_re\", \"alipay_path\"\n]\n"
  },
  {
    "path": "Mooc/Mooc_Download.py",
    "content": "'''\n    Mooc 下载功能模块：调用 aria2c.exe 下载文件\n'''\n\nimport os\nimport re\nimport subprocess\nfrom time import sleep\nfrom Mooc.Mooc_Config import *\n\n__all__ = [\n    \"aria2_download_file\", \"DownloadFailed\"\n]\n\nRE_SPEED = re.compile(r'\\d+MiB/(\\d+)MiB\\((\\d+)%\\).*?DL:(\\d*?\\.?\\d*?)([KM])iB')\nRE_AVESPEED = re.compile(r'\\|\\s*?([\\S]*?)([KM])iB/s\\|')\n\nclass DownloadFailed(Exception):\n    pass\n\ndef aria2_download_file(url, filename, dirname='.'):\n    cnt = 0\n    while cnt < 3:\n        p = None\n        try:\n            cmd = aira2_cmd.format(url=url, dirname=dirname, filename=filename)\n            p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, universal_newlines=True, encoding='utf8')\n            lines = ''\n            while p.poll() is None:\n                line = p.stdout.readline().strip()\n                if filename.endswith('.mp4') and line:\n                    lines += line\n                    match = RE_SPEED.search(line)\n                    if match:\n                        size, percent, speed, unit = match.groups()\n                        percent = float(percent)\n                        speed = float(speed)\n                        if unit == 'K':\n                            speed /= 1024\n                        per = min(int(LENGTH*percent/100) , LENGTH)\n                        print('\\r  |-['+per*'*'+(LENGTH-per)*'.'+'] {:.0f}% {:.2f}M/s'.format(percent,speed),end=' (ctrl+c中断)')\n            if p.returncode != 0:\n                cnt += 1\n                if cnt==1:\n                    clear_files(dirname, filename)\n                    sleep(0.16)\n            else:\n                if filename.endswith('.mp4'):\n                    match = RE_AVESPEED.search(lines)\n                    if match:\n                        ave_speed, unit = match.groups()\n                        ave_speed = float(ave_speed)\n                        if unit == 'K':\n                            ave_speed /= 1024\n                    print('\\r  |-['+LENGTH*'*'+'] {:.0f}% {:.2f}M/s'.format(100,ave_speed),end='  (完成)    \\n')\n                return\n        finally:\n            if p:\n                p.kill()   # 保证子进程已终止\n    clear_files(dirname, filename)\n    raise DownloadFailed(\"download failed\")\n\n\ndef clear_files(dirname, filename):\n    filepath = os.path.join(dirname, filename)\n    if os.path.exists(filepath):\n        os.remove(filepath)\n    if os.path.exists(filepath+'.aria2'):\n        os.remove(filepath+'.aria2')\n"
  },
  {
    "path": "Mooc/Mooc_Interface.py",
    "content": "'''\n    Mooc 人机交互的接口函数\n'''\n\nimport os\nimport re\nif __package__ is None:\n    import sys\n    sys.path.append('.\\\\')\n    sys.path.append(\"..\\\\\")\nfrom Mooc.Mooc_Config import *\nfrom Mooc.Mooc_Request import *\nfrom Mooc.Mooc_Download import *\nfrom Mooc.Icourse163.Icourse163_Mooc import *\nfrom Mooc.Icourses.Icourse_Cuoc import *\nfrom Mooc.Icourses.Icourse_Mooc import *\n\n__all__ = [\n    \"mooc_interface\"\n]\n\n# 课程名对应的Mooc类\ncourses_mooc = {\n    \"icourse163_mooc\": Icourse163_Mooc,\n    \"icourse_cuoc\": Icourse_Cuoc, \n    \"icourse_mooc\": Icourse_Mooc\n}\n\ndef mooc_interface():\n    try:\n        while True:\n            os.system(\"cls\")\n            print(\"\\t\"+\"=\"*91)\n            print('\\t|\\t\\t      慕课下载器(免费版v3.4.2)      \\tQQ群: {:^27s} |'.format(__QQgroup__))\n            print(\"\\t|\\t\\t    icourse163.org, icourses.cn    \\t邮箱: {:^27s} |\".format(__email__))\n            print(\"\\t\"+\"=\"*91)\n            print(\"\\t{:^90}\".format(\"Github: https://github.com/PyJun/Mooc_Downloader\"))\n            print(\"\\t{:^90}\".format(\"博客: https://blog.csdn.net/qq_16166591/article/details/85249743\"))\n            print(\"\\t{:^90}\".format(\"下载路径: \"+PATH))\n            urlstr = None\n            while not urlstr:\n                try:\n                    urlstr = input('\\n输入一个视频课程网址(q退出): ')\n                except KeyboardInterrupt:\n                    print()\n            if urlstr == 'q':\n                break\n            mooc = match_mooc(urlstr)\n            if not mooc:\n                input(\"视频课程链接不合法，请回车继续...\")\n                continue\n            if not mooc.set_mode():\n                continue\n            print(\"正在连接资源......\")\n            try:\n                mooc.prepare(urlstr)\n            except RequestFailed:\n                print(\"网路请求异常！\")\n                input(\"请按回车键返回主界面...\")\n                continue\n            while True:\n                try:\n                    isdownload = mooc.download()\n                    if isdownload:\n                        print('\"{}\" 下载完毕!'.format(mooc.title))\n                        print(\"下载路径: {}\".format(mooc.rootDir))\n                        os.startfile(mooc.rootDir)\n                    else:\n                        print('\"{}\" 还未开课！'.format(mooc.title))\n                    input(\"请按回车键返回主界面...\")\n                    break\n                except (RequestFailed, DownloadFailed) as err:\n                    if isinstance(err, RequestFailed):\n                        print(\"网路请求异常！\")\n                    else:\n                        print(\"文件下载异常！\")\n                    if inquire():\n                        continue\n                    else:\n                        break\n                except KeyboardInterrupt:\n                    print()\n                    if inquire():\n                        continue\n                    else:\n                        break\n                except:\n                    print(\"程序异常退出，希望反馈作者！\")\n                    return\n    except KeyboardInterrupt:\n        input(\"程序退出...\")\n    finally:\n        # if (input(\"\\n小哥哥，小姐姐，打个赏再走呗 …(⊙_⊙)… [y/n]: \") != 'n'):\n        #     os.startfile(alipay_path)\n        os.system(\"pause\")\n\ndef inquire():\n    redown = None\n    while redown not in ('y','n'):\n        try: \n            redown = input(\"是否继续[y/n]: \")\n        except (KeyboardInterrupt, EOFError):\n            print()\n    return redown=='y'\n\ndef match_mooc(url):\n    mooc = None\n    for mooc_name in courses_mooc:\n        if courses_re.get(mooc_name).match(url):\n            mooc = courses_mooc.get(mooc_name)()\n            break\n    return mooc\n\ndef main():\n    mooc_interface()\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "Mooc/Mooc_Main.py",
    "content": "'''\n    Mooc下载器主程序\n\n    作者：PyJun\n    邮箱：py.jun@qq.com\n'''\n\nif __package__ is None:\n    import sys\n    sys.path.append('.\\\\')\n    sys.path.append('..\\\\')\nfrom Mooc.Mooc_Interface import *\n\ndef main():\n    try:\n        mooc_interface()\n    except:\n        pass\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "Mooc/Mooc_Potplayer.py",
    "content": "'''\n    Mooc 生成 potplayer 播放列表 dpl 文件的类\n'''\n\nimport os\nfrom functools import wraps\nfrom Mooc.Mooc_Config import * \n\n__all__ = [\n    \"Mooc_Potplayer\"\n]\n\nclass Mooc_Potplayer():\n    def __init__(self):\n        self.cnt = 0\n        self.lines = []\n        self.available = False\n\n    def init(self, rootdir):\n        self.rootdir = rootdir\n        self.listpath = os.path.join(rootdir, PLAYLIST)\n        self.listpath_back = os.path.join(rootdir, PALYBACK)\n        self.batpath = os.path.join(rootdir, BATNAME)\n\n    def __call__(self, func):\n        @wraps(func)\n        def wrap_func(*args, **kwargs):\n            succeed = func(*args, **kwargs)\n            if self.available and succeed:\n                self.cnt += 1\n                video_dir = kwargs['video_dir']\n                video_name = kwargs['video_name']\n                video_path = os.path.join(video_dir, video_name+'.mp4')\n                video_relpath = os.path.relpath(video_path, self.rootdir)\n                if self.lines == [] and self.cnt == 1:\n                    self.lines.append('DAUMPLAYLIST\\n')\n                    self.lines.append(\"playname=%s\\n\"%(video_relpath))\n                    with open(self.batpath, 'w') as batfile:\n                        batfile.write(BATSTRING)\n                self.lines.append(\"%d*file*%s\\n\"%(self.cnt,video_relpath))\n                self.lines.append(\"%d*title*%s\\n\"%(self.cnt,video_name))\n                self.update()\n            return succeed\n        return wrap_func\n\n    def update(self):\n        with open(self.listpath, 'w', encoding='utf8') as listfile:\n            listfile.writelines(self.lines)\n        with open(self.listpath_back, 'w',  encoding='utf8') as listfile:\n            listfile.writelines(self.lines)\n\n    def enable(self):\n        self.cnt = 0\n        self.lines = []\n        self.available = True\n\n    def disable(self):\n        self.available = False\n"
  },
  {
    "path": "Mooc/Mooc_Request.py",
    "content": "'''\n    Mooc 的请求模块：包含 get, post, head 常用的三大请求\n'''\n\nfrom time import sleep\nfrom functools import wraps\nfrom socket import timeout, setdefaulttimeout\nfrom urllib import request, parse\nfrom urllib.error import ContentTooShortError, URLError, HTTPError\nfrom Mooc.Mooc_Config import *\n\n__all__ = [\n    'RequestFailed', 'request_get', 'request_post', 'request_head', 'request_check'\n]\n\nheaders = (\"User-Agent\",\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36\")  #这里模拟浏览器  \nopener = request.build_opener()  \nopener.addheaders = [headers]\nrequest.install_opener(opener)\nsetdefaulttimeout(TIMEOUT)\n\nclass RequestFailed(Exception):\n    pass\n\ndef request_decorate(count=3):\n    def decorate(func):\n        @wraps(func)\n        def wrap_func(*args, **kwargs):\n            cnt = 0\n            while True:\n                try:\n                    return func(*args, **kwargs)\n                except (ContentTooShortError, URLError, HTTPError, ConnectionResetError):\n                    cnt += 1\n                    if cnt >= count:\n                        break\n                    sleep(0.32)\n                except (timeout):\n                    break\n            raise RequestFailed(\"request failed\")\n        return wrap_func\n    return decorate\n\n@request_decorate()\ndef request_get(url, decoding='utf8'):\n    '''get请求'''\n    req = request.Request(url=url)\n    response = request.urlopen(req, timeout=TIMEOUT)\n    text = response.read().decode(decoding)\n    response.close()\n    return text\n\n@request_decorate()\ndef request_post(url, data, decoding='utf8'):\n    '''post请求'''\n    data = parse.urlencode(data).encode('utf8')\n    req = request.Request(url=url, data=data, method='POST')\n    response = request.urlopen(req, timeout=TIMEOUT)\n    text = response.read().decode(decoding)\n    response.close()\n    return text\n\n@request_decorate()\ndef request_head(url):\n    '''head请求'''\n    req = request.Request(url=url);\n    response = request.urlopen(req, timeout=TIMEOUT)\n    header =  dict(response.getheaders())\n    response.close()\n    return header\n\n@request_decorate(1)\ndef request_check(url):\n    '''检查url是否可以访问'''\n    req = request.Request(url=url);\n    response = request.urlopen(req, timeout=TIMEOUT//10)\n    response.close()\n"
  },
  {
    "path": "Mooc/__init__.py",
    "content": "'''\nMooc下载器版本  3.4.1\n作者            PyJun\n邮箱            py.jun@qq.com\ngithub          https://github.com/PyJun/Mooc_Downloader\n博客            https://blog.csdn.net/qq_16166591/article/details/85249743\n'''\n\n__version__ = 'Mooc-3.4.1'\n__author__  = 'PyJun'\n__email__   = 'py.jun@qq.com'\n__github__  = 'https://github.com/PyJun/Mooc_Downloader'\n__blog__    = 'https://blog.csdn.net/qq_16166591/article/details/85249743'\n"
  },
  {
    "path": "Mooc/__main__.py",
    "content": "from Mooc.Mooc_Main import main\n\nmain()\n"
  },
  {
    "path": "Mooc.spec",
    "content": "# -*- mode: python -*-\n\nblock_cipher = None\n\n\na = Analysis(['Mooc\\\\Mooc_Main.py'],\n             pathex=['.'],\n             binaries=[],\n             datas=[\n              ('Mooc\\\\aria2c.exe', '.'),\n              ('Mooc\\\\Alipay.jpg', '.')\n             ],\n             hiddenimports=[],\n             hookspath=[],\n             runtime_hooks=[],\n             excludes=[],\n             win_no_prefer_redirects=False,\n             win_private_assemblies=False,\n             cipher=block_cipher,\n             noarchive=False)\npyz = PYZ(a.pure, a.zipped_data,\n             cipher=block_cipher)\nexe = EXE(pyz,\n          a.scripts,\n          a.binaries,\n          a.zipfiles,\n          a.datas,\n          [],\n          name='Mooc-3.4.2',\n          debug=False,\n          bootloader_ignore_signals=False,\n          strip=False,\n          upx=True,\n          runtime_tmpdir=None,\n          console=True , icon='Mooc\\\\Mooc.ico')\n"
  },
  {
    "path": "README.md",
    "content": "### \t\t\t\t基于Python 爬虫的慕课视频下载【开源代码停止维护，软件仍在维护更新】\n\n##### 1.  项目简介：\n\n- 项目环境为 Windows10,  Python3\n- 用 Python3.6 urllib3 模块爬虫，涉及模块包括标准库、三方库和其它开源组件，已打包成exe文件\n- 支持Mooc视频，字幕，课件下载，课程以目录树形式下载到硬盘，支持Potplayer播放\n- 支持中国大学，网易云课堂，网易公开课，有道精品课，有道领世，启航教育，腾讯课堂，腾讯会议，钉钉，飞书，中公网校，荔枝微课，海豚知道，伯索云学堂，爱问云，家辉云，百家云，学浪，抖音课堂，B站课堂，希望学，希望学素养，希望优课，研途考研，高途，途途，高途高中规划，高途素养，千聊，兴趣岛，橙啦，爱课程，学堂在线，超星学习通（学银在线），知到智慧树，智慧职教，华尔街学堂，等网课的视频课程下载，核心下载调用 Aria2c\n- 用户可以直接下载 Release 下的 [学无止下载器](https://github.com/PyJun/Mooc_Downloader/releases)  安装即可使用\n- 有关下载器的使用以及相关问题，点击查看[Mooc下载器帮助文档](https://github.com/PyJun/Mooc_Downloader/wiki)\n\n##### 2. 功能演示：\n\n![demo1.png](http://xuewuzhi.cn/images/demo1.png)\n\n![demo2.png](http://xuewuzhi.cn/images/demo2.png)\n\n##### 4.项目文件\n\n- Mooc_Main.py\t          整个项目的主程序,  其实是调用了 Mooc_Interface\n- Mooc_Interface.py       人机交互接口模块\n- Mooc_Config.py            Mooc 的配置文件\n- Mooc_Base.py               Mooc  抽象基类\n- Mooc_Potplayer.py       用于生成专用于 Potplayer 播放的 dpl 文件 \n- Mooc_Request.py          用 urllib 包装的一个Mooc请求库\n- Mooc_Download.py      调用 Aira2c 下载的命令接口\n- Icourses                          有关爱课程的模块包\n  - Icourse_Base.py              爱课程下载器的基类，继承自 Mooc_Base\n  - Icourse_Config.py            配置文件\n  - Icourse_Cuoc.py              爱课程视频公开课的下载的子类，http://www.icourses.cn/cuoc/\n  - Icourse_Mooc.py             爱课程资源共享课的下载的子类，http://www.icourses.cn/mooc/\n\n- Icourse163                      有关中国大学慕课的模块包\n  - Icourse163_Base.py         中国大学慕课下载器的基类，继承自 Mooc_Base\n  - Icourse163_Config.py       配置文件\n  - Icourse163_Mooc.py        中国大学慕课下载器得子类，继承自 Icourse163_Base.py\n\n##### 5.运行项目\n\n请确保在项目工程的根目录下，然后在终端输入以下指令（python3 环境，无依赖的第三方模块）\n\n```powershell\npython -m Mooc\n```\n\n##### 6.打包指令\n\n1. 首先确保已经安装 **pyinstaller**，若未安装，则用 pip 安装，打开终端，输入：\n\n   ```powershell\n   pip install pyinstaller\n   ```\n\n2. 然后在项目工程的根目录下，终端输入：\n\n   ```powershell\n   pyinstaller Mooc.spec\n   ```\n\n3. 最后会在项目工程根目录下出现一个**dist**文件夹，该文件夹会出现一个**Mooc-3.4.0.exe**程序\n\n![package.png](http://xuewuzhi.cn/images/package.png)\n\n\n##### 7.注意事项\n项目代码已好久未更新，Releases下有我打包好的exe文件，可直接下载使用~\n【该项目为早期开源的代码，最新版本代码未开源】\n1. 新版代码涉及网站爬虫、解析、解密，开源后容易和谐失效\n2. 新版本涉及太多的模块依赖（包括且不限于nodejs,electron,ariac2,annie,ffmpeg,wkhtmltopdf和一些自编译的python依赖库），难以分离出可独立可用的开源版\n3. 实在没有精力同时维护二个开源和闭源版本的代码\n4. 该项目并非完整的开源项目，提供的软件无病毒，可免费使用（也包含付费功能）\n"
  }
]