Aternos Universal Plugin Loader

Universal loader for plugins, mods, and custom scripts on Aternos

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Aternos Universal Plugin Loader
// @namespace    http://tampermonkey.net/
// @version      3.0
// @description  Universal loader for plugins, mods, and custom scripts on Aternos
// @author       You
// @match        https://aternos.org/*
// @match        https://*.aternos.org/*
// @grant        GM.xmlHttpRequest
// @grant        GM.addStyle
// @grant        GM.notification
// @grant        GM.info
// @grant        GM.setValue
// @grant        GM.getValue
// @connect      *
// @run-at       document-end
// @license      MIT
// ==/UserScript==

class UniversalPluginLoader {
    constructor() {
        this.pluginsLoaded = 0;
        this.totalPlugins = 0;
        this.isInitialized = false;
        this.pluginRegistry = new Map();
        
        this.config = {
            timeout: 30000,
            retryAttempts: 2,
            retryDelay: 1000,
            sequentialLoading: true,
            enablePluginManager: true,
            autoUpdateCheck: false
        };
        
        // Enhanced plugin configuration with types
        this.plugins = Object.freeze([
            // === PLUGIN TYPES EXAMPLES ===
            
            // 1. JAVASCRIPT PLUGINS (Most common)
            {
                name: "Custom Chat Enhancer",
                url: "https://example.com/plugins/chat-enhancer.js",
                type: "script",
                enabled: true,
                description: "Enhances chat functionality"
            },
            
            // 2. CSS/THEME PLUGINS
            {
                name: "Dark Theme",
                url: "https://example.com/themes/dark-theme.css",
                type: "style",
                enabled: true,
                description: "Dark theme for Aternos"
            },
            
            // 3. JSON CONFIGURATION PLUGINS
            {
                name: "Server Settings",
                url: "https://example.com/configs/server-settings.json",
                type: "config",
                enabled: true,
                description: "Custom server configurations"
            },
            
            // 4. HTML WIDGET PLUGINS
            {
                name: "Status Widget",
                url: "https://example.com/widgets/status-widget.html",
                type: "widget",
                enabled: true,
                description: "Server status widget"
            },
            
            // 5. EXTERNAL LIBRARIES
            {
                name: "Utility Library",
                url: "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js",
                type: "library",
                enabled: true,
                description: "Utility functions library"
            },
            
            // 6. CUSTOM ELEMENTS
            {
                name: "Quick Actions",
                url: "https://example.com/plugins/quick-actions.js",
                type: "custom-element",
                enabled: true,
                description: "Adds quick action buttons"
            }
            
            // Add your actual plugin URLs here
            // Example for Minecraft server plugins:
            // {
            //     name: "EssentialsX Helper",
            //     url: "https://raw.githubusercontent.com/your-repo/essentials-helper/main/plugin.js",
            //     type: "script",
            //     enabled: true
            // }
        ]);
        
        this.bindMethods();
    }

    bindMethods() {
        this.init = this.init.bind(this);
        this.handleError = this.handleError.bind(this);
        this.handlePromiseRejection = this.handlePromiseRejection.bind(this);
        this.showStatusNotification = this.showStatusNotification.bind(this);
        this.togglePlugin = this.togglePlugin.bind(this);
        this.openPluginManager = this.openPluginManager.bind(this);
    }

    async init() {
        if (this.isInitialized) {
            this.log('Loader already initialized', 'warn');
            return;
        }

        try {
            this.isInitialized = true;
            await this.waitForDOMReady();
            await this.waitForAternosInterface();
            await this.loadPlugins();
            await this.injectPluginManager();
            this.setupErrorHandling();
            this.log('Universal Plugin Loader initialized successfully', 'success');
        } catch (error) {
            this.handleError('Initialization failed', error);
        }
    }

    waitForDOMReady() {
        return new Promise((resolve) => {
            if (document.readyState === 'loading') {
                document.addEventListener('DOMContentLoaded', resolve, { once: true });
            } else {
                resolve();
            }
        });
    }

