// ==UserScript==
// @name Official TriX Proxy Bot Script
// @version 0.3.0
// @description A multi-tab bot script for territorial.io that simulates typing on proxy sites. | Supported Proxies: CroxyProxy
// @author painsel
// @license MIT
// @homepageURL https://greasyfork.org/en/scripts/549132-trix-executor-beta-for-territorial-io
// @match *://*/*?__cpo=*
// @grant GM_addStyle
// @grant unsafeWindow
// @namespace http://tampermonkey.net/
// ==/UserScript==
(function() {
'use strict';
// --- 1. Conditional Execution & Config ---
const urlParams = new URLSearchParams(window.location.search);
if (!urlParams.has('__cpo')) return;
try {
if (!atob(urlParams.get('__cpo')).includes('territorial.io')) {
console.log('[TriX Bot] Not a territorial.io proxy. Halting execution.');
return;
}
} catch (e) { return; }
const CONFIG = {
STORAGE_KEY: 'd122_bot_settings',
MAIN_TAB_ID_KEY: 'd122_main_tab_id',
BROADCAST_CHANNEL: 'trix_proxy_bot_channel',
HEARTBEAT_INTERVAL: 2500, // 2.5 seconds
MAIN_TAB_TIMEOUT: 7000, // 7 seconds (if no heartbeat is received)
PROXY_TAB_TIMEOUT: 7000, // 7 seconds
MAIN_SOCKET_URL_PART: '/s52/',
AI_NAMES: [
"Apex", "Vortex", "Shadow", "Nova", "Cipher", "Blaze", "Reaper", "Phantom", "Genesis", "Specter", "Warden", "Rogue", "Titan", "Fury", "Serpent", "Oracle", "Zenith", "Pulsar", "Jester", "Mirage", "Nomad", "Havoc", "Crux", "Wraith", "Glitch", "Vector", "Flux", "Byte", "Pixel", "Matrix", "Kernel", "Grid", "Node", "Syntax", "Griffin", "Wyvern", "Goliath", "Leviathan", "Hydra", "Phoenix", "Golem", "Paladin", "Warlock", "Rune", "Storm", "Quake", "Inferno", "Frost", "Cyclone", "Meteor", "Comet", "Abyss", "Ember", "Tempest", "Striker", "Raider", "Vanguard", "Sentinel", "Commando", "Juggernaut", "Marshal", "Legion", "Phalanx", "Blitz", "Saber", "Echo", "Impulse", "Catalyst", "Paradox", "Rift", "Karma", "Legacy", "Valor", "Creed", "Requiem", "Solstice", "Equinox", "Infinity", "Axiom", "Enigma", "Viper", "Cobra", "Hawk", "Falcon", "Tiger", "Wolf", "Panther", "Jackal", "Goliath", "Javelin", "Overload", "Torrent", "Cascade", "Helix", "Ion", "Ronin", "Monarch", "Zealot", "Herald", "Jinx", "Quasar"
]
};
let isMainTab = false;
const tabId = Date.now() + Math.random().toString(36).substring(2);
const channel = new BroadcastChannel(CONFIG.BROADCAST_CHANNEL);
// --- 2. Shared Functions ---
function simulateTyping(element, text, onComplete) {
let i = 0;
element.value = '';
element.focus();
function typeCharacter() {
if (i < text.length) {
element.value += text.charAt(i);
element.dispatchEvent(new Event('input', { bubbles: true }));
i++;
setTimeout(typeCharacter, Math.random() * 150 + 50);
} else {
element.blur();
if (onComplete) onComplete();
}
}
typeCharacter();
}
// --- 3. Main Tab Logic ---
const mainTab = {
proxyTabs: new Map(),
uiInitialized: false,
init() {
isMainTab = true;
document.title = "[Main] " + document.title;
this.listenForProxyTabs();
setInterval(() => this.sendHeartbeat(), CONFIG.HEARTBEAT_INTERVAL);
waitForGameUI(() => {
this.initializeUI();
setInterval(() => this.cleanupStaleTabs(), CONFIG.PROXY_TAB_TIMEOUT);
});
},
sendHeartbeat() {
channel.postMessage({ type: 'MAIN_HEARTBEAT', payload: { id: tabId } });
},
listenForProxyTabs() {
channel.onmessage = (event) => {
const { type, payload } = event.data;
if (type === 'PROXY_HEARTBEAT') {
this.proxyTabs.set(payload.id, { ...payload, lastSeen: Date.now() });
this.updateTabList();
}
};
},
cleanupStaleTabs() {
const now = Date.now();
let changed = false;
for (const [id, tab] of this.proxyTabs.entries()) {
if (now - tab.lastSeen > CONFIG.PROXY_TAB_TIMEOUT) {
this.proxyTabs.delete(id);
changed = true;
}
}
if (changed) this.updateTabList();
},
updateTabList() {
if (!this.uiInitialized) return;
const list = document.getElementById('trix-tab-list');
const counter = document.getElementById('trix-tab-counter');
if (!list || !counter) return;
list.innerHTML = '';
if (this.proxyTabs.size === 0) {
list.innerHTML = '<li class="trix-tab-item-empty">No other tabs found.</li>';
} else {
this.proxyTabs.forEach(tab => {
const li = document.createElement('li');
li.className = `trix-tab-item status-${tab.status.toLowerCase()}`;
li.innerHTML = `<span>Tab ${tab.id.slice(-6)}</span> <span class="tab-status">${tab.status}</span>`;
list.appendChild(li);
});
}
counter.textContent = this.proxyTabs.size;
},
initializeUI() {
if (this.uiInitialized) return;
this.uiInitialized = true;
const trixCSS = `
:root{--trix-bg:#1e1e1e;--trix-bg-light:#252526;--trix-header-bg:#333333;--trix-border:#3c3c3c;--trix-text:#d4d4d4;--trix-text-secondary:#cccccc;--trix-blue-accent:#007acc;--trix-button-bg:#0e639c;--trix-button-hover-bg:#1177bb;--trix-input-bg:#3c3c3c}
#trix-container{position:fixed;top:20px;left:20px;width:280px;background-color:var(--trix-bg-light);border:1px solid var(--trix-border);border-radius:6px;color:var(--trix-text);font-family:'Consolas','Menlo','Courier New',monospace;font-size:14px;z-index:99999;box-shadow:0 5px 20px rgba(0,0,0,0.5);user-select:none;overflow:hidden}
#trix-header{background-color:var(--trix-header-bg);padding:8px 12px;cursor:move;font-weight:bold;border-bottom:1px solid var(--trix-border)}
#trix-header a{color:var(--trix-blue-accent);text-decoration:none}#trix-header a:hover{text-decoration:underline}
#trix-body{padding:15px;display:flex;flex-direction:column;gap:15px}
.trix-input-group{display:flex;flex-direction:column;gap:5px}
.trix-input-group label{font-size:13px;color:var(--trix-text-secondary)}
.trix-input-group input[type="text"]{background-color:var(--trix-input-bg);border:1px solid var(--trix-border);color:var(--trix-text);padding:8px;border-radius:4px;font-family:inherit;outline:none;transition:border-color .2s,background-color .2s}
.trix-input-group input[type="text"]:focus{border-color:var(--trix-blue-accent)}
.trix-input-group input[type="text"]:disabled{background-color:#2a2a2a;color:#888;cursor:not-allowed}
#trix-start-btn{background-color:var(--trix-button-bg);color:white;border:none;padding:10px;border-radius:4px;font-family:inherit;font-size:14px;font-weight:bold;cursor:pointer;transition:background-color .2s}
#trix-start-btn:hover{background-color:var(--trix-button-hover-bg)}
#trix-start-btn:disabled{background-color:#5a5a5a;cursor:not-allowed}
.trix-radio-group{display:flex;gap:15px}
.trix-radio-group label{display:flex;align-items:center;gap:5px;cursor:pointer}
#trix-tab-manager{margin-top:15px;border-top:1px solid var(--trix-border);padding-top:10px}
#trix-tab-header{display:flex;justify-content:space-between;align-items:center;font-weight:bold;margin-bottom:5px}
#trix-tab-counter{background-color:var(--trix-blue-accent);color:white;padding:2px 6px;border-radius:10px;font-size:12px}
#trix-tab-list{list-style:none;padding:0;margin:0;max-height:120px;overflow-y:auto;background-color:var(--trix-bg);border-radius:4px;border:1px solid var(--trix-border)}
.trix-tab-item,.trix-tab-item-empty{padding:5px 8px;border-bottom:1px solid var(--trix-border);font-size:12px;display:flex;justify-content:space-between;align-items:center}
.trix-tab-item:last-child{border-bottom:none}
.trix-tab-item-empty{justify-content:center;color:var(--trix-text-secondary)}
.tab-status{font-weight:bold}
.status-live .tab-status{color:#2ecc71}
.status-idle .tab-status{color:#f1c40f}
.status-disconnected .tab-status{color:#e74c3c}`;
GM_addStyle(trixCSS);
const trixHTML = `
<div id="trix-container">
<div id="trix-header">Official TriX Proxy Bot [MAIN]</div>
<div id="trix-body">
<div class="trix-input-group">
<label for="trix-clan-tag">Clan Tag (Customizable in AI mode)</label>
<input type="text" id="trix-clan-tag" placeholder="e.g., TriX">
</div>
<div class="trix-input-group">
<label for="trix-username">Username</label>
<input type="text" id="trix-username" placeholder="Enter your name">
</div>
<div class="trix-input-group">
<label>Mode</label>
<div class="trix-radio-group">
<label><input type="radio" name="trix-typing-style" value="custom" checked> Custom</label>
<label><input type="radio" name="trix-typing-style" value="ai"> AI</label>
</div>
</div>
<button id="trix-start-btn">Begin Typing Simulation (All Tabs)</button>
<div id="trix-tab-manager">
<div id="trix-tab-header"><span>Proxy Tabs</span><span id="trix-tab-counter">0</span></div>
<ul id="trix-tab-list"><li class="trix-tab-item-empty">Searching for other tabs...</li></ul>
</div>
</div>
</div>`;
document.body.insertAdjacentHTML('beforeend', trixHTML);
// ... (UI event handling logic remains the same as previous 'controller' version)
const clanTagInput = document.getElementById('trix-clan-tag'), usernameInput = document.getElementById('trix-username'), radioButtons = document.querySelectorAll('input[name="trix-typing-style"]'), startBtn = document.getElementById('trix-start-btn');
const toggleUsernameInput = () => { const mode = document.querySelector('input[name="trix-typing-style"]:checked').value; usernameInput.disabled = (mode === 'ai'); usernameInput.placeholder = (mode === 'ai') ? "AI will generate a name" : "Enter your name"; };
radioButtons.forEach(radio => radio.addEventListener('change', toggleUsernameInput));
const saveSettings = () => { localStorage.setItem(CONFIG.STORAGE_KEY, JSON.stringify({ clanTag: clanTagInput.value, username: usernameInput.value })); };
const loadSettings = () => { const settings = JSON.parse(localStorage.getItem(CONFIG.STORAGE_KEY) || '{}'); clanTagInput.value = settings.clanTag || ''; usernameInput.value = settings.username || ''; };
loadSettings(); toggleUsernameInput(); clanTagInput.addEventListener('input', saveSettings); usernameInput.addEventListener('input', saveSettings);
startBtn.addEventListener('click', () => { const payload = { clanTag: clanTagInput.value.trim(), mode: document.querySelector('input[name="trix-typing-style"]:checked').value, customUsername: usernameInput.value.trim() }; channel.postMessage({ type: 'START_TYPING', payload }); });
const container = document.getElementById('trix-container'), header = document.getElementById('trix-header'); let isDragging = false, offsetX, offsetY;
header.onmousedown = (e) => { isDragging = true; offsetX = e.clientX - container.offsetLeft; offsetY = e.clientY - container.offsetTop; document.onmousemove = (ev) => { if (isDragging) { container.style.left = `${ev.clientX - offsetX}px`; container.style.top = `${ev.clientY - offsetY}px`; } }; document.onmouseup = () => { isDragging = false; document.onmousemove = null; document.onmouseup = null; }; };
}
};
// --- 4. Proxy Tab Logic ---
const proxyTab = {
status: 'IDLE',
lastMainHeartbeat: Date.now(),
mainTabId: null,
init(mainTabId) {
this.mainTabId = mainTabId;
document.title = "[Proxy Tab] " + document.title;
this.proxyWebSocket();
this.listenForCommands();
setInterval(() => this.sendHeartbeat(), CONFIG.HEARTBEAT_INTERVAL);
setInterval(() => this.checkMainTabHealth(), CONFIG.MAIN_TAB_TIMEOUT);
},
sendHeartbeat() {
channel.postMessage({ type: 'PROXY_HEARTBEAT', payload: { id: tabId, status: this.status } });
},
updateStatus(newStatus) {
if (this.status !== newStatus) {
this.status = newStatus;
this.sendHeartbeat();
}
},
checkMainTabHealth() {
if (Date.now() - this.lastMainHeartbeat > CONFIG.MAIN_TAB_TIMEOUT) {
console.warn('[TriX Bot] Main tab timed out. Attempting to claim role.');
// Attempt to take over. The first tab to write its ID wins the election.
localStorage.setItem(CONFIG.MAIN_TAB_ID_KEY, tabId);
// Verify we won the election before reloading
if (localStorage.getItem(CONFIG.MAIN_TAB_ID_KEY) === tabId) {
window.location.reload();
}
}
},
listenForCommands() {
channel.onmessage = (event) => {
const { type, payload } = event.data;
if (type === 'MAIN_HEARTBEAT') {
this.lastMainHeartbeat = Date.now();
this.mainTabId = payload.id; // Update in case of re-election
} else if (type === 'START_TYPING' && this.status === 'LIVE') {
// ... (Typing simulation logic remains the same as previous 'worker' version)
const nameInput = document.querySelector('#input0'); if (!nameInput) return; let fullName, username;
if (payload.mode === 'ai') {
username = CONFIG.AI_NAMES[Math.floor(Math.random() * CONFIG.AI_NAMES.length)];
if (!payload.clanTag) { fullName = username; } else {
const formats = ['standard', 'possessive', 'lowerTag', 'tagAfter']; const chosenFormat = formats[Math.floor(Math.random() * formats.length)];
switch(chosenFormat) { case 'standard': fullName = `[${payload.clanTag}] ${username}`; break; case 'possessive': fullName = `[${payload.clanTag}]'s ${username}`; break; case 'lowerTag': fullName = `[${payload.clanTag.toLowerCase()}] ${username}`; break; case 'tagAfter': fullName = `${username} [${payload.clanTag.toLowerCase()}]`; break; }
}
} else { username = payload.customUsername; if (!username) return; fullName = payload.clanTag ? `[${payload.clanTag}] ${username}` : username; }
simulateTyping(nameInput, fullName);
}
};
},
proxyWebSocket() {
const self = this;
const OriginalWebSocket = unsafeWindow.WebSocket;
unsafeWindow.WebSocket = function(url, protocols) {
if (url.includes(CONFIG.MAIN_SOCKET_URL_PART)) {
const ws = new OriginalWebSocket(url, protocols);
ws.addEventListener('open', () => self.updateStatus('LIVE'));
ws.addEventListener('close', () => self.updateStatus('DISCONNECTED'));
ws.addEventListener('error', () => self.updateStatus('DISCONNECTED'));
return ws;
}
return new OriginalWebSocket(url, protocols);
};
}
};
// --- 5. Initialization and Role Election ---
function findMultiplayerButton() {
for (const btn of document.querySelectorAll('button')) { if (btn.innerText && btn.innerText.includes('Multiplayer')) return btn; }
return null;
}
function waitForGameUI(callback) {
if (findMultiplayerButton()) { callback(); return; }
const observer = new MutationObserver((mutations, obs) => {
if (findMultiplayerButton()) { obs.disconnect(); callback(); }
});
observer.observe(document.documentElement, { childList: true, subtree: true });
}
function electRole() {
const currentMainId = localStorage.getItem(CONFIG.MAIN_TAB_ID_KEY);
// The first tab to arrive finds no ID and becomes the Main Tab.
if (!currentMainId) {
localStorage.setItem(CONFIG.MAIN_TAB_ID_KEY, tabId);
mainTab.init();
} else {
proxyTab.init(currentMainId);
}
}
window.addEventListener('beforeunload', () => {
// If this tab is the Main Tab, relinquish the role so another can take over.
if (isMainTab) {
localStorage.removeItem(CONFIG.MAIN_TAB_ID_KEY);
}
});
electRole();
/*
The MIT License (MIT)
Copyright (c) 2024 painsel
... (license text) ...
*/
})();