kill-e-book

文泉书局(bit)|高教书苑等公开免费电子书下载

当前为 2024-07-11 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         kill-e-book 
// @namespace    http://tampermonkey.net/
// @homepage	 https://github.com/systemmin/kill-doc
// @version      1.0.5
// @description  文泉书局(bit)|高教书苑等公开免费电子书下载
// @author       Mr.Fang
// @match        https://*.wqxuetang.com/deep/read/pdf*
// @match        https://nlibvpn.bit.edu.cn/*/*/deep/read/pdf?bid=*
// @match        https://ebook.hep.com.cn/index.html*
// @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        none
// @license      Apache-2.0
// ==/UserScript==

(function() {
	'use strict';
	let MF =
		'#MF_fixed{position:fixed;top:50%;transform:translateY(-50%);right:20px;gap:10px;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);}.MF_active{color: green}#MF_k_page_no,#MF_k_page_size{color: red;}';
	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 = ""; // 按钮文本
		title = "";
		fun = ""; // 执行方法
		constructor(id, label, fun) {
			this.id = id;
			this.label = label;
			this.fun = fun;
		}

		setTitle(title) {
			this.title = title;
			return this;
		}
	}

	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');
				if (item.title) {
					el.title = item.title;
				}
				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 === 'k_page_no') {
					this.attr(el, 'contenteditable', true)
				}
				if (item.id === 'k_page_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('k_page_no', '1').setTitle('指定页码,从第几页开始'),
		new Box('k_page_size', '100').setTitle('指定每次下载多少页面'), ,
		new Box('handleStart', '开始执行', 'handleStart()'),
		new Box('handleClean', '结束执行', 'handleClean()'),
		new Box('start', '继续预览', 'autoPreview()'),
		new Box('stop', '停止预览', 'stopPreview()'),
		new Box('pdf', '下载PDF', 'download()')
	]

	const domain = {
		wqxuetang: 'wqxuetang.com',
		nlibvpn: 'nlibvpn.bit.edu.cn',
		ebook: 'ebook.hep.com.cn',
	};
	const {
		host,
		href,
		origin
	} = window.location;
	const jsPDF = jspdf.jsPDF;

	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,
		select = null,
		selectPages = null,
		observeClassName = null,
		dom = null,
		params = null,
		interval = null;

	// ,页码,几次
	let k_page_size = 0, // 页面容量
		k_count = 0, // 计次=第几次分页
		k_total = 0; // 计数=每次分页预览数量

	const lastIndex = href.lastIndexOf('?')
	if (lastIndex != -1) {
		params = new URLSearchParams(href.substring(lastIndex));
	}


	/**
	 * @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) || host.includes(domain.nlibvpn)) {
			select = "#pagebox .page-lmg";
			selectPages = ".page-head-tol";
			observeClassName = "page-lmg";
			dom = u.query('#scroll');
		} else if (host.includes(domain.ebook)) {
			if (!params)
				return;
			if (href.includes('detail')) {
				setTimeout(() => {
					localStorage.setItem('title', u.query('.bookName').innerText)
				}, 1000)
				return;
			}
			select = ".pdf-main .pdf-page";
			selectPages = ".toolbar-process";
			observeClassName = "pdf-reader";
			dom = u.query('#viewerContainer');
		}
		u.gui(btns);
		console.log('文件名称:', title);
	}


	const loginfo = () => {
		console.log('k_page_size', localStorage.getItem('k_page_size'))
		console.log('k_page_no', localStorage.getItem('k_page_no'))
		console.log('k_count', localStorage.getItem('k_count'))
		console.log('k_total', k_total)
	}


	// load 事件
	document.onreadystatechange = function() {
		if (document.readyState === "complete") {
			init()
			const k_start = localStorage.getItem('k_start');
			k_count = Number(localStorage.getItem('k_count')) || 0;
			k_page_size = Number(localStorage.getItem('k_page_size')) || 0;
			if (k_start) {
				setTimeout(() => {
					autoPreview();
				}, 2000)
			}
			loginfo()
		}
	};


	/**
	 * @description 前置方法
	 * @author Mr.Fang
	 * @time 2024年2月2日
	 */
	const before = () => {
		console.log('before=============>')
		if (host.includes(domain.wqxuetang) || host.includes(domain.nlibvpn)) {
			if (u.query('.reload_image')) {
				console.log('重新加载')
				u.query('.reload_image').click();
			}
		}
	}

	/**
	 * @description 后置方法
	 * @author Mr.Fang
	 * @time 2024年2月2日
	 */
	const after = () => {
		console.log('after=============>')
		if (host.includes(domain.wqxuetang) || host.includes(domain.nlibvpn)) {
			let nodeTitle = u.query('.read-header-title');
			if (!nodeTitle) {
				nodeTitle = u.query('.read-header-name');
			}
			if (nodeTitle) {
				title = nodeTitle.innerText;
			}
		} else if (host.includes(domain.ebook)) {
			let t = localStorage.getItem('title');
			if (t) {
				title = t;
			}
		}
	}


	/**
	 * @description 开始执行
	 */
	const handleStart = () => {
		console.log('handleStart=============>')
		// 重新设置页码参数
		const k_page_no = Number(u.query('#MF_k_page_no').innerText) - 1;
		if (k_page_no > 0) {
			localStorage.setItem('k_page_no', k_page_no)
		} else {
			localStorage.setItem('k_page_no', 0)
		}

		// 设置页面容量
		k_page_size = localStorage.getItem('k_page_size');
		const size = Number(u.query('#MF_k_page_size').innerText);
		if (size > 0) {
			k_page_size = size
		}
		localStorage.setItem('k_page_size', k_page_size)
		u.update('#MF_k_page_size', k_page_size)

		// 自动预览
		autoPreview();
	}

	/**
	 * @description 清空缓存数据
	 */
	const handleClean = () => {
		console.log('handleClean=============>')
		stopPreview();
		k_total = 0;
		k_count = 0;
		localStorage.removeItem('k_page_size')
		localStorage.removeItem('k_page_no')
		localStorage.removeItem('k_count')
		localStorage.removeItem('k_total')
		localStorage.removeItem('k_start')
		u.preview(-1, null, "已终止");
	}

	/**
	 * @description 开始方法,自动预览
	 * @author Mr.Fang
	 * @time 2024年2月2日
	 */
	const autoPreview = async () => {
		// 开始执行标识
		localStorage.setItem('k_start', '1');
		// 初始化页码
		if (host.includes(domain.wqxuetang) || host.includes(domain.nlibvpn)) {

		}
		// 自动翻页
		await autoPager()
	}

	/**
	 * @description 结束方法,停止预览
	 * @author Mr.Fang
	 * @time 2024年2月2日
	 */
	const stopPreview = async () => {
		console.log('stopPreview=============>')
		if (interval) {
			clearInterval(interval);
			interval = null;
		}
		localStorage.removeItem('k_start')
	}

	/**
	 * 判断 dom 是否在可视范围内
	 */
	const isVisible = (el) => {
		const rect = el.getBoundingClientRect();
		return (
			rect.top >= 0 && rect.top <= (window.innerHeight || document.documentElement.clientHeight)
		);
	}
	// wq 保存图片
	const saveImagePDF = async (els, i) => {
		localStorage.setItem('k_page_no', i + 1);
		let canvas;
		if (host.includes(domain.wqxuetang) || host.includes(domain.nlibvpn)) {
			canvas = await MF_ImageJoinToBlob(els);
		} else if (host.includes(domain.ebook)) {
			canvas = await MF_ImageToBase64(els.src);
		}
		doc.addPage();
		doc.addImage(canvas, 'JPEG', 0, 0, pdf_w, pdf_h, i, 'FAST')
		if (doc.internal.pages[1].length === 2) {
			doc.deletePage(1); // 删除空白页
		}
		k_total++;
		// 更新dom
		u.update('#MF_k_page_size', k_page_size)
		u.update('#MF_k_page_no', i + 1)

	}

	/**
	 * @description 自动翻页
	 */
	const autoPager = async () => {
		if (!localStorage.getItem("k_start")) {
			u.preview(-1, null, "已停止");
			return;
		}
		before()

		/**
		 * @description 图片是否加载完成
		 * @param {HTMLImageElement} img 图片节点
		 */
		const imageComplete = (img) => {
			return img.complete && img.naturalWidth > 0 && img.naturalHeight > 0;
		}

		/**
		 * @description 所有字加载完成
		 * @param {HTMLElement} nodes 孩子节点
		 */
		const nodeComplete = (nodes) => {
			if (!nodes.length) return false;
			for (let i = 0; i < nodes.length; i++) {
				if (!imageComplete(nodes[i])) return false;
			}
			return true;
		}

		//======================== 前置条件判断
		// 当前页码
		const nodes = u.queryAll(select);
		const length = nodes.length;
		const k_page_no = Number(localStorage.getItem('k_page_no')) || 0;

		// 多增加一页码,防止缺页
		if (k_total % k_page_size === 0 && k_total != 0) { // 满足分页条件
			download().then(() => {
				localStorage.setItem('k_count', k_count + 1);
				setTimeout(() => {
					window.location.reload()
				}, 2000)
			});
			return;
		}

		try {
			const node = nodes[k_page_no];
			if (host.includes(domain.wqxuetang) || host.includes(domain.nlibvpn)) {
				if (nodeComplete(node.children)) {
					// 保存
					await saveImagePDF(node, k_page_no)
					// 滚动到下一个范围
					if (k_page_no !== length - 1) {
						nodes[k_page_no + 1].scrollIntoView({
							behavior: "smooth"
						});
					}
				} else {
					nodes[k_page_no].scrollIntoView({
						behavior: "smooth"
					});
				}
			} else if (host.includes(domain.ebook)) {
				const img = node.querySelector('img')
				if (isVisible(node) && img) {
					// 保存
					await saveImagePDF(img, k_page_no)
					// 滚动到下一个范围
					if (k_page_no !== length - 1) {
						nodes[k_page_no + 1].scrollIntoView({
							behavior: "smooth"
						});
					}
				} else {
					nodes[k_page_no].scrollIntoView({
						behavior: "smooth"
					});
				}
			}
			u.preview(k_page_no, length);
		} catch (e) {
			console.error(e);
			u.preview(-1);
			download().then(() => {
				handleClean();
			})
			return;
		}
		loginfo()

		if (k_page_no !== length) { // 继续执行
			setTimeout(async () => {
				await autoPager()
				console.log(loading, 'ms 后执行');
			}, loading)
		}
	}


	/**
	 * @description 下载 PDF
	 * @author Mr.Fang
	 * @time 2024年2月2日
	 */
	const download = () => {
		after()

		// 下载 PDF 文件
		return doc.save(`${title}_${k_count}.pdf`, {
			returnPromise: true
		});
	}

	/**
	 * @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 加载图片
	 * @author Mr.Fang
	 * @time 2024年1月20日18:05:49
	 * @param src 图片地址
	 * @returns {Promise<unknown>}
	 */
	const MF_ImageToBase64 = (src) => {
		return new Promise((resolve, reject) => {
			const image = new Image();
			image.onload = function() {
				try {
					const canvas = u.createEl('', 'canvas');
					const {
						naturalWidth: width,
						naturalHeight: height
					} = image;
					canvas.width = width;
					canvas.height = height;
					let ctx = canvas.getContext('2d');
					ctx.fillStyle = '#FFFFFF';
					ctx.fillRect(0, 0, width, height);
					ctx.drawImage(image, 0, 0, width, height);
					resolve(canvas);
				} catch (e) {
					reject(e);
				}
			}
			image.onerror = reject;
			image.src = src;
		})
	}

	/**
	 * @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,
			);
		})
	}
})();