    waitForAternosInterface() {
        const aternosSelectors = [
            '.server-status',
            '.navigation',
            '[class*="aternos"]',
            '[id*="aternos"]',
            'body'
        ];

        return new Promise((resolve) => {
            const checkInterface = () => {
                const isLoaded = aternosSelectors.some(selector => {
                    try {
                        return document.querySelector(selector);
                    } catch (e) {
                        return false;
                    }
                });
                
                if (isLoaded) {
                    resolve();
                } else {
                    setTimeout(checkInterface, 500);
                }
            };
            
            checkInterface();
            setTimeout(resolve, 10000);
        });
    }

    async loadPlugins() {
        if (!this.plugins || !Array.isArray(this.plugins)) {
            this.log('Invalid plugins configuration', 'error');
            return;
        }

        const enabledPlugins = this.plugins.filter(plugin => 
            plugin && plugin.enabled !== false && plugin.url
        );

        this.totalPlugins = enabledPlugins.length;
        
        if (this.totalPlugins === 0) {
            this.log('No enabled plugins configured', 'info');
            return;
        }

        this.log(`Loading ${this.totalPlugins} plugins...`, 'info');

        try {
            if (this.config.sequentialLoading) {
                for (const [index, plugin] of enabledPlugins.entries()) {
                    await this.loadSinglePlugin(plugin, index);
                    await this.delay(100);
                }
            } else {
                const concurrencyLimit = 3;
                for (let i = 0; i < enabledPlugins.length; i += concurrencyLimit) {
                    const chunk = enabledPlugins.slice(i, i + concurrencyLimit);
                    await Promise.allSettled(
                        chunk.map((plugin, index) => 
                            this.loadSinglePlugin(plugin, i + index)
                        )
                    );
                }
            }

            this.log(`Completed loading ${this.pluginsLoaded}/${this.totalPlugins} plugins`, 'success');
        } catch (error) {
            this.handleError('Plugin loading process failed', error);
        }
    }

    async loadSinglePlugin(plugin, index) {
        try {
            this.log(`Loading plugin ${index + 1}/${this.totalPlugins}: ${plugin.name}`, 'info');
            
            switch (plugin.type) {
                case 'script':
                case 'library':
                case 'custom-element':
                    await this.loadScriptPlugin(plugin);
                    break;
                    
                case 'style':
                case 'theme':
                    await this.loadStylePlugin(plugin);
                    break;
                    
                case 'config':
                    await this.loadConfigPlugin(plugin);
                    break;
                    
                case 'widget':
                    await this.loadWidgetPlugin(plugin);
                    break;
                    
                default:
                    // Auto-detect type by extension
                    await this.autoDetectAndLoad(plugin);
            }
            
            this.pluginRegistry.set(plugin.name, {
                ...plugin,
                loaded: true,
                loadTime: new Date()
            });
            
        } catch (error) {
            this.log(`Failed to load plugin: ${plugin.name} - ${error.message}`, 'error');
            this.pluginRegistry.set(plugin.name, {
                ...plugin,
                loaded: false,
                error: error.message
            });
        }
    }

    async loadScriptPlugin(plugin) {
        await this.loadScriptWithRetry(plugin.url);
        this.pluginsLoaded++;
        this.log(`✅ Script plugin loaded: ${plugin.name}`, 'success');
        
        // Initialize plugin if it has an init function
        if (typeof window[`init${plugin.name.replace(/\s+/g, '')}`] === 'function') {
            try {
                window[`init${plugin.name.replace(/\s+/g, '')}`]();
                this.log(`🔧 Plugin initialized: ${plugin.name}`, 'success');
            } catch (e) {
                this.log(`⚠️ Plugin init failed: ${plugin.name}`, 'warn');
            }
        }
    }

    async loadStylePlugin(plugin) {
        await this.loadCSSWithRetry(plugin.url);
        this.pluginsLoaded++;
        this.log(`🎨 Style plugin loaded: ${plugin.name}`, 'success');
    }

    async loadConfigPlugin(plugin) {
        const config = await this.fetchJSON(plugin.url);
        if (config && typeof config === 'object') {
            // Store config in global namespace
            window[`config${plugin.name.replace(/\s+/g, '')}`] = config;
            this.pluginsLoaded++;
            this.log(`⚙️ Config plugin loaded: ${plugin.name}`, 'success');
        } else {
            throw new Error('Invalid JSON configuration');
        }
    }

