└── crx-downloader.user.js /crx-downloader.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name CRX Downloader 3 | // @description Allows you to download ".crx" files directly from Chrome Web Store and Microsoft Edge Addons websites. 4 | // @namespace http://tampermonkey.net/ 5 | // @icon https://www.chromium.org/favicon.ico 6 | // @version 1.0.6 7 | // @author AngelBruni 8 | // @match https://chromewebstore.google.com/* 9 | // @match https://microsoftedge.microsoft.com/* 10 | // @match https://web.archive.org/web/2011*/https://chrome.google.com/webstore* 11 | // ==/UserScript== 12 | 13 | (function() { 14 | const VERSION = "130.0"; 15 | 16 | function waitForElm(selector) { 17 | return new Promise(resolve => { 18 | if (document.querySelector(selector)) 19 | return resolve(document.querySelector(selector)); 20 | 21 | const observer = new MutationObserver(mutations => { 22 | if (document.querySelector(selector)) { 23 | observer.disconnect(); 24 | resolve(document.querySelector(selector)); 25 | } 26 | }); 27 | 28 | // If you get "parameter 1 is not of type 'Node'" error, see https://stackoverflow.com/a/77855838/492336 29 | observer.observe(document.body, { 30 | childList: true, 31 | subtree: true 32 | }); 33 | }); 34 | } 35 | 36 | function removePromo() { 37 | const url = window.location.href; 38 | if (url.includes("chrome.google.com/webstore")) { 39 | const promoBannerSelector = '#cx-browser-warning'; 40 | waitForElm(promoBannerSelector).then(() => { 41 | const promoBannerElm = document.querySelector(promoBannerSelector); 42 | promoBannerElm.remove(); 43 | }); 44 | } else if (url.includes("chromewebstore.google.com")) { 45 | const promoCardSelector = '[aria-labelledby="promo-header"]'; 46 | waitForElm(promoCardSelector).then(() => { 47 | const promoCardElm = document.querySelector(promoCardSelector); 48 | promoCardElm.remove(); 49 | }); 50 | 51 | const promoBannerSelector = '#c6'; 52 | waitForElm(promoBannerSelector).then(() => { 53 | const promoBannerElm = document.querySelector(promoBannerSelector); 54 | promoBannerElm.remove(); 55 | }); 56 | } else if (url.includes("microsoftedge.microsoft.com")) { 57 | const promoBannerSelector = '.BannerRedesigned[role="banner"]'; 58 | waitForElm(promoBannerSelector).then(() => { 59 | const promoBannerElm = document.querySelector(promoBannerSelector); 60 | promoBannerElm.remove(); 61 | }); 62 | } 63 | } 64 | 65 | function extractExtensionId() { 66 | const urlParts = window.location.href.split("/"); 67 | let extensionId = urlParts.pop(); 68 | return extensionId.includes("?") ? extensionId.split("?")[0] : extensionId; 69 | } 70 | 71 | function generateCdnUrl(extensionId, isChrome = true) { 72 | const baseUrl = isChrome 73 | ? `https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&prodversion=${VERSION}` 74 | : "https://edge.microsoft.com/extensionwebstorebase/v1/crx?response=redirect"; 75 | return `${baseUrl}&x=id%3D${extensionId}%26installsource%3Dondemand%26uc`; 76 | } 77 | 78 | function enableChromeGoogleStoreDownload(extensionId) { 79 | document.querySelector("#cwspage.cx-cannot-install").classList.remove("cx-cannot-install"); 80 | 81 | const addBtn = document.querySelector("#cx-install-free-btn[disabled]"); 82 | if (!addBtn) return; 83 | addBtn.removeAttribute("disabled"); 84 | addBtn.addEventListener("click", () => { window.location.href = generateCdnUrl(extensionId); }); 85 | } 86 | 87 | function enableChromeWebStoreDownload(extensionId) { 88 | const addBtn = document.querySelector(`[data-p*="${extensionId}"] button[jsaction*="click"][disabled]:not([aria-label])`); 89 | if (!addBtn) return; 90 | addBtn.removeAttribute("disabled"); 91 | addBtn.addEventListener("click", () => { window.location.href = generateCdnUrl(extensionId); }); 92 | } 93 | 94 | function enableEdgeDownload(extensionId) { 95 | const getBtn = document.querySelector(`#getOrRemoveButton-${extensionId}[disabled]`); 96 | if (!getBtn) return; 97 | getBtn.removeAttribute("disabled"); 98 | getBtn.style.setProperty ("cursor", "pointer", "important"); 99 | getBtn.style.opacity = 1; 100 | getBtn.addEventListener("click", () => { window.location.href = generateCdnUrl(extensionId, false); }); 101 | } 102 | 103 | function enableAllEdgeButtons() { 104 | document.querySelectorAll(` 105 | button[id*="getOrRemoveButton"][disabled], 106 | button[id*="installButton"][disabled], 107 | button[name="GetButton"] 108 | `).forEach(getBtn => { 109 | const extensionId = getBtn.id.split("-").pop(); 110 | getBtn.removeAttribute("disabled"); 111 | getBtn.style.setProperty ("cursor", "pointer", "important"); 112 | getBtn.style.opacity = 1; 113 | getBtn.addEventListener("click", () => { window.location.href = generateCdnUrl(extensionId, false); }); 114 | }); 115 | } 116 | 117 | function initDownloader() { 118 | const url = window.location.href; 119 | const extensionId = extractExtensionId(); 120 | 121 | if (url.includes("chrome.google.com/webstore/detail/")) 122 | enableChromeGoogleStoreDownload(extensionId); 123 | else if (url.includes("chromewebstore.google.com/detail/")) 124 | enableChromeWebStoreDownload(extensionId); 125 | else if (url.includes("microsoftedge.microsoft.com/addons/detail/")) 126 | enableEdgeDownload(extensionId); 127 | else if (url.includes("microsoftedge.microsoft.com/addons/")) 128 | setInterval(enableAllEdgeButtons, 500); // Yes, this runs every half a second, I am too lazy and the buttons get created on hover which is annoying to keep track of. 129 | } 130 | 131 | removePromo(); 132 | initDownloader(); 133 | const titleObserver = new MutationObserver(() => { initDownloader(); }); 134 | titleObserver.observe(document.querySelector('title'), { childList: true }); 135 | })(); --------------------------------------------------------------------------------