🏠 返回首頁 

Greasy Fork is available in English.

Blendermarket Downloader

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


安装此脚本?
  1. // ==UserScript==
  2. // @name Blendermarket Downloader
  3. // @description Added CGDownload and GFXCamp buttons for downloading paid items from Blendermarket.
  4. // @icon https://assets.superhivemarket.com/site_assets/images/black_bee.png
  5. // @version 1.4
  6. // @author afkarxyz
  7. // @namespace https://github.com/afkarxyz/misc-scripts/
  8. // @supportURL https://github.com/afkarxyz/misc-scripts/issues
  9. // @license MIT
  10. // @match https://blendermarket.com/*
  11. // @grant none
  12. // @run-at document-idle
  13. // ==/UserScript==
  14. (function() {
  15. 'use strict';
  16. let observer;
  17. let buttonCheckInterval;
  18. let retryCount = 0;
  19. const MAX_RETRIES = 10;
  20. const RETRY_DELAY = 500;
  21. const ICONS = {
  22. cgdownload: 'https://raw.githubusercontent.com/afkarxyz/misc-scripts/refs/heads/main/blendermarket/cgdownload.png',
  23. gfxcamp: 'https://raw.githubusercontent.com/afkarxyz/misc-scripts/refs/heads/main/blendermarket/gfxcamp.png'
  24. };
  25. function addStyles() {
  26. const styles = `
  27. .download-btn {
  28. background: linear-gradient(120deg, #6800f0, #ff6b00);
  29. color: white;
  30. border: none;
  31. padding: 4px;
  32. border-radius: 4px;
  33. cursor: pointer;
  34. position: absolute;
  35. transition: all 0.3s ease;
  36. display: flex;
  37. align-items: center;
  38. justify-content: center;
  39. width: 28px;
  40. height: 28px;
  41. }
  42. .download-btn:hover {
  43. background: linear-gradient(120deg, #5600c7, #e65d00);
  44. }
  45. .download-btn.cgdownload-btn {
  46. right: 45px;
  47. bottom: 15px;
  48. }
  49. .download-btn.gfxcamp-btn {
  50. right: 12px;
  51. bottom: 15px;
  52. }
  53. .card-body {
  54. position: relative;
  55. }
  56. .download-btn img {
  57. width: 16px;
  58. height: 16px;
  59. object-fit: contain;
  60. }
  61. `;
  62. const styleSheet = document.createElement('style');
  63. styleSheet.textContent = styles;
  64. document.head.appendChild(styleSheet);
  65. }
  66. function getProductNameFromURL() {
  67. const currentURL = window.location.href;
  68. const match = currentURL.match(/products\/([^/?]+)/);
  69. return match ? match[1] : '';
  70. }
  71. function getProductNameFromLink(cardBody) {
  72. const cardProduct = cardBody.closest('.card.card-product');
  73. if (cardProduct) {
  74. const link = cardProduct.querySelector('a[href*="/products/"]');
  75. if (link) {
  76. const match = link.href.match(/products\/([^/?]+)/);
  77. return match ? match[1] : '';
  78. }
  79. }
  80. const link = cardBody.querySelector('a[href*="/products/"]');
  81. if (link) {
  82. const match = link.href.match(/products\/([^/?]+)/);
  83. return match ? match[1] : '';
  84. }
  85. return '';
  86. }
  87. function createCGDownloadURL(productName) {
  88. return `https://cgdownload.ru/?s=${encodeURIComponent(productName.replace(/-/g, ' '))}`;
  89. }
  90. function createGFXCampURL(productName) {
  91. return `https://www.gfxcamp.com/${productName}/`;
  92. }
  93. function clearExistingButtons() {
  94. const existingButtons = document.querySelectorAll('.download-btn, .cgdownload-button, .gfxcamp-button');
  95. existingButtons.forEach(button => button.remove());
  96. }
  97. function createCardButton(type, text, urlCreator, iconUrl, className) {
  98. const button = document.createElement('button');
  99. button.className = `download-btn ${className}`;
  100. const icon = document.createElement('img');
  101. icon.src = iconUrl;
  102. icon.alt = text;
  103. button.appendChild(icon);
  104. return button;
  105. }
  106. function addCardButtons() {
  107. const cardBodies = document.querySelectorAll('.card.card-product .card-body');
  108. cardBodies.forEach(cardBody => {
  109. if (!cardBody.closest('.card.card-product')) return;
  110. const existingButtons = cardBody.querySelectorAll('.download-btn');
  111. existingButtons.forEach(button => button.remove());
  112. const cgDownloadButton = createCardButton(
  113. 'cgdownload',
  114. 'CGDownload',
  115. createCGDownloadURL,
  116. ICONS.cgdownload,
  117. 'cgdownload-btn'
  118. );
  119. const gfxCampButton = createCardButton(
  120. 'gfxcamp',
  121. 'GFXCamp',
  122. createGFXCampURL,
  123. ICONS.gfxcamp,
  124. 'gfxcamp-btn'
  125. );
  126. cgDownloadButton.addEventListener('click', (e) => {
  127. e.preventDefault();
  128. const productName = getProductNameFromLink(cardBody);
  129. if (productName) {
  130. window.open(createCGDownloadURL(productName), '_blank');
  131. }
  132. });
  133. gfxCampButton.addEventListener('click', (e) => {
  134. e.preventDefault();
  135. const productName = getProductNameFromLink(cardBody);
  136. if (productName) {
  137. window.open(createGFXCampURL(productName), '_blank');
  138. }
  139. });
  140. cardBody.appendChild(cgDownloadButton);
  141. cardBody.appendChild(gfxCampButton);
  142. });
  143. }
  144. function createButton(className, text, urlCreator, iconUrl) {
  145. const originalButton = document.querySelector('.button_to input[type="submit"]');
  146. if (!originalButton) return null;
  147. const button = document.createElement('button');
  148. button.className = originalButton.className;
  149. button.classList.add(className);
  150. if (originalButton.style.cssText) {
  151. button.style.cssText = originalButton.style.cssText;
  152. }
  153. const contentWrapper = document.createElement('div');
  154. contentWrapper.style.display = 'flex';
  155. contentWrapper.style.alignItems = 'center';
  156. contentWrapper.style.justifyContent = 'center';
  157. contentWrapper.style.gap = '8px';
  158. const icon = document.createElement('img');
  159. icon.src = iconUrl;
  160. icon.alt = text;
  161. icon.style.width = '20px';
  162. icon.style.height = '20px';
  163. icon.style.objectFit = 'contain';
  164. const textSpan = document.createElement('span');
  165. textSpan.textContent = text;
  166. contentWrapper.appendChild(icon);
  167. contentWrapper.appendChild(textSpan);
  168. button.appendChild(contentWrapper);
  169. button.addEventListener('click', function(e) {
  170. e.preventDefault();
  171. const productName = getProductNameFromURL();
  172. if (productName) {
  173. const downloadURL = urlCreator(productName);
  174. window.open(downloadURL, '_blank');
  175. }
  176. });
  177. return button;
  178. }
  179. function addProductPageButtons() {
  180. if (!window.location.href.includes('/products/')) {
  181. return;
  182. }
  183. clearExistingButtons();
  184. const originalForm = document.querySelector('.button_to');
  185. if (!originalForm) {
  186. return;
  187. }
  188. if (document.querySelector('.cgdownload-button') && document.querySelector('.gfxcamp-button')) {
  189. return;
  190. }
  191. const priceElement = document.querySelector('.js-price-cart');
  192. if (priceElement) {
  193. priceElement.classList.remove('d-none', 'd-md-block');
  194. priceElement.classList.add('text-center');
  195. priceElement.style.marginBottom = '1rem';
  196. priceElement.style.display = 'block';
  197. priceElement.style.width = '100%';
  198. originalForm.parentNode.insertBefore(priceElement, originalForm);
  199. }
  200. const cgDownloadButton = createButton(
  201. 'cgdownload-button',
  202. 'CGDownload',
  203. createCGDownloadURL,
  204. ICONS.cgdownload
  205. );
  206. const gfxCampButton = createButton(
  207. 'gfxcamp-button',
  208. 'GFXCamp',
  209. createGFXCampURL,
  210. ICONS.gfxcamp
  211. );
  212. if (cgDownloadButton && gfxCampButton) {
  213. const wrapper = document.createElement('div');
  214. wrapper.style.marginTop = '0.5rem';
  215. wrapper.appendChild(cgDownloadButton);
  216. const wrapper2 = document.createElement('div');
  217. wrapper2.style.marginTop = '0.5rem';
  218. wrapper2.appendChild(gfxCampButton);
  219. originalForm.insertAdjacentElement('afterend', wrapper2);
  220. originalForm.insertAdjacentElement('afterend', wrapper);
  221. }
  222. }
  223. function addAllButtons() {
  224. addStyles();
  225. addProductPageButtons();
  226. addCardButtons();
  227. }
  228. function startButtonCheck() {
  229. if (buttonCheckInterval) {
  230. clearInterval(buttonCheckInterval);
  231. }
  232. retryCount = 0;
  233. buttonCheckInterval = setInterval(() => {
  234. if (addAllButtons() || retryCount >= MAX_RETRIES) {
  235. clearInterval(buttonCheckInterval);
  236. buttonCheckInterval = null;
  237. retryCount = 0;
  238. } else {
  239. retryCount++;
  240. }
  241. }, RETRY_DELAY);
  242. }
  243. function startObserver() {
  244. if (observer) {
  245. observer.disconnect();
  246. }
  247. startButtonCheck();
  248. observer = new MutationObserver((mutations) => {
  249. const hasRelevantChanges = mutations.some(mutation => {
  250. const addedNodes = Array.from(mutation.addedNodes);
  251. return addedNodes.some(node => {
  252. if (node.nodeType === Node.ELEMENT_NODE) {
  253. return node.querySelector('.button_to, .card-body') ||
  254. node.classList.contains('button_to', 'card-body') ||
  255. node.closest('.button_to, .card-body');
  256. }
  257. return false;
  258. });
  259. });
  260. if (hasRelevantChanges) {
  261. startButtonCheck();
  262. }
  263. });
  264. observer.observe(document.body, {
  265. childList: true,
  266. subtree: true,
  267. attributes: true,
  268. attributeFilter: ['class', 'style'],
  269. characterData: false
  270. });
  271. }
  272. function setupHistoryListener() {
  273. const pushState = history.pushState;
  274. history.pushState = function() {
  275. pushState.apply(history, arguments);
  276. setTimeout(startObserver, 100);
  277. };
  278. const replaceState = history.replaceState;
  279. history.replaceState = function() {
  280. replaceState.apply(history, arguments);
  281. setTimeout(startObserver, 100);
  282. };
  283. window.addEventListener('popstate', () => setTimeout(startObserver, 100));
  284. }
  285. if (document.readyState === 'loading') {
  286. document.addEventListener('DOMContentLoaded', () => {
  287. setupHistoryListener();
  288. startObserver();
  289. });
  290. } else {
  291. setupHistoryListener();
  292. startObserver();
  293. }
  294. })();