Greasy Fork is available in English.
Automatically invites users to your team or adds as friend after a race
// ==UserScript== // @name Nitro Type Auto Invite // @namespace https://www.nitrotype.com/ // @version 2.8.3 // @description Automatically invites users to your team or adds as friend after a race // @author Isaac Weber // @match https://www.nitrotype.com/race/* // @match https://www.nitrotype.com/race // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; // Simple configuration const config = { minDelay: 100, maxDelay: 200, debug: true, checkInterval: 200, startupDelay: 250 }; // Processed players tracking const processed = new Set(); // Track if processing has started to avoid duplicate detection let processingStarted = false; // Helper functions function log(message) { if (config.debug) console.log(`[Team Inviter] ${message}`); } function randomDelay() { return Math.floor(Math.random() * (config.maxDelay - config.minDelay + 1)) + config.minDelay; } // Find player rows using various selectors function findPlayerRows() { const selectors = [ '.player-row', '[class*="player-container"]', '[class*="racer"]', '[id*="racer"]', '[id*="player"]', '[class*="player"]', '.race-r###lts-player' ]; for (const selector of selectors) { const elements = document.querySelectorAll(selector); if (elements.length > 0) { log(`Found ${elements.length} players using selector: ${selector}`); return Array.from(elements); } } log("No players found"); return []; } // Reliable hover simulation function simulateHover(element) { try { const rect = element.getBoundingClientRect(); const centerX = rect.left + (rect.width / 2); const centerY = rect.top + (rect.height / 2); // Clear existing hovers document.dispatchEvent(new MouseEvent('mouseout', { bubbles: true, cancelable: true })); // Hover events element.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true, cancelable: true, clientX: centerX, clientY: centerY })); element.dispatchEvent(new MouseEvent('mouseover', { bubbles: true, cancelable: true, clientX: centerX, clientY: centerY })); return true; } catch (e) { log(`Hover error: ${e.message}`); return false; } } // Find and click relevant buttons function findAndClickButtons() { try { // Team invite button by text const inviteButtons = Array.from(document.querySelectorAll('a, button, .btn, [role="button"], div[class*="button"]')) .filter(el => { const text = (el.textContent || '').toLowerCase(); const isVisible = el.offsetParent !== null; return isVisible && text.includes('invite') && text.includes('team'); }); if (inviteButtons.length > 0) { log("Clicking team invite button"); inviteButtons[0].click(); return true; } // Team invite button by class const specificButton = document.querySelector('a[class*="invite-team"], a[class*="team-invite"], [class*="invite-to-team"]'); if (specificButton && specificButton.offsetParent !== null) { log("Clicking invite button by class"); specificButton.click(); return true; } // Add friend button const friendButtons = Array.from(document.querySelectorAll('a, button, .btn, [role="button"], div[class*="button"]')) .filter(el => { const text = (el.textContent || '').toLowerCase(); const isVisible = el.offsetParent !== null; return isVisible && text.includes('add') && text.includes('friend'); }); if (friendButtons.length > 0) { log("Clicking Add Friend button"); friendButtons[0].click(); return true; } log("No buttons found"); return false; } catch (e) { log(`Button error: ${e.message}`); return false; } } // Process players sequentially function processPlayers(players) { let currentIndex = 0; function processNext() { // Check if we're done if (currentIndex >= players.length) { log("Finished processing all players"); setTimeout(() => window.location.reload(), randomDelay()); return; } const player = players[currentIndex]; // Skip if already processed if (processed.has(player)) { currentIndex++; processNext(); return; } log(`Processing player ${currentIndex + 1} of ${players.length}`); processed.add(player); // Hover over player if (simulateHover(player)) { // Check for buttons after hover with a short delay using the existing checkInterval setTimeout(() => { const buttonFound = findAndClickButtons(); // Move to next player currentIndex++; // If button was found, apply the full delay // If no button was found, move to the next player immediately if (buttonFound) { setTimeout(processNext, randomDelay()); } else { setTimeout(processNext, 20); // tiny delay when no button's found } }, randomDelay); // Use randomDelay } else { // If hover failed, move to next without extra delay currentIndex++; setTimeout(processNext, randomDelay()); } } // Start processing processNext(); } // Enhanced race completion detection function detectRaceCompletion() { return ( document.querySelector(".raceR###lts") || document.querySelector("[class*='race-r###lts']") || document.querySelector(".race-r###lts-container") || document.querySelector("[class*='finished']") || document.querySelector("[class*='complete']") || document.querySelector("[class*='raceOver']") || (document.querySelector("[class*='race-stats']") && document.querySelectorAll("[class*='player']").length > 1) ); } // Early race detection with simplified approach function monitorRace() { // Variables to track race state let raceInProgress = false; let raceCheckInterval = null; // Function to detect race activity function checkRaceActivity() { // Indicators that a race is in progress const raceActive = document.querySelector("[class*='race-stats']") || document.querySelector("[class*='racer-progress']") || document.querySelector("[class*='typing-input']") || document.querySelector("input[type='text'][class*='race']"); // If race wasn't in progress before but is now, mark it as started if (!raceInProgress && raceActive) { log("Race started"); raceInProgress = true; } // If race was in progress but is no longer active, it just ended else if (raceInProgress && !raceActive) { log("Race just ended - checking for r###lts"); raceInProgress = false; // Race just ended - immediately check for r###lts if (!processingStarted) { processingStarted = true; clearInterval(raceCheckInterval); // Allow a brief moment for UI to update setTimeout(() => { startTeamInviter(); }, 300); } } } // Start monitoring for race activity raceCheckInterval = setInterval(checkRaceActivity, 250); // Also start the normal r###lts detection as a backup checkForRaceR###lts(); } // Start the team inviter process function startTeamInviter() { log("Starting team inviter process"); // Wait a moment for UI to stabilize setTimeout(() => { // First check if race is complete if (detectRaceCompletion()) { log("Race completion confirmed"); // Find players to process const players = findPlayerRows(); if (players.length > 0) { log(`Found ${players.length} players to process`); processPlayers(players); } else { log("No players found, trying again in 500ms"); // Try again after a short delay setTimeout(() => { const playersRetry = findPlayerRows(); if (playersRetry.length > 0) { processPlayers(playersRetry); } else { log("Still no players found, reloading page"); window.location.reload(); } }, 500); } } else { log("Race not complete yet, waiting for race r###lts"); // If no race completion found, fall back to normal detection processingStarted = false; } }, config.startupDelay); } // Original check for race r###lts - kept as fallback function checkForRaceR###lts() { let hasChecked = false; const interval = setInterval(() => { if (hasChecked || processingStarted) { clearInterval(interval); return; } const raceComplete = detectRaceCompletion(); if (raceComplete) { log("Race r###lts detected through fallback method"); hasChecked = true; processingStarted = true; clearInterval(interval); // Wait for UI to stabilize setTimeout(() => { const players = findPlayerRows(); if (players.length > 0) { processPlayers(players); } else { log("No players found, reloading"); window.location.reload(); } }, 500); } }, config.checkInterval); // Safety timeout setTimeout(() => { if (!hasChecked && !processingStarted) { log("Safety reload triggered"); window.location.reload(); } }, 600000); } // Initialize function init() { log("Team Inviter initialized"); processingStarted = false; // Start monitoring for race completion monitorRace(); } // Start when page is ready if (document.readyState !== "loading") { setTimeout(init, 300); } else { document.addEventListener("DOMContentLoaded", () => setTimeout(init, 300)); } })();