// ==UserScript==
// @name 学习强国梨酱小帮手
// @namespace https://qinlili.bid/
// @version 1.1.8
// @description 页面内登录/搜索+视频/音频/电子书一键批量下载+拦截Log请求+电子书去水印
// @author 琴梨梨
// @match *://www.xuexi.cn/*
// @match *://boot-source.xuexi.cn/newmoocdown?*
// @match *://boot-source.xuexi.cn/audiodown?*
// @match *://preview-pdf.xuexi.cn/*
// @match *://article.xuexi.cn/*
// @match https://login.xuexi.cn/login/xuexiWeb?*
// @match https://static.xuexi.cn/search/*
// @icon https://www.xuexi.cn/favicon.ico
// @homepage https://github.com/qinlili23333/XXQG-DL
// @supportURL https://github.com/qinlili23333/XXQG-DL
// @grant none
// @run-at document-end
// @require https://lib.baomitu.com/jspdf/2.5.1/jspdf.umd.min.js
// @license Anti996License
// ==/UserScript==
(async function () {
'use strict';
//既然连喜欢的人都没能力留住,那还是把更多时间投入到写代码吧--记于与悦悦子分手的7天之后(2022.1.11)
//这些内容给本项目开发提供了帮助,感谢
//https://stackoverflow.com/a/60644673
//https://stackoverflow.com/a/55165133
//也感谢每一位相信琴梨梨的用户
//真的有人会看琴梨梨写的注释吗?在看的话MUA你一下~
//是否开启跨源服务器
//开启跨源服务器可以下载部分本来会出错的电子书
//请访问此地址获取跨源服务器https://github.com/Rob--W/cors-anywhere
//Clone到本地后运行npm install,然后运行node server.js,即可运行在默认地址和端口上
var corsServer = "http://localhost:8080/";
if (document.location.host == "preview-pdf.xuexi.cn" && (document.location.search.indexOf("boot-video.xuexi.cn") > 1) && (window.self === window.top) && confirm("该地址可能需要跨源服务器下载。启用跨源服务器吗?请在确认跨源服务器已启动之后点击确定。\n不知道跨源服务器是什么的话打开脚本源码看注释")) {
var valueProp = Object.getOwnPropertyDescriptor(Image.prototype, 'src');
Object.defineProperty(Image.prototype, 'src', {
set: function(newimgValue){
if (!newimgValue.startsWith("data:")) {
newimgValue = corsServer + newimgValue;
}
this.crossOrigin = "anonymous"
valueProp.set.call(this, newimgValue);
}
});
}
//iframe页面处理
if (!(window.self === window.top)) {
var transparentStyle = "background:none transparent !important;";
document.documentElement.style = transparentStyle;
document.body.style = transparentStyle;
if (document.location.href.indexOf("login.xuexi.cn/login/xuexiWeb?") > 1) {
document.getElementsByClassName("login_content")[0].style.background = "none"
};
if (document.location.href.indexOf("static.xuexi.cn/search/online/index.html") > 1) {
document.getElementById("root").style.background = transparentStyle;
if ((window.self.innerWidth > window.self.innerHeight) && window.self.innerWidth > 1000) {
document.getElementsByClassName("search-content")[0].style = "padding-left:20px;padding-right:20px;"
}
};
}
//干掉日志
(open=> {
XMLHttpRequest.prototype.open = function (method, url, async, user, pass) {
if (!(async === false)) {
async = true;
};
if (url.startsWith("https://iflow-api.xuexi.cn/logflow/api/v1/pclog") || url.startsWith("https://arms-retcode.aliyuncs.com/r.png")) {
console.log("Rejected Log XHR! " + url + " -Qinlili")
url = "data:text,null"
}
open.call(this, method, url, async, user, pass);
};
})(XMLHttpRequest.prototype.open);
var originFetch = fetch;
window.fetch = (url, options) => {
if (url.startsWith("https://iflow-api.xuexi.cn/logflow/api/v1/pclog") || url.startsWith("https://arms-retcode.aliyuncs.com/r.png")) {
console.log("Rejected Log Fetch! " + url + " -Qinlili")
url = "data:text,null"
}
return originFetch(url, options);
}
//干掉PDF水印
if (document.location.host == "preview-pdf.xuexi.cn") {
CanvasRenderingContext2D.prototype.fillText = () => { }
}
//共享库
const SakiProgress = {
isLoaded: false,
progres: false,
pgDiv: false,
textSpan: false,
first: false,
alertMode: false,
init: function (color) {
if (!this.isLoaded) {
this.isLoaded = true;
console.info("SakiProgress Initializing!\nVersion:1.0.3\nQinlili Tech:Github@qinlili23333");
this.pgDiv = document.createElement("div");
this.pgDiv.id = "pgdiv";
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;";
this.pgDiv.style.opacity = 0;
this.first = document.body.firstElementChild;
document.body.insertBefore(this.pgDiv, this.first);
this.first.style.transition = "margin-top 0.5s"
this.progress = document.createElement("div");
this.progress.id = "dlprogress"
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;"
if (color) {
this.setColor(color);
}
this.pgDiv.appendChild(this.progress);
this.textSpan = document.createElement("span");
this.textSpan.style = "padding-left:4px;font-size:24px;";
this.textSpan.style.display = "inline-block"
this.pgDiv.appendChild(this.textSpan);
var css = ".barBtn:hover{ background-color: #cccccc }.barBtn:active{ background-color: #999999 }";
var style = document.createElement('style');
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
document.getElementsByTagName('head')[0].appendChild(style);
console.info("SakiProgress Initialized!");
} else {
console.error("Multi Instance Error-SakiProgress Already Loaded!");
}
},
destroy: function () {
if (this.pgDiv) {
document.body.removeChild(this.pgDiv);
this.isLoaded = false;
this.progres = false;
this.pgDiv = false;
this.textSpan = false;
this.first = false;
console.info("SakiProgress Destroyed!You Can Reload Later!");
}
},
setPercent: function (percent) {
if (this.progress) {
this.progress.style.width = percent + "%";
} else {
console.error("Not Initialized Error-Please Call `init` First!");
}
},
clearProgress: function () {
if (this.progress) {
this.progress.style.opacity = 0;
setTimeout(function () { SakiProgress.progress.style.width = "0%"; }, 500);
setTimeout(function () { SakiProgress.progress.style.opacity = 1; }, 750);
} else {
console.error("Not Initialized Error-Please Call `init` First!")
}
},
hideDiv: function () {
if (this.pgDiv) {
if (this.alertMode) {
setTimeout(function () {
SakiProgress.pgDiv.style.opacity = 0;
SakiProgress.first.style.marginTop = "";
setTimeout(function () {
SakiProgress.pgDiv.style.display = "none";
}, 500);
}, 3000);
} else {
this.pgDiv.style.opacity = 0;
this.first.style.marginTop = "";
setTimeout(function () {
SakiProgress.pgDiv.style.display = "none";
}, 500);
}
}
else {
console.error("Not Initialized Error-Please Call `init` First!");
}
},
showDiv: function () {
if (this.pgDiv) {
this.pgDiv.style.display = "";
setTimeout(function () { SakiProgress.pgDiv.style.opacity = 1; }, 10);
this.first.style.marginTop = (this.pgDiv.clientHeight + 8) + "px";
}
else {
console.error("Not Initialized Error-Please Call `init` First!");
}
},
setText: function (text) {
if (this.textSpan) {
if (this.alertMode) {
setTimeout(function () {
if (!SakiProgress.alertMode) {
SakiProgress.textSpan.innerText = text;
}
}, 3000);
} else {
this.textSpan.innerText = text;
}
}
else {
console.error("Not Initialized Error-Please Call `init` First!");
}
},
setTextAlert: function (text) {
if (this.textSpan) {
this.textSpan.innerText = text;
this.alertMode = true;
setTimeout(function () { this.alertMode = false; }, 3000);
}
else {
console.error("Not Initialized Error-Please Call `init` First!");
}
},
setColor: function (color) {
if (this.progress) {
this.progress.style.backgroundColor = color;
}
else {
console.error("Not Initialized Error-Please Call `init` First!");
}
},
addBtn: function (img) {
if (this.pgDiv) {
var btn = document.createElement("img");
btn.style = "display: inline-block;right:0px;float:right;height:32px;width:32px;transition:background-color 0.2s;"
btn.className = "barBtn"
btn.src = img;
this.pgDiv.appendChild(btn);
return btn;
}
else {
console.error("Not Initialized Error-Please Call `init` First!");
}
},
removeBtn: function (btn) {
if (this.pgDiv) {
if (btn) {
this.pgDiv.removeChild(btn);
}
}
else {
console.error("Not Initialized Error-Please Call `init` First!");
}
}
}
const XHRDL = {
isLoaded: false,
dlList: [],
listBtn: false,
listDiv: false,
listBar: false,
clsBtn: false,
init: function () {
if (!this.isLoaded) {
console.info("WebXHRDL Initializing!\nVersion:Preview0.1.0\nQinlili Tech:Github@qinlili23333")
try {
SakiProgress.init();
} catch {
console.error("Initialize Failed!Is SakiProgress Loaded?")
return false;
}
this.isLoaded = true;
//this.listBtn = SakiProgress.addBtn("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiBoZWlnaHQ9IjQ4cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjQ4cHgiIGZpbGw9IiMwMDAwMDAiPjxnPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iMjQiIHdpZHRoPSIyNCIvPjwvZz48Zz48Zz48cGF0aCBkPSJNMTguMzIsNC4yNkMxNi44NCwzLjA1LDE1LjAxLDIuMjUsMTMsMi4wNXYyLjAyYzEuNDYsMC4xOCwyLjc5LDAuNzYsMy45LDEuNjJMMTguMzIsNC4yNnogTTE5LjkzLDExaDIuMDIgYy0wLjItMi4wMS0xLTMuODQtMi4yMS01LjMyTDE4LjMxLDcuMUMxOS4xNyw4LjIxLDE5Ljc1LDkuNTQsMTkuOTMsMTF6IE0xOC4zMSwxNi45bDEuNDMsMS40M2MxLjIxLTEuNDgsMi4wMS0zLjMyLDIuMjEtNS4zMiBoLTIuMDJDMTkuNzUsMTQuNDYsMTkuMTcsMTUuNzksMTguMzEsMTYuOXogTTEzLDE5LjkzdjIuMDJjMi4wMS0wLjIsMy44NC0xLDUuMzItMi4yMWwtMS40My0xLjQzIEMxNS43OSwxOS4xNywxNC40NiwxOS43NSwxMywxOS45M3ogTTEzLDEyVjdoLTJ2NUg3bDUsNWw1LTVIMTN6IE0xMSwxOS45M3YyLjAyYy01LjA1LTAuNS05LTQuNzYtOS05Ljk1czMuOTUtOS40NSw5LTkuOTV2Mi4wMiBDNy4wNSw0LjU2LDQsNy45Miw0LDEyUzcuMDUsMTkuNDQsMTEsMTkuOTN6Ii8+PC9nPjwvZz48L3N2Zz4=");
//this.listBtn.onclick = XHRDL.showList;
SakiProgress.showDiv();
SakiProgress.setText("初始化下载器...");
SakiProgress.setPercent(20);
this.listDiv = document.createElement("div");
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;";
this.listDiv.style.opacity = 0;
this.listBar = document.createElement("div");
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);";
this.listDiv.appendChild(this.listBar);
document.body.appendChild(this.listDiv);
var btn = document.createElement("img");
btn.style = "display: inline-block;right:0px;float:right;height:32px;width:32px;transition:background-color 0.2s;"
btn.className = "barBtn"
btn.src = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiBoZWlnaHQ9IjQ4cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjQ4cHgiIGZpbGw9IiMwMDAwMDAiPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iMjQiIHdpZHRoPSIyNCIvPjxwYXRoIGQ9Ik0yMiwzLjQxbC01LjI5LDUuMjlMMjAsMTJoLThWNGwzLjI5LDMuMjlMMjAuNTksMkwyMiwzLjQxeiBNMy40MSwyMmw1LjI5LTUuMjlMMTIsMjB2LThINGwzLjI5LDMuMjlMMiwyMC41OUwzLjQxLDIyeiIvPjwvc3ZnPg==";
this.listBar.appendChild(btn);
btn.onclick = function () {
XHRDL.hideList();
}
this.clsBtn = btn;
SakiProgress.setPercent(100);
SakiProgress.setText("下载器已加载!");
setTimeout(function () { SakiProgress.clearProgress(); SakiProgress.hideDiv(); }, 1000);
console.info("WebXHRDL Initialized!");
} else {
console.error("Multi Instance Error-WebXHRDL Already Loaded!")
}
},
destroy: function (saki) {
if (this.isLoaded) {
if (saki) {
SakiProgress.destroy();
}
this.isLoaded = false;
this.dlList = [];
this.listBtn = false;
this.listDiv = false;
this.listBar = false;
this.clsBtn = false;
console.info("WebXHRDL Destroyed!You Can Reload Later!");
}
},
showList: function () {
if (XHRDL.isLoaded) {
XHRDL.listDiv.style.display = "";
setTimeout(function () { XHRDL.listDiv.style.opacity = 1; }, 10);
} else {
console.error("Not Initialized Error-Please Call `init` First!")
}
},
hideList: function () {
if (XHRDL.isLoaded) {
XHRDL.listDiv.style.opacity = 0;
setTimeout(function () { XHRDL.listDiv.style.display = "none"; }, 500);
} else {
console.error("Not Initialized Error-Please Call `init` First!")
}
},
saveTaskList: function () {
if (XHRDL.isLoaded) {
var storage = window.localStorage;
storage.setItem("XHRDL_List", JSON.stringify(this.dlList));
} else {
console.error("Not Initialized Error-Please Call `init` First!")
}
},
loadTaskList: function () {
if (XHRDL.isLoaded) {
var storage = window.localStorage;
this.dlList = JSON.parse(storage.getItem("XHRDL_List"));
} else {
console.error("Not Initialized Error-Please Call `init` First!")
}
},
newTask: function (url, name) {
if (this.isLoaded) {
var list = this.dlList;
list[list.length] = {
taskUrl: url,
fileName: name
}
SakiProgress.showDiv();
SakiProgress.setText("已添加新任务:" + name);
if (!this.DLEngine.isWorking) {
this.DLEngine.start();
}
} else {
console.error("Not Initialized Error-Please Call `init` First!")
}
},
DLEngine: {
isWorking: false,
start: function () {
if (!this.isWorking) {
console.info("Start WebXHRDL Engine...\nChecking Tasks...");
this.isWorking = true;
SakiProgress.showDiv();
this.dlFirstFile();
} else {
console.error("WebXHRDL Engine Already Started!");
}
},
stop: function () {
this.isWorking = false;
SakiProgress.hideDiv();
SakiProgress.setText("");
if (XHRDL.dlList[0]) {
console.info("All Tasks Done!WebXHRDL Engine Stopped!");
} else {
console.info("WebXHRDL Engine Stopped!Tasks Paused!");
}
},
dlFirstFile: function () {
var taskInfo = XHRDL.dlList[0];
SakiProgress.showDiv();
SakiProgress.setPercent(0);
SakiProgress.setText("正在下载" + taskInfo.fileName);
var xhr = new XMLHttpRequest();
xhr.responseType = "blob";
xhr.onprogress = event => {
if (event.loaded && event.total) {
var percent = String(Number(event.loaded) / Number(event.total) * 100).substring(0, 4);
SakiProgress.setText(taskInfo.fileName + "已下载" + percent + "%");
SakiProgress.setPercent(percent)
}
};
xhr.onload = event => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
var bloburl = URL.createObjectURL(xhr.response);
SakiProgress.setText("正在写出" + taskInfo.fileName);
var a = document.createElement('a');
var filename = taskInfo.fileName;
a.href = bloburl;
a.download = filename;
a.click();
window.URL.revokeObjectURL(bloburl);
SakiProgress.clearProgress();
XHRDL.dlList.splice(0, 1);
XHRDL.DLEngine.checkNext();
} else {
//TODO:支持更多特殊状态处理
SakiProgress.setTextAlert(taskInfo.fileName + "暂不支持下载,跳过");
XHRDL.dlList.splice(0, 1);
XHRDL.DLEngine.checkNext();
}
}
}
xhr.onerror = function (e) {
//TODO:支持处理不同类别出错
if (!taskInfo.errorRetry) {
SakiProgress.setTextAlert(taskInfo.fileName + "下载失败,置入列尾等待重试");
taskInfo.errorRetry = true;
var list = XHRDL.dlList;
list[list.length] = taskInfo;
} else {
SakiProgress.setTextAlert(taskInfo.fileName + "下载又失败了,放弃");
}
XHRDL.dlList.splice(0, 1);
XHRDL.DLEngine.checkNext();
}
xhr.open('GET', taskInfo.taskUrl, true)
xhr.send()
},
checkNext: function () {
if (XHRDL.dlList[0]) {
this.dlFirstFile();
} else {
this.stop();
}
}
}
}
const sleep = delay => new Promise(resolve => setTimeout(resolve, delay));
const openFrame=url=>{
var searchFrame = document.createElement("iframe");
searchFrame.frameBorder = 0;
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;";
document.body.appendChild(searchFrame);
searchFrame.src = url;
var clsBtn = document.createElement("img");
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;"
clsBtn.className = "barBtn"
clsBtn.src = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiBoZWlnaHQ9IjQ4cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjQ4cHgiIGZpbGw9IiMwMDAwMDAiPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iMjQiIHdpZHRoPSIyNCIvPjxwYXRoIGQ9Ik0yMiwzLjQxbC01LjI5LDUuMjlMMjAsMTJoLThWNGwzLjI5LDMuMjlMMjAuNTksMkwyMiwzLjQxeiBNMy40MSwyMmw1LjI5LTUuMjlMMTIsMjB2LThINGwzLjI5LDMuMjlMMiwyMC41OUwzLjQxLDIyeiIvPjwvc3ZnPg==";
document.body.appendChild(clsBtn);
searchFrame.addEventListener("load", async () => { await sleep(150); searchFrame.style.padding = "0px"; });
clsBtn.onclick = () => {
document.body.removeChild(searchFrame);
document.body.removeChild(clsBtn);
}
};
//主站检测
if (document.location.host == "www.xuexi.cn" || document.location.host == "preview-pdf.xuexi.cn") {
console.log("JS Loaded,Sleep 3 Sec-Qinlili");
await sleep(3000)
if (window.self === window.top) {
//初始化下载工具条
XHRDL.init();
var dlPannel = document.createElement("div");
var downloadBtn = document.createElement("button");
downloadBtn.innerText = "下载本页内容";
downloadBtn.style.display = "inline-block";
dlPannel.appendChild(downloadBtn);
var dlText = document.createElement("p");
dlText.style.display = "inline-block";
dlText.innerText = "等待检测页面类型";
dlPannel.appendChild(dlText);
var first = document.body.firstChild;
document.body.insertBefore(dlPannel, first);
//接管搜索
if (document.getElementsByClassName("icon search-icon")[0]) {
var scBtn = document.getElementsByClassName("icon search-icon")[0];
var scPrt = scBtn.parentElement;
scPrt.removeChild(scBtn);
scBtn = document.createElement("a");
scBtn.className = "icon search-icon";
scPrt.appendChild(scBtn);
scBtn.addEventListener("click", async e => {
var searchFrame = document.createElement("iframe");
searchFrame.frameBorder = 0;
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;";
document.body.appendChild(searchFrame);
searchFrame.src = "https://static.xuexi.cn/search/online/index.html";
var clsBtn = document.createElement("img");
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;"
clsBtn.className = "barBtn"
clsBtn.src = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiBoZWlnaHQ9IjQ4cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjQ4cHgiIGZpbGw9IiMwMDAwMDAiPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iMjQiIHdpZHRoPSIyNCIvPjxwYXRoIGQ9Ik0yMiwzLjQxbC01LjI5LDUuMjlMMjAsMTJoLThWNGwzLjI5LDMuMjlMMjAuNTksMkwyMiwzLjQxeiBNMy40MSwyMmw1LjI5LTUuMjlMMTIsMjB2LThINGwzLjI5LDMuMjlMMiwyMC41OUwzLjQxLDIyeiIvPjwvc3ZnPg==";
document.body.appendChild(clsBtn);
searchFrame.addEventListener("load", async () => { await sleep(150); searchFrame.style.padding = "0px"; });
clsBtn.onclick = () => {
document.body.removeChild(searchFrame);
document.body.removeChild(clsBtn);
window.removeEventListener("message", msg, false);
}
function msg(e) {
//抄的官方JS改出来的,我对于这种非要传值到上层窗口的做法完全无法理解,但既然能跑,管他呢
console.log('e:', e)
console.log('e.data:', e.data)
try {
var params = JSON.parse(e.data);
if (params.type) {
console.log('params.type:', params.type);
console.log('params.data:', params.data);
switch (params.type) {
case 'search':
var useQuestionMark = false;
var targetUrl = 'https://static.xuexi.cn/search/online/index.html'
for (var key in params.data) {
var value = params.data[key];
var op = '&'
if (!useQuestionMark) {
op = '?';
useQuestionMark = true;
}
targetUrl += op + key + '=' + value;
}
searchFrame.style.padding = "100%";
searchFrame.src = targetUrl;
break;
default:
break;
}
}
} catch (error) {
console.log(error)
}
}
window.addEventListener("message", msg, false);
})
}
//接管登录
if (document.getElementsByClassName("icon login-icon")[0]) {
var dlBtn = document.getElementsByClassName("icon login-icon")[0];
var dlPrt = dlBtn.parentElement;
dlPrt.removeChild(dlBtn);
dlBtn = document.createElement("a");
dlBtn.className = "icon login-icon";
dlPrt.appendChild(dlBtn);
dlBtn.addEventListener("click", async e => {
e.preventDefault();
SakiProgress.showDiv();
SakiProgress.setPercent(5);
SakiProgress.setText("正在准备登录...")
console.log("Login Hooked!-Qinlili");
var closeBtn = SakiProgress.addBtn("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiBoZWlnaHQ9IjQ4cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjQ4cHgiIGZpbGw9IiMwMDAwMDAiPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iMjQiIHdpZHRoPSIyNCIvPjxwYXRoIGQ9Ik0yMiwzLjQxbC01LjI5LDUuMjlMMjAsMTJoLThWNGwzLjI5LDMuMjlMMjAuNTksMkwyMiwzLjQxeiBNMy40MSwyMmw1LjI5LTUuMjlMMTIsMjB2LThINGwzLjI5LDMuMjlMMiwyMC41OUwzLjQxLDIyeiIvPjwvc3ZnPg==")
var loginFrame = document.createElement("iframe");
loginFrame.frameBorder = 0;
loginFrame.scrolling = "no";
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;";
document.body.appendChild(loginFrame);
loginFrame.addEventListener("load", async function () { await sleep(250); loginFrame.style.padding = "0px"; });
var refreshBtn = SakiProgress.addBtn("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iNDhweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iNDhweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPjxwYXRoIGQ9Ik0xNy42NSA2LjM1QzE2LjIgNC45IDE0LjIxIDQgMTIgNGMtNC40MiAwLTcuOTkgMy41OC03Ljk5IDhzMy41NyA4IDcuOTkgOGMzLjczIDAgNi44NC0yLjU1IDcuNzMtNmgtMi4wOGMtLjgyIDIuMzMtMy4wNCA0LTUuNjUgNC0zLjMxIDAtNi0yLjY5LTYtNnMyLjY5LTYgNi02YzEuNjYgMCAzLjE0LjY5IDQuMjIgMS43OEwxMyAxMWg3VjRsLTIuMzUgMi4zNXoiLz48L3N2Zz4=")
refreshBtn.onclick = () => {
scanLogin();
}
closeBtn.onclick = () => {
document.body.removeChild(loginFrame);
SakiProgress.removeBtn(closeBtn);
SakiProgress.removeBtn(refreshBtn);
closeBtn = false;
SakiProgress.clearProgress();
SakiProgress.hideDiv();
}
async function scanLogin() {
await sleep(100);
SakiProgress.setPercent(10);
SakiProgress.setText("正在获取登录口令...");
let token;
let tokenText;
try {
token = await fetch("https://pc-api.xuexi.cn/open/api/sns/sign")
tokenText = JSON.parse(await token.text())
} catch (e) {
SakiProgress.setPercent(100);
SakiProgress.setText("出错了:网络连接中断! " + e.message);
return;
}
if (tokenText.code = "200") {
token = tokenText.data.sign;
SakiProgress.setPercent(40);
SakiProgress.setText("口令获取成功,加载登录页面...");
loginFrame.onload = () => {
SakiProgress.setPercent(65);
SakiProgress.setText("等待扫码...");
}
loginFrame.style.padding = "100%";
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"
window.addEventListener("message", event => {
event.preventDefault();
console.log(event);
if (event.data.success == true) {
SakiProgress.setPercent(100);
SakiProgress.setText("登录成功,正在刷新页面...");
document.location.reload();
} else {
SakiProgress.setPercent(100);
SakiProgress.setText("出错了:扫码登录失败,错误码为" + event.data.errorCode);
}
}, false);
} else {
SakiProgress.setPercent(100);
SakiProgress.setText("出错了:获取口令失败!");
}
}
scanLogin();
})
}
//检测爬取页面类型
console.log("Detecting Page " + document.location.pathname + "-Qinlili");
var detected = false;
//旧慕课列表
if ((document.body.innerText.indexOf("课程介绍") >= 1) || (document.body.innerText.indexOf("课程详情") >= 1)) {
console.log("Old Mooc List Detected " + document.location.pathname + "-Qinlili");
detected = true;
dlText.innerText = "页面类型:旧慕课列表,支持全部批量下载,请开启网站自动下载权限";
downloadBtn.onclick = () => {
OldMoocListDL();
}
}
//页面有播放器
if (window.Aliplayer) {
//旧慕课、电视剧播放单页
if (document.getElementsByClassName("radio-inline")[0]) {
console.log("Old Video Player Detected " + document.location.pathname + "-Qinlili");
detected = true;
dlText.innerText = "页面类型:旧视频播放单页,支持全部批量下载,请开启网站自动下载权限";
downloadBtn.onclick = () => {
OldMoocVideoDL();
}
}
//新慕课、影视总页
if (document.getElementsByClassName("video-article-content")[0] || document.getElementsByClassName("videoSet-article-wrap")[0]) {
console.log("New Video Player Detected " + document.location.pathname + "-Qinlili");
detected = true;
dlText.innerText = "页面类型:新视频总,支持全部批量下载最高清晰度,需要打开新标签页下载";
downloadBtn.onclick = () => {
NewMoocPageDL();
}
}
}
//音频专题
if (document.getElementsByClassName("album-play-btn")[0]) {
console.log("Audio Detected " + document.location.pathname + "-Qinlili");
detected = true;
dlText.innerText = "页面类型:音频,支持全部批量下载,需要打开新标签页下载";
downloadBtn.onclick = () => {
AudioDL();
}
}
//页面上就一个音频
//解锁音频播放器下载按钮
if (document.getElementsByTagName("audio").length) {
detected = true;
dlText.innerText = "页面类型:单个音频,已经解锁播放器下载能力,点击播放器右侧菜单下载";
downloadBtn.style.display = "none";
for (var la = 0; document.getElementsByTagName("audio")[la]; la++) {
document.getElementsByTagName("audio")[la].removeAttribute("controlslist");
}
}
//电子书下载
if (document.location.host == "preview-pdf.xuexi.cn") {
detected = true;
dlText.innerText = "页面类型:电子书,支持打包下载";
downloadBtn.onclick = () => {
PDFDL();
}
}
if (!detected) {
console.log("Unsupported Page " + document.location.pathname + "-Qinlili");
dlText.innerText = "本页面不支持下载";
downloadBtn.innerText = "暂不支持";
}
//下载器部分
//旧慕课列表
function OldMoocListDL() {
//读取全部视频列表
var videoList = globalCache[Object.keys(globalCache)[0]];
console.log("Found " + videoList.length + " Videos-Qinlili")
for (var i = 0; videoList[i]; i++) {
console.log("Try Analysis " + i + " Video-Qinlili")
getInfoAndDL(pagetoinfourl(videoList[i].static_page_url));
}
}
//旧慕课播放单页
function OldMoocVideoDL() {
console.log("Analysis Page Info-Qinlili")
getInfoAndDL(pagetoinfourl(document.location.href))
}
function NewMoocPageDL() {
console.log("Open DL Page-Qinlili");
var searchParams = new URLSearchParams(document.location.search);
var dlurl = "https://boot-source.xuexi.cn/newmoocdown?id=" + searchParams.get("id");
openFrame(dlurl);
}
function AudioDL() {
console.log("Open DL Page-Qinlili");
var searchParams = new URLSearchParams(document.location.search);
var dlurl = "https://boot-source.xuexi.cn/audiodown?id=" + searchParams.get("id");
openFrame(dlurl);
}
async function PDFDL() {
//webp压缩用处和顶碗人一样大,所以换成灰度压缩
let enableGreyCompress = confirm("是否启用灰度压缩?\n适合保存以黑白文本内容的书籍或用于Kindle等墨水屏阅读,可大幅削减文件体积,需额外消耗压缩时间。\n根据琴梨梨自己的测试可削减大约44%大小,可用于解决Chrome无法爬512M以上书的问题。")
let lowQuality = confirm("是否启用低质量模式?\n轻微降低画质换取显著的文件缩小");
SakiProgress.showDiv();
SakiProgress.setText("正在加载依赖...");
await sleep(100)
console.log("Preparing jsPDF Library...-Qinlili");
var jsPDF = jspdf.jsPDF;
try {
console.log(jsPDF)
console.log("jsPDF Ready!")
} catch {
console.error("jsPDF Not Ready!")
alert("jsPDF加载失败,请检查网络并尝试重新安装脚本!")
}
SakiProgress.setText("正在调整尺寸...");
SakiProgress.setPercent(2);
await sleep(100)
if(confirm("是否开启高清模式?请观察原书清晰度,若原书清晰度较高建议开启。\n对超过300页的书开启可能导致无法生成文件。")){
//按钮循环点击得稍微延迟一点否则可能卡死
//放大到最大保障清晰度
for (; document.getElementsByClassName("ctrl-icon")[0].className.animVal.indexOf("disabled") < 0;) {
document.getElementsByClassName("ctrl-icon")[0].parentElement.click()
await sleep(50)
}
}
SakiProgress.setText("正在回到第一页...");
SakiProgress.setPercent(4);
await sleep(100)
//回到第一页
for (; document.getElementsByClassName("ctrl-icon")[2].className.animVal.indexOf("disabled") < 0;) {
document.getElementsByClassName("ctrl-icon")[2].parentElement.click()
await sleep(50)
}
//创建文件
SakiProgress.setText("正在创建文件...");
SakiProgress.setPercent(6);
await sleep(100)
var samplePage = document.getElementsByTagName("canvas")[0]
var ori;
let wP = samplePage.width;
let hP = samplePage.height;
if (wP > hP) { ori = "l" } else { ori = "p" }
var PDFfile = new jsPDF({
orientation: ori,
unit: 'px',
format: [wP, hP],
putOnlyUsedFonts: true,
});
console.log("Preparing PDF File...-Qinlili");
console.log(PDFfile);
//监听函数
SakiProgress.setText("正在设置监听函数...");
SakiProgress.setPercent(8);
await sleep(100)
var onPageChange = function () { };
var val = document.getElementsByTagName("input")[0];
function waitPageChange() {
return new Promise(resolve => {
onPageChange = () => {
resolve();
}
});
}
//加载完成后页码显示才会变化,监听页码显示来等待加载
Object.defineProperty(val, 'value', {
set: newValue => {
var valueProp = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
valueProp.set.call(val, newValue);
onPageChange();
}
});
//循环保存
var page = 1;
var totalPage = parseInt(document.getElementsByClassName("total")[0].innerText.substr(1));
const saveCurrent=()=>{
//不管有几页,把当前全部canvas保存再说
for (var i = 0; document.getElementsByTagName("canvas")[i]; i++) {
if (enableGreyCompress) {
//灰度压缩
let cnv = document.getElementsByTagName("canvas")[i];
let cnx = cnv.getContext('2d');
let width = cnv.width;
let height = cnv.height;
var imgPixels = cnx.getImageData(0, 0, width, height);
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
let i = (y * 4) * width + x * 4;
var avg = (imgPixels.data[i] + imgPixels.data[i + 1] + imgPixels.data[i + 2]) / 3;
imgPixels.data[i] = avg;
imgPixels.data[i + 1] = avg;
imgPixels.data[i + 2] = avg;
}
}
cnx.putImageData(imgPixels, 0, 0, 0, 0, imgPixels.width, imgPixels.height);
}
if(lowQuality){
PDFfile.addImage(document.getElementsByTagName("canvas")[i].toDataURL("image/webp",0.75),"WEBP",0,0,wP,hP,null,"SLOW");
}else{
PDFfile.addImage(document.getElementsByTagName("canvas")[i], "WEBP", 0, 0, wP, hP, null, "SLOW");
}
PDFfile.addPage();
page++
console.log("Saved One Page!-Qinlili");
}
}
for (; document.getElementsByClassName("ctrl-icon")[3].className.animVal.indexOf("disabled") < 0;) {
SakiProgress.setText("正在保存第" + page + "页...");
SakiProgress.setPercent(10 + 80 * (page / totalPage));
console.log("Work Current Page:" + page + "...-Qinlili");
await sleep(100)
saveCurrent();
//虽然不知道为什么加了延迟半秒就不会卡住,但既然能用管他为什么呢
setTimeout(() => { document.getElementsByClassName("ctrl-icon")[3].parentElement.click(); }, 500)
//显示的正在加载的页面可能比实际加载页面小一页,但估计1919810个用户里也没一个意识到,不影响保存效果这种细节就不管了,问就是爷懒的写
SakiProgress.setText("正在等待加载第" + (page + 1) + "页...");
console.log("Waiting For Loading...-Qinlili");
await waitPageChange();
};
saveCurrent();
PDFfile.setFontSize(40);
PDFfile.text('Powered By Qinlili',35, 65);
PDFfile.textWithLink('https://greasyfork.org/zh-CN/scripts/429991',35, 25,{align: 'center', url: 'https://greasyfork.org/zh-CN/scripts/429991'});
//生成文件导出
SakiProgress.setText("正在导出文件...");
SakiProgress.setPercent(90);
PDFfile.save("学习强国电子书导出.pdf", { returnPromise: true }).then(finish => {
SakiProgress.clearProgress;
SakiProgress.hideDiv();
});
}
} else {
console.log("Iframe Page " + document.location.pathname + "\nSkip Detect-Qinlili");
}
}
//全局共享函数
//读取视频信息并下载
function getInfoAndDL(infourl) {
console.log("Get Video Info:" + infourl + "\n-Qinlili")
var xhr = new XMLHttpRequest();
xhr.onload = event => {
console.log("Success Get Video Info:" + infourl + "\n-Qinlili")
if (xhr.readyState === 4 && xhr.status === 200) {
var videoInfo = JSON.parse(xhr.response.replace("globalCache = ", "").replace(";", ""));
stringToObject(videoInfo);
//判断慕课
if (videoInfo[Object.keys(videoInfo)[0]].info) {
videoInfo = videoInfo[Object.keys(videoInfo)[0]].info;
for (var vi = 0; videoInfo.ossUrl[vi]; vi++) {
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");
var filename = videoInfo.frst_name + "-" + (vi + 1) + "-" + videoInfo.mooc_class + "-" + videoInfo.mooc + ".mp4"
console.log("File Name:" + filename + "\nPrepare Download-Qinlili");
downloadFile(videoInfo.ossUrl[vi], filename);
}
}
//判断电视剧
if (videoInfo[Object.keys(videoInfo)[0]].list) {
videoInfo = videoInfo[Object.keys(videoInfo)[0]].list;
for (var vii = 0; videoInfo[vii]; vii++) {
console.log("Video Name:" + videoInfo[vii].frst_name + "-" + (vi + 1) + "\nList Name:" + videoInfo[vii].title + "\nVideo Url:" + videoInfo[vii].ossUrl + "\n-Qinlili");
var filename2 = videoInfo[vii].frst_name + "-" + (vi + 1) + "-" + videoInfo[vii].title + ".mp4"
console.log("File Name:" + filename2 + "\nPrepare Download-Qinlili");
downloadFile(videoInfo[vii].ossUrl, filename2);
}
}
}
xhr.onerror = () => {
console.log("Fail Get Video Info:" + infourl + "\n-Qinlili")
}
}
xhr.open('GET', infourl, false);
xhr.send();
//平整化Array工具,从学习强国本身的js里抄过来的,大概原理就是尝试把值作为json解析,解析成功就把解析结果替换回去,总之我大受震撼
function stringToObject(params) {
for (var key in params) {
var value = params[key];
if (isString(value)) {
try {
if (typeof JSON.parse(value) == 'object') {
params[key] = JSON.parse(value);
}
} catch (e) { }
} else if (isArray(value)) {
try {
for (var index = 0; index < value.length; index++) {
stringToObject(value[index]);
}
} catch (e) { }
} else if (isObject(value)) {
try {
stringToObject(params[key])
} catch (e) { }
}
}
}
function isArray(o) {
return Object.prototype.toString.call(o) === '[object Array]';
}
function isString(o) {
return Object.prototype.toString.call(o) === '[object String]';
}
function isObject(o) {
return Object.prototype.toString.call(o) === '[object Object]';
}
}
//跳转避免跨域问题
if (document.location.host == "article.xuexi.cn") {
//检测是不是PDF页面,移动端分享文章也是这个域名,之前没发现
var obj = document.getElementsByTagName("link");
var isPDF = false;
for (var pdfJS = 0; obj[pdfJS]; pdfJS++) {
if (obj[pdfJS].href.indexOf("js/pdf") > 1) {
isPDF = true;
}
}
if (isPDF) {
console.log("JS Loaded,Sleep 5 Sec-Qinlili");
var tip = document.createElement("H1");
tip.innerText = "即将跳转页面,请等待五秒";
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;";
document.body.appendChild(tip);
await sleep(5000)
document.location.href = document.getElementsByClassName("pdf-iframe")[0].src;
}
}
//404注入页面避免cors
if (document.location.host == "boot-source.xuexi.cn") {
console.log("JS Domain Detected, Prepare Inject-Qinlili");
document.querySelector("body").innerHTML = "<H2>本页面仅用于CORS注入,分享网址没有用,请允许下载多个文件</H2><H4 id=\"logcat\"></H4>"
XHRDL.init();
logcat("Initializing Downloader...");
await sleep(3000)
var searchParams = new URLSearchParams(document.location.search);
var vid = searchParams.get("id");
document.title = "下载:" + vid;
logcat("Found ID:" + vid);
logcat("Get Video Info:" + vid)
var xhr = new XMLHttpRequest();
xhr.onload = event => {
if (xhr.readyState === 4 && xhr.status === 200) {
logcat("Success Get JSON Info:" + vid)
var videoInfo = JSON.parse(xhr.response.replace("callback(", "").replace("})", "}"));
console.log(videoInfo);
logcat("List Name:" + videoInfo.normalized_title)
logcat("List Origin:" + videoInfo.show_source)
//文件名后缀
var filenamesource = "-" + videoInfo.normalized_title + "-" + videoInfo.show_source;
//视频下载模式
if (document.location.pathname == "/newmoocdown") {
//检测是否为多视频
if (videoInfo.sub_items) {
logcat("Found " + videoInfo.sub_items.length + " Videos");
//循环解析并下载视频
for (var vi = 0; videoInfo.sub_items[vi]; vi++) {
logcat("Analysis Video " + (vi + 1));
//currentVideo,缩写为cV看起来清爽点
var cV = videoInfo.sub_items[vi];
var vName = cV.title;
logcat("Video Name:" + vName);
//检测多个清晰度
var vurl = getHighest(cV.videos[0].video_storage_info);
logcat("Video Url:" + vurl);
var fName = vName + filenamesource + ".mp4";
logcat("File Name:" + fName);
logcat("Call Downloader, Downloader Log Output In F12");
downloadFile(vurl, fName);
}
} else {
//单个视频
var singlevurl = getHighest(videoInfo.videos[0].video_storage_info);
logcat("Video Url:" + singlevurl);
vName = videoInfo.title;
var sfName = vName + filenamesource + ".mp4";
logcat("File Name:" + sfName);
logcat("Call Downloader, Downloader Log Output In F12");
downloadFile(singlevurl, sfName);
}
}
//音频下载模式
if (document.location.pathname == "/audiodown") {
if (videoInfo.sub_items) {
logcat("Found " + videoInfo.sub_items.length + " Audios");
//循环解析并下载音频
for (var ai = 0; videoInfo.sub_items[ai]; ai++) {
logcat("Analysis Video " + (ai + 1));
//currentAudio,缩写为cA看起来清爽点
var cA = videoInfo.sub_items[ai];
var aName = cA.title;
logcat("Audio Name:" + aName);
//音频不区分清晰度
var aurl = cA.audios[0].audio_storage_info[0].url;
logcat("Audio Url:" + aurl);
var afName = aName + filenamesource + ".mp3";
logcat("File Name:" + fName);
logcat("Call Downloader, Downloader Log Output In F12");
downloadFile(aurl, afName);
}
}
}
}
}
xhr.onerror = () => {
logcat("Fail Get Json Info:" + vid)
}
xhr.open('GET', "https://boot-source.xuexi.cn/data/app/" + vid + ".js?callback=callback&_st=" + Date.now());
xhr.send();
//打印日志方法,空页面就不用console.log了
function logcat(text) {
//获取时间参考https://www.jianshu.com/p/067469a4eed8,稍微整合了一下
document.getElementById("logcat").innerText = new Date().toTimeString().substring(0, 8) + " " + text + "\n" + document.getElementById("logcat").innerText;
}
//分析最高清晰度
function getHighest(vObj) {
var maxHeight = 1;
var maxId = 0;
for (var vii = 0; vObj[vii]; vii++) {
if (!(vObj[vii].format == "m3u8")) {
if (vObj[vii].height > maxHeight) {
maxHeight = vObj[vii].height;
maxId = vii;
}
}
}
logcat("Max Vide Height:" + maxHeight);
return vObj[maxId].normal
}
}
//地址转换函数
function pagetoinfourl(pageurl) {
var tempurl = pageurl.replace(".html", ".js");
tempurl = insertStr(tempurl, tempurl.indexOf("/", 21) + 1, "data");
return tempurl;
}
//插入
function insertStr(soure, start, newStr) {
return soure.slice(0, start) + newStr + soure.slice(start);
}
//下载
function downloadFile(url, name) {
XHRDL.newTask(url, name);
}
}
)();