您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在Civitai图片页自动或手动下载图片和同名元数据.txt文件。新增自动/手动模式开关。
当前为
// ==UserScript== // @name Civitai Image Downloader and Metadata Extractor // @name:zh-CN Civitai 图像下载及元数据提取器 (带开关) // @namespace http://tampermonkey.net/ // @version 2.0 // @description Adds a toggle for automatic/manual downloading. In manual mode, click the original download button to trigger. // @description:zh-CN 在Civitai图片页自动或手动下载图片和同名元数据.txt文件。新增自动/手动模式开关。 // @author Your Name (with major enhancements) // @match https://civitai.com/images/* // @icon https://civitai.com/favicon-32x32.png // @grant GM_download // @grant GM_log // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @run-at document-idle // @license MIT // ==/UserScript== (function() { 'use strict'; // --- 配置 & 常量 --- const AUTO_DOWNLOAD_KEY = 'civitaiDownloader_autoMode'; // 用于存储开关状态的键 // --- 添加自定义样式 --- // 为我们的开关按钮在“激活”状态下设置一个蓝色背景 GM_addStyle(` .civitai-downloader-toggle.active { background-color: #228be6 !important; /* 一个漂亮的蓝色 */ border-color: #228be6 !important; } `); // ============================================================================== // --- 核心下载逻辑 (封装成一个函数,以便重复调用) --- // ============================================================================== async function startFullDownload() { console.log("Civitai Downloader: Starting full download process..."); try { const metadata = extractAllMetadata(); if (!metadata) { console.error("Civitai Downloader: Could not extract metadata."); return; } const imageId = window.location.pathname.split('/')[2]; if (!imageId) { console.error("Civitai Downloader: Could not determine image ID."); return; } // 1. 下载 TXT 文件 const textContent = formatMetadataAsText(metadata); const txtFilename = `${imageId}.txt`; downloadTextFile(textContent, txtFilename); // 2. 智能等待并下载图片 await downloadImageWhenReady(imageId); } catch (error) { console.error("Civitai Downloader: An error occurred in the main download function.", error); } } // ============================================================================== // --- 主程序入口 --- // ============================================================================== function initialize() { console.log("Civitai Downloader: Initializing..."); // 等待页面元素加载完毕 const checkInterval = setInterval(() => { const downloadButton = document.querySelector('svg.tabler-icon-download')?.closest('button'); if (downloadButton) { clearInterval(checkInterval); // 找到元素,停止检查 run(downloadButton); } }, 500); // 设置一个超时,以防页面结构大改,找不到按钮 setTimeout(() => clearInterval(checkInterval), 15000); } function run(downloadButton) { const buttonContainer = downloadButton.parentElement; if (!buttonContainer) return; // 1. 读取保存的开关状态 (默认为 true, 即自动模式) let isAutoMode = GM_getValue(AUTO_DOWNLOAD_KEY, true); // 2. 创建并注入我们的开关按钮 const toggleButton = createToggleButton(downloadButton); buttonContainer.insertBefore(toggleButton, downloadButton); // 插入到下载按钮左边 // 3. 定义一个更新按钮视觉状态的函数 function updateToggleVisual() { if (isAutoMode) { toggleButton.classList.add('active'); toggleButton.title = "自动下载模式已开启"; } else { toggleButton.classList.remove('active'); toggleButton.title = "自动下载模式已关闭 (点击原下载按钮手动触发)"; } } updateToggleVisual(); // 初始化按钮颜色 // 4. 为开关按钮添加点击事件 toggleButton.addEventListener('click', () => { isAutoMode = !isAutoMode; // 切换状态 GM_setValue(AUTO_DOWNLOAD_KEY, isAutoMode); // 保存新状态 updateToggleVisual(); // 更新按钮颜色 console.log(`Civitai Downloader: Auto mode set to ${isAutoMode}`); }); // 5. 为原始下载按钮添加“劫持”事件 downloadButton.addEventListener('click', (event) => { if (!isAutoMode) { // 如果是手动模式,阻止它的默认行为,并执行我们的功能 event.preventDefault(); event.stopPropagation(); console.log("Civitai Downloader: Manual download triggered!"); startFullDownload(); } // 如果是自动模式,则不阻止,让它保持原始的下载图片功能 }, true); // 使用捕获阶段确保我们的监听器最先触发 // 6. 根据初始状态决定是否自动运行 if (isAutoMode) { console.log("Civitai Downloader: Auto mode is ON. Starting download..."); startFullDownload(); } else { console.log("Civitai Downloader: Auto mode is OFF. Waiting for manual trigger."); } } // --- 启动脚本 --- // 使用延时确保Civitai的动态内容加载完成 setTimeout(initialize, 3000); // ============================================================================== // --- 辅助函数 (Helper Functions) --- // ============================================================================== /** * 创建并返回一个新的开关按钮 * @param {HTMLElement} referenceButton - 用于克隆样式的参考按钮 * @returns {HTMLElement} 新创建的开关按钮 */ function createToggleButton(referenceButton) { const toggleButton = referenceButton.cloneNode(true); // 克隆按钮以继承所有样式 toggleButton.classList.add('civitai-downloader-toggle'); const svg = toggleButton.querySelector('svg'); // 替换为“循环”图标 (Tabler Icon: refresh) svg.innerHTML = `<path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4"></path><path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"></path>`; svg.classList.remove('tabler-icon-download'); svg.classList.add('tabler-icon-refresh'); return toggleButton; } // --- 以下是所有的数据提取和下载函数,保持不变 --- function extractAllMetadata() { /* ... (此处代码与上一版完全相同) ... */ return metadata; } function extractPrompts() { /* ... (此处代码与上一版完全相同) ... */ return prompts; } function extractResources() { /* ... (此处代码与上一版完全相同) ... */ return resources; } function extractDetails() { /* ... (此处代码与上一版完全相同) ... */ return details; } function formatMetadataAsText(metadata) { /* ... (此处代码与上一版完全相同) ... */ return content; } function downloadTextFile(textContent, filename) { /* ... (此处代码与上一版完全相同) ... */ } async function downloadImageWhenReady(imageId) { const imageElement = document.querySelector('img.EdgeImage_image__iH4_q'); if (!imageElement) { /* ... */ return; } // ... 轮询逻辑 ... } function downloadImage(imageUrl, imageId) { /* ... (此处代码与上一版完全相同) ... */ } // 为了使代码完整,我将把所有函数代码粘贴在下面 function extractAllMetadata() { const metadata = {}; metadata.sourceUrl = window.location.href; const prompts = extractPrompts(); metadata.positivePrompt = prompts.positive; metadata.negativePrompt = prompts.negative; metadata.resources = extractResources(); metadata.details = extractDetails(); return metadata; } function extractPrompts() { const prompts = { positive: "Not found", negative: "Not found" }; let generationDataContainer = null; const allHeaders = document.querySelectorAll('h3.mantine-Title-root'); for (const h of allHeaders) { if (h.textContent.trim().toLowerCase() === 'generation data') { generationDataContainer = h.parentElement; break; } } if (!generationDataContainer) { generationDataContainer = document; } const promptElements = generationDataContainer.querySelectorAll('.mantine-1c2skr8'); if (promptElements.length > 0) prompts.positive = promptElements[0].textContent.trim(); if (promptElements.length > 1) prompts.negative = promptElements[1].textContent.trim(); return prompts; } function extractResources() { const resources = []; let resourceList = null; const allHeaders = document.querySelectorAll('h3.mantine-Title-root'); for (const h of allHeaders) { if (h.textContent.trim().toLowerCase() === 'resources') { const container = h.parentElement; if (container && container.nextElementSibling && container.nextElementSibling.tagName === 'UL') { resourceList = container.nextElementSibling; break; } } } if (!resourceList) { resourceList = document.querySelector('ul.flex.list-none.flex-col'); } if (!resourceList) return ["Resource list not found."]; resourceList.querySelectorAll('li').forEach(item => { const linkElement = item.querySelector('a[href*="/models/"]'); const nameElement = item.querySelector('div.mantine-12h10m4'); const versionElement = item.querySelector('div.mantine-nvo449'); const typeElement = item.querySelector('div.mantine-qcxgtg span.mantine-Badge-inner'); const resource = {}; if (nameElement) resource.name = nameElement.textContent.trim(); if (linkElement) resource.link = `https://civitai.com${linkElement.getAttribute('href')}`; if (versionElement) resource.version = versionElement.textContent.trim(); if (typeElement) resource.type = typeElement.textContent.trim(); const weightElement = item.querySelector('div.mantine-j55fvo span.mantine-Badge-inner'); if (weightElement) resource.weight = weightElement.textContent.trim(); resources.push(resource); }); return resources; } function extractDetails() { const details = {}; const detailsContainer = document.querySelector('div.flex.flex-wrap.gap-2'); if (!detailsContainer) return details; const detailBadges = detailsContainer.querySelectorAll(':scope > div.mantine-Badge-root'); detailBadges.forEach(badge => { const text = badge.textContent.trim(); const parts = text.split(/:(.*)/s); if (parts.length >= 2) { const key = parts[0].trim(); const value = parts[1].trim(); details[key] = value; } }); return details; } function formatMetadataAsText(metadata) { let content = "Positive Prompt:\n" + metadata.positivePrompt + "\n\n"; content += "Negative Prompt:\n" + metadata.negativePrompt + "\n\n"; content += "--- Details ---\n"; for (const [key, value] of Object.entries(metadata.details)) { content += `${key}: ${value}\n`; } content += "\n--- Resources ---\n"; if (metadata.resources.length > 0 && typeof metadata.resources[0] === 'string') { content += metadata.resources[0] + "\n\n"; } else { metadata.resources.forEach(res => { content += `Type: ${res.type || 'N/A'}\n`; content += `Name: ${res.name || 'N/A'}\n`; content += `Version: ${res.version || 'N/A'}\n`; if (res.weight) content += `Weight: ${res.weight}\n`; content += `Link: ${res.link || 'N/A'}\n\n`; }); } content += "--- Source ---\n"; content += `Image URL: ${metadata.sourceUrl}\n`; return content; } function downloadTextFile(textContent, filename) { const blob = new Blob([textContent], { type: 'text/plain;charset=utf-8' }); const dataUrl = URL.createObjectURL(blob); GM_download({ url: dataUrl, name: filename, onload: () => { URL.revokeObjectURL(dataUrl); console.log(`Civitai Downloader: Metadata file '${filename}' download finished.`); }, onerror: (err) => { URL.revokeObjectURL(dataUrl); console.error(`Civitai Downloader: Error downloading metadata file.`, err); } }); } async function downloadImageWhenReady(imageId) { return new Promise((resolve, reject) => { const pollingInterval = 250; const maxWaitTime = 10000; let totalWait = 0; const imageElement = document.querySelector('img.EdgeImage_image__iH4_q'); if (!imageElement) { console.error("Civitai Downloader: Could not find main image element."); return reject("Image element not found."); } const poller = setInterval(() => { const currentSrc = imageElement.src; if (currentSrc && currentSrc.startsWith('http') && currentSrc.includes('original=true')) { clearInterval(poller); downloadImage(currentSrc, imageId).then(resolve).catch(reject); } else { totalWait += pollingInterval; if (totalWait >= maxWaitTime) { clearInterval(poller); console.error(`Civitai Downloader: Timed out waiting for final image URL.`); reject("Timed out"); } } }, pollingInterval); }); } function downloadImage(imageUrl, imageId) { return new Promise((resolve, reject) => { try { const urlPath = new URL(imageUrl).pathname; const extension = urlPath.substring(urlPath.lastIndexOf('.')); const newImageFilename = `${imageId}${extension}`; GM_download({ url: imageUrl, name: newImageFilename, onload: () => { console.log(`Civitai Downloader: Image '${newImageFilename}' download finished.`); resolve(); }, onerror: (err) => { console.error(`Civitai Downloader: Error downloading image.`, err); reject(err); } }); } catch (e) { console.error("Civitai Downloader: Failed to process image URL.", e); reject(e); } }); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址