Free Blendermarket Downloader

Add Free Download buttons to Blendermarket products.

目前為 2024-11-18 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Free Blendermarket Downloader
// @description  Add Free Download buttons to Blendermarket products.
// @icon         https://assets.superhivemarket.com/site_assets/images/black_bee.png
// @version      1.2
// @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 lastUrl = location.href;
    let observer = null;
    let buttonCheckInterval = null;
    let retryCount = 0;
    const MAX_RETRIES = 10;
    const RETRY_DELAY = 500;

    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 createButtonContent(iconUrl, text) {
        const container = document.createElement('div');
        container.style.display = 'flex';
        container.style.alignItems = 'center';
        container.style.justifyContent = 'center';
        container.style.gap = '8px';

        const img = document.createElement('img');
        img.src = iconUrl;
        img.style.height = '20px';
        img.style.width = '20px';
        img.alt = `${text} Icon`;

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

        container.appendChild(img);
        container.appendChild(span);
        
        return container;
    }

    function createDownloadButton(className, iconUrl, buttonText, urlCreator) {
        const button = document.createElement('button');
        button.className = `btn btn-primary d-grid btn-lg shadow w-100 mt-2 ${className}`;
        button.appendChild(createButtonContent(iconUrl, buttonText));

        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 false;
        }

        clearExistingButtons();

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

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

        const cgDownloadButton = createDownloadButton(
            'cgdownload-button',
            'https://cgdownload.ru/wp-content/themes/dark/images/mstile-150x150.png',
            'Free Download',
            createCGDownloadURL
        );

        const gfxCampButton = createDownloadButton(
            'gfxcamp-button',
            'https://www.gfxcamp.com/images/60.jpg',
            'Free Download',
            createGFXCampURL
        );

        originalForm.insertAdjacentElement('afterend', gfxCampButton);
        originalForm.insertAdjacentElement('afterend', cgDownloadButton);
        return true;
    }

    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();
    }

    setInterval(() => {
        if (location.href !== lastUrl) {
            lastUrl = location.href;
            setTimeout(startObserver, 100);
        }
    }, 100);

})();