const WebSocket = require('ws'); const logger = require('./logger'); const protocol = require('./protocol'); const readline = require('readline'); // In a real app, these would come from env or arguments const CONFIG = { URL: 'wss://magicgarden.gg/version/436ff68/api/rooms/NJMF/connect?surface=%22web%22&platform=%22desktop%22&playerId=%22p_WU9ea4LiMfR9AZsq%22&version=%22436ff68%22&source=%22router%22&capabilities=%22fbo_mipmap_unsupported%22', HEADERS: { 'Host': 'magicgarden.gg', 'Origin': 'https://magicgarden.gg', 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36', 'Cookie': 'mc_jwt=eyJhbGciOiJIUzI1NiJ9.eyJwcm92aWRlciI6ImRpc2NvcmQiLCJ1c2VySWQiOiI0MTk2Mjk0MDU5MDA2MzYxNzAiLCJ0b2tlblJlc3BvbnNlIjp7ImFjY2Vzc190b2tlbiI6Ik1USXlOemN4T1RZd05qSXlNemMyTlRZNE53LlpwRjdpMFRFN2xIQ05MRUgxN0MzTmpqTGFvbTNySCIsInRva2VuX3R5cGUiOiJCZWFyZXIiLCJleHBpcmVzX2luIjo2MDQ4MDAsInJlZnJlc2hfdG9rZW4iOiJMSGRKNXhTblQweGl6Y1l3anhZQzhOYW5pV0dWMUkiLCJzY29wZSI6Imd1aWxkcy5tZW1iZXJzLnJlYWQgZ3VpbGRzIGFwcGxpY2F0aW9ucy5jb21tYW5kcyBycGMudm9pY2UucmVhZCBpZGVudGlmeSJ9LCJkaXNjb3JkVXNlckZsYWdzIjowLCJvYnRhaW5lZEF0IjoxNzY0OTU5NjU3MjU0LCJpYXQiOjE3NjQ5NjQxNTAsImV4cCI6MTc5NjUyMTc1MH0.D2O3tdQRWL2LODjahK1B4MUJGAAaYjCxQzE1-eg_680; ph_phc_5NQnL0ALxa7n1xjFEeSAe3lMsL8gYu8c8F2RhgSiIkN_posthog=%7B%22distinct_id%22%3A%22419629405900636170%22%2C%22%24sesid%22%3A%5B1764964150683%2C%22019af00f-a521-78c9-976c-ae566391ae37%22%2C1764964148513%5D%2C%22%24epp%22%3Atrue%2C%22%24initial_person_info%22%3A%7B%22r%22%3A%22https%3A%2F%2Fwww.google.com%2F%22%2C%22u%22%3A%22https%3A%2F%2Fmagicgarden.gg%2Fr%2FQMQC%22%7D%7D' } }; class MagicGardenBot { constructor() { this.ws = null; this.pingInterval = null; this.rl = readline.createInterface({ input: process.stdin, output: process.stdout }); } connect() { logger.info('Connecting to Magic Garden server...'); this.ws = new WebSocket(CONFIG.URL, { headers: CONFIG.HEADERS }); this.ws.on('open', () => this.onOpen()); this.ws.on('message', (data) => this.onMessage(data)); this.ws.on('close', () => this.onClose()); this.ws.on('error', (err) => logger.error('WebSocket error', err)); } onOpen() { logger.info('WebSocket connection established!'); // 1. Send Handshake const handshakes = protocol.createHandshakeMessages(); handshakes.forEach(msg => { // logger.info('Sending handshake:', msg.type); this.send(msg); }); // 2. Start Heartbeat this.pingInterval = setInterval(() => { const ping = protocol.createPing(); this.send(ping); }, 2000); // 3. Start CLI this.startCLI(); } startCLI() { console.log('--- CLI COMMANDS ---'); console.log('move '); console.log('plant '); console.log('harvest '); console.log('sell'); console.log('buy '); console.log('--------------------'); this.rl.on('line', (line) => { const args = line.trim().split(' '); const command = args[0].toLowerCase(); try { switch (command) { case 'move': if (args.length < 3) throw new Error('Usage: move '); this.send(protocol.createTeleport(parseInt(args[1]), parseInt(args[2]))); logger.info(`Moving to ${ args[1] }, ${ args[2] } `); break; case 'plant': if (args.length < 3) throw new Error('Usage: plant '); this.send(protocol.createPlant(args[1], args[2])); logger.info(`Planting ${ args[2] } in slot ${ args[1] } `); break; case 'harvest': if (args.length < 2) throw new Error('Usage: harvest '); this.send(protocol.createHarvest(args[1])); logger.info(`Harvesting slot ${ args[1] } `); break; case 'sell': this.send(protocol.createSellAll()); logger.info('Selling all crops'); break; case 'buy': if (args.length < 2) throw new Error('Usage: buy '); this.send(protocol.createPurchase(args[1])); logger.info(`Buying ${ args[1] } `); break; default: console.log('Unknown command'); } } catch (e) { logger.error('Command error:', e.message); } }); } send(msg) { if (this.ws && this.ws.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify(msg)); } } onMessage(data) { const str = data.toString(); // Handle server heartbeat if (str === 'ping') { // logger.info('Server ping -> Client pong'); this.ws.send('pong'); return; } try { const msg = JSON.parse(str); this.handleGameMessage(msg); } catch (e) { // logger.warn('Non-JSON message received', str); } } handleGameMessage(msg) { if (msg.type === protocol.TYPES.WELCOME) { logger.info('WELCOME received!', { roomId: msg.fullState.roomId, playerId: msg.fullState.hostPlayerId }); } else if (msg.type === protocol.TYPES.PARTIAL_STATE) { // Can be spammy // logger.info('State update received'); } else if (msg.type === protocol.TYPES.PONG) { // ignore } else { logger.info('Received Message:', msg.type); } } onClose() { logger.warn('Disconnected from server.'); clearInterval(this.pingInterval); // Simple reconnect after 5s logger.info('Reconnecting in 5 seconds...'); setTimeout(() => this.connect(), 5000); } } // Start the bot const bot = new MagicGardenBot(); bot.connect(); // Handle graceful shutdown process.on('SIGINT', () => { logger.info('Stopping bot...'); process.exit(0); });