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

安裝腳本
  1. // ==UserScript==
  2. // @name NTL Ban Check for NT & NM
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.1
  5. // @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.
  6. // @match https://www.nitrotype.com/team/*
  7. // @match https://www.nitrotype.com/racer/*
  8. // @match https://www.nitrotype.com/leagues
  9. // @match https://www.nitrotype.com/friends
  10. // @match https://www.nitromath.com/team/*
  11. // @match https://www.nitromath.com/racer/*
  12. // @match https://www.nitromath.com/leagues
  13. // @match https://www.nitromath.com/friends
  14. // @grant GM_xmlhttpRequest
  15. // @connect ntleaderboards.com
  16. // @grant GM_getValue
  17. // @grant GM_setValue
  18. // @license MIT
  19. // ==/UserScript==
  20.  
  21. (function () {
  22. 'use strict';
  23.  
  24. console.log("[DEBUG] Script initialized.");
  25. observeMainContent();
  26.  
  27. function observeMainContent() {
  28. const mainContentObserver = new MutationObserver(() => {
  29. const path = window.location.pathname;
  30. const hostname = window.location.hostname;
  31.  
  32. if (path.startsWith("/team") && document.querySelector('.team-members')) {
  33. console.log("[DEBUG] Team members loaded.");
  34. mainContentObserver.disconnect();
  35. handleTeamPage();
  36. } else if (path.startsWith("/racer") && document.querySelector('.profile-title')) {
  37. console.log("[DEBUG] Racer profile loaded.");
  38. mainContentObserver.disconnect();
  39. handleRacerPage();
  40. } else if (path === "/leagues" && document.querySelector('.table-row')) {
  41. console.log("[DEBUG] Leagues loaded.");
  42. mainContentObserver.disconnect();
  43. handleLeaguesPage();
  44. } else if (path === "/friends" && document.querySelector('.tab')) {
  45. console.log("[DEBUG] Friends loaded.");
  46. mainContentObserver.disconnect();
  47. handleFriendsPage();
  48. }
  49. });
  50. mainContentObserver.observe(document.body, { childList: true, subtree: true });
  51. }
  52.  
  53. // ========================
  54. // Shared Functions (Unified)
  55. // ========================
  56. function fetchAndDisplayStatus(username, element, context, callback = null) {
  57. const cachedStatus = GM_getValue(`status_${username}`);
  58. if (cachedStatus) {
  59. console.log(`[DEBUG] Using cached status for ${username}: ${cachedStatus.status}`);
  60. if (callback) callback(cachedStatus.status, cachedStatus.color);
  61. else updateMemberStatus(element, cachedStatus.status, cachedStatus.color);
  62. return;
  63. }
  64.  
  65. const url = `https://ntleaderboards.com/is_user_banned/${username}`;
  66. console.log(`[DEBUG] Fetching status for: ${username} (Context: ${context})`);
  67. GM_xmlhttpRequest({
  68. method: "GET",
  69. url: url,
  70. onload: function (response) {
  71. if (response.status === 200) {
  72. const data = response.responseText.trim();
  73. console.log(`[DEBUG] Status fetched for ${username}: ${data}`);
  74. let status, color;
  75. if (data === "N") {
  76. status = "Legit";
  77. color = "rgb(0, 255, 0)";
  78. } else if (data === "Y (ban)") {
  79. status = "Banned";
  80. color = "rgb(255, 165, 0)";
  81. } else if (data === "Y (ban+flag)") {
  82. status = "Banned (Flagged)";
  83. color = "rgb(255, 0, 0)";
  84. } else {
  85. status = "Unknown";
  86. color = "rgb(255, 255, 0)";
  87. }
  88. GM_setValue(`status_${username}`, { status, color });
  89. if (callback) callback(status, color);
  90. else updateMemberStatus(element, status, color);
  91. } else {
  92. console.error(`[DEBUG] Failed to fetch status for ${username}. HTTP Status: ${response.status}`);
  93. }
  94. },
  95. onerror: function (error) {
  96. console.error(`[DEBUG] Error fetching status for ${username}:`, error);
  97. }
  98. });
  99. }
  100.  
  101. function updateMemberStatus(element, status, color) {
  102. let statusField;
  103. if (element.classList.contains('table-row')) {
  104. statusField = element.querySelector('.tsi.tc-lemon.tsxs');
  105. } else if (element.classList.contains('profile-title')) {
  106. statusField = element;
  107. } else {
  108. const allRows = document.querySelectorAll('.table-row');
  109. allRows.forEach(row => {
  110. const racerContainer = row.querySelector('.player-name--container');
  111. if (racerContainer && racerContainer.getAttribute('title') === element.getAttribute('title')) {
  112. statusField = row.querySelector('.tsi.tc-lemon.tsxs');
  113. }
  114. });
  115. }
  116. if (!statusField) return;
  117. const statusLabel = document.createElement('span');
  118. statusLabel.textContent = ` ${status}`;
  119. statusLabel.style.color = color;
  120. statusLabel.style.marginLeft = "5px";
  121. const existing = statusField.querySelector('.status-label');
  122. if (existing) existing.remove();
  123. statusLabel.classList.add('status-label');
  124. statusField.appendChild(statusLabel);
  125. }
  126.  
  127. function observeDOMChanges(callback) {
  128. const observer = new MutationObserver(() => {
  129. callback();
  130. });
  131. observer.observe(document.body, { childList: true, subtree: true });
  132. }
  133.  
  134. // ========================
  135. // /team Page Implementation (Corrected)
  136. // ========================
  137. const processedMembers = new Set();
  138.  
  139. function handleTeamPage() {
  140. const teamSlug = getTeamSlugFromUrl();
  141. if (teamSlug) {
  142. fetchTeamData(teamSlug);
  143. } else {
  144. console.error("[DEBUG] No team slug found in URL.");
  145. }
  146. }
  147.  
  148. function getTeamSlugFromUrl() {
  149. const pathParts = window.location.pathname.split('/');
  150. return pathParts[pathParts.length - 1];
  151. }
  152.  
  153. function fetchTeamData(teamSlug) {
  154. const hostname = window.location.hostname;
  155. const TEAM_API_URL = hostname === "www.nitrotype.com" ? "https://www.nitrotype.com/api/v2/teams/" : "https://www.nitromath.com/api/v2/teams/";
  156. const url = `${TEAM_API_URL}${teamSlug}`;
  157. GM_xmlhttpRequest({
  158. method: "GET",
  159. url: url,
  160. onload: function (response) {
  161. if (response.status === 200) {
  162. const teamData = JSON.parse(response.responseText);
  163. if (teamData && teamData.results && teamData.results.members) {
  164. processTeamMembers(teamData.results.members);
  165. }
  166. }
  167. },
  168. onerror: function (error) {
  169. console.error("Error fetching team data:", error);
  170. }
  171. });
  172. }
  173.  
  174. function processTeamMembers(members) {
  175. members.forEach(member => {
  176. const username = member.username;
  177. const displayName = member.displayName || username;
  178.  
  179. const memberElement = findMemberElement(displayName);
  180. if (memberElement && !processedMembers.has(username)) {
  181. processedMembers.add(username);
  182. fetchAndDisplayStatus(username, memberElement, "Team");
  183. }
  184. });
  185. }
  186.  
  187. function findMemberElement(displayName) {
  188. const elements = document.querySelectorAll('.player-name--container[title]');
  189. return Array.from(elements).find(element => element.getAttribute('title').trim() === displayName.trim());
  190. }
  191.  
  192. // Call handleTeamPage() when on a team page:
  193. if (window.location.pathname.startsWith("/team")) {
  194. handleTeamPage();
  195. }
  196.  
  197. // ========================
  198. // /racer Page Implementation
  199. // ========================
  200. function handleRacerPage() {
  201. const username = getUsernameFromUrl();
  202. if (username) {
  203. const profileTitleElement = document.querySelector('.profile-title');
  204. if (profileTitleElement) {
  205. const statusSpan = document.createElement('span');
  206. statusSpan.style.marginLeft = "5px";
  207. fetchAndDisplayStatus(username, profileTitleElement, "Racer", (status, color) => {
  208. statusSpan.textContent = ` ${status}`;
  209. statusSpan.style.color = color;
  210. profileTitleElement.appendChild(statusSpan);
  211. });
  212. } else {
  213. console.log("[DEBUG] Profile title not found. Observing DOM changes...");
  214. observeDOMChanges(() => {
  215. if (document.querySelector('.profile-title')) {
  216. handleRacerPage();
  217. }
  218. });
  219. }
  220. }
  221. }
  222.  
  223. function getUsernameFromUrl() {
  224. const pathParts = window.location.pathname.split('/');
  225. return pathParts[pathParts.length - 1];
  226. }
  227.  
  228. // ========================
  229. // /leagues Page Implementation
  230. // ========================
  231. async function handleLeaguesPage() {
  232. const hostname = window.location.hostname;
  233. 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";
  234. const NT_TOKEN = `Bearer ${localStorage.getItem("player_token")}`;
  235.  
  236. async function fetchUserActivity() {
  237. try {
  238. const response = await fetch(LEAGUES_API_URL, {
  239. headers: {
  240. accept: "application/json, text/plain, */*",
  241. authorization: NT_TOKEN,
  242. },
  243. referrer: window.location.href,
  244. referrerPolicy: "same-origin",
  245. method: "GET",
  246. mode: "cors",
  247. credentials: "include"
  248. });
  249. const data = await response.json();
  250. if (data.status === "OK") {
  251. const standings = data.results.standings;
  252. standings.sort((a, b) => b.experience - a.experience);
  253. const userMap = {};
  254. standings.forEach(user => {
  255. const { displayName, username } = user;
  256. userMap[displayName || username] = username;
  257. });
  258. return userMap;
  259. } else {
  260. console.error("[DEBUG] Error: ", data.status);
  261. return null;
  262. }
  263. } catch (error) {
  264. console.error("[DEBUG] Fetch error: ", error);
  265. return null;
  266. }
  267. }
  268.  
  269. const userMap = await fetchUserActivity();
  270. if (userMap) {
  271. processLeagues(userMap);
  272. setTimeout(() => { observeDOMChangesLeagues(userMap); }, 100);
  273. observeTabChanges(userMap);
  274. } else {
  275. console.error("[DEBUG] Failed to fetch user activity.");
  276. }
  277. }
  278.  
  279. function processLeagues(userMap) {
  280. const leaderboardRows = document.querySelectorAll('.table-row');
  281. if (leaderboardRows.length > 0) {
  282. console.log(`[DEBUG] Processing ${leaderboardRows.length} leaderboard rows.`);
  283. processLeaderboardRows(leaderboardRows, userMap);
  284. }
  285. }
  286.  
  287. function processLeaderboardRows(rows, userMap) {
  288. rows.forEach(row => {
  289. const playerElement = row.querySelector('.player-name--container[title]');
  290. if (!playerElement) return;
  291. const displayName = playerElement.getAttribute('title');
  292. const username = userMap[displayName];
  293. if (username && !row.classList.contains('status-processed')) {
  294. console.log(`[DEBUG] Processing user: ${username} (Display: ${displayName})`);
  295. fetchAndDisplayStatus(username, row, "Leagues");
  296. row.classList.add('status-processed');
  297. }
  298. });
  299. }
  300.  
  301. function observeDOMChangesLeagues(userMap) {
  302. const observer = new MutationObserver(() => {
  303. processLeagues(userMap);
  304. });
  305. console.log("[DEBUG] MutationObserver initialized for /leagues.");
  306. observer.observe(document.body, { childList: true, subtree: true });
  307. }
  308.  
  309. function observeTabChanges(userMap) {
  310. const observer = new MutationObserver(mutationsList => {
  311. for (const mutation of mutationsList) {
  312. if (mutation.type === 'attributes' && mutation.attributeName === 'checked') {
  313. const personalInput = document.getElementById('showindividual');
  314. if (personalInput && personalInput.checked) {
  315. console.log("[DEBUG] Personal tab activated, re-processing leagues.");
  316. processLeagues(userMap);
  317. break;
  318. }
  319. }
  320. }
  321. });
  322. observer.observe(document.querySelector('.switch'), { attributes: true, subtree: true });
  323. }
  324.  
  325. // ========================
  326. // /friends Page Implementation
  327. // ========================
  328. function handleFriendsPage() {
  329. observeDOMChanges(function () {
  330. const activeTab = getActiveTab();
  331. if (activeTab) {
  332. console.log(`[DEBUG] Active tab detected: ${activeTab}`);
  333. const rows = getRowsForTab(activeTab);
  334. console.log(`[DEBUG] Found ${rows.length} rows on the ${activeTab} tab.`);
  335. processFriends(rows, activeTab);
  336. } else {
  337. console.log("[DEBUG] No active tab detected. Logging all tabs...");
  338. logAllTabs();
  339. }
  340. });
  341. }
  342.  
  343. function processFriends(rows, context) {
  344. rows.forEach(row => {
  345. const playerElement = row.querySelector('.player-name--container');
  346. if (playerElement) {
  347. let playerName = playerElement.getAttribute('title');
  348. if (!playerName) {
  349. playerName = playerElement.textContent.trim();
  350. }
  351. if (playerName) {
  352. if (!row.classList.contains('status-processed')) {
  353. console.log(`[DEBUG] Processing player: ${playerName} (Context: ${context})`);
  354. findUsernameByDisplayName(playerName, row, context);
  355. row.classList.add('status-processed');
  356. }
  357. }
  358. } else {
  359. if (!row.querySelector('th')) {
  360. console.log("[DEBUG] No player element found in row:", row.outerHTML);
  361. }
  362. }
  363. });
  364. }
  365.  
  366. function findUsernameByDisplayName(targetName, row, context) {
  367. const token = GM_getValue('nt_token');
  368. if (!token) {
  369. getTokenAndRetry(targetName, row, context);
  370. return;
  371. }
  372.  
  373. const NT_TOKEN = `Bearer ${token}`;
  374. const hostname = window.location.hostname;
  375. const API_BASE = hostname === "www.nitrotype.com" ? "https://www.nitrotype.com/api/v2" : "https://www.nitromath.com/api/v2";
  376.  
  377. GM_xmlhttpRequest({
  378. method: "GET",
  379. url: `${API_BASE}/friends`,
  380. headers: {
  381. "Authorization": NT_TOKEN,
  382. "Content-Type": "application/json"
  383. },
  384. onload: function (response) {
  385. if (response.status === 200) {
  386. try {
  387. const data = JSON.parse(response.responseText);
  388. const fields = data.results.fields;
  389. const values = data.results.values;
  390.  
  391. const displayNameIndex = fields.indexOf("displayName");
  392. const usernameIndex = fields.indexOf("username");
  393.  
  394. if (displayNameIndex === -1 || usernameIndex === -1) {
  395. console.error("Display name or username fields not found in API response.");
  396. return;
  397. }
  398.  
  399. let foundUsername = null;
  400.  
  401. for (const friendData of values) {
  402. if (friendData[displayNameIndex] === targetName) {
  403. foundUsername = friendData[usernameIndex];
  404. break;
  405. } else if (friendData[usernameIndex] === targetName) {
  406. foundUsername = targetName;
  407. break;
  408. }
  409. }
  410.  
  411. if (foundUsername) {
  412. console.log(`Username for display name "${targetName}": ${foundUsername}`);
  413. fetchAndDisplayStatus(foundUsername, row, context);
  414. } else {
  415. console.log(`Player "${targetName}" not found in friends list.`);
  416. }
  417. } catch (error) {
  418. console.error("JSON Parsing Error:", error);
  419. console.log("Raw Response:", response.responseText);
  420. }
  421. } else if (response.status === 401) {console.log("[DEBUG] Token expired, attempting to refresh.");
  422. getTokenAndRetry(targetName, row, context);
  423. } else {
  424. console.error("Error fetching friends data:", response.status, response.statusText);
  425. console.log("Raw Response:", response.responseText);
  426. }
  427. },
  428. onerror: function (response) {
  429. console.error("Network error:", response);
  430. }
  431. });
  432. }
  433.  
  434. function getTokenAndRetry(displayName, row, context) {
  435. const token = localStorage.getItem("player_token");
  436. if (token) {
  437. GM_setValue('nt_token', token);
  438. findUsernameByDisplayName(displayName, row, context);
  439. } else {
  440. console.error("[DEBUG] No token found in localStorage.");
  441. }
  442. }
  443.  
  444. function getActiveTab() {
  445. const activeTabElement = document.querySelector('.tab.is-active');
  446. return activeTabElement ? activeTabElement.textContent.trim() : null;
  447. }
  448.  
  449. function getRowsForTab(tabName) {
  450. const rows = document.querySelectorAll('.table-row');
  451. return Array.from(rows).filter(row => !row.querySelector('th'));
  452. }
  453.  
  454. function logAllTabs() {
  455. const tabs = document.querySelectorAll('.tab');
  456. tabs.forEach((tab, index) => {
  457. console.log(`[DEBUG] Tab ${index + 1}:`, tab.textContent.trim(), tab.className, tab.outerHTML);
  458. });
  459. }
  460. })();

QingJ © 2025

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