Extension for moomoo.io

A new extension

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})();
Extension for moomoo.io | Robomonkey