Greasy Fork

原创力文档、人人文库文档下载

你能看见多少我能下载多少,下载公开免费的PPTX、PDF、DOCX文件。

目前为 2024-02-02 提交的版本。查看 最新版本

// ==UserScript==
// @name         原创力文档、人人文库文档下载
// @namespace    http://tampermonkey.net/
// @version      0.4
// @description  你能看见多少我能下载多少,下载公开免费的PPTX、PDF、DOCX文件。
// @author       Mr.Fang
// @match        https://*.book118.com/*
// @match        https://*.renrendoc.com/*
// @require      https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/jspdf/2.4.0/jspdf.umd.min.js
// @require      https://cdn.jsdelivr.net/npm/@zip.js/[email protected]/dist/zip.min.js
// @require      https://cdn.bootcdn.net/ajax/libs/html2canvas/1.4.1/html2canvas.min.js
// @icon         https://dtking.cn/favicon.ico
// @grant       GM_getValue
// @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;}';
	const prefix = "MF_";

	class Box {
		id = ""; // id
		label = ""; // 按钮文本
		fun = ""; // 执行方法
		constructor(id, label, fun) {
			this.id = id;
			this.label = label;
			this.fun = fun;
		}
	}

	class Utility {

		/**
		 * 添加 css 样式
		 * @param e 节点
		 * @param data JSON 格式样式
		 */
		style(e, data) {
			Object.keys(data).forEach(key => {
				e.style[key] = data[key]
			})
		}

		attr(e, key) {
			return e.getAttribute(key)
		}

		/**
		 *  追加样式
		 * @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) {
			if (current === -1) {
				this.update('#' + prefix + 'text', content ? content : "已完成");
			} else {
				let p = (current / total) * 100;
				console.log('当前进度', p.toFixed(2))
				this.update('#' + prefix + 'text', '进度' + p.toFixed(0) + '%');
			}
		}

		gui(boxs) {
			const box = this.createEl(prefix + "fixed", 'div');
			for (let x in boxs) {
				let item = boxs[x];
				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);
					}
				}
				box.append(el);
			}
			document.body.append(box);
		}

		sleep(ms) {
			return new Promise(resolve => setTimeout(resolve, ms));
		}


		
	}

	const u = new Utility();
	u.appendStyle(MF);

	const btns = [
		new Box('text', '状态 0 %'),
		new Box('PPT', '获取地址', 'downloadTypeFile(3)')
	]
	const initBtn = [new Box('start', '自动预览', 'startScroll()'),
		new Box('stop', '停止预览', 'stopScroll()'),
		new Box('down', '下载图片', 'downloadTypeFile(1)'),
		new Box('pdf', '下载PDF', 'downloadTypeFile(2)')
	]
	btns.push(...initBtn)
	const domain = {
		REN: "renrendoc.com",
		BOOK: 'book118.com'
	};
	const {
		host,
		href,
		origin
	} = window.location;
	const jsPDF = jspdf.jsPDF;
	let interval = null; // 定时器
	let dom = null; // DOM
	let attr = ".webpreview-item"; // 列表选择
	let attrImage = null; // 图片选择
	let beforeFun = null; // 前置方法
	let doc = null;
	let zipWriter = null;
	let fileType = ''; // 文件类型 docx pdf pptx
	let downType = 1; // 下载类型 1 ZIP  2 PDF 3 TXT
	let isPPT = null; // 文件类型
	let pdf_w = 446,
		pdf_h = 631,
		pdf_ratio = 0.56;

	/**
	 * 初始化
	 */
	const init = () => {
		let title = GM_getValue("title");
		// 初始化 jspdf 对象 p 竖向 l 横向
		doc = new jsPDF({
			unit: 'px',
			compress: true
		});
		// 初始化 zip 对象
		zipWriter = new zip.ZipWriter(new zip.BlobWriter("application/zip"), {
			bufferedWrite: true,
			useCompressionStream: false
		});
		if (host.includes(domain.REN)) {
			const node = u.query('h1');
			if (node) {
				title = node.innerText.replaceAll(" ", "");
				if (node.nextElementSibling?.children) {
					const innerText = node.nextElementSibling.children[6].innerText;
					fileType = innerText.split(":")[1].toLowerCase();
				}
			}
			beforeFun =
				"let er = u.query('#load_preview_btn');if (er && er.style.display !== 'none') {er.click()}";
			dom = u.query('.main-content');
			attrImage = "#page img";
			if (fileType.includes('ppt')) {
				doc = new jsPDF({
					orientation: 'l',
					unit: 'px',
					compress: true
				});
				pdf_w = pdf_h;
				pdf_h = 446;
			}
		} else if (host.includes(domain.BOOK)) {
			const node = u.query('h1');
			if (node) {
				u.query('h1>em')?.remove()
				title = node.innerText.replaceAll(" ", "");
			}
			const number = title.lastIndexOf(".");
			fileType = title.substring(number + 1).toLowerCase();

			beforeFun = "let eb = u.query('#btn_preview_remain');if (eb) {eb.click();}";
			dom = document.documentElement || document.body;
			attrImage = ".webpreview-item img";
			if (fileType.includes('ppt')) {
				doc = new jsPDF({
					orientation: 'l',
					unit: 'px',
					compress: true
				});
				pdf_w = pdf_h;
				pdf_h = 446;
			}
		}
		// 文档标题
		localStorage.setItem("title", title);
		GM_setValue("title", title)
		console.log("当前标题:", title)
		console.log("当前主机:", host)
		console.log("当前URL:", href)
		console.log("当前来源:", origin)
		console.log("文件类型:", fileType)
		// 删除缓存数据
		localStorage.removeItem("current");
		localStorage.removeItem("down");
	}
	window.onload = function() {
		init()
		// 在这里执行渲染完成后的操作
		console.log('HTML 渲染完成!');
		isPPT = u.query("#btn_ppt_front_pc");
		// 添加你的代码...
		if (!isPPT || host.includes(domain.REN)) {
			u.gui(btns);
			isPPT = href.includes("pptView");
			// isPPT = fileType.includes('ppt');
		} else {
			isPPT.click();
		}
	};

	/**
	 * 人人监听侧边栏点击切换问题
	 */
	const renObserve = () => {
		const targetNode = u.query(".center-wrap");
		if (targetNode) {
			const observerOptions = {
				childList: true,
				attributes: false,
				subtree: false
			};
			// dom 监听器
			const observer = new MutationObserver(function(mutationList, observer) {
				mutationList.forEach((mutation) => {
					const addedNodes = mutation.addedNodes;
					if (addedNodes.length) {
						addedNodes.forEach(item => {
							if (item.className === 'main-content') {
								dom = item;
							}
						})
					}
				});
			});
			// 触发监听
			observer.observe(targetNode, observerOptions);
		}

	}
	if (host.includes(domain.REN)) {
		renObserve()
	}



	const startScroll = () => {
		if (isPPT) { // PPT
			localStorage.setItem("start", "1")
			startPPT();
		} else {
			if (!interval) {
				interval = setInterval(() => {
					loadingImages()
				}, 500); // 设置滚动速度,单位为毫秒
			}
		}
	}
	const stopScroll = () => {
		if (isPPT) { // PPT
			localStorage.removeItem('start');
		} else {
			if (interval) {
				clearInterval(interval);
				interval = null;
			}
			domParseImage()
		}
	}

	/**
	 * 开始解析PPT
	 * @returns {Promise<void>}
	 */
	const startPPT = async () => {
		if (beforeFun) {
			eval(beforeFun)
		}
		localStorage.setItem("start", "1")
		await autoParsingPPT();
	}

	/**
	 * @description 遍历节点,将没有图的元素滚动到可视范围内
	 * @author Mr.Fang
	 */
	const loadingImages = () => {
		if (beforeFun) {
			eval(beforeFun)
		}
		const clientHeight = dom.clientHeight;
		let end = 0;
		const images = u.queryAll(attr);
		for (let i = 0; i < images.length; i++) {
			let item = images[i];
			const {
				top
			} = item.getBoundingClientRect();
			let lastChild = host.includes(domain.REN) ? item.lastChild : item.firstChild;
			if (lastChild instanceof HTMLImageElement) {
				if (!lastChild.src && !lastChild.getAttribute('data-src')) {
					end = 1;
					dom.scrollTo({
						top: dom.scrollTop + top,
						left: 0,
						behavior: "smooth",
					});
					u.preview(i + 1, images.length);
					break;
				}
			} else if (lastChild instanceof HTMLDivElement) {
				end = 1;
				dom.scrollTo({
					top: dom.scrollTop + top,
					left: 0,
					behavior: "smooth",
				});
				u.preview(i + 1, images.length);
				break;
			}
		}
		if (end === 0) {
			u.preview(-1);
			stopScroll();
		}
	}

	const autoParsingPPT = async () => {
		if (!localStorage.getItem("start")) {
			u.preview(-1, null, "已终止");
			return;
		}
		const page = Number(u.query('#PageIndex').innerText);
		const total = Number(u.query('#PageCount').innerText);
		const childNodes = u.query("#view").childNodes;
		const count = childNodes.length;
		const max_index = page - 1;
		const current = childNodes[max_index];
		// 动作数量
		const a_len = u.queryAll(`#view${max_index} #animt${max_index}>div`).length;
		if (a_len !== 0) {
			await u.sleep(1000);
		}
		const bgs = MF_RecursiveParsingImages(current);
		await new Promise((resolve) => {
			html2canvas(current, {
				useCORS: true
			}).then(function(canvas) {
				// 将canvas转换为图片并下载.
				let data = canvas.toDataURL();
				let fileName = max_index + "_" + a_len + ".png";
				zipWriter.add(fileName, new zip.Data64URIReader(data));
				// 添加PDF
				// 794px*1123px ;
				doc.addPage([canvas.width * pdf_ratio, canvas.height * pdf_ratio]);
				doc.addImage(data, 'JPEG', 0, 0, canvas.width * pdf_ratio, canvas.height *
					pdf_ratio, max_index + "_" + a_len, 'FAST')
				if (max_index === 1) {
					doc.deletePage(1);
				}
				resolve();
			});
		})
		if (a_len === 0) {
			const detail = bgs.map((item, i) => {
				return zipWriter.add(max_index + "/" + i + ".png", new zip.HttpReader(item));
			});
			await Promise.all(detail);
			zipWriter.add(max_index + "/" + "文本描述.txt", new zip.TextReader(current.innerText));
		}
		u.preview(page, total);
		const pageNext = u.query('#pageNext');
		const btmRight = u.query('.btmRight');

		if (page !== total) {
			btmRight.click();
			await autoParsingPPT(zipWriter);
		}
	}


	const domParseImage = () => {
		const listData = [];
		const images = u.queryAll(attrImage);
		const length = images.length;
		for (let i = 0; i < length; i++) {
			const item = images[i];
			let src = item.src || u.attr(item, 'data-src');
			if (!src) {
				continue;
			}
			const page = u.attr(item, 'data-page') || u.attr(item.parentElement, 'data-id');
			if (src.includes('"http:')) {
				src = src.replace("http:", "https:")
			} else if (src.startsWith("//")) {
				src = "https:" + src;
			}
			listData.push({
				page,
				src
			});
		}
		GM_setValue('listData', JSON.stringify(listData));
		localStorage.setItem('listData', JSON.stringify(listData))
	}

	window.addEventListener("message", (e) => {
		const {
			type,
			value
		} = e.data;
		if (type === 'parent') { // 父级-子页面消息
			if (value.includes(origin)) {
				MF_ImageToBase64(value).then(data => {
					childMessage(data);
				})
			}
		} else if (type === 'child') { // 子页面-到父页面消息
			let index = Number(localStorage.getItem("current") || "0");
			const {
				data,
				width,
				height
			} = value;
			if (fileType.includes('ppt')) {
				doc.addPage([width * pdf_ratio, height * pdf_ratio]);
				doc.addImage(data, 'JPEG', 0, 0, width * pdf_ratio, height * pdf_ratio, index, 'FAST')
			} else {
				doc.addPage();
				doc.addImage(data, 'JPEG', 0, 0, pdf_w, pdf_h, index, 'FAST')
			}
			if (index === 1) {
				doc.deletePage(1);
			}
			localStorage.setItem('current', index + 1 + "");
			zipWriter.add(index + ".png", new zip.Data64URIReader(data));
			handleMultipleDomain();
		} else if (type === 'onload') {
			const url = new URL(e.origin);
			const attrId = url.host.replaceAll(".", "");
			const query = document.getElementById('#' + attrId);
			if (query) {
				handleMultipleDomain();
			}
		}
	})
	const downloadTypeFile = (type) => {
		downType = type;
		localStorage.removeItem("current");
		if (isPPT) { // PPT
			MF_Download(type);
		} else {
			if (type === 3 || localStorage.getItem('down')) {
				MF_Download(type);
			} else {
				handleMultipleDomain();
			}
		}
	}

	/**
	 * 处理资源多域名问题
	 */
	const handleMultipleDomain = async () => {
		const images = JSON.parse(GM_getValue('listData'));
		const length = images.length;
		localStorage.setItem('length', length);
		let current = Number(localStorage.getItem("current")) || 0;
		for (let index = current; index < images.length; index++) {
			const image = images[index];
			const src = image.src;
			if (src.includes(host)) { // 当前域
				const {
					data,
					width,
					height
				} = await MF_ImageToBase64(src);
				if (fileType.includes('ppt')) {
					doc.addPage([width * pdf_ratio, height * pdf_ratio]);
					doc.addImage(data, 'JPEG', 0, 0, width * pdf_ratio, height * pdf_ratio, max_index +
						"_" + a_len, 'FAST')
				} else {
					doc.addPage();
					doc.addImage(data, 'JPEG', 0, 0, pdf_w, pdf_h, index, 'FAST')
				}
				if (index === 1) {
					doc.deletePage(1);
				}
				zipWriter.add(index + ".png", new zip.Data64URIReader(data));
				u.preview(index + 1, length);
				current = index;
			} else {
				const {
					host,
					origin: org
				} = new URL(src);
				const attrId = host.replaceAll(".", "")
				const query = document.getElementById('#' + attrId);
				if (query) { // 框架是否存在
					parentMessage(src, "#" + attrId, org);
				} else {
					// TODO 这里有问题,消息第一次无法送达
					const el = u.createEl("#" + attrId, 'iframe');
					el.src = org;
					el.style.visibility = "hidden";
					document.body.append(el);
				}
				localStorage.setItem('current', index + "");
				u.preview(index + 1, length);
				break;
			}
		}
		if (current === length - 1) {
			MF_Download(downType);
		}
	}



	const childMessage = (message) => {
		window.parent.postMessage({
			type: "child",
			value: message ? message : ''
		}, "*")
	}

	if (host.includes('file') || host.includes('view-cache')) {
		var frameEl = window.frameElement;
		window.parent.postMessage({
			type: "onload",
			value: 'success'
		}, "*")
	}

	const parentMessage = (message, attr, org) => {
		const nodeListOf = document.querySelectorAll('iframe');
		for (const nodeListOfElement of nodeListOf) {
			if (nodeListOfElement.id === attr) {
				let _window = nodeListOfElement.contentWindow;
				_window.postMessage({
					type: 'parent',
					value: message ? message : ''
				}, "*")
				break;
			}
		}
	}

	const downloadText = (title) => {
		const images = JSON.parse(GM_getValue('listData'));
		const text = images.map(item => {
			return item.src
		}).join("\n");
		MF_ExportTxt(text, title + ".txt");
	}

	/**
	 * @description 导出文件
	 * @author Mr.Fang
	 * @time 2024年1月20日18:05:49
	 * @param {Object} type 1 压缩包 2 pdf 3 txt
	 */
	const MF_Download = (type) => {
		const title = GM_getValue('title') || localStorage.getItem("title")
		if (type === 1) {
			// 下载 zip 图片
			zipWriter.close().then((blob => {
				GM_download(URL.createObjectURL(blob), title + '.zip');
				URL.revokeObjectURL(blob);
			}));
			localStorage.setItem('down', '1');
		} else if (type === 2) {
			// 下载 PDF 文件
			doc.save(title + ".pdf", {
				returnPromise: true
			});
			localStorage.setItem('down', '1');
		} else if (type === 3) {
			downloadText(title);
		}
	}

	/**
	 * @description 导出 txt 文件
	 * @author Mr.Fang
	 * @time 2024年1月20日18:05:49
	 * @param {Object} data 数据
	 * @param {Object} filename 文件名
	 */
	const MF_ExportTxt = (data, filename) => {
		const csvContent = "data:text/txt;charset=utf-8," + data;
		const encodedUri = encodeURI(csvContent);
		const link = document.createElement("a");
		link.setAttribute("href", encodedUri);
		link.setAttribute("download", filename);

		// 点击链接以下载文件
		document.body.appendChild(link);
		link.click();
		document.body.removeChild(link);
	}

	/**
	 * @description 递归加载子节点,获取子节点背景,img 属性值
	 * @author Mr.Fang
	 * @time 2024年1月20日18:05:49
	 * @param children
	 * @returns {*[]}
	 */
	const MF_RecursiveParsingImages = (children) => {
		const list = [];
		if (children.childNodes.length) {
			children.childNodes.forEach(item => {
				if (item || item instanceof HTMLImageElement) {
					if (item instanceof HTMLImageElement) { // 图片
						let src = item.src;
						list.push(src);
					} else if (item.style) {
						let bgi = item.style.backgroundImage;
						if (bgi && bgi !== 'initial') {
							let src = bgi.substring(bgi.indexOf("\"") + 1, bgi.lastIndexOf("\""));
							src = src.indexOf("/") === 0 ? src : "/" + src;
							list.push(origin + src);
						}
					}
					if (item.childNodes.length) {
						const images = MF_RecursiveParsingImages(item);
						list.push(...images);
					}
				}
			})
		}
		return list;
	}

	/**
	 * @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) => {
			// 1、创建 Image 对象
			const image = new Image();
			// 2、onload 加载成功触发
			image.onload = function() {
				try {
					let canvas = u.createEl('', 'canvas');
					canvas.width = image.width;
					canvas.height = image.height;
					let context = canvas.getContext('2d');
					context.drawImage(image, 0, 0, image.width, image.height);
					const dataURL = canvas.toDataURL();
					resolve({
						data: canvas.toDataURL(),
						width: image.width,
						height: image.height
					});
				} catch (e) {
					reject(e);
				}
			}
			image.onerror = reject;
			image.src = src;
		})
	}
})();