// ==UserScript== // @name 淘宝详情、天猫详情、阿里巴巴详情,主图、主图视频、SKU图一键打包下载,淘宝链接、天猫链接、阿里巴巴链接精简 // @version 2024.11.29 // @description 一键打包下载详情、主图、SKU和视频 // @author Suren_Chan // @match https://detail.tmall.com/* // @match https://item.taobao.com/* // @match https://detail.1688.com/* // @match https://chaoshi.detail.tmall.com/* // @match https://detail.tmall.hk/* // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js // @grant none // @license MIT // @namespace https://greasyfork.org/users/786427 // @downloadURL https://update.greasyfork.icu/scripts/460143/%E6%B7%98%E5%AE%9D%E8%AF%A6%E6%83%85%E3%80%81%E5%A4%A9%E7%8C%AB%E8%AF%A6%E6%83%85%E3%80%81%E9%98%BF%E9%87%8C%E5%B7%B4%E5%B7%B4%E8%AF%A6%E6%83%85%EF%BC%8C%E4%B8%BB%E5%9B%BE%E3%80%81%E4%B8%BB%E5%9B%BE%E8%A7%86%E9%A2%91%E3%80%81SKU%E5%9B%BE%E4%B8%80%E9%94%AE%E6%89%93%E5%8C%85%E4%B8%8B%E8%BD%BD%EF%BC%8C%E6%B7%98%E5%AE%9D%E9%93%BE%E6%8E%A5%E3%80%81%E5%A4%A9%E7%8C%AB%E9%93%BE%E6%8E%A5%E3%80%81%E9%98%BF%E9%87%8C%E5%B7%B4%E5%B7%B4%E9%93%BE%E6%8E%A5%E7%B2%BE%E7%AE%80.user.js // @updateURL https://update.greasyfork.icu/scripts/460143/%E6%B7%98%E5%AE%9D%E8%AF%A6%E6%83%85%E3%80%81%E5%A4%A9%E7%8C%AB%E8%AF%A6%E6%83%85%E3%80%81%E9%98%BF%E9%87%8C%E5%B7%B4%E5%B7%B4%E8%AF%A6%E6%83%85%EF%BC%8C%E4%B8%BB%E5%9B%BE%E3%80%81%E4%B8%BB%E5%9B%BE%E8%A7%86%E9%A2%91%E3%80%81SKU%E5%9B%BE%E4%B8%80%E9%94%AE%E6%89%93%E5%8C%85%E4%B8%8B%E8%BD%BD%EF%BC%8C%E6%B7%98%E5%AE%9D%E9%93%BE%E6%8E%A5%E3%80%81%E5%A4%A9%E7%8C%AB%E9%93%BE%E6%8E%A5%E3%80%81%E9%98%BF%E9%87%8C%E5%B7%B4%E5%B7%B4%E9%93%BE%E6%8E%A5%E7%B2%BE%E7%AE%80.meta.js // ==/UserScript== (function() { 'use strict'; // 定义全局变量 const Domain = window.location.protocol + "//" + window.location.hostname; let Product_Name = "", Main_Video = "", Main_Image = [], SKU_Diagram = [], SKU_Name = [], Details_Page = []; // 创建JSZip实例 const zip = new JSZip(); // 获取产品名称 function ObtainPN() { Product_Name = document.querySelector('title').textContent; Product_Name = Product_Name.replace(/\|/g, '_'); console.log(Product_Name); } //定义、获取基础内容------------------------------------------------------------------------------------- // 获取图片 URL 并清洗 const cleanImageUrl = (url) => url.replace(/(\.jpg|\.jpeg|\.png|\.gif)(.*)?$/, '$1'); // 清洗页面链接 function CleaningLinks() { const params = new URLSearchParams(window.location.search); const id = params.get('id'); const offerId = params.get('offerId'); let simplifiedUrl; if (offerId) { simplifiedUrl = `${window.location.origin}/offer/${offerId}.html`; } else if (id) { simplifiedUrl = `${window.location.origin}/item.htm?id=${id}`; } else { // 如果没有id和offerId,保留原始URL但添加.html扩展名 const currentPath = window.location.pathname; const pathWithoutExtension = currentPath.substring(0, currentPath.lastIndexOf('.')); simplifiedUrl = `${pathWithoutExtension}.html`; } if (simplifiedUrl) { window.history.pushState({}, '', simplifiedUrl); } } CleaningLinks(); //自动播放视频 function ActiveVideo() { const ul = document.querySelector('ul[class^="thumbnails--"]'); if (ul) { const lis = ul.querySelectorAll('li'); if (lis.length > 0) { const firstLi = lis[0]; firstLi.click(); firstLi.classList.add('active'); } } } ActiveVideo(); //自动滚动页面 function autoScrollAndLoadImages() { return new Promise((resolve) => { const step = 500; // 每次滚动的步长 const interval = 100; // 每次滚动的时间间隔(毫秒) const scrollDuration = 100; // 到达底部后等待的时间(毫秒) let currentScroll = 0; let isScrolledToBottom = false; const scrollInterval = setInterval(() => { const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight; const clientHeight = document.documentElement.clientHeight || document.body.clientHeight; const scrollTop = document.documentElement.scrollTop || document.body.scrollTop; currentScroll += step; if (currentScroll < scrollHeight - clientHeight) { window.scrollTo(0, currentScroll); } else { clearInterval(scrollInterval); isScrolledToBottom = true; setTimeout(() => { document.documentElement.scrollIntoView({ behavior: 'smooth' }); resolve(); }, scrollDuration); } }, interval); }); } //结束后放烟花 function showFireworkEffect() { // 创建爆炸容器 const container = document.createElement('div'); container.id = 'firework-container'; container.style.position = 'absolute'; container.style.width = '1024px'; container.style.height = '1024px'; container.style.left = (window.innerWidth / 2 - 512) + 'px'; // 中心位置 container.style.top = (window.innerHeight / 2 - 512) + 'px'; // 中心位置 document.body.appendChild(container); // 创建碎片 for (let i = 0; i < 100; i++) { const fragment = document.createElement('div'); fragment.className = 'fragment'; fragment.style.position = 'absolute'; fragment.style.width = '10px'; fragment.style.height = '10px'; fragment.style.backgroundColor = '#2196F3'; fragment.style.borderRadius = '50%'; fragment.style.opacity = '1'; fragment.style.pointerEvents = 'none'; // 避免影响其他元素的点击事件 fragment.style.left = '507px'; // 初始位置在容器中心 fragment.style.top = '507px'; // 初始位置在容器中心 container.appendChild(fragment); // 设置随机运动轨迹和速度 const angle = Math.random() * 2 * Math.PI; const speed = 500 + Math.random() * 500; // 速度范围在500到1000之间 const duration = 1; // 持续时间1秒 fragment.style.animation = `fly ${duration}s forwards`; fragment.style.setProperty('--angle', `${angle}rad`); fragment.style.setProperty('--speed', `${speed}px`); } // 添加CSS动画 const style = document.createElement('style'); style.innerHTML = ` .fragment { will-change: transform, opacity; } @keyframes fly { to { transform: translate( calc(var(--speed) * cos(var(--angle))), calc(var(--speed) * sin(var(--angle))) ); opacity: 0; } } `; document.head.appendChild(style); // 动画结束后删除容器和样式 setTimeout(() => { document.body.removeChild(container); document.head.removeChild(style); }, 1000); // 这个值应该和最长的动画持续时间一致 } //辅助功能模块------------------------------------------------------------------------------------- // 获取主视频 function ObtainMV() { const videoElement = document.querySelector('.lib-video video'); if (videoElement) Main_Video = videoElement.src.split('?')[0]; } // 获取主图 function ObtainMI() { document.querySelectorAll('ul[class*="thumbnails--"], div.img-list-wrapper').forEach(element => { element.querySelectorAll('img').forEach(img => Main_Image.push(cleanImageUrl(img.src))); }); } // 获取 SKU 图 function ObtainSKU() { // 查找class="skuWrapper--juKIVod0"的div标签并处理 document.querySelectorAll('div[class^="skuWrapper--"]').forEach(skuWrapper => { // 查找所有class="valueItemImg--Jd1OD58R"的img标签 const images = skuWrapper.querySelectorAll('img[class^="valueItemImg--"]'); images.forEach(img => { const cleanedUrl = cleanImageUrl(img.src); // 清理图片地址 SKU_Diagram.push(cleanedUrl); // 放入SKU_Diagram数组 // 查找同级别的class="valueItemText--HiKnUqGa"的span标签 const spans = skuWrapper.querySelectorAll('span[class^="valueItemText--"]'); for (let i = 0; i < spans.length; i++) { if (spans[i].parentElement === img.parentElement) { const textContent = spans[i].textContent.trim().replace(/\//g, '每').replace(/\*/g, 'x'); // 替换文本内容中的“/”和“*” SKU_Name.push(textContent); // 放入SKU_Name数组 break; // 找到匹配的span后停止循环 } } }); }); // 查找class="pc-sku-wrapper"的div标签并处理 const pcSkuWrapper = document.querySelector('div.pc-sku-wrapper'); if (pcSkuWrapper) { // 处理class="sku-item-image"的div标签 pcSkuWrapper.querySelectorAll('div.sku-item-image').forEach(imageDiv => { const bgImage = window.getComputedStyle(imageDiv).backgroundImage; const urlMatch = /url\(['"]?(.*?)['"]?\)/.exec(bgImage); if (urlMatch) { const cleanedUrl = cleanImageUrl(urlMatch[1].replace(/['"]/g, '')); // 清理图片地址 SKU_Diagram.push(cleanedUrl); // 放入SKU_Diagram数组 // 查找同级别的class="sku-item-left"的div标签里的class="sku-item-name"的div标签 const skuItemNameDiv = imageDiv.parentNode.querySelector('div.sku-item-left div.sku-item-name'); if (skuItemNameDiv) { const textContent = skuItemNameDiv.textContent.trim().replace(/\//g, '每').replace(/\*/g, 'x'); // 替换文本内容中的“/”和“*” SKU_Name.push(textContent); // 放入SKU_Name数组 } } }); // 处理class="prop-img"的div标签 pcSkuWrapper.querySelectorAll('div.prop-img').forEach(imageDiv => { const bgImage = window.getComputedStyle(imageDiv).backgroundImage; const urlMatch = /url\(['"]?(.*?)['"]?\)/.exec(bgImage); if (urlMatch) { const cleanedUrl = cleanImageUrl(urlMatch[1].replace(/['"]/g, '')); // 清理图片地址 SKU_Diagram.push(cleanedUrl); // 放入SKU_Diagram数组 // 查找同级别的class="prop-name"的div标签 const propNameDiv = imageDiv.nextElementSibling; if (propNameDiv && propNameDiv.classList.contains('prop-name')) { const textContent = propNameDiv.textContent.trim().replace(/\//g, '每').replace(/\*/g, 'x'); // 替换文本内容中的“/”和“*” SKU_Name.push(textContent); // 放入SKU_Name数组 } } }); } } // 获取详情页图 function ObtainDP() { const contentDiv = document.querySelector('.desc-root') || document.querySelector('.content-detail'); if (contentDiv) { contentDiv.querySelectorAll('img').forEach(img => { let src = img.src.split('?')[0]; if (img.width >= 700 && src.match(/\.(jpg|jpeg|png|gif)$/)) Details_Page.push(src); }); } } //合成长版详情 async function MDLong() { const longCanvas = document.createElement('canvas'); longCanvas.width = 790; let totalHeight = 0; const imagesForLongImg = []; for (const imgSrc of Details_Page) { const img = await createImageBitmap(await fetch(imgSrc).then(res => res.blob())); imagesForLongImg.push(img); totalHeight += img.height * (790 / img.width); } longCanvas.height = totalHeight; const ctx = longCanvas.getContext('2d'); let currentHeight = 0; for (const img of imagesForLongImg) { ctx.drawImage(img, 0, currentHeight, 790, img.height * (790 / img.width)); currentHeight += img.height * (790 / img.width); } return new Promise(resolve => longCanvas.toBlob(resolve, "image/png")); } //主要功能模块------------------------------------------------------------------------------------- // 创建下载按钮 function createDownloadButton() { // 创建包含按钮的div容器 const divContainer = document.createElement('div'); divContainer.id = 'DLBT'; divContainer.style.cssText = ` position: fixed; width: 56px; height: 200px; background-color: #fff; right: 0px; top: 200px; z-index: 9999; border-radius: 18px 0 0 18px; box-shadow: -2px 0 30px 2px rgba(97, 105, 119, 0.18); cursor: pointer; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 8px 0; /* Adjust padding to fit the buttons */ `; document.body.appendChild(divContainer); // 添加容器到body // 创建三个按钮并添加到容器中 const buttonsInfo = [ { id: 'DLA', text: '打包', icon: ` `, onClick: DownloadALL, // 点击事件对应的函数 }, { id: 'DLV', text: '视频', icon: ` `, onClick: DownloadVD, // 点击事件对应的函数 }, { id: 'DLI', text: '长版', icon: ` `, onClick: DownloadLD, // 点击事件对应的函数 }, ]; buttonsInfo.forEach(info => { const btn = document.createElement('div'); btn.id = info.id; btn.style.cssText = ` width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; cursor: pointer; color: #2196F3; font-size: 14px; font-family: '微软雅黑'; text-align: center; margin: 4px 0; /* Adjust margin to fit the buttons */ user-select: none; /* Prevent text selection */ `; btn.innerHTML = ` ${info.icon}
${info.text}
`; // 绑定点击事件 btn.addEventListener('click', info.onClick); divContainer.appendChild(btn); }); } // 创建进度条容器 function createProgressBar() { const container = document.createElement('div'); container.style.cssText = 'position: fixed;width: 500px;top: 50%;left: 50%;transform: translate(-50%, -50%);background-color: rgba(255,255,255,0.8);padding: 5px;border-radius: 20px;z-index: 9999;border: 2px solid #666; display: none;'; const progressBar = document.createElement('div'); progressBar.style.cssText = 'width: 100%;height: 20px;background-color: rgb(221, 221, 221);position: relative;border-radius: 10px;border: 2px solid white;text-align: center;'; const progressFill = document.createElement('div'); progressFill.style.cssText = 'width: 0%;height: 100%;background-color: rgb(33, 150, 243);border-radius: 10px;'; const progressText = document.createElement('span'); progressText.textContent = '正在下载……'; // 设置文字内容 progressText.style.cssText = 'position: absolute;top: 50px;/* left: 100px; *//* text-align: center; */transform: translate(-50%, -50%);color: rgb(33, 150, 243);font-family: 微软雅黑;font-weight: 600;font-size: 24px;text-shadow: rgb(255, 255, 255) 1px 1px 0px, rgb(255, 255, 255) -1px -1px 0px, rgb(255, 255, 255) 1px -1px 0px, rgb(255, 255, 255) -1px 1px 0px;'; // 设置样式,添加白色描边 progressBar.appendChild(progressFill); progressBar.appendChild(progressText); // 将文字添加到进度条容器中 container.appendChild(progressBar); document.body.appendChild(container); return { container, progressFill }; } //页面内容注入------------------------------------------------------------------------------------- // 下载并打包所有图片 async function DownloadALL() { await ActiveVideo(); await autoScrollAndLoadImages(); await new Promise(resolve => setTimeout(resolve, 500)); const { container, progressFill } = createProgressBar(); container.style.display = 'block'; progressFill.style.width = '0%'; const mainFolder = zip.folder("主图"); const skuFolder = zip.folder("SKU"); const slicesFolder = zip.folder("切片"); progressFill.style.width = '5%'; // 获取所有资源 ObtainPN(); ObtainMV(); ObtainMI(); ObtainSKU(); ObtainDP(); progressFill.style.width = '15%'; // 处理主视频 if (Main_Video) { const videoBlob = await fetch(Main_Video).then(res => res.blob()); mainFolder.file("主图视频.mp4", videoBlob); progressFill.style.width = '30%'; } // 处理主图 for (let i = 0; i < Main_Image.length; i++) { const imgBlob = await fetch(Main_Image[i]).then(res => res.blob()); mainFolder.file(`主图${i + 1}.${Main_Image[i].split('.').pop()}`, imgBlob); progressFill.style.width = '45%'; } // 处理 SKU 图 for (let i = 0; i < SKU_Diagram.length; i++) { const imgBlob = await fetch(SKU_Diagram[i]).then(res => res.blob()); const fileExtension = SKU_Diagram[i].split('.').pop(); const fileName = `${SKU_Name[i]}.${fileExtension}`; skuFolder.file(fileName, imgBlob); progressFill.style.width = '60%'; } // 处理详情图 for (let i = 0; i < Details_Page.length; i++) { const imgBlob = await fetch(Details_Page[i]).then(res => res.blob()); const paddedIndex = (i + 1).toString().padStart(2, '0'); const fileName = `image${paddedIndex}`; const fileExtension = Details_Page[i].split('.').pop(); slicesFolder.file(`${fileName}.${fileExtension}`, imgBlob); progressFill.style.width = '80%'; } const longImgBlob = await MDLong(); zip.file(`${Product_Name}.png`, longImgBlob); progressFill.style.width = '90%'; // 生成并保存 ZIP 文件 const zipContent = await zip.generateAsync({ type: "blob" }); saveAs(zipContent, `${Product_Name}.zip`); progressFill.style.width = '100%'; container.style.display = 'none'; showFireworkEffect(); } //下载所有------------------------------------------------------------------------------------- async function DownloadVD() { await ActiveVideo(); ObtainPN(); ObtainMV(); try { // 检查是否有视频地址 if (Main_Video) { // 获取视频Blob对象 const res = await fetch(Main_Video); if (!res.ok) { throw new Error(`Failed to fetch video: HTTP status ${res.status}`); } const videoBlob = await res.blob(); // 创建一个可下载的视频链接 const videoUrl = URL.createObjectURL(videoBlob); const downloadLink = document.createElement('a'); downloadLink.href = videoUrl; downloadLink.download = `${Product_Name}.mp4`; // 使用全局变量Product_Name设置下载文件名 document.body.appendChild(downloadLink); // 将链接添加到页面中 downloadLink.click(); // 触发下载 document.body.removeChild(downloadLink); // 下载后移除链接 URL.revokeObjectURL(videoUrl); // 释放创建的URL对象 showFireworkEffect(); } else { // 如果没有视频地址,弹出对话框 alert("抱歉!没有发现主图视频!"); } } catch (error) { // 捕获到错误时弹出对话框 alert("抱歉!没有发现主图视频!"); } } //下载视频------------------------------------------------------------------------------------- async function DownloadLD() { await autoScrollAndLoadImages(); await new Promise(resolve => setTimeout(resolve, 500)); ObtainPN(); ObtainDP(); // 调用MDLong函数获取合成图片的Blob对象 const longImgBlob = await MDLong(); const longImgUrl = URL.createObjectURL(longImgBlob); const downloadLink = document.createElement('a'); downloadLink.href = longImgUrl; downloadLink.download = `${Product_Name}.png`; // 使用Product_Name变量设置下载文件名 document.body.appendChild(downloadLink); // 将链接添加到页面中 downloadLink.click(); // 触发下载 document.body.removeChild(downloadLink); // 下载后移除链接 URL.revokeObjectURL(longImgUrl); // 释放创建的URL对象 showFireworkEffect(); } //下载长版------------------------------------------------------------------------------------- // 创建下载按钮和触发下载过程 const downloadButton = createDownloadButton(); window.addEventListener('load', ActiveVideo); })();