您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
A powerful, multi-tabbed, persistent code execution and network logging environment for developers. Now with configurable themes, Anti-Lag mode, a music player, and more.
当前为
// ==UserScript== // @name TriX Executor (BETA) for Territorial.io // @namespace https://gf.qytechs.cn/en/users/COURTESYCOIL // @version Beta-Sculptor-2024.09.16 // @description A powerful, multi-tabbed, persistent code execution and network logging environment for developers. Now with configurable themes, Anti-Lag mode, a music player, and more. // @author Painsel // @match *://territorial.io/* // @require https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-javascript.min.js // @grant GM_getValue // @grant GM_setValue // @grant unsafeWindow // @run-at document-start // @license MIT // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iIzAwYWFmZiI+PHBhdGggZD0iTTE4LjM2IDIyLjA1bC00LjQyLTQuNDJDMTMuNDcgMTcuODcgMTMgMTguMTcgMTMgMTguNUMxMyAyMC45OSAxNS4wMSAyMyAxNy41IDIzaDMuNTRjLTIuNDUtMS40OC00LjQyLTMuNDUtNS5LTUuOTV6TTggMTNjMS42NiAwIDMuMTgtLjU5IDQuMzgtMS42MkwxMCAxMy41VjIybDIuNS0yLjVMMTggMTMuOTZjLjM1LS40OC42NS0xIC44Ny0xLjU1QzE4LjYxIDEzLjQxIDE4IDEyLjM0IDE4IDEyVjBoLTJ2MTJjMCAuMzQtLjAyLjY3LS4wNiAxLS4zMy4xOC0uNjguMy0xLjA0LjM3LTEuNzMgMC0zLjI3LS45My00LjE2LTIuMzZMNiAxMy42VjVINXY4eiIvPjwvc3ZnPg== // ==/UserScript== /* global Prism, unsafeWindow */ (function() { 'use strict'; // --- Global State & Settings --- let settings = { theme: 'vscode', // vscode, retro, crimson antiLag: false }; let interceptionEnabled = false; let isLoggerSuspended = false; let queuedMessages = new Map(); let renderInterceptorQueue = () => {}; let customWs = null; let antiLagHeartbeatInterval = null; // New state for connection monitoring const monitoredConnections = new Map(); let viewingConnection = null; // Holds the WebSocket instance being viewed in detail let updateConnectionStatus = () => {}; let updatePingDisplay = () => {}; let logPacketCallback = () => {}; let showToastCallback = null; let connectionUrlQueue = []; // Load settings at the very beginning const savedSettings = GM_getValue('trixSettings', {}); settings = { ...settings, ...savedSettings }; // --- WebSocket Proxy Setup --- const OriginalWebSocket = unsafeWindow.WebSocket; unsafeWindow.WebSocket = function(url, protocols) { if (!url.includes('/s52/')) { return new OriginalWebSocket(url, protocols); } console.log(`[TriX] Intercepting WebSocket connection to: ${url}`); if (showToastCallback) { showToastCallback(url); } else { connectionUrlQueue.push(url); } const ws = new OriginalWebSocket(url, protocols); monitoredConnections.set(ws, { url, state: 'CONNECTING', log: [] }); let originalOnMessageHandler = null; const originalSend = ws.send.bind(ws); ws.send = function(data) { logPacketCallback(ws, 'send', data); // Log original send attempt if (interceptionEnabled && !settings.antiLag) { const messageId = `send-${Date.now()}-${Math.random()}`; const promise = new Promise(resolve => queuedMessages.set(messageId, { direction: 'send', data, resolve })); renderInterceptorQueue(); promise.then(decision => { queuedMessages.delete(messageId); renderInterceptorQueue(); if (decision.action === 'forward') { originalSend(decision.data); logPacketCallback(ws, 'send-forwarded', `[Forwarded] ${decision.data}`); } else { logPacketCallback(ws, 'send-blocked', `[Blocked] ${data}`); } }); } else { return originalSend(data); } }; Object.defineProperty(ws, 'onmessage', { get: () => originalOnMessageHandler, set: (handler) => { originalOnMessageHandler = handler; ws.addEventListener('message', event => { logPacketCallback(ws, 'receive', event.data); if (interceptionEnabled && !settings.antiLag) { const messageId = `recv-${Date.now()}-${Math.random()}`; const promise = new Promise(resolve => queuedMessages.set(messageId, { direction: 'receive', data: event.data, resolve })); renderInterceptorQueue(); promise.then(decision => { queuedMessages.delete(messageId); renderInterceptorQueue(); if (decision.action === 'forward') { logPacketCallback(ws, 'receive-forwarded', `[Forwarded] ${decision.data}`); if (originalOnMessageHandler) originalOnMessageHandler({ data: decision.data }); } else { logPacketCallback(ws, 'receive-blocked', `[Blocked] ${event.data}`); } }); } else { if (originalOnMessageHandler) originalOnMessageHandler(event); } }); }, configurable: true }); ws.addEventListener('open', () => { const conn = monitoredConnections.get(ws); if(conn) conn.state = 'OPEN'; if (state.activeTabId === 'logger') renderPacketLoggerView(); // Update monitor updateConnectionStatus('connected', 'Connection established.'); if (settings.antiLag) { /* ... Anti-Lag logic ... */ } }); ws.addEventListener('close', (event) => { monitoredConnections.delete(ws); if (state.activeTabId === 'logger') renderPacketLoggerView(); // Update monitor if (unsafeWindow.webSocketManager && unsafeWindow.webSocketManager.activeSocket === ws) { unsafeWindow.webSocketManager.activeSocket = null; updateConnectionStatus('disconnected', `Disconnected. Code: ${event.code}`); updatePingDisplay('---'); } }); ws.addEventListener('error', () => { monitoredConnections.delete(ws); if (state.activeTabId === 'logger') renderPacketLoggerView(); // Update monitor if (unsafeWindow.webSocketManager && unsafeWindow.webSocketManager.activeSocket === ws) { updateConnectionStatus('error', 'A connection error occurred.'); } }); unsafeWindow.webSocketManager = { activeSocket: ws }; return ws; }; // --- Main script logic --- function initialize() { if (settings.antiLag) { console.log('[TriX] Anti-Lag mode enabled. UI is disabled.'); return; } const shadowHost = document.createElement('div'); shadowHost.id = 'trix-host'; shadowHost.dataset.theme = settings.theme; document.body.appendChild(shadowHost); const shadowRoot = shadowHost.attachShadow({ mode: 'open' }); const styleElement = document.createElement('style'); styleElement.textContent = ` /* --- PrismJS Theme --- */ code[class*="language-"],pre[class*="language-"]{color:#ccc;background:0 0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;tab-size:4;}pre[class*="language-"]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*="language-"],pre[class*="language-"]{background:#2d2d2d}:not(pre)>code[class*="language-"]{padding:.1em;border-radius:.3em;white-space:normal}.token.comment{color:#999}.token.punctuation{color:#ccc}.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.number,.token.function{color:#f08d49}.token.property,.token.class-name,.token.constant,.token.symbol{color:#f8c555}.token.selector,.token.important,.token.atrule,.token.keyword,.token.builtin{color:#cc99cd}.token.string,.token.char,.attr-value,.token.regex,.token.variable{color:#7ec699}.token.operator,.token.entity,.token.url{color:#67cdcc} /* --- THEMES --- */ :host { /* VSCode Default Theme */ --accent-color: #3b82f6; --accent-color-hover: #4c8df6; --bg-darker: #1e1e22; --bg-dark: #2a2a30; --bg-medium: #3a3a42; --border-color: #444; --text-color: #e5e7eb; --text-muted: #9ca3af; --button-shadow: none; --container-shadow: 0 5px 25px rgba(0,0,0,0.5); } :host([data-theme="retro"]) { --accent-color: #00ff9b; --accent-color-hover: #3affb2; --bg-darker: #121212; --bg-dark: #1d1d1d; --bg-medium: #282828; --border-color: #00ff9b; --text-color: #00ff9b; --text-muted: #00b36d; --button-shadow: 2px 2px 0px #00b36d; --container-shadow: 4px 4px 0px #00b36d, 8px 8px 15px rgba(0,0,0,0.5); font-family: 'Courier New', monospace !important; } :host([data-theme="crimson"]) { --accent-color: #e11d48; --accent-color-hover: #f43f5e; --bg-darker: #1f0a0e; --bg-dark: #2c0e14; --bg-medium: #4c1d27; --border-color: #881337; --text-color: #fce7f3; --text-muted: #fda4af; --button-shadow: 0 0 8px rgba(225, 29, 72, 0.5); --container-shadow: 0 5px 25px rgba(0,0,0,0.5); } @keyframes trix-fade-in { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } } @keyframes trix-slide-in { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } #trix-toggle-btn { position: fixed; top: 15px; right: 15px; z-index: 99999; width: 50px; height: 50px; background-color: rgba(30, 30, 34, 0.8); color: var(--accent-color); border: 2px solid var(--accent-color); border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 24px; transition: all 0.3s ease; backdrop-filter: blur(5px); font-family: monospace; box-shadow: 0 0 10px var(--accent-color); } #trix-toggle-btn:hover { transform: scale(1.1) rotate(15deg); box-shadow: 0 0 15px var(--accent-color); } #trix-container { position: fixed; top: 80px; right: 15px; width: 500px; min-height: 450px; z-index: 99998; color: var(--text-color); font-family: 'Segoe UI', 'Roboto', sans-serif; border-radius: 10px; overflow: hidden; box-shadow: var(--container-shadow); display: flex; flex-direction: column; backdrop-filter: blur(10px); animation: trix-slide-in 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards; resize: both; background-color: rgba(30, 30, 34, 0.9); border: 1px solid var(--border-color); } #trix-container.hidden { display: none; } #trix-header, #trix-footer { cursor: move; user-select: none; flex-shrink: 0; } #trix-header { padding: 10px 15px; display: flex; justify-content: space-between; align-items: center; background-color: rgba(0,0,0,0.3); } .trix-title { font-size: 16px; font-weight: 600; } .trix-version-tag { font-size: 10px; font-weight: 300; opacity: 0.7; margin-left: 8px; } #trix-header-controls { display: flex; align-items: center; gap: 15px; font-size: 12px; } #trix-close-btn { cursor: pointer; font-size: 20px; font-weight: bold; opacity: 0.7; } #trix-close-btn:hover { opacity: 1; color: #ef4444; } #trix-conn-status { width: 10px; height: 10px; border-radius: 50%; } #trix-conn-status.connected { background-color: #28a745; } #trix-conn-status.disconnected { background-color: #dc3545; } #trix-conn-status.connecting { background-color: #ffc107; } .ping-good { color: #28a745; } .ping-ok { color: #ffc107; } .ping-bad { color: #dc3545; } #trix-content { padding: 0 15px 15px 15px; flex-grow: 1; display: flex; flex-direction: column; overflow: hidden; background-color: var(--bg-darker); } .trix-tabs { display: flex; flex-wrap: wrap; flex-shrink: 0; } .trix-tab { background: var(--bg-dark); color: var(--text-muted); padding: 8px 12px; cursor: pointer; border-top: 2px solid transparent; border-left: 1px solid transparent; border-right: 1px solid transparent; border-radius: 5px 5px 0 0; margin-right: 2px; position: relative; } .trix-tab.active { background: var(--bg-darker); font-weight: 600; color: var(--text-color); border-top-color: var(--accent-color); } .trix-tab-name { padding-right: 15px; } .trix-tab-close { position: absolute; top: 50%; right: 5px; transform: translateY(-50%); opacity: 0.6; } .trix-tab-close:hover { opacity: 1; color: #ef4444; } #trix-new-tab-btn { background: none; border: none; color: var(--text-muted); font-size: 20px; cursor: pointer; padding: 5px 10px; } .trix-tab-rename-input { background: transparent; border: 1px solid var(--accent-color); color: inherit; font-family: inherit; font-size: inherit; padding: 0; margin: 0; width: 100px; } .trix-view { display: flex; flex-direction: column; flex-grow: 1; padding-top: 10px; overflow: hidden; } .trix-action-bar { display: flex; gap: 10px; margin-top: 10px; flex-shrink: 0; } .trix-button { background-color: var(--accent-color); color: white; border: none; padding: 10px 15px; cursor: pointer; border-radius: 6px; flex-grow: 1; transition: all 0.2s; font-weight: 600; box-shadow: var(--button-shadow); } .trix-button:hover { filter: brightness(1.1); background-color: var(--accent-color-hover); } .trix-button:disabled { background-color: #555; cursor: not-allowed; filter: none; box-shadow: none; } #trix-suspend-log-btn.suspended { background-color: #ffc107; color: #1e1e22; } #trix-suspend-log-btn.suspended:hover { background-color: #ffc107; filter: brightness(1.1); } .trix-input { background: var(--bg-dark); border: 1px solid var(--border-color); color: var(--text-color); padding: 8px; border-radius: 5px; font-family: monospace; box-sizing: border-box; } .trix-status-bar { margin-top: 10px; padding: 5px; background: rgba(0,0,0,0.2); font-size: 12px; border-radius: 3px; min-height: 1em; } #trix-welcome-view h2 { margin-top: 0; color: #fff; } #trix-welcome-view p { color: var(--text-color); line-height: 1.6; } #trix-welcome-view code { background: var(--bg-dark); padding: 2px 5px; border-radius: 4px; font-family: Consolas, monospace; } #trix-ws-client-view { display: grid; grid-template-rows: auto auto 1fr auto; gap: 10px; height: 100%; } .trix-ws-client-grid { display: grid; grid-template-columns: 1fr auto; gap: 10px; align-items: center; } .trix-ws-client-controls { display: flex; gap: 10px; align-items: center; } .trix-ws-client-controls label { display: flex; align-items: center; gap: 5px; font-size: 12px; cursor: pointer; } #trix-ws-url { width: 100%; } #trix-ws-connect-btn.connected { background-color: #dc3545; } #trix-ws-console { flex-grow: 1; background: var(--bg-darker); border: 1px solid var(--border-color); border-radius: 5px; padding: 10px; overflow-y: auto; font-family: monospace; font-size: 12px; white-space: pre-wrap; word-break: break-all; } .ws-console-send { color: #7ec699; } .ws-console-receive { color: #cc99cd; } .ws-console-system { color: #f08d49; } #trix-ws-message { width: 100%; min-height: 80px; resize: vertical; } .trix-ws-message-controls, .trix-injector-message-controls { display: flex; flex-direction: column; gap: 5px; } #trix-injector-view { display: flex; flex-direction: column; flex-grow: 1; } .trix-injector-grid { display: grid; grid-template-columns: 1fr auto; gap: 10px; } #trix-injector-message { flex-grow: 1; resize: vertical; min-height: 100px; } #trix-interceptor-view { flex-grow: 1; display: flex; flex-direction: column; } #trix-interceptor-controls { display: flex; gap: 10px; margin-bottom: 10px; } #trix-toggle-interception-btn.active { background-color: #dc3545; box-shadow: 0 0 8px #dc3545; } #trix-interceptor-queue { flex-grow: 1; background: var(--bg-darker); border: 1px solid var(--border-color); border-radius: 5px; padding: 10px; overflow-y: auto; font-family: monospace; font-size: 12px; } .interceptor-item { border: 1px solid var(--border-color); border-radius: 4px; padding: 8px; margin-bottom: 8px; background: var(--bg-dark); } .interceptor-item-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; font-weight: bold; } .interceptor-item-header .send { color: #7ec699; } .interceptor-item-header .receive { color: #cc99cd; } .interceptor-item textarea { width: 100%; box-sizing: border-box; background: var(--bg-dark); color: var(--text-color); border: 1px solid var(--border-color); min-height: 40px; font-family: inherit; } .interceptor-item-actions { display: flex; gap: 5px; margin-top: 5px; } .interceptor-btn { flex-grow: 1; padding: 4px; font-size: 11px; } .interceptor-forward-btn { background-color: #28a745; } .interceptor-block-btn { background-color: #dc3545; } #trix-packet-log-view { flex-grow: 1; display: flex; flex-direction: column; } #trix-packet-log-content { flex-grow: 1; overflow-y: auto; } #trix-packet-log { flex-grow: 1; background: var(--bg-darker); border: 1px solid var(--border-color); border-radius: 5px; padding: 10px; font-family: monospace; font-size: 12px; } .packet-item { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #333; padding: 4px 2px; } .packet-data { flex-grow: 1; word-break: break-all; } .packet-actions { display: flex; gap: 5px; margin-left: 10px; } .packet-action-btn { background: var(--bg-medium); border: 1px solid var(--border-color); color: var(--text-color); cursor: pointer; font-size: 11px; padding: 2px 6px; border-radius: 3px; } .packet-action-btn:hover { background: var(--accent-color); color: white; border-color: var(--accent-color); } .packet-inject .packet-meta, .packet-send-forwarded .packet-meta, .packet-receive-forwarded .packet-meta { color: #f08d49; } .packet-send-blocked .packet-meta, .packet-receive-blocked .packet-meta { color: #dc3545; } .connection-card { background: var(--bg-dark); border-left: 4px solid; border-radius: 6px; padding: 12px; margin-bottom: 10px; cursor: pointer; transition: all 0.2s ease-in-out; } .connection-card:hover { background-color: var(--bg-medium); transform: scale(1.02); } .card-header { display: flex; justify-content: space-between; align-items: center; font-weight: bold; } .card-state { display: flex; align-items: center; gap: 6px; font-size: 12px; } .card-state-dot { width: 10px; height: 10px; border-radius: 50%; } .card-state.OPEN { color: #28a745; } .card-state.OPEN .card-state-dot { background-color: #28a745; } .card-state.CONNECTING { color: #ffc107; } .card-state.CONNECTING .card-state-dot { background-color: #ffc107; } .card-url { font-family: monospace; font-size: 12px; color: var(--text-muted); margin-top: 5px; word-break: break-all; } .trix-editor-area { position: relative; flex-grow: 1; background: #2d2d2d; border: 1px solid var(--border-color); border-radius: 5px; } .trix-editor-area textarea, .trix-editor-area pre { margin: 0; padding: 10px; font-family: 'Fira Code', 'Consolas', monospace; font-size: 14px; line-height: 1.5; width: 100%; height: 100%; box-sizing: border-box; position: absolute; top: 0; left: 0; overflow: auto; } .trix-editor-area textarea { z-index: 1; background: transparent; color: inherit; resize: none; border: none; outline: none; -webkit-text-fill-color: transparent; } .trix-editor-area pre { z-index: 0; pointer-events: none; } #trix-storage-view { flex-grow: 1; overflow-y: auto; font-family: monospace; font-size: 13px; } .storage-table { margin-top: 10px; } .storage-header { font-weight: bold; color: var(--accent-color); border-bottom: 1px solid var(--border-color); padding-bottom: 5px; margin-bottom: 5px; } .storage-row { display: flex; align-items: center; border-bottom: 1px solid #333; padding: 4px 2px; } .storage-key { color: #cc99cd; min-width: 100px; max-width: 100px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .storage-value { color: #7ec699; flex-grow: 1; cursor: pointer; word-break: break-all; } .storage-value-input { background: var(--bg-medium); color: var(--text-color); border: 1px solid var(--accent-color); width: 100%; font-family: inherit; font-size: inherit; } .storage-delete { cursor: pointer; color: #ef4444; opacity: 0.5; margin-left: 10px; font-weight: bold; } .storage-delete:hover { opacity: 1; } #trix-music-toggle-btn { position: fixed; bottom: 20px; left: 20px; z-index: 99999; width: 50px; height: 50px; background-color: rgba(30, 30, 34, 0.8); color: var(--text-color); border: 1px solid var(--border-color); border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(5px); transition: all 0.3s ease; } #trix-music-toggle-btn:hover { transform: scale(1.1); box-shadow: 0 0 10px var(--accent-color); color: var(--accent-color); } #trix-music-player { display: flex; flex-direction: column; position: fixed; bottom: 80px; left: 20px; width: 300px; background: rgba(30, 30, 34, 0.9); border: 1px solid var(--border-color); backdrop-filter: blur(10px); border-radius: 8px; padding: 15px; z-index: 99998; box-shadow: var(--container-shadow); opacity: 0; transform: scale(0.95) translateY(10px); transition: opacity 0.3s ease, transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease; pointer-events: none; } #trix-music-player.visible { opacity: 1; transform: scale(1) translateY(0); pointer-events: auto; } @keyframes rainbow-glow { 0%{box-shadow:0 0 10px 2px hsl(0,100%,60%)}16%{box-shadow:0 0 10px 2px hsl(60,100%,60%)}33%{box-shadow:0 0 10px 2px hsl(120,100%,60%)}50%{box-shadow:0 0 10px 2px hsl(180,100%,60%)}66%{box-shadow:0 0 10px 2px hsl(240,100%,60%)}83%{box-shadow:0 0 10px 2px hsl(300,100%,60%)}100%{box-shadow:0 0 10px 2px hsl(360,100%,60%)} } #trix-music-player.rainbow-outline { animation: rainbow-glow 2s linear infinite; border-color: transparent; } .music-drag-handle { position: absolute; top: 0; left: 0; width: 100%; height: 20px; cursor: move; } .music-info { text-align: center; margin-bottom: 10px; } .music-title { font-size: 16px; font-weight: 600; display: block; } .music-artist { font-size: 12px; color: var(--text-muted); } .music-progress-container { width: 100%; background: var(--bg-medium); border-radius: 5px; cursor: pointer; height: 6px; margin-bottom: 5px; } .music-progress-bar { background: var(--accent-color); width: 0%; height: 100%; border-radius: 5px; } .music-time { display: flex; justify-content: space-between; font-size: 11px; color: var(--text-muted); } .music-controls { display: flex; justify-content: space-around; align-items: center; margin-bottom: 10px; } .music-options-bar { display: flex; justify-content: space-around; align-items: center; width: 100%; margin-bottom: 10px; } .music-control-btn { background: none; border: none; color: var(--text-color); cursor: pointer; padding: 5px; opacity: 0.8; transition: all 0.2s; } .music-control-btn:hover { opacity: 1; color: var(--accent-color); } .music-control-btn.active { color: var(--accent-color); } .music-play-btn { font-size: 24px; width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; } .music-volume-container { display: flex; align-items: center; justify-content: center; position: relative; } .music-volume-slider { -webkit-appearance: none; appearance: none; width: 80px; height: 5px; background: var(--bg-medium); border-radius: 5px; outline: none; transition: opacity 0.3s; } .music-volume-slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 12px; height: 12px; background: var(--text-color); cursor: pointer; border-radius: 50%; } #trix-volume-indicator { position: absolute; top: -30px; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,0.7); color: white; padding: 4px 8px; border-radius: 4px; font-size: 12px; opacity: 0; transition: opacity 0.3s; pointer-events: none; } #trix-volume-indicator.visible { opacity: 1; } #trix-music-menu, #trix-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 100000; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(5px); opacity: 0; transition: opacity 0.3s; pointer-events: none; } #trix-music-menu.visible, #trix-modal.visible { opacity: 1; pointer-events: auto; } .music-menu-content, .modal-content { background: var(--bg-darker); border: 1px solid var(--border-color); width: 90%; max-width: 500px; max-height: 80%; border-radius: 8px; display: flex; flex-direction: column; } .music-menu-header, .modal-header { padding: 15px; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; } #music-search-input { width: 100%; box-sizing: border-box; } .music-song-list, .modal-body { overflow-y: auto; padding: 10px; } .modal-body pre { white-space: pre-wrap; word-break: break-all; margin: 0; } .music-song-item { padding: 10px 15px; cursor: pointer; border-radius: 4px; transition: background-color 0.2s; } .music-song-item:hover, .music-song-item.playing { background-color: var(--bg-dark); } .music-song-title { display: block; } .music-song-artist { font-size: 12px; color: var(--text-muted); } /* --- Toast Notifications --- */ #trix-toast-container { position: fixed; bottom: 20px; right: 20px; z-index: 100001; display: flex; flex-direction: column; gap: 10px; align-items: flex-end; } @keyframes trix-toast-in { from { transform: translateX(110%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes trix-toast-out { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(0.9); } } .trix-toast { background-color: var(--bg-dark); color: var(--text-color); border-left: 5px solid var(--accent-color); padding: 15px; border-radius: 8px; box-shadow: var(--container-shadow); width: 350px; animation: trix-toast-in 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94); position: relative; overflow: hidden; cursor: pointer; } .trix-toast.fade-out { animation: trix-toast-out 0.4s ease-in forwards; } .toast-header { display: flex; justify-content: space-between; align-items: center; font-weight: bold; pointer-events: none; } .toast-timer { font-size: 11px; color: var(--text-muted); } .toast-body { font-size: 14px; margin-top: 5px; word-break: break-all; pointer-events: none; } .toast-body code { background: var(--bg-darker); padding: 2px 4px; border-radius: 4px; } .toast-actions { margin-top: 10px; } .toast-button { background: var(--accent-color); color: white; border: none; padding: 5px 10px; font-size: 12px; border-radius: 4px; cursor: pointer; transition: filter 0.2s; } .toast-button:hover { filter: brightness(1.1); } .toast-progress { position: absolute; bottom: 0; left: 0; height: 4px; background-color: var(--accent-color); width: 100%; opacity: 0.7; pointer-events: none; } `; shadowRoot.appendChild(styleElement); let state = { tabs: [], activeTabId: null, settings: { position: { top: '80px', right: '15px' }, size: { width: '500px', height: '450px' } }, wsClient: { url: 'wss://echo.websocket.events', protocols: '', autoConnect: false, savedMessages: { 'Example JSON': '{"action":"ping","id":123}'} }, injector: { savedMessages: { 'Example Chat': '42["chat","Hello from TriX!"]'} }, musicPlayer: { volume: 0.5, currentSongIndex: 0, position: { bottom: '80px', left: '20px'} } }; const $ = (selector, parent = shadowRoot) => parent.querySelector(selector); const debounce = (func, delay) => { let t; return (...a) => { clearTimeout(t); t = setTimeout(() => func.apply(this, a), delay); }; }; function loadState() { const savedState = GM_getValue('trixExecutorState'); if (savedState) { state.tabs = savedState.tabs || []; state.activeTabId = savedState.activeTabId; state.settings = { ...state.settings, ...savedState.settings }; state.wsClient = { ...state.wsClient, ...savedState.wsClient }; state.injector = { ...state.injector, ...savedState.injector }; state.musicPlayer = { ...state.musicPlayer, ...savedState.musicPlayer }; } const defaultTabs = [ { id: 'welcome', name: 'Welcome', isPermanent: true }, { id: 'logger', name: 'Packet Logger', isPermanent: true }, { id: 'interceptor', name: 'Interceptor', isPermanent: true }, { id: 'injector', name: 'Injector', isPermanent: true }, { id: 'ws_client', name: 'WS Client', isPermanent: true }, { id: 'storage', name: 'Storage', isPermanent: true }, { id: Date.now(), name: "My First Script", code: "// Welcome to TriX Executor!\nconsole.log('Hello from a user script!');" } ]; ['welcome', 'logger', 'interceptor', 'injector', 'ws_client', 'storage'].forEach((id, index) => { const defaultTab = defaultTabs.find(d => d.id === id); if (!state.tabs.find(t => t.id === id)) { state.tabs.splice(index, 0, defaultTab); } }); if (!state.tabs.find(t => t.code)) { state.tabs.push(defaultTabs.find(t => t.code)); } if (!state.activeTabId || !state.tabs.find(t => t.id === state.activeTabId)) { state.activeTabId = state.tabs[0].id; } } const saveState = debounce(() => { const container = $('#trix-container'); const musicPlayer = $('#trix-music-player'); if (container) { state.settings.position = { top: container.style.top, left: container.style.left, right: container.style.right, bottom: 'auto' }; state.settings.size = { width: container.style.width, height: container.style.height }; } if (musicPlayer) { state.musicPlayer.position = { top: musicPlayer.style.top, left: musicPlayer.style.left, bottom: musicPlayer.style.bottom, right: musicPlayer.style.right }; } GM_setValue('trixExecutorState', state); }, 500); let ui, editor; function createUI() { const toggleBtn = document.createElement('div'); toggleBtn.id = 'trix-toggle-btn'; toggleBtn.title = 'Toggle TriX Executor (BETA)'; toggleBtn.innerHTML = 'X'; const container = document.createElement('div'); container.id = 'trix-container'; container.classList.add('hidden'); container.innerHTML = ` <div id="trix-header"> <div><span class="trix-title">TriX Executor</span><span class="trix-version-tag">(v${GM_info.script.version})</span></div> <div id="trix-header-controls"> <span id="trix-conn-status" class="disconnected" title="No connection"></span> <span id="trix-ping-display">Ping: ---</span> <span id="trix-fps-display">FPS: --</span> <span id="trix-close-btn" title="Close">✖</span> </div> </div> <div id="trix-content"></div> <div id="trix-footer" style="padding:10px; text-align:center; background:rgba(0,0,0,0.2);"></div>`; const musicToggle = document.createElement('div'); musicToggle.id = 'trix-music-toggle-btn'; musicToggle.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18V5l12-2v13"></path><circle cx="6" cy="18" r="3"></circle><circle cx="18" cy="16" r="3"></circle></svg>`; const musicPlayer = document.createElement('div'); musicPlayer.id = 'trix-music-player'; musicPlayer.innerHTML = ` <div class="music-drag-handle"></div> <div class="music-info"><span class="music-title">Select a Song</span><span class="music-artist">...</span></div> <div class="music-progress-container"><div class="music-progress-bar"></div></div> <div class="music-time"><span id="music-current-time">0:00</span><span id="music-duration">0:00</span></div> <div class="music-controls"> <button class="music-control-btn" id="music-prev-btn" title="Previous"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M6 6h2v12H6zm3.5 6 8.5 6V6z"/></svg></button> <button class="music-control-btn" id="music-rewind-btn" title="Back 10s"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M11.999 5v2.999l-4-3-4 3v10l4-3 4 3V19l-8-6 8-6z m8 0v2.999l-4-3-4 3v10l4-3 4 3V19l-8-6 8-6z"/></svg></button> <button class="music-control-btn music-play-btn" id="music-play-pause-btn"><svg width="32" height="32" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg></button> <button class="music-control-btn" id="music-forward-btn" title="Forward 10s"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="m3.999 19 8-6-8-6v12zm8 0 8-6-8-6v12z"/></svg></button> <button class="music-control-btn" id="music-next-btn" title="Next"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z"/></svg></button> </div> <div class="music-options-bar"> <button class="music-control-btn" id="music-playlist-btn" title="Playlist"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z"/></svg></button> <button class="music-control-btn" id="music-loop-btn" title="Loop"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4z"/></svg></button> <button class="music-control-btn" id="music-autoplay-btn" title="Autoplay"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="m10 16.5 6-4.5-6-4.5v9zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"></path></svg></button> </div> <div class="music-volume-container"> <input type="range" min="0" max="1" step="0.01" class="music-volume-slider" id="music-volume-slider"> <div id="trix-volume-indicator">100%</div> </div>`; const musicMenu = document.createElement('div'); musicMenu.id = 'trix-music-menu'; musicMenu.innerHTML = ` <div class="music-menu-content"> <div class="music-menu-header"><input type="text" id="music-search-input" class="trix-input" placeholder="Search songs..."></div> <div class="music-song-list"></div> </div>`; const modal = document.createElement('div'); modal.id = 'trix-modal'; modal.innerHTML = ` <div class="modal-content"> <div class="modal-header"> <span>Full Packet Data</span> <span id="trix-modal-close" style="cursor:pointer;font-size:20px;">×</span> </div> <div class="modal-body"> <pre id="trix-modal-body-content"></pre> </div> </div>`; const toastContainer = document.createElement('div'); toastContainer.id = 'trix-toast-container'; shadowRoot.append(toggleBtn, container, musicToggle, musicPlayer, musicMenu, modal, toastContainer); return { toggleBtn, container, musicPlayer, modal }; } function applySettings() { const container = $('#trix-container'); const musicPlayer = $('#trix-music-player'); if (container) { Object.assign(container.style, state.settings.position, state.settings.size); } if (musicPlayer && state.musicPlayer.position) { Object.assign(musicPlayer.style, state.musicPlayer.position); } } updateConnectionStatus = function(status, title) { const statusIndicator = $('#trix-conn-status'); if (statusIndicator) { statusIndicator.className = ''; statusIndicator.classList.add(status); statusIndicator.title = title; } }; updatePingDisplay = function(ping) { const pingDisplay = $('#trix-ping-display'); if (!pingDisplay) return; pingDisplay.className = ''; if (typeof ping === 'number') { pingDisplay.textContent = `Ping: ${ping}`; if (ping < 100) pingDisplay.classList.add('ping-good'); else if (ping < 200) pingDisplay.classList.add('ping-ok'); else pingDisplay.classList.add('ping-bad'); } else { pingDisplay.textContent = `Ping: ${ping}`; } }; async function measurePing() { const startTime = performance.now(); const url = `${window.location.protocol}//${window.location.host}/favicon.ico?_ts=${Date.now()}`; try { const response = await fetch(url, { method: 'HEAD', cache: 'no-store' }); if (response.ok) { const endTime = performance.now(); updatePingDisplay(Math.round(endTime - startTime)); } else { updatePingDisplay('Error'); } } catch (error) { updatePingDisplay('Error'); } } function renderTabs() { const tabsContainer = $('.trix-tabs'); if (!tabsContainer) return; tabsContainer.innerHTML = ''; state.tabs.forEach(tab => { const tabEl = document.createElement('div'); tabEl.className = 'trix-tab'; tabEl.dataset.tabId = tab.id; if (tab.id === state.activeTabId) tabEl.classList.add('active'); tabEl.innerHTML = `<span class="trix-tab-name">${tab.name}</span>` + (!tab.isPermanent ? `<span class="trix-tab-close">x</span>` : ''); tabsContainer.appendChild(tabEl); }); const newTabBtn = document.createElement('button'); newTabBtn.id = 'trix-new-tab-btn'; newTabBtn.textContent = '+'; tabsContainer.appendChild(newTabBtn); renderEditor(); } function renderActiveView() { const content = $('#trix-content'); const activeTab = state.tabs.find(t => t.id === state.activeTabId); content.innerHTML = ''; if (activeTab.id === 'welcome') { content.innerHTML = ` <div class="trix-view"> <div class="trix-tabs"></div> <div id="trix-welcome-view" style="padding: 10px;"> <h2>Welcome to TriX Executor!</h2> <p>This is a powerful tool for web developers and gamers. Here's what you can do:</p> <ul> <li><b>Packet Logger:</b> See all network traffic, with tools to copy, view, or suspend the log.</li> <li><b>Interceptor:</b> Pause, edit, or block WebSocket messages in real-time.</li> <li><b>Injector:</b> Send custom packets to the live game server.</li> <li><b>WS Client:</b> Test any WebSocket server by sending custom messages.</li> <li><b>Storage:</b> View and edit <code>localStorage</code> and <code>sessionStorage</code> directly.</li> <li><b>Scripts:</b> Create and run your own JavaScript snippets on the page.</li> </ul> </div> </div>`; renderTabs(); } else if (activeTab.id === 'ws_client') { content.innerHTML = ` <div id="trix-ws-client-view" class="trix-view"> <div class="trix-tabs"></div> <div class="trix-ws-client-controls"> <input type="text" id="trix-ws-url" class="trix-input" style="flex-grow:1;" placeholder="WebSocket URL (e.g., wss://...)" value="${state.wsClient.url}"> <button id="trix-ws-connect-btn" class="trix-button" style="flex-grow:0;">Connect</button> <label><input type="checkbox" id="trix-ws-auto-connect"> Auto-Connect</label> </div> <div id="trix-ws-console"><div class="ws-console-system">Status: Disconnected</div></div> <div class="trix-ws-client-grid"> <textarea id="trix-ws-message" class="trix-input" placeholder="Enter message to send..."></textarea> <div class="trix-ws-message-controls"> <button id="trix-ws-send-btn" class="trix-button">Send</button> <select id="trix-ws-saved-messages" class="trix-input"><option value="">Load</option></select> <button id="trix-ws-save-msg-btn" class="trix-button" style="background-color:#0d6efd;">Save</button> <button id="trix-ws-del-msg-btn" class="trix-button" style="background-color:#6c757d;">Del</button> </div> </div> </div>`; renderTabs(); renderWsSavedMessages(); const autoConnectCheck = $('#trix-ws-auto-connect'); autoConnectCheck.checked = state.wsClient.autoConnect; autoConnectCheck.addEventListener('change', () => { state.wsClient.autoConnect = autoConnectCheck.checked; saveState(); }); if (state.wsClient.autoConnect && (!customWs || customWs.readyState > 1)) { connectCustomWs(); } } else if (activeTab.id === 'interceptor') { content.innerHTML = ` <div id="trix-interceptor-view" class="trix-view"> <div class="trix-tabs"></div> <div id="trix-interceptor-controls" class="trix-action-bar"> <button id="trix-toggle-interception-btn" class="trix-button">Enable Interception</button> <button id="trix-forward-all-btn" class="trix-button" style="background-color:#28a745;">Forward All</button> <button id="trix-block-all-btn" class="trix-button" style="background-color:#dc3545;">Block All</button> </div> <div id="trix-interceptor-queue"></div> <div class="trix-status-bar">Warning: Enabling interception may cause disconnects if messages are not processed quickly.</div> </div>`; renderTabs(); renderInterceptorQueue(); const toggleBtn = $('#trix-toggle-interception-btn'); if (interceptionEnabled) { toggleBtn.classList.add('active'); toggleBtn.textContent = 'Interception Enabled'; } } else if (activeTab.id === 'injector') { content.innerHTML = ` <div id="trix-injector-view" class="trix-view"> <div class="trix-tabs"></div> <div class="trix-injector-grid" style="flex-grow:1; display:flex; flex-direction:column;"> <textarea id="trix-injector-message" class="trix-input" placeholder="Enter packet data to inject..."></textarea> <div class="trix-injector-grid"> <button id="trix-inject-btn" class="trix-button">Inject Packet</button> <div class="trix-injector-message-controls"> <select id="trix-injector-saved-messages" class="trix-input"><option value="">Load</option></select> <button id="trix-injector-save-msg-btn" class="trix-button" style="background-color:#0d6efd;">Save</button> <button id="trix-injector-del-msg-btn" class="trix-button" style="background-color:#6c757d;">Del</button> </div> </div> </div> <div class="trix-status-bar">Inject packets into the live game connection.</div> </div>`; renderTabs(); renderInjectorSavedMessages(); } else if (activeTab.id === 'logger') { content.innerHTML = ` <div id="trix-packet-log-view" class="trix-view"> <div class="trix-tabs"></div> <div id="trix-packet-log-content"></div> </div>`; renderTabs(); renderPacketLoggerView(); } else if (activeTab.id === 'storage') { content.innerHTML = ` <div id="trix-storage-view-container" class="trix-view"> <div class="trix-tabs"></div> <div id="trix-storage-view"></div> <div class="trix-action-bar"> <button id="trix-refresh-storage-btn" class="trix-button">Refresh</button> <button id="trix-add-storage-btn" class="trix-button">Add Local Storage Entry</button> </div> </div>`; renderTabs(); renderStorageView(); } else { content.innerHTML = ` <div id="trix-script-injector-container" class="trix-view"> <div class="trix-tabs"></div> <div class="trix-editor-area"></div> <div class="trix-action-bar"> <button id="trix-execute-btn" class="trix-button">Execute</button> <button id="trix-clear-btn" class="trix-button">Clear</button> </div> <div class="trix-status-bar">Ready.</div> </div>`; renderTabs(); } } renderInterceptorQueue = function() { const queueContainer = $('#trix-interceptor-queue'); if (!queueContainer) return; queueContainer.innerHTML = ''; if (queuedMessages.size === 0) { queueContainer.textContent = 'No messages pending.'; return; } for (const [id, msg] of queuedMessages.entries()) { const item = document.createElement('div'); item.className = 'interceptor-item'; item.dataset.messageId = id; item.innerHTML = ` <div class="interceptor-item-header"><span class="${msg.direction}">${msg.direction.toUpperCase()}</span></div> <textarea>${msg.data}</textarea> <div class="interceptor-item-actions"> <button class="trix-button interceptor-btn interceptor-forward-btn">Forward</button> <button class="trix-button interceptor-btn interceptor-block-btn">Block</button> </div>`; queueContainer.appendChild(item); } }; function renderStorageView() { const view = $('#trix-storage-view'); if (!view) return; view.innerHTML = ''; const storages = { 'Local Storage': unsafeWindow.localStorage, 'Session Storage': unsafeWindow.sessionStorage }; for (const [name, storage] of Object.entries(storages)) { const container = document.createElement('div'); container.className = 'storage-table'; container.innerHTML = `<div class="storage-header">${name} (${storage.length} items)</div>`; for (let i = 0; i < storage.length; i++) { const key = storage.key(i); const value = storage.getItem(key); const row = document.createElement('div'); row.className = 'storage-row'; row.dataset.key = key; row.dataset.storage = name === 'Local Storage' ? 'local' : 'session'; row.innerHTML = `<span class="storage-key" title="${key}">${key}</span><span class="storage-value">${value}</span><span class="storage-delete" title="Delete key">✖</span>`; container.appendChild(row); } view.appendChild(container); } } function renderWsSavedMessages() { const select = $('#trix-ws-saved-messages'); if (!select) return; select.innerHTML = '<option value="">Load Message</option>'; for (const name in state.wsClient.savedMessages) { const option = document.createElement('option'); option.value = name; option.textContent = name; select.appendChild(option); } } function renderInjectorSavedMessages() { const select = $('#trix-injector-saved-messages'); if (!select) return; select.innerHTML = '<option value="">Load Packet</option>'; for (const name in state.injector.savedMessages) { const option = document.createElement('option'); option.value = name; option.textContent = name; select.appendChild(option); } } function logToWsClientConsole(message, type = 'system') { const consoleEl = $('#trix-ws-console'); if (!consoleEl) return; const line = document.createElement('div'); line.className = `ws-console-${type}`; line.textContent = `[${type.toUpperCase()}] ${message}`; consoleEl.appendChild(line); consoleEl.scrollTop = consoleEl.scrollHeight; } function connectCustomWs() { const urlInput = $('#trix-ws-url'); const connectBtn = $('#trix-ws-connect-btn'); if (customWs && customWs.readyState < 2) { customWs.close(); return; } state.wsClient.url = urlInput.value.trim(); saveState(); if (!state.wsClient.url) { logToWsClientConsole('Error: URL cannot be empty.', 'system'); return; } connectBtn.textContent = 'Connecting...'; connectBtn.disabled = true; logToWsClientConsole(`Connecting to ${state.wsClient.url}...`, 'system'); customWs = new OriginalWebSocket(state.wsClient.url); customWs.onopen = () => { logToWsClientConsole('Connection established.', 'system'); connectBtn.textContent = 'Disconnect'; connectBtn.classList.add('connected'); connectBtn.disabled = false; }; customWs.onmessage = (event) => { logToWsClientConsole(event.data, 'receive'); }; customWs.onclose = (event) => { logToWsClientConsole(`Connection closed. Code: ${event.code}`, 'system'); connectBtn.textContent = 'Connect'; connectBtn.classList.remove('connected'); connectBtn.disabled = false; customWs = null; if (state.wsClient.autoConnect) { logToWsClientConsole('Auto-reconnect enabled. Retrying in 3 seconds...', 'system'); setTimeout(connectCustomWs, 3000); } else { showToast('WS Client Disconnected', `The connection to <code>${state.wsClient.url}</code> was closed.`, { action: { text: 'Reconnect', callback: connectCustomWs } }); } }; customWs.onerror = () => { logToWsClientConsole('Connection error.', 'system'); connectBtn.textContent = 'Connect'; connectBtn.classList.remove('connected'); connectBtn.disabled = false; customWs = null; }; } function renderEditor() { const editorArea = $('.trix-editor-area'); if (!editorArea) { editor = null; return; } const activeTab = state.tabs.find(t => t.id === state.activeTabId); if (!activeTab || typeof activeTab.code !== 'string') { editorArea.innerHTML = ''; editor = null; return; } editorArea.innerHTML = `<textarea spellcheck="false" autocapitalize="off" autocomplete="off" autocorrect="off"></textarea><pre class="language-js"><code></code></pre>`; editor = { textarea: $('textarea', editorArea), pre: $('pre', editorArea), code: $('code', editorArea) }; editor.textarea.value = activeTab.code; highlightCode(activeTab.code); addEditorEventListeners(); } function highlightCode(code) { if (editor) editor.code.innerHTML = Prism.highlight(code + '\n', Prism.languages.javascript, 'javascript'); } function addEditorEventListeners() { if (!editor) return; editor.textarea.addEventListener('input', () => { const activeTab = state.tabs.find(t => t.id === state.activeTabId); if (activeTab) { activeTab.code = editor.textarea.value; highlightCode(activeTab.code); saveState(); } }); editor.textarea.addEventListener('scroll', () => { if (editor) { editor.pre.scrollTop = editor.textarea.scrollTop; editor.pre.scrollLeft = editor.textarea.scrollLeft; }}); editor.textarea.addEventListener('keydown', e => { if (e.key === 'Tab') { e.preventDefault(); const s = e.target.selectionStart, end = e.target.selectionEnd; e.target.value = e.target.value.substring(0, s) + ' ' + e.target.value.substring(end); e.target.selectionStart = e.target.selectionEnd = s + 2; editor.textarea.dispatchEvent(new Event('input')); } }); } function initEventListeners() { ui.toggleBtn.addEventListener('click', () => ui.container.classList.toggle('hidden')); $('#trix-close-btn').addEventListener('click', () => ui.container.classList.add('hidden')); initDraggable(ui.container, [$('#trix-header'), $('#trix-footer')]); ui.container.addEventListener('click', handleContainerClick); ui.container.addEventListener('dblclick', handleContainerDblClick); const resizeObserver = new ResizeObserver(debounce(saveState, 500)); resizeObserver.observe(ui.container); ui.modal.addEventListener('click', (e) => { if (e.target.id === 'trix-modal' || e.target.id === 'trix-modal-close') { ui.modal.classList.remove('visible'); } }); // Update logger view when connections change const originalLog = console.log; console.log = function(...args) { if (typeof args[0] === 'string' && args[0].startsWith('[TriX] Intercepting')) { if (state.activeTabId === 'logger') renderPacketLoggerView(); } originalLog.apply(console, args); }; } function handleContainerClick(e) { const target = e.target; const tabEl = target.closest('.trix-tab'); if (tabEl && !target.classList.contains('trix-tab-close')) { const tabId = isNaN(parseInt(tabEl.dataset.tabId)) ? tabEl.dataset.tabId : parseInt(tabEl.dataset.tabId); if (tabId !== state.activeTabId) { viewingConnection = null; // Reset logger view when changing tabs state.activeTabId = tabId; renderActiveView(); saveState(); } } if (target.classList.contains('trix-tab-close')) { const tabId = parseInt(tabEl.dataset.tabId, 10); state.tabs = state.tabs.filter(t => t.id !== tabId); if (state.activeTabId === tabId) state.activeTabId = state.tabs[0].id; renderActiveView(); saveState(); } if (target.id === 'trix-new-tab-btn') { const newId = Date.now(), newName = `Script ${state.tabs.length}`; state.tabs.push({ id: newId, name: newName, code: `// ${newName}` }); state.activeTabId = newId; renderActiveView(); saveState(); } if (target.id === 'trix-execute-btn') executeScript(); if (target.id === 'trix-clear-btn') { const activeTab = state.tabs.find(t => t.id === state.activeTabId); if (activeTab) { activeTab.code = ''; renderEditor(); saveState(); } } if (target.id === 'trix-inject-btn') injectPacket(); if (target.classList.contains('storage-delete')) { const row = target.closest('.storage-row'); const { key, storage } = row.dataset; if (confirm(`Delete key "${key}"?`)) { (storage === 'local' ? unsafeWindow.localStorage : unsafeWindow.sessionStorage).removeItem(key); renderStorageView(); } } else if (target.classList.contains('storage-value')) { const row = target.closest('.storage-row'); const { key, storage } = row.dataset; const input = document.createElement('input'); input.type = 'text'; input.className = 'storage-value-input'; input.value = target.textContent; target.replaceWith(input); input.focus(); const finishEdit = () => { (storage === 'local' ? unsafeWindow.localStorage : unsafeWindow.sessionStorage).setItem(key, input.value); renderStorageView(); }; input.addEventListener('blur', finishEdit, { once: true }); input.addEventListener('keydown', e => { if (e.key === 'Enter') finishEdit(); if(e.key === 'Escape') renderStorageView(); }); } if (target.id === 'trix-toggle-interception-btn') { interceptionEnabled = !interceptionEnabled; target.classList.toggle('active', interceptionEnabled); target.textContent = interceptionEnabled ? 'Interception Enabled' : 'Enable Interception'; } if (target.id === 'trix-forward-all-btn') { for (const msg of queuedMessages.values()) { msg.resolve({ action: 'forward', data: msg.data }); } } if (target.id === 'trix-block-all-btn') { for (const msg of queuedMessages.values()) { msg.resolve({ action: 'block' }); } } const messageItem = target.closest('.interceptor-item'); if (messageItem) { const messageId = messageItem.dataset.messageId; const message = queuedMessages.get(messageId); if (!message) return; if (target.classList.contains('interceptor-forward-btn')) { const editedData = messageItem.querySelector('textarea').value; message.resolve({ action: 'forward', data: editedData }); } else if (target.classList.contains('interceptor-block-btn')) { message.resolve({ action: 'block' }); } } if (target.id === 'trix-ws-connect-btn') connectCustomWs(); if (target.id === 'trix-ws-send-btn') { if (customWs && customWs.readyState === 1) { const message = $('#trix-ws-message').value; customWs.send(message); logToWsClientConsole(message, 'send'); } else { logToWsClientConsole('Error: Not connected.', 'system'); } } if (target.id === 'trix-ws-save-msg-btn') { const message = $('#trix-ws-message').value; if (!message) return; const name = prompt('Enter a name for this message:', 'My Message'); if (name) { state.wsClient.savedMessages[name] = message; saveState(); renderWsSavedMessages(); } } if (target.id === 'trix-ws-del-msg-btn') { const select = $('#trix-ws-saved-messages'); const name = select.value; if (name && confirm(`Delete saved message "${name}"?`)) { delete state.wsClient.savedMessages[name]; saveState(); renderWsSavedMessages(); } } if (target.id === 'trix-injector-save-msg-btn') { const message = $('#trix-injector-message').value; if (!message) return; const name = prompt('Enter a name for this packet:', 'My Packet'); if (name) { state.injector.savedMessages[name] = message; saveState(); renderInjectorSavedMessages(); } } if (target.id === 'trix-injector-del-msg-btn') { const select = $('#trix-injector-saved-messages'); const name = select.value; if (name && confirm(`Delete saved packet "${name}"?`)) { delete state.injector.savedMessages[name]; saveState(); renderInjectorSavedMessages(); } } if (target.id === 'trix-refresh-storage-btn') renderStorageView(); if (target.id === 'trix-add-storage-btn') { const key = prompt('Enter new key:'); if (key) { const value = prompt(`Enter value for "${key}":`); unsafeWindow.localStorage.setItem(key, value); renderStorageView(); } } if (target.id === 'trix-clear-log-btn' && viewingConnection) { const conn = monitoredConnections.get(viewingConnection); if (conn) conn.log = []; renderPacketLoggerView(); } if (target.id === 'trix-suspend-log-btn') { isLoggerSuspended = !isLoggerSuspended; target.classList.toggle('suspended', isLoggerSuspended); target.textContent = isLoggerSuspended ? 'Resume Log' : 'Suspend Log'; } const wsSavedMessagesSelect = target.closest('#trix-ws-saved-messages'); if(wsSavedMessagesSelect && wsSavedMessagesSelect.value) { $('#trix-ws-message').value = state.wsClient.savedMessages[wsSavedMessagesSelect.value]; } const injectorSavedMessagesSelect = target.closest('#trix-injector-saved-messages'); if(injectorSavedMessagesSelect && injectorSavedMessagesSelect.value) { $('#trix-injector-message').value = state.injector.savedMessages[injectorSavedMessagesSelect.value]; } } function handleContainerDblClick(e) { const nameEl = e.target.closest('.trix-tab-name'); if (!nameEl) return; const tabEl = nameEl.closest('.trix-tab'); const tabId = parseInt(tabEl.dataset.tabId, 10); const tab = state.tabs.find(t => t.id === tabId); if (!tab || tab.isPermanent) return; const input = document.createElement('input'); input.type = 'text'; input.className = 'trix-tab-rename-input'; input.value = tab.name; nameEl.replaceWith(input); input.focus(); input.select(); const finishEditing = () => { const newName = input.value.trim(); if (newName) tab.name = newName; saveState(); renderTabs(); }; input.addEventListener('blur', finishEditing, { once: true }); input.addEventListener('keydown', e => { if (e.key === 'Enter') finishEditing(); else if (e.key === 'Escape') renderTabs(); }); } function initDraggable(container, handles) { let isDragging = false, offsetX, offsetY; handles.forEach(handle => { handle.addEventListener('mousedown', e => { if (e.target.closest('button, input, select, textarea')) return; isDragging = true; offsetX = e.clientX - container.offsetLeft; offsetY = e.clientY - container.offsetTop; container.style.right = 'auto'; container.style.bottom = 'auto'; document.body.style.userSelect = 'none'; }); }); document.addEventListener('mousemove', e => { if (isDragging) { container.style.left = `${e.clientX - offsetX}px`; container.style.top = `${Math.max(0, e.clientY - offsetY)}px`; } }); document.addEventListener('mouseup', () => { if (isDragging) { isDragging = false; document.body.style.userSelect = ''; saveState(); } }); } function injectPacket() { const statusBar = $('#trix-injector-view .trix-status-bar'); const messageInput = $('#trix-injector-message'); if (!statusBar || !messageInput) return; const ws = unsafeWindow.webSocketManager?.activeSocket; if (ws && ws.readyState === 1) { const message = messageInput.value; ws.send(message); statusBar.textContent = `Success: Packet injected at ${new Date().toLocaleTimeString()}`; } else { statusBar.textContent = 'Error: Not connected to the game server.'; } } function executeScript() { const statusBar = $('#trix-script-injector-container .trix-status-bar'); if (!statusBar) return; const activeTab = state.tabs.find(t => t.id === state.activeTabId); if (!activeTab || !activeTab.code) { statusBar.textContent = 'Nothing to execute.'; return; } try { new Function(activeTab.code)(); statusBar.textContent = `Success: Executed '${activeTab.name}' at ${new Date().toLocaleTimeString()}`; } catch (error) { console.error('TriX Executor Error:', error); statusBar.textContent = `Error: ${error.message}`; } } function createPacketElement({ type, data }) { const item = document.createElement('div'); item.className = `packet-item packet-${type}`; item.dataset.fullData = data; const dataSpan = document.createElement('span'); dataSpan.className = 'packet-data'; dataSpan.innerHTML = `<span class="packet-meta">[${type.toUpperCase()}]</span> ${data}`; const actionsDiv = document.createElement('div'); actionsDiv.className = 'packet-actions'; const copyBtn = document.createElement('button'); copyBtn.className = 'packet-action-btn'; copyBtn.textContent = 'Copy'; copyBtn.onclick = (e) => { navigator.clipboard.writeText(data).then(() => { e.target.textContent = 'Copied!'; setTimeout(() => e.target.textContent = 'Copy', 1000); }); }; actionsDiv.appendChild(copyBtn); if (type.startsWith('receive')) { const viewBtn = document.createElement('button'); viewBtn.className = 'packet-action-btn'; viewBtn.textContent = 'View'; viewBtn.onclick = () => { $('#trix-modal-body-content').textContent = data; ui.modal.classList.add('visible'); }; actionsDiv.appendChild(viewBtn); } item.appendChild(dataSpan); item.appendChild(actionsDiv); return item; } function renderPacketLoggerView() { const content = $('#trix-packet-log-content'); if (!content) return; content.innerHTML = ''; if (viewingConnection) { // --- DETAILED LOG VIEW --- const conn = monitoredConnections.get(viewingConnection); if (!conn) { // Connection might have closed while viewing viewingConnection = null; renderPacketLoggerView(); // Go back to monitor return; } const backButton = document.createElement('button'); backButton.className = 'trix-button'; backButton.textContent = '← Back To Monitor'; backButton.style.marginBottom = '10px'; backButton.style.flexGrow = 0; backButton.onclick = () => { viewingConnection = null; renderPacketLoggerView(); }; const logContainer = document.createElement('div'); logContainer.id = 'trix-packet-log'; logContainer.style.height = 'calc(100% - 90px)'; // Adjust for buttons const fragment = document.createDocumentFragment(); conn.log.forEach(packet => fragment.appendChild(createPacketElement(packet))); logContainer.appendChild(fragment); const actionBar = document.createElement('div'); actionBar.className = 'trix-action-bar'; actionBar.innerHTML = ` <button id="trix-suspend-log-btn" class="trix-button">Suspend Log</button> <button id="trix-clear-log-btn" class="trix-button">Clear Log</button> `; content.append(backButton, logContainer, actionBar); logContainer.scrollTop = logContainer.scrollHeight; const suspendBtn = $('#trix-suspend-log-btn'); if (suspendBtn && isLoggerSuspended) { suspendBtn.classList.add('suspended'); suspendBtn.textContent = 'Resume Log'; } } else { // --- MONITOR VIEW --- if (monitoredConnections.size === 0) { content.textContent = 'Waiting for WebSocket connections...'; return; } monitoredConnections.forEach((conn, ws) => { const card = document.createElement('div'); card.className = 'connection-card'; card.style.borderLeftColor = conn.state === 'OPEN' ? '#28a745' : '#ffc107'; card.innerHTML = ` <div class="card-header"> <span>Active Connection</span> <span class="card-state ${conn.state}"><div class="card-state-dot"></div>${conn.state}</span> </div> <div class="card-url">${conn.url}</div> `; card.addEventListener('click', () => { viewingConnection = ws; renderPacketLoggerView(); }); content.appendChild(card); }); } } logPacketCallback = function(ws, type, data) { if (isLoggerSuspended) return; const conn = monitoredConnections.get(ws); if (conn) { conn.log.push({ type, data }); // If we are currently viewing this connection's log, append the new packet live if (viewingConnection === ws) { const logContainer = $('#trix-packet-log'); if (logContainer) { const item = createPacketElement({ type, data }); logContainer.appendChild(item); logContainer.scrollTop = logContainer.scrollHeight; } } } }; function showToast(title, body, options = {}) { const toastContainer = $('#trix-toast-container'); if (!toastContainer) return; const toast = document.createElement('div'); toast.className = 'trix-toast'; const duration = options.duration || (Math.random() * (16000 - 8000) + 8000); const startTime = performance.now(); let actionsHTML = ''; if (options.action) { actionsHTML = `<div class="toast-actions"><button class="toast-button">${options.action.text}</button></div>`; } toast.innerHTML = ` <div class="toast-header"> <span>${title}</span> <span class="toast-timer">${(duration / 1000).toFixed(1)}s</span> </div> <div class="toast-body">${body}</div> ${actionsHTML} <div class="toast-progress"></div> `; toastContainer.appendChild(toast); const timerEl = toast.querySelector('.toast-timer'); const progressEl = toast.querySelector('.toast-progress'); const intervalId = setInterval(() => { const elapsed = performance.now() - startTime; const remaining = duration - elapsed; if (remaining <= 0) { clearInterval(intervalId); return; } timerEl.textContent = `${(remaining / 1000).toFixed(1)}s`; progressEl.style.width = `${(remaining / duration) * 100}%`; }, 100); const dismissToast = () => { clearInterval(intervalId); clearTimeout(timeoutId); toast.classList.add('fade-out'); toast.addEventListener('animationend', () => { if (toast.parentNode) toast.parentNode.removeChild(toast); }, { once: true }); }; if (options.action) { toast.querySelector('.toast-button').addEventListener('click', () => { options.action.callback(); dismissToast(); }); } const timeoutId = setTimeout(dismissToast, duration); toast.addEventListener('click', (e) => { if (!e.target.classList.contains('toast-button')) { dismissToast(); } }); } loadState(); ui = createUI(); applySettings(); renderActiveView(); initEventListeners(); const musicPlayer = initMusicPlayer(); initDraggable(ui.musicPlayer, [$('.music-drag-handle', ui.musicPlayer)]); // Create the callback that the proxy will use const toastOnConnect = (url) => { if (settings.antiLag) return; // Still respect anti-lag setting showToast('New Connection!', `<code>${url}</code>`, { action: { text: 'Save to WS Client', callback: () => { state.wsClient.url = url; saveState(); const urlInput = $('#trix-ws-url'); if (urlInput) urlInput.value = url; // Show confirmation toast showToast('Success', 'URL saved to WS Client.', { duration: 3000 }); } } }); }; // Assign the callback for the early-running proxy showToastCallback = toastOnConnect; // Process any URLs that were queued before the UI was ready connectionUrlQueue.forEach(url => showToastCallback(url)); connectionUrlQueue = []; // Clear the queue setInterval(measurePing, 2000); let lastFrameTime = performance.now(), frameCount = 0; const fpsDisplay = $('#trix-fps-display'); function updateFPS(now) { frameCount++; if (now >= lastFrameTime + 1000) { if (fpsDisplay) fpsDisplay.textContent = `FPS: ${frameCount}`; lastFrameTime = now; frameCount = 0; } requestAnimationFrame(updateFPS); } requestAnimationFrame(updateFPS); } function initMusicPlayer() { const player = { audio: document.createElement('audio'), songList: [ { title: "Animal I Have Become", artist: "Skillet", url: "https://www.dropbox.com/scl/fi/b3lgo2fkz85h0m3nklx3b/Skilet_-_Animal_I_Have_Become_-mp3.pm-1.mp3?rlkey=yok2i5r5in404ili6ozf776px&st=5xmvo7c5&dl=1" }, { title: "Passo Bem Solto", artist: "ATLXS", url: "https://evzxirgylircpnblikrw.supabase.co/storage/v1/object/public/MOOZIK/ATLXS_-_PASSO_BEM_SOLTO_-_Slowed_@BaseNaija%20(2).mp3" }, { title: "FROM THE SCREEN TO THE-", artist: "KSI (Audio: Ben4062)", url: "https://www.myinstants.com/media/sounds/from-the-screen-to-the-ring.mp3" }, { title: "Honeypie", artist: "JAWNY", url: "https://evzxirgylircpnblikrw.supabase.co/storage/v1/object/public/MOOZIK/JAWNY_-_Honeypie_muzonov.net_(mp3.pm).mp3" }, { title: "Kamin", artist: "EMIN ft. JONY", url: "https://evzxirgylircpnblikrw.supabase.co/storage/v1/object/public/MOOZIK/EMIN_Ft_JONY_-_.mp3" }, { title: "It Has To Be This Way", artist: "Jamie Christopherson", url: "https://evzxirgylircpnblikrw.supabase.co/storage/v1/object/public/MOOZIK/Metal%20Gear%20Rising-%20Revengeance%20OST%20-%20It%20Has%20To%20Be%20This%20Way%20_Senator%20Battle_%20-%20Jamie%20Christopherson%20-%20SoundLoadMate.com.mp3" } ], isPlaying: false, currentSongIndex: 0, volume: 0.5, loop: false, autoplay: true }; const $ = (sel) => document.querySelector('#trix-host').shadowRoot.querySelector(sel); const playerEl = $('#trix-music-player'); const titleEl = $('.music-title'); const artistEl = $('.music-artist'); const playPauseBtn = $('#music-play-pause-btn'); const progressBar = $('.music-progress-bar'); const progressContainer = $('.music-progress-container'); const currentTimeEl = $('#music-current-time'); const durationEl = $('#music-duration'); const volumeSlider = $('#music-volume-slider'); const volumeIndicator = $('#trix-volume-indicator'); const loopBtn = $('#music-loop-btn'); const autoplayBtn = $('#music-autoplay-btn'); const menuEl = $('#trix-music-menu'); const songListEl = $('.music-song-list'); let volumeTimeout; const updateSpecialEffects = () => { playerEl.classList.toggle('rainbow-outline', player.isPlaying); }; const loadSong = (index) => { player.currentSongIndex = index; const song = player.songList[index]; player.audio.src = song.url; player.audio.loop = player.loop; titleEl.textContent = song.title; artistEl.textContent = song.artist; renderSongList(); updateSpecialEffects(); }; const playSong = () => { player.isPlaying = true; player.audio.play().catch(e => console.error("Audio play failed:", e)); playPauseBtn.innerHTML = `<svg width="32" height="32" viewBox="0 0 24 24" fill="currentColor"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>`; updateSpecialEffects(); }; const pauseSong = () => { player.isPlaying = false; player.audio.pause(); playPauseBtn.innerHTML = `<svg width="32" height="32" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>`; updateSpecialEffects(); }; const nextSong = () => { let newIndex = player.currentSongIndex + 1; if (newIndex >= player.songList.length) newIndex = 0; loadSong(newIndex); playSong(); }; const prevSong = () => { let newIndex = player.currentSongIndex - 1; if (newIndex < 0) newIndex = player.songList.length - 1; loadSong(newIndex); playSong(); }; const setVolume = (value) => { player.volume = value; player.audio.volume = value; volumeSlider.value = value; volumeIndicator.textContent = `${Math.round(value * 100)}%`; volumeIndicator.classList.add('visible'); clearTimeout(volumeTimeout); volumeTimeout = setTimeout(() => volumeIndicator.classList.remove('visible'), 1000); }; const renderSongList = (filter = '') => { songListEl.innerHTML = ''; const filteredList = player.songList.filter(song => song.title.toLowerCase().includes(filter) || song.artist.toLowerCase().includes(filter)); filteredList.forEach((song) => { const originalIndex = player.songList.indexOf(song); const item = document.createElement('div'); item.className = 'music-song-item'; if (originalIndex === player.currentSongIndex) item.classList.add('playing'); item.innerHTML = `<span class="music-song-title">${song.title}</span><span class="music-song-artist">${song.artist}</span>`; item.addEventListener('click', () => { loadSong(originalIndex); playSong(); menuEl.classList.remove('visible'); }); songListEl.appendChild(item); }); }; $('#trix-music-toggle-btn').addEventListener('click', () => playerEl.classList.toggle('visible')); playPauseBtn.addEventListener('click', () => player.isPlaying ? pauseSong() : playSong()); $('#music-next-btn').addEventListener('click', nextSong); $('#music-prev-btn').addEventListener('click', prevSong); $('#music-forward-btn').addEventListener('click', () => player.audio.currentTime += 10); $('#music-rewind-btn').addEventListener('click', () => player.audio.currentTime -= 10); volumeSlider.addEventListener('input', (e) => setVolume(e.target.value)); loopBtn.addEventListener('click', () => { player.loop = !player.loop; player.audio.loop = player.loop; loopBtn.classList.toggle('active', player.loop); }); autoplayBtn.addEventListener('click', () => { player.autoplay = !player.autoplay; autoplayBtn.classList.toggle('active', player.autoplay); }); player.audio.addEventListener('timeupdate', () => { const { currentTime, duration } = player.audio; if (duration) { progressBar.style.width = `${(currentTime / duration) * 100}%`; const formatTime = (s) => `${Math.floor(s/60)}:${String(Math.floor(s%60)).padStart(2,'0')}`; currentTimeEl.textContent = formatTime(currentTime); durationEl.textContent = formatTime(duration); } }); player.audio.addEventListener('ended', () => { if (player.autoplay) { nextSong(); } else { pauseSong(); player.audio.currentTime = 0; } }); progressContainer.addEventListener('click', (e) => { if (!isNaN(player.audio.duration)) { player.audio.currentTime = (e.offsetX / progressContainer.clientWidth) * player.audio.duration; } }); $('#music-playlist-btn').addEventListener('click', () => menuEl.classList.add('visible')); $('#music-search-input').addEventListener('input', (e) => renderSongList(e.target.value.toLowerCase())); menuEl.addEventListener('click', (e) => { if(e.target.id === 'trix-music-menu') menuEl.classList.remove('visible'); }); // Initialize UI states setVolume(player.volume); autoplayBtn.classList.toggle('active', player.autoplay); loadSong(player.currentSongIndex); renderSongList(); return player; } function showLoadingScreen() { const themeVars = { vscode: { accent: '#00aaff', shadow1: '#0077cc', shadow2: '#0055aa' }, retro: { accent: '#00ff9b', shadow1: '#00b36d', shadow2: '#008752' }, crimson: { accent: '#e11d48', shadow1: '#be123c', shadow2: '#881337' } }; const currentTheme = themeVars[settings.theme] || themeVars.vscode; const style = document.createElement('style'); style.textContent = ` #trix-loading-screen, #trix-loading-settings-modal { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background-color: #000; 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: ${currentTheme.accent}; text-shadow: 2px 2px 0px ${currentTheme.shadow1}, 4px 4px 0px ${currentTheme.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: #2a2a30; border-radius: 10px; border: 1px solid #444; overflow: hidden; margin-bottom: 1rem; } .loading-bar-progress { width: 0%; height: 100%; background: linear-gradient(90deg, ${currentTheme.accent}, ${currentTheme.shadow1}); border-radius: 10px; transition: width 0.1s linear; } .loading-info { display: flex; justify-content: center; gap: 20px; font-size: 1.2rem; margin-bottom: 2rem; } .loading-message { font-style: italic; color: #9ca3af; min-height: 1.2em; } #trix-loading-settings-modal .settings-grid { display: grid; grid-template-columns: 1fr 2fr; gap: 1rem 2rem; align-items: center; margin-top: 2rem; } #trix-loading-settings-modal .settings-label { text-align: right; font-weight: bold; } #trix-loading-settings-modal select, #trix-loading-settings-modal input[type="checkbox"] { background: #2a2a30; color: white; border: 1px solid #444; padding: 5px; border-radius: 4px; } #trix-loading-settings-modal input[type="checkbox"] { width: 20px; height: 20px; } #trix-loading-settings-modal .trix-button { background-color: ${currentTheme.accent}; color: white; border: none; padding: 10px 15px; cursor: pointer; border-radius: 6px; flex-grow: 0; width: 200px; margin-top: 2rem; transition: filter 0.2s; font-weight: 600; } #trix-loading-settings-modal .trix-button:hover { filter: brightness(1.1); } `; document.documentElement.appendChild(style); const loadingScreen = document.createElement('div'); loadingScreen.id = 'trix-loading-screen'; const loadingMessages = [ "Disable all other extensions to reduce disconnections (Error 1009).", "the worst it can do is crash your game, right?", "Definitely not a cheating tool...", "did you know? I didnt know", "you can report any harmful scripts to me. (my discord: painsel)", "wanna break from the ads?", "I'm not sure how some features even work, if you're a developer pls help me (my discord: painsel)" ]; 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-info"><span class="loading-percentage">0.0%</span><span class="loading-timer">...s left</span></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" for="trix-theme-select">Theme:</label> <select id="trix-theme-select"> <option value="vscode">VS Code (Default)</option> <option value="retro">Retro 3D</option> <option value="crimson">Crimson</option> </select> <label class="settings-label" for="trix-antilag-check">Anti-Lag Mode:</label> <input type="checkbox" id="trix-antilag-check"> </div> <button id="trix-resume-loading" class="trix-button">Resume</button> </div>`; document.documentElement.appendChild(loadingScreen); document.documentElement.appendChild(settingsModal); const themeSelect = settingsModal.querySelector('#trix-theme-select'); const antiLagCheck = settingsModal.querySelector('#trix-antilag-check'); themeSelect.value = settings.theme; antiLagCheck.checked = settings.antiLag; const saveSettings = () => GM_setValue('trixSettings', settings); themeSelect.onchange = () => { settings.theme = themeSelect.value; saveSettings(); }; antiLagCheck.onchange = () => { settings.antiLag = antiLagCheck.checked; saveSettings(); }; setTimeout(() => loadingScreen.classList.add('visible'), 10); const progressBar = loadingScreen.querySelector('.loading-bar-progress'); const percentageText = loadingScreen.querySelector('.loading-percentage'); const timerText = loadingScreen.querySelector('.loading-timer'); const messageText = loadingScreen.querySelector('.loading-message'); const loadingContent = loadingScreen.querySelector('.loading-content'); const randomDuration = Math.random() * (50000 - 10000) + 10000; let startTime = null; let lastMessageChange = 0; let currentMessageIndex = 0; let isPaused = false; let pauseTime = 0; let animFrameId; function loadingLoop(timestamp) { if (!startTime) startTime = timestamp; if (isPaused) { animFrameId = requestAnimationFrame(loadingLoop); return; } const elapsed = timestamp - startTime; const progress = Math.min(elapsed / randomDuration, 1); progressBar.style.width = `${progress * 100}%`; percentageText.textContent = `${(progress * 100).toFixed(1)}%`; const secondsLeft = (randomDuration - elapsed) / 1000; timerText.textContent = `${Math.max(0, secondsLeft).toFixed(1)}s left`; if (elapsed > lastMessageChange + 4000) { lastMessageChange = elapsed; let newMessageIndex; do { newMessageIndex = Math.floor(Math.random() * loadingMessages.length); } while (newMessageIndex === currentMessageIndex); currentMessageIndex = newMessageIndex; messageText.textContent = loadingMessages[currentMessageIndex]; } if (progress < 1) { animFrameId = requestAnimationFrame(loadingLoop); } else { window.removeEventListener('keydown', f12Handler); percentageText.textContent = '100.0%'; timerText.textContent = '0.0s left'; setTimeout(() => { loadingContent.classList.add('fade-out'); setTimeout(() => { loadingScreen.classList.remove('visible'); settingsModal.classList.remove('visible'); setTimeout(() => { loadingScreen.remove(); settingsModal.remove(); style.remove(); }, 1500); }, 1000); }, 500); } } const f12Handler = (e) => { if (e.key === 'F12') { e.preventDefault(); isPaused = true; pauseTime = performance.now(); settingsModal.style.visibility = 'visible'; settingsModal.classList.add('visible'); } }; settingsModal.querySelector('#trix-resume-loading').onclick = () => { settingsModal.classList.remove('visible'); setTimeout(() => settingsModal.style.visibility = 'hidden', 500); startTime += (performance.now() - pauseTime); isPaused = false; }; window.addEventListener('keydown', f12Handler); animFrameId = requestAnimationFrame(loadingLoop); } function run() { let loadCount = GM_getValue('trixLoadCount', 0); GM_setValue('trixLoadCount', loadCount + 1); if ((loadCount + 1) % 5 === 1) { showLoadingScreen(); } waitForElement('#canvasA', initialize); } function waitForElement(selector, callback) { const observer = new MutationObserver((mutations, obs) => { if (document.querySelector(selector)) { obs.disconnect(); callback(); } }); observer.observe(document.documentElement, { childList: true, subtree: true }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', run); } else { run(); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址