TriX Executor

A Comprehensive Script Executor for Territorial.io with multi-tab support, automation, and a user-friendly interface.

当前为 2025-07-02 提交的版本,查看 最新版本

// ==UserScript==
// @name         TriX Executor
// @namespace    https://github.com/YourUsername/TriX-Executor
// @version      1.0.0
// @description  A Comprehensive Script Executor for Territorial.io with multi-tab support, automation, and a user-friendly interface.
// @author       You
// @match        *://territorial.io/*
// @icon         https://i.postimg.cc/0NkRZxDm/image.png
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_listValues
// @grant        GM_deleteValue
// @grant        GM_addValueChangeListener
// @grant        GM.xmlHttpRequest
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

/*
 *   _____  ____  _   __  ___________
 *  |_   _||_  _|| | / / |  _  | ___ \
 *    | |    | | | |/ /  | |/' | |_/ /
 *    | |    | | |    \  |  /| |  __/
 *   _| |_  _| |_| |\  \ \ |_/ / |
 *   \___/  \___/\_| \_/  \___/\_|
 *
 *   TriX Executor - v1.0.0
 *   Logo: https://i.postimg.cc/0NkRZxDm/image.png
 *
 *   An unparalleled level of control and automation for Territorial.io.
 */

(function() {
    'use strict';

    // --- Configuration & Constants ---
    const SCRIPT_PREFIX = 'trix_script_';
    const BROADCAST_CHANNEL = 'trix_broadcast_channel';
    const TAB_ID = `tab_${Date.now().toString(36)}_${Math.random().toString(36).substring(2)}`;

    // --- UI Module ---
    // Manages the creation, styling, and interaction of the executor's UI panel.
    const UI = {
        isDragging: false,
        dragOffsetX: 0,
        dragOffsetY: 0,

        init() {
            this.injectCSS();
            this.injectHTML();
            this.attachEventListeners();
            this.showTab('executor'); // Default tab
            ScriptManager.populateScriptSelector();
            this.log('TriX Executor v1.0.0 initialized.');
            this.log(`Tab ID: ${TAB_ID}`);
        },

        injectCSS() {
            GM_addStyle(`
                #trix-container {
                    position: fixed;
                    top: 20px;
                    left: 20px;
                    width: 500px;
                    max-width: 90vw;
                    background-color: #282c34;
                    border: 1px solid #4f5b66;
                    border-radius: 8px;
                    box-shadow: 0 5px 15px rgba(0,0,0,0.5);
                    z-index: 99999;
                    color: #abb2bf;
                    font-family: 'Menlo', 'Monaco', 'Consolas', monospace;
                    font-size: 14px;
                    display: flex;
                    flex-direction: column;
                    resize: both;
                    overflow: hidden;
                    min-width: 350px;
                    min-height: 250px;
                }
                #trix-header {
                    background-color: #21252b;
                    padding: 8px 12px;
                    cursor: move;
                    user-select: none;
                    display: flex;
                    align-items: center;
                    justify-content: space-between;
                    border-bottom: 1px solid #4f5b66;
                }
                #trix-header-title {
                    display: flex;
                    align-items: center;
                    font-weight: bold;
                }
                #trix-logo {
                    width: 24px;
                    height: 24px;
                    margin-right: 8px;
                }
                #trix-controls button {
                    background: none;
                    border: none;
                    color: #abb2bf;
                    font-size: 18px;
                    cursor: pointer;
                    margin-left: 5px;
                }
                #trix-controls button:hover {
                    color: #61afef;
                }
                #trix-content {
                    padding: 10px;
                    flex-grow: 1;
                    display: flex;
                    flex-direction: column;
                }
                .trix-tab-content {
                    display: none;
                    height: 100%;
                    flex-direction: column;
                }
                .trix-tab-content.active {
                    display: flex;
                }
                #trix-tabs {
                    display: flex;
                    border-bottom: 1px solid #4f5b66;
                    margin-bottom: 10px;
                }
                .trix-tab-button {
                    padding: 8px 15px;
                    cursor: pointer;
                    border: none;
                    background-color: transparent;
                    color: #abb2bf;
                    border-bottom: 2px solid transparent;
                }
                .trix-tab-button.active {
                    color: #61afef;
                    border-bottom: 2px solid #61afef;
                }
                #trix-editor {
                    width: 100%;
                    flex-grow: 1;
                    background-color: #1e2227;
                    color: #d1d1d1;
                    border: 1px solid #4f5b66;
                    border-radius: 4px;
                    padding: 5px;
                    box-sizing: border-box;
                    resize: none;
                }
                .trix-button-bar {
                    margin-top: 10px;
                    display: flex;
                    gap: 10px;
                    flex-wrap: wrap;
                }
                .trix-button, input[type="text"].trix-input {
                    background-color: #3a3f4b;
                    border: 1px solid #4f5b66;
                    color: #abb2bf;
                    padding: 8px 12px;
                    border-radius: 4px;
                    cursor: pointer;
                }
                .trix-button:hover {
                    background-color: #4b515f;
                }
                .trix-button.primary {
                    background-color: #61afef;
                    color: #282c34;
                    font-weight: bold;
                }
                .trix-button.primary:hover {
                    background-color: #72baff;
                }
                .trix-button.danger {
                    background-color: #e06c75;
                    color: #282c34;
                }
                #trix-console {
                    height: 100%;
                    background-color: #1e2227;
                    border: 1px solid #4f5b66;
                    padding: 5px;
                    overflow-y: auto;
                    font-size: 12px;
                    flex-grow: 1;
                }
                #trix-console div {
                    padding: 2px 4px;
                    border-bottom: 1px solid #2c313a;
                }
                #trix-console .log-error { color: #e06c75; }
                #trix-console .log-warn { color: #e5c07b; }
                #trix-console .log-info { color: #61afef; }
                #trix-console .log-broadcast { color: #98c379; font-style: italic; }

                #trix-script-manager { display: flex; align-items: center; gap: 10px; }
                #trix-script-selector { flex-grow: 1; }
            `);
        },

        injectHTML() {
            const html = `
                <div id="trix-container" style="display: none;">
                    <div id="trix-header">
                        <div id="trix-header-title">
                            <img id="trix-logo" src="https://i.postimg.cc/0NkRZxDm/image.png" alt="TriX Logo">
                            <span>TriX Executor</span>
                        </div>
                        <div id="trix-controls">
                            <button id="trix-toggle-btn" title="Toggle Executor Panel">▼</button>
                        </div>
                    </div>
                    <div id="trix-content">
                        <div id="trix-tabs">
                            <button class="trix-tab-button" data-tab="executor">Executor</button>
                            <button class="trix-tab-button" data-tab="scripts">Scripts</button>
                            <button class="trix-tab-button" data-tab="console">Console</button>
                        </div>

                        <!-- Executor Tab -->
                        <div id="executor-tab" class="trix-tab-content">
                            <textarea id="trix-editor" placeholder="Enter your script here..."></textarea>
                            <div class="trix-button-bar">
                                <button id="trix-execute-btn" class="trix-button primary">Execute</button>
                                <button id="trix-clear-editor-btn" class="trix-button">Clear</button>
                            </div>
                        </div>

                        <!-- Scripts Tab -->
                        <div id="scripts-tab" class="trix-tab-content">
                            <div id="trix-script-manager">
                                <select id="trix-script-selector" class="trix-input"></select>
                                <button id="trix-load-script-btn" class="trix-button">Load</button>
                                <button id="trix-delete-script-btn" class="trix-button danger">Delete</button>
                            </div>
                            <input type="text" id="trix-script-name" class="trix-input" placeholder="Enter script name to save..." style="margin-top: 10px;">
                            <div class="trix-button-bar">
                                <button id="trix-save-script-btn" class="trix-button primary">Save Current Script</button>
                            </div>
                             <hr style="width:100%; border-color:#4f5b66; margin: 15px 0;">
                             <input type="text" id="trix-external-url" class="trix-input" placeholder="https://example.com/script.js">
                             <div class="trix-button-bar">
                                <button id="trix-fetch-run-btn" class="trix-button">Fetch & Run</button>
                             </div>
                        </div>

                        <!-- Console Tab -->
                        <div id="console-tab" class="trix-tab-content">
                            <div id="trix-console"></div>
                            <div class="trix-button-bar">
                                <button id="trix-clear-console-btn" class="trix-button">Clear Console</button>
                            </div>
                        </div>
                    </div>
                </div>
                <button id="trix-open-btn" class="trix-button" style="position:fixed; top:20px; right:20px; z-index:99998;">Open TriX</button>
            `;
            document.body.insertAdjacentHTML('beforeend', html);
        },

        attachEventListeners() {
            // Panel Dragging
            const header = document.getElementById('trix-header');
            const container = document.getElementById('trix-container');
            header.addEventListener('mousedown', (e) => {
                if (e.target.tagName === 'BUTTON') return;
                this.isDragging = true;
                this.dragOffsetX = e.clientX - container.offsetLeft;
                this.dragOffsetY = e.clientY - container.offsetTop;
            });
            document.addEventListener('mousemove', (e) => {
                if (!this.isDragging) return;
                container.style.left = `${e.clientX - this.dragOffsetX}px`;
                container.style.top = `${e.clientY - this.dragOffsetY}px`;
            });
            document.addEventListener('mouseup', () => {
                this.isDragging = false;
            });

            // Panel Visibility
            document.getElementById('trix-open-btn').addEventListener('click', () => this.togglePanel(true));
            document.getElementById('trix-toggle-btn').addEventListener('click', () => this.togglePanel(false));


            // Tab Switching
            document.querySelectorAll('.trix-tab-button').forEach(btn => {
                btn.addEventListener('click', () => this.showTab(btn.dataset.tab));
            });

            // Executor Buttons
            document.getElementById('trix-execute-btn').addEventListener('click', () => {
                const code = document.getElementById('trix-editor').value;
                Executor.execute(code);
            });
            document.getElementById('trix-clear-editor-btn').addEventListener('click', () => {
                document.getElementById('trix-editor').value = '';
            });

            // Script Manager Buttons
            document.getElementById('trix-save-script-btn').addEventListener('click', ScriptManager.saveScriptFromEditor);
            document.getElementById('trix-load-script-btn').addEventListener('click', ScriptManager.loadScriptToEditor);
            document.getElementById('trix-delete-script-btn').addEventListener('click', ScriptManager.deleteSelectedScript);
            document.getElementById('trix-fetch-run-btn').addEventListener('click', () => {
                const url = document.getElementById('trix-external-url').value;
                if(url) ScriptManager.fetchAndRun(url);
                else UI.log('External script URL is empty.', 'warn');
            });


            // Console Buttons
            document.getElementById('trix-clear-console-btn').addEventListener('click', () => {
                document.getElementById('trix-console').innerHTML = '';
            });
        },

        togglePanel(forceShow) {
            const container = document.getElementById('trix-container');
            const openBtn = document.getElementById('trix-open-btn');
            if (forceShow === true || container.style.display === 'none') {
                container.style.display = 'flex';
                openBtn.style.display = 'none';
            } else {
                container.style.display = 'none';
                openBtn.style.display = 'block';
            }
        },

        showTab(tabId) {
            document.querySelectorAll('.trix-tab-content').forEach(tab => tab.classList.remove('active'));
            document.querySelectorAll('.trix-tab-button').forEach(btn => btn.classList.remove('active'));
            document.getElementById(`${tabId}-tab`).classList.add('active');
            document.querySelector(`.trix-tab-button[data-tab="${tabId}"]`).classList.add('active');
        },

        log(message, type = 'log') {
            const consoleEl = document.getElementById('trix-console');
            const entry = document.createElement('div');
            const timestamp = new Date().toLocaleTimeString();
            entry.className = `log-${type}`;
            entry.textContent = `[${timestamp}] ${message}`;
            consoleEl.appendChild(entry);
            consoleEl.scrollTop = consoleEl.scrollHeight; // Auto-scroll
        }
    };

    // --- Script Manager ---
    // Handles loading, saving, and deleting user scripts from local storage.
    const ScriptManager = {
        async saveScriptFromEditor() {
            const name = document.getElementById('trix-script-name').value.trim();
            const code = document.getElementById('trix-editor').value;
            if (!name) {
                UI.log('Cannot save script: Name is required.', 'error');
                return;
            }
            if (!code) {
                UI.log('Cannot save script: Editor is empty.', 'warn');
                return;
            }
            await GM_setValue(SCRIPT_PREFIX + name, code);
            UI.log(`Script '${name}' saved successfully.`, 'info');
            this.populateScriptSelector();
        },

        async loadScriptToEditor() {
            const selector = document.getElementById('trix-script-selector');
            const name = selector.value;
            if (!name) {
                UI.log('No script selected to load.', 'warn');
                return;
            }
            const code = await GM_getValue(name, '');
            document.getElementById('trix-editor').value = code;
            document.getElementById('trix-script-name').value = name.replace(SCRIPT_PREFIX, '');
            UI.log(`Script '${name.replace(SCRIPT_PREFIX, '')}' loaded into editor.`, 'info');
        },

        async deleteSelectedScript() {
            const selector = document.getElementById('trix-script-selector');
            const name = selector.value;
            if (!name) {
                UI.log('No script selected to delete.', 'warn');
                return;
            }
            if (confirm(`Are you sure you want to delete the script '${name.replace(SCRIPT_PREFIX, '')}'?`)) {
                await GM_deleteValue(name);
                UI.log(`Script '${name.replace(SCRIPT_PREFIX, '')}' deleted.`, 'info');
                this.populateScriptSelector();
            }
        },

        async populateScriptSelector() {
            const selector = document.getElementById('trix-script-selector');
            selector.innerHTML = '<option value="">-- Select a saved script --</option>';
            const allKeys = await GM_listValues();
            const scriptKeys = allKeys.filter(key => key.startsWith(SCRIPT_PREFIX));
            scriptKeys.forEach(key => {
                const option = document.createElement('option');
                option.value = key;
                option.textContent = key.replace(SCRIPT_PREFIX, '');
                selector.appendChild(option);
            });
        },

        fetchAndRun(url) {
            UI.log(`Fetching script from: ${url}`, 'info');
            GM.xmlHttpRequest({
                method: "GET",
                url: url,
                onload: function(response) {
                    if (response.status >= 200 && response.status < 300) {
                        UI.log(`Successfully fetched script from ${url}. Executing...`, 'info');
                        Executor.execute(response.responseText);
                    } else {
                         UI.log(`Failed to fetch script. Status: ${response.status}`, 'error');
                    }
                },
                onerror: function(response) {
                    UI.log(`Error fetching script: ${response.statusText}`, 'error');
                }
            });
        }
    };

    // --- Executor & API ---
    // The core that executes scripts and provides a safe API for them.
    const Executor = {
        execute(code) {
            if (!code.trim()) {
                UI.log('Execution skipped: script is empty.', 'warn');
                return;
            }
            UI.log('Executing script...', 'info');
            try {
                // The "sandbox": scripts are executed within this function's scope.
                // They get access to the window, document, and the custom TriX API.
                const TriX = this.createAPI();
                const scriptFunction = new Function('TriX', code);
                scriptFunction(TriX);
            } catch (e) {
                UI.log(`Execution Error: ${e.message}`, 'error');
                console.error("TriX Executor Error:", e);
            }
        },

        createAPI() {
            return {
                /**
                 * Logs a message to the TriX console.
                 * @param {any} message The message to log.
                 * @param {'log'|'info'|'warn'|'error'} [type='log'] The type of log.
                 */
                log: (message, type = 'log') => {
                    // Ensure message is a string for the UI
                    const msgStr = typeof message === 'object' ? JSON.stringify(message) : String(message);
                    UI.log(msgStr, type);
                },

                /**
                 * Broadcasts a data payload to all other open Territorial.io tabs with TriX Executor.
                 * @param {any} payload The data to send. Must be JSON-serializable.
                 */
                broadcast: (payload) => {
                    MultiTab.broadcast(payload);
                },

                /**
                 * A safe way to get game data by querying the DOM.
                 * @param {string} selector The CSS selector for the element.
                 * @param {'text'|'html'|'value'|'element'} [dataType='text'] What to extract.
                 * @returns {string|HTMLElement|null} The extracted data or element.
                 */
                query: (selector, dataType = 'text') => {
                    const element = document.querySelector(selector);
                    if (!element) return null;
                    switch(dataType) {
                        case 'html': return element.innerHTML;
                        case 'value': return element.value;
                        case 'element': return element;
                        case 'text':
                        default:
                            return element.textContent;
                    }
                },

                /**
                 * A safe way to get arrays of game data.
                 * @param {string} selector The CSS selector for the elements.
                 * @param {'text'|'html'|'value'|'element'} [dataType='text'] What to extract.
                 * @returns {Array<string|HTMLElement>} An array of the extracted data.
                 */
                queryAll: (selector, dataType = 'text') => {
                    const elements = document.querySelectorAll(selector);
                    return Array.from(elements).map(el => {
                         switch(dataType) {
                            case 'html': return el.innerHTML;
                            case 'value': return el.value;
                            case 'element': return el;
                            case 'text':
                            default:
                                return el.textContent;
                        }
                    });
                }
            };
        }
    };

    // --- Multi-Tab Communication ---
    // Manages broadcasting and listening for messages between tabs.
    const MultiTab = {
        init() {
            GM_addValueChangeListener(BROADCAST_CHANNEL, this.listener);
        },

        /**
         * @param {string} key The key that changed.
         * @param {any} oldValue The old value.
         * @param {any} newValue The new value.
         * @param {boolean} remote Whether the change was from another tab.
         */
        listener(key, oldValue, newValue, remote) {
            if (remote && newValue.senderId !== TAB_ID) {
                UI.log(`Received broadcast: ${JSON.stringify(newValue.payload)}`, 'broadcast');
                // You can dispatch a custom event here for scripts to listen to
                // e.g., window.dispatchEvent(new CustomEvent('trix:broadcast', { detail: newValue.payload }));
            }
        },

        broadcast(payload) {
            const message = {
                senderId: TAB_ID,
                timestamp: Date.now(),
                payload: payload
            };
            GM_setValue(BROADCAST_CHANNEL, message);
            UI.log(`Broadcast sent: ${JSON.stringify(payload)}`, 'broadcast');
        }
    };

    // --- Main Initialization ---
    function main() {
        UI.init();
        MultiTab.init();
    }

    // Wait for the page to be fully loaded to avoid conflicts with game scripts.
    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        main();
    } else {
        window.addEventListener('load', main);
    }

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址