Greasy Fork is available in English.
Skip Countdown (supports Manual/Vortex/MO2/NMM); File archive access; Skip Pop-up;
// ==UserScript==
// @name Nexus No Wait
// @description Skip Countdown (supports Manual/Vortex/MO2/NMM); File archive access; Skip Pop-up;
// @namespace NexusNoWait
// @include https://www.nexusmods.com/*/mods/*
// @run-at document-idle
// @grant GM.xmlHttpRequest
// @grant GM_xmlhttpRequest
// @grant GM_registerMenuCommand
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_listValues
// @grant unsafeWindow
// @version 2.20
// ==/UserScript==
(function() {
'use strict';
var win = unsafeWindow || window;
var doc = win.document;
var DEFAULT_CONFIG = {
ProcessArchivedFiles: true,
AddButtonArchivedFiles: true,
AutoStartDownload: true,
LogAdditionalInfo: false,
BlockNotifications: true,
};
var Config = (function() {
var settings = Object.assign({}, DEFAULT_CONFIG);
try {
GM_listValues().forEach(function(key) {
if (Object.prototype.hasOwnProperty.call(DEFAULT_CONFIG, key)) {
settings[key] = GM_getValue(key, DEFAULT_CONFIG[key]);
}
});
} catch (e) { console.warn('NNW: Load config failed', e); }
return {
get: function(key) { return settings[key]; },
set: function(key, val) {
settings[key] = val;
GM_setValue(key, val);
},
getAll: function() { return settings; }
};
})();
var STYLES = {
modal: `
.jsf-modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #1e1e1e; border: 2px solid #ff790a; border-radius: 8px; padding: 20px; z-index: 10000; box-shadow: 0 4px 20px rgba(0,0,0,0.3); min-width: 400px; color: #fff; font-family: Arial, sans-serif; }
.jsf-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 9999; }
.jsf-head { border-bottom: 1px solid #6441cf; padding-bottom: 10px; margin-bottom: 15px; font-size: 18px; font-weight: bold; }
.jsf-row { margin-bottom: 10px; display: flex; align-items: center; color: #c3c2c2; cursor: pointer; }
.jsf-chk { margin-right: 10px; accent-color: #005a87; }
.jsf-footer { margin-top: 20px; text-align: right; border-top: 1px solid #6441cf; padding-top: 15px; }
.jsf-btn { margin-left: 10px; padding: 6px 14px; border-radius: 4px; cursor: pointer; border: 1px solid #ccc; background: #ccc; }
.jsf-btn.p { background: #007cba; color: white; border-color: #007cba; }
.jsf-btn.p:hover { background: #005a87; }
`,
notify: 'position: fixed; top: 20px; right: 20px; padding: 10px 20px; border-radius: 4px; z-index: 10001; color: white; box-shadow: 0 2px 10px rgba(0,0,0,0.2);',
success: 'background: #4CAF50;',
error: 'background: #c73015;'
};
function el(tag, attrs, children) {
var element = doc.createElement(tag);
if (attrs) {
for (var key in attrs) {
if (key.startsWith('on') && typeof attrs[key] === 'function') element[key] = attrs[key];
else if (key === 'text') element.textContent = attrs[key];
else if (key === 'html') element.innerHTML = attrs[key];
else element.setAttribute(key, attrs[key]);
}
}
if (children) {
(Array.isArray(children) ? children : [children]).forEach(function(c) {
element.appendChild(typeof c === 'string' ? doc.createTextNode(c) : c);
});
}
return element;
}
function injectStyles(css) {
doc.head.appendChild(el('style', { text: css }));
}
function notify(text, isError) {
var div = el('div', { style: STYLES.notify + (isError ? STYLES.error : STYLES.success), text: text });
doc.body.appendChild(div);
setTimeout(function() { div.remove(); }, 2000);
}
function logError(data) {
console.error("NNW Error:", data);
notify('Error! Check console.', true);
}
function parsePrm(text) {
if (!text) return null;
var raw;
try {
raw = JSON.parse(text)?.url;
} catch (e) {
raw = String(text).match(/id=['"]dl_link['"].*?value=['"]([^'"]+)['"]/i)?.[1];
}
return raw?.replace(/&/g, '&') || null;
}
function getLink(text) {
return text?.match(/(nxm:\/\/[\s\S]+?)(?=["'\s<>]|$)/i)?.[1] ||
text?.match(/['"]([^'"]*?key[^'"]*?)['"]/)?.[1] || null;
}
function getDesc(key) {
var map = {
ProcessArchivedFiles: 'Process archived files section',
AddButtonArchivedFiles: 'Add button for archived files',
AutoStartDownload: 'Auto-start download from URL',
LogAdditionalInfo: 'Log info to console',
BlockNotifications: 'Skip pop-up notifications'
};
return map[key] || key;
}
function showSettings() {
var content = [];
Object.keys(Config.getAll()).forEach(function(key) {
var chk = el('input', { type: 'checkbox', class: 'jsf-chk', 'data-key': key });
chk.checked = Config.get(key);
content.push(el('label', { class: 'jsf-row' }, [chk, getDesc(key)]));
});
var overlay = el('div', { class: 'jsf-overlay' });
var saveBtn = el('button', { class: 'jsf-btn p', text: 'Save', onclick: function() {
overlay.querySelectorAll('input').forEach(function(c) { Config.set(c.dataset.key, c.checked); });
overlay.remove();
notify('Settings saved!');
}});
var modal = el('div', { class: 'jsf-modal' }, [
el('div', { class: 'jsf-head', text: 'Nexus No Wait' }, []),
el('div', {}, content),
el('div', { class: 'jsf-footer' }, [
el('button', { class: 'jsf-btn', text: 'Cancel', onclick: function() { overlay.remove(); } }),
saveBtn
])
]);
overlay.onclick = function(e) { if (e.target === overlay) overlay.remove(); };
overlay.appendChild(modal);
doc.body.appendChild(overlay);
}
GM_registerMenuCommand('⚙️ Settings', showSettings);
injectStyles(STYLES.modal);
function makeRequest(opts) {
var req = GM_xmlhttpRequest || (GM && GM.xmlHttpRequest);
if (!req) return opts.error && opts.error({ error: 'No XHR' });
req({
method: opts.method || 'GET',
url: opts.url,
data: opts.data,
headers: {
'Origin': 'https://www.nexusmods.com',
'Referer': win.location.href,
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
onload: function(res) {
(res.status === 200 && opts.success) ? opts.success(res.responseText) : (opts.error && opts.error(res));
},
onerror: opts.error
});
}
var ButtonState = {
set: function(btn, color, text) {
btn.style.setProperty('color', color, 'important');
var span = btn.querySelector('span.flex-label');
if (span) span.innerText = text;
}
};
function extractParams(htmlContent, params) {
var url = (typeof htmlContent === 'string' ? htmlContent : htmlContent?.url) || '';
if (!url) logError('Empty URL');
params.key = url.match(/md5=([^&"]+)/)?.[1];
params.expires = url.match(/expires=([^&"]+)/)?.[1];
params.user_id = url.match(/user_id=([^&"]+)/)?.[1];
if (!params.key || !params.expires) logError(params);
return params;
}
function handleDownloadClick(e) {
var btn = e.target.closest('a.btn');
if (!btn || (!btn.querySelector('.icon-manual, .icon-nmm') && !btn.getAttribute('href'))) return;
var url = new URL(btn.href || win.location.href);
var fileId = url.searchParams.get('file_id') || url.searchParams.get('id');
if (!fileId) return;
if (Config.get('BlockNotifications') && (/ModRequirementsPopUp|DownloadPopUp/.test(btn.href))) {
e.stopPropagation(); e.stopImmediatePropagation();
} else if (/tab=requirements|ModRequirementsPopUp|DownloadPopUp/.test(btn.href)) return;
e.preventDefault();
ButtonState.set(btn, 'yellow', 'WAIT');
var params = {
fileId: fileId,
gameId: doc.getElementById('section')?.dataset.gameId || btn.current_game_id,
isNMM: url.searchParams.has('nmm'),
game: win.location.pathname.split('/')[1],
modId: win.location.pathname.split('/')[3],
prm: /DownloadPopUp/.test(btn.href),
button: btn
};
params.isNMM ? processNMM(params) : processManual(params);
var popup = btn.closest('.popup');
if (popup) popup.querySelector('button')?.click();
}
function processManual(params) {
var endpoint = params.prm ? ('Widgets/DownloadPopUp?id=' + params.fileId + '&game_id=' + params.gameId) :
'Managers/Downloads?GenerateDownloadUrl';
makeRequest({
method: params.prm ? 'GET' : 'POST',
url: '/Core/Libs/Common/' + endpoint,
data: params.prm ? null : 'fid=' + params.fileId + '&game_id=' + params.gameId,
success: function(text) {
var link = params.prm ? parsePrm(text) : parsePrm(text);
if (link) {
ButtonState.set(params.button, 'green', 'Loading');
win.location.href = link;
} else ButtonState.set(params.button, 'red', 'ERROR');
},
error: function() { ButtonState.set(params.button, 'red', 'ERROR'); }
});
}
function processNMM(params) {
if (params.prm) {
makeRequest({
url: '/Core/Libs/Common/Widgets/DownloadPopUp?id=' + params.fileId + '&game_id=' + params.gameId,
success: function(text) {
params = extractParams(text, params);
var nxm = `nxm://${params.game}/mods/${params.modId}/files/${params.fileId}?key=${params.key}&expires=${params.expires}&user_id=${params.user_id}`;
win.location.href = nxm;
ButtonState.set(params.button, 'green', 'Loading');
}
});
} else {
makeRequest({
url: win.location.href + '&file_id=' + params.fileId + '&nmm=1',
success: function(text) {
var link = getLink(text);
if (link) {
win.location.href = link;
ButtonState.set(params.button, 'green', 'Loading');
} else ButtonState.set(params.button, 'red', 'ERROR');
}
});
}
}
function init() {
if (Config.get('LogAdditionalInfo')) console.log('NNW Loaded', Config.getAll());
if (Config.get('AddButtonArchivedFiles') && /[?&]tab=files/.test(win.location.href)) {
var footer = doc.getElementById('files-tab-footer');
if (footer && !footer.querySelector('.icon-archive')) {
footer.innerHTML = `<a class="btn inline-flex" href="${win.location.href}&category=archived"><svg class="icon icon-archive"><use xlink:href="/assets/images/icons/icons.svg#icon-archive"></use></svg><span class="flex-label">File archive</span></a>`;
}
}
if (Config.get('ProcessArchivedFiles') && /[?&]category=archived/.test(win.location.href)) {
Array.from(doc.getElementsByClassName('accordion-downloads')).forEach(function(el, i) {
var fid = doc.getElementsByClassName('file-expander-header')[i]?.getAttribute('data-id');
if (fid) {
var base = win.location.pathname + '?tab=files&file_id=' + fid;
el.innerHTML = `<li><a class="btn inline-flex" href="${base}&nmm=1"><svg 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</span></a></li><li></li><li><a class="btn inline-flex" href="${base}"><svg 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</span></a></li>`;
}
});
}
if (Config.get('AutoStartDownload') && /\bfile_id=/.test(win.location.href)) {
setTimeout(function() {
var btn = doc.getElementById('slowDownloadButton') || doc.getElementById('startDownloadButton') ||
doc.querySelector('mod-file-download')?.shadowRoot?.getElementById('slowDownloadButton');
if (btn) btn.click();
}, 1000);
}
doc.body.addEventListener('click', handleDownloadClick, true);
}
init();
})();