- // ==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);
- });
- }
- })();