您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Cleverly download all images on a webpage
当前为
// ==UserScript== // @name Ripper // @namespace http://tampermonkey.net/ // @version 0.2 // @description Cleverly download all images on a webpage // @author TetteDev // @match *://*/* // @icon https://icons.duckduckgo.com/ip2/tampermonkey.net.ico // @license MIT // @grant GM_cookie // @grant GM_xmlhttpRequest // @grant GM.xmlHttpRequest // @grant GM_registerMenuCommand // @grant GM_getValue // @grant GM_deleteValue // @grant GM_setValue // @run-at document-idle // @noframes // ==/UserScript== const RenderGui = () => { const highlightSelector = "4px dashed purple"; const highlightElement = (element) => { element.style.border = highlightSelector; }; const unhighlightElement = (element) => { element.style.border = ""; } let container = null; const guiClassName = 'gui-container'; if ((container = document.querySelector(`.${guiClassName}`))) { container.remove(); } else { const style = document.createElement('style'); style.textContent = ` .gui-container { font-family: 'Segoe UI', Arial, sans-serif; max-width: 600px; margin: 20px auto; padding: 10px; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); position: fixed; z-index: 9999; width: auto; height: auto; top: 15px; right: 15px; border: 1px solid black; } .input-group { display: flex; gap: 5px; margin-bottom: 10px; } .input-text { flex: 1; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; color: black !important; } .btn { padding: 8px 16px; background:rgb(250, 0, 0); color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; } .item-list { list-style: none; padding: 0; margin: 0 0 20px 0; max-height: 450px; overflow-y: auto; overflow-x: hidden; } .item-list li { display: flex; align-items: center; padding: 3px; border-bottom: 1px solid #eee; -webkit-user-select: none !important; -khtml-user-select: none !important; -moz-user-select: -moz-none !important; -o-user-select: none !important; user-select: none !important; } .item-list li:hover { background-color: yellow; } .checkbox-group { margin-bottom: 10px; } .checkbox-label { display: inline-flex; align-items: center; margin-right: 20px; cursor: pointer; color: black !important; } .download-btn { width: 100%; padding: 12px; background: rgb(250, 0, 0); color: white; font-weight: bold; } `; document.body.appendChild(style); } // Create GUI elements container = document.createElement('div'); container.className = guiClassName; // Add dragging functionality let isDragging = false; let currentX; let currentY; let initialX; let initialY; let xOffset = 0; let yOffset = 0; const dragStart = (e) => { if (e.target !== container) return; // Only drag from container itself initialX = e.clientX - xOffset; initialY = e.clientY - yOffset; if (e.target === container) { isDragging = true; container.style.cursor = "move"; } }; const dragEnd = () => { initialX = currentX; initialY = currentY; isDragging = false; container.style.cursor = ""; }; const drag = (e) => { if (!isDragging) return; e.preventDefault(); currentX = e.clientX - initialX; currentY = e.clientY - initialY; xOffset = currentX; yOffset = currentY; container.style.transform = `translate(${currentX}px, ${currentY}px)`; }; container.removeEventListener('mousedown', dragStart); document.removeEventListener('mousemove', drag); document.removeEventListener('mouseup', dragEnd); container.addEventListener('mousedown', dragStart); document.addEventListener('mousemove', drag); document.addEventListener('mouseup', dragEnd); // Input group const inputGroup = document.createElement('div'); inputGroup.className = 'input-group'; const textbox = document.createElement('input'); textbox.type = 'text'; textbox.className = 'input-text'; textbox.placeholder = 'Enter a valid CSS selector'; const getMatchesButton = document.createElement('button'); getMatchesButton.className = 'btn'; getMatchesButton.textContent = '⟳'; getMatchesButton.style.fontWeight = "bold"; getMatchesButton.title = "Execute the CSS Selector (or just press enter)"; let matchedElements = []; textbox.addEventListener("keyup", (e) => { if (e.key !== "Enter") return; getMatchesButton.dispatchEvent(new Event('click', { 'bubbles': true })); }); getMatchesButton.onclick = () => { matchedElements.forEach(match => { unhighlightElement(match); }); matchedElements = []; Array.from(document.querySelectorAll('.item-list > li')).forEach(li => { li.remove(); }); const selector = textbox.value; if (!selector) return; try { const matches = Array.from(document.querySelectorAll(selector)); matches.forEach((match, index) => { addListItem(`Match ${index + 1}`, match, () => { matchedElements.forEach(match => { unhighlightElement(match); }); highlightElement(match); match.scrollIntoView(); setTimeout(() => { unhighlightElement(match); }, 4000); }); matchedElements.push(match); }); const lis = Array.from(document.querySelectorAll('.item-list > li')); const selected = matches.filter(match => { const cb = lis.find(li => li.ref.isEqualNode(match)).querySelector('input[type="checkbox"]'); cb.onchange = () => { const dlbtn = document.querySelector(".download-btn"); const lis = Array.from(document.querySelectorAll('.item-list > li')); const selected = matches.filter(match => { const cb = lis.find(li => li.ref.isEqualNode(match)).querySelector('input[type="checkbox"]'); return cb.checked; }); dlbtn.textContent = `Download ${selected.length} Item(s)`; }; return cb.checked; }); document.querySelector(".download-btn").textContent = `Download ${selected.length} Item(s)`; } catch (err) { } }; // List const itemList = document.createElement('ul'); itemList.className = 'item-list'; // Checkbox group const checkboxGroup = document.createElement('div'); checkboxGroup.className = 'checkbox-group'; const options = [['Humanize', 'checked'], ['Inherit HTTP Only Cookies', 'checked'], ['Placeholder Disabled', 'disabled'], 'Placeholder Normal']; options.forEach(opt => { const label = document.createElement('label'); label.className = 'checkbox-label'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; label.style.display = "block"; label.appendChild(checkbox); if (typeof opt === "object") { const text = opt[0]; label.appendChild(document.createTextNode(` ${text}`)); opt.slice(1).forEach(o => { switch (o) { case "checked": checkbox.checked = true; break; case "disabled": checkbox.disabled = true; break; default: console.warn(`Unrecognized checkbox opt: '${o}'`); break; } }) } else { label.appendChild(document.createTextNode(` ${opt}`)); } checkboxGroup.appendChild(label); }); // Download button const downloadBtn = document.createElement('button'); downloadBtn.className = 'btn download-btn'; downloadBtn.textContent = 'Download 0 Item(s)'; downloadBtn.onclick = async () => { if (matchedElements.length === 0) return; const ResolveImageUrl = (img) => { const lazyAttributes = [ "data-src", "data-pagespeed-lazy-src", "srcset", "src", "zoomfile", "file", "original", "load-src", "_src", "imgsrc", "real_src", "src2", "origin-src", "data-lazyload", "data-lazyload-src", "data-lazy-load-src", "data-ks-lazyload", "data-ks-lazyload-custom", "loading", "data-defer-src", "data-actualsrc", "data-cover", "data-original", "data-thumb", "data-imageurl", "data-placeholder", ]; const IsUrl = (url) => { // TODO: needs support for relative file paths also? var pattern = new RegExp( '^(https?:\\/\\/)?'+ // protocol '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path '(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string '(\\#[-a-z\\d_]*)?$','i'); let isUrl = !!pattern.test(url); if (!isUrl) { try { new URL(url); return true; } catch(err) { return false; } } return true; }; let possibleImageUrls = lazyAttributes.filter(attr => { let attributeValue = img.getAttribute(attr); if (!attributeValue) return false; attributeValue = attributeValue.replaceAll('\t', '').replaceAll('\n',''); let ok = IsUrl(attributeValue.trim()); if (!ok && attr === "srcset") { // srcset usually contains a comma delimited string that is formatted like // <URL1>, <WIDTH>w, <URL2>, <WIDTH>w, <URL3>, <WIDTH>w, // TODO: handle this case const srcsetItems = attributeValue.split(',').map(attr => attr.trim()).map(item => item.split(' ')); if (srcsetItems.length > 0) { img.setAttribute("srcset", srcsetItems[srcsetItems.length - 1][0]); ok = IsUrl(img.getAttribute("srcset")); } } return ok; }).map(validAttr => img.getAttribute(validAttr).trim()); if (!possibleImageUrls || possibleImageUrls.length < 1) { if (img.hasAttribute("src")) return img.src.trim(); console.error(`Could not resolve the image source URL from the image object`, img); return ""; } return possibleImageUrls.length > 1 ? [...new Set(possibleImageUrls)][0] : possibleImageUrls[0]; }; const lis = Array.from(document.querySelectorAll('.item-list > li')); let urls = matchedElements.map(match => { const matchCb = lis.find(li => li.ref.isEqualNode(match)).querySelector('input[type="checkbox"]'); let actualMatch = match instanceof HTMLImageElement ? match : match.querySelector('img'); if (!actualMatch) return ''; const src = matchCb.checked ? ResolveImageUrl(actualMatch) : ''; return src; }).filter(url => { return url.length > 0; }); // TODO: filter out duplicates? await Download(urls); }; // Add elements to container inputGroup.appendChild(textbox); inputGroup.appendChild(getMatchesButton); container.appendChild(inputGroup); //container.appendChild(itemListHeader); container.appendChild(itemList); container.appendChild(checkboxGroup); container.appendChild(downloadBtn); // Add to document document.body.appendChild(container); // Function to add new item to list function addListItem(text, elemRef, itemClickCallback = null) { const li = document.createElement('li'); li.style.cssText = "cursor: pointer; padding: 0px; color: black !important;" if (itemClickCallback && typeof itemClickCallback === "function") { li.ondblclick = itemClickCallback; } if (elemRef) { li.ref = elemRef; } li.title = 'Double click an entry to scroll to it and highlight it'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.checked = true; if (!elemRef && !itemClickCallback) checkbox.disabled = true; checkbox.style.marginRight = '10px'; const textNode = document.createTextNode(text); li.appendChild(checkbox); li.appendChild(textNode); itemList.appendChild(li); } addListItem("No matches", null, null); }; const SleepRange = (min, max) => { const _min = Math.min(min, max); const _max = Math.max(min, max); const ms = Math.floor(Math.random() * (_max - _min + 1) + _min); if (ms <= 0) return; return new Promise(r => setTimeout(r, ms)); }; const GetBlob = (url, inheritHttpOnlyCookies = true) => { return new Promise(async (resolve, reject) => { const res = await GM.xmlHttpRequest({ method: "GET", url: url, headers: { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Language": "en-US,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "DNT": `${window.navigator.doNotTrack || "1"}`, "Referer": document.location.href || url, "Origin": document.location.origin || url, "Host": window.location.host || window.location.hostname, "User-Agent": window.navigator.userAgent, "Priority": "u=0, i", "Upgrade-Insecure-Requests": "1", "Connection": "keep-alive", //"Cache-Control": "no-cache", "Cache-Control": "max-age=0", "Sec-Fetch-Dest": "document", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-User": "?1", "Sec-GPC": "1", }, responseType: "blob", cookiePartition: { topLevelSite: inheritHttpOnlyCookies ? location.origin : null } }) .catch((error) => { debugger; return reject(error); }); const allowedImageTypes = ["webp","png","jpg","jpeg","gif","bmp"]; const HTTP_OK_CODE = 200; const ok = res.readyState == res["DONE"] && res.status === HTTP_OK_CODE && //res.response && ["webp","image"].some(t => res.response.type.includes(t)) res.response && (res.response.type.startsWith('image/') && allowedImageTypes.includes(res.response.type.split('/')[1].toLowerCase())); if (!ok) { debugger; return reject(error); } return resolve({ blob: res.response, filetype: res.response.type.split('/')[1], }); }); }; const SaveBlob = async (blob, fileName) => { const MakeAndClickATagAsync = async (blobUrl, fileName) => { try { let link; // Reuse existing element for sequential downloads if (!window._downloadLink) { window._downloadLink = document.createElement("a"); window._downloadLink.style.cssText = "display: none !important;"; try { document.body.appendChild(window._downloadLink); } catch (err) { // Handle Trusted Types policy if (window.trustedTypes && window.trustedTypes.createPolicy) { const policy = window.trustedTypes.createPolicy('default', { createHTML: (string) => string }); } document.body.appendChild(window._downloadLink); } } link = window._downloadLink; // Set attributes and trigger download link.href = blobUrl; link.download = fileName; await Promise.resolve(link.click()); return true; } catch (error) { console.error('Download failed:', error); await Promise.reject([false, error]); } }; const blobUrl = window.URL.createObjectURL(blob) await MakeAndClickATagAsync(blobUrl, fileName) .catch(([state, errorMessage]) => { window.URL.revokeObjectURL(blobUrl); console.error(errorMessage); debugger; return reject([false, errorMessage, res]); }); window.URL.revokeObjectURL(blobUrl); }; const cancelSignal = {cancelled:false}; async function Download(urls) { if (typeof urls === "string") urls = [urls]; const progressbar = document.createElement("div"); progressbar.style.cssText = `position:fixed;z-index:9999;bottom:0px;right:0px;width:100%;max-height:30px;background-color:white;`; progressbar.innerHTML = ` <span class="dl_text" style="color:black;padding-right:5px;"></span> <button class="dl_cancel">Cancel</button `; document.body.appendChild(progressbar); const text = progressbar.querySelector(".dl_text"); const btn = progressbar.querySelector(".dl_cancel"); btn.onclick = () => { cancelSignal.cancelled = true; }; for (let i = 0; i < urls.length; i++) { if (cancelSignal.cancelled) break; const url = urls[i]; text.textContent = `Downloading ${url} ... (${i+1}/${urls.length})`; try { const {blob, filetype} = await GetBlob(url, true); await SaveBlob(blob, `${i}.${filetype}`) } catch (err) { console.error("Something went wrong downloading from url ", url); console.error(err); } await SleepRange(650, 850); } progressbar.remove(); } RenderGui();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址