Blendermarket Downloader

Added CGDownload and GFXCamp buttons for downloading paid items from Blendermarket.

当前为 2024-12-25 提交的版本,查看 最新版本

// ==UserScript==
// @name         Blendermarket Downloader
// @description  Added CGDownload and GFXCamp buttons for downloading paid items from Blendermarket.
// @icon         https://assets.superhivemarket.com/site_assets/images/black_bee.png
// @version      1.3
// @author       afkarxyz
// @namespace    https://github.com/afkarxyz/misc-scripts/
// @supportURL   https://github.com/afkarxyz/misc-scripts/issues
// @license      MIT
// @match        https://blendermarket.com/*
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    let observer;
    let buttonCheckInterval;
    let retryCount = 0;
    const MAX_RETRIES = 10;
    const RETRY_DELAY = 500;

    const ICONS = {
        cgdownload: 'https://raw.githubusercontent.com/afkarxyz/misc-scripts/refs/heads/main/blendermarket/cgdownload.png',
        gfxcamp: 'https://raw.githubusercontent.com/afkarxyz/misc-scripts/refs/heads/main/blendermarket/gfxcamp.png'
    };

    function getProductNameFromURL() {
        const currentURL = window.location.href;
        const match = currentURL.match(/products\/([^/?]+)/);
        if (match) {
            return match[1];
        }
        return '';
    }

    function createCGDownloadURL(productName) {
        return `https://cgdownload.ru/?s=${encodeURIComponent(productName.replace(/-/g, ' '))}`;
    }

    function createGFXCampURL(productName) {
        return `https://www.gfxcamp.com/${productName}/`;
    }

    function clearExistingButtons() {
        const existingButtons = document.querySelectorAll('.cgdownload-button, .gfxcamp-button');
        existingButtons.forEach(button => button.remove());
    }

    function createButton(className, text, urlCreator, iconUrl) {
        const originalButton = document.querySelector('.button_to input[type="submit"]');
        if (!originalButton) return null;

        const button = document.createElement('button');
        button.className = originalButton.className;
        button.classList.add(className);
        
        if (originalButton.style.cssText) {
            button.style.cssText = originalButton.style.cssText;
        }

        const contentWrapper = document.createElement('div');
        contentWrapper.style.display = 'flex';
        contentWrapper.style.alignItems = 'center';
        contentWrapper.style.justifyContent = 'center';
        contentWrapper.style.gap = '8px';

        const icon = document.createElement('img');
        icon.src = iconUrl;
        icon.alt = text;
        icon.style.width = '20px';
        icon.style.height = '20px';
        icon.style.objectFit = 'contain';

        const textSpan = document.createElement('span');
        textSpan.textContent = text;

        contentWrapper.appendChild(icon);
        contentWrapper.appendChild(textSpan);
        button.appendChild(contentWrapper);

        button.addEventListener('click', function(e) {
            e.preventDefault();
            const productName = getProductNameFromURL();
            if (productName) {
                const downloadURL = urlCreator(productName);
                window.open(downloadURL, '_blank');
            }
        });

        return button;
    }

    function addDownloadButtons() {
        if (!window.location.href.includes('/products/')) {
            return;
        }

        clearExistingButtons();

        const originalForm = document.querySelector('.button_to');
        if (!originalForm) {
            return;
        }

        if (document.querySelector('.cgdownload-button') && document.querySelector('.gfxcamp-button')) {
            return;
        }

        const cgDownloadButton = createButton(
            'cgdownload-button',
            'CGDownload',
            createCGDownloadURL,
            ICONS.cgdownload
        );

        const gfxCampButton = createButton(
            'gfxcamp-button',
            'GFXCamp',
            createGFXCampURL,
            ICONS.gfxcamp
        );

        if (cgDownloadButton && gfxCampButton) {
            const wrapper = document.createElement('div');
            wrapper.style.marginTop = '0.5rem';
            wrapper.appendChild(cgDownloadButton);
            
            const wrapper2 = document.createElement('div');
            wrapper2.style.marginTop = '0.5rem';
            wrapper2.appendChild(gfxCampButton);

            originalForm.insertAdjacentElement('afterend', wrapper2);
            originalForm.insertAdjacentElement('afterend', wrapper);
        }
    }

    function startButtonCheck() {
        if (buttonCheckInterval) {
            clearInterval(buttonCheckInterval);
        }

        retryCount = 0;
        buttonCheckInterval = setInterval(() => {
            if (addDownloadButtons() || retryCount >= MAX_RETRIES) {
                clearInterval(buttonCheckInterval);
                buttonCheckInterval = null;
                retryCount = 0;
            } else {
                retryCount++;
            }
        }, RETRY_DELAY);
    }

    function startObserver() {
        if (observer) {
            observer.disconnect();
        }

        startButtonCheck();

        observer = new MutationObserver((mutations) => {
            const hasRelevantChanges = mutations.some(mutation => {
                const addedNodes = Array.from(mutation.addedNodes);
                return addedNodes.some(node => {
                    if (node.nodeType === Node.ELEMENT_NODE) {
                        return node.querySelector('.button_to') || 
                               node.classList.contains('button_to') ||
                               node.closest('.button_to');
                    }
                    return false;
                });
            });

            if (hasRelevantChanges) {
                startButtonCheck();
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ['class', 'style'],
            characterData: false
        });
    }

    function setupHistoryListener() {
        const pushState = history.pushState;
        history.pushState = function() {
            pushState.apply(history, arguments);
            setTimeout(startObserver, 100);
        };

        const replaceState = history.replaceState;
        history.replaceState = function() {
            replaceState.apply(history, arguments);
            setTimeout(startObserver, 100);
        };

        window.addEventListener('popstate', () => setTimeout(startObserver, 100));
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => {
            setupHistoryListener();
            startObserver();
        });
    } else {
        setupHistoryListener();
        startObserver();
    }
})();