// ==UserScript==
// @name NTL Ban Check for NT & NM
// @namespace http://tampermonkey.net/
// @version 2.1
// @description Checks if users are banned or flagged from the ntleaderboards website for both Nitro Type and Nitro Math.Made by [NHS]✨superjoelzy✨ based on the original script by dph.
// @match https://www.nitrotype.com/team/*
// @match https://www.nitrotype.com/racer/*
// @match https://www.nitrotype.com/leagues
// @match https://www.nitrotype.com/friends
// @match https://www.nitromath.com/team/*
// @match https://www.nitromath.com/racer/*
// @match https://www.nitromath.com/leagues
// @match https://www.nitromath.com/friends
// @grant GM_xmlhttpRequest
// @connect ntleaderboards.com
// @grant GM_getValue
// @grant GM_setValue
// @license MIT
// ==/UserScript==
(function () {
'use strict';
console.log("[DEBUG] Script initialized.");
observeMainContent();
function observeMainContent() {
const mainContentObserver = new MutationObserver(() => {
const path = window.location.pathname;
const hostname = window.location.hostname;
if (path.startsWith("/team") && document.querySelector('.team-members')) {
console.log("[DEBUG] Team members loaded.");
mainContentObserver.disconnect();
handleTeamPage();
} else if (path.startsWith("/racer") && document.querySelector('.profile-title')) {
console.log("[DEBUG] Racer profile loaded.");
mainContentObserver.disconnect();
handleRacerPage();
} else if (path === "/leagues" && document.querySelector('.table-row')) {
console.log("[DEBUG] Leagues loaded.");
mainContentObserver.disconnect();
handleLeaguesPage();
} else if (path === "/friends" && document.querySelector('.tab')) {
console.log("[DEBUG] Friends loaded.");
mainContentObserver.disconnect();
handleFriendsPage();
}
});
mainContentObserver.observe(document.body, { childList: true, subtree: true });
}
// ========================
// Shared Functions (Unified)
// ========================
function fetchAndDisplayStatus(username, element, context, callback = null) {
const cachedStatus = GM_getValue(`status_${username}`);
if (cachedStatus) {
console.log(`[DEBUG] Using cached status for ${username}: ${cachedStatus.status}`);
if (callback) callback(cachedStatus.status, cachedStatus.color);
else updateMemberStatus(element, cachedStatus.status, cachedStatus.color);
return;
}
const url = `https://ntleaderboards.com/is_user_banned/${username}`;
console.log(`[DEBUG] Fetching status for: ${username} (Context: ${context})`);
GM_xmlhttpRequest({
method: "GET",
url: url,
onload: function (response) {
if (response.status === 200) {
const data = response.responseText.trim();
console.log(`[DEBUG] Status fetched for ${username}: ${data}`);
let status, color;
if (data === "N") {
status = "Legit";
color = "rgb(0, 255, 0)";
} else if (data === "Y (ban)") {
status = "Banned";
color = "rgb(255, 165, 0)";
} else if (data === "Y (ban+flag)") {
status = "Banned (Flagged)";
color = "rgb(255, 0, 0)";
} else {
status = "Unknown";
color = "rgb(255, 255, 0)";
}
GM_setValue(`status_${username}`, { status, color });
if (callback) callback(status, color);
else updateMemberStatus(element, status, color);
} else {
console.error(`[DEBUG] Failed to fetch status for ${username}. HTTP Status: ${response.status}`);
}
},
onerror: function (error) {
console.error(`[DEBUG] Error fetching status for ${username}:`, error);
}
});
}
function updateMemberStatus(element, status, color) {
let statusField;
if (element.classList.contains('table-row')) {
statusField = element.querySelector('.tsi.tc-lemon.tsxs');
} else if (element.classList.contains('profile-title')) {
statusField = element;
} else {
const allRows = document.querySelectorAll('.table-row');
allRows.forEach(row => {
const racerContainer = row.querySelector('.player-name--container');
if (racerContainer && racerContainer.getAttribute('title') === element.getAttribute('title')) {
statusField = row.querySelector('.tsi.tc-lemon.tsxs');
}
});
}
if (!statusField) return;
const statusLabel = document.createElement('span');
statusLabel.textContent = ` ${status}`;
statusLabel.style.color = color;
statusLabel.style.marginLeft = "5px";
const existing = statusField.querySelector('.status-label');
if (existing) existing.remove();
statusLabel.classList.add('status-label');
statusField.appendChild(statusLabel);
}
function observeDOMChanges(callback) {
const observer = new MutationObserver(() => {
callback();
});
observer.observe(document.body, { childList: true, subtree: true });
}
// ========================
// /team Page Implementation (Corrected)
// ========================
const processedMembers = new Set();
function handleTeamPage() {
const teamSlug = getTeamSlugFromUrl();
if (teamSlug) {
fetchTeamData(teamSlug);
} else {
console.error("[DEBUG] No team slug found in URL.");
}
}
function getTeamSlugFromUrl() {
const pathParts = window.location.pathname.split('/');
return pathParts[pathParts.length - 1];
}
function fetchTeamData(teamSlug) {
const hostname = window.location.hostname;
const TEAM_API_URL = hostname === "www.nitrotype.com" ? "https://www.nitrotype.com/api/v2/teams/" : "https://www.nitromath.com/api/v2/teams/";
const url = `${TEAM_API_URL}${teamSlug}`;
GM_xmlhttpRequest({
method: "GET",
url: url,
onload: function (response) {
if (response.status === 200) {
const teamData = JSON.parse(response.responseText);
if (teamData && teamData.results && teamData.results.members) {
processTeamMembers(teamData.results.members);
}
}
},
onerror: function (error) {
console.error("Error fetching team data:", error);
}
});
}
function processTeamMembers(members) {
members.forEach(member => {
const username = member.username;
const displayName = member.displayName || username;
const memberElement = findMemberElement(displayName);
if (memberElement && !processedMembers.has(username)) {
processedMembers.add(username);
fetchAndDisplayStatus(username, memberElement, "Team");
}
});
}
function findMemberElement(displayName) {
const elements = document.querySelectorAll('.player-name--container[title]');
return Array.from(elements).find(element => element.getAttribute('title').trim() === displayName.trim());
}
// Call handleTeamPage() when on a team page:
if (window.location.pathname.startsWith("/team")) {
handleTeamPage();
}
// ========================
// /racer Page Implementation
// ========================
function handleRacerPage() {
const username = getUsernameFromUrl();
if (username) {
const profileTitleElement = document.querySelector('.profile-title');
if (profileTitleElement) {
const statusSpan = document.createElement('span');
statusSpan.style.marginLeft = "5px";
fetchAndDisplayStatus(username, profileTitleElement, "Racer", (status, color) => {
statusSpan.textContent = ` ${status}`;
statusSpan.style.color = color;
profileTitleElement.appendChild(statusSpan);
});
} else {
console.log("[DEBUG] Profile title not found. Observing DOM changes...");
observeDOMChanges(() => {
if (document.querySelector('.profile-title')) {
handleRacerPage();
}
});
}
}
}
function getUsernameFromUrl() {
const pathParts = window.location.pathname.split('/');
return pathParts[pathParts.length - 1];
}
// ========================
// /leagues Page Implementation
// ========================
async function handleLeaguesPage() {
const hostname = window.location.hostname;
const LEAGUES_API_URL = hostname === "www.nitrotype.com" ? "https://www.nitrotype.com/api/v2/leagues/user/activity" : "https://www.nitromath.com/api/v2/leagues/user/activity";
const NT_TOKEN = `Bearer ${localStorage.getItem("player_token")}`;
async function fetchUserActivity() {
try {
const response = await fetch(LEAGUES_API_URL, {
headers: {
accept: "application/json, text/plain, */*",
authorization: NT_TOKEN,
},
referrer: window.location.href,
referrerPolicy: "same-origin",
method: "GET",
mode: "cors",
credentials: "include"
});
const data = await response.json();
if (data.status === "OK") {
const standings = data.results.standings;
standings.sort((a, b) => b.experience - a.experience);
const userMap = {};
standings.forEach(user => {
const { displayName, username } = user;
userMap[displayName || username] = username;
});
return userMap;
} else {
console.error("[DEBUG] Error: ", data.status);
return null;
}
} catch (error) {
console.error("[DEBUG] Fetch error: ", error);
return null;
}
}
const userMap = await fetchUserActivity();
if (userMap) {
processLeagues(userMap);
setTimeout(() => { observeDOMChangesLeagues(userMap); }, 100);
observeTabChanges(userMap);
} else {
console.error("[DEBUG] Failed to fetch user activity.");
}
}
function processLeagues(userMap) {
const leaderboardRows = document.querySelectorAll('.table-row');
if (leaderboardRows.length > 0) {
console.log(`[DEBUG] Processing ${leaderboardRows.length} leaderboard rows.`);
processLeaderboardRows(leaderboardRows, userMap);
}
}
function processLeaderboardRows(rows, userMap) {
rows.forEach(row => {
const playerElement = row.querySelector('.player-name--container[title]');
if (!playerElement) return;
const displayName = playerElement.getAttribute('title');
const username = userMap[displayName];
if (username && !row.classList.contains('status-processed')) {
console.log(`[DEBUG] Processing user: ${username} (Display: ${displayName})`);
fetchAndDisplayStatus(username, row, "Leagues");
row.classList.add('status-processed');
}
});
}
function observeDOMChangesLeagues(userMap) {
const observer = new MutationObserver(() => {
processLeagues(userMap);
});
console.log("[DEBUG] MutationObserver initialized for /leagues.");
observer.observe(document.body, { childList: true, subtree: true });
}
function observeTabChanges(userMap) {
const observer = new MutationObserver(mutationsList => {
for (const mutation of mutationsList) {
if (mutation.type === 'attributes' && mutation.attributeName === 'checked') {
const personalInput = document.getElementById('showindividual');
if (personalInput && personalInput.checked) {
console.log("[DEBUG] Personal tab activated, re-processing leagues.");
processLeagues(userMap);
break;
}
}
}
});
observer.observe(document.querySelector('.switch'), { attributes: true, subtree: true });
}
// ========================
// /friends Page Implementation
// ========================
function handleFriendsPage() {
observeDOMChanges(function () {
const activeTab = getActiveTab();
if (activeTab) {
console.log(`[DEBUG] Active tab detected: ${activeTab}`);
const rows = getRowsForTab(activeTab);
console.log(`[DEBUG] Found ${rows.length} rows on the ${activeTab} tab.`);
processFriends(rows, activeTab);
} else {
console.log("[DEBUG] No active tab detected. Logging all tabs...");
logAllTabs();
}
});
}
function processFriends(rows, context) {
rows.forEach(row => {
const playerElement = row.querySelector('.player-name--container');
if (playerElement) {
let playerName = playerElement.getAttribute('title');
if (!playerName) {
playerName = playerElement.textContent.trim();
}
if (playerName) {
if (!row.classList.contains('status-processed')) {
console.log(`[DEBUG] Processing player: ${playerName} (Context: ${context})`);
findUsernameByDisplayName(playerName, row, context);
row.classList.add('status-processed');
}
}
} else {
if (!row.querySelector('th')) {
console.log("[DEBUG] No player element found in row:", row.outerHTML);
}
}
});
}
function findUsernameByDisplayName(targetName, row, context) {
const token = GM_getValue('nt_token');
if (!token) {
getTokenAndRetry(targetName, row, context);
return;
}
const NT_TOKEN = `Bearer ${token}`;
const hostname = window.location.hostname;
const API_BASE = hostname === "www.nitrotype.com" ? "https://www.nitrotype.com/api/v2" : "https://www.nitromath.com/api/v2";
GM_xmlhttpRequest({
method: "GET",
url: `${API_BASE}/friends`,
headers: {
"Authorization": NT_TOKEN,
"Content-Type": "application/json"
},
onload: function (response) {
if (response.status === 200) {
try {
const data = JSON.parse(response.responseText);
const fields = data.results.fields;
const values = data.results.values;
const displayNameIndex = fields.indexOf("displayName");
const usernameIndex = fields.indexOf("username");
if (displayNameIndex === -1 || usernameIndex === -1) {
console.error("Display name or username fields not found in API response.");
return;
}
let foundUsername = null;
for (const friendData of values) {
if (friendData[displayNameIndex] === targetName) {
foundUsername = friendData[usernameIndex];
break;
} else if (friendData[usernameIndex] === targetName) {
foundUsername = targetName;
break;
}
}
if (foundUsername) {
console.log(`Username for display name "${targetName}": ${foundUsername}`);
fetchAndDisplayStatus(foundUsername, row, context);
} else {
console.log(`Player "${targetName}" not found in friends list.`);
}
} catch (error) {
console.error("JSON Parsing Error:", error);
console.log("Raw Response:", response.responseText);
}
} else if (response.status === 401) {console.log("[DEBUG] Token expired, attempting to refresh.");
getTokenAndRetry(targetName, row, context);
} else {
console.error("Error fetching friends data:", response.status, response.statusText);
console.log("Raw Response:", response.responseText);
}
},
onerror: function (response) {
console.error("Network error:", response);
}
});
}
function getTokenAndRetry(displayName, row, context) {
const token = localStorage.getItem("player_token");
if (token) {
GM_setValue('nt_token', token);
findUsernameByDisplayName(displayName, row, context);
} else {
console.error("[DEBUG] No token found in localStorage.");
}
}
function getActiveTab() {
const activeTabElement = document.querySelector('.tab.is-active');
return activeTabElement ? activeTabElement.textContent.trim() : null;
}
function getRowsForTab(tabName) {
const rows = document.querySelectorAll('.table-row');
return Array.from(rows).filter(row => !row.querySelector('th'));
}
function logAllTabs() {
const tabs = document.querySelectorAll('.tab');
tabs.forEach((tab, index) => {
console.log(`[DEBUG] Tab ${index + 1}:`, tab.textContent.trim(), tab.className, tab.outerHTML);
});
}
})();