Sleazy Fork is available in English.

Civitai Text Downloader Mod

Click Download button will download the file and save the JSON and images at the same time. Also add a button to download JSON and images under details.

// ==UserScript==
// @name            Civitai Text Downloader Mod
// @name:ja         Civitai Text Downloader Mod
// @namespace       http://tampermonkey.net/
// @version         5.3.5
// @description     Click Download button will download the file and save the JSON and images at the same time. Also add a button to download JSON and images under details.
// @description:ja  Downloadボタンをクリックするとファイルのダウンロードと同時にJSONと画像が保存されます。また、Detailsの下にJSONと画像をダウンロードするボタンを追加します。
// @author          Takenoko3333
// @match           https://civitai.com/*
// @icon            https://civitai.com/favicon.ico
// @grant           GM.addStyle
// @grant           GM.xmlHttpRequest
// @connect         civitai.com
// @license         BSD
// ==/UserScript==

(function() {
    'use strict';
    GM.addStyle(`.ctd-button:not([data-disabled]) {color: #ffff00;}`);

    let file_id = null;
    let category = "";
    if (typeof MutationObserver !== 'undefined') {
        let lastUrl = location.href;
        let observerTriggered = false;
        const observer = new MutationObserver((mutations) => {
            if (lastUrl !== location.href) {
                lastUrl = location.href;
                observerTriggered = true;
                const interval = setInterval(function() {
                    downloadFiles();
                    if (file_id || category != "models" ) {
                        clearInterval(interval);
                    }
                }, 200);
            }
        });
        const config = { subtree: true, childList: true };
        observer.observe(document, config);
        // URL change not detected (initial access)
        setTimeout(() => {
            if (!observerTriggered) {
                if (isFirefox()) {
                    runDownloadFilesForFirefox();
                } else {
                    downloadFiles();
                }
            }
        }, 0);
    } else {
        // MutationObserver is not supported
        setInterval(function(){
            downloadFiles();
        }, 500);
    }

    function isFirefox() {
        const isFirefoxBrowser = typeof InstallTrigger !== 'undefined';
        return isFirefoxBrowser;
    }

    function runDownloadFilesForFirefox() {
        let counter = 0;
        const interval = setInterval(function() {
            downloadFiles();
            counter++;
            if (category != "models" || counter >= 3) {
                clearInterval(interval);
            }
        }, 500);
    }

    function createButton() {
        const newElement = document.createElement('a');
        newElement.className = 'mantine-UnstyledButton-root mantine-Button-root mantine-4fe1an json-image-download-button';
        newElement.type = 'button';
        newElement.setAttribute('data-button', 'true');
        newElement.style.marginTop = '8px';

        const innerDiv = document.createElement('div');
        innerDiv.className = 'mantine-3xbgk5 mantine-Button-inner';

        const innerSpan = document.createElement('span');
        innerSpan.className = 'mantine-qo1k2 mantine-Button-label';
        innerSpan.textContent = 'JSON and Image Download';

        innerDiv.appendChild(innerSpan);
        newElement.appendChild(innerDiv);

        const tableElement = document.querySelector('table');
        let currentElement = tableElement;
        while (currentElement && !currentElement.classList.contains('mantine-Accordion-item')) {
            currentElement = currentElement.parentElement;
        }
        if (currentElement) {
            currentElement.insertAdjacentElement('afterend', newElement);
        }
    }

    function downloadFiles() {
        file_id = null
        category = location.pathname.split("/")[1];
        if (category != "models") return;

        const codeElements = document.querySelectorAll('table code');
        codeElements.forEach((element, index) => {
            if (element.textContent === '@') {
                if (codeElements[index + 1]) {
                    file_id = codeElements[index + 1].textContent;
                }
            }
        });

        const jsonImageDownloadButton = document.querySelector('.json-image-download-button');
        if (file_id && !jsonImageDownloadButton) createButton();

        document.querySelectorAll('main a[type^="button"]').forEach(button => {
            if(!button.classList.contains("ctd-done")){
                button.addEventListener("click", function(){
                    const modelName = document.querySelector('main h1').textContent;
                    const spanElements = document.querySelectorAll('table span');
                    let strength = null;
                    let _id = location.pathname.split("/")[2];

                    spanElements.forEach((element, index) => {
                        if (/^Strength:/.test(element.textContent)) {
                            strength = element.textContent.split(":")[1].trim();
                        }
                    });

                    GM.xmlHttpRequest({
                        method: "GET",
                        url: "https://civitai.com/api/v1/models/" + _id,
                        onload: function(response) {
                            let j = JSON.parse(response.responseText);
                            let file = j.modelVersions.find(x => x.id == file_id);
                            let link = document.createElement('a');
                            let text = {
                                "description": "",
                                "model name": modelName,
                                "model url": document.URL,
                                "base model": file.baseModel,
                                "sd version": "Unknown",
                                "activation text": "",
                                "preferred weight": 0,
                                "notes": document.URL + "\nModel name: " + modelName + "\nBase model: " + file.baseModel
                            };

                            if (/^SD 1/.test(file.baseModel)) {
                                text["sd version"] = "SD1";
                            } else if (/^SD 2/.test(file.baseModel)) {
                                text["sd version"] = "SD2";
                            } else if (/^SDXL/.test(file.baseModel) || /^Pony/.test(file.baseModel)) {
                                text["sd version"] = "SDXL";
                            } else if (/^SD 3/.test(file.baseModel)) {
                                text["sd version"] = "SD3";
                            }

                            if (j.description && j.description.textContent) {
                                text.description = j.description.textContent;
                            }
                            if (file.trainedWords) {
                                text["activation text"] = file.trainedWords.join(" ");
                            }
                            if (strength) {
                                text["preferred weight"] = strength;
                            }
                            text = [JSON.stringify(text)];
                            link.href = window.URL.createObjectURL(new Blob(text));
                            let filename = file.files.find(x => x).name || file_id + ".json";
                            filename = filename.replace(/\.[a-z]*$/, ".json")
                            link.download = filename;
                            link.click();

                            let image = file.images.find(x =>x.type === 'image');
                            if (image) {
                                GM.xmlHttpRequest({
                                    method: "GET",
                                    url: image.url,
                                    responseType: "blob",
                                    onload: function (response) {
                                        let dlLink = document.createElement("a");
                                        const dataUrl = URL.createObjectURL(response.response);
                                        dlLink.href = dataUrl;
                                        let suffix = "." + image.url.slice(image.url.lastIndexOf('.') + 1).toLowerCase();
                                        if (suffix === ".jpeg") {
                                            suffix = ".jpg";
                                        }
                                        filename = filename.replace(".json", suffix)
                                        dlLink.download = filename;
                                        document.body.insertAdjacentElement("beforeEnd", dlLink);
                                        dlLink.click();
                                        dlLink.remove();
                                        setTimeout(function () {
                                            window.URL.revokeObjectURL(dataUrl);
                                        }, 1000);
                                    }
                                });
                            }
                        }
                    });
                });
                if (file_id && !jsonImageDownloadButton) button.classList.add("ctd-button");
                button.classList.add("ctd-done");
            }
        });
    }
})();