NTL Ban Check for NT & NM

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.

安装此脚本?
作者推荐脚本

您可能也喜欢Nitro Type Leaderboards Overlay

安装此脚本
// ==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);
        });
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址