Nexus Mods Fast Download

Overrides the "Manual" button on NexusMods mod pages to download the file without extra steps.

  1. // ==UserScript==
  2. // @name Nexus Mods Fast Download
  3. // @namespace https://gf.qytechs.cn/en/users/814191-ctosango
  4. // @version 1.1
  5. // @description Overrides the "Manual" button on NexusMods mod pages to download the file without extra steps.
  6. // @author Your Name
  7. // @match https://www.nexusmods.com/*/mods/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=nexusmods.com
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. const spinnerCSS = `
  17. @keyframes spinner {
  18. to { transform: rotate(360deg); }
  19. }
  20. .nm-download-spinner {
  21. border: 2px solid rgba(0,0,0,0.2);
  22. border-left-color: #000;
  23. border-radius: 50%;
  24. width: 15px;
  25. height: 15px;
  26. margin: 0 5px 0 0;
  27. animation: spinner 0.6s linear infinite;
  28. display: inline-block;
  29. vertical-align: middle;
  30. }
  31. .nm-download-error {
  32. color: red;
  33. font-size: 0.9em;
  34. position: absolute;
  35. top: -20px;
  36. text-wrap: nowrap;
  37. right: 0;
  38. }
  39. @media (max-width: 1280px) {
  40. .nm-download-error {
  41. left: 0;
  42. right: unset;
  43. }
  44. }
  45. .modactions.clearfix {
  46. position: relative;
  47. margin-top: 15px;
  48. }
  49. `;
  50. const style = document.createElement('style');
  51. style.textContent = spinnerCSS;
  52. document.head.appendChild(style);
  53.  
  54. function showError(message, manualBtn) {
  55. console.error(message);
  56. let errorEl = manualBtn.parentNode.querySelector('.nm-download-error');
  57. if (!errorEl) {
  58. errorEl = document.createElement('div');
  59. errorEl.className = 'nm-download-error';
  60. manualBtn.parentNode.appendChild(errorEl);
  61. }
  62. errorEl.textContent = message;
  63. }
  64.  
  65. function clearError(manualBtn) {
  66. const errorEl = manualBtn.parentNode.querySelector('.nm-download-error');
  67. if (errorEl) {
  68. errorEl.remove();
  69. }
  70. }
  71.  
  72. function setupManualButton() {
  73. const manualLi = document.getElementById('action-manual');
  74. if (!manualLi) return;
  75. const manualBtn = manualLi.querySelector('a.btn.inline-flex');
  76. if (!manualBtn) return;
  77.  
  78. if (manualBtn.dataset.downloadOverrideSet === "true") return;
  79.  
  80. observer.disconnect();
  81.  
  82. if (!manualBtn.dataset.originalHref) {
  83. manualBtn.dataset.originalHref = manualBtn.href;
  84. }
  85.  
  86. manualBtn.removeAttribute('href');
  87.  
  88. manualBtn.dataset.downloadOverrideSet = "true";
  89.  
  90. const svgIcon = manualBtn.querySelector('svg.icon');
  91. if (svgIcon && !manualBtn._originalSvg) {
  92. manualBtn._originalSvg = svgIcon.cloneNode(true);
  93. }
  94.  
  95. manualBtn.addEventListener('click', async function(e) {
  96. e.preventDefault();
  97. e.stopImmediatePropagation();
  98. clearError(manualBtn);
  99.  
  100. let spinner;
  101. const currentSvg = manualBtn.querySelector('svg.icon');
  102. if (currentSvg) {
  103. spinner = document.createElement('span');
  104. spinner.className = 'nm-download-spinner';
  105. currentSvg.parentNode.replaceChild(spinner, currentSvg);
  106. } else {
  107. spinner = document.createElement('span');
  108. spinner.className = 'nm-download-spinner';
  109. manualBtn.appendChild(spinner);
  110. }
  111.  
  112. const originalUrl = manualBtn.dataset.originalHref;
  113. if (!originalUrl) {
  114. showError("Error: Original URL not available.", manualBtn);
  115. spinner.remove();
  116. return;
  117. }
  118.  
  119. let urlObj;
  120. try {
  121. urlObj = new URL(originalUrl, location.href);
  122. } catch (err) {
  123. showError("Error parsing the original URL.", manualBtn);
  124. spinner.remove();
  125. return;
  126. }
  127. const fileId = urlObj.searchParams.get('file_id');
  128. if (!fileId) {
  129. showError("Error: No file_id found in the URL.", manualBtn);
  130. spinner.remove();
  131. window.location.href = originalUrl;
  132. return;
  133. }
  134.  
  135. const gameId = window.current_game_id;
  136. if (!gameId) {
  137. showError("Error: Variable current_game_id is unavailable.", manualBtn);
  138. spinner.remove();
  139. if (manualBtn._originalSvg) {
  140. spinner.parentNode.replaceChild(manualBtn._originalSvg, spinner);
  141. }
  142. return;
  143. }
  144.  
  145. const params = new URLSearchParams();
  146. params.append('fid', fileId);
  147. params.append('game_id', gameId);
  148.  
  149. const postUrl = 'https://www.nexusmods.com/Core/Libs/Common/Managers/Downloads?GenerateDownloadUrl=';
  150.  
  151. try {
  152. const response = await fetch(postUrl, {
  153. method: 'POST',
  154. headers: {
  155. 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
  156. },
  157. body: params.toString()
  158. });
  159.  
  160. if (!response.ok) {
  161. if (response.status === 403) {
  162. showError("Error: Not logged in or access denied.", manualBtn);
  163. } else {
  164. showError("Error: Failed to get download URL (" + response.statusText + ").", manualBtn);
  165. }
  166. if (spinner && spinner.parentNode && manualBtn._originalSvg) {
  167. spinner.parentNode.replaceChild(manualBtn._originalSvg, spinner);
  168. }
  169. return;
  170. }
  171.  
  172. const data = await response.json();
  173. if (data && data.url) {
  174. if (spinner && spinner.parentNode && manualBtn._originalSvg) {
  175. spinner.parentNode.replaceChild(manualBtn._originalSvg, spinner);
  176. }
  177. setTimeout(() => {
  178. window.location.href = data.url;
  179. }, 100);
  180. } else if (data && data.length === 0) {
  181. showError("Error: Please login to download mods.", manualBtn);
  182. if (spinner && spinner.parentNode && manualBtn._originalSvg) {
  183. spinner.parentNode.replaceChild(manualBtn._originalSvg, spinner);
  184. }
  185. } else {
  186. console.log(data);
  187. showError("Error: Unexpected response format.", manualBtn);
  188. if (spinner && spinner.parentNode && manualBtn._originalSvg) {
  189. spinner.parentNode.replaceChild(manualBtn._originalSvg, spinner);
  190. }
  191. }
  192. } catch (err) {
  193. showError("Error during POST request: " + err.message, manualBtn);
  194. if (spinner && spinner.parentNode && manualBtn._originalSvg) {
  195. spinner.parentNode.replaceChild(manualBtn._originalSvg, spinner);
  196. }
  197. }
  198. }, true);
  199.  
  200. observer.observe(document.body, { childList: true, subtree: true });
  201. }
  202.  
  203. const observer = new MutationObserver(() => {
  204. setupManualButton();
  205. });
  206. observer.observe(document.body, { childList: true, subtree: true });
  207.  
  208. if (document.readyState === 'loading') {
  209. document.addEventListener('DOMContentLoaded', setupManualButton);
  210. } else {
  211. setupManualButton();
  212. }
  213. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址