// ==UserScript==
// @name TriX Executor
// @namespace https://github.com/YourUsername/TriX-Executor
// @version 1.1.0
// @description A Comprehensive Script Executor for Territorial.io with a modern, Roblox-style UI.
// @author You
// @match *://territorial.io/*
// @match *://www.territorial.io/*
// @icon https://i.postimg.cc/0NkRZxDm/image.png
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_listValues
// @grant GM_deleteValue
// @grant GM_addValueChangeListener
// @grant GM.xmlHttpRequest
// @run-at document-end
// @license MIT
// ==/UserScript==
/*
* _____ ____ _ __ ___________
* |_ _||_ _|| | / / | _ | ___ \
* | | | | | |/ / | |/' | |_/ /
* | | | | | \ | /| | __/
* _| |_ _| |_| |\ \ \ |_/ / |
* \___/ \___/\_| \_/ \___/\_|
*
* TriX Executor - v1.1.0 (Roblox UI Edition)
* Logo: https://i.postimg.cc/0NkRZxDm/image.png
*/
(function() {
'use strict';
console.log('TriX Executor: Script starting...');
// --- Configuration & Constants ---
const SCRIPT_PREFIX = 'trix_script_';
const BROADCAST_CHANNEL = 'trix_broadcast_channel';
const TAB_ID = `tab_${Date.now().toString(36)}_${Math.random().toString(36).substring(2)}`;
// --- UI Module ---
const UI = {
isDragging: false,
dragOffsetX: 0,
dragOffsetY: 0,
isMinimized: false,
currentScriptName: '', // To keep track of the loaded script for deletion
init() {
this.injectCSS();
this.injectHTML();
this.attachEventListeners();
ScriptManager.populateScriptList();
this.log('TriX Executor v1.1.0 initialized.');
},
injectCSS() {
GM_addStyle(`
:root {
--trix-bg-primary: #1e1e1e;
--trix-bg-secondary: #2d2d2d;
--trix-bg-tertiary: #3c3c3c;
--trix-accent-color: #00aeff;
--trix-text-primary: #d4d4d4;
--trix-text-secondary: #8c8c8c;
--trix-border-color: #4a4a4a;
}
#trix-container, #trix-open-btn {
font-family: 'Segoe UI', 'Roboto', sans-serif;
}
#trix-container {
position: fixed !important;
top: 50px !important;
left: 50px !important;
width: 600px;
height: 400px;
background-color: var(--trix-bg-primary);
border: 1px solid var(--trix-border-color);
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
z-index: 1000000 !important;
display: flex !important;
flex-direction: column;
resize: both;
overflow: hidden;
min-width: 450px;
min-height: 300px;
}
#trix-container.hidden {
display: none !important;
}
#trix-container.minimized #trix-body,
#trix-container.minimized #trix-footer,
#trix-container.minimized #trix-console {
display: none !important;
}
#trix-container.minimized {
height: auto !important;
resize: none !important;
}
#trix-header {
background-color: var(--trix-bg-secondary);
padding: 5px 10px;
cursor: move;
user-select: none;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid var(--trix-border-color);
}
#trix-header-title {
display: flex;
align-items: center;
font-weight: bold;
color: var(--trix-text-primary);
}
#trix-logo { width: 24px; height: 24px; margin-right: 8px; }
#trix-window-controls button {
background: none; border: none; color: var(--trix-text-secondary);
font-size: 18px; cursor: pointer; margin-left: 8px;
padding: 0 4px; line-height: 1;
}
#trix-window-controls button:hover { color: var(--trix-accent-color); }
#trix-close-btn:hover { color: #ff5555; }
#trix-body {
flex-grow: 1;
display: flex;
overflow: hidden;
}
#trix-left-panel {
width: 150px;
background-color: var(--trix-bg-secondary);
padding: 10px 0;
display: flex;
flex-direction: column;
border-right: 1px solid var(--trix-border-color);
}
#trix-script-list-title {
padding: 0 10px 10px 10px;
font-weight: bold;
color: var(--trix-text-primary);
border-bottom: 1px solid var(--trix-border-color);
}
#trix-script-list {
flex-grow: 1;
overflow-y: auto;
margin-top: 10px;
}
.trix-script-item {
padding: 8px 10px;
cursor: pointer;
color: var(--trix-text-secondary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.trix-script-item:hover {
background-color: var(--trix-bg-tertiary);
color: var(--trix-text-primary);
}
.trix-script-item.active {
background-color: var(--trix-accent-color);
color: var(--trix-bg-primary) !important;
font-weight: bold;
}
#trix-right-panel {
flex-grow: 1; display: flex; flex-direction: column;
}
#trix-editor {
flex-grow: 1;
background-color: var(--trix-bg-primary);
color: var(--trix-text-primary);
border: none;
padding: 10px;
box-sizing: border-box;
resize: none;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 14px;
}
#trix-console {
height: 120px;
background-color: var(--trix-bg-secondary);
border-top: 1px solid var(--trix-border-color);
padding: 5px;
overflow-y: auto;
font-size: 12px;
font-family: 'Consolas', 'Monaco', monospace;
}
#trix-console div { padding: 2px 4px; border-bottom: 1px solid #333; word-break: break-all; }
#trix-console .log-error { color: #ff5555; }
#trix-console .log-warn { color: #e5c07b; }
#trix-console .log-info { color: #00aeff; }
#trix-console .log-broadcast { color: #98c379; font-style: italic; }
#trix-footer {
display: flex;
gap: 10px;
padding: 10px;
background-color: var(--trix-bg-secondary);
border-top: 1px solid var(--trix-border-color);
align-items: center;
}
.trix-button {
background-color: var(--trix-bg-tertiary);
border: 1px solid var(--trix-border-color);
color: var(--trix-text-primary);
padding: 8px 15px;
cursor: pointer;
transition: background-color 0.2s;
}
.trix-button:hover { background-color: #555; }
.trix-button.primary {
background-color: var(--trix-accent-color);
color: var(--trix-bg-primary);
font-weight: bold;
}
.trix-button.primary:hover { background-color: #00bfff; }
.trix-button.danger { background-color: #c0392b; }
.trix-button.danger:hover { background-color: #e74c3c; }
#trix-open-btn {
position: fixed !important; top: 20px !important; right: 20px !important;
z-index: 999999 !important; display: block !important;
background-color: var(--trix-accent-color); color: white;
border: none; padding: 10px 15px; cursor: pointer;
box-shadow: 0 0 10px rgba(0, 174, 255, 0.7);
font-weight: bold;
}
`);
},
injectHTML() {
const html = `
<div id="trix-container" class="hidden">
<div id="trix-header">
<div id="trix-header-title">
<img id="trix-logo" src="https://i.postimg.cc/0NkRZxDm/image.png" alt="TriX Logo">
<span>TriX Executor</span>
</div>
<div id="trix-window-controls">
<button id="trix-minimize-btn" title="Minimize">-</button>
<button id="trix-close-btn" title="Close">✕</button>
</div>
</div>
<div id="trix-body">
<div id="trix-left-panel">
<div id="trix-script-list-title">Script List</div>
<div id="trix-script-list"></div>
</div>
<div id="trix-right-panel">
<textarea id="trix-editor" placeholder="-- Paste or load a script here..."></textarea>
<div id="trix-console"></div>
</div>
</div>
<div id="trix-footer">
<button id="trix-execute-btn" class="trix-button primary">▶ Execute</button>
<button id="trix-clear-btn" class="trix-button">Clear</button>
<input type="text" id="trix-save-name" placeholder="Script Name..." class="trix-button" style="flex-grow:1; cursor:text;">
<button id="trix-save-btn" class="trix-button">Save</button>
<button id="trix-delete-btn" class="trix-button danger">Delete</button>
</div>
</div>
<button id="trix-open-btn">Open TriX</button>
`;
document.body.insertAdjacentHTML('beforeend', html);
},
attachEventListeners() {
const container = document.getElementById('trix-container');
const header = document.getElementById('trix-header');
// --- Window Management ---
header.addEventListener('mousedown', (e) => {
if (e.target.closest('#trix-window-controls')) return;
this.isDragging = true;
this.dragOffsetX = e.clientX - container.offsetLeft;
this.dragOffsetY = e.clientY - container.offsetTop;
});
document.addEventListener('mousemove', (e) => {
if (!this.isDragging) return;
container.style.left = `${e.clientX - this.dragOffsetX}px`;
container.style.top = `${e.clientY - this.dragOffsetY}px`;
});
document.addEventListener('mouseup', () => { this.isDragging = false; });
document.getElementById('trix-open-btn').addEventListener('click', () => this.togglePanel(true));
document.getElementById('trix-close-btn').addEventListener('click', () => this.togglePanel(false));
document.getElementById('trix-minimize-btn').addEventListener('click', () => {
this.isMinimized = !this.isMinimized;
container.classList.toggle('minimized', this.isMinimized);
});
// --- Script List ---
document.getElementById('trix-script-list').addEventListener('click', (e) => {
if (e.target.matches('.trix-script-item')) {
const scriptKey = e.target.dataset.scriptKey;
ScriptManager.loadScriptToEditor(scriptKey);
}
});
// --- Footer Buttons ---
document.getElementById('trix-execute-btn').addEventListener('click', () => Executor.execute(document.getElementById('trix-editor').value));
document.getElementById('trix-clear-btn').addEventListener('click', () => {
document.getElementById('trix-editor').value = '';
document.getElementById('trix-save-name').value = '';
this.setActiveScriptItem(null);
this.currentScriptName = '';
});
document.getElementById('trix-save-btn').addEventListener('click', ScriptManager.saveScriptFromEditor);
document.getElementById('trix-delete-btn').addEventListener('click', ScriptManager.deleteCurrentScript);
},
togglePanel(forceShow) {
const container = document.getElementById('trix-container');
const openBtn = document.getElementById('trix-open-btn');
const isHidden = container.classList.contains('hidden');
if (forceShow === true || isHidden) {
container.classList.remove('hidden');
openBtn.style.display = 'none';
} else {
container.classList.add('hidden');
openBtn.style.display = 'block';
}
},
log(message, type = 'log') {
const consoleEl = document.getElementById('trix-console');
if (!consoleEl) return;
const entry = document.createElement('div');
entry.className = `log-${type}`;
entry.textContent = `> ${String(message)}`;
consoleEl.prepend(entry); // Prepend to show newest first
},
setActiveScriptItem(key) {
document.querySelectorAll('.trix-script-item').forEach(item => {
item.classList.toggle('active', item.dataset.scriptKey === key);
});
}
};
// --- Script Manager ---
const ScriptManager = {
async saveScriptFromEditor() {
const name = document.getElementById('trix-save-name').value.trim();
const code = document.getElementById('trix-editor').value;
if (!name) return UI.log('Cannot save: Name is required.', 'error');
if (!code) return UI.log('Cannot save: Editor is empty.', 'warn');
await GM_setValue(SCRIPT_PREFIX + name, code);
UI.log(`Script '${name}' saved.`, 'info');
this.populateScriptList(SCRIPT_PREFIX + name);
},
async loadScriptToEditor(key) {
if (!key) return UI.log('Invalid script key.', 'error');
const code = await GM_getValue(key, '');
const name = key.replace(SCRIPT_PREFIX, '');
document.getElementById('trix-editor').value = code;
document.getElementById('trix-save-name').value = name;
UI.log(`Loaded script: ${name}`, 'info');
UI.currentScriptName = name; // Track for deletion
UI.setActiveScriptItem(key);
},
async deleteCurrentScript() {
const name = UI.currentScriptName;
if (!name) return UI.log('No script loaded to delete.', 'warn');
if (confirm(`Are you sure you want to delete '${name}'?`)) {
await GM_deleteValue(SCRIPT_PREFIX + name);
UI.log(`Script '${name}' deleted.`, 'info');
document.getElementById('trix-editor').value = '';
document.getElementById('trix-save-name').value = '';
UI.currentScriptName = '';
this.populateScriptList();
}
},
async populateScriptList(activeKey = null) {
const listEl = document.getElementById('trix-script-list');
listEl.innerHTML = '';
const allKeys = (await GM_listValues()).filter(k => k.startsWith(SCRIPT_PREFIX));
allKeys.sort().forEach(key => {
const item = document.createElement('div');
item.className = 'trix-script-item';
item.textContent = key.replace(SCRIPT_PREFIX, '');
item.dataset.scriptKey = key;
listEl.appendChild(item);
});
UI.setActiveScriptItem(activeKey || UI.currentScriptName ? SCRIPT_PREFIX + UI.currentScriptName : null);
}
};
// --- Executor & MultiTab (no changes needed) ---
const Executor={execute(e){if(!e.trim())return void UI.log("Execution skipped: script is empty.","warn");UI.log("Executing script...","info");try{const t=this.createAPI(),s=new Function("TriX",e);s(t)}catch(e){UI.log(`Execution Error: ${e.message}`,"error"),console.error("TriX Executor Error:",e)}},createAPI:()=>({log:(e,t="log")=>{const s="object"==typeof e?JSON.stringify(e):String(e);UI.log(s,t)},broadcast:e=>{MultiTab.broadcast(e)},query:(e,t="text")=>{const s=document.querySelector(e);return s?"html"===t?s.innerHTML:"value"===t?s.value:"element"===t?s:s.textContent:null},queryAll:(e,t="text")=>{const s=document.querySelectorAll(e);return Array.from(s).map(e=>"html"===t?e.innerHTML:"value"===t?e.value:"element"===t?e:e.textContent)}})};
const MultiTab={init(){GM_addValueChangeListener(BROADCAST_CHANNEL,this.listener)},listener(e,t,s,o){o&&s.senderId!==TAB_ID&&UI.log(`Received broadcast: ${JSON.stringify(s.payload)}`,"broadcast")},broadcast(e){const t={senderId:TAB_ID,timestamp:Date.now(),payload:e};GM_setValue(BROADCAST_CHANNEL,t),UI.log(`Broadcast sent: ${JSON.stringify(e)}`,"broadcast")}};
// --- Main Initialization ---
function main() {
if (!document.body) {
console.warn('TriX Executor: document.body not ready, retrying in 200ms.');
setTimeout(main, 200);
return;
}
console.log('TriX Executor: Body ready. Initializing UI and systems.');
UI.init();
MultiTab.init();
UI.togglePanel(false); // Start with the panel hidden
}
setTimeout(main, 0);
})();