Nitro Type Auto Invite

Automatically invites users to your team or adds as friend after a race

Stan na 09-03-2025. Zobacz najnowsza wersja.

  1. // ==UserScript==
  2. // @name Nitro Type Auto Invite
  3. // @namespace https://www.nitrotype.com/
  4. // @version 2.8.2
  5. // @description Automatically invites users to your team or adds as friend after a race
  6. // @author Isaac Weber
  7. // @match https://www.nitrotype.com/race/*
  8. // @match https://www.nitrotype.com/race
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. // Simple configuration
  17. const config = {
  18. minDelay: 100,
  19. maxDelay: 200,
  20. debug: true,
  21. checkInterval: 200,
  22. startupDelay: 250
  23. };
  24.  
  25. // Processed players tracking
  26. const processed = new Set();
  27.  
  28. // Track if processing has started to avoid duplicate detection
  29. let processingStarted = false;
  30.  
  31. // Helper functions
  32. function log(message) {
  33. if (config.debug) console.log(`[Team Inviter] ${message}`);
  34. }
  35.  
  36. function randomDelay() {
  37. return Math.floor(Math.random() * (config.maxDelay - config.minDelay + 1)) + config.minDelay;
  38. }
  39.  
  40. // Find player rows using various selectors
  41. function findPlayerRows() {
  42. const selectors = [
  43. '.player-row',
  44. '[class*="player-container"]',
  45. '[class*="racer"]',
  46. '[id*="racer"]',
  47. '[id*="player"]',
  48. '[class*="player"]',
  49. '.race-results-player'
  50. ];
  51.  
  52. for (const selector of selectors) {
  53. const elements = document.querySelectorAll(selector);
  54. if (elements.length > 0) {
  55. log(`Found ${elements.length} players using selector: ${selector}`);
  56. return Array.from(elements);
  57. }
  58. }
  59.  
  60. log("No players found");
  61. return [];
  62. }
  63.  
  64. // Reliable hover simulation
  65. function simulateHover(element) {
  66. try {
  67. const rect = element.getBoundingClientRect();
  68. const centerX = rect.left + (rect.width / 2);
  69. const centerY = rect.top + (rect.height / 2);
  70.  
  71. // Clear existing hovers
  72. document.dispatchEvent(new MouseEvent('mouseout', {
  73. bubbles: true,
  74. cancelable: true
  75. }));
  76.  
  77. // Hover events
  78. element.dispatchEvent(new MouseEvent('mouseenter', {
  79. bubbles: true,
  80. cancelable: true,
  81. clientX: centerX,
  82. clientY: centerY
  83. }));
  84.  
  85. element.dispatchEvent(new MouseEvent('mouseover', {
  86. bubbles: true,
  87. cancelable: true,
  88. clientX: centerX,
  89. clientY: centerY
  90. }));
  91.  
  92. return true;
  93. } catch (e) {
  94. log(`Hover error: ${e.message}`);
  95. return false;
  96. }
  97. }
  98.  
  99. // Find and click relevant buttons
  100. function findAndClickButtons() {
  101. try {
  102. // Team invite button by text
  103. const inviteButtons = Array.from(document.querySelectorAll('a, button, .btn, [role="button"], div[class*="button"]'))
  104. .filter(el => {
  105. const text = (el.textContent || '').toLowerCase();
  106. const isVisible = el.offsetParent !== null;
  107. return isVisible && text.includes('invite') && text.includes('team');
  108. });
  109.  
  110. if (inviteButtons.length > 0) {
  111. log("Clicking team invite button");
  112. inviteButtons[0].click();
  113. return true;
  114. }
  115.  
  116. // Team invite button by class
  117. const specificButton = document.querySelector('a[class*="invite-team"], a[class*="team-invite"], [class*="invite-to-team"]');
  118. if (specificButton && specificButton.offsetParent !== null) {
  119. log("Clicking invite button by class");
  120. specificButton.click();
  121. return true;
  122. }
  123.  
  124. // Add friend button
  125. const friendButtons = Array.from(document.querySelectorAll('a, button, .btn, [role="button"], div[class*="button"]'))
  126. .filter(el => {
  127. const text = (el.textContent || '').toLowerCase();
  128. const isVisible = el.offsetParent !== null;
  129. return isVisible && text.includes('add') && text.includes('friend');
  130. });
  131.  
  132. if (friendButtons.length > 0) {
  133. log("Clicking Add Friend button");
  134. friendButtons[0].click();
  135. return true;
  136. }
  137.  
  138. log("No buttons found");
  139. return false;
  140. } catch (e) {
  141. log(`Button error: ${e.message}`);
  142. return false;
  143. }
  144. }
  145.  
  146. // Process players sequentially
  147. function processPlayers(players) {
  148. let currentIndex = 0;
  149.  
  150. function processNext() {
  151. // Check if we're done
  152. if (currentIndex >= players.length) {
  153. log("Finished processing all players");
  154. setTimeout(() => window.location.reload(), randomDelay());
  155. return;
  156. }
  157.  
  158. const player = players[currentIndex];
  159.  
  160. // Skip if already processed
  161. if (processed.has(player)) {
  162. currentIndex++;
  163. processNext();
  164. return;
  165. }
  166.  
  167. log(`Processing player ${currentIndex + 1} of ${players.length}`);
  168. processed.add(player);
  169.  
  170. // Hover over player
  171. if (simulateHover(player)) {
  172. // Check for buttons after hover with a short delay using the existing checkInterval
  173. setTimeout(() => {
  174. const buttonFound = findAndClickButtons();
  175.  
  176. // Move to next player
  177. currentIndex++;
  178.  
  179. // If button was found, apply the full delay
  180. // If no button was found, move to the next player immediately
  181. if (buttonFound) {
  182. setTimeout(processNext, randomDelay());
  183. } else {
  184. setTimeout(processNext, 50); // tiny delay when no button's found
  185. }
  186. }, randomDelay); // Use randomDelay
  187. } else {
  188. // If hover failed, move to next without extra delay
  189. currentIndex++;
  190. setTimeout(processNext, randomDelay());
  191. }
  192. }
  193.  
  194. // Start processing
  195. processNext();
  196. }
  197.  
  198. // Enhanced race completion detection
  199. function detectRaceCompletion() {
  200. return (
  201. document.querySelector(".raceResults") ||
  202. document.querySelector("[class*='race-results']") ||
  203. document.querySelector(".race-results-container") ||
  204. document.querySelector("[class*='finished']") ||
  205. document.querySelector("[class*='complete']") ||
  206. document.querySelector("[class*='raceOver']") ||
  207. (document.querySelector("[class*='race-stats']") && document.querySelectorAll("[class*='player']").length > 1)
  208. );
  209. }
  210.  
  211. // Early race detection with simplified approach
  212. function monitorRace() {
  213. // Variables to track race state
  214. let raceInProgress = false;
  215. let raceCheckInterval = null;
  216.  
  217. // Function to detect race activity
  218. function checkRaceActivity() {
  219. // Indicators that a race is in progress
  220. const raceActive =
  221. document.querySelector("[class*='race-stats']") ||
  222. document.querySelector("[class*='racer-progress']") ||
  223. document.querySelector("[class*='typing-input']") ||
  224. document.querySelector("input[type='text'][class*='race']");
  225.  
  226. // If race wasn't in progress before but is now, mark it as started
  227. if (!raceInProgress && raceActive) {
  228. log("Race started");
  229. raceInProgress = true;
  230. }
  231.  
  232. // If race was in progress but is no longer active, it just ended
  233. else if (raceInProgress && !raceActive) {
  234. log("Race just ended - checking for results");
  235. raceInProgress = false;
  236.  
  237. // Race just ended - immediately check for results
  238. if (!processingStarted) {
  239. processingStarted = true;
  240. clearInterval(raceCheckInterval);
  241.  
  242. // Allow a brief moment for UI to update
  243. setTimeout(() => {
  244. startTeamInviter();
  245. }, 300);
  246. }
  247. }
  248. }
  249.  
  250. // Start monitoring for race activity
  251. raceCheckInterval = setInterval(checkRaceActivity, 250);
  252.  
  253. // Also start the normal results detection as a backup
  254. checkForRaceResults();
  255. }
  256.  
  257. // Start the team inviter process
  258. function startTeamInviter() {
  259. log("Starting team inviter process");
  260.  
  261. // Wait a moment for UI to stabilize
  262. setTimeout(() => {
  263. // First check if race is complete
  264. if (detectRaceCompletion()) {
  265. log("Race completion confirmed");
  266.  
  267. // Find players to process
  268. const players = findPlayerRows();
  269. if (players.length > 0) {
  270. log(`Found ${players.length} players to process`);
  271. processPlayers(players);
  272. } else {
  273. log("No players found, trying again in 500ms");
  274.  
  275. // Try again after a short delay
  276. setTimeout(() => {
  277. const playersRetry = findPlayerRows();
  278. if (playersRetry.length > 0) {
  279. processPlayers(playersRetry);
  280. } else {
  281. log("Still no players found, reloading page");
  282. window.location.reload();
  283. }
  284. }, 500);
  285. }
  286. } else {
  287. log("Race not complete yet, waiting for race results");
  288. // If no race completion found, fall back to normal detection
  289. processingStarted = false;
  290. }
  291. }, config.startupDelay);
  292. }
  293.  
  294. // Original check for race results - kept as fallback
  295. function checkForRaceResults() {
  296. let hasChecked = false;
  297.  
  298. const interval = setInterval(() => {
  299. if (hasChecked || processingStarted) {
  300. clearInterval(interval);
  301. return;
  302. }
  303.  
  304. const raceComplete = detectRaceCompletion();
  305.  
  306. if (raceComplete) {
  307. log("Race results detected through fallback method");
  308. hasChecked = true;
  309. processingStarted = true;
  310. clearInterval(interval);
  311.  
  312. // Wait for UI to stabilize
  313. setTimeout(() => {
  314. const players = findPlayerRows();
  315. if (players.length > 0) {
  316. processPlayers(players);
  317. } else {
  318. log("No players found, reloading");
  319. window.location.reload();
  320. }
  321. }, 500);
  322. }
  323. }, config.checkInterval);
  324.  
  325. // Safety timeout
  326. setTimeout(() => {
  327. if (!hasChecked && !processingStarted) {
  328. log("Safety reload triggered");
  329. window.location.reload();
  330. }
  331. }, 600000);
  332. }
  333.  
  334. // Initialize
  335. function init() {
  336. log("Team Inviter initialized");
  337. processingStarted = false;
  338.  
  339. // Start monitoring for race completion
  340. monitorRace();
  341. }
  342.  
  343. // Start when page is ready
  344. if (document.readyState !== "loading") {
  345. setTimeout(init, 300);
  346. } else {
  347. document.addEventListener("DOMContentLoaded", () => setTimeout(init, 300));
  348. }
  349. })();