慕课网 下载视频(失效)

获取视频下载链接,使用方法:进入任意课程点击下载即可。如http://www.imooc.com/learn/814。慕课网已废弃v1和v2接口, 全面启用HLS, 此脚本失效, 详情看脚本内说明。github:https://github.com/Ahaochan/Tampermonkey,欢迎star和fork。

  1. // ==UserScript==
  2. // @name 慕课网 下载视频(失效)
  3. // @namespace https://github.com/Ahaochan/Tampermonkey
  4. // @version 0.2.7
  5. // @description 获取视频下载链接,使用方法:进入任意课程点击下载即可。如http://www.imooc.com/learn/814。慕课网已废弃v1和v2接口, 全面启用HLS, 此脚本失效, 详情看脚本内说明。github:https://github.com/Ahaochan/Tampermonkey,欢迎star和fork。
  6. // @author Ahaochan
  7. // @match http://www.imooc.com/learn/*
  8. // @match https://www.imooc.com/learn/*
  9. // @grant GM_xmlhttpRequest
  10. // @grant GM_setClipboard
  11. // @require http://code.jquery.com/jquery-1.11.0.min.js
  12. // ==/UserScript==
  13. (function () {
  14. 'use strict';
  15. // 最新视频下载方法
  16. // 例如下载http://www.imooc.com/video/14351,点击F12,点击Network,筛选XHR,找到medium.hxk。复制神秘代码58c65fc3e520e5677f8b457a。
  17. // 下载地址就是http://v3.mukewang.com/584e2423b3fee3bb558b7896/H.mp4。
  18. // 估计是通过http://www.imooc.com/course/14351/medium.m3u8?cdn=aliyun返回的数据,通过某种解密方式获得的神秘代码
  19. // 思路在这,个人水平不够,修复不了,有能力希望能fork并pull request一下。。。
  20.  
  21. // 2017年10月3日
  22. // 现在慕课网已经把http://v1.mukewang.com和http://v2.mukewang.com的DNS解析停掉了。
  23. // 看来已经全面启用HLS传输视频流。可以看到http://m.imooc.com/course/3725/high.m3u8?cdn=aliyun应该就是获取m3u8文件的链接。
  24. // 而且慕课网对m3u8文件进行了加密, 奇怪的是, 每次刷新的加密后m3u8文件字符串都不一样, 但是请求的ts文件是一样的url。不知道慕课网是怎么做到的。
  25. // 上面的解决方案也只能解决一部分视频的下载。
  26. // 还是本人技术不够, 所以放弃此脚本的维护。
  27.  
  28.  
  29. /**--------------------------------获取下载链接---------------------------------------------*/
  30. var videoes = [];
  31. var $medias = $('.mod-chapters').find('a.J-media-item');
  32. var total = $medias.length;
  33. var len = total;
  34. //添加提示标签
  35. $('.course-menu').append($('<li><a href="javascript:void(0)"><span id="downTip">慕课网下载脚本已失效</span></a></li>'));
  36.  
  37. return; // 中止此脚本运行
  38.  
  39. if (!isLogin) {
  40. $('#downTip').text('视频下载异常,点击进行登录')
  41. .click(function () {
  42. var clickEvent = document.createEvent('MouseEvents');
  43. clickEvent.initEvent('click', true, true);
  44. document.getElementById('js-signin-btn').dispatchEvent(clickEvent);
  45. });
  46. return;
  47. }
  48. //遍历获取下载链接
  49. $medias.each(function (key, value) {
  50. var vid = $(this).parent().attr('data-media-id');
  51. var name = $(this).text();
  52. var pattern = /\(\d\d:\d\d\)/;
  53. if (!pattern.test(name)) {
  54. total--;
  55. if (key === len - 1 && !total) {
  56. $('#downTip').text('无视频下载');
  57. }
  58. return;
  59. }
  60. name = name.replace(/\(\d{2}:\d{2}\)/, '').replace(/\s/g, '');
  61. v1(vid, name, $(this));
  62. //v2(vid, name, $(this));
  63. //v3(vid, name, $(this));
  64. });
  65. /**--------------------------------获取下载链接---------------------------------------------*/
  66. /**--------------------------------视频下载解析接口-----------------------------------------*/
  67. /** v1接口,强制转换为v1接口 */
  68. function v1(vid, name, item) {
  69. $.getJSON('/course/ajaxmediainfo/?mid=' + vid + '&mode=flash', function (response) {
  70. console.log('加载数据:' + vid);
  71. if (response.data.result.mpath instanceof Array) {
  72. var url = response.data.result.mpath[0].replace('http://v2', 'http://v1');
  73. parseVideo(vid, name, url, item);
  74. } else {
  75. $('#downTip').text('不支持新版视频, 若要下载请查看源码中的注释');
  76. }
  77.  
  78. });
  79. }
  80.  
  81. /** v2接口,只能解析v1,v2(已废弃) */
  82. function v2(vid, name, item) {
  83. $.getJSON('/course/ajaxmediainfo/?mid=' + vid + '&mode=flash', function (response) {
  84. var url = response.data.result.mpath[0];
  85. parseVideo(vid, name, url, item);
  86. });
  87. }
  88.  
  89. /** v3接口,解析v1,v2,v3(已废弃) */
  90. function v3(vid, name, item) {
  91. GM_xmlhttpRequest({
  92. method: 'GET',
  93. url: 'http://m.imooc.com/video/' + vid,
  94. onload: function (response) {
  95. var pattern = /(http.+mp4)/;
  96. var url = response.responseText.match(pattern)[0];
  97. parseVideo(vid, name, url, item);
  98. }
  99. });
  100. }
  101.  
  102. /**--------------------------------视频下载解析接口-----------------------------------------*/
  103. /**--------------------------------处理数据-------------------------------------------------*/
  104. function parseVideo(vid, name, url, item) {
  105. var urlL = url.replace('H.mp4', 'M.mp4').replace('M.mp4', 'L.mp4');
  106. var urlM = url.replace('H.mp4', 'M.mp4').replace('L.mp4', 'M.mp4');
  107. var urlH = url.replace('L.mp4', 'M.mp4').replace('M.mp4', 'H.mp4');
  108. var video = {
  109. vid: vid,
  110. name: name,
  111. url: [urlL, urlM, urlH]
  112. };
  113. videoes.push(video);
  114. //添加单个下载链接
  115. var $link = $('<a href="' + video.url[clarityType] + '" style="position:absolute;right:100px;top:0;" target="_blank">下载</a>');
  116. $link.bind('DOMNodeInserted', function () {
  117. $(this).find('i').remove();
  118. });//移除子标签
  119. item.after($link);
  120. //添加全部下载链接
  121. if (videoes.length === total) {
  122. $('#downTip').text('视频下载(' + total + '),已复制到剪贴板');
  123. videoes.sort(function (a, b) {
  124. if (a.name > b.name) return 1;
  125. else if (a.name < b.name) return -1;
  126. else return 0;
  127. });
  128. //显示
  129. $('#downloadBox').append($('<div style="margin-top:24px;">' +
  130. '<div style="border:1px solid #b7bbbf;box-sizing:border-box;border-radius:2px;">' +
  131. '<textarea id="downLoadArea" style="width:97%;min-height:100px;padding:8px;color:#555;resize:none;line-height:18px;"></textarea>' +
  132. '</div>')
  133. );
  134. textAreaChange();
  135. }
  136. }
  137.  
  138. /**--------------------------------处理数据-------------------------------------------------*/
  139.  
  140. /**--------------------------------导出设置-------------------------------------------------*/
  141. var clarityType = 2;
  142. var outTextType = 'idm';
  143. $('div.mod-tab-menu').after(
  144. $('<div id="downloadBox" class="course-brief">' +
  145. '<div style="float:left;margin-right:30px;">' +
  146. '<h4 style="font-weight:700;font-size: 16px;marginTop:10px">下载清晰度 : </h4>' +
  147. '<label for="lowClarity" >普清(L)</label><input type="radio" id="lowClarity" name="clarity" value="0" />' +
  148. '<label for="middleClarity">高清(M)</label><input type="radio" id="middleClarity" name="clarity" value="1" />' +
  149. '<label for="highClarity" >超清(H)</label><input type="radio" id="highClarity" name="clarity" value="2" checked="checked" />' +
  150. '</div>' +
  151. '<div>' +
  152. '<h4 style="font-weight:700;font-size: 16px;marginTop:10px">导出格式 : </h4>' +
  153. '<label for="rawOutText" >raw </label><input type="radio" id="rawOutText" name="outText" value="raw"/>' +
  154. '<label for="idmOutText" >idm </label><input type="radio" id="idmOutText" name="outText" value="idm" checked="checked" />' +
  155. '<label for="xmlOutText" >xml </label><input type="radio" id="xmlOutText" name="outText" value="xml" />' +
  156. '<label for="jsomOutText">json</label><input type="radio" id="jsomOutText" name="outText" value="json"/><br/>' +
  157. '</div>' +
  158. '</div>')
  159. );
  160. $('input:radio').css('margin', 'auto 50px auto 3px');//设置单选框
  161. $('input:radio[name=clarity]').change(function () {
  162. clarityType = this.value;
  163. textAreaChange();
  164. });
  165. $('input:radio[name=outText]').change(function () {
  166. outTextType = this.value;
  167. textAreaChange();
  168. });
  169. function textAreaChange() {
  170. var downloadTextArea = getTextLinks(clarityType, outTextType);
  171. GM_setClipboard(downloadTextArea);
  172. $('#downloadBox').find('textarea').text(downloadTextArea);
  173. }
  174.  
  175. /**--------------------------------导出设置-------------------------------------------------*/
  176.  
  177.  
  178. /**--------------------------------格式化下载链接用以显示---------------------------------*/
  179. function getTextLinks(clarityType, outTextType) {
  180. if (outTextType === 'json') return JSON.stringify(videoes);
  181. else {
  182. var str = '';
  183. for (var i in videoes) {
  184. if (outTextType === 'xml') {
  185. str += '\t<video>\n\t\t<url>' + videoes[i].url[clarityType] + '</url>\n\t\t<name>' + videoes[i].name + '</name>\n\t</video>\n';
  186. } else if (outTextType === 'raw') {
  187. str += videoes[i].url[clarityType] + '\n';
  188. } else {//idm
  189. str += 'filename=' + videoes[i].name + '&fileurl=' + videoes[i].url[clarityType] + '\n';
  190. }
  191. }
  192. if (outTextType === 'xml') str = '<?xml version="1.0" encoding="utf-8" ?>\n<videoes>\n' + str + '</videoes>';
  193. return str;
  194. }
  195. }
  196.  
  197. /**--------------------------------格式化下载链接用以显示---------------------------------*/
  198. })();