// ==UserScript==
// @name 原创力文档、人人文库文档下载
// @namespace http://tampermonkey.net/
// @version 0.3
// @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));
}
notice(content) {
// GM_notification({
// text: content,
// title: "脚本执行结果通知",
// url: location.href,
// onclick: (event) => {
// event.preventDefault();
// console.log(event)
// }
// });
}
}
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);
u.notice('自动预览结束,请点击下载”图片“或”pdf“文件')
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') {
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')) {
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);
}
u.notice(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;
})
}
})();