- // ==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/html2canvas@1.4.1/dist/html2canvas.min.js
- // @require https://cdn.jsdelivr.net/npm/pdf-lib@1.17.1/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...
- })();