您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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.
// ==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或关注我们的公众号极客氢云获取最新地址