Nexus Endorsement Manager

Adds buttons to the "My Nexus account" screen related to the mass endorsement and abstainment from endorsement of mods.

< Feedback on Nexus Endorsement Manager

Review: OK - script works, but has bugs

§
Posted: 2025-05-08

Great script!

Updated version that works on current Nexus site and has throttling (adjustable via UI) to prevent getting rate limited/temp IP banned:

// ==UserScript==
// @name Nexus Endorsement Manager
// @version 1.2.0
// @description Adds buttons to the "My Nexus account" screen related to the mass endorsement and abstainment from endorsement of mods.
// @author pointfeev & wefalltomorrow
// @copyright 2021, pointfeev (https://github.com/pointfeev)
// @license MIT
// @match *://*.nexusmods.com/users/myaccount*
// @match *://*.nexusmods.com/settings/preferences*
// @icon https://www.nexusmods.com/favicon.ico
// @grant none
// @namespace https://github.com/pointfeev
// @homepageURL https://gist.github.com/pointfeev/aa70c3d600698df40141c3a79ad9bf59
// @downloadURL https://update.greasyfork.org/scripts/437215/Nexus%20Endorsement%20Manager.user.js
// @updateURL https://update.greasyfork.org/scripts/437215/Nexus%20Endorsement%20Manager.meta.js
// ==/UserScript==

