TriX Executor v5.0.1 (REVAMP)

A modern, powerful developer toolkit and script executor for enhancing your web experience. Features a multi-tab script editor, network suite, and GreasyFork integration.

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         TriX Executor v5.0.1 (REVAMP)
// @namespace    https://greasyfork.org/en/users/COURTESYCOIL
// @version      5.0.1
// @description  A modern, powerful developer toolkit and script executor for enhancing your web experience. Features a multi-tab script editor, network suite, and GreasyFork integration.
// @author       Painsel
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_getClipboard
// @grant        unsafeWindow
// @license      MIT
// @icon         data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iIzRmNDZlNSI+PHBhdGggZD0iTTE4LjM2IDIyLjA1bC00LjQyLTQuNDJDMTMuNDcgMTcuODcgMTMgMTguMTcgMTMgMTguNUMxMyAyMC45OSAxNS4wMSAyMyAxNy.1IDIzaDMuNTRjLTIuNDUtMS40OC00LjQyLTMuNDUtNS4LTUuOTV6TTggMTNjMS42NiAwIDMuMTgtLjU5IDQuMzgtMS42MkwxMCAxMy41VjIybDIuNS0yLjVMMTggMTMuOTZjLjM1LS40OC42NS0xIC44Ny0xLjU1QzE4LjYxIDEzLjQxIDE4IDEyLjM0IDE4IDEyVjBoLTJ2MTJjMCAuMzQtLjAyLjY3LS4wNiAxLS4zMy4xOC0uNjguMy0xLjA0LjM3LTEuNzMgMC0zLjI3LS45My00LjE2LTIuMzZMNiAxMy42VjVINXY4eiIvPjwvc3ZnPg==
// ==/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";
  let settings = { theme: 'dark', antiScam: true, autoUpdate: true };

  // Initialize storage
  if (!GM_getValue("scripts", null)) {
    GM_setValue("scripts", JSON.stringify([]));
  }
  const savedSettings = GM_getValue("settings", null);
  if (savedSettings) {
      settings = { ...settings, ...JSON.parse(savedSettings) };
  } else {
      GM_setValue("settings", JSON.stringify(settings));
  }

  // 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 10px 0 15px;
        border-radius: 12px 12px 0 0; flex-shrink: 0; cursor: move; user-select: none;
    }
    .trix-title-area { display: flex; align-items: center; gap: 15px; }
    #trix-user-profile { display: none; align-items: center; gap: 8px; }
    #trix-user-pfp { width: 24px; height: 24px; border-radius: 50%; border: 2px solid rgba(255,255,255,0.5); }
    #trix-user-name { font-size: 12px; font-weight: 600; }
    .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; }

    /* Other styles remain mostly the same... */
    .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-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.receive { color: #f0abfc; }
    .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; }
    .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);

  function showNotification(message, type = "success") { /* unchanged */ }

  function createExecutorPanel() {
    const panel = document.createElement("div");
    panel.className = "trix-executor";
    panel.innerHTML = `
        <div class="trix-header">
            <div class="trix-title-area">
                <div id="trix-user-profile">
                    <img id="trix-user-pfp" src="">
                    <span id="trix-user-name"></span>
                </div>
                <div class="g-signin2" data-onsuccess="onSignIn" data-theme="dark"></div>
                <div class="trix-title">TriX Executor v5.0.1 (REVAMP)</div>
            </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="files">📁 Files</div>
                <div class="sidebar-item" data-tab="settings">⚙ Settings</div>
            </div>
            <div class="trix-content">
                ${createHomeContent()}
                ${createMainContent()}
                ${createNetworkContent()}
                ${createCloudContent()}
                ${createFilesContent()}
                ${createSettingsContent()}
            </div>
        </div>
    `;
    document.body.appendChild(panel);
    return panel;
  }

  // --- Content Section Creators ---
  function createHomeContent() { /* unchanged */ }
  function createMainContent() { /* unchanged */ }
  function createCloudContent() { /* unchanged */ }
  function createSettingsContent() { /* unchanged */ }
  function createNetworkContent() { /* unchanged */ }
  function createFilesContent() {
      return `<div class="content-section" id="files-content">
                  <h2>File Explorer</h2>
                  <p style="font-size:12px; color: #ccc; margin-bottom:10px;">A read-only view of data this script has stored in your browser.</p>
                  <div class="script-cards" id="file-explorer-view"></div>
              </div>`;
  }
  
  // --- Network View Renderers ---
  function renderActiveNetworkView() { /* unchanged */ }
  function renderPacketLog() { /* unchanged */ }
  function renderStorageView() { /* unchanged */ }

  // --- File Explorer ---
  function loadFileExplorer() {
      const container = document.getElementById("file-explorer-view");
      if (!container) return;
      container.innerHTML = "";

      const gmScripts = JSON.parse(GM_getValue("scripts", "[]"));
      const gmSettings = JSON.parse(GM_getValue("settings", "{}"));

      const files = [
          { name: "scripts.json", type: "GM Storage", size: `${JSON.stringify(gmScripts).length} bytes` },
          { name: "settings.json", type: "GM Storage", size: `${JSON.stringify(gmSettings).length} bytes` },
          ...gmScripts.map(s => ({ name: s.name, type: 'Saved Script', size: `${s.code.length} chars` })),
      ];

      files.forEach(file => {
          const card = document.createElement("div");
          card.className = "script-card";
          card.style.cursor = "default";
          card.innerHTML = `<div class="card-title">📄 ${file.name}</div>
                          <div class="card-description">${file.type} • ${file.size}</div>`;
          container.appendChild(card);
      });
  }
  
  // --- Panel Management & Draggability ---
  function minimizePanel() { /* unchanged */ }
  function restorePanel() { /* unchanged */ }
  function createMinimizedIcon() { /* unchanged */ }
  function closePanel() { /* unchanged */ }

  // --- Tab Management ---
  function switchTab(tabName) {
      if (tabName === "files") { loadFileExplorer(); }
      // ... rest of function is 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/HELPER FUNCTIONS ---
  // (This section contains the full code for functions marked as unchanged for completeness)
  function showNotification(message, type = "success") { const n = document.createElement("div"); n.className="notification"; n.textContent=message; if(type==="error"){n.style.background="linear-gradient(135deg, #ef4444 0%, #dc2626 100%)";} document.body.appendChild(n); setTimeout(()=>n.classList.add("show"),100); setTimeout(()=>{n.classList.remove("show"); setTimeout(()=>n.remove(),300)},3000); }
  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 s = 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" ${s.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" ${s.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" ${s.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>`; }
  function renderActiveNetworkView() { const c=document.querySelector("#network-content .network-view-content"); if(!c)return; c.innerHTML=""; switch(activeNetworkTab){case "logger": c.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": c.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": c.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": c.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 l=document.getElementById("packet-log-output"); if(!l)return; const a=Array.from(monitoredConnections.values()).find(c=>c.state==='OPEN'||c.log.length>0); if(!a){l.textContent='Waiting for active connection...'; return;} if(isLoggerSuspended)return; l.innerHTML=''; a.log.forEach(p=>{const i=document.createElement('div'); i.className=`log-item ${p.type}`; i.textContent=`[${p.type.toUpperCase()}] ${p.data}`; l.appendChild(i);}); l.scrollTop=l.scrollHeight; }
  function renderStorageView() { const l=document.getElementById('local-storage-view'),s=document.getElementById('session-storage-view'); if(!l||!s)return; const c=(st,t)=>{let h=''; for(let i=0;i<st.length;i++){const k=st.key(i),v=st.getItem(k); h+=`<div class="storage-table"><div class="storage-key" title="${k}">${k}</div><div class="storage-value" title="${v}">${v}</div><button class="exec-btn danger storage-delete-btn" data-type="${t}" data-key="${k}" style="padding: 5px 8px; font-size:10px;">X</button></div>`;} return h||'<div style="font-size:12px; color:#888; padding: 10px 0;">Empty</div>';}; l.innerHTML=c(localStorage,'local'); s.innerHTML=c(sessionStorage,'session'); }
  function createMinimizedIcon() { const i=document.createElement("div"); i.className="minimized-icon"; i.title="TriX Executor"; i.style.top="50px"; i.style.right="50px"; i.textContent="T"; document.body.appendChild(i); let d=!1,o={x:0,y:0}; i.addEventListener("mousedown",e=>{d=!0;const r=i.getBoundingClientRect();o={x:e.clientX-r.left,y:e.clientY-r.top};e.preventDefault();}); document.addEventListener("mousemove",e=>{if(d){const x=e.clientX-o.x,y=e.clientY-o.y; i.style.left=Math.max(0,Math.min(x,window.innerWidth-i.offsetWidth))+"px"; i.style.top=Math.max(0,Math.min(y,window.innerHeight-i.offsetHeight))+"px"; i.style.right="auto";}}); document.addEventListener("mouseup",e=>{if(d){d=!1;if(e.target===i)restorePanel();}}); return i; }
  function minimizePanel() { const p=document.querySelector(".trix-executor"); if(p){p.style.display="none"; isMinimized=!0; createMinimizedIcon();} }
  function restorePanel() { const p=document.querySelector(".trix-executor"), i=document.querySelector(".minimized-icon"); if(p)p.style.display="flex"; if(i)i.remove(); isMinimized=!1; }
  function closePanel() { const p=document.querySelector(".trix-executor"); if(p)p.remove(); }
  function switchTab(tabName) { document.querySelectorAll(".sidebar-item").forEach(i=>i.classList.remove("active")); document.querySelector(`[data-tab="${tabName}"]`).classList.add("active"); document.querySelectorAll(".content-section").forEach(s=>s.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(); else if(tabName==="files")loadFileExplorer(); }
  function updateScriptTabs() { const t=document.getElementById("script-tabs"); t.innerHTML=""; scriptTabs.forEach(tab=>{const e=document.createElement("div"); e.className=`script-tab ${tab.id===activeScriptTab?"active":""}`; e.textContent=tab.name; e.onclick=()=>switchScriptTab(tab.id); e.ondblclick=()=>renameScriptTab(tab.id); t.appendChild(e);}); const a=document.createElement("div"); a.className="add-tab-btn"; a.textContent="+"; a.onclick=addScriptTab; t.appendChild(a); const c=scriptTabs.find(tab=>tab.id===activeScriptTab); if(c)document.getElementById("script-editor").value=c.content; }
  function switchScriptTab(tabId) { const c=scriptTabs.find(tab=>tab.id===activeScriptTab); if(c)c.content=document.getElementById("script-editor").value; activeScriptTab=tabId; updateScriptTabs(); }
  function addScriptTab() { const n="tab"+Date.now(), t={id:n,name:`Script ${scriptTabs.length+1}`,content:""}; scriptTabs.push(t); activeScriptTab=n; updateScriptTabs(); showNotification("New tab created"); }
  function renameScriptTab(tabId) { const t=scriptTabs.find(t=>t.id===tabId); if(t){const n=prompt("Enter new tab name:",t.name); if(n&&n.trim()){t.name=n.trim(); updateScriptTabs(); showNotification("Tab renamed");}}}
  function executeScript(code) { try{if(settings.antiScam&&[/document\.cookie/i,/localStorage\./i,/sessionStorage\./i,/\.send\(/i,/fetch\(/i,/XMLHttpRequest/i].some(p=>p.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(e){showNotification(`Execution error: ${e.message}`,"error"); console.error("[TriX] Script execution error:",e);}}
  function saveScript() { const c=document.getElementById("script-editor").value; if(!c.trim()){showNotification("No script to save","error"); return;} const n=prompt("Enter script name:"); if(!n||!n.trim())return; const s=JSON.parse(GM_getValue("scripts")), a={id:Date.now().toString(),name:n.trim(),code:c,created:new Date().toISOString()}; s.push(a); GM_setValue("scripts",JSON.stringify(s)); showNotification("Script saved successfully"); if(currentTab==="home")loadSavedScripts(); }
  function loadSavedScripts() { const s=JSON.parse(GM_getValue("scripts")), c=document.getElementById("saved-scripts"); c.innerHTML=""; s.forEach(script=>{const a=document.createElement("div"); a.className="script-card"; a.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>`; a.addEventListener("click",e=>{if(e.target.classList.contains("delete-btn"))return; switchTab("main"); const n={id:`loaded-${script.id}`,name:script.name,content:script.code}, t=scriptTabs.find(t=>t.id===n.id); if(!t)scriptTabs.push(n); activeScriptTab=n.id; updateScriptTabs(); showNotification(`Script "${script.name}" loaded`);}); a.querySelector(".delete-btn").addEventListener("click",e=>{e.stopPropagation(); if(confirm(`Are you sure you want to delete "${script.name}"?`)){const t=s.filter(s=>s.id!==script.id); GM_setValue("scripts",JSON.stringify(t)); showNotification("Script deleted"); loadSavedScripts();}}); c.appendChild(a);}); if(s.length===0)c.innerHTML='<div style="text-align: center; color: rgba(255,255,255,0.5); padding: 40px;">No saved scripts</div>'; }
  async function searchGreasyFork(query) { if(!query.trim())return; const r=document.getElementById("cloud-results"); r.innerHTML='<div style="text-align:center; padding:20px;">Searching...</div>'; try{const e=await fetch(`https://greasyfork.org/en/scripts.json?q=${encodeURIComponent(query)}`),s=await e.json(); r.innerHTML=""; if(!s.length){r.innerHTML='<div style="text-align:center; padding:20px; color: rgba(255,255,255,0.5);">No scripts found</div>'; return;} s.slice(0,20).forEach(script=>{const c=document.createElement("div"); c.className="script-card"; c.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>`; c.querySelector("button").onclick=async e=>{showNotification("Loading script..."); const t=await fetch(e.target.dataset.url), a=await t.text(); switchTab("main"); addScriptTab(); const d=scriptTabs.find(t=>t.id===activeScriptTab); d.name=e.target.dataset.name; d.content=a; updateScriptTabs(); showNotification("Script loaded into new tab!");}; r.appendChild(c);});}catch(e){r.innerHTML='<div style="text-align:center; padding:20px; color:red;">Failed to fetch scripts</div>';}}

  // --- GOOGLE SIGN-IN ---
  unsafeWindow.onSignIn = function(googleUser) {
      const profile = googleUser.getBasicProfile();
      const userName = profile.getName();
      const userImageUrl = profile.getImageUrl();

      const profileDiv = document.querySelector('.trix-executor #trix-user-profile');
      const signInButton = document.querySelector('.trix-executor .g-signin2');
      const titleEl = document.querySelector('.trix-executor .trix-title');

      if (profileDiv && signInButton) {
          document.getElementById('trix-user-pfp').src = userImageUrl;
          document.getElementById('trix-user-name').textContent = userName;
          profileDiv.style.display = 'flex';
          signInButton.style.display = 'none';
          if (titleEl) titleEl.style.display = 'none'; // Hide default title
      }
  };

  // --- LOADING SCREEN ---
  function showLoadingScreen(callback) {
      const themeVars = { accent: '#4f46e5', shadow1: '#4338ca', shadow2: '#312e81' };
      const loadingStyle = document.createElement('style');
      loadingStyle.textContent = `
          #trix-loading-screen, #trix-loading-settings-modal { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background-color: #11111b; z-index: 2147483647; display: flex; flex-direction: column; align-items: center; justify-content: center; color: #fff; font-family: 'Segoe UI', sans-serif; opacity: 0; transition: opacity 0.5s ease-in-out; pointer-events: none; }
          #trix-loading-screen.visible, #trix-loading-settings-modal.visible { opacity: 1; pointer-events: auto; }
          .loading-content { opacity: 1; transition: opacity 1s ease-in-out; text-align: center; }
          .loading-content.fade-out { opacity: 0; }
          .loading-header { font-size: 5rem; font-weight: bold; color: ${themeVars.accent}; text-shadow: 2px 2px 0px ${themeVars.shadow1}, 4px 4px 0px ${themeVars.shadow2}, 6px 6px 10px rgba(0,0,0,0.5); margin-bottom: 2rem; }
          .loading-bar-container { width: 50%; max-width: 600px; height: 20px; background-color: #2d2d44; border-radius: 10px; border: 1px solid #4f46e5; overflow: hidden; margin-bottom: 1rem; }
          .loading-bar-progress { width: 0%; height: 100%; background: linear-gradient(90deg, #7c3aed, ${themeVars.accent}); border-radius: 10px; transition: width 0.1s linear; }
          .loading-message { font-style: italic; color: #9ca3af; min-height: 1.2em; margin-top: 1rem; }
          #trix-loading-settings-modal .settings-grid { display: grid; grid-template-columns: 1fr 2fr; gap: 1rem 2rem; align-items: center; margin-top: 2rem; }
      `;
      document.head.appendChild(loadingStyle);

      const loadingScreen = document.createElement('div');
      loadingScreen.id = 'trix-loading-screen';
      const loadingMessages = ["Enhancing experience, not breaking rules.", "A dev tool for curious minds.", "Building, not battling.", "Scripts are your superpower."];
      loadingScreen.innerHTML = `<div class="loading-content">
              <div class="loading-header">TriX Executor</div>
              <div class="loading-bar-container"><div class="loading-bar-progress"></div></div>
              <div class="loading-message">${loadingMessages[0]}</div>
          </div>`;
      
      const settingsModal = document.createElement('div');
      settingsModal.id = 'trix-loading-settings-modal';
      settingsModal.style.visibility = 'hidden';
      settingsModal.innerHTML = `<div class="loading-content">
              <div class="loading-header">Settings</div>
              <div class="settings-grid">
                  <label class="settings-label">Theme:</label>
                  <select class="settings-select" id="trix-theme-select-loading"><option value="dark">Dark (Default)</option></select>
              </div>
              <button id="trix-resume-loading" class="exec-btn" style="margin-top: 2rem;">Resume</button>
          </div>`;

      document.body.append(loadingScreen, settingsModal);
      setTimeout(() => loadingScreen.classList.add("visible"), 10);

      const progressBar = loadingScreen.querySelector('.loading-bar-progress');
      const messageText = loadingScreen.querySelector('.loading-message');
      let progress = 0;
      const interval = setInterval(() => {
          progress += Math.random() * 5;
          progressBar.style.width = `${Math.min(progress, 100)}%`;
          if (progress > 25 && progress < 30) messageText.textContent = loadingMessages[1];
          if (progress > 55 && progress < 60) messageText.textContent = loadingMessages[2];
          if (progress > 85 && progress < 90) messageText.textContent = loadingMessages[3];
          if (progress >= 100) {
              clearInterval(interval);
              window.removeEventListener('keydown', f12Handler);
              loadingScreen.querySelector('.loading-content').classList.add('fade-out');
              setTimeout(() => {
                  loadingScreen.classList.remove('visible');
                  setTimeout(() => {
                      loadingScreen.remove(); settingsModal.remove(); loadingStyle.remove();
                      callback();
                  }, 500);
              }, 500);
          }
      }, 100);

      const f12Handler = e => { if (e.key === 'F12') { e.preventDefault(); clearInterval(interval); settingsModal.style.visibility = 'visible'; settingsModal.classList.add('visible'); } };
      window.addEventListener('keydown', f12Handler);
      settingsModal.querySelector('#trix-resume-loading').onclick = () => {
          settingsModal.classList.remove('visible');
          // Since there are no settings to save, we just resume loading. A real implementation would save settings here.
          showLoadingScreen(callback); // Restart animation for simplicity
      };
  }

  // --- Initialization ---
  function init() {
    const panel = createExecutorPanel();
    const googleApiScript = document.createElement('script');
    googleApiScript.src = "https://apis.google.com/js/platform.js";
    googleApiScript.async = true;
    googleApiScript.defer = true;
    document.head.appendChild(googleApiScript);

    // Event Delegation for the whole panel
    panel.addEventListener('click', async e => {
        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();
        if (e.target.matches('#execute-btn')) executeScript(document.getElementById("script-editor").value);
        if (e.target.matches('#execute-clipboard-btn')) { try { const t = await GM_getClipboard(); if (t && t.trim()) executeScript(t); else showNotification("Clipboard is empty", "error"); } catch (err) { showNotification("Failed to read clipboard", "error"); } }
        if (e.target.matches('#save-script-btn')) saveScript();
        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 a = Array.from(monitoredConnections.values()).find(c => c.state === 'OPEN'); if(a) a.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 s = type === 'local' ? localStorage : sessionStorage; if (confirm(`Delete "${key}" from ${type} storage?`)) { s.removeItem(key); renderStorageView(); } }
        if (e.target.matches('.sidebar-item')) switchTab(e.target.dataset.tab);
        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(); }
    });
    panel.addEventListener('input', e => { if (e.target.matches('#script-editor')) { const t = scriptTabs.find(tab => tab.id === activeScriptTab); if (t) t.content = e.target.value; } if (e.target.matches('#script-search')) { const q = e.target.value.toLowerCase(); panel.querySelectorAll("#saved-scripts .script-card").forEach(c => { const t = c.querySelector(".card-title").textContent.toLowerCase(); c.style.display = t.includes(q) ? "block" : "none"; }); } });
    panel.addEventListener('change', e => { if (e.target.matches('#settings-content input, #settings-content select')) { settings.antiScam = document.getElementById("anti-scam").checked; settings.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); } });
    let isDragging = false, dragOffset = { x: 0, y: 0 };
    panel.querySelector(".trix-header").onmousedown = (e) => { if (e.target.closest(".trix-btn, .g-signin2")) return; isDragging = true; const r = panel.getBoundingClientRect(); dragOffset = { x: e.clientX - r.left, y: e.clientY - r.top }; e.preventDefault(); };
    document.onmousemove = (e) => { if (isDragging && !isMinimized) { const x = e.clientX - dragOffset.x, 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; };
    document.onkeydown = (e) => { if (e.ctrlKey && e.shiftKey && e.key === "E") { e.preventDefault(); const p = document.querySelector('.trix-executor'); if (!p) init(); else if (isMinimized) restorePanel(); else minimizePanel(); } };
    
    // Connect TriX callbacks
    logPacketCallback = (ws, type, data) => { const c = monitoredConnections.get(ws); if (c) { c.log.push({ type, data }); if (c.log.length > 200) c.log.shift(); if (currentTab === 'network' && activeNetworkTab === 'logger' && !isLoggerSuspended) renderPacketLog(); } };
    onConnectionStateChange = () => { if(currentTab === 'network' && activeNetworkTab === 'logger') renderPacketLog(); };
    updateConnectionStatus = (status, title) => { const e = document.getElementById('trix-conn-status'); if(e) { e.className = status; e.title = title; } };
    updatePingDisplay = async () => { const e = document.getElementById('trix-ping-display'); if (!e) return; const s = performance.now(); try { await fetch(`${window.location.protocol}//${window.location.host}/favicon.ico?_=${Date.now()}`, { method: 'HEAD', cache: 'no-store' }); e.textContent = `Ping: ${Math.round(performance.now() - s)}`; } catch { e.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);

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