您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Auto-start when offline; auto-extend when online with 0 players and <60s left; dismiss notifications
// ==UserScript== // @name Aternos Auto Start & Extend // @namespace https://aternos.org/ // @version 1.1.0 // @description Auto-start when offline; auto-extend when online with 0 players and <60s left; dismiss notifications // @match https://aternos.org/server // @match https://aternos.org/server/* // @run-at document-idle // @grant none // ==/UserScript== (function () { 'use strict'; const SELECTORS = { offlineContainer: 'div.status.offline', onlineContainer: 'div.status.online', startButton: '#start', players: '.live-status-box-value.js-players', countdown: '.server-end-countdown', extendButton: 'button.btn.btn-tiny.btn-success.server-extend-end', notificationHeader: 'header span.alert-title', notificationClose: 'i.fa-times.fa-solid', }; let lastStartClickMs = 0; let lastExtendClickMs = 0; let lastNotificationClickMs = 0; let isRunning = true; // Cleanup function to prevent memory leaks function cleanup() { isRunning = false; if (window.aternosObserver) { window.aternosObserver.disconnect(); delete window.aternosObserver; } if (window.aternosIntervals) { window.aternosIntervals.forEach(clearInterval); window.aternosIntervals = []; } } // Cleanup on page unload window.addEventListener('beforeunload', cleanup); function isVisible(el) { if (!el) return false; const style = window.getComputedStyle(el); return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0'; } function queryText(selector) { const el = document.querySelector(selector); return el ? el.textContent.trim() : ''; } function isOffline() { return !!document.querySelector(SELECTORS.offlineContainer); } function isOnline() { return !!document.querySelector(SELECTORS.onlineContainer); } function playersAreZero() { const txt = queryText(SELECTORS.players); // e.g., "0/20" return /^0\s*\/\s*\d+$/i.test(txt); } function getCountdownSeconds() { const txt = queryText(SELECTORS.countdown); // e.g., "5:22" or "59s" if (!txt) return null; const trimmed = txt.replace(/\s+/g, ''); if (/^\d{1,2}:\d{2}$/.test(trimmed)) { const [m, s] = trimmed.split(':').map(Number); if (Number.isFinite(m) && Number.isFinite(s)) return m * 60 + s; } else if (/^\d{1,3}s$/i.test(trimmed)) { const s = Number(trimmed.slice(0, -1)); if (Number.isFinite(s)) return s; } return null; } function hasNotification() { return !!document.querySelector(SELECTORS.notificationHeader); } function clickIfAvailable(selector, lastClickRef, minIntervalMs) { if (!isRunning) return false; const now = Date.now(); if (now - lastClickRef.value < minIntervalMs) return false; const btn = document.querySelector(selector); if (!btn || !isVisible(btn) || btn.disabled) return false; btn.click(); lastClickRef.value = now; return true; } function tryStartIfOffline() { if (!isRunning) return; if (!isOffline()) return; clickIfAvailable(SELECTORS.startButton, { get value() { return lastStartClickMs; }, set value(v) { lastStartClickMs = v; } }, 10000); } function tryExtendIfEndingSoon() { if (!isRunning) return; if (!isOnline()) return; if (!playersAreZero()) return; const seconds = getCountdownSeconds(); if (seconds === null) return; // If <= 59 seconds remaining, extend if (seconds <= 59) { clickIfAvailable(SELECTORS.extendButton, { get value() { return lastExtendClickMs; }, set value(v) { lastExtendClickMs = v; } }, 5000); } } function tryDismissNotification() { if (!isRunning) return; if (!hasNotification()) return; clickIfAvailable(SELECTORS.notificationClose, { get value() { return lastNotificationClickMs; }, set value(v) { lastNotificationClickMs = v; } }, 2000); } // Initialize intervals array for cleanup window.aternosIntervals = []; // Initial delay to allow SPA content to render setTimeout(() => { if (!isRunning) return; // Poll for offline -> start (every 5 seconds) window.aternosIntervals.push(setInterval(tryStartIfOffline, 5000)); // Poll for extend condition (every second) window.aternosIntervals.push(setInterval(tryExtendIfEndingSoon, 1000)); // Poll for notifications (every 2 seconds) window.aternosIntervals.push(setInterval(tryDismissNotification, 2000)); // Optimized mutation observer with throttling let mutationTimeout; const throttledMutationHandler = () => { if (mutationTimeout) return; mutationTimeout = setTimeout(() => { if (isRunning) { tryStartIfOffline(); tryExtendIfEndingSoon(); tryDismissNotification(); } mutationTimeout = null; }, 1000); // Throttle to max once per second }; // More targeted mutation observer to reduce memory usage window.aternosObserver = new MutationObserver((mutations) => { // Only react to significant changes const hasRelevantChanges = mutations.some(mutation => { if (mutation.type === 'childList') { // Check if status containers, buttons, or notifications were added/removed return Array.from(mutation.addedNodes).some(node => node.nodeType === 1 && ( node.matches && ( node.matches('.status') || node.matches('#start') || node.matches('.server-extend-end') || node.matches('header span.alert-title') ) ) ); } return false; }); if (hasRelevantChanges) { throttledMutationHandler(); } }); // Observe only the main content area instead of entire document const mainContent = document.querySelector('#main') || document.body; if (mainContent) { window.aternosObserver.observe(mainContent, { childList: true, subtree: true, attributes: false, // Don't watch attribute changes to reduce memory usage characterData: false // Don't watch text changes to reduce memory usage }); } // Initial check tryStartIfOffline(); tryExtendIfEndingSoon(); tryDismissNotification(); }, 1500); // Additional cleanup on visibility change (when tab becomes inactive) document.addEventListener('visibilitychange', () => { if (document.hidden) { // Pause some operations when tab is not visible isRunning = false; } else { // Resume when tab becomes visible again isRunning = true; } }); })();