您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Download every mods of a collection in a single click
当前为
// ==UserScript== // @name Nexus Download Collection // @namespace NDC // @version 0.2 // @description Download every mods of a collection in a single click // @author Drigtime // @match https://next.nexusmods.com/*/collections/* // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // @grant GM.xmlHttpRequest // @grant GM_xmlhttpRequest // ==/UserScript== (function () { 'use strict'; /** CORSViaGM BEGINING */ const CORSViaGM = document.body.appendChild(Object.assign(document.createElement('div'), { id: 'CORSViaGM' })) addEventListener('fetchViaGM', e => GM_fetch(e.detail.forwardingFetch)) CORSViaGM.init = function (window) { if (!window) throw 'The `window` parameter must be passed in!' window.fetchViaGM = fetchViaGM.bind(window) // Support for service worker window.forwardingFetch = new BroadcastChannel('forwardingFetch') window.forwardingFetch.onmessage = async e => { const req = e.data const { url } = req const res = await fetchViaGM(url, req) const response = await res.blob() window.forwardingFetch.postMessage({ type: 'fetchResponse', url, response }) } window._CORSViaGM && window._CORSViaGM.inited.done() const info = '🙉 CORS-via-GM initiated!' console.info(info) return info } function GM_fetch(p) { GM_xmlhttpRequest({ ...p.init, url: p.url, method: p.init.method || 'GET', onload: responseDetails => p.res(new Response(responseDetails.response, responseDetails)) }) } function fetchViaGM(url, init) { let _r const p = new Promise(r => _r = r) p.res = _r p.url = url p.init = init || {} dispatchEvent(new CustomEvent('fetchViaGM', { detail: { forwardingFetch: p } })) return p } CORSViaGM.init(window); /** CORSViaGM END */ const getModCollection = async (gameId, collectionId) => { const response = await fetch("https://next.nexusmods.com/api/graphql", { "headers": { "accept": "*/*", "accept-language": "fr;q=0.5", "api-version": "2023-09-05", "content-type": "application/json", "sec-ch-ua": "\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\", \"Brave\";v=\"120\"", "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": "\"Windows\"", "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-origin", "sec-gpc": "1" }, "referrer": `https://next.nexusmods.com/${gameId}/collections/${collectionId}?tab=mods`, "referrerPolicy": "strict-origin-when-cross-origin", "body": JSON.stringify({ "query": "query CollectionRevisionMods ($revision: Int, $slug: String!, $viewAdultContent: Boolean) { collectionRevision (revision: $revision, slug: $slug, viewAdultContent: $viewAdultContent) { externalResources { id, name, resourceType, resourceUrl }, modFiles { fileId, optional, file { fileId, name, scanned, size, sizeInBytes, version, mod { adult, author, category, modId, name, pictureUrl, summary, version, game { domainName }, uploader { avatar, memberId, name } } } } } }", "variables": { "slug": collectionId, "viewAdultContent": true }, "operationName": "CollectionRevisionMods" }), "method": "POST", "mode": "cors", "credentials": "include" }); const data = await response.json(); return data.data.collectionRevision; } // create a button to open all urls in new tabs const button = document.createElement("button"); button.classList.add("font-montserrat", "font-semibold", "text-sm", "leading-none", "tracking-wider", "uppercase", "leading-none", "flex", "gap-x-2", "justify-center", "items-center", "transition-colors", "relative", "min-h-9", "focus:outline", "focus:outline-2", "focus:outline-accent", "focus:outline-offset-2", "rounded", "px-4", "py-1", "cursor-pointer", "bg-primary", "fill-font-primary", "text-font-primary", "border-transparent", "focus:bg-primary-lighter", "hover:bg-primary-darker", "add-to-vortex-button"); button.innerText = "Download all mods"; const progress = document.createElement("div"); progress.classList.add("mt-2", "w-full", "bg-gray-200", "rounded-full"); progress.style.display = "none"; progress.style.height = "1.1rem"; progress.style.backgroundColor = "rgb(229 231 235 / 1)"; const progressBar = document.createElement("div"); progressBar.classList.add("bg-blue-600", "rounded-full"); progressBar.style.height = "1.1rem"; progressBar.style.fontSize = "0.8rem"; progressBar.style.padding = "0 5px"; progressBar.style.width = "0%"; progressBar.style.backgroundColor = "rgb(28 100 242 / 1)"; progress.appendChild(progressBar); button.onclick = async () => { // from current document get gameId and collectionId eg. https://next.nexusmods.com/newvegas/collections/jscbqj const gameId = document.location.pathname.split("/")[1]; const collectionId = document.location.pathname.split("/")[3]; // open all modFiles in new tabs const modCollection = await getModCollection(gameId, collectionId); const { modFiles, externalResources } = modCollection; const modUrls = modFiles.map(modFile => { // https://www.nexusmods.com/newvegas/mods/45126?tab=files&file_id=103242&nmm=1 const modId = modFile.file.mod.modId; const gameId = modFile.file.mod.game.domainName; const fileId = modFile.file.fileId; return `https://www.nexusmods.com/${gameId}/mods/${modId}?tab=files&file_id=${fileId}&nmm=1`; }); // const externalUrls = externalResources.map(externalResource => { // return externalResource.resourceUrl; // }); const urls = [ ...modUrls, // ...externalUrls ]; progress.style.display = "block"; let downloadProgress = 0; let downloadProgressPercent = 0; const downloadMod = async (url) => { const response = await fetchViaGM(url, { "headers": { "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8", "accept-language": "fr;q=0.6", "cache-control": "max-age=0", "sec-ch-ua": "\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\", \"Brave\";v=\"120\"", "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": "\"Windows\"", "sec-fetch-dest": "document", "sec-fetch-mode": "navigate", "sec-fetch-site": "same-origin", "sec-fetch-user": "?1", "sec-gpc": "1", "upgrade-insecure-requests": "1" }, "referrer": url, "referrerPolicy": "strict-origin-when-cross-origin", "body": null, "method": "GET", "mode": "cors", "credentials": "include" }); const text = await response.text(); const xml = new DOMParser().parseFromString(text, "text/html"); const slow = xml.getElementById("slowDownloadButton"); const downloadUrl = slow.getAttribute("data-download-url"); document.location.href = downloadUrl; // open downloadUrl to Nexusmods console.log(`[NDC] Opened : ${downloadUrl}`); downloadProgress += 1; downloadProgressPercent = downloadProgress / urls.length * 100; progressBar.style.width = `${downloadProgressPercent}%`; progressBar.innerText = `${Math.round(downloadProgressPercent)}%`; console.log(`[NDC] Progress : ${Math.round(downloadProgressPercent)}%`); }; const downloadAllMods = async () => { const promises = urls.map((url, index) => { return new Promise((resolve) => { setTimeout(async () => { await downloadMod(url, index); resolve(); }, index * 250); }); }); await Promise.all(promises); progress.style.display = "none"; progressBar.style.width = "0%"; }; await downloadAllMods(); } const observerCallback = () => { // on element div id="tabcontent-mods" appear append button const tabcontentMods = document.querySelector("#tabcontent-mods"); if (tabcontentMods) { document.querySelector("#tabcontent-mods > div > div > div").prepend(progress); document.querySelector("#tabcontent-mods > div > div > div").prepend(button); } }; // on element div id="tabcontent-mods" appear append button const observer = new MutationObserver(observerCallback); const sectionWrapper = document.querySelector("#collection-tabs > section.py-4.section.bg-background-primary > div"); // sectionWrapper child is the content of a tab, so sometime its (id="tabcontent-mods") sometime its (id="tabcontent-comments") observer.observe(sectionWrapper, { childList: true }); // trigger the observer callback for the first time observerCallback(); })();