您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
A multi-tab bot for territorial.io on proxy sites. The first tab is the Main Controller; subsequent tabs are silent workers.
当前为
// ==UserScript== // @name Official TriX Proxy Bot Script // @version 0.3.3 // @description A multi-tab bot for territorial.io on proxy sites. The first tab is the Main Controller; subsequent tabs are silent workers. // @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')) return; } catch (e) { return; } const CONFIG = { STORAGE_KEY: 'd122_bot_settings', // A single GLOBAL key for the Main Tab, not one per IP. MAIN_TAB_ID_KEY: 'trix_main_tab_global', BROADCAST_CHANNEL: 'trix_proxy_bot_channel', HEARTBEAT_INTERVAL: 2500, MAIN_TAB_TIMEOUT: 7000, PROXY_TAB_TIMEOUT: 7000, 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 proxyIp = window.location.hostname; 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] TriX Bot"; 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 proxy tabs found.</li>'; } else { this.proxyTabs.forEach(tab => { const li = document.createElement('li'); li.className = `trix-tab-item status-${tab.status.toLowerCase()}`; // Display the IP of each sub-tab for clarity li.innerHTML = `<span>${tab.ip}</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"></ul></div></div></div>`; document.body.insertAdjacentHTML('beforeend',trixHTML); 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 e=document.querySelector('input[name="trix-typing-style"]:checked').value;usernameInput.disabled="ai"===e,usernameInput.placeholder="ai"===e?"AI will generate a name":"Enter your name"};radioButtons.forEach(e=>e.addEventListener("change",toggleUsernameInput));const saveSettings=()=>{localStorage.setItem(CONFIG.STORAGE_KEY,JSON.stringify({clanTag:clanTagInput.value,username:usernameInput.value}))},loadSettings=()=>{const e=JSON.parse(localStorage.getItem(CONFIG.STORAGE_KEY)||"{}" );clanTagInput.value=e.clanTag||"",usernameInput.value=e.username||""};loadSettings(),toggleUsernameInput(),clanTagInput.addEventListener("input",saveSettings),usernameInput.addEventListener("input",saveSettings),startBtn.addEventListener("click",()=>{const e={clanTag:clanTagInput.value.trim(),mode:document.querySelector('input[name="trix-typing-style"]:checked').value,customUsername:usernameInput.value.trim()};channel.postMessage({type:"START_TYPING",payload:e})});const container=document.getElementById("trix-container"),header=document.getElementById("trix-header");let isDragging=!1,offsetX,offsetY;header.onmousedown=e=>{isDragging=!0,offsetX=e.clientX-container.offsetLeft,offsetY=e.clientY-container.offsetTop,document.onmousemove=e=>{isDragging&&(container.style.left=`${e.clientX-offsetX}px`,container.style.top=`${e.clientY-offsetY}px`)},document.onmouseup=()=>{isDragging=!1,document.onmousemove=null,document.onmouseup=null}}; } }; // --- 4. Proxy Tab (Subtab) Logic --- const proxyTab = { status: 'IDLE', lastMainHeartbeat: Date.now(), init() { document.title = "[Proxy Tab] " + document.title; this.proxyWebSocket(); this.listenForCommands(); setInterval(() => this.sendHeartbeat(), CONFIG.HEARTBEAT_INTERVAL); setInterval(() => this.checkMainTabHealth(), CONFIG.HEARTBEAT_INTERVAL); }, sendHeartbeat() { channel.postMessage({ type: 'PROXY_HEARTBEAT', payload: { id: tabId, status: this.status, ip: proxyIp } }); }, 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.'); localStorage.setItem(CONFIG.MAIN_TAB_ID_KEY, tabId); setTimeout(() => { if (localStorage.getItem(CONFIG.MAIN_TAB_ID_KEY) === tabId) { window.location.reload(); } }, 250); } }, listenForCommands() { channel.onmessage = (event) => { const { type, payload } = event.data; if (type === 'MAIN_HEARTBEAT') { this.lastMainHeartbeat = Date.now(); } else if (type === 'START_TYPING' && this.status === 'LIVE') { const nameInput=document.querySelector("#input0");if(!nameInput)return;let fullName,username;if("ai"===payload.mode){username=CONFIG.AI_NAMES[Math.floor(Math.random()*CONFIG.AI_NAMES.length)],fullName=payload.clanTag?(()=>{const e=["standard","possessive","lowerTag","tagAfter"][Math.floor(4*Math.random())];switch(e){case"standard":return`[${payload.clanTag}] ${username}`;case"possessive":return`[${payload.clanTag}]'s ${username}`;case"lowerTag":return`[${payload.clanTag.toLowerCase()}] ${username}`;case"tagAfter":return`${username} [${payload.clanTag.toLowerCase()}]`}})():username}else{if(!(username=payload.customUsername))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); // If no global main tab exists, this tab becomes the main one. if (!currentMainId) { localStorage.setItem(CONFIG.MAIN_TAB_ID_KEY, tabId); mainTab.init(); } else { // Otherwise, it's a proxy tab. proxyTab.init(); } } 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) ... */ })();