Added CGDownload and GFXCamp buttons for downloading paid items from Blendermarket.
// ==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.4 // @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 addStyles() { const styles = ` .download-btn { background: linear-gradient(120deg, #6800f0, #ff6b00); color: white; border: none; padding: 4px; border-radius: 4px; cursor: pointer; position: absolute; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; } .download-btn:hover { background: linear-gradient(120deg, #5600c7, #e65d00); } .download-btn.cgdownload-btn { right: 45px; bottom: 15px; } .download-btn.gfxcamp-btn { right: 12px; bottom: 15px; } .card-body { position: relative; } .download-btn img { width: 16px; height: 16px; object-fit: contain; } `; const styleSheet = document.createElement('style'); styleSheet.textContent = styles; document.head.appendChild(styleSheet); } function getProductNameFromURL() { const currentURL = window.location.href; const match = currentURL.match(/products\/([^/?]+)/); return match ? match[1] : ''; } function getProductNameFromLink(cardBody) { const cardProduct = cardBody.closest('.card.card-product'); if (cardProduct) { const link = cardProduct.querySelector('a[href*="/products/"]'); if (link) { const match = link.href.match(/products\/([^/?]+)/); return match ? match[1] : ''; } } const link = cardBody.querySelector('a[href*="/products/"]'); if (link) { const match = link.href.match(/products\/([^/?]+)/); return match ? 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('.download-btn, .cgdownload-button, .gfxcamp-button'); existingButtons.forEach(button => button.remove()); } function createCardButton(type, text, urlCreator, iconUrl, className) { const button = document.createElement('button'); button.className = `download-btn ${className}`; const icon = document.createElement('img'); icon.src = iconUrl; icon.alt = text; button.appendChild(icon); return button; } function addCardButtons() { const cardBodies = document.querySelectorAll('.card.card-product .card-body'); cardBodies.forEach(cardBody => { if (!cardBody.closest('.card.card-product')) return; const existingButtons = cardBody.querySelectorAll('.download-btn'); existingButtons.forEach(button => button.remove()); const cgDownloadButton = createCardButton( 'cgdownload', 'CGDownload', createCGDownloadURL, ICONS.cgdownload, 'cgdownload-btn' ); const gfxCampButton = createCardButton( 'gfxcamp', 'GFXCamp', createGFXCampURL, ICONS.gfxcamp, 'gfxcamp-btn' ); cgDownloadButton.addEventListener('click', (e) => { e.preventDefault(); const productName = getProductNameFromLink(cardBody); if (productName) { window.open(createCGDownloadURL(productName), '_blank'); } }); gfxCampButton.addEventListener('click', (e) => { e.preventDefault(); const productName = getProductNameFromLink(cardBody); if (productName) { window.open(createGFXCampURL(productName), '_blank'); } }); cardBody.appendChild(cgDownloadButton); cardBody.appendChild(gfxCampButton); }); } 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 addProductPageButtons() { 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 priceElement = document.querySelector('.js-price-cart'); if (priceElement) { priceElement.classList.remove('d-none', 'd-md-block'); priceElement.classList.add('text-center'); priceElement.style.marginBottom = '1rem'; priceElement.style.display = 'block'; priceElement.style.width = '100%'; originalForm.parentNode.insertBefore(priceElement, originalForm); } 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 addAllButtons() { addStyles(); addProductPageButtons(); addCardButtons(); } function startButtonCheck() { if (buttonCheckInterval) { clearInterval(buttonCheckInterval); } retryCount = 0; buttonCheckInterval = setInterval(() => { if (addAllButtons() || 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, .card-body') || node.classList.contains('button_to', 'card-body') || node.closest('.button_to, .card-body'); } 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(); } })();