Civitai Tool

一键下载模型、图例、模型说明(附触发词)

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Civitai Tool
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  一键下载模型、图例、模型说明(附触发词)
// @author       宇泽同学
// @match        https://civitai.com/*
// @grant        GM_download
// @license      MIT
// ==/UserScript==

(function () {
'use strict';

var style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = `
.custom-button {
    background: radial-gradient(circle, #00BFFF,#005ec9); /* 渐变色 */
    color: #b3ffe7;
    width: 100px;
    height: 35px;
    padding: 5px 15px;
    text-align: center;
    text-decoration: none;
    display: inline-block;
    font-size: 16px;
    cursor: pointer;
    border: none;
    margin-left: 5px;
    border-radius: 7px;
}
.image-count-selector {
    width: 85px; /* 边框宽度 */
    height: 35px;
    padding: 5px;
    border: 1px solid #23caff; /* 输入框描边色 */
    border-radius: 7px;
    margin-left: 5px;
    display: inline-block;
}
`;
document.head.appendChild(style);

// 获取当前页面的URL中的模型ID
function getModelIdFromUrl() {
    const urlRegex = /https:\/\/civitai.com\/models\/(\d+)(?:\/|$|\?modelVersionId=\d+)/;
    const match = window.location.href.match(urlRegex);
    return match ? match[1] : null;
}

// 从API获取模型文件名的函数
function fetchModelFileName(modelId, modelVersionId) {
    let apiUrl = `https://civitai.com/api/v1/models/${modelId}`;
    if (modelVersionId) {
        apiUrl += `?modelVersionId=${modelVersionId}`;
    }

    return fetch(apiUrl)
        .then(response => response.json())
        .then(data => {
            if (modelVersionId) {
                const modelVersion = data.modelVersions.find(version => version.id.toString() === modelVersionId);
                return modelVersion ? modelVersion.files.find(file => file.primary).name : null;
            } else {
                const publishedVersion = data.modelVersions.find(version => version.status === 'Published');
                return publishedVersion ? publishedVersion.files.find(file => file.primary).name : null;
            }
        })
        .catch(error => {
            console.error('Error fetching model file name:', error);
            return null;
        });
}

// 下载图例
function downloadImages(modelFileName, callback) {
    const numberOfImages = document.getElementById('imageCountInput').value;
    const thumbnailElements = document.querySelectorAll('.mantine-7aj0so');
    for (let i = 0; i < Math.min(numberOfImages, thumbnailElements.length); i++) {
        const thumbnailSrc = thumbnailElements[i].src;
        const highResSrc = thumbnailSrc.replace('/width=450/', '/original=true/');
        const filename = `${modelFileName.split('.')[0]}.preview.png`;
        GM_download(highResSrc, filename);
    }
    // 图例下载完成后执行回调函数
    if (typeof callback === 'function') {
        callback();
    }
}


// 下载说明
function downloadDescription(modelFileName, modelId) {
    // 使用Model ID构造API URL
    const apiUrl = `https://civitai.com/api/v1/models/${modelId}`;
    fetch(apiUrl)
        .then(response => response.json())
        .then(data => {
            if (data.description) {
                let descriptionHtml = data.description; // API返回的HTML格式文本

                // 将<p>和<br>标签替换为换行符,保留段落间的空行
                descriptionHtml = descriptionHtml.replace(/<p>/gi, "");
                descriptionHtml = descriptionHtml.replace(/<\/p>/gi, "\n\n");
                descriptionHtml = descriptionHtml.replace(/<br\s*[\/]?>/gi, "\n");

                // 创建一个DOM元素来解析HTML文本,并提取文本内容
                const tempDiv = document.createElement('div');
                tempDiv.innerHTML = descriptionHtml;
                // 获取纯文本内容,去除所有HTML标签
                let plainTextDescription = tempDiv.textContent || tempDiv.innerText || "";

                // 确保纯文本中的连续换行被保留
                plainTextDescription = plainTextDescription.replace(/\n\s*\n/g, "\n\n");

                // 获取触发词
                const triggerWordElements = document.querySelectorAll('.mantine-Group-root.mantine-i72d0e');
                let triggerWords = [];
                triggerWordElements.forEach(element => {
                    const word = element.textContent.trim();
                    if (word) {
                        triggerWords.push(word);
                    }
                });
                const triggerWordsText = triggerWords.length > 0 ? triggerWords.join(', ') : '未发现';

                // 组合下载内容
                const modelName = data.name || '模型名称未知';
                const currentUrl = window.location.href;
                const combinedText = `##触发词: ${triggerWordsText}\n\n${plainTextDescription.trim()}\n\n##本模型地址:${currentUrl}\n\n`;

                // 触发下载
                const blob = new Blob([combinedText], { type: 'text/plain;charset=utf-8' });
                const link = document.createElement('a');
                link.href = window.URL.createObjectURL(blob);
                link.download = `${modelFileName.split('.')[0]}.txt`;
                document.body.appendChild(link);
                link.click();
                document.body.removeChild(link);
            } else {
                console.error('获取不到模型说明文本。');
            }
        }).catch(error => {
            console.error('Error fetching model description:', error);
        });
}


// 模拟原生下载按钮点击
function simulateNativeButtonClick() {
    const nativeDownloadButton = document.querySelector('.mantine-UnstyledButton-root.mantine-Button-root.mantine-4fe1an');
    nativeDownloadButton && nativeDownloadButton.click();
}

// 下载全部内容
function downloadAll(modelFileName, modelId) {
    downloadImages(modelFileName, () => {
        downloadDescription(modelFileName, modelId);
        setTimeout(() => {
            simulateNativeButtonClick();
        }, 1000);
    });
}

// 创建下载按钮
function createButton(text, onClick) {
    const button = document.createElement('button');
    button.textContent = text;
    button.className = 'custom-button';
    button.addEventListener('click', onClick);
    return button;
}

// 创建数字输入框
function createNumberInput() {
    const input = document.createElement('input');
    input.className = 'image-count-selector';
    input.id = 'imageCountInput';
    input.type = 'number';
    input.value = '1';
    input.min = '1';
    return input;
}

// 主函数
function main() {
    const modelId = getModelIdFromUrl();
    const urlParams = new URLSearchParams(window.location.search);
    const modelVersionId = urlParams.get('modelVersionId');
    if (modelId) {
        fetchModelFileName(modelId, modelVersionId).then(modelFileName => {
            if (modelFileName) {
                const imageCountInput = createNumberInput();
                const downloadImagesButton = createButton('下载图例', () => downloadImages(modelFileName));
 const downloadDescriptionButton = createButton('下载说明', () => downloadDescription(modelFileName, modelId)); // 更新这一行

                const downloadAllButton = createButton('我全都要', () => downloadAll(modelFileName, modelId));

                const buttonContainer = document.createElement('div');
                buttonContainer.className = 'custom-button-container';
                buttonContainer.appendChild(downloadImagesButton);
                buttonContainer.appendChild(imageCountInput);
                buttonContainer.appendChild(downloadDescriptionButton);
                buttonContainer.appendChild(downloadAllButton);

                const targetElement = document.querySelector('.mantine-UnstyledButton-root.mantine-Accordion-control.mantine-17tws88');
                if (targetElement && !targetElement.parentNode.querySelector('.custom-button-container')) {
                    targetElement.parentNode.insertBefore(buttonContainer, targetElement);
                } else {
                    console.error('未找到目标元素,无法插入按钮容器。');
                }
            }
        });
    }
}

function checkAndInsertButtons() {
    setTimeout(() => {
        main();
        checkAndInsertButtons();
    }, 1000);
}

window.addEventListener('load', checkAndInsertButtons);

 // 监听网址变化
    function checkUrlChange() {
        const currentUrl = window.location.href;
        if (currentUrl !== sessionStorage.getItem('previousUrl')) {
            sessionStorage.setItem('previousUrl', currentUrl);
            window.location.reload();
        }
    }

    window.addEventListener('load', main);
    setInterval(checkUrlChange, 50);

})();