TriX Executor (Revamped)

A powerful, feature-rich executor with a modern UI. Includes multi-tab scripting, a network suite (Logger, Injector, WS Client), GreasyFork integration, and more.

当前为 2025-11-08 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         TriX Executor (Revamped)
// @namespace    https://greasyfork.org/en/users/COURTESYCOIL
// @version      5.0.0
// @description  A powerful, feature-rich executor with a modern UI. Includes multi-tab scripting, a network suite (Logger, Injector, WS Client), GreasyFork integration, and more.
// @author       Painsel
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_getClipboard
// @grant        unsafeWindow
// @license      MIT
// @icon         data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iIzRmNDZlNSI+PHBhdGggZD0iTTE4LjM2IDIyLjA1bC00LjQyLTQuNDJDMTMuNDcgMTcuODcgMTMgMTguMTcgMTMgMTguNUMxMyAyMC45OSAxNS4wMSAyMyAxNy41IDIzaDMuNTRjLTIuNDUtMS40OC00LjQyLTMuNDUtNS4LTUuOTV6TTggMTNjMS42NiAwIDMuMTgtLjU5IDQuMzgtMS42MkwxMCAxMy41VjIybDIuNS0yLjVMMTggMTMuOTZjLjM1LS40OC42NS0xIC44Ny0xLjU1QzE4LjYxIDEzLjQxIDE4IDEyLjM0IDE4IDEyVjBoLTJ2MTJjMCAuMzQtLjAyLjY3LS4wNiAxLS4zMy4xOC0uNjguMy0xLjA0LjM3LTEuNzMgMC0zLjI3LS45My00LjE2LTIuMzZMNiAxMy42VjVINXY4eiIvPjwvc3ZnPg==
// ==/UserScript==

