// ==UserScript==
// @name WQ
// @namespace http://tampermonkey.net/
// @homepage https://github.com/systemmin/kill-doc
// @version 1.0.2
// @description 文泉书局
// @author Mr.Fang
// @match https://*.wqxuetang.com/deep/read/pdf*
// @require https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/jspdf/2.4.0/jspdf.umd.min.js
// @require https://unpkg.com/@zip.js/[email protected]/dist/zip.min.js
// @icon https://dtking.cn/favicon.ico
// @run-at document-idle
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_setValue
// @grant GM_download
// @grant GM_notification
// @grant unsafeWindow
// @license Apache-2.0
// ==/UserScript==
(function() {
'use strict';
let MF =
'#MF_fixed{position:fixed;top:50%;transform:translateY(-50%);right:20px;gap:20px;flex-direction:column;z-index:2147483647;display:flex}';
MF +=
'.MF_box{padding:10px;cursor:pointer;border-color:rgb(0,102,255);border-radius:5px;background-color:white;color:rgb(0,102,255);margin-right:10px;box-shadow:rgb(207,207,207) 1px 1px 9px 3px}.MF_active{color: green}#MF_size,#MF_speed{color: red;}';
MF +=
'@media print{html{height:auto !important}body{display:block !important}#app-left{display:none !important}#app-right{display:none !important}#MF_fixed{display:none !important}.menubar{display:none !important}.top-bar-right{display:none !important}.user-guide{display:none !important}#app-reader-editor-below{display:none !important}.no-full-screen{display:none !important}.comp-vip-pop{display:none !important}.center-wrapper{width:auto !important}.reader-thumb,.related-doc-list,.fold-page-content,.try-end-fold-page,.lazy-load,#MF_textarea,#nav-menu-wrap{display:none !important}}'
const prefix = "MF_";
// canvas 禁止重写 drawImage
const canvasRenderingContext2DPrototype = CanvasRenderingContext2D.prototype;
const originalDrawImage = canvasRenderingContext2DPrototype.drawImage;
Object.defineProperty(canvasRenderingContext2DPrototype, 'drawImage', {
value: originalDrawImage,
writable: false,
configurable: false
});
class Box {
id = ""; // id
label = ""; // 按钮文本
fun = ""; // 执行方法
constructor(id, label, fun) {
this.id = id;
this.label = label;
this.fun = fun;
}
}
class Utility {
debug = true;
/**
* 添加 css 样式
* @param e 节点
* @param data JSON 格式样式
*/
style(e, data) {
Object.keys(data).forEach(key => {
e.style[key] = data[key]
})
}
attr(e, key, val) {
if (!val) {
return e.getAttribute(key);
} else {
e.setAttribute(key, val);
}
}
/**
* 追加样式
* @param css 格式样式
*/
appendStyle(css) {
let style = this.createEl('', 'style');
style.textContent = css;
style.type = 'text/css';
let dom = document.head || document.documentElement;
dom.appendChild(style);
}
/**
* @description 创建 dom
* @param id 必填
* @param elType
* @param data
*/
createEl(id, elType, data) {
const el = document.createElement(elType);
el.id = id || '';
if (data) {
this.style(el, data);
}
return el;
}
query(el) {
return document.querySelector(el);
}
queryAll(el) {
return document.querySelectorAll(el);
}
update(el, text) {
const elNode = this.query(el);
if (!elNode) {
console.log('节点不存在');
} else {
elNode.innerHTML = text;
}
}
/**
* 进度
* @param current 当前数量 -1预览结束
* @param total 总数量
* @param content 内容
*/
preview(current, total, content) {
return new Promise(async (resolve, reject) => {
if (current === -1) {
this.update('#' + prefix + 'text', content ? content : "已完成");
} else {
let p = (current / total) * 100;
let ps = p.toFixed(0) > 100 ? 100 : p.toFixed(0);
console.log('当前进度', ps)
this.update('#' + prefix + 'text', '进度' + ps + '%');
await this.sleep(500);
resolve();
}
})
}
preText(content) {
this.update('#' + prefix + 'text', content);
}
gui(boxs) {
const box = this.createEl(prefix + "fixed", 'div');
for (let x in boxs) {
let item = boxs[x];
if (!item.id) continue;
let el = this.createEl(prefix + item.id, 'button');
el.append(new Text(item.label));
if (x === '0') {
el.classList = prefix + 'box ' + prefix + "active";
} else {
el.className = prefix + "box";
}
if (item.fun) {
el.onclick = function() {
eval(item.fun);
}
}
if (item.id === 'speed') {
this.attr(el, 'contenteditable', true)
}
if (item.id === 'size') {
this.attr(el, 'contenteditable', true)
}
box.append(el);
}
document.body.append(box);
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
log(msg) {
if (this.debug) {
console.log(msg);
}
}
logt(msg) {
if (this.debug) {
console.table(msg);
}
}
}
const u = new Utility();
u.appendStyle(MF);
const btns = [
new Box('text', '状态 0 %'),
new Box('speed', '1'),
new Box('size', '100'),
new Box('startHandle', '开始执行', 'startHandle()'),
new Box('clearHandle', '结束执行', 'clearHandle()'),
new Box('start', '继续预览', 'autoPreview()'),
new Box('stop', '停止预览', 'stopPreview()'),
new Box('pdf', '下载PDF', 'executeDownload(1)')
]
const domain = {
wqxuetang: 'wqxuetang.com'
};
const {
host,
href,
origin
} = window.location;
const jsPDF = jspdf.jsPDF;
let zipWriter; // 声明全局变量
zipWriter = new zip.ZipWriter(new zip.BlobWriter("application/zip"), {
bufferedWrite: true,
useCompressionStream: false
});
const doc = new jsPDF({
orientation: 'p',
unit: 'px',
compress: true
});
// 794 x 1123 px
let pdf_w = 446,
pdf_h = 631,
loading = 500, // 毫秒
pdf_ratio = 0.56,
title = document.title,
fileType = '',
downType = 1, // 下载文件类型
select = null,
selectBox = null,
dom = null,
beforeFun = null,
interval = null,
BASE_URL = 'https://wkretype.bdimg.com/retype',
readerInfoBai = null, // 百度文档参数
intervalBai = null; // 百度定时任务
if (host.includes(domain.taodocs)) {
iscopy = 'TRUE'; // taodocs copy flag
}
let size = 0; // 页面容量
let count = 0; // 计数
let times = 0; // 计次
const params = new URLSearchParams(document.location.search.substring(1));
if (params.size && params.get('custom')) {
window.parent.postMessage({
type: "onload",
value: 'success'
}, "*")
u.log('子页面加载完成!');
}
// 监听页面卸载,移除百度定时删除广告等 DOM 定时器
window.onunload = function() {
if (intervalBai) {
clearInterval(intervalBai);
intervalBai = null;
}
}
/**
* @description 前置方法
* @author Mr.Fang
* @time 2024年2月2日
*/
const before = () => {
if (beforeFun) {
u.log('---------->beforeFun');
eval(beforeFun)
}
}
/**
* @description 初始化方法
* @author Mr.Fang
* @time 2024年2月2日
*/
const init = () => {
console.table({
host,
href,
origin
})
dom = document.documentElement || document.body;
if (host.includes(domain.wqxuetang)) {
fileType = "pdf";
select = "#pagebox .page-lmg";
dom = u.query('#scroll');
btns.splice(1, 0, );
}
u.gui(btns);
console.log('文件名称:', title);
console.log('文件类型:', fileType);
}
// load 事件
document.onreadystatechange = function() {
if (document.readyState === "complete") {
console.log('readyState:', document.readyState);
// 在这里执行渲染完成后的操作
console.log('HTML 渲染完成!');
init()
const start = GM_getValue('start');
times = Number(GM_getValue('times')) || 0;
size = Number(GM_getValue('size')) || 0;
if (start) {
console.log('自动开始')
setTimeout(() => {
autoPreview();
console.log('1 ms')
}, 1000)
}
loginfo()
}
};
const startHandle = () => {
// 重新设置页面容量参数
if (GM_getValue('size')) {
size = Number(GM_getValue('size'));
} else {
let MF_size = Number(u.query('#MF_size').innerText);
if (MF_size > 0) {
size = MF_size
GM_setValue('size', size)
} else {
u.update('#MF_size', size)
GM_setValue('size', size)
}
}
// 重新设置页码参数
let MF_page = Number(u.query('#MF_speed').innerText) - 1;
if (MF_page > 0) {
GM_setValue('page', MF_page)
localStorage.setItem('WQ_index', MF_page)
}
GM_setValue('start', 1);
console.log('startHandle')
autoPreview();
}
const clearHandle = () => {
console.log('clearHandle')
stopPreview();
localStorage.removeItem('start')
localStorage.removeItem('WQ_index')
GM_deleteValue('page')
GM_deleteValue('start')
GM_deleteValue('size')
GM_deleteValue('times')
}
const loginfo = () => {
console.log('start', localStorage.getItem('start'))
console.log('WQ_index', localStorage.getItem('WQ_index'))
console.log('GM_page', GM_getValue('page'))
console.log('GM_start', GM_getValue('start'))
console.log('size', size)
console.log('count', count)
console.log('times', times)
}
/**
* @description 开始方法,自动预览
* @author Mr.Fang
* @time 2024年2月2日
*/
const autoPreview = async () => {
localStorage.setItem('start', '1');
if (GM_getValue('page')) {
localStorage.setItem('WQ_index', GM_getValue('page'))
} else {
let pages = u.query('.page-head-tol').innerText.split('/');
let index = Number(pages[0]) - 1 || 0;
localStorage.setItem('WQ_index', index)
}
await scrollWQxuetang()
return false;
}
/**
* @description 结束方法,停止预览
* @author Mr.Fang
* @time 2024年2月2日
*/
const stopPreview = async () => {
console.log('---------->stopPreview');
if (interval) {
clearInterval(interval);
interval = null;
}
localStorage.removeItem('start')
}
/**
* @description 执行文件下载
* @author Mr.Fang
* @time 2024年2月20日
* @param type 文件类型
*/
const executeDownload = async (type) => {
downType = type;
const down = localStorage.getItem('down');
console.log('down', down)
console.log('down', host)
if (!down) {
if (host.includes(domain.wqxuetang)) {
title = u.query('.read-header-title').innerText;
conditionDownload();
}
} else {
conditionDownload();
}
}
/**
* 根据指定条件下载文件
*/
const conditionDownload = () => {
if (downType === 1) {
downpdf()
localStorage.setItem('down', '1')
} else if (downType === 2) {
downzip()
}
u.preText('下载完成')
}
/**
* 判断 dom 是否在可视范围内
*/
const isElementInViewport = (el) => {
const rect = el.getBoundingClientRect();
return (
rect.top >= 0 && rect.top <= (window.innerHeight || document.documentElement.clientHeight)
);
}
// wq 保存图片
const saveWQImage = async (els, i) => {
let canvas = await MF_ImageJoinToBlob(els);
doc.addPage();
doc.addImage(canvas, 'JPEG', 0, 0, pdf_w, pdf_h, i, 'FAST')
if (doc.internal.pages[1].length === 2) {
doc.deletePage(1); // 删除空白页
}
count++;
localStorage.setItem('WQ_index', i + 1);
GM_setValue('page', i + 1)
// 更新dom
u.update('#MF_size', size)
u.update('#MF_speed', i + 1)
// 处理分页
if (size === count && count != 0) {
let res = await downpdf();
console.log(res);
GM_setValue('times', times + 1);
await u.sleep(500);
console.log('重载');
window.location.reload()
}
}
/**
* wq 边预览边下载
*/
const scrollWQxuetang = async () => {
if (!localStorage.getItem("start")) {
u.preview(-1, null, "已终止");
return;
}
if (u.query('.reload_image')) {
console.log('重新加载')
u.query('.reload_image').click();
}
// 判断图片是否加载完成
function isImageLoaded(img) {
return img.complete && img.naturalWidth > 0 && img.naturalHeight > 0;
}
function isAllLoaded(childrens) {
if (!childrens.length) {
return false;
}
for (let i = 0; i < childrens.length; i++) {
if (!isImageLoaded(childrens[i])) {
return false;
}
}
return true;
}
let i = Number(localStorage.getItem('WQ_index')) || 0;
let children = u.queryAll(select)
let pages = u.query('.page-head-tol').innerText.split('/');
let index = Number(pages[1]);
if (i === index) {
console.log('执行结束');
u.preview(-1);
clearHandle()
if (size !== count && count != 0) {
let res = await downpdf();
console.log(res);
}
return;
}
let current = children[i];
if (isAllLoaded(current.children)) {
await saveWQImage(current, i)
// 滚动到下一个范围
if (i !== children.length - 1) {
children[i + 1].scrollIntoView({
behavior: "smooth"
});
}
} else {
children[i].scrollIntoView({
behavior: "smooth"
});
}
u.preview(i, children.length);
if (i !== children.length) {
setTimeout(() => {
console.log(loading, 'ms 后执行');
scrollWQxuetang()
}, loading)
}
}
/**
* @description 下载压缩包,包含图片
* @author Mr.Fang
* @time 2024年2月2日
*/
const downzip = () => {
zipWriter.close().then(blob => {
GM_download(URL.createObjectURL(blob), `${title}.zip`);
URL.revokeObjectURL(blob);
// 在关闭旧的 ZipWriter 后,创建新的 ZipWriter
zipWriter = new zip.ZipWriter(new zip.BlobWriter("application/zip"), {
bufferedWrite: true,
useCompressionStream: false
});
}).catch(error => {
console.error(error);
});
}
/**
* @description 下载 PDF
* @author Mr.Fang
* @time 2024年2月2日
*/
const downpdf = async () => {
title = u.query('.read-header-title').innerText;
// 下载 PDF 文件
return doc.save(`${title}_${times}.pdf`, {
returnPromise: true
});
}
// document.querySelector('.reload_image')
// const event = new EventTarget()
// event.dispatchEvent(document.querySelector("#pageImgBox1 > div.page-m-mark"))
// event.onclick()
/**
* @description 图片拼接转 blob
* @author Mr.Fang
* @time 2024年6月5日
* @param el 节点对象
* @returns {Promise<blob>}
*/
const MF_ImageJoinToBlob = (el) => {
return new Promise((resolve, reject) => {
const children = el.children;
const {
naturalWidth,
naturalHeight
} = children[0];
// 1、创建画布
let canvas = u.createEl('', 'canvas');
canvas.width = naturalWidth * 6;
canvas.height = naturalHeight;
const ctx = canvas.getContext('2d');
// 2、获取所有图片节点
const listData = []
for (var i = 0; i < children.length; i++) {
const img = children[i];
const left = img.style.left.replace('px', '')
listData.push({
index: i,
left: Number(left)
})
}
listData.sort((a, b) => a.left - b.left);
// 3、遍历绘制画布
for (var i = 0; i < listData.length; i++) {
const img = children[listData[i].index];
ctx.drawImage(img, i * naturalWidth, 0, naturalWidth, naturalHeight);
}
resolve(canvas)
})
}
/**
* @description 将 blob 对象转 uint8Array
* @author Mr.Fang
* @time 2024年5月27日
* @param {Object} blob 图片对象
* @returns {Promise<Uint8Array>}
*/
const MF_BlobToUint8Array = (blob) => {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = function() {
resolve(new Uint8Array(this.result));
};
fileReader.onerror = function(error) {
reject(error);
};
fileReader.readAsArrayBuffer(blob);
});
}
/**
* @description 画布输出 blob 对象
* @author Mr.Fang
* @time 2024年1月20日18:05:49
* @param src 图片地址
* @returns {Promise<Object>}
*/
const MF_CanvasToBase64 = (canvas) => {
return new Promise((resolve, reject) => {
const {
width,
height
} = canvas;
canvas.toBlob(
(blob) => {
resolve({
blob,
width,
height
});
},
"image/png",
1,
);
})
}
})();