    async loadWidgetPlugin(plugin) {
        const html = await this.fetchText(plugin.url);
        const widgetContainer = document.createElement('div');
        widgetContainer.innerHTML = html;
        widgetContainer.setAttribute('data-plugin-widget', plugin.name);
        widgetContainer.style.cssText = 'position: fixed; z-index: 10000;';
        document.body.appendChild(widgetContainer);
        this.pluginsLoaded++;
        this.log(`🧩 Widget plugin loaded: ${plugin.name}`, 'success');
    }

    async autoDetectAndLoad(plugin) {
        const url = plugin.url.toLowerCase();
        if (url.endsWith('.js')) {
            await this.loadScriptPlugin(plugin);
        } else if (url.endsWith('.css')) {
            await this.loadStylePlugin(plugin);
        } else if (url.endsWith('.json')) {
            await this.loadConfigPlugin(plugin);
        } else if (url.endsWith('.html')) {
            await this.loadWidgetPlugin(plugin);
        } else {
            // Default to script
            await this.loadScriptPlugin(plugin);
        }
    }

    async loadScriptWithRetry(url, attempt = 0) {
        try {
            await this.loadScript(url);
        } catch (error) {
            if (attempt < this.config.retryAttempts) {
                this.log(`Retrying script load (${attempt + 1}/${this.config.retryAttempts}): ${url}`, 'warn');
                await this.delay(this.config.retryDelay * (attempt + 1));
                return this.loadScriptWithRetry(url, attempt + 1);
            }
            throw error;
        }
    }

    async loadCSSWithRetry(url, attempt = 0) {
        try {
            await this.loadCSS(url);
        } catch (error) {
            if (attempt < this.config.retryAttempts) {
                this.log(`Retrying CSS load (${attempt + 1}/${this.config.retryAttempts}): ${url}`, 'warn');
                await this.delay(this.config.retryDelay * (attempt + 1));
                return this.loadCSSWithRetry(url, attempt + 1);
            }
            throw error;
        }
    }

    loadScript(url) {
        return new Promise((resolve, reject) => {
            if (typeof GM?.xmlHttpRequest !== 'function') {
                reject(new Error('GM.xmlHttpRequest is not available'));
                return;
            }

            GM.xmlHttpRequest({
                method: 'GET',
                url: url,
                timeout: this.config.timeout,
                responseType: 'text',
                onload: (response) => {
                    if (response.status >= 200 && response.status < 300) {
                        try {
                            const script = document.createElement('script');
                            script.textContent = response.responseText;
                            script.setAttribute('data-plugin-url', url);
                            script.setAttribute('data-loaded-by', 'UniversalPluginLoader');
                            script.setAttribute('type', 'text/javascript');
                            
                            script.onerror = (e) => {
                                this.log(`Script execution error: ${url}`, 'error');
                            };
                            
                            document.head.appendChild(script);
                            resolve(response);
                        } catch (e) {
                            reject(new Error(`Script execution failed: ${e.message}`));
                        }
                    } else {
                        reject(new Error(`HTTP ${response.status}: ${response.statusText}`));
                    }
                },
                onerror: (error) => reject(new Error(`Network error: ${error}`)),
                ontimeout: () => reject(new Error('Request timeout'))
            });
        });
    }

    loadCSS(url) {
        return new Promise((resolve, reject) => {
            if (typeof GM?.xmlHttpRequest !== 'function') {
                reject(new Error('GM.xmlHttpRequest is not available'));
                return;
            }

            GM.xmlHttpRequest({
                method: 'GET',
                url: url,
                timeout: this.config.timeout,
                responseType: 'text',
                onload: (response) => {
                    if (response.status >= 200 && response.status < 300) {
                        try {
                            if (typeof GM.addStyle === 'function') {
                                GM.addStyle(response.responseText);
                            } else {
                                const style = document.createElement('style');
                                style.textContent = response.responseText;
                                style.setAttribute('data-plugin-url', url);
                                document.head.appendChild(style);
                            }
                            resolve(response);
                        } catch (e) {
                            reject(new Error(`CSS application failed: ${e.message}`));
                        }
                    } else {
                        reject(new Error(`HTTP ${response.status}: ${response.statusText}`));
                    }
                },
                onerror: (error) => reject(new Error(`Network error: ${error}`)),
                ontimeout: () => reject(new Error('Request timeout'))
            });
        });
    }

    async fetchJSON(url) {
        const response = await this.fetchText(url);
        return JSON.parse(response);
    }