(function() {
'use strict';

var requests = [];
function endorse(i, item, positive) {
return new Promise(function(resolve, reject) {
if (item[12] != positive) {
var request = $.ajax({
type: "POST",
url: "https://www.nexusmods.com/Core/Libs/Common/Managers/Mods?Endorse",
data: {
game_id: item[7],
mod_id: item[8],
positive: positive
},
dataType : 'json',
success: function (response) {
resolve(response);
requests.pop(request);
},
error: function(err) {
reject(err);
requests.pop(request);
}
});
requests.push(request);
}
else {
resolve();
}
});
}

var working = false;
var working_on = "none";
var working_index = 0;
var aborting = false;
function work(positive, btn_text, original_text, count_func) {
if (aborting) return;
if (working) {
if (working_on == original_text) {
aborting = true;
btn_text.text("Aborting . . .");
requests.forEach(function(request) {
request.abort();
requests.pop(request);
});
working = false;
working_on = "none";
aborting = false;
btn_text.text(original_text);
}
return;
}
requests.forEach(function(request) {
request.abort();
requests.pop(request);
});
working = true;
working_on = original_text;
working_index++;
var current_working_index = working_index;
function active()
{
return working && working_index == current_working_index;
}
btn_text.text("Requesting download history . . .");
new Promise(function(resolve, reject) {
var request = $.ajax({
type: "GET",
url: "https://www.nexusmods.com/Core/Libs/Common/Managers/Mods?GetDownloadHistory",
dataType : 'json',
success: function (response) {
resolve(response.data);
requests.pop(request);
},
error: function(err) {
reject(err);
requests.pop(request);
}
});
requests.push(request);
}).then(function(data) {
const delayInputValue = getDelay();
function processItems(index) {
if (!active() || index >= data.length) {
btn_text.text(original_text);
working = false;
working_on = "none";
return;
}

const item = data[index];
endorse(index, item, positive).then(function(response) {
if (!active()) return;
btn_text.text(count_func(data.length - index - 1));
setTimeout(() => processItems(index + 1), delayInputValue);
}).catch(function(err) {
console.clear(); // prevent console lag
if (!active()) return;
btn_text.text(count_func(data.length - index));
setTimeout(() => processItems(index), delayInputValue);
});
}

processItems(0);
var count = data.length;
btn_text.text(count_func(count));
}).catch(function(err) {
console.clear(); // prevent console lag
if (!active()) return;
btn_text.text(original_text);
});
}

function create_icon(icon_id, icon_class) {
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'),
use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
use.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", "https://www.nexusmods.com/assets/images/icons/icons.svg#icon-" + icon_id);
$(svg).addClass("icon icon-" + icon_class);
svg.append(use);
return svg;
}

var btn_endorse_text = document.createElement("span");
$(btn_endorse_text).addClass("flex-label");
$(btn_endorse_text).text("Endorse all mods");
var btn_endorse_a = document.createElement("a");
$(btn_endorse_a).addClass("btn inline-flex");
btn_endorse_a.tabIndex = 0;
btn_endorse_a.append(create_icon("endorse", "endorse"));
btn_endorse_a.append(btn_endorse_text);
$(btn_endorse_a).click(function() {
work(1, $(btn_endorse_text), "Endorse all mods", function(count) {
return "Endorsing " + count + " mods . . .";
});
});
var btn_endorse_li = document.createElement("li");
btn_endorse_li.append(btn_endorse_a);
// Ensure #action-preview exists or use a more robust insertion point
// For now, assuming it exists based on original script
if ($("#action-preview").length) {
$(btn_endorse_li).insertBefore("#action-preview");
} else {
// Fallback or error handling if #action-preview is not found
// e.g., append to a known parent or log an error
console.warn("Nexus Endorsement Manager: #action-preview not found for button insertion.");
// As a fallback, you might try to find a similar container
// This is just an example, you might need to inspect the page for a better selector
let container = $("ul.subby.subforum.double");
if (container.length) {
container.first().prepend(btn_endorse_li);
} else {
// Or even just append to body if nothing else found, though not ideal
// document.body.appendChild(btn_endorse_li);
}
}


var btn_abstain_text = document.createElement("span");
$(btn_abstain_text).addClass("flex-label");
$(btn_abstain_text).text("Abstain from endorsing all mods");
var btn_abstain_a = document.createElement("a");
$(btn_abstain_a).addClass("btn inline-flex");
btn_abstain_a.tabIndex = 0;
btn_abstain_a.append(create_icon("ignore", "endorse"));
btn_abstain_a.append(btn_abstain_text);
$(btn_abstain_a).click(function() {
work(0, $(btn_abstain_text), "Abstain from endorsing all mods", function(count) {
return "Abstaining from endorsing " + count + " mods . . .";
});
});
var btn_abstain_li = document.createElement("li");
btn_abstain_li.append(btn_abstain_a);
$(btn_abstain_li).insertAfter(btn_endorse_li); // This assumes btn_endorse_li was successfully inserted

// Add UI for delay configuration
var delayContainer = document.createElement("li");
var delayLabel = document.createElement("label");
delayLabel.textContent = "Delay between requests (ms): ";
var delayInput = document.createElement("input");
delayInput.type = "number";
delayInput.min = "0";
delayInput.value = "1000"; // Default value
delayInput.style.marginLeft = "5px";
delayInput.style.width = "80px";
// --- ADDED STYLES FOR VISIBILITY ---
delayInput.style.color = "black";
delayInput.style.backgroundColor = "white";
// --- END OF ADDED STYLES ---
delayLabel.appendChild(delayInput);
delayContainer.appendChild(delayLabel);

// Insert delayContainer before #action-preview, or after btn_abstain_li if #action-preview is problematic
if ($("#action-preview").length) {
$("#action-preview").before(delayContainer);
} else if ($(btn_abstain_li).parent().length) { // If btn_abstain_li was inserted
$(delayContainer).insertAfter(btn_abstain_li);
} else {
// Fallback for delayContainer if other elements also failed to insert
console.warn("Nexus Endorsement Manager: Could not find a suitable place for delay input.");
}


// Event listener for input change
delayInput.addEventListener('input', function() {
// On input change, ensure it is updated and valid
const delay = getDelay();
console.log("New delay set: " + delay + " ms"); // Optional: debug output to check input changes
});

// Function to get the current delay
function getDelay() {
const value = delayInput.value;
if (value === "") return 1000; // Default to 1000ms if empty
const parsedValue = parseInt(value, 10);
return isNaN(parsedValue) || parsedValue < 0 ? 1000 : parsedValue; // Ensure non-negative
}
})();

Post reply

Sign in to post a reply.