TriX Executor

A Comprehensive Script Executor for Territorial.io with a modern, Roblox-style UI and smart-loading capabilities.

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

// ==UserScript==
// @name         TriX Executor
// @namespace    https://github.com/YourUsername/TriX-Executor
// @version      1.2.0
// @description  A Comprehensive Script Executor for Territorial.io with a modern, Roblox-style UI and smart-loading capabilities.
// @author       You
// @match        *://territorial.io/*
// @match        *://www.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-body
// @license      MIT
// ==/UserScript==

/*
 *   _____  ____  _   __  ___________
 *  |_   _||_  _|| | / / |  _  | ___ \
 *    | |    | | | |/ /  | |/' | |_/ /
 *    | |    | | |    \  |  /| |  __/
 *   _| |_  _| |_| |\  \ \ |_/ / |
 *   \___/  \___/\_| \_/  \___/\_|
 *
 *   TriX Executor - v1.2.0 (Smart-Loading Edition)
 *   Logo: https://i.postimg.cc/0NkRZxDm/image.png
 *
 *   Changelog:
 *   - Implemented MutationObserver to wait for the game's UI (#menu-area)
 *     to load before injecting the executor. This fixes all timing issues.
 */

(function() {
    'use strict';
    console.log('TriX Executor: Script injected. Waiting for game to load...');

    // --- 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)}`;
    const GAME_READY_SELECTOR = '#menu-area'; // The key element from the homepage we wait for

    // --- UI Module (Identical to v1.1.0) ---
    const UI = {
        isDragging: false,
        dragOffsetX: 0,
        dragOffsetY: 0,
        isMinimized: false,
        currentScriptName: '',

        init() {
            this.injectCSS();
            this.injectHTML();
            this.attachEventListeners();
            ScriptManager.populateScriptList();
            this.log('TriX Executor v1.2.0 initialized.');
        },

        injectCSS() {
            GM_addStyle(`
                :root {
                    --trix-bg-primary: #1e1e1e;
                    --trix-bg-secondary: #2d2d2d;
                    --trix-bg-tertiary: #3c3c3c;
                    --trix-accent-color: #00aeff;
                    --trix-text-primary: #d4d4d4;
                    --trix-text-secondary: #8c8c8c;
                    --trix-border-color: #4a4a4a;
                }

                #trix-container, #trix-open-btn {
                    font-family: 'Segoe UI', 'Roboto', sans-serif;
                }

                #trix-container {
                    position: fixed !important;
                    top: 50px !important;
                    left: 50px !important;
                    width: 600px;
                    height: 400px;
                    background-color: var(--trix-bg-primary);
                    border: 1px solid var(--trix-border-color);
                    box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
                    z-index: 1000000 !important;
                    display: flex !important;
                    flex-direction: column;
                    resize: both;
                    overflow: hidden;
                    min-width: 450px;
                    min-height: 300px;
                }
                #trix-container.hidden {
                    display: none !important;
                }
                #trix-container.minimized #trix-body,
                #trix-container.minimized #trix-footer,
                #trix-container.minimized #trix-console {
                    display: none !important;
                }
                 #trix-container.minimized {
                    height: auto !important;
                    resize: none !important;
                 }


                #trix-header {
                    background-color: var(--trix-bg-secondary);
                    padding: 5px 10px;
                    cursor: move;
                    user-select: none;
                    display: flex;
                    align-items: center;
                    justify-content: space-between;
                    border-bottom: 1px solid var(--trix-border-color);
                }
                #trix-header-title {
                    display: flex;
                    align-items: center;
                    font-weight: bold;
                    color: var(--trix-text-primary);
                }
                #trix-logo { width: 24px; height: 24px; margin-right: 8px; }

                #trix-window-controls button {
                    background: none; border: none; color: var(--trix-text-secondary);
                    font-size: 18px; cursor: pointer; margin-left: 8px;
                    padding: 0 4px; line-height: 1;
                }
                #trix-window-controls button:hover { color: var(--trix-accent-color); }
                #trix-close-btn:hover { color: #ff5555; }

                #trix-body {
                    flex-grow: 1;
                    display: flex;
                    overflow: hidden;
                }

                #trix-left-panel {
                    width: 150px;
                    background-color: var(--trix-bg-secondary);
                    padding: 10px 0;
                    display: flex;
                    flex-direction: column;
                    border-right: 1px solid var(--trix-border-color);
                }
                #trix-script-list-title {
                    padding: 0 10px 10px 10px;
                    font-weight: bold;
                    color: var(--trix-text-primary);
                    border-bottom: 1px solid var(--trix-border-color);
                }
                #trix-script-list {
                    flex-grow: 1;
                    overflow-y: auto;
                    margin-top: 10px;
                }
                .trix-script-item {
                    padding: 8px 10px;
                    cursor: pointer;
                    color: var(--trix-text-secondary);
                    white-space: nowrap;
                    overflow: hidden;
                    text-overflow: ellipsis;
                }
                .trix-script-item:hover {
                    background-color: var(--trix-bg-tertiary);
                    color: var(--trix-text-primary);
                }
                .trix-script-item.active {
                    background-color: var(--trix-accent-color);
                    color: var(--trix-bg-primary) !important;
                    font-weight: bold;
                }

                #trix-right-panel {
                    flex-grow: 1; display: flex; flex-direction: column;
                }

                #trix-editor {
                    flex-grow: 1;
                    background-color: var(--trix-bg-primary);
                    color: var(--trix-text-primary);
                    border: none;
                    padding: 10px;
                    box-sizing: border-box;
                    resize: none;
                    font-family: 'Consolas', 'Monaco', monospace;
                    font-size: 14px;
                }

                #trix-console {
                    height: 120px;
                    background-color: var(--trix-bg-secondary);
                    border-top: 1px solid var(--trix-border-color);
                    padding: 5px;
                    overflow-y: auto;
                    font-size: 12px;
                    font-family: 'Consolas', 'Monaco', monospace;
                }
                #trix-console div { padding: 2px 4px; border-bottom: 1px solid #333; word-break: break-all; }
                #trix-console .log-error { color: #ff5555; }
                #trix-console .log-warn { color: #e5c07b; }
                #trix-console .log-info { color: #00aeff; }
                #trix-console .log-broadcast { color: #98c379; font-style: italic; }

                #trix-footer {
                    display: flex;
                    gap: 10px;
                    padding: 10px;
                    background-color: var(--trix-bg-secondary);
                    border-top: 1px solid var(--trix-border-color);
                    align-items: center;
                }
                .trix-button {
                    background-color: var(--trix-bg-tertiary);
                    border: 1px solid var(--trix-border-color);
                    color: var(--trix-text-primary);
                    padding: 8px 15px;
                    cursor: pointer;
                    transition: background-color 0.2s;
                }
                .trix-button:hover { background-color: #555; }
                .trix-button.primary {
                    background-color: var(--trix-accent-color);
                    color: var(--trix-bg-primary);
                    font-weight: bold;
                }
                .trix-button.primary:hover { background-color: #00bfff; }
                .trix-button.danger { background-color: #c0392b; }
                .trix-button.danger:hover { background-color: #e74c3c; }

                #trix-open-btn {
                    position: fixed !important; top: 20px !important; right: 20px !important;
                    z-index: 999999 !important; display: block !important;
                    background-color: var(--trix-accent-color); color: white;
                    border: none; padding: 10px 15px; cursor: pointer;
                    box-shadow: 0 0 10px rgba(0, 174, 255, 0.7);
                    font-weight: bold;
                }
            `);
        },
        injectHTML() {
            const html = `
                <div id="trix-container" class="hidden">
                    <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-window-controls">
                            <button id="trix-minimize-btn" title="Minimize">-</button>
                            <button id="trix-close-btn" title="Close">✕</button>
                        </div>
                    </div>
                    <div id="trix-body">
                        <div id="trix-left-panel">
                            <div id="trix-script-list-title">Script List</div>
                            <div id="trix-script-list"></div>
                        </div>
                        <div id="trix-right-panel">
                            <textarea id="trix-editor" placeholder="-- Paste or load a script here..."></textarea>
                            <div id="trix-console"></div>
                        </div>
                    </div>
                    <div id="trix-footer">
                        <button id="trix-execute-btn" class="trix-button primary">▶ Execute</button>
                        <button id="trix-clear-btn" class="trix-button">Clear</button>
                        <input type="text" id="trix-save-name" placeholder="Script Name..." class="trix-button" style="flex-grow:1; cursor:text;">
                        <button id="trix-save-btn" class="trix-button">Save</button>
                        <button id="trix-delete-btn" class="trix-button danger">Delete</button>
                    </div>
                </div>
                <button id="trix-open-btn">Open TriX</button>
            `;
            document.body.insertAdjacentHTML('beforeend', html);
        },
        attachEventListeners() {
            const container = document.getElementById('trix-container');
            const header = document.getElementById('trix-header');
            header.addEventListener('mousedown', (e) => { if (e.target.closest('#trix-window-controls')) 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; });
            document.getElementById('trix-open-btn').addEventListener('click', () => this.togglePanel(true));
            document.getElementById('trix-close-btn').addEventListener('click', () => this.togglePanel(false));
            document.getElementById('trix-minimize-btn').addEventListener('click', () => { this.isMinimized = !this.isMinimized; container.classList.toggle('minimized', this.isMinimized); });
            document.getElementById('trix-script-list').addEventListener('click', (e) => { if (e.target.matches('.trix-script-item')) { const scriptKey = e.target.dataset.scriptKey; ScriptManager.loadScriptToEditor(scriptKey); } });
            document.getElementById('trix-execute-btn').addEventListener('click', () => Executor.execute(document.getElementById('trix-editor').value));
            document.getElementById('trix-clear-btn').addEventListener('click', () => { document.getElementById('trix-editor').value = ''; document.getElementById('trix-save-name').value = ''; this.setActiveScriptItem(null); this.currentScriptName = ''; });
            document.getElementById('trix-save-btn').addEventListener('click', ScriptManager.saveScriptFromEditor);
            document.getElementById('trix-delete-btn').addEventListener('click', ScriptManager.deleteCurrentScript);
        },
        togglePanel(forceShow) {
            const container = document.getElementById('trix-container');
            const openBtn = document.getElementById('trix-open-btn');
            const isHidden = container.classList.contains('hidden');
            if (forceShow === true || isHidden) { container.classList.remove('hidden'); openBtn.style.display = 'none'; } else { container.classList.add('hidden'); openBtn.style.display = 'block'; }
        },
        log(message, type = 'log') {
            const consoleEl = document.getElementById('trix-console');
            if (!consoleEl) return;
            const entry = document.createElement('div');
            entry.className = `log-${type}`;
            entry.textContent = `> ${String(message)}`;
            consoleEl.prepend(entry);
        },
        setActiveScriptItem(key) { document.querySelectorAll('.trix-script-item').forEach(item => { item.classList.toggle('active', item.dataset.scriptKey === key); }); }
    };

    // --- Script Manager, Executor & MultiTab (Identical to v1.1.0) ---
    const ScriptManager = {
        async saveScriptFromEditor() { const name = document.getElementById('trix-save-name').value.trim(); const code = document.getElementById('trix-editor').value; if (!name) return UI.log('Cannot save: Name is required.', 'error'); if (!code) return UI.log('Cannot save: Editor is empty.', 'warn'); await GM_setValue(SCRIPT_PREFIX + name, code); UI.log(`Script '${name}' saved.`, 'info'); this.populateScriptList(SCRIPT_PREFIX + name); },
        async loadScriptToEditor(key) { if (!key) return UI.log('Invalid script key.', 'error'); const code = await GM_getValue(key, ''); const name = key.replace(SCRIPT_PREFIX, ''); document.getElementById('trix-editor').value = code; document.getElementById('trix-save-name').value = name; UI.log(`Loaded script: ${name}`, 'info'); UI.currentScriptName = name; UI.setActiveScriptItem(key); },
        async deleteCurrentScript() { const name = UI.currentScriptName; if (!name) return UI.log('No script loaded to delete.', 'warn'); if (confirm(`Are you sure you want to delete '${name}'?`)) { await GM_deleteValue(SCRIPT_PREFIX + name); UI.log(`Script '${name}' deleted.`, 'info'); document.getElementById('trix-editor').value = ''; document.getElementById('trix-save-name').value = ''; UI.currentScriptName = ''; this.populateScriptList(); } },
        async populateScriptList(activeKey = null) { const listEl = document.getElementById('trix-script-list'); listEl.innerHTML = ''; const allKeys = (await GM_listValues()).filter(k => k.startsWith(SCRIPT_PREFIX)); allKeys.sort().forEach(key => { const item = document.createElement('div'); item.className = 'trix-script-item'; item.textContent = key.replace(SCRIPT_PREFIX, ''); item.dataset.scriptKey = key; listEl.appendChild(item); }); UI.setActiveScriptItem(activeKey || UI.currentScriptName ? SCRIPT_PREFIX + UI.currentScriptName : null); }
    };
    const Executor={execute(e){if(!e.trim())return void UI.log("Execution skipped: script is empty.","warn");UI.log("Executing script...","info");try{const t=this.createAPI(),s=new Function("TriX",e);s(t)}catch(e){UI.log(`Execution Error: ${e.message}`,"error"),console.error("TriX Executor Error:",e)}},createAPI:()=>({log:(e,t="log")=>{const s="object"==typeof e?JSON.stringify(e):String(e);UI.log(s,t)},broadcast:e=>{MultiTab.broadcast(e)},query:(e,t="text")=>{const s=document.querySelector(e);return s?"html"===t?s.innerHTML:"value"===t?s.value:"element"===t?s:s.textContent:null},queryAll:(e,t="text")=>{const s=document.querySelectorAll(e);return Array.from(s).map(e=>"html"===t?e.innerHTML:"value"===t?e.value:"element"===t?e:e.textContent)}})};
    const MultiTab={init(){GM_addValueChangeListener(BROADCAST_CHANNEL,this.listener)},listener(e,t,s,o){o&&s.senderId!==TAB_ID&&UI.log(`Received broadcast: ${JSON.stringify(s.payload)}`,"broadcast")},broadcast(e){const t={senderId:TAB_ID,timestamp:Date.now(),payload:e};GM_setValue(BROADCAST_CHANNEL,t),UI.log(`Broadcast sent: ${JSON.stringify(e)}`,"broadcast")}};


    // --- NEW: Smart Initialization Logic ---

    /**
     * Waits for a key element to appear in the DOM, then executes a callback.
     * This is crucial for web apps that load content dynamically.
     * @param {string} selector The CSS selector of the element to wait for.
     * @param {function} callback The function to execute once the element is found.
     */
    function waitForElement(selector, callback) {
        // First, check if the element already exists.
        if (document.querySelector(selector)) {
            callback();
            return;
        }

        // If not, set up a MutationObserver to watch for changes.
        const observer = new MutationObserver((mutations, obs) => {
            if (document.querySelector(selector)) {
                obs.disconnect(); // We found the element, so we can stop observing.
                callback();
            }
        });

        // Start observing the document for additions of new nodes.
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }

    /**
     * The main function to run once the page is ready.
     */
    function main() {
        console.log(`TriX Executor: Game element '${GAME_READY_SELECTOR}' found! Initializing UI.`);
        UI.init();
        MultiTab.init();
        UI.togglePanel(false); // Start with the panel hidden.
    }

    // --- Entry Point ---
    // We don't run main() immediately. Instead, we wait for the game's menu to be ready.
    waitForElement(GAME_READY_SELECTOR, main);

})();

QingJ © 2025

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