    fetchText(url) {
        return new Promise((resolve, reject) => {
            GM.xmlHttpRequest({
                method: 'GET',
                url: url,
                timeout: this.config.timeout,
                onload: (response) => {
                    if (response.status >= 200 && response.status < 300) {
                        resolve(response.responseText);
                    } else {
                        reject(new Error(`HTTP ${response.status}: ${response.statusText}`));
                    }
                },
                onerror: reject,
                ontimeout: () => reject(new Error('Request timeout'))
            });
        });
    }

    // Plugin Manager UI
    async injectPluginManager() {
        if (!this.config.enablePluginManager) return;

        try {
            this.createStatusIndicator();
            this.createPluginManagerPanel();
            this.log('Plugin manager injected successfully', 'success');
        } catch (error) {
            this.handleError('Failed to inject plugin manager', error);
        }
    }

    createStatusIndicator() {
        if (!document.body) {
            this.log('Document body not available for status indicator', 'warn');
            return;
        }

        try {
            const indicator = document.createElement('div');
            indicator.innerHTML = `
                <div class="universal-plugin-indicator">
                    <div class="plugin-stats">
                        <span class="plugin-icon">🔌</span>
                        <span class="plugin-count">${this.pluginsLoaded}/${this.totalPlugins}</span>
                    </div>
                    <div class="plugin-status">PLUGINS ACTIVE</div>
                </div>
            `;

            const style = `
                .universal-plugin-indicator {
                    position: fixed;
                    top: 20px;
                    right: 20px;
                    z-index: 10000;
                    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                    color: white;
                    padding: 12px 16px;
                    border-radius: 12px;
                    font-family: 'Segoe UI', system-ui, sans-serif;
                    font-size: 12px;
                    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
                    backdrop-filter: blur(10px);
                    border: 1px solid rgba(255,255,255,0.2);
                    cursor: pointer;
                    transition: all 0.3s ease;
                    user-select: none;
                }
                
                .universal-plugin-indicator:hover {
                    transform: translateY(-2px);
                    box-shadow: 0 6px 20px rgba(0,0,0,0.2);
                }
                
                .plugin-stats {
                    display: flex;
                    align-items: center;
                    gap: 8px;
                    margin-bottom: 4px;
                }
                
                .plugin-icon {
                    font-size: 14px;
                }
                
                .plugin-count {
                    font-weight: 600;
                    font-size: 14px;
                }
                
                .plugin-status {
                    opacity: 0.9;
                    font-size: 10px;
                    letter-spacing: 0.5px;
                }
            `;

            try {
                if (typeof GM.addStyle === 'function') {
                    GM.addStyle(style);
                } else {
                    const styleElement = document.createElement('style');
                    styleElement.textContent = style;
                    document.head.appendChild(styleElement);
                }
            } catch (styleError) {
                this.log('Failed to add status indicator styles', 'warn');
            }

            document.body.appendChild(indicator);
            indicator.addEventListener('click', this.openPluginManager);
            
        } catch (error) {
            this.handleError('Failed to create status indicator', error);
        }
    }

