学习强国梨酱小帮手

页面内登录(不可用)/搜索+视频/音频/电子书一键批量下载+拦截Log请求+电子书去水印

  1. // ==UserScript==
  2. // @name 学习强国梨酱小帮手
  3. // @namespace https://qinlili.bid/
  4. // @version 1.1.8
  5. // @description 页面内登录(不可用)/搜索+视频/音频/电子书一键批量下载+拦截Log请求+电子书去水印
  6. // @author 琴梨梨
  7. // @match *://www.xuexi.cn/*
  8. // @match *://boot-source.xuexi.cn/newmoocdown?*
  9. // @match *://boot-source.xuexi.cn/audiodown?*
  10. // @match *://preview-pdf.xuexi.cn/*
  11. // @match *://article.xuexi.cn/*
  12. // @match https://login.xuexi.cn/login/xuexiWeb?*
  13. // @match https://static.xuexi.cn/search/*
  14. // @icon https://www.xuexi.cn/favicon.ico
  15. // @homepage https://github.com/qinlili23333/XXQG-DL
  16. // @supportURL https://github.com/qinlili23333/XXQG-DL
  17. // @grant none
  18. // @run-at document-end
  19. // @require https://lib.baomitu.com/jspdf/2.5.1/jspdf.umd.min.js
  20. // @license Anti996License
  21. // ==/UserScript==
  22.  
  23.  
  24. (async function () {
  25. 'use strict';
  26. //既然连喜欢的人都没能力留住,那还是把更多时间投入到写代码吧--记于与悦悦子分手的7天之后(2022.1.11)
  27. //这些内容给本项目开发提供了帮助,感谢
  28. //https://stackoverflow.com/a/60644673
  29. //https://stackoverflow.com/a/55165133
  30. //也感谢每一位相信琴梨梨的用户
  31.  
  32. //真的有人会看琴梨梨写的注释吗?在看的话MUA你一下~
  33.  
  34. //是否开启跨源服务器
  35. //开启跨源服务器可以下载部分本来会出错的电子书
  36. //请访问此地址获取跨源服务器https://github.com/Rob--W/cors-anywhere
  37. //Clone到本地后运行npm install,然后运行node server.js,即可运行在默认地址和端口上
  38. var corsServer = "http://localhost:8080/";
  39. if (document.location.host == "preview-pdf.xuexi.cn" && (document.location.search.indexOf("boot-video.xuexi.cn") > 1) && (window.self === window.top) && confirm("该地址可能需要跨源服务器下载。启用跨源服务器吗?请在确认跨源服务器已启动之后点击确定。\n不知道跨源服务器是什么的话打开脚本源码看注释")) {
  40. var valueProp = Object.getOwnPropertyDescriptor(Image.prototype, 'src');
  41. Object.defineProperty(Image.prototype, 'src', {
  42. set: function(newimgValue){
  43. if (!newimgValue.startsWith("data:")) {
  44. newimgValue = corsServer + newimgValue;
  45. }
  46. this.crossOrigin = "anonymous"
  47. valueProp.set.call(this, newimgValue);
  48. }
  49. });
  50. }
  51. //iframe页面处理
  52. if (!(window.self === window.top)) {
  53. var transparentStyle = "background:none transparent !important;";
  54. document.documentElement.style = transparentStyle;
  55. document.body.style = transparentStyle;
  56. if (document.location.href.indexOf("login.xuexi.cn/login/xuexiWeb?") > 1) {
  57. document.getElementsByClassName("login_content")[0].style.background = "none"
  58. };
  59. if (document.location.href.indexOf("static.xuexi.cn/search/online/index.html") > 1) {
  60. document.getElementById("root").style.background = transparentStyle;
  61. if ((window.self.innerWidth > window.self.innerHeight) && window.self.innerWidth > 1000) {
  62. document.getElementsByClassName("search-content")[0].style = "padding-left:20px;padding-right:20px;"
  63. }
  64. };
  65. }
  66. //干掉日志
  67. (open=> {
  68. XMLHttpRequest.prototype.open = function (method, url, async, user, pass) {
  69. if (!(async === false)) {
  70. async = true;
  71. };
  72. if (url.startsWith("https://iflow-api.xuexi.cn/logflow/api/v1/pclog") || url.startsWith("https://arms-retcode.aliyuncs.com/r.png")) {
  73. console.log("Rejected Log XHR! " + url + " -Qinlili")
  74. url = "data:text,null"
  75. }
  76. open.call(this, method, url, async, user, pass);
  77. };
  78. })(XMLHttpRequest.prototype.open);
  79. var originFetch = fetch;
  80. window.fetch = (url, options) => {
  81. if (url.startsWith("https://iflow-api.xuexi.cn/logflow/api/v1/pclog") || url.startsWith("https://arms-retcode.aliyuncs.com/r.png")) {
  82. console.log("Rejected Log Fetch! " + url + " -Qinlili")
  83. url = "data:text,null"
  84. }
  85. return originFetch(url, options);
  86. }
  87. //干掉PDF水印
  88. if (document.location.host == "preview-pdf.xuexi.cn") {
  89. CanvasRenderingContext2D.prototype.fillText = () => { }
  90. }
  91. //共享库
  92. const SakiProgress = {
  93. isLoaded: false,
  94. progres: false,
  95. pgDiv: false,
  96. textSpan: false,
  97. first: false,
  98. alertMode: false,
  99. init: function (color) {
  100. if (!this.isLoaded) {
  101. this.isLoaded = true;
  102. console.info("SakiProgress Initializing!\nVersion:1.0.3\nQinlili Tech:Github@qinlili23333");
  103. this.pgDiv = document.createElement("div");
  104. this.pgDiv.id = "pgdiv";
  105. this.pgDiv.style = "z-index:9999;position:fixed;background-color:white;min-height:32px;width:auto;height:32px;left:0px;right:0px;top:0px;box-shadow:0px 2px 2px 1px rgba(0, 0, 0, 0.5);transition:opacity 0.5s;display:none;";
  106. this.pgDiv.style.opacity = 0;
  107. this.first = document.body.firstElementChild;
  108. document.body.insertBefore(this.pgDiv, this.first);
  109. this.first.style.transition = "margin-top 0.5s"
  110. this.progress = document.createElement("div");
  111. this.progress.id = "dlprogress"
  112. this.progress.style = "position: absolute;top: 0;bottom: 0;left: 0;background-color: #F17C67;z-index: -1;width:0%;transition: width 0.25s ease-in-out,opacity 0.25s,background-color 1s;"
  113. if (color) {
  114. this.setColor(color);
  115. }
  116. this.pgDiv.appendChild(this.progress);
  117. this.textSpan = document.createElement("span");
  118. this.textSpan.style = "padding-left:4px;font-size:24px;";
  119. this.textSpan.style.display = "inline-block"
  120. this.pgDiv.appendChild(this.textSpan);
  121. var css = ".barBtn:hover{ background-color: #cccccc }.barBtn:active{ background-color: #999999 }";
  122. var style = document.createElement('style');
  123. if (style.styleSheet) {
  124. style.styleSheet.cssText = css;
  125. } else {
  126. style.appendChild(document.createTextNode(css));
  127. }
  128. document.getElementsByTagName('head')[0].appendChild(style);
  129. console.info("SakiProgress Initialized!");
  130. } else {
  131. console.error("Multi Instance Error-SakiProgress Already Loaded!");
  132. }
  133. },
  134. destroy: function () {
  135. if (this.pgDiv) {
  136. document.body.removeChild(this.pgDiv);
  137. this.isLoaded = false;
  138. this.progres = false;
  139. this.pgDiv = false;
  140. this.textSpan = false;
  141. this.first = false;
  142. console.info("SakiProgress Destroyed!You Can Reload Later!");
  143. }
  144. },
  145. setPercent: function (percent) {
  146. if (this.progress) {
  147. this.progress.style.width = percent + "%";
  148. } else {
  149. console.error("Not Initialized Error-Please Call `init` First!");
  150. }
  151. },
  152. clearProgress: function () {
  153. if (this.progress) {
  154. this.progress.style.opacity = 0;
  155. setTimeout(function () { SakiProgress.progress.style.width = "0%"; }, 500);
  156. setTimeout(function () { SakiProgress.progress.style.opacity = 1; }, 750);
  157. } else {
  158. console.error("Not Initialized Error-Please Call `init` First!")
  159. }
  160. },
  161. hideDiv: function () {
  162. if (this.pgDiv) {
  163. if (this.alertMode) {
  164. setTimeout(function () {
  165. SakiProgress.pgDiv.style.opacity = 0;
  166. SakiProgress.first.style.marginTop = "";
  167. setTimeout(function () {
  168. SakiProgress.pgDiv.style.display = "none";
  169. }, 500);
  170. }, 3000);
  171. } else {
  172. this.pgDiv.style.opacity = 0;
  173. this.first.style.marginTop = "";
  174. setTimeout(function () {
  175. SakiProgress.pgDiv.style.display = "none";
  176. }, 500);
  177. }
  178. }
  179. else {
  180. console.error("Not Initialized Error-Please Call `init` First!");
  181. }
  182. },
  183. showDiv: function () {
  184. if (this.pgDiv) {
  185. this.pgDiv.style.display = "";
  186. setTimeout(function () { SakiProgress.pgDiv.style.opacity = 1; }, 10);
  187. this.first.style.marginTop = (this.pgDiv.clientHeight + 8) + "px";
  188. }
  189. else {
  190. console.error("Not Initialized Error-Please Call `init` First!");
  191. }
  192. },
  193. setText: function (text) {
  194. if (this.textSpan) {
  195. if (this.alertMode) {
  196. setTimeout(function () {
  197. if (!SakiProgress.alertMode) {
  198. SakiProgress.textSpan.innerText = text;
  199. }
  200. }, 3000);
  201. } else {
  202. this.textSpan.innerText = text;
  203. }
  204. }
  205. else {
  206. console.error("Not Initialized Error-Please Call `init` First!");
  207. }
  208. },
  209. setTextAlert: function (text) {
  210. if (this.textSpan) {
  211. this.textSpan.innerText = text;
  212. this.alertMode = true;
  213. setTimeout(function () { this.alertMode = false; }, 3000);
  214. }
  215. else {
  216. console.error("Not Initialized Error-Please Call `init` First!");
  217. }
  218. },
  219. setColor: function (color) {
  220. if (this.progress) {
  221. this.progress.style.backgroundColor = color;
  222. }
  223. else {
  224. console.error("Not Initialized Error-Please Call `init` First!");
  225. }
  226. },
  227. addBtn: function (img) {
  228. if (this.pgDiv) {
  229. var btn = document.createElement("img");
  230. btn.style = "display: inline-block;right:0px;float:right;height:32px;width:32px;transition:background-color 0.2s;"
  231. btn.className = "barBtn"
  232. btn.src = img;
  233. this.pgDiv.appendChild(btn);
  234. return btn;
  235. }
  236. else {
  237. console.error("Not Initialized Error-Please Call `init` First!");
  238. }
  239. },
  240. removeBtn: function (btn) {
  241. if (this.pgDiv) {
  242. if (btn) {
  243. this.pgDiv.removeChild(btn);
  244. }
  245. }
  246. else {
  247. console.error("Not Initialized Error-Please Call `init` First!");
  248. }
  249. }
  250. }
  251. const XHRDL = {
  252. isLoaded: false,
  253. dlList: [],
  254. listBtn: false,
  255. listDiv: false,
  256. listBar: false,
  257. clsBtn: false,
  258. init: function () {
  259. if (!this.isLoaded) {
  260. console.info("WebXHRDL Initializing!\nVersion:Preview0.1.0\nQinlili Tech:Github@qinlili23333")
  261. try {
  262. SakiProgress.init();
  263. } catch {
  264. console.error("Initialize Failed!Is SakiProgress Loaded?")
  265. return false;
  266. }
  267. this.isLoaded = true;
  268. //this.listBtn = SakiProgress.addBtn("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiBoZWlnaHQ9IjQ4cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjQ4cHgiIGZpbGw9IiMwMDAwMDAiPjxnPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iMjQiIHdpZHRoPSIyNCIvPjwvZz48Zz48Zz48cGF0aCBkPSJNMTguMzIsNC4yNkMxNi44NCwzLjA1LDE1LjAxLDIuMjUsMTMsMi4wNXYyLjAyYzEuNDYsMC4xOCwyLjc5LDAuNzYsMy45LDEuNjJMMTguMzIsNC4yNnogTTE5LjkzLDExaDIuMDIgYy0wLjItMi4wMS0xLTMuODQtMi4yMS01LjMyTDE4LjMxLDcuMUMxOS4xNyw4LjIxLDE5Ljc1LDkuNTQsMTkuOTMsMTF6IE0xOC4zMSwxNi45bDEuNDMsMS40M2MxLjIxLTEuNDgsMi4wMS0zLjMyLDIuMjEtNS4zMiBoLTIuMDJDMTkuNzUsMTQuNDYsMTkuMTcsMTUuNzksMTguMzEsMTYuOXogTTEzLDE5LjkzdjIuMDJjMi4wMS0wLjIsMy44NC0xLDUuMzItMi4yMWwtMS40My0xLjQzIEMxNS43OSwxOS4xNywxNC40NiwxOS43NSwxMywxOS45M3ogTTEzLDEyVjdoLTJ2NUg3bDUsNWw1LTVIMTN6IE0xMSwxOS45M3YyLjAyYy01LjA1LTAuNS05LTQuNzYtOS05Ljk1czMuOTUtOS40NSw5LTkuOTV2Mi4wMiBDNy4wNSw0LjU2LDQsNy45Miw0LDEyUzcuMDUsMTkuNDQsMTEsMTkuOTN6Ii8+PC9nPjwvZz48L3N2Zz4=");
  269. //this.listBtn.onclick = XHRDL.showList;
  270. SakiProgress.showDiv();
  271. SakiProgress.setText("初始化下载器...");
  272. SakiProgress.setPercent(20);
  273. this.listDiv = document.createElement("div");
  274. this.listDiv.style = "z-index:9999;position:fixed;background-color:white;width:auto;margin-top:32px;height:100%;left:0px;right:0px;top:0px;transition:opacity 0.5s;display:none;";
  275. this.listDiv.style.opacity = 0;
  276. this.listBar = document.createElement("div");
  277. this.listBar.style = "z-index:10000;position:fixed;background-color:white;min-height:32px;margin-top:32px;width:auto;height:32px;left:0px;right:0px;top:0px;box-shadow:0px 2px 2px 1px rgba(0, 0, 0, 0.5);";
  278. this.listDiv.appendChild(this.listBar);
  279. document.body.appendChild(this.listDiv);
  280. var btn = document.createElement("img");
  281. btn.style = "display: inline-block;right:0px;float:right;height:32px;width:32px;transition:background-color 0.2s;"
  282. btn.className = "barBtn"
  283. btn.src = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiBoZWlnaHQ9IjQ4cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjQ4cHgiIGZpbGw9IiMwMDAwMDAiPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iMjQiIHdpZHRoPSIyNCIvPjxwYXRoIGQ9Ik0yMiwzLjQxbC01LjI5LDUuMjlMMjAsMTJoLThWNGwzLjI5LDMuMjlMMjAuNTksMkwyMiwzLjQxeiBNMy40MSwyMmw1LjI5LTUuMjlMMTIsMjB2LThINGwzLjI5LDMuMjlMMiwyMC41OUwzLjQxLDIyeiIvPjwvc3ZnPg==";
  284. this.listBar.appendChild(btn);
  285. btn.onclick = function () {
  286. XHRDL.hideList();
  287. }
  288. this.clsBtn = btn;
  289. SakiProgress.setPercent(100);
  290. SakiProgress.setText("下载器已加载!");
  291. setTimeout(function () { SakiProgress.clearProgress(); SakiProgress.hideDiv(); }, 1000);
  292. console.info("WebXHRDL Initialized!");
  293. } else {
  294. console.error("Multi Instance Error-WebXHRDL Already Loaded!")
  295. }
  296. },
  297. destroy: function (saki) {
  298. if (this.isLoaded) {
  299. if (saki) {
  300. SakiProgress.destroy();
  301. }
  302. this.isLoaded = false;
  303. this.dlList = [];
  304. this.listBtn = false;
  305. this.listDiv = false;
  306. this.listBar = false;
  307. this.clsBtn = false;
  308. console.info("WebXHRDL Destroyed!You Can Reload Later!");
  309. }
  310. },
  311. showList: function () {
  312. if (XHRDL.isLoaded) {
  313. XHRDL.listDiv.style.display = "";
  314. setTimeout(function () { XHRDL.listDiv.style.opacity = 1; }, 10);
  315. } else {
  316. console.error("Not Initialized Error-Please Call `init` First!")
  317. }
  318. },
  319. hideList: function () {
  320. if (XHRDL.isLoaded) {
  321. XHRDL.listDiv.style.opacity = 0;
  322. setTimeout(function () { XHRDL.listDiv.style.display = "none"; }, 500);
  323. } else {
  324. console.error("Not Initialized Error-Please Call `init` First!")
  325. }
  326. },
  327. saveTaskList: function () {
  328. if (XHRDL.isLoaded) {
  329. var storage = window.localStorage;
  330. storage.setItem("XHRDL_List", JSON.stringify(this.dlList));
  331. } else {
  332. console.error("Not Initialized Error-Please Call `init` First!")
  333. }
  334. },
  335. loadTaskList: function () {
  336. if (XHRDL.isLoaded) {
  337. var storage = window.localStorage;
  338. this.dlList = JSON.parse(storage.getItem("XHRDL_List"));
  339. } else {
  340. console.error("Not Initialized Error-Please Call `init` First!")
  341. }
  342. },
  343. newTask: function (url, name) {
  344. if (this.isLoaded) {
  345. var list = this.dlList;
  346. list[list.length] = {
  347. taskUrl: url,
  348. fileName: name
  349. }
  350. SakiProgress.showDiv();
  351. SakiProgress.setText("已添加新任务:" + name);
  352. if (!this.DLEngine.isWorking) {
  353. this.DLEngine.start();
  354. }
  355. } else {
  356. console.error("Not Initialized Error-Please Call `init` First!")
  357. }
  358. },
  359. DLEngine: {
  360. isWorking: false,
  361. start: function () {
  362. if (!this.isWorking) {
  363. console.info("Start WebXHRDL Engine...\nChecking Tasks...");
  364. this.isWorking = true;
  365. SakiProgress.showDiv();
  366. this.dlFirstFile();
  367. } else {
  368. console.error("WebXHRDL Engine Already Started!");
  369. }
  370. },
  371. stop: function () {
  372. this.isWorking = false;
  373. SakiProgress.hideDiv();
  374. SakiProgress.setText("");
  375. if (XHRDL.dlList[0]) {
  376. console.info("All Tasks Done!WebXHRDL Engine Stopped!");
  377. } else {
  378. console.info("WebXHRDL Engine Stopped!Tasks Paused!");
  379. }
  380. },
  381. dlFirstFile: function () {
  382. var taskInfo = XHRDL.dlList[0];
  383. SakiProgress.showDiv();
  384. SakiProgress.setPercent(0);
  385. SakiProgress.setText("正在下载" + taskInfo.fileName);
  386. var xhr = new XMLHttpRequest();
  387. xhr.responseType = "blob";
  388. xhr.onprogress = event => {
  389. if (event.loaded && event.total) {
  390. var percent = String(Number(event.loaded) / Number(event.total) * 100).substring(0, 4);
  391. SakiProgress.setText(taskInfo.fileName + "已下载" + percent + "%");
  392. SakiProgress.setPercent(percent)
  393. }
  394. };
  395. xhr.onload = event => {
  396. if (xhr.readyState === 4) {
  397. if (xhr.status === 200) {
  398. var bloburl = URL.createObjectURL(xhr.response);
  399. SakiProgress.setText("正在写出" + taskInfo.fileName);
  400. var a = document.createElement('a');
  401. var filename = taskInfo.fileName;
  402. a.href = bloburl;
  403. a.download = filename;
  404. a.click();
  405. window.URL.revokeObjectURL(bloburl);
  406. SakiProgress.clearProgress();
  407. XHRDL.dlList.splice(0, 1);
  408. XHRDL.DLEngine.checkNext();
  409. } else {
  410. //TODO:支持更多特殊状态处理
  411. SakiProgress.setTextAlert(taskInfo.fileName + "暂不支持下载,跳过");
  412. XHRDL.dlList.splice(0, 1);
  413. XHRDL.DLEngine.checkNext();
  414. }
  415. }
  416. }
  417. xhr.onerror = function (e) {
  418. //TODO:支持处理不同类别出错
  419. if (!taskInfo.errorRetry) {
  420. SakiProgress.setTextAlert(taskInfo.fileName + "下载失败,置入列尾等待重试");
  421. taskInfo.errorRetry = true;
  422. var list = XHRDL.dlList;
  423. list[list.length] = taskInfo;
  424. } else {
  425. SakiProgress.setTextAlert(taskInfo.fileName + "下载又失败了,放弃");
  426. }
  427. XHRDL.dlList.splice(0, 1);
  428. XHRDL.DLEngine.checkNext();
  429. }
  430. xhr.open('GET', taskInfo.taskUrl, true)
  431. xhr.send()
  432. },
  433. checkNext: function () {
  434. if (XHRDL.dlList[0]) {
  435. this.dlFirstFile();
  436. } else {
  437. this.stop();
  438. }
  439. }
  440. }
  441. }
  442.  
  443.  
  444. const sleep = delay => new Promise(resolve => setTimeout(resolve, delay));
  445. const openFrame=url=>{
  446. var searchFrame = document.createElement("iframe");
  447. searchFrame.frameBorder = 0;
  448. searchFrame.style = "padding:100%;z-index:9999;position:fixed;backdrop-filter: blur(10px) brightness(100%);background-color: rgba(255, 255, 255, .6);width:100%;margin-top:0px;height:100%;left:0px;right:0px;top:0px;";
  449. document.body.appendChild(searchFrame);
  450. searchFrame.src = url;
  451. var clsBtn = document.createElement("img");
  452. clsBtn.style = "z-index:10000;position:fixed;display: inline-block;right:0px;top:0px;float:right;height:32px;width:32px;transition:background-color 0.2s;"
  453. clsBtn.className = "barBtn"
  454. clsBtn.src = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiBoZWlnaHQ9IjQ4cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjQ4cHgiIGZpbGw9IiMwMDAwMDAiPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iMjQiIHdpZHRoPSIyNCIvPjxwYXRoIGQ9Ik0yMiwzLjQxbC01LjI5LDUuMjlMMjAsMTJoLThWNGwzLjI5LDMuMjlMMjAuNTksMkwyMiwzLjQxeiBNMy40MSwyMmw1LjI5LTUuMjlMMTIsMjB2LThINGwzLjI5LDMuMjlMMiwyMC41OUwzLjQxLDIyeiIvPjwvc3ZnPg==";
  455. document.body.appendChild(clsBtn);
  456. searchFrame.addEventListener("load", async () => { await sleep(150); searchFrame.style.padding = "0px"; });
  457. clsBtn.onclick = () => {
  458. document.body.removeChild(searchFrame);
  459. document.body.removeChild(clsBtn);
  460. }
  461. };
  462. //主站检测
  463. if (document.location.host == "www.xuexi.cn" || document.location.host == "preview-pdf.xuexi.cn") {
  464. console.log("JS Loaded,Sleep 3 Sec-Qinlili");
  465. await sleep(3000)
  466. if (window.self === window.top) {
  467. //初始化下载工具条
  468. XHRDL.init();
  469. var dlPannel = document.createElement("div");
  470. var downloadBtn = document.createElement("button");
  471. downloadBtn.innerText = "下载本页内容";
  472. downloadBtn.style.display = "inline-block";
  473. dlPannel.appendChild(downloadBtn);
  474. var dlText = document.createElement("p");
  475. dlText.style.display = "inline-block";
  476. dlText.innerText = "等待检测页面类型";
  477. dlPannel.appendChild(dlText);
  478. var first = document.body.firstChild;
  479. document.body.insertBefore(dlPannel, first);
  480. //接管搜索
  481. if (document.getElementsByClassName("icon search-icon")[0]) {
  482. var scBtn = document.getElementsByClassName("icon search-icon")[0];
  483. var scPrt = scBtn.parentElement;
  484. scPrt.removeChild(scBtn);
  485. scBtn = document.createElement("a");
  486. scBtn.className = "icon search-icon";
  487. scPrt.appendChild(scBtn);
  488. scBtn.addEventListener("click", async e => {
  489. var searchFrame = document.createElement("iframe");
  490. searchFrame.frameBorder = 0;
  491. searchFrame.style = "padding:100%;z-index:9999;position:fixed;backdrop-filter: blur(10px) brightness(100%);background-color: rgba(255, 255, 255, .6);width:100%;margin-top:0px;height:100%;left:0px;right:0px;top:0px;";
  492. document.body.appendChild(searchFrame);
  493. searchFrame.src = "https://static.xuexi.cn/search/online/index.html";
  494. var clsBtn = document.createElement("img");
  495. clsBtn.style = "z-index:10000;position:fixed;display: inline-block;right:0px;top:0px;float:right;height:32px;width:32px;transition:background-color 0.2s;"
  496. clsBtn.className = "barBtn"
  497. clsBtn.src = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiBoZWlnaHQ9IjQ4cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjQ4cHgiIGZpbGw9IiMwMDAwMDAiPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iMjQiIHdpZHRoPSIyNCIvPjxwYXRoIGQ9Ik0yMiwzLjQxbC01LjI5LDUuMjlMMjAsMTJoLThWNGwzLjI5LDMuMjlMMjAuNTksMkwyMiwzLjQxeiBNMy40MSwyMmw1LjI5LTUuMjlMMTIsMjB2LThINGwzLjI5LDMuMjlMMiwyMC41OUwzLjQxLDIyeiIvPjwvc3ZnPg==";
  498. document.body.appendChild(clsBtn);
  499. searchFrame.addEventListener("load", async () => { await sleep(150); searchFrame.style.padding = "0px"; });
  500. clsBtn.onclick = () => {
  501. document.body.removeChild(searchFrame);
  502. document.body.removeChild(clsBtn);
  503. window.removeEventListener("message", msg, false);
  504. }
  505. function msg(e) {
  506. //抄的官方JS改出来的,我对于这种非要传值到上层窗口的做法完全无法理解,但既然能跑,管他呢
  507. console.log('e:', e)
  508. console.log('e.data:', e.data)
  509. try {
  510. var params = JSON.parse(e.data);
  511. if (params.type) {
  512. console.log('params.type:', params.type);
  513. console.log('params.data:', params.data);
  514. switch (params.type) {
  515. case 'search':
  516. var useQuestionMark = false;
  517. var targetUrl = 'https://static.xuexi.cn/search/online/index.html'
  518. for (var key in params.data) {
  519. var value = params.data[key];
  520. var op = '&'
  521. if (!useQuestionMark) {
  522. op = '?';
  523. useQuestionMark = true;
  524. }
  525. targetUrl += op + key + '=' + value;
  526. }
  527. searchFrame.style.padding = "100%";
  528. searchFrame.src = targetUrl;
  529. break;
  530. default:
  531. break;
  532. }
  533. }
  534. } catch (error) {
  535. console.log(error)
  536. }
  537. }
  538. window.addEventListener("message", msg, false);
  539. })
  540. }
  541. //接管登录(不可用)
  542. if (document.getElementsByClassName("icon login-icon")[0]) {
  543. var dlBtn = document.getElementsByClassName("icon login-icon")[0];
  544. var dlPrt = dlBtn.parentElement;
  545. dlPrt.removeChild(dlBtn);
  546. dlBtn = document.createElement("a");
  547. dlBtn.className = "icon login-icon";
  548. dlPrt.appendChild(dlBtn);
  549. dlBtn.addEventListener("click", async e => {
  550. e.preventDefault();
  551. SakiProgress.showDiv();
  552. SakiProgress.setPercent(5);
  553. SakiProgress.setText("正在准备登录(不可用)...")
  554. console.log("Login Hooked!-Qinlili");
  555. var closeBtn = SakiProgress.addBtn("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiBoZWlnaHQ9IjQ4cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjQ4cHgiIGZpbGw9IiMwMDAwMDAiPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iMjQiIHdpZHRoPSIyNCIvPjxwYXRoIGQ9Ik0yMiwzLjQxbC01LjI5LDUuMjlMMjAsMTJoLThWNGwzLjI5LDMuMjlMMjAuNTksMkwyMiwzLjQxeiBNMy40MSwyMmw1LjI5LTUuMjlMMTIsMjB2LThINGwzLjI5LDMuMjlMMiwyMC41OUwzLjQxLDIyeiIvPjwvc3ZnPg==")
  556. var loginFrame = document.createElement("iframe");
  557. loginFrame.frameBorder = 0;
  558. loginFrame.scrolling = "no";
  559. loginFrame.style = "padding:100%;z-index:9999;position:fixed;backdrop-filter: blur(10px) brightness(100%);background-color: rgba(255, 255, 255, .6);width:100%;margin-top:32px;height:100%;left:0px;right:0px;top:0px;";
  560. document.body.appendChild(loginFrame);
  561. loginFrame.addEventListener("load", async function () { await sleep(250); loginFrame.style.padding = "0px"; });
  562. var refreshBtn = SakiProgress.addBtn("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iNDhweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iNDhweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPjxwYXRoIGQ9Ik0xNy42NSA2LjM1QzE2LjIgNC45IDE0LjIxIDQgMTIgNGMtNC40MiAwLTcuOTkgMy41OC03Ljk5IDhzMy41NyA4IDcuOTkgOGMzLjczIDAgNi44NC0yLjU1IDcuNzMtNmgtMi4wOGMtLjgyIDIuMzMtMy4wNCA0LTUuNjUgNC0zLjMxIDAtNi0yLjY5LTYtNnMyLjY5LTYgNi02YzEuNjYgMCAzLjE0LjY5IDQuMjIgMS43OEwxMyAxMWg3VjRsLTIuMzUgMi4zNXoiLz48L3N2Zz4=")
  563. refreshBtn.onclick = () => {
  564. scanLogin();
  565. }
  566. closeBtn.onclick = () => {
  567. document.body.removeChild(loginFrame);
  568. SakiProgress.removeBtn(closeBtn);
  569. SakiProgress.removeBtn(refreshBtn);
  570. closeBtn = false;
  571. SakiProgress.clearProgress();
  572. SakiProgress.hideDiv();
  573. }
  574. async function scanLogin() {
  575. await sleep(100);
  576. SakiProgress.setPercent(10);
  577. SakiProgress.setText("正在获取登录(不可用)口令...");
  578. let token;
  579. let tokenText;
  580. try {
  581. token = await fetch("https://pc-api.xuexi.cn/open/api/sns/sign")
  582. tokenText = JSON.parse(await token.text())
  583. } catch (e) {
  584. SakiProgress.setPercent(100);
  585. SakiProgress.setText("出错了:网络连接中断! " + e.message);
  586. return;
  587. }
  588. if (tokenText.code = "200") {
  589. token = tokenText.data.sign;
  590. SakiProgress.setPercent(40);
  591. SakiProgress.setText("口令获取成功,加载登录(不可用)页面...");
  592. loginFrame.onload = () => {
  593. SakiProgress.setPercent(65);
  594. SakiProgress.setText("等待扫码...");
  595. }
  596. loginFrame.style.padding = "100%";
  597. loginFrame.src = "https://login.xuexi.cn/login/xuexiWeb?appid=dingoankubyrfkttorhpou&goto=https%3A%2F%2Foa.xuexi.cn&type=1&state=" + token + "&check_login=https%3A%2F%2Fpc-api.xuexi.cn"
  598. window.addEventListener("message", event => {
  599. event.preventDefault();
  600. console.log(event);
  601. if (event.data.success == true) {
  602. SakiProgress.setPercent(100);
  603. SakiProgress.setText("登录(不可用)成功,正在刷新页面...");
  604. document.location.reload();
  605. } else {
  606. SakiProgress.setPercent(100);
  607. SakiProgress.setText("出错了:扫码登录(不可用)失败,错误码为" + event.data.errorCode);
  608. }
  609. }, false);
  610. } else {
  611. SakiProgress.setPercent(100);
  612. SakiProgress.setText("出错了:获取口令失败!");
  613. }
  614. }
  615. scanLogin();
  616. })
  617. }
  618. //检测爬取页面类型
  619. console.log("Detecting Page " + document.location.pathname + "-Qinlili");
  620. var detected = false;
  621. //旧慕课列表
  622. if ((document.body.innerText.indexOf("课程介绍") >= 1) || (document.body.innerText.indexOf("课程详情") >= 1)) {
  623. console.log("Old Mooc List Detected " + document.location.pathname + "-Qinlili");
  624. detected = true;
  625. dlText.innerText = "页面类型:旧慕课列表,支持全部批量下载,请开启网站自动下载权限";
  626. downloadBtn.onclick = () => {
  627. OldMoocListDL();
  628. }
  629. }
  630. //页面有播放器
  631. if (window.Aliplayer) {
  632. //旧慕课、电视剧播放单页
  633. if (document.getElementsByClassName("radio-inline")[0]) {
  634. console.log("Old Video Player Detected " + document.location.pathname + "-Qinlili");
  635. detected = true;
  636. dlText.innerText = "页面类型:旧视频播放单页,支持全部批量下载,请开启网站自动下载权限";
  637. downloadBtn.onclick = () => {
  638. OldMoocVideoDL();
  639. }
  640. }
  641. //新慕课、影视总页
  642. if (document.getElementsByClassName("video-article-content")[0] || document.getElementsByClassName("videoSet-article-wrap")[0]) {
  643. console.log("New Video Player Detected " + document.location.pathname + "-Qinlili");
  644. detected = true;
  645. dlText.innerText = "页面类型:新视频总,支持全部批量下载最高清晰度,需要打开新标签页下载";
  646. downloadBtn.onclick = () => {
  647. NewMoocPageDL();
  648. }
  649. }
  650. }
  651. //音频专题
  652. if (document.getElementsByClassName("album-play-btn")[0]) {
  653. console.log("Audio Detected " + document.location.pathname + "-Qinlili");
  654. detected = true;
  655. dlText.innerText = "页面类型:音频,支持全部批量下载,需要打开新标签页下载";
  656. downloadBtn.onclick = () => {
  657. AudioDL();
  658. }
  659. }
  660. //页面上就一个音频
  661. //解锁音频播放器下载按钮
  662. if (document.getElementsByTagName("audio").length) {
  663. detected = true;
  664. dlText.innerText = "页面类型:单个音频,已经解锁播放器下载能力,点击播放器右侧菜单下载";
  665. downloadBtn.style.display = "none";
  666. for (var la = 0; document.getElementsByTagName("audio")[la]; la++) {
  667. document.getElementsByTagName("audio")[la].removeAttribute("controlslist");
  668. }
  669. }
  670. //电子书下载
  671. if (document.location.host == "preview-pdf.xuexi.cn") {
  672. detected = true;
  673. dlText.innerText = "页面类型:电子书,支持打包下载";
  674. downloadBtn.onclick = () => {
  675. PDFDL();
  676. }
  677. }
  678. if (!detected) {
  679. console.log("Unsupported Page " + document.location.pathname + "-Qinlili");
  680. dlText.innerText = "本页面不支持下载";
  681. downloadBtn.innerText = "暂不支持";
  682. }
  683.  
  684. //下载器部分
  685. //旧慕课列表
  686. function OldMoocListDL() {
  687. //读取全部视频列表
  688. var videoList = globalCache[Object.keys(globalCache)[0]];
  689. console.log("Found " + videoList.length + " Videos-Qinlili")
  690. for (var i = 0; videoList[i]; i++) {
  691. console.log("Try Analysis " + i + " Video-Qinlili")
  692. getInfoAndDL(pagetoinfourl(videoList[i].static_page_url));
  693. }
  694.  
  695. }
  696. //旧慕课播放单页
  697. function OldMoocVideoDL() {
  698. console.log("Analysis Page Info-Qinlili")
  699. getInfoAndDL(pagetoinfourl(document.location.href))
  700. }
  701. function NewMoocPageDL() {
  702. console.log("Open DL Page-Qinlili");
  703. var searchParams = new URLSearchParams(document.location.search);
  704. var dlurl = "https://boot-source.xuexi.cn/newmoocdown?id=" + searchParams.get("id");
  705. openFrame(dlurl);
  706. }
  707. function AudioDL() {
  708. console.log("Open DL Page-Qinlili");
  709. var searchParams = new URLSearchParams(document.location.search);
  710. var dlurl = "https://boot-source.xuexi.cn/audiodown?id=" + searchParams.get("id");
  711. openFrame(dlurl);
  712. }
  713. async function PDFDL() {
  714. //webp压缩用处和顶碗人一样大,所以换成灰度压缩
  715. let enableGreyCompress = confirm("是否启用灰度压缩?\n适合保存以黑白文本内容的书籍或用于Kindle等墨水屏阅读,可大幅削减文件体积,需额外消耗压缩时间。\n根据琴梨梨自己的测试可削减大约44%大小,可用于解决Chrome无法爬512M以上书的问题。")
  716. let lowQuality = confirm("是否启用低质量模式?\n轻微降低画质换取显著的文件缩小");
  717. SakiProgress.showDiv();
  718. SakiProgress.setText("正在加载依赖...");
  719. await sleep(100)
  720. console.log("Preparing jsPDF Library...-Qinlili");
  721. var jsPDF = jspdf.jsPDF;
  722. try {
  723. console.log(jsPDF)
  724. console.log("jsPDF Ready!")
  725. } catch {
  726. console.error("jsPDF Not Ready!")
  727. alert("jsPDF加载失败,请检查网络并尝试重新安装脚本!")
  728. }
  729. SakiProgress.setText("正在调整尺寸...");
  730. SakiProgress.setPercent(2);
  731. await sleep(100)
  732. if(confirm("是否开启高清模式?请观察原书清晰度,若原书清晰度较高建议开启。\n对超过300页的书开启可能导致无法生成文件。")){
  733. //按钮循环点击得稍微延迟一点否则可能卡死
  734. //放大到最大保障清晰度
  735. for (; document.getElementsByClassName("ctrl-icon")[0].className.animVal.indexOf("disabled") < 0;) {
  736. document.getElementsByClassName("ctrl-icon")[0].parentElement.click()
  737. await sleep(50)
  738. }
  739. }
  740. SakiProgress.setText("正在回到第一页...");
  741. SakiProgress.setPercent(4);
  742. await sleep(100)
  743. //回到第一页
  744. for (; document.getElementsByClassName("ctrl-icon")[2].className.animVal.indexOf("disabled") < 0;) {
  745. document.getElementsByClassName("ctrl-icon")[2].parentElement.click()
  746. await sleep(50)
  747. }
  748. //创建文件
  749. SakiProgress.setText("正在创建文件...");
  750. SakiProgress.setPercent(6);
  751. await sleep(100)
  752. var samplePage = document.getElementsByTagName("canvas")[0]
  753. var ori;
  754. let wP = samplePage.width;
  755. let hP = samplePage.height;
  756. if (wP > hP) { ori = "l" } else { ori = "p" }
  757. var PDFfile = new jsPDF({
  758. orientation: ori,
  759. unit: 'px',
  760. format: [wP, hP],
  761. putOnlyUsedFonts: true,
  762. });
  763. console.log("Preparing PDF File...-Qinlili");
  764. console.log(PDFfile);
  765. //监听函数
  766. SakiProgress.setText("正在设置监听函数...");
  767. SakiProgress.setPercent(8);
  768. await sleep(100)
  769. var onPageChange = function () { };
  770. var val = document.getElementsByTagName("input")[0];
  771. function waitPageChange() {
  772. return new Promise(resolve => {
  773. onPageChange = () => {
  774. resolve();
  775. }
  776. });
  777. }
  778. //加载完成后页码显示才会变化,监听页码显示来等待加载
  779. Object.defineProperty(val, 'value', {
  780. set: newValue => {
  781. var valueProp = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
  782. valueProp.set.call(val, newValue);
  783. onPageChange();
  784. }
  785. });
  786. //循环保存
  787. var page = 1;
  788. var totalPage = parseInt(document.getElementsByClassName("total")[0].innerText.substr(1));
  789. const saveCurrent=()=>{
  790. //不管有几页,把当前全部canvas保存再说
  791. for (var i = 0; document.getElementsByTagName("canvas")[i]; i++) {
  792. if (enableGreyCompress) {
  793. //灰度压缩
  794. let cnv = document.getElementsByTagName("canvas")[i];
  795. let cnx = cnv.getContext('2d');
  796. let width = cnv.width;
  797. let height = cnv.height;
  798. var imgPixels = cnx.getImageData(0, 0, width, height);
  799. for (var y = 0; y < height; y++) {
  800. for (var x = 0; x < width; x++) {
  801. let i = (y * 4) * width + x * 4;
  802. var avg = (imgPixels.data[i] + imgPixels.data[i + 1] + imgPixels.data[i + 2]) / 3;
  803. imgPixels.data[i] = avg;
  804. imgPixels.data[i + 1] = avg;
  805. imgPixels.data[i + 2] = avg;
  806. }
  807. }
  808. cnx.putImageData(imgPixels, 0, 0, 0, 0, imgPixels.width, imgPixels.height);
  809.  
  810. }
  811. if(lowQuality){
  812. PDFfile.addImage(document.getElementsByTagName("canvas")[i].toDataURL("image/webp",0.75),"WEBP",0,0,wP,hP,null,"SLOW");
  813. }else{
  814. PDFfile.addImage(document.getElementsByTagName("canvas")[i], "WEBP", 0, 0, wP, hP, null, "SLOW");
  815. }
  816. PDFfile.addPage();
  817. page++
  818. console.log("Saved One Page!-Qinlili");
  819. }
  820. }
  821. for (; document.getElementsByClassName("ctrl-icon")[3].className.animVal.indexOf("disabled") < 0;) {
  822. SakiProgress.setText("正在保存第" + page + "页...");
  823. SakiProgress.setPercent(10 + 80 * (page / totalPage));
  824. console.log("Work Current Page:" + page + "...-Qinlili");
  825. await sleep(100)
  826. saveCurrent();
  827. //虽然不知道为什么加了延迟半秒就不会卡住,但既然能用管他为什么呢
  828. setTimeout(() => { document.getElementsByClassName("ctrl-icon")[3].parentElement.click(); }, 500)
  829. //显示的正在加载的页面可能比实际加载页面小一页,但估计1919810个用户里也没一个意识到,不影响保存效果这种细节就不管了,问就是爷懒的写
  830. SakiProgress.setText("正在等待加载第" + (page + 1) + "页...");
  831. console.log("Waiting For Loading...-Qinlili");
  832. await waitPageChange();
  833. };
  834. saveCurrent();
  835. PDFfile.setFontSize(40);
  836. PDFfile.text('Powered By Qinlili',35, 65);
  837. PDFfile.textWithLink('https://gf.qytechs.cn/zh-CN/scripts/429991',35, 25,{align: 'center', url: 'https://gf.qytechs.cn/zh-CN/scripts/429991'});
  838. //生成文件导出
  839. SakiProgress.setText("正在导出文件...");
  840. SakiProgress.setPercent(90);
  841. PDFfile.save("学习强国电子书导出.pdf", { returnPromise: true }).then(finish => {
  842. SakiProgress.clearProgress;
  843. SakiProgress.hideDiv();
  844. });
  845. }
  846. } else {
  847. console.log("Iframe Page " + document.location.pathname + "\nSkip Detect-Qinlili");
  848. }
  849. }
  850.  
  851. //全局共享函数
  852. //读取视频信息并下载
  853. function getInfoAndDL(infourl) {
  854. console.log("Get Video Info:" + infourl + "\n-Qinlili")
  855. var xhr = new XMLHttpRequest();
  856. xhr.onload = event => {
  857. console.log("Success Get Video Info:" + infourl + "\n-Qinlili")
  858. if (xhr.readyState === 4 && xhr.status === 200) {
  859. var videoInfo = JSON.parse(xhr.response.replace("globalCache = ", "").replace(";", ""));
  860. stringToObject(videoInfo);
  861. //判断慕课
  862. if (videoInfo[Object.keys(videoInfo)[0]].info) {
  863. videoInfo = videoInfo[Object.keys(videoInfo)[0]].info;
  864. for (var vi = 0; videoInfo.ossUrl[vi]; vi++) {
  865. console.log("Video Name:" + videoInfo.frst_name + "-" + (vi + 1) + "\nChapter Name:" + videoInfo.mooc_class + "\nMooc Name:" + videoInfo.mooc + "\nVideo Url:" + videoInfo.ossUrl[vi] + "\n-Qinlili");
  866. var filename = videoInfo.frst_name + "-" + (vi + 1) + "-" + videoInfo.mooc_class + "-" + videoInfo.mooc + ".mp4"
  867. console.log("File Name:" + filename + "\nPrepare Download-Qinlili");
  868. downloadFile(videoInfo.ossUrl[vi], filename);
  869. }
  870. }
  871. //判断电视剧
  872. if (videoInfo[Object.keys(videoInfo)[0]].list) {
  873. videoInfo = videoInfo[Object.keys(videoInfo)[0]].list;
  874. for (var vii = 0; videoInfo[vii]; vii++) {
  875. console.log("Video Name:" + videoInfo[vii].frst_name + "-" + (vi + 1) + "\nList Name:" + videoInfo[vii].title + "\nVideo Url:" + videoInfo[vii].ossUrl + "\n-Qinlili");
  876. var filename2 = videoInfo[vii].frst_name + "-" + (vi + 1) + "-" + videoInfo[vii].title + ".mp4"
  877. console.log("File Name:" + filename2 + "\nPrepare Download-Qinlili");
  878. downloadFile(videoInfo[vii].ossUrl, filename2);
  879. }
  880. }
  881. }
  882. xhr.onerror = () => {
  883. console.log("Fail Get Video Info:" + infourl + "\n-Qinlili")
  884. }
  885. }
  886. xhr.open('GET', infourl, false);
  887. xhr.send();
  888.  
  889. //平整化Array工具,从学习强国本身的js里抄过来的,大概原理就是尝试把值作为json解析,解析成功就把解析结果替换回去,总之我大受震撼
  890. function stringToObject(params) {
  891. for (var key in params) {
  892. var value = params[key];
  893. if (isString(value)) {
  894. try {
  895. if (typeof JSON.parse(value) == 'object') {
  896. params[key] = JSON.parse(value);
  897. }
  898. } catch (e) { }
  899. } else if (isArray(value)) {
  900. try {
  901. for (var index = 0; index < value.length; index++) {
  902. stringToObject(value[index]);
  903. }
  904. } catch (e) { }
  905. } else if (isObject(value)) {
  906. try {
  907. stringToObject(params[key])
  908. } catch (e) { }
  909. }
  910. }
  911. }
  912.  
  913. function isArray(o) {
  914. return Object.prototype.toString.call(o) === '[object Array]';
  915. }
  916.  
  917. function isString(o) {
  918. return Object.prototype.toString.call(o) === '[object String]';
  919. }
  920.  
  921. function isObject(o) {
  922. return Object.prototype.toString.call(o) === '[object Object]';
  923. }
  924. }
  925.  
  926. //跳转避免跨域问题
  927. if (document.location.host == "article.xuexi.cn") {
  928. //检测是不是PDF页面,移动端分享文章也是这个域名,之前没发现
  929. var obj = document.getElementsByTagName("link");
  930. var isPDF = false;
  931. for (var pdfJS = 0; obj[pdfJS]; pdfJS++) {
  932. if (obj[pdfJS].href.indexOf("js/pdf") > 1) {
  933. isPDF = true;
  934. }
  935. }
  936. if (isPDF) {
  937. console.log("JS Loaded,Sleep 5 Sec-Qinlili");
  938. var tip = document.createElement("H1");
  939. tip.innerText = "即将跳转页面,请等待五秒";
  940. tip.style = "z-index:9999;position:fixed;backdrop-filter: blur(10px) brightness(100%);background-color: rgba(255, 255, 255, .6);width:100%;margin-top:0px;height:100%;left:0px;right:0px;top:0px;";
  941. document.body.appendChild(tip);
  942. await sleep(5000)
  943. document.location.href = document.getElementsByClassName("pdf-iframe")[0].src;
  944. }
  945. }
  946.  
  947.  
  948. //404注入页面避免cors
  949. if (document.location.host == "boot-source.xuexi.cn") {
  950. console.log("JS Domain Detected, Prepare Inject-Qinlili");
  951. document.querySelector("body").innerHTML = "<H2>本页面仅用于CORS注入,分享网址没有用,请允许下载多个文件</H2><H4 id=\"logcat\"></H4>"
  952. XHRDL.init();
  953. logcat("Initializing Downloader...");
  954. await sleep(3000)
  955. var searchParams = new URLSearchParams(document.location.search);
  956. var vid = searchParams.get("id");
  957. document.title = "下载:" + vid;
  958. logcat("Found ID:" + vid);
  959. logcat("Get Video Info:" + vid)
  960. var xhr = new XMLHttpRequest();
  961. xhr.onload = event => {
  962. if (xhr.readyState === 4 && xhr.status === 200) {
  963. logcat("Success Get JSON Info:" + vid)
  964. var videoInfo = JSON.parse(xhr.response.replace("callback(", "").replace("})", "}"));
  965. console.log(videoInfo);
  966. logcat("List Name:" + videoInfo.normalized_title)
  967. logcat("List Origin:" + videoInfo.show_source)
  968. //文件名后缀
  969. var filenamesource = "-" + videoInfo.normalized_title + "-" + videoInfo.show_source;
  970. //视频下载模式
  971. if (document.location.pathname == "/newmoocdown") {
  972. //检测是否为多视频
  973. if (videoInfo.sub_items) {
  974. logcat("Found " + videoInfo.sub_items.length + " Videos");
  975. //循环解析并下载视频
  976. for (var vi = 0; videoInfo.sub_items[vi]; vi++) {
  977. logcat("Analysis Video " + (vi + 1));
  978. //currentVideo,缩写为cV看起来清爽点
  979. var cV = videoInfo.sub_items[vi];
  980. var vName = cV.title;
  981. logcat("Video Name:" + vName);
  982. //检测多个清晰度
  983. var vurl = getHighest(cV.videos[0].video_storage_info);
  984. logcat("Video Url:" + vurl);
  985. var fName = vName + filenamesource + ".mp4";
  986. logcat("File Name:" + fName);
  987. logcat("Call Downloader, Downloader Log Output In F12");
  988. downloadFile(vurl, fName);
  989. }
  990. } else {
  991. //单个视频
  992. var singlevurl = getHighest(videoInfo.videos[0].video_storage_info);
  993. logcat("Video Url:" + singlevurl);
  994. vName = videoInfo.title;
  995. var sfName = vName + filenamesource + ".mp4";
  996. logcat("File Name:" + sfName);
  997. logcat("Call Downloader, Downloader Log Output In F12");
  998. downloadFile(singlevurl, sfName);
  999. }
  1000. }
  1001. //音频下载模式
  1002. if (document.location.pathname == "/audiodown") {
  1003. if (videoInfo.sub_items) {
  1004. logcat("Found " + videoInfo.sub_items.length + " Audios");
  1005. //循环解析并下载音频
  1006. for (var ai = 0; videoInfo.sub_items[ai]; ai++) {
  1007. logcat("Analysis Video " + (ai + 1));
  1008. //currentAudio,缩写为cA看起来清爽点
  1009. var cA = videoInfo.sub_items[ai];
  1010. var aName = cA.title;
  1011. logcat("Audio Name:" + aName);
  1012. //音频不区分清晰度
  1013. var aurl = cA.audios[0].audio_storage_info[0].url;
  1014. logcat("Audio Url:" + aurl);
  1015. var afName = aName + filenamesource + ".mp3";
  1016. logcat("File Name:" + fName);
  1017. logcat("Call Downloader, Downloader Log Output In F12");
  1018. downloadFile(aurl, afName);
  1019. }
  1020. }
  1021. }
  1022. }
  1023. }
  1024. xhr.onerror = () => {
  1025. logcat("Fail Get Json Info:" + vid)
  1026. }
  1027. xhr.open('GET', "https://boot-source.xuexi.cn/data/app/" + vid + ".js?callback=callback&_st=" + Date.now());
  1028. xhr.send();
  1029. //打印日志方法,空页面就不用console.log了
  1030. function logcat(text) {
  1031. //获取时间参考https://www.jianshu.com/p/067469a4eed8,稍微整合了一下
  1032. document.getElementById("logcat").innerText = new Date().toTimeString().substring(0, 8) + " " + text + "\n" + document.getElementById("logcat").innerText;
  1033. }
  1034. //分析最高清晰度
  1035. function getHighest(vObj) {
  1036. var maxHeight = 1;
  1037. var maxId = 0;
  1038. for (var vii = 0; vObj[vii]; vii++) {
  1039. if (!(vObj[vii].format == "m3u8")) {
  1040. if (vObj[vii].height > maxHeight) {
  1041. maxHeight = vObj[vii].height;
  1042. maxId = vii;
  1043. }
  1044. }
  1045. }
  1046. logcat("Max Vide Height:" + maxHeight);
  1047. return vObj[maxId].normal
  1048. }
  1049. }
  1050.  
  1051.  
  1052. //地址转换函数
  1053. function pagetoinfourl(pageurl) {
  1054. var tempurl = pageurl.replace(".html", ".js");
  1055. tempurl = insertStr(tempurl, tempurl.indexOf("/", 21) + 1, "data");
  1056. return tempurl;
  1057. }
  1058. //插入
  1059. function insertStr(soure, start, newStr) {
  1060. return soure.slice(0, start) + newStr + soure.slice(start);
  1061. }
  1062. //下载
  1063. function downloadFile(url, name) {
  1064. XHRDL.newTask(url, name);
  1065. }
  1066. }
  1067. )();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址