Nitro Type Auto Invite To Team

Automatically hits the invite to team button if you are an officer and the add as a friend button after a race.

目前為 2025-03-09 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Nitro Type Auto Invite To Team
// @namespace    https://www.nitrotype.com/
// @version      1.5
// @description  Automatically hits the invite to team button if you are an officer and the add as a friend button after a race.
// @author       Your Name
// @match        https://www.nitrotype.com/race/*
// @match        https://www.nitrotype.com/race
// @match        https://*.nitrotype.com/race
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Configuration 
    const config = {
        minDelay: 50, // Minimum delay in milliseconds
        maxDelay: 150, // Maximum delay in milliseconds
        debug: true, // Enable console logging for debugging
        noButtonsDelay: true, // Skip second delay when no buttons are found
        checkInterval: 50, // Reduced: How often to check for players (milliseconds)
        startupDelay: 100, // Reduced but not too aggressive
        earlyDetection: true, // Enable trying to detect race completion earlier
    };

    // 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;
    }

    // Main functions
    function findPlayerRows() {
        const selectors = [
            '.player-row',
            '[class*="player-container"]',
            '[class*="racer"]',
            '[id*="racer"]',
            '[id*="player"]',
            '[class*="player"]',
            '.race-results-player'
        ];

        for (const selector of selectors) {
            const elements = document.querySelectorAll(selector);
            if (elements.length > 0) {
                log(`Found ${elements.length} player rows using selector: ${selector}`);
                return Array.from(elements);
            }
        }

        log("No player rows found with any selector");
        return [];
    }

    function simulateHover(element) {
        try {
            element.dispatchEvent(new MouseEvent('mouseenter', {bubbles: true, cancelable: true}));
            element.dispatchEvent(new MouseEvent('mouseover', {bubbles: true, cancelable: true}));
            return true;
        } catch (e) {
            log(`Error simulating hover: ${e.message}`);
            return false;
        }
    }

    function findAndClickButtons() {
        try {
            // Method 1: Try to find team invite button by text content
            const visibleInviteButtons = Array.from(document.querySelectorAll('a, button, .btn, [role="button"], div[class*="button"]'))
                .filter(el => {
                    const text = (el.textContent || '').toLowerCase();
                    const isVisible = el.offsetParent !== null;
                    const hasInviteText = text.includes('invite') && text.includes('team');
                    return isVisible && hasInviteText;
                });

            if (visibleInviteButtons.length > 0) {
                log("Clicking invite button found by text");
                visibleInviteButtons[0].click();
                return true;
            }

            // Method 2: Try to find team invite button by class name
            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 found by class");
                specificButton.click();
                return true;
            }

            // Method 3: If no team invite button found, try Add As A 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("No team invite button found. Clicking Add As Friend instead");
                friendButtons[0].click();
                return true;
            }

            log("No invite or add friend buttons found");
            return false;
        } catch (e) {
            log(`Error finding/clicking buttons: ${e.message}`);
            return false;
        }
    }

    function reloadPage() {
        try {
            log("Reloading the page");
            document.body.dispatchEvent(new MouseEvent('mouseout', {bubbles: true, cancelable: true}));
            window.location.reload();
            return true;
        } catch (e) {
            log(`Error reloading page: ${e.message}`);
            return false;
        }
    }

    // Keeping your original processing function intact
    function processPlayerRows(rows, index = 0) {
        if (index >= rows.length) {
            log("Finished processing all player rows");
            setTimeout(reloadPage, randomDelay());
            return;
        }

        const row = rows[index];
        log(`Processing player row ${index + 1} of ${rows.length}`);

        // Hover over player row
        if (simulateHover(row)) {
            // Wait for button to appear, then try to click it
            setTimeout(() => {
                const buttonFound = findAndClickButtons();

                if (buttonFound || !config.noButtonsDelay) {
                    // If button found or we're using original behavior,
                    // continue to next player after another delay
                    setTimeout(() => {
                        processPlayerRows(rows, index + 1);
                    }, randomDelay());
                } else {
                    // Skip the second delay if no buttons found and noButtonsDelay is true
                    log("No buttons found, skipping second delay");
                    processPlayerRows(rows, index + 1);
                }
            }, randomDelay());
        } else {
            // If hover fails, continue to next player
            setTimeout(() => {
                processPlayerRows(rows, index + 1);
            }, randomDelay());
        }
    }

    // Race detection - multiple methods to catch race completion early
    function detectRaceCompletion() {
        // Try to identify race completion through various means
        return (
            document.querySelector(".raceResults") ||
            document.querySelector("[class*='race-results']") ||
            document.querySelector(".race-results-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 - try to detect race state changes
    function monitorRaceStatus() {
        if (!config.earlyDetection) return;

        // Track race state
        let isRacing = false;
        let completionDetected = false;

        // Look for indicators of active race
        const raceIndicators = [
            () => document.querySelector("[class*='race-stats']"),
            () => document.querySelector("[class*='racer-progress']"),
            () => document.querySelector("[class*='typing-input']"),
            () => document.querySelector("input[type='text'][class*='race']")
        ];

        // Check race status periodically
        const statusInterval = setInterval(() => {
            // Check if we're in an active race
            const activeRace = raceIndicators.some(check => check());

            // Race transition detection
            if (activeRace && !isRacing) {
                log("Race started");
                isRacing = true;
                completionDetected = false;
            }
            else if (!activeRace && isRacing) {
                log("Race appears to have ended");
                isRacing = false;
                completionDetected = true;

                // Race just ended - start looking for results immediately
                clearInterval(statusInterval);
                checkForPlayers();
            }
        }, 100);

        // Also run the normal detection as backup
        checkForPlayers();
    }

    // Faster player detection - improved but maintains your basic structure
    function checkForPlayers() {
        let hasRunOnce = false;
        let retryCount = 0;
        const maxRetries = 30;

        const checkInterval = setInterval(() => {
            if (hasRunOnce) return;

            if (detectRaceCompletion()) {
                const playerRows = findPlayerRows();
                if (playerRows.length > 0) {
                    log("Found players, starting team inviter");
                    hasRunOnce = true;
                    clearInterval(checkInterval);

                    // Start processing after configured delay - keeping your process flow
                    setTimeout(() => {
                        processPlayerRows(playerRows);
                    }, config.startupDelay);
                }
            } else {
                retryCount++;
                if (retryCount >= maxRetries) {
                    log(`Failed to find race completion after ${maxRetries} attempts`);
                    clearInterval(checkInterval);

                    // Wait for race results as a fallback (original method)
                    checkForRaceResults();
                }
            }
        }, config.checkInterval);
    }

    // Original race results detection as fallback
    function checkForRaceResults() {
        log("Falling back to race results detection");
        const resultsCheckInterval = setInterval(() => {
            if (document.querySelector(".raceResults") ||
                document.querySelector("[class*='race-results']") ||
                document.querySelector(".race-results-container")) {

                log("Race results detected, starting team inviter");
                clearInterval(resultsCheckInterval);

                // Wait a moment for the UI to stabilize
                setTimeout(() => {
                    const playerRows = findPlayerRows();
                    if (playerRows.length > 0) {
                        processPlayerRows(playerRows);
                    } else {
                        log("No player rows found, reloading page");
                        reloadPage();
                    }
                }, 500); // Reduced from 1000ms but not too aggressive
            }
        }, 200); // Reduced from 500ms
    }

    // Safety fallback to refresh if stuck
    function setupSafetyRefresh() {
        setTimeout(() => {
            log("Safety refresh triggered");
            window.location.reload();
        }, 120000); // 2 minutes timeout
    }

    // Initialize
    function initialize() {
        log("Team Inviter script initialized");
        // Start with the improved race detection
        monitorRaceStatus();
        setupSafetyRefresh();
    }

    // Start the script faster
    if (document.readyState === "complete" || document.readyState === "interactive") {
        setTimeout(initialize, 200); // Reduced but not too aggressive
    } else {
        document.addEventListener("DOMContentLoaded", initialize);
    }
})();