// ==UserScript==
// @name 云展网下载PDF
// @namespace http://tampermonkey.net/yunzhanpDF
// @version 1.21
// @description 云展网下载高清PDF!支持多种模式!
// @author ZouYS
// @match https://*.yunzhan365.com/*
// @icon https://book.yunzhan365.com/web/images/nav_logo_big.jpg
// @require https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js
// @require https://cdn.jsdelivr.net/npm/sweetalert2@11
// @require https://fastly.jsdelivr.net/npm/sweetalert2@11
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/html2canvas.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/pdf-lib.min.js
// @grant GM_addStyle
// @grant GM_addElement
// @grant unsafeWindow
// @run-at document-end
// ==/UserScript==
(function () {
'use strict';
window.onload = function () {
/*if(!window.htmlConfig){
console.error('error!')
return 1;
}*/
/*const zip = new JSZip();
if (!zip) {
console.error('error loading zip!')
return
}*/
let css = `.mydiv1{
position: absolute;
z-index: 9999999999;
left: 100px;
top: 300px;
display: none;
}
.mybtn1 {
z-index: 9999999999;
position: absolute;
top: 300px;
font-size: inherit;
font-family: inherit;
color: white;
padding: 0.5em 1em;
outline: none;
border: none;
background-color: hsl(236, 32%, 26%);
overflow: hidden;
transition: color 0.4s ease-in-out;
}
.mybtn2 {
z-index: 9999999999;
position: absolute;
top: 350px;
font-size: inherit;
font-family: inherit;
color: white;
padding: 0.5em 1em;
outline: none;
border: none;
background-color: hsl(236, 32%, 26%);
overflow: hidden;
transition: color 0.4s ease-in-out;
}
.mybtn2::before,.mybtn1::before {
content: '';
z-index: -1;
position: absolute;
top: 100%;
right: 100%;
width: 1em;
height: 1em;
border-radius: 50%;
background-color: #3cefff;
transform-origin: center;
transform: translate3d(50%, -50%, 0) scale3d(0, 0, 0);
transition: transform 0.45s ease-in-out;
}
.mybtn2:hover,.mybtn1:hover {
cursor: pointer;
color: #161616;
}
.mybtn2:hover::before,.mybtn1:hover::before {
transform: translate3d(50%, -50%, 0) scale3d(15, 15, 15);
}`;
GM_addStyle(css);
const rules=new Map()
const rulesClass=['cover_shadow side','flip-shadowB','flip-topshadow','HandPainted','midShadow','grayShadow','ViewModule','leftShadow book','rightShadow book','cover_shadow side flip_x']
rulesClass.forEach(item=>{
rules.set(item,true)
})
const button = document.createElement('button')
button.classList.add('mybtn1')
button.innerText = '高清下载PDF'
document.body.append(button)
button.onclick = () => {
// console.log(unsafeWindow)
try {
let newUrl = document.getElementsByTagName('iframe')[0] && document.getElementsByTagName('iframe')[0].src
if (newUrl) {
unsafeWindow.location.href = newUrl
}
if (Swal) {
Swal.fire({
position: 'top-end', //定位 左上角
icon: 'success',
title: '↑↑↑下载中,请留意浏览器右上角↑↑↑弹窗~',
showConfirmButton: false,
timer: 1500
})
}
let meta = unsafeWindow.htmlConfig || console.error('no meta data!')
let imgUrls = meta.fliphtml5_pages
let basicUrl = meta.meta.url
basicUrl = basicUrl.slice(0, basicUrl.lastIndexOf('/') + 1)
addImageToPDF(meta, imgUrls, basicUrl).then(pdf => {
pdf.save(`${meta.meta.title}.pdf`);
});
} catch (e) {
if (Swal) {
Swal.fire({
position: 'center', //定位 左上角
icon: 'error',
title: `下载失败,请尝试兼容模式!~错误信息:${e}`,
// showConfirmButton: false,
// timer: 1500
})
}
console.error(e)
}
}
const button_01 = document.createElement('button')
button_01.classList.add('mybtn2')
button_01.innerText = '下载-兼容模式'
document.body.append(button_01)
button_01.onclick = async () => {
if(button_01.innerText==='下载中...'){
return
}
const start = new Date();
try {
button_01.innerText='下载中...'
let oriPDF = unsafeWindow['pdfjs-dist/build/pdf']
let htmlConfig = unsafeWindow.htmlConfig || console.error('no meta data!')
let fliphtml5_pages = htmlConfig.fliphtml5_pages || ''
let meta = htmlConfig.meta;
let pos = 0
const pageCount = meta.pageCount;
let pdf;
//zip
for (let i = 0; i < fliphtml5_pages.length; i++) {
if (fliphtml5_pages[i].n[0] && fliphtml5_pages[i].n[0].includes("zip")) {
//zip的情况
pos = 1
}
}
// console.log('fliphtml5_pages:',fliphtml5_pages)
console.log('pos:', pos)
//处理各种情况
switch (pos) {
/**
* 情況一:html拼接
* 图片分解后写入为html拼接
*/
case 0:
let scale=parseInt(prompt('case 0:html拼接,选择像素倍数(渲染高像素耗时较长,基数为页面展示的长宽,倍数默认5倍时,平均每页大约60s)','5'))
for (let i = 0; i < pageCount; i++) {
let page;
do{
page = document.getElementById(`page${i + 1}`)
if(!page){
unsafeWindow.nextPageFun("flip shot bar");
await sleep(3000);
unsafeWindow.nextPageFun("flip shot bar");
await sleep(3000);
}
}while (!page)
// 记录开始时间
const startTime = new Date();
// page = page.childNodes[0].childNodes[1]
console.log(`---------------------------------------------------------------------------------`)
console.log(`page${i + 1}: `, page)
const width = page.style.width;
const height = page.style.height;
if (page) {
const dom = page
//图片加像素
const startTimeDiv = new Date();
await replaceBackgroundImage(dom);
console.log(`page${i+1}:loop div using ${((new Date()-startTimeDiv)/1000).toFixed(2)}s`)
// 解决转换出来的图片的清晰度问题
// 手动创建一个 canvas 标签
let root = document.documentElement
root.style.overflow = 'auto'
const canvas = document.createElement('canvas')
// 获取父级的宽高
const width = parseInt(window.getComputedStyle(dom).width)
const height = parseInt(window.getComputedStyle(dom).height)
// 定义放大倍数,可支持小数
canvas.width = width * scale
canvas.height = height * scale
canvas.style.width = width + 'px'
canvas.style.height = height + 'px'
// 拿到目标dom调用一下html2canvas方法就能生成canvas对象了
// 获取要转换的元素
await html2canvas(dom, {
canvas: canvas,
scale: scale,
dpi:96*2,
useCORS: true,// 开启跨域设置,需要后台设置cors
ignoreElements: (element) => {
// console.log(element)
if(rules.has(element.className)){
return true;
}
/*if(element.tagName==='DIV' &&element.getAttribute('style')&& element.getAttribute('style').includes('background-image: url("blob:https://www.gaoding.com/')){
return true;
}*/
}
}).then((canvas) => {
if (!pdf) {
pdf = new jspdf.jsPDF({
orientation: canvas.width / canvas.height > 1 ? 'l' : 'p',
unit: 'px',
format: [canvas.width, canvas.height],
compressPdf: true
});
}
if (i !== 0) {
pdf.addPage({
format: [canvas.width, canvas.height],
});
}
let dataURL = canvas.toDataURL('image/png')
// 计算居中位置
const x = 0;
const y = 0;
// 确保坐标和尺寸有效
pdf.addImage(dataURL, 'PNG', x, y, canvas.width, canvas.height);
console.log(`page ${i + 1}: loaded in ${((new Date()-startTime)/1000).toFixed(2)}s`)
console.log(`---------------------------------------------------------------------------------`)
})
}
}
pdf.save(`${meta.title}.pdf`);
break
/**
* 情況二:zip的pdf文件
* 图片为PDF单页文件
*/
case 1:
//https://book.yunzhan365.com/eurha/wafe/files/content-page/04f0837c365de291805215b7b97a5bdf.zip
let baseUrl = meta.url && meta.url.slice(0, meta.url.lastIndexOf('/') + 1) + 'files/content-page/'
for (let i = 0; i < fliphtml5_pages.length; i++) {
if (fliphtml5_pages[i].n[0] && fliphtml5_pages[i].n[0].includes("zip")) {
let url = `${baseUrl}${fliphtml5_pages[i].n[0]}`
console.log('url:', url)
let blobRes =await getBlob(url)
console.log('blobRes:', blobRes)
this.loadingTask = oriPDF.getDocument({url: blobRes.url, password: blobRes.password});
const l = await this.loadingTask.promise;
const page = await l.getPage(1);
console.log(`Page ${i} loaded`);
const scale = 2; // 缩放比例
const viewport = page.getViewport({scale});
if (!pdf) {
pdf = new jspdf.jsPDF({
orientation: viewport.width / viewport.height > 1 ? 'l' : 'p',
unit: 'px',
format: [viewport.width, viewport.height],
});
}
if (i !== 0) {
pdf.addPage({
format: [viewport.width, viewport.height],
});
}
// 创建用于渲染的 canvas
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
// 渲染 PDF 页面到 canvas
const renderContext = {
canvasContext: context,
viewport: viewport,
};
await page.render(renderContext).promise;
// 获取图像数据
const imgData = canvas.toDataURL('image/png');
// 将图像数据添加到 jsPDF 中
pdf.addImage(imgData, 'PNG', 0, 0, viewport.width, viewport.height);
canvas.remove();
}
}
pdf.save(`${meta.title}.pdf`);
break
default:
}
button_01.innerText = '下载-兼容模式'
console.log('下载成功!')
if (Swal) {
Swal.fire({
position: 'center', //定位 左上角
icon: 'success',
title: `下载成功!~用时:${((new Date()-start)/1000).toFixed(2)}s`,
// showConfirmButton: false,
// timer: 3000
})
}
} catch (e) {
if (Swal) {
Swal.fire({
position: 'center', //定位 左上角
icon: 'error',
title: `下载失败,请刷新重试!~${e}`,
// showConfirmButton: false,
// timer: 1500
})
}
button_01.innerText = '下载-兼容模式'
console.error(e)
}
}
// 定义一个递归函数来遍历所有 div
async function replaceBackgroundImage(div) {
// 获取当前 div 的背景图像样式
const style = div.style.backgroundImage;
// 检查是否包含 background-image
if (style) {
// 提取 URL 部分
const urlMatch = style.match(/url\(["']?(.*?)["']?\)/);
if (urlMatch) {
let url = urlMatch[1];
/**
* 解决 图片模糊
* 生成img标签
*/
await new Promise(resolve => {
const imgElement = document.createElement('img');
// 替换掉查询字符串部分
url = url.split('?')[0]; // 删除 '?' 及其后面内容
// 更新背景图像的样式
div.style.backgroundImage = ``;
Array.from(div.attributes).forEach(attr => {
imgElement.setAttribute(attr.name, attr.value);
});
imgElement.src = url;
imgElement.onload=()=>{
div.parentNode.replaceChild(imgElement, div);
console.log(`img replaced url:${url}`)
resolve();
}
});
}
}
// 遍历当前 div 的所有子元素
const children = div.children;
for (let i = 0; i < children.length; i++) {
if (rules.has(children[i].className)) {
console.log(`${children[i].className} has been remove! `);
children[i].remove()
break
}
await replaceBackgroundImage(children[i]); // 递归调用
}
}
/**
* 解密PDF文件,返回密码和文件url
* @param b
* @returns {*}
*/
function getBlob(b) {
return new Promise(function (c, d) {
d = new XMLHttpRequest;
d.open("get", b, !0);
d.responseType = "blob";
d.onload = function () {
if (4 == this.readyState && 200 == this.status) {
(new Date).getTime();
window.response = this.response;
let e = new FileReader;
e.onload = function () {
window.arrayBuffer = new Uint8Array(this.result)
};
e.readAsArrayBuffer(response);
let f = response.slice(1083, response.size - 1003, "application/pdf"), g = "",
h = response.slice(1080, 1083),
k = response.slice(response.size - 1003, response.size - 1E3);
e = new FileReader;
e.onload = function () {
g = this.result + g;
let l = new FileReader;
l.onload = function () {
g += this.result;
var n = f.slice(0, 4E3), p = new FileReader;
p.onload = function () {
var v = new Uint8Array(this.result);
for (let i = 0; i < this.result.byteLength; ++i) v[i] = 255 - v[i];
v = new Blob([new Blob([v.buffer]), f.slice(4E3, f.size)], {type: "application/pdf"});
v = window.URL.createObjectURL(v);
c({url: v, password: g})
};
p.readAsArrayBuffer(n)
};
l.readAsText(k)
};
e.readAsText(h)
}
};
d.send()
}.bind(this))
}
async function addImageToPDF(meta, imageUrls, basicUrl) {
let pdf;
for (let i = 0; i < imageUrls.length; i++) {
try {
let url = imageUrls[i].n[0].split('?')[0]
url += `?x-oss-process=image/sharpen,100`
if (url.includes('files/large/')) {
url = url.replace('../', '')
} else {
url = 'files/large/' + url
}
url = basicUrl + url;
console.log(`第${i + 1}页 url:`, url)
const img = await loadImage(url);
const imgWidth = img.width;
const imgHeight = img.height;
if (!pdf) {
pdf = new jspdf.jsPDF({
orientation: imgWidth / imgHeight > 1 ? 'l' : 'p',
unit: 'px',
format: [imgWidth, imgHeight],
compress: true
});
}
if (i !== 0) {
pdf.addPage({
format: [imgWidth, imgHeight],
});
}
/*const pageWidth = pdf.internal.pageSize.width;
const pageHeight = pdf.internal.pageSize.height;
// 计算缩放比例
const ratio = Math.min(pageWidth / imgWidth, pageHeight / imgHeight);
const newWidth = imgWidth * ratio;
const newHeight = imgHeight * ratio;*/
// 计算居中位置
const x = 0;
const y = 0;
// 确保坐标和尺寸有效
pdf.addImage(img, 'WEBP', x, y, imgWidth, imgHeight);
/*if (newWidth > 0 && newHeight > 0 && x >= 0 && y >= 0) {
pdf.addImage(img, 'WEBP', x, y, newWidth, newHeight);
} else {
console.error('Invalid dimensions or coordinates:', { newWidth, newHeight, x, y });
}*/
/*if(i!==imageUrls.length-1){
pdf.addPage()
}*/
if (Swal && i > 10) {
Swal.fire({
position: 'top-end', //定位 左上角
icon: 'success',
title: `加载中,第${i + 1}页`,
showConfirmButton: false,
})
}
} catch (e) {
if (Swal) {
Swal.fire({
icon: 'error',
title: `下载失败,${e.message}`,
showConfirmButton: true,
})
}
}
}
return pdf;
}
function loadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'anonymous'; // 处理跨域问题
img.src = url;
img.onload = () => {
/*const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);*/
// resolve(canvas.toDataURL('image/webp'));
resolve(img);
};
img.onerror = reject;
});
}
function sleep(ms){
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Your code here...
})();