Files
Magic-Garden-Bot/extension/injected_bot.js
2025-12-09 23:21:09 +00:00

476 lines
19 KiB
JavaScript

(function () {
console.log("%c Magic Garden Bot Extension Loaded ", "background: #222; color: #bada55; font-size: 20px");
// --- LOGIC: WebSocket Hook ---
window.gameSocket = null;
const OriginalSend = WebSocket.prototype.send;
WebSocket.prototype.send = function (data) {
if (!window.gameSocket || window.gameSocket.readyState >= 2) {
window.gameSocket = this;
updateStatus(true);
// Extract Player ID from URL
try {
const urlObj = new URL(this.url);
let pid = urlObj.searchParams.get('playerId');
// Remove quotes content if wrapped in " " (e.g. from %22p_...%22)
if (pid) pid = pid.replace(/^"|"$/g, '');
gameState.playerId = pid;
console.log("[MagicBot] Captured Player ID:", gameState.playerId);
} catch (e) {
console.error("[MagicBot] Failed to parse Player ID:", e);
}
this.addEventListener('close', () => {
updateStatus(false);
});
// Capture Incoming
this.addEventListener('message', (e) => {
logToOverlay('RX', e.data);
try {
const msg = JSON.parse(e.data);
if (msg.type === 'Welcome') {
handleWelcome(msg);
}
} catch (err) { }
});
}
// Capture Outgoing
logToOverlay('TX', data);
return OriginalSend.apply(this, arguments);
};
// --- STATE MANAGEMENT ---
let gameState = {
coins: 0,
garden: {},
inventory: {},
playerId: null
};
function handleWelcome(msg) {
console.log("Parsing Welcome Message...");
const root = msg.fullState.data;
console.log("Looking for slot for PlayerID:", gameState.playerId);
// Find our user slot
const userSlot = root.userSlots.find(s => s.playerId === gameState.playerId);
if (userSlot) {
console.log("Found User Slot!", userSlot);
gameState.coins = userSlot.data.coinsCount;
gameState.garden = userSlot.data.garden.tileObjects;
gameState.inventory = userSlot.data.inventory;
updateVisuals();
} else {
console.error("Could not find user slot for ID:", gameState.playerId);
// Debug: Log all available IDs
console.log("Available Slots:", root.userSlots.map(s => s.playerId));
}
}
function updateVisuals() {
// Update Wallet
const walletContent = document.getElementById('window-wallet-content');
if (walletContent) {
walletContent.innerHTML = `
<div style="font-size: 24px; color: #ffd700; text-align: center;">CA$H: ${gameState.coins.toLocaleString()}</div>
`;
}
// Update Garden
const gardenContent = document.getElementById('window-garden-content');
if (gardenContent) {
let html = '<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px;">';
const now = Date.now();
Object.entries(gameState.garden).forEach(([slotId, tile]) => {
const crop = tile.slots[0]; // Assuming single slot for now
if (!crop) return;
const isReady = now >= tile.maturedAt;
const timeLeft = Math.max(0, Math.ceil((tile.maturedAt - now) / 1000));
const color = isReady ? '#66bb6a' : '#ffa726';
html += `
<div style="background: rgba(255,255,255,0.05); padding: 5px; border-radius: 4px; border: 1px solid ${color}; font-size: 10px; text-align: center;">
<div style="font-weight: bold; color: ${color};">${tile.species}</div>
<div>${slotId}</div>
<div>${isReady ? 'READY' : timeLeft + 's'}</div>
</div>
`;
});
html += '</div>';
gardenContent.innerHTML = html;
}
}
// --- UI HELPER: Floating Windows ---
function createFloatingWindow(id, title, x, y, width, height) {
if (document.getElementById(id)) return; // Already exists
const win = document.createElement('div');
win.id = id;
win.style.cssText = `
position: fixed;
top: ${y}px;
left: ${x}px;
width: ${width}px;
height: ${height}px;
background: rgba(10, 10, 20, 0.9);
border: 1px solid #444;
border-radius: 8px;
z-index: 9999995;
display: flex;
flex-direction: column;
box-shadow: 0 44px 15px rgba(0,0,0,0.5);
font-family: sans-serif;
color: #eee;
backdrop-filter: blur(5px);
resize: both;
overflow: hidden;
`;
win.innerHTML = `
<div id="${id}-header" style="padding: 8px; background: rgba(255,255,255,0.05); border-bottom: 1px solid #444; cursor: move; font-weight: bold; font-size: 12px; display: flex; justify-content: space-between;">
<span>${title}</span>
<span style="cursor: pointer; color: #ff5252;" onclick="document.getElementById('${id}').remove()">✕</span>
</div>
<div id="${id}-content" style="flex: 1; overflow-y: auto; padding: 10px;"></div>
`;
document.body.appendChild(win);
// Drag Logic
const header = win.querySelector(`#${id}-header`);
let isDragging = false, offX = 0, offY = 0;
header.addEventListener('mousedown', (e) => {
isDragging = true;
offX = e.clientX - win.offsetLeft;
offY = e.clientY - win.offsetTop;
});
window.addEventListener('mousemove', (e) => {
if (isDragging) {
win.style.left = (e.clientX - offX) + 'px';
win.style.top = (e.clientY - offY) + 'px';
}
});
window.addEventListener('mouseup', () => isDragging = false);
}
// --- LOGIC: Commands ---
function sendMsg(msg) {
if (!window.gameSocket) {
alert("Socket not connected! Wait for game to load.");
return;
}
window.gameSocket.send(JSON.stringify(msg));
}
async function harvestLoop(start, end, count, delay) {
let sent = 0;
for (let slot = start; slot <= end; slot++) {
for (let i = 0; i < count; i++) {
sendMsg({
type: 'HarvestCrop',
slot: slot,
slotsIndex: i,
scopePath: ["Room", "Quinoa"]
});
sent++;
await new Promise(r => setTimeout(r, delay));
}
}
console.log(`Harvested ${sent} items.`);
}
// --- UI: Sidebar ---
const sidebar = document.createElement('div');
sidebar.id = 'magic-bot-sidebar';
sidebar.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
width: 280px;
background: rgba(20, 20, 25, 0.95);
color: #eee;
padding: 20px;
border-radius: 12px;
z-index: 9999999;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
box-shadow: 0 4px 15px rgba(0,0,0,0.6);
border: 1px solid #444;
backdrop-filter: blur(5px);
`;
sidebar.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h3 style="margin: 0; color: #bada55; font-size: 18px;">🌱 Magic Bot</h3>
<div id="status-indicator" style="font-size: 12px; font-weight: bold; color: #ff5252;">● Disconnected</div>
</div>
<!-- Teleport -->
<div style="background: rgba(255,255,255,0.05); padding: 10px; border-radius: 8px; margin-bottom: 15px;">
<div id="head-tp" style="display: flex; justify-content: space-between; cursor: pointer; margin-bottom: 5px; user-select: none;">
<label style="font-size: 12px; color: #aaa; cursor: pointer;">Teleport</label>
<span id="arrow-tp" style="font-size: 10px; color: #aaa;">▼</span>
</div>
<div id="section-tp">
<div style="display: flex; gap: 8px;">
<input type="number" id="tp-x" placeholder="X" value="15" style="width: 100%; padding: 6px; background: #333; border: 1px solid #555; color: white; border-radius: 4px;">
<input type="number" id="tp-y" placeholder="Y" value="15" style="width: 100%; padding: 6px; background: #333; border: 1px solid #555; color: white; border-radius: 4px;">
<button id="btn-tp" style="cursor: pointer; background: #448aff; color: white; border: none; padding: 0 15px; border-radius: 4px; font-weight: bold;">Go</button>
</div>
</div>
</div>
<!-- Harvest -->
<div style="background: rgba(255,255,255,0.05); padding: 10px; border-radius: 8px; margin-bottom: 15px;">
<div id="head-hv" style="display: flex; justify-content: space-between; cursor: pointer; margin-bottom: 5px; user-select: none;">
<label style="font-size: 12px; color: #aaa; cursor: pointer;">Harvest</label>
<span id="arrow-hv" style="font-size: 10px; color: #aaa;">▼</span>
</div>
<div id="section-hv">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 8px;">
<div>
<span style="font-size: 10px; color: #888;">Start Slot</span>
<input type="number" id="hv-start" value="140" style="width: 100%; padding: 6px; background: #333; border: 1px solid #555; color: white; border-radius: 4px;">
</div>
<div>
<span style="font-size: 10px; color: #888;">End Slot</span>
<input type="number" id="hv-end" value="160" style="width: 100%; padding: 6px; background: #333; border: 1px solid #555; color: white; border-radius: 4px;">
</div>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 10px;">
<div>
<span style="font-size: 10px; color: #888;">Iter. (Count)</span>
<input type="number" id="hv-count" value="1" style="width: 100%; padding: 6px; background: #333; border: 1px solid #555; color: white; border-radius: 4px;">
</div>
<div>
<span style="font-size: 10px; color: #888;">Delay (ms)</span>
<input type="number" id="hv-delay" value="20" style="width: 100%; padding: 6px; background: #333; border: 1px solid #555; color: white; border-radius: 4px;">
</div>
</div>
<button id="btn-harvest" style="width: 100%; cursor: pointer; background: #66bb6a; color: white; border: none; padding: 8px; border-radius: 4px; font-weight: bold; transition: background 0.2s;">Run Harvest</button>
</div>
</div>
<button id="btn-sell" style="width: 100%; cursor: pointer; background: #ffa726; color: white; border: none; padding: 10px; border-radius: 8px; font-weight: bold; box-shadow: 0 2px 5px rgba(0,0,0,0.2);">Sell All Crops</button>
<div style="border-top: 1px solid #444; margin: 10px 0;"></div>
<div style="display: flex; gap: 5px;">
<button id="btn-logs" style="flex: 1; cursor: pointer; background: #5c6bc0; color: white; border: none; padding: 8px; border-radius: 4px; font-weight: bold;">Logs</button>
<button id="btn-state" style="flex: 1; cursor: pointer; background: #ab47bc; color: white; border: none; padding: 8px; border-radius: 4px; font-weight: bold;">State</button>
</div>
`;
document.body.appendChild(sidebar);
document.getElementById('btn-state').onclick = () => {
createFloatingWindow('window-wallet', '💰 Wallet', 20, 400, 200, 80);
createFloatingWindow('window-garden', '🌻 Garden', 240, 400, 300, 300);
updateVisuals(); // Refresh if data exists
};
// --- LOG OVERLAY ---
const logOverlay = document.createElement('div');
logOverlay.id = 'magic-bot-logs';
logOverlay.style.cssText = `
display: none;
position: fixed;
bottom: 20px;
left: 20px;
width: 600px;
height: 300px;
background: rgba(10, 10, 15, 0.95);
border: 1px solid #444;
border-radius: 8px;
z-index: 9999999;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 11px;
color: #eee;
display: flex;
flex-direction: column;
box-shadow: 0 4px 15px rgba(0,0,0,0.6);
resize: both;
overflow: hidden;
`;
logOverlay.innerHTML = `
<div style="padding: 5px 10px; background: #222; border-bottom: 1px solid #444; display: flex; justify-content: space-between; align-items: center; cursor: move;" id="log-header">
<span style="font-weight: bold; color: #aaa;">Network Logs</span>
<div>
<button id="btn-clear-logs" style="background: none; border: none; color: #888; cursor: pointer; margin-right: 10px;">Clear</button>
<button id="btn-close-logs" style="background: none; border: none; color: #ff5252; cursor: pointer;">X</button>
</div>
</div>
<div id="log-content" style="flex: 1; overflow-y: auto; padding: 5px; word-break: break-all;"></div>
`;
document.body.appendChild(logOverlay);
logOverlay.style.display = 'none'; // Ensure hidden initially
// Make Draggable
let isDragging = false;
let dragOffsetX = 0;
let dragOffsetY = 0;
const header = logOverlay.querySelector('#log-header');
header.addEventListener('mousedown', (e) => {
isDragging = true;
dragOffsetX = e.clientX - logOverlay.offsetLeft;
dragOffsetY = e.clientY - logOverlay.offsetTop;
});
window.addEventListener('mousemove', (e) => {
if (isDragging) {
logOverlay.style.left = (e.clientX - dragOffsetX) + 'px';
logOverlay.style.top = (e.clientY - dragOffsetY) + 'px';
}
});
window.addEventListener('mouseup', () => isDragging = false);
// Stop propagation for logs too
logOverlay.addEventListener('keydown', (e) => e.stopPropagation());
logOverlay.addEventListener('keyup', (e) => e.stopPropagation());
logOverlay.addEventListener('keypress', (e) => e.stopPropagation());
function logToOverlay(type, data) {
const container = document.getElementById('log-content');
if (!container) return;
// Check if we are near the bottom BEFORE adding new content (within 50px)
const isAtBottom = (container.scrollHeight - container.scrollTop - container.clientHeight) < 50;
const line = document.createElement('div');
line.style.borderBottom = '1px solid #222';
line.style.padding = '2px 0';
const timestamp = new Date().toLocaleTimeString();
let color = type === 'TX' ? '#66bb6a' : '#448aff';
let content = data;
// Pretty print JSON
try {
if (typeof data === 'string' && (data.startsWith('{') || data.startsWith('['))) {
content = JSON.stringify(JSON.parse(data)); // Minify
}
} catch (e) { }
line.innerHTML = `<span style="color: #666;">[${timestamp}]</span> <span style="color: ${color}; font-weight: bold;">${type}</span> <span style="color: #ccc;">${content}</span>`;
// Click to copy
line.style.cursor = 'pointer';
line.title = 'Click to copy raw message';
line.addEventListener('click', () => {
navigator.clipboard.writeText(data).then(() => {
// Visual feedback
line.style.background = 'rgba(255, 255, 255, 0.1)';
setTimeout(() => line.style.background = 'transparent', 200);
console.log('Copied to clipboard:', data);
}).catch(err => console.error('Failed to copy matches:', err));
});
container.appendChild(line);
// Auto scroll ONLY if we were already at the bottom
if (isAtBottom) {
container.scrollTop = container.scrollHeight;
}
// Limit history (500 lines)
if (container.children.length > 500) {
container.removeChild(container.firstChild);
}
}
// Stop keyboard events from reaching the game (Fixes specific keys not working)
sidebar.addEventListener('keydown', (e) => e.stopPropagation());
sidebar.addEventListener('keyup', (e) => e.stopPropagation());
sidebar.addEventListener('keypress', (e) => e.stopPropagation());
// --- COLLAPSE LOGIC ---
const setupCollapse = (headId, bodyId, arrowId) => {
document.getElementById(headId).onclick = () => {
const el = document.getElementById(bodyId);
const arrow = document.getElementById(arrowId);
if (el.style.display === 'none') {
el.style.display = 'block';
arrow.textContent = '▼';
} else {
el.style.display = 'none';
arrow.textContent = '▶';
}
};
};
setupCollapse('head-tp', 'section-tp', 'arrow-tp');
setupCollapse('head-hv', 'section-hv', 'arrow-hv');
document.getElementById('btn-logs').onclick = () => {
const el = document.getElementById('magic-bot-logs');
el.style.display = el.style.display === 'none' ? 'flex' : 'none';
// Reset position if off-screen (optional safety)
if (el.style.display === 'flex') {
el.style.top = 'auto';
el.style.bottom = '20px';
el.style.left = '20px';
}
};
document.getElementById('btn-close-logs').onclick = () => {
document.getElementById('magic-bot-logs').style.display = 'none';
};
document.getElementById('btn-clear-logs').onclick = () => {
document.getElementById('log-content').innerHTML = '';
};
// --- UI LOGIC ---
function updateStatus(connected) {
const el = document.getElementById('status-indicator');
if (connected) {
el.innerHTML = '● Connected';
el.style.color = '#66bb6a'; // Green
} else {
el.innerHTML = '● Disconnected';
el.style.color = '#ff5252'; // Red
}
}
document.getElementById('btn-tp').onclick = () => {
const x = parseInt(document.getElementById('tp-x').value);
const y = parseInt(document.getElementById('tp-y').value);
sendMsg({
type: 'Teleport',
position: { x, y },
scopePath: ["Room", "Quinoa"]
});
};
document.getElementById('btn-harvest').onclick = () => {
const start = parseInt(document.getElementById('hv-start').value);
const end = parseInt(document.getElementById('hv-end').value);
const count = parseInt(document.getElementById('hv-count').value);
const delay = parseInt(document.getElementById('hv-delay').value);
harvestLoop(start, end, count, delay);
};
document.getElementById('btn-sell').onclick = () => {
sendMsg({
type: 'SellAllCrops',
scopePath: ["Room", "Quinoa"]
});
};
})();