    createPluginManagerPanel() {
        // Advanced plugin manager UI
        const panel = document.createElement('div');
        panel.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            border-radius: 12px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.3);
            z-index: 10001;
            padding: 20px;
            min-width: 500px;
            max-height: 80vh;
            overflow-y: auto;
            font-family: 'Segoe UI', sans-serif;
            display: none;
        `;

        panel.innerHTML = `
            <div class="plugin-manager">
                <h2 style="margin: 0 0 20px 0; color: #333;">🔌 Plugin Manager</h2>
                <div class="plugins-list">
                    ${this.plugins.map(plugin => `
                        <div class="plugin-item" data-plugin-name="${plugin.name}">
                            <div class="plugin-info">
                                <h3>${plugin.name}</h3>
                                <p>${plugin.description || 'No description'}</p>
                                <small>Type: ${plugin.type} | URL: ${plugin.url}</small>
                            </div>
                            <div class="plugin-controls">
                                <button class="toggle-btn ${plugin.enabled ? 'enabled' : 'disabled'}">
                                    ${plugin.enabled ? 'Disable' : 'Enable'}
                                </button>
                                <span class="status ${this.pluginRegistry.get(plugin.name)?.loaded ? 'loaded' : 'failed'}">
                                    ${this.pluginRegistry.get(plugin.name)?.loaded ? '✅' : '❌'}
                                </span>
                            </div>
                        </div>
                    `).join('')}
                </div>
                <div style="margin-top: 20px; text-align: right;">
                    <button id="close-plugin-manager" style="padding: 8px 16px; background: #667eea; color: white; border: none; border-radius: 6px; cursor: pointer;">
                        Close
                    </button>
                </div>
            </div>
        `;

        document.body.appendChild(panel);

        // Add panel styles
        const panelStyle = `
            .plugin-manager {
                color: #333;
            }
            .plugins-list {
                max-height: 400px;
                overflow-y: auto;
            }
            .plugin-item {
                display: flex;
                justify-content: space-between;
                align-items: center;
                padding: 12px;
                border: 1px solid #ddd;
                border-radius: 8px;
                margin-bottom: 8px;
                background: #f9f9f9;
            }
            .plugin-info h3 {
                margin: 0 0 5px 0;
                font-size: 14px;
            }
            .plugin-info p {
                margin: 0 0 5px 0;
                font-size: 12px;
                color: #666;
            }
            .plugin-info small {
                color: #999;
            }
            .toggle-btn {
                padding: 6px 12px;
                border: none;
                border-radius: 4px;
                cursor: pointer;
                font-size: 12px;
            }
            .toggle-btn.enabled {
                background: #4CAF50;
                color: white;
            }
            .toggle-btn.disabled {
                background: #f44336;
                color: white;
            }
        `;

        GM.addStyle(panelStyle);

        // Event listeners
        panel.querySelector('#close-plugin-manager').addEventListener('click', () => {
            panel.style.display = 'none';
        });

        // Toggle buttons
        panel.querySelectorAll('.toggle-btn').forEach(btn => {
            btn.addEventListener('click', (e) => {
                const pluginName = e.target.closest('.plugin-item').dataset.pluginName;
                this.togglePlugin(pluginName);
            });
        });
    }

    openPluginManager() {
        const panel = document.querySelector('.plugin-manager')?.closest('div');
        if (panel) {
            panel.style.display = 'block';
        }
    }

    togglePlugin(pluginName) {
        this.log(`Toggling plugin: ${pluginName}`, 'info');
        // Implementation for dynamically enabling/disabling plugins
        // This would require more advanced state management
    }

    // Utility methods
    isValidUrl(string) {
        try {
            new URL(string);
            return true;
        } catch (_) {
            return false;
        }
    }

    delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    log(message, type = 'info') {
        const emojis = { info: 'ℹ️', success: '✅', warn: '⚠️', error: '❌' };
        const emoji = emojis[type] || '📝';
        const styles = { info: 'color: blue;', success: 'color: green;', warn: 'color: orange;', error: 'color: red;' };
        console.log(`%c${emoji} Aternos Plugin Loader: ${message}`, styles[type] || '');
    }

    handleError(context, error) {
        this.log(`${context}: ${error?.message || error}`, 'error');
        console.error(context, error);
    }

    handlePromiseRejection(event) {
        this.log(`Unhandled promise rejection: ${event.reason}`, 'error');
    }

    setupErrorHandling() {
        window.addEventListener('error', (event) => {
            if (event.target?.tagName === 'SCRIPT' && event.target.hasAttribute('data-plugin-url')) {
                this.log(`Runtime error in plugin: ${event.target.getAttribute('data-plugin-url')}`, 'error');
            }
        });

        window.addEventListener('unhandledrejection', this.handlePromiseRejection);
    }
}

// Initialize the universal plugin loader
(async function() {
    'use strict';
    
    if (window.aternosPluginLoaderInstance) {
        console.log('Aternos Plugin Loader already running');
        return;
    }
    
    try {
        if (document.readyState === 'loading') {
            await new Promise(resolve => 
                document.addEventListener('DOMContentLoaded', resolve, { once: true })
            );
        }
        
        await new Promise(resolve => setTimeout(resolve, 100));
        
        const loader = new UniversalPluginLoader();
        window.aternosPluginLoaderInstance = loader;
        await loader.init();
        
    } catch (error) {
        console.error('🚨 Aternos Universal Plugin Loader: Critical initialization error', error);
    }
})();