Automatically hits the invite to team button if you are an officer and the add as a friend button after a race.
目前為
// ==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);
}
})();