(function () {
  "use strict";

  // --- TriX Core: WebSocket Logging & Global State ---
  let isLoggerSuspended = false;
  let customWs = null;
  const monitoredConnections = new Map();

  // Callbacks for UI updates
  let updateConnectionStatus = () => {};
  let updatePingDisplay = () => {};
  let logPacketCallback = () => {};
  let onConnectionStateChange = () => {};

  const OriginalWebSocket = unsafeWindow.WebSocket;
  unsafeWindow.WebSocket = function(url, protocols) {
    let isGameSocket = false;
    try {
        const isTerritorial = window.location.hostname.includes('territorial.io');
        const urlObj = new URL(url, window.location.href);
        const urlString = urlObj.toString();
        const proxyParam = urlObj.searchParams.get('u');
        isGameSocket = isTerritorial && (urlString.includes('/s52/') || (proxyParam && atob(proxyParam).includes('/s52/')));
    } catch (e) { /* Invalid URL, not a game socket */ }

    if (!isGameSocket) return new OriginalWebSocket(url, protocols);

    console.log(`[TriX] Intercepting WebSocket: ${url}`);
    const ws = new OriginalWebSocket(url, protocols);
    monitoredConnections.set(ws, { url, state: 'CONNECTING', log: [] });
    onConnectionStateChange();

    const originalSend = ws.send.bind(ws);
    ws.send = function(data) {
        logPacketCallback(ws, 'send', data);
        return originalSend(data);
    };

    ws.addEventListener('message', event => {
        logPacketCallback(ws, 'receive', event.data);
    });

    ws.addEventListener('open', () => {
        const conn = monitoredConnections.get(ws);
        if (conn) conn.state = 'OPEN';
        onConnectionStateChange();
        updateConnectionStatus('connected', 'Connection Established');
    });
    ws.addEventListener('close', () => {
        monitoredConnections.delete(ws);
        onConnectionStateChange();
        updateConnectionStatus('disconnected', 'Disconnected');
    });
    ws.addEventListener('error', () => {
        monitoredConnections.delete(ws);
        onConnectionStateChange();
        updateConnectionStatus('error', 'Connection Error');
    });

    unsafeWindow.trixSocket = ws;
    return ws;
  };


  // --- Revamped UI: Global variables ---
  let isMinimized = false;
  let currentTab = "home";
  let activeNetworkTab = "logger";
  let scriptTabs = [{ id: "tab1", name: "Script 1", content: "// Welcome to the revamped TriX Executor!\nconsole.log('Phoenix Rising!');" }];
  let activeScriptTab = "tab1";

  // Initialize storage
  if (!GM_getValue("scripts", null)) {
    GM_setValue("scripts", JSON.stringify([]));
  }
  if (!GM_getValue("settings", null)) {
    GM_setValue("settings", JSON.stringify({
        theme: "dark",
        antiScam: true,
        autoUpdate: true,
    }));
  }

  // CSS Styles
  const styles = `
    .trix-executor {
        position: fixed; top: 50px; right: 50px; width: 650px; height: 500px;
        background: linear-gradient(135deg, #1e1e2e 0%, #2d2d44 100%);
        border-radius: 12px; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3); z-index: 999999;
        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; color: #ffffff;
        overflow: hidden; transition: all 0.3s ease; display: flex; flex-direction: column;
    }
    .trix-header {
        background: linear-gradient(90deg, #4f46e5 0%, #7c3aed 100%); height: 40px;
        display: flex; justify-content: space-between; align-items: center; padding: 0 15px;
        border-radius: 12px 12px 0 0; flex-shrink: 0; cursor: move; user-select: none;
    }
    .trix-title { font-weight: 600; font-size: 14px; color: white; }
    .trix-header-info { display: flex; align-items: center; gap: 15px; font-size: 12px; color: rgba(255,255,255,0.8); }
    #trix-conn-status { width: 10px; height: 10px; border-radius: 50%; background-color: #ef4444; transition: background-color 0.3s; }
    #trix-conn-status.connected { background-color: #10b981; }
    #trix-conn-status.error { background-color: #f59e0b; }
    .trix-controls { display: flex; align-items: center; gap: 8px; }
    .trix-btn {
        width: 20px; height: 20px; border-radius: 4px; border: none; cursor: pointer; font-size: 12px; font-weight: bold;
        display: flex; justify-content: center; align-items: center; padding: 0; transition: all 0.2s ease;
    }
    .minimize-btn { background: #fbbf24; color: #92400e; }
    .maximize-btn { background: #10b981; color: #065f46; }
    .close-btn { background: #ef4444; color: #991b1b; }
    .trix-btn:hover { transform: scale(1.1); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); }
    .trix-body { display: flex; height: 100%; overflow: hidden; }
    .trix-sidebar {
        width: 150px; background: rgba(0, 0, 0, 0.2); padding: 15px 0;
        border-right: 1px solid rgba(255, 255, 255, 0.1); flex-shrink: 0;
    }
    .sidebar-item {
        padding: 12px 20px; cursor: pointer; transition: all 0.2s ease;
        font-size: 13px; border-left: 3px solid transparent;
    }
    .sidebar-item:hover { background: rgba(255, 255, 255, 0.1); border-left-color: #4f46e5; }
    .sidebar-item.active { background: rgba(79, 70, 229, 0.3); border-left-color: #4f46e5; }
    .trix-content { flex: 1; padding: 20px; overflow-y: auto; }
    .content-section { display: none; }
    .content-section.active { display: block; }

    /* --- Component Styles --- */
    .script-input, .network-textarea {
        width: 100%; height: 200px; background: rgba(0, 0, 0, 0.3);
        border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 8px; padding: 15px;
        color: white; font-family: 'Courier New', monospace; font-size: 12px; resize: vertical; outline: none; box-sizing: border-box;
    }
    .script-tabs, .network-tabs { display: flex; gap: 5px; margin-bottom: 10px; flex-wrap: wrap; }
    .script-tab, .network-tab {
        background: rgba(0, 0, 0, 0.3); border: 1px solid rgba(255, 255, 255, 0.2);
        border-radius: 6px; padding: 6px 12px; font-size: 11px; cursor: pointer;
        transition: all 0.2s ease; position: relative;
    }
    .script-tab.active, .network-tab.active { background: rgba(79, 70, 229, 0.5); border-color: #4f46e5; }
    .add-tab-btn {
        background: rgba(16, 185, 129, 0.3); border: 1px solid #10b981; color: #10b981;
        border-radius: 6px; padding: 6px 12px; font-size: 11px; cursor: pointer; transition: all 0.2s ease;
    }
    .executor-buttons { display: flex; gap: 10px; margin-top: 15px; flex-wrap: wrap; }
    .exec-btn {
        background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%); border: none;
        border-radius: 6px; padding: 10px 20px; color: white; font-size: 12px;
        font-weight: 600; cursor: pointer; transition: all 0.2s ease;
    }
    .exec-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(79, 70, 229, 0.4); }
    .exec-btn.secondary { background: linear-gradient(135deg, #6b7280 0%, #4b5563 100%); }
    .exec-btn.success { background: linear-gradient(135deg, #10b981 0%, #059669 100%); }
    .exec-btn.danger { background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); }
    .search-input {
        width: 100%; background: rgba(0, 0, 0, 0.3); border: 1px solid rgba(255, 255, 255, 0.2);
        border-radius: 8px; padding: 12px; color: white; font-size: 13px; margin-bottom: 15px; outline: none; box-sizing: border-box;
    }
    .script-cards { display: grid; gap: 15px; max-height: 300px; overflow-y: auto; }
    .script-card {
        background: rgba(0, 0, 0, 0.3); border: 1px solid rgba(255, 255, 255, 0.2);
        border-radius: 8px; padding: 15px; cursor: pointer; transition: all 0.2s ease;
    }
    .script-card:hover { background: rgba(255, 255, 255, 0.1); transform: translateY(-2px); }
    .card-title { font-weight: 600; margin-bottom: 5px; color: #818cf8; }
    .card-description { font-size: 12px; color: rgba(255, 255, 255, 0.7); }
    .settings-group { margin-bottom: 20px; }
    .settings-label { display: block; margin-bottom: 8px; font-weight: 600; font-size: 13px; }
    .settings-input, .settings-select {
        width: 100%; background: rgba(0, 0, 0, 0.3); border: 1px solid rgba(255, 255, 255, 0.2);
        border-radius: 6px; padding: 10px; color: white; font-size: 12px; outline: none; box-sizing: border-box;
    }
    .notification {
        position: fixed; top: 20px; right: 20px; background: linear-gradient(135deg, #10b981 0%, #059669 100%);
        color: white; padding: 12px 20px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
        z-index: 1000000; font-size: 13px; font-weight: 600; opacity: 0;
        transform: translateX(100%); transition: all 0.3s ease;
    }
    .notification.show { opacity: 1; transform: translateX(0); }
    .minimized-icon {
        position: fixed; width: 50px; height: 50px; background-color: #1e1e2e;
        border: 3px solid #4f46e5; border-radius: 12px; cursor: pointer; z-index: 999999;
        transition: all 0.2s ease; box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
        display: flex; justify-content: center; align-items: center; font-size: 24px; font-weight: bold; color: #818cf8; user-select: none;
    }
    .minimized-icon:hover { transform: scale(1.05); box-shadow: 0 12px 35px rgba(79, 70, 229, 0.4); }

    /* Network Tab Specific Styles */
    .network-view-content { margin-top: 15px; height: calc(100% - 50px); display: flex; flex-direction: column; }
    .packet-log { height: 100%; overflow-y: auto; background: rgba(0,0,0,0.2); padding: 10px; border-radius: 8px; font-family: 'Courier New', monospace; font-size: 12px; }
    .log-item { padding: 4px; border-bottom: 1px solid rgba(255,255,255,0.1); word-break: break-all; }
    .log-item.send { color: #6ee7b7; } .log-item.send-forwarded { color: #a7f3d0; }
    .log-item.receive { color: #f0abfc; } .log-item.receive-forwarded { color: #f5d0fe; }
    .log-item.send-blocked, .log-item.receive-blocked { color: #fda4af; text-decoration: line-through; }
    .storage-table { display: grid; grid-template-columns: 150px 1fr auto; gap: 10px; align-items: center; font-size: 12px; padding: 8px 0; border-bottom: 1px solid rgba(255,255,255,0.1); }
    .storage-key { color: #f0abfc; font-weight: bold; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .storage-value { color: #6ee7b7; word-break: break-all; }

    /* Scrollbar Styling */
    .trix-content::-webkit-scrollbar, .script-cards::-webkit-scrollbar, .packet-log::-webkit-scrollbar { width: 8px; }
    .trix-content::-webkit-scrollbar-track, .script-cards::-webkit-scrollbar-track, .packet-log::-webkit-scrollbar-track { background: rgba(0, 0, 0, 0.2); border-radius: 4px; }
    .trix-content::-webkit-scrollbar-thumb, .script-cards::-webkit-scrollbar-thumb, .packet-log::-webkit-scrollbar-thumb { background: rgba(79, 70, 229, 0.6); border-radius: 4px; }
    .trix-content::-webkit-scrollbar-thumb:hover, .script-cards::-webkit-scrollbar-thumb:hover, .packet-log::-webkit-scrollbar-thumb:hover { background: rgba(79, 70, 229, 0.8); }
  `;

  const styleSheet = document.createElement("style");
  styleSheet.textContent = styles;
  document.head.appendChild(styleSheet);

  // --- Notification System ---
  function showNotification(message, type = "success") {
    const notification = document.createElement("div");
    notification.className = "notification";
    notification.textContent = message;
    if (type === "error") {
      notification.style.background = "linear-gradient(135deg, #ef4444 0%, #dc2626 100%)";
    }
    document.body.appendChild(notification);
    setTimeout(() => notification.classList.add("show"), 100);
    setTimeout(() => {
      notification.classList.remove("show");
      setTimeout(() => notification.remove(), 300);
    }, 3000);
  }

  // --- UI Creation ---
  function createExecutorPanel() {
    const panel = document.createElement("div");
    panel.className = "trix-executor";
    panel.innerHTML = `
        <div class="trix-header">
            <div class="trix-title">TriX Executor v3.1</div>
            <div class="trix-header-info">
                <div id="trix-conn-status" title="Disconnected"></div>
                <span id="trix-ping-display">Ping: ---</span>
                <span id="trix-fps-display">FPS: --</span>
            </div>
            <div class="trix-controls">
                <button class="trix-btn minimize-btn" title="Minimize">−</button>
                <button class="trix-btn maximize-btn" title="Maximize">□</button>
                <button class="trix-btn close-btn" title="Close">×</button>
            </div>
        </div>
        <div class="trix-body">
            <div class="trix-sidebar">
                <div class="sidebar-item active" data-tab="home">🏠 Home</div>
                <div class="sidebar-item" data-tab="main">⚡ Main</div>
                <div class="sidebar-item" data-tab="network">📡 Network</div>
                <div class="sidebar-item" data-tab="cloud">☁ Cloud</div>
                <div class="sidebar-item" data-tab="settings">⚙ Settings</div>
            </div>
            <div class="trix-content">
                ${createHomeContent()}
                ${createMainContent()}
                ${createNetworkContent()}
                ${createCloudContent()}
                ${createSettingsContent()}
            </div>
        </div>
    `;
    document.body.appendChild(panel);
    return panel;
  }

  // --- Content Section Creators ---
  function createHomeContent() {
    return `<div class="content-section active" id="home-content">
                <h2>Script Store</h2>
                <input type="text" class="search-input" placeholder="Search saved scripts..." id="script-search">
                <div class="script-cards" id="saved-scripts"></div>
            </div>`;
  }
  function createMainContent() {
    return `<div class="content-section" id="main-content">
                <h2>Live JS Executor</h2>
                <div class="script-tabs" id="script-tabs"></div>
                <textarea class="script-input" placeholder="Enter Script Here..." id="script-editor"></textarea>
                <div class="executor-buttons">
                    <button class="exec-btn" id="execute-btn">Execute</button>
                    <button class="exec-btn secondary" id="execute-clipboard-btn">Execute Clipboard</button>
                    <button class="exec-btn success" id="save-script-btn">Save Script</button>
                </div>
            </div>`;
  }
  function createCloudContent() {
    return `<div class="content-section" id="cloud-content">
                <h2>GreasyFork Scripts</h2>
                <input type="text" class="search-input" placeholder="Search GreasyFork..." id="greasyfork-search">
                <div class="script-cards" id="cloud-results"></div>
            </div>`;
  }
  function createSettingsContent() {
    const settings = JSON.parse(GM_getValue("settings"));
    return `<div class="content-section" id="settings-content">
                <h2>Settings</h2>
                <div class="settings-group">
                    <label class="settings-label">Theme</label>
                    <select class="settings-select" id="theme-select" disabled>
                        <option value="dark" ${settings.theme === "dark" ? "selected" : ""}>Dark (Default)</option>
                    </select>
                </div>
                <div class="settings-group">
                    <label class="settings-label">
                        <input type="checkbox" class="settings-checkbox" id="anti-scam" ${settings.antiScam ? "checked" : ""}>
                        Anti-Scam Protection (confirm potentially harmful scripts)
                    </label>
                </div>
                <div class="settings-group">
                    <label class="settings-label">
                        <input type="checkbox" class="settings-checkbox" id="auto-update" ${settings.autoUpdate ? "checked" : ""}>
                        Auto Update Scripts
                    </label>
                </div>
            </div>`;
  }
  function createNetworkContent() {
    return `<div class="content-section" id="network-content">
            <div class="network-tabs">
                <div class="network-tab active" data-net-tab="logger">Logger</div>
                <div class="network-tab" data-net-tab="injector">Injector</div>
                <div class="network-tab" data-net-tab="ws_client">WS Client</div>
                <div class="network-tab" data-net-tab="storage">Storage</div>
            </div>
            <div class="network-view-content"></div>
        </div>`;
  }

  // --- Network View Renderers ---
  function renderActiveNetworkView() {
      const container = document.querySelector("#network-content .network-view-content");
      if (!container) return;
      container.innerHTML = "";
      switch (activeNetworkTab) {
          case "logger":
              container.innerHTML = `<div class="executor-buttons" style="margin-top:0; margin-bottom:10px;">
                      <button class="exec-btn" id="suspend-log-btn">Suspend Log</button>
                      <button class="exec-btn secondary" id="clear-log-btn">Clear Log</button>
                  </div>
                  <div class="packet-log" id="packet-log-output">Waiting for connection...</div>`;
              renderPacketLog();
              break;
          case "injector":
             container.innerHTML = `<h3>Packet Injector</h3>
                <p style="font-size:12px; color: #ccc; margin-bottom:10px;">Send a custom packet to the current game connection.</p>
                <textarea class="network-textarea" id="injector-input" placeholder='e.g., 42["chat","Hello from TriX!"]' style="height: 150px;"></textarea>
                <div class="executor-buttons">
                    <button class="exec-btn" id="inject-packet-btn">Inject Packet</button>
                </div>`;
             break;
          case "ws_client":
             container.innerHTML = `<h3>WebSocket Client</h3>
                <input type="text" id="ws-client-url" class="search-input" placeholder="wss://your-socket-url.com">
                <div class="executor-buttons" style="margin-top:0;">
                    <button class="exec-btn" id="ws-client-connect-btn">Connect</button>
                </div>
                <div class="packet-log" id="ws-client-log" style="height: 150px; margin-top:10px;"></div>
                <textarea class="network-textarea" id="ws-client-input" placeholder="Message to send..." style="height: 80px; margin-top:10px;"></textarea>
                <div class="executor-buttons">
                    <button class="exec-btn" id="ws-client-send-btn">Send</button>
                </div>`;
             break;
          case "storage":
             container.innerHTML = `<h3>Storage Explorer</h3>
                <h4>Local Storage</h4>
                <div id="local-storage-view"></div>
                <h4 style="margin-top:20px;">Session Storage</h4>
                <div id="session-storage-view"></div>`;
             renderStorageView();
             break;
      }
  }

  function renderPacketLog() {
      const logContainer = document.getElementById("packet-log-output");
      if (!logContainer) return;
      const activeConnection = Array.from(monitoredConnections.values()).find(c => c.state === 'OPEN' || c.log.length > 0);
      if (!activeConnection) {
          logContainer.textContent = 'Waiting for active connection...';
          return;
      }
      if (isLoggerSuspended) return;
      logContainer.innerHTML = '';
      activeConnection.log.forEach(packet => {
          const item = document.createElement('div');
          item.className = `log-item ${packet.type}`;
          item.textContent = `[${packet.type.toUpperCase()}] ${packet.data}`;
          logContainer.appendChild(item);
      });
      logContainer.scrollTop = logContainer.scrollHeight;
  }
  
  function renderStorageView() { /* ... unchanged ... */ }
  
  // --- Panel Management & Draggability ---
  function minimizePanel() { /* ... unchanged ... */ }
  function restorePanel() { /* ... unchanged ... */ }
  function createMinimizedIcon() { /* ... unchanged ... */ }
  function closePanel() { /* ... unchanged ... */ }

  // --- Tab Management ---
  function switchTab(tabName) { /* ... unchanged ... */ }
  function updateScriptTabs() { /* ... unchanged ... */ }
  function switchScriptTab(tabId) { /* ... unchanged ... */ }
  function addScriptTab() { /* ... unchanged ... */ }
  function renameScriptTab(tabId) { /* ... unchanged ... */ }

  // --- Script Logic ---
  function executeScript(code) { /* ... unchanged ... */ }
  function saveScript() { /* ... unchanged ... */ }
  function loadSavedScripts() { /* ... unchanged ... */ }

  // --- Cloud Logic ---
  function searchGreasyFork(query) { /* ... unchanged ... */ }
  
  // --- FULL IMPLEMENTATIONS OF UNCHANGED FUNCTIONS ---
  
  function createMinimizedIcon() {
    const icon = document.createElement("div");
    icon.className = "minimized-icon";
    icon.title = "TriX Executor";
    icon.style.top = "50px";
    icon.style.right = "50px";
    icon.textContent = "T";
    document.body.appendChild(icon);
    
    let isDraggingIcon = false, dragOffsetIcon = { x: 0, y: 0 };
    icon.addEventListener("mousedown", e => {
        isDraggingIcon = true;
        const rect = icon.getBoundingClientRect();
        dragOffsetIcon = { x: e.clientX - rect.left, y: e.clientY - rect.top };
        e.preventDefault();
    });
    document.addEventListener("mousemove", e => {
        if (isDraggingIcon) {
            const x = e.clientX - dragOffsetIcon.x;
            const y = e.clientY - dragOffsetIcon.y;
            icon.style.left = Math.max(0, Math.min(x, window.innerWidth - icon.offsetWidth)) + "px";
            icon.style.top = Math.max(0, Math.min(y, window.innerHeight - icon.offsetHeight)) + "px";
            icon.style.right = "auto";
        }
    });
    document.addEventListener("mouseup", e => {
        if (isDraggingIcon) {
            isDraggingIcon = false;
            if (e.target === icon) restorePanel();
        }
    });
    return icon;
  }

  function minimizePanel() {
    const panel = document.querySelector(".trix-executor");
    if (panel) { panel.style.display = "none"; isMinimized = true; createMinimizedIcon(); }
  }
  function restorePanel() {
    const panel = document.querySelector(".trix-executor");
    const icon = document.querySelector(".minimized-icon");
    if (panel) panel.style.display = "flex";
    if (icon) icon.remove();
    isMinimized = false;
  }
  function closePanel() {
    const panel = document.querySelector(".trix-executor");
    if (panel) panel.remove();
  }

  function switchTab(tabName) {
    document.querySelectorAll(".sidebar-item").forEach(item => item.classList.remove("active"));
    document.querySelector(`[data-tab="${tabName}"]`).classList.add("active");
    document.querySelectorAll(".content-section").forEach(section => section.classList.remove("active"));
    document.getElementById(`${tabName}-content`).classList.add("active");
    currentTab = tabName;
    if (tabName === "home") loadSavedScripts();
    else if (tabName === "main") updateScriptTabs();
    else if (tabName === "network") renderActiveNetworkView();
  }

  function updateScriptTabs() {
    const tabsContainer = document.getElementById("script-tabs");
    tabsContainer.innerHTML = "";
    scriptTabs.forEach((tab) => {
      const tabElement = document.createElement("div");
      tabElement.className = `script-tab ${tab.id === activeScriptTab ? "active" : ""}`;
      tabElement.textContent = tab.name;
      tabElement.onclick = () => switchScriptTab(tab.id);
      tabElement.ondblclick = () => renameScriptTab(tab.id);
      tabsContainer.appendChild(tabElement);
    });
    const addButton = document.createElement("div");
    addButton.className = "add-tab-btn";
    addButton.textContent = "+";
    addButton.onclick = addScriptTab;
    tabsContainer.appendChild(addButton);
    const activeTab = scriptTabs.find((tab) => tab.id === activeScriptTab);
    if (activeTab) {
      document.getElementById("script-editor").value = activeTab.content;
    }
  }

  function switchScriptTab(tabId) {
    const currentTab = scriptTabs.find((tab) => tab.id === activeScriptTab);
    if (currentTab) {
      currentTab.content = document.getElementById("script-editor").value;
    }
    activeScriptTab = tabId;
    updateScriptTabs();
  }

  function addScriptTab() {
    const newId = "tab" + Date.now();
    const newTab = { id: newId, name: `Script ${scriptTabs.length + 1}`, content: "" };
    scriptTabs.push(newTab);
    activeScriptTab = newId;
    updateScriptTabs();
    showNotification("New tab created");
  }

  function renameScriptTab(tabId) {
    const tab = scriptTabs.find((t) => t.id === tabId);
    if (tab) {
      const newName = prompt("Enter new tab name:", tab.name);
      if (newName && newName.trim()) {
        tab.name = newName.trim();
        updateScriptTabs();
        showNotification("Tab renamed");
      }
    }
  }

  function executeScript(code) {
    try {
      const settings = JSON.parse(GM_getValue("settings"));
      if (settings.antiScam) {
        const suspiciousPatterns = [/document\.cookie/i, /localStorage\./i, /sessionStorage\./i, /\.send\(/i, /fetch\(/i, /XMLHttpRequest/i];
        if (suspiciousPatterns.some((pattern) => pattern.test(code)) && !confirm("This script contains potentially suspicious code that could access your data. Are you sure you want to execute it?")) {
            showNotification("Execution cancelled by user.", "error");
            return;
        }
      }
      (new Function(code))();
      showNotification("Script executed successfully");
    } catch (error) {
      showNotification(`Execution error: ${error.message}`, "error");
      console.error("[TriX] Script execution error:", error);
    }
  }

  function saveScript() {
    const code = document.getElementById("script-editor").value;
    if (!code.trim()) {
      showNotification("No script to save", "error");
      return;
    }
    const name = prompt("Enter script name:");
    if (!name || !name.trim()) return;
    const scripts = JSON.parse(GM_getValue("scripts"));
    const newScript = { id: Date.now().toString(), name: name.trim(), code: code, created: new Date().toISOString() };
    scripts.push(newScript);
    GM_setValue("scripts", JSON.stringify(scripts));
    showNotification("Script saved successfully");
    if (currentTab === "home") loadSavedScripts();
  }
  
  function loadSavedScripts() {
    const scripts = JSON.parse(GM_getValue("scripts"));
    const container = document.getElementById("saved-scripts");
    container.innerHTML = "";
    scripts.forEach((script) => {
      const card = document.createElement("div");
      card.className = "script-card";
      card.innerHTML = `<div class="card-title">${script.name}</div>
            <div class="card-description">Created: ${new Date(script.created).toLocaleDateString()}</div>
            <button class="exec-btn danger delete-btn" style="margin-top: 10px; padding: 5px 10px; font-size: 11px;">Delete</button>`;
      card.addEventListener("click", (e) => {
        if (e.target.classList.contains("delete-btn")) return;
        switchTab("main");
        const newTab = { id: `loaded-${script.id}`, name: script.name, content: script.code };
        const existingTab = scriptTabs.find(t => t.id === newTab.id);
        if(!existingTab) scriptTabs.push(newTab);
        activeScriptTab = newTab.id;
        updateScriptTabs();
        showNotification(`Script "${script.name}" loaded`);
      });
      card.querySelector(".delete-btn").addEventListener("click", (e) => {
        e.stopPropagation();
        if (confirm(`Are you sure you want to delete "${script.name}"?`)) {
          const updatedScripts = scripts.filter((s) => s.id !== script.id);
          GM_setValue("scripts", JSON.stringify(updatedScripts));
          showNotification("Script deleted");
          loadSavedScripts();
        }
      });
      container.appendChild(card);
    });
    if (scripts.length === 0) {
      container.innerHTML = '<div style="text-align: center; color: rgba(255,255,255,0.5); padding: 40px;">No saved scripts</div>';
    }
  }

  function renderStorageView() {
      const lsView = document.getElementById('local-storage-view');
      const ssView = document.getElementById('session-storage-view');
      if (!lsView || !ssView) return;

      const createTable = (storage, type) => {
          let html = '';
          for (let i = 0; i < storage.length; i++) {
              const key = storage.key(i);
              const value = storage.getItem(key);
              html += `<div class="storage-table">
                  <div class="storage-key" title="${key}">${key}</div>
                  <div class="storage-value" title="${value}">${value}</div>
                  <button class="exec-btn danger storage-delete-btn" data-type="${type}" data-key="${key}" style="padding: 5px 8px; font-size:10px;">X</button>
              </div>`;
          }
          return html || '<div style="font-size:12px; color:#888; padding: 10px 0;">Empty</div>';
      };
      
      lsView.innerHTML = createTable(localStorage, 'local');
      ssView.innerHTML = createTable(sessionStorage, 'session');
  }

  async function searchGreasyFork(query) {
      if (!query.trim()) return;
      const resultsContainer = document.getElementById("cloud-results");
      resultsContainer.innerHTML = '<div style="text-align:center; padding:20px;">Searching...</div>';
      try {
          const response = await fetch(`https://greasyfork.org/en/scripts.json?q=${encodeURIComponent(query)}`);
          const scripts = await response.json();
          resultsContainer.innerHTML = "";
          if (!scripts.length) {
              resultsContainer.innerHTML = '<div style="text-align:center; padding:20px; color: rgba(255,255,255,0.5);">No scripts found</div>';
              return;
          }
          scripts.slice(0, 20).forEach(script => {
              const item = document.createElement("div");
              item.className = "script-card";
              item.innerHTML = `<div class="card-title">${script.name}</div>
                  <div class="card-description">${script.description}</div>
                  <div style="font-size:11px; color: rgba(255,255,255,0.6); margin-top:8px;">By ${script.users[0].name} • ${script.total_installs} installs</div>
                  <button class="exec-btn success" data-url="${script.code_url}" data-name="${script.name}" style="margin-top:10px; padding: 5px 10px; font-size:11px;">Load Script</button>`;
              item.querySelector("button").onclick = async (e) => {
                  showNotification("Loading script...");
                  const codeRes = await fetch(e.target.dataset.url);
                  const code = await codeRes.text();
                  switchTab("main");
                  addScriptTab();
                  const activeTab = scriptTabs.find(t => t.id === activeScriptTab);
                  activeTab.name = e.target.dataset.name;
                  activeTab.content = code;
                  updateScriptTabs();
                  showNotification("Script loaded into new tab!");
              };
              resultsContainer.appendChild(item);
          });
      } catch (err) {
          resultsContainer.innerHTML = '<div style="text-align:center; padding:20px; color:red;">Failed to fetch scripts</div>';
      }
  }


  // --- Initialization ---
  function init() {
    const panel = createExecutorPanel();

    // Event Delegation for the whole panel
    panel.addEventListener('click', async e => {
        // Header Controls
        if (e.target.matches('.minimize-btn')) minimizePanel();
        if (e.target.matches('.maximize-btn')) {
            if (panel.style.width === "100vw") {
                Object.assign(panel.style, { width: "650px", height: "500px", top: "50px", left: "auto", right: "50px" });
            } else {
                Object.assign(panel.style, { width: "100vw", height: "100vh", top: "0", left: "0", right: "auto" });
            }
        }
        if (e.target.matches('.close-btn')) closePanel();

        // Main Tab
        if (e.target.matches('#execute-btn')) executeScript(document.getElementById("script-editor").value);
        if (e.target.matches('#execute-clipboard-btn')) {
            try {
                const text = await GM_getClipboard();
                if (text && text.trim()) executeScript(text);
                else showNotification("Clipboard is empty", "error");
            } catch (err) { showNotification("Failed to read clipboard", "error"); }
        }
        if (e.target.matches('#save-script-btn')) saveScript();

        // Network Tab
        if (e.target.matches('#suspend-log-btn')) {
            isLoggerSuspended = !isLoggerSuspended;
            e.target.textContent = isLoggerSuspended ? "Resume Log" : "Suspend Log";
            e.target.classList.toggle('secondary', isLoggerSuspended);
        }
        if (e.target.matches('#clear-log-btn')) {
            const activeConnection = Array.from(monitoredConnections.values()).find(c => c.state === 'OPEN');
            if(activeConnection) activeConnection.log = [];
            renderPacketLog();
        }
        if (e.target.matches('#inject-packet-btn')) {
            if(unsafeWindow.trixSocket && unsafeWindow.trixSocket.readyState === 1) {
                unsafeWindow.trixSocket.send(document.getElementById("injector-input").value);
                showNotification("Packet injected");
            } else {
                showNotification("Not connected to game server.", "error");
            }
        }
        if (e.target.matches('.storage-delete-btn')) {
            const { type, key } = e.target.dataset;
            const storage = type === 'local' ? localStorage : sessionStorage;
            if (confirm(`Delete "${key}" from ${type} storage?`)) {
                storage.removeItem(key);
                renderStorageView();
            }
        }

        // Sidebar
        if (e.target.matches('.sidebar-item')) switchTab(e.target.dataset.tab);
        
        // Network Sub-tabs
        if (e.target.matches('.network-tab')) {
            panel.querySelectorAll('.network-tab').forEach(t => t.classList.remove('active'));
            e.target.classList.add('active');
            activeNetworkTab = e.target.dataset.netTab;
            renderActiveNetworkView();
        }
    });

    // Input/Change events
    panel.addEventListener('input', e => {
        if (e.target.matches('#script-editor')) {
            const activeTab = scriptTabs.find(tab => tab.id === activeScriptTab);
            if (activeTab) activeTab.content = e.target.value;
        }
        if (e.target.matches('#script-search')) {
            const query = e.target.value.toLowerCase();
            panel.querySelectorAll("#saved-scripts .script-card").forEach(card => {
                const title = card.querySelector(".card-title").textContent.toLowerCase();
                card.style.display = title.includes(query) ? "block" : "none";
            });
        }
    });
    panel.addEventListener('change', e => {
        if (e.target.matches('#settings-content input, #settings-content select')) {
            const settings = { theme: "dark", antiScam: document.getElementById("anti-scam").checked, autoUpdate: document.getElementById("auto-update").checked };
            GM_setValue("settings", JSON.stringify(settings));
            showNotification("Settings saved");
        }
    });
    panel.addEventListener('keypress', e => {
        if (e.target.matches('#greasyfork-search') && e.key === 'Enter') {
            searchGreasyFork(e.target.value);
        }
    });

    // Draggability
    let isDragging = false, dragOffset = { x: 0, y: 0 };
    panel.querySelector(".trix-header").onmousedown = (e) => {
      if (e.target.classList.contains("trix-btn")) return;
      isDragging = true;
      const rect = panel.getBoundingClientRect();
      dragOffset = { x: e.clientX - rect.left, y: e.clientY - rect.top };
      e.preventDefault();
    };
    document.onmousemove = (e) => {
      if (isDragging && !isMinimized) {
        const x = e.clientX - dragOffset.x;
        const y = e.clientY - dragOffset.y;
        panel.style.left = Math.max(0, Math.min(x, window.innerWidth - panel.offsetWidth)) + "px";
        panel.style.top = Math.max(0, Math.min(y, window.innerHeight - panel.offsetHeight)) + "px";
        panel.style.right = "auto";
      }
    };
    document.onmouseup = () => { isDragging = false; };

    // Keyboard shortcuts
    document.onkeydown = (e) => {
      if (e.ctrlKey && e.shiftKey && e.key === "E") {
        e.preventDefault();
        const panelExists = document.querySelector('.trix-executor');
        if (!panelExists) init();
        else if (isMinimized) restorePanel();
        else minimizePanel();
      }
    };
    
    // Connect TriX callbacks
    logPacketCallback = (ws, type, data) => {
        const conn = monitoredConnections.get(ws);
        if (conn) {
            conn.log.push({ type, data });
            if (conn.log.length > 200) conn.log.shift(); // Limit log size
            if (currentTab === 'network' && activeNetworkTab === 'logger' && !isLoggerSuspended) {
                renderPacketLog();
            }
        }
    };
    onConnectionStateChange = () => {
        if(currentTab === 'network' && activeNetworkTab === 'logger') {
           renderPacketLog();
        }
    };
    updateConnectionStatus = (status, title) => {
        const el = document.getElementById('trix-conn-status');
        if(el) { el.className = status; el.title = title; }
    };
    updatePingDisplay = async () => {
        const el = document.getElementById('trix-ping-display');
        if (!el) return;
        const startTime = performance.now();
        try {
            await fetch(`${window.location.protocol}//${window.location.host}/favicon.ico?_=${Date.now()}`, { method: 'HEAD', cache: 'no-store' });
            el.textContent = `Ping: ${Math.round(performance.now() - startTime)}`;
        } catch {
            el.textContent = 'Ping: ---';
        }
    };
    setInterval(updatePingDisplay, 5000);
    let lastFrameTime = performance.now(), frameCount = 0;
    const fpsDisplay = document.getElementById('trix-fps-display');
    function updateFPS(now) {
        frameCount++;
        if (now >= lastFrameTime + 1000) {
            if(fpsDisplay) fpsDisplay.textContent = `FPS: ${frameCount}`;
            lastFrameTime = now;
            frameCount = 0;
        }
        requestAnimationFrame(updateFPS);
    }
    if (fpsDisplay) requestAnimationFrame(updateFPS);

    // Initial load
    loadSavedScripts();
    updateScriptTabs();
    showNotification("TriX Executor loaded! Press Ctrl+Shift+E to toggle.");
  }
  
  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", init);
  } else {
    init();
  }
})();