// ==UserScript==
// @name TriX Executor
// @namespace https://github.com/YourUsername/TriX-Executor
// @version 1.0.0
// @description A Comprehensive Script Executor for Territorial.io with multi-tab support, automation, and a user-friendly interface.
// @author You
// @match *://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-idle
// @license MIT
// ==/UserScript==
/*
* _____ ____ _ __ ___________
* |_ _||_ _|| | / / | _ | ___ \
* | | | | | |/ / | |/' | |_/ /
* | | | | | \ | /| | __/
* _| |_ _| |_| |\ \ \ |_/ / |
* \___/ \___/\_| \_/ \___/\_|
*
* TriX Executor - v1.0.0
* Logo: https://i.postimg.cc/0NkRZxDm/image.png
*
* An unparalleled level of control and automation for Territorial.io.
*/
(function() {
'use strict';
// --- 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 ---
// Manages the creation, styling, and interaction of the executor's UI panel.
const UI = {
isDragging: false,
dragOffsetX: 0,
dragOffsetY: 0,
init() {
this.injectCSS();
this.injectHTML();
this.attachEventListeners();
this.showTab('executor'); // Default tab
ScriptManager.populateScriptSelector();
this.log('TriX Executor v1.0.0 initialized.');
this.log(`Tab ID: ${TAB_ID}`);
},
injectCSS() {
GM_addStyle(`
#trix-container {
position: fixed;
top: 20px;
left: 20px;
width: 500px;
max-width: 90vw;
background-color: #282c34;
border: 1px solid #4f5b66;
border-radius: 8px;
box-shadow: 0 5px 15px rgba(0,0,0,0.5);
z-index: 99999;
color: #abb2bf;
font-family: 'Menlo', 'Monaco', 'Consolas', monospace;
font-size: 14px;
display: flex;
flex-direction: column;
resize: both;
overflow: hidden;
min-width: 350px;
min-height: 250px;
}
#trix-header {
background-color: #21252b;
padding: 8px 12px;
cursor: move;
user-select: none;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #4f5b66;
}
#trix-header-title {
display: flex;
align-items: center;
font-weight: bold;
}
#trix-logo {
width: 24px;
height: 24px;
margin-right: 8px;
}
#trix-controls button {
background: none;
border: none;
color: #abb2bf;
font-size: 18px;
cursor: pointer;
margin-left: 5px;
}
#trix-controls button:hover {
color: #61afef;
}
#trix-content {
padding: 10px;
flex-grow: 1;
display: flex;
flex-direction: column;
}
.trix-tab-content {
display: none;
height: 100%;
flex-direction: column;
}
.trix-tab-content.active {
display: flex;
}
#trix-tabs {
display: flex;
border-bottom: 1px solid #4f5b66;
margin-bottom: 10px;
}
.trix-tab-button {
padding: 8px 15px;
cursor: pointer;
border: none;
background-color: transparent;
color: #abb2bf;
border-bottom: 2px solid transparent;
}
.trix-tab-button.active {
color: #61afef;
border-bottom: 2px solid #61afef;
}
#trix-editor {
width: 100%;
flex-grow: 1;
background-color: #1e2227;
color: #d1d1d1;
border: 1px solid #4f5b66;
border-radius: 4px;
padding: 5px;
box-sizing: border-box;
resize: none;
}
.trix-button-bar {
margin-top: 10px;
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.trix-button, input[type="text"].trix-input {
background-color: #3a3f4b;
border: 1px solid #4f5b66;
color: #abb2bf;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
}
.trix-button:hover {
background-color: #4b515f;
}
.trix-button.primary {
background-color: #61afef;
color: #282c34;
font-weight: bold;
}
.trix-button.primary:hover {
background-color: #72baff;
}
.trix-button.danger {
background-color: #e06c75;
color: #282c34;
}
#trix-console {
height: 100%;
background-color: #1e2227;
border: 1px solid #4f5b66;
padding: 5px;
overflow-y: auto;
font-size: 12px;
flex-grow: 1;
}
#trix-console div {
padding: 2px 4px;
border-bottom: 1px solid #2c313a;
}
#trix-console .log-error { color: #e06c75; }
#trix-console .log-warn { color: #e5c07b; }
#trix-console .log-info { color: #61afef; }
#trix-console .log-broadcast { color: #98c379; font-style: italic; }
#trix-script-manager { display: flex; align-items: center; gap: 10px; }
#trix-script-selector { flex-grow: 1; }
`);
},
injectHTML() {
const html = `
<div id="trix-container" style="display: none;">
<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-controls">
<button id="trix-toggle-btn" title="Toggle Executor Panel">▼</button>
</div>
</div>
<div id="trix-content">
<div id="trix-tabs">
<button class="trix-tab-button" data-tab="executor">Executor</button>
<button class="trix-tab-button" data-tab="scripts">Scripts</button>
<button class="trix-tab-button" data-tab="console">Console</button>
</div>
<!-- Executor Tab -->
<div id="executor-tab" class="trix-tab-content">
<textarea id="trix-editor" placeholder="Enter your script here..."></textarea>
<div class="trix-button-bar">
<button id="trix-execute-btn" class="trix-button primary">Execute</button>
<button id="trix-clear-editor-btn" class="trix-button">Clear</button>
</div>
</div>
<!-- Scripts Tab -->
<div id="scripts-tab" class="trix-tab-content">
<div id="trix-script-manager">
<select id="trix-script-selector" class="trix-input"></select>
<button id="trix-load-script-btn" class="trix-button">Load</button>
<button id="trix-delete-script-btn" class="trix-button danger">Delete</button>
</div>
<input type="text" id="trix-script-name" class="trix-input" placeholder="Enter script name to save..." style="margin-top: 10px;">
<div class="trix-button-bar">
<button id="trix-save-script-btn" class="trix-button primary">Save Current Script</button>
</div>
<hr style="width:100%; border-color:#4f5b66; margin: 15px 0;">
<input type="text" id="trix-external-url" class="trix-input" placeholder="https://example.com/script.js">
<div class="trix-button-bar">
<button id="trix-fetch-run-btn" class="trix-button">Fetch & Run</button>
</div>
</div>
<!-- Console Tab -->
<div id="console-tab" class="trix-tab-content">
<div id="trix-console"></div>
<div class="trix-button-bar">
<button id="trix-clear-console-btn" class="trix-button">Clear Console</button>
</div>
</div>
</div>
</div>
<button id="trix-open-btn" class="trix-button" style="position:fixed; top:20px; right:20px; z-index:99998;">Open TriX</button>
`;
document.body.insertAdjacentHTML('beforeend', html);
},
attachEventListeners() {
// Panel Dragging
const header = document.getElementById('trix-header');
const container = document.getElementById('trix-container');
header.addEventListener('mousedown', (e) => {
if (e.target.tagName === 'BUTTON') 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;
});
// Panel Visibility
document.getElementById('trix-open-btn').addEventListener('click', () => this.togglePanel(true));
document.getElementById('trix-toggle-btn').addEventListener('click', () => this.togglePanel(false));
// Tab Switching
document.querySelectorAll('.trix-tab-button').forEach(btn => {
btn.addEventListener('click', () => this.showTab(btn.dataset.tab));
});
// Executor Buttons
document.getElementById('trix-execute-btn').addEventListener('click', () => {
const code = document.getElementById('trix-editor').value;
Executor.execute(code);
});
document.getElementById('trix-clear-editor-btn').addEventListener('click', () => {
document.getElementById('trix-editor').value = '';
});
// Script Manager Buttons
document.getElementById('trix-save-script-btn').addEventListener('click', ScriptManager.saveScriptFromEditor);
document.getElementById('trix-load-script-btn').addEventListener('click', ScriptManager.loadScriptToEditor);
document.getElementById('trix-delete-script-btn').addEventListener('click', ScriptManager.deleteSelectedScript);
document.getElementById('trix-fetch-run-btn').addEventListener('click', () => {
const url = document.getElementById('trix-external-url').value;
if(url) ScriptManager.fetchAndRun(url);
else UI.log('External script URL is empty.', 'warn');
});
// Console Buttons
document.getElementById('trix-clear-console-btn').addEventListener('click', () => {
document.getElementById('trix-console').innerHTML = '';
});
},
togglePanel(forceShow) {
const container = document.getElementById('trix-container');
const openBtn = document.getElementById('trix-open-btn');
if (forceShow === true || container.style.display === 'none') {
container.style.display = 'flex';
openBtn.style.display = 'none';
} else {
container.style.display = 'none';
openBtn.style.display = 'block';
}
},
showTab(tabId) {
document.querySelectorAll('.trix-tab-content').forEach(tab => tab.classList.remove('active'));
document.querySelectorAll('.trix-tab-button').forEach(btn => btn.classList.remove('active'));
document.getElementById(`${tabId}-tab`).classList.add('active');
document.querySelector(`.trix-tab-button[data-tab="${tabId}"]`).classList.add('active');
},
log(message, type = 'log') {
const consoleEl = document.getElementById('trix-console');
const entry = document.createElement('div');
const timestamp = new Date().toLocaleTimeString();
entry.className = `log-${type}`;
entry.textContent = `[${timestamp}] ${message}`;
consoleEl.appendChild(entry);
consoleEl.scrollTop = consoleEl.scrollHeight; // Auto-scroll
}
};
// --- Script Manager ---
// Handles loading, saving, and deleting user scripts from local storage.
const ScriptManager = {
async saveScriptFromEditor() {
const name = document.getElementById('trix-script-name').value.trim();
const code = document.getElementById('trix-editor').value;
if (!name) {
UI.log('Cannot save script: Name is required.', 'error');
return;
}
if (!code) {
UI.log('Cannot save script: Editor is empty.', 'warn');
return;
}
await GM_setValue(SCRIPT_PREFIX + name, code);
UI.log(`Script '${name}' saved successfully.`, 'info');
this.populateScriptSelector();
},
async loadScriptToEditor() {
const selector = document.getElementById('trix-script-selector');
const name = selector.value;
if (!name) {
UI.log('No script selected to load.', 'warn');
return;
}
const code = await GM_getValue(name, '');
document.getElementById('trix-editor').value = code;
document.getElementById('trix-script-name').value = name.replace(SCRIPT_PREFIX, '');
UI.log(`Script '${name.replace(SCRIPT_PREFIX, '')}' loaded into editor.`, 'info');
},
async deleteSelectedScript() {
const selector = document.getElementById('trix-script-selector');
const name = selector.value;
if (!name) {
UI.log('No script selected to delete.', 'warn');
return;
}
if (confirm(`Are you sure you want to delete the script '${name.replace(SCRIPT_PREFIX, '')}'?`)) {
await GM_deleteValue(name);
UI.log(`Script '${name.replace(SCRIPT_PREFIX, '')}' deleted.`, 'info');
this.populateScriptSelector();
}
},
async populateScriptSelector() {
const selector = document.getElementById('trix-script-selector');
selector.innerHTML = '<option value="">-- Select a saved script --</option>';
const allKeys = await GM_listValues();
const scriptKeys = allKeys.filter(key => key.startsWith(SCRIPT_PREFIX));
scriptKeys.forEach(key => {
const option = document.createElement('option');
option.value = key;
option.textContent = key.replace(SCRIPT_PREFIX, '');
selector.appendChild(option);
});
},
fetchAndRun(url) {
UI.log(`Fetching script from: ${url}`, 'info');
GM.xmlHttpRequest({
method: "GET",
url: url,
onload: function(response) {
if (response.status >= 200 && response.status < 300) {
UI.log(`Successfully fetched script from ${url}. Executing...`, 'info');
Executor.execute(response.responseText);
} else {
UI.log(`Failed to fetch script. Status: ${response.status}`, 'error');
}
},
onerror: function(response) {
UI.log(`Error fetching script: ${response.statusText}`, 'error');
}
});
}
};
// --- Executor & API ---
// The core that executes scripts and provides a safe API for them.
const Executor = {
execute(code) {
if (!code.trim()) {
UI.log('Execution skipped: script is empty.', 'warn');
return;
}
UI.log('Executing script...', 'info');
try {
// The "sandbox": scripts are executed within this function's scope.
// They get access to the window, document, and the custom TriX API.
const TriX = this.createAPI();
const scriptFunction = new Function('TriX', code);
scriptFunction(TriX);
} catch (e) {
UI.log(`Execution Error: ${e.message}`, 'error');
console.error("TriX Executor Error:", e);
}
},
createAPI() {
return {
/**
* Logs a message to the TriX console.
* @param {any} message The message to log.
* @param {'log'|'info'|'warn'|'error'} [type='log'] The type of log.
*/
log: (message, type = 'log') => {
// Ensure message is a string for the UI
const msgStr = typeof message === 'object' ? JSON.stringify(message) : String(message);
UI.log(msgStr, type);
},
/**
* Broadcasts a data payload to all other open Territorial.io tabs with TriX Executor.
* @param {any} payload The data to send. Must be JSON-serializable.
*/
broadcast: (payload) => {
MultiTab.broadcast(payload);
},
/**
* A safe way to get game data by querying the DOM.
* @param {string} selector The CSS selector for the element.
* @param {'text'|'html'|'value'|'element'} [dataType='text'] What to extract.
* @returns {string|HTMLElement|null} The extracted data or element.
*/
query: (selector, dataType = 'text') => {
const element = document.querySelector(selector);
if (!element) return null;
switch(dataType) {
case 'html': return element.innerHTML;
case 'value': return element.value;
case 'element': return element;
case 'text':
default:
return element.textContent;
}
},
/**
* A safe way to get arrays of game data.
* @param {string} selector The CSS selector for the elements.
* @param {'text'|'html'|'value'|'element'} [dataType='text'] What to extract.
* @returns {Array<string|HTMLElement>} An array of the extracted data.
*/
queryAll: (selector, dataType = 'text') => {
const elements = document.querySelectorAll(selector);
return Array.from(elements).map(el => {
switch(dataType) {
case 'html': return el.innerHTML;
case 'value': return el.value;
case 'element': return el;
case 'text':
default:
return el.textContent;
}
});
}
};
}
};
// --- Multi-Tab Communication ---
// Manages broadcasting and listening for messages between tabs.
const MultiTab = {
init() {
GM_addValueChangeListener(BROADCAST_CHANNEL, this.listener);
},
/**
* @param {string} key The key that changed.
* @param {any} oldValue The old value.
* @param {any} newValue The new value.
* @param {boolean} remote Whether the change was from another tab.
*/
listener(key, oldValue, newValue, remote) {
if (remote && newValue.senderId !== TAB_ID) {
UI.log(`Received broadcast: ${JSON.stringify(newValue.payload)}`, 'broadcast');
// You can dispatch a custom event here for scripts to listen to
// e.g., window.dispatchEvent(new CustomEvent('trix:broadcast', { detail: newValue.payload }));
}
},
broadcast(payload) {
const message = {
senderId: TAB_ID,
timestamp: Date.now(),
payload: payload
};
GM_setValue(BROADCAST_CHANNEL, message);
UI.log(`Broadcast sent: ${JSON.stringify(payload)}`, 'broadcast');
}
};
// --- Main Initialization ---
function main() {
UI.init();
MultiTab.init();
}
// Wait for the page to be fully loaded to avoid conflicts with game scripts.
if (document.readyState === 'complete' || document.readyState === 'interactive') {
main();
} else {
window.addEventListener('load', main);
}
})();