// ==UserScript== // @name Nexus Download Collection++ // @namespace NDC // @version 1.0.2 // @description Download every mod in a collection from one panel // @author 1Tdd // @license MIT // @match https://www.nexusmods.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=nexusmods.com // @compatible chrome // @compatible edge // @compatible firefox // @compatible safari // @compatible brave // @grant GM.setValue // @grant GM.getValue // @grant GM_addStyle // @downloadURL https://update.greasyfork.icu/scripts/563176/Nexus%20Download%20Collection%2B%2B.user.js // @updateURL https://update.greasyfork.icu/scripts/563176/Nexus%20Download%20Collection%2B%2B.meta.js // ==/UserScript== GM_addStyle(` .bottom-auto { bottom: auto; } .left-auto { left: auto; } .right-0 { right: 0px; } .top-0 { top: 0px; } .translate-y-\\[2rem\\] { --tw-translate-y: 2rem; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } .min-h-7 { min-height: 1.75rem; } .w-11 { width: 2.75rem; } .w-20{ width:5rem; } .w-32 { width: 8rem; } .w-52 { width: 13rem; } .text-green-600 { --tw-text-opacity: 1; color: rgb(22 163 74 / var(--tw-text-opacity, 1)); } .text-red-600 { --tw-text-opacity: 1; color: rgb(220 38 38 / var(--tw-text-opacity, 1)) } .text-sky-500 { --tw-text-opacity: 1; color: rgb(14 165 233 / var(--tw-text-opacity, 1)); } .backdrop-blur-sm { backdrop-filter: blur(3px); } .backdrop-brightness-50 { backdrop-filter: brightness(50%); } @media (min-width: 768px) { .sm\\:rounded-none { border-radius: 0; } .sm\\:gap-0 { gap: 0; } .sm\\:w-52 { width: 13rem; } .sm\\:justify-start { justify-content: flex-start; } } .bg-ndc-orange { background-color: #FA933C !important; color: #0f0f10 !important; fill: #0f0f10 !important; } .bg-ndc-orange:hover { background-color: #fb923c !important; } `); const convertSize = (sizeInKB) => { const sizeInMB = sizeInKB / 1024; const sizeInGB = sizeInMB / 1024; return sizeInGB >= 1 ? `${sizeInGB.toFixed(2)} GB` : `${sizeInMB.toFixed(2)} MB`; }; const CONSTANTS = { DOWNLOAD_PAUSE_BASE: 5, // Base pause (s) DOWNLOAD_SPEED_EST: 1.5, // Est. speed (MB/s) RATE_LIMIT_THRESHOLD: 200, // Anti-ban limit RATE_LIMIT_PAUSE: 300, // Cooldown (s) RETRY_MAX_ATTEMPTS: 3, // Max retries RETRY_DELAY_MS: 2000, // Base retry delay (ms) HISTORY_KEY: "history", LAUNCHED_DOWNLOAD_KEY: "launchedDownload", API_URL_GRAPHQL: "https://api-router.nexusmods.com/graphql", API_URL_DOWNLOAD_GEN: "https://www.nexusmods.com/Core/Libs/Common/Managers/Downloads?GenerateDownloadUrl", SELECTOR_MAIN_CONTENT: "#mainContent > div > div.relative > div.next-container", }; class NDC { mods = { all: [], mandatory: [], optional: [], }; constructor(gameId, collectionId, revision = null) { this.element = document.createElement("div"); this.element.classList.add('bg-surface-low', 'w-full', 'space-y-3', 'rounded-lg', 'p-4', 'mt-4'); this.gameId = gameId; this.collectionId = collectionId; this.revision = revision; this.pauseBetweenDownload = CONSTANTS.DOWNLOAD_PAUSE_BASE; this.downloadSpeed = CONSTANTS.DOWNLOAD_SPEED_EST; this.downloadMethod = NDCDownloadButton.DOWNLOAD_METHOD_VORTEX; this.downloadButton = new NDCDownloadButton(this); this.progressBar = new NDCProgressBar(this); this.console = new NDCLogConsole(this); } async init() { this.pauseBetweenDownload = await GM.getValue("pauseBetweenDownload", CONSTANTS.DOWNLOAD_PAUSE_BASE); this.downloadSpeed = await GM.getValue("downloadSpeed", CONSTANTS.DOWNLOAD_SPEED_EST); this.downloadMethod = await GM.getValue( "downloadMethod", NDCDownloadButton.DOWNLOAD_METHOD_VORTEX, ); this.element.innerHTML = ` `; const response = await this.fetchMods(); if (!response) { this.element.innerHTML = '
An error occurred while fetching the collection revisions. Please try again later.
Revision ${revision.revisionNumber}
${revision.adultContent ? `Adult` : ''}${sizeGB}
Game version ${revision.gameVersions && revision.gameVersions.length ? revision.gameVersions[0].reference : 'Unknown'}
`; li.appendChild(a); return li; }; const populateList = (listElement, btnElement, isCurrent) => { const ul = listElement.querySelector("ul"); ul.innerHTML = ""; revisions.forEach(rev => { const li = createItem(rev); li.querySelector("a").addEventListener("click", () => { this.selectRevision(rev, btnElement); listElement.classList.add("hidden"); if (isCurrent) { this.currentRevisionId = rev.revisionNumber; } else { this.newRevisionId = rev.revisionNumber; } this.renderListOfUpdates(); }); ul.appendChild(li); }); }; populateList(this.currentCollectionRevisionList, this.currentCollectionRevisionBtn, true); populateList(this.newCollectionRevisionList, this.newCollectionRevisionBtn, false); const toggleList = (list) => { const isHidden = list.classList.contains("hidden"); this.currentCollectionRevisionList.classList.add("hidden"); this.newCollectionRevisionList.classList.add("hidden"); if (isHidden) list.classList.remove("hidden"); }; this.currentCollectionRevisionBtn.addEventListener("click", (e) => { e.stopPropagation(); toggleList(this.currentCollectionRevisionList); }); this.newCollectionRevisionBtn.addEventListener("click", (e) => { e.stopPropagation(); toggleList(this.newCollectionRevisionList); }); document.addEventListener("click", (e) => { if (!this.currentCollectionRevisionList.contains(e.target) && !this.currentCollectionRevisionBtn.contains(e.target)) { this.currentCollectionRevisionList.classList.add("hidden"); } if (!this.newCollectionRevisionList.contains(e.target) && !this.newCollectionRevisionBtn.contains(e.target)) { this.newCollectionRevisionList.classList.add("hidden"); } }); this.element.querySelector(".loadingSpinner").classList.add("hidden"); this.element.querySelector(".elementBody").classList.remove("hidden"); this.element.addEventListener("click", (event) => { if (event.target === this.element) this.close(); }); } selectRevision(revision, btnElement) { const date = new Date(revision.createdAt); const dateStr = date.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }); const sizeGB = (revision.totalSize / (1024 * 1024 * 1024)).toFixed(1) + "GB"; btnElement.innerHTML = `Revision ${revision.revisionNumber}
${revision.adultContent ? `Adult` : ''}${sizeGB}
Game version ${revision.gameVersions && revision.gameVersions.length ? revision.gameVersions[0].reference : 'Unknown'}
`; } close() { document.body.style.overflow = ""; this.element.remove(); } } class NDCProgressBar { static STATUS_DOWNLOADING = 0; static STATUS_PAUSED = 1; static STATUS_FINISHED = 2; static STATUS_STOPPED = 3; static STATUS_TEXT = { [NDCProgressBar.STATUS_DOWNLOADING]: "Downloading...", [NDCProgressBar.STATUS_PAUSED]: "Paused", [NDCProgressBar.STATUS_FINISHED]: "Finished", [NDCProgressBar.STATUS_STOPPED]: "Stopped", }; constructor(ndc) { this.element = document.createElement("div"); this.element.classList.add("flex", "flex-wrap", "w-100"); this.element.style.display = "none"; this.ndc = ndc; this.modsCount = 0; this.progress = 0; this.skipPause = false; this.skipTo = false; this.skipToIndex = 0; this.status = NDCProgressBar.STATUS_DOWNLOADING; this.html = `