Nexus No Wait ++

Download from Nexusmods.com without wait and redirect (Manual/Vortex/MO2/NMM), Tweaked with extra features.

当前为 2025-02-04 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Nexus No Wait ++
  3. // @description Download from Nexusmods.com without wait and redirect (Manual/Vortex/MO2/NMM), Tweaked with extra features.
  4. // @namespace NexusNoWaitPlusPlus
  5. // @include https://www.nexusmods.com/*/mods/*
  6. // @run-at document-idle
  7. // @iconURL https://raw.githubusercontent.com/torkelicious/nexus-no-wait-pp/refs/heads/main/icon.png
  8. // @grant GM_xmlhttpRequest
  9. // @version 1.0.6
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. /* jshint esversion: 6 */
  14.  
  15. (function () {
  16. if (window.location.href.includes('tab=requirements')) {
  17. const newUrl = window.location.href.replace('tab=requirements', 'tab=files');
  18. window.location.replace(newUrl);
  19. return;
  20. }
  21.  
  22. let ajaxRequestRaw;
  23.  
  24. if (typeof(GM_xmlhttpRequest) !== "undefined") {
  25. ajaxRequestRaw = GM_xmlhttpRequest;
  26. } else if (typeof(GM) !== "undefined" && typeof(GM.xmlHttpRequest) !== "undefined") {
  27. ajaxRequestRaw = GM.xmlHttpRequest;
  28. }
  29.  
  30. function ajaxRequest(obj) {
  31. if (!ajaxRequestRaw) {
  32. console.log("Unable to request", obj);
  33.  
  34. return;
  35. }
  36.  
  37. const requestObj = {
  38. url: obj.url,
  39. method: obj.type,
  40. data: obj.data,
  41. headers: obj.headers
  42. };
  43.  
  44. let loadCb = function (result) {
  45. if (result.readyState !== 4) {
  46. return;
  47. }
  48.  
  49. if (result.status !== 200) {
  50. return obj.error(result);
  51. }
  52.  
  53. return obj.success(result.responseText);
  54. };
  55.  
  56. requestObj.onload = loadCb;
  57. requestObj.onerror = loadCb;
  58.  
  59. ajaxRequestRaw(requestObj);
  60. }
  61.  
  62. function btnError(button) {
  63. button.style.color = "red";
  64. button.innerText = "ERROR";
  65. alert("Nexus Error, download failed!. Manually download or try again.\n More information may exist in chrome developer console \n(Ctrl + Shift + J) ");
  66.  
  67. }
  68.  
  69. function btnSuccess(button) {
  70. button.style.color = "green";
  71. button.innerText = "Downloading!";
  72. console.log("Download started.");
  73.  
  74. }
  75.  
  76. function btnWait(button) {
  77. button.style.color = "yellow";
  78. button.innerText = "Wait...";
  79. console.log("Loading...");
  80. }
  81.  
  82. function clickListener(event) {
  83. const href = this.href || window.location.href;
  84. const params = new URL(href).searchParams;
  85.  
  86. if (params.get("file_id")) {
  87. let button = event;
  88. if (this.href) {
  89. button = this;
  90. event.preventDefault();
  91. }
  92. btnWait(button);
  93.  
  94. const section = document.getElementById("section");
  95. const gameId = section ? section.dataset.gameId : this.current_game_id;
  96.  
  97. let fileId = params.get("file_id");
  98. if (!fileId) {
  99. fileId = params.get("id");
  100. }
  101.  
  102. if (!params.get("nmm")) {
  103. ajaxRequest({
  104. type: "POST",
  105. url: "/Core/Libs/Common/Managers/Downloads?GenerateDownloadUrl",
  106. data: "fid=" + fileId + "&game_id=" + gameId,
  107. headers: {
  108. Origin: "https://www.nexusmods.com",
  109. Referer: href,
  110. "Sec-Fetch-Site": "same-origin",
  111. "X-Requested-With": "XMLHttpRequest",
  112. "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
  113. },
  114. success(data) {
  115. if (data) {
  116. try {
  117. data = JSON.parse(data);
  118.  
  119. if (data.url) {
  120. btnSuccess(button);
  121. document.location.href = data.url;
  122.  
  123. setTimeout(function() {
  124. window.close();
  125. }, 2500);
  126.  
  127. }
  128. } catch (e) {
  129. console.error(e);
  130. }
  131. }
  132. },
  133. error() {
  134. btnError(button);
  135. }
  136. });
  137. } else {
  138. ajaxRequest({
  139. type: "GET",
  140. url: href,
  141. headers: {
  142. Origin: "https://www.nexusmods.com",
  143. Referer: document.location.href,
  144. "Sec-Fetch-Site": "same-origin",
  145. "X-Requested-With": "XMLHttpRequest"
  146. },
  147. success(data) {
  148. if (data) {
  149. const xml = new DOMParser().parseFromString(data, "text/html");
  150. const slow = xml.getElementById("slowDownloadButton");
  151. const downloadUrl = slow.getAttribute("data-download-url");
  152. btnSuccess(button);
  153. document.location.href = downloadUrl;
  154.  
  155. setTimeout(function() {
  156. window.close();
  157. }, 2500);
  158.  
  159. }
  160. },
  161. error(ajaxContext) {
  162. console.error(ajaxContext.responseText);
  163. btnError(button);
  164. }
  165. });
  166. }
  167.  
  168. const popup = this.parentNode;
  169. if (popup && popup.classList.contains("popup")) {
  170. popup.getElementsByTagName("button")[0].click();
  171. const popupButton = document.getElementById("popup" + fileId);
  172. if (popupButton) {
  173. btnSuccess(popupButton);
  174. }
  175. }
  176. } else if (/ModRequirementsPopUp/.test(href)) {
  177. const fileId = params.get("id");
  178.  
  179. if (fileId) {
  180. this.setAttribute("id", "popup" + fileId);
  181. }
  182. }
  183. }
  184.  
  185. function addClickListener(el) {
  186. el.addEventListener("click", clickListener, true);
  187. }
  188.  
  189. function addClickListeners(els) {
  190. for (let i = 0; i < els.length; i++) {
  191. addClickListener(els[i]);
  192. }
  193. }
  194.  
  195. function autoStartFileLink() {
  196. if (/file_id=/.test(window.location.href)) {
  197. clickListener(document.getElementById("slowDownloadButton"));
  198. }
  199. }
  200.  
  201. function autoClickRequiredFileDownload() {
  202. const observer = new MutationObserver(() => {
  203. const downloadButton = document.querySelector(".popup-mod-requirements a.btn");
  204. if (downloadButton) {
  205. downloadButton.click();
  206. observer.disconnect();
  207. }
  208. });
  209.  
  210. observer.observe(document.body, { childList: true, subtree: true });
  211. }
  212.  
  213.  
  214. function archivedFile() {
  215. if (/[?&]category=archived/.test(window.location.href)) {
  216. const fileIds = document.getElementsByClassName("file-expander-header");
  217. const elements = document.getElementsByClassName("accordion-downloads");
  218. const path = `${location.protocol}//${location.host}${location.pathname}`;
  219. for (let i = 0; i < elements.length; i++) {
  220. elements[i].innerHTML = ''
  221. + `<li><a class="btn inline-flex" href="${path}?tab=files&amp;file_id=${fileIds[i].getAttribute("data-id")}&amp;nmm=1" tabindex="0">`
  222. + "<svg title=\"\" class=\"icon icon-nmm\"><use xlink:href=\"https://www.nexusmods.com/assets/images/icons/icons.svg#icon-nmm\"></use></svg> <span class=\"flex-label\">Mod manager download</span>"
  223. + "</a></li><li></li><li>"
  224. + `<li><a class="btn inline-flex" href="${path}?tab=files&amp;file_id=${fileIds[i].getAttribute("data-id")}" tabindex="0">`
  225. + "<svg title=\"\" class=\"icon icon-manual\"><use xlink:href=\"https://www.nexusmods.com/assets/images/icons/icons.svg#icon-manual\"></use></svg> <span class=\"flex-label\">Manual download</span>"
  226. + "</a></li>";
  227. }
  228. }
  229. }
  230.  
  231. archivedFile();
  232. addClickListeners(document.querySelectorAll("a.btn"));
  233. autoStartFileLink();
  234. autoClickRequiredFileDownload();
  235.  
  236.  
  237. let observer = new MutationObserver(((mutations, observer) => {
  238. for (let i = 0; i < mutations.length; i++) {
  239. if (mutations[i].addedNodes) {
  240. for (let x = 0; x < mutations[i].addedNodes.length; x++) {
  241. const node = mutations[i].addedNodes[x];
  242.  
  243. if (node.tagName === "A" && node.classList.contains("btn")) {
  244. addClickListener(node);
  245. } else if (node.children && node.children.length > 0) {
  246. addClickListeners(node.querySelectorAll("a.btn"));
  247. }
  248. }
  249. }
  250. }
  251. }));
  252. observer.observe(document, {childList: true, subtree: true});
  253. })();