Size
40.9 KB
Version
1.1.9
Created
Nov 2, 2025
Updated
about 1 month ago
1// ==UserScript==
2// @name Extension for moomoo.io
3// @description A new extension
4// @version 1.1.9
5// @match https://*.moomoo.io/*
6// @icon https://moomoo.io/img/favicon.png?v=1
7// ==/UserScript==
8// ArTorias Moomoo.io Mod - Complete Rewrite
9// Press ` (backtick) to open main menu
10// Press X to open lyrics spammer
11
12(function() {
13 'use strict';
14
15 console.log('[ArTorias] Starting fresh v5.0...');
16
17 // Load required libraries
18 const fontLink = document.createElement('link');
19 fontLink.rel = 'stylesheet';
20 fontLink.href = 'https://fonts.googleapis.com/css?family=Ubuntu:700';
21 document.head.appendChild(fontLink);
22
23 const msgpackScript = document.createElement('script');
24 msgpackScript.src = 'https://cdn.jsdelivr.net/npm/msgpack-lite@0.1.26/dist/msgpack.min.js';
25 document.head.appendChild(msgpackScript);
26
27 // Wait for msgpack to load
28 function waitForLibraries() {
29 return new Promise((resolve) => {
30 let attempts = 0;
31 const check = setInterval(() => {
32 attempts++;
33 if (window.msgpack) {
34 clearInterval(check);
35 console.log('[ArTorias] Libraries loaded!');
36 resolve();
37 }
38 if (attempts > 100) {
39 clearInterval(check);
40 console.error('[ArTorias] Failed to load libraries');
41 resolve(); // Continue anyway
42 }
43 }, 50);
44 });
45 }
46
47 waitForLibraries().then(() => {
48 console.log('[ArTorias] Initializing...');
49 init();
50 });
51
52 function init() {
53 const msgpack = window.msgpack;
54 const OriginalWebSocket = window.WebSocket;
55
56 // Global variables
57 window.mainSocket = null;
58 window.bots = [];
59 window.syncBot = null;
60 window.ownPlayer = {sid: null, x: 0, y: 0, dir: 0};
61 let chatEnabled = true;
62 let movementEnabled = true;
63 let respawnEnabled = true;
64
65 const chatMessages = ['tomatooo', 'i dont like you', 'are you scared?', '~ArTorias~', 'best mod!'];
66 const randomHats = [28, 29, 30, 36, 37, 38, 42, 43, 44, 49];
67
68 // ===== UTILITY FUNCTIONS =====
69 async function safeDecode(event) {
70 try {
71 let buf;
72 if (event.data instanceof Blob) {
73 buf = new Uint8Array(await event.data.arrayBuffer());
74 } else if (event.data instanceof ArrayBuffer) {
75 buf = new Uint8Array(event.data);
76 } else {
77 return null;
78 }
79 return msgpack.decode(buf);
80 } catch (e) {
81 return null;
82 }
83 }
84
85 function hookPacket(decoded) {
86 if (!decoded) return null;
87 if (decoded.length > 1 && Array.isArray(decoded[1])) {
88 return [decoded[0], ...decoded[1]];
89 }
90 return decoded;
91 }
92
93 function addLog(message, type = 'info', logId = 'botLog') {
94 const logElement = document.getElementById(logId);
95 if (!logElement) return;
96
97 const entry = document.createElement('div');
98 entry.className = type;
99 entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
100 logElement.appendChild(entry);
101 logElement.scrollTop = logElement.scrollHeight;
102
103 while (logElement.children.length > 50) {
104 logElement.removeChild(logElement.firstChild);
105 }
106 }
107
108 // ===== LYRICS SPAMMER =====
109 function createLyricsMenu() {
110 const menu = document.createElement('div');
111 menu.id = 'lyricsMenu';
112 menu.style.cssText = `
113 display: none;
114 position: fixed;
115 top: 50%;
116 left: 50%;
117 transform: translate(-50%, -50%);
118 width: 500px;
119 max-height: 600px;
120 background: rgba(0, 0, 0, 0.95);
121 border: 2px solid #ff0000;
122 border-radius: 10px;
123 padding: 20px;
124 z-index: 10001;
125 overflow-y: auto;
126 box-shadow: 0 0 30px rgba(255, 0, 0, 0.8);
127 `;
128
129 menu.innerHTML = `
130 <div style="font-size: 24px; text-align: center; margin-bottom: 20px; color: #ff0000; text-shadow: 0 0 10px rgba(255, 0, 0, 0.8); font-family: Ubuntu, sans-serif;">
131 🎵 Lyrics Spammer
132 </div>
133 <button id="closeLyrics" style="position: absolute; top: 10px; right: 10px; background: rgba(255, 0, 0, 0.3); border: 1px solid #ff0000; color: white; width: 30px; height: 30px; border-radius: 5px; cursor: pointer; font-size: 16px;">✖</button>
134 <select id="songSelect" style="width: 100%; padding: 10px; margin: 10px 0; background: rgba(0, 0, 0, 0.7); border: 1px solid #ff0000; border-radius: 5px; color: white; font-size: 14px; font-family: Ubuntu, sans-serif;">
135 <option value="0">Never Gonna Give You Up</option>
136 <option value="1">All Star</option>
137 <option value="2">Bohemian Rhapsody</option>
138 <option value="3">Sweet Caroline</option>
139 <option value="4">Don't Stop Believin'</option>
140 <option value="5">Wonderwall</option>
141 <option value="6">Hey Jude</option>
142 <option value="7">Imagine</option>
143 </select>
144 <div style="color: white; margin: 15px 0 5px 0; font-size: 14px;">Spam Speed (seconds):</div>
145 <input type="number" id="lyricsSpeed" value="2" min="0.5" max="10" step="0.5" style="width: 100%; padding: 8px; background: rgba(0, 0, 0, 0.7); border: 1px solid #ff0000; border-radius: 5px; color: white; font-size: 14px;">
146 <button id="startLyrics" style="width: 100%; padding: 12px; margin: 15px 0 5px 0; background: rgba(0, 255, 0, 0.3); border: 1px solid #00ff00; border-radius: 5px; color: white; cursor: pointer; font-weight: bold; font-size: 16px;">▶ Start</button>
147 <button id="stopLyrics" style="width: 100%; padding: 12px; margin: 5px 0; background: rgba(255, 0, 0, 0.3); border: 1px solid #ff0000; border-radius: 5px; color: white; cursor: pointer; font-weight: bold; font-size: 16px;">■ Stop</button>
148 <div id="lyricsStatus" style="margin-top: 15px; padding: 10px; background: rgba(0, 0, 0, 0.5); border-radius: 5px; color: #aaa; text-align: center; font-size: 12px;">Status: Idle</div>
149 `;
150
151 document.body.appendChild(menu);
152
153 const songs = [
154 ['We\'re no strangers to love', 'You know the rules and so do I', 'Never gonna give you up', 'Never gonna let you down'],
155 ['Somebody once told me', 'The world is gonna roll me', 'Hey now, you\'re an all star'],
156 ['Is this the real life?', 'Is this just fantasy?', 'Caught in a landslide'],
157 ['Where it began', 'Sweet Caroline', 'Good times never seemed so good'],
158 ['Just a small town girl', 'Don\'t stop believin\'', 'Hold on to that feelin\''],
159 ['Today is gonna be the day', 'I don\'t believe that anybody', 'Feels the way I do'],
160 ['Hey Jude, don\'t make it bad', 'Take a sad song', 'Na na na na-na-na na'],
161 ['Imagine there\'s no heaven', 'Imagine all the people', 'Living life in peace']
162 ];
163
164 let lyricsInterval = null;
165 let currentLine = 0;
166
167 document.getElementById('closeLyrics').onclick = () => menu.style.display = 'none';
168
169 document.getElementById('startLyrics').onclick = () => {
170 if (lyricsInterval) clearInterval(lyricsInterval);
171 const songIndex = parseInt(document.getElementById('songSelect').value);
172 const speed = parseFloat(document.getElementById('lyricsSpeed').value) * 1000;
173 const lyrics = songs[songIndex];
174 currentLine = 0;
175
176 document.getElementById('lyricsStatus').textContent = 'Status: Spamming...';
177 document.getElementById('lyricsStatus').style.color = '#0f0';
178
179 lyricsInterval = setInterval(() => {
180 if (currentLine >= lyrics.length) currentLine = 0;
181 if (window.mainSocket && window.mainSocket.readyState === 1) {
182 window.mainSocket.send(new Uint8Array(msgpack.encode(['6', [lyrics[currentLine]]])));
183 }
184 currentLine++;
185 }, speed);
186 };
187
188 document.getElementById('stopLyrics').onclick = () => {
189 if (lyricsInterval) {
190 clearInterval(lyricsInterval);
191 lyricsInterval = null;
192 }
193 document.getElementById('lyricsStatus').textContent = 'Status: Stopped';
194 document.getElementById('lyricsStatus').style.color = '#f00';
195 };
196 }
197
198 // ===== MAIN MENU =====
199 function createMainMenu() {
200 const styles = document.createElement('style');
201 styles.textContent = `
202 #customMenu { position: fixed; top: 50%; left: -320px; transform: translateY(-50%); width: 300px; background: rgba(0, 0, 0, 0.95); border: 2px solid #ff0000; border-radius: 10px; padding: 15px; font-family: Ubuntu, sans-serif; color: white; z-index: 10000; transition: left 0.3s ease; box-shadow: 0 0 20px rgba(255, 0, 0, 0.5); }
203 #customMenu.open { left: 10px; }
204 .menuTitle { font-size: 20px; text-align: center; margin-bottom: 15px; color: #ff0000; text-shadow: 0 0 10px rgba(255, 0, 0, 0.8); }
205 .tabBar { display: flex; gap: 10px; margin-bottom: 15px; }
206 .tabButton { flex: 1; padding: 8px; background: rgba(255, 0, 0, 0.2); border: 1px solid #ff0000; border-radius: 5px; color: white; cursor: pointer; transition: all 0.3s; font-size: 14px; }
207 .tabButton:hover { background: rgba(255, 0, 0, 0.4); }
208 .tabButton.active { background: rgba(255, 0, 0, 0.6); box-shadow: 0 0 10px rgba(255, 0, 0, 0.8); }
209 .tabContent { display: none; max-height: 500px; overflow-y: auto; }
210 .tabContent.active { display: block; }
211 .menuSelect { width: 100%; padding: 8px; margin: 5px 0; background: rgba(0, 0, 0, 0.7); border: 1px solid #ff0000; border-radius: 5px; color: white; font-size: 14px; }
212 .menuButton { width: 100%; padding: 10px; margin: 5px 0; background: rgba(255, 0, 0, 0.3); border: 1px solid #ff0000; border-radius: 5px; color: white; cursor: pointer; font-size: 14px; font-weight: bold; }
213 .menuButton:hover { background: rgba(255, 0, 0, 0.5); }
214 .menuInput { width: 100%; padding: 8px; margin: 5px 0; background: rgba(0, 0, 0, 0.7); border: 1px solid #ff0000; border-radius: 5px; color: white; font-size: 14px; }
215 .menuLabel { color: white; margin: 10px 0 5px 0; font-size: 14px; }
216 .botStatus { font-size: 12px; padding: 8px; margin: 5px 0; background: rgba(0, 0, 0, 0.5); border-radius: 5px; text-align: center; color: #aaa; }
217 .botLog { max-height: 150px; overflow-y: auto; background: rgba(0, 0, 0, 0.5); padding: 8px; margin: 10px 0; border-radius: 5px; font-size: 11px; }
218 .botLog div { margin: 2px 0; color: #aaa; }
219 .botLog .success { color: #0f0; }
220 .botLog .error { color: #f00; }
221 .botLog .info { color: #0af; }
222 .checkboxLabel { display: flex; align-items: center; gap: 10px; margin: 10px 0; color: white; font-size: 14px; cursor: pointer; }
223 .checkboxLabel input { width: 20px; height: 20px; cursor: pointer; }
224 `;
225 document.head.appendChild(styles);
226
227 const menu = document.createElement('div');
228 menu.id = 'customMenu';
229 menu.innerHTML = `
230 <div class="tabBar">
231 <button class="tabButton active" data-tab="music">🎵</button>
232 <button class="tabButton" data-tab="bots">🤖</button>
233 <button class="tabButton" data-tab="sync">🔄</button>
234 </div>
235
236 <div id="musicTab" class="tabContent active">
237 <div class="menuTitle">🎵 Music Player</div>
238 <select class="menuSelect" id="musicSelect">
239 <option value="https://files.catbox.moe/r92fa7.mp3">1nonly - Stay With Me</option>
240 <option value="https://files.catbox.moe/c9rjjd.mp3">Swae Lee - Sunflower</option>
241 <option value="https://files.catbox.moe/9qbdxd.mp3">Juice WRLD - Lucid Dreams</option>
242 <option value="https://files.catbox.moe/lk7q8n.mp3">The Weeknd - Blinding Lights</option>
243 </select>
244 <div style="display: flex; gap: 10px;">
245 <button class="menuButton" id="playMusic">▶ Play</button>
246 <button class="menuButton" id="stopMusic">■ Stop</button>
247 </div>
248 <audio id="audioPlayer"></audio>
249 </div>
250
251 <div id="botsTab" class="tabContent">
252 <div class="menuTitle">🤖 Bot Army</div>
253 <div class="menuLabel">Bot Name:</div>
254 <input type="text" class="menuInput" id="botName" placeholder="Bot" maxlength="15">
255 <div class="menuLabel">Number of Bots:</div>
256 <select class="menuSelect" id="botCount">
257 ${Array.from({length: 30}, (_, i) => `<option value="${i+1}" ${i===2?'selected':''}>${i+1}</option>`).join('')}
258 </select>
259 <button class="menuButton" id="spawnBots">🚀 Spawn Bots</button>
260 <button class="menuButton" id="killBots" style="display:none;">💀 Kill All</button>
261 <div class="botStatus" id="botStatus">Bots: 0 spawned</div>
262 <button class="menuButton" id="respawnToggle">Auto Respawn: ON</button>
263 <button class="menuButton" id="chatToggle">Chat Spam: ON</button>
264 <button class="menuButton" id="moveToggle">Movement: ON</button>
265 <div class="botLog" id="botLog"></div>
266 </div>
267
268 <div id="syncTab" class="tabContent">
269 <div class="menuTitle">🔄 Sync Bot</div>
270 <div class="menuLabel">Sync Bot Name:</div>
271 <input type="text" class="menuInput" id="syncBotName" placeholder="Sync Bot" maxlength="15">
272 <div class="menuLabel">Clan to Join:</div>
273 <input type="text" class="menuInput" id="clanName" placeholder="Enter clan name" maxlength="15">
274 <button class="menuButton" id="spawnSyncBot">🚀 Spawn Sync Bot</button>
275 <button class="menuButton" id="killSyncBot" style="display:none;">💀 Kill Sync Bot</button>
276 <div class="botStatus" id="syncStatus">Sync Bot: Not spawned</div>
277 <div style="color: white; margin: 15px 0 10px 0; font-size: 16px; font-weight: bold;">Sync Actions:</div>
278 <label class="checkboxLabel">
279 <input type="checkbox" id="syncMovement" checked>
280 <span>Sync Movement</span>
281 </label>
282 <label class="checkboxLabel">
283 <input type="checkbox" id="syncHit" checked>
284 <span>Sync Hit/Attack</span>
285 </label>
286 <label class="checkboxLabel">
287 <input type="checkbox" id="syncWindmills">
288 <span>Place Windmills</span>
289 </label>
290 <label class="checkboxLabel">
291 <input type="checkbox" id="syncSpikes">
292 <span>Place Spikes</span>
293 </label>
294 <label class="checkboxLabel">
295 <input type="checkbox" id="syncTraps">
296 <span>Place Traps</span>
297 </label>
298 <div class="botLog" id="syncLog"></div>
299 </div>
300 `;
301
302 document.body.appendChild(menu);
303
304 // Tab switching
305 document.querySelectorAll('.tabButton').forEach(btn => {
306 btn.onclick = () => {
307 document.querySelectorAll('.tabButton').forEach(b => b.classList.remove('active'));
308 document.querySelectorAll('.tabContent').forEach(c => c.classList.remove('active'));
309 btn.classList.add('active');
310 document.getElementById(btn.dataset.tab + 'Tab').classList.add('active');
311 };
312 });
313
314 // Music controls
315 const audio = document.getElementById('audioPlayer');
316 document.getElementById('playMusic').onclick = () => {
317 audio.src = document.getElementById('musicSelect').value;
318 audio.play();
319 };
320 document.getElementById('stopMusic').onclick = () => {
321 audio.pause();
322 audio.currentTime = 0;
323 };
324
325 // Bot controls
326 document.getElementById('spawnBots').onclick = spawnBots;
327 document.getElementById('killBots').onclick = killAllBots;
328 document.getElementById('spawnSyncBot').onclick = spawnSyncBot;
329 document.getElementById('killSyncBot').onclick = killSyncBot;
330
331 document.getElementById('respawnToggle').onclick = function() {
332 respawnEnabled = !respawnEnabled;
333 this.textContent = `Auto Respawn: ${respawnEnabled ? 'ON' : 'OFF'}`;
334 addLog(`Auto respawn ${respawnEnabled ? 'enabled' : 'disabled'}`, 'info');
335 };
336
337 document.getElementById('chatToggle').onclick = function() {
338 chatEnabled = !chatEnabled;
339 this.textContent = `Chat Spam: ${chatEnabled ? 'ON' : 'OFF'}`;
340 addLog(`Chat spam ${chatEnabled ? 'enabled' : 'disabled'}`, 'info');
341 };
342
343 document.getElementById('moveToggle').onclick = function() {
344 movementEnabled = !movementEnabled;
345 this.textContent = `Movement: ${movementEnabled ? 'ON' : 'OFF'}`;
346 addLog(`Movement ${movementEnabled ? 'enabled' : 'disabled'}`, 'info');
347 };
348 }
349
350 // ===== TOKEN GENERATOR =====
351 class TokenGenerator {
352 constructor() {
353 this.workers = [];
354 this.coreCount = Math.min(16, navigator.hardwareConcurrency || 8);
355 }
356
357 async generate() {
358 try {
359 const challenge = await this.getChallenge();
360 const solution = await this.solve(challenge);
361 return `alt:${btoa(JSON.stringify({
362 algorithm: 'SHA-256',
363 challenge: challenge.challenge,
364 salt: challenge.salt,
365 number: solution.number,
366 signature: challenge.signature || null,
367 took: solution.took
368 }))}`;
369 } catch (e) {
370 console.error('[ArTorias] Token error:', e);
371 throw e;
372 }
373 }
374
375 async getChallenge() {
376 const res = await fetch('https://api.moomoo.io/verify');
377 return await res.json();
378 }
379
380 async solve(data) {
381 const workerCode = `
382 importScripts('https://cdn.jsdelivr.net/npm/js-sha256@0.9.0/build/sha256.min.js');
383 self.onmessage = e => {
384 const {challenge, salt, start, end} = e.data;
385 for (let i = start; i <= end; i++) {
386 if (sha256(salt + i) === challenge) {
387 self.postMessage({found: i});
388 return;
389 }
390 }
391 self.postMessage({done: true});
392 };
393 `;
394
395 const blob = new Blob([workerCode], {type: 'application/javascript'});
396 const url = URL.createObjectURL(blob);
397 const segment = Math.ceil(data.maxnumber / this.coreCount);
398 const startTime = performance.now();
399
400 return new Promise((resolve) => {
401 let solved = false;
402 for (let i = 0; i < this.coreCount; i++) {
403 const worker = new Worker(url);
404 worker.onmessage = e => {
405 if (e.data.found && !solved) {
406 solved = true;
407 this.workers.forEach(w => w.terminate());
408 this.workers = [];
409 URL.revokeObjectURL(url);
410 resolve({
411 number: e.data.found,
412 took: ((performance.now() - startTime) / 1000).toFixed(2)
413 });
414 }
415 };
416 worker.postMessage({
417 challenge: data.challenge,
418 salt: data.salt,
419 start: i * segment,
420 end: Math.min(data.maxnumber, (i + 1) * segment - 1)
421 });
422 this.workers.push(worker);
423 }
424 });
425 }
426 }
427
428 const tokenGen = new TokenGenerator();
429
430 // ===== BOT CLIENT =====
431 class BotClient {
432 constructor(region, token, number, name, isSync = false) {
433 this.region = region;
434 this.token = token;
435 this.number = number;
436 this.name = name;
437 this.isSync = isSync;
438 this.socket = null;
439 this.sid = null;
440 this.x = 0;
441 this.y = 0;
442 this.dir = 0;
443 this.connected = false;
444 this.spawned = false;
445 this.packetCount = 0;
446 this.chatIndex = 0;
447 this.intervals = [];
448 this.clanToJoin = null;
449 }
450
451 async connect() {
452 return new Promise((resolve, reject) => {
453 const url = `wss://${this.region}/?token=${this.token}`;
454 console.log(`[Bot ${this.number}] Connecting...`);
455
456 this.socket = new OriginalWebSocket(url);
457
458 this.socket.onmessage = async (e) => {
459 const decoded = await safeDecode(e);
460 const packet = hookPacket(decoded);
461 if (!packet) return;
462
463 // Got session ID
464 if (packet[0] === 'C' && !this.sid) {
465 this.sid = packet[1];
466 console.log(`[Bot ${this.number}] Got SID:`, this.sid);
467 setTimeout(() => this.spawn(), 300);
468 }
469
470 // Spawn confirmation
471 if (packet[0] === 'id') {
472 console.log(`[Bot ${this.number}] Spawned successfully`);
473 if (this.isSync && this.clanToJoin) {
474 setTimeout(() => {
475 this.send('8', this.clanToJoin);
476 addLog(`Joining clan: ${this.clanToJoin}`, 'info', 'syncLog');
477 }, 1000);
478 }
479 }
480
481 // Clan joined
482 if (packet[0] === 'st' && this.isSync) {
483 addLog('Successfully joined clan!', 'success', 'syncLog');
484 }
485
486 // Death - respawn
487 if (packet[0] === 'P' && respawnEnabled) {
488 addLog(`Bot ${this.number} died, respawning...`, 'info', this.isSync ? 'syncLog' : 'botLog');
489 this.spawned = false;
490 setTimeout(() => this.spawn(), 1000);
491 }
492
493 // Position update
494 if (packet[0] === 'a') {
495 for (let i = 0; i < packet[1].length / 13; i++) {
496 const p = packet[1].slice(13 * i, 13 * i + 13);
497 if (p[0] === this.sid) {
498 this.x = p[1];
499 this.y = p[2];
500 this.dir = p[3];
501
502 // Equip hat once
503 if (this.spawned && !this.hasHat) {
504 const hat = randomHats[Math.floor(Math.random() * randomHats.length)];
505 this.send('c', 0, hat, 0);
506 this.hasHat = true;
507 }
508 }
509 }
510 }
511 };
512
513 this.socket.onopen = () => {
514 this.connected = true;
515 addLog(`Bot ${this.number} connected!`, 'success', this.isSync ? 'syncLog' : 'botLog');
516 console.log(`[Bot ${this.number}] Connected!`);
517
518 // Packet counter reset
519 this.intervals.push(setInterval(() => {
520 this.packetCount = 0;
521 }, 1000));
522
523 // Ping
524 this.intervals.push(setInterval(() => {
525 if (this.connected) this.send('pp');
526 }, 2000));
527
528 if (!this.isSync) {
529 // Chat spam
530 this.intervals.push(setInterval(() => {
531 if (this.sid && chatEnabled && this.connected) {
532 this.send('6', chatMessages[this.chatIndex]);
533 this.chatIndex = (this.chatIndex + 1) % chatMessages.length;
534 }
535 }, 4000));
536
537 // Follow player
538 this.intervals.push(setInterval(() => {
539 if (this.sid && movementEnabled && this.connected) {
540 const dx = window.ownPlayer.x - this.x;
541 const dy = window.ownPlayer.y - this.y;
542 const dist = Math.hypot(dx, dy);
543
544 if (dist > 5) {
545 const angle = Math.atan2(dy, dx);
546 this.send('9', angle);
547 } else {
548 this.send('9', null);
549 }
550 }
551 }, 150));
552 } else {
553 // Sync bot movement
554 this.intervals.push(setInterval(() => {
555 if (!this.sid || !this.connected) return;
556
557 const syncMove = document.getElementById('syncMovement');
558 if (syncMove && syncMove.checked) {
559 const dx = window.ownPlayer.x - this.x;
560 const dy = window.ownPlayer.y - this.y;
561 const dist = Math.hypot(dx, dy);
562
563 if (dist > 5) {
564 const angle = Math.atan2(dy, dx);
565 this.send('9', angle);
566 }
567 }
568
569 this.send('D', window.ownPlayer.dir);
570 }, 100));
571 }
572
573 resolve();
574 };
575
576 this.socket.onerror = (err) => {
577 console.error(`[Bot ${this.number}] Error:`, err);
578 addLog(`Bot ${this.number} error`, 'error', this.isSync ? 'syncLog' : 'botLog');
579 };
580
581 this.socket.onclose = () => {
582 this.connected = false;
583 this.cleanup();
584 addLog(`Bot ${this.number} disconnected`, 'error', this.isSync ? 'syncLog' : 'botLog');
585 };
586
587 setTimeout(() => {
588 if (!this.connected) reject(new Error('Timeout'));
589 }, 15000);
590 });
591 }
592
593 spawn() {
594 if (!this.connected) return;
595 const skin = Math.floor(Math.random() * 15);
596 this.send('M', {name: this.name, moofoll: true, skin: skin});
597 this.spawned = true;
598 this.hasHat = false;
599 addLog(`Bot ${this.number} spawned as "${this.name}"`, 'success', this.isSync ? 'syncLog' : 'botLog');
600 }
601
602 send(type, ...args) {
603 if (this.packetCount < 100 && this.socket && this.socket.readyState === 1) {
604 try {
605 this.socket.send(new Uint8Array(msgpack.encode([type, args])));
606 this.packetCount++;
607 } catch (e) {
608 console.error(`[Bot ${this.number}] Send error:`, e);
609 }
610 }
611 }
612
613 cleanup() {
614 this.intervals.forEach(i => clearInterval(i));
615 this.intervals = [];
616 }
617
618 disconnect() {
619 this.cleanup();
620 if (this.socket) {
621 this.socket.close();
622 this.socket = null;
623 }
624 }
625 }
626
627 // ===== WEBSOCKET PROXY =====
628 class WebSocketProxy extends OriginalWebSocket {
629 constructor(...args) {
630 super(...args);
631
632 if (!window.mainSocket) {
633 window.mainSocket = this;
634 console.log('[ArTorias] Main socket hooked');
635
636 this.addEventListener('message', async (e) => {
637 const decoded = await safeDecode(e);
638 const packet = hookPacket(decoded);
639 if (!packet) return;
640
641 // Track player
642 if (packet[0] === 'C' && !window.ownPlayer.sid) {
643 window.ownPlayer.sid = packet[1];
644 console.log('[ArTorias] Player SID:', packet[1]);
645 }
646
647 if (packet[0] === 'D' && packet[1][1] === window.ownPlayer.sid) {
648 window.ownPlayer.name = packet[1][2];
649 }
650
651 if (packet[0] === 'st') {
652 window.ownPlayer.team = packet[1][0];
653 }
654
655 if (packet[0] === 'a') {
656 for (let i = 0; i < packet[1].length / 13; i++) {
657 const p = packet[1].slice(13 * i, 13 * i + 13);
658 if (p[0] === window.ownPlayer.sid) {
659 window.ownPlayer.x = p[1];
660 window.ownPlayer.y = p[2];
661 window.ownPlayer.dir = p[3];
662 }
663 }
664 }
665
666 // Sync bot actions
667 if (window.syncBot && window.syncBot.connected && window.syncBot.spawned) {
668 const syncHit = document.getElementById('syncHit');
669 if (packet[0] === 'd' && syncHit && syncHit.checked) {
670 window.syncBot.send('d', packet[1][0], packet[1][1], packet[1][2]);
671 }
672
673 if (packet[0] === 'G') {
674 const windmills = document.getElementById('syncWindmills');
675 const spikes = document.getElementById('syncSpikes');
676 const traps = document.getElementById('syncTraps');
677
678 if (windmills && windmills.checked && packet[1][0] === 10) {
679 window.syncBot.send('G', packet[1][0], packet[1][1]);
680 }
681 if (spikes && spikes.checked && packet[1][0] === 6) {
682 window.syncBot.send('G', packet[1][0], packet[1][1]);
683 }
684 if (traps && traps.checked && packet[1][0] === 15) {
685 window.syncBot.send('G', packet[1][0], packet[1][1]);
686 }
687 }
688 }
689 });
690 }
691 }
692 }
693
694 window.WebSocket = WebSocketProxy;
695
696 // ===== BOT SPAWN FUNCTIONS =====
697 async function spawnBots() {
698 if (!window.mainSocket) {
699 addLog('Connect to a server first!', 'error');
700 return;
701 }
702
703 const count = parseInt(document.getElementById('botCount').value);
704 const baseName = document.getElementById('botName').value.trim() || 'Bot';
705 const btn = document.getElementById('spawnBots');
706 btn.disabled = true;
707 btn.textContent = '⏳ Spawning...';
708
709 addLog(`Spawning ${count} bots...`, 'info');
710
711 try {
712 const region = window.mainSocket.url.split('/')[2];
713 console.log('[ArTorias] Region:', region);
714
715 for (let i = 0; i < count; i++) {
716 try {
717 addLog(`Token ${i + 1}/${count}...`, 'info');
718 const token = await tokenGen.generate();
719
720 addLog(`Bot ${i + 1}/${count}...`, 'info');
721 const bot = new BotClient(region, token, i + 1, `${baseName} ${i + 1}`);
722 await bot.connect();
723 window.bots.push(bot);
724
725 document.getElementById('botStatus').textContent = `Bots: ${window.bots.length} spawned`;
726
727 if (i < count - 1) {
728 await new Promise(r => setTimeout(r, 2000));
729 }
730 } catch (e) {
731 addLog(`Bot ${i + 1} failed: ${e.message}`, 'error');
732 }
733 }
734
735 addLog(`Spawned ${window.bots.length} bots!`, 'success');
736 document.getElementById('killBots').style.display = 'block';
737 btn.style.display = 'none';
738 } catch (e) {
739 addLog(`Error: ${e.message}`, 'error');
740 btn.disabled = false;
741 btn.textContent = '🚀 Spawn Bots';
742 }
743 }
744
745 function killAllBots() {
746 addLog(`Killing ${window.bots.length} bots...`, 'info');
747 window.bots.forEach(b => b.disconnect());
748 window.bots = [];
749 document.getElementById('botStatus').textContent = 'Bots: 0 spawned';
750 document.getElementById('killBots').style.display = 'none';
751 document.getElementById('spawnBots').style.display = 'block';
752 document.getElementById('spawnBots').disabled = false;
753 addLog('All bots killed', 'success');
754 }
755
756 async function spawnSyncBot() {
757 if (!window.mainSocket) {
758 addLog('Connect to a server first!', 'error', 'syncLog');
759 return;
760 }
761
762 if (window.syncBot) {
763 addLog('Sync bot already exists!', 'error', 'syncLog');
764 return;
765 }
766
767 const btn = document.getElementById('spawnSyncBot');
768 btn.disabled = true;
769 btn.textContent = '⏳ Spawning...';
770
771 try {
772 const region = window.mainSocket.url.split('/')[2];
773 const name = document.getElementById('syncBotName').value.trim() || 'Sync Bot';
774 const clan = document.getElementById('clanName').value.trim();
775
776 addLog('Generating token...', 'info', 'syncLog');
777 const token = await tokenGen.generate();
778
779 addLog('Creating sync bot...', 'info', 'syncLog');
780 window.syncBot = new BotClient(region, token, 1, name, true);
781
782 if (clan) {
783 window.syncBot.clanToJoin = clan;
784 addLog(`Will join clan: ${clan}`, 'info', 'syncLog');
785 }
786
787 await window.syncBot.connect();
788
789 document.getElementById('syncStatus').textContent = 'Sync Bot: Active';
790 document.getElementById('killSyncBot').style.display = 'block';
791 btn.style.display = 'none';
792 addLog('Sync bot ready!', 'success', 'syncLog');
793 } catch (e) {
794 addLog(`Error: ${e.message}`, 'error', 'syncLog');
795 btn.disabled = false;
796 btn.textContent = '🚀 Spawn Sync Bot';
797 window.syncBot = null;
798 }
799 }
800
801 function killSyncBot() {
802 if (window.syncBot) {
803 addLog('Killing sync bot...', 'info', 'syncLog');
804 window.syncBot.disconnect();
805 window.syncBot = null;
806 document.getElementById('syncStatus').textContent = 'Sync Bot: Not spawned';
807 document.getElementById('killSyncBot').style.display = 'none';
808 document.getElementById('spawnSyncBot').style.display = 'block';
809 document.getElementById('spawnSyncBot').disabled = false;
810 addLog('Sync bot killed', 'success', 'syncLog');
811 }
812 }
813
814 // ===== KEYBOARD CONTROLS =====
815 document.addEventListener('keydown', (e) => {
816 // Toggle main menu with backtick
817 if (e.key === '`') {
818 const menu = document.getElementById('customMenu');
819 if (menu) menu.classList.toggle('open');
820 }
821
822 // Toggle lyrics with X
823 if (e.key === 'x' || e.key === 'X') {
824 if (e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA') {
825 const lyrics = document.getElementById('lyricsMenu');
826 if (lyrics) {
827 lyrics.style.display = lyrics.style.display === 'none' ? 'block' : 'none';
828 }
829 }
830 }
831 });
832
833 // ===== INITIALIZE =====
834 createLyricsMenu();
835 createMainMenu();
836
837 console.log('[ArTorias] v5.0 loaded successfully!');
838 addLog('ArTorias v5.0 ready! Press ` for menu, X for lyrics', 'success');
839 